Compare commits

...

86 Commits

Author SHA1 Message Date
James Seibel 57c5b2d5fc Up version 2.2.1-dev -> 2.2.1 and API 3.0.0 -> 3.0.1 2024-09-04 06:58:20 -05:00
James Seibel 50c5701836 Fix referencing unavailable GLFW methods for MC 1.18.2 and lower 2024-09-02 08:00:43 -05:00
James Seibel 18e075538d Fix LODs flashing when moving and improve LodQuadTree code 2024-09-01 21:10:22 -05:00
James Seibel b00e8a08e9 add annotations to QuadTree 2024-09-01 21:09:50 -05:00
James Seibel ac4ab11a74 Fix LodRenderSection and QuadNode toString methods 2024-09-01 21:09:17 -05:00
James Seibel c26631db57 Cull beacons based on X/Z distance instead of 3D distance 2024-09-01 17:28:08 -05:00
James Seibel 1daa06fff4 Disable instanced rendering on Mac when Sodium is present
Closes !793 (Generic Rendering crashes with Sodium on M1 Mac)
2024-09-01 17:02:43 -05:00
James Seibel f3ef6f25f4 Fix some beacon rendering/updating issues 2024-09-01 16:36:37 -05:00
James Seibel ec012d9fd6 Fix glass panes not affecting beacon colors 2024-09-01 15:04:24 -05:00
James Seibel fc90cf3377 add disableUnchangedChunkCheck config 2024-08-31 22:11:25 -05:00
James Seibel e1e42d1caf Fix cloud color not matching MC 2024-08-31 21:55:54 -05:00
James Seibel 95ce29e355 Update all pos objects to use getters to match with DhBlockPos
I'd prefer not to need getters/setters since it's cleaner to just call pos.x, but that doesn't allow for immutable/mutable distinction.
2024-08-31 20:50:22 -05:00
James Seibel 0fd818b077 Allow users to re-activate DH rendering if a rendering error is thrown 2024-08-31 20:20:51 -05:00
James Seibel ba59daf747 Add beacon nearby culling 2024-08-31 20:20:24 -05:00
James Seibel b7d94c2ed1 Fix RenderableBoxGroup not supporting clear() 2024-08-31 20:06:04 -05:00
James Seibel 7a057a8d53 fix Lod builder not showing the correct stacktrace in Intellij 2024-08-31 15:04:30 -05:00
James Seibel 49c6ab97a9 Remove unused threadpool 2024-08-31 15:04:12 -05:00
James Seibel ed0d80b37e Reduce deplayed LOD modified save from 2 sec -> 500 ms
This should make some LOD update operations appear faster
2024-08-31 15:01:37 -05:00
James Seibel 9768728c92 Add RollingAverage object 2024-08-31 14:58:08 -05:00
James Seibel 0c68544f2f Fix DhLightingEngine putting lights at relative (0,0) and add debug logic 2024-08-31 12:45:39 -05:00
James Seibel b1f154a0ea Add DhBlockPosMutable and make the original immutable
This is to prevent issues with some methods accidentally mutating shared positions
2024-08-30 07:35:59 -05:00
James Seibel 628c9b071f DhBlockPos cleanup 2024-08-29 20:05:26 -05:00
James Seibel ed39b6181f Fix DH beacon detection logic mutating input block pos
alternate title: Fix DH beacon detection logic breaking the lighting engine
2024-08-29 19:54:36 -05:00
James Seibel 1d6d712483 Fix LODs not updating in the nether or when blocks are changed underground 2024-08-29 07:32:55 -05:00
James Seibel 73c4f0ffcd Add a quick DH cloud UI config 2024-08-28 07:16:20 -05:00
James Seibel 48d1005be6 Up version number 2.2.0 -> 2.2.1-dev 2024-08-20 19:16:18 -05:00
James Seibel e71e8d1966 Up version number 2.1.3-dev -> 2.2.0 2024-08-20 17:44:41 -05:00
James Seibel 489b3d3ae1 Fix Legacy GL causing fog to smear 2024-08-20 17:44:41 -05:00
James Seibel 1b162f10e6 Add sharedApi.isChunkAtChunkPosAlreadyUpdating() 2024-08-18 14:46:12 -05:00
James Seibel 301cce3d11 Remove unused chunkUnloadEvent 2024-08-18 14:25:51 -05:00
James Seibel e68d0d5c45 Fix fog and SSAO being broken by some mods 2024-08-17 22:29:28 -05:00
James Seibel 2cf952fb76 Deprecate IDhApiWorldGenerator.isBusy(), task queuing is now handled internally 2024-08-12 22:20:06 -05:00
James Seibel c7c5ab17bc Add optional DhApiChunk validation for world gen 2024-08-12 21:47:48 -05:00
James Seibel a98955530f Allow adding empty lists to DhApiChunk 2024-08-11 22:01:24 -05:00
James Seibel c63a509f9e Fix DhApiChunk setDataPoints failing for empty lists 2024-08-11 21:55:11 -05:00
James Seibel dac51a9eea Add mod compat warning message config 2024-08-11 09:54:54 -05:00
James Seibel 8d78a1ad74 Rename BeaconBeamDTO.pos -> blockPos 2024-08-09 07:25:49 -05:00
James Seibel 7c11bb4258 Fix beacons not enabling/disabling correctly 2024-08-09 07:25:24 -05:00
James Seibel 0d6ec3d836 Fix frustum culling when the screen is warped 2024-08-07 18:55:02 -05:00
James Seibel 9449433fe8 Fix deleting beacons 2024-08-07 07:47:11 -05:00
James Seibel 2c976c9fb1 Improve fast chunk hash to respect surface block heights 2024-08-07 07:47:02 -05:00
James Seibel 90fdfbbe61 Fix direct memory leak and remove config for GpuUpload 2024-08-07 07:29:52 -05:00
James Seibel a8df13fdd2 Fix LodRenderSection holding onto unnecessary memory 2024-08-06 21:47:03 -05:00
James Seibel f05eac2637 test 2024-08-06 20:47:23 -05:00
James Seibel 4ae30b3d47 Improve LOD detail level detection and hole filling 2024-08-04 08:30:58 -05:00
James Seibel 8abefdcfd5 Attempt to improve LOD building speed and reduce broken lighting on servers 2024-08-03 17:11:18 -05:00
James Seibel 801a126de0 Fix chunk has being saved before LOD update 2024-08-03 16:50:12 -05:00
James Seibel 377d0fbe12 Allow DhApiChunk to accept top down or bottom up data point orders 2024-08-03 09:32:57 -05:00
James Seibel 1ecf968668 Remove unused ChunkToLodBuilder 2024-08-03 09:32:29 -05:00
James Seibel c43b985f4e Fix off by 1 error in Render data transformer 2024-08-02 18:30:55 -05:00
James Seibel b7fea64925 Fix out of bounds exception in Full Data Transformer 2024-08-02 17:56:25 -05:00
James Seibel 116d7f4a3f Fix incorrect comment in DhSectionPos 2024-08-01 06:52:05 -05:00
James Seibel 0c36dc03a5 Fix cave culling affecting floating islands and add LOD reload to some configs 2024-07-31 19:06:44 -05:00
James Seibel c658269eb7 Remove deprecated methods and move method to StringUtil 2024-07-30 17:06:55 -05:00
James Seibel defaea86f0 Remove unused sodium and McRenderWrapper methods
Removed methods were originally used to cull LODs if MC had loaded chunks, however this turned out to be more trouble than it was worth and caused more problems than it solved.
2024-07-30 17:00:59 -05:00
James Seibel f1564cc90b Fix presets only using "custom" after any value was changed 2024-07-30 15:47:48 -05:00
James Seibel 66feb0b9c2 Fix SSAO shader crashing when entering empty config values 2024-07-30 15:35:01 -05:00
James Seibel 79bfa21115 Fix default logging debug to file 2024-07-29 20:40:23 -05:00
James Seibel 5588b0d2fa Add TODO to cleanup LodRenderSection futures 2024-07-29 07:29:48 -05:00
James Seibel e9788acb46 Fix rapidly changing dimensions causing the game to crash 2024-07-29 07:13:06 -05:00
James Seibel 8056a5b8bf Add Buffer Builder/Uploader thread pools to the F3 menu 2024-07-29 07:08:42 -05:00
James Seibel e0ad956e34 Fix incorrect DhApiChunk create constructor parameter order (again) 2024-07-28 20:18:15 -05:00
James Seibel 9546f9cbc8 Close !66 (fix incorrect positions being fed into biome color code) 2024-07-28 09:33:24 -05:00
James Seibel 9834b20a9f Revert and Deprecate DhApiChunk and DhApiTerrainDataPoint constructors 2024-07-28 08:56:18 -05:00
James Seibel 752008e8ac Re-add deprecated IDhApiLevelWrapper.getHeight() 2024-07-28 08:55:54 -05:00
James Seibel fbe81021c0 Update API javadocs 2024-07-28 08:55:35 -05:00
James Seibel 882c5399bd Fix holes in LODs boarding different detail levels 2024-07-27 21:06:50 -05:00
James Seibel dbe0461d5f Fix LOD upload warning 2024-07-27 20:25:55 -05:00
James Seibel d3d166dd02 Undo experimental change to LOD reloading 2024-07-27 20:11:28 -05:00
James Seibel d0dd1f38ff Fix LODs flashing twice when changing configs 2024-07-27 20:11:00 -05:00
James Seibel 53300a3028 Update IDhApiRenderProxy.clearRenderDataCache() to also clear cached block colors 2024-07-27 17:36:40 -05:00
James Seibel 56303dd82a level wrapper renamings 2024-07-27 16:51:08 -05:00
James Seibel 19d8c89bd8 Fix ice/water vertical LOD lighting 2024-07-27 09:30:41 -05:00
James Seibel adba3e4c15 clear testing default debug config values 2024-07-27 08:40:59 -05:00
James Seibel 479ce8093e minor cleanup/refactor 2024-07-27 08:28:31 -05:00
James Seibel 1ed6c619d9 Add localization for new column builder debug options 2024-07-27 08:26:46 -05:00
Builderb0y 825f439ffb fix incorrect positions being fed into biome color code. 2024-07-27 02:32:31 +00:00
James Seibel b1b487e63f Improve RenderDataPointUtil toString() 2024-07-24 06:46:28 -05:00
James Seibel 2763a7ca75 add missing brackets 2024-07-23 20:41:22 -05:00
James Seibel b4b1a2a549 remove deprecated RenderDataPointUtil logic 2024-07-23 20:05:41 -05:00
James Seibel 5865317394 Remove deprecated LodQuadBuilder code 2024-07-23 19:53:13 -05:00
James Seibel b70c090e94 Refactor and cleanup buffer building 2024-07-23 19:39:44 -05:00
James Seibel a9f6b924c2 Move CubicLodTemplate to ColumnRenderBufferBuilder 2024-07-23 07:05:28 -05:00
James Seibel 9c25a6450a comment ColumnArrayView 2024-07-22 20:47:34 -05:00
James Seibel 448982fbaf refactor FullDataToRenderDataTransformer 2024-07-22 19:50:23 -05:00
James Seibel 2ee06c59f1 Add configs for testing the ColumnRenderBufferBuilder 2024-07-22 17:34:04 -05:00
107 changed files with 2850 additions and 3374 deletions
@@ -180,7 +180,7 @@ public class DhApi
* This version should be updated whenever non-breaking fixes are added to the Distant Horizons API.
* @since API 1.0.0
*/
public static int getApiPatchVersion() { return ModInfo.API_PATH_VERSION; }
public static int getApiPatchVersion() { return ModInfo.API_PATCH_VERSION; }
/**
* Returns the mod's semantic version number in the format: Major.Minor.Patch
@@ -24,7 +24,7 @@ package com.seibel.distanthorizons.api.enums;
* CHUNK - Detail Level: 4, width 16 block, <br>
* REGION - Detail Level: 9, width 512 block <br> <br>
*
* Detail levels in Distant Horizons represent how large a section (of either LODs or MC chunks)
* Detail levels in Distant Horizons represent how large a LOD
* is, with the smallest being 0 (1 block wide). <br>
* The width of a detail level can be calculated by putting the detail level to the power of 2. <br>
* Example for the chunk detail level (4): 2^4 = 16 blocks wide <Br><br>
@@ -23,13 +23,12 @@ package com.seibel.distanthorizons.api.enums.config;
* AUTO, <br>
* BUFFER_STORAGE, <br>
* SUB_DATA, <br>
* BUFFER_MAPPING, <br>
* DATA <br>
*
* @author Leetom
* @author James Seibel
* @version 2024-4-6
* @since API 2.0.0
* @since API 3.0.0
*/
public enum EDhApiGpuUploadMethod
{
@@ -49,7 +48,10 @@ public enum EDhApiGpuUploadMethod
* May end up storing buffers in System memory. <br>
* Fast rending if in GPU memory, slow if in system memory, <br>
* but won't stutter when uploading.
*
* @deprecated not currently supported
*/
@Deprecated
BUFFER_MAPPING(true, false),
/** Fast rendering but may stutter when uploading. */
@@ -21,6 +21,7 @@ package com.seibel.distanthorizons.api.enums.rendering;
* AIR, <br>
* ILLUMINATED, <br>
*
* @author IMS
* @author James Seibel
* @since API 3.0.0
* @version 2024-7-11
@@ -19,6 +19,7 @@
package com.seibel.distanthorizons.api.interfaces.block;
import com.seibel.distanthorizons.api.enums.rendering.EDhApiBlockMaterial;
import com.seibel.distanthorizons.api.interfaces.IDhApiUnsafeWrapper;
/**
@@ -44,7 +45,11 @@ public interface IDhApiBlockStateWrapper extends IDhApiUnsafeWrapper
* @since API 3.0.0
*/
String getSerialString();
/** @since API 3.0.0 */
/**
* Returns the byte value representing the {@link EDhApiBlockMaterial} enum.
* @see EDhApiBlockMaterial
* @since API 3.0.0
*/
byte getMaterialId();
}
@@ -36,7 +36,6 @@ public interface IDhApiConfig
IDhApiWorldGenerationConfig worldGenerator();
IDhApiMultiplayerConfig multiplayer();
IDhApiMultiThreadingConfig multiThreading();
IDhApiGpuBuffersConfig gpuBuffers();
// note: DON'T add the Auto Updater to this API. We only want the user's to have the ability to control when things are downloaded to their machines.
//IDhApiLoggingConfig logging(); // TODO implement
IDhApiDebuggingConfig debugging();
@@ -1,48 +0,0 @@
/*
* This file is part of the Distant Horizons mod
* licensed under the GNU LGPL v3 License.
*
* Copyright (C) 2020-2023 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.distanthorizons.api.interfaces.config.client;
import com.seibel.distanthorizons.api.enums.config.EDhApiGpuUploadMethod;
import com.seibel.distanthorizons.api.interfaces.config.IDhApiConfigGroup;
import com.seibel.distanthorizons.api.interfaces.config.IDhApiConfigValue;
/**
* Distant Horizons' OpenGL buffer configuration.
*
* @author James Seibel
* @version 2023-6-14
* @since API 1.0.0
*/
public interface IDhApiGpuBuffersConfig extends IDhApiConfigGroup
{
/** Defines how geometry data is uploaded to the GPU. */
IDhApiConfigValue<EDhApiGpuUploadMethod> gpuUploadMethod();
/**
* Defines how long we should wait after uploading one
* Megabyte of geometry data to the GPU before uploading
* the next Megabyte of data. <br>
* This can be set to a non-zero number to reduce stuttering caused by
* uploading buffers to the GPU.
*/
IDhApiConfigValue<Integer> gpuUploadPerMegabyteInMilliseconds();
}
@@ -91,10 +91,28 @@ public interface IDhApiWorldGenerator extends Closeable, IDhApiOverrideable
default byte getMaxGenerationGranularity() { return (byte) (EDhApiDetailLevel.CHUNK.detailLevel + 2); }
/**
* @return true if the generator is unable to accept new generation requests.
* Starting in API 3.0.0 DH now handles future queuing/management internally. <br><br>
*
* Previous description: <br>
* true if the generator is unable to accept new generation requests. <br>
*
* @since API 1.0.0
* @deprecated API 3.0.0
*/
boolean isBusy();
@Deprecated
default boolean isBusy() { return false; }
/**
* Only used if {@link #getReturnType()} returns {@link EDhApiWorldGeneratorReturnType#API_CHUNKS}. <Br>
* If true DH will run additional validation on the {@link DhApiChunk}'s returned. <Br>
* This should be disabled during release but should be enabled during development to help spot issues with your data format.
*
* @see #getReturnType()
* @see DhApiChunk
* @see EDhApiWorldGeneratorReturnType#API_CHUNKS
* @since API 3.0.0
*/
default boolean runApiChunkValidation() { return true; }
@@ -26,7 +26,7 @@ import com.seibel.distanthorizons.api.objects.DhApiResult;
* Used to interact with Distant Horizons' rendering system.
*
* @author James Seibel
* @version 2023-10-13
* @version 2024-7-27
* @since API 1.0.0
*/
public interface IDhApiRenderProxy
@@ -39,10 +39,8 @@ public interface IDhApiRenderProxy
* If this is called on a dedicated server it won't do anything and will return {@link DhApiResult#success} = false <Br><Br>
*
* Background: <Br>
* Distant Horizons has two different file formats: Full data and Render data. <Br>
* - Full data files store the block, biome, etc. information and is the result of loading or generating new chunks. <Br>
* - Render data files store LOD colors and are created using the Full data and currently loaded resource packs. <Br>
* This is the data cleared by this method.
* When rendering Distant Horizons bakes each block's color into the geometry that's rendered. <Br>
* This improves rendering speed and VRAM size, but prevents dynamically changing LOD colors. <Br>
*/
DhApiResult<Boolean> clearRenderDataCache();
@@ -28,7 +28,7 @@ import com.seibel.distanthorizons.api.interfaces.render.IDhApiCustomRenderRegist
* A level is equivalent to a dimension in vanilla Minecraft.
*
* @author James Seibel
* @version 2022-7-14
* @version 2024-7-28
* @since API 1.0.0
*/
public interface IDhApiLevelWrapper extends IDhApiUnsafeWrapper
@@ -41,7 +41,18 @@ public interface IDhApiLevelWrapper extends IDhApiUnsafeWrapper
boolean hasSkyLight();
/** Returns the max block height of the level(?) */
/**
* Deprecated, use {@link IDhApiLevelWrapper#getMaxHeight} instead. <br>
* Returns the max block height of the level.
*
* @see IDhApiLevelWrapper#getMaxHeight
*/
@Deprecated
default int getHeight() { return this.getMaxHeight(); }
/**
* Returns the max block height of the level
* @since API 3.0.0
*/
int getMaxHeight();
/**
@@ -53,6 +64,8 @@ public interface IDhApiLevelWrapper extends IDhApiUnsafeWrapper
/**
* Will return null if called on the server,
* or if called before the renderer has been set up.
*
* @since API 3.0.0
*/
IDhApiCustomRenderRegister getRenderRegister();
@@ -21,8 +21,10 @@ package com.seibel.distanthorizons.api.objects.data;
import com.seibel.distanthorizons.api.interfaces.factories.IDhApiWrapperFactory;
import com.seibel.distanthorizons.api.interfaces.override.worldGenerator.IDhApiWorldGenerator;
import org.apache.logging.log4j.LogManager;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
@@ -52,7 +54,27 @@ public class DhApiChunk
// constructors //
//==============//
public DhApiChunk(int chunkPosX, int chunkPosZ, int bottomYBlockPos, int topYBlockPos)
/**
* Deprecated due to the topYBlockPos and bottomYBlockPos variables being put in the wrong order.
* They should have been in bottom -> top order.
*
* @see DhApiChunk#create(int, int, int, int)
*/
@Deprecated
public DhApiChunk(int chunkPosX, int chunkPosZ, int topYBlockPos, int bottomYBlockPos)
{ this(chunkPosX, chunkPosZ, bottomYBlockPos, topYBlockPos, false); }
/**
* @since API 3.0.0
*/
public static DhApiChunk create(int chunkPosX, int chunkPosZ, int bottomYBlockPos, int topYBlockPos)
{ return new DhApiChunk(chunkPosX, chunkPosZ, bottomYBlockPos, topYBlockPos, false); }
/**
* Only visible to internal DH methods
* @param ignoredParameter is only present to differentiate the two constructors and isn't actually used
*/
private DhApiChunk(int chunkPosX, int chunkPosZ, int bottomYBlockPos, int topYBlockPos, boolean ignoredParameter)
{
this.chunkPosX = chunkPosX;
this.chunkPosZ = chunkPosZ;
@@ -94,27 +116,34 @@ public class DhApiChunk
*/
public void setDataPoints(int relX, int relZ, List<DhApiTerrainDataPoint> dataPoints) throws IndexOutOfBoundsException, IllegalArgumentException
{
//==================//
// basic validation //
//==================//
// heavier validation is done in the world generator if requested
int internalArrayIndex = (relZ << 4) | relX;
throwIfRelativePosOutOfBounds(relX, relZ);
// validate the incoming datapoints
if (dataPoints != null)
if (dataPoints == null)
{
for (int i = 0; i < dataPoints.size(); i++) // standard for-loop used instead of an enhanced for-loop to slightly reduce GC overhead due to iterator allocation
{
DhApiTerrainDataPoint dataPoint = dataPoints.get(i);
if (dataPoint == null)
{
throw new IllegalArgumentException("Null DhApiTerrainDataPoints are not allowed. If you want to represent empty terrain, please use AIR.");
}
if (dataPoint.detailLevel != 0)
{
throw new IllegalArgumentException("DhApiTerrainDataPoints has the wrong detail level ["+dataPoint.detailLevel+"], all data points must be block sized; IE their detail level must be [0].");
}
}
// we don't allow null columns
throw new IllegalArgumentException("Null columns aren't allowed. If you want to remove all data from a column please clear the list or pass in an empty list.");
}
this.dataPoints.set((relZ << 4) | relX, dataPoints);
//================//
// set datapoints //
//================//
List<DhApiTerrainDataPoint> column = this.dataPoints.get(internalArrayIndex);
if (column == null)
{
column = new ArrayList<>();
this.dataPoints.set(internalArrayIndex, column);
}
column.addAll(dataPoints);
}
@@ -19,9 +19,12 @@
package com.seibel.distanthorizons.api.objects.data;
import com.seibel.distanthorizons.api.enums.EDhApiDetailLevel;
import com.seibel.distanthorizons.api.interfaces.block.IDhApiBiomeWrapper;
import com.seibel.distanthorizons.api.interfaces.block.IDhApiBlockStateWrapper;
import java.util.ArrayList;
/**
* Holds a single datapoint of terrain data.
*
@@ -37,6 +40,8 @@ public class DhApiTerrainDataPoint
* 2 = 4x4 blocks <br>
* 4 = chunk (16x16 blocks) <br>
* 9 = region (512x512 blocks) <br>
*
* @see EDhApiDetailLevel
*/
public final byte detailLevel;
@@ -50,7 +55,57 @@ public class DhApiTerrainDataPoint
public DhApiTerrainDataPoint(byte detailLevel, int blockLightLevel, int skyLightLevel, int bottomYBlockPos, int topYBlockPos, IDhApiBlockStateWrapper blockStateWrapper, IDhApiBiomeWrapper biomeWrapper)
//==============//
// constructors //
//==============//
/**
* Deprecated due to the topYBlockPos and bottomYBlockPos variables being put in the wrong order.
* They should have been in bottom -> top order.
*
* @see DhApiTerrainDataPoint#create(byte, int, int, int, int, IDhApiBlockStateWrapper, IDhApiBiomeWrapper)
*/
@Deprecated
public DhApiTerrainDataPoint(
byte detailLevel,
int blockLightLevel, int skyLightLevel,
int topYBlockPos, int bottomYBlockPos,
IDhApiBlockStateWrapper blockStateWrapper, IDhApiBiomeWrapper biomeWrapper)
{
this(detailLevel, blockLightLevel, skyLightLevel,
bottomYBlockPos, topYBlockPos,
blockStateWrapper, biomeWrapper,
false);
}
/**
* @since API 3.0.0
*/
public static DhApiTerrainDataPoint create(
byte detailLevel,
int blockLightLevel, int skyLightLevel,
int bottomYBlockPos, int topYBlockPos,
IDhApiBlockStateWrapper blockStateWrapper, IDhApiBiomeWrapper biomeWrapper
)
{
return new DhApiTerrainDataPoint(
detailLevel, blockLightLevel, skyLightLevel,
bottomYBlockPos, topYBlockPos,
blockStateWrapper, biomeWrapper,
false);
}
/**
* Only visible to internal DH methods
* @param ignoredParameter is only present to differentiate the two constructors and isn't actually used
*/
private DhApiTerrainDataPoint(
byte detailLevel,
int blockLightLevel, int skyLightLevel,
int bottomYBlockPos, int topYBlockPos,
IDhApiBlockStateWrapper blockStateWrapper, IDhApiBiomeWrapper biomeWrapper,
boolean ignoredParameter
)
{
this.detailLevel = detailLevel;
@@ -34,7 +34,7 @@ public final class ModInfo
public static final String NAME = "DistantHorizons";
/** Human-readable version of NAME */
public static final String READABLE_NAME = "Distant Horizons";
public static final String VERSION = "2.1.3-a-dev";
public static final String VERSION = "2.2.1-a";
/** Returns true if the current build is an unstable developer build, false otherwise. */
public static boolean IS_DEV_BUILD = VERSION.toLowerCase().contains("dev");
@@ -43,7 +43,7 @@ public final class ModInfo
/** This version should be updated whenever new methods are added to the DH API */
public static final int API_MINOR_VERSION = 0;
/** This version should be updated whenever non-breaking fixes are added to the DH API */
public static final int API_PATH_VERSION = 0;
public static final int API_PATCH_VERSION = 1;
public static final String NETWORKING_RESOURCE_NAMESPACE = "distant_horizons";
public static final String MULTIVERSE_PLUGIN_NAMESPACE = "world_control";
@@ -24,12 +24,12 @@ import java.util.Arrays;
/**
* Miscellaneous string helper functions.
*
* @author James Seibel
* @version 2022-7-19
*/
public class StringUtil
{
private static final char[] HEX_ARRAY = "0123456789ABCDEF".toCharArray();
/**
* Returns the n-th index of the given string. <br> <br>
*
@@ -67,8 +67,6 @@ public class StringUtil
return stringBuilder.toString();
}
private static final char[] HEX_ARRAY = "0123456789ABCDEF".toCharArray();
/**
* Converts the given byte array into a hex string representation. <br>
* source: https://stackoverflow.com/a/9855338
@@ -85,4 +83,20 @@ public class StringUtil
return new String(hexChars);
}
/**
* Returns a shortened version of the given string that is no longer than maxLength. <br>
* If null returns the empty string.
*/
public static String shortenString(String str, int maxLength)
{
if (str == null)
{
return "";
}
else
{
return str.substring(0, Math.min(str.length(), maxLength));
}
}
}
@@ -42,8 +42,6 @@ public class DhApiConfig implements IDhApiConfig
@Override
public IDhApiMultiThreadingConfig multiThreading() { return DhApiMultiThreadingConfig.INSTANCE; }
@Override
public IDhApiGpuBuffersConfig gpuBuffers() { return DhApiGpuBuffersConfig.INSTANCE; }
@Override
public IDhApiDebuggingConfig debugging() { return DhApiDebuggingConfig.INSTANCE; }
}
@@ -1,42 +0,0 @@
/*
* This file is part of the Distant Horizons mod
* licensed under the GNU LGPL v3 License.
*
* Copyright (C) 2020-2023 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.distanthorizons.core.api.external.methods.config.client;
import com.seibel.distanthorizons.api.interfaces.config.IDhApiConfigValue;
import com.seibel.distanthorizons.api.interfaces.config.client.IDhApiGpuBuffersConfig;
import com.seibel.distanthorizons.api.objects.config.DhApiConfigValue;
import com.seibel.distanthorizons.core.config.Config;
import com.seibel.distanthorizons.api.enums.config.EDhApiGpuUploadMethod;
public class DhApiGpuBuffersConfig implements IDhApiGpuBuffersConfig
{
public static DhApiGpuBuffersConfig INSTANCE = new DhApiGpuBuffersConfig();
private DhApiGpuBuffersConfig() { }
public IDhApiConfigValue<EDhApiGpuUploadMethod> gpuUploadMethod()
{ return new DhApiConfigValue<>(Config.Client.Advanced.GpuBuffers.gpuUploadMethod); }
public IDhApiConfigValue<Integer> gpuUploadPerMegabyteInMilliseconds()
{ return new DhApiConfigValue<>(Config.Client.Advanced.GpuBuffers.gpuUploadPerMegabyteInMilliseconds); }
}
@@ -326,9 +326,10 @@ public class DhApiTerrainDataRepo implements IDhApiTerrainDataRepo
int height = FullDataPointUtil.getHeight(dataPoint);
int topY = bottomY + height;
return new DhApiTerrainDataPoint(detailLevel,
return DhApiTerrainDataPoint.create(
detailLevel,
FullDataPointUtil.getBlockLight(dataPoint), FullDataPointUtil.getSkyLight(dataPoint),
topY, bottomY,
bottomY, topY,
blockState, biomeWrapper);
}
@@ -497,7 +497,7 @@ public class ClientApi
{
// logging //
this.sendChatMessagesNow();
this.sendQueuedChatMessages();
IProfilerWrapper profiler = MC.getProfiler();
profiler.pop(); // get out of "terrain"
@@ -552,8 +552,17 @@ public class ClientApi
}
IDhClientLevel level = dhClientWorld.getOrLoadClientLevel(levelWrapper);
if (this.rendererDisabledBecauseOfExceptions)
{
// re-enable rendering if the user toggles DH rendering
if (!Config.Client.quickEnableRendering.get())
{
LOGGER.info("DH Renderer re-enabled after exception. Some rendering issues may occur. Please reboot Minecraft if you see any rendering issues.");
this.rendererDisabledBecauseOfExceptions = false;
Config.Client.quickEnableRendering.set(true);
}
return;
}
@@ -604,9 +613,9 @@ public class ClientApi
LOGGER.error("Unexpected Renderer error in render pass [" + renderPass + "]. Error: " + e.getMessage(), e);
MC.sendChatMessage("\u00A74\u00A7l\u00A7uERROR: Distant Horizons renderer has encountered an exception!");
MC.sendChatMessage("\u00A74Renderer is now disabled to prevent further issues.");
MC.sendChatMessage("\u00A74Please restart your game to re-enable Distant Horizons' LOD rendering.");
MC.sendChatMessage("\u00A74Exception detail: " + e);
MC.sendChatMessage("\u00A74Renderer disabled to try preventing GL state corruption.");
MC.sendChatMessage("\u00A74Toggle DH rendering via the config UI to re-activate DH rendering.");
MC.sendChatMessage("\u00A74Error: " + e);
}
finally
{
@@ -660,7 +669,7 @@ public class ClientApi
}
}
private void sendChatMessagesNow()
private void sendQueuedChatMessages()
{
// dev build
if (ModInfo.IS_DEV_BUILD && !this.configOverrideReminderPrinted && MC.playerExists())
@@ -668,10 +677,12 @@ public class ClientApi
this.configOverrideReminderPrinted = true;
// remind the user that this is a development build
MC.sendChatMessage("\u00A72" + "Distant Horizons: nightly/unstable build, version: [" + ModInfo.VERSION+"]." + "\u00A7r");
MC.sendChatMessage("Issues may occur with this version.");
MC.sendChatMessage("Here be dragons!");
MC.sendChatMessage("");
String message =
// green text
"\u00A72" + "Distant Horizons: nightly/unstable build, version: [" + ModInfo.VERSION+"]." + "\u00A7r\n" +
"Issues may occur with this version.\n" +
"Here be dragons!\n";
MC.sendChatMessage(message);
}
// memory
@@ -686,11 +697,13 @@ public class ClientApi
long maxMemoryInBytes = Runtime.getRuntime().maxMemory();
if (maxMemoryInBytes < minimumRecommendedMemoryInBytes)
{
MC.sendChatMessage("\u00A76" + "Distant Horizons: Low memory detected." + "\u00A7r");
MC.sendChatMessage("Stuttering or low FPS may occur.");
MC.sendChatMessage("Please increase Minecraft's available memory to 4 gigabytes.");
MC.sendChatMessage("This warning can be disabled in DH's config under Advanced -> Logging.");
MC.sendChatMessage("");
String message =
// orange text
"\u00A76" + "Distant Horizons: Low memory detected." + "\u00A7r \n" +
"Stuttering or low FPS may occur. \n" +
"Please increase Minecraft's available memory to 4 gigabytes. \n" +
"This warning can be disabled in DH's config under Advanced -> Logging. \n";
MC.sendChatMessage(message);
}
}
@@ -26,11 +26,11 @@ import com.seibel.distanthorizons.core.generation.DhLightingEngine;
import com.seibel.distanthorizons.core.level.IDhLevel;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.logging.f3.F3Screen;
import com.seibel.distanthorizons.core.pos.DhBlockPos2D;
import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos2D;
import com.seibel.distanthorizons.core.pos.DhChunkPos;
import com.seibel.distanthorizons.core.render.renderer.DebugRenderer;
import com.seibel.distanthorizons.core.sql.dto.BeaconBeamDTO;
import com.seibel.distanthorizons.core.sql.repo.AbstractDhRepo;
import com.seibel.distanthorizons.core.util.LodUtil;
import com.seibel.distanthorizons.core.util.TimerUtil;
import com.seibel.distanthorizons.core.util.objects.Pair;
import com.seibel.distanthorizons.core.util.threading.ThreadPoolUtil;
@@ -43,9 +43,7 @@ import org.apache.logging.log4j.Logger;
import org.jetbrains.annotations.Nullable;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.*;
/** Contains code and variables used by both {@link ClientApi} and {@link ServerApi} */
public class SharedApi
@@ -62,6 +60,7 @@ public class SharedApi
private static final Timer CHUNK_UPDATE_TIMER = TimerUtil.CreateTimer("ChunkUpdateTimer");
private static AbstractDhWorld currentWorld;
private static int lastWorldGenTickDelta = 0;
private static long lastOverloadedLogMessageMsTime = 0;
@@ -139,23 +138,13 @@ public class SharedApi
public static boolean isChunkAtBlockPosAlreadyUpdating(int blockPosX, int blockPosZ)
{ return UPDATING_CHUNK_POS_SET.contains(new DhChunkPos(new DhBlockPos2D(blockPosX, blockPosZ))); }
public static boolean isChunkAtChunkPosAlreadyUpdating(int chunkPosX, int chunkPosZ)
{ return UPDATING_CHUNK_POS_SET.contains(new DhChunkPos(chunkPosX, chunkPosZ)); }
/** handles both block place and break events */
public void chunkBlockChangedEvent(IChunkWrapper chunk, ILevelWrapper level) { this.applyChunkUpdate(chunk, level, true); }
public void chunkLoadEvent(IChunkWrapper chunk, ILevelWrapper level) { this.applyChunkUpdate(chunk, level, false); }
public void chunkUnloadEvent(IChunkWrapper chunk, ILevelWrapper level)
{
// temporarily disabled since this was originally incorrectly designated as "chunkSaveEvent"
// but didn't actually fire on chunk save
// and generally this is unnecessary and drastically reduces LOD building performance
// when traveling around the world
if (false)
{
this.applyChunkUpdate(chunk, level, false);
}
}
public void applyChunkUpdate(IChunkWrapper chunkWrapper, ILevelWrapper level, boolean updateNeighborChunks)
{
@@ -265,7 +254,7 @@ public class SharedApi
else
{
// neighboring chunk
DhChunkPos neighbourPos = new DhChunkPos(chunkWrapper.getChunkPos().x + xOffset, chunkWrapper.getChunkPos().z + zOffset);
DhChunkPos neighbourPos = new DhChunkPos(chunkWrapper.getChunkPos().getX() + xOffset, chunkWrapper.getChunkPos().getZ() + zOffset);
IChunkWrapper neighbourChunk = dhLevel.getLevelWrapper().tryGetChunk(neighbourPos);
if (neighbourChunk != null)
{
@@ -282,36 +271,43 @@ public class SharedApi
}
}
}
private static void bakeChunkLightingAndSendToLevelAsync(IChunkWrapper chunkWrapper, @Nullable ArrayList<IChunkWrapper> neighbourChunkList, IDhLevel dhLevel)
/** returning a {@link CompletableFuture} isn't necessary, but allows Intellij to properly show the full stack trace when debugging. */
@SuppressWarnings("UnusedReturnValue")
private static CompletableFuture<Void> bakeChunkLightingAndSendToLevelAsync(IChunkWrapper chunkWrapper, @Nullable ArrayList<IChunkWrapper> neighbourChunkList, IDhLevel dhLevel)
{
// lighting the chunk needs to be done on a separate thread to prevent lagging any of the event threads
ThreadPoolExecutor executor = ThreadPoolUtil.getLightPopulatorExecutor();
ThreadPoolExecutor executor = ThreadPoolUtil.getChunkToLodBuilderExecutor();
if (executor == null)
{
return;
return CompletableFuture.completedFuture(null);
}
try
{
executor.execute(() ->
return CompletableFuture.runAsync(() ->
{
//LOGGER.trace(chunkWrapper.getChunkPos() + " " + executor.getActiveCount() + " / " + executor.getQueue().size() + " - " + executor.getCompletedTaskCount());
try
{
boolean checkChunkHash = !Config.Client.Advanced.LodBuilding.disableUnchangedChunkCheck.get();
// check if this chunk has been converted into an LOD already
int oldChunkHash = dhLevel.getChunkHash(chunkWrapper.getChunkPos()); // shouldn't happen on the render thread since it may take a few moments to run
int newChunkHash = chunkWrapper.getBlockBiomeHashCode();
if (oldChunkHash == newChunkHash)
if (checkChunkHash)
{
// if the chunk hashes are the same then we don't need to bother with lighting the chunk
// or creating/updating the LODs
//LOGGER.info("skipping: "+chunkWrapper.getChunkPos()+" "+newChunkHash);
return;
}
else
{
//LOGGER.info("g: "+chunkWrapper.getChunkPos()+" "+newChunkHash);
if (oldChunkHash == newChunkHash)
{
// if the chunk hashes are the same then we don't need to bother with lighting the chunk
// or creating/updating the LODs
//LOGGER.info("skipping: "+chunkWrapper.getChunkPos()+" "+newChunkHash);
return;
}
else
{
//LOGGER.info("g: "+chunkWrapper.getChunkPos()+" "+newChunkHash);
}
}
@@ -328,36 +324,44 @@ public class SharedApi
}
// Save or populate the chunk wrapper's lighting
// this is done so we don't have to worry about MC unloading the lighting data for this chunk
boolean onlyUseDhLighting = Config.Client.Advanced.LodBuilding.onlyUseDhLightingEngine.get();
if (!onlyUseDhLighting && chunkWrapper.isLightCorrect())
// chunk light baking is disabled since profiling revealed it used
// roughly the same amount of time as generating the lighting ourselves and
// was much more likely to have issues with corrupt (all black or all bright) chunks
boolean tryUsingMcLightingEngine = false;
if (tryUsingMcLightingEngine)
{
try
// Save or populate the chunk wrapper's lighting
// this is done so we don't have to worry about MC unloading the lighting data for this chunk
boolean chunkLightPopulated = false;
boolean onlyUseDhLighting = Config.Client.Advanced.LodBuilding.onlyUseDhLightingEngine.get();
if (!onlyUseDhLighting && chunkWrapper.isLightCorrect())
{
// If MC's lighting engine isn't thread safe this may cause the server thread to lag
chunkWrapper.bakeDhLightingUsingMcLightingEngine();
chunkLightPopulated = chunkWrapper.bakeDhLightingUsingMcLightingEngine(dhLevel.getLevelWrapper());
if (!chunkLightPopulated)
{
// clear any existing data to prevent partial or corrupt lighting
// when re-generating it
chunkWrapper.clearDhBlockLighting();
chunkWrapper.clearDhSkyLighting();
}
}
catch (IllegalStateException e)
// something went wrong during the baking process so we have to generate the lighting ourselves
if (!chunkLightPopulated)
{
LOGGER.warn("Chunk light baking error: " + e.getMessage(), e);
DhLightingEngine.INSTANCE.lightChunk(chunkWrapper, nearbyChunkList, dhLevel.hasSkyLight() ? LodUtil.MAX_MC_LIGHT : LodUtil.MIN_MC_LIGHT);
}
}
else
{
// generate the chunk's lighting, using neighboring chunks if present
DhLightingEngine.INSTANCE.lightChunk(chunkWrapper, nearbyChunkList, dhLevel.hasSkyLight() ? 15 : 0);
DhLightingEngine.INSTANCE.lightChunk(chunkWrapper, nearbyChunkList, dhLevel.hasSkyLight() ? LodUtil.MAX_MC_LIGHT : LodUtil.MIN_MC_LIGHT);
}
// get this chunk's active beacons
List<BeaconBeamDTO> beaconBeamList = chunkWrapper.getAllActiveBeacons(nearbyChunkList);
dhLevel.setBeaconBeamsForChunk(chunkWrapper.getChunkPos(), beaconBeamList);
dhLevel.updateChunkAsync(chunkWrapper);
dhLevel.setChunkHash(chunkWrapper.getChunkPos(), newChunkHash);
dhLevel.updateBeaconBeamsForChunk(chunkWrapper, nearbyChunkList);
dhLevel.updateChunkAsync(chunkWrapper, newChunkHash);
}
catch (Exception e)
{
@@ -382,9 +386,13 @@ public class SharedApi
UPDATING_CHUNK_POS_SET.remove(chunkWrapper.getChunkPos());
}
}
});
}, executor);
}
catch (RejectedExecutionException ignore)
{
// the executor was shut down, it should be back up shortly and able to accept new jobs
return CompletableFuture.completedFuture(null);
}
catch (RejectedExecutionException ignore) { /* the executor was shut down, it should be back up shortly and able to accept new jobs */ }
}
@@ -54,7 +54,6 @@ import java.util.List;
* Otherwise, you will have issues where only some of the config entries will exist when your listener is created.
*
* @author coolGi
* @version 2023-7-16
*/
public class Config
@@ -107,6 +106,8 @@ public class Config
public static ConfigLinkedEntry quickEnableWorldGenerator = new ConfigLinkedEntry(Advanced.WorldGenerator.enableDistantGeneration);
public static ConfigLinkedEntry quickLodCloudRendering = new ConfigLinkedEntry(Advanced.Graphics.GenericRendering.enableCloudRendering);
public static ConfigEntry<Boolean> optionsButton = new ConfigEntry.Builder<Boolean>()
.set(true)
.setAppearance(EConfigEntryAppearance.ONLY_IN_FILE)
@@ -126,7 +127,6 @@ public class Config
public static ConfigCategory multiplayer = new ConfigCategory.Builder().set(Multiplayer.class).build();
public static ConfigCategory lodBuilding = new ConfigCategory.Builder().set(LodBuilding.class).build();
public static ConfigCategory multiThreading = new ConfigCategory.Builder().set(MultiThreading.class).build();
public static ConfigCategory buffers = new ConfigCategory.Builder().set(GpuBuffers.class).build();
public static ConfigCategory autoUpdater = new ConfigCategory.Builder().set(AutoUpdater.class).build();
public static ConfigCategory logging = new ConfigCategory.Builder().set(Logging.class).build();
@@ -179,6 +179,7 @@ public class Config
+ "Lowest Quality: " + EDhApiVerticalQuality.HEIGHT_MAP + "\n"
+ "Highest Quality: " + EDhApiVerticalQuality.EXTREME)
.setPerformance(EConfigEntryPerformance.VERY_HIGH)
.addListener(ReloadLodsConfigEventHandler.INSTANCE)
.build();
public static ConfigEntry<EDhApiHorizontalQuality> horizontalQuality = new ConfigEntry.Builder<EDhApiHorizontalQuality>()
@@ -200,6 +201,7 @@ public class Config
+ EDhApiTransparency.DISABLED + ": LODs will be opaque. \n"
+ "")
.setPerformance(EConfigEntryPerformance.MEDIUM)
.addListener(ReloadLodsConfigEventHandler.INSTANCE)
.build();
public static ConfigEntry<EDhApiBlocksToAvoid> blocksToIgnore = new ConfigEntry.Builder<EDhApiBlocksToAvoid>()
@@ -211,6 +213,7 @@ public class Config
+ EDhApiBlocksToAvoid.NON_COLLIDING + ": Only represent solid blocks in the LODs (tall grass, torches, etc. won't count for a LOD's height) \n"
+ "")
.setPerformance(EConfigEntryPerformance.NONE)
.addListener(ReloadLodsConfigEventHandler.INSTANCE)
.build();
public static ConfigEntry<Boolean> tintWithAvoidedBlocks = new ConfigEntry.Builder<Boolean>()
@@ -222,6 +225,7 @@ public class Config
+ "False: skipped blocks will not change color of surface below them. "
+ "")
.setPerformance(EConfigEntryPerformance.NONE)
.addListener(ReloadLodsConfigEventHandler.INSTANCE)
.build();
// TODO fixme
@@ -587,6 +591,7 @@ public class Config
+ "0 = black \n"
+ "1 = normal \n"
+ "2 = near white")
.addListener(ReloadLodsConfigEventHandler.INSTANCE)
.build();
public static ConfigEntry<Double> saturationMultiplier = new ConfigEntry.Builder<Double>() // TODO: Make this a float (the ClassicConfigGUI doesnt support floats)
@@ -597,6 +602,7 @@ public class Config
+ "0 = black and white \n"
+ "1 = normal \n"
+ "2 = very saturated")
.addListener(ReloadLodsConfigEventHandler.INSTANCE)
.build();
public static ConfigEntry<Boolean> enableCaveCulling = new ConfigEntry.Builder<Boolean>()
@@ -611,14 +617,15 @@ public class Config
+ "Additional Info: Currently this cull all faces \n"
+ " with skylight value of 0 in dimensions that \n"
+ " does not have a ceiling.")
.addListener(ReloadLodsConfigEventHandler.INSTANCE)
.build();
@Deprecated
public static ConfigEntry<Integer> caveCullingHeight = new ConfigEntry.Builder<Integer>()
.setMinDefaultMax(-4096, 40, 4096)
.setAppearance(EConfigEntryAppearance.ONLY_IN_API)
.setMinDefaultMax(-4096, 60, 4096)
.comment(""
+ "At what Y value should cave culling start?")
+ "At what Y value should cave culling start? \n"
+ "Lower this value if you get walls for areas with 0 light.")
.addListener(ReloadLodsConfigEventHandler.INSTANCE)
.build();
public static ConfigEntry<Integer> earthCurveRatio = new ConfigEntry.Builder<Integer>()
@@ -656,6 +663,7 @@ public class Config
+ EDhApiLodShading.DISABLED + ": All LOD sides will be rendered with the same brightness. \n"
+ "")
.setPerformance(EConfigEntryPerformance.NONE)
.addListener(ReloadLodsConfigEventHandler.INSTANCE)
.build();
public static ConfigEntry<Boolean> disableFrustumCulling = new ConfigEntry.Builder<Boolean>()
@@ -690,6 +698,18 @@ public class Config
+ EDhApiGrassSideRendering.AS_DIRT + ": sides render entirely as dirt. \n"
+ "")
.setPerformance(EConfigEntryPerformance.NONE)
.addListener(ReloadLodsConfigEventHandler.INSTANCE)
.build();
public static ConfigEntry<Boolean> disableBeaconDistanceCulling = new ConfigEntry.Builder<Boolean>()
.set(false)
.comment(""
+ "If true all beacons near the camera won't be drawn to prevent vanilla overdraw. \n"
+ "If false all beacons will be rendered. \n"
+ "\n"
+ "Generally this should be left as false. It's main purpose is for debugging\n"
+ "beacon updating/rendering.\n"
+ "")
.build();
}
@@ -773,8 +793,23 @@ public class Config
+ "")
.build();
public static ConfigEntry<Boolean> disableUnchangedChunkCheck = new ConfigEntry.Builder<Boolean>()
.set(false)
.comment(""
+ "Normally DH will attempt to skip creating LODs for chunks it's already seen\n"
+ "and that haven't changed.\n"
+ "\n"
+ "However sometimes that logic incorrecly prevents LODs from being updated.\n"
+ "Disabling this check may fix issues where LODs aren't updated after\n"
+ "blocks have been changed.\n"
+ "")
.build();
/** Currently we always use the DH lighting engine because there's a high likelyhood of MC returning incorrect lighting otherwise */
@Deprecated
public static ConfigEntry<Boolean> onlyUseDhLightingEngine = new ConfigEntry.Builder<Boolean>()
.set(false)
.setAppearance(EConfigEntryAppearance.ONLY_IN_API)
.comment(""
+ "If false LODs will be lit by Minecraft's lighting engine when possible \n"
+ "and fall back to the DH lighting engine only when necessary. \n"
@@ -1062,53 +1097,6 @@ public class Config
}
public static class GpuBuffers
{
public static ConfigEntry<EDhApiGpuUploadMethod> gpuUploadMethod = new ConfigEntry.Builder<EDhApiGpuUploadMethod>()
.set(EDhApiGpuUploadMethod.AUTO)
.comment(""
+ "What method should be used to upload geometry to the GPU? \n"
+ "\n"
+ EDhApiGpuUploadMethod.AUTO + ": Picks the best option based on the GPU you have. \n"
+ "\n"
+ EDhApiGpuUploadMethod.BUFFER_STORAGE + ": Default if OpenGL 4.5 is supported. \n"
+ " Fast rendering, no stuttering. \n"
+ "\n"
+ EDhApiGpuUploadMethod.SUB_DATA + ": Backup option for NVIDIA. \n"
+ " Fast rendering but may stutter when uploading. \n"
+ "\n"
+ EDhApiGpuUploadMethod.BUFFER_MAPPING + ": Slow rendering but won't stutter when uploading. \n"
+ " Generally the best option for integrated GPUs. \n"
+ " Default option for AMD/Intel if OpenGL 4.5 isn't supported. \n"
+ " May end up storing buffers in System memory. \n"
+ " Fast rendering if in GPU memory, slow if in system memory, \n"
+ " but won't stutter when uploading. \n"
+ "\n"
+ EDhApiGpuUploadMethod.DATA + ": Fast rendering but will stutter when uploading. \n"
+ " Backup option for AMD/Intel. \n"
+ " Fast rendering but may stutter when uploading. \n"
+ "\n"
+ "If you don't see any difference when changing these settings, \n"
+ "or the world looks corrupted: restart your game."
+ "")
.build();
public static ConfigEntry<Integer> gpuUploadPerMegabyteInMilliseconds = new ConfigEntry.Builder<Integer>()
.setMinDefaultMax(0, 0, 50)
.comment(""
+ "How long should a buffer wait per Megabyte of data uploaded? \n"
+ "Helpful resource for frame times: https://fpstoms.com \n"
+ "\n"
+ "Longer times may reduce stuttering but will make LODs \n"
+ "transition and load slower. Change this to [0] for no timeout. \n"
+ "\n"
+ "NOTE:\n"
+ "Before changing this config, try changing the \"GPU Upload method\" first. \n"
+ "")
.build();
}
public static class AutoUpdater
{
public static ConfigEntry<Boolean> enableAutoUpdater = new ConfigEntry.Builder<Boolean>()
@@ -1140,63 +1128,63 @@ public class Config
// TODO add change all option
// TODO default to error chat and info file
public static ConfigEntry<EDhApiLoggerMode> logWorldGenEvent = new ConfigEntry.Builder<EDhApiLoggerMode>()
.set(EDhApiLoggerMode.LOG_ERROR_TO_CHAT)
.set(EDhApiLoggerMode.LOG_ERROR_TO_CHAT_AND_INFO_TO_FILE)
.comment(""
+ "If enabled, the mod will log information about the world generation process. \n"
+ "This can be useful for debugging.")
.build();
public static ConfigEntry<EDhApiLoggerMode> logWorldGenPerformance = new ConfigEntry.Builder<EDhApiLoggerMode>()
.set(EDhApiLoggerMode.LOG_ERROR_TO_CHAT)
.set(EDhApiLoggerMode.LOG_ERROR_TO_CHAT_AND_INFO_TO_FILE)
.comment(""
+ "If enabled, the mod will log performance about the world generation process. \n"
+ "This can be useful for debugging.")
.build();
public static ConfigEntry<EDhApiLoggerMode> logWorldGenLoadEvent = new ConfigEntry.Builder<EDhApiLoggerMode>()
.set(EDhApiLoggerMode.LOG_ERROR_TO_CHAT)
.set(EDhApiLoggerMode.LOG_ERROR_TO_CHAT_AND_INFO_TO_FILE)
.comment(""
+ "If enabled, the mod will log information about the world generation process. \n"
+ "This can be useful for debugging.")
.build();
public static ConfigEntry<EDhApiLoggerMode> logLodBuilderEvent = new ConfigEntry.Builder<EDhApiLoggerMode>()
.set(EDhApiLoggerMode.LOG_ERROR_TO_CHAT)
.set(EDhApiLoggerMode.LOG_ERROR_TO_CHAT_AND_INFO_TO_FILE)
.comment(""
+ "If enabled, the mod will log information about the LOD generation process. \n"
+ "This can be useful for debugging.")
.build();
public static ConfigEntry<EDhApiLoggerMode> logRendererBufferEvent = new ConfigEntry.Builder<EDhApiLoggerMode>()
.set(EDhApiLoggerMode.LOG_ERROR_TO_CHAT)
.set(EDhApiLoggerMode.LOG_ERROR_TO_CHAT_AND_INFO_TO_FILE)
.comment(""
+ "If enabled, the mod will log information about the renderer buffer process. \n"
+ "This can be useful for debugging.")
.build();
public static ConfigEntry<EDhApiLoggerMode> logRendererGLEvent = new ConfigEntry.Builder<EDhApiLoggerMode>()
.set(EDhApiLoggerMode.LOG_ERROR_TO_CHAT)
.set(EDhApiLoggerMode.LOG_ERROR_TO_CHAT_AND_INFO_TO_FILE)
.comment(""
+ "If enabled, the mod will log information about the renderer OpenGL process. \n"
+ "This can be useful for debugging.")
.build();
public static ConfigEntry<EDhApiLoggerMode> logFileReadWriteEvent = new ConfigEntry.Builder<EDhApiLoggerMode>()
.set(EDhApiLoggerMode.LOG_ERROR_TO_CHAT)
.set(EDhApiLoggerMode.LOG_ERROR_TO_CHAT_AND_INFO_TO_FILE)
.comment(""
+ "If enabled, the mod will log information about file read/write operations. \n"
+ "This can be useful for debugging.")
.build();
public static ConfigEntry<EDhApiLoggerMode> logFileSubDimEvent = new ConfigEntry.Builder<EDhApiLoggerMode>()
.set(EDhApiLoggerMode.LOG_ERROR_TO_CHAT)
.set(EDhApiLoggerMode.LOG_ERROR_TO_CHAT_AND_INFO_TO_FILE)
.comment(""
+ "If enabled, the mod will log information about file sub-dimension operations. \n"
+ "This can be useful for debugging.")
.build();
public static ConfigEntry<EDhApiLoggerMode> logNetworkEvent = new ConfigEntry.Builder<EDhApiLoggerMode>()
.set(EDhApiLoggerMode.LOG_ERROR_TO_CHAT)
.set(EDhApiLoggerMode.LOG_ERROR_TO_CHAT_AND_INFO_TO_FILE)
.comment(""
+ "If enabled, the mod will log information about network operations. \n"
+ "This can be useful for debugging.")
@@ -1217,6 +1205,13 @@ public class Config
+ "giving some basic information about how DH will function.")
.build();
public static ConfigEntry<Boolean> showModCompatibilityWarningsOnStartup = new ConfigEntry.Builder<Boolean>()
.set(true)
.comment(""
+ "If enabled, a chat message will be displayed when a potentially problematic \n"
+ "mod is installed alongside DH.")
.build();
}
public static class Debugging
@@ -1303,22 +1298,38 @@ public class Config
public static ConfigEntry<Boolean> columnBuilderDebugEnable = new ConfigEntry.Builder<Boolean>()
.set(false)
.setAppearance(EConfigEntryAppearance.ONLY_IN_GUI)
.addListener(DebugColumnConfigEventHandler.INSTANCE)
.addListener(ReloadLodsConfigEventHandler.INSTANCE)
.build();
public static ConfigEntry<Integer> columnBuilderDebugDetailLevel = new ConfigEntry.Builder<Integer>()
.set((int) DhSectionPos.SECTION_MINIMUM_DETAIL_LEVEL)
.setAppearance(EConfigEntryAppearance.ONLY_IN_GUI)
.addListener(DebugColumnConfigEventHandler.INSTANCE)
.addListener(ReloadLodsConfigEventHandler.INSTANCE)
.build();
public static ConfigEntry<Integer> columnBuilderDebugXPos = new ConfigEntry.Builder<Integer>()
.set(0)
.setAppearance(EConfigEntryAppearance.ONLY_IN_GUI)
.addListener(DebugColumnConfigEventHandler.INSTANCE)
.addListener(ReloadLodsConfigEventHandler.INSTANCE)
.build();
public static ConfigEntry<Integer> columnBuilderDebugZPos = new ConfigEntry.Builder<Integer>()
.set(0)
.setAppearance(EConfigEntryAppearance.ONLY_IN_GUI)
.addListener(DebugColumnConfigEventHandler.INSTANCE)
.addListener(ReloadLodsConfigEventHandler.INSTANCE)
.build();
public static ConfigEntry<Integer> columnBuilderDebugXRow = new ConfigEntry.Builder<Integer>()
.set(-1)
.setAppearance(EConfigEntryAppearance.ONLY_IN_GUI)
.addListener(ReloadLodsConfigEventHandler.INSTANCE)
.build();
public static ConfigEntry<Integer> columnBuilderDebugZRow = new ConfigEntry.Builder<Integer>()
.set(-1)
.setAppearance(EConfigEntryAppearance.ONLY_IN_GUI)
.addListener(ReloadLodsConfigEventHandler.INSTANCE)
.build();
public static ConfigEntry<Integer> columnBuilderDebugColumnIndex = new ConfigEntry.Builder<Integer>()
.set(-1)
.setAppearance(EConfigEntryAppearance.ONLY_IN_GUI)
.addListener(ReloadLodsConfigEventHandler.INSTANCE)
.build();
@@ -23,9 +23,9 @@ import com.seibel.distanthorizons.api.DhApi;
import com.seibel.distanthorizons.api.interfaces.render.IDhApiRenderProxy;
import com.seibel.distanthorizons.core.config.listeners.IConfigListener;
public class DebugColumnConfigEventHandler implements IConfigListener
public class ReloadLodsConfigEventHandler implements IConfigListener
{
public static DebugColumnConfigEventHandler INSTANCE = new DebugColumnConfigEventHandler();
public static ReloadLodsConfigEventHandler INSTANCE = new ReloadLodsConfigEventHandler();
@Override
public void onConfigValueSet()
@@ -168,7 +168,7 @@ public abstract class AbstractPresetConfigEventHandler<TPresetEnum extends Enum<
if (newPreset != currentPreset)
{
this.getPresetConfigEntry().set(this.getCustomPresetEnum());
this.getPresetConfigEntry().set(newPreset);
}
}
@@ -25,7 +25,7 @@ import com.seibel.distanthorizons.core.dataObjects.transformers.FullDataToRender
import com.seibel.distanthorizons.core.file.DataSourcePool;
import com.seibel.distanthorizons.core.file.IDataSource;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.pos.DhBlockPos2D;
import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos2D;
import com.seibel.distanthorizons.core.pos.DhSectionPos;
import com.seibel.distanthorizons.coreapi.ModInfo;
import com.seibel.distanthorizons.core.dataObjects.render.columnViews.ColumnArrayView;
@@ -182,7 +182,7 @@ public class ColumnRenderSource implements IDataSource<IDhClientLevel>
EDhApiWorldGenerationStep worldGenStep = inputFullDataSource.getWorldGenStepAtRelativePos(x, z);
if (dataColumn != null && worldGenStep != EDhApiWorldGenerationStep.EMPTY)
{
FullDataToRenderDataTransformer.updateRenderDataViewWithFullDataColumn(
FullDataToRenderDataTransformer.updateOrReplaceRenderDataViewColumnWithFullDataColumn(
level, inputFullDataSource.mapping,
minBlockPos.x + x,
minBlockPos.z + z,
@@ -288,7 +288,7 @@ public class ColumnRenderSource implements IDataSource<IDhClientLevel>
String SUBDATA_DELIMITER = ",";
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append(this.pos);
stringBuilder.append(DhSectionPos.toString(this.pos));
stringBuilder.append(LINE_DELIMITER);
int size = 1;
@@ -19,29 +19,60 @@
package com.seibel.distanthorizons.core.dataObjects.render.bufferBuilding;
import com.seibel.distanthorizons.api.enums.rendering.EDhApiBlockMaterial;
import com.seibel.distanthorizons.core.config.Config;
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
import com.seibel.distanthorizons.core.enums.EDhDirection;
import com.seibel.distanthorizons.core.level.IDhClientLevel;
import com.seibel.distanthorizons.core.util.ColorUtil;
import com.seibel.distanthorizons.core.util.LodUtil;
import com.seibel.distanthorizons.core.util.RenderDataPointUtil;
import com.seibel.distanthorizons.core.wrapperInterfaces.block.IBlockStateWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper;
import com.seibel.distanthorizons.core.dataObjects.render.columnViews.ColumnArrayView;
import com.seibel.distanthorizons.core.render.renderer.LodRenderer;
import com.seibel.distanthorizons.coreapi.util.MathUtil;
import org.jetbrains.annotations.NotNull;
import java.util.Arrays;
public class ColumnBox
{
private static final IMinecraftClientWrapper MC = SingletonInjector.INSTANCE.get(IMinecraftClientWrapper.class);
/**
* if the skylight has this value that means
* no data is expected
*/
private static final byte SKYLIGHT_EMPTY = -1;
/**
* if the skylight has this value that means
* that block position is covered/occuled by an adjacent block/column.
*/
private static final byte SKYLIGHT_COVERED = -2;
private static final ThreadLocal<byte[]> THREAD_LOCAL_SKY_LIGHT_ARRAY = ThreadLocal.withInitial(() ->
{
byte[] array = new byte[RenderDataPointUtil.MAX_WORLD_Y_SIZE];
Arrays.fill(array, SKYLIGHT_EMPTY);
return array;
});
//=========//
// builder //
//=========//
public static void addBoxQuadsToBuilder(
LodQuadBuilder builder,
LodQuadBuilder builder, IDhClientLevel clientLevel,
short xSize, short ySize, short zSize,
short x, short minY, short z,
int color, byte irisBlockMaterialId, byte skyLight, byte blockLight,
long topData, long bottomData, ColumnArrayView[][] adjData)
long topData, long bottomData, ColumnArrayView[] adjData, boolean[] isAdjDataSameDetailLevel)
{
//================//
// variable setup //
//================//
short maxX = (short) (x + xSize);
short maxY = (short) (minY + ySize);
short maxZ = (short) (z + zSize);
@@ -53,33 +84,24 @@ public class ColumnBox
boolean isTopTransparent = RenderDataPointUtil.getAlpha(topData) < 255 && LodRenderer.transparencyEnabled;
boolean isBottomTransparent = RenderDataPointUtil.getAlpha(bottomData) < 255 && LodRenderer.transparencyEnabled;
// defaulting to a value far below what we can normally render means we
// don't need to have an additional "is cave culling enabled" check
int caveCullingMaxY = Integer.MIN_VALUE;
if (Config.Client.Advanced.Graphics.AdvancedGraphics.enableCaveCulling.get())
{
caveCullingMaxY = Config.Client.Advanced.Graphics.AdvancedGraphics.caveCullingHeight.get() - clientLevel.getMinY();
}
// if there isn't any data below this LOD, make this LOD's color opaque to prevent seeing void through transparent blocks
// Note: this LOD should still be considered transparent for this method's checks, otherwise rendering bugs may occur
// FIXME this transparency change should be applied before this point since this could affect other areas
// This may also be better than handling the LOD as transparent, but that is TBD
if (!RenderDataPointUtil.doesDataPointExist(bottomData))
{
color = ColorUtil.setAlpha(color, 255);
}
// cave culling prevention
// prevents certain faces from being culled underground that should be allowed
if (builder.skipQuadsWithZeroSkylight
&& 0 == skyLight
&& builder.skyLightCullingBelow > maxY
&& (
(RenderDataPointUtil.getAlpha(topData) < 255 && RenderDataPointUtil.getYMax(topData) >= builder.skyLightCullingBelow)
|| (RenderDataPointUtil.getYMin(topData) >= builder.skyLightCullingBelow)
|| !RenderDataPointUtil.doesDataPointExist(topData)
)
)
{
maxY = builder.skyLightCullingBelow;
}
// fake ocean transparency
if (LodRenderer.transparencyEnabled && LodRenderer.fakeOceanFloor)
{
@@ -99,7 +121,9 @@ public class ColumnBox
// add top and bottom faces if requested //
//==========================//
// add top and bottom faces //
//==========================//
boolean skipTop = RenderDataPointUtil.doesDataPointExist(topData) && (RenderDataPointUtil.getYMin(topData) == maxY) && !isTopTransparent;
if (!skipTop)
@@ -114,409 +138,291 @@ public class ColumnBox
}
// add North, south, east, and west faces if requested //
// TODO merge duplicate code
//NORTH face vertex creation
//========================================//
// add North, south, east, and west faces //
//========================================//
// NORTH face
{
ColumnArrayView[] adjDataNorth = adjData[EDhDirection.NORTH.ordinal() - 2]; // TODO can we use something other than ordinal-2?
int adjOverlapNorth = ColorUtil.INVISIBLE;
if (adjDataNorth == null)
ColumnArrayView adjCol = adjData[EDhDirection.NORTH.ordinal() - 2]; // TODO can we use something other than ordinal-2?
boolean adjSameDetailLevel = isAdjDataSameDetailLevel[EDhDirection.NORTH.ordinal() - 2];
// if the adjacent column is null that generally means the adjacent area hasn't been generated yet
if (adjCol == null)
{
// add an adjacent face if this is opaque face or transparent over the void
// Add an adjacent face if this is opaque face or transparent over the void.
if (!isTransparent || overVoid)
{
builder.addQuadAdj(EDhDirection.NORTH, x, minY, z, xSize, ySize, color, irisBlockMaterialId, LodUtil.MAX_MC_LIGHT, blockLight);
}
}
else if (adjDataNorth.length == 1)
{
makeAdjVerticalQuad(builder, adjDataNorth[0], EDhDirection.NORTH, x, minY, z, xSize, ySize,
color, adjOverlapNorth, irisBlockMaterialId, skyLightTop, blockLight,
topData, bottomData);
}
else
{
makeAdjVerticalQuad(builder, adjDataNorth[0], EDhDirection.NORTH, x, minY, z, (short) (xSize / 2), ySize,
color, adjOverlapNorth, irisBlockMaterialId, skyLightTop, blockLight,
topData, bottomData);
makeAdjVerticalQuad(builder, adjDataNorth[1], EDhDirection.NORTH, (short) (x + xSize / 2), minY, z, (short) (xSize / 2), ySize,
color, adjOverlapNorth, irisBlockMaterialId, skyLightTop, blockLight,
topData, bottomData);
makeAdjVerticalQuad(builder, adjCol, adjSameDetailLevel, caveCullingMaxY, EDhDirection.NORTH, x, minY, z, xSize, ySize,
color, irisBlockMaterialId, blockLight);
}
}
//SOUTH face vertex creation
// SOUTH face
{
ColumnArrayView[] adjDataSouth = adjData[EDhDirection.SOUTH.ordinal() - 2];
int adjOverlapSouth = ColorUtil.INVISIBLE;
if (adjDataSouth == null)
ColumnArrayView adjCol = adjData[EDhDirection.SOUTH.ordinal() - 2];
boolean adjSameDetailLevel = isAdjDataSameDetailLevel[EDhDirection.SOUTH.ordinal() - 2];
if (adjCol == null)
{
if (!isTransparent || overVoid)
{
builder.addQuadAdj(EDhDirection.SOUTH, x, minY, maxZ, xSize, ySize, color, irisBlockMaterialId, LodUtil.MAX_MC_LIGHT, blockLight);
}
else if (adjDataSouth.length == 1)
{
makeAdjVerticalQuad(builder, adjDataSouth[0], EDhDirection.SOUTH, x, minY, maxZ, xSize, ySize,
color, adjOverlapSouth, irisBlockMaterialId, skyLightTop, blockLight,
topData, bottomData);
}
}
else
{
makeAdjVerticalQuad(builder, adjDataSouth[0], EDhDirection.SOUTH, x, minY, maxZ, (short) (xSize / 2), ySize,
color, adjOverlapSouth, irisBlockMaterialId, skyLightTop, blockLight,
topData, bottomData);
makeAdjVerticalQuad(builder, adjDataSouth[1], EDhDirection.SOUTH, (short) (x + xSize / 2), minY, maxZ, (short) (xSize / 2), ySize,
color, adjOverlapSouth, irisBlockMaterialId, skyLightTop, blockLight,
topData, bottomData);
makeAdjVerticalQuad(builder, adjCol, adjSameDetailLevel, caveCullingMaxY, EDhDirection.SOUTH, x, minY, maxZ, xSize, ySize,
color, irisBlockMaterialId, blockLight);
}
}
//WEST face vertex creation
// WEST face
{
ColumnArrayView[] adjDataWest = adjData[EDhDirection.WEST.ordinal() - 2];
int adjOverlapWest = ColorUtil.INVISIBLE;
if (adjDataWest == null)
ColumnArrayView adjCol = adjData[EDhDirection.WEST.ordinal() - 2];
boolean adjSameDetailLevel = isAdjDataSameDetailLevel[EDhDirection.WEST.ordinal() - 2];
if (adjCol == null)
{
if (!isTransparent || overVoid)
{
builder.addQuadAdj(EDhDirection.WEST, x, minY, z, zSize, ySize, color, irisBlockMaterialId, LodUtil.MAX_MC_LIGHT, blockLight);
}
else if (adjDataWest.length == 1)
{
makeAdjVerticalQuad(builder, adjDataWest[0], EDhDirection.WEST, x, minY, z, zSize, ySize,
color, adjOverlapWest, irisBlockMaterialId, skyLightTop, blockLight,
topData, bottomData);
}
}
else
{
makeAdjVerticalQuad(builder, adjDataWest[0], EDhDirection.WEST, x, minY, z, (short) (zSize / 2), ySize,
color, adjOverlapWest, irisBlockMaterialId, skyLightTop, blockLight,
topData, bottomData);
makeAdjVerticalQuad(builder, adjDataWest[1], EDhDirection.WEST, x, minY, (short) (z + zSize / 2), (short) (zSize / 2), ySize,
color, adjOverlapWest, irisBlockMaterialId, skyLightTop, blockLight,
topData, bottomData);
makeAdjVerticalQuad(builder, adjCol, adjSameDetailLevel, caveCullingMaxY, EDhDirection.WEST, x, minY, z, zSize, ySize,
color, irisBlockMaterialId, blockLight);
}
}
//EAST face vertex creation
// EAST face
{
ColumnArrayView[] adjDataEast = adjData[EDhDirection.EAST.ordinal() - 2];
int adjOverlapEast = ColorUtil.INVISIBLE;
if (adjData[EDhDirection.EAST.ordinal() - 2] == null)
ColumnArrayView adjCol = adjData[EDhDirection.EAST.ordinal() - 2];
boolean adjSameDetailLevel = isAdjDataSameDetailLevel[EDhDirection.EAST.ordinal() - 2];
if (adjCol == null)
{
if (!isTransparent || overVoid)
{
builder.addQuadAdj(EDhDirection.EAST, maxX, minY, z, zSize, ySize, color, irisBlockMaterialId, LodUtil.MAX_MC_LIGHT, blockLight);
}
else if (adjDataEast.length == 1)
{
makeAdjVerticalQuad(builder, adjDataEast[0], EDhDirection.EAST, maxX, minY, z, zSize, ySize,
color, adjOverlapEast, irisBlockMaterialId, skyLightTop, blockLight,
topData, bottomData);
}
}
else
{
makeAdjVerticalQuad(builder, adjDataEast[0], EDhDirection.EAST, maxX, minY, z, (short) (zSize / 2), ySize,
color, adjOverlapEast, irisBlockMaterialId, skyLightTop, blockLight,
topData, bottomData);
makeAdjVerticalQuad(builder, adjDataEast[1], EDhDirection.EAST, maxX, minY, (short) (z + zSize / 2), (short) (zSize / 2), ySize,
color, adjOverlapEast, irisBlockMaterialId, skyLightTop, blockLight,
topData, bottomData);
makeAdjVerticalQuad(builder, adjCol, adjSameDetailLevel, caveCullingMaxY, EDhDirection.EAST, maxX, minY, z, zSize, ySize,
color, irisBlockMaterialId, blockLight);
}
}
}
// the overlap color can be used to see faces that shouldn't be rendered
private static void makeAdjVerticalQuad(
LodQuadBuilder builder, ColumnArrayView adjColumnView, EDhDirection direction,
LodQuadBuilder builder, @NotNull ColumnArrayView adjColumnView, boolean adjacentIsSameDetailLevel, int caveCullingMaxY, EDhDirection direction,
short x, short yMin, short z, short horizontalWidth, short ySize,
int color, int debugOverlapColor, byte irisBlockMaterialId, byte skyLightTop, byte blockLight,
long topData, long bottomData)
int color, byte irisBlockMaterialId, byte blockLight)
{
//==================//
// create face with //
// no adjacent data //
//==================//
color = ColorUtil.applyShade(color, MC.getShade(direction));
if (adjColumnView == null || adjColumnView.size == 0 || RenderDataPointUtil.isVoid(adjColumnView.get(0)))
// if there isn't any data adjacent to this LOD,
// just add the full vertical quad
if (adjColumnView.size == 0 || RenderDataPointUtil.isVoid(adjColumnView.get(0)))
{
// there isn't any data adjacent to this LOD, add the vertical quad
builder.addQuadAdj(direction, x, yMin, z, horizontalWidth, ySize, color, irisBlockMaterialId, LodUtil.MAX_MC_LIGHT, blockLight);
return;
}
int yMax = yMin + ySize;
int adjIndex;
boolean firstFace = true;
boolean inputAboveAdjLods = true;
short previousAdjDepth = -1;
byte nextTopSkyLight = skyLightTop;
boolean inputTransparent = ColorUtil.getAlpha(color) < 255 && LodRenderer.transparencyEnabled;
boolean lastAdjWasTransparent = false;
//===========================//
// Determine face visibility //
// based on it's neighbors //
//===========================//
short yMax = (short) (yMin + ySize); // min is inclusive, max is exclusive
byte[] skyLightAtInputPos = THREAD_LOCAL_SKY_LIGHT_ARRAY.get();
if (!RenderDataPointUtil.doesDataPointExist(bottomData))
try
{
// there isn't anything under this LOD,
// to prevent seeing through the world, make it opaque
color = ColorUtil.setAlpha(color, 255);
}
// set the initial sky-lights for this face,
// if nothing overlaps or overhangs the face should have max sky light
Arrays.fill(skyLightAtInputPos, yMin, yMax, LodUtil.MAX_MC_LIGHT);
// Add adjacent faces if this LOD is surrounded by transparent LODs
// (prevents invisible sides underwater)
int adjCount = adjColumnView.size();
for (adjIndex = 0; // iterates top down
adjIndex < adjCount
&& RenderDataPointUtil.doesDataPointExist(adjColumnView.get(adjIndex))
&& !RenderDataPointUtil.isVoid(adjColumnView.get(adjIndex));
adjIndex++)
{
long adjPoint = adjColumnView.get(adjIndex);
// if the adjacent data point is over the void
// don't consider it as transparent
// FIXME this transparency change should be applied before this point since this could affect other areas
boolean adjOverVoid = false;
if (adjIndex > 0)
// iterate top down
int adjCount = adjColumnView.size();
for (int adjIndex = 0; adjIndex < adjCount; adjIndex++)
{
long adjBellowPoint = adjColumnView.get(adjIndex-1);
adjOverVoid = !RenderDataPointUtil.doesDataPointExist(adjBellowPoint);
}
boolean adjTransparent = !adjOverVoid && RenderDataPointUtil.getAlpha(adjPoint) < 255 && LodRenderer.transparencyEnabled;
long adjPoint = adjColumnView.get(adjIndex);
short adjMinY = RenderDataPointUtil.getYMin(adjPoint);
short adjMaxY = RenderDataPointUtil.getYMax(adjPoint);
// continue if this data point is transparent or the adjacent point is not
if (inputTransparent || !adjTransparent) // TODO inputIsTransparent may be unnecessary
{
short adjYMin = RenderDataPointUtil.getYMin(adjPoint);
short adjYMax = RenderDataPointUtil.getYMax(adjPoint);
// if fake transparency is enabled, allow for 1 block of transparency,
// everything under that should be opaque
if (LodRenderer.transparencyEnabled && LodRenderer.fakeOceanFloor)
// skip empty adjacent datapoints
if (!RenderDataPointUtil.doesDataPointExist(adjPoint)
|| RenderDataPointUtil.isVoid(adjPoint))
{
if (lastAdjWasTransparent && !adjTransparent)
{
adjYMax = (short) (RenderDataPointUtil.getYMax(adjColumnView.get(adjIndex - 1)) - 1);
}
else if (adjTransparent && (adjIndex + 1) < adjCount)
{
if (RenderDataPointUtil.getAlpha(adjColumnView.get(adjIndex + 1)) == 255)
{
adjYMin = (short) (adjYMax - 1);
}
}
}
if (yMax <= adjYMin)
{
// the adjacent LOD is above the input LOD and won't affect its rendering,
// skip to the next adjacent
continue;
}
inputAboveAdjLods = false;
if (adjYMax < yMin)
// skip this adjacent datapoint if it's above the input datapoint (since it can't affect the input data point)
if (yMax <= adjMinY)
{
// the adjacent LOD is below the input LOD
// getting the skylight is more complicated
// since LODs can be adjacent to water, which changes how skylight works
byte skyLight;
if (adjIndex == 0)
{
// this adj LOD is at the highest position,
// its sky lighting won't be affected by anything above it
skyLight = RenderDataPointUtil.getLightSky(adjPoint);
}
else
{
// TODO improve the comments here, this is a bit confusing
long aboveAdjPoint = adjColumnView.get(adjIndex - 1);
if (RenderDataPointUtil.getAlpha(aboveAdjPoint) != 255)
{
// above adjacent LOD is transparent...
boolean inputMaxHigherThanAboveAdj = yMax > RenderDataPointUtil.getYMax(aboveAdjPoint);
if (inputMaxHigherThanAboveAdj)
{
// ...and higher than the input yMax,
// use its sky light
skyLight = RenderDataPointUtil.getLightSky(aboveAdjPoint);
}
else
{
// ...and at or below the input yMax,
skyLight = RenderDataPointUtil.getLightSky(adjPoint);
}
}
else
{
// LOD above adjacent is opaque, use the adj LOD's skylight
skyLight = RenderDataPointUtil.getLightSky(adjPoint);
}
}
if (firstFace)
{
builder.addQuadAdj(direction, x, yMin, z, horizontalWidth, ySize, color, irisBlockMaterialId, skyLight, blockLight);
}
else
{
// Now: adjMaxHeight < y < previousAdjDepth < yMax
if (previousAdjDepth == -1)
{
// TODO why is this an error?
throw new RuntimeException("Loop error");
}
builder.addQuadAdj(direction, x, yMin, z, horizontalWidth, (short) (previousAdjDepth - yMin), color, irisBlockMaterialId, skyLight, blockLight);
previousAdjDepth = -1;
}
// TODO why break here?
break;
continue;
}
if (adjYMin <= yMin)
long adjAbovePoint = (adjIndex != 0) ? adjColumnView.get(adjIndex - 1) : RenderDataPointUtil.EMPTY_DATA;
long adjBelowPoint = (adjIndex + 1 < adjCount) ? adjColumnView.get(adjIndex + 1) : RenderDataPointUtil.EMPTY_DATA;
// if the adjacent data point is over the void
// don't consider it as transparent
boolean adjOverVoid = !RenderDataPointUtil.doesDataPointExist(adjBelowPoint);
boolean adjTransparent = !adjOverVoid && RenderDataPointUtil.getAlpha(adjPoint) < 255 && LodRenderer.transparencyEnabled;
//=================================//
// set sky light based on adjacent //
//=================================//
// set light based on overlapping adjacent
if (!adjTransparent)
{
// the adjacent LOD's base is at or below the input's base
if (yMax <= adjYMax)
// adj opaque
// mark positions adjacent is covering
byte adjSkyLight = RenderDataPointUtil.getLightSky(adjPoint);
for (int i = adjMinY; i < adjMaxY; i++)
{
// The input face is completely inside the adj's face, don't render it
if (debugOverlapColor != 0)
{
builder.addQuadAdj(direction, x, yMin, z, horizontalWidth, ySize, debugOverlapColor, irisBlockMaterialId, LodUtil.MAX_MC_LIGHT, LodUtil.MAX_MC_LIGHT);
}
byte skyLightAtPos = skyLightAtInputPos[i];
// if the adjacent is a different detail level, we want to render adjacent opaque
// faces to try and reduce the chance of holes on detail level borders
boolean adjacentCoversThis =
// if the adjacent is the same detail level, no special handling is necessary
!adjacentIsSameDetailLevel
// if the adjacent face is underground we probably don't need it
&& RenderDataPointUtil.getYMax(adjPoint) >= caveCullingMaxY
// check if this face is on a border
&&
(
(x == 0 && direction == EDhDirection.WEST)
|| (z == 0 && direction == EDhDirection.NORTH)
// TODO why does 256 represent a border? aren't LODs only 64 datapoints wide?
|| (x == 256 && direction == EDhDirection.EAST)
|| (z == 256 && direction == EDhDirection.SOUTH)
);
byte newSkyLightAtPos = adjacentCoversThis ? adjSkyLight : SKYLIGHT_COVERED;
skyLightAtInputPos[i] = (byte) Math.min(newSkyLightAtPos, skyLightAtPos);
}
else
{
// the adj data intersects the lower part of the input data, don't render below the intersection
if (adjYMax > yMin && debugOverlapColor != 0)
{
builder.addQuadAdj(direction, x, yMin, z, horizontalWidth, (short) (adjYMax - yMin), debugOverlapColor, irisBlockMaterialId, LodUtil.MAX_MC_LIGHT, LodUtil.MAX_MC_LIGHT);
}
// if this is the only face, use the yMax and break,
// if there was another face finish the last one and then break
if (firstFace)
{
builder.addQuadAdj(direction, x, adjYMax, z, horizontalWidth, (short) (yMax - adjYMax), color, irisBlockMaterialId,
RenderDataPointUtil.getLightSky(adjPoint), blockLight);
}
else
{
// Now: depth <= y <= height <= previousAdjDepth < yMax
if (previousAdjDepth == -1)
{
// TODO why is this an error?
throw new RuntimeException("Loop error");
}
if (previousAdjDepth > adjYMax)
{
builder.addQuadAdj(direction, x, adjYMax, z, horizontalWidth, (short) (previousAdjDepth - adjYMax), color, irisBlockMaterialId,
RenderDataPointUtil.getLightSky(adjPoint), blockLight);
}
previousAdjDepth = -1;
}
}
// we don't need to check any other adjacent LODs
// since this one completely covers the input
break;
}
// In here always true: y < adjYMin < yMax
// _________________&&: y < ________ (height and yMax)
if (adjYMax >= yMax)
{
// Basically: y _______ < yMax <= height
// _______&&: y < depth < yMax
// the adj data intersects the higher part of the current data
if (debugOverlapColor != 0)
{
builder.addQuadAdj(direction, x, adjYMin, z, horizontalWidth, (short) (yMax - adjYMin), debugOverlapColor, irisBlockMaterialId, LodUtil.MAX_MC_LIGHT, LodUtil.MAX_MC_LIGHT);
}
// we start the creation of a new face
}
else
{
// Otherwise: y < _____ height < yMax
// _______&&: y < depth ______ < yMax
if (debugOverlapColor != 0)
// adjacent is transparent,
// use datapoint below adjacent for lighting
byte belowSkyLight = RenderDataPointUtil.getLightSky(adjBelowPoint);
for (int i = adjMinY; i < adjMaxY; i++)
{
builder.addQuadAdj(direction, x, adjYMin, z, horizontalWidth, (short) (adjYMax - adjYMin), debugOverlapColor, irisBlockMaterialId, LodUtil.MAX_MC_LIGHT, LodUtil.MAX_MC_LIGHT);
}
if (firstFace)
{
builder.addQuadAdj(direction, x, adjYMax, z, horizontalWidth, (short) (yMax - adjYMax), color, irisBlockMaterialId,
RenderDataPointUtil.getLightSky(adjPoint), blockLight);
}
else
{
// Now: y < depth < height <= previousAdjDepth < yMax
if (previousAdjDepth == -1)
throw new RuntimeException("Loop error");
if (previousAdjDepth > adjYMax)
{
if (irisBlockMaterialId == EDhApiBlockMaterial.GRASS.index)
{
// this LOD is underneath another, grass will never show here
irisBlockMaterialId = EDhApiBlockMaterial.DIRT.index;
}
builder.addQuadAdj(direction, x, adjYMax, z, horizontalWidth, (short) (previousAdjDepth - adjYMax), color, irisBlockMaterialId,
RenderDataPointUtil.getLightSky(adjPoint), blockLight);
}
previousAdjDepth = -1;
byte skyLightAtPos = skyLightAtInputPos[i];
skyLightAtInputPos[i] = (byte) Math.min(belowSkyLight, skyLightAtPos);
}
}
// set next top as current depth
previousAdjDepth = adjYMin;
firstFace = false;
nextTopSkyLight = skyLightTop;
if (adjIndex + 1 < adjColumnView.size() && RenderDataPointUtil.doesDataPointExist(adjColumnView.get(adjIndex + 1)))
// fill in sky light up to the next DP,
// this is done to handle overhangs
byte adjSkyLight = RenderDataPointUtil.getLightSky(adjPoint);
int adjAboveMinY = RenderDataPointUtil.getYMin(adjAbovePoint);
for (int i = adjMaxY; i < adjAboveMinY; i++)
{
nextTopSkyLight = RenderDataPointUtil.getLightSky(adjColumnView.get(adjIndex + 1));
byte skyLightAtPos = skyLightAtInputPos[i];
skyLightAtInputPos[i] = (byte) Math.min(adjSkyLight, skyLightAtPos);
}
}
//=======================//
// create vertical faces //
//=======================//
boolean inputTransparent = ColorUtil.getAlpha(color) < 255 && LodRenderer.transparencyEnabled;
byte lastSkyLight = skyLightAtInputPos[yMin];
int quadBottomY = yMin;
int quadTopY = -1;
// walk up the sky lights and create a new face
// whenever the light changes to different valid value
for (int i = yMin; i < yMax; i++)
{
byte skyLight = skyLightAtInputPos[i];
if (skyLight != lastSkyLight)
{
// the sky light changed, create the in-progress face
tryAddVerticalFaceWithSkyLightToBuilder(
builder, direction,
x, z, horizontalWidth,
color, irisBlockMaterialId, blockLight,
lastSkyLight, inputTransparent, quadTopY, quadBottomY
);
lastSkyLight = skyLight;
quadBottomY = i;
}
lastAdjWasTransparent = adjTransparent;
quadTopY = (i + 1);
}
// add the in-progress face if present
if (quadTopY != -1)
{
tryAddVerticalFaceWithSkyLightToBuilder(
builder, direction,
x, z, horizontalWidth,
color, irisBlockMaterialId, blockLight,
lastSkyLight, inputTransparent, quadTopY, quadBottomY
);
}
}
if (inputAboveAdjLods)
finally
{
// the input LOD is above all adjacent LODs and won't be affected
// by them, add the vertical quad using the input's lighting and height
builder.addQuadAdj(direction, x, yMin, z, horizontalWidth, ySize, color, irisBlockMaterialId, skyLightTop, blockLight);
// clean up the array before the next thread uses it
// (may be unnecessary since we only work between the yMin-yMax anyway, but is helpful for debugging)
Arrays.fill(skyLightAtInputPos, yMin, yMax, SKYLIGHT_EMPTY);
}
else if (previousAdjDepth != -1)
}
private static void tryAddVerticalFaceWithSkyLightToBuilder(
LodQuadBuilder builder, EDhDirection direction,
short x, short z, short horizontalWidth,
int color, byte irisBlockMaterialId, byte blockLight,
byte lastSkyLight, boolean inputTransparent, int quadTopY, int quadBottomY
)
{
// invalid positions will have a negative skylight
if (lastSkyLight >= 0)
{
// We need to finish the last quad.
builder.addQuadAdj(direction, x, yMin, z, horizontalWidth, (short) (previousAdjDepth - yMin), color, irisBlockMaterialId, nextTopSkyLight, blockLight);
// Don't add transparent vertical faces
// unless the adjacent position is empty.
// This is done to prevent walls between water blocks in the ocean.
if (!inputTransparent
|| (lastSkyLight == LodUtil.MAX_MC_LIGHT))
{
// don't add negative/empty height faces
short height = (short) (quadTopY - quadBottomY);
if (height > 0)
{
builder.addQuadAdj(direction, x, (short) quadBottomY, z, horizontalWidth, height, color, irisBlockMaterialId, lastSkyLight, blockLight);
}
}
}
}
}
@@ -19,23 +19,23 @@
package com.seibel.distanthorizons.core.dataObjects.render.bufferBuilding;
import com.seibel.distanthorizons.api.DhApi;
import com.seibel.distanthorizons.api.methods.events.sharedParameterObjects.DhApiRenderParam;
import com.seibel.distanthorizons.core.config.Config;
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.pos.DhBlockPos;
import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos;
import com.seibel.distanthorizons.core.render.glObject.GLProxy;
import com.seibel.distanthorizons.core.render.glObject.buffer.GLVertexBuffer;
import com.seibel.distanthorizons.core.render.renderer.LodRenderer;
import com.seibel.distanthorizons.core.util.LodUtil;
import com.seibel.distanthorizons.core.util.objects.StatsMap;
import com.seibel.distanthorizons.api.enums.config.EDhApiGpuUploadMethod;
import com.seibel.distanthorizons.core.util.*;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper;
import org.apache.logging.log4j.Logger;
import org.lwjgl.system.MemoryUtil;
import java.nio.ByteBuffer;
import java.util.Iterator;
import java.util.ArrayList;
import java.util.concurrent.*;
/**
@@ -91,7 +91,7 @@ public class ColumnRenderBuffer implements AutoCloseable
/** Should be run on a DH thread. */
public void uploadBuffer(LodQuadBuilder builder, EDhApiGpuUploadMethod gpuUploadMethod) throws InterruptedException
{
LodUtil.assertTrue(Thread.currentThread().getName().startsWith(ThreadUtil.THREAD_NAME_PREFIX), "Buffer uploading needs to be done on a DH thread to prevent locking up any MC threads.");
LodUtil.assertTrue(DhApi.isDhThread(), "Buffer uploading needs to be done on a DH thread to prevent locking up any MC threads.");
// upload on MC's render thread
@@ -100,7 +100,7 @@ public class ColumnRenderBuffer implements AutoCloseable
{
try
{
this.uploadBuffersUsingUploadMethod(builder, gpuUploadMethod);
this.uploadBuffers(builder, gpuUploadMethod);
uploadFuture.complete(null);
}
catch (InterruptedException e)
@@ -126,72 +126,46 @@ public class ColumnRenderBuffer implements AutoCloseable
//LOGGER.warn("Error uploading builder ["+builder+"] synchronously. Error: "+e.getMessage(), e);
}
}
private void uploadBuffersUsingUploadMethod(LodQuadBuilder builder, EDhApiGpuUploadMethod gpuUploadMethod) throws InterruptedException
private void uploadBuffers(LodQuadBuilder builder, EDhApiGpuUploadMethod method) throws InterruptedException
{
if (gpuUploadMethod.useEarlyMapping)
{
this.uploadBuffersMapped(builder, gpuUploadMethod);
}
else
{
this.uploadBuffersDirect(builder, gpuUploadMethod);
}
// uploading mapped buffers used to be done here,
// however due to a memory leak and complication with the previous code,
// now we only allow direct uploading.
// (There's also insufficient data to state whether mapped buffers are necessary
// for DH to upload without stuttering the main thread)
this.vbos = makeAndUploadBuffers(builder, method, this.vbos, builder.makeOpaqueVertexBuffers());
this.vbosTransparent = makeAndUploadBuffers(builder, method, this.vbosTransparent, builder.makeTransparentVertexBuffers());
this.buffersUploaded = true;
}
private void uploadBuffersMapped(LodQuadBuilder builder, EDhApiGpuUploadMethod method)
/** This resizes and returns the vbo array if necessary based on the amount of data needed for this area. */
private static GLVertexBuffer[] makeAndUploadBuffers(LodQuadBuilder builder, EDhApiGpuUploadMethod method, GLVertexBuffer[] vbos, ArrayList<ByteBuffer> buffers) throws InterruptedException
{
// opaque vbos //
this.vbos = ColumnRenderBufferBuilder.resizeBuffer(this.vbos, builder.getCurrentNeededOpaqueVertexBufferCount());
for (int i = 0; i < this.vbos.length; i++)
try
{
if (this.vbos[i] == null)
vbos = resizeBuffer(vbos, buffers.size());
uploadBuffersDirect(vbos, buffers, method);
}
finally
{
// all the buffers must be manually freed to prevent memory leaks
if (buffers != null)
{
this.vbos[i] = new GLVertexBuffer(method.useBufferStorage);
for (ByteBuffer buffer : buffers)
{
MemoryUtil.memFree(buffer);
}
}
}
LodQuadBuilder.BufferFiller func = builder.makeOpaqueBufferFiller(method);
for (GLVertexBuffer vbo : this.vbos)
{
func.fill(vbo);
}
// transparent vbos //
this.vbosTransparent = ColumnRenderBufferBuilder.resizeBuffer(this.vbosTransparent, builder.getCurrentNeededTransparentVertexBufferCount());
for (int i = 0; i < this.vbosTransparent.length; i++)
{
if (this.vbosTransparent[i] == null)
{
this.vbosTransparent[i] = new GLVertexBuffer(method.useBufferStorage);
}
}
LodQuadBuilder.BufferFiller transparentFillerFunc = builder.makeTransparentBufferFiller(method);
for (GLVertexBuffer vbo : this.vbosTransparent)
{
transparentFillerFunc.fill(vbo);
}
// return the array in case it was resized
return vbos;
}
private void uploadBuffersDirect(LodQuadBuilder builder, EDhApiGpuUploadMethod method) throws InterruptedException
private static void uploadBuffersDirect(GLVertexBuffer[] vbos, ArrayList<ByteBuffer> byteBuffers, EDhApiGpuUploadMethod method) throws InterruptedException
{
this.vbos = ColumnRenderBufferBuilder.resizeBuffer(this.vbos, builder.getCurrentNeededOpaqueVertexBufferCount());
uploadBuffersDirect(this.vbos, builder.makeOpaqueVertexBuffers(), method);
this.vbosTransparent = ColumnRenderBufferBuilder.resizeBuffer(this.vbosTransparent, builder.getCurrentNeededTransparentVertexBufferCount());
uploadBuffersDirect(this.vbosTransparent, builder.makeTransparentVertexBuffers(), method);
}
private static void uploadBuffersDirect(GLVertexBuffer[] vbos, Iterator<ByteBuffer> iter, EDhApiGpuUploadMethod method) throws InterruptedException
{
long remainingMS = 0;
long MBPerMS = Config.Client.Advanced.GpuBuffers.gpuUploadPerMegabyteInMilliseconds.get();
int vboIndex = 0;
while (iter.hasNext())
for (int i = 0; i < byteBuffers.size(); i++)
{
if (vboIndex >= vbos.length)
{
@@ -207,13 +181,13 @@ public class ColumnRenderBuffer implements AutoCloseable
GLVertexBuffer vbo = vbos[vboIndex];
ByteBuffer bb = iter.next();
int size = bb.limit() - bb.position();
ByteBuffer buffer = byteBuffers.get(i);
int size = buffer.limit() - buffer.position();
try
{
vbo.bind();
vbo.uploadBuffer(bb, size / LodUtil.LOD_VERTEX_FORMAT.getByteSize(), method, FULL_SIZED_BUFFER);
vbo.uploadBuffer(buffer, size / LodUtil.LOD_VERTEX_FORMAT.getByteSize(), method, FULL_SIZED_BUFFER);
}
catch (Exception e)
{
@@ -222,24 +196,6 @@ public class ColumnRenderBuffer implements AutoCloseable
LOGGER.error("Failed to upload buffer: ", e);
}
if (MBPerMS > 0)
{
// upload buffers over an extended period of time
// to hopefully prevent stuttering.
remainingMS += size * MBPerMS;
if (remainingMS >= TimeUnit.NANOSECONDS.convert(1000 / 60, TimeUnit.MILLISECONDS))
{
if (remainingMS > MAX_BUFFER_UPLOAD_TIMEOUT_NANOSECONDS)
{
remainingMS = MAX_BUFFER_UPLOAD_TIMEOUT_NANOSECONDS;
}
Thread.sleep(remainingMS / 1000000, (int) (remainingMS % 1000000));
remainingMS = 0;
}
}
vboIndex++;
}
@@ -318,9 +274,9 @@ public class ColumnRenderBuffer implements AutoCloseable
//==============//
// misc methods //
//==============//
//================//
// helper methods //
//================//
/** can be used when debugging */
public boolean hasNonNullVbos() { return this.vbos != null || this.vbosTransparent != null; }
@@ -366,6 +322,35 @@ public class ColumnRenderBuffer implements AutoCloseable
}
}
public static GLVertexBuffer[] resizeBuffer(GLVertexBuffer[] vbos, int newSize)
{
if (vbos.length == newSize)
{
return vbos;
}
GLVertexBuffer[] newVbos = new GLVertexBuffer[newSize];
System.arraycopy(vbos, 0, newVbos, 0, Math.min(vbos.length, newSize));
if (newSize < vbos.length)
{
for (int i = newSize; i < vbos.length; i++)
{
if (vbos[i] != null)
{
vbos[i].close();
}
}
}
return newVbos;
}
//================//
// base overrides //
//================//
/**
* This method is called when object is no longer in use.
* Called either after uploadBuffers() returned false (On buffer Upload
@@ -19,6 +19,7 @@
package com.seibel.distanthorizons.core.dataObjects.render.bufferBuilding;
import com.seibel.distanthorizons.api.enums.rendering.EDhApiBlockMaterial;
import com.seibel.distanthorizons.api.enums.rendering.EDhApiDebugRendering;
import com.seibel.distanthorizons.core.enums.EDhDirection;
import com.seibel.distanthorizons.core.config.Config;
@@ -26,15 +27,16 @@ import com.seibel.distanthorizons.core.dataObjects.render.ColumnRenderSource;
import com.seibel.distanthorizons.core.level.IDhClientLevel;
import com.seibel.distanthorizons.core.logging.ConfigBasedLogger;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.pos.DhBlockPos;
import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos;
import com.seibel.distanthorizons.core.pos.DhSectionPos;
import com.seibel.distanthorizons.core.render.glObject.GLProxy;
import com.seibel.distanthorizons.core.render.glObject.buffer.GLVertexBuffer;
import com.seibel.distanthorizons.core.util.ColorUtil;
import com.seibel.distanthorizons.core.util.LodUtil;
import com.seibel.distanthorizons.core.util.RenderDataPointUtil;
import com.seibel.distanthorizons.core.util.objects.UncheckedInterruptedException;
import com.seibel.distanthorizons.core.dataObjects.render.columnViews.ColumnArrayView;
import com.seibel.distanthorizons.core.util.threading.ThreadPoolUtil;
import com.seibel.distanthorizons.coreapi.util.BitShiftUtil;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
@@ -59,21 +61,21 @@ public class ColumnRenderBufferBuilder
// vbo building //
//==============//
public static CompletableFuture<ColumnRenderBuffer> buildAndUploadBuffersAsync(
public static CompletableFuture<LodQuadBuilder> buildBuffersAsync(
IDhClientLevel clientLevel,
ColumnRenderSource renderSource, ColumnRenderSource[] adjData)
ColumnRenderSource renderSource, ColumnRenderSource[] adjData, boolean[] isSameDetailLevel
)
{
ThreadPoolExecutor bufferBuilderExecutor = ThreadPoolUtil.getBufferBuilderExecutor();
ThreadPoolExecutor bufferUploaderExecutor = ThreadPoolUtil.getBufferUploaderExecutor();
if ((bufferBuilderExecutor == null || bufferBuilderExecutor.isTerminated()) ||
(bufferUploaderExecutor == null || bufferUploaderExecutor.isTerminated()))
if (bufferBuilderExecutor == null || bufferBuilderExecutor.isTerminated())
{
// one or more of the thread pools has been shut down
CompletableFuture<ColumnRenderBuffer> future = new CompletableFuture<>();
CompletableFuture<LodQuadBuilder> future = new CompletableFuture<>();
future.cancel(true);
return future;
}
try
{
return CompletableFuture.supplyAsync(() ->
@@ -81,16 +83,8 @@ public class ColumnRenderBufferBuilder
try
{
boolean enableTransparency = Config.Client.Advanced.Graphics.Quality.transparency.get().transparencyEnabled;
long builderStartTime = System.currentTimeMillis();
LodQuadBuilder builder = new LodQuadBuilder(enableTransparency, clientLevel.getClientLevelWrapper());
makeLodRenderData(builder, renderSource, adjData);
long builderEndTime = System.currentTimeMillis();
long buildMs = builderEndTime - builderStartTime;
//LOGGER.debug("RenderRegion end QuadBuild @ " + renderSource.pos + " took: " + buildMs);
makeLodRenderData(builder, renderSource, clientLevel, adjData, isSameDetailLevel);
return builder;
}
catch (UncheckedInterruptedException e)
@@ -99,11 +93,42 @@ public class ColumnRenderBufferBuilder
}
catch (Throwable e3)
{
LOGGER.error("\"LodNodeBufferBuilder\" was unable to build quads: ", e3);
LOGGER.error("LodNodeBufferBuilder was unable to build quads for pos ["+DhSectionPos.toString(renderSource.pos)+"], error: ["+ e3.getMessage()+"].", e3);
throw e3;
}
}, bufferBuilderExecutor)
.thenApplyAsync((quadBuilder) ->
}, bufferBuilderExecutor);
}
catch (RejectedExecutionException ignore)
{
// the thread pool was probably shut down because it's size is being changed, just wait a sec and it should be back
CompletableFuture<LodQuadBuilder> future = new CompletableFuture<>();
future.cancel(true);
return future;
}
}
/** @link adjData should be null for adjacent sections that cross detail level boundaries */
public static CompletableFuture<ColumnRenderBuffer> uploadBuffersAsync(
IDhClientLevel clientLevel,
ColumnRenderSource renderSource,
LodQuadBuilder quadBuilder
)
{
// TODO put into a single future/thread so it can be easily canceled
ThreadPoolExecutor bufferUploaderExecutor = ThreadPoolUtil.getBufferUploaderExecutor();
if (bufferUploaderExecutor == null || bufferUploaderExecutor.isTerminated())
{
// one or more of the thread pools has been shut down
CompletableFuture<ColumnRenderBuffer> future = new CompletableFuture<>();
future.cancel(true);
return future;
}
try
{
return CompletableFuture.supplyAsync(() ->
{
try
{
@@ -111,8 +136,15 @@ public class ColumnRenderBufferBuilder
try
{
buffer.uploadBuffer(quadBuilder, GLProxy.getInstance().getGpuUploadMethod());
LodUtil.assertTrue(buffer.buffersUploaded);
return buffer;
if (buffer.buffersUploaded)
{
return buffer;
}
else
{
buffer.close();
return null;
}
}
catch (Exception e)
{
@@ -126,35 +158,38 @@ public class ColumnRenderBufferBuilder
}
catch (Throwable e3)
{
LOGGER.error("LodNodeBufferBuilder was unable to upload buffer: " + e3.getMessage(), e3);
LOGGER.error("LodNodeBufferBuilder was unable to upload buffer for pos ["+DhSectionPos.toString(renderSource.pos)+"], error: [" + e3.getMessage() + "].", e3);
throw e3;
}
}, bufferUploaderExecutor);
}
catch (RejectedExecutionException ignore)
{
// the thread pool was probably shut down because it's size is being changed, just wait a sec and it should be back
// shouldn't happen, but just in case
CompletableFuture<ColumnRenderBuffer> future = new CompletableFuture<>();
future.cancel(true);
return future;
}
}
private static void makeLodRenderData(LodQuadBuilder quadBuilder, ColumnRenderSource renderSource, ColumnRenderSource[] adjRegions)
private static void makeLodRenderData(
LodQuadBuilder quadBuilder, ColumnRenderSource renderSource, IDhClientLevel clientLevel,
ColumnRenderSource[] adjRegions, boolean[] isSameDetailLevel)
{
// Variable initialization
EDhApiDebugRendering debugMode = Config.Client.Advanced.Debugging.debugRendering.get();
//=============//
// debug check //
//=============//
// can be used to limit which section positions are build and thus, rendered
// useful when debugging a specific section
boolean enableColumnBufferLimit = Config.Client.Advanced.Debugging.columnBuilderDebugEnable.get();
if (enableColumnBufferLimit)
boolean columnBuilderDebugEnabled = Config.Client.Advanced.Debugging.columnBuilderDebugEnable.get();
if (columnBuilderDebugEnabled)
{
if (DhSectionPos.getDetailLevel(renderSource.pos) == Config.Client.Advanced.Debugging.columnBuilderDebugDetailLevel.get()
&& DhSectionPos.getX(renderSource.pos) == Config.Client.Advanced.Debugging.columnBuilderDebugXPos.get()
&& DhSectionPos.getZ(renderSource.pos) == Config.Client.Advanced.Debugging.columnBuilderDebugZPos.get())
{
int test = 0;
int breakpoint = 0;
}
else
{
@@ -162,24 +197,22 @@ public class ColumnRenderBufferBuilder
}
}
byte detailLevel = renderSource.getDataDetailLevel();
for (int x = 0; x < ColumnRenderSource.SECTION_SIZE; x++)
//===================//
// build each column //
//===================//
byte thisDetailLevel = renderSource.getDataDetailLevel();
for (int relX = 0; relX < ColumnRenderSource.SECTION_SIZE; relX++)
{
for (int z = 0; z < ColumnRenderSource.SECTION_SIZE; z++)
for (int relZ = 0; relZ < ColumnRenderSource.SECTION_SIZE; relZ++)
{
// TODO make a config for this
// can be uncommented to limit the buffer building to a specific
// relative position in this section.
// useful for debugging a single column's rendering
// if (x != 0 || (z != 0 && z != 1))
// {
// continue;
// }
// stop the builder if requested
UncheckedInterruptedException.throwIfInterrupted();
ColumnArrayView columnRenderData = renderSource.getVerticalDataPointView(x, z);
// ignore empty/null columns
ColumnArrayView columnRenderData = renderSource.getVerticalDataPointView(relX, relZ);
if (columnRenderData.size() == 0
|| !RenderDataPointUtil.doesDataPointExist(columnRenderData.get(0))
|| RenderDataPointUtil.isVoid(columnRenderData.get(0)))
@@ -187,43 +220,66 @@ public class ColumnRenderBufferBuilder
continue;
}
ColumnRenderSource.DebugSourceFlag debugSourceFlag = renderSource.debugGetFlag(x, z);
ColumnArrayView[][] adjColumnViews = new ColumnArrayView[4][];
// We extract the adj data in the four cardinal direction
// we first reset the adjShadeDisabled. This is used to disable the shade on the
// border when we have transparent block like water or glass
// to avoid having a "darker border" underground
// Arrays.fill(adjShadeDisabled, false);
// We check every adj block in each direction
//=============//
// debug limit //
//=============//
// If the adj block is rendered in the same region and with same detail
// and is positioned in a place that is not going to be rendered by vanilla game
// then we can set this position as adj
// We avoid cases where the adjPosition is in player chunk while the position is
// not
// to always have a wall underwater
// can be used to limit the buffer building to a specific relative position.
// useful for debugging a single column
if (columnBuilderDebugEnabled)
{
int wantedX = Config.Client.Advanced.Debugging.columnBuilderDebugXRow.get();
if (wantedX >= 0 && relX != wantedX)
{
continue;
}
int wantedZ = Config.Client.Advanced.Debugging.columnBuilderDebugZRow.get();
if (wantedZ >= 0 && relZ != wantedZ)
{
continue;
}
}
//==================================//
// get adjacent render data columns //
//==================================//
ColumnArrayView[] adjColumnViews = new ColumnArrayView[EDhDirection.ADJ_DIRECTIONS.length];
for (EDhDirection lodDirection : EDhDirection.ADJ_DIRECTIONS)
{
try
{
int xAdj = x + lodDirection.getNormal().x;
int zAdj = z + lodDirection.getNormal().z;
boolean isCrossRegionBoundary =
int xAdj = relX + lodDirection.getNormal().x;
int zAdj = relZ + lodDirection.getNormal().z;
boolean isCrossRenderSourceBoundary =
(xAdj < 0 || xAdj >= ColumnRenderSource.SECTION_SIZE) ||
(zAdj < 0 || zAdj >= ColumnRenderSource.SECTION_SIZE);
(zAdj < 0 || zAdj >= ColumnRenderSource.SECTION_SIZE);
ColumnRenderSource adjRenderSource;
byte adjDetailLevel;
//we check if the detail of the adjPos is equal to the correct one (region border fix)
//or if the detail is wrong by 1 value (region+circle border fix)
if (isCrossRegionBoundary)
//=========================//
// get the adjacent render //
// source if present //
//=========================//
if (!isCrossRenderSourceBoundary)
{
//we compute at which detail that position should be rendered
// the adjacent position is inside this same render source
adjRenderSource = renderSource;
adjDetailLevel = thisDetailLevel;
}
else
{
// the adjacent position is outside this render source
// skip empty sections
adjRenderSource = adjRegions[lodDirection.ordinal() - 2];
if (adjRenderSource == null)
{
@@ -231,67 +287,70 @@ public class ColumnRenderBufferBuilder
}
adjDetailLevel = adjRenderSource.getDataDetailLevel();
if (adjDetailLevel != detailLevel)
{
//TODO: Implement this
}
else
if (adjDetailLevel == thisDetailLevel)
{
// if the adjacent position is outside this render source,
// wrap the position around so it's inside the adjacent source
if (xAdj < 0)
{
xAdj += ColumnRenderSource.SECTION_SIZE;
}
if (xAdj >= ColumnRenderSource.SECTION_SIZE)
{
xAdj -= ColumnRenderSource.SECTION_SIZE;
}
if (zAdj < 0)
{
zAdj += ColumnRenderSource.SECTION_SIZE;
if (xAdj >= ColumnRenderSource.SECTION_SIZE)
xAdj -= ColumnRenderSource.SECTION_SIZE;
}
if (zAdj >= ColumnRenderSource.SECTION_SIZE)
{
zAdj -= ColumnRenderSource.SECTION_SIZE;
}
}
}
else
{
adjRenderSource = renderSource;
adjDetailLevel = detailLevel;
}
if (adjDetailLevel < detailLevel - 1 || adjDetailLevel > detailLevel + 1)
{
continue;
}
if (adjDetailLevel == detailLevel || adjDetailLevel > detailLevel)
{
adjColumnViews[lodDirection.ordinal() - 2] = new ColumnArrayView[1];
adjColumnViews[lodDirection.ordinal() - 2][0] = adjRenderSource.getVerticalDataPointView(xAdj, zAdj);
}
else
{
adjColumnViews[lodDirection.ordinal() - 2] = new ColumnArrayView[2];
adjColumnViews[lodDirection.ordinal() - 2][0] = adjRenderSource.getVerticalDataPointView(xAdj, zAdj);
adjColumnViews[lodDirection.ordinal() - 2][1] = adjRenderSource.getVerticalDataPointView(
xAdj + (lodDirection.getAxis() == EDhDirection.Axis.X ? 0 : 1),
zAdj + (lodDirection.getAxis() == EDhDirection.Axis.Z ? 0 : 1));
}
//========================//
// get the adjacent views //
//========================//
// the old logic handled additional cases, but they never appeared to fire,
// so just these two cases should be fine
LodUtil.assertTrue(adjDetailLevel == thisDetailLevel || adjDetailLevel > thisDetailLevel);
adjColumnViews[lodDirection.ordinal() - 2] = adjRenderSource.getVerticalDataPointView(xAdj, zAdj);
}
catch (RuntimeException e)
{
EVENT_LOGGER.warn("Failed to get adj data for [" + detailLevel + ":" + x + "," + z + "] at [" + lodDirection + "], Error: "+e.getMessage(), e);
EVENT_LOGGER.warn("Failed to get adj data for relative pos: [" + thisDetailLevel + ":" + relX + "," + relZ + "] at [" + lodDirection + "], Error: "+e.getMessage(), e);
}
} // for adjacent directions
//==========================//
// build this render column //
//==========================//
ColumnRenderSource.DebugSourceFlag debugSourceFlag = renderSource.debugGetFlag(relX, relZ);
// We render every vertical lod present in this position
// We only stop when we find a block that is void or non-existing block
for (int i = 0; i < columnRenderData.size(); i++)
{
// TODO make a config for this
// can be uncommented to limit which vertical LOD is generated
// if (i != 0)
// {
// continue;
// }
if (Config.Client.Advanced.Debugging.columnBuilderDebugEnable.get())
{
int wantedColumnIndex = Config.Client.Advanced.Debugging.columnBuilderDebugColumnIndex.get();
if (wantedColumnIndex >= 0 && i != wantedColumnIndex)
{
continue;
}
}
long data = columnRenderData.get(i);
// If the data is not render-able (Void or non-existing) we stop since there is
@@ -304,8 +363,12 @@ public class ColumnRenderBufferBuilder
long topDataPoint = (i - 1) >= 0 ? columnRenderData.get(i - 1) : RenderDataPointUtil.EMPTY_DATA;
long bottomDataPoint = (i + 1) < columnRenderData.size() ? columnRenderData.get(i + 1) : RenderDataPointUtil.EMPTY_DATA;
CubicLodTemplate.addLodToBuffer(data, topDataPoint, bottomDataPoint, adjColumnViews, detailLevel,
x, z, quadBuilder, debugMode, debugSourceFlag);
addLodToBuffer(
clientLevel,
data, topDataPoint, bottomDataPoint,
adjColumnViews, isSameDetailLevel,
thisDetailLevel, relX, relZ,
quadBuilder, debugSourceFlag);
}
}// for z
@@ -313,33 +376,147 @@ public class ColumnRenderBufferBuilder
quadBuilder.finalizeData();
}
//=================//
// vbo interaction //
//=================//
public static GLVertexBuffer[] resizeBuffer(GLVertexBuffer[] vbos, int newSize)
private static void addLodToBuffer(
IDhClientLevel clientLevel,
long data, long topData, long bottomData,
ColumnArrayView[] adjColumnViews, boolean[] isSameDetailLevel,
byte detailLevel, int renderSourceOffsetPosX, int renderSourceOffsetPosZ,
LodQuadBuilder quadBuilder, ColumnRenderSource.DebugSourceFlag debugSource)
{
if (vbos.length == newSize)
long sectionPos = DhSectionPos.encode(detailLevel, renderSourceOffsetPosX, renderSourceOffsetPosZ);
short width = (short) BitShiftUtil.powerOfTwo(detailLevel);
short x = (short) DhSectionPos.getMinCornerBlockX(sectionPos);
short yMin = RenderDataPointUtil.getYMin(data);
short z = (short) DhSectionPos.getMinCornerBlockZ(sectionPos);
short ySize = (short) (RenderDataPointUtil.getYMax(data) - yMin);
if (ySize == 0)
{
return vbos;
return;
}
else if (ySize < 0)
{
throw new IllegalArgumentException("Negative y size for the data! Data: [" + RenderDataPointUtil.toString(data) + "].");
}
GLVertexBuffer[] newVbos = new GLVertexBuffer[newSize];
System.arraycopy(vbos, 0, newVbos, 0, Math.min(vbos.length, newSize));
if (newSize < vbos.length)
byte blockMaterialId = RenderDataPointUtil.getBlockMaterialId(data);
int color;
boolean fullBright = false;
EDhApiDebugRendering debugging = Config.Client.Advanced.Debugging.debugRendering.get();
switch (debugging)
{
for (int i = newSize; i < vbos.length; i++)
case OFF:
{
if (vbos[i] != null)
float saturationMultiplier = Config.Client.Advanced.Graphics.AdvancedGraphics.saturationMultiplier.get().floatValue();
float brightnessMultiplier = Config.Client.Advanced.Graphics.AdvancedGraphics.brightnessMultiplier.get().floatValue();
if (saturationMultiplier == 1.0 && brightnessMultiplier == 1.0)
{
vbos[i].close();
color = RenderDataPointUtil.getColor(data);
}
else
{
float[] ahsv = ColorUtil.argbToAhsv(RenderDataPointUtil.getColor(data));
color = ColorUtil.ahsvToArgb(ahsv[0], ahsv[1], ahsv[2] * saturationMultiplier, ahsv[3] * brightnessMultiplier);
}
break;
}
case SHOW_DETAIL:
{
color = LodUtil.DEBUG_DETAIL_LEVEL_COLORS[detailLevel];
fullBright = true;
break;
}
case SHOW_BLOCK_MATERIAL:
{
switch (EDhApiBlockMaterial.getFromIndex(blockMaterialId))
{
case UNKNOWN:
case AIR: // shouldn't normally be rendered, but just in case
color = ColorUtil.HOT_PINK;
break;
case LEAVES:
color = ColorUtil.DARK_GREEN;
break;
case STONE:
color = ColorUtil.GRAY;
break;
case WOOD:
color = ColorUtil.BROWN;
break;
case METAL:
color = ColorUtil.DARK_GRAY;
break;
case DIRT:
color = ColorUtil.LIGHT_BROWN;
break;
case LAVA:
color = ColorUtil.ORANGE;
break;
case DEEPSLATE:
color = ColorUtil.BLACK;
break;
case SNOW:
color = ColorUtil.WHITE;
break;
case SAND:
color = ColorUtil.TAN;
break;
case TERRACOTTA:
color = ColorUtil.DARK_ORANGE;
break;
case NETHER_STONE:
color = ColorUtil.DARK_RED;
break;
case WATER:
color = ColorUtil.BLUE;
break;
case GRASS:
color = ColorUtil.GREEN;
break;
case ILLUMINATED:
color = ColorUtil.YELLOW;
break;
default:
// undefined color
color = ColorUtil.CYAN;
break;
}
fullBright = true;
break;
}
case SHOW_OVERLAPPING_QUADS:
{
color = ColorUtil.WHITE;
fullBright = true;
break;
}
case SHOW_RENDER_SOURCE_FLAG:
{
color = debugSource == null ? ColorUtil.RED : debugSource.color;
fullBright = true;
break;
}
default:
throw new IllegalArgumentException("Unknown debug mode: " + debugging);
}
return newVbos;
ColumnBox.addBoxQuadsToBuilder(
quadBuilder, clientLevel,
width, ySize, width,
x, yMin, z,
color,
blockMaterialId,
RenderDataPointUtil.getLightSky(data),
fullBright ? 15 : RenderDataPointUtil.getLightBlock(data),
topData, bottomData, adjColumnViews, isSameDetailLevel);
}
}
@@ -1,186 +0,0 @@
/*
* This file is part of the Distant Horizons mod
* licensed under the GNU LGPL v3 License.
*
* Copyright (C) 2020-2023 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.distanthorizons.core.dataObjects.render.bufferBuilding;
import com.seibel.distanthorizons.api.enums.rendering.EDhApiBlockMaterial;
import com.seibel.distanthorizons.core.config.Config;
import com.seibel.distanthorizons.core.dataObjects.render.ColumnRenderSource;
import com.seibel.distanthorizons.core.pos.DhLodPos;
import com.seibel.distanthorizons.core.util.ColorUtil;
import com.seibel.distanthorizons.core.util.LodUtil;
import com.seibel.distanthorizons.core.util.RenderDataPointUtil;
import com.seibel.distanthorizons.api.enums.rendering.EDhApiDebugRendering;
import com.seibel.distanthorizons.core.dataObjects.render.columnViews.ColumnArrayView;
import com.seibel.distanthorizons.core.wrapperInterfaces.block.IBlockStateWrapper;
import com.seibel.distanthorizons.coreapi.util.BitShiftUtil;
/**
* Builds LODs as rectangular prisms.
*
* @author James Seibel
* @version 2022-1-2
*/
public class CubicLodTemplate
{
public static void addLodToBuffer(
long data, long topData, long bottomData, ColumnArrayView[][] adjColumnViews,
byte detailLevel, int offsetPosX, int offsetOosZ, LodQuadBuilder quadBuilder,
EDhApiDebugRendering debugging, ColumnRenderSource.DebugSourceFlag debugSource)
{
DhLodPos blockOffsetPos = new DhLodPos(detailLevel, offsetPosX, offsetOosZ).convertToDetailLevel(LodUtil.BLOCK_DETAIL_LEVEL);
short width = (short) BitShiftUtil.powerOfTwo(detailLevel);
short x = (short) blockOffsetPos.x;
short yMin = RenderDataPointUtil.getYMin(data);
short z = (short) (short) blockOffsetPos.z;
short ySize = (short) (RenderDataPointUtil.getYMax(data) - yMin);
if (ySize == 0)
{
return;
}
else if (ySize < 0)
{
throw new IllegalArgumentException("Negative y size for the data! Data: " + RenderDataPointUtil.toString(data));
}
byte blockMaterialId = RenderDataPointUtil.getBlockMaterialId(data);
int color;
boolean fullBright = false;
switch (debugging)
{
case OFF:
{
float saturationMultiplier = Config.Client.Advanced.Graphics.AdvancedGraphics.saturationMultiplier.get().floatValue();
float brightnessMultiplier = Config.Client.Advanced.Graphics.AdvancedGraphics.brightnessMultiplier.get().floatValue();
if (saturationMultiplier == 1.0 && brightnessMultiplier == 1.0)
{
color = RenderDataPointUtil.getColor(data);
}
else
{
float[] ahsv = ColorUtil.argbToAhsv(RenderDataPointUtil.getColor(data));
color = ColorUtil.ahsvToArgb(ahsv[0], ahsv[1], ahsv[2] * saturationMultiplier, ahsv[3] * brightnessMultiplier);
//ApiShared.LOGGER.info("Raw color:[{}], AHSV:{}, Out color:[{}]",
// ColorUtil.toString(DataPointUtil.getColor(data)),
// ahsv, ColorUtil.toString(color));
}
break;
}
case SHOW_DETAIL:
{
color = LodUtil.DEBUG_DETAIL_LEVEL_COLORS[detailLevel];
fullBright = true;
break;
}
case SHOW_BLOCK_MATERIAL:
{
switch (EDhApiBlockMaterial.getFromIndex(blockMaterialId))
{
case UNKNOWN:
case AIR: // shouldn't normally be rendered, but just in case
color = ColorUtil.HOT_PINK;
break;
case LEAVES:
color = ColorUtil.DARK_GREEN;
break;
case STONE:
color = ColorUtil.GRAY;
break;
case WOOD:
color = ColorUtil.BROWN;
break;
case METAL:
color = ColorUtil.DARK_GRAY;
break;
case DIRT:
color = ColorUtil.LIGHT_BROWN;
break;
case LAVA:
color = ColorUtil.ORANGE;
break;
case DEEPSLATE:
color = ColorUtil.BLACK;
break;
case SNOW:
color = ColorUtil.WHITE;
break;
case SAND:
color = ColorUtil.TAN;
break;
case TERRACOTTA:
color = ColorUtil.DARK_ORANGE;
break;
case NETHER_STONE:
color = ColorUtil.DARK_RED;
break;
case WATER:
color = ColorUtil.BLUE;
break;
case GRASS:
color = ColorUtil.GREEN;
break;
case ILLUMINATED:
color = ColorUtil.YELLOW;
break;
default:
// undefined color
color = ColorUtil.CYAN;
break;
}
fullBright = true;
break;
}
case SHOW_OVERLAPPING_QUADS:
{
color = ColorUtil.WHITE;
fullBright = true;
break;
}
case SHOW_RENDER_SOURCE_FLAG:
{
color = debugSource == null ? ColorUtil.RED : debugSource.color;
fullBright = true;
break;
}
default:
throw new IllegalArgumentException("Unknown debug mode: " + debugging);
}
ColumnBox.addBoxQuadsToBuilder(
quadBuilder, // buffer
width, ySize, width, // setWidth
x, yMin, z, // setOffset
color, // setColor
blockMaterialId, // irisBlockMaterialId
RenderDataPointUtil.getLightSky(data), // setSkyLights
fullBright ? 15 : RenderDataPointUtil.getLightBlock(data), // setBlockLights
topData, bottomData, adjColumnViews); // setAdjData
}
}
@@ -38,8 +38,7 @@ import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftCli
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper;
import com.seibel.distanthorizons.coreapi.util.MathUtil;
import org.apache.logging.log4j.Logger;
//TODO: Recheck this class for refactoring
import org.lwjgl.system.MemoryUtil;
/**
* Used to create the quads before they are converted to render-able buffers. <br><br>
@@ -51,11 +50,6 @@ public class LodQuadBuilder
private static final Logger LOGGER = DhLoggerBuilder.getLogger();
private static final IMinecraftClientWrapper MC = SingletonInjector.INSTANCE.get(IMinecraftClientWrapper.class);
@Deprecated
public final boolean skipQuadsWithZeroSkylight;
@Deprecated
public final short skyLightCullingBelow;
@SuppressWarnings("unchecked")
private final ArrayList<BufferQuad>[] opaqueQuads = (ArrayList<BufferQuad>[]) new ArrayList[6];
@SuppressWarnings("unchecked")
@@ -134,8 +128,6 @@ public class LodQuadBuilder
this.transparentQuads[i] = new ArrayList<>();
}
this.skipQuadsWithZeroSkylight = false;
this.skyLightCullingBelow = 0;
this.clientLevelWrapper = clientLevelWrapper;
this.debugRenderingMode = Config.Client.Advanced.Debugging.debugRendering.get();
@@ -159,11 +151,6 @@ public class LodQuadBuilder
throw new IllegalArgumentException("addQuadAdj() is only for adj direction! Not UP or Down!");
}
if (this.skipQuadsWithZeroSkylight && skyLight == 0 && y + widthNorthSouthOrUpDown < this.skyLightCullingBelow)
{
return;
}
BufferQuad quad = new BufferQuad(x, y, z, widthEastWest, widthNorthSouthOrUpDown, color, irisBlockMaterialId, skyLight, blockLight, dir);
ArrayList<BufferQuad> quadList = (this.doTransparency && ColorUtil.getAlpha(color) < 255) ? this.transparentQuads[dir.ordinal()] : this.opaqueQuads[dir.ordinal()];
if (!quadList.isEmpty() &&
@@ -182,12 +169,6 @@ public class LodQuadBuilder
// XZ
public void addQuadUp(short x, short maxY, short z, short widthEastWest, short widthNorthSouthOrUpDown, int color, byte irisBlockMaterialId, byte skylight, byte blocklight) // TODO argument names are wrong
{
// cave culling
if (this.skipQuadsWithZeroSkylight && skylight == 0 && maxY < this.skyLightCullingBelow)
{
return;
}
BufferQuad quad = new BufferQuad(x, maxY, z, widthEastWest, widthNorthSouthOrUpDown, color, irisBlockMaterialId, skylight, blocklight, EDhDirection.UP);
boolean isTransparent = (this.doTransparency && ColorUtil.getAlpha(color) < 255);
ArrayList<BufferQuad> quadList = isTransparent ? this.transparentQuads[EDhDirection.UP.ordinal()] : this.opaqueQuads[EDhDirection.UP.ordinal()];
@@ -209,15 +190,13 @@ public class LodQuadBuilder
public void addQuadDown(short x, short y, short z, short width, short wz, int color, byte irisBlockMaterialId, byte skylight, byte blocklight)
{
if (skipQuadsWithZeroSkylight && skylight == 0 && y < skyLightCullingBelow)
return;
BufferQuad quad = new BufferQuad(x, y, z, width, wz, color, irisBlockMaterialId, skylight, blocklight, EDhDirection.DOWN);
ArrayList<BufferQuad> qs = (doTransparency && ColorUtil.getAlpha(color) < 255)
? transparentQuads[EDhDirection.DOWN.ordinal()] : opaqueQuads[EDhDirection.DOWN.ordinal()];
if (!qs.isEmpty() &&
(qs.get(qs.size() - 1).tryMerge(quad, BufferMergeDirectionEnum.EastWest)
if (!qs.isEmpty()
&& (qs.get(qs.size() - 1).tryMerge(quad, BufferMergeDirectionEnum.EastWest)
|| qs.get(qs.size() - 1).tryMerge(quad, BufferMergeDirectionEnum.NorthSouthOrUpDown))
)
)
{
premergeCount++;
return;
@@ -227,10 +206,124 @@ public class LodQuadBuilder
//=================//
// data finalizing //
//=================//
/** runs any final data cleanup, merging, etc. */
public void finalizeData() { this.mergeQuads(); }
/** Uses Greedy meshing to merge this builder's Quads. */
public void mergeQuads()
{
long mergeCount = 0; // can be used for debugging
long preQuadsCount = this.getCurrentOpaqueQuadsCount() + this.getCurrentTransparentQuadsCount();
if (preQuadsCount <= 1)
{
return;
}
for (int directionIndex = 0; directionIndex < 6; directionIndex++)
{
mergeCount += mergeQuadsInternal(this.opaqueQuads, directionIndex, BufferMergeDirectionEnum.EastWest);
if (this.doTransparency)
{
mergeCount += mergeQuadsInternal(this.transparentQuads, directionIndex, BufferMergeDirectionEnum.EastWest);
}
// only run the second merge if the face is the top or bottom
if (directionIndex == EDhDirection.UP.ordinal() || directionIndex == EDhDirection.DOWN.ordinal())
{
mergeCount += mergeQuadsInternal(this.opaqueQuads, directionIndex, BufferMergeDirectionEnum.NorthSouthOrUpDown);
if (this.doTransparency)
{
mergeCount += mergeQuadsInternal(this.transparentQuads, directionIndex, BufferMergeDirectionEnum.NorthSouthOrUpDown);
}
}
}
//long postQuadsCount = this.getCurrentOpaqueQuadsCount() + this.getCurrentTransparentQuadsCount();
//LOGGER.trace("Merged "+mergeCount+"/"+preQuadsCount+"("+(mergeCount / (double) preQuadsCount)+") quads");
}
/** Merges all of this builder's quads for the given directionIndex (up, down, left, etc.) in the given direction */
private static long mergeQuadsInternal(ArrayList<BufferQuad>[] list, int directionIndex, BufferMergeDirectionEnum mergeDirection)
{
if (list[directionIndex].size() <= 1)
return 0;
list[directionIndex].sort((objOne, objTwo) -> objOne.compare(objTwo, mergeDirection));
long mergeCount = 0;
ListIterator<BufferQuad> iter = list[directionIndex].listIterator();
BufferQuad currentQuad = iter.next();
while (iter.hasNext())
{
BufferQuad nextQuad = iter.next();
if (currentQuad.tryMerge(nextQuad, mergeDirection))
{
// merge successful, attempt to merge the next quad
mergeCount++;
iter.set(null);
}
else
{
// merge fail, move on to the next quad
currentQuad = nextQuad;
}
}
list[directionIndex].removeIf(Objects::isNull);
return mergeCount;
}
//==============//
// add vertices //
// buffer setup //
//==============//
public ArrayList<ByteBuffer> makeOpaqueVertexBuffers() { return this.makeVertexBuffers(this.opaqueQuads); }
public ArrayList<ByteBuffer> makeTransparentVertexBuffers() { return this.makeVertexBuffers(this.transparentQuads); }
private ArrayList<ByteBuffer> makeVertexBuffers(ArrayList<BufferQuad>[] quadList)
{
ArrayList<ByteBuffer> byteBufferList = new ArrayList<>(3);
ByteBuffer buffer = null;
for (int directionIndex = 0; directionIndex < 6; directionIndex++)
{
// ignore empty directions
if (quadList[directionIndex].isEmpty())
{
continue;
}
// put all the quads in this direction into the buffer
for (int quadIndex = 0; quadIndex < quadList[directionIndex].size(); quadIndex++)
{
// if this is the first iteration or the buffer is full,
// create a new buffer
if (buffer == null || !buffer.hasRemaining())
{
buffer = MemoryUtil.memAlloc(ColumnRenderBuffer.FULL_SIZED_BUFFER);
byteBufferList.add(buffer);
}
this.putQuad(buffer, quadList[directionIndex].get(quadIndex));
}
}
// rewind all the buffers so they can be read from
for (int i = 0; i < byteBufferList.size(); i++)
{
buffer = byteBufferList.get(i);
buffer.limit(buffer.position());
buffer.rewind();
}
return byteBufferList;
}
private void putQuad(ByteBuffer bb, BufferQuad quad)
{
int[][] quadBase = DIRECTION_VERTEX_IBO_QUAD[quad.direction.ordinal()];
@@ -288,10 +381,10 @@ public class LodQuadBuilder
if (quad.direction.getAxis().isHorizontal() || quad.direction == EDhDirection.DOWN)
{
if (this.grassSideRenderingMode == EDhApiGrassSideRendering.AS_DIRT
// if we want the color to fade, only apply the dirt color to the bottom vertices
|| (this.grassSideRenderingMode == EDhApiGrassSideRendering.FADE_TO_DIRT && quadBase[i][1] == 0)
// always render the bottom as dirt
|| quad.direction == EDhDirection.DOWN)
// if we want the color to fade, only apply the dirt color to the bottom vertices
|| (this.grassSideRenderingMode == EDhApiGrassSideRendering.FADE_TO_DIRT && quadBase[i][1] == 0)
// always render the bottom as dirt
|| quad.direction == EDhDirection.DOWN)
{
// for horizontal and bottom faces of grass blocks, use the dirt color to
// prevent green cliff walls
@@ -313,7 +406,6 @@ public class LodQuadBuilder
mx, my, mz);
}
}
private void putVertex(ByteBuffer bb, short x, short y, short z, int color, byte normalIndex, byte irisBlockMaterialId, byte skylight, byte blocklight, int mx, int my, int mz)
{
skylight %= 16;
@@ -354,389 +446,6 @@ public class LodQuadBuilder
//=================//
// data finalizing //
//=================//
/** runs any final data cleanup, merging, etc. */
public void finalizeData() { this.mergeQuads(); }
/** Uses Greedy meshing to merge this builder's Quads. */
public void mergeQuads()
{
long mergeCount = 0;
long preQuadsCount = this.getCurrentOpaqueQuadsCount() + this.getCurrentTransparentQuadsCount();
if (preQuadsCount <= 1)
{
return;
}
for (int directionIndex = 0; directionIndex < 6; directionIndex++)
{
mergeCount += mergeQuadsInternal(this.opaqueQuads, directionIndex, BufferMergeDirectionEnum.EastWest);
if (this.doTransparency)
{
mergeCount += mergeQuadsInternal(this.transparentQuads, directionIndex, BufferMergeDirectionEnum.EastWest);
}
// only run the second merge if the face is the top or bottom
if (directionIndex == EDhDirection.UP.ordinal() || directionIndex == EDhDirection.DOWN.ordinal())
{
mergeCount += mergeQuadsInternal(this.opaqueQuads, directionIndex, BufferMergeDirectionEnum.NorthSouthOrUpDown);
if (this.doTransparency)
{
mergeCount += mergeQuadsInternal(this.transparentQuads, directionIndex, BufferMergeDirectionEnum.NorthSouthOrUpDown);
}
}
}
long postQuadsCount = this.getCurrentOpaqueQuadsCount() + this.getCurrentTransparentQuadsCount();
LOGGER.debug("Merged "+mergeCount+"/"+preQuadsCount+"("+(mergeCount / (double) preQuadsCount)+") quads");
}
/** Merges all of this builder's quads for the given directionIndex (up, down, left, etc.) in the given direction */
private static long mergeQuadsInternal(ArrayList<BufferQuad>[] list, int directionIndex, BufferMergeDirectionEnum mergeDirection)
{
if (list[directionIndex].size() <= 1)
return 0;
list[directionIndex].sort((objOne, objTwo) -> objOne.compare(objTwo, mergeDirection));
long mergeCount = 0;
ListIterator<BufferQuad> iter = list[directionIndex].listIterator();
BufferQuad currentQuad = iter.next();
while (iter.hasNext())
{
BufferQuad nextQuad = iter.next();
if (currentQuad.tryMerge(nextQuad, mergeDirection))
{
// merge successful, attempt to merge the next quad
mergeCount++;
iter.set(null);
}
else
{
// merge fail, move on to the next quad
currentQuad = nextQuad;
}
}
list[directionIndex].removeIf(Objects::isNull);
return mergeCount;
}
//==============//
// buffer setup //
//==============//
public Iterator<ByteBuffer> makeOpaqueVertexBuffers()
{
return new Iterator<ByteBuffer>()
{
final ByteBuffer bb = ByteBuffer.allocateDirect(ColumnRenderBuffer.FULL_SIZED_BUFFER)
.order(ByteOrder.nativeOrder());
int dir = skipEmpty(0);
int quad = 0;
private int skipEmpty(int d)
{
while (d < 6 && opaqueQuads[d].isEmpty())
{
d++;
}
return d;
}
@Override
public boolean hasNext()
{
return dir < 6;
}
@Override
public ByteBuffer next()
{
if (dir >= 6)
{
return null;
}
bb.clear();
bb.limit(ColumnRenderBuffer.FULL_SIZED_BUFFER);
while (bb.hasRemaining() && dir < 6)
{
writeData();
}
bb.limit(bb.position());
bb.rewind();
return bb;
}
private void writeData()
{
int i = quad;
for (; i < opaqueQuads[dir].size(); i++)
{
if (!bb.hasRemaining())
{
break;
}
putQuad(bb, opaqueQuads[dir].get(i));
}
if (i >= opaqueQuads[dir].size())
{
quad = 0;
dir++;
dir = skipEmpty(dir);
}
else
{
quad = i;
}
}
};
}
public Iterator<ByteBuffer> makeTransparentVertexBuffers()
{
return new Iterator<ByteBuffer>()
{
final ByteBuffer bb = ByteBuffer.allocateDirect(ColumnRenderBuffer.FULL_SIZED_BUFFER)
.order(ByteOrder.nativeOrder());
int directionIndex = this.skipEmptyDirectionIndices(0);
int quad = 0;
private int skipEmptyDirectionIndices(int directionIndex)
{
while (directionIndex < 6 &&
(LodQuadBuilder.this.transparentQuads[directionIndex] == null
|| LodQuadBuilder.this.transparentQuads[directionIndex].isEmpty()))
{
directionIndex++;
}
return directionIndex;
}
@Override
public boolean hasNext() { return this.directionIndex < 6; }
@Override
public ByteBuffer next()
{
if (this.directionIndex >= 6)
{
return null;
}
this.bb.clear();
this.bb.limit(ColumnRenderBuffer.FULL_SIZED_BUFFER);
while (this.bb.hasRemaining() && this.directionIndex < 6)
{
this.writeData();
}
this.bb.limit(this.bb.position());
this.bb.rewind();
return this.bb;
}
private void writeData()
{
int i = this.quad;
for (; i < LodQuadBuilder.this.transparentQuads[this.directionIndex].size(); i++)
{
if (!this.bb.hasRemaining())
{
break;
}
putQuad(this.bb, LodQuadBuilder.this.transparentQuads[this.directionIndex].get(i));
}
if (i >= LodQuadBuilder.this.transparentQuads[this.directionIndex].size())
{
this.quad = 0;
this.directionIndex++;
this.directionIndex = this.skipEmptyDirectionIndices(this.directionIndex);
}
else
{
this.quad = i;
}
}
};
}
public interface BufferFiller
{
/** If true: more data needs to be filled */
boolean fill(GLVertexBuffer vbo);
}
public BufferFiller makeOpaqueBufferFiller(EDhApiGpuUploadMethod method)
{
return new BufferFiller()
{
int dir = 0;
int quad = 0;
public boolean fill(GLVertexBuffer vbo)
{
if (dir >= 6)
{
vbo.setVertexCount(0);
return false;
}
int numOfQuads = _countRemainingQuads();
if (numOfQuads > ColumnRenderBuffer.MAX_QUADS_PER_BUFFER)
numOfQuads = ColumnRenderBuffer.MAX_QUADS_PER_BUFFER;
if (numOfQuads == 0)
{
vbo.setVertexCount(0);
return false;
}
ByteBuffer bb = vbo.mapBuffer(numOfQuads * ColumnRenderBuffer.QUADS_BYTE_SIZE, method,
ColumnRenderBuffer.FULL_SIZED_BUFFER);
if (bb == null)
throw new NullPointerException("mapBuffer returned null");
bb.clear();
bb.limit(numOfQuads * ColumnRenderBuffer.QUADS_BYTE_SIZE);
while (bb.hasRemaining() && dir < 6)
{
writeData(bb);
}
bb.rewind();
vbo.unmapBuffer();
vbo.setVertexCount(numOfQuads * 4);
return dir < 6;
}
private int _countRemainingQuads()
{
int a = opaqueQuads[dir].size() - quad;
for (int i = dir + 1; i < opaqueQuads.length; i++)
{
a += opaqueQuads[i].size();
}
return a;
}
private void writeData(ByteBuffer bb)
{
int startQ = quad;
int i = startQ;
for (i = startQ; i < opaqueQuads[dir].size(); i++)
{
if (!bb.hasRemaining())
{
break;
}
putQuad(bb, opaqueQuads[dir].get(i));
}
if (i >= opaqueQuads[dir].size())
{
quad = 0;
dir++;
while (dir < 6 && opaqueQuads[dir].isEmpty())
{
dir++;
}
}
else
{
quad = i;
}
}
};
}
public BufferFiller makeTransparentBufferFiller(EDhApiGpuUploadMethod method)
{
return new BufferFiller()
{
int dir = 0;
int quad = 0;
public boolean fill(GLVertexBuffer vbo)
{
if (dir >= 6)
{
vbo.setVertexCount(0);
return false;
}
int numOfQuads = _countRemainingQuads();
if (numOfQuads > ColumnRenderBuffer.MAX_QUADS_PER_BUFFER)
numOfQuads = ColumnRenderBuffer.MAX_QUADS_PER_BUFFER;
if (numOfQuads == 0)
{
vbo.setVertexCount(0);
return false;
}
ByteBuffer bb = vbo.mapBuffer(numOfQuads * ColumnRenderBuffer.QUADS_BYTE_SIZE, method,
ColumnRenderBuffer.FULL_SIZED_BUFFER);
if (bb == null)
throw new NullPointerException("mapBuffer returned null");
bb.clear();
bb.limit(numOfQuads * ColumnRenderBuffer.QUADS_BYTE_SIZE);
while (bb.hasRemaining() && dir < 6)
{
writeData(bb);
}
bb.rewind();
vbo.unmapBuffer();
vbo.setVertexCount(numOfQuads * 4);
return dir < 6;
}
private int _countRemainingQuads()
{
int a = transparentQuads[dir].size() - quad;
for (int i = dir + 1; i < transparentQuads.length; i++)
{
a += transparentQuads[i].size();
}
return a;
}
private void writeData(ByteBuffer bb)
{
int startQ = quad;
int i = startQ;
for (i = startQ; i < transparentQuads[dir].size(); i++)
{
if (!bb.hasRemaining())
{
break;
}
putQuad(bb, transparentQuads[dir].get(i));
}
if (i >= transparentQuads[dir].size())
{
quad = 0;
dir++;
while (dir < 6 && transparentQuads[dir].isEmpty())
{
dir++;
}
}
else
{
quad = i;
}
}
};
}
//=========//
// getters //
//=========//
@@ -20,6 +20,7 @@
package com.seibel.distanthorizons.core.dataObjects.render.columnViews;
import com.seibel.distanthorizons.core.dataObjects.render.ColumnRenderSource;
import com.seibel.distanthorizons.core.util.RenderDataPointUtil;
import it.unimi.dsi.fastutil.longs.LongArrayList;
@@ -28,41 +29,60 @@ import java.util.Arrays;
public final class ColumnArrayView implements IColumnDataView
{
public final LongArrayList data;
/**
* How many data points are currently being represented by this view. <br>
* Will be equal to or less than {@link ColumnArrayView#verticalSize}.
*/
public final int size;
public final int offset; // offset in longs
/** can be 0 if this column was created for an empty data source */
public final int vertSize; // vertical size in longs
/**
* Vertical size in data points. <Br>
* Can be 0 if this column was created for an empty data source.
*/
public final int verticalSize;
/**
* Where the relative starting index is in the {@link ColumnArrayView#data} array
* if this view is representing part of a {@link ColumnRenderSource}.
*/
public final int offset;
public ColumnArrayView(LongArrayList data, int size, int offset, int vertSize)
//=============//
// constructor //
//=============//
public ColumnArrayView(LongArrayList data, int size, int offset, int verticalSize)
{
this.data = data;
this.size = size;
this.offset = offset;
this.vertSize = vertSize;
this.verticalSize = verticalSize;
}
//=====================//
// getters and setters //
//=====================//
@Override
public long get(int index) { return data.getLong(index + offset); }
public void set(int index, long value) { data.set(index + offset, value); }
@Override
public int size() { return size; }
@Override
public int verticalSize() { return verticalSize; }
@Override
public int verticalSize() { return vertSize; }
@Override
public int dataCount() { return (this.vertSize != 0) ? (this.size / this.vertSize) : 0; }
public int dataCount() { return (this.verticalSize != 0) ? (this.size / this.verticalSize) : 0; } // TODO what does the divide by mean?
@Override
public ColumnArrayView subView(int dataIndexStart, int dataCount)
{
return new ColumnArrayView(data, dataCount * vertSize, offset + dataIndexStart * vertSize, vertSize);
return new ColumnArrayView(data, dataCount * verticalSize, offset + dataIndexStart * verticalSize, verticalSize);
}
public void fill(long value) { Arrays.fill(data.elements(), offset, offset + size, value); }
@@ -70,7 +90,7 @@ public final class ColumnArrayView implements IColumnDataView
public void copyFrom(IColumnDataView source) { copyFrom(source, 0); }
public void copyFrom(IColumnDataView source, int outputDataIndexOffset)
{
if (source.verticalSize() > vertSize)
if (source.verticalSize() > verticalSize)
{
throw new IllegalArgumentException("source verticalSize must be <= self's verticalSize to copy");
}
@@ -78,19 +98,19 @@ public final class ColumnArrayView implements IColumnDataView
{
throw new IllegalArgumentException("dataIndexStart + source.dataCount() must be <= self.dataCount() to copy");
}
else if (source.verticalSize() != vertSize)
else if (source.verticalSize() != verticalSize)
{
for (int i = 0; i < source.dataCount(); i++)
{
int outputOffset = offset + outputDataIndexOffset * vertSize + i * vertSize;
int outputOffset = offset + outputDataIndexOffset * verticalSize + i * verticalSize;
source.subView(i, 1).copyTo(data.elements(), outputOffset, source.verticalSize());
Arrays.fill(data.elements(), outputOffset + source.verticalSize(),
outputOffset + vertSize, 0);
outputOffset + verticalSize, 0);
}
}
else
{
source.copyTo(data.elements(), offset + outputDataIndexOffset * vertSize, source.size());
source.copyTo(data.elements(), offset + outputDataIndexOffset * verticalSize, source.size());
}
}
@@ -103,19 +123,19 @@ public final class ColumnArrayView implements IColumnDataView
{
throw new IllegalArgumentException("Cannot merge views of different sizes");
}
if (vertSize != source.vertSize)
if (verticalSize != source.verticalSize)
{
throw new IllegalArgumentException("Cannot merge views of different vertical sizes");
}
boolean anyChange = false;
for (int o = 0; o < (source.size() * vertSize); o += vertSize)
for (int o = 0; o < (source.size() * verticalSize); o += verticalSize)
{
if (override)
{
if (RenderDataPointUtil.compareDatapointPriority(source.get(o), get(o)) >= 0)
{
anyChange = true;
System.arraycopy(source.data, source.offset + o, data, offset + o, vertSize);
System.arraycopy(source.data, source.offset + o, data, offset + o, verticalSize);
}
}
else
@@ -123,7 +143,7 @@ public final class ColumnArrayView implements IColumnDataView
if (RenderDataPointUtil.compareDatapointPriority(source.get(o), get(o)) > 0)
{
anyChange = true;
System.arraycopy(source.data, source.offset + o, data, offset + o, vertSize);
System.arraycopy(source.data, source.offset + o, data, offset + o, verticalSize);
}
}
}
@@ -137,7 +157,7 @@ public final class ColumnArrayView implements IColumnDataView
throw new IllegalArgumentException("Cannot copy and resize to views with different dataCounts");
}
if (this.vertSize >= source.verticalSize())
if (this.verticalSize >= source.verticalSize())
{
this.copyFrom(source);
}
@@ -160,12 +180,18 @@ public final class ColumnArrayView implements IColumnDataView
RenderDataPointUtil.mergeMultiData(source, this);
}
//================//
// base overrides //
//================//
@Override
public String toString()
{
StringBuilder sb = new StringBuilder();
sb.append("S:").append(size);
sb.append(" V:").append(vertSize);
sb.append(" V:").append(verticalSize);
sb.append(" O:").append(offset);
sb.append(" [");
@@ -182,6 +208,7 @@ public final class ColumnArrayView implements IColumnDataView
return sb.toString();
}
public int getDataHash()
{
return arrayHash(data, offset, size);
@@ -1,253 +0,0 @@
/*
* This file is part of the Distant Horizons mod
* licensed under the GNU LGPL v3 License.
*
* Copyright (C) 2020-2023 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.distanthorizons.core.dataObjects.transformers;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;
import com.seibel.distanthorizons.core.config.Config;
import com.seibel.distanthorizons.core.dataObjects.fullData.sources.FullDataSourceV2;
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
import com.seibel.distanthorizons.core.logging.ConfigBasedLogger;
import com.seibel.distanthorizons.core.pos.DhChunkPos;
import com.seibel.distanthorizons.core.util.threading.ThreadPoolUtil;
import com.seibel.distanthorizons.core.wrapperInterfaces.chunk.IChunkWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper;
import org.apache.logging.log4j.LogManager;
public class ChunkToLodBuilder implements AutoCloseable
{
public static final ConfigBasedLogger LOGGER = new ConfigBasedLogger(LogManager.getLogger(), () -> Config.Client.Advanced.Logging.logLodBuilderEvent.get());
private static final IMinecraftClientWrapper MC = SingletonInjector.INSTANCE.get(IMinecraftClientWrapper.class);
public static final long MAX_TICK_TIME_NS = 1000000000L / 20L;
private final ConcurrentHashMap<DhChunkPos, IChunkWrapper> concurrentChunkToBuildByChunkPos = new ConcurrentHashMap<>();
private final ConcurrentLinkedDeque<Task> concurrentTaskToBuildList = new ConcurrentLinkedDeque<>();
private final AtomicInteger runningCount = new AtomicInteger(0);
//==============//
// constructors //
//==============//
public ChunkToLodBuilder() { }
//=================//
// data generation //
//=================//
public CompletableFuture<FullDataSourceV2> tryGenerateData(IChunkWrapper chunkWrapper)
{
if (chunkWrapper == null)
{
throw new NullPointerException("ChunkWrapper cannot be null!");
}
IChunkWrapper oldChunk = this.concurrentChunkToBuildByChunkPos.put(chunkWrapper.getChunkPos(), chunkWrapper); // an Exchange operation
// If there's old chunk, that means we just replaced an unprocessed old request on generating data on this pos.
// if so, we can just return null to signal this, as the old request's future will instead be the proper one
// that will return the latest generated data.
if (oldChunk != null)
{
return null;
}
// Otherwise, it means we're the first to do so. Let's submit our task to this entry.
CompletableFuture<FullDataSourceV2> future = new CompletableFuture<>();
this.concurrentTaskToBuildList.addLast(new Task(chunkWrapper.getChunkPos(), future));
return future;
}
// TODO why on tick?
public void tick()
{
int threadCount = ThreadPoolUtil.getWorkerThreadCount();
if (this.runningCount.get() >= threadCount)
{
return;
}
else if (this.concurrentTaskToBuildList.isEmpty())
{
return;
}
else if (MC == null || !MC.playerExists())
{
// TODO handle server side properly
// MC hasn't finished loading (or is currently unloaded)
// can be uncommented if tasks aren't being cleared correctly
//this.clearCurrentTasks();
return;
}
ThreadPoolExecutor lodBuilderExecutor = ThreadPoolUtil.getChunkToLodBuilderExecutor();
if (lodBuilderExecutor == null)
{
return;
}
for (int i = 0; i < threadCount; i++)
{
this.runningCount.incrementAndGet();
try
{
CompletableFuture.runAsync(() ->
{
try
{
this.tickThreadTask();
}
finally
{
this.runningCount.decrementAndGet();
}
}, lodBuilderExecutor);
}
catch (RejectedExecutionException ignore) { /* the thread pool was probably shut down because it's size is being changed, just wait a sec and it should be back */ }
}
}
private void tickThreadTask()
{
long time = System.nanoTime();
int count = 0;
boolean allDone = false;
while (true)
{
// run until we either run out of time, or all tasks are complete
if (System.nanoTime() - time > MAX_TICK_TIME_NS && !this.concurrentTaskToBuildList.isEmpty())
{
break;
}
Task task = this.concurrentTaskToBuildList.pollFirst();
if (task == null)
{
allDone = true;
break;
}
count++;
IChunkWrapper latestChunk = this.concurrentChunkToBuildByChunkPos.remove(task.chunkPos); // Basically an Exchange operation
if (latestChunk == null)
{
LOGGER.error("Somehow Task at " + task.chunkPos + " has latestChunk as null. Skipping task.");
task.future.complete(null);
continue;
}
try
{
if (LodDataBuilder.canGenerateLodFromChunk(latestChunk))
{
FullDataSourceV2 dataSource = LodDataBuilder.createGeneratedDataSource(latestChunk);
if (dataSource != null)
{
task.future.complete(dataSource);
continue;
}
}
else if (task.generationAttemptExpirationTimeMs < System.currentTimeMillis())
{
// this task won't be re-queued
//LOGGER.trace("removed chunk "+task.chunkPos);
continue;
}
}
catch (Exception ex)
{
LOGGER.error("Error while processing Task at " + task.chunkPos, ex);
}
// Failed to build due to chunk not meeting requirement,
// re-add it to the queue so it can be tested next time
IChunkWrapper casChunk = this.concurrentChunkToBuildByChunkPos.putIfAbsent(task.chunkPos, latestChunk); // CAS operation with expected=null
if (casChunk == null || latestChunk.isStillValid()) // That means CAS have been successful
{
this.concurrentTaskToBuildList.addLast(task); // Then add back the same old task.
}
else // Else, it means someone managed to sneak in a new gen request in this pos. Then lets drop this old task.
{
task.future.complete(null);
}
count--;
}
long time2 = System.nanoTime();
if (!allDone)
{
//LOGGER.info("Completed {} tasks in {} in this tick", count, Duration.ofNanos(time2 - time));
}
else if (count > 0)
{
//LOGGER.info("Completed all {} tasks in {}", count, Duration.ofNanos(time2 - time));
}
}
/**
* should be called whenever changing levels/worlds
* to prevent trying to generate LODs for chunk(s) that are no longer loaded
* (which can cause exceptions)
*/
public void clearCurrentTasks()
{
this.concurrentTaskToBuildList.clear();
this.concurrentChunkToBuildByChunkPos.clear();
}
//==============//
// base methods //
//==============//
@Override
public void close() { this.clearCurrentTasks(); }
//================//
// helper classes //
//================//
private static class Task
{
public final DhChunkPos chunkPos;
public final CompletableFuture<FullDataSourceV2> future;
/** This is tracked so impossible tasks can be removed from the queue */
public long generationAttemptExpirationTimeMs = System.currentTimeMillis() + TimeUnit.SECONDS.toMillis(10);
Task(DhChunkPos chunkPos, CompletableFuture<FullDataSourceV2> future)
{
this.chunkPos = chunkPos;
this.future = future;
}
}
}
@@ -20,7 +20,6 @@
package com.seibel.distanthorizons.core.dataObjects.transformers;
import com.seibel.distanthorizons.api.enums.config.EDhApiBlocksToAvoid;
import com.seibel.distanthorizons.api.enums.rendering.EDhApiBlockMaterial;
import com.seibel.distanthorizons.core.config.Config;
import com.seibel.distanthorizons.core.dataObjects.fullData.FullDataPointIdMap;
import com.seibel.distanthorizons.core.dataObjects.fullData.sources.FullDataSourceV2;
@@ -29,7 +28,7 @@ import com.seibel.distanthorizons.core.dataObjects.render.columnViews.ColumnArra
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
import com.seibel.distanthorizons.core.level.IDhClientLevel;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.pos.DhBlockPos;
import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos;
import com.seibel.distanthorizons.core.pos.DhSectionPos;
import com.seibel.distanthorizons.core.util.ColorUtil;
import com.seibel.distanthorizons.core.util.FullDataPointUtil;
@@ -39,6 +38,7 @@ import com.seibel.distanthorizons.core.wrapperInterfaces.IWrapperFactory;
import com.seibel.distanthorizons.core.wrapperInterfaces.block.IBlockStateWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IBiomeWrapper;
import com.seibel.distanthorizons.coreapi.util.BitShiftUtil;
import it.unimi.dsi.fastutil.longs.LongArrayList;
import it.unimi.dsi.fastutil.longs.LongOpenHashSet;
import org.apache.logging.log4j.Logger;
@@ -122,7 +122,12 @@ public class FullDataToRenderDataTransformer
ColumnArrayView columnArrayView = columnSource.getVerticalDataPointView(x, z);
LongArrayList dataColumn = fullDataSource.get(x, z);
updateRenderDataViewWithFullDataColumn(level, fullDataSource.mapping, baseX + x, baseZ + z, columnArrayView, dataColumn);
updateOrReplaceRenderDataViewColumnWithFullDataColumn(
level, fullDataSource.mapping,
// bitshift is to account for LODs with a detail level greater than 0 so the block pos is correct
baseX + BitShiftUtil.pow(x,dataDetail), baseZ + BitShiftUtil.pow(z,dataDetail),
columnArrayView, dataColumn);
}
}
@@ -132,40 +137,48 @@ public class FullDataToRenderDataTransformer
}
/** Updates the given {@link ColumnArrayView} to match the incoming Full data {@link LongArrayList} */
public static void updateRenderDataViewWithFullDataColumn(
public static void updateOrReplaceRenderDataViewColumnWithFullDataColumn(
IDhClientLevel level,
FullDataPointIdMap fullDataMapping, int blockX, int blockZ,
ColumnArrayView columnArrayView,
LongArrayList fullDataColumn)
{
// we can't do anything if the full data is missing or empty
if (fullDataColumn == null || fullDataColumn.size() == 0)
{
return;
}
int dataTotalLength = fullDataColumn.size();
if (dataTotalLength > columnArrayView.verticalSize())
int fullDataLength = fullDataColumn.size();
if (fullDataLength <= columnArrayView.verticalSize())
{
ColumnArrayView totalColumnData = new ColumnArrayView(new LongArrayList(new long[dataTotalLength]), dataTotalLength, 0, dataTotalLength);
iterateAndConvert(level, fullDataMapping, blockX, blockZ, totalColumnData, fullDataColumn);
columnArrayView.changeVerticalSizeFrom(totalColumnData);
// Directly use the arrayView since it fits.
setRenderColumnView(level, fullDataMapping, blockX, blockZ, columnArrayView, fullDataColumn);
}
else
{
iterateAndConvert(level, fullDataMapping, blockX, blockZ, columnArrayView, fullDataColumn); //Directly use the arrayView since it fits.
// expand the ColumnArrayView to fit the new larger max vertical size
ColumnArrayView newColumnArrayView = new ColumnArrayView(new LongArrayList(new long[fullDataLength]), fullDataLength, 0, fullDataLength);
setRenderColumnView(level, fullDataMapping, blockX, blockZ, newColumnArrayView, fullDataColumn);
columnArrayView.changeVerticalSizeFrom(newColumnArrayView);
}
}
private static void iterateAndConvert(
private static void setRenderColumnView(
IDhClientLevel level, FullDataPointIdMap fullDataMapping,
int blockX, int blockZ,
ColumnArrayView renderColumnData, LongArrayList fullColumnData)
{
//===============//
// config values //
//===============//
boolean ignoreNonCollidingBlocks = (Config.Client.Advanced.Graphics.Quality.blocksToIgnore.get() == EDhApiBlocksToAvoid.NON_COLLIDING);
boolean colorBelowWithAvoidedBlocks = Config.Client.Advanced.Graphics.Quality.tintWithAvoidedBlocks.get();
HashSet<IBlockStateWrapper> blockStatesToIgnore = WRAPPER_FACTORY.getRendererIgnoredBlocks(level.getLevelWrapper());
HashSet<IBlockStateWrapper> caveBlockStatesToIgnore = WRAPPER_FACTORY.getRendererIgnoredCaveBlocks(level.getLevelWrapper());
int caveCullingMaxY = Config.Client.Advanced.Graphics.AdvancedGraphics.caveCullingHeight.get() - level.getMinY();
boolean caveCullingEnabled =
Config.Client.Advanced.Graphics.AdvancedGraphics.enableCaveCulling.get()
&& (
@@ -176,30 +189,36 @@ public class FullDataToRenderDataTransformer
&& !level.getLevelWrapper().getDimensionType().isTheEnd()
);
boolean isVoid = true;
boolean isColumnVoid = true;
int colorToApplyToNextBlock = -1;
int lastColor = 0;
int lastBottom = -10000;
int lastBottom = -10_000;
int skylightToApplyToNextBlock = -1;
int blocklightToApplyToNextBlock = -1;
int columnOffset = 0;
int renderDataIndex = 0;
IBiomeWrapper biome = null;
IBlockStateWrapper block = null;
//==================================//
// convert full data to render data //
//==================================//
// goes from the top down
for (int i = 0; i < fullColumnData.size(); i++)
for (int fullDataIndex = 0; fullDataIndex < fullColumnData.size(); fullDataIndex++)
{
long fullData = fullColumnData.getLong(i);
long fullData = fullColumnData.getLong(fullDataIndex);
int bottomY = FullDataPointUtil.getBottomY(fullData);
int blockHeight = FullDataPointUtil.getHeight(fullData);
int topY = bottomY + blockHeight;
int id = FullDataPointUtil.getId(fullData);
int blockLight = FullDataPointUtil.getBlockLight(fullData);
int skyLight = FullDataPointUtil.getSkyLight(fullData);
IBiomeWrapper biome;
IBlockStateWrapper block;
try
{
biome = fullDataMapping.getBiomeWrapper(id);
@@ -207,7 +226,6 @@ public class FullDataToRenderDataTransformer
}
catch (IndexOutOfBoundsException e)
{
// FIXME sometimes the data map has a length of 0
if (!brokenPos.contains(fullDataMapping.getPos()))
{
brokenPos.add(fullDataMapping.getPos());
@@ -219,11 +237,12 @@ public class FullDataToRenderDataTransformer
"Further errors for this position won't be logged.");
}
// skip rendering broken data
// don't render broken data
continue;
}
//====================//
// ignored block and //
// cave culling check //
@@ -236,23 +255,25 @@ public class FullDataToRenderDataTransformer
if (caveCullingEnabled
// assume this data point is underground if it has no sky-light
&& skyLight == LodUtil.MIN_MC_LIGHT
// ignore caves above a certain height to prevent floating islands from having walls underneath them
&& topY < caveCullingMaxY
// cave culling shouldn't happen when at the top of the world
&& columnOffset != 0
&& renderDataIndex != 0 && fullDataIndex != 0
// cave culling can't happen when at the bottom of the world
&& columnOffset != fullColumnData.size())
&& (fullDataIndex+1) < fullColumnData.size())
{
// we need to get the next sky/block lights because
// the air block here will always have a light of 0/0 due to only the top of the LOD's light being saved.
long nextFullData = fullColumnData.getLong(i+1);
long nextFullData = fullColumnData.getLong(fullDataIndex+1);
int nextSkyLight = FullDataPointUtil.getSkyLight(nextFullData);
if (nextSkyLight == LodUtil.MIN_MC_LIGHT
&& ColorUtil.getAlpha(lastColor) == 255)
{
// replace the previous block with new bottom
long columnData = renderColumnData.get(columnOffset - 1);
long columnData = renderColumnData.get(renderDataIndex - 1);
columnData = RenderDataPointUtil.setYMin(columnData, bottomY);
renderColumnData.set(columnOffset - 1, columnData);
renderColumnData.set(renderDataIndex - 1, columnData);
}
continue;
@@ -272,27 +293,28 @@ public class FullDataToRenderDataTransformer
}
//===================//
// solid block check //
//===================//
if (ignoreNonCollidingBlocks && !block.isSolid() && !block.isLiquid() && block.getOpacity() != LodUtil.BLOCK_FULLY_OPAQUE)
//=======================//
// non-solid block check //
//=======================//
if (ignoreNonCollidingBlocks
&& !block.isSolid() && !block.isLiquid() && block.getOpacity() != LodUtil.BLOCK_FULLY_OPAQUE)
{
if (colorBelowWithAvoidedBlocks)
{
int tempColor = level.computeBaseColor(new DhBlockPos(blockX, bottomY + level.getMinY(), blockZ), biome, block);
// don't transfer the color when alpha is 0
// this prevents issues if grass is transparent
if (ColorUtil.getAlpha(tempColor) != 0)
{
// don't transfer alpha if for some reason grass is semi transparent
colorToApplyToNextBlock = ColorUtil.setAlpha(tempColor,255);
skylightToApplyToNextBlock = skyLight;
blocklightToApplyToNextBlock = blockLight;
}
}
// don't add this block
// skip this non-colliding block
continue;
}
@@ -312,31 +334,36 @@ public class FullDataToRenderDataTransformer
blockLight = blocklightToApplyToNextBlock;
}
//check if they share a top-bottom face and if they have same color
if (color == lastColor && bottomY + blockHeight == lastBottom && columnOffset > 0)
//=============================//
// merge same-colored adjacent //
//=============================//
// check if they share a top-bottom face and if they have same color
if (color == lastColor && bottomY + blockHeight == lastBottom && renderDataIndex > 0)
{
//replace the previous block with new bottom
long columnData = renderColumnData.get(columnOffset - 1);
long columnData = renderColumnData.get(renderDataIndex - 1);
columnData = RenderDataPointUtil.setYMin(columnData, bottomY);
renderColumnData.set(columnOffset - 1, columnData);
renderColumnData.set(renderDataIndex - 1, columnData);
}
else
{
// add the block
isVoid = false;
isColumnVoid = false;
long columnData = RenderDataPointUtil.createDataPoint(bottomY + blockHeight, bottomY, color, skyLight, blockLight, block.getMaterialId());
renderColumnData.set(columnOffset, columnData);
columnOffset++;
renderColumnData.set(renderDataIndex, columnData);
renderDataIndex++;
}
lastBottom = bottomY;
lastColor = color;
}
if (isVoid)
if (isColumnVoid)
{
renderColumnData.set(0, RenderDataPointUtil.createVoidDataPoint());
renderColumnData.set(0, RenderDataPointUtil.EMPTY_DATA);
}
}
@@ -19,6 +19,7 @@
package com.seibel.distanthorizons.core.dataObjects.transformers;
import java.util.Collections;
import java.util.List;
import com.seibel.distanthorizons.api.enums.config.EDhApiWorldCompressionMode;
@@ -30,10 +31,12 @@ import com.seibel.distanthorizons.core.dataObjects.fullData.sources.FullDataSour
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
import com.seibel.distanthorizons.core.enums.EDhDirection;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.pos.DhBlockPos;
import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos;
import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPosMutable;
import com.seibel.distanthorizons.core.pos.DhSectionPos;
import com.seibel.distanthorizons.core.util.FullDataPointUtil;
import com.seibel.distanthorizons.core.util.LodUtil;
import com.seibel.distanthorizons.core.util.RenderDataPointUtil;
import com.seibel.distanthorizons.core.util.objects.DataCorruptedException;
import com.seibel.distanthorizons.core.wrapperInterfaces.block.IBlockStateWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.chunk.IChunkWrapper;
@@ -64,8 +67,8 @@ public class LodDataBuilder
int sectionPosX = getXOrZSectionPosFromChunkPos(chunkWrapper.getChunkPos().x);
int sectionPosZ = getXOrZSectionPosFromChunkPos(chunkWrapper.getChunkPos().z);
int sectionPosX = getXOrZSectionPosFromChunkPos(chunkWrapper.getChunkPos().getX());
int sectionPosZ = getXOrZSectionPosFromChunkPos(chunkWrapper.getChunkPos().getZ());
long pos = DhSectionPos.encode(DhSectionPos.SECTION_BLOCK_DETAIL_LEVEL, sectionPosX, sectionPosZ);
FullDataSourceV2 dataSource = FullDataSourceV2.createEmpty(pos);
@@ -75,8 +78,8 @@ public class LodDataBuilder
// compute the chunk dataSource offset
// this offset is used to determine where in the dataSource this chunk's data should go
int chunkOffsetX = chunkWrapper.getChunkPos().x;
if (chunkWrapper.getChunkPos().x < 0)
int chunkOffsetX = chunkWrapper.getChunkPos().getX();
if (chunkWrapper.getChunkPos().getX() < 0)
{
// expected offset positions:
// chunkPos -> offset
@@ -103,8 +106,8 @@ public class LodDataBuilder
}
chunkOffsetX *= LodUtil.CHUNK_WIDTH;
int chunkOffsetZ = chunkWrapper.getChunkPos().z;
if (chunkWrapper.getChunkPos().z < 0)
int chunkOffsetZ = chunkWrapper.getChunkPos().getZ();
if (chunkWrapper.getChunkPos().getZ() < 0)
{
chunkOffsetZ = ((chunkOffsetZ) % FullDataSourceV2.NUMB_OF_CHUNKS_WIDE);
if (chunkOffsetZ != 0)
@@ -152,8 +155,8 @@ public class LodDataBuilder
else
{
//we are at the height limit. There are no torches here, and sky is not obscured.
blockLight = 0;
skyLight = 15;
blockLight = LodUtil.MIN_MC_LIGHT;
skyLight = LodUtil.MAX_MC_LIGHT;
}
@@ -235,7 +238,7 @@ public class LodDataBuilder
private static boolean blockVisible(IChunkWrapper chunkWrapper, int relBlockX, int blockY, int relBlockZ)
{
DhBlockPos originalBlockPos = new DhBlockPos(relBlockX,blockY,relBlockZ);
DhBlockPos testBlockPos = new DhBlockPos(relBlockX,blockY,relBlockZ);
final DhBlockPosMutable testBlockPos = new DhBlockPosMutable(relBlockX,blockY,relBlockZ);
// up/down
if (blockInDirectionVisible(chunkWrapper, EDhDirection.UP, originalBlockPos, testBlockPos))
@@ -270,20 +273,20 @@ public class LodDataBuilder
return false;
}
private static boolean blockInDirectionVisible(IChunkWrapper chunkWrapper, EDhDirection direction, DhBlockPos originalBlockPos, DhBlockPos testBlockPos)
private static boolean blockInDirectionVisible(IChunkWrapper chunkWrapper, EDhDirection direction, DhBlockPos originalBlockPos, DhBlockPosMutable testBlockPos)
{
originalBlockPos.mutateOffset(direction, testBlockPos);
// if the block is next to the border of a chunk, assume it's visible
if (testBlockPos.x < 0 || testBlockPos.x >= LodUtil.CHUNK_WIDTH)
if (testBlockPos.getX() < 0 || testBlockPos.getX() >= LodUtil.CHUNK_WIDTH)
{
return true;
}
if (testBlockPos.z < 0 || testBlockPos.z >= LodUtil.CHUNK_WIDTH)
if (testBlockPos.getZ() < 0 || testBlockPos.getZ() >= LodUtil.CHUNK_WIDTH)
{
return true;
}
if (testBlockPos.y < chunkWrapper.getMinBuildHeight() || testBlockPos.y > chunkWrapper.getMaxBuildHeight())
if (testBlockPos.getY() < chunkWrapper.getMinBuildHeight() || testBlockPos.getY() > chunkWrapper.getMaxBuildHeight())
{
return true;
}
@@ -295,7 +298,7 @@ public class LodDataBuilder
/** @throws ClassCastException if an API user returns the wrong object type(s) */
public static FullDataSourceV2 createFromApiChunkData(DhApiChunk apiChunk) throws ClassCastException, DataCorruptedException
public static FullDataSourceV2 createFromApiChunkData(DhApiChunk apiChunk, boolean runAdditionalValidation) throws ClassCastException, DataCorruptedException, IllegalArgumentException
{
// get the section position
int sectionPosX = getXOrZSectionPosFromChunkPos(apiChunk.chunkPosX);
@@ -312,6 +315,10 @@ public class LodDataBuilder
for (int relBlockX = 0; relBlockX < LodUtil.CHUNK_WIDTH; relBlockX++)
{
List<DhApiTerrainDataPoint> columnDataPoints = apiChunk.getDataPoints(relBlockX, relBlockZ);
if (runAdditionalValidation)
{
validateOrThrowDataColumn(columnDataPoints);
}
// this null check does 2 nice things at the same time:
@@ -320,6 +327,8 @@ public class LodDataBuilder
// AND the below loop won't run.
int size = (columnDataPoints != null) ? columnDataPoints.size() : 0;
// TODO make missing air LODs
// TODO merge duplicate datapoints
LongArrayList packedDataPoints = new LongArrayList(new long[size]);
for (int index = 0; index < size; index++)
{
@@ -350,6 +359,75 @@ public class LodDataBuilder
}
return dataSource;
}
private static void validateOrThrowDataColumn(List<DhApiTerrainDataPoint> dataPoints) throws IllegalArgumentException
{
// order doesn't need to be checked if there is 0 or 1 items
if (dataPoints.size() > 1)
{
// DH expects datapoints to be in a top-down order
DhApiTerrainDataPoint first = dataPoints.get(0);
DhApiTerrainDataPoint last = dataPoints.get(dataPoints.size() - 1);
if (first.bottomYBlockPos < last.bottomYBlockPos)
{
// flip the array if it's in bottom-up order
Collections.reverse(dataPoints);
}
}
// check that each datapoint is valid
int lastBottomYPos = Integer.MIN_VALUE;
for (int i = 0; i < dataPoints.size(); i++) // standard for-loop used instead of an enhanced for-loop to slightly reduce GC overhead due to iterator allocation
{
DhApiTerrainDataPoint dataPoint = dataPoints.get(i);
if (dataPoint == null)
{
throw new IllegalArgumentException("Datapoint: ["+i+"] is null DhApiTerrainDataPoints are not allowed. If you want to represent empty terrain, please use AIR.");
}
if (dataPoint.detailLevel != 0)
{
throw new IllegalArgumentException("Datapoint: ["+i+"] has the wrong detail level ["+dataPoint.detailLevel+"], all data points must be block sized; IE their detail level must be [0].");
}
int bottomYPos = dataPoint.bottomYBlockPos;
int topYPos = dataPoint.topYBlockPos;
int height = (dataPoint.topYBlockPos - dataPoint.bottomYBlockPos);
// is the datapoint right side up?
if (bottomYPos > topYPos)
{
throw new IllegalArgumentException("Datapoint: ["+i+"] is upside down. Top Pos: ["+topYPos+"], bottom pos: ["+bottomYPos+"].");
}
// valid height?
if (height <= 0 || height >= RenderDataPointUtil.MAX_WORLD_Y_SIZE)
{
throw new IllegalArgumentException("Datapoint: ["+i+"] has invalid height. Height must be in the range [1 - "+RenderDataPointUtil.MAX_WORLD_Y_SIZE+"] (inclusive).");
}
// is this datapoint overlapping the last one?
if (lastBottomYPos > topYPos)
{
throw new IllegalArgumentException("DhApiTerrainDataPoint ["+i+"] is overlapping with the last datapoint, this top Y: ["+topYPos+"], lastBottomYPos: ["+lastBottomYPos+"].");
}
// is there a gap between the last datapoint?
if (topYPos != lastBottomYPos
&& lastBottomYPos != Integer.MIN_VALUE)
{
throw new IllegalArgumentException("DhApiTerrainDataPoint ["+i+"] has a gap between it and index ["+(i-1)+"]. Empty spaces should be filled by air, otherwise DH's downsampling won't calculate lighting correctly.");
}
lastBottomYPos = bottomYPos;
}
}
@@ -26,12 +26,12 @@ import com.seibel.distanthorizons.core.config.Config;
import com.seibel.distanthorizons.api.enums.config.EDhApiServerFolderNameMode;
import com.seibel.distanthorizons.core.level.IServerKeyedClientLevel;
import com.seibel.distanthorizons.core.util.objects.ParsedIp;
import com.seibel.distanthorizons.core.util.LodUtil;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftSharedWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IDimensionTypeWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper;
import com.seibel.distanthorizons.coreapi.util.StringUtil;
import java.io.File;
import java.util.*;
@@ -138,7 +138,7 @@ public class ClientOnlySaveStructure extends AbstractSaveStructure
{
// use the first existing sub-dimension
String folderName = folders.get(0).getName();
LOGGER.info("Default Sub Dimension set to: [" + LodUtil.shortenString(folderName, 8) + "...]");
LOGGER.info("Default Sub Dimension set to: [" + StringUtil.shortenString(folderName, 8) + "...]");
return folders.get(0);
}
else
@@ -40,6 +40,7 @@ import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftCli
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IBiomeWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper;
import com.seibel.distanthorizons.coreapi.util.StringUtil;
import it.unimi.dsi.fastutil.longs.LongArrayList;
import org.apache.logging.log4j.LogManager;
@@ -92,7 +93,7 @@ public class SubDimensionLevelMatcher implements AutoCloseable
if (potentialLevelFolders.size() == 0)
{
String newId = UUID.randomUUID().toString();
LOGGER.info("No potential level files found. Creating a new sub dimension with the ID ["+LodUtil.shortenString(newId, 8)+"]...");
LOGGER.info("No potential level files found. Creating a new sub dimension with the ID ["+ StringUtil.shortenString(newId, 8)+"]...");
this.foundLevelFile = this.CreateSubDimFolder(newId);
}
}
@@ -201,13 +202,13 @@ public class SubDimensionLevelMatcher implements AutoCloseable
// log the start of this attempt
LOGGER.info("Attempting to determine sub-dimension for [" + MC_CLIENT.getWrappedClientLevel().getDimensionType().getDimensionName() + "]");
LOGGER.info("Player block pos in dimension: [" + this.playerData.playerBlockPos.x + "," + this.playerData.playerBlockPos.y + "," + this.playerData.playerBlockPos.z + "]");
LOGGER.info("Player block pos in dimension: [" + this.playerData.playerBlockPos.getX() + "," + this.playerData.playerBlockPos.getY() + "," + this.playerData.playerBlockPos.getZ() + "]");
LOGGER.info("Potential Sub Dimension folders: [" + this.potentialLevelFolders.size() + "]");
SubDimCompare mostSimilarSubDim = null;
for (File testLevelFolder : this.potentialLevelFolders)
{
LOGGER.info("Testing level folder: [" + LodUtil.shortenString(testLevelFolder.getName(), 8) + "]");
LOGGER.info("Testing level folder: [" + StringUtil.shortenString(testLevelFolder.getName(), 8) + "]");
FullDataSourceV2 testFullDataSource = null;
try
@@ -314,7 +315,7 @@ public class SubDimensionLevelMatcher implements AutoCloseable
// get the player data for this dimension folder
SubDimensionPlayerData testPlayerData = new SubDimensionPlayerData(testLevelFolder);
LOGGER.info("Last known player pos: [" + testPlayerData.playerBlockPos.x + "," + testPlayerData.playerBlockPos.y + "," + testPlayerData.playerBlockPos.z + "]");
LOGGER.info("Last known player pos: [" + testPlayerData.playerBlockPos.getX() + "," + testPlayerData.playerBlockPos.getY() + "," + testPlayerData.playerBlockPos.getZ() + "]");
// check if the block positions are close
int playerBlockDist = testPlayerData.playerBlockPos.getManhattanDistance(this.playerData.playerBlockPos);
@@ -328,8 +329,8 @@ public class SubDimensionLevelMatcher implements AutoCloseable
}
String subDimShortName = LodUtil.shortenString(testLevelFolder.getName(), 8); // variables are separated out for easier debugging
String equalPercent = LodUtil.shortenString(mostSimilarSubDim.getPercentEqual()+"", 5);
String subDimShortName = StringUtil.shortenString(testLevelFolder.getName(), 8); // variables are separated out for easier debugging
String equalPercent = StringUtil.shortenString(mostSimilarSubDim.getPercentEqual()+"", 5);
LOGGER.info("Sub dimension ["+subDimShortName+"...] is current dimension probability: "+equalPercent+" ("+equalDataPoints+"/"+totalDataPointCount+")");
}
catch (Exception e)
@@ -359,7 +360,7 @@ public class SubDimensionLevelMatcher implements AutoCloseable
{
// we found a sub dim folder that is similar, use it
LOGGER.info("Sub Dimension set to: [" + LodUtil.shortenString(mostSimilarSubDim.folder.getName(), 8) + "...] with an equality of [" + mostSimilarSubDim.getPercentEqual() + "]");
LOGGER.info("Sub Dimension set to: [" + StringUtil.shortenString(mostSimilarSubDim.folder.getName(), 8) + "...] with an equality of [" + mostSimilarSubDim.getPercentEqual() + "]");
return mostSimilarSubDim.folder;
}
else
@@ -369,7 +370,7 @@ public class SubDimensionLevelMatcher implements AutoCloseable
String newId = UUID.randomUUID().toString();
double highestEqualityPercent = mostSimilarSubDim != null ? mostSimilarSubDim.getPercentEqual() : 0;
String message = "No suitable sub dimension found. The highest equality was [" + LodUtil.shortenString(highestEqualityPercent + "", 5) + "]. Creating a new sub dimension with ID: " + LodUtil.shortenString(newId, 8) + "...";
String message = "No suitable sub dimension found. The highest equality was [" + StringUtil.shortenString(highestEqualityPercent + "", 5) + "]. Creating a new sub dimension with ID: " + StringUtil.shortenString(newId, 8) + "...";
LOGGER.info(message);
File folder = this.CreateSubDimFolder(newId);
@@ -22,7 +22,7 @@ package com.seibel.distanthorizons.core.file.subDimMatching;
import com.electronwill.nightconfig.core.file.CommentedFileConfig;
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
import com.seibel.distanthorizons.core.pos.DhBlockPos;
import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos;
import com.seibel.distanthorizons.core.wrapperInterfaces.IWrapperFactory;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper;
@@ -127,9 +127,9 @@ public class SubDimensionPlayerData
public void toTomlFile(CommentedFileConfig toml)
{
// player block pos
toml.add(PLAYER_BLOCK_POS_X_PATH, this.playerBlockPos.x);
toml.add(PLAYER_BLOCK_POS_Y_PATH, this.playerBlockPos.y);
toml.add(PLAYER_BLOCK_POS_Z_PATH, this.playerBlockPos.z);
toml.add(PLAYER_BLOCK_POS_X_PATH, this.playerBlockPos.getX());
toml.add(PLAYER_BLOCK_POS_Y_PATH, this.playerBlockPos.getY());
toml.add(PLAYER_BLOCK_POS_Z_PATH, this.playerBlockPos.getZ());
toml.save();
}
@@ -138,7 +138,7 @@ public class SubDimensionPlayerData
@Override
public String toString()
{
return "PlayerBlockPos: [" + this.playerBlockPos.x + "," + this.playerBlockPos.y + "," + this.playerBlockPos.z + "]";
return "PlayerBlockPos: [" + this.playerBlockPos.getX() + "," + this.playerBlockPos.getY() + "," + this.playerBlockPos.getZ() + "]";
}
}
@@ -33,7 +33,7 @@ public class AdjacentChunkHolder
{
for (int zOffset = -1; zOffset <= 1; zOffset++)
{
DhChunkPos adjacentPos = new DhChunkPos(centerChunkPos.x + xOffset, centerChunkPos.z + zOffset);
DhChunkPos adjacentPos = new DhChunkPos(centerChunkPos.getX() + xOffset, centerChunkPos.getZ() + zOffset);
requestedAdjacentPositions.add(adjacentPos);
}
}
@@ -69,13 +69,13 @@ public class AdjacentChunkHolder
DhChunkPos centerPos = this.chunkArray[4].getChunkPos();
DhChunkPos offsetPos = centerWrapper.getChunkPos();
int offsetX = offsetPos.x - centerPos.x;
int offsetX = offsetPos.getX() - centerPos.getX();
if (offsetX < -1 || offsetX > 1)
{
return;
}
int offsetZ = offsetPos.z - centerPos.z;
int offsetZ = offsetPos.getZ() - centerPos.getZ();
if (offsetZ < -1 || offsetZ > 1)
{
return;
@@ -91,18 +91,18 @@ public class AdjacentChunkHolder
int chunkZ = BitShiftUtil.divideByPowerOfTwo(blockZ, 4);
IChunkWrapper centerChunk = this.chunkArray[4];
DhChunkPos centerPos = centerChunk.getChunkPos();
if (centerPos.x == chunkX && centerPos.z == chunkZ)
if (centerPos.getX() == chunkX && centerPos.getZ() == chunkZ)
{
return centerChunk;
}
int offsetX = chunkX - centerPos.x;
int offsetX = chunkX - centerPos.getX();
if (offsetX < -1 || offsetX > 1)
{
return null;
}
int offsetZ = chunkZ - centerPos.z;
int offsetZ = chunkZ - centerPos.getZ();
if (offsetZ < -1 || offsetZ > 1)
{
return null;
@@ -47,15 +47,6 @@ public class BatchGenerator implements IDhApiWorldGenerator
private static final IWrapperFactory FACTORY = SingletonInjector.INSTANCE.get(IWrapperFactory.class);
private static final Logger LOGGER = DhLoggerBuilder.getLogger();
/**
* Defines how many tasks can be queued per thread. <br><br>
*
* TODO the multiplier here should change dynamically based on how fast the generator is vs the queuing thread,
* if this is too high it may cause issues when moving,
* but if it is too low the generator threads won't have enough tasks to work on
*/
private static final int MAX_QUEUED_TASKS_PER_THREAD = 3;
public AbstractBatchGenerationEnvironmentWrapper generationEnvironment;
public IDhLevel targetDhLevel;
@@ -147,14 +138,6 @@ public class BatchGenerator implements IDhApiWorldGenerator
@Override
public void preGeneratorTaskStart() { this.generationEnvironment.updateAllFutures(); }
@Override
public boolean isBusy()
{
int worldGenThreadCount = Math.max(Config.Client.Advanced.MultiThreading.numberOfWorldGenerationThreads.get(), 1);
int maxWorldGenTaskCount = worldGenThreadCount * MAX_QUEUED_TASKS_PER_THREAD;
return this.generationEnvironment.getEventCount() > maxWorldGenTaskCount;
}
//=========//
@@ -21,13 +21,17 @@ package com.seibel.distanthorizons.core.generation;
import com.seibel.distanthorizons.core.enums.EDhDirection;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.pos.DhBlockPos;
import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos;
import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPosMutable;
import com.seibel.distanthorizons.core.pos.DhChunkPos;
import com.seibel.distanthorizons.core.pos.DhSectionPos;
import com.seibel.distanthorizons.core.render.renderer.DebugRenderer;
import com.seibel.distanthorizons.core.util.LodUtil;
import com.seibel.distanthorizons.core.wrapperInterfaces.block.IBlockStateWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.chunk.IChunkWrapper;
import org.apache.logging.log4j.Logger;
import java.awt.*;
import java.util.*;
import java.util.concurrent.locks.ReentrantLock;
@@ -49,15 +53,28 @@ public class DhLightingEngine
* Since these objects are always mutated anyway, using a {@link ThreadLocal} will allow us to
* only create as many of these {@link DhBlockPos} as necessary.
*/
private static final ThreadLocal<DhBlockPos> PRIMARY_BLOCK_POS_REF = ThreadLocal.withInitial(() -> new DhBlockPos());
private static final ThreadLocal<DhBlockPos> SECONDARY_BLOCK_POS_REF = ThreadLocal.withInitial(() -> new DhBlockPos());
private static final ThreadLocal<DhBlockPosMutable> PRIMARY_BLOCK_POS_REF = ThreadLocal.withInitial(() -> new DhBlockPosMutable());
private static final ThreadLocal<DhBlockPosMutable> SECONDARY_BLOCK_POS_REF = ThreadLocal.withInitial(() -> new DhBlockPosMutable());
/** if enabled will render each block light value when the lighting engine is run */
private static final boolean RENDER_BLOCK_LIGHT_WIREFRAME = false;
/** if enabled will render each sky light value when the lighting engine is run */
private static final boolean RENDER_SKY_LIGHT_WIREFRAME = false;
//=============//
// constructor //
//=============//
private DhLightingEngine() { }
//=========//
// methods //
//=========//
/**
* Note: depending on the implementation of {@link IChunkWrapper#setDhBlockLight(int, int, int, int)} and {@link IChunkWrapper#setDhSkyLight(int, int, int, int)}
* the light values may be stored in the wrapper itself instead of the wrapped chunk object.
@@ -76,12 +93,12 @@ public class DhLightingEngine
// try-finally to handle the stableArray resources
StableLightPosStack blockLightPosQueue = null;
StableLightPosStack skyLightPosQueue = null;
StableLightPosStack blockLightWorldPosQueue = null;
StableLightPosStack skyLightWorldPosQueue = null;
try
{
blockLightPosQueue = StableLightPosStack.borrowStableLightPosArray();
skyLightPosQueue = StableLightPosStack.borrowStableLightPosArray();
blockLightWorldPosQueue = StableLightPosStack.borrowStableLightPosArray();
skyLightWorldPosQueue = StableLightPosStack.borrowStableLightPosArray();
@@ -92,7 +109,7 @@ public class DhLightingEngine
{
for (int zOffset = -1; zOffset <= 1; zOffset++)
{
DhChunkPos adjacentPos = new DhChunkPos(centerChunkPos.x + xOffset, centerChunkPos.z + zOffset);
DhChunkPos adjacentPos = new DhChunkPos(centerChunkPos.getX() + xOffset, centerChunkPos.getZ() + zOffset);
requestedAdjacentPositions.add(adjacentPos);
}
}
@@ -114,11 +131,14 @@ public class DhLightingEngine
// get and set the adjacent chunk's initial block lights
final DhBlockPos relLightBlockPos = PRIMARY_BLOCK_POS_REF.get();
final DhBlockPos relBlockPos = SECONDARY_BLOCK_POS_REF.get();
//==================//
// set block lights //
//==================//
ArrayList<DhBlockPos> blockLightPosList = chunk.getBlockLightPosList();
// get and set the adjacent chunk's initial block lights
final DhBlockPosMutable relLightBlockPos = PRIMARY_BLOCK_POS_REF.get();
ArrayList<DhBlockPos> blockLightPosList = chunk.getWorldBlockLightPosList();
for (int blockLightIndex = 0; blockLightIndex < blockLightPosList.size(); blockLightIndex++) // using iterators in high traffic areas can cause GC issues due to allocating a bunch of iterators, use an indexed for-loop instead
{
DhBlockPos blockLightPos = blockLightPosList.get(blockLightIndex);
@@ -127,14 +147,18 @@ public class DhLightingEngine
// get the light
IBlockStateWrapper blockState = chunk.getBlockState(relLightBlockPos);
int lightValue = blockState.getLightEmission();
blockLightPosQueue.push(blockLightPos.x, blockLightPos.y, blockLightPos.z, lightValue);
blockLightWorldPosQueue.push(blockLightPos.getX(), blockLightPos.getY(), blockLightPos.getZ(), lightValue);
// set the light
blockLightPos.mutateToChunkRelativePos(relBlockPos);
chunk.setDhBlockLight(relBlockPos.x, relBlockPos.y, relBlockPos.z, lightValue);
chunk.setDhBlockLight(relLightBlockPos.getX(), relLightBlockPos.getY(), relLightBlockPos.getZ(), lightValue);
}
//================//
// set sky lights //
//================//
// get and set the adjacent chunk's initial skylights,
// if the dimension has skylights
if (maxSkyLight > 0)
@@ -147,7 +171,7 @@ public class DhLightingEngine
{
for (int relZ = 0; relZ < LodUtil.CHUNK_WIDTH; relZ++)
{
// set each pos' sky light all the way down until a opaque block is hit
// set each pos' sky light all the way down until an opaque block is hit
for (int y = maxY; y >= minY; y--)
{
IBlockStateWrapper block = chunk.getBlockState(relX, y, relZ);
@@ -160,11 +184,11 @@ public class DhLightingEngine
// add sky light to the queue
DhBlockPos skyLightPos = new DhBlockPos(chunk.getMinBlockX() + relX, y, chunk.getMinBlockZ() + relZ);
skyLightPosQueue.push(skyLightPos.x, skyLightPos.y, skyLightPos.z, maxSkyLight);
skyLightWorldPosQueue.push(skyLightPos.getX(), skyLightPos.getY(), skyLightPos.getZ(), maxSkyLight);
// set the chunk's sky light
skyLightPos.mutateToChunkRelativePos(relBlockPos);
chunk.setDhSkyLight(relBlockPos.x, relBlockPos.y, relBlockPos.z, maxSkyLight);
skyLightPos.mutateToChunkRelativePos(relLightBlockPos);
chunk.setDhSkyLight(relLightBlockPos.getX(), relLightBlockPos.getY(), relLightBlockPos.getZ(), maxSkyLight);
}
}
}
@@ -180,14 +204,16 @@ public class DhLightingEngine
}
// block light
this.propagateLightPosList(blockLightPosQueue, adjacentChunkHolder,
(neighbourChunk, relBlockPos) -> neighbourChunk.getDhBlockLight(relBlockPos.x, relBlockPos.y, relBlockPos.z),
(neighbourChunk, relBlockPos, newLightValue) -> neighbourChunk.setDhBlockLight(relBlockPos.x, relBlockPos.y, relBlockPos.z, newLightValue));
this.propagateLightPosList(blockLightWorldPosQueue, adjacentChunkHolder,
(neighbourChunk, relBlockPos) -> neighbourChunk.getDhBlockLight(relBlockPos.getX(), relBlockPos.getY(), relBlockPos.getZ()),
(neighbourChunk, relBlockPos, newLightValue) -> neighbourChunk.setDhBlockLight(relBlockPos.getX(), relBlockPos.getY(), relBlockPos.getZ(), newLightValue),
true);
// sky light
this.propagateLightPosList(skyLightPosQueue, adjacentChunkHolder,
(neighbourChunk, relBlockPos) -> neighbourChunk.getDhSkyLight(relBlockPos.x, relBlockPos.y, relBlockPos.z),
(neighbourChunk, relBlockPos, newLightValue) -> neighbourChunk.setDhSkyLight(relBlockPos.x, relBlockPos.y, relBlockPos.z, newLightValue));
this.propagateLightPosList(skyLightWorldPosQueue, adjacentChunkHolder,
(neighbourChunk, relBlockPos) -> neighbourChunk.getDhSkyLight(relBlockPos.getX(), relBlockPos.getY(), relBlockPos.getZ()),
(neighbourChunk, relBlockPos, newLightValue) -> neighbourChunk.setDhSkyLight(relBlockPos.getX(), relBlockPos.getY(), relBlockPos.getZ(), newLightValue),
false);
}
catch (Exception e)
{
@@ -195,8 +221,8 @@ public class DhLightingEngine
}
finally
{
StableLightPosStack.returnStableLightPosArray(blockLightPosQueue);
StableLightPosStack.returnStableLightPosArray(skyLightPosQueue);
StableLightPosStack.returnStableLightPosArray(blockLightWorldPosQueue);
StableLightPosStack.returnStableLightPosArray(skyLightWorldPosQueue);
}
@@ -212,13 +238,14 @@ public class DhLightingEngine
/** Applies each {@link LightPos} from the queue to the given set of {@link IChunkWrapper}'s. */
private void propagateLightPosList(
StableLightPosStack lightPosQueue, AdjacentChunkHolder adjacentChunkHolder,
IGetLightFunc getLightFunc, ISetLightFunc setLightFunc)
IGetLightFunc getLightFunc, ISetLightFunc setLightFunc,
boolean propagatingBlockLights)
{
// these objects are saved so they can be mutated throughout the method,
// this reduces the number of allocations necessary, reducing GC pressure
final LightPos lightPos = new LightPos(0, 0, 0, 0);
final DhBlockPos neighbourBlockPos = PRIMARY_BLOCK_POS_REF.get();
final DhBlockPos relNeighbourBlockPos = SECONDARY_BLOCK_POS_REF.get();
final DhBlockPosMutable neighbourBlockPos = PRIMARY_BLOCK_POS_REF.get();
final DhBlockPosMutable relNeighbourBlockPos = SECONDARY_BLOCK_POS_REF.get();
// update each light position
@@ -239,14 +266,14 @@ public class DhLightingEngine
// only continue if the light position is inside one of our chunks
IChunkWrapper neighbourChunk = adjacentChunkHolder.getByBlockPos(neighbourBlockPos.x, neighbourBlockPos.z);
IChunkWrapper neighbourChunk = adjacentChunkHolder.getByBlockPos(neighbourBlockPos.getX(), neighbourBlockPos.getZ());
if (neighbourChunk == null)
{
// the light pos is outside our generator's range, ignore it
continue;
}
if (relNeighbourBlockPos.y < neighbourChunk.getMinNonEmptyHeight() || relNeighbourBlockPos.y > neighbourChunk.getMaxBuildHeight())
if (relNeighbourBlockPos.getY() < neighbourChunk.getMinNonEmptyHeight() || relNeighbourBlockPos.getY() > neighbourChunk.getMaxBuildHeight())
{
// the light pos is outside the chunk's min/max height,
// this can happen if given a chunk that hasn't finished generating
@@ -273,16 +300,103 @@ public class DhLightingEngine
// now that light has been propagated to this blockPos
// we need to queue it up so its neighbours can be propagated as well
lightPosQueue.push(neighbourBlockPos.x, neighbourBlockPos.y, neighbourBlockPos.z, targetLevel);
lightPosQueue.push(neighbourBlockPos.getX(), neighbourBlockPos.getY(), neighbourBlockPos.getZ(), targetLevel);
}
}
}
// can be enable if troubleshooting lighting issues
if (RENDER_BLOCK_LIGHT_WIREFRAME && propagatingBlockLights)
{
RenderDhLightValuesAsWireframe(adjacentChunkHolder, true);
}
else if (RENDER_SKY_LIGHT_WIREFRAME && !propagatingBlockLights)
{
RenderDhLightValuesAsWireframe(adjacentChunkHolder, false);
}
// propagation complete
}
//===========//
// debugging //
//===========//
/** Draw a wireframe representing each block's light value */
private static void RenderDhLightValuesAsWireframe(AdjacentChunkHolder adjacentChunkHolder, boolean renderBlockLights)
{
for (IChunkWrapper chunk : adjacentChunkHolder.chunkArray)
{
if (chunk == null)
{
continue;
}
int chunkMinX = chunk.getMinBlockX();
int chunkMinZ = chunk.getMinBlockZ();
int minY = chunk.getMinNonEmptyHeight();
int maxY = chunk.getMaxNonEmptyHeight();
// check each position's light
for (int x = 0; x < LodUtil.CHUNK_WIDTH; x++)
{
for (int z = 0; z < LodUtil.CHUNK_WIDTH; z++)
{
for (int y = minY; y < maxY; y++)
{
int lightValue = renderBlockLights? chunk.getDhBlockLight(x, y, z) : chunk.getDhSkyLight(x, y, z);
if (lightValue != LodUtil.MIN_MC_LIGHT)
{
// hotter colors for more intense light
Color color;
if (lightValue >= 14)
{
color = Color.WHITE;
}
else if (lightValue >= 10)
{
color = Color.PINK;
}
else if (lightValue >= 6)
{
color = Color.YELLOW;
}
else if (lightValue >= 4)
{
color = Color.ORANGE;
}
else
{
color = Color.RED;
}
// a color can be set to null if you only want to troubleshoot up to a certain light level
if (color != null)
{
DebugRenderer.makeParticle(
new DebugRenderer.BoxParticle(
new DebugRenderer.Box(DhSectionPos.encode((byte) 0, chunkMinX + x, chunkMinZ + z), y, y + 1, 0.2f, color),
10.0, 0f
)
);
}
}
}
}
}
}
}
//================//
// helper classes //
//================//
@@ -293,7 +407,7 @@ public class DhLightingEngine
@FunctionalInterface
interface ISetLightFunc { void setLight(IChunkWrapper chunk, DhBlockPos pos, int lightValue); }
private static class LightPos extends DhBlockPos
private static class LightPos extends DhBlockPosMutable
{
public int lightValue;
@@ -303,6 +417,11 @@ public class DhLightingEngine
this.lightValue = lightValue;
}
@Override
public String toString() { return this.lightValue+" - ["+ this.x +", "+ this.y +", "+ this.z +"]"; }
}
/**
@@ -402,9 +521,9 @@ public class DhLightingEngine
{
int subIndex = this.index * INTS_PER_LIGHT_POS;
pos.x = this.lightPositions.getInt(subIndex);
pos.y = this.lightPositions.getInt(subIndex + 1);
pos.z = this.lightPositions.getInt(subIndex + 2);
pos.setX(this.lightPositions.getInt(subIndex));
pos.setY(this.lightPositions.getInt(subIndex + 1));
pos.setZ(this.lightPositions.getInt(subIndex + 2));
pos.lightValue = this.lightPositions.getInt(subIndex + 3);
this.index--;
@@ -21,7 +21,7 @@ package com.seibel.distanthorizons.core.generation;
import com.seibel.distanthorizons.core.generation.tasks.IWorldGenTaskTracker;
import com.seibel.distanthorizons.core.generation.tasks.WorldGenResult;
import com.seibel.distanthorizons.core.pos.DhBlockPos2D;
import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos2D;
import com.seibel.distanthorizons.core.pos.DhSectionPos;
import com.seibel.distanthorizons.core.render.LodQuadTree;
@@ -27,7 +27,7 @@ import com.seibel.distanthorizons.core.dataObjects.fullData.sources.FullDataSour
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
import com.seibel.distanthorizons.core.generation.tasks.*;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.pos.DhBlockPos2D;
import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos2D;
import com.seibel.distanthorizons.core.pos.DhChunkPos;
import com.seibel.distanthorizons.core.pos.DhSectionPos;
import com.seibel.distanthorizons.core.config.Config;
@@ -55,6 +55,16 @@ public class WorldGenerationQueue implements IFullDataSourceRetrievalQueue, IDeb
private static final Logger LOGGER = DhLoggerBuilder.getLogger();
private static final IWrapperFactory WRAPPER_FACTORY = SingletonInjector.INSTANCE.get(IWrapperFactory.class);
/**
* Defines how many tasks can be queued per thread. <br><br>
*
* TODO the multiplier here should change dynamically based on how fast the generator is vs the queuing thread,
* if this is too high it may cause issues when moving,
* but if it is too low the generator threads won't have enough tasks to work on
*/
private static final int MAX_QUEUED_TASKS_PER_THREAD = 3;
private final IDhApiWorldGenerator generator;
/** contains the positions that need to be generated */
@@ -206,7 +216,7 @@ public class WorldGenerationQueue implements IFullDataSourceRetrievalQueue, IDeb
// queue generation tasks until the generator is full, or there are no more tasks to generate
boolean taskStarted = true;
while (!this.generator.isBusy() && taskStarted)
while (!this.isGeneratorBusy() && taskStarted)
{
taskStarted = this.startNextWorldGenTask(this.generationTargetPos);
if (!taskStarted)
@@ -233,6 +243,19 @@ public class WorldGenerationQueue implements IFullDataSourceRetrievalQueue, IDeb
}
});
}
public boolean isGeneratorBusy()
{
ThreadPoolExecutor executor = ThreadPoolUtil.getWorldGenExecutor();
if (executor == null)
{
// shouldn't happen, but just in case, don't queue more tasks
return true;
}
int worldGenThreadCount = Math.max(Config.Client.Advanced.MultiThreading.numberOfWorldGenerationThreads.get(), 1);
int maxWorldGenTaskCount = worldGenThreadCount * MAX_QUEUED_TASKS_PER_THREAD;
return executor.getQueue().size() > maxWorldGenTaskCount;
}
/**
* @param targetPos the position to center the generation around
@@ -433,8 +456,8 @@ public class WorldGenerationQueue implements IFullDataSourceRetrievalQueue, IDeb
case VANILLA_CHUNKS:
{
return this.generator.generateChunks(
chunkPosMin.x,
chunkPosMin.z,
chunkPosMin.getX(),
chunkPosMin.getZ(),
granularity,
targetDataDetail,
generatorMode,
@@ -459,8 +482,8 @@ public class WorldGenerationQueue implements IFullDataSourceRetrievalQueue, IDeb
case API_CHUNKS:
{
return this.generator.generateApiChunks(
chunkPosMin.x,
chunkPosMin.z,
chunkPosMin.getX(),
chunkPosMin.getZ(),
granularity,
targetDataDetail,
generatorMode,
@@ -469,10 +492,10 @@ public class WorldGenerationQueue implements IFullDataSourceRetrievalQueue, IDeb
{
try
{
FullDataSourceV2 dataSource = LodDataBuilder.createFromApiChunkData(dataPoints);
FullDataSourceV2 dataSource = LodDataBuilder.createFromApiChunkData(dataPoints, this.generator.runApiChunkValidation());
chunkDataConsumer.accept(dataSource);
}
catch (DataCorruptedException e)
catch (DataCorruptedException | IllegalArgumentException e)
{
LOGGER.error("World generator returned a corrupt chunk. Error: [" + e.getMessage() + "]. World generator disabled.", e);
Config.Client.Advanced.WorldGenerator.enableDistantGeneration.set(false);
@@ -19,28 +19,19 @@
package com.seibel.distanthorizons.core.level;
import com.seibel.distanthorizons.api.interfaces.render.IDhApiRenderableBoxGroup;
import com.seibel.distanthorizons.api.methods.events.abstractEvents.DhApiChunkModifiedEvent;
import com.seibel.distanthorizons.api.objects.math.DhApiVec3f;
import com.seibel.distanthorizons.api.objects.render.DhApiRenderableBox;
import com.seibel.distanthorizons.api.objects.render.DhApiRenderableBoxGroupShading;
import com.seibel.distanthorizons.core.config.Config;
import com.seibel.distanthorizons.core.dataObjects.fullData.sources.FullDataSourceV2;
import com.seibel.distanthorizons.core.dataObjects.transformers.ChunkToLodBuilder;
import com.seibel.distanthorizons.core.file.fullDatafile.DelayedFullDataSourceSaveCache;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.pos.DhBlockPos;
import com.seibel.distanthorizons.core.pos.DhChunkPos;
import com.seibel.distanthorizons.core.pos.DhSectionPos;
import com.seibel.distanthorizons.core.render.renderer.generic.BeaconRenderHandler;
import com.seibel.distanthorizons.core.render.renderer.generic.CloudRenderHandler;
import com.seibel.distanthorizons.core.render.renderer.generic.GenericObjectRenderer;
import com.seibel.distanthorizons.core.render.renderer.generic.GenericRenderObjectFactory;
import com.seibel.distanthorizons.core.sql.dto.BeaconBeamDTO;
import com.seibel.distanthorizons.core.sql.dto.ChunkHashDTO;
import com.seibel.distanthorizons.core.sql.repo.BeaconBeamRepo;
import com.seibel.distanthorizons.core.sql.repo.ChunkHashRepo;
import com.seibel.distanthorizons.core.util.LodUtil;
import com.seibel.distanthorizons.core.wrapperInterfaces.chunk.IChunkWrapper;
import com.seibel.distanthorizons.coreapi.DependencyInjection.ApiEventInjector;
import org.apache.logging.log4j.Logger;
@@ -49,18 +40,14 @@ import org.jetbrains.annotations.Nullable;
import java.io.File;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
public abstract class AbstractDhLevel implements IDhLevel
{
private static final Logger LOGGER = DhLoggerBuilder.getLogger();
public final ChunkToLodBuilder chunkToLodBuilder;
/** if this is null then the other handler is probably null too, but just in case */
@Nullable
public ChunkHashRepo chunkHashRepo;
@@ -68,9 +55,10 @@ public abstract class AbstractDhLevel implements IDhLevel
@Nullable
public BeaconBeamRepo beaconBeamRepo;
protected final DelayedFullDataSourceSaveCache delayedFullDataSourceSaveCache = new DelayedFullDataSourceSaveCache(this::onDataSourceSave, 2_000);
protected final DelayedFullDataSourceSaveCache delayedFullDataSourceSaveCache = new DelayedFullDataSourceSaveCache(this::onDataSourceSave, 500);
/** contains the {@link DhChunkPos} for each {@link DhSectionPos} that are queued to save via {@link AbstractDhLevel#delayedFullDataSourceSaveCache} */
protected final ConcurrentHashMap<Long, HashSet<DhChunkPos>> updatedChunkPosSetBySectionPos = new ConcurrentHashMap<>();
protected final ConcurrentHashMap<DhChunkPos, Integer> updatedChunkHashesByChunkPos = new ConcurrentHashMap<>();
/** Will be null if clouds shouldn't be rendered for this level. */
@Nullable
@@ -83,10 +71,7 @@ public abstract class AbstractDhLevel implements IDhLevel
// constructor //
//=============//
protected AbstractDhLevel()
{
this.chunkToLodBuilder = new ChunkToLodBuilder();
}
protected AbstractDhLevel() { }
/**
* Creating the repos requires access to the level file, which isn't
@@ -126,11 +111,15 @@ public abstract class AbstractDhLevel implements IDhLevel
GenericObjectRenderer genericRenderer = this.getGenericRenderer();
if (genericRenderer != null)
{
// only add clouds for certain dimension types
if (!this.getLevelWrapper().hasCeiling()
&& !this.getLevelWrapper().getDimensionType().isTheEnd())
// only client levels can render clouds
if (this instanceof IDhClientLevel)
{
this.cloudRenderHandler = new CloudRenderHandler(this, genericRenderer);
// only add clouds for certain dimension types
if (!this.getLevelWrapper().hasCeiling()
&& !this.getLevelWrapper().getDimensionType().isTheEnd())
{
this.cloudRenderHandler = new CloudRenderHandler((IDhClientLevel)this, genericRenderer);
}
}
@@ -152,7 +141,7 @@ public abstract class AbstractDhLevel implements IDhLevel
public int getUnsavedDataSourceCount() { return this.delayedFullDataSourceSaveCache.getUnsavedCount(); }
@Override
public void updateChunkAsync(IChunkWrapper chunkWrapper)
public void updateChunkAsync(IChunkWrapper chunkWrapper, int chunkHash)
{
FullDataSourceV2 dataSource = FullDataSourceV2.createFromChunk(chunkWrapper);
if (dataSource == null)
@@ -171,6 +160,7 @@ public abstract class AbstractDhLevel implements IDhLevel
chunkPosSet.add(chunkWrapper.getChunkPos());
return chunkPosSet;
});
this.updatedChunkHashesByChunkPos.put(chunkWrapper.getChunkPos(), chunkHash);
// batch updates to reduce overhead when flying around or breaking/placing a lot of blocks in an area
this.delayedFullDataSourceSaveCache.queueDataSourceForUpdateAndSave(dataSource);
@@ -185,9 +175,16 @@ public abstract class AbstractDhLevel implements IDhLevel
{
for (DhChunkPos chunkPos : updatedChunkPosSet)
{
// save after the data source has been updated to prevent saving the hash without the associated datasource
Integer chunkHash = this.updatedChunkHashesByChunkPos.remove(chunkPos);
if (this.chunkHashRepo != null && chunkHash != null)
{
this.chunkHashRepo.save(new ChunkHashDTO(chunkPos, chunkHash));
}
ApiEventInjector.INSTANCE.fireAllEvents(
DhApiChunkModifiedEvent.class,
new DhApiChunkModifiedEvent.EventParam(this.getLevelWrapper(), chunkPos.x, chunkPos.z));
new DhApiChunkModifiedEvent.EventParam(this.getLevelWrapper(), chunkPos.getX(), chunkPos.getZ()));
}
}
});
@@ -212,14 +209,6 @@ public abstract class AbstractDhLevel implements IDhLevel
ChunkHashDTO dto = this.chunkHashRepo.getByKey(pos);
return (dto != null) ? dto.chunkHash : 0;
}
@Override
public void setChunkHash(DhChunkPos pos, int chunkHash)
{
if (this.chunkHashRepo != null)
{
this.chunkHashRepo.save(new ChunkHashDTO(pos, chunkHash));
}
}
@@ -228,11 +217,12 @@ public abstract class AbstractDhLevel implements IDhLevel
//=================//
@Override
public void setBeaconBeamsForChunk(DhChunkPos chunkPos, List<BeaconBeamDTO> newBeamList)
public void updateBeaconBeamsForChunk(IChunkWrapper chunkToUpdate, ArrayList<IChunkWrapper> nearbyChunkList)
{
if (this.beaconRenderHandler != null)
{
this.beaconRenderHandler.setBeaconBeamsForChunk(chunkPos, newBeamList);
List<BeaconBeamDTO> activeBeamList = chunkToUpdate.getAllActiveBeacons(nearbyChunkList);
this.beaconRenderHandler.setBeaconBeamsForChunk(chunkToUpdate.getChunkPos(), activeBeamList);
}
}
@@ -262,8 +252,6 @@ public abstract class AbstractDhLevel implements IDhLevel
@Override
public void close()
{
this.chunkToLodBuilder.close();
if (this.chunkHashRepo != null)
{
this.chunkHashRepo.close();
@@ -27,7 +27,7 @@ import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
import com.seibel.distanthorizons.core.file.AbstractDataSourceHandler;
import com.seibel.distanthorizons.core.file.fullDatafile.FullDataSourceProviderV2;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.pos.DhBlockPos2D;
import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos2D;
import com.seibel.distanthorizons.core.render.LodQuadTree;
import com.seibel.distanthorizons.core.render.RenderBufferHandler;
import com.seibel.distanthorizons.core.render.renderer.generic.GenericObjectRenderer;
@@ -258,6 +258,8 @@ public class ClientLevelModule implements Closeable, AbstractDataSourceHandler.I
public void clearRenderCache()
{
this.clientLevel.getClientLevelWrapper().clearBlockColorCache();
ClientRenderState ClientRenderState = this.ClientRenderStateRef.get();
if (ClientRenderState != null && ClientRenderState.quadtree != null)
{
@@ -25,7 +25,7 @@ import com.seibel.distanthorizons.core.file.fullDatafile.FullDataSourceProviderV
import com.seibel.distanthorizons.core.file.fullDatafile.RemoteFullDataSourceProvider;
import com.seibel.distanthorizons.core.file.structure.AbstractSaveStructure;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.pos.DhBlockPos;
import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos;
import com.seibel.distanthorizons.core.render.RenderBufferHandler;
import com.seibel.distanthorizons.core.render.renderer.generic.GenericObjectRenderer;
import com.seibel.distanthorizons.core.wrapperInterfaces.block.IBlockStateWrapper;
@@ -86,7 +86,6 @@ public class DhClientLevel extends AbstractDhLevel implements IDhClientLevel
{
try
{
this.chunkToLodBuilder.tick();
this.clientside.clientTick();
}
catch (Exception e)
@@ -110,7 +109,7 @@ public class DhClientLevel extends AbstractDhLevel implements IDhClientLevel
//================//
@Override
public int computeBaseColor(DhBlockPos pos, IBiomeWrapper biome, IBlockStateWrapper block) { return this.levelWrapper.computeBaseColor(pos, biome, block); }
public int computeBaseColor(DhBlockPos pos, IBiomeWrapper biome, IBlockStateWrapper block) { return this.levelWrapper.getBlockColor(pos, biome, block); }
@Override
public IClientLevelWrapper getClientLevelWrapper() { return this.levelWrapper; }
@@ -28,8 +28,8 @@ import com.seibel.distanthorizons.core.render.RenderBufferHandler;
import com.seibel.distanthorizons.core.render.renderer.DebugRenderer;
import com.seibel.distanthorizons.core.file.structure.AbstractSaveStructure;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.pos.DhBlockPos;
import com.seibel.distanthorizons.core.pos.DhBlockPos2D;
import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos;
import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos2D;
import com.seibel.distanthorizons.core.render.renderer.generic.GenericObjectRenderer;
import com.seibel.distanthorizons.core.wrapperInterfaces.block.IBlockStateWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IProfilerWrapper;
@@ -95,7 +95,7 @@ public class DhClientServerLevel extends AbstractDhLevel implements IDhClientLev
{ this.clientside.renderDeferred(renderEventParam, profiler); }
@Override
public void serverTick() { this.chunkToLodBuilder.tick(); }
public void serverTick() { }
@Override
public void doWorldGen()
@@ -146,7 +146,7 @@ public class DhClientServerLevel extends AbstractDhLevel implements IDhClientLev
}
else
{
return clientLevel.computeBaseColor(pos, biome, block);
return clientLevel.getBlockColor(pos, biome, block);
}
}
@@ -154,10 +154,7 @@ public class DhClientServerLevel extends AbstractDhLevel implements IDhClientLev
public IClientLevelWrapper getClientLevelWrapper() { return MC_CLIENT.getWrappedClientLevel(); }
@Override
public void clearRenderCache()
{
clientside.clearRenderCache();
}
public void clearRenderCache() { this.clientside.clearRenderCache(); }
@Override
public IServerLevelWrapper getServerLevelWrapper() { return serverLevelWrapper; }
@@ -22,7 +22,7 @@ package com.seibel.distanthorizons.core.level;
import com.seibel.distanthorizons.core.dataObjects.fullData.sources.FullDataSourceV2;
import com.seibel.distanthorizons.core.file.fullDatafile.FullDataSourceProviderV2;
import com.seibel.distanthorizons.core.file.structure.AbstractSaveStructure;
import com.seibel.distanthorizons.core.pos.DhBlockPos2D;
import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos2D;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.render.RenderBufferHandler;
import com.seibel.distanthorizons.core.render.renderer.generic.GenericObjectRenderer;
@@ -65,7 +65,7 @@ public class DhServerLevel extends AbstractDhLevel implements IDhServerLevel
// methods //
//=========//
public void serverTick() { this.chunkToLodBuilder.tick(); }
public void serverTick() { }
@Override
public CompletableFuture<Void> updateDataSourcesAsync(FullDataSourceV2 data) { return this.getFullDataProvider().updateDataSourceAsync(data); }
@@ -20,7 +20,7 @@
package com.seibel.distanthorizons.core.level;
import com.seibel.distanthorizons.api.methods.events.sharedParameterObjects.DhApiRenderParam;
import com.seibel.distanthorizons.core.pos.DhBlockPos;
import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos;
import com.seibel.distanthorizons.core.wrapperInterfaces.block.IBlockStateWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IProfilerWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IBiomeWrapper;
@@ -25,11 +25,11 @@ import com.seibel.distanthorizons.core.file.structure.AbstractSaveStructure;
import com.seibel.distanthorizons.core.pos.DhChunkPos;
import com.seibel.distanthorizons.core.render.RenderBufferHandler;
import com.seibel.distanthorizons.core.render.renderer.generic.GenericObjectRenderer;
import com.seibel.distanthorizons.core.sql.dto.BeaconBeamDTO;
import com.seibel.distanthorizons.core.wrapperInterfaces.chunk.IChunkWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CompletableFuture;
@@ -45,11 +45,10 @@ public interface IDhLevel extends AutoCloseable
/** @return 0 if no hash is known */
int getChunkHash(DhChunkPos pos);
void setChunkHash(DhChunkPos pos, int chunkHash);
void updateChunkAsync(IChunkWrapper chunk);
void updateChunkAsync(IChunkWrapper chunk, int newChunkHash);
void loadBeaconBeamsInPos(long pos);
void setBeaconBeamsForChunk(DhChunkPos chunkPos, List<BeaconBeamDTO> beamList);
void updateBeaconBeamsForChunk(IChunkWrapper chunkToUpdate, ArrayList<IChunkWrapper> nearbyChunkList);
void unloadBeaconBeamsInPos(long pos);
FullDataSourceProviderV2 getFullDataProvider();
@@ -23,7 +23,7 @@ import com.seibel.distanthorizons.core.file.fullDatafile.GeneratedFullDataSource
import com.seibel.distanthorizons.core.generation.IFullDataSourceRetrievalQueue;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.logging.f3.F3Screen;
import com.seibel.distanthorizons.core.pos.DhBlockPos2D;
import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos2D;
import org.apache.logging.log4j.Logger;
import java.io.Closeable;
@@ -72,6 +72,9 @@ public class F3Screen
ThreadPoolExecutor worldGenPool = ThreadPoolUtil.getWorldGenExecutor();
ThreadPoolExecutor fileHandlerPool = ThreadPoolUtil.getFileHandlerExecutor();
ThreadPoolExecutor updatePool = ThreadPoolUtil.getUpdatePropagatorExecutor();
ThreadPoolExecutor lodBuilderPool = ThreadPoolUtil.getChunkToLodBuilderExecutor();
ThreadPoolExecutor bufferBuilderPool = ThreadPoolUtil.getBufferBuilderExecutor();
ThreadPoolExecutor bufferUploaderPool = ThreadPoolUtil.getBufferUploaderExecutor();
AbstractDhWorld world = SharedApi.getAbstractDhWorld();
Iterable<? extends IDhLevel> levelIterator = world.getAllLoadedLevels();
@@ -84,6 +87,9 @@ public class F3Screen
messageList.add(getThreadPoolStatString("World Gen", worldGenPool));//"World Gen Tasks: 40/5304, (in progress: 7)");
messageList.add(getThreadPoolStatString("File Handler", fileHandlerPool));
messageList.add(getThreadPoolStatString("Update Propagator", updatePool));
messageList.add(getThreadPoolStatString("LOD Builder", lodBuilderPool));
messageList.add(getThreadPoolStatString("Buffer Builder", bufferBuilderPool));
messageList.add(getThreadPoolStatString("Buffer Uploader", bufferUploaderPool));
messageList.add("");
// chunk updates
messageList.add(SharedApi.INSTANCE.getDebugMenuString());
@@ -1,231 +0,0 @@
/*
* This file is part of the Distant Horizons mod
* licensed under the GNU LGPL v3 License.
*
* Copyright (C) 2020-2023 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.distanthorizons.core.pos;
import com.seibel.distanthorizons.core.enums.EDhDirection;
import com.seibel.distanthorizons.core.util.LodUtil;
import org.jetbrains.annotations.Nullable;
import java.util.Objects;
public class DhBlockPos
{
public static final boolean DO_CHECKS = false;
// 26 bits wide as that just encompasses the maximum possible value
// of +- 30,000,000 blocks in each direction. Yes this packing method
// is how Minecraft packs it.
// NOTE: Remember to ALWAYS check that DHBlockPos packing is EXACTLY
// the same as Minecraft's!!!!
public static final int PACKED_X_LENGTH = 26;
public static final int PACKED_Z_LENGTH = 26;
public static final int PACKED_Y_LENGTH = 12;
public static final long PACKED_X_MASK = (1L << PACKED_X_LENGTH) - 1L;
public static final long PACKED_Y_MASK = (1L << PACKED_Y_LENGTH) - 1L;
public static final long PACKED_Z_MASK = (1L << PACKED_Z_LENGTH) - 1L;
public static final int PACKED_Y_OFFSET = 0;
public static final int PACKED_Z_OFFSET = PACKED_Y_LENGTH;
public static final int PACKED_X_OFFSET = PACKED_Y_LENGTH + PACKED_Z_LENGTH;
/** Useful for methods that need a position passed in but won't actually be used */
public static final DhBlockPos ZERO = new DhBlockPos(0, 0, 0);
public int x;
public int y;
public int z;
public DhBlockPos(int x, int y, int z)
{
this.x = x;
this.y = y;
this.z = z;
}
public DhBlockPos()
{
this(0, 0, 0);
}
public DhBlockPos(DhBlockPos pos)
{
this(pos.x, pos.y, pos.z);
}
public DhBlockPos(DhBlockPos2D pos, int y)
{
this(pos.x, y, pos.z);
}
public static long asLong(int x, int y, int z)
{
if (DO_CHECKS)
{
if ((x & ~PACKED_X_MASK) != 0)
{
throw new IllegalArgumentException("x is out of range: " + x);
}
if ((y & ~PACKED_Y_MASK) != 0)
{
throw new IllegalArgumentException("y is out of range: " + y);
}
if ((z & ~PACKED_Z_MASK) != 0)
{
throw new IllegalArgumentException("z is out of range: " + z);
}
}
return ((long) x & PACKED_X_MASK) << PACKED_X_OFFSET |
((long) y & PACKED_Y_MASK) << PACKED_Y_OFFSET |
((long) z & PACKED_Z_MASK) << PACKED_Z_OFFSET;
}
public static int getX(long packed)
{ // X is at the top
return (int) (packed << (64 - PACKED_X_OFFSET - PACKED_X_LENGTH) >> (64 - PACKED_X_LENGTH));
}
public static int getY(long packed)
{ // Y is at the bottom
return (int) (packed << (64 - PACKED_Y_OFFSET - PACKED_Y_LENGTH) >> (64 - PACKED_Y_LENGTH));
}
public static int getZ(long packed)
{ // Z is at the middle
return (int) (packed << (64 - PACKED_Z_OFFSET - PACKED_Z_LENGTH) >> (64 - PACKED_Z_LENGTH));
}
public DhBlockPos(long packed)
{
this(getX(packed), getY(packed), getZ(packed));
}
public long asLong()
{
return asLong(x, y, z);
}
/** creates a new {@link DhBlockPos} with the given offset from the current pos. */
public DhBlockPos offset(EDhDirection direction) { return this.mutateOffset(direction, null); }
/** if not null, mutates "mutablePos" so it matches the current pos after being offset. Otherwise creates a new {@link DhBlockPos}. */
public DhBlockPos mutateOffset(EDhDirection direction, @Nullable DhBlockPos mutablePos) { return this.mutateOffset(direction.getNormal().x, direction.getNormal().y, direction.getNormal().z, mutablePos); }
public DhBlockPos offset(int x, int y, int z) { return this.mutateOffset(x,y,z, null); }
public DhBlockPos mutateOffset(int x, int y, int z, @Nullable DhBlockPos mutablePos)
{
int newX = this.x + x;
int newY = this.y + y;
int newZ = this.z + z;
if (mutablePos != null)
{
mutablePos.x = newX;
mutablePos.y = newY;
mutablePos.z = newZ;
return mutablePos;
}
else
{
return new DhBlockPos(newX, newY, newZ);
}
}
/** Returns a new {@link DhBlockPos} limits to a value between 0 and 15 (inclusive) */
public DhBlockPos convertToChunkRelativePos() { return this.mutateToChunkRelativePos(null); }
/**
* Limits the block position to a value between 0 and 15 (inclusive)
* If not null, mutates "mutableBlockPos"
*/
public DhBlockPos mutateToChunkRelativePos(@Nullable DhBlockPos mutableBlockPos)
{
// move the position into the range -15 and +15
int relX = (this.x % LodUtil.CHUNK_WIDTH);
// if the position is negative move it into the range 0 and 15
relX = (relX < 0) ? (relX + LodUtil.CHUNK_WIDTH) : relX;
int relZ = (this.z % LodUtil.CHUNK_WIDTH);
relZ = (relZ < 0) ? (relZ + LodUtil.CHUNK_WIDTH) : relZ;
// the y value shouldn't need to be changed
if (mutableBlockPos != null)
{
mutableBlockPos.x = relX;
mutableBlockPos.y = this.y;
mutableBlockPos.z = relZ;
return mutableBlockPos;
}
else
{
return new DhBlockPos(relX, this.y, relZ);
}
}
/**
* Can be used to quickly determine the rough distance between two points<Br>
* or determine the taxi cab (manhattan) distance between two points. <Br><Br>
*
* Manhattan distance is equivalent to determining the distance between two street intersections,
* where you can only drive along each street, instead of directly to the other point.
*/
public int getManhattanDistance(DhBlockPos otherPos)
{
return Math.abs(this.x - otherPos.x) + Math.abs(this.y - otherPos.y) + Math.abs(this.z - otherPos.z);
}
@Override
public boolean equals(Object o)
{
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
DhBlockPos that = (DhBlockPos) o;
return x == that.x && y == that.y && z == that.z;
}
@Override
public int hashCode()
{
return Objects.hash(x, y, z);
}
@Override
public String toString()
{
return "DHBlockPos[" +
"" + x +
", " + y +
", " + z +
']';
}
public static void _DebugCheckPacker(int x, int y, int z, long expected)
{
long packed = asLong(x, y, z);
if (packed != expected)
{
throw new IllegalArgumentException("Packed values don't match: " + packed + " != " + expected);
}
DhBlockPos pos = new DhBlockPos(packed);
if (pos.x != x || pos.y != y || pos.z != z)
{
throw new IllegalArgumentException("Values after decode don't match: " + pos + " != " + x + ", " + y + ", " + z);
}
}
}
@@ -19,12 +19,23 @@
package com.seibel.distanthorizons.core.pos;
import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos;
import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos2D;
import com.seibel.distanthorizons.core.util.LodUtil;
/**
* immutable <br><br>
*
* Dev note: if for some reason we want to store these as longs check the old commits. <br>
* That logic was removed since it wasn't needed at the time
*/
public class DhChunkPos
{
public final int x; // Low 32 bits
public final int z; // High 32 bits
private final int x;
public int getX() { return x; }
private final int z;
public int getZ() { return z; }
/** cached to improve hashing speed */
public final int hashCode;
@@ -45,15 +56,14 @@ public class DhChunkPos
}
public DhChunkPos(DhBlockPos blockPos)
{
// >> 4 is the Same as div 16
this(blockPos.x >> 4, blockPos.z >> 4);
// >> 4 is the Same as divide by 16
this(blockPos.getX() >> 4, blockPos.getZ() >> 4);
}
public DhChunkPos(DhBlockPos2D blockPos)
{
// >> 4 is the Same as div 16
this(blockPos.x >> 4, blockPos.z >> 4);
}
public DhChunkPos(long packed) { this(getXFromPackedLong(packed), getZFromPackedLong(packed)); }
@@ -61,13 +71,8 @@ public class DhChunkPos
// methods //
//=========//
public DhBlockPos center() { return new DhBlockPos(8 + this.x << 4, 0, 8 + this.z << 4); }
public DhBlockPos corner() { return new DhBlockPos(this.x << 4, 0, this.z << 4); }
public static long toLong(int x, int z) { return ((long) x & 0xFFFFFFFFL) << 32 | (long) z & 0xFFFFFFFFL; }
private static int getXFromPackedLong(long chunkPos) { return (int) (chunkPos >> 32); }
private static int getZFromPackedLong(long chunkPos) { return (int) (chunkPos & 0xFFFFFFFFL); }
public DhBlockPos centerBlockPos() { return new DhBlockPos(8 + this.x << 4, 0, 8 + this.z << 4); }
public DhBlockPos minCornerBlockPos() { return new DhBlockPos(this.x << 4, 0, this.z << 4); }
public int getMinBlockX() { return this.x << 4; }
public int getMinBlockZ() { return this.z << 4; }
@@ -81,11 +86,10 @@ public class DhChunkPos
int maxBlockX = minBlockX + LodUtil.CHUNK_WIDTH;
int maxBlockZ = minBlockZ + LodUtil.CHUNK_WIDTH;
return minBlockX <= pos.x && pos.x <= maxBlockX
&& minBlockZ <= pos.z && pos.z <= maxBlockZ;
return minBlockX <= pos.getX() && pos.getX() < maxBlockX
&& minBlockZ <= pos.getZ() && pos.getZ() < maxBlockZ;
}
public long getLong() { return toLong(this.x, this.z); }
//================//
@@ -116,39 +120,4 @@ public class DhChunkPos
@Override
public String toString() { return "C[" + this.x + "," + this.z + "]"; }
//=======================//
// static helper methods //
//=======================//
public static void _DebugCheckPacker(int x, int z, long expected)
{
long packed = toLong(x, z);
if (packed != expected)
{
throw new IllegalArgumentException("Packed values don't match: " + packed + " != " + expected);
}
DhChunkPos pos = new DhChunkPos(packed);
if (pos.x != x || pos.z != z)
{
throw new IllegalArgumentException("Values after decode don't match: " + pos + " != " + x + ", " + z);
}
}
/** @return true if testPos is within the area defined by the min and max positions. */
public static boolean isChunkPosBetween(DhChunkPos minChunkPos, DhChunkPos testPos, DhChunkPos maxChunkPos)
{
int minChunkX = Math.min(minChunkPos.x, maxChunkPos.x);
int minChunkZ = Math.min(minChunkPos.z, maxChunkPos.z);
int maxChunkX = Math.max(minChunkPos.x, maxChunkPos.x);
int maxChunkZ = Math.max(minChunkPos.z, maxChunkPos.z);
return minChunkX <= testPos.x && testPos.x <= maxChunkX &&
minChunkZ <= testPos.z && testPos.z <= maxChunkZ;
}
}
@@ -20,6 +20,7 @@
package com.seibel.distanthorizons.core.pos;
import com.seibel.distanthorizons.core.dataObjects.fullData.sources.FullDataSourceV2;
import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos2D;
import com.seibel.distanthorizons.coreapi.util.BitShiftUtil;
import com.seibel.distanthorizons.core.util.LodUtil;
import org.jetbrains.annotations.NotNull;
@@ -47,6 +48,10 @@ public class DhLodPos implements Comparable<DhLodPos>
//==============//
// constructors //
//==============//
public DhLodPos(byte detailLevel, int x, int z)
{
this.detailLevel = detailLevel;
@@ -36,6 +36,10 @@ public class DhLodUnit
//==============//
// constructors //
//==============//
public DhLodUnit(byte detailLevel, int numberOfLodSectionsWide)
{
this.detailLevel = detailLevel;
@@ -43,6 +47,11 @@ public class DhLodUnit
}
//=========//
// methods //
//=========//
/** @return the size of this LOD unit in Minecraft blocks */
public int toBlockWidth() { return BitShiftUtil.pow(this.numberOfLodSectionsWide, this.detailLevel); }
/** @return the LOD Unit relative to the given block width and detail level */
@@ -21,6 +21,8 @@ package com.seibel.distanthorizons.core.pos;
import com.seibel.distanthorizons.core.dataObjects.fullData.sources.FullDataSourceV2;
import com.seibel.distanthorizons.core.enums.EDhDirection;
import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos;
import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos2D;
import com.seibel.distanthorizons.core.util.LodUtil;
import com.seibel.distanthorizons.coreapi.util.BitShiftUtil;
@@ -34,11 +36,11 @@ import java.util.function.LongConsumer;
* For the specifics of how they compare can be viewed in the constants {@link #SECTION_BLOCK_DETAIL_LEVEL},
* {@link #SECTION_CHUNK_DETAIL_LEVEL}, and {@link #SECTION_REGION_DETAIL_LEVEL}).<br><br>
*
* <strong>Why does the smallest render section represent 2x2 MC chunks (section detail level 6)? </strong> <br>
* <strong>Why does the smallest render section represent 4x4 MC chunks (section detail level 6)? </strong> <br>
* A section defines what unit the quad tree works in, because of that we don't want that unit to be too big or too small. <br>
* <strong>Too small</strong>, and we'll have 1,000s of sections running around, all needing individual files and render buffers.<br>
* <strong>Too big</strong>, and the LOD dropoff will be very noticeable.<br>
* With those thoughts in mind we decided on a smallest section size of 32 data points square (IE 2x2 chunks).
* With those thoughts in mind we decided on a smallest section size of 64 data points square (IE 4x4 chunks).
*
* @author Leetom
*/
@@ -105,16 +107,16 @@ public class DhSectionPos
/** Returns the section pos at the requested detail level containing the given BlockPos */
public static long encodeContaining(byte outputSectionDetailLevel, DhBlockPos pos)
{
int sectionPosX = getXOrZSectionPosFromChunkOrBlockPos(pos.x, false);
int sectionPosZ = getXOrZSectionPosFromChunkOrBlockPos(pos.z, false);
int sectionPosX = getXOrZSectionPosFromChunkOrBlockPos(pos.getX(), false);
int sectionPosZ = getXOrZSectionPosFromChunkOrBlockPos(pos.getZ(), false);
long blockPos = DhSectionPos.encode(DhSectionPos.SECTION_BLOCK_DETAIL_LEVEL, sectionPosX, sectionPosZ);
return convertToDetailLevel(blockPos, outputSectionDetailLevel);
}
/** Returns the section pos at the requested detail level containing the given ChunkPos */
public static long encodeContaining(byte outputSectionDetailLevel, DhChunkPos pos)
{
int sectionPosX = getXOrZSectionPosFromChunkOrBlockPos(pos.x, true);
int sectionPosZ = getXOrZSectionPosFromChunkOrBlockPos(pos.z, true);
int sectionPosX = getXOrZSectionPosFromChunkOrBlockPos(pos.getX(), true);
int sectionPosZ = getXOrZSectionPosFromChunkOrBlockPos(pos.getZ(), true);
long blockPos = DhSectionPos.encode(DhSectionPos.SECTION_BLOCK_DETAIL_LEVEL, sectionPosX, sectionPosZ);
return convertToDetailLevel(blockPos, outputSectionDetailLevel);
}
@@ -23,15 +23,23 @@ import com.seibel.distanthorizons.coreapi.util.MathUtil;
import java.util.Objects;
/** immutable */
public class Pos2D
{
public static final Pos2D ZERO = new Pos2D(0, 0);
public final int x;
public final int y;
private final int x;
public int getX() { return this.x; }
private final int y;
public int getY() { return this.y; }
//==============//
// constructors //
//==============//
public Pos2D(int x, int y)
{
this.x = x;
@@ -40,6 +48,11 @@ public class Pos2D
//======//
// math //
//======//
public Pos2D add(Pos2D other) { return new Pos2D(this.x + other.x, this.y + other.y); }
public Pos2D subtract(Pos2D other) { return new Pos2D(this.x - other.x, this.y - other.y); }
public Pos2D subtract(int value) { return new Pos2D(this.x - value, this.y - value); }
@@ -72,10 +85,17 @@ public class Pos2D
//================//
// base overrides //
//================//
@Override
public int hashCode() { return Objects.hash(this.x, this.y); }
@Override
public String toString() { return "[" + this.x + ", " + this.y + "]"; }
@Override
public boolean equals(Object otherObj)
{
if (otherObj == this)
@@ -0,0 +1,194 @@
/*
* This file is part of the Distant Horizons mod
* licensed under the GNU LGPL v3 License.
*
* Copyright (C) 2020-2023 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.distanthorizons.core.pos.blockPos;
import com.seibel.distanthorizons.core.enums.EDhDirection;
import com.seibel.distanthorizons.core.util.LodUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Objects;
/**
* immutable <br><br>
*
* Dev note: if for some reason we want to store these as longs check the old commits. <br>
* That logic was removed since it wasn't needed at the time.
*
* @see DhBlockPosMutable
*/
public class DhBlockPos
{
/** Useful for methods that need a position passed in but won't actually be used */
public static final DhBlockPos ZERO = new DhBlockPos(0, 0, 0);
protected int x;
public int getX() { return this.x; }
protected int y;
public int getY() { return this.y; }
protected int z;
public int getZ() { return this.z; }
//==============//
// constructors //
//==============//
public DhBlockPos(int x, int y, int z)
{
this.x = x;
this.y = y;
this.z = z;
}
public DhBlockPos() { this(0, 0, 0); }
public DhBlockPos(DhBlockPos pos) { this(pos.x, pos.y, pos.z); }
public DhBlockPos(DhBlockPos2D pos, int y) { this(pos.x, y, pos.z); }
//========//
// offset //
//========//
/** creates a new {@link DhBlockPos} with the given offset from the current pos. */
public DhBlockPos createOffset(EDhDirection direction) { return this.mutateOrCreateOffset(direction.getNormal().x, direction.getNormal().y, direction.getNormal().z, null); }
/** if not null, mutates "mutablePos" so it matches the current pos after being offset. Otherwise creates a new {@link DhBlockPos}. */
public void mutateOffset(EDhDirection direction, @NotNull DhBlockPosMutable mutablePos) { this.mutateOrCreateOffset(direction.getNormal().x, direction.getNormal().y, direction.getNormal().z, mutablePos); }
public DhBlockPos createOffset(int x, int y, int z) { return this.mutateOrCreateOffset(x,y,z, null); }
public void mutateOffset(int x, int y, int z, @NotNull DhBlockPosMutable mutablePos) { this.mutateOrCreateOffset(x, y, z, mutablePos); }
protected DhBlockPos mutateOrCreateOffset(int x, int y, int z, @Nullable DhBlockPosMutable mutablePos)
{
int newX = this.x + x;
int newY = this.y + y;
int newZ = this.z + z;
if (mutablePos != null)
{
mutablePos.x = newX;
mutablePos.y = newY;
mutablePos.z = newZ;
return mutablePos;
}
else
{
return new DhBlockPos(newX, newY, newZ);
}
}
//================//
// chunk relative //
//================//
/** Returns a new {@link DhBlockPos} limited to a value between 0 and 15 (inclusive) */
public DhBlockPos createChunkRelativePos() { return this.mutateOrCreateChunkRelativePos(null); }
/** Limits the input {@link DhBlockPos} to a value between 0 and 15 (inclusive) */
public void mutateToChunkRelativePos(DhBlockPosMutable mutableBlockPos) { this.mutateOrCreateChunkRelativePos(mutableBlockPos); }
/**
* Limits the block position to a value between 0 and 15 (inclusive)
* If not null, mutates "mutableBlockPos"
*
* @return the mutated or created {@link DhBlockPos}
*/
protected DhBlockPos mutateOrCreateChunkRelativePos(@Nullable DhBlockPosMutable mutableBlockPos)
{
int relX = convertWorldPosToChunkRelative(this.x);
// the y value shouldn't need to be changed
int relZ = convertWorldPosToChunkRelative(this.z);
if (mutableBlockPos != null)
{
mutableBlockPos.x = relX;
mutableBlockPos.y = this.y;
mutableBlockPos.z = relZ;
return mutableBlockPos;
}
else
{
return new DhBlockPos(relX, this.y, relZ);
}
}
protected static int convertWorldPosToChunkRelative(int xOrZ)
{
// move the position into the range -15 and +15
int relPos = (xOrZ % LodUtil.CHUNK_WIDTH);
// if the position is negative move it into the range 0 and 15
relPos = (relPos < 0) ? (relPos + LodUtil.CHUNK_WIDTH) : relPos;
return relPos;
}
//==========//
// distance //
//==========//
/**
* Can be used to quickly determine the rough distance between two points<Br>
* or determine the taxi cab (manhattan) distance between two points. <Br><Br>
*
* Manhattan distance is equivalent to determining the distance between two street intersections,
* where you can only drive along each street, instead of directly to the other point.
*/
public int getManhattanDistance(DhBlockPos otherPos)
{ return Math.abs(this.x - otherPos.x) + Math.abs(this.y - otherPos.y) + Math.abs(this.z - otherPos.z); }
//================//
// base overrides //
//================//
@Override
public boolean equals(Object obj)
{
if (this == obj)
{
return true;
}
else if (obj == null || this.getClass() != obj.getClass())
{
return false;
}
else
{
DhBlockPos that = (DhBlockPos) obj;
return this.x == that.x && this.y == that.y && this.z == that.z;
}
}
@Override
public int hashCode() { return Objects.hash(this.x, this.y, this.z); }
@Override
public String toString() { return "DHBlockPos["+ this.x +", "+ this.y +", "+ this.z +"]"; }
}
@@ -17,10 +17,12 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.distanthorizons.core.pos;
package com.seibel.distanthorizons.core.pos.blockPos;
import com.seibel.distanthorizons.core.pos.Pos2D;
import com.seibel.distanthorizons.coreapi.util.MathUtil;
/** immutable */
public class DhBlockPos2D
{
public static final DhBlockPos2D ZERO = new DhBlockPos2D(0, 0);
@@ -41,11 +43,11 @@ public class DhBlockPos2D
public DhBlockPos2D(DhBlockPos blockPos)
{
this.x = blockPos.x;
this.z = blockPos.z;
this.x = blockPos.getX();
this.z = blockPos.getZ();
}
public static DhBlockPos2D fromPos2D(Pos2D pos) { return new DhBlockPos2D(pos.x, pos.y); }
public static DhBlockPos2D fromPos2D(Pos2D pos) { return new DhBlockPos2D(pos.getX(), pos.getY()); }
@@ -0,0 +1,66 @@
/*
* This file is part of the Distant Horizons mod
* licensed under the GNU LGPL v3 License.
*
* Copyright (C) 2020-2023 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.distanthorizons.core.pos.blockPos;
import com.seibel.distanthorizons.core.enums.EDhDirection;
public class DhBlockPosMutable extends DhBlockPos
{
/** Useful for methods that need a position passed in but won't actually be used */
public static final DhBlockPosMutable ZERO = new DhBlockPosMutable(0, 0, 0);
public void setX(int x) { this.x = x; }
public void setY(int y) { this.y = y; }
public void setZ(int z) { this.z = z; }
//==============//
// constructors //
//==============//
public DhBlockPosMutable(int x, int y, int z) { super(x,y,z); }
public DhBlockPosMutable() { super(0, 0, 0); }
public DhBlockPosMutable(DhBlockPos pos) { super(pos); }
public DhBlockPosMutable(DhBlockPos2D pos, int y) { super(pos.x, y, pos.z); }
//========//
// offset //
//========//
/** @see DhBlockPos#createOffset(EDhDirection) */
public DhBlockPosMutable createOffset(EDhDirection direction) { return new DhBlockPosMutable(super.mutateOrCreateOffset(direction.getNormal().x, direction.getNormal().y, direction.getNormal().z, null)); }
/** @see DhBlockPos#createOffset(int, int, int) */
public DhBlockPosMutable createOffset(int x, int y, int z) { return new DhBlockPosMutable(this.mutateOrCreateOffset(x,y,z, null)); }
//================//
// chunk relative //
//================//
public DhBlockPosMutable createChunkRelativePos() { return new DhBlockPosMutable(this.mutateOrCreateChunkRelativePos(null)); }
}
@@ -54,10 +54,6 @@ public class DhFrustumBounds implements IDhApiCullingFrustum
Vector3f lodMin = new Vector3f(lodBlockPosMinX, this.worldMinY, lodBlockPosMinZ);
Vector3f lodMax = new Vector3f(lodBlockPosMinX + lodBlockWidth, this.worldMaxY, lodBlockPosMinZ + lodBlockWidth);
if (lodMax.x < this.boundsMin.x || lodMin.x > this.boundsMax.x) return false;
if (lodMax.z < this.boundsMin.z || lodMin.z > this.boundsMax.z) return false;
if (this.worldMaxY < this.boundsMin.y || this.worldMinY > this.boundsMax.y) return false;
return this.frustum.testAab(lodMin, lodMax);
}
@@ -19,19 +19,16 @@
package com.seibel.distanthorizons.core.render;
import com.seibel.distanthorizons.api.enums.config.EDhApiHorizontalQuality;
import com.seibel.distanthorizons.core.config.Config;
import com.seibel.distanthorizons.core.config.listeners.ConfigChangeListener;
import com.seibel.distanthorizons.core.dataObjects.render.ColumnRenderSource;
import com.seibel.distanthorizons.core.enums.EDhDirection;
import com.seibel.distanthorizons.core.file.fullDatafile.FullDataSourceProviderV2;
import com.seibel.distanthorizons.core.level.IDhClientLevel;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.pos.DhBlockPos2D;
import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos2D;
import com.seibel.distanthorizons.core.pos.DhSectionPos;
import com.seibel.distanthorizons.core.render.renderer.DebugRenderer;
import com.seibel.distanthorizons.core.render.renderer.IDebugRenderable;
import com.seibel.distanthorizons.core.sql.repo.BeaconBeamRepo;
import com.seibel.distanthorizons.core.util.LodUtil;
import com.seibel.distanthorizons.core.util.ThreadUtil;
import com.seibel.distanthorizons.core.util.objects.quadTree.QuadNode;
@@ -74,7 +71,6 @@ public class LodQuadTree extends QuadTree<LodRenderSection> implements IDebugRen
*/
private final ConcurrentLinkedQueue<Long> sectionsToReload = new ConcurrentLinkedQueue<>();
private final IDhClientLevel level; //FIXME: Proper hierarchy to remove this reference!
private final ConfigChangeListener<EDhApiHorizontalQuality> horizontalScaleChangeListener;
private final ReentrantLock treeReadWriteLock = new ReentrantLock();
private final AtomicBoolean fullDataRetrievalQueueRunning = new AtomicBoolean(false);
@@ -110,8 +106,6 @@ public class LodQuadTree extends QuadTree<LodRenderSection> implements IDebugRen
this.level = level;
this.fullDataSourceProvider = fullDataSourceProvider;
this.blockRenderDistanceDiameter = viewDiameterInBlocks;
this.horizontalScaleChangeListener = new ConfigChangeListener<>(Config.Client.Advanced.Graphics.Quality.horizontalQuality, (newHorizontalScale) -> this.onHorizontalQualityChange());
}
@@ -151,7 +145,7 @@ public class LodQuadTree extends QuadTree<LodRenderSection> implements IDebugRen
}
catch (Exception e)
{
LOGGER.error("Quad Tree tick exception for dimension: " + this.level.getClientLevelWrapper().getDimensionType().getDimensionName() + ", exception: " + e.getMessage(), e);
LOGGER.error("Quad Tree tick exception for dimension: " + this.level.getLevelWrapper().getDimensionType().getDimensionName() + ", exception: " + e.getMessage(), e);
}
finally
{
@@ -192,7 +186,7 @@ public class LodQuadTree extends QuadTree<LodRenderSection> implements IDebugRen
try
{
LodRenderSection renderSection = this.getValue(pos);
if (renderSection != null && renderSection.renderingEnabled)
if (renderSection != null && renderSection.getRenderingEnabled())
{
renderSection.uploadRenderDataToGpuAsync();
}
@@ -256,11 +250,12 @@ public class LodQuadTree extends QuadTree<LodRenderSection> implements IDebugRen
ArrayList<LodRenderSection> nodesNeedingRetrieval,
ArrayList<LodRenderSection> nodesNeedingLoading)
{
//===============================//
// node and render section setup //
//===============================//
//=====================//
// get/create the node //
// and render section //
//=====================//
// make sure the node is created
// create the node
if (quadNode == null && this.isSectionPosInBounds(sectionPos)) // the position bounds should only fail when at the edge of the user's render distance
{
rootNode.setValue(sectionPos, new LodRenderSection(sectionPos, this, this.level, this.fullDataSourceProvider));
@@ -272,15 +267,12 @@ public class LodQuadTree extends QuadTree<LodRenderSection> implements IDebugRen
return false;
}
// make sure the render section is created
// make sure the render section is created (shouldn't be necessary, but just in case)
LodRenderSection renderSection = quadNode.value;
// create a new render section if missing
if (renderSection == null)
{
LodRenderSection newRenderSection = new LodRenderSection(sectionPos, this, this.level, this.fullDataSourceProvider);
rootNode.setValue(sectionPos, newRenderSection);
renderSection = newRenderSection; // TODO this never seemed to be called, is it necessary?
renderSection = new LodRenderSection(sectionPos, this, this.level, this.fullDataSourceProvider);
rootNode.setValue(sectionPos, renderSection);
}
@@ -293,16 +285,18 @@ public class LodQuadTree extends QuadTree<LodRenderSection> implements IDebugRen
//byte expectedDetailLevel = DhSectionPos.SECTION_MINIMUM_DETAIL_LEVEL + 3; // can be used instead of the following logic for testing
byte expectedDetailLevel = this.calculateExpectedDetailLevel(playerPos, sectionPos);
expectedDetailLevel = (byte) Math.min(expectedDetailLevel, this.minRenderDetailLevel);
expectedDetailLevel += DhSectionPos.SECTION_BLOCK_DETAIL_LEVEL;
expectedDetailLevel += DhSectionPos.SECTION_MINIMUM_DETAIL_LEVEL;
if (DhSectionPos.getDetailLevel(sectionPos) > expectedDetailLevel)
{
// section detail level too high //
boolean thisPosIsRendering = renderSection.renderingEnabled;
//=======================//
// detail level too high //
//=======================//
boolean thisPosIsRendering = renderSection.getRenderingEnabled();
boolean allChildrenSectionsAreLoaded = true;
// recursively update all child render sections
// recursively update each child render section
for (int i = 0; i < 4; i++)
{
QuadNode<LodRenderSection> childNode = quadNode.getChildByIndex(i);
@@ -310,6 +304,7 @@ public class LodQuadTree extends QuadTree<LodRenderSection> implements IDebugRen
allChildrenSectionsAreLoaded = childSectionLoaded && allChildrenSectionsAreLoaded;
}
if (!allChildrenSectionsAreLoaded)
{
// not all child positions are loaded yet, or this section is out of render range
@@ -317,111 +312,90 @@ public class LodQuadTree extends QuadTree<LodRenderSection> implements IDebugRen
}
else
{
if (renderSection.renderingEnabled
&& Config.Client.Advanced.Debugging.DebugWireframe.showRenderSectionStatus.get())
// onRenderingDisabled() needs to be fired before the children are enabled so beacons render correctly
if (renderSection.getRenderingEnabled())
{
// show that this position has just been disabled
DebugRenderer.makeParticle(
new DebugRenderer.BoxParticle(
new DebugRenderer.Box(renderSection.pos, 128f, 156f, 0.09f, Color.CYAN.darker()),
0.2, 32f
)
);
renderSection.onRenderingDisabled();
// this position's rendering has been disabled due to children being rendered
DebugRenderer.makeParticle(new DebugRenderer.BoxParticle(new DebugRenderer.Box(renderSection.pos, 128f, 156f, 0.09f, Color.WHITE), 0.2, 32f));
}
// all child positions are loaded, disable this section and enable its children.
if (renderSection.renderingEnabled)
{
this.level.unloadBeaconBeamsInPos(renderSection.pos);
}
renderSection.renderingEnabled = false;
// walk back down the tree and enable the child sections //TODO there are probably more efficient ways of doing this, but this will work for now
// walk back down the tree and enable each child section
for (int i = 0; i < 4; i++)
{
QuadNode<LodRenderSection> childNode = quadNode.getChildByIndex(i);
boolean childSectionLoaded = this.recursivelyUpdateRenderSectionNode(playerPos, rootNode, childNode, DhSectionPos.getChildByIndex(sectionPos, i), parentSectionIsRendering, nodesNeedingRetrieval, nodesNeedingLoading);
allChildrenSectionsAreLoaded = childSectionLoaded && allChildrenSectionsAreLoaded;
}
if (!allChildrenSectionsAreLoaded)
{
// FIXME having world generation enabled in a pre-generated world that doesn't have any DH data can cause this to happen
// surprisingly reloadPos() doesn't appear to be the culprit, maybe there is an issue with reloading/changing the full data source?
//LOGGER.warn("Potential QuadTree concurrency issue. All child sections should be enabled and ready to render for pos: "+DhSectionPos.toString(sectionPos));
this.recursivelyUpdateRenderSectionNode(playerPos, rootNode, childNode, DhSectionPos.getChildByIndex(sectionPos, i), parentSectionIsRendering, nodesNeedingRetrieval, nodesNeedingLoading);
}
// disabling rendering must be done after the children are enabled
// otherwise holes may appear in the world, overlaps are less noticeable
renderSection.setRenderingEnabled(false);
// this section is now being rendered via its children
return allChildrenSectionsAreLoaded;
return true;
}
}
// TODO this should only equal the expected detail level, the (expectedDetailLevel-1) is a temporary fix to prevent corners from being cut out
else if (DhSectionPos.getDetailLevel(sectionPos) == expectedDetailLevel || DhSectionPos.getDetailLevel(sectionPos) == expectedDetailLevel - 1)
{
// this is the detail level we want to render //
//======================//
// desired detail level //
//======================//
/* Can be uncommented to easily debug a single render section. */
/* Don't forget the disableRendering() at the bottom though. */
//if (sectionPos.getDetailLevel() == 10
// &&
// (
// sectionPos.getX() == 0 &&
// sectionPos.getZ() == -4
// ))
// prepare this section for rendering
if (!renderSection.gpuUploadInProgress() && renderSection.renderBuffer == null)
{
// prepare this section for rendering
// TODO this should fire for the lowest detail level first to improve loading speed
if (!renderSection.gpuUploadInProgress() && renderSection.renderBuffer == null)
{
nodesNeedingLoading.add(renderSection);
}
nodesNeedingLoading.add(renderSection);
}
if (Config.Client.Advanced.Debugging.DebugWireframe.showQuadTreeRenderStatus.get())
{
this.debugRenderSections.add(renderSection);
}
// queue world gen if needed
if (!renderSection.isFullyGenerated())
{
nodesNeedingRetrieval.add(renderSection);
}
// wait for the parent to disable before enabling this section, so we don't overdraw/overlap render sections
if (!parentSectionIsRendering && renderSection.canRender())
// update debug if needed
if (Config.Client.Advanced.Debugging.DebugWireframe.showQuadTreeRenderStatus.get())
{
this.debugRenderSections.add(renderSection);
}
// wait for the parent to disable before enabling this section, so we don't have a hole
if (!parentSectionIsRendering && renderSection.canRender())
{
// if rendering is already enabled we don't have to re-enable it
if (!renderSection.getRenderingEnabled())
{
// if rendering is already enabled we don't have to re-enable it
if (!renderSection.renderingEnabled)
renderSection.setRenderingEnabled(true);
// disabling rendering must be done after the parent is enabled
// otherwise holes may appear in the world, overlaps are less noticeable
quadNode.deleteAllChildren((childRenderSection) ->
{
renderSection.renderingEnabled = true;
this.level.loadBeaconBeamsInPos(renderSection.pos);
// delete/disable children, all of them will be a lower detail level than requested
quadNode.deleteAllChildren((childRenderSection) ->
if (childRenderSection != null)
{
if (childRenderSection != null)
if (childRenderSection.getRenderingEnabled())
{
if (childRenderSection.renderingEnabled)
{
// show that this position's rendering has been disabled due to a parent rendering
DebugRenderer.makeParticle(
new DebugRenderer.BoxParticle(
new DebugRenderer.Box(childRenderSection.pos, 128f, 156f, 0.09f, Color.MAGENTA.darker()),
0.2, 32f
)
);
}
childRenderSection.renderingEnabled = false;
childRenderSection.close();
// this position's rendering has been disabled due to a parent rendering
DebugRenderer.makeParticle(new DebugRenderer.BoxParticle(new DebugRenderer.Box(childRenderSection.pos, 128f, 156f, 0.09f, Color.MAGENTA),0.2, 32f));
}
});
}
}
if (!renderSection.isFullyGenerated())
{
nodesNeedingRetrieval.add(renderSection);
childRenderSection.setRenderingEnabled(false);
childRenderSection.onRenderingDisabled();
childRenderSection.close();
}
});
// onRenderingEnabled() needs to be fired after the children are disabled so beacons render correctly
renderSection.onRenderingEnabled();
}
}
//else
//{
// renderSection.disableRendering();
//}
return renderSection.canRender();
}
@@ -611,12 +585,6 @@ public class LodQuadTree extends QuadTree<LodRenderSection> implements IDebugRen
}
//==================//
// config listeners //
//==================//
private void onHorizontalQualityChange() { this.clearRenderDataCache(); }
//===========//
// debugging //
@@ -678,8 +646,6 @@ public class LodQuadTree extends QuadTree<LodRenderSection> implements IDebugRen
{
LOGGER.info("Shutting down " + LodQuadTree.class.getSimpleName() + "...");
this.horizontalScaleChangeListener.close();
DebugRenderer.unregister(this, Config.Client.Advanced.Debugging.DebugWireframe.showQuadTreeRenderStatus);
Iterator<QuadNode<LodRenderSection>> nodeIterator = this.nodeIterator();
@@ -23,17 +23,21 @@ import com.seibel.distanthorizons.core.config.Config;
import com.seibel.distanthorizons.core.dataObjects.fullData.sources.FullDataSourceV2;
import com.seibel.distanthorizons.core.dataObjects.render.ColumnRenderSource;
import com.seibel.distanthorizons.core.dataObjects.render.bufferBuilding.ColumnRenderBufferBuilder;
import com.seibel.distanthorizons.core.dataObjects.render.bufferBuilding.LodQuadBuilder;
import com.seibel.distanthorizons.core.dataObjects.transformers.FullDataToRenderDataTransformer;
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
import com.seibel.distanthorizons.core.enums.EDhDirection;
import com.seibel.distanthorizons.core.file.fullDatafile.FullDataSourceProviderV2;
import com.seibel.distanthorizons.core.level.IDhClientLevel;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos2D;
import com.seibel.distanthorizons.core.pos.DhSectionPos;
import com.seibel.distanthorizons.core.render.glObject.GLProxy;
import com.seibel.distanthorizons.core.render.renderer.IDebugRenderable;
import com.seibel.distanthorizons.core.dataObjects.render.bufferBuilding.ColumnRenderBuffer;
import com.seibel.distanthorizons.core.render.renderer.DebugRenderer;
import com.seibel.distanthorizons.core.util.threading.ThreadPoolUtil;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper;
import it.unimi.dsi.fastutil.longs.LongArrayList;
import org.apache.logging.log4j.Logger;
import org.jetbrains.annotations.Nullable;
@@ -54,6 +58,7 @@ import java.util.concurrent.locks.ReentrantLock;
public class LodRenderSection implements IDebugRenderable, AutoCloseable
{
private static final Logger LOGGER = DhLoggerBuilder.getLogger();
private static final IMinecraftClientWrapper MC = SingletonInjector.INSTANCE.get(IMinecraftClientWrapper.class);
@@ -65,7 +70,7 @@ public class LodRenderSection implements IDebugRenderable, AutoCloseable
private final LodQuadTree quadTree;
public boolean renderingEnabled = false;
private boolean renderingEnabled = false;
private boolean canRender = false;
/** this reference is necessary so we can determine what VBO to render */
@@ -76,7 +81,19 @@ public class LodRenderSection implements IDebugRenderable, AutoCloseable
* Encapsulates everything between pulling data from the database (including neighbors)
* up to the point when geometry data is uploaded to the GPU.
*/
private CompletableFuture<Void> uploadRenderDataToGpuFuture = null;
private CompletableFuture<Void> buildAndUploadRenderDataToGpuFuture = null;
/**
* Represents just building the {@link LodQuadBuilder}. <br>
* Separate from {@link LodRenderSection#bufferUploadFuture} because they run on
* different thread pools and need to be canceled separately.
*/
private CompletableFuture<LodQuadBuilder> bufferBuildFuture = null;
/**
* Represents just uploading the {@link LodQuadBuilder} to the GPU. <br>
* Separate from {@link LodRenderSection#bufferBuildFuture} because they run on
* different thread pools and need to be canceled separately.
*/
private CompletableFuture<ColumnRenderBuffer> bufferUploadFuture = null;
private final ReentrantLock getRenderSourceLock = new ReentrantLock();
/** Stored as a class variable so we can reuse it's result across multiple LOD loads if necessary */
@@ -110,6 +127,7 @@ public class LodRenderSection implements IDebugRenderable, AutoCloseable
// render data loading/uploading //
//===============================//
// TODO cleanup, there's a lot of nested futures and duplicate error handling here and it's hard to read
public synchronized void uploadRenderDataToGpuAsync()
{
if (!GLProxy.hasInstance())
@@ -119,7 +137,7 @@ public class LodRenderSection implements IDebugRenderable, AutoCloseable
return;
}
if (this.uploadRenderDataToGpuFuture != null)
if (this.buildAndUploadRenderDataToGpuFuture != null)
{
// don't accidentally queue multiple uploads at the same time
return;
@@ -133,7 +151,7 @@ public class LodRenderSection implements IDebugRenderable, AutoCloseable
return;
}
this.uploadRenderDataToGpuFuture = CompletableFuture.runAsync(() ->
this.buildAndUploadRenderDataToGpuFuture = CompletableFuture.runAsync(() ->
{
//==================//
// load render data //
@@ -141,15 +159,15 @@ public class LodRenderSection implements IDebugRenderable, AutoCloseable
this.tryDecrementingLoadFutureArray(this.adjacentLoadRefFutures);
ReferencedFutureWrapper thisLoadFuture = this.getRenderSourceAsync();
ReferencedFutureWrapper[] adjLoadRefFutures = this.getNeighborRenderSourcesAsync();
ReferencedFutureWrapper thisRenderSourceLoadFuture = this.getRenderSourceAsync();
ReferencedFutureWrapper[] adjRenderSourceLoadRefFutures = this.getNeighborRenderSourcesAsync();
// wait for all futures to complete together,
// merging the futures makes loading significantly faster than loading this position then loading its neighbors
ArrayList<CompletableFuture<ColumnRenderSource>> futureList = new ArrayList<>();
futureList.add(thisLoadFuture.future);
for (ReferencedFutureWrapper refFuture : adjLoadRefFutures)
futureList.add(thisRenderSourceLoadFuture.future);
for (ReferencedFutureWrapper refFuture : adjRenderSourceLoadRefFutures)
{
futureList.add(refFuture.future);
}
@@ -158,72 +176,107 @@ public class LodRenderSection implements IDebugRenderable, AutoCloseable
{
try
{
ColumnRenderSource renderSource = thisLoadFuture.future.get();
ColumnRenderSource renderSource = thisRenderSourceLoadFuture.future.get();
if (renderSource == null || renderSource.isEmpty())
{
thisLoadFuture.decrementRefCount();
for (ReferencedFutureWrapper futureWrapper : adjLoadRefFutures)
thisRenderSourceLoadFuture.decrementRefCount();
for (ReferencedFutureWrapper futureWrapper : adjRenderSourceLoadRefFutures)
{
futureWrapper.decrementRefCount();
}
// nothing needs to be rendered
this.canRender = false;
this.uploadRenderDataToGpuFuture = null;
this.buildAndUploadRenderDataToGpuFuture = null;
this.bufferBuildFuture = null;
return;
}
//==============================//
// build/upload new render data //
//==============================//
//=======================//
// build new render data //
//=======================//
try
{
ColumnRenderBuffer previousBuffer = this.renderBuffer;
ColumnRenderSource[] adjacentRenderSections = new ColumnRenderSource[EDhDirection.ADJ_DIRECTIONS.length];
boolean[] adjIsSameDetailLevel = new boolean[EDhDirection.ADJ_DIRECTIONS.length];
for (int i = 0; i < EDhDirection.ADJ_DIRECTIONS.length; i++)
{
adjacentRenderSections[i] = adjLoadRefFutures[i].future.getNow(null);
adjacentRenderSections[i] = adjRenderSourceLoadRefFutures[i].future.getNow(null);
// if the adjacent position isn't the same detail level the buffer building logic
// will need to be slightly different in order to reduce holes in the LODs
EDhDirection direction = EDhDirection.ADJ_DIRECTIONS[i];
adjIsSameDetailLevel[direction.ordinal() - 2] = this.isAdjacentPosSameDetailLevel(direction);
}
ColumnRenderBufferBuilder.buildAndUploadBuffersAsync(this.level, renderSource, adjacentRenderSections).thenAccept((buffer) ->
if (this.bufferBuildFuture != null)
{
// shouldn't normally happen, but just in case canceling the previous future
// prevents the CPU from working on something that won't be used
this.bufferBuildFuture.cancel(true);
}
this.bufferBuildFuture = ColumnRenderBufferBuilder.buildBuffersAsync(this.level, renderSource, adjacentRenderSections, adjIsSameDetailLevel);
this.bufferBuildFuture.thenAccept((lodQuadBuilder) ->
{
// upload complete, clean up the old data if
this.renderBuffer = buffer;
this.canRender = true;
this.uploadRenderDataToGpuFuture = null;
if (previousBuffer != null)
//===================================//
// upload new render data to the GPU //
//===================================//
if (this.bufferUploadFuture != null)
{
previousBuffer.close();
// shouldn't normally happen, but just in case canceling the previous future
// prevents the CPU from working on something that won't be used
this.bufferUploadFuture.cancel(true);
}
this.bufferUploadFuture = ColumnRenderBufferBuilder.uploadBuffersAsync(this.level, renderSource, lodQuadBuilder);
this.bufferUploadFuture.thenAccept((buffer) ->
{
// upload complete, clean up the old data if
this.renderBuffer = buffer;
this.canRender = (buffer != null);
this.buildAndUploadRenderDataToGpuFuture = null;
this.bufferBuildFuture = null;
thisLoadFuture.decrementRefCount();
this.tryDecrementingLoadFutureArray(adjLoadRefFutures);
this.adjacentLoadRefFutures = null;
if (previousBuffer != null)
{
previousBuffer.close();
}
thisRenderSourceLoadFuture.decrementRefCount();
this.tryDecrementingLoadFutureArray(adjRenderSourceLoadRefFutures);
this.adjacentLoadRefFutures = null;
});
});
}
catch (Exception e)
{
thisLoadFuture.decrementRefCount();
this.tryDecrementingLoadFutureArray(adjLoadRefFutures);
thisRenderSourceLoadFuture.decrementRefCount();
this.tryDecrementingLoadFutureArray(adjRenderSourceLoadRefFutures);
this.adjacentLoadRefFutures = null;
LOGGER.error("Unexpected error in LodRenderSection loading, Error: "+e.getMessage(), e);
this.uploadRenderDataToGpuFuture = null;
this.buildAndUploadRenderDataToGpuFuture = null;
this.bufferBuildFuture = null;
}
}
catch (Exception e)
{
thisLoadFuture.decrementRefCount();
this.tryDecrementingLoadFutureArray(adjLoadRefFutures);
thisRenderSourceLoadFuture.decrementRefCount();
this.tryDecrementingLoadFutureArray(adjRenderSourceLoadRefFutures);
this.adjacentLoadRefFutures = null;
LOGGER.error("Unexpected error in LodRenderSection loading, Error: "+e.getMessage(), e);
this.uploadRenderDataToGpuFuture = null;
this.buildAndUploadRenderDataToGpuFuture = null;
this.bufferBuildFuture = null;
}
});
}, executor);
@@ -304,6 +357,32 @@ public class LodRenderSection implements IDebugRenderable, AutoCloseable
this.getRenderSourceLock.unlock();
}
}
private boolean isAdjacentPosSameDetailLevel(EDhDirection direction)
{
long adjPos = DhSectionPos.getAdjacentPos(this.pos, direction);
byte detailLevel = this.quadTree.calculateExpectedDetailLevel(new DhBlockPos2D(MC.getPlayerBlockPos()), adjPos);
detailLevel += DhSectionPos.SECTION_MINIMUM_DETAIL_LEVEL;
boolean adjacentIsSameDetailLevel = (detailLevel == DhSectionPos.getDetailLevel(this.pos));
return adjacentIsSameDetailLevel;
}
/**
* Note: can cause issues with neighboring LOD sections
* if only some (vs all) futures are canceled.
*/
public void cancelGpuUpload()
{
CompletableFuture<Void> future = this.buildAndUploadRenderDataToGpuFuture;
this.buildAndUploadRenderDataToGpuFuture = null;
this.bufferBuildFuture = null;
if (future != null)
{
// interrupting the future speeds things up, but also causes
// some LODs to never load in properly
future.cancel(false);
}
}
@@ -313,7 +392,37 @@ public class LodRenderSection implements IDebugRenderable, AutoCloseable
public boolean canRender() { return this.canRender; }
public boolean gpuUploadInProgress() { return this.uploadRenderDataToGpuFuture != null; }
public boolean getRenderingEnabled() { return this.renderingEnabled; }
/**
* Separate from {@link LodRenderSection#onRenderingEnabled} and {@link LodRenderSection#onRenderingDisabled}
* since we need to trigger external changes in disabled -> enabled order
* so beacons are removed and then re-added.
* However, to prevent holes in the world when disabling sections we need to
* enable the new section(s) first before disabling the old one(s).
*/
public void setRenderingEnabled(boolean enabled) { this.renderingEnabled = enabled;}
/** @see LodRenderSection#setRenderingEnabled */
public void onRenderingEnabled() { this.level.loadBeaconBeamsInPos(this.pos); }
/** @see LodRenderSection#setRenderingEnabled */
public void onRenderingDisabled()
{
this.level.unloadBeaconBeamsInPos(this.pos);
if (Config.Client.Advanced.Debugging.DebugWireframe.showRenderSectionStatus.get())
{
// show that this position has just been disabled
DebugRenderer.makeParticle(
new DebugRenderer.BoxParticle(
new DebugRenderer.Box(this.pos, 128f, 156f, 0.09f, Color.CYAN.darker()),
0.2, 32f
)
);
}
}
public boolean gpuUploadInProgress() { return this.buildAndUploadRenderDataToGpuFuture != null; }
@@ -394,9 +503,9 @@ public class LodRenderSection implements IDebugRenderable, AutoCloseable
@Override
public String toString()
{
return "LodRenderSection{" +
"pos=" + this.pos +
'}';
return "pos=[" + DhSectionPos.toString(this.pos) + "] " +
"enabled=[" + this.renderingEnabled + "] " +
"uploading=[" + this.gpuUploadInProgress() + "] ";
}
@Override
@@ -423,9 +532,18 @@ public class LodRenderSection implements IDebugRenderable, AutoCloseable
this.renderBuffer.close();
}
if (this.uploadRenderDataToGpuFuture != null)
// cancel all in-progress futures since they aren't needed any more
if (this.buildAndUploadRenderDataToGpuFuture != null)
{
this.uploadRenderDataToGpuFuture.cancel(true);
this.buildAndUploadRenderDataToGpuFuture.cancel(true);
}
if (this.bufferBuildFuture != null)
{
this.bufferBuildFuture.cancel(true);
}
if (this.bufferUploadFuture != null)
{
this.bufferUploadFuture.cancel(true);
}
// this render section won't be rendering, we don't need to load any data for it
@@ -460,7 +578,7 @@ public class LodRenderSection implements IDebugRenderable, AutoCloseable
{
color = Color.green;
}
else if (this.uploadRenderDataToGpuFuture != null)
else if (this.buildAndUploadRenderDataToGpuFuture != null)
{
color = Color.yellow;
}
@@ -195,11 +195,11 @@ public class RenderBufferHandler implements AutoCloseable
int abPosDifference;
if (axisDirection.getAxis().equals(EDhDirection.Axis.X))
{
abPosDifference = aPos.x - bPos.x;
abPosDifference = aPos.getX() - bPos.getX();
}
else
{
abPosDifference = aPos.y - bPos.y;
abPosDifference = aPos.getY() - bPos.getY();
}
if (abPosDifference == 0)
@@ -311,7 +311,7 @@ public class RenderBufferHandler implements AutoCloseable
}
ColumnRenderBuffer buffer = renderSection.renderBuffer;
if (buffer == null || !renderSection.renderingEnabled)
if (buffer == null || !renderSection.getRenderingEnabled())
{
continue;
}
@@ -193,17 +193,7 @@ public class GLProxy
return instance;
}
public EDhApiGpuUploadMethod getGpuUploadMethod()
{
EDhApiGpuUploadMethod method = Config.Client.Advanced.GpuBuffers.gpuUploadMethod.get();
if (!this.bufferStorageSupported && method == EDhApiGpuUploadMethod.BUFFER_STORAGE)
{
// if buffer storage isn't supported
// default to DATA since that is the most compatible
method = EDhApiGpuUploadMethod.DATA;
}
return method == EDhApiGpuUploadMethod.AUTO ? this.preferredUploadMethod : method;
}
public EDhApiGpuUploadMethod getGpuUploadMethod() { return this.preferredUploadMethod; }
public boolean runningOnRenderThread()
{
@@ -238,7 +238,7 @@ public class GLBuffer implements AutoCloseable
// buffer mapping //
//================//
public ByteBuffer mapBuffer(int targetSize, EDhApiGpuUploadMethod uploadMethod, int maxExpensionSize, int bufferHint, int mapFlags)
public ByteBuffer mapBuffer(int targetSize, EDhApiGpuUploadMethod uploadMethod, int maxExpansionSize, int bufferHint, int mapFlags)
{
LodUtil.assertTrue(targetSize != 0, "MapBuffer targetSize is 0");
LodUtil.assertTrue(uploadMethod.useEarlyMapping, "Upload method must be one that use early mappings in order to call mapBuffer");
@@ -252,7 +252,7 @@ public class GLBuffer implements AutoCloseable
if (this.size < targetSize || this.size > targetSize * BUFFER_SHRINK_TRIGGER)
{
int newSize = (int) (targetSize * BUFFER_EXPANSION_MULTIPLIER);
if (newSize > maxExpensionSize) newSize = maxExpensionSize;
if (newSize > maxExpansionSize) newSize = maxExpansionSize;
this.size = newSize;
if (this.bufferStorage)
{
@@ -77,9 +77,9 @@ public class GLVertexBuffer extends GLBuffer
this.vertexCount = vertCount;
}
public ByteBuffer mapBuffer(int targetSize, EDhApiGpuUploadMethod uploadMethod, int maxExpensionSize)
public ByteBuffer mapBuffer(int targetSize, EDhApiGpuUploadMethod uploadMethod, int maxExpansionSize)
{
return super.mapBuffer(targetSize, uploadMethod, maxExpensionSize,
return super.mapBuffer(targetSize, uploadMethod, maxExpansionSize,
uploadMethod.useBufferStorage ? GL32.GL_MAP_WRITE_BIT :
uploadMethod.useEarlyMapping ? GL32.GL_DYNAMIC_DRAW : GL32.GL_STATIC_DRAW,
GL32.GL_MAP_WRITE_BIT | GL32.GL_MAP_UNSYNCHRONIZED_BIT | GL32.GL_MAP_INVALIDATE_BUFFER_BIT);
@@ -25,6 +25,7 @@ import com.seibel.distanthorizons.core.render.glObject.GLEnums;
import com.seibel.distanthorizons.core.render.glObject.GLProxy;
import org.apache.logging.log4j.Logger;
import org.lwjgl.opengl.GL32;
import org.lwjgl.system.MemoryUtil;
import java.lang.invoke.MethodHandles;
import java.nio.ByteBuffer;
@@ -154,7 +155,7 @@ public class QuadElementBuffer extends GLElementBuffer
LOGGER.info("Quad IBO Resizing from [" + getCapacity() + "] to [" + quadCount + "]" + " with type: " +
GLEnums.getString(type));
ByteBuffer buffer = ByteBuffer.allocateDirect(indicesCount * GLEnums.getTypeSize(type)).order(ByteOrder.nativeOrder());
ByteBuffer buffer = MemoryUtil.memAlloc(indicesCount * GLEnums.getTypeSize(type));
buildBuffer(quadCount, buffer, type);
if (!gl.bufferStorageSupported)
{
@@ -169,6 +170,8 @@ public class QuadElementBuffer extends GLElementBuffer
super.uploadBuffer(buffer, EDhApiGpuUploadMethod.BUFFER_STORAGE,
indicesCount * GLEnums.getTypeSize(type), 0);
}
MemoryUtil.memFree(buffer);
}
}
@@ -26,7 +26,7 @@ import com.seibel.distanthorizons.core.config.types.ConfigEntry;
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
import com.seibel.distanthorizons.core.logging.ConfigBasedLogger;
import com.seibel.distanthorizons.core.logging.ConfigBasedSpamLogger;
import com.seibel.distanthorizons.core.pos.DhBlockPos2D;
import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos2D;
import com.seibel.distanthorizons.core.pos.DhLodPos;
import com.seibel.distanthorizons.core.pos.DhSectionPos;
import com.seibel.distanthorizons.core.render.glObject.GLState;
@@ -30,7 +30,7 @@ import com.seibel.distanthorizons.core.dependencyInjection.ModAccessorInjector;
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
import com.seibel.distanthorizons.core.logging.ConfigBasedLogger;
import com.seibel.distanthorizons.core.logging.ConfigBasedSpamLogger;
import com.seibel.distanthorizons.core.pos.DhBlockPos;
import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos;
import com.seibel.distanthorizons.core.render.DhApiRenderProxy;
import com.seibel.distanthorizons.core.render.RenderBufferHandler;
import com.seibel.distanthorizons.core.render.glObject.GLProxy;
@@ -475,7 +475,7 @@ public class LodRenderer
public void setModelViewMatrixOffset(DhBlockPos pos, DhApiRenderParam renderEventParam) throws IllegalStateException
{
Vec3d cam = MC_RENDER.getCameraExactPosition();
Vec3f modelPos = new Vec3f((float) (pos.x - cam.x), (float) (pos.y - cam.y), (float) (pos.z - cam.z));
Vec3f modelPos = new Vec3f((float) (pos.getX() - cam.x), (float) (pos.getY() - cam.y), (float) (pos.getZ() - cam.z));
IDhApiShaderProgram shaderProgram = this.lodRenderProgram;
@@ -24,6 +24,7 @@ import com.seibel.distanthorizons.core.render.glObject.buffer.GLVertexBuffer;
import com.seibel.distanthorizons.core.render.glObject.vertexAttribute.AbstractVertexAttribute;
import com.seibel.distanthorizons.core.render.glObject.vertexAttribute.VertexPointer;
import org.lwjgl.opengl.GL32;
import org.lwjgl.system.MemoryUtil;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
@@ -86,14 +87,14 @@ public class ScreenQuad
private void createBuffer()
{
ByteBuffer buffer = ByteBuffer.allocateDirect(box_vertices.length * Float.BYTES);
buffer.order(ByteOrder.nativeOrder());
ByteBuffer buffer = MemoryUtil.memAlloc(box_vertices.length * Float.BYTES);
buffer.asFloatBuffer().put(box_vertices);
buffer.rewind();
this.boxBuffer = new GLVertexBuffer(false);
this.boxBuffer.bind();
this.boxBuffer.uploadBuffer(buffer, box_vertices.length, EDhApiGpuUploadMethod.DATA, box_vertices.length * Float.BYTES);
MemoryUtil.memFree(buffer);
}
}
@@ -21,18 +21,20 @@ package com.seibel.distanthorizons.core.render.renderer.generic;
import com.seibel.distanthorizons.api.enums.rendering.EDhApiBlockMaterial;
import com.seibel.distanthorizons.api.interfaces.render.IDhApiRenderableBoxGroup;
import com.seibel.distanthorizons.api.methods.events.sharedParameterObjects.DhApiRenderParam;
import com.seibel.distanthorizons.api.objects.math.DhApiVec3d;
import com.seibel.distanthorizons.api.objects.math.DhApiVec3f;
import com.seibel.distanthorizons.api.objects.render.DhApiRenderableBox;
import com.seibel.distanthorizons.api.objects.render.DhApiRenderableBoxGroupShading;
import com.seibel.distanthorizons.core.config.Config;
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.pos.DhBlockPos;
import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos;
import com.seibel.distanthorizons.core.pos.DhChunkPos;
import com.seibel.distanthorizons.core.sql.dto.BeaconBeamDTO;
import com.seibel.distanthorizons.core.sql.repo.BeaconBeamRepo;
import com.seibel.distanthorizons.core.util.LodUtil;
import com.seibel.distanthorizons.core.util.math.Vec3d;
import com.seibel.distanthorizons.core.util.threading.ThreadPoolUtil;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftRenderWrapper;
import com.seibel.distanthorizons.coreapi.ModInfo;
import org.apache.logging.log4j.Logger;
@@ -42,7 +44,10 @@ import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.locks.ReentrantLock;
import java.util.function.Predicate;
public class BeaconRenderHandler
{
@@ -51,12 +56,22 @@ public class BeaconRenderHandler
private static final int BEAM_TOP_Y = 6_000;
/** how often should we check if a beacon should be culled? */
private static final int MAX_CULLING_FREQUENCY_IN_MS = 1_000;
/** if this is null then the other handler is probably null too, but just in case */
private final BeaconBeamRepo beaconBeamRepo;
private final ReentrantLock updateLock = new ReentrantLock();
private final IDhApiRenderableBoxGroup beaconBoxGroup;
private final HashMap<DhBlockPos, AtomicInteger> beaconRefCountByBlockPos = new HashMap<>();
private final ArrayList<DhApiRenderableBox> fullBeaconBoxList = new ArrayList<>();
private final HashSet<DhBlockPos> beaconBlockPosSet = new HashSet<>();
private boolean cullingThreadRunning = false;
private boolean updateRenderDataNextFrame = false;
@@ -73,7 +88,7 @@ public class BeaconRenderHandler
this.beaconBoxGroup.setSkyLight(LodUtil.MAX_MC_LIGHT);
this.beaconBoxGroup.setSsaoEnabled(false);
this.beaconBoxGroup.setShading(DhApiRenderableBoxGroupShading.getUnshaded());
this.beaconBoxGroup.setPreRenderFunc((renderEventParam) -> this.beaconBoxGroup.setActive(Config.Client.Advanced.Graphics.GenericRendering.enableBeaconRendering.get()));
this.beaconBoxGroup.setPreRenderFunc(this::beforeRender);
renderer.add(this.beaconBoxGroup);
}
@@ -84,7 +99,7 @@ public class BeaconRenderHandler
// level loading/unloading //
//=========================//
public void setBeaconBeamsForChunk(DhChunkPos chunkPos, List<BeaconBeamDTO> newBeamList)
public void setBeaconBeamsForChunk(DhChunkPos chunkPos, List<BeaconBeamDTO> activeBeamList)
{
// synchronized to prevent two threads from updating the same chunk at the same time
synchronized (this)
@@ -92,12 +107,12 @@ public class BeaconRenderHandler
HashSet<DhBlockPos> allPosSet = new HashSet<>();
// sort new beams
HashMap<DhBlockPos, BeaconBeamDTO> newBeamByPos = new HashMap<>(newBeamList.size());
for (int i = 0; i < newBeamList.size(); i++)
HashMap<DhBlockPos, BeaconBeamDTO> activeBeamByPos = new HashMap<>(activeBeamList.size());
for (int i = 0; i < activeBeamList.size(); i++)
{
BeaconBeamDTO beam = newBeamList.get(i);
newBeamByPos.put(beam.pos, beam);
allPosSet.add(beam.pos);
BeaconBeamDTO beam = activeBeamList.get(i);
activeBeamByPos.put(beam.blockPos, beam);
allPosSet.add(beam.blockPos);
}
// get existing beams
@@ -106,8 +121,8 @@ public class BeaconRenderHandler
for (int i = 0; i < existingBeamList.size(); i++)
{
BeaconBeamDTO beam = existingBeamList.get(i);
existingBeamByPos.put(beam.pos, beam);
allPosSet.add(beam.pos);
existingBeamByPos.put(beam.blockPos, beam);
allPosSet.add(beam.blockPos);
}
@@ -121,28 +136,28 @@ public class BeaconRenderHandler
}
BeaconBeamDTO existingBeam = existingBeamByPos.get(beaconPos);
BeaconBeamDTO newBeam = newBeamByPos.get(beaconPos);
BeaconBeamDTO activeBeam = activeBeamByPos.get(beaconPos);
if (existingBeam != null && newBeam != null)
if (existingBeam != null && activeBeam != null)
{
// beam still exists in chunk
if (!existingBeam.color.equals(newBeam.color))
if (!existingBeam.color.equals(activeBeam.color))
{
// beam colors were changed
this.beaconBeamRepo.save(newBeam);
this.updateBeaconColor(newBeam);
this.beaconBeamRepo.save(activeBeam);
this.updateBeaconColor(activeBeam);
}
}
else if (existingBeam == null && newBeam != null)
else if (existingBeam == null && activeBeam != null)
{
// new beam found, add to DB
this.beaconBeamRepo.save(newBeam);
this.startRenderingBeacon(newBeam);
this.beaconBeamRepo.save(activeBeam);
this.startRenderingBeacon(activeBeam);
}
else if (existingBeam != null && newBeam == null)
else if (existingBeam != null && activeBeam == null)
{
// beam no longer exists at position, remove from DB
this.beaconBeamRepo.deleteWithKey(beaconPos); // TODO broken when updating adjacent chunks
this.beaconBeamRepo.deleteWithKey(beaconPos);
this.stopRenderingBeaconAtPos(beaconPos);
}
@@ -168,7 +183,7 @@ public class BeaconRenderHandler
for (int i = 0; i < existingBeamList.size(); i++)
{
BeaconBeamDTO beam = existingBeamList.get(i);
this.stopRenderingBeaconAtPos(beam.pos);
this.stopRenderingBeaconAtPos(beam.blockPos);
}
}
@@ -180,62 +195,164 @@ public class BeaconRenderHandler
private void startRenderingBeacon(BeaconBeamDTO beacon)
{
this.beaconRefCountByBlockPos.compute(beacon.pos, (beamPos, beaconRefCount) ->
try
{
if (beaconRefCount == null) { beaconRefCount = new AtomicInteger(); }
if (beaconRefCount.getAndIncrement() == 0)
this.updateLock.lock();
if (this.beaconBlockPosSet.add(beacon.blockPos))
{
DhApiRenderableBox beaconBox = new DhApiRenderableBox(
new DhApiVec3d(beacon.pos.x, beacon.pos.y+1, beacon.pos.z),
new DhApiVec3d(beacon.pos.x+1, BEAM_TOP_Y, beacon.pos.z+1),
new DhApiVec3d(beacon.blockPos.getX(), beacon.blockPos.getY() + 1, beacon.blockPos.getZ()),
new DhApiVec3d(beacon.blockPos.getX() + 1, BEAM_TOP_Y, beacon.blockPos.getZ() + 1),
beacon.color,
EDhApiBlockMaterial.ILLUMINATED
);
this.beaconBoxGroup.add(beaconBox);
this.fullBeaconBoxList.add(beaconBox);
this.beaconBoxGroup.triggerBoxChange();
}
return beaconRefCount;
});
}
finally
{
this.updateLock.unlock();
}
}
private void stopRenderingBeaconAtPos(DhBlockPos beaconPos)
{
this.beaconRefCountByBlockPos.compute(beaconPos, (pos, beaconRefCount) ->
try
{
if (beaconRefCount != null
&& beaconRefCount.decrementAndGet() <= 0)
this.updateLock.lock();
if (this.beaconBlockPosSet.remove(beaconPos))
{
this.beaconBoxGroup.removeIf((box) ->
box.minPos.x == beaconPos.x
&& box.minPos.y == beaconPos.y+1 // plus 1 because the beam starts above the beacon
&& box.minPos.z == beaconPos.z
);
Predicate<DhApiRenderableBox> removePredicate = (DhApiRenderableBox box) ->
{
return box.minPos.x == beaconPos.getX()
&& box.minPos.y == beaconPos.getY() + 1 // plus 1 because the beam starts above the beacon
&& box.minPos.z == beaconPos.getZ();
};
this.beaconBoxGroup.removeIf(removePredicate);
this.fullBeaconBoxList.removeIf(removePredicate);
this.beaconBoxGroup.triggerBoxChange();
return null;
}
else
{
return beaconRefCount;
}
});
}
finally
{
this.updateLock.unlock();
}
}
private void updateBeaconColor(BeaconBeamDTO newBeam)
{
DhBlockPos pos = newBeam.pos;
for (int i = 0; i < this.beaconBoxGroup.size(); i++)
try
{
DhApiRenderableBox box = this.beaconBoxGroup.get(i);
if (box.minPos.x == pos.x
&& box.minPos.y == pos.y+1 // plus 1 because the beam starts above the beacon
&& box.minPos.z == pos.z)
this.updateLock.lock();
DhBlockPos pos = newBeam.blockPos;
for (int i = 0; i < this.fullBeaconBoxList.size(); i++)
{
box.color = newBeam.color;
this.beaconBoxGroup.triggerBoxChange();
break;
DhApiRenderableBox box = this.fullBeaconBoxList.get(i);
if (box.minPos.x == pos.getX()
&& box.minPos.y == pos.getY() + 1 // plus 1 because the beam starts above the beacon
&& box.minPos.z == pos.getZ())
{
box.color = newBeam.color;
this.beaconBoxGroup.triggerBoxChange();
break;
}
}
}
finally
{
this.updateLock.unlock();
}
}
private void beforeRender(DhApiRenderParam renderEventParam)
{
if (!Config.Client.Advanced.Graphics.AdvancedGraphics.disableBeaconDistanceCulling.get())
{
// this could be called only when the player moves, but it's an extremely cheap check,
// so there isn't much of a reason to bother
this.tryUpdateBeaconCullingAsync();
}
// this must be called on the render thread to prevent concurrency issues
if (this.updateRenderDataNextFrame)
{
this.beaconBoxGroup.triggerBoxChange();
this.updateRenderDataNextFrame = false;
}
this.beaconBoxGroup.setActive(Config.Client.Advanced.Graphics.GenericRendering.enableBeaconRendering.get());
}
/** does nothing if the culling thread is already running */
private void tryUpdateBeaconCullingAsync()
{
ThreadPoolExecutor executor = ThreadPoolUtil.getBeaconCullingExecutor();
if (executor != null
&& !this.cullingThreadRunning)
{
this.cullingThreadRunning = true;
try
{
executor.execute(() ->
{
try
{
Thread.sleep(MAX_CULLING_FREQUENCY_IN_MS);
}
catch (InterruptedException ignore) { }
try
{
// lock to make sure we don't try adding beacons to the arrays while processing them
this.updateLock.lock();
Vec3d cameraPos = MC_RENDER.getCameraExactPosition();
double mcRenderDistance = MC_RENDER.getRenderDistance() * LodUtil.CHUNK_WIDTH;
// Clear the existing box group so we can re-populate it.
// Since the box group is only used when we trigger an update, clearing it here
// and repopulating it is fine.
this.beaconBoxGroup.clear();
// While iterating over every beacon isn't a great way of doing this,
// when 940 beacons were tested this only took ~0.9 Milliseconds, so as long as
// we aren't freezing the render thread this method of culling works just fine.
for (DhApiRenderableBox box : this.fullBeaconBoxList)
{
// if a beacon is outside the vanilla render distance render it
double distance = Vec3d.getHorizontalDistance(cameraPos, box.minPos);
if (distance > mcRenderDistance)
{
this.beaconBoxGroup.add(box);
}
}
this.updateRenderDataNextFrame = true;
}
catch (Exception e)
{
LOGGER.error("Unexpected issue while updating beacon culling. Error: " + e.getMessage(), e);
}
finally
{
this.updateLock.unlock();
this.cullingThreadRunning = false;
}
});
}
catch (RejectedExecutionException ignore)
{ /* If this happens that means everything is already shut down and no culling is necessary */ }
}
}
}
@@ -21,12 +21,14 @@ package com.seibel.distanthorizons.core.render.renderer.generic;
import com.seibel.distanthorizons.api.enums.rendering.EDhApiBlockMaterial;
import com.seibel.distanthorizons.api.interfaces.render.IDhApiRenderableBoxGroup;
import com.seibel.distanthorizons.api.methods.events.sharedParameterObjects.DhApiRenderParam;
import com.seibel.distanthorizons.api.objects.math.DhApiVec3d;
import com.seibel.distanthorizons.api.objects.math.DhApiVec3f;
import com.seibel.distanthorizons.api.objects.render.DhApiRenderableBox;
import com.seibel.distanthorizons.api.objects.render.DhApiRenderableBoxGroupShading;
import com.seibel.distanthorizons.core.config.Config;
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
import com.seibel.distanthorizons.core.level.IDhClientLevel;
import com.seibel.distanthorizons.core.level.IDhLevel;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.util.LodUtil;
@@ -48,8 +50,6 @@ public class CloudRenderHandler
private static final IMinecraftRenderWrapper MC_RENDER = SingletonInjector.INSTANCE.get(IMinecraftRenderWrapper.class);
private static final String CLOUD_RESOURCE_TEXTURE_PATH = "assets/distanthorizons/textures/clouds.png";
// FIXME transparency sorting makes having transparent clouds impossible
private static final Color CLOUD_COLOR = new Color(255,255,255,255);
private static final boolean DEBUG_BORDER_COLORS = false;
@@ -62,7 +62,7 @@ public class CloudRenderHandler
private static final int CLOUD_BOX_THICKNESS = 16;
private final IDhApiRenderableBoxGroup[][] boxGroupByOffset = new IDhApiRenderableBoxGroup[3][3];
private final IDhLevel level;
private final IDhClientLevel level;
private final GenericObjectRenderer renderer;
private float moveSpeedInBlocksPerSecond = 3.0f;
@@ -74,7 +74,7 @@ public class CloudRenderHandler
// constructor //
//=============//
public CloudRenderHandler(IDhLevel level, GenericObjectRenderer renderer)
public CloudRenderHandler(IDhClientLevel level, GenericObjectRenderer renderer)
{
this.level = level;
this.renderer = renderer;
@@ -195,16 +195,8 @@ public class CloudRenderHandler
int maxXBlockPos = endX * CLOUD_BOX_WIDTH;
int maxZBlockPos = endZ * CLOUD_BOX_WIDTH;
Color color = CLOUD_COLOR;
if (DEBUG_BORDER_COLORS)
{
// equals is included so the board is 2 blocks wide, it makes it easier to see
if (startX <= 1) { color = Color.RED; }
else if (startX >= textureWidth - 2) { color = Color.GREEN; }
if (startZ <= 1) { color = Color.BLUE; }
else if (endZ >= textureWidth - 2) { color = Color.BLACK; }
}
// this color is changed at render time based on the level time
Color color = new Color(255,255,255,255);
DhApiRenderableBox box = new DhApiRenderableBox(
new DhApiVec3d(minXBlockPos, 0, minZBlockPos),
new DhApiVec3d(maxXBlockPos, CLOUD_BOX_THICKNESS, maxZBlockPos),
@@ -238,13 +230,15 @@ public class CloudRenderHandler
ModInfo.NAME + ":Clouds",
new DhApiVec3d(0, 0, 0), // the offset will be set during rendering
boxList);
boxGroup.setBlockLight(LodUtil.MIN_MC_LIGHT);
// 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.setSkyLight(LodUtil.MAX_MC_LIGHT);
boxGroup.setSsaoEnabled(false);
boxGroup.setShading(cloudShading);
CloudParams params = new CloudParams(textureWidth, x, z);
boxGroup.setPreRenderFunc((renderParam) -> this.preRender(params));
CloudParams cloudParams = new CloudParams(textureWidth, x, z);
boxGroup.setPreRenderFunc((renderParam) -> this.preRender(renderParam, cloudParams));
renderer.add(boxGroup);
this.boxGroupByOffset[x+1][z+1] = boxGroup;
@@ -252,9 +246,15 @@ public class CloudRenderHandler
}
}
private void preRender(CloudParams clouds)
private void preRender(DhApiRenderParam renderParam, CloudParams cloudParams)
{
IDhApiRenderableBoxGroup boxGroup = this.boxGroupByOffset[clouds.instanceOffsetX+1][clouds.instanceOffsetZ+1];
IDhApiRenderableBoxGroup boxGroup = this.boxGroupByOffset[cloudParams.instanceOffsetX+1][cloudParams.instanceOffsetZ+1];
//===================//
// should we render? //
//===================//
boolean renderClouds = Config.Client.Advanced.Graphics.GenericRendering.enableCloudRendering.get();
boxGroup.setActive(renderClouds);
@@ -275,19 +275,46 @@ public class CloudRenderHandler
}
//=============//
// cloud color //
//=============//
// FIXME transparency sorting makes having transparent clouds impossible
// maybe someday we could add the option to cull individual faces? a single bit for each direction should be enough
// cloud color changes based on the time of day and weather so we need to get it from the level
Color cloudColor = this.level.getClientLevelWrapper().getCloudColor(renderParam.partialTicks);
if (DEBUG_BORDER_COLORS)
{
// equals is included so the board is 2 blocks wide, it makes it easier to see
if (cloudParams.instanceOffsetX <= 1) { cloudColor = Color.RED; }
else if (cloudParams.instanceOffsetX >= cloudParams.textureWidth - 2) { cloudColor = Color.GREEN; }
if (cloudParams.instanceOffsetZ <= 1) { cloudColor = Color.BLUE; }
else if (cloudParams.instanceOffsetZ >= cloudParams.textureWidth - 2) { cloudColor = Color.BLACK; }
}
for (DhApiRenderableBox box : boxGroup)
{
box.color = cloudColor;
}
boxGroup.triggerBoxChange();
//================//
// cloud movement //
//================//
long currentTime = System.currentTimeMillis();
float deltaTime = (currentTime - clouds.lastFrameTime) / 1000.0f; // Delta time in seconds
clouds.lastFrameTime = currentTime;
float deltaTime = (currentTime - cloudParams.lastFrameTime) / 1000.0f; // Delta time in seconds
cloudParams.lastFrameTime = currentTime;
float deltaX = this.moveSpeedInBlocksPerSecond * deltaTime;
// negative delta is to match vanilla's cloud movement
clouds.xOffset -= deltaX;
cloudParams.xOffset -= deltaX;
// wrap the cloud around after reaching the edge
clouds.xOffset %= clouds.widthInBlocks;
cloudParams.xOffset %= cloudParams.widthInBlocks;
@@ -299,16 +326,16 @@ public class CloudRenderHandler
int cameraPosX = (int)MC_RENDER.getCameraExactPosition().x;
int cameraPosZ = (int)MC_RENDER.getCameraExactPosition().z;
// offset the camera position by negative 1 width when below zero to fix off-by-one errors in the negative direction
if (cameraPosX < 0) { cameraPosX -= clouds.widthInBlocks; }
if (cameraPosZ < 0) { cameraPosZ -= clouds.widthInBlocks; }
if (cameraPosX < 0) { cameraPosX -= (int)cloudParams.widthInBlocks; }
if (cameraPosZ < 0) { cameraPosZ -= (int)cloudParams.widthInBlocks; }
// determine how many cloud instances away from the origin we are
int cloudInstanceOffsetX = cameraPosX / (int)clouds.widthInBlocks;
int cloudInstanceOffsetZ = cameraPosZ / (int)clouds.widthInBlocks;
int cloudInstanceOffsetX = cameraPosX / (int)cloudParams.widthInBlocks;
int cloudInstanceOffsetZ = cameraPosZ / (int)cloudParams.widthInBlocks;
// calculate the new offset
float xOffset = (cloudInstanceOffsetX * clouds.widthInBlocks);
float zOffset = (cloudInstanceOffsetZ * clouds.widthInBlocks);
float xOffset = (cloudInstanceOffsetX * cloudParams.widthInBlocks);
float zOffset = (cloudInstanceOffsetZ * cloudParams.widthInBlocks);
@@ -318,9 +345,9 @@ public class CloudRenderHandler
boxGroup.setOriginBlockPos(
new DhApiVec3d(
clouds.xOffset + (clouds.instanceOffsetX * clouds.widthInBlocks) + xOffset + clouds.halfWidthInBlocks,
cloudParams.xOffset + (cloudParams.instanceOffsetX * cloudParams.widthInBlocks) + xOffset + cloudParams.halfWidthInBlocks,
this.level.getLevelWrapper().getMaxHeight() + 200,
clouds.zOffset + (clouds.instanceOffsetZ * clouds.widthInBlocks) + zOffset + clouds.halfWidthInBlocks
cloudParams.zOffset + (cloudParams.instanceOffsetZ * cloudParams.widthInBlocks) + zOffset + cloudParams.halfWidthInBlocks
)
);
}
@@ -30,7 +30,9 @@ import com.seibel.distanthorizons.api.methods.events.sharedParameterObjects.DhAp
import com.seibel.distanthorizons.api.objects.math.DhApiVec3d;
import com.seibel.distanthorizons.api.objects.render.DhApiRenderableBox;
import com.seibel.distanthorizons.api.objects.render.DhApiRenderableBoxGroupShading;
import com.seibel.distanthorizons.core.dependencyInjection.ModAccessorInjector;
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
import com.seibel.distanthorizons.core.jar.EPlatform;
import com.seibel.distanthorizons.core.logging.ConfigBasedSpamLogger;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.logging.f3.F3Screen;
@@ -42,14 +44,18 @@ import com.seibel.distanthorizons.core.util.LodUtil;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftRenderWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IProfilerWrapper;
import com.seibel.distanthorizons.core.util.math.Vec3d;
import com.seibel.distanthorizons.core.wrapperInterfaces.modAccessor.IModAccessor;
import com.seibel.distanthorizons.core.wrapperInterfaces.modAccessor.ISodiumAccessor;
import com.seibel.distanthorizons.coreapi.DependencyInjection.ApiEventInjector;
import com.seibel.distanthorizons.coreapi.DependencyInjection.OverrideInjector;
import com.seibel.distanthorizons.coreapi.ModInfo;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.lwjgl.glfw.GLFW;
import org.lwjgl.opengl.ARBInstancedArrays;
import org.lwjgl.opengl.GL32;
import org.lwjgl.opengl.GL33;
import org.lwjgl.system.MemoryUtil;
import java.awt.*;
import java.nio.ByteBuffer;
@@ -69,6 +75,7 @@ public class GenericObjectRenderer implements IDhApiCustomRenderRegister
public static final ConfigBasedSpamLogger SPAM_LOGGER = new ConfigBasedSpamLogger(LogManager.getLogger(GenericObjectRenderer.class), () -> EDhApiLoggerMode.LOG_ALL_TO_CHAT, 1);
private static final IMinecraftRenderWrapper MC_RENDER = SingletonInjector.INSTANCE.get(IMinecraftRenderWrapper.class);
private static final ISodiumAccessor SODIUM = ModAccessorInjector.INSTANCE.get(ISodiumAccessor.class);
/**
* Can be used to troubleshoot the renderer.
@@ -171,17 +178,37 @@ public class GenericObjectRenderer implements IDhApiCustomRenderRegister
}
this.init = true;
//===================================//
// is instanced rendering available? //
//===================================//
this.vertexAttribDivisorSupported = GLProxy.getInstance().vertexAttribDivisorSupported;
this.instancedArraysSupported = GLProxy.getInstance().instancedArraysSupported;
this.useInstancedRendering = this.vertexAttribDivisorSupported || this.instancedArraysSupported;
if (!this.useInstancedRendering)
{
LOGGER.warn("Instanced rendering not supported by this GPU, falling back to direct rendering. Generic object rendering will be slow.");
LOGGER.warn("Instanced rendering not supported by this GPU, falling back to direct rendering. Generic object rendering will be slow and some effects may be disabled.");
}
else
{
boolean isMac = (EPlatform.get() == EPlatform.MACOS);
if (isMac && SODIUM != null)
{
this.useInstancedRendering = false;
LOGGER.warn("Instanced rendering is broken on Mac when Sodium is present, falling back to direct rendering. Generic object rendering will be slow and some effects may be disabled.");
}
}
this.shaderProgram = new GenericObjectShaderProgram(this.useInstancedRendering);
//======================//
// startup the renderer //
//======================//
this.shaderProgram = new GenericObjectShaderProgram(this.useInstancedRendering);
this.createBuffers();
if (RENDER_DEBUG_OBJECTS)
@@ -192,24 +219,22 @@ public class GenericObjectRenderer implements IDhApiCustomRenderRegister
private void createBuffers()
{
// box vertices
ByteBuffer boxVerticesBuffer = ByteBuffer.allocateDirect(BOX_VERTICES.length * Float.BYTES);
boxVerticesBuffer.order(ByteOrder.nativeOrder());
ByteBuffer boxVerticesBuffer = MemoryUtil.memAlloc(BOX_VERTICES.length * Float.BYTES);
boxVerticesBuffer.asFloatBuffer().put(BOX_VERTICES);
boxVerticesBuffer.rewind();
this.boxVertexBuffer = new GLVertexBuffer(false);
this.boxVertexBuffer.bind();
this.boxVertexBuffer.uploadBuffer(boxVerticesBuffer, 8, EDhApiGpuUploadMethod.DATA, BOX_VERTICES.length * Float.BYTES);
MemoryUtil.memFree(boxVerticesBuffer);
// box vertex indexes
ByteBuffer solidIndexBuffer = ByteBuffer.allocateDirect(BOX_INDICES.length * Integer.BYTES);
solidIndexBuffer.order(ByteOrder.nativeOrder());
ByteBuffer solidIndexBuffer = MemoryUtil.memAlloc(BOX_INDICES.length * Integer.BYTES);
solidIndexBuffer.asIntBuffer().put(BOX_INDICES);
solidIndexBuffer.rewind();
this.boxIndexBuffer = new GLElementBuffer(false);
this.boxIndexBuffer.uploadBuffer(solidIndexBuffer, EDhApiGpuUploadMethod.DATA, BOX_INDICES.length * Integer.BYTES, GL32.GL_STATIC_DRAW);
this.boxIndexBuffer.bind();
MemoryUtil.memFree(solidIndexBuffer);
}
private void addGenericDebugObjects()
{
@@ -215,7 +215,8 @@ public class RenderableBoxGroup
public Stream<DhApiRenderableBox> stream() { return this.boxList.stream(); }
@Override
public Stream<DhApiRenderableBox> parallelStream() { return this.boxList.parallelStream(); }
@Override
public void clear() { this.boxList.clear(); }
//===================//
@@ -94,6 +94,12 @@ public class FogApplyShader extends AbstractShaderRenderer
GL32.glBlendEquation(GL32.GL_FUNC_ADD);
GL32.glBlendFuncSeparate(GL32.GL_SRC_ALPHA, GL32.GL_ONE_MINUS_SRC_ALPHA, GL32.GL_ONE, GL32.GL_ONE_MINUS_SRC_ALPHA);
// Depth testing must be disabled otherwise this application shader won't apply anything.
// setting this isn't necessary in vanilla, but some mods may change this, requiring it to be set manually,
// it should be automatically restored after rendering is complete.
GL32.glDisable(GL32.GL_DEPTH_TEST);
// apply the rendered Fog to DH's framebuffer
GL32.glBindFramebuffer(GL32.GL_READ_FRAMEBUFFER, FogShader.INSTANCE.frameBuffer);
GL32.glBindFramebuffer(GL32.GL_DRAW_FRAMEBUFFER, LodRenderer.getActiveFramebufferId());
@@ -167,6 +167,11 @@ public class FogShader extends AbstractShaderRenderer
GL32.glBindTexture(GL32.GL_TEXTURE_2D, LodRenderer.getActiveDepthTextureId());
GL32.glUniform1i(this.uDepthMap, 0);
// this is necessary for MC 1.16 (IE Legacy OpenGL)
// otherwise the framebuffer isn't cleared correctly and the fog smears across the screen
GL32.glClear(GL32.GL_COLOR_BUFFER_BIT | GL32.GL_DEPTH_BUFFER_BIT);
ScreenQuad.INSTANCE.render();
state.restore();
@@ -125,6 +125,12 @@ public class SSAOApplyShader extends AbstractShaderRenderer
GL32.glBlendEquation(GL32.GL_FUNC_ADD);
GL32.glBlendFuncSeparate(GL32.GL_ZERO, GL32.GL_SRC_ALPHA, GL32.GL_ZERO, GL32.GL_ONE);
// Depth testing must be disabled otherwise this application shader won't apply anything.
// setting this isn't necessary in vanilla, but some mods may change this, requiring it to be set manually,
// it should be automatically restored after rendering is complete.
GL32.glDisable(GL32.GL_DEPTH_TEST);
// apply the rendered SSAO to the LODs
GL32.glBindFramebuffer(GL32.GL_READ_FRAMEBUFFER, SSAOShader.INSTANCE.frameBuffer);
GL32.glBindFramebuffer(GL32.GL_DRAW_FRAMEBUFFER, LodRenderer.getActiveFramebufferId());
@@ -103,17 +103,19 @@ public class SSAOShader extends AbstractShaderRenderer
this.shader.setUniform(this.gSampleCountUniform,
Config.Client.Advanced.Graphics.Ssao.sampleCount.get());
this.shader.setUniform(this.gRadiusUniform,
Config.Client.Advanced.Graphics.Ssao.radius.get().floatValue());
// Implicit Number cast needs to be done to prevent issues with the default value being a int
Number radius = Config.Client.Advanced.Graphics.Ssao.radius.get();
this.shader.setUniform(this.gRadiusUniform, radius.floatValue());
this.shader.setUniform(this.gStrengthUniform,
Config.Client.Advanced.Graphics.Ssao.strength.get().floatValue());
this.shader.setUniform(this.gMinLightUniform,
Config.Client.Advanced.Graphics.Ssao.minLight.get().floatValue());
Number strength = Config.Client.Advanced.Graphics.Ssao.strength.get();
this.shader.setUniform(this.gStrengthUniform, strength.floatValue());
this.shader.setUniform(this.gBiasUniform,
Config.Client.Advanced.Graphics.Ssao.bias.get().floatValue());
Number minLight = Config.Client.Advanced.Graphics.Ssao.minLight.get();
this.shader.setUniform(this.gMinLightUniform, minLight.floatValue());
Number bias = Config.Client.Advanced.Graphics.Ssao.bias.get();
this.shader.setUniform(this.gBiasUniform, bias.floatValue());
GL32.glActiveTexture(GL32.GL_TEXTURE0);
GL32.glBindTexture(GL32.GL_TEXTURE_2D, LodRenderer.getActiveDepthTextureId());
@@ -20,15 +20,14 @@
package com.seibel.distanthorizons.core.sql.dto;
import com.seibel.distanthorizons.core.dataObjects.fullData.sources.FullDataSourceV2;
import com.seibel.distanthorizons.core.pos.DhBlockPos;
import com.seibel.distanthorizons.core.pos.DhBlockPos;
import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos;
import java.awt.*;
/** handles storing {@link FullDataSourceV2}'s in the database. */
public class BeaconBeamDTO implements IBaseDTO<DhBlockPos>
{
public DhBlockPos pos;
public DhBlockPos blockPos;
public Color color;
@@ -37,9 +36,9 @@ public class BeaconBeamDTO implements IBaseDTO<DhBlockPos>
// constructor //
//=============//
public BeaconBeamDTO(DhBlockPos pos, Color color)
public BeaconBeamDTO(DhBlockPos blockPos, Color color)
{
this.pos = pos;
this.blockPos = blockPos;
this.color = color;
}
@@ -50,6 +49,6 @@ public class BeaconBeamDTO implements IBaseDTO<DhBlockPos>
//===========//
@Override
public DhBlockPos getKey() { return this.pos; }
public DhBlockPos getKey() { return this.blockPos; }
}
@@ -20,7 +20,7 @@
package com.seibel.distanthorizons.core.sql.repo;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.pos.DhBlockPos;
import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos;
import com.seibel.distanthorizons.core.pos.DhChunkPos;
import com.seibel.distanthorizons.core.pos.DhSectionPos;
import com.seibel.distanthorizons.core.sql.dto.BeaconBeamDTO;
@@ -60,7 +60,7 @@ public class BeaconBeamRepo extends AbstractDhRepo<DhBlockPos, BeaconBeamDTO>
public String getTableName() { return "BeaconBeam"; }
@Override
public String createWhereStatement(DhBlockPos pos) { return "BlockPosX = "+pos.x+" AND BlockPosY = "+pos.y+" AND BlockPosZ = "+pos.z; }
public String createWhereStatement(DhBlockPos pos) { return "BlockPosX = "+ pos.getX() +" AND BlockPosY = "+ pos.getY() +" AND BlockPosZ = "+ pos.getZ(); }
@@ -100,9 +100,9 @@ public class BeaconBeamRepo extends AbstractDhRepo<DhBlockPos, BeaconBeamDTO>
PreparedStatement statement = this.createPreparedStatement(sql);
int i = 1;
statement.setObject(i++, dto.pos.x);
statement.setObject(i++, dto.pos.y);
statement.setObject(i++, dto.pos.z);
statement.setObject(i++, dto.blockPos.getX());
statement.setObject(i++, dto.blockPos.getY());
statement.setObject(i++, dto.blockPos.getZ());
statement.setObject(i++, dto.color.getRed());
statement.setObject(i++, dto.color.getGreen());
@@ -132,9 +132,9 @@ public class BeaconBeamRepo extends AbstractDhRepo<DhBlockPos, BeaconBeamDTO>
statement.setObject(i++, System.currentTimeMillis()); // last modified unix time
statement.setObject(i++, dto.pos.x);
statement.setObject(i++, dto.pos.y);
statement.setObject(i++, dto.pos.z);
statement.setObject(i++, dto.blockPos.getX());
statement.setObject(i++, dto.blockPos.getY());
statement.setObject(i++, dto.blockPos.getZ());
return statement;
}
@@ -19,11 +19,9 @@
package com.seibel.distanthorizons.core.sql.repo;
import com.seibel.distanthorizons.core.file.structure.AbstractSaveStructure;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.pos.DhChunkPos;
import com.seibel.distanthorizons.core.sql.dto.ChunkHashDTO;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper;
import org.apache.logging.log4j.Logger;
import java.io.File;
@@ -56,7 +54,7 @@ public class ChunkHashRepo extends AbstractDhRepo<DhChunkPos, ChunkHashDTO>
public String getTableName() { return "ChunkHash"; }
@Override
public String createWhereStatement(DhChunkPos pos) { return "ChunkPosX = '"+pos.x+"' AND ChunkPosZ = '"+pos.z+"'"; }
public String createWhereStatement(DhChunkPos pos) { return "ChunkPosX = '"+ pos.getX() +"' AND ChunkPosZ = '"+ pos.getZ() +"'"; }
@@ -93,8 +91,8 @@ public class ChunkHashRepo extends AbstractDhRepo<DhChunkPos, ChunkHashDTO>
PreparedStatement statement = this.createPreparedStatement(sql);
int i = 1;
statement.setObject(i++, dto.pos.x);
statement.setObject(i++, dto.pos.z);
statement.setObject(i++, dto.pos.getX());
statement.setObject(i++, dto.pos.getZ());
statement.setObject(i++, dto.chunkHash);
@@ -119,8 +117,8 @@ public class ChunkHashRepo extends AbstractDhRepo<DhChunkPos, ChunkHashDTO>
statement.setObject(i++, dto.chunkHash);
statement.setObject(i++, System.currentTimeMillis()); // last modified unix time
statement.setObject(i++, dto.pos.x);
statement.setObject(i++, dto.pos.z);
statement.setObject(i++, dto.pos.getX());
statement.setObject(i++, dto.pos.getZ());
return statement;
}
@@ -20,7 +20,6 @@
package com.seibel.distanthorizons.core.util;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.CancellationException;
import java.util.concurrent.CompletionException;
@@ -29,11 +28,8 @@ import java.util.concurrent.RejectedExecutionException;
import com.seibel.distanthorizons.api.enums.config.EDhApiVanillaOverdraw;
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.pos.DhChunkPos;
import com.seibel.distanthorizons.core.pos.Pos2D;
import com.seibel.distanthorizons.core.render.vertexFormat.DefaultLodVertexFormats;
import com.seibel.distanthorizons.core.render.vertexFormat.LodVertexFormat;
import com.seibel.distanthorizons.core.util.gridList.EdgeDistanceBooleanGrid;
import com.seibel.distanthorizons.core.util.objects.UncheckedInterruptedException;
import com.seibel.distanthorizons.core.wrapperInterfaces.block.IBlockStateWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftRenderWrapper;
@@ -44,9 +40,6 @@ import org.apache.logging.log4j.Logger;
/**
* This class holds methods and constants that may be used in multiple places.
*
* @author James Seibel
* @version 2022-12-5
*/
public class LodUtil
{
@@ -54,31 +47,7 @@ public class LodUtil
private static final IMinecraftRenderWrapper MC_RENDER = SingletonInjector.INSTANCE.get(IMinecraftRenderWrapper.class);
private static final Logger LOGGER = DhLoggerBuilder.getLogger();
/**
* Vanilla render distances less than or equal to this will not allow partial
* overdraw. The VanillaOverdraw will either be ALWAYS or NEVER.
*/
public static final int MINIMUM_RENDER_DISTANCE_FOR_PARTIAL_OVERDRAW = 4;
/**
* Vanilla render distances less than or equal to this will cause the overdraw to
* run at a smaller fraction of the vanilla render distance.
*/
public static final int MINIMUM_RENDER_DISTANCE_FOR_FAR_OVERDRAW = 11;
/**
* alpha used when drawing chunks in debug mode
*/
public static final int DEBUG_ALPHA = 255; // 0 - 25;
public static final int COLOR_DEBUG_BLACK = ColorUtil.rgbToInt(DEBUG_ALPHA, 0, 0, 0);
public static final int COLOR_DEBUG_WHITE = ColorUtil.rgbToInt(DEBUG_ALPHA, 255, 255, 255);
public static final int COLOR_INVISIBLE = ColorUtil.rgbToInt(0, 0, 0, 0);
//FIXME: WE NEED MORE COLORS!!!!
/**
* In order of nearest to farthest: <br>
* Red, Orange, Yellow, Green, Cyan, Blue, Magenta, white, gray, black
@@ -166,92 +135,9 @@ public class LodUtil
/**
* Gets the ServerWorld for the relevant dimension.
*
* @return null if there is no ServerWorld for the given dimension
*/
public static ILevelWrapper getServerWorldFromDimension(IDimensionTypeWrapper newDimension)
{
if (!MC_CLIENT.hasSinglePlayerServer())
return null;
Iterable<ILevelWrapper> worlds = MC_CLIENT.getAllServerWorlds();
ILevelWrapper returnWorld = null;
for (ILevelWrapper world : worlds)
{
if (world.getDimensionType() == newDimension)
{
returnWorld = world;
break;
}
}
return returnWorld;
}
public static int computeOverdrawOffset()
{
int chunkRenderDist = MC_RENDER.getRenderDistance() + 1;
EDhApiVanillaOverdraw overdraw = EDhApiVanillaOverdraw.ALWAYS; //Config.Client.Advanced.Graphics.AdvancedGraphics.vanillaOverdraw.get();
if (overdraw == EDhApiVanillaOverdraw.ALWAYS) return Integer.MAX_VALUE;
int offset;
if (overdraw == EDhApiVanillaOverdraw.NEVER)
{
offset = 0; //Config.Client.Advanced.Graphics.AdvancedGraphics.overdrawOffset.get();
}
else
{
if (chunkRenderDist < MINIMUM_RENDER_DISTANCE_FOR_FAR_OVERDRAW)
{
offset = 1;
}
else
{
offset = chunkRenderDist / 5;
}
}
if (chunkRenderDist - offset <= 1)
{
return Integer.MAX_VALUE;
}
return offset;
}
/** not currently used since the new rendering system can't easily toggle single chunks to render */
@Deprecated
public static EdgeDistanceBooleanGrid readVanillaRenderedChunks()
{
int offset = computeOverdrawOffset();
if (offset == Integer.MAX_VALUE) return null;
int renderDist = MC_RENDER.getRenderDistance() + 1;
Iterator<DhChunkPos> posIter = MC_RENDER.getVanillaRenderedChunks().iterator();
return new EdgeDistanceBooleanGrid(new Iterator<Pos2D>()
{
@Override
public boolean hasNext()
{
return posIter.hasNext();
}
@Override
public Pos2D next()
{
DhChunkPos pos = posIter.next();
return new Pos2D(pos.x, pos.z);
}
},
MC_CLIENT.getPlayerChunkPos().x - renderDist,
MC_CLIENT.getPlayerChunkPos().z - renderDist,
renderDist * 2 + 1);
}
//=========//
// methods //
//=========//
/** Returns the chunk int position for the given double position */
public static int getChunkPosFromDouble(double value) { return (int) Math.floor(value / CHUNK_WIDTH); }
@@ -275,11 +161,6 @@ public class LodUtil
return true;
}
public static void checkInterrupts() throws InterruptedException
{
if (Thread.interrupted()) throw new InterruptedException();
}
/**
* Format a given string with params using log4j's MessageFormat
*
@@ -290,26 +171,7 @@ public class LodUtil
* Do not use it for deserialization or naming of objects.</b>
* @author leetom
*/
public static String formatLog(String str, Object... param)
{
return LOGGER.getMessageFactory().newMessage(str, param).getFormattedMessage();
}
/**
* Returns a shortened version of the given string that is no longer than maxLength. <br>
* If null returns the empty string.
*/
public static String shortenString(String str, int maxLength)
{
if (str == null)
{
return "";
}
else
{
return str.substring(0, Math.min(str.length(), maxLength));
}
}
public static String formatLog(String str, Object... param) { return LOGGER.getMessageFactory().newMessage(str, param).getFormattedMessage(); }
public static class AssertFailureException extends RuntimeException
{
@@ -829,7 +829,7 @@ public class RenderDataPointReducingList
int size = view.size();
if (size <= 0)
{
return RenderDataPointUtil.createVoidDataPoint();
return RenderDataPointUtil.EMPTY_DATA;
}
long highestDataPoint;
@@ -849,7 +849,7 @@ public class RenderDataPointReducingList
}
}
//no visible segments, return void.
return RenderDataPointUtil.createVoidDataPoint();
return RenderDataPointUtil.EMPTY_DATA;
}
//second loop: merge the rest of the segments.
@@ -889,7 +889,7 @@ public class RenderDataPointReducingList
// so, if we didn't set any data points, add a void data point.
if (writeIndex == 0)
{
view.set(writeIndex++, RenderDataPointUtil.createVoidDataPoint());
view.set(writeIndex++, RenderDataPointUtil.EMPTY_DATA);
}
for (int size = view.size(); writeIndex < size; writeIndex++)
@@ -19,6 +19,7 @@
package com.seibel.distanthorizons.core.util;
import com.seibel.distanthorizons.api.enums.rendering.EDhApiBlockMaterial;
import com.seibel.distanthorizons.core.level.AbstractDhLevel;
import com.seibel.distanthorizons.core.logging.SpamReducedLogger;
import com.seibel.distanthorizons.core.dataObjects.render.columnViews.ColumnArrayView;
@@ -110,8 +111,6 @@ public class RenderDataPointUtil
// datapoint manipulation //
//========================//
public static long createVoidDataPoint() { return EMPTY_DATA; }
public static long createDataPoint(int height, int depth, int color, int lightSky, int lightBlock, int irisBlockMaterialId)
{
return createDataPoint(
@@ -249,7 +248,7 @@ public class RenderDataPointUtil
getBlue(dataPoint) +
" BL:" + getLightBlock(dataPoint) +
" SL:" + getLightSky(dataPoint) +
" BID:" + getBlockMaterialId(dataPoint);
" MAT:" + getBlockMaterialId(dataPoint) + "["+ EDhApiBlockMaterial.getFromIndex(getBlockMaterialId(dataPoint))+"]";
}
}
@@ -303,352 +302,6 @@ public class RenderDataPointUtil
list.reduce(output.verticalSize());
list.copyTo(output);
}
//old logic left here in case it's ever needed again.
/*
if (output.dataCount() != 1)
{
throw new IllegalArgumentException("output must be only reserved for one datapoint!");
}
int inputVerticalSize = sourceData.verticalSize();
int outputVerticalSize = output.verticalSize();
output.fill(0);
//dataCount indicate how many position we are merging in one position
int dataCount = sourceData.dataCount();
// We initialize the arrays that are going to be used
int heightAndDepthLength = (MAX_WORLD_Y_SIZE / 2 + 16) * 2;
short[] heightAndDepth = tLocalHeightAndDepth.get();
if (heightAndDepth == null || heightAndDepth.length != heightAndDepthLength)
{
heightAndDepth = new short[heightAndDepthLength];
tLocalHeightAndDepth.set(heightAndDepth);
}
byte genMode = getGenerationMode(sourceData.get(0));
if (genMode == 0)
{
genMode = 1; // FIXME: Hack to make the version 10 genMode never be 0.
}
boolean allEmpty = true;
boolean allVoid = true;
boolean limited = false;
boolean allDefault;
long singleData;
short yMin;
short yMax;
int count = 0;
int i;
int ii;
int[] indices = tLocalIndices.get();
if (indices == null || indices.length != dataCount)
{
indices = new int[dataCount];
tLocalIndices.set(indices);
}
Arrays.fill(indices, 0);
boolean[] increaseIndex = tLocalIncreaseIndex.get();
if (increaseIndex == null || increaseIndex.length != dataCount)
{
increaseIndex = new boolean[dataCount];
tLocalIncreaseIndex.set(increaseIndex);
}
boolean[] indexHandled = tLocalIndexHandled.get();
if (indexHandled == null || indexHandled.length != dataCount)
{
indexHandled = new boolean[dataCount];
tLocalIndexHandled.set(indexHandled);
}
long tempData;
for (int index = 0; index < dataCount; index++)
{
tempData = sourceData.get(index * inputVerticalSize);
allVoid = allVoid && RenderDataPointUtil.isVoid(tempData);
allEmpty = allEmpty && !RenderDataPointUtil.doesDataPointExist(tempData);
}
//We check if there is any data that's not empty or void
if (allEmpty)
{
return;
}
else if (allVoid)
{
output.set(0, createVoidDataPoint(genMode));
return;
}
//this check is used only to see if we have checked all the values in the array
boolean stillHasDataToCheck = true;
short prevDepth;
while (stillHasDataToCheck)
{
Arrays.fill(indexHandled, false);
boolean connected = true;
int newHeight = -10000;
int newDepth = -10000;
int tempYMax;
int tempYMin;
while (connected)
{
Arrays.fill(increaseIndex, false);
for (int index = 0; index < dataCount; index++)
{
if (indices[index] < inputVerticalSize)
{
tempData = sourceData.get(index * inputVerticalSize + indices[index]);
if (!RenderDataPointUtil.isVoid(tempData) && RenderDataPointUtil.doesDataPointExist(tempData))
{
tempYMax = RenderDataPointUtil.getYMax(tempData);
tempYMin = RenderDataPointUtil.getYMin(tempData);
if (tempYMin >= newHeight)
{
//First case
//the column we are checking is higher than the current column
newDepth = tempYMin;
newHeight = tempYMax;
Arrays.fill(increaseIndex, false);
Arrays.fill(indexHandled, false);
increaseIndex[index] = true;
indexHandled[index] = true;
}
else if ((tempYMin >= newDepth) && (tempYMax <= newHeight))
{
//the column we are checking is contained in the current column
//we simply increase this index
increaseIndex[index] = true;
indexHandled[index] = true;
}
else if (tempYMax > newHeight && tempYMin <= newDepth)
{
newDepth = tempYMin;
newHeight = tempYMax;
increaseIndex[index] = true;
indexHandled[index] = true;
}
else if (tempYMax > newDepth && tempYMax <= newHeight)
{
//the column we are checking touches the current column from the bottom
//for this reason we extend what's below
//We want to avoid to expend this column if it has already been expanded by
//this index
if (!indexHandled[index])
{
newDepth = tempYMin;
increaseIndex[index] = true;
indexHandled[index] = true;
}
}
else if (tempYMin < newHeight && tempYMin > newDepth)
{
//the column we are checking touches the current column from the top
//for this reason we extend the top
newHeight = tempYMax;
increaseIndex[index] = true;
}
}
else
{
indexHandled[index] = true;
}
}
}
//if we added any new data there is a chance that we could add more
//for this reason we would continue
//if no data is added than the column hasn't changed.
//for this reason we can start working on a new column
connected = false;
for (int index = 0; index < dataCount; index++)
{
if (increaseIndex[index])
{
connected = true;
indices[index]++;
}
}
}
//Now we add the height and depth data we extracted to the heightAndDepth array
if (newDepth != newHeight)
{
if (count != 0)
{
prevDepth = heightAndDepth[(count - 1) * 2 + 1];
if (newHeight > prevDepth)
{
newHeight = (short) Math.min(newHeight, prevDepth);
}
}
heightAndDepth[count * 2] = (short) newHeight;
heightAndDepth[count * 2 + 1] = (short) newDepth;
count++;
}
//Here we check the condition that makes the loop continue
//We stop the loop only if there is no more data to check
stillHasDataToCheck = false;
for (int index = 0; index < dataCount; index++)
{
if (indices[index] < inputVerticalSize)
{
tempData = sourceData.get(index * inputVerticalSize + indices[index]);
stillHasDataToCheck |= !RenderDataPointUtil.isVoid(tempData) && RenderDataPointUtil.doesDataPointExist(tempData);
}
}
}
//we limit the vertical portion to maxVerticalData
int j = 0;
while (count > outputVerticalSize)
{
limited = true;
ii = MAX_WORLD_Y_SIZE;
for (i = 0; i < count - 1; i++)
{
if (heightAndDepth[i * 2 + 1] - heightAndDepth[(i + 1) * 2] <= ii)
{
ii = heightAndDepth[i * 2 + 1] - heightAndDepth[(i + 1) * 2];
j = i;
}
}
heightAndDepth[j * 2 + 1] = heightAndDepth[(j + 1) * 2 + 1];
for (i = j + 1; i < count - 1; i++)
{
heightAndDepth[i * 2] = heightAndDepth[(i + 1) * 2];
heightAndDepth[i * 2 + 1] = heightAndDepth[(i + 1) * 2 + 1];
}
//System.arraycopy(heightAndDepth, j + 1, heightAndDepth, j, count - j - 1);
count--;
}
//As standard the vertical lods are ordered from top to bottom
if (!limited && dataCount == 1) // This mean source vertSize < output vertSize AND both dataCount == 1
{
sourceData.copyTo(output.data, output.offset, output.vertSize);
}
else
{
//We want to efficiently memorize indexes
int[] dataIndexesCache = tDataIndexCache.get();
if (dataIndexesCache == null || dataIndexesCache.length != dataCount)
{
dataIndexesCache = new int[dataCount];
tDataIndexCache.set(dataIndexesCache);
}
Arrays.fill(dataIndexesCache, 0);
//For each lod height-depth value we have found we now want to generate the rest of the data
//by merging all lods at lower level that are contained inside the new ones
for (j = 0; j < count; j++)
{
//We firstly collect height and depth data
//this will be added to each realtive long DataPoint
yMax = heightAndDepth[j * 2];
yMin = heightAndDepth[j * 2 + 1];
//if both height and depth are at 0 then we finished
if ((yMin == 0 && yMax == 0) || j >= heightAndDepth.length / 2)
{
break;
}
//We initialize data useful for the merge
int numberOfChildren = 0;
allEmpty = true;
allVoid = true;
//We initialize all the new values that we are going to put in the dataPoint
int tempAlpha = 0;
int tempRed = 0;
int tempGreen = 0;
int tempBlue = 0;
int tempLightBlock = 0;
int tempLightSky = 0;
long data = 0;
//For each position that we want to merge
for (int index = 0; index < dataCount; index++)
{
//we scan the lods in the position from top to bottom
while (dataIndexesCache[index] < inputVerticalSize)
{
singleData = sourceData.get(index * inputVerticalSize + dataIndexesCache[index]);
if (doesDataPointExist(singleData) && !isVoid(singleData))
{
dataIndexesCache[index]++;
if ((yMin <= getYMin(singleData) && getYMin(singleData) < yMax)
|| (yMin < getYMax(singleData) && getYMax(singleData) <= yMax))
{
data = singleData;
break;
}
}
else
{
break;
}
}
if (!doesDataPointExist(data))
{
data = createVoidDataPoint(genMode);
}
if (doesDataPointExist(data))
{
allEmpty = false;
if (!isVoid(data))
{
numberOfChildren++;
allVoid = false;
tempAlpha = Math.max(getAlpha(data), tempAlpha);
tempRed += getRed(data) * getRed(data);
tempGreen += getGreen(data) * getGreen(data);
tempBlue += getBlue(data) * getBlue(data);
tempLightBlock += getLightBlock(data);
tempLightSky += getLightSky(data);
}
}
}
//we have at least 1 child
if (dataCount != 1)
{
tempRed = tempRed / numberOfChildren;
tempGreen = tempGreen / numberOfChildren;
tempBlue = tempBlue / numberOfChildren;
tempLightBlock = tempLightBlock / numberOfChildren;
tempLightSky = tempLightSky / numberOfChildren;
}
//data = createDataPoint(tempAlpha, tempRed, tempGreen, tempBlue, height, depth, tempLightSky, tempLightBlock, tempGenMode, allDefault);
//if (j > 0 && getColor(data) == getColor(dataPoint[j]))
//{
// add simplification at the end due to color
//}
output.set(j, createDataPoint(tempAlpha, (int) Math.sqrt(tempRed), (int) Math.sqrt(tempGreen), (int) Math.sqrt(tempBlue), yMax, yMin, tempLightSky, tempLightBlock, genMode));
}
}
*/
}
}
@@ -23,15 +23,12 @@ import com.seibel.distanthorizons.core.api.internal.SharedApi;
import com.seibel.distanthorizons.core.config.Config;
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
import com.seibel.distanthorizons.core.level.IDhClientLevel;
import com.seibel.distanthorizons.core.pos.DhBlockPos;
import com.seibel.distanthorizons.core.pos.DhChunkPos;
import com.seibel.distanthorizons.core.world.IDhClientWorld;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftRenderWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper;
import com.seibel.distanthorizons.coreapi.util.MathUtil;
import com.seibel.distanthorizons.core.util.math.Mat4f;
import com.seibel.distanthorizons.core.util.math.Vec3f;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper;
/**
@@ -48,93 +45,6 @@ public class RenderUtil
//=================//
// culling methods //
//=================//
/**
* Returns if the given ChunkPos is in the loaded area of the world.
*
* @param center the center of the loaded world (probably the player's ChunkPos)
*/
public static boolean isChunkPosInLoadedArea(DhChunkPos pos, DhChunkPos center)
{
return (pos.x >= center.x - MC_RENDER.getRenderDistance()
&& pos.x <= center.x + MC_RENDER.getRenderDistance())
&&
(pos.z >= center.z - MC_RENDER.getRenderDistance()
&& pos.z <= center.z + MC_RENDER.getRenderDistance());
}
/**
* Returns if the given coordinate is in the loaded area of the world.
*
* @param centerCoordinate the center of the loaded world
*/
public static boolean isCoordinateInLoadedArea(int x, int z, int centerCoordinate)
{
return (x >= centerCoordinate - MC_RENDER.getRenderDistance()
&& x <= centerCoordinate + MC_RENDER.getRenderDistance())
&&
(z >= centerCoordinate - MC_RENDER.getRenderDistance()
&& z <= centerCoordinate + MC_RENDER.getRenderDistance());
}
/**
* Find the coordinates that are in the center half of the given
* 2D matrix, starting at (0,0) and going to (2 * lodRadius, 2 * lodRadius).
*/
public static boolean isCoordinateInNearFogArea(int i, int j, int lodRadius)
{
int halfRadius = lodRadius / 2;
return (i >= lodRadius - halfRadius
&& i <= lodRadius + halfRadius)
&&
(j >= lodRadius - halfRadius
&& j <= lodRadius + halfRadius);
}
/**
* Returns true if one of the region's 4 corners is in front
* of the camera.
*/
public static boolean isRegionInViewFrustum(DhBlockPos playerBlockPos, Vec3f cameraDir, int vboRegionX, int vboRegionZ)
{
// convert the vbo position into a direction vector
// starting from the player's position
Vec3f vboVec = new Vec3f(vboRegionX * LodUtil.REGION_WIDTH, 0, vboRegionZ * LodUtil.REGION_WIDTH);
Vec3f playerVec = new Vec3f(playerBlockPos.x, playerBlockPos.y, playerBlockPos.z);
vboVec.subtract(playerVec);
// calculate the 4 corners
Vec3f vboSeVec = new Vec3f(vboVec.x + LodUtil.REGION_WIDTH, vboVec.y, vboVec.z + LodUtil.REGION_WIDTH);
Vec3f vboSwVec = new Vec3f(vboVec.x, vboVec.y, vboVec.z + LodUtil.REGION_WIDTH);
Vec3f vboNwVec = new Vec3f(vboVec.x, vboVec.y, vboVec.z);
Vec3f vboNeVec = new Vec3f(vboVec.x + LodUtil.REGION_WIDTH, vboVec.y, vboVec.z);
// if any corner is visible, this region should be rendered
return isNormalizedVectorInViewFrustum(vboSeVec, cameraDir) ||
isNormalizedVectorInViewFrustum(vboSwVec, cameraDir) ||
isNormalizedVectorInViewFrustum(vboNwVec, cameraDir) ||
isNormalizedVectorInViewFrustum(vboNeVec, cameraDir);
}
/**
* Currently takes the dot product of the two vectors,
* but in the future could do more complicated frustum culling tests.
*/
private static boolean isNormalizedVectorInViewFrustum(Vec3f objectVector, Vec3f cameraDir)
{
// the -0.1 is to offer a slight buffer, so we are
// more likely to render LODs and thus, hopefully prevent
// flickering or odd disappearances
return objectVector.dotProduct(cameraDir) > -0.1;
}
//=====================//
// matrix manipulation //
//=====================//
@@ -166,19 +76,6 @@ public class RenderUtil
return mcModelViewMat.copy();
}
/**
* create and return a new combined modelView/projection matrix based on MC's modelView and projection matrices
*
* @param mcProjMat Minecraft's current projection matrix
* @param mcModelViewMat Minecraft's current model view matrix
*/
public static Mat4f createCombinedModelViewProjectionMatrix(Mat4f mcProjMat, Mat4f mcModelViewMat, float partialTicks)
{
Mat4f lodProj = createLodProjectionMatrix(mcProjMat, partialTicks);
lodProj.multiply(createLodModelViewMatrix(mcModelViewMat));
return lodProj;
}
public static float getNearClipPlaneDistanceInBlocks(float partialTicks)
{
int chunkRenderDistance = MC_RENDER.getRenderDistance();
@@ -243,7 +140,7 @@ public class RenderUtil
{
// if the player is a significant distance above the work, increase the
// near clip plane to fix Z imprecision issues
int playerHeight = MC.getPlayerBlockPos().y;
int playerHeight = MC.getPlayerBlockPos().getY();
int levelMaxHeight = level.getMaxHeight();
if (playerHeight > levelMaxHeight + 1_000)
{
@@ -87,11 +87,11 @@ public class ArrayGridList<T> extends ArrayList<T>
public final T get(Pos2D pos)
{
return get(pos.x, pos.y);
return get(pos.getX(), pos.getY());
}
public final T set(Pos2D pos, T e)
{
return set(pos.x, pos.y, e);
return set(pos.getX(), pos.getY(), e);
}
public T get(int x, int y)
{
@@ -1,100 +0,0 @@
/*
* This file is part of the Distant Horizons mod
* licensed under the GNU LGPL v3 License.
*
* Copyright (C) 2020-2023 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.distanthorizons.core.util.gridList;
import com.seibel.distanthorizons.core.pos.Pos2D;
import com.seibel.distanthorizons.core.util.objects.BoolType;
import java.util.Iterator;
import java.util.function.IntPredicate;
public class EdgeDistanceBooleanGrid extends PosArrayGridList<BoolType>
{
ArrayGridList<Integer> edgeCache = null;
public EdgeDistanceBooleanGrid(Iterator<Pos2D> posIter, int offsetX, int offsetY, int gridSize)
{
super(gridSize, offsetX, offsetY);
while (posIter.hasNext())
{
Pos2D p = posIter.next();
this.set(p, BoolType.TRUE);
}
}
// Return false if it is indeed updated
private static boolean updatePos(ArrayGridList<Integer> grid, int ox, int oy)
{
if (grid.get(ox, oy) < 0) return true;
if (ox == 0 || oy == 0 || ox == grid.gridSize - 1 || oy == grid.gridSize - 1)
{
return true;
}
int v = grid.get(ox, oy);
if (
grid.get(ox, oy + 1) < v ||
grid.get(ox, oy - 1) < v ||
grid.get(ox + 1, oy) < v ||
grid.get(ox - 1, oy) < v
)
{
return true;
}
else
{
grid.set(ox, oy, v + 1);
return false;
}
}
//FIXME: This is slow and expensive. Use queue to make this skip recheck done pos
private void computeEdgeCache()
{
if (edgeCache != null) return;
edgeCache = new ArrayGridList<Integer>(gridSize, (ox, oy) -> {
BoolType b = get(ox + getOffsetX(), oy + getOffsetY());
return b == null ? -1 : 0;
});
final boolean[] isDone = {false};
while (!isDone[0])
{
isDone[0] = true;
edgeCache.forEachPos((ox, oy) -> {
isDone[0] &= updatePos(edgeCache, ox, oy);
});
}
}
// 0 means right on the edge, while 1 means 1 ceil away. Uses Manhattan Distance
public <T extends ArrayGridList<BoolType>> void flagAllWithDistance(T list, IntPredicate predicate)
{
computeEdgeCache();
edgeCache.forEachPos((ox, oy) -> {
int v = edgeCache.get(ox, oy);
if (v < 0 || !predicate.test(v)) return;
list.set(ox + getOffsetX(), oy + getOffsetY(), BoolType.TRUE);
});
}
}
@@ -49,7 +49,7 @@ public class MovableGridRingList<T> extends ArrayList<T> implements List<T>
// constructors //
//==============//
public MovableGridRingList(int halfWidth, Pos2D center) { this(halfWidth, center.x, center.y); }
public MovableGridRingList(int halfWidth, Pos2D center) { this(halfWidth, center.getX(), center.getY()); }
public MovableGridRingList(int halfWidth, int centerX, int centerY)
{
super((halfWidth * 2 + 1) * (halfWidth * 2 + 1));
@@ -68,7 +68,7 @@ public class MovableGridRingList<T> extends ArrayList<T> implements List<T>
//=====================//
/** see {@link MovableGridRingList#get(int, int)} for full documentation */
public T get(Pos2D pos) { return this.get(pos.x, pos.y); }
public T get(Pos2D pos) { return this.get(pos.getX(), pos.getY()); }
/** returns null if x,y is outside the grid */
public T get(int x, int y)
{
@@ -100,7 +100,7 @@ public class MovableGridRingList<T> extends ArrayList<T> implements List<T>
/** see {@link MovableGridRingList#set(int, int, T)} for full documentation */
public boolean set(Pos2D pos, T item) { return this.set(pos.x, pos.y, item); }
public boolean set(Pos2D pos, T item) { return this.set(pos.getX(), pos.getY(), item); }
/** returns false if x,y is outside the grid */
public boolean set(int x, int y, T item)
{
@@ -132,7 +132,7 @@ public class MovableGridRingList<T> extends ArrayList<T> implements List<T>
}
/** see {@link MovableGridRingList#setChained(int, int, T)} for full documentation */
public T setChained(Pos2D pos, T item) { return this.setChained(pos.x, pos.y, item); }
public T setChained(Pos2D pos, T item) { return this.setChained(pos.getX(), pos.getY(), item); }
/**
* returns null if x,y is outside the grid
* Otherwise, returns the new value
@@ -146,7 +146,7 @@ public class MovableGridRingList<T> extends ArrayList<T> implements List<T>
//================//
/** see {@link MovableGridRingList#swap(int, int, T)} for full documentation */
public T swap(Pos2D pos, T item) { return this.swap(pos.x, pos.y, item); }
public T swap(Pos2D pos, T item) { return this.swap(pos.getX(), pos.getY(), item); }
/** returns the input item if x,y is outside the grid */
public T swap(int x, int y, T item)
{
@@ -178,7 +178,7 @@ public class MovableGridRingList<T> extends ArrayList<T> implements List<T>
/** see {@link MovableGridRingList#remove(int, int)} for full documentation */
public T remove(Pos2D pos) { return this.remove(pos.x, pos.y); }
public T remove(Pos2D pos) { return this.remove(pos.getX(), pos.getY()); }
/** remove and return the item at x,y; returns null if the x,y are outside the grid */
public T remove(int x, int y) { return this.swap(x, y, null); }
@@ -230,7 +230,7 @@ public class MovableGridRingList<T> extends ArrayList<T> implements List<T>
Pos2D cPos = this.minPosRef.get();
int newMinX = newCenterX - this.halfWidth;
int newMinY = newCenterY - this.halfWidth;
if (cPos.x == newMinX && cPos.y == newMinY)
if (cPos.getX() == newMinX && cPos.getY() == newMinY)
{
return false;
}
@@ -239,8 +239,8 @@ public class MovableGridRingList<T> extends ArrayList<T> implements List<T>
try
{
cPos = this.minPosRef.get();
int deltaX = newMinX - cPos.x;
int deltaY = newMinY - cPos.y;
int deltaX = newMinX - cPos.getX();
int deltaY = newMinY - cPos.getY();
if (deltaX == 0 && deltaY == 0)
{
return false;
@@ -259,14 +259,14 @@ public class MovableGridRingList<T> extends ArrayList<T> implements List<T>
{
for (int y = 0; y < this.width; y++)
{
Pos2D itemPos = new Pos2D(x + cPos.x, y + cPos.y);
Pos2D itemPos = new Pos2D(x + cPos.getX(), y + cPos.getY());
if (x - deltaX < 0
|| y - deltaY < 0
|| x - deltaX >= this.width
|| y - deltaY >= this.width)
{
T item = this._swapUnsafe(itemPos.x, itemPos.y, null);
T item = this._swapUnsafe(itemPos.getX(), itemPos.getY(), null);
if (item != null && removedItemConsumer != null)
{
removedItemConsumer.accept(item);
@@ -300,10 +300,10 @@ public class MovableGridRingList<T> extends ArrayList<T> implements List<T>
// position getters //
//==================//
public Pos2D getCenter() { return new Pos2D(this.minPosRef.get().x + this.halfWidth, this.minPosRef.get().y + this.halfWidth); }
public Pos2D getCenter() { return new Pos2D(this.minPosRef.get().getX() + this.halfWidth, this.minPosRef.get().getY() + this.halfWidth); }
public Pos2D getMinPosInRange() { return this.minPosRef.get(); }
public Pos2D getMaxPosInRange() { return new Pos2D(this.minPosRef.get().x + this.width - 1, this.minPosRef.get().y + this.width - 1); }
public Pos2D getMaxPosInRange() { return new Pos2D(this.minPosRef.get().getX() + this.width - 1, this.minPosRef.get().getY() + this.width - 1); }
public int getWidth() { return this.width; }
public int getHalfWidth() { return this.halfWidth; }
@@ -321,18 +321,18 @@ public class MovableGridRingList<T> extends ArrayList<T> implements List<T>
public boolean inRange(int x, int y)
{
Pos2D minPos = this.minPosRef.get();
return (x >= minPos.x
&& x < minPos.x + this.width
&& y >= minPos.y
&& y < minPos.y + this.width);
return (x >= minPos.getX()
&& x < minPos.getX() + this.width
&& y >= minPos.getY()
&& y < minPos.getY() + this.width);
}
private boolean _inRangeAcquired(int x, int y, Pos2D min)
{
return (x >= min.x
&& x < min.x + this.width
&& y >= min.y
&& y < min.y + this.width);
return (x >= min.getX()
&& x < min.getX() + this.width
&& y >= min.getY()
&& y < min.getY() + this.width);
}
private T _getUnsafe(int x, int y) { return super.get(Math.floorMod(x, this.width) + Math.floorMod(y, this.width) * this.width); }
@@ -379,9 +379,9 @@ public class MovableGridRingList<T> extends ArrayList<T> implements List<T>
try
{
Pos2D min = this.minPosRef.get();
for (int x = min.x; x < min.x + this.width; x++)
for (int x = min.getX(); x < min.getX() + this.width; x++)
{
for (int y = min.y; y < min.y + this.width; y++)
for (int y = min.getY(); y < min.getY() + this.width; y++)
{
T t = this._getUnsafe(x, y);
consumer.accept(t, new Pos2D(x, y));
@@ -412,7 +412,7 @@ public class MovableGridRingList<T> extends ArrayList<T> implements List<T>
Pos2D min = this.minPosRef.get();
for (Pos2D offset : this.ringPositionIteratorArray)
{
T item = this._getUnsafe(min.x + offset.x, min.y + offset.y);
T item = this._getUnsafe(min.getX() + offset.getX(), min.getY() + offset.getY());
if (item != null)
{
consumer.accept(item);
@@ -443,9 +443,9 @@ public class MovableGridRingList<T> extends ArrayList<T> implements List<T>
Pos2D min = this.minPosRef.get();
for (Pos2D offset : this.ringPositionIteratorArray)
{
LodUtil.assertTrue(this._inRangeAcquired(min.x + offset.x, min.y + offset.y, min));
T item = this._getUnsafe(min.x + offset.x, min.y + offset.y);
consumer.accept(item, new Pos2D(min.x + offset.x, min.y + offset.y));
LodUtil.assertTrue(this._inRangeAcquired(min.getX() + offset.getX(), min.getY() + offset.getY(), min));
T item = this._getUnsafe(min.getX() + offset.getX(), min.getY() + offset.getY());
consumer.accept(item, new Pos2D(min.getX() + offset.getX(), min.getY() + offset.getY()));
}
}
finally
@@ -479,8 +479,8 @@ public class MovableGridRingList<T> extends ArrayList<T> implements List<T>
// sort the positions from nearest to farthest from the world origin
Arrays.sort(posArray, (a, b) ->
{
long disSqrA = (long) a.x * a.x + (long) a.y * a.y;
long disSqrB = (long) b.x * b.x + (long) b.y * b.y;
long disSqrA = (long) a.getX() * a.getX() + (long) a.getY() * a.getY();
long disSqrB = (long) b.getX() * b.getX() + (long) b.getY() * b.getY();
return Double.compare(disSqrA, disSqrB);
});
@@ -490,8 +490,8 @@ public class MovableGridRingList<T> extends ArrayList<T> implements List<T>
}
for (Pos2D pos2D : posArray)
{
LodUtil.assertTrue(pos2D.x >= 0 && pos2D.x < this.width);
LodUtil.assertTrue(pos2D.y >= 0 && pos2D.y < this.width);
LodUtil.assertTrue(pos2D.getX() >= 0 && pos2D.getX() < this.width);
LodUtil.assertTrue(pos2D.getY() >= 0 && pos2D.getY() < this.width);
}
this.ringPositionIteratorArray = posArray;
@@ -508,7 +508,7 @@ public class MovableGridRingList<T> extends ArrayList<T> implements List<T>
public String toString()
{
Pos2D p = this.minPosRef.get();
return this.getClass().getSimpleName() + "[" + (p.x + this.halfWidth) + "," + (p.y + this.halfWidth) + "] " + this.width + "*" + this.width + "[" + this.size() + "]";
return this.getClass().getSimpleName() + "[" + (p.getX() + this.halfWidth) + "," + (p.getY() + this.halfWidth) + "] " + this.width + "*" + this.width + "[" + this.size() + "]";
}
public String toDetailString()
@@ -20,6 +20,7 @@
package com.seibel.distanthorizons.core.util.math;
import com.seibel.distanthorizons.api.objects.math.DhApiVec3d;
import com.seibel.distanthorizons.api.objects.math.DhApiVec3f;
import com.seibel.distanthorizons.coreapi.util.MathUtil;
/**
@@ -134,7 +135,6 @@ public class Vec3d extends DhApiVec3d
public Vec3d copy() { return new Vec3d(this.x, this.y, this.z); }
// Forge start
public Vec3d(double[] values) { this.set(values); }
public void set(double[] values)
@@ -144,4 +144,31 @@ public class Vec3d extends DhApiVec3d
this.z = values[2];
}
public static double getManhattanDistance(DhApiVec3d a, DhApiVec3d b)
{
return Math.abs(a.x - b.x)
+ Math.abs(a.y - b.y)
+ Math.abs(a.z - b.z);
}
public static double getDistance(DhApiVec3d a, DhApiVec3d b)
{
return Math.sqrt(Math.pow(a.x - b.x, 2)
+ Math.pow(a.y - b.y, 2)
+ Math.pow(a.z - b.z, 2));
}
/** slightly faster version of {@link Vec3d#getDistance} */
public static double getSquaredDistance(DhApiVec3d a, DhApiVec3d b)
{
return Math.pow(a.x - b.x, 2)
+ Math.pow(a.y - b.y, 2)
+ Math.pow(a.z - b.z, 2);
}
/** Gets the distance between points A and B, ignoring Y height. */
public static double getHorizontalDistance(DhApiVec3d a, DhApiVec3d b)
{
return Math.sqrt(Math.pow(a.x - b.x, 2)
+ Math.pow(a.z - b.z, 2));
}
}
@@ -20,7 +20,6 @@
package com.seibel.distanthorizons.core.util.math;
import com.seibel.distanthorizons.api.objects.math.DhApiVec3f;
import com.seibel.distanthorizons.core.pos.DhBlockPos2D;
import com.seibel.distanthorizons.coreapi.util.MathUtil;
/**
@@ -0,0 +1,111 @@
package com.seibel.distanthorizons.core.util.objects;
import java.util.Arrays;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* This can be used for easily profiling methods to get the average execution time. <br>
* This is a thread safe implementation.
*/
public class RollingAverage
{
// properties //
private final double[] values;
private final int size;
private int index = 0;
private double sum = 0.0;
private final Lock arrayLock = new ReentrantLock();
//=============//
// constructor //
//=============//
public RollingAverage(int size)
{
if (size <= 0)
{
throw new IllegalArgumentException("Size must be greater than 0");
}
this.size = size;
this.values = new double[size];
}
//=======//
// input //
//=======//
public void addValue(double value)
{
this.arrayLock.lock();
try
{
// Subtract the oldest value from the sum
this.sum -= this.values[this.index];
// Update the buffer with the new value
this.values[this.index] = value;
// Add the new value to the sum
this.sum += value;
// Move to the next index
this.index = (this.index + 1) % this.size;
}
finally
{
this.arrayLock.unlock();
}
}
public void clear()
{
this.arrayLock.lock();
try
{
this.sum = 0;
this.index = 0;
Arrays.fill(this.values, 0);
}
finally
{
this.arrayLock.unlock();
}
}
//========//
// output //
//========//
/** Gets the current rolling average. */
public double getAverage()
{
this.arrayLock.lock();
try
{
return (this.sum / this.size);
}
finally
{
this.arrayLock.unlock();
}
}
/** rounded to two decimals*/
public String getAverageRoundedString() { return String.format("%.2f", this.getAverage()); }
//================//
// base overrides //
//================//
@Override
public String toString() { return "avg: "+this.getAverageRoundedString()+" max count: "+this.size; }
}
@@ -350,6 +350,6 @@ public class QuadNode<T>
//==============//
@Override
public String toString() { return "pos: " + this.sectionPos + ", children #: " + this.getTotalChildCount() + ", value: " + this.value; }
public String toString() { return "pos: " + DhSectionPos.toString(this.sectionPos) + ", children #: " + this.getTotalChildCount() + ", value: " + this.value; }
}
@@ -20,7 +20,7 @@
package com.seibel.distanthorizons.core.util.objects.quadTree;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.pos.DhBlockPos2D;
import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos2D;
import com.seibel.distanthorizons.core.pos.DhLodPos;
import com.seibel.distanthorizons.core.pos.DhSectionPos;
import com.seibel.distanthorizons.core.pos.Pos2D;
@@ -31,6 +31,7 @@ import com.seibel.distanthorizons.core.util.gridList.MovableGridRingList;
import it.unimi.dsi.fastutil.longs.LongArrayFIFOQueue;
import it.unimi.dsi.fastutil.longs.LongIterator;
import org.apache.logging.log4j.Logger;
import org.jetbrains.annotations.Nullable;
import java.util.*;
import java.util.function.Consumer;
@@ -85,7 +86,7 @@ public class QuadTree<T>
Pos2D ringListCenterPos = new Pos2D(
BitShiftUtil.divideByPowerOfTwo(this.centerBlockPos.x, this.treeMinDetailLevel),
BitShiftUtil.divideByPowerOfTwo(this.centerBlockPos.z, this.treeMinDetailLevel));
this.topRingList = new MovableGridRingList<>(halfSizeInRootNodes, ringListCenterPos.x, ringListCenterPos.y);
this.topRingList = new MovableGridRingList<>(halfSizeInRootNodes, ringListCenterPos.getX(), ringListCenterPos.getY());
}
@@ -96,8 +97,10 @@ public class QuadTree<T>
//=====================//
/** @return the node at the given section position */
@Nullable
public final QuadNode<T> getNode(long pos) throws IndexOutOfBoundsException { return this.getOrSetNode(pos, false, null, true); }
/** @return the value at the given section position */
@Nullable
public final T getValue(long pos) throws IndexOutOfBoundsException
{
QuadNode<T> node = this.getNode(pos);
@@ -109,6 +112,7 @@ public class QuadTree<T>
}
/** @return the value that was previously in the given position, null if nothing */
@Nullable
public final T setValue(long pos, T value) throws IndexOutOfBoundsException
{
T previousValue = this.getValue(pos);
@@ -117,6 +121,7 @@ public class QuadTree<T>
}
/** @param runBoundaryChecks should only ever be set to true internally for removing out of bound nodes */
@Nullable
protected final QuadNode<T> getOrSetNode(long pos, boolean setNewValue, T newValue, boolean runBoundaryChecks) throws IndexOutOfBoundsException
{
if (runBoundaryChecks && !this.isSectionPosInBounds(pos))
@@ -260,7 +265,7 @@ public class QuadTree<T>
// remove out of bounds root nodes
this.topRingList.moveTo(expectedCenterPos.x, expectedCenterPos.y, (quadNode) ->
this.topRingList.moveTo(expectedCenterPos.getX(), expectedCenterPos.getY(), (quadNode) ->
{
if (quadNode != null && removedItemConsumer != null)
{
@@ -409,7 +414,7 @@ public class QuadTree<T>
{
if (node != null || includeNullNodes)
{
long rootPos = DhSectionPos.encode(QuadTree.this.treeMinDetailLevel, pos2D.x, pos2D.y);
long rootPos = DhSectionPos.encode(QuadTree.this.treeMinDetailLevel, pos2D.getX(), pos2D.getY());
if (QuadTree.this.isSectionPosInBounds(rootPos))
{
this.iteratorPosQueue.enqueue(rootPos);
@@ -66,6 +66,11 @@ public class ThreadPoolUtil
@Nullable
public static ThreadPoolExecutor getCleanupExecutor() { return cleanupThreadPool; }
public static final String BEACON_CULLING_THREAD_NAME = "Beacon Culling";
private static ThreadPoolExecutor beaconCullingThreadPool;
@Nullable
public static ThreadPoolExecutor getBeaconCullingExecutor() { return beaconCullingThreadPool; }
//======================//
@@ -76,11 +81,6 @@ public class ThreadPoolUtil
// and all share an underlying number of threads.
// WARNING: great care should be used when setting up these threads since deadlock can occur if they are handled poorly.
public static final DhThreadFactory LIGHT_POPULATOR_THREAD_FACTORY = new DhThreadFactory("LOD Builder - Light Populator", Thread.MIN_PRIORITY);
private static ConfigThreadPool lightPopulatorThreadPool;
@Nullable
public static ThreadPoolExecutor getLightPopulatorExecutor() { return (lightPopulatorThreadPool != null) ? lightPopulatorThreadPool.executor : null; }
public static final DhThreadFactory CHUNK_TO_LOD_BUILDER_THREAD_FACTORY = new DhThreadFactory("LOD Builder - Chunk to Lod Builder", Thread.MIN_PRIORITY);
private static ConfigThreadPool chunkToLodBuilderThreadPool;
@Nullable
@@ -114,6 +114,7 @@ public class ThreadPoolUtil
worldGenThreadPool = new ConfigThreadPool(WORLD_GEN_THREAD_FACTORY, Config.Client.Advanced.MultiThreading.numberOfWorldGenerationThreads, Config.Client.Advanced.MultiThreading.runTimeRatioForWorldGenerationThreads, null);
bufferUploaderThreadPool = ThreadUtil.makeSingleThreadPool(BUFFER_UPLOADER_THREAD_NAME);
cleanupThreadPool = ThreadUtil.makeSingleThreadPool(CLEANUP_THREAD_NAME);
beaconCullingThreadPool = ThreadUtil.makeSingleThreadPool(BEACON_CULLING_THREAD_NAME);
@@ -141,7 +142,6 @@ public class ThreadPoolUtil
}
// create thread pools
lightPopulatorThreadPool = new ConfigThreadPool(LIGHT_POPULATOR_THREAD_FACTORY, Config.Client.Advanced.MultiThreading.numberOfLodBuilderThreads, Config.Client.Advanced.MultiThreading.runTimeRatioForLodBuilderThreads, workerThreadSemaphore);
chunkToLodBuilderThreadPool = new ConfigThreadPool(CHUNK_TO_LOD_BUILDER_THREAD_FACTORY, Config.Client.Advanced.MultiThreading.numberOfLodBuilderThreads, Config.Client.Advanced.MultiThreading.runTimeRatioForLodBuilderThreads, workerThreadSemaphore);
bufferBuilderThreadPool = new ConfigThreadPool(BUFFER_BUILDER_THREAD_FACTORY, Config.Client.Advanced.MultiThreading.numberOfLodBuilderThreads, Config.Client.Advanced.MultiThreading.runTimeRatioForLodBuilderThreads, workerThreadSemaphore);
@@ -155,10 +155,10 @@ public class ThreadPoolUtil
worldGenThreadPool.shutdownExecutorService();
bufferUploaderThreadPool.shutdown();
cleanupThreadPool.shutdown();
beaconCullingThreadPool.shutdown();
// worker threads
ThreadPoolUtil.lightPopulatorThreadPool.shutdownExecutorService();
ThreadPoolUtil.chunkToLodBuilderThreadPool.shutdownExecutorService();
ThreadPoolUtil.bufferBuilderThreadPool.shutdownExecutorService();
@@ -47,9 +47,15 @@ public interface IBlockStateWrapper extends IDhApiBlockStateWrapper
byte getMaterialId();
boolean isBeaconBlock();
/** IE a glass block that can affect the beacon beam color */
boolean isBeaconTintBlock();
/**
* The blocks used by a beacon's base
* IE Iron, diamond, gold, etc.
*/
boolean isBeaconBaseBlock();
Color getMapColor();
boolean isGlassBlock();
Color getBeaconTintColor();
}
@@ -141,6 +141,25 @@ public class ChunkLightStorage
lightSection.set(x, y, z, lightLevel);
}
public void clear()
{
if (this.lightSections != null)
{
for (int i = 0; i < this.lightSections.length; i++)
{
LightSection section = this.lightSections[i];
if (section != null)
{
section.constantValue = LodUtil.MIN_MC_LIGHT;
if (section.data != null)
{
Arrays.fill(section.data, 0L);
}
}
}
}
}
//================//
@@ -20,11 +20,16 @@
package com.seibel.distanthorizons.core.wrapperInterfaces.chunk;
import com.seibel.distanthorizons.core.generation.AdjacentChunkHolder;
import com.seibel.distanthorizons.core.pos.DhBlockPos;
import com.seibel.distanthorizons.core.pos.DhBlockPos2D;
import com.seibel.distanthorizons.core.level.IDhLevel;
import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos;
import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos2D;
import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPosMutable;
import com.seibel.distanthorizons.core.pos.DhChunkPos;
import com.seibel.distanthorizons.core.sql.dto.BeaconBeamDTO;
import com.seibel.distanthorizons.core.util.ColorUtil;
import com.seibel.distanthorizons.core.wrapperInterfaces.block.IBlockStateWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper;
import com.seibel.distanthorizons.coreapi.ModInfo;
import com.seibel.distanthorizons.coreapi.interfaces.dependencyInjection.IBindable;
import com.seibel.distanthorizons.core.util.LodUtil;
@@ -79,18 +84,21 @@ public interface IChunkWrapper extends IBindable
int getDhSkyLight(int relX, int relY, int relZ);
void setDhSkyLight(int relX, int relY, int relZ, int lightValue);
void clearDhSkyLighting();
int getDhBlockLight(int relX, int relY, int relZ);
void setDhBlockLight(int relX, int relY, int relZ, int lightValue);
void clearDhBlockLighting();
int getBlockLight(int relX, int relY, int relZ);
int getSkyLight(int relX, int relY, int relZ);
ArrayList<DhBlockPos> getBlockLightPosList();
/** Note: don't modify this array, it will only be generated once and then shared between uses */
ArrayList<DhBlockPos> getWorldBlockLightPosList();
default boolean blockPosInsideChunk(DhBlockPos blockPos) { return this.blockPosInsideChunk(blockPos.x, blockPos.y, blockPos.z); }
default boolean blockPosInsideChunk(DhBlockPos blockPos) { return this.blockPosInsideChunk(blockPos.getX(), blockPos.getY(), blockPos.getZ()); }
default boolean blockPosInsideChunk(int x, int y, int z)
{
return (x >= this.getMinBlockX() && x <= this.getMaxBlockX()
@@ -107,7 +115,7 @@ public interface IChunkWrapper extends IBindable
String toString();
default IBlockStateWrapper getBlockState(DhBlockPos pos) { return this.getBlockState(pos.x, pos.y, pos.z); }
default IBlockStateWrapper getBlockState(DhBlockPos pos) { return this.getBlockState(pos.getX(), pos.getY(), pos.getZ()); }
IBlockStateWrapper getBlockState(int relX, int relY, int relZ);
IBiomeWrapper getBiome(int relX, int relY, int relZ);
@@ -150,29 +158,77 @@ public interface IChunkWrapper extends IBindable
* This is generally done in cases where MC's lighting is correct now, but may not be later (like when a chunk is unloading).
*
* @throws IllegalStateException if the chunk's lighting isn't valid. This is done to prevent accidentally baking broken lighting.
* @return true if the chunk's lighting was successfully populated, false otherwise
*/
default void bakeDhLightingUsingMcLightingEngine() throws IllegalStateException
@Deprecated
default boolean bakeDhLightingUsingMcLightingEngine(ILevelWrapper levelWrapper) throws IllegalStateException
{
if (!this.isLightCorrect())
{
throw new IllegalStateException("Unable to bake lighting for for chunk [" + this.getChunkPos() + "], Minecraft lighting not valid.");
return false;
}
// get the lighting for every relative block pos
//=======================//
// get lighting for each //
// relative block pos //
//=======================//
boolean lightingFound = false;
// if the level doesn't have sky lights, then this check can be ignored
// since all sky light values will be 0 anyway
boolean skyLightingFound = !levelWrapper.hasSkyLight();
for (int relX = 0; relX < LodUtil.CHUNK_WIDTH; relX++)
{
for (int relZ = 0; relZ < LodUtil.CHUNK_WIDTH; relZ++)
{
for (int y = this.getMinBuildHeight(); y < this.getMaxBuildHeight(); y++)
{
this.setDhSkyLight(relX, y, relZ, this.getSkyLight(relX, y, relZ));
this.setDhBlockLight(relX, y, relZ, this.getBlockLight(relX, y, relZ));
int skyLight = this.getSkyLight(relX, y, relZ);
this.setDhSkyLight(relX, y, relZ, skyLight);
int blockLight = this.getBlockLight(relX, y, relZ);
this.setDhBlockLight(relX, y, relZ, blockLight);
// MC defaults to max sky light and no block light, including underground blocks.
// If any position has something different then those default values, it's likely that the
// lighting was properly populated for at least part of the chunk
if (!lightingFound &&
(skyLight != LodUtil.MAX_MC_LIGHT || blockLight != LodUtil.MIN_MC_LIGHT))
{
lightingFound = true;
}
if (!skyLightingFound
&& skyLight != LodUtil.MIN_MC_LIGHT)
{
skyLightingFound = true;
}
}
}
}
//=================//
// validate result //
//=================//
// if no lighting was found or the sky is always black, the lighting is likely broken
if (!lightingFound || !skyLightingFound
// if lighting is no longer correct or doesn't match the saved values
// its very likely it broke halfway through and will need regenerating
|| !this.isLightCorrect()
|| this.getSkyLight(0, 0, 0) != this.getDhSkyLight(0,0,0)
|| this.getBlockLight(0, 0, 0) != this.getDhBlockLight(0,0,0))
{
return false;
}
// lighting is valid
this.setIsDhLightCorrect(true);
this.setUseDhLighting(true);
return true;
}
@@ -229,22 +285,59 @@ public interface IChunkWrapper extends IBindable
int hash = 31;
int primeBlockMultiplier = 227;
int primeBiomeMultiplier = 701;
int primeHeightMultiplier = 137;
int minBuildHeight = this.getMinBuildHeight();
int maxBuildHeight = this.getMaxBuildHeight();
int minBuildHeight = this.getMinNonEmptyHeight();
int maxBuildHeight = this.getMaxNonEmptyHeight();
// most blocks (only some blocks are sampled since checking every block is a very slow operation)
for (int x = 0; x < LodUtil.CHUNK_WIDTH; x+=2)
{
for (int z = 0; z < LodUtil.CHUNK_WIDTH; z+=2)
{
for (int y = minBuildHeight; y < maxBuildHeight; y+=2)
{
hash = (hash * primeBlockMultiplier) + this.getBlockState(x, y, z).hashCode();
hash = (hash * primeBiomeMultiplier) + this.getBiome(x, y, z).hashCode();
hash = (hash * primeHeightMultiplier) + y;
}
}
}
// surface (this should cover most cases for when users modify chunks)
for (int x = 0; x < LodUtil.CHUNK_WIDTH; x++)
{
for (int z = 0; z < LodUtil.CHUNK_WIDTH; z++)
{
for (int y = minBuildHeight; y < maxBuildHeight; y++)
int lightBlockingY = this.getLightBlockingHeightMapValue(x, z);
hash = (hash * primeBlockMultiplier) + this.getBlockState(x, lightBlockingY, z).hashCode();
hash = (hash * primeBiomeMultiplier) + this.getBiome(x, lightBlockingY, z).hashCode();
hash = (hash * primeHeightMultiplier) + lightBlockingY;
int solidY = this.getSolidHeightMapValue(x, z);
if (solidY != lightBlockingY)
{
hash = (hash * primeBlockMultiplier) + this.getBlockState(x, y, z).hashCode();
hash = (hash * primeBiomeMultiplier) + this.getBiome(x, y, z).hashCode();
hash = (hash * primeBlockMultiplier) + this.getBlockState(x, solidY, z).hashCode();
hash = (hash * primeBiomeMultiplier) + this.getBiome(x, solidY, z).hashCode();
hash = (hash * primeHeightMultiplier) + solidY;
}
}
}
// light emitting blocks (if the light changes then the LOD definitely needs to be updated)
final DhBlockPosMutable relPos = new DhBlockPosMutable();
ArrayList<DhBlockPos> lightPosList = this.getWorldBlockLightPosList();
for (int i = 0; i < lightPosList.size(); i++)
{
DhBlockPos pos = lightPosList.get(i);
pos.mutateToChunkRelativePos(relPos);
hash = (hash * primeBlockMultiplier) + this.getBlockState(relPos.getX(), relPos.getY(), relPos.getZ()).hashCode();
hash = (hash * primeHeightMultiplier) + relPos.getY();
}
return hash;
}
@@ -254,20 +347,24 @@ public interface IChunkWrapper extends IBindable
AdjacentChunkHolder adjacentChunkHolder = new AdjacentChunkHolder(this, neighbourChunkList);
// since beacons emit light we can check only the positions that are emitting light
ArrayList<DhBlockPos> blockPosList = this.getBlockLightPosList();
// find the beacon block positions,
// since beacons emit light we only need to check the positions that emit light
final DhBlockPosMutable relPos = new DhBlockPosMutable();
ArrayList<DhBlockPos> blockPosList = this.getWorldBlockLightPosList();
for (int i = 0; i < blockPosList.size(); i++)
{
DhBlockPos pos = blockPosList.get(i);
DhBlockPos relPos = pos.convertToChunkRelativePos();
pos.mutateToChunkRelativePos(relPos);
IBlockStateWrapper block = this.getBlockState(relPos);
if (block.isBeaconBlock())
{
// check if this beacon is active and if so what color it should be
Color beaconColor = getBeaconColor(pos, adjacentChunkHolder);
if (beaconColor != null)
{
// beacon is active
BeaconBeamDTO beam = new BeaconBeamDTO(blockPosList.get(i), beaconColor);
beaconBeamList.add(beam);
}
@@ -280,8 +377,8 @@ public interface IChunkWrapper extends IBindable
@Nullable
static Color getBeaconColor(DhBlockPos beaconPos, AdjacentChunkHolder chunkHolder)
{
DhBlockPos beaconRelPos = beaconPos.convertToChunkRelativePos();
DhBlockPos baseRelPos = new DhBlockPos(0, beaconRelPos.y-1, 0);
DhBlockPos beaconRelPos = beaconPos.createChunkRelativePos();
DhBlockPosMutable baseRelPos = new DhBlockPosMutable(0, beaconRelPos.getY() -1, 0);
@@ -293,14 +390,15 @@ public interface IChunkWrapper extends IBindable
{
for (int z = -1; z <= 1; z++)
{
baseRelPos.x = beaconRelPos.x + x;
baseRelPos.z = beaconRelPos.z + z;
baseRelPos.setX(beaconRelPos.getX() + x);
baseRelPos.setZ(beaconRelPos.getZ() + z);
baseRelPos.mutateToChunkRelativePos(baseRelPos);
IChunkWrapper chunk = chunkHolder.getByBlockPos(beaconPos.x + x, beaconPos.z + z);
// if no chunk is loaded assume the beacon is complete in that direction
IChunkWrapper chunk = chunkHolder.getByBlockPos(beaconPos.getX() + x, beaconPos.getZ() + z);
if (chunk != null)
{
IBlockStateWrapper block = chunk.getBlockState(baseRelPos.x, baseRelPos.y, baseRelPos.z);
IBlockStateWrapper block = chunk.getBlockState(baseRelPos.getX(), baseRelPos.getY(), baseRelPos.getZ());
if (!block.isBeaconBaseBlock())
{
return null;
@@ -319,37 +417,35 @@ public interface IChunkWrapper extends IBindable
int red = 0;
int green = 0;
int blue = 0;
boolean glassBlockFound = false;
boolean beaconTintBlockFound = false;
IChunkWrapper centerChunk = chunkHolder.getByBlockPos(beaconPos.x, beaconPos.z);
IChunkWrapper centerChunk = chunkHolder.getByBlockPos(beaconPos.getX(), beaconPos.getZ());
int maxY = centerChunk.getMaxNonEmptyHeight();
for (int y = beaconRelPos.y+1; y <= maxY; y++)
for (int y = beaconRelPos.getY() +1; y <= maxY; y++)
{
IBlockStateWrapper block = centerChunk.getBlockState(beaconRelPos.x, y, beaconRelPos.z);
IBlockStateWrapper block = centerChunk.getBlockState(beaconRelPos.getX(), y, beaconRelPos.getZ());
if (!block.isAir() && block.getOpacity() == LodUtil.BLOCK_FULLY_OPAQUE)
{
return null;
}
if (block.isGlassBlock()
// ignore invisible blocks (which have pure black as their map color, luckily black stained-glass is actually extremely dark gray)
&& !block.getMapColor().equals(Color.BLACK))
if (block.isBeaconTintBlock())
{
red += block.getMapColor().getRed();
green += block.getMapColor().getGreen();
blue += block.getMapColor().getBlue();
red += block.getBeaconTintColor().getRed();
green += block.getBeaconTintColor().getGreen();
blue += block.getBeaconTintColor().getBlue();
if (glassBlockFound)
if (beaconTintBlockFound)
{
red /= 2;
green /= 2;
blue /= 2;
}
glassBlockFound = true;
beaconTintBlockFound = true;
}
}
return glassBlockFound ? new Color(red, green, blue) : Color.WHITE;
return beaconTintBlockFound ? new Color(red, green, blue) : Color.WHITE;
}
@@ -19,12 +19,11 @@
package com.seibel.distanthorizons.core.wrapperInterfaces.minecraft;
import java.io.File;
import java.util.ArrayList;
import java.util.UUID;
import com.seibel.distanthorizons.core.enums.EDhDirection;
import com.seibel.distanthorizons.core.pos.DhBlockPos;
import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos;
import com.seibel.distanthorizons.core.pos.DhChunkPos;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper;
import com.seibel.distanthorizons.coreapi.ModInfo;
@@ -20,20 +20,11 @@
package com.seibel.distanthorizons.core.wrapperInterfaces.minecraft;
import java.awt.Color;
import java.util.HashSet;
import com.seibel.distanthorizons.core.dependencyInjection.ModAccessorInjector;
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
import com.seibel.distanthorizons.core.pos.DhBlockPos;
import com.seibel.distanthorizons.core.pos.DhChunkPos;
import com.seibel.distanthorizons.core.wrapperInterfaces.misc.ILightMapWrapper;
import com.seibel.distanthorizons.coreapi.interfaces.dependencyInjection.IBindable;
import com.seibel.distanthorizons.core.util.math.Mat4f;
import com.seibel.distanthorizons.core.util.math.Vec3d;
import com.seibel.distanthorizons.core.util.math.Vec3f;
import com.seibel.distanthorizons.core.wrapperInterfaces.IVersionConstants;
import com.seibel.distanthorizons.core.wrapperInterfaces.IWrapperFactory;
import com.seibel.distanthorizons.core.wrapperInterfaces.modAccessor.ISodiumAccessor;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper;
import org.jetbrains.annotations.Nullable;
@@ -48,18 +39,10 @@ public interface IMinecraftRenderWrapper extends IBindable
{
Vec3f getLookAtVector();
DhBlockPos getCameraBlockPosition();
boolean playerHasBlindingEffect();
Vec3d getCameraExactPosition();
Mat4f getWorldViewMatrix();
Mat4f getDefaultProjectionMatrix(float partialTicks);
double getGamma();
Color getFogColor(float partialTicks);
default Color getSpecialFogColor(float partialTicks) { return getFogColor(partialTicks); }
@@ -90,63 +73,6 @@ public interface IMinecraftRenderWrapper extends IBindable
*/
void clearTargetFrameBuffer();
/**
* This method returns the ChunkPos of all chunks that Minecraft
* is going to render this frame.
* <br>
* If not implemented this calls {@link #getMaximumRenderedChunks()}.
*/
default HashSet<DhChunkPos> getVanillaRenderedChunks()
{
// FIXME: Is this actually required? Does it make a differance if it exists or not?
ISodiumAccessor sodium = ModAccessorInjector.INSTANCE.get(ISodiumAccessor.class);
return sodium == null ? getMaximumRenderedChunks() : sodium.getNormalRenderedChunks();
}
static boolean correctedCheckRadius(int dx, int dz, int radius2Mul4)
{
dx = dx * 2;// + (dx < 0 ? -1 : 1);
dz = dz * 2;// + (dz < 0 ? -1 : 1);
return (dx * dx + dz * dz <= radius2Mul4);
}
/**
* <strong>Doesn't need to be implemented.</strong> <br>
* Returns every chunk position within the vanilla render distance.
*/
default HashSet<DhChunkPos> getMaximumRenderedChunks()
{
IMinecraftClientWrapper mcWrapper = SingletonInjector.INSTANCE.get(IMinecraftClientWrapper.class);
IWrapperFactory factory = SingletonInjector.INSTANCE.get(IWrapperFactory.class);
IVersionConstants versionConstants = SingletonInjector.INSTANCE.get(IVersionConstants.class);
IMinecraftClientWrapper minecraft = SingletonInjector.INSTANCE.get(IMinecraftClientWrapper.class);
ILevelWrapper clientWorld = minecraft.getWrappedClientLevel();
int chunkDist = this.getRenderDistance() + 1; // For some reason having '+1' is actually closer to real value
DhChunkPos centerChunkPos = mcWrapper.getPlayerChunkPos();
int centerChunkX = centerChunkPos.x;
int centerChunkZ = centerChunkPos.z;
int chunkDist2Mul4 = chunkDist * chunkDist * 4;
// add every position within render distance
HashSet<DhChunkPos> renderedPos = new HashSet<DhChunkPos>();
for (int deltaChunkX = -chunkDist; deltaChunkX <= chunkDist; deltaChunkX++)
{
for (int deltaChunkZ = -chunkDist; deltaChunkZ <= chunkDist; deltaChunkZ++)
{
if (!versionConstants.isVanillaRenderedChunkSquare() &&
!correctedCheckRadius(deltaChunkX, deltaChunkZ, chunkDist2Mul4))
{
continue;
}
if (!clientWorld.hasChunkLoaded(centerChunkX + deltaChunkX, centerChunkZ + deltaChunkZ)) continue;
renderedPos.add(new DhChunkPos(centerChunkX + deltaChunkX, centerChunkZ + deltaChunkZ));
}
}
return renderedPos;
}
/** Can return null if the given level hasn't had a light map assigned to it */
@Nullable
ILightMapWrapper getLightmapWrapper(ILevelWrapper level);
@@ -25,8 +25,6 @@ import java.util.HashSet;
public interface ISodiumAccessor extends IModAccessor
{
HashSet<DhChunkPos> getNormalRenderedChunks();
/** A temporary overwrite for a config in sodium 0.5 to fix their terrain from showing, will be removed once a proper fix is added */
void setFogOcclusion(boolean b); // FIXME
@@ -19,12 +19,12 @@
package com.seibel.distanthorizons.core.wrapperInterfaces.world;
import com.seibel.distanthorizons.core.level.IDhClientLevel;
import com.seibel.distanthorizons.core.level.IDhLevel;
import com.seibel.distanthorizons.core.pos.DhBlockPos;
import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos;
import com.seibel.distanthorizons.core.wrapperInterfaces.block.IBlockStateWrapper;
import org.jetbrains.annotations.Nullable;
import java.awt.*;
/**
* @version 2022-9-16
*/
@@ -34,13 +34,15 @@ public interface IClientLevelWrapper extends ILevelWrapper
@Nullable
IServerLevelWrapper tryGetServerSideWrapper();
int computeBaseColor(DhBlockPos pos, IBiomeWrapper biome, IBlockStateWrapper blockState);
int getBlockColor(DhBlockPos pos, IBiomeWrapper biome, IBlockStateWrapper blockState);
/** @return -1 if there was a problem getting the color */
int getDirtBlockColor();
void clearBlockColorCache();
/** Will return null if there was an issue finding the biome. */
@Nullable
IBiomeWrapper getPlainsBiomeWrapper();
Color getCloudColor(float tickDelta);
}

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