Compare commits

...

633 Commits

Author SHA1 Message Date
James Seibel d59f9dc4c6 Create pre-release a1.5.1
I made this version so hopefully someone could test if Vivecraft is just ignoring Minecraft's FOV when creating their projection matrices or if they are doing something more complicated.
2021-09-28 22:56:17 -05:00
James Seibel 8f329ac785 auto-format 2021-09-28 22:18:38 -05:00
James Seibel 9865b23e5b Move MAX_ALLOCATEABLE_DIRECT_MEMORY to LodUtil 2021-09-28 21:29:25 -05:00
James Seibel a987149aa3 Create Eclipse Auto Formatting V1.xml 2021-09-28 21:06:15 -05:00
Leonardo 22b7e46d39 small fixes 2021-09-29 02:38:19 +02:00
Leonardo e5c1e67369 Changed various names, changed the memory calculator 2021-09-29 02:16:23 +02:00
James Seibel 328792cf4e Start commenting and refactoring SingleLevelContainer 2021-09-28 07:50:07 -05:00
James Seibel 344901aad5 Comment RegionPos 2021-09-28 07:41:13 -05:00
James Seibel 5f89bd8e7d comment and refactor PosToGenerateContainer 2021-09-27 22:12:55 -05:00
James Seibel 7c5713fac2 Refactor and comment LodWorld 2021-09-27 21:59:51 -05:00
James Seibel d222854717 auto-indent 2021-09-27 21:49:11 -05:00
James Seibel 6b9be10635 Add a few missing comments and refactors to LodRegion 2021-09-27 21:48:19 -05:00
James Seibel 69a72312e4 Refactor and comment LodRegion 2021-09-27 21:43:16 -05:00
James Seibel 948b58d8e9 auto-indent and move a reminder comment 2021-09-27 20:20:26 -05:00
James Seibel 86406fd12b Rename MULTI_LOD to VOXEL 2021-09-27 20:17:33 -05:00
James Seibel cc3e1d1b12 Fix a null pointer and finish commenting/refactoring LodDimension 2021-09-27 20:13:16 -05:00
James Seibel 85a0110af6 comments and regactor LodDimension 2021-09-27 07:59:55 -05:00
James Seibel a76436b73d Bring back the drawLODs config 2021-09-26 08:20:37 -05:00
James Seibel 391c99fd21 comment and refactor LodDimensionFildHandler and ReflectionHandler 2021-09-25 23:51:14 -05:00
James Seibel fb5aceb44d auto-indent 2021-09-25 23:20:05 -05:00
James Seibel 4c76a48e84 Update comments for many enums 2021-09-25 23:17:17 -05:00
James Seibel d7a42d3a51 auto-indent 2021-09-25 20:35:12 -05:00
James Seibel e0a946673e refactor LodBuilderConfig 2021-09-25 20:30:44 -05:00
James Seibel ad793fe17e Refactor and comment LodBufferBuilder and LodBuilder 2021-09-25 18:52:53 -05:00
James Seibel 209e2579c3 refactor and comment LodWorldGenerator 2021-09-25 16:09:29 -05:00
James Seibel 8627b87aa4 auto-indent 2021-09-25 16:09:12 -05:00
James Seibel 5f855dd573 CubicLodTemplate refactoring and commenting 2021-09-25 15:34:39 -05:00
James Seibel d6c918427b have the LodRenderer recreated only when leaving a server 2021-09-25 15:34:25 -05:00
James Seibel a9bdbc4300 Potentially fix a false buffer IndexOutOfBoundsException 2021-09-25 15:32:34 -05:00
James Seibel 58a150f8de fix the ThreadMapUtil not creating arrays correctly 2021-09-25 13:22:31 -05:00
James Seibel 1a3e1dfa8c add get fresh methods to TheadMapUtil
the getFresh versions create/clear the data for the given map.
2021-09-25 11:48:05 -05:00
James Seibel 430a908829 refactor clientProxy 2021-09-25 11:42:49 -05:00
James Seibel 0ca0cd9213 auto-indent 2021-09-25 09:07:07 -05:00
James Seibel 2f392375fe Refactor and Comment Box 2021-09-25 09:04:26 -05:00
James Seibel ff43118976 Update the version number to a1.5.0-pre 2021-09-24 18:06:57 -05:00
James Seibel 3b4e28d74a Add a warning chat message for the 1.5.0 pre-release 2021-09-24 18:05:05 -05:00
James Seibel 13abed4eb8 comment out the shadingMode config since it isn't currently used 2021-09-24 07:59:26 -05:00
James Seibel ccb2c601a6 add a temporary fix to color debug mode not having shading 2021-09-24 07:59:07 -05:00
James Seibel f6816d9974 auto-indent 2021-09-24 07:45:00 -05:00
James Seibel 5156ae8e63 auto-format 2021-09-24 07:43:22 -05:00
James Seibel 55c0c95339 Improve config wording 2021-09-24 07:37:14 -05:00
James Seibel 0b8503f1fa move applyConfigOverrides 2021-09-24 07:27:07 -05:00
James Seibel 79d9ac5d56 auto-indent 2021-09-24 07:26:50 -05:00
James Seibel 6cf4e663fb Fix a potential null pointer exception and improve clientProxy error logging 2021-09-23 22:07:46 -05:00
cola98765 bf9c3ce567 fixed saving multidata in void dimentions 2021-09-23 23:52:34 +02:00
Leonardo 5bbdd22b58 light works again, changed the region_fast option, fixed a indexout of buond, added a TODO for the future for better memery use 2021-09-23 16:02:32 +02:00
Leonardo 6c1c04bb5f Merge remote-tracking branch 'origin/1.16.5' into 1.16.5 2021-09-23 15:18:39 +02:00
Leonardo 66728cc1eb fixed the memory crash problem.
Added config to draw always in generation quality (if true the game will crash with  render distance>256)
2021-09-23 15:18:33 +02:00
James Seibel d4739b9bed Merge branch '1.16.5' of gitlab.com:jeseibel/minecraft-lod-mod into 1.16.5 2021-09-23 07:55:44 -05:00
James Seibel a93bb63c44 Fix an index out of bounds exception for vanillaRenderedChunks 2021-09-23 07:55:33 -05:00
Leonardo a2e18de9f3 small fix to generation 2021-09-23 14:38:35 +02:00
Leonardo 6183152d99 small fix to culling 2021-09-23 14:37:57 +02:00
James Seibel 5d9f36bb5f Hopefully fix the openGL errors 2021-09-22 22:26:23 -05:00
James Seibel d330083d7b auto-format 2021-09-22 22:20:03 -05:00
James Seibel 6d489b498e add additional error checking to BufferBuilder vboUpload 2021-09-22 22:16:10 -05:00
James Seibel d36d836bb3 prevent a null point in LodRegion addData 2021-09-22 22:15:42 -05:00
James Seibel 29d2d1cff1 Merge branch '1.16.5' of gitlab.com:jeseibel/minecraft-lod-mod into
1.16.5

# Conflicts:
#	src/main/java/com/seibel/lod/objects/LodDimension.java
2021-09-22 21:52:03 -05:00
James Seibel 0a9ea1dfce improve the error reporting in GlProxy 2021-09-22 21:50:05 -05:00
James Seibel b9424cbe9f Fix a index out of bounds error in ThreadMapUtil 2021-09-22 21:04:11 -05:00
Leonardo 593a014dfc fixed far and near generation, fixed holes in the rendering in near gen 2021-09-23 02:03:02 +02:00
James Seibel 83ac04dca4 auto-format 2021-09-22 18:33:20 -05:00
Leonardo c9aed389ae various changes to the generation + some small fixes 2021-09-23 01:05:54 +02:00
Leonardo 00da0e0520 Fixed tall grass and flower colors 2021-09-22 21:32:08 +02:00
Leonardo 5124739348 removed reset of bufferBuilder and added better color creation for grass, leaves, water and flowers 2021-09-22 20:47:31 +02:00
Leonardo d65bfd408e Fixed the artifacts (was caused by wrong array initialisation) and added the reset to while changing dimension 2021-09-22 19:38:04 +02:00
Leonardo da413f594e Added different detail drop off option 2021-09-22 17:17:17 +02:00
cola98765 1fcef4be96 some warnings taken care of 2021-09-22 15:10:20 +02:00
cola98765 d7cddd9b39 fixed null check 2021-09-22 15:02:08 +02:00
Leonardo fb480c2695 fix to memory use 2021-09-22 13:17:56 +02:00
James Seibel 265abb64b3 Improve the wording for a few config options 2021-09-21 21:15:02 -05:00
James Seibel 1cf4852788 auto-format 2021-09-21 20:27:55 -05:00
Leonardo 90d0fbe45b various fix to the generation 2021-09-22 01:26:55 +02:00
cola98765 ea9eac89ec podzol netherrack and other blocks now use proper top color without biome color (nether plants might still be broken didn't test it) 2021-09-22 00:26:11 +02:00
cola98765 017cc201b1 enabled save loading for vertical lods 2021-09-21 23:09:48 +02:00
Leonardo bda963036d Introduced correctly spiral generation 2021-09-21 18:35:48 +02:00
Leonardo 46bdf5763f Added spiral generation, changed how some arrays are created 2021-09-21 18:12:15 +02:00
Leonardo 8228a3b7a6 fixed a thread problem 2021-09-21 16:30:53 +02:00
cola98765 e3df6c99da added void and !exist compression to VerticalLevelContainer 2021-09-21 16:18:58 +02:00
Leonardo a4e9e22d2c fixed end bug 2021-09-21 16:09:24 +02:00
Leonardo 435c5ee73a fixed nether roof bug 2021-09-21 15:09:28 +02:00
cola98765 c6b063a380 in SingleLevelContainer made void chunks save as 1 byte instead of 8 2021-09-21 11:34:07 +02:00
cola98765 1f5c3f5bd8 in SingleLevelContainer made void chunks save as 1 byte instead of 8 2021-09-21 11:33:01 +02:00
Leonardo 688cb3f89a New memory getter system 2021-09-20 18:58:02 +02:00
Leonardo af80ff5267 Refactoring 2021-09-20 16:33:24 +02:00
Leonardo 2bc5c43b19 small fix 2021-09-20 15:51:34 +02:00
Leonardo e7c4827d08 Now the single merge use min and max depth and height and no more the mean to make it more consistent with the multi-lod 2021-09-20 15:49:22 +02:00
Leonardo 482dfb918e Refactoring + added the ability to disable lods with f6 2021-09-20 15:44:18 +02:00
Leonardo 5aaf6e0185 removed redundant toString() 2021-09-20 15:23:56 +02:00
Leonardo ab014af15d more refactoring 2021-09-20 15:15:10 +02:00
Leonardo 772de1b869 Refactoring, removed unused class/enum, fixed a small array bug 2021-09-20 14:59:42 +02:00
Leonardo cb42683774 Merge remote-tracking branch 'origin/1.16.5' into 1.16.5 2021-09-20 14:01:28 +02:00
Leonardo a00698ccab added the dataPoint to the threadMaputil 2021-09-20 14:01:18 +02:00
cola98765 29ed26f023 More warnings addressed 2021-09-20 13:31:16 +02:00
cola98765 af3c4ab801 More warnings addressed 2021-09-20 13:23:22 +02:00
cola98765 1156b7dd28 now using ThreadMapUtil for temp array during saving 2021-09-20 12:50:49 +02:00
cola98765 25fd29b97e replaced short[][] with short[] in mergeMultiData hotfix 2021-09-20 12:16:47 +02:00
cola98765 a11ff5b493 Merge remote-tracking branch 'origin/1.16.5' into 1.16.5 2021-09-20 12:13:32 +02:00
cola98765 7655ae03b0 replaced short[][] with short[] in mergeMultiData 2021-09-20 12:13:24 +02:00
Leonardo 075e29c617 small fixes for better gc use 2021-09-20 12:09:45 +02:00
cola98765 e482bf02a5 fixed negative numbers in mergeMultiData 2021-09-20 11:57:32 +02:00
cola98765 0dab4a2274 addressed couple warnings 2021-09-20 11:54:01 +02:00
cola98765 001335ab47 I'm stupid 2021-09-20 11:46:47 +02:00
Leonardo faedb85f41 Merge remote-tracking branch 'origin/1.16.5' into 1.16.5 2021-09-20 11:35:31 +02:00
Leonardo 01b9a3f3b0 Fixed culling 2021-09-20 11:35:20 +02:00
cola98765 cfd9bd903d + 1 will not hurt anyone 2021-09-20 11:34:25 +02:00
cola98765 3e634c082a addressed couple warnings 2021-09-20 11:30:56 +02:00
cola98765 6e86a808a5 addressed couple warnings 2021-09-20 11:12:54 +02:00
cola98765 e615674246 Merge remote-tracking branch 'origin/1.16.5' into 1.16.5 2021-09-20 11:03:48 +02:00
cola98765 86b5fc48a2 addressed couple warnings 2021-09-20 11:03:24 +02:00
Leonardo 2fab33fa78 Merge remote-tracking branch 'origin/1.16.5' into 1.16.5 2021-09-20 10:57:28 +02:00
Leonardo b56b581bb6 Refactoring 2021-09-20 10:57:20 +02:00
cola98765 2ee5289881 Merge remote-tracking branch 'origin/1.16.5' into 1.16.5 2021-09-20 10:56:36 +02:00
cola98765 3da4e4818c replaced arraycopy in mergeMultiData in favor of simpler method for debugging 2021-09-20 10:56:28 +02:00
Leonardo 686062f39c Merge remote-tracking branch 'origin/1.16.5' into 1.16.5 2021-09-20 10:46:25 +02:00
Leonardo ae857dfeae fixed a small bug in the datapoint
Simplified the code in the LodBufferBuilder
2021-09-20 10:46:14 +02:00
cola98765 686592effb fixed bug in loading vertical data 2021-09-20 10:43:12 +02:00
James Seibel 34b92d1053 Fix #67 (LODs not rotating with camera) and #68 (LODs culled based on player direction, not camera) 2021-09-19 19:25:01 -05:00
James Seibel 01a850eb9d Merge branch '1.16.5' of gitlab.com:jeseibel/minecraft-lod-mod into 1.16.5 2021-09-19 18:14:09 -05:00
James Seibel 7d6f7f35ff Fix the buffer builder failing on LodQualityMode.HEIGHTMAP 2021-09-19 18:13:48 -05:00
James Seibel 28764bc16c auto-format 2021-09-19 17:58:22 -05:00
Leonardo ee1657a798 fixed a small bug 2021-09-19 23:58:24 +02:00
cola98765 c7bf60b4e0 fixed byte cast 2021-09-19 23:34:55 +02:00
cola98765 031af5f25c Merge branch '1.16.5' of https://gitlab.com/jeseibel/minecraft-lod-mod into vertical_merging
 Conflicts:
	src/main/java/com/seibel/lod/objects/VerticalLevelContainer.java
	src/main/java/com/seibel/lod/util/DataPointUtil.java
2021-09-19 23:31:16 +02:00
Leonardo d8200422b2 disabled the clear + small change 2021-09-19 23:18:31 +02:00
cola98765 c89cd54429 implemented multi level saving and loading. they use new long[] system instead of long[][][] 2021-09-19 23:16:03 +02:00
cola98765 ca940d5a36 reworked mergeMultiData to take in long[] instead of long[][] 2021-09-19 22:59:15 +02:00
Leonardo 2241492bc5 there is a thunderstorm i don't want to loose progress 2021-09-19 22:08:30 +02:00
Leonardo 7b807bcea2 small changes 2021-09-19 21:32:13 +02:00
Leonardo ebe2c22a28 Changed how the VerticalLevelContainer contains the data 2021-09-19 20:48:07 +02:00
cola98765 cffb17aeb1 reversed grass brightness boost as it's better that way at 4+ view distance 2021-09-19 16:35:47 +02:00
cola98765 c67a5a5e29 removed commented and now unused code 2021-09-19 16:25:53 +02:00
cola98765 1d687fd1d4 new mergeMultiData. Now actually working 2021-09-19 14:44:55 +02:00
cola98765 783d4086c6 some fixes to new mergeMultiData 2021-09-19 14:40:06 +02:00
cola98765 d5243b7d16 some fixes to new mergeMultiData 2021-09-19 14:18:42 +02:00
cola98765 43f3854068 some fixes to new mergeMultiData 2021-09-19 11:41:29 +02:00
cola98765 0025f43a2e Merge branch '1.16.5' of https://gitlab.com/jeseibel/minecraft-lod-mod into vertical_merging
 Conflicts:
	src/main/java/com/seibel/lod/util/ThreadMapUtil.java
2021-09-19 10:36:33 +02:00
James Seibel 07d792979c downgrade to frogeGradle 3.+ from 5.1.+
mixins weren't compiling correctly on the newer version
2021-09-18 23:34:15 -05:00
James Seibel a939d29357 hopefully prevent a index out of bounds exception 2021-09-18 17:44:52 -05:00
James Seibel ba1d096a48 Add error catching / logging 2021-09-18 17:44:37 -05:00
James Seibel 1c9c72cfe3 Clean up a lot of warnings related to: type casting, unused variables, and unused imports 2021-09-18 17:06:41 -05:00
James Seibel aa95361866 Refactor LodDimension and change some error reporting in PosToRenderContainer 2021-09-18 16:58:23 -05:00
James Seibel ff5139d403 Remove debug code and fix a potential OpenGL error 2021-09-18 14:52:14 -05:00
James Seibel 16fc4914fb Merge branch '1.16.5' of gitlab.com:jeseibel/minecraft-lod-mod into 1.16.5 2021-09-18 14:13:05 -05:00
James Seibel c110524c11 fix vanillaRenderedChunks having index out of bounds exceptions 2021-09-18 14:12:54 -05:00
James Seibel 400b263059 Try to fix GlProxy.setGlContext failing in the BufferBuilder 2021-09-18 14:12:33 -05:00
Leonardo dadda4bdc9 Disabled the culling until it's fixed 2021-09-18 17:32:34 +02:00
Leonardo 1888ac7adc Fixed the culling coordinate 2021-09-18 17:09:13 +02:00
James Seibel 5c59ba7a80 Prevent a error related to loading textures on a null clientWorld 2021-09-18 09:39:53 -05:00
James Seibel f3dd99e792 Improve error message in GlProxy 2021-09-18 09:38:51 -05:00
Leonardo 103ec128ac small fix 2021-09-18 14:46:18 +02:00
Leonardo b4226ec9ec small fix 2021-09-18 14:46:06 +02:00
Leonardo 7c28dd70d3 Fixed the culling 2021-09-18 13:58:19 +02:00
Leonardo d8c9a41314 fixed face culling 2021-09-18 10:09:35 +02:00
Leonardo 5a7f77479f Merge remote-tracking branch 'origin/1.16.5' into 1.16.5 2021-09-18 10:09:14 +02:00
James Seibel d200a5ac24 Add the ability to disable direction based culling
This is a band aid fix for #68, hopefully a better solution can be found in the future.
2021-09-17 23:33:21 -05:00
James Seibel 5099f85da6 Merge branch '1.16.5' of gitlab.com:jeseibel/minecraft-lod-mod into
1.16.5

# Conflicts:
#	src/main/java/com/seibel/lod/render/LodRenderer.java
2021-09-17 23:11:28 -05:00
James Seibel 6e150fe378 Update to ForgeGradlew from 3.+ to 5.1.+ 2021-09-17 22:58:09 -05:00
James Seibel fd67d1ac66 Change setupProjectionMatrix to be mc version agnostic
Now the LOD projection matrix's zoom, transformation, distortion, etc. are all gotten dynamically from Minecraft's projection matrix.
Hopefully this should prevent camera mods from breaking the mod.
2021-09-17 22:57:20 -05:00
cola98765 7161bd52de reworked mergeVerticalData... not actually working 2021-09-18 00:09:22 +02:00
Leonardo 82cf25c341 fixed colors 2021-09-17 23:37:16 +02:00
Leonardo 68d279807f small fix to the vertex order 2021-09-17 22:50:48 +02:00
Leonardo 3530158def Added new culling system 2021-09-17 22:37:33 +02:00
Leonardo 62223480e2 Fixed nether roof 2021-09-17 16:58:59 +02:00
Leonardo 50ab424497 added regen for the lightmap 2021-09-17 15:57:01 +02:00
Leonardo 900467cef2 System uses lightmap now 2021-09-17 13:02:05 +02:00
cola98765 fa1d950ff9 grass is actually using texture now 2021-09-17 12:55:14 +02:00
Leonardo 83571951be Added the ability to get the top texture 2021-09-17 12:29:57 +02:00
Leonardo c31b1f4039 added the ability to avoid not full block or small block 2021-09-17 11:14:23 +02:00
James Seibel 8fcd428194 improve error handling and error messages related to contexts 2021-09-16 20:32:32 -05:00
James Seibel 87c275f768 Add improved error reporting and commenting 2021-09-16 20:32:03 -05:00
James Seibel 8df6e972cb Fix a out of memory error when changing worlds 2021-09-16 20:28:07 -05:00
James Seibel d7d88d61ee Merge branch '1.16.5' of gitlab.com:jeseibel/minecraft-lod-mod into
1.16.5

# Conflicts:
#	src/main/java/com/seibel/lod/wrappers/MinecraftWrapper.java
2021-09-16 19:05:05 -05:00
James Seibel 9b1c0a1125 Add the ability to access the lightmap from the MinecraftWrapper 2021-09-16 19:03:43 -05:00
cola98765 b008b70255 colors are better than it were... again 2021-09-17 00:05:29 +02:00
cola98765 b6c350f667 colors are better than it were 2021-09-16 22:21:04 +02:00
Leonardo e23bbfcd91 small changes 2021-09-16 18:53:26 +02:00
cola98765 6bc14fbb2d brightness fix with real block colors TODO foliage 2021-09-16 18:12:22 +02:00
Leonardo d911017112 new color for grass, leaf and water 2021-09-16 17:11:08 +02:00
Leonardo fc4546538f Reintroduced manual color for nether blocks 2021-09-16 16:17:38 +02:00
Leonardo a90b3e9d37 Merge remote-tracking branch 'origin/1.16.5_new_file_handling' into 1.16.5 2021-09-16 16:15:29 +02:00
Leonardo b2aca27615 Added texture mean in lod builder 2021-09-16 15:44:28 +02:00
cola98765 6bf9e187e0 this breaks old file version system by simply ignoring the old files. 2021-09-16 11:41:53 +02:00
James Seibel b5f32705e8 remove commented out code 2021-09-15 23:12:10 -05:00
James Seibel 8bc72af63f Improve rendering performance and improve buffer building stability
Specifically improve the rendering speed so we can get over 100 fps again.
2021-09-15 22:01:52 -05:00
Leonardo 0e4cf8e882 added a TODO in data util 2021-09-15 17:02:44 +02:00
Leonardo 5ede5fa202 Added fake lighting system. Improved some point 2021-09-15 16:17:26 +02:00
Leonardo cdeba2616c Re-Introduced HEIGHMAP mode correctly.
Added single get and add to the LevelContainer to avoid using temp arrays
2021-09-15 13:23:41 +02:00
Leonardo 6dc94b0cc2 Introduced lodQualityMode in config to change from 3d to 2d 2021-09-15 10:42:09 +02:00
James Seibel 7f9c7d8722 Merge branch '1.16.5' of gitlab.com:jeseibel/minecraft-lod-mod into
1.16.5

# Conflicts:
#	src/main/java/com/seibel/lod/builders/LodBufferBuilder.java
2021-09-14 23:46:01 -05:00
James Seibel aebbeb6ade Simplify the multi-context uploading, improve stability, and refactor 2021-09-14 22:49:08 -05:00
James Seibel a945eb4579 Fully add multi-context uploading 2021-09-14 21:27:36 -05:00
Leonardo f9cf27a2c7 Changed the dataPoint, started the introduction of the lightmap use 2021-09-14 23:21:48 +02:00
Leonardo e03e09a243 Fixed various bugs and introduced more maps in the ThreadMapUtil 2021-09-14 19:02:37 +02:00
Leonardo fe02813d17 Disabled a section in the getNearbyLodChunkPosToSkip 2021-09-14 13:45:08 +02:00
Leonardo 145479267d Fixed Nether color 2021-09-14 13:15:36 +02:00
Leonardo 77ccd9eec3 changed how the valid block is determined, and added a mask and a shift for future use 2021-09-14 12:58:09 +02:00
Leonardo 9b216fedad Vertical lod now works (optimization required) 2021-09-14 01:05:41 +02:00
Leonardo e9798ace13 kinda working version 2021-09-13 23:23:55 +02:00
Leonardo 75e78d9000 First vertical data working 2021-09-13 17:34:37 +02:00
Leonardo 34776074fd added the vertical merge 2021-09-13 13:16:52 +02:00
James Seibel 8822e2d8a1 partially add different context rendering 2021-09-12 23:14:43 -05:00
Leonardo 32de70b4f0 Removed the normals 2021-09-13 01:48:11 +02:00
Leonardo 76a7baeb32 Saving works again 2021-09-13 01:47:04 +02:00
Leonardo 95d9c17e49 Added new colors for the torches 2021-09-13 01:19:39 +02:00
Leonardo a6544d3bb6 Introduced light and added normal in the template for future shading improvement 2021-09-13 00:37:23 +02:00
Leonardo c3115caa8f Re-organized the LodBuilder code 2021-09-12 16:29:41 +02:00
Leonardo 4e1e5b24ee Changed the lodBuilder to use a single function 2021-09-11 20:05:11 +02:00
Leonardo 7b73445f4d Changed the lodBuilder to use a single function 2021-09-11 19:59:30 +02:00
Leonardo 5f1f5f0948 Fixed some smalls bugs 2021-09-11 16:29:20 +02:00
Leonardo bdcc4c7755 Introduced map util and now the Loading does not create new String object. 2021-09-11 15:56:46 +02:00
Leonardo 1395e32a50 introduced the mergeSingleData function 2021-09-11 13:49:51 +02:00
Leonardo 99f7d70613 disabled the class until it's ready 2021-09-10 18:22:13 +02:00
Leonardo 59bfd739f4 Added max detail at FULL 2021-09-10 18:21:46 +02:00
Leonardo 3f1cf6c305 Added some new temporal data storing 2021-09-10 18:21:09 +02:00
Leonardo 5c05df0361 changed the thread names 2021-09-10 18:20:33 +02:00
Leonardo feebc3564a Created the single level container 2021-09-10 16:42:21 +02:00
Leonardo a9aa630aff Started convertion for vertical data 2021-09-10 15:47:43 +02:00
James Seibel 6ec146ba60 re-add depreciation warning suppressions 2021-09-09 19:06:09 -05:00
Leonardo 21140593e2 Started convertion for vertical data 2021-09-10 01:05:12 +02:00
Leonardo 878714dae3 Fixed the lod not showing at screen corner bug 2021-09-09 15:51:14 +02:00
Leonardo e876d7bec6 Fixed the lod not showing at screen corner bug 2021-09-09 14:43:04 +02:00
Leonardo 4f06395557 Fixed glass color 2021-09-09 14:36:05 +02:00
Leonardo aa3dbe8f32 Fixed the allocation of long[] 2021-09-09 14:10:45 +02:00
James Seibel b1bcdb24f0 comment out the config override for release a1.4.1 2021-09-08 18:30:37 -05:00
James Seibel cbd7f4300e Improve logging wording 2021-09-08 18:17:36 -05:00
James Seibel 4d8e30121e fix a spelling mistake 2021-09-08 18:17:17 -05:00
James Seibel fbe39542fc update the version number to a1.4.1 2021-09-08 18:13:25 -05:00
James Seibel 6a1f448b41 comment out testing code 2021-09-08 18:13:06 -05:00
James Seibel e13b23832c Merge branch '1.16.5' of gitlab.com:jeseibel/minecraft-lod-mod into 1.16.5 2021-09-08 18:09:15 -05:00
James Seibel 4c9e0edf32 Add barrier blocks to the list of blocks to ignore 2021-09-08 18:08:59 -05:00
Leonardo 8664a06d3b fixed a small bug that was causing visual artifacts 2021-09-08 19:38:17 +02:00
James Seibel 18b8359f0d rename the wrapper package to wrappers 2021-09-07 21:12:58 -05:00
James Seibel 7de4c7c72a Make the reflection handler a singleton 2021-09-07 21:12:16 -05:00
James Seibel b4bbabae42 enable debugging settings in ClientProxy 2021-09-07 19:52:27 -05:00
James Seibel 40321d6e21 Remove unused variables, imports, etc. 2021-09-07 19:52:06 -05:00
James Seibel cf1702bd0c Fix file reading/writing not working, Closes #61 2021-09-07 19:38:41 -05:00
James Seibel a9b50cdb32 remove invalid imports / organize imports 2021-09-07 19:10:30 -05:00
James Seibel 7eed472029 Fix stuttering caused by a merge 2021-09-07 19:09:50 -05:00
James Seibel 0262e452c7 Improve FileHandler info message 2021-09-07 19:04:48 -05:00
James Seibel 30287d5a7d Merge branch '1.16.5' of gitlab.com:jeseibel/minecraft-lod-mod into 1.16.5 2021-09-07 18:54:41 -05:00
James Seibel 83346fed1f Improve the error messages in the LodDimensionFileHandler 2021-09-07 18:54:24 -05:00
Leonardo e897c3edba fixed generation 2021-09-08 00:51:39 +02:00
Leonardo bd9e2acaf6 Changed the default heigth and depth and small fix to the culling 2021-09-08 00:23:44 +02:00
Leonardo 1e15d372c4 Added box caching 2021-09-07 21:22:12 +02:00
Leonardo 849d563425 Added box to replace AxisAlignedBB 2021-09-07 20:07:39 +02:00
Leonardo aa5a8aa3b8 small change 2021-09-07 19:05:58 +02:00
Leonardo b096cc53aa Working version 2021-09-07 19:02:51 +02:00
Leonardo 94d994e761 re-added the commit 2021-09-07 18:52:58 +02:00
Leonardo f76fa17e25 Removed level pos and data arrays 2021-09-07 17:23:22 +02:00
James Seibel 53a66268cb Close issue #37 (z-fighting far from the origin)
I'm not certain why in LodRenderer swapBuffers has to be called after the frame has been rendered, but otherwise it causes stuttering / rubber banding.
2021-09-06 22:51:46 -05:00
James Seibel 139867d4b8 auto-indent 2021-09-06 12:17:49 -05:00
James Seibel 81cf05ba19 fix a issue where nothing renders in the compiled version 2021-09-06 11:55:44 -05:00
James Seibel 13a1e7ed56 Add the MinecraftWrapper 2021-09-06 11:20:32 -05:00
James Seibel 42bd0fbde9 Delete 1.4 release notes.txt 2021-09-05 17:25:46 -05:00
James Seibel ebe2bd97bc Remove the compiled jars (they are now handled by CurseForge) 2021-09-05 16:51:26 -05:00
James Seibel a3ac3386d0 Update 1.4 release notes.txt 2021-09-05 16:50:42 -05:00
Leonardo 69ba83d34f small change to far Fog 2021-09-05 16:15:14 +02:00
James Seibel e048d277f8 Merge branch '1.16.5' of gitlab.com:jeseibel/minecraft-lod-mod into 1.16.5 2021-09-04 11:01:26 -05:00
James Seibel 0bca47a92b Update pack.mcmeta
I don't even know if we need this file
2021-09-04 11:01:05 -05:00
James Seibel ce84b30ddb Change a println to use the logger 2021-09-04 11:00:54 -05:00
Leonardo 1843f96526 various fix to the new classes 2021-09-04 12:48:22 +02:00
Leonardo e46864431b Fixing the new classes 2021-09-03 23:33:54 +02:00
Leonardo 152a3fa108 Optimized the generation memory use 2021-09-03 22:35:08 +02:00
Leonardo 3420133bd3 Added LevelPosUtil, PosToGenerateContainer and PosToRenderContainer to be used in the future 2021-09-03 19:34:04 +02:00
Leonardo c79bf7c3f7 Fixed chunk data generation during first world loading 2021-09-03 13:10:16 +02:00
James Seibel 2219da4050 revert the fog changes 2021-09-01 21:47:31 -05:00
James Seibel a1aa90cccb Split up the configs to make it easier to understand 2021-09-01 21:39:02 -05:00
Leonardo d19abaef7b small fix to a bug that was causing crash if the player went over y 256 2021-09-01 18:20:51 +02:00
Leonardo 77625c65c7 small fix 2021-09-01 18:11:25 +02:00
Leonardo 91ba48ad4a Max draw detail now works correctly and node bigger than a chunk do not disappear if you touch a border 2021-09-01 18:11:01 +02:00
Leonardo 32e587d536 Small fix to the index of vanillaRenderedChunks 2021-09-01 17:27:37 +02:00
Leonardo e3dfe658f5 Fixed chunkPos being created multiple time in the buffer 2021-09-01 17:20:52 +02:00
Leonardo 4e249e943a Revert "Added LevelPosUtil, now the node are added using it and not with the LevelPos object"
LevelPos are useful and the small stutter in the buffer builder is probably caused by the creation of ChunkPos
2021-09-01 16:33:34 +02:00
Leonardo 1360edb459 Added LevelPosUtil, now the node are added using it and not with the LevelPos object 2021-09-01 15:15:23 +02:00
James Seibel 4c7ca395c6 Add the buffer rebuild timeouts to ClientProxy.applyConfigOverrides() 2021-08-31 21:14:19 -05:00
James Seibel a1c720d588 Fix a issue where the projection matrix was given a depth of 0
This caused the depth buffer to stop working.
2021-08-31 21:05:53 -05:00
James Seibel 7b09840d02 Organize inputs 2021-08-31 19:26:17 -05:00
Leonardo e65579f346 temporary fix to the missing region bug 2021-08-31 18:08:02 +02:00
Leonardo 78d6481a49 Another small fix 2021-08-31 12:55:42 +02:00
Leonardo 1428e72d46 small fix to the generation 2021-08-31 11:47:18 +02:00
James Seibel 2c2c6d6785 Add configs for buffer building timeouts and small refactors 2021-08-30 23:16:27 -05:00
Leonardo d5d48f2448 now the vanillaRenderedChunk is reset if the player goes to high 2021-08-30 23:41:56 +02:00
Leonardo 536de1a22d fixed a small error with the timer 2021-08-30 23:25:37 +02:00
Leonardo 193c579712 changed the generation. Added re-use of levelPos 2021-08-30 23:03:22 +02:00
Leonardo cdba7b20f5 Buffer that does not change won't be rebuilt 2021-08-30 17:55:34 +02:00
Leonardo 5467d007b8 preparing the mod for future buffer caching 2021-08-30 12:36:07 +02:00
Leonardo b24d691d8a Removed a useless temp Object in the getNodeToRender 2021-08-30 10:36:15 +02:00
James Seibel f68e6a9a13 Add a config option to disable the F4 keybinding 2021-08-29 21:23:16 -05:00
James Seibel 2dcdc854e3 Improve stone color 2021-08-29 21:09:30 -05:00
James Seibel 42da213ea9 Update the mod meta info 2021-08-29 16:35:59 -05:00
James Seibel dfea2f561a Replace the old logo files with the new logo files 2021-08-29 14:32:19 -05:00
James Seibel d8b4730ee0 Remove a unneeded depreciation suppression warning 2021-08-29 14:20:18 -05:00
Leonardo 74cbf794a1 the culling in templace can be disabled using the disableCulling boolean 2021-08-29 18:22:41 +02:00
Leonardo 812fa65054 divided the time checker for the regen 2021-08-29 17:18:21 +02:00
Leonardo ba63b44288 small change 2021-08-29 16:44:36 +02:00
Leonardo b19a80f411 Optimized the regen 2021-08-29 16:42:44 +02:00
Leonardo 450f15ad36 Finally fixed the regen problem 2021-08-29 10:56:14 +02:00
James Seibel a6ed4968f5 Add a config option for the number of buffer builder threads 2021-08-28 23:38:51 -05:00
James Seibel 65dc629771 Remove a unsettable configOverride 2021-08-28 23:38:16 -05:00
James Seibel 80aa49e28b Update 1.4 release notes.txt 2021-08-28 23:37:34 -05:00
James Seibel 2c3149ef30 Fix a potential issue with compiling to the wrong Java version 2021-08-28 17:15:38 -05:00
James Seibel cd792d7045 Fix a potential crash where Setupbuffers isn't called fast enough for the BufferBuilder 2021-08-28 17:13:17 -05:00
Leonardo 3ade43651f small Optimization to the buffer 2021-08-28 20:55:18 +02:00
Leonardo f4c3ad8bb5 Fixed the garbage collector being called too much 2021-08-28 20:32:58 +02:00
James Seibel 13d2c7b421 Create the comment for issue #62 2021-08-28 12:10:19 -05:00
James Seibel c54c2c6612 Remove the unused parameter in getBufferMemoryForSingleLod() 2021-08-28 11:55:48 -05:00
James Seibel 13878b7387 auto-format 2021-08-28 11:27:20 -05:00
James Seibel 2f20fc6b77 Update the default config options 2021-08-28 11:18:19 -05:00
James Seibel d4ada067c5 auto-indent 2021-08-28 11:05:33 -05:00
James Seibel 9277b3ad38 Add different debug modes (off, detail, detail_wireframe) 2021-08-28 11:04:59 -05:00
James Seibel c48d409015 remove the unused enum LodQuality 2021-08-28 10:18:23 -05:00
James Seibel ccdebb4242 auto-indent 2021-08-28 10:18:05 -05:00
James Seibel 18c94cea46 Update known issues 2021-08-28 10:09:32 -05:00
James Seibel fe381fc207 Rearrange ClientProxy and fix a potential saving issues 2021-08-28 10:07:20 -05:00
James Seibel acc73af666 Remove some debugging 2021-08-28 10:00:11 -05:00
Leonardo 685284f131 removed some e.printStack for the release 2021-08-28 13:40:39 +02:00
Leonardo f803bd58af fixed server not overriding lower quality 2021-08-28 13:21:50 +02:00
Leonardo 26fbdfc92c fixed memory use method and put F4 as debug key 2021-08-28 12:41:54 +02:00
Leonardo e3e8ef705a geneartionType is no longer used in LodRegion. Every region has a generationMode (+ server) 2021-08-28 12:22:09 +02:00
Leonardo e1a425dbcc fixed a small generation errore,
Now cut and gen tree are automatic
2021-08-28 11:15:34 +02:00
Leonardo d8bd4e2347 Fixed a small loading bug,
made the getDataToGenerate fully automatic, added an option to update the DetailDistanceUtil values
2021-08-28 10:38:44 +02:00
James Seibel 49cdd5702f Add 1.4 release notes
This also includes bugs that need to be fixed before 1.4 can be released
2021-08-27 21:25:26 -05:00
James Seibel 32c19eab7f move a comment 2021-08-27 20:59:41 -05:00
James Seibel a32b66aaab organize imports 2021-08-27 18:05:27 -05:00
James Seibel 425f4948dc Remove unneeded imports and a unneeded cast 2021-08-27 18:05:02 -05:00
James Seibel d05908af32 Auto-formatting 2021-08-27 18:04:50 -05:00
Leonardo 822a088096 fixed loading 2021-08-28 00:36:45 +02:00
Leonardo 36079e1624 fix to the Rendering holes 2021-08-28 00:16:44 +02:00
Leonardo 8db782a406 Now nodeToRender is fully automatic 2021-08-27 23:05:37 +02:00
Leonardo dd018f90d3 Small change 2021-08-27 18:47:42 +02:00
Leonardo af4e30588f new z fix 2021-08-27 18:27:37 +02:00
Leonardo 2630d50147 temporary z fix 2021-08-27 17:43:17 +02:00
Leonardo db2410e7fe changed indent 2021-08-27 14:34:43 +02:00
Leonardo 9990132db2 small changes 2021-08-27 14:31:13 +02:00
Leonardo 21069d6479 Optimization to getLodToRender 2021-08-27 12:02:02 +02:00
Leonardo b27afbb18c Optimized the getDataToGenerate 2021-08-27 11:34:55 +02:00
Leonardo 783c0c97a1 Preparing the BufferBuilder for new system 2021-08-27 11:28:29 +02:00
Leonardo 3dfa2d778a Fixed end bug 2021-08-27 10:59:24 +02:00
James Seibel b44a641d96 Add a TODO 2021-08-26 22:59:55 -05:00
James Seibel 8139b83a01 Fix heights saving higher then they are 2021-08-26 21:01:59 -05:00
James Seibel 456ba183da Fix updating regions where LODs contain no blocks 2021-08-26 20:28:48 -05:00
James Seibel 7525c1b257 Fix the "ffffff" error when reading files 2021-08-26 19:37:38 -05:00
James Seibel 5d4d634537 Re-add the Number Of World Generation Threads config option 2021-08-26 19:31:33 -05:00
James Seibel e1149be7c1 add a missing closing comment "/" 2021-08-26 18:14:49 -05:00
Leonardo fc0aaac69f added better shading + small changes 2021-08-27 00:54:46 +02:00
Leonardo 9786a12273 Fixed a small bug with getDataToGenerate 2021-08-26 23:34:14 +02:00
Leonardo c169cb6b7f Changed how color are calculated 2021-08-26 23:02:32 +02:00
Leonardo afcdce4667 Small fix to LevelPos 2021-08-26 20:17:00 +02:00
Leonardo ab7ed9a4c5 Optimized getDataToGenerate 2021-08-26 20:13:32 +02:00
Leonardo 120e8e5f6d small fixes and changed settings 2021-08-26 19:33:59 +02:00
Leonardo 65824a6fed fixed fog 2021-08-26 19:33:32 +02:00
Leonardo 114aaf9fe4 Update doesn't use temp object anymore 2021-08-26 19:33:15 +02:00
Leonardo 347b149037 The generation is working now 2021-08-26 17:41:01 +02:00
Leonardo 70317a37cb fixed LevelPos and colors 2021-08-26 14:28:27 +02:00
Leonardo 74e4744ff7 Correctly removed LodDataPoint and optimized the LevelPos use 2021-08-26 13:18:18 +02:00
Leonardo 98cbc30709 Small changes to how number of thread is chosen and returned to old LINEAR system 2021-08-25 16:12:18 +02:00
Leonardo b44900e3f0 Added new render/generation distance calculator 2021-08-25 13:44:53 +02:00
Leonardo 1755a252b1 Fixed how request works 2021-08-25 13:13:48 +02:00
Leonardo afcedd0c6d Improved how LINEAR is determined 2021-08-25 13:13:27 +02:00
Leonardo 525005bcb6 small changes to how the node to render are chosen 2021-08-25 12:26:16 +02:00
Leonardo 360294d37c fixed the generation Request 2021-08-25 12:24:59 +02:00
Leonardo ea05e3f9e6 fixed the cull 2021-08-25 12:24:01 +02:00
Leonardo 70a75f0543 added an offset to the cull 2021-08-25 11:09:49 +02:00
Leonardo 488d520b8f Improved the template, shared faces are not rendered 2021-08-25 10:55:05 +02:00
Leonardo 30bb175ea0 small changes 2021-08-25 08:17:39 +02:00
Leonardo 500a68e0fc Merge remote-tracking branch 'origin/1.16.5' into 1.16.5
# Conflicts:
#	src/main/java/com/seibel/lod/builders/LodBufferBuilder.java
#	src/main/java/com/seibel/lod/handlers/LodDimensionFileHandler.java
#	src/main/java/com/seibel/lod/proxy/ClientProxy.java
2021-08-25 07:59:57 +02:00
James Seibel 0cd9bfaef2 Put all LOD save files in the same folder 2021-08-24 21:48:09 -05:00
James Seibel a464176a25 Close #32 (make distance generation independent of the buffer builder) 2021-08-24 21:47:51 -05:00
James Seibel a0dcde48ae Remove unneeded TODOs and unused code 2021-08-24 18:31:42 -05:00
Leonardo 8ae81d3aac added some debugging comments 2021-08-24 23:11:14 +02:00
Leonardo c4f864f0e3 changed how the distance generation mode is determined and added
zFix boolean in node to render to disable the zFighting fix
2021-08-24 23:10:29 +02:00
Leonardo cddd239fc5 Introduced try and catch were needed and fixed some small bugs 2021-08-24 14:58:22 +02:00
Leonardo 189b0ec878 Now the system work stable with SINGLE 2021-08-24 02:35:25 +02:00
Leonardo bd305a0269 small fixes to tree generator new tree cutter 2021-08-24 01:54:01 +02:00
Leonardo bbf99ed145 fixed tree generator new tree cutter 2021-08-24 00:33:46 +02:00
Leonardo cac4807986 Added tree generator and new tree cutter 2021-08-23 23:44:55 +02:00
Leonardo 181539b83b small fixes 2021-08-23 16:05:21 +02:00
Leonardo 4795ddd1ff Fixed circle generation bug 2021-08-23 15:33:51 +02:00
Leonardo c1c6680d04 small changes 2021-08-23 14:20:09 +02:00
Leonardo 81cbc7a12b added the big update function 2021-08-23 12:36:32 +02:00
Leonardo 4c6185556f Small fixes 2021-08-23 12:10:55 +02:00
Leonardo b0ed460230 small fix to the tree cutter 2021-08-23 11:45:15 +02:00
Leonardo 38e323a12f small fix to the tree cutter 2021-08-23 11:24:06 +02:00
Leonardo 9840c594e6 Added the tree cutter 2021-08-23 11:12:23 +02:00
James Seibel fdd8204a46 Add multi-threaded buffer building 2021-08-22 22:54:16 -05:00
James Seibel fb188e4819 auto indentation 2021-08-22 18:46:47 -05:00
James Seibel 9b3d9cbba3 javadoc formatting 2021-08-22 18:46:22 -05:00
James Seibel a2aa1dd081 add a lock to the buffer builder when the buffers are being used
Hopefully this should prevent some crashes and errors when generating buffers
2021-08-22 18:42:27 -05:00
James Seibel cb889d7430 auto formatting 2021-08-22 18:26:59 -05:00
James Seibel 2a88b2e746 Remove a unused method 2021-08-22 18:25:58 -05:00
James Seibel 93dd441708 Close #60 (LODs rendering on top of the player) and improve get Lod pos to skip 2021-08-22 18:24:41 -05:00
James Seibel a81ce6c28a auto reformat 2021-08-22 17:55:24 -05:00
James Seibel 967627c57e Move getRenderedChunks to LodUtil 2021-08-22 17:54:10 -05:00
Leonardo 31cb684bec Added different generation detail option 2021-08-22 19:54:10 +02:00
Leonardo d5112be385 small fixes 2021-08-22 19:18:14 +02:00
Leonardo 547d54aab1 Fixed the near clipping 2021-08-22 16:54:09 +02:00
Leonardo 397573bc08 removed lodChunkRadiusMultiplier from proxy, config and renderer 2021-08-22 16:46:55 +02:00
Leonardo b52d7e4f20 small fix to the Near fog 2021-08-22 16:33:20 +02:00
Leonardo a450f44aa7 small fix to the fog 2021-08-22 16:20:27 +02:00
Leonardo a4e8e9ef60 Merge remote-tracking branch 'origin/1.16.5' into 1.16.5
# Conflicts:
#	src/main/java/com/seibel/lod/proxy/ClientProxy.java
2021-08-22 16:09:07 +02:00
Leonardo 3c28ed5601 small fix to fog 2021-08-22 16:06:30 +02:00
James Seibel c5ec41eb2d Add buffer recreation when changing worlds 2021-08-22 08:42:55 -05:00
Leonardo d1bb96c2dc small fix 2021-08-22 14:46:16 +02:00
James Seibel d72ba5bb82 Merge branch '1.16.5' of gitlab.com:jeseibel/minecraft-lod-mod into 1.16.5 2021-08-22 07:40:09 -05:00
James Seibel baba54bdb8 Add error reporting and handling when unable to access the save folder 2021-08-22 07:40:00 -05:00
Leonardo aa7ea85cde New config 2021-08-22 14:34:02 +02:00
Leonardo a1f01bef78 Fixed game chunk not overriding lod chunk 2021-08-22 11:51:26 +02:00
Leonardo 1a7655e752 Added DetailUtil and it's use in the buffer builder +
small fixes
2021-08-22 11:27:40 +02:00
James Seibel e2a983e4ac comment grammar change 2021-08-21 21:54:40 -05:00
James Seibel e2f0263643 Remove synchronized from getters 2021-08-21 21:54:23 -05:00
James Seibel b280303f8f Improve vine colors 2021-08-21 20:06:22 -05:00
James Seibel ff3b2aee6b Add error catching and improve the None distance generation mode 2021-08-21 20:02:30 -05:00
James Seibel 804f830a10 fix third person backwards culling the wrong regions 2021-08-21 19:57:03 -05:00
James Seibel 68b5978135 Change the color of Mycelium to better match its block color 2021-08-21 19:45:43 -05:00
James Seibel 9ebb84bf9c Make air and Cave air act the same when generating LODs 2021-08-21 19:25:31 -05:00
James Seibel a08e08856a Merge branch '1.16.5' of gitlab.com:jeseibel/minecraft-lod-mod into
1.16.5

# Conflicts:
#	src/main/java/com/seibel/lod/builders/LodBufferBuilder.java
2021-08-21 18:57:39 -05:00
James Seibel 8d5c8bf7b8 Finish adding simple frustum culling 2021-08-21 18:54:20 -05:00
Leonardo 168eb537cb Improved generation 2021-08-21 22:21:41 +02:00
James Seibel ca57c15d46 Merge branch '1.16.5' of gitlab.com:jeseibel/minecraft-lod-mod into
1.16.5

# Conflicts:
#	src/main/java/com/seibel/lod/builders/LodBufferBuilder.java
2021-08-21 15:19:04 -05:00
James Seibel 5aea9877e0 re-organize RenderUtil 2021-08-21 15:11:13 -05:00
Leonardo c20ec4ef59 added commented use of biomeUtils and terrainUtils 2021-08-21 22:01:52 +02:00
James Seibel 32fb349621 Merge branch '1.16.5' of gitlab.com:jeseibel/minecraft-lod-mod into 1.16.5 2021-08-21 13:05:35 -05:00
James Seibel c5b4e20787 Add simple partially functioning frustum culling 2021-08-21 13:05:32 -05:00
James Seibel a246dd7561 improve a few comments 2021-08-21 13:02:21 -05:00
James Seibel 615e29451a add conversions to chunk and block pos 2021-08-21 13:01:39 -05:00
James Seibel bcac2bd7ba Update a few comments 2021-08-21 13:01:24 -05:00
Leonardo a02c232f5f improved getDataToGenerate and changed how it's used in the buffer builder 2021-08-21 17:28:11 +02:00
James Seibel d27dbbb64e Merge branch '1.16.5' of gitlab.com:jeseibel/minecraft-lod-mod into
1.16.5

# Conflicts:
#	src/main/java/com/seibel/lod/builders/LodBufferBuilder.java
2021-08-21 10:06:44 -05:00
James Seibel fdfbee108a minor client proxy debug setting changes/commenting 2021-08-21 09:57:14 -05:00
James Seibel de54c37cf1 Optimize Buffer Builder chunk generator queuing 2021-08-21 09:55:04 -05:00
Leonardo d4580806d1 minor fixes, fixed z fighting 2021-08-21 16:25:09 +02:00
James Seibel 5a856c65fa Update the debug colors to work up to region size 2021-08-21 08:03:35 -05:00
James Seibel 75d6da98dc Remove invalid imports (again) 2021-08-21 07:36:49 -05:00
Leonardo 7654cac01d minor fixes 2021-08-21 14:33:11 +02:00
James Seibel cbe242cd2f Remove invalid import 2021-08-21 07:23:55 -05:00
Leonardo 7f69b11ae4 minor fixes 2021-08-21 12:43:38 +02:00
Leonardo 426c9edb05 new generation system fixed 2021-08-21 12:25:49 +02:00
James Seibel 88fb9b5b21 Add debug code to the LodNodeGenWorker 2021-08-20 21:32:57 -05:00
James Seibel 163f8ca979 Remove Node from the names of LodRenderer, BufferBuilder, LodTemplates, and LodBuilder 2021-08-20 20:28:37 -05:00
James Seibel ee416a10ec Add null checking to the BufferBuilder 2021-08-20 20:24:33 -05:00
James Seibel c5ef03aa0e Add null checks and minor formatting changes 2021-08-20 20:19:46 -05:00
James Seibel 25cb08f541 Eclipse auto-formating 2021-08-20 20:19:05 -05:00
Leonardo fcd06bde87 fixed getDataToGenerate use in buffer builder,
Fixed buffers indexing
2021-08-21 02:57:57 +02:00
Leonardo 00f50a2e60 Added getNodeToGenerate to the buffer builder 2021-08-20 19:46:46 +02:00
Leonardo 550c0f1bf2 Added getDataToRender to the bufferBuilder 2021-08-20 19:02:11 +02:00
Leonardo 0f0179bd19 Various fix 2021-08-20 17:06:41 +02:00
Leonardo 7885d14a91 getDataToRender and getDataToGenerate added to dimension 2021-08-20 16:35:38 +02:00
Leonardo bd9c3bba3f added getDataTeRender 2021-08-20 16:07:27 +02:00
Leonardo 04f8cd653a getDataToGenerate should work correctly 2021-08-20 15:03:28 +02:00
James Seibel 02bfc20ae6 Fix small mushrooms using the grass color 2021-08-20 07:34:14 -05:00
James Seibel d65017366a comment out the buffer building time console printing 2021-08-20 07:25:52 -05:00
James Seibel 05d17ba9fa Close #57 (Add support for night vision) 2021-08-20 07:25:22 -05:00
Leonardo 967aab3b3b new line brackets 2021-08-20 11:46:41 +02:00
Leonardo 9f77bf4e61 Merge remote-tracking branch 'origin/1.16.5' into 1.16.5 2021-08-20 11:32:21 +02:00
James Seibel 6aa096de57 Remove an incorrect import 2021-08-19 19:41:56 -05:00
Leonardo d312d60cd9 Fixed loading not working, started the creation of dataToGenerate and dataToRender 2021-08-20 00:50:08 +02:00
Leonardo 65fde550cb Small fix to the position 2021-08-19 15:49:36 +02:00
Leonardo 736b5ba625 new enum for render distance and quality of lods 2021-08-19 12:47:54 +02:00
Leonardo b2227688bc Merge remote-tracking branch 'origin/1.16.5' into 1.16.5 2021-08-19 11:31:40 +02:00
Leonardo cbe8b647b1 New fast save system 2021-08-19 11:31:26 +02:00
James Seibel 0b0f0eab2f Merge branch '1.16.5' of gitlab.com:jeseibel/minecraft-lod-mod into 1.16.5 2021-08-18 17:37:38 -05:00
James Seibel ffb63ce8ef Move the setupBuffers method to the ClientProxy 2021-08-18 17:37:23 -05:00
Leonardo 0e6f5d9805 Changed the save system
Now lod render distance and quality are not dependent on game render distance
2021-08-18 23:54:13 +02:00
Leonardo a0bd9648dc Conversion to new quad tree LodRegion 2021-08-18 19:46:56 +02:00
James Seibel aa1778cf82 Remove the direct memory Buffer limit
Now the LOD multiplier won't be lowered when the detail is increased.
2021-08-17 22:44:50 -05:00
Leonardo 10d66a4775 LodRegion now works (might still have bugs) 2021-08-16 23:03:21 +02:00
Leonardo 4082c97e0e LodRegion update (not yet complete) 2021-08-16 18:45:42 +02:00
Leonardo 665f979e82 LodRegion update (not yet complete) 2021-08-16 14:23:26 +02:00
Leonardo 911205ce0a Create the new LodRegion class that will replace the quadTrees 2021-08-16 12:24:49 +02:00
James Seibel 51078fde5b Merge branch '1.16.5' of gitlab.com:jeseibel/minecraft-lod-mod into 1.16.5 2021-08-15 19:39:55 -05:00
James Seibel adde7e8d67 Add a ThreadFactory to name executor threads 2021-08-15 19:38:51 -05:00
Leonardo 582e38eec5 Added the ability to change the save type to bin (slower method at the moment) 2021-08-16 00:05:24 +02:00
James Seibel e987e679d0 Merge branch '1.16.5' of gitlab.com:jeseibel/minecraft-lod-mod into
1.16.5

# Conflicts:
#	src/main/java/com/seibel/lod/builders/LodNodeBufferBuilder.java
2021-08-15 15:14:43 -05:00
James Seibel 6d776407a4 Optimize the BufferBuilder 2021-08-15 14:52:17 -05:00
Leonardo 8a61a5f0e7 Slightly optimize addLodToBuffer 2021-08-15 16:40:13 +02:00
James Seibel 49a9ff8640 Re-add the render.drawLODs method to clientProxy
Ops.
2021-08-15 08:32:00 -05:00
James Seibel 47ebdb4aee Remove BiomeContainer from BufferBuilder
Having it prevents server use
2021-08-15 08:19:00 -05:00
James Seibel 2b10a9d977 Remove objectSizeCalculator Java Mission Control is much better 2021-08-15 07:50:31 -05:00
James Seibel 247b7d5633 Add toString methods 2021-08-14 21:01:27 -05:00
James Seibel ae9eba0608 Add distanceGenerationMode when generating nodes 2021-08-14 19:56:16 -05:00
James Seibel 7983681b70 Update the config 2021-08-14 18:13:08 -05:00
James Seibel 85d4106f28 Add a try catch to LodQuadTreeDimension 2021-08-14 18:08:47 -05:00
James Seibel 22d329b8aa remove illegal state exception in LodQuadTreeWorld
This fixes an exception being thrown when leaving the world.
2021-08-14 18:02:25 -05:00
James Seibel 004d36ffa7 Remove old commented out code 2021-08-14 18:01:35 -05:00
James Seibel 231340d979 Comment out debug code 2021-08-14 17:38:40 -05:00
James Seibel f55f5b881f Optimize the region distance logic 2021-08-14 17:26:47 -05:00
James Seibel 03ac090e06 Refactor and improve the comments in LodNodeBufferBuilder 2021-08-14 17:26:28 -05:00
James Seibel 3b375e9c1b minor spacing change 2021-08-14 17:22:59 -05:00
James Seibel e44157fae5 Update / Fix RegionPos 2021-08-14 17:22:44 -05:00
James Seibel 997513231c Improve variable naming in LodNodeRenderer 2021-08-14 16:00:12 -05:00
James Seibel 64c3ba297b Merge branch '1.16.5' of gitlab.com:jeseibel/minecraft-lod-mod into 1.16.5 2021-08-14 09:34:01 -05:00
James Seibel 3ae9c49de4 Improve file writing logic 2021-08-14 09:33:46 -05:00
Leonardo 5692f9fa69 Added the cutTreeAtLevel method 2021-08-14 15:52:14 +02:00
Leonardo 72ba9f5699 Added the cutTreeAtLevel method 2021-08-14 15:50:44 +02:00
James Seibel 696be6f796 Merge branch '1.16.5' of gitlab.com:jeseibel/minecraft-lod-mod into 1.16.5 2021-08-14 08:20:09 -05:00
James Seibel 5198335948 Improve LOD saving logic 2021-08-14 08:19:44 -05:00
Leonardo 8102063850 Added the cutTreeAtLevel method 2021-08-14 15:17:03 +02:00
Leonardo 86bf551de8 Lod not saving fix 2021-08-14 15:04:54 +02:00
Leonardo ec2933f23c Fixed generation detail not being used 2021-08-14 14:14:26 +02:00
James Seibel f6daf62c7d Add a Size calculator class 2021-08-13 23:35:41 -05:00
James Seibel 50988db28c Use a lower level biome generation method 2021-08-13 23:34:53 -05:00
James Seibel 047cef184d Update TODO comment 2021-08-13 23:34:23 -05:00
James Seibel ee5b8662d1 Merge branch '1.16.5' of gitlab.com:jeseibel/minecraft-lod-mod into 1.16.5 2021-08-13 18:01:25 -05:00
James Seibel 9470ee2507 Add KaptainWutax TerrainUtils to build.gradle 2021-08-13 18:00:06 -05:00
James Seibel 8f63e755be Rename saveRegionToDisk to saveRegionToFile 2021-08-13 17:59:33 -05:00
Leonardo b0d62da7fe Remove QuadTreeImage and remove the start, center and end field from the LodNode 2021-08-13 20:25:28 +02:00
James Seibel 7a4347b288 Fix the try catch block in LodNodeGenWorker
Try catch finally blocks need catch to work properly, who knew.
2021-08-12 21:00:35 -05:00
James Seibel bbaa583a6d Fix a config typo 2021-08-12 19:41:09 -05:00
James Seibel c3709f726c Improve how distance based quality is determined 2021-08-11 21:52:16 -05:00
James Seibel d1417069d9 Remove the old LOD objects 2021-08-11 07:33:51 -05:00
James Seibel 412f1bead0 Start removing the old LOD objects 2021-08-11 07:29:51 -05:00
James Seibel 1095f63832 Remove unneeded code in LodDetail 2021-08-10 21:19:14 -05:00
James Seibel 89d0317c7f Add full detail to the LodNodeBufferBuilder 2021-08-10 21:15:34 -05:00
James Seibel 4f489a5272 Change the distance debug colors 2021-08-10 21:14:52 -05:00
James Seibel 8c843ba4b5 Fix the Full detail mode 2021-08-10 21:14:33 -05:00
James Seibel 77401cbfa0 Auto Reformat 2021-08-10 21:14:04 -05:00
James Seibel b983692fc1 Merge branch '1.16.5' of gitlab.com:jeseibel/minecraft-lod-mod into 1.16.5 2021-08-10 20:49:42 -05:00
James Seibel e1a40c6868 Add a comment to LodNodeBufferBuilder 2021-08-10 20:47:28 -05:00
James Seibel 9c2add4e3b Clean up distance based detail and change debugMode 2021-08-10 20:34:06 -05:00
Leonardo 8cede4760e reduced quality to improve performance 2021-08-11 01:46:11 +02:00
Leonardo bceab238d5 Small fix to the offset 2021-08-11 01:45:18 +02:00
James Seibel 121676e661 Remove a unneeded offset in LodNodeBufferBuilder 2021-08-10 17:44:44 -05:00
Leonardo 9ec3be8a48 Added different level 2021-08-10 17:23:43 +02:00
Leonardo bb61cdad70 Added different LodDetail support 2021-08-10 17:11:36 +02:00
Leonardo 2e32e4d768 LodNodeBuilder and LodNodeGenWorker now support correctly LodDetail 2021-08-10 13:07:59 +02:00
Leonardo d4ee641362 Fixed file saving not working 2021-08-10 12:28:32 +02:00
James Seibel 953c2b2e92 Improve color generation and fix Biome Only generation 2021-08-09 22:25:49 -05:00
James Seibel 2cc78c874b Remove comment for issue #54 2021-08-09 22:23:47 -05:00
Leonardo f6f0ecb21b Fixed negative region not showing 2021-08-09 17:54:04 +02:00
Leonardo 190fef934c Added getNodeAtPos for generic node level 2021-08-09 17:01:31 +02:00
James Seibel 52608a9f3f Create issue #54 2021-08-08 22:06:45 -05:00
James Seibel 54e5fd30ab Refactor and comment 2021-08-08 22:04:13 -05:00
James Seibel 89115fd5d5 Add a text reminder when overriding the config file 2021-08-08 21:48:13 -05:00
James Seibel 5631ae52be Refactoring and Commenting 2021-08-08 16:41:25 -05:00
James Seibel 6520cdb184 Refactoring and commenting 2021-08-08 16:30:04 -05:00
James Seibel ce5f8708cd Remove unused code in LodNodeBuilder
I don't think using a 3rd party generator is a good idea; even if it is much faster than vanilla MC.
For a couple of reasons:
1. we could run into issues where the 3rd party isn't updated fast enough
2. the 3rd party generator may not generate the same terrain/biomes/etc.
3.  we wouldn't be able to generate mod terrain
2021-08-08 16:25:42 -05:00
James Seibel 270a83ddbc Refactoring and Commenting 2021-08-08 09:58:55 -05:00
James Seibel 64b8cb3556 Refactoring, reformatting, and commenting
I removed the getters from LodQuadTreeNode since they were public variables anyway.
2021-08-08 00:09:12 -05:00
James Seibel 7f7884c130 update LodNodeBufferBuilder version date 2021-08-07 23:38:14 -05:00
James Seibel 5a95613cee spacing reformatting 2021-08-07 23:38:00 -05:00
James Seibel 9a58800499 Refactoring and commenting 2021-08-07 23:31:40 -05:00
James Seibel d68e46a2d0 Refactoring 2021-08-07 19:31:31 -05:00
James Seibel 20d1ff6d49 revert LodQuadTree region to LodQuadTree and other refactors 2021-08-07 18:30:36 -05:00
James Seibel ed188448f2 Fix LodNodeBufferBuilder queueing the same chunk for generation multiple times 2021-08-07 18:29:20 -05:00
James Seibel 87c803e4c6 Minor refactoring 2021-08-07 13:40:04 -05:00
James Seibel 58f51aae91 reword a TODO 2021-08-07 13:36:15 -05:00
James Seibel 97a773176d Refactoring fixes 2021-08-07 13:30:03 -05:00
James Seibel f6d92021ed Merge branch '1.16.5' of gitlab.com:jeseibel/minecraft-lod-mod into 1.16.5 2021-08-07 13:29:05 -05:00
James Seibel 026cbd5da3 add fix from 2d70e8eb 2021-08-07 12:33:34 -05:00
James Seibel 3538e79a6b Reformat, Refactor, and add comments 2021-08-07 12:22:49 -05:00
Leonardo d520fb8535 previus fix was not correct 2021-08-07 19:03:56 +02:00
Leonardo d7dbf8b994 Fixed the repeating region bug 2021-08-07 18:56:51 +02:00
Leonardo 2d70e8ebe2 Fixed the repeating region bug 2021-08-07 18:54:10 +02:00
James Seibel 5eafe8f818 Improve formatting and a few variable names 2021-08-05 21:42:58 -05:00
James Seibel e875c664fd fix LODs generating multiple times (at least in single threaded gen) 2021-08-05 21:41:08 -05:00
James Seibel e313a03410 Fix/improve rendering again 2021-08-05 20:40:12 -05:00
James Seibel 781aa339bc Partially fix LodNode rendering 2021-08-05 07:56:00 -05:00
James Seibel 73e318ee38 Merge branch '1.16.5' of gitlab.com:jeseibel/minecraft-lod-mod into 1.16.5 2021-08-04 23:07:12 -05:00
James Seibel 337853cdfa Start merging in 1.16.5_QuadTree
The mod does compile and render, however distance LODs don't generate or render correctly and there are other problems as well.
2021-08-04 23:07:03 -05:00
James Seibel afe4e111c3 Add release version a1.3.2 2021-08-04 22:58:06 -05:00
James Seibel 3675c5da46 Add deprecation suppressions in LodRenderer 2021-07-29 07:20:22 -05:00
James Seibel a4cc39dbac Add 400 px icons 2021-07-27 22:32:18 -05:00
James Seibel e3787079b7 Add icon files 2021-07-27 21:24:11 -05:00
James Seibel b2f98e2d92 Improve grass colors (#36) 2021-07-26 22:27:20 -05:00
James Seibel e1edd01069 Implement a few small features in LodServerWorld 2021-07-26 22:20:00 -05:00
James Seibel eb3455452b Fix getting the GPU's fancy fog capabilities 2021-07-26 20:26:24 -05:00
James Seibel 34baf923b2 Fix #45 (Half and Full qualities checkerboard) 2021-07-26 07:56:00 -05:00
James Seibel 499308870d fix a typo 2021-07-25 20:55:15 -05:00
James Seibel f852a79a0e Fix LODs rendering with an offset 2021-07-25 20:52:30 -05:00
James Seibel fa98454151 Hopefully reduce the chance of index out of bounds exceptions 2021-07-25 20:51:52 -05:00
James Seibel 286fc91db6 Slightly improve gamma lighting 2021-07-25 20:51:18 -05:00
James Seibel 99209f920d Fix a few truncation and off by 1 errors 2021-07-25 18:48:25 -05:00
James Seibel 19fd1aff98 Close #36 (Improve foliage colors)
While the colors could still be improved, I'm not sure what else to do with them to do so.
2021-07-25 18:20:34 -05:00
James Seibel 9ebc5aeb32 Improve comments in CublicLodTemplate 2021-07-25 18:06:42 -05:00
James Seibel 7c890a4627 Add a config option to change LOD brightness and saturation
Related to issue #36
2021-07-25 18:00:39 -05:00
James Seibel ccc661225e Improve the DARKEN_SIDES shading mode 2021-07-25 17:58:46 -05:00
James Seibel d7b13a2e0b fix a typo 2021-07-25 15:49:18 -05:00
James Seibel c53ce15f7d Hopefully fix #38 (teleporting causing regions to save in the wrong place) 2021-07-25 14:14:07 -05:00
James Seibel 35ac6fc756 Fix a typo 2021-07-25 14:10:08 -05:00
James Seibel 6bcf58959b Fix #46 (MC crashing when joining a server) 2021-07-25 13:18:47 -05:00
James Seibel 5051508e42 Fix #48 (Lava showing up as water) 2021-07-25 12:29:40 -05:00
James Seibel 196e02e61a Partially close #43 (add SSAO)
I am surprised how much this small change improves how things look. I may still implement SSAO in the future but for the time being this is a big improvement for how things look.
2021-07-25 12:19:14 -05:00
Leonardo bc8188beb0 Added important TODOs 2021-07-21 10:43:51 +02:00
Leonardo 0fc4c5532b Minor fixes 2021-07-19 17:00:40 +02:00
Leonardo 98586d6af9 now different generation setting can be used in the same Dimension without conflict 2021-07-19 16:23:14 +02:00
Leonardo bdd1e2410a Updated the guide 2021-07-19 11:47:26 +02:00
Leonardo c0240c71bf Updated the guide 2021-07-19 11:46:22 +02:00
Leonardo 65ffc93e0f Updated the guide 2021-07-19 11:35:38 +02:00
Leonardo 01183be383 getLevelToGenerate has been renamed getNodesToGenerate. Now it return node and not the complete level 2021-07-19 11:32:20 +02:00
Leonardo 359791ea58 Fixed getLevelToGenerate. Now it works correctly 2021-07-19 11:26:05 +02:00
Leonardo 5b3aa82817 Updated the image creation 2021-07-14 15:08:10 +02:00
Leonardo d77bb48da1 Updated the guide 2021-07-14 15:07:09 +02:00
Leonardo 8b941cb95c Updated the guide 2021-07-14 14:41:03 +02:00
Leonardo b9d991db63 Updated the guide 2021-07-14 14:37:48 +02:00
Leonardo 1a8a7be494 Updated the guide 2021-07-14 14:33:58 +02:00
Leonardo d054ac91b1 Fixed all negative coords bug 2021-07-14 14:33:17 +02:00
Leonardo e40ee20460 Added GUIDE to Readme.txt 2021-07-14 11:14:44 +02:00
Leonardo 29b3e9fadc The lodQuadTree is now correctly converted to use LodDataPoint and DistanceGenerationMode 2021-07-13 20:40:55 +02:00
Leonardo 3cc78c62a0 The lodQuadTree is now correctly converted to use LodDataPoint and DistanceGenerationMode 2021-07-13 15:38:20 +02:00
Leonardo 2c719c41d9 Various change to support LodDataPoint and the DistanceGenerationMode enum 2021-07-13 11:38:08 +02:00
Leonardo cd06b42b02 changed pc 2021-07-13 00:53:24 +02:00
Leonardo e8b46a6fd2 Converting the quadTree to use the LodDataPoint class 2021-07-13 00:52:24 +02:00
James Seibel 814ac5df84 Change distanceBiomeOnlyGeneration to distanceGenerationMode in the configHandler 2021-07-11 18:41:38 -05:00
Morippi 7854f659a3 various change 2021-07-10 15:02:26 +02:00
Morippi 6cb1d2f8f4 second fix for negative coordinate 2021-07-10 13:36:15 +02:00
Morippi f95d57ab6d Several chages to converto to quadTree + first fix for negative coordinate 2021-07-10 12:43:23 +02:00
James Seibel 55f9d142e6 Add 1.16.5 a1.3.1 to the compiled Jars 2021-07-09 23:10:36 -05:00
James Seibel 58607ab1fc Update the version to a1.3.1 2021-07-09 23:09:32 -05:00
James Seibel 433ab4f92f Fix #37 (Z fighting far from the world's origin) 2021-07-09 23:08:30 -05:00
James Seibel 8840973a1e Fix #41 (leaving a world causing Lod generation to break) 2021-07-09 22:57:32 -05:00
Morippi f91479829d Several chages to converto to quadTree 2021-07-09 21:07:00 +02:00
Morippi 829c9531fa Several chages to converto to quadTree 2021-07-09 21:01:37 +02:00
Morippi f9bb248eef Several chages to converto to quadTree 2021-07-09 21:01:17 +02:00
Morippi 03ce4d9c42 Several chages to converto to quadTree 2021-07-09 19:23:14 +02:00
Morippi f181d073a3 fixed LodQuadTreeDimension 2021-07-09 17:56:44 +02:00
Morippi 63b01f3ec7 fixed LodQuadTreeDimension 2021-07-09 15:53:32 +02:00
Morippi 14f1b6db54 fixed LodQuadTreeDimension 2021-07-09 15:26:03 +02:00
Morippi 604089cfa8 fixed LodQuadTreeDimension 2021-07-09 15:08:48 +02:00
Morippi 712d9db2fa fixed LodQuadTreeDimension 2021-07-09 14:58:48 +02:00
Morippi 2b5b023472 fixed LodQuadTreeDimension 2021-07-09 13:33:11 +02:00
Morippi 4a9bc7ca74 fixed LodQuadTreeDimension 2021-07-09 13:11:25 +02:00
Morippi 804f910584 added region in image + various fix 2021-07-08 19:31:27 +02:00
Morippi d5b2512e27 various fix 2021-07-08 13:00:29 +02:00
Morippi 280d281604 various fix 2021-07-08 12:52:50 +02:00
Morippi 95bcc71d23 various fix 2021-07-08 12:40:36 +02:00
Morippi b623f1581f various fix 2021-07-07 23:12:45 +02:00
Morippi 62b6d9ea30 Modified to used getNodeToRender 2021-07-07 22:44:53 +02:00
Morippi 6c8d0f98a6 Added circular rendering,
Fixed get node to render
2021-07-07 22:43:44 +02:00
Morippi dc353cd029 Map works correctly 2021-07-07 21:16:18 +02:00
Morippi 86e4ab7e83 Map works correctly 2021-07-07 20:22:05 +02:00
Morippi c6a96ae710 Fixed QuadTreeImage 2021-07-07 19:44:10 +02:00
Morippi 9e24ee0ef7 Fixed LodQuadTree
Added QuadTreeImage to visualize how the quadTree is builded
2021-07-07 00:53:32 +02:00
Morippi 72bcc87de7 QuadTree version, still doesn't work 2021-07-05 14:46:51 +02:00
72 changed files with 9496 additions and 3900 deletions
+390
View File
@@ -0,0 +1,390 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<profiles version="21">
<profile kind="CodeFormatterProfile" name="Eclipse (James' Edit)" version="21">
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_ellipsis" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_declarations" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_allocation_expression" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.parentheses_positions_in_for_statment" value="common_lines"/>
<setting id="org.eclipse.jdt.core.formatter.comment.new_lines_at_block_boundaries" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_parameters" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.comment.insert_new_line_for_parameter" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_package" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_enum_constant" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_while" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_annotation_type_member_declaration" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.comment.format_javadoc_comments" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.indentation.size" value="4"/>
<setting id="org.eclipse.jdt.core.formatter.parentheses_positions_in_enum_constant_declaration" value="common_lines"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_for" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.align_with_spaces" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.continuation_indentation" value="2"/>
<setting id="org.eclipse.jdt.core.formatter.number_of_blank_lines_before_code_block" value="0"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_switch_case_expressions" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.blank_lines_after_package" value="1"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_local_declarations" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_enum_constant" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_parameterized_type_reference" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.comment.indent_root_tags" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.wrap_before_or_operator_multicatch" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.enabling_tag" value="@formatter:on"/>
<setting id="org.eclipse.jdt.core.formatter.comment.count_line_length_from_starting_position" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_record_components" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_method_declaration" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_parameter" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.wrap_before_multiplicative_operator" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.keep_then_statement_on_same_line" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_explicitconstructorcall_arguments" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_prefix_operator" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_brace_in_array_initializer" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_arguments" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_method" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_parameterized_type_references" value="0"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_logical_operator" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_parenthesized_expression" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.keep_annotation_declaration_on_one_line" value="one_line_never"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_record_declaration" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_enum_constant" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_multiplicative_operator" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_and_in_type_parameter" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_invocation" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_assignment_operator" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_type_declaration" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_for" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.comment.preserve_white_space_between_code_and_line_comments" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_local_variable" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_abstract_method" value="1"/>
<setting id="org.eclipse.jdt.core.formatter.keep_enum_constant_declaration_on_one_line" value="one_line_never"/>
<setting id="org.eclipse.jdt.core.formatter.align_variable_declarations_on_columns" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_invocation" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_union_type_in_multicatch" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.number_of_blank_lines_at_beginning_of_method_body" value="0"/>
<setting id="org.eclipse.jdt.core.formatter.keep_else_statement_on_same_line" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.parentheses_positions_in_catch_clause" value="common_lines"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_parameterized_type_reference" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_array_initializer" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_annotation" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_explicit_constructor_call" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_multiplicative_operator" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.keep_anonymous_type_declaration_on_one_line" value="one_line_never"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_switch_case_expressions" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.wrap_before_shift_operator" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_annotation_declaration_header" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_block" value="next_line"/>
<setting id="org.eclipse.jdt.core.formatter.number_of_blank_lines_at_end_of_code_block" value="0"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_bitwise_operator" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.put_empty_statement_on_new_line" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_parameters_in_constructor_declaration" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_type_parameters" value="0"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_compact_loops" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_block_comment" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.keep_simple_for_body_on_same_line" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_at_end_of_file_if_missing" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_array_initializer" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_unary_operator" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.format_line_comment_starting_on_first_column" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.parentheses_positions_in_annotation" value="common_lines"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_ellipsis" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_try_resources" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_colon_in_assert" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_annotations_on_enum_constant" value="49"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_and_in_type_parameter" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_parenthesized_expression" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.text_block_indentation" value="0"/>
<setting id="org.eclipse.jdt.core.formatter.align_type_members_on_columns" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_assignment" value="0"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_module_statements" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_type_header" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_declaration" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.comment.align_tags_names_descriptions" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_enum_constant" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.keep_if_then_body_block_on_one_line" value="one_line_never"/>
<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_first_class_body_declaration" value="0"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_before_closing_brace_in_array_initializer" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_parameters" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.format_guardian_clause_on_one_line" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_if" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.align_assignment_statements_on_columns" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_block_in_case" value="next_line"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_constructor_declaration" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_conditional_expression_chain" value="0"/>
<setting id="org.eclipse.jdt.core.formatter.comment.format_header" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_type_annotations" value="0"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_allocation_expression" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.wrap_before_assertion_message_operator" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_switch" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_method_declaration" value="0"/>
<setting id="org.eclipse.jdt.core.formatter.align_fields_grouping_blank_lines" value="2147483647"/>
<setting id="org.eclipse.jdt.core.formatter.comment.new_lines_at_javadoc_boundaries" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_bitwise_operator" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_annotation_type_declaration" value="next_line"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_for" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_resources_in_try" value="80"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_selector_in_method_invocation" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.never_indent_block_comments_on_first_column" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_synchronized" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_allocation_expression" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.comment.format_source_code" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_array_initializer" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_field" value="0"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_method" value="1"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_superclass_in_type_declaration" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_throw" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_not_operator" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_type_annotation" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_brace_in_array_initializer" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_parenthesized_expression" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.comment.format_html" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation_type_declaration" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.parentheses_positions_in_method_delcaration" value="common_lines"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_compact_if" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.indent_empty_lines" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_type_arguments" value="0"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_unary_operator" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_enum_constant" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_annotation" value="0"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_declarations" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_annotations_on_package" value="49"/>
<setting id="org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_switch" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_before_else_in_if_statement" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_assignment_operator" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_label" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_declaration_header" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_conditional" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_parameters" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_cast" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_arrow_in_switch_case" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_before_while_in_do_statement" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_type_reference" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_record_header" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_parameterized_type_reference" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_opening_brace_in_array_initializer" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.indent_breaks_compare_to_cases" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_declaration" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.wrap_before_bitwise_operator" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_try" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_lambda_arrow" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_declaration" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.comment.indent_tag_description" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.keep_imple_if_on_one_line" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_record_constructor" value="next_line"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_declaration" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_between_brackets_in_array_type_reference" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_parameters" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_string_concatenation" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_for" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_allocation_expression" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_multiple_fields" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_constant_arguments" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_prefix_operator" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_array_initializer" value="end_of_line"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_shift_operator" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_method_declaration" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_parameters" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_catch" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_shift_operator" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_braces_in_array_initializer" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_local_declarations" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.keep_simple_do_while_body_on_same_line" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_annotation_type_declaration" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_record_components" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.wrap_outer_expressions_when_nested" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_closing_paren_in_cast" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_synchronized" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation_type_member_declaration" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_expressions_in_for_loop_header" value="0"/>
<setting id="org.eclipse.jdt.core.formatter.wrap_before_additive_operator" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.keep_simple_getter_setter_on_one_line" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_while" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_parameters" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_string_concatenation" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_lambda_arrow" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.join_lines_in_comments" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_record_declaration" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_relational_operator" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_field_declarations" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.blank_lines_between_import_groups" value="1"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_at_in_annotation_type_declaration" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_logical_operator" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.parentheses_positions_in_method_invocation" value="common_lines"/>
<setting id="org.eclipse.jdt.core.formatter.blank_lines_after_imports" value="1"/>
<setting id="org.eclipse.jdt.core.formatter.comment.insert_new_line_before_root_tags" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.parentheses_positions_in_record_declaration" value="common_lines"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_throws" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.parentheses_positions_in_switch_statement" value="common_lines"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_postfix_operator" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_increments" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_arguments" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_arrow_in_switch_default" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_inits" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.disabling_tag" value="@formatter:off"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_enum_constants" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_imports" value="1"/>
<setting id="org.eclipse.jdt.core.formatter.number_of_blank_lines_at_end_of_method_body" value="0"/>
<setting id="org.eclipse.jdt.core.formatter.parentheses_positions_in_if_while_statement" value="common_lines"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_closing_brace_in_block" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_return" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_arrow_in_switch_case" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_field" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.blank_lines_between_type_declarations" value="1"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_for" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_catch" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_switch" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_anonymous_type_declaration" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.never_indent_line_comments_on_first_column" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_inits" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.indent_statements_compare_to_block" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_anonymous_type_declaration" value="next_line"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_question_in_wildcard" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_invocation_arguments" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_switch" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.comment.align_tags_descriptions_grouped" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.comment.line_length" value="80"/>
<setting id="org.eclipse.jdt.core.formatter.use_on_off_tags" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.keep_method_body_on_one_line" value="one_line_never"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_brackets_in_array_allocation_expression" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.keep_loop_body_block_on_one_line" value="one_line_never"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_constant" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_method_declaration" value="next_line"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_colon_in_for" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.keep_type_declaration_on_one_line" value="one_line_never"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_arguments" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_additive_operator" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_field_declarations" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_record_constructor" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_relational_operator" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_superinterfaces" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.keep_record_declaration_on_one_line" value="one_line_never"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_default" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_question_in_conditional" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_constructor_declaration" value="next_line"/>
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_lambda_body" value="next_line"/>
<setting id="org.eclipse.jdt.core.formatter.compact_else_if" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_parameters" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_catch" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_invocation" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_invocation_arguments" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_method_invocation" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_constructor_declaration" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_before_catch_in_try_statement" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_try" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_annotations_on_parameter" value="0"/>
<setting id="org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_javadoc_comment" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_relational_operator" value="0"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_expressions_in_array_initializer" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.number_of_empty_lines_to_preserve" value="99"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_colon_in_case" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_additive_operator" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_if" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_arguments" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_string_concatenation" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.comment.format_line_comments" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_record_declaration" value="next_line"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_colon_in_labeled_statement" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.number_of_blank_lines_after_code_block" value="0"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_type_declaration" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_conditional_expression" value="80"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_type" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_annotations_on_type" value="49"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_block" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_annotations_on_local_variable" value="49"/>
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_enum_declaration" value="next_line"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_arrow_in_switch_default" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.comment.insert_new_line_between_different_tags" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_additive_operator" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_invocation" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_while" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.join_wrapped_lines" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_constructor_declaration" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_annotations_on_field" value="49"/>
<setting id="org.eclipse.jdt.core.formatter.wrap_before_conditional_operator" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_cases" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_allocation_expression" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_synchronized" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_shift_operator" value="0"/>
<setting id="org.eclipse.jdt.core.formatter.use_tabs_only_for_leading_indentations" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.parentheses_positions_in_try_clause" value="common_lines"/>
<setting id="org.eclipse.jdt.core.formatter.keep_code_block_on_one_line" value="one_line_never"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_throws" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_record_components" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.tabulation.size" value="4"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_bitwise_operator" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_reference" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_colon_in_conditional" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_try" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_try_resources" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.continuation_indentation_for_array_initializer" value="2"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_question_in_wildcard" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_record_declaration" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_enum_declaration" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.wrap_before_assignment_operator" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_labeled_statement" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_switch" value="next_line"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_superinterfaces" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_parameters" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_parameters" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.keep_lambda_body_block_on_one_line" value="one_line_never"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_annotations_on_method" value="49"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_parameterized_type_reference" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.keep_record_constructor_on_one_line" value="one_line_never"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_record_declaration" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.keep_empty_array_initializer_on_one_line" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_assertion_message" value="0"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_constructor_declaration" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_new_chunk" value="1"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_allocation_expression" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_constructor_declaration" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_parameterized_type_reference" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_arguments" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_assert" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_member_type" value="1"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_logical_operator" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_qualified_allocation_expression" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_record_declaration" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_if" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_semicolon" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.wrap_before_relational_operator" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_postfix_operator" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_arguments" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_cast" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.comment.format_block_comments" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_parameters_in_method_declaration" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_throws" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.blank_lines_after_last_class_body_declaration" value="0"/>
<setting id="org.eclipse.jdt.core.formatter.indent_statements_compare_to_body" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.keep_simple_while_body_on_same_line" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.wrap_before_logical_operator" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.blank_lines_between_statement_group_in_switch" value="0"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_reference" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_annotation" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_constant_arguments" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.parentheses_positions_in_lambda_declaration" value="common_lines"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_case" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_reference" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.keep_enum_declaration_on_one_line" value="one_line_never"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_declaration" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_enum_constant" value="next_line"/>
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_type_declaration" value="next_line"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_multiplicative_operator" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_package" value="1"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_for" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_increments" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_enum_constant" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_explicitconstructorcall_arguments" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_annotation" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_constant_header" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_constructor_declaration" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_throws" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_parameters" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_question_in_conditional" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.comment.indent_parameter_description" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.number_of_blank_lines_at_beginning_of_code_block" value="0"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_before_finally_in_try_statement" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.tabulation.char" value="tab"/>
<setting id="org.eclipse.jdt.core.formatter.wrap_before_string_concatenation" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.lineSplit" value="1200"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_annotation" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_switch" value="insert"/>
</profile>
</profiles>
Binary file not shown.
Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 113 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 83 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 81 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 191 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 172 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 194 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 174 KiB

+52 -23
View File
@@ -1,11 +1,12 @@
buildscript {
repositories {
maven { url = 'https://files.minecraftforge.net/maven' }
jcenter()
maven { url = 'https://files.minecraftforge.net' }
mavenCentral()
// potential replacement in case of problems:
// https://dist.creeper.host/Sponge/maven
maven { url = 'https://repo.spongepowered.org/maven/' }
// potential replacement in case of problems:
// https://dist.creeper.host/Sponge/maven
// used to download and compile dependencies from git repos
maven { url 'https://jitpack.io' }
}
dependencies {
classpath group: 'net.minecraftforge.gradle', name: 'ForgeGradle', version: '3.+', changing: true
@@ -18,10 +19,11 @@ apply plugin: 'org.spongepowered.mixin'
apply plugin: 'eclipse'
apply plugin: 'maven-publish'
version = 'a1.3'
group = 'com.backsun.lod'
version = 'a1.5.1-pre'
group = 'com.seibel.lod'
archivesBaseName = 'lod_1.16.5'
sourceCompatibility = targetCompatibility = compileJava.sourceCompatibility = compileJava.targetCompatibility = '1.8' // Need this here so eclipse task generates correctly.
println('Java: ' + System.getProperty('java.version') + ' JVM: ' + System.getProperty('java.vm.version') + '(' + System.getProperty('java.vendor') + ') Arch: ' + System.getProperty('os.arch'))
@@ -116,26 +118,53 @@ minecraft {
// Include resources generated by data generators.
sourceSets.main.resources { srcDir 'src/generated/resources' }
// this is required so that we can use
// jitpack in the dependencies section below
repositories {
mavenCentral()
// used to download and compile dependencies from git repos
maven { url 'https://jitpack.io' }
}
dependencies {
// Specify the version of Minecraft to use, If this is any group other then 'net.minecraft' it is assumed
// that the dep is a ForgeGradle 'patcher' dependency. And it's patches will be applied.
// The userdev artifact is a special name and will get all sorts of transformations applied to it.
minecraft 'net.minecraftforge:forge:1.16.5-36.1.0'
// these were added to hopefully allow for cloning
// configuredFeatures to allow for safe
// multi threaded feature generation. Sadly I couldn't find
// a way to duplicate lambda functions (which features use)
// so for now I'm not sure what to do.
//implementation 'io.github.kostaskougios:cloning:1.10.3'
//
//implementation ('com.esotericsoftware:kryo:5.1.1') {
// exclude group: "org.objenesis"
//}
//implementation 'org.objenesis:objenesis:3.2'
// these aren't needed right now
//implementation ('com.github.KaptainWutax:TerrainUtils:1.0.0'){
// transitive = false
//}
//implementation ('com.github.KaptainWutax:BiomeUtils:1.0.0'){
// transitive = false
//}
//implementation ('com.github.KaptainWutax:SeedUtils:-SNAPSHOT'){
// transitive = false
//}
//implementation ('com.github.KaptainWutax:MCUtils:1.0.0'){
// transitive = false
//}
//implementation ('com.github.KaptainWutax:NoiseUtils:1.0.0'){
// transitive = false
//}
//implementation ('com.github.KaptainWutax:MathUtils:-SNAPSHOT'){
// transitive = false
//}
// these were added to hopefully allow for cloning
// configuredFeatures to allow for safe
// multi threaded feature generation. Sadly I couldn't find
// a way to duplicate lambda functions (which features use)
// so for now I'm not sure what to do.
//implementation 'io.github.kostaskougios:cloning:1.10.3'
//
//implementation ('com.esotericsoftware:kryo:5.1.1') {
// exclude group: "org.objenesis"
//}
//implementation 'org.objenesis:objenesis:3.2'
// You may put jars on which you depend on in ./libs or you may define them like so..
// compile "some.group:artifact:version:classifier"
// compile "some.group:artifact:version"
@@ -160,10 +189,10 @@ dependencies {
jar {
manifest {
attributes([
"Specification-Title": "Level of Detail",
"Specification-Title": "LOD",
"Specification-Version": "1", // We are version 1 of ourselves
"Implementation-Title": project.name,
"Implementation-Version": "{version}",
"Implementation-Version": "1",
"Implementation-Timestamp": new Date().format("yyyy-MM-dd'T'HH:mm:ssZ"),
"MixinConfigs": "lod.mixins.json",
])
@@ -191,4 +220,4 @@ publishing {
mixin {
add sourceSets.main, "lod.refmap.json"
}
}
+2 -23
View File
@@ -17,15 +17,11 @@
*/
package com.seibel.lod;
import org.lwjgl.opengl.GL;
import com.seibel.lod.handlers.LodConfig;
import com.seibel.lod.config.LodConfig;
import com.seibel.lod.proxy.ClientProxy;
import com.seibel.lod.render.LodRenderer;
import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.eventbus.api.SubscribeEvent;
import net.minecraftforge.fml.DeferredWorkQueue;
import net.minecraftforge.fml.ModLoadingContext;
import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.config.ModConfig;
@@ -51,26 +47,9 @@ public class LodMain
public static ClientProxy client_proxy;
@SuppressWarnings("deprecation")
private void init(final FMLCommonSetupEvent event)
{
ModLoadingContext.get().registerConfig(ModConfig.Type.CLIENT, LodConfig.clientSpec);
Thread setFancyFog = new Thread(() ->
{
LodRenderer.fancyFogAvailable = GL.getCapabilities().GL_NV_fog_distance;
if (!LodRenderer.fancyFogAvailable)
{
ClientProxy.LOGGER.info("This GPU does not support GL_NV_fog_distance. This means that fancy fog options will not be available.");
}
});
// This will be run on the main thread when it is able.
// If it wasn't run on the main thread GL.getCapabilities()
// would fail.
DeferredWorkQueue.runLater(setFancyFog);
ModLoadingContext.get().registerConfig(ModConfig.Type.CLIENT, LodConfig.CLIENT_SPEC);
}
+43
View File
@@ -0,0 +1,43 @@
package com.seibel.lod;
import java.util.HashMap;
import java.util.Map;
import com.seibel.lod.builders.lodTemplates.Box;
import com.seibel.lod.config.LodConfig;
import com.seibel.lod.enums.HorizontalQuality;
import com.seibel.lod.enums.HorizontalScale;
import com.seibel.lod.util.DataPointUtil;
import net.minecraft.util.Direction;
public class Main
{
public static void main(String[] args)
{
for(byte detail = 0; detail < 13; detail++)
{
byte minGenDetail = 0;
byte maxDetail = 10;
int distance;
if (detail <= minGenDetail)
distance = 0;
else if (detail >= maxDetail)
distance = 10000;
else
{
int distanceUnit = HorizontalScale.LOW.distanceUnit;
switch (HorizontalQuality.HIGH)
{
case LINEAR:
;
distance = (detail * distanceUnit);
default:
double base = HorizontalQuality.HIGH.quadraticBase;
distance = (int) (Math.pow(base, detail) * distanceUnit);
}
}
System.out.println(distance);
}
}
}
+3 -3
View File
@@ -21,12 +21,12 @@ package com.seibel.lod;
* This file is similar to mcmod.info
*
* @author James Seibel
* @version 05-31-2021
* @version 08-29-2021
*/
public final class ModInfo
{
public static final String MODID = "lod";
public static final String MODNAME = "Level of Detail";
public static final String MODNAME = "LOD";
public static final String MODAPI = "LodAPI";
public static final String VERSION = "a1.3";
public static final String VERSION = "a1.5.1-pre";
}
@@ -17,421 +17,572 @@
*/
package com.seibel.lod.builders;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.Future;
import java.util.concurrent.locks.ReentrantLock;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL15;
import org.lwjgl.opengl.GL15C;
import com.seibel.lod.builders.worldGeneration.LodChunkGenWorker;
import com.seibel.lod.handlers.LodConfig;
import com.seibel.lod.objects.LodChunk;
import com.seibel.lod.builders.lodTemplates.Box;
import com.seibel.lod.config.LodConfig;
import com.seibel.lod.enums.VerticalQuality;
import com.seibel.lod.objects.LodDimension;
import com.seibel.lod.objects.NearFarBuffer;
import com.seibel.lod.objects.LodRegion;
import com.seibel.lod.objects.PosToRenderContainer;
import com.seibel.lod.objects.RegionPos;
import com.seibel.lod.proxy.ClientProxy;
import com.seibel.lod.proxy.GlProxy;
import com.seibel.lod.proxy.GlProxy.GlProxyContext;
import com.seibel.lod.render.LodRenderer;
import com.seibel.lod.util.DataPointUtil;
import com.seibel.lod.util.LevelPosUtil;
import com.seibel.lod.util.LodThreadFactory;
import com.seibel.lod.util.LodUtil;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.BufferBuilder;
import net.minecraft.client.renderer.vertex.VertexBuffer;
import net.minecraft.util.Direction;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.ChunkPos;
import net.minecraft.world.biome.BiomeContainer;
import net.minecraft.world.chunk.ChunkStatus;
import net.minecraft.world.server.ServerWorld;
import net.minecraftforge.common.WorldWorkerManager;
/**
* This object is used to create NearFarBuffer objects.
*
*
* @author James Seibel
* @version 06-19-2021
* @version 9-25-2021
*/
public class LodBufferBuilder
{
private Minecraft mc;
/**
* This holds the thread used to generate new LODs off the main thread.
*/
public static ExecutorService mainGenThread = Executors.newSingleThreadExecutor(new LodThreadFactory(LodBufferBuilder.class.getSimpleName() + " - main"));
/**
* This holds the threads used to generate buffers.
*/
public static ExecutorService bufferBuilderThreads = Executors.newFixedThreadPool(LodConfig.CLIENT.threading.numberOfBufferBuilderThreads.get(), new LodThreadFactory(LodBufferBuilder.class.getSimpleName() + " - builder"));
/** This holds the thread used to generate new LODs off the main thread. */
private ExecutorService genThread = Executors.newSingleThreadExecutor();
/**
* The buffers that are used to create LODs using far fog
*/
public volatile BufferBuilder[][] buildableBuffers;
private LodChunkBuilder lodChunkBuilder;
/**
* Used when building new VBOs
*/
public volatile VertexBuffer[][] buildableVbos;
/** The buffers that are used to create LODs using near fog */
public volatile BufferBuilder buildableNearBuffer;
/** The buffers that are used to create LODs using far fog */
public volatile BufferBuilder buildableFarBuffer;
/**
* VBOs that are sent over to the LodNodeRenderer
*/
public volatile VertexBuffer[][] drawableVbos;
/** if this is true the LOD buffers are currently being
* regenerated. */
public volatile boolean generatingBuffers = false;
/**
* if this is true the LOD buffers are currently being
* regenerated.
*/
public boolean generatingBuffers = false;
/** if this is true new LOD buffers have been generated
* and are waiting to be swapped with the drawable buffers*/
private volatile boolean switchBuffers = false;
/**
* if this is true new LOD buffers have been generated
* and are waiting to be swapped with the drawable buffers
*/
private boolean switchVbos = false;
/** This keeps track of how many chunk generation requests are on going.
* This is to prevent chunks from being generated for a long time in an area
* the player is no longer in. */
public volatile AtomicInteger numberOfChunksWaitingToGenerate = new AtomicInteger(0);
/**
* Size of the buffer builders in bytes last time we created them
*/
public int previousBufferSize = 0;
/**
* Width of the dimension in regions last time we created the buffers
*/
public int previousRegionWidth = 0;
/**
* this is used to prevent multiple threads creating, destroying, or using the buffers at the same time
*/
private ReentrantLock bufferLock = new ReentrantLock();
private volatile Box[][] boxCache;
private volatile PosToRenderContainer[][] setsToRender;
private volatile RegionPos center;
/**
* This is the ChunkPos the player was at the last time the buffers were built.
* IE the center of the buffers last time they were built
*/
private volatile ChunkPos drawableCenterChunkPos = new ChunkPos(0, 0);
private volatile ChunkPos buildableCenterChunkPos = new ChunkPos(0, 0);
/** how many chunks to generate outside of the player's
* view distance at one time. (or more specifically how
* many requests to make at one time).
* I multiply by 8 to make sure there is always a buffer of chunk requests,
* to make sure the CPU is always busy and we can generate LODs as quickly as
* possible. */
public int maxChunkGenRequests = LodConfig.CLIENT.numberOfWorldGenerationThreads.get() * 8;
public LodBufferBuilder(LodChunkBuilder newLodBuilder)
public LodBufferBuilder()
{
mc = Minecraft.getInstance();
lodChunkBuilder = newLodBuilder;
}
private BiomeContainer biomeContainer = null;
private LodDimension previousDimension = null;
/**
* Create a thread to asynchronously generate LOD buffers
* centered around the given camera X and Z.
* <br>
* This method will write to the drawableNearBuffers and drawableFarBuffers.
* This method will write to the drawable near and far buffers.
* <br>
* After the buildable buffers have been generated they must be
* swapped with the drawable buffers in the LodRenderer to be drawn.
*/
public void generateLodBuffersAsync(LodRenderer renderer, LodDimension lodDim,
double playerX, double playerZ, int numbChunksWide)
BlockPos playerBlockPos, boolean fullRegen)
{
// only allow one generation process to happen at a time
if (generatingBuffers)
return;
if (buildableNearBuffer == null || buildableFarBuffer == null)
throw new IllegalStateException("generateLodBuffersAsync was called before the buildableNearBuffer and buildableFarBuffer were created.");
if (previousDimension != lodDim)
{
biomeContainer = LodUtil.getServerWorldFromDimension(lodDim.dimension).getChunk(0, 0, ChunkStatus.EMPTY).getBiomes();
previousDimension = lodDim;
}
if (buildableBuffers == null)
// setupBuffers hasn't been called yet
return;
generatingBuffers = true;
// round the player's block position down to the nearest chunk BlockPos
ChunkPos playerChunkPos = new ChunkPos(playerBlockPos);
BlockPos playerBlockPosRounded = playerChunkPos.getWorldPosition();
// this seemingly useless math is required,
// just using (int) playerX/Z doesn't work
int playerXChunkOffset = ((int) playerX / LodChunk.WIDTH) * LodChunk.WIDTH;
int playerZChunkOffset = ((int) playerZ / LodChunk.WIDTH) * LodChunk.WIDTH;
// this is where we will start drawing squares
// (exactly half the total width)
int startX = (-LodChunk.WIDTH * (numbChunksWide / 2)) + playerXChunkOffset;
int startZ = (-LodChunk.WIDTH * (numbChunksWide / 2)) + playerZChunkOffset;
Thread thread = new Thread(()->
Thread thread = new Thread(() ->
{
// index of the chunk currently being added to the
// generation list
int chunkGenIndex = 0;
bufferLock.lock();
ChunkPos[] chunksToGen = new ChunkPos[maxChunkGenRequests];
// if we don't have a full number of chunks to generate in chunksToGen
// we can top it off from the reserve
ChunkPos[] chunksToGenReserve = new ChunkPos[maxChunkGenRequests];
int minChunkDist = Integer.MAX_VALUE;
ChunkPos playerChunkPos = new ChunkPos((int)playerX / LodChunk.WIDTH, (int)playerZ / LodChunk.WIDTH);
// generate our new buildable buffers
buildableNearBuffer.begin(GL11.GL_QUADS, LodRenderer.LOD_VERTEX_FORMAT);
buildableFarBuffer.begin(GL11.GL_QUADS, LodRenderer.LOD_VERTEX_FORMAT);
// x axis
for (int i = 0; i < numbChunksWide; i++)
try
{
// z axis
for (int j = 0; j < numbChunksWide; j++)
{
int chunkX = i + (startX / LodChunk.WIDTH);
int chunkZ = j + (startZ / LodChunk.WIDTH);
// skip any chunks that Minecraft is going to render
if(isCoordInCenterArea(i, j, (numbChunksWide / 2))
&& renderer.vanillaRenderedChunks.contains(new ChunkPos(chunkX, chunkZ)))
{
continue;
}
// set where this square will be drawn in the world
double xOffset = (LodChunk.WIDTH * i) + // offset by the number of LOD blocks
startX; // offset so the center LOD block is centered underneath the player
double yOffset = 0;
double zOffset = (LodChunk.WIDTH * j) + startZ;
LodChunk lod = lodDim.getLodFromCoordinates(chunkX, chunkZ);
if (lod == null || lod.isLodEmpty())
{
// generate a new chunk if no chunk currently exists
// and we aren't waiting on any other chunks to generate
if (lod == null && numberOfChunksWaitingToGenerate.get() < maxChunkGenRequests)
{
ChunkPos pos = new ChunkPos(chunkX, chunkZ);
// can be used for debugging
// if (chunksToGen == null)
// {
// chunkGenIndex = 0;
// chunksToGen = new ChunkPos[maxChunkGenRequests];
// }
//
// if (chunkGenIndex < maxChunkGenRequests)
// {
// chunksToGen[chunkGenIndex] = pos;
// chunkGenIndex++;
// }
// determine if this position is closer to the player
// than the previous
int newDistance = playerChunkPos.getChessboardDistance(pos);
// issue #40
// TODO optimize this code,
// using the purely optimized code above we can achieve close to
// 100% CPU utilization, this code generally achieves 40 - 50 %
// I'm sure there is a better data structure for this.
if (newDistance < minChunkDist)
{
// this chunk is closer, clear any previous
// positions and update the new minimum distance
minChunkDist = newDistance;
// move all the old chunks into the reserve
ChunkPos[] newReserve = new ChunkPos[maxChunkGenRequests];
int oldToGenIndex = 0;
int oldReserveIndex = 0;
for(int tmpIndex = 0; tmpIndex < newReserve.length; tmpIndex++)
{
// we don't check if the boundaries are good since
// the tmp array will always be the same length
// as chunksToGen and chunksToGenReserve
if (chunksToGen[oldToGenIndex] != null)
{
// add all the closest chunks...
newReserve[tmpIndex] = chunksToGen[oldToGenIndex];
oldToGenIndex++;
}
else if (chunksToGenReserve[oldReserveIndex] != null)
{
// ...then add all the previous reserve chunks
// (which are farther away)
newReserve[tmpIndex] = chunksToGenReserve[oldToGenIndex];
oldReserveIndex++;
}
else
{
// we have moved all the items from
// the old chunksToGen and reserve
break;
}
}
chunksToGenReserve = newReserve;
chunkGenIndex = 0;
chunksToGen = new ChunkPos[maxChunkGenRequests];
chunksToGen[chunkGenIndex] = pos;
chunkGenIndex++;
}
else if (newDistance <= minChunkDist)
{
// this chunk position is as close or closers than the
// minimum distance
if(chunkGenIndex < maxChunkGenRequests)
{
// we are still under the number of chunks to generate
// add this position to the list
chunksToGen[chunkGenIndex] = pos;
chunkGenIndex++;
}
}
}
// don't render this null chunk
continue;
}
BufferBuilder currentBuffer = null;
if (isCoordinateInNearFogArea(i, j, numbChunksWide / 2))
currentBuffer = buildableNearBuffer;
else
currentBuffer = buildableFarBuffer;
// get the desired LodTemplate and
// add this LOD to the buffer
LodConfig.CLIENT.lodTemplate.get().
template.addLodToBuffer(currentBuffer, lodDim, lod,
xOffset, yOffset, zOffset, renderer.debugging);
}
}
// TODO add a way for a server side mod to generate chunks requested here
if(mc.hasSingleplayerServer())
{
ServerWorld serverWorld = LodUtil.getServerWorldFromDimension(lodDim.dimension);
long startTime = System.currentTimeMillis();
// make sure we have as many chunks to generate as we are allowed
if (chunkGenIndex < maxChunkGenRequests)
{
for(int i = chunkGenIndex, j = 0; i < maxChunkGenRequests; i++, j++)
{
chunksToGen[i] = chunksToGenReserve[j];
}
}
// start chunk generation
for(ChunkPos chunkPos : chunksToGen)
ArrayList<Callable<Boolean>> nodeToRenderThreads = new ArrayList<>(lodDim.getWidth() * lodDim.getWidth());
startBuffers(fullRegen, lodDim);
RegionPos playerRegionPos = new RegionPos(playerChunkPos);
if (center == null)
center = playerRegionPos;
if (setsToRender == null)
setsToRender = new PosToRenderContainer[lodDim.getWidth()][lodDim.getWidth()];
if (setsToRender.length != lodDim.getWidth())
setsToRender = new PosToRenderContainer[lodDim.getWidth()][lodDim.getWidth()];
if (boxCache == null)
boxCache = new Box[lodDim.getWidth()][lodDim.getWidth()];
if (boxCache.length != lodDim.getWidth())
boxCache = new Box[lodDim.getWidth()][lodDim.getWidth()];
// this will be the center of the VBOs once they have been built
buildableCenterChunkPos = playerChunkPos;
//================================//
// create the nodeToRenderThreads //
//================================//
for (int xRegion = 0; xRegion < lodDim.getWidth(); xRegion++)
{
if(chunkPos == null)
break;
// make sure we don't generate this chunk again
lodDim.addLod(new LodChunk(chunkPos));
numberOfChunksWaitingToGenerate.addAndGet(1);
LodChunkGenWorker genWorker = new LodChunkGenWorker(chunkPos, renderer, lodChunkBuilder, this, lodDim, serverWorld, biomeContainer);
WorldWorkerManager.addWorker(genWorker);
for (int zRegion = 0; zRegion < lodDim.getWidth(); zRegion++)
{
if (lodDim.doesRegionNeedBufferRegen(xRegion, zRegion) || fullRegen)
{
RegionPos regionPos = new RegionPos(
xRegion + lodDim.getCenterRegionPosX() - Math.floorDiv(lodDim.getWidth(), 2),
zRegion + lodDim.getCenterRegionPosZ() - Math.floorDiv(lodDim.getWidth(), 2));
// local position in the vbo and bufferBuilder arrays
BufferBuilder currentBuffer = buildableBuffers[xRegion][zRegion];
LodRegion region = lodDim.getRegion(regionPos.x, regionPos.z);
if (region == null)
continue;
// make sure the buffers weren't
// changed while we were running this method
if (currentBuffer == null || !currentBuffer.building())
return;
byte minDetail = region.getMinDetailLevel();
final int xR = xRegion;
final int zR = zRegion;
Callable<Boolean> dataToRenderThread = () ->
{
Map<Direction, long[]> adjData = new HashMap<>();
// determine how many LODs we can stack vertically
int maxVerticalData = 1;
if (LodConfig.CLIENT.worldGenerator.lodQualityMode.get() == VerticalQuality.VOXEL)
maxVerticalData = 256;
// create adjData's arrays
for (Direction direction : Box.ADJ_DIRECTIONS)
adjData.put(direction, new long[maxVerticalData]);
//previous setToRender cache
if (setsToRender[xR][zR] == null)
setsToRender[xR][zR] = new PosToRenderContainer(minDetail, regionPos.x, regionPos.z);
if (boxCache[xR][zR] == null)
boxCache[xR][zR] = new Box();
PosToRenderContainer posToRender = setsToRender[xR][zR];
posToRender.clear(minDetail, regionPos.x, regionPos.z);
lodDim.getDataToRender(
posToRender,
regionPos,
playerBlockPosRounded.getX(),
playerBlockPosRounded.getZ());
byte detailLevel;
int posX;
int posZ;
int xAdj;
int zAdj;
int chunkXdist;
int chunkZdist;
// keep a local version so we don't have to worry about indexOutOfBounds Exceptions
// if it changes in the LodRenderer while we are working here
boolean[][] vanillaRenderedChunks = renderer.vanillaRenderedChunks;
short gameChunkRenderDistance = (short) (vanillaRenderedChunks.length / 2 - 1);
for (int index = 0; index < posToRender.getNumberOfPos(); index++)
{
detailLevel = posToRender.getNthDetailLevel(index);
posX = posToRender.getNthPosX(index);
posZ = posToRender.getNthPosZ(index);
// skip any chunks that Minecraft is going to render
chunkXdist = LevelPosUtil.getChunkPos(detailLevel, posX) - playerChunkPos.x;
chunkZdist = LevelPosUtil.getChunkPos(detailLevel, posZ) - playerChunkPos.z;
if (gameChunkRenderDistance >= Math.abs(chunkXdist)
&& gameChunkRenderDistance >= Math.abs(chunkZdist)
&& detailLevel <= LodUtil.CHUNK_DETAIL_LEVEL
&& vanillaRenderedChunks[chunkXdist + gameChunkRenderDistance + 1][chunkZdist + gameChunkRenderDistance + 1])
{
continue;
}
// skip any chunks that Minecraft is going to render
for (Direction direction : Box.ADJ_DIRECTIONS)
{
xAdj = posX + direction.getNormal().getX();
zAdj = posZ + direction.getNormal().getZ();
chunkXdist = LevelPosUtil.getChunkPos(detailLevel, xAdj) - playerChunkPos.x;
chunkZdist = LevelPosUtil.getChunkPos(detailLevel, zAdj) - playerChunkPos.z;
boolean performFaceCulling = true;
if (performFaceCulling
&& posToRender.contains(detailLevel, xAdj, zAdj)
&& (gameChunkRenderDistance < Math.abs(chunkXdist)
|| gameChunkRenderDistance < Math.abs(chunkZdist)
|| !vanillaRenderedChunks[chunkXdist + gameChunkRenderDistance + 1][chunkZdist + gameChunkRenderDistance + 1]))
{
if (!adjData.containsKey(direction) || adjData.get(direction) == null)
adjData.put(direction, new long[maxVerticalData]);
for (int verticalIndex = 0; verticalIndex < lodDim.getMaxVerticalData(detailLevel, xAdj, zAdj); verticalIndex++)
{
long data = lodDim.getData(detailLevel, xAdj, zAdj, verticalIndex);
adjData.get(direction)[verticalIndex] = data;
}
}
else
{
adjData.put(direction, null);
}
}
long data;
for (int verticalIndex = 0; verticalIndex < lodDim.getMaxVerticalData(detailLevel, posX, posZ); verticalIndex++)
{
data = lodDim.getData(detailLevel, posX, posZ, verticalIndex);
if (DataPointUtil.isVoid(data) || !DataPointUtil.doesItExist(data))
break;
LodConfig.CLIENT.graphics.lodTemplate.get().template.addLodToBuffer(currentBuffer, playerBlockPosRounded, data, adjData,
detailLevel, posX, posZ, boxCache[xR][zR], renderer.previousDebugMode, renderer.lightMap);
}
} // for pos to in list to render
// the thread executed successfully
return true;
};
nodeToRenderThreads.add(dataToRenderThread);
}
} // region z
} // region z
long executeStart = System.currentTimeMillis();
// wait for all threads to finish
List<Future<Boolean>> futuresBuffer = bufferBuilderThreads.invokeAll(nodeToRenderThreads);
for (Future<Boolean> future : futuresBuffer)
{
// the future will be false if its thread failed
if (!future.get())
{
ClientProxy.LOGGER.warn("LodBufferBuilder ran into trouble and had to start over.");
closeBuffers(fullRegen, lodDim);
return;
}
}
long executeEnd = System.currentTimeMillis();
long endTime = System.currentTimeMillis();
@SuppressWarnings("unused")
long buildTime = endTime - startTime;
@SuppressWarnings("unused")
long executeTime = executeEnd - executeStart;
// ClientProxy.LOGGER.info("Thread Build time: " + buildTime + " ms" + '\n' +
// "thread execute time: " + executeTime + " ms");
// mark that the buildable buffers as ready to swap
switchVbos = true;
}
catch (Exception e)
{
ClientProxy.LOGGER.warn("\"LodNodeBufferBuilder.generateLodBuffersAsync\" ran into trouble: ");
e.printStackTrace();
}
finally
{
// regardless of if we successfully created the buffers
// we are done generating.
generatingBuffers = false;
// clean up any potentially open resources
if (buildableBuffers != null)
closeBuffers(fullRegen, lodDim);
// upload the new buffers
uploadBuffers(fullRegen, lodDim);
bufferLock.unlock();
}
// finish the buffer building
buildableNearBuffer.end();
buildableFarBuffer.end();
// mark that the buildable buffers as ready to swap
generatingBuffers = false;
switchBuffers = true;
});
genThread.execute(thread);
return;
mainGenThread.execute(thread);
}
//====================//
// generation helpers //
//====================//
/**
* Returns if the given coordinate is in the loaded area of the world.
* @param centerCoordinate the center of the loaded world
*/
private boolean isCoordInCenterArea(int i, int j, int centerCoordinate)
{
return (i >= centerCoordinate - mc.options.renderDistance
&& i <= centerCoordinate + mc.options.renderDistance)
&&
(j >= centerCoordinate - mc.options.renderDistance
&& j <= centerCoordinate + mc.options.renderDistance);
}
/**
* 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).
*/
private static boolean isCoordinateInNearFogArea(int chunkX, int chunkZ, int lodRadius)
{
int halfRadius = lodRadius / 2;
return (chunkX >= lodRadius - halfRadius
&& chunkX <= lodRadius + halfRadius)
&&
(chunkZ >= lodRadius - halfRadius
&& chunkZ <= lodRadius + halfRadius);
}
//===============================//
// BufferBuilder related methods //
//===============================//
/**
* Called from the LodRenderer to create the
* BufferBuilders at the right size.
*
* @param bufferMaxCapacity
* BufferBuilders. <br><br>
* <p>
* May have to wait for the bufferLock to open.
*/
public void setupBuffers(int bufferMaxCapacity)
public void setupBuffers(LodDimension lodDimension)
{
buildableNearBuffer = new BufferBuilder(bufferMaxCapacity);
buildableFarBuffer = new BufferBuilder(bufferMaxCapacity);
bufferLock.lock();
int numbRegionsWide = lodDimension.getWidth();
int bufferMaxCapacity;
previousRegionWidth = numbRegionsWide;
buildableBuffers = new BufferBuilder[numbRegionsWide][numbRegionsWide];
buildableVbos = new VertexBuffer[numbRegionsWide][numbRegionsWide];
drawableVbos = new VertexBuffer[numbRegionsWide][numbRegionsWide];
for (int x = 0; x < numbRegionsWide; x++)
{
for (int z = 0; z < numbRegionsWide; z++)
{
bufferMaxCapacity = lodDimension.getMemoryRequired(x, z, LodConfig.CLIENT.graphics.lodTemplate.get());
buildableBuffers[x][z] = new BufferBuilder(bufferMaxCapacity);
buildableVbos[x][z] = new VertexBuffer(LodRenderer.LOD_VERTEX_FORMAT);
drawableVbos[x][z] = new VertexBuffer(LodRenderer.LOD_VERTEX_FORMAT);
}
}
bufferLock.unlock();
}
/**
* Swap the drawable and buildable buffers and return
* the old drawable buffers.
* @param drawableNearBuffer
* @param drawableFarBuffer
* sets the buffers and Vbos to null, forcing them to be recreated. <br><br>
* <p>
* May have to wait for the bufferLock to open.
*/
public NearFarBuffer swapBuffers(BufferBuilder drawableNearBuffer, BufferBuilder drawableFarBuffer)
public void destroyBuffers()
{
// swap the BufferBuilders
BufferBuilder tmp = buildableNearBuffer;
buildableNearBuffer = drawableNearBuffer;
drawableNearBuffer = tmp;
bufferLock.lock();
tmp = buildableFarBuffer;
buildableFarBuffer = drawableFarBuffer;
drawableFarBuffer = tmp;
buildableBuffers = null;
buildableVbos = null;
drawableVbos = null;
bufferLock.unlock();
}
/**
* Calls begin on each of the buildable BufferBuilders.
*/
private void startBuffers(boolean fullRegen, LodDimension lodDim)
{
for (int x = 0; x < buildableBuffers.length; x++)
{
for (int z = 0; z < buildableBuffers.length; z++)
{
if (fullRegen || lodDim.doesRegionNeedBufferRegen(x, z))
{
// for some reason BufferBuilder.vertexCounts
// isn't reset unless this is called, which can cause
// a false indexOutOfBoundsException
buildableBuffers[x][z].discard();
buildableBuffers[x][z].begin(GL11.GL_QUADS, LodRenderer.LOD_VERTEX_FORMAT);
}
}
}
}
/**
* Calls end on each of the buildable BufferBuilders.
*/
private void closeBuffers(boolean fullRegen, LodDimension lodDim)
{
for (int x = 0; x < buildableBuffers.length; x++)
for (int z = 0; z < buildableBuffers.length; z++)
if (buildableBuffers[x][z] != null && buildableBuffers[x][z].building() && (fullRegen || lodDim.doesRegionNeedBufferRegen(x, z)))
buildableBuffers[x][z].end();
}
/**
* Upload all buildableBuffers to the GPU.
*/
private void uploadBuffers(boolean fullRegen, LodDimension lodDim)
{
GlProxy glProxy = GlProxy.getInstance();
// the buffers have been swapped
switchBuffers = false;
try
{
// make sure we are uploading to a different OpenGL context,
// to prevent interference (IE stuttering) with the Minecraft context.
glProxy.setGlContext(GlProxyContext.LOD_BUILDER);
for (int x = 0; x < buildableVbos.length; x++)
{
for (int z = 0; z < buildableVbos.length; z++)
{
if (fullRegen || lodDim.doesRegionNeedBufferRegen(x, z))
{
ByteBuffer builderBuffer = buildableBuffers[x][z].popNextBuffer().getSecond();
vboUpload(buildableVbos[x][z], builderBuffer);
lodDim.setRegenRegionBufferByArrayIndex(x, z, false);
}
}
}
}
catch (IllegalStateException e)
{
ClientProxy.LOGGER.error(LodBufferBuilder.class.getSimpleName() + " - UploadBuffers failed: " + e.getMessage());
e.printStackTrace();
}
finally
{
// make sure the context is disabled
glProxy.setGlContext(GlProxyContext.NONE);
}
}
/**
* Uploads the uploadBuffer into the VBO in GPU memory.
*/
private void vboUpload(VertexBuffer vbo, ByteBuffer uploadBuffer)
{
// this shouldn't happen, but just to be safe
if (vbo.id != -1 && GlProxy.getInstance().getGlContext() == GlProxyContext.LOD_BUILDER)
{
// this is how many points will be rendered
vbo.vertexCount = (uploadBuffer.remaining() / vbo.format.getVertexSize());
GL15C.glBindBuffer(GL15.GL_ARRAY_BUFFER, vbo.id);
// subData only works if the memory is allocated beforehand.
GL15C.glBufferData(GL15.GL_ARRAY_BUFFER, uploadBuffer.remaining(), GL15C.GL_DYNAMIC_DRAW);
// interestingly bufferSubData renders faster than glMapBuffer
// even though OpenGLInsights-AsynchronousBufferTransfers says glMapBuffer
// is faster for transferring data. They must put the data in different memory
// or something.
GL15C.glBufferSubData(GL15.GL_ARRAY_BUFFER, 0, uploadBuffer);
GL15C.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
}
}
/**
* Get the newly created VBOs
*/
public VertexBuffersAndOffset getVertexBuffers()
{
// don't wait for the lock to open
// since this is called on the main render thread
if (bufferLock.tryLock())
{
VertexBuffer[][] tmpVbo = drawableVbos;
drawableVbos = buildableVbos;
buildableVbos = tmpVbo;
drawableCenterChunkPos = buildableCenterChunkPos;
// the vbos have been swapped
switchVbos = false;
bufferLock.unlock();
}
return new NearFarBuffer(drawableNearBuffer, drawableFarBuffer);
return new VertexBuffersAndOffset(drawableVbos, drawableCenterChunkPos);
}
/**
* A simple container to pass multiple objects back in the getVertexBuffers method.
*/
public class VertexBuffersAndOffset
{
public VertexBuffer[][] vbos;
public ChunkPos drawableCenterChunkPos;
public VertexBuffersAndOffset(VertexBuffer[][] newVbos, ChunkPos newDrawableCenterChunkPos)
{
vbos = newVbos;
drawableCenterChunkPos = newDrawableCenterChunkPos;
}
}
/**
* If this is true the buildable near and far
* buffers have been generated and are ready to be
* sent to the LodRenderer.
* sent to the LodRenderer.
*/
public boolean newBuffersAvaliable()
public boolean newBuffersAvaliable()
{
return switchBuffers;
return switchVbos;
}
}
}
@@ -0,0 +1,978 @@
/*
* This file is part of the LOD Mod, licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.lod.builders;
import java.awt.Color;
import java.util.List;
import java.util.Random;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import com.seibel.lod.config.LodConfig;
import com.seibel.lod.enums.DistanceGenerationMode;
import com.seibel.lod.enums.HorizontalResolution;
import com.seibel.lod.enums.VerticalQuality;
import com.seibel.lod.objects.LodDimension;
import com.seibel.lod.objects.LodRegion;
import com.seibel.lod.objects.LodWorld;
import com.seibel.lod.util.ColorUtil;
import com.seibel.lod.util.DataPointUtil;
import com.seibel.lod.util.DetailDistanceUtil;
import com.seibel.lod.util.LevelPosUtil;
import com.seibel.lod.util.LodThreadFactory;
import com.seibel.lod.util.LodUtil;
import com.seibel.lod.util.ThreadMapUtil;
import com.seibel.lod.wrappers.MinecraftWrapper;
import net.minecraft.block.AbstractPlantBlock;
import net.minecraft.block.AbstractTopPlantBlock;
import net.minecraft.block.Block;
import net.minecraft.block.BlockState;
import net.minecraft.block.Blocks;
import net.minecraft.block.BushBlock;
import net.minecraft.block.FlowerBlock;
import net.minecraft.block.GrassBlock;
import net.minecraft.block.IGrowable;
import net.minecraft.block.LeavesBlock;
import net.minecraft.block.TallGrassBlock;
import net.minecraft.block.material.MaterialColor;
import net.minecraft.client.renderer.model.BakedQuad;
import net.minecraft.client.renderer.texture.TextureAtlasSprite;
import net.minecraft.client.world.ClientWorld;
import net.minecraft.util.Direction;
import net.minecraft.util.math.AxisAlignedBB;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.ChunkPos;
import net.minecraft.util.math.shapes.VoxelShape;
import net.minecraft.world.DimensionType;
import net.minecraft.world.IWorld;
import net.minecraft.world.LightType;
import net.minecraft.world.World;
import net.minecraft.world.biome.Biome;
import net.minecraft.world.chunk.ChunkSection;
import net.minecraft.world.chunk.IChunk;
import net.minecraft.world.gen.Heightmap;
import net.minecraftforge.client.model.data.ModelDataMap;
/**
* This object is in charge of creating Lod related objects. (specifically: Lod
* World, Dimension, and Region objects)
*
* @author Leonardo Amato
* @author James Seibel
* @version 9-25-2021
*/
public class LodBuilder
{
private static MinecraftWrapper mc = MinecraftWrapper.INSTANCE;
private ExecutorService lodGenThreadPool = Executors.newSingleThreadExecutor(new LodThreadFactory(this.getClass().getSimpleName()));
public static final Direction[] directions = new Direction[] { Direction.UP, Direction.EAST, Direction.SOUTH, Direction.WEST, Direction.NORTH, Direction.DOWN };
public static final int CHUNK_DATA_WIDTH = LodUtil.CHUNK_WIDTH;
public static final int CHUNK_SECTION_HEIGHT = CHUNK_DATA_WIDTH;
public static final Heightmap.Type DEFAULT_HEIGHTMAP = Heightmap.Type.WORLD_SURFACE_WG;
public static final ConcurrentMap<Block, Integer> colorMap = new ConcurrentHashMap<>();
public static final ConcurrentMap<Block, Boolean> toTint = new ConcurrentHashMap<>();
public static final ConcurrentMap<Block, VoxelShape> shapeMap = new ConcurrentHashMap<>();
public static final ModelDataMap dataMap = new ModelDataMap.Builder().build();
/** If no blocks are found in the area in determineBottomPointForArea return this */
public static final short DEFAULT_DEPTH = 0;
/** If no blocks are found in the area in determineHeightPointForArea return this */
public static final short DEFAULT_HEIGHT = 0;
/** Minecraft's max light value */
public static final short DEFAULT_MAX_LIGHT = 15;
/** TODO is this needed / used? */
public static final boolean onlyUseFullBlock = false;
/** TODO is this needed / used? */
public static final boolean avoidSmallBlock = false;
/** How wide LodDimensions should be in regions */
public int defaultDimensionWidthInRegions = 5;
public LodBuilder()
{
}
public void generateLodNodeAsync(IChunk chunk, LodWorld lodWorld, IWorld world)
{
generateLodNodeAsync(chunk, lodWorld, world, DistanceGenerationMode.SERVER);
}
public void generateLodNodeAsync(IChunk chunk, LodWorld lodWorld, IWorld world, DistanceGenerationMode generationMode)
{
if (lodWorld == null || !lodWorld.getIsWorldLoaded())
return;
// don't try to create an LOD object
// if for some reason we aren't
// given a valid chunk object
if (chunk == null)
return;
Thread thread = new Thread(() ->
{
try
{
// we need a loaded client world in order to
// get the textures for blocks
if (mc.getClientWorld() == null)
return;
DimensionType dim = world.dimensionType();
// make sure the dimension exists
LodDimension lodDim;
if (lodWorld.getLodDimension(dim) == null)
{
lodDim = new LodDimension(dim, lodWorld, defaultDimensionWidthInRegions);
lodWorld.addLodDimension(lodDim);
}
else
{
lodDim = lodWorld.getLodDimension(dim);
}
generateLodNodeFromChunk(lodDim, chunk, new LodBuilderConfig(generationMode));
}
catch (IllegalArgumentException | NullPointerException e)
{
e.printStackTrace();
// if the world changes while LODs are being generated
// they will throw errors as they try to access things that no longer
// exist.
}
});
lodGenThreadPool.execute(thread);
}
/**
* Creates a LodNode for a chunk in the given world.
*
* @throws IllegalArgumentException thrown if either the chunk or world is null.
*/
public void generateLodNodeFromChunk(LodDimension lodDim, IChunk chunk) throws IllegalArgumentException
{
generateLodNodeFromChunk(lodDim, chunk, new LodBuilderConfig());
}
/**
* Creates a LodNode for a chunk in the given world.
*
* @throws IllegalArgumentException thrown if either the chunk or world is null.
*/
public void generateLodNodeFromChunk(LodDimension lodDim, IChunk chunk, LodBuilderConfig config)
throws IllegalArgumentException
{
if (chunk == null)
throw new IllegalArgumentException("generateLodFromChunk given a null chunk");
int startX;
int startZ;
int endX;
int endZ;
LodRegion region = lodDim.getRegion(chunk.getPos().getRegionX(), chunk.getPos().getRegionZ());
if (region == null)
return;
// determine how many LODs to generate horizontally
HorizontalResolution detail;
byte minDetailLevel = region.getMinDetailLevel();
detail = DetailDistanceUtil.getLodGenDetail(minDetailLevel);
// determine how many LODs to generate vertically
VerticalQuality verticalQuality = LodConfig.CLIENT.worldGenerator.lodQualityMode.get();
byte detailLevel = detail.detailLevel;
// generate the LODs
int posX;
int posZ;
for (int i = 0; i < detail.dataPointLengthCount * detail.dataPointLengthCount; i++)
{
startX = detail.startX[i];
startZ = detail.startZ[i];
endX = detail.endX[i];
endZ = detail.endZ[i];
posX = LevelPosUtil.convert((byte) 0, chunk.getPos().x * 16 + startX, detail.detailLevel);
posZ = LevelPosUtil.convert((byte) 0, chunk.getPos().z * 16 + startZ, detail.detailLevel);
long[] data;
switch (verticalQuality)
{
default:
case HEIGHTMAP:
long singleData;
long[] dataToMergeSingle = createSingleDataToMerge(detail, chunk, config, startX, startZ, endX, endZ);
singleData = DataPointUtil.mergeSingleData(dataToMergeSingle);
lodDim.addData(detailLevel,
posX,
posZ,
0,
singleData,
false);
break;
case VOXEL:
long[] dataToMergeVertical = createVerticalDataToMerge(detail, chunk, config, startX, startZ, endX, endZ);
data = DataPointUtil.mergeMultiData(dataToMergeVertical, DataPointUtil.worldHeight, DetailDistanceUtil.getMaxVerticalData(detailLevel));
//lodDim.clear(detailLevel, posX, posZ);
if (data != null && data.length != 0)
{
for (int verticalIndex = 0; verticalIndex < lodDim.getMaxVerticalData(detailLevel, posX, posZ); verticalIndex++)
{
if (!DataPointUtil.doesItExist(data[verticalIndex]))
break;
lodDim.addData(detailLevel,
posX,
posZ,
verticalIndex,
data[verticalIndex],
false);
}
}
break;
}
}
lodDim.updateData(LodUtil.CHUNK_DETAIL_LEVEL, chunk.getPos().x, chunk.getPos().z);
}
/**
* creates a vertical DataPoint
*/
private long[] createVerticalDataToMerge(HorizontalResolution detail, IChunk chunk, LodBuilderConfig config, int startX, int startZ, int endX, int endZ)
{
// equivalent to 2^detailLevel
int size = 1 << detail.detailLevel;
long[] dataToMerge = ThreadMapUtil.getFreshBuilderVerticalArray(size * size * DataPointUtil.worldHeight + 1, detail.detailLevel);
int verticalData = DataPointUtil.worldHeight;
ChunkPos chunkPos = chunk.getPos();
int height;
int depth;
int color;
int light;
int lightSky;
int lightBlock;
int generation = config.distanceGenerationMode.complexity;
int xRel;
int zRel;
int xAbs;
int yAbs;
int zAbs;
boolean hasCeiling = mc.getClientWorld().dimensionType().hasCeiling();
boolean hasSkyLight = mc.getClientWorld().dimensionType().hasSkyLight();
BlockPos.Mutable blockPos = new BlockPos.Mutable(0, 0, 0);
int index;
for (index = 0; index < size * size; index++)
{
xRel = Math.floorMod(index, size) + startX;
zRel = Math.floorDiv(index, size) + startZ;
xAbs = chunkPos.getMinBlockX() + xRel;
zAbs = chunkPos.getMinBlockZ() + zRel;
//Calculate the height of the lod
yAbs = DataPointUtil.worldHeight + 2;
int count = 0;
boolean topBlock = true;
while (yAbs > 0)
{
height = determineHeightPointFrom(chunk, config, xRel, zRel, yAbs, blockPos);
// If the lod is at the default height, it must be void data
if (height == DEFAULT_HEIGHT)
{
if (topBlock)
dataToMerge[index * verticalData] = DataPointUtil.createVoidDataPoint(generation);
break;
}
yAbs = height - 1;
// We search light on above air block
depth = determineBottomPointFrom(chunk, config, xRel, zRel, yAbs, blockPos);
if (hasCeiling && topBlock)
{
yAbs = depth;
blockPos.set(xAbs, yAbs, zAbs);
light = getLightValue(chunk, blockPos, hasCeiling, hasSkyLight, topBlock);
color = generateLodColor(chunk, config, xRel, yAbs, zRel, blockPos);
blockPos.set(xAbs, yAbs - 1, zAbs);
}
else
{
blockPos.set(xAbs, yAbs, zAbs);
light = getLightValue(chunk, blockPos, hasCeiling, hasSkyLight, topBlock);
color = generateLodColor(chunk, config, xRel, yAbs, zRel, blockPos);
blockPos.set(xAbs, yAbs + 1, zAbs);
}
lightBlock = light & 0b1111;
lightSky = (light >> 4) & 0b1111;
dataToMerge[index * verticalData + count] = DataPointUtil.createDataPoint(height, depth, color, lightSky, lightBlock, generation);
topBlock = false;
yAbs = depth - 1;
count++;
}
}
return dataToMerge;
}
/**
* Find the lowest valid point from the bottom.
* Used when creating a vertical LOD.
*/
private short determineBottomPointFrom(IChunk chunk, LodBuilderConfig config, int xRel, int zRel, int yAbs, BlockPos.Mutable blockPos)
{
short depth = DEFAULT_DEPTH;
if (config.useHeightmap)
{
// when using the generated heightmap there is no data about the lowest point
depth = 0;
}
else
{
boolean voidData = true;
ChunkSection[] chunkSections = chunk.getSections();
for (int sectionIndex = chunkSections.length - 1; sectionIndex >= 0; sectionIndex--)
{
for (int yRel = CHUNK_DATA_WIDTH - 1; yRel >= 0; yRel--)
{
if (sectionIndex * CHUNK_DATA_WIDTH + yRel > yAbs)
continue;
blockPos.set(chunk.getPos().getMinBlockX() + xRel, sectionIndex * CHUNK_DATA_WIDTH + yRel, chunk.getPos().getMinBlockZ() + zRel);
if (!isLayerValidLodPoint(chunk, blockPos))
{
depth = (short) (sectionIndex * CHUNK_DATA_WIDTH + yRel + 1);
voidData = false;
break;
}
}
if (!voidData)
break;
}
}
return depth;
}
/**
* Find the highest valid point from the Top
*/
private short determineHeightPointFrom(IChunk chunk, LodBuilderConfig config, int xRel, int zRel, int yAbs, BlockPos.Mutable blockPos)
{
short height = DEFAULT_HEIGHT;
if (config.useHeightmap)
{
height = (short) chunk.getOrCreateHeightmapUnprimed(LodUtil.DEFAULT_HEIGHTMAP).getFirstAvailable(xRel, zRel);
}
else
{
boolean voidData = true;
ChunkSection[] chunkSections = chunk.getSections();
for (int sectionIndex = chunkSections.length - 1; sectionIndex >= 0; sectionIndex--)
{
for (int yRel = CHUNK_DATA_WIDTH - 1; yRel >= 0; yRel--)
{
if (sectionIndex * CHUNK_DATA_WIDTH + yRel > yAbs)
continue;
blockPos.set(chunk.getPos().getMinBlockX() + xRel, sectionIndex * CHUNK_DATA_WIDTH + yRel, chunk.getPos().getMinBlockZ() + zRel);
if (isLayerValidLodPoint(chunk, blockPos))
{
height = (short) (sectionIndex * CHUNK_DATA_WIDTH + yRel + 1);
voidData = false;
break;
}
}
if (!voidData)
break;
}
}
return height;
}
/**
* Creates a single HeightMap style LOD data point.
*/
private long[] createSingleDataToMerge(HorizontalResolution detail, IChunk chunk, LodBuilderConfig config, int startX, int startZ, int endX, int endZ)
{
long[] dataToMerge = ThreadMapUtil.getBuilderArray(detail.detailLevel);
ChunkPos chunkPos = chunk.getPos();
int size = 1 << detail.detailLevel;
int height = 0;
int depth = 0;
int color = 0;
int light = 0;
int generation = config.distanceGenerationMode.complexity;
int xRel;
int zRel;
int xAbs;
int yAbs;
int zAbs;
int lightBlock;
int lightSky;
boolean hasCeiling = mc.getClientWorld().dimensionType().hasCeiling();
boolean hasSkyLight = mc.getClientWorld().dimensionType().hasSkyLight();
BlockPos.Mutable blockPos = new BlockPos.Mutable(0, 0, 0);
int index = 0;
if (dataToMerge == null)
{
dataToMerge = new long[size * size];
}
for (index = 0; index < size * size; index++)
{
xRel = Math.floorMod(index, size) + startX;
zRel = Math.floorDiv(index, size) + startZ;
xAbs = chunkPos.getMinBlockX() + xRel;
zAbs = chunkPos.getMinBlockZ() + zRel;
//Calculate the height of the lod
height = determineHeightPoint(chunk, config, xRel, zRel, blockPos);
//If the lod is at the default height it must be void
if (height == DEFAULT_HEIGHT)
{
dataToMerge[index] = DataPointUtil.createVoidDataPoint(generation);
continue;
}
yAbs = height - 1;
color = generateLodColor(chunk, config, xRel, yAbs, zRel, blockPos);
depth = determineBottomPoint(chunk, config, xRel, zRel, blockPos);
// get the light value from the above air block
blockPos.set(xAbs, yAbs + 1, zAbs);
light = getLightValue(chunk, blockPos, hasCeiling, hasSkyLight, true);
lightBlock = light & 0b1111;
lightSky = DEFAULT_MAX_LIGHT;
dataToMerge[index] = DataPointUtil.createDataPoint(height, depth, color, lightSky, lightBlock, generation);
}
return dataToMerge;
}
// =====================//
// constructor helpers //
// =====================//
/**
* Find the lowest valid point from the bottom.
*/
private short determineBottomPoint(IChunk chunk, LodBuilderConfig config, int xRel, int zRel, BlockPos.Mutable blockPos)
{
ChunkSection[] chunkSections = chunk.getSections();
short depth = DEFAULT_DEPTH;
if (config.useHeightmap)
{
depth = 0;
}
else
{
boolean found = false;
for (int sectionIndex = 0; sectionIndex < chunkSections.length; sectionIndex++)
{
for (int yRel = 0; yRel < CHUNK_DATA_WIDTH; yRel++)
{
blockPos.set(chunk.getPos().getMinBlockX() + xRel, sectionIndex * CHUNK_DATA_WIDTH + yRel, chunk.getPos().getMinBlockZ() + zRel);
if (isLayerValidLodPoint(chunk, blockPos))
{
depth = (short) (sectionIndex * CHUNK_DATA_WIDTH + yRel);
found = true;
break;
}
}
if (found)
break;
}
}
return depth;
}
/**
* Find the highest valid point from the Top
*/
private short determineHeightPoint(IChunk chunk, LodBuilderConfig config, int xRel, int zRel, BlockPos.Mutable blockPos)
{
short height = DEFAULT_HEIGHT;
if (config.useHeightmap)
{
height = (short) chunk.getOrCreateHeightmapUnprimed(LodUtil.DEFAULT_HEIGHTMAP).getFirstAvailable(xRel, zRel);
}
else
{
boolean voidData = true;
ChunkSection[] chunkSections = chunk.getSections();
for (int sectionIndex = chunkSections.length - 1; sectionIndex >= 0; sectionIndex--)
{
for (int yRel = CHUNK_DATA_WIDTH - 1; yRel >= 0; yRel--)
{
blockPos.set(chunk.getPos().getMinBlockX() + xRel, sectionIndex * CHUNK_DATA_WIDTH + yRel, chunk.getPos().getMinBlockZ() + zRel);
if (isLayerValidLodPoint(chunk, blockPos))
{
height = (short) (sectionIndex * CHUNK_DATA_WIDTH + yRel + 1);
voidData = false;
break;
}
}
if (!voidData)
break;
}
}
return height;
}
/**
* Generate the color for the given chunk using biome water color, foliage
* color, and grass color.
*/
private int generateLodColor(IChunk chunk, LodBuilderConfig config, int xRel, int yAbs, int zRel, BlockPos.Mutable blockPos)
{
ChunkSection[] chunkSections = chunk.getSections();
int colorInt = 0;
if (config.useBiomeColors)
{
// I have no idea why I need to bit shift to the right, but
// if I don't the biomes don't show up correctly.
Biome biome = chunk.getBiomes().getNoiseBiome(xRel >> 2, yAbs >> 2, zRel >> 2);
colorInt = getColorForBiome(xRel, zRel, biome);
}
else
{
int sectionIndex = Math.floorDiv(yAbs, CHUNK_SECTION_HEIGHT);
int yRel = Math.floorMod(yAbs, CHUNK_SECTION_HEIGHT);
if (chunkSections[sectionIndex] != null)
{
blockPos.set(chunk.getPos().getMinBlockX() + xRel, sectionIndex * CHUNK_DATA_WIDTH + yRel, chunk.getPos().getMinBlockZ() + zRel);
colorInt = getColorForBlock(chunk, blockPos);
}
if (colorInt == 0 && yAbs > 0)
// if this block is invisible, check the block below it
colorInt = generateLodColor(chunk, config, xRel, yAbs - 1, zRel, blockPos);
}
return colorInt;
}
/**
* Gets the light value for the given block position
*/
private int getLightValue(IChunk chunk, BlockPos.Mutable blockPos, boolean hasCeiling, boolean hasSkyLight, boolean topBlock)
{
int skyLight;
int blockLight = 0;
if (mc.getClientWorld() == null)
return 0;
ClientWorld world = mc.getClientWorld();
int blockBrightness = chunk.getLightEmission(blockPos);
if (hasCeiling && topBlock)
blockPos.set(blockPos.getX(), blockPos.getY() - 1, blockPos.getZ());
else
blockPos.set(blockPos.getX(), blockPos.getY() + 1, blockPos.getZ());
if (!hasSkyLight && hasCeiling)
{
skyLight = 0;
}
else if (topBlock)
{
skyLight = DEFAULT_MAX_LIGHT;
}
else
{
if (chunk.isLightCorrect())
{
skyLight = world.getBrightness(LightType.SKY, blockPos);
}
else
{
// we don't know what the light here is,
// lets just take a guess
if (blockPos.getY() >= mc.getClientWorld().getSeaLevel() - 5)
skyLight = 10;
else
skyLight = 0;
}
}
blockLight = world.getBrightness(LightType.BLOCK, blockPos);
blockLight = LodUtil.clamp(0, blockLight + blockBrightness, DEFAULT_MAX_LIGHT);
return blockLight + (skyLight << 4);
}
/**
* Generate the color of the given block from its texture
* and store it for later use.
*/
private int getColorTextureForBlock(BlockState blockState, BlockPos blockPos, boolean useTopTexture)
{
// use the pre-generated color if we can
Block block = blockState.getBlock();
if (colorMap.containsKey(block) && toTint.containsKey(block))
return colorMap.get(block);
World world = mc.getClientWorld();
TextureAtlasSprite texture;
List<BakedQuad> quad = null;
// get the first quad we can for this block
for (Direction direction : directions)
{
quad = mc.getModelManager().getBlockModelShaper().getBlockModel(blockState).getQuads(blockState, direction, new Random(0), dataMap);
if (!quad.isEmpty())
break;
}
if (!quad.isEmpty())
toTint.put(block, quad.get(0).isTinted());
else
toTint.put(blockState.getBlock(), false);
if (useTopTexture && !quad.isEmpty())
texture = quad.get(0).getSprite();
else
texture = mc.getModelManager().getBlockModelShaper().getTexture(blockState, world, blockPos);
int count = 0;
int alpha = 0;
int red = 0;
int green = 0;
int blue = 0;
int numberOfGreyPixel = 0;
int color;
int colorMultiplier;
// generate the block's color
for (int frameIndex = 0; frameIndex < texture.getFrameCount(); frameIndex++)
{
// textures normally use u and v instead of x and y
for (int u = 0; u < texture.getHeight(); u++)
{
for (int v = 0; v < texture.getWidth(); v++)
{
if (texture.isTransparent(frameIndex, u, v))
continue;
color = texture.getPixelRGBA(frameIndex, u, v);
// determine if this pixel is gray
int colorMax = Math.max(Math.max(ColorUtil.getBlue(color), ColorUtil.getGreen(color)), ColorUtil.getRed(color));
int colorMin = 4 + Math.min(Math.min(ColorUtil.getBlue(color), ColorUtil.getGreen(color)), ColorUtil.getRed(color));
boolean isGray = colorMax < colorMin;
if (isGray)
numberOfGreyPixel++;
// for flowers, weight their non-green color higher
if (block instanceof FlowerBlock && (!(ColorUtil.getGreen(color) > (ColorUtil.getBlue(color) + 30)) || !(ColorUtil.getGreen(color) > (ColorUtil.getRed(color) + 30))))
colorMultiplier = 5;
else
colorMultiplier = 1;
// add to the running averages
count = colorMultiplier + count; // TODO shouldn't colorMultiplier be multiplied by?
alpha += ColorUtil.getAlpha(color) * colorMultiplier;
red += ColorUtil.getBlue(color) * colorMultiplier;
green += ColorUtil.getGreen(color) * colorMultiplier;
blue += ColorUtil.getRed(color) * colorMultiplier;
}
}
}
if (count == 0)
{
// this block is entirely transparent
color = 0;
}
else
{
// determine the average color
alpha /= count;
red /= count;
green /= count;
blue /= count;
color = ColorUtil.rgbToInt(alpha, red, green, blue);
}
// determine if this block should use the biome color tint
if (block instanceof TallGrassBlock || (useGrassTint(block) || useLeafTint(block) || useWaterTint(block)) && numberOfGreyPixel / count > 0.75f)
toTint.replace(block, true);
// add the newly generated block color to the map for later use
colorMap.put(block, color);
return color;
}
/** determine if the given block should use the biome's grass color */
private boolean useGrassTint(Block block)
{
return block instanceof GrassBlock
|| block instanceof BushBlock
|| block instanceof IGrowable
|| block instanceof AbstractPlantBlock
|| block instanceof AbstractTopPlantBlock
|| block instanceof TallGrassBlock;
}
/** determine if the given block should use the biome's foliage color */
private boolean useLeafTint(Block block)
{
return block instanceof LeavesBlock
|| block == Blocks.VINE
|| block == Blocks.SUGAR_CANE;
}
/** determine if the given block should use the biome's water color */
private boolean useWaterTint(Block block)
{
return block == Blocks.WATER;
}
/** Returns a color int for the given block. */
private int getColorForBlock(IChunk chunk, BlockPos blockPos)
{
int blockColor;
int colorInt = 0;
int xRel = blockPos.getX() - chunk.getPos().getMinBlockX();
int zRel = blockPos.getZ() - chunk.getPos().getMinBlockZ();
int x = blockPos.getX();
int y = blockPos.getY();
int z = blockPos.getZ();
Biome biome = chunk.getBiomes().getNoiseBiome(xRel >> 2, y >> 2, zRel >> 2);
BlockState blockState = chunk.getBlockState(blockPos);
// block special cases
// TODO: this needs to be replaced by a config file of some sort
if (blockState == Blocks.AIR.defaultBlockState()
|| blockState == Blocks.CAVE_AIR.defaultBlockState()
|| blockState == Blocks.BARRIER.defaultBlockState())
{
Color tmp = LodUtil.intToColor(biome.getGrassColor(x, z));
tmp = tmp.darker();
colorInt = LodUtil.colorToInt(tmp);
}
blockColor = getColorTextureForBlock(blockState, blockPos, true);
if (toTint.get(blockState.getBlock()).booleanValue())
{
if (useLeafTint(blockState.getBlock()))
{
// leaves
colorInt = ColorUtil.multiplyRGBcolors(biome.getFoliageColor(), blockColor);
}
else if (useGrassTint(blockState.getBlock()))
{
// grass and green plants
colorInt = ColorUtil.multiplyRGBcolors(biome.getGrassColor(x, z), blockColor);
}
else if (useWaterTint(blockState.getBlock()))
{
// water
colorInt = ColorUtil.multiplyRGBcolors(biome.getWaterColor(), blockColor);
}
}
else
{
colorInt = blockColor;
}
return colorInt;
}
/**
* Returns a color int for the given biome.
*/
private int getColorForBiome(int x, int z, Biome biome)
{
int colorInt;
switch (biome.getBiomeCategory())
{
case NETHER:
colorInt = LodUtil.NETHERRACK_COLOR_INT;
break;
case THEEND:
colorInt = Blocks.END_STONE.defaultBlockState().materialColor.col;
break;
case BEACH:
case DESERT:
colorInt = Blocks.SAND.defaultBlockState().materialColor.col;
break;
case EXTREME_HILLS:
colorInt = Blocks.STONE.defaultMaterialColor().col;
break;
case MUSHROOM:
colorInt = MaterialColor.COLOR_LIGHT_GRAY.col;
break;
case ICY:
colorInt = Blocks.SNOW.defaultMaterialColor().col;
break;
case MESA:
colorInt = Blocks.RED_SAND.defaultMaterialColor().col;
break;
case OCEAN:
case RIVER:
colorInt = biome.getWaterColor();
break;
case NONE:
case FOREST:
case TAIGA:
case JUNGLE:
case PLAINS:
case SAVANNA:
case SWAMP:
default:
Color tmp = LodUtil.intToColor(biome.getGrassColor(x, z));
tmp = tmp.darker();
colorInt = LodUtil.colorToInt(tmp);
break;
}
return colorInt;
}
/** Is the block at the given blockPos a valid LOD point? */
private boolean isLayerValidLodPoint(IChunk chunk, BlockPos.Mutable blockPos)
{
BlockState blockState = chunk.getBlockState(blockPos);
if (blockState != null)
{
// TODO this code is dead since avoidSmallBlock and onlyUseFullBlock
// are set to false and are never changed.
// should this code be changed?
if (avoidSmallBlock || onlyUseFullBlock)
{
if (!blockState.getFluidState().isEmpty())
return true;
VoxelShape voxelShape;
if (shapeMap.containsKey(blockState.getBlock()))
{
voxelShape = shapeMap.get(blockState.getBlock());
}
else
{
voxelShape = blockState.getShape(chunk, blockPos);
shapeMap.put(blockState.getBlock(), voxelShape);
}
if (!voxelShape.isEmpty())
{
AxisAlignedBB bbox = voxelShape.bounds();
int xWidth = (int) (bbox.maxX - bbox.minX);
int yWidth = (int) (bbox.maxY - bbox.minY);
int zWidth = (int) (bbox.maxZ - bbox.minZ);
if (xWidth < 1 && zWidth < 1 && yWidth < 1 && onlyUseFullBlock)
{
return false;
}
if (xWidth < 0.7 && zWidth < 0.7 && yWidth < 1 && avoidSmallBlock)
{
return false;
}
}
else
{
return false;
}
}
return blockState.getBlock() != Blocks.AIR
&& blockState.getBlock() != Blocks.CAVE_AIR
&& blockState.getBlock() != Blocks.BARRIER;
}
return false;
}
}
@@ -17,45 +17,82 @@
*/
package com.seibel.lod.builders;
import com.seibel.lod.enums.DistanceGenerationMode;
/**
* This is used to easily configure how LodChunks are generated.
* Generally this will only be used if we want to generate a
* LodChunk using a incomplete Chunk, otherwise the defaults
* work best for a fully generated chunk (IE has correct surface blocks).
*
*
* @author James Seibel
* @version 6-27-2021
* @version 8-14-2021
*/
public class LodBuilderConfig
{
/** default false */
/** default: false */
public boolean useHeightmap;
/** default false */
/** default: false */
public boolean useBiomeColors;
/** default true */
/** default: true */
public boolean useSolidBlocksInColorGen;
/** default settings for a normal chunk
* useHeightmap = false
* useBiomeColors = false
* useSolidBlocksInColorGen = true
/** default: server */
public DistanceGenerationMode distanceGenerationMode;
/**
* default settings for a normal chunk <br>
* useHeightmap = false <br>
* useBiomeColors = false <br>
* useSolidBlocksInColorGen = true <br>
* generationMode = Server <br>
*/
public LodBuilderConfig()
{
useHeightmap = false;
useBiomeColors = false;
useSolidBlocksInColorGen = true;
distanceGenerationMode = DistanceGenerationMode.SERVER;
}
/**
* @param newUseHeightmap default = false
* @param newUseBiomeColors default = false
* @param newUseHeightmap default = false
* @param newUseBiomeColors default = false
* @param newUseSolidBlocksInBiomeColor default = true
* @param newDistanceGenerationMode default = Server
*/
public LodBuilderConfig(boolean newUseHeightmap, boolean newUseBiomeColors, boolean newUseSolidBlocksInBiomeColor)
public LodBuilderConfig(boolean newUseHeightmap, boolean newUseBiomeColors,
boolean newUseSolidBlocksInBiomeColor, DistanceGenerationMode newDistanceGenerationMode)
{
useHeightmap = newUseHeightmap;
useBiomeColors = newUseBiomeColors;
useSolidBlocksInColorGen = newUseSolidBlocksInBiomeColor;
distanceGenerationMode = newDistanceGenerationMode;
}
/**
* @param newUseHeightmap default = false
* @param newUseBiomeColors default = false
* @param newUseSolidBlocksInBiomeColor default = true
* @param newDistanceGenerationMode default = Server
*/
public LodBuilderConfig(boolean newUseHeightmap, boolean newUseBiomeColors, boolean newUseSolidBlocksInBiomeColor)
{
this();
useHeightmap = newUseHeightmap;
useBiomeColors = newUseBiomeColors;
useSolidBlocksInColorGen = newUseSolidBlocksInBiomeColor;
distanceGenerationMode = newUseHeightmap? DistanceGenerationMode.BIOME_ONLY_SIMULATE_HEIGHT : DistanceGenerationMode.BIOME_ONLY;
}
/**
* @param newUseHeightmap default = false
* @param newUseBiomeColors default = false
* @param newUseSolidBlocksInBiomeColor default = true
* @param newDistanceGenerationMode default = Server
*/
public LodBuilderConfig(DistanceGenerationMode newDistanceGenerationMode)
{
this();
distanceGenerationMode = newDistanceGenerationMode;
}
}
@@ -1,527 +0,0 @@
/*
* This file is part of the LOD Mod, licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.lod.builders;
import java.awt.Color;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import com.seibel.lod.enums.LodDetail;
import com.seibel.lod.handlers.LodConfig;
import com.seibel.lod.objects.LodChunk;
import com.seibel.lod.objects.LodDataPoint;
import com.seibel.lod.objects.LodDimension;
import com.seibel.lod.objects.LodWorld;
import com.seibel.lod.util.LodUtil;
import net.minecraft.block.AbstractPlantBlock;
import net.minecraft.block.BlockState;
import net.minecraft.block.Blocks;
import net.minecraft.block.BushBlock;
import net.minecraft.block.FlowingFluidBlock;
import net.minecraft.block.GrassBlock;
import net.minecraft.block.IGrowable;
import net.minecraft.block.LeavesBlock;
import net.minecraft.world.DimensionType;
import net.minecraft.world.IWorld;
import net.minecraft.world.biome.Biome;
import net.minecraft.world.chunk.ChunkSection;
import net.minecraft.world.chunk.IChunk;
import net.minecraft.world.gen.Heightmap;
/**
* This object is in charge of creating Lod
* related objects.
* (specifically: Lod World, Dimension, Region, and Chunk objects)
*
* @author James Seibel
* @version 6-27-2021
*/
public class LodChunkBuilder
{
private ExecutorService lodGenThreadPool = Executors.newSingleThreadExecutor();
/** Default size of any LOD regions we use */
public int regionWidth = 5;
public static final int CHUNK_DATA_WIDTH = LodChunk.WIDTH;
public static final int CHUNK_SECTION_HEIGHT = LodChunk.WIDTH;
public LodChunkBuilder()
{
}
public void generateLodChunkAsync(IChunk chunk, LodWorld lodWorld, IWorld world)
{
generateLodChunkAsync(chunk, new LodBuilderConfig(), lodWorld, world);
}
public void generateLodChunkAsync(IChunk chunk, LodBuilderConfig config, LodWorld lodWorld, IWorld world)
{
if (lodWorld == null || !lodWorld.getIsWorldLoaded())
return;
// don't try to create an LOD object
// if for some reason we aren't
// given a valid chunk object
if (chunk == null)
return;
Thread thread = new Thread(() ->
{
try
{
DimensionType dim = world.dimensionType();
LodChunk lod = generateLodFromChunk(chunk, config);
LodDimension lodDim;
if (lodWorld.getLodDimension(dim) == null)
{
lodDim = new LodDimension(dim, lodWorld, regionWidth);
lodWorld.addLodDimension(lodDim);
}
else
{
lodDim = lodWorld.getLodDimension(dim);
}
lodDim.addLod(lod);
}
catch(IllegalArgumentException | NullPointerException e)
{
// if the world changes while LODs are being generated
// they will throw errors as they try to access things that no longer
// exist.
}
});
lodGenThreadPool.execute(thread);
return;
}
/**
* Creates a LodChunk for a chunk in the given world.
*
* @throws IllegalArgumentException
* thrown if either the chunk or world is null.
*/
public LodChunk generateLodFromChunk(IChunk chunk) throws IllegalArgumentException
{
return generateLodFromChunk(chunk, new LodBuilderConfig());
}
/**
* Creates a LodChunk for a chunk in the given world.
*
* @throws IllegalArgumentException
* thrown if either the chunk or world is null.
*/
public LodChunk generateLodFromChunk(IChunk chunk, LodBuilderConfig config) throws IllegalArgumentException
{
if(chunk == null)
throw new IllegalArgumentException("generateLodFromChunk given a null chunk");
LodDetail detail = LodConfig.CLIENT.lodDetail.get();
LodDataPoint[][] dataPoints = new LodDataPoint[detail.dataPointLengthCount][detail.dataPointLengthCount];
for(int i = 0; i < detail.dataPointLengthCount * detail.dataPointLengthCount; i++)
{
int startX = detail.startX[i];
int startZ = detail.startZ[i];
int endX = detail.endX[i];
int endZ = detail.endZ[i];
Color color;
color = generateLodColorForArea(chunk, config, startX, startZ, endX, endZ);
short height;
short depth;
if (!config.useHeightmap)
{
height = determineHeightPointForArea(chunk.getSections(), startX, startZ, endX, endZ);
depth = determineBottomPointForArea(chunk.getSections(), startX, startZ, endX, endZ);
}
else
{
height = determineHeightPoint(chunk.getOrCreateHeightmapUnprimed(LodChunk.DEFAULT_HEIGHTMAP), startX, startZ, endX, endZ);
depth = 0;
}
int x = i / detail.dataPointLengthCount;
int z = i % detail.dataPointLengthCount;
dataPoints[x][z] = new LodDataPoint(height, depth, color);
}
return new LodChunk(chunk.getPos(), dataPoints, detail);
}
//=====================//
// constructor helpers //
//=====================//
/**
* Find the lowest valid point from the bottom.
* @param chunkSections
* @param startX
* @param startZ
* @param endX
* @param endZ
*/
private short determineBottomPointForArea(ChunkSection[] chunkSections,
int startX, int startZ, int endX, int endZ)
{
int numberOfBlocksRequired = ((endX-startX) * (endZ-startZ) / 2);
// search from the bottom up
for(int section = 0; section < CHUNK_DATA_WIDTH; section++)
{
for(int y = 0; y < CHUNK_SECTION_HEIGHT; y++)
{
int numberOfBlocksFound = 0;
for(int x = startX; x < endX; x++)
{
for(int z = startZ; z < endZ; z++)
{
if(isLayerValidLodPoint(chunkSections, section, y, x, z))
{
numberOfBlocksFound++;
if (numberOfBlocksFound >= numberOfBlocksRequired)
{
// we found
// enough blocks in this
// layer to count as an
// LOD point
return (short) (y + (section * CHUNK_SECTION_HEIGHT));
}
}
}
}
}
}
// we never found a valid LOD point
return -1;
}
/**
* Find the lowest valid point from the bottom.
*/
@SuppressWarnings("unused")
private short determineBottomPoint(Heightmap heightmap)
{
// the heightmap only shows how high the blocks go, it
// doesn't have any info about how low they go
return 0;
}
/**
* Find the highest valid point from the Top
* @param chunkSections
* @param startX
* @param startZ
* @param endX
* @param endZ
*/
private short determineHeightPointForArea(ChunkSection[] chunkSections,
int startX, int startZ, int endX, int endZ)
{
int numberOfBlocksRequired = ((endX-startX) * (endZ-startZ) / 2);
// search from the top down
for(int section = chunkSections.length - 1; section >= 0; section--)
{
for(int y = CHUNK_DATA_WIDTH - 1; y >= 0; y--)
{
int numberOfBlocksFound = 0;
for(int x = startX; x < endX; x++)
{
for(int z = startZ; z < endZ; z++)
{
if(isLayerValidLodPoint(chunkSections, section, y, x, z))
{
numberOfBlocksFound++;
if (numberOfBlocksFound >= numberOfBlocksRequired)
{
// we found
// enough blocks in this
// layer to count as an
// LOD point
return (short) (y + (section * CHUNK_SECTION_HEIGHT));
}
}
}
}
}
}
// we never found a valid LOD point
return -1;
}
/**
* Find the highest point from the Top
*/
private short determineHeightPoint(Heightmap heightmap,
int startX, int startZ, int endX, int endZ)
{
short highest = 0;
for(int x = startX; x < endX; x++)
{
for(int z = startZ; z < endZ; z++)
{
short newHeight = (short) heightmap.getFirstAvailable(x, z);
if (newHeight > highest)
highest = newHeight;
}
}
return highest;
}
/**
* Generate the color for the given chunk using biome
* water color, foliage color, and grass color.
*
* @param config_useSolidBlocksInColorGen <br>
* If true we look down from the top of the <br>
* chunk until we find a non-invisible block, and then use <br>
* its color. If false we generate the color immediately for <br>
* each x and z.
* @param config_useBiomeColors <br>
* If true use biome foliage, water, and grass colors, <br>
* otherwise only use the block's material color
*/
private Color generateLodColorForArea(IChunk chunk, LodBuilderConfig config, int startX, int startZ, int endX, int endZ)
{
ChunkSection[] chunkSections = chunk.getSections();
int numbOfBlocks = 0;
int red = 0;
int green = 0;
int blue = 0;
for(int x = startX; x < endX; x++)
{
for(int z = startZ; z < endZ; z++)
{
boolean foundBlock = false;
// go top down
for(int i = chunkSections.length - 1; !foundBlock && i >= 0; i--)
{
if( !foundBlock && (chunkSections[i] != null || !config.useSolidBlocksInColorGen))
{
for(int y = CHUNK_SECTION_HEIGHT - 1; !foundBlock && y >= 0; y--)
{
int colorInt = 0;
BlockState blockState = null;
if (chunkSections[i] != null)
{
blockState = chunkSections[i].getBlockState(x, y, z);
colorInt = blockState.materialColor.col;
}
if(colorInt == 0 && config.useSolidBlocksInColorGen)
{
// skip air or invisible blocks
continue;
}
if (config.useBiomeColors)
{
Biome biome = chunk.getBiomes().getNoiseBiome(x, y + i * chunkSections.length, z);
if (biome.getBiomeCategory() == Biome.Category.OCEAN ||
biome.getBiomeCategory() == Biome.Category.RIVER)
{
if (config.useSolidBlocksInColorGen &&
blockState != Blocks.WATER.defaultBlockState())
{
// non-water block
colorInt = getColorForBlock(x, z, blockState, biome);
}
else
{
// water block
colorInt = biome.getWaterColor();
}
}
else if (biome.getBiomeCategory() == Biome.Category.EXTREME_HILLS)
{
colorInt = Blocks.STONE.defaultMaterialColor().col;
}
else if (biome.getBiomeCategory() == Biome.Category.ICY)
{
colorInt = LodUtil.colorToInt(Color.WHITE);
}
else if (biome.getBiomeCategory() == Biome.Category.THEEND)
{
colorInt = Blocks.END_STONE.defaultBlockState().materialColor.col;
}
else if (config.useSolidBlocksInColorGen)
{
colorInt = getColorForBlock(x, z, blockState, biome);
}
else
{
colorInt = biome.getGrassColor(x, z);
}
}
else
{
Biome biome = chunk.getBiomes().getNoiseBiome(x, y + i * chunkSections.length, z);
colorInt = getColorForBlock(x,z, blockState, biome);
}
Color c = LodUtil.intToColor(colorInt);
red += c.getRed();
green += c.getGreen();
blue += c.getBlue();
numbOfBlocks++;
// we found a valid block, skip to the
// next x and z
foundBlock = true;
}
}
}
}
}
if(numbOfBlocks == 0)
numbOfBlocks = 1;
red /= numbOfBlocks;
green /= numbOfBlocks;
blue /= numbOfBlocks;
return new Color(red, green, blue);
}
/**
* Returns a color int for a given block.
*/
private int getColorForBlock(int x, int z, BlockState blockState, Biome biome)
{
int colorInt = 0;
if (blockState == Blocks.AIR.defaultBlockState())
{
colorInt = biome.getGrassColor(x, z);
}
else if (blockState.getBlock() instanceof LeavesBlock)
{
Color leafColor = LodUtil.intToColor(biome.getFoliageColor()).darker();
colorInt = LodUtil.colorToInt(leafColor);
}
else if (blockState.getBlock() instanceof GrassBlock ||
blockState.getBlock() instanceof AbstractPlantBlock ||
blockState.getBlock() instanceof BushBlock ||
blockState.getBlock() instanceof IGrowable)
{
colorInt = biome.getGrassColor(x, z);
}
else if (blockState.getBlock() instanceof FlowingFluidBlock)
{
colorInt = biome.getWaterColor();
}
else
{
colorInt = blockState.materialColor.col;
}
return colorInt;
}
/**
* Is the layer between the given X, Z, and dataIndex
* values a valid LOD point?
*/
private boolean isLayerValidLodPoint(
ChunkSection[] chunkSections,
int sectionIndex, int y,
int x, int z)
{
if(chunkSections[sectionIndex] == null)
{
// this section doesn't have any blocks,
// it is not a valid section
return false;
}
else
{
if(chunkSections[sectionIndex].getBlockState(x, y, z) != null &&
chunkSections[sectionIndex].getBlockState(x, y, z).getBlock() != Blocks.AIR)
{
return true;
}
}
return false;
}
}
@@ -17,38 +17,45 @@
*/
package com.seibel.lod.builders.lodTemplates;
import com.seibel.lod.enums.LodDetail;
import com.seibel.lod.objects.LodChunk;
import com.seibel.lod.objects.LodDimension;
import java.util.Map;
import com.seibel.lod.enums.DebugMode;
import com.seibel.lod.util.ColorUtil;
import net.minecraft.client.renderer.BufferBuilder;
import net.minecraft.client.renderer.texture.NativeImage;
import net.minecraft.util.Direction;
import net.minecraft.util.math.BlockPos;
/**
* This is the abstract class used to create different
* BufferBuilders.
*
*
* @author James Seibel
* @version 06-16-2021
* @version 8-8-2021
*/
public abstract class AbstractLodTemplate
{
public abstract void addLodToBuffer(BufferBuilder buffer,
LodDimension lodDim, LodChunk lod,
double xOffset, double yOffset, double zOffset,
boolean debugging);
/** add the given position and color to the buffer */
protected void addPosAndColor(BufferBuilder buffer,
double x, double y, double z,
int red, int green, int blue, int alpha)
/**
* Uploads the given LOD to the buffer.
*/
public abstract void addLodToBuffer(BufferBuilder buffer, BlockPos bufferCenterBlockPos, long data, Map<Direction, long[]> adjData,
byte detailLevel, int posX, int posZ, Box box, DebugMode debugging, NativeImage lightMap);
/**
* add the given position and color to the buffer
*/
protected void addPosAndColor(BufferBuilder buffer,
double x, double y, double z,
int color)
{
buffer.vertex(x, y, z).color(red, green, blue, alpha).endVertex();
buffer.vertex(x, y, z).color(ColorUtil.getRed(color), ColorUtil.getGreen(color), ColorUtil.getBlue(color), ColorUtil.getAlpha(color)).endVertex();
}
/** Returns in bytes how much buffer memory is required
* for one LOD object */
public abstract int getBufferMemoryForSingleLod(LodDetail detail);
/**
* Returns in bytes how much buffer memory is required
* for one LOD object
*/
public abstract int getBufferMemoryForSingleNode(int maxVerticalData);
}
@@ -0,0 +1,512 @@
package com.seibel.lod.builders.lodTemplates;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import com.seibel.lod.config.LodConfig;
import com.seibel.lod.enums.DebugMode;
import com.seibel.lod.util.ColorUtil;
import com.seibel.lod.util.DataPointUtil;
import com.seibel.lod.wrappers.MinecraftWrapper;
import net.minecraft.util.Direction;
import net.minecraft.util.math.BlockPos;
/**
* Similar to Minecraft's AxisAlignedBoundingBox.
*
* @author Leonardo Amato
* @version 9-25-2021
*/
public class Box
{
public static final int ADJACENT_HEIGHT_INDEX = 0;
public static final int ADJACENT_DEPTH_INDEX = 1;
public static final int X = 0;
public static final int Y = 1;
public static final int Z = 2;
public static final int MIN = 0;
public static final int MAX = 1;
public static final int VOID_FACE = 0;
/** The six cardinal directions */
public static final Direction[] DIRECTIONS = new Direction[]{
Direction.UP,
Direction.DOWN,
Direction.WEST,
Direction.EAST,
Direction.NORTH,
Direction.SOUTH};
/** North, South, East, West */
public static final Direction[] ADJ_DIRECTIONS = new Direction[]{
Direction.EAST,
Direction.WEST,
Direction.SOUTH,
Direction.NORTH};
/** TODO what does this represent? I don't understand the name */
@SuppressWarnings("serial")
public static final Map<Direction, int[][]> DIRECTION_VERTEX_MAP = new HashMap<Direction, int[][]>()
{{
put(Direction.UP, new int[][]{
{0, 1, 0},
{0, 1, 1},
{1, 1, 1},
{1, 1, 0}});
put(Direction.DOWN, new int[][]{
{1, 0, 0},
{1, 0, 1},
{0, 0, 1},
{0, 0, 0}});
put(Direction.EAST, new int[][]{
{1, 1, 0},
{1, 1, 1},
{1, 0, 1},
{1, 0, 0}});
put(Direction.WEST, new int[][]{
{0, 0, 0},
{0, 0, 1},
{0, 1, 1},
{0, 1, 0}});
put(Direction.SOUTH, new int[][]{
{1, 0, 1},
{1, 1, 1},
{0, 1, 1},
{0, 0, 1}});
put(Direction.NORTH, new int[][]{
{0, 0, 0},
{0, 1, 0},
{1, 1, 0},
{1, 0, 0}});
}};
@SuppressWarnings("serial")
public static final Map<Direction, int[]> FACE_DIRECTION = new HashMap<Direction, int[]>()
{{
put(Direction.UP, new int[]{Y, MAX});
put(Direction.DOWN, new int[]{Y, MIN});
put(Direction.EAST, new int[]{X, MAX});
put(Direction.WEST, new int[]{X, MIN});
put(Direction.SOUTH, new int[]{Z, MAX});
put(Direction.NORTH, new int[]{Z, MIN});
}};
@SuppressWarnings("serial")
public static final Map<Direction, int[]> DIRECTION_NORMAL_MAP = new HashMap<Direction, int[]>()
{{
put(Direction.UP, new int[]{0, 1, 0});
put(Direction.DOWN, new int[]{0, -1, 0});
put(Direction.EAST, new int[]{1, 0, 0});
put(Direction.WEST, new int[]{-1, 0, 0});
put(Direction.SOUTH, new int[]{0, 0, 1});
put(Direction.NORTH, new int[]{0, 0, -1});
}};
/** holds the box's x, y, z offset */
public int[] boxOffset;
/** holds the box's x, y, z width */
public int[] boxWidth;
/** Holds each direction's color */
public Map<Direction, Integer> colorMap;
/** The original color (before shading) of this box */
public int color;
/** */
public Map<Direction, int[]> adjHeight;
public Map<Direction, int[]> adjDepth;
/** Holds if the given direction should be culled or not */
public Map<Direction, Boolean> culling;
/** creates a empty box */
@SuppressWarnings("serial")
public Box()
{
boxOffset = new int[3];
boxWidth = new int[3];
colorMap = new HashMap<Direction, Integer>()
{{
put(Direction.UP, 0);
put(Direction.DOWN, 0);
put(Direction.EAST, 0);
put(Direction.WEST, 0);
put(Direction.SOUTH, 0);
put(Direction.NORTH, 0);
}};
// TODO what does the 32 represent?
adjHeight = new HashMap<Direction, int[]>()
{{
put(Direction.EAST, new int[32]);
put(Direction.WEST, new int[32]);
put(Direction.SOUTH, new int[32]);
put(Direction.NORTH, new int[32]);
}};
adjDepth = new HashMap<Direction, int[]>()
{{
put(Direction.EAST, new int[32]);
put(Direction.WEST, new int[32]);
put(Direction.SOUTH, new int[32]);
put(Direction.NORTH, new int[32]);
}};
culling = new HashMap<Direction, Boolean>()
{{
put(Direction.UP, false);
put(Direction.DOWN, false);
put(Direction.EAST, false);
put(Direction.WEST, false);
put(Direction.SOUTH, false);
put(Direction.NORTH, false);
}};
}
public void setColor(int color)
{
this.color = color;
for (Direction direction : DIRECTIONS)
{
colorMap.put(direction, ColorUtil.applyShade(color, MinecraftWrapper.INSTANCE.getClientWorld().getShade(direction, true)));
}
}
public int getColor(Direction direction)
{
if (LodConfig.CLIENT.debugging.debugMode.get() != DebugMode.SHOW_DETAIL)
{
return colorMap.get(direction);
}
else
{
return ColorUtil.applyShade(color, MinecraftWrapper.INSTANCE.getClientWorld().getShade(direction, true));
}
}
/** clears this box, reseting everything to default values */
public void reset()
{
Arrays.fill(boxWidth, 0);
Arrays.fill(boxOffset, 0);
for (Direction direction : DIRECTIONS)
{
colorMap.put(direction, 0);
}
for (Direction direction : ADJ_DIRECTIONS)
{
// TODO wouldn't we want to set all adjHeightAndDepth
// to VOID_FACE regardless of the culled status?
if (isCulled(direction))
continue;
for (int i = 0; i < adjHeight.get(direction).length; i++)
{
adjHeight.get(direction)[i] = VOID_FACE;
adjDepth.get(direction)[i] = VOID_FACE;
}
}
}
/** determine which faces should be culled */
public void setUpCulling(int cullingDistance, BlockPos playerPos)
{
for (Direction direction : DIRECTIONS)
{
if (direction == Direction.DOWN)
culling.put(direction, playerPos.get(direction.getAxis()) > getFacePos(direction) + cullingDistance);
else if (direction == Direction.UP)
culling.put(direction, playerPos.get(direction.getAxis()) < getFacePos(direction) - cullingDistance);
else if (direction == Direction.WEST)
culling.put(direction, -playerPos.get(direction.getAxis()) > getFacePos(direction) + cullingDistance);
else if (direction == Direction.NORTH)
culling.put(direction, -playerPos.get(direction.getAxis()) > getFacePos(direction) + cullingDistance);
else if (direction == Direction.EAST)
culling.put(direction, -playerPos.get(direction.getAxis()) < getFacePos(direction) - cullingDistance);
else if (direction == Direction.SOUTH)
culling.put(direction, -playerPos.get(direction.getAxis()) < getFacePos(direction) - cullingDistance);
}
}
public boolean isCulled(Direction direction)
{
return culling.get(direction);
}
public void setAdjData(Map<Direction, long[]> adjData)
{
int height;
int depth;
int minY = getMinY();
int maxY = getMaxY();
for (Direction direction : ADJ_DIRECTIONS)
{
long[] dataPoint = adjData.get(direction);
if (dataPoint == null || DataPointUtil.isVoid(dataPoint[0]))
{
adjHeight.get(direction)[0] = maxY;
adjDepth.get(direction)[0] = minY;
adjHeight.get(direction)[1] = VOID_FACE;
adjDepth.get(direction)[1] = VOID_FACE;
continue;
}
//We order the adj list
/**TODO remove this if the order is maintained naturally*/
/*order[0] = 0;
int count = 0;
for (int i = 0; i < dataPoint.length; i++)
{
int j = i - 1;
if(DataPointUtil.isItVoid(dataPoint[i]) || !DataPointUtil.doesItExist(dataPoint[i]))
{
continue;
}
while (j >= 0 && DataPointUtil.getHeight(order[j]) < DataPointUtil.getHeight(dataPoint[i]))
{
order[j + 1] = order[j];
j = j - 1;
}
order[j + 1] = dataPoint[i];
count++;
}*/
int i;
int faceToDraw = 0;
boolean firstFace = true;
boolean toFinish = false;
boolean allAbove = true;
long singleAdjDataPoint;
for (i = 0; i < dataPoint.length; i++)
{
singleAdjDataPoint = dataPoint[i];
if(DataPointUtil.isVoid(singleAdjDataPoint) || !DataPointUtil.doesItExist(singleAdjDataPoint))
break;
height = DataPointUtil.getHeight(singleAdjDataPoint);
depth = DataPointUtil.getDepth(singleAdjDataPoint);
if (depth <= maxY)
{
allAbove = false;
if (height < minY)
{
// the adj data is lower than the current data
if (firstFace)
{
adjHeight.get(direction)[0] = getMaxY();
adjDepth.get(direction)[0] = getMinY();
}
else
{
adjDepth.get(direction)[faceToDraw] = getMinY();
}
faceToDraw++;
toFinish = false;
// break since all the other data will be lower
break;
}
else if (depth <= minY && height >= maxY)
{
// the adj data is inside the current data
// don't draw the face
adjHeight.get(direction)[0] = VOID_FACE;
adjDepth.get(direction)[0] = VOID_FACE;
break;
}
else if (depth <= minY)//&& height < maxY
{
// the adj data intersects the lower part of the current data
// if this is the only face, use the maxY and break,
// if there was another face we finish the last one and break
if (firstFace)
{
adjHeight.get(direction)[0] = getMaxY();
adjDepth.get(direction)[0] = height;
}
else
{
adjDepth.get(direction)[faceToDraw] = height;
}
toFinish = false;
faceToDraw++;
break;
}
else if (height >= maxY)//depth > minY &&
{
// the adj data intersects the higher part of the current data
// we start the creation of a new face
adjHeight.get(direction)[faceToDraw] = depth;
firstFace = false;
toFinish = true;
}
else
{
// if (depth > minY && height < maxY)
// the adj data is contained in the current data
if (firstFace)
{
adjHeight.get(direction)[0] = getMaxY();
}
adjDepth.get(direction)[faceToDraw] = height;
faceToDraw++;
adjHeight.get(direction)[faceToDraw] = depth;
firstFace = false;
toFinish = true;
}
}
//else
//{
// // the adj data is higher than the current data
// // we continue since there could be some other data that intersect the current
//}
}
if(allAbove)
{
adjHeight.get(direction)[0] = getMaxY();
adjDepth.get(direction)[0] = getMinY();
faceToDraw++;
}
else if (toFinish)
{
adjDepth.get(direction)[faceToDraw] = minY;
faceToDraw++;
}
adjHeight.get(direction)[faceToDraw] = VOID_FACE;
adjDepth.get(direction)[faceToDraw] = VOID_FACE;
}
}
public void setWidth(int xWidth, int yWidth, int zWidth)
{
boxWidth[X] = xWidth;
boxWidth[Y] = yWidth;
boxWidth[Z] = zWidth;
}
public void setOffset(int xOffset, int yOffset, int zOffset)
{
boxOffset[X] = xOffset;
boxOffset[Y] = yOffset;
boxOffset[Z] = zOffset;
}
// TODO what does this mean?
public int getFacePos(Direction direction)
{
return boxOffset[FACE_DIRECTION.get(direction)[0]] + boxWidth[FACE_DIRECTION.get(direction)[0]] * FACE_DIRECTION.get(direction)[1];
}
// TODO is this still needed?
// public int getCoord(Direction direction, int axis, int vertexIndex)
// {
// return box[OFFSET][axis] + boxWidth[axis] * DIRECTION_VERTEX_MAP.get(direction)[vertexIndex][axis];
// }
/**
* returns true if the given direction should be rendered.
*/
public boolean shouldRenderFace(Direction direction, int adjIndex)
{
if (direction == Direction.UP || direction == Direction.DOWN)
{
return adjIndex == 0;
}
return !(adjHeight.get(direction)[adjIndex] == VOID_FACE && adjDepth.get(direction)[adjIndex] == VOID_FACE);
}
// TODO what does vertexIndex mean, is it 0-3 and represent
// the 4 vertices in the quad we send to the bufferBuilder?
public int getX(Direction direction, int vertexIndex)
{
return boxOffset[X] + boxWidth[X] * DIRECTION_VERTEX_MAP.get(direction)[vertexIndex][X];
}
public int getY(Direction direction, int vertexIndex)
{
return boxOffset[Y] + boxWidth[Y] * DIRECTION_VERTEX_MAP.get(direction)[vertexIndex][Y];
}
public int getY(Direction direction, int vertexIndex, int adjIndex)
{
if (direction == Direction.DOWN || direction == Direction.UP)
{
return boxOffset[Y] + boxWidth[Y] * DIRECTION_VERTEX_MAP.get(direction)[vertexIndex][Y];
}
else
{
// this could probably be cleaned up more,
// but it still works
if (1 - DIRECTION_VERTEX_MAP.get(direction)[vertexIndex][Y] == ADJACENT_HEIGHT_INDEX)
{
return adjHeight.get(direction)[adjIndex];
}
else
{
return adjDepth.get(direction)[adjIndex];
}
}
}
public int getZ(Direction direction, int vertexIndex)
{
return boxOffset[Z] + boxWidth[Z] * DIRECTION_VERTEX_MAP.get(direction)[vertexIndex][Z];
}
public int getMinX()
{
return boxOffset[X];
}
public int getMaxX()
{
return boxOffset[X] + boxWidth[X];
}
public int getMinY()
{
return boxOffset[Y];
}
public int getMaxY()
{
return boxOffset[Y] + boxWidth[Y];
}
public int getMinZ()
{
return boxOffset[Z];
}
public int getMaxZ()
{
return boxOffset[Z] + boxWidth[Z];
}
}
@@ -17,22 +17,22 @@
*/
package com.seibel.lod.builders.lodTemplates;
import java.awt.Color;
import java.util.Map;
import com.seibel.lod.enums.ColorDirection;
import com.seibel.lod.enums.LodDetail;
import com.seibel.lod.handlers.LodConfig;
import com.seibel.lod.objects.LodChunk;
import com.seibel.lod.objects.LodDimension;
import com.seibel.lod.enums.DebugMode;
import com.seibel.lod.util.DataPointUtil;
import com.seibel.lod.util.LodUtil;
import net.minecraft.client.renderer.BufferBuilder;
import net.minecraft.util.math.AxisAlignedBB;
import net.minecraft.client.renderer.texture.NativeImage;
import net.minecraft.util.Direction;
import net.minecraft.util.math.BlockPos;
/**
* Builds LODs as rectangular prisms.
*
*
* @author James Seibel
* @version 06-16-2021
* @version 9-25-2021
*/
public class CubicLodTemplate extends AbstractLodTemplate
{
@@ -42,53 +42,46 @@ public class CubicLodTemplate extends AbstractLodTemplate
}
@Override
public void addLodToBuffer(BufferBuilder buffer,
LodDimension lodDim, LodChunk centerLod,
double xOffset, double yOffset, double zOffset,
boolean debugging)
public void addLodToBuffer(BufferBuilder buffer, BlockPos bufferCenterBlockPos, long data, Map<Direction, long[]> adjData,
byte detailLevel, int posX, int posZ, Box box, DebugMode debugging, NativeImage lightMap)
{
AxisAlignedBB bbox;
if (box == null)
return;
// Add this LOD to the BufferBuilder
// using the quality setting set by the config
LodDetail detail = LodConfig.CLIENT.lodDetail.get();
// equivalent to 2^detailLevel
int blockWidth = 1 << detailLevel;
int halfWidth = detail.dataPointWidth / 2;
int color;
if (debugging != DebugMode.OFF)
color = LodUtil.DEBUG_DETAIL_LEVEL_COLORS[detailLevel].getRGB();
else
color = DataPointUtil.getLightColor(data, lightMap);
for(int i = 0; i < detail.dataPointLengthCount * detail.dataPointLengthCount; i++)
{
int startX = detail.startX[i];
int startZ = detail.startZ[i];
int endX = detail.endX[i];
int endZ = detail.endZ[i];
// returns null if the lod is empty at the given location
bbox = generateBoundingBox(
centerLod.getAverageHeightOverArea(startX, startZ, endX, endZ),
centerLod.getAverageDepthOverArea(startX, startZ, endX, endZ),
detail.dataPointWidth,
xOffset - (halfWidth / 2) + detail.startX[i],
yOffset,
zOffset - (halfWidth / 2) + detail.startZ[i]);
if (bbox != null)
{
addBoundingBoxToBuffer(buffer, bbox, centerLod.getAverageColorOverArea(startX, startZ, endX, endZ, debugging));
}
}
generateBoundingBox(
box,
DataPointUtil.getHeight(data),
DataPointUtil.getDepth(data),
blockWidth,
posX * blockWidth, 0, posZ * blockWidth, // x, y, z offset
bufferCenterBlockPos,
adjData,
color);
addBoundingBoxToBuffer(buffer, box);
}
private AxisAlignedBB generateBoundingBox(int height, int depth, int width, double xOffset, double yOffset, double zOffset)
private void generateBoundingBox(Box box,
int height, int depth, int width,
double xOffset, double yOffset, double zOffset,
BlockPos bufferCenterBlockPos,
Map<Direction, long[]> adjData,
int color)
{
// don't add an LOD if it is empty
if (height == -1 && depth == -1)
return null;
return;
if (depth == height)
{
@@ -97,95 +90,45 @@ public class CubicLodTemplate extends AbstractLodTemplate
height++;
}
return new AxisAlignedBB(0, depth, 0, width, height, width).move(xOffset, yOffset, zOffset);
// offset the AABB by it's x/z position in the world since
// it uses doubles to specify its location, unlike the model view matrix
// which only uses floats
double x = -bufferCenterBlockPos.getX();
double z = -bufferCenterBlockPos.getZ();
box.reset();
box.setColor(color);
box.setWidth(width, height - depth, width);
box.setOffset((int) (xOffset + x), (int) (depth + yOffset), (int) (zOffset + z));
box.setUpCulling(32, bufferCenterBlockPos);
box.setAdjData(adjData);
}
private void addBoundingBoxToBuffer(BufferBuilder buffer, AxisAlignedBB bb, Color c)
private void addBoundingBoxToBuffer(BufferBuilder buffer, Box box)
{
// top (facing up)
addPosAndColor(buffer, bb.minX, bb.maxY, bb.minZ, c.getRed(), c.getGreen(), c.getBlue(), c.getAlpha());
addPosAndColor(buffer, bb.minX, bb.maxY, bb.maxZ, c.getRed(), c.getGreen(), c.getBlue(), c.getAlpha());
addPosAndColor(buffer, bb.maxX, bb.maxY, bb.maxZ, c.getRed(), c.getGreen(), c.getBlue(), c.getAlpha());
addPosAndColor(buffer, bb.maxX, bb.maxY, bb.minZ, c.getRed(), c.getGreen(), c.getBlue(), c.getAlpha());
// bottom (facing down)
addPosAndColor(buffer, bb.maxX, bb.minY, bb.minZ, c.getRed(), c.getGreen(), c.getBlue(), c.getAlpha());
addPosAndColor(buffer, bb.maxX, bb.minY, bb.maxZ, c.getRed(), c.getGreen(), c.getBlue(), c.getAlpha());
addPosAndColor(buffer, bb.minX, bb.minY, bb.maxZ, c.getRed(), c.getGreen(), c.getBlue(), c.getAlpha());
addPosAndColor(buffer, bb.minX, bb.minY, bb.minZ, c.getRed(), c.getGreen(), c.getBlue(), c.getAlpha());
// south (facing -Z)
addPosAndColor(buffer, bb.maxX, bb.minY, bb.maxZ, c.getRed(), c.getGreen(), c.getBlue(), c.getAlpha());
addPosAndColor(buffer, bb.maxX, bb.maxY, bb.maxZ, c.getRed(), c.getGreen(), c.getBlue(), c.getAlpha());
addPosAndColor(buffer, bb.minX, bb.maxY, bb.maxZ, c.getRed(), c.getGreen(), c.getBlue(), c.getAlpha());
addPosAndColor(buffer, bb.minX, bb.minY, bb.maxZ, c.getRed(), c.getGreen(), c.getBlue(), c.getAlpha());
// north (facing +Z)
addPosAndColor(buffer, bb.minX, bb.minY, bb.minZ, c.getRed(), c.getGreen(), c.getBlue(), c.getAlpha());
addPosAndColor(buffer, bb.minX, bb.maxY, bb.minZ, c.getRed(), c.getGreen(), c.getBlue(), c.getAlpha());
addPosAndColor(buffer, bb.maxX, bb.maxY, bb.minZ, c.getRed(), c.getGreen(), c.getBlue(), c.getAlpha());
addPosAndColor(buffer, bb.maxX, bb.minY, bb.minZ, c.getRed(), c.getGreen(), c.getBlue(), c.getAlpha());
// west (facing -X)
addPosAndColor(buffer, bb.minX, bb.minY, bb.minZ, c.getRed(), c.getGreen(), c.getBlue(), c.getAlpha());
addPosAndColor(buffer, bb.minX, bb.minY, bb.maxZ, c.getRed(), c.getGreen(), c.getBlue(), c.getAlpha());
addPosAndColor(buffer, bb.minX, bb.maxY, bb.maxZ, c.getRed(), c.getGreen(), c.getBlue(), c.getAlpha());
addPosAndColor(buffer, bb.minX, bb.maxY, bb.minZ, c.getRed(), c.getGreen(), c.getBlue(), c.getAlpha());
// east (facing +X)
addPosAndColor(buffer, bb.maxX, bb.maxY, bb.minZ, c.getRed(), c.getGreen(), c.getBlue(), c.getAlpha());
addPosAndColor(buffer, bb.maxX, bb.maxY, bb.maxZ, c.getRed(), c.getGreen(), c.getBlue(), c.getAlpha());
addPosAndColor(buffer, bb.maxX, bb.minY, bb.maxZ, c.getRed(), c.getGreen(), c.getBlue(), c.getAlpha());
addPosAndColor(buffer, bb.maxX, bb.minY, bb.minZ, c.getRed(), c.getGreen(), c.getBlue(), c.getAlpha());
for (Direction direction : Box.DIRECTIONS)
{
// TODO what does adjacentIndex mean?
int adjIndex = 0;
while (box.shouldRenderFace(direction, adjIndex))
{
for (int vertexIndex = 0; vertexIndex < 4; vertexIndex++)
{
addPosAndColor(buffer,
box.getX(direction, vertexIndex),
box.getY(direction, vertexIndex, adjIndex),
box.getZ(direction, vertexIndex),
box.getColor(direction));
}
adjIndex++;
}
}
}
@SuppressWarnings("unused")
private void addBoundingBoxToBuffer(BufferBuilder buffer, AxisAlignedBB bb, Color[] c)
{
// top (facing up)
addPosAndColor(buffer, bb.minX, bb.maxY, bb.minZ, c[ColorDirection.TOP.value].getRed(), c[ColorDirection.TOP.value].getGreen(), c[ColorDirection.TOP.value].getBlue(), c[ColorDirection.TOP.value].getAlpha());
addPosAndColor(buffer, bb.minX, bb.maxY, bb.maxZ, c[ColorDirection.TOP.value].getRed(), c[ColorDirection.TOP.value].getGreen(), c[ColorDirection.TOP.value].getBlue(), c[ColorDirection.TOP.value].getAlpha());
addPosAndColor(buffer, bb.maxX, bb.maxY, bb.maxZ, c[ColorDirection.TOP.value].getRed(), c[ColorDirection.TOP.value].getGreen(), c[ColorDirection.TOP.value].getBlue(), c[ColorDirection.TOP.value].getAlpha());
addPosAndColor(buffer, bb.maxX, bb.maxY, bb.minZ, c[ColorDirection.TOP.value].getRed(), c[ColorDirection.TOP.value].getGreen(), c[ColorDirection.TOP.value].getBlue(), c[ColorDirection.TOP.value].getAlpha());
// bottom (facing down)
addPosAndColor(buffer, bb.maxX, bb.minY, bb.minZ, c[ColorDirection.BOTTOM.value].getRed(), c[ColorDirection.BOTTOM.value].getGreen(), c[ColorDirection.BOTTOM.value].getBlue(), c[ColorDirection.BOTTOM.value].getAlpha());
addPosAndColor(buffer, bb.maxX, bb.minY, bb.maxZ, c[ColorDirection.BOTTOM.value].getRed(), c[ColorDirection.BOTTOM.value].getGreen(), c[ColorDirection.BOTTOM.value].getBlue(), c[ColorDirection.BOTTOM.value].getAlpha());
addPosAndColor(buffer, bb.minX, bb.minY, bb.maxZ, c[ColorDirection.BOTTOM.value].getRed(), c[ColorDirection.BOTTOM.value].getGreen(), c[ColorDirection.BOTTOM.value].getBlue(), c[ColorDirection.BOTTOM.value].getAlpha());
addPosAndColor(buffer, bb.minX, bb.minY, bb.minZ, c[ColorDirection.BOTTOM.value].getRed(), c[ColorDirection.BOTTOM.value].getGreen(), c[ColorDirection.BOTTOM.value].getBlue(), c[ColorDirection.BOTTOM.value].getAlpha());
// south (facing -Z)
addPosAndColor(buffer, bb.maxX, bb.minY, bb.maxZ, c[ColorDirection.SOUTH.value].getRed(), c[ColorDirection.SOUTH.value].getGreen(), c[ColorDirection.SOUTH.value].getBlue(), c[ColorDirection.SOUTH.value].getAlpha());
addPosAndColor(buffer, bb.maxX, bb.maxY, bb.maxZ, c[ColorDirection.SOUTH.value].getRed(), c[ColorDirection.SOUTH.value].getGreen(), c[ColorDirection.SOUTH.value].getBlue(), c[ColorDirection.SOUTH.value].getAlpha());
addPosAndColor(buffer, bb.minX, bb.maxY, bb.maxZ, c[ColorDirection.SOUTH.value].getRed(), c[ColorDirection.SOUTH.value].getGreen(), c[ColorDirection.SOUTH.value].getBlue(), c[ColorDirection.SOUTH.value].getAlpha());
addPosAndColor(buffer, bb.minX, bb.minY, bb.maxZ, c[ColorDirection.SOUTH.value].getRed(), c[ColorDirection.SOUTH.value].getGreen(), c[ColorDirection.SOUTH.value].getBlue(), c[ColorDirection.SOUTH.value].getAlpha());
// north (facing +Z)
addPosAndColor(buffer, bb.minX, bb.minY, bb.minZ, c[ColorDirection.NORTH.value].getRed(), c[ColorDirection.NORTH.value].getGreen(), c[ColorDirection.NORTH.value].getBlue(), c[ColorDirection.NORTH.value].getAlpha());
addPosAndColor(buffer, bb.minX, bb.maxY, bb.minZ, c[ColorDirection.NORTH.value].getRed(), c[ColorDirection.NORTH.value].getGreen(), c[ColorDirection.NORTH.value].getBlue(), c[ColorDirection.NORTH.value].getAlpha());
addPosAndColor(buffer, bb.maxX, bb.maxY, bb.minZ, c[ColorDirection.NORTH.value].getRed(), c[ColorDirection.NORTH.value].getGreen(), c[ColorDirection.NORTH.value].getBlue(), c[ColorDirection.NORTH.value].getAlpha());
addPosAndColor(buffer, bb.maxX, bb.minY, bb.minZ, c[ColorDirection.NORTH.value].getRed(), c[ColorDirection.NORTH.value].getGreen(), c[ColorDirection.NORTH.value].getBlue(), c[ColorDirection.NORTH.value].getAlpha());
// west (facing -X)
addPosAndColor(buffer, bb.minX, bb.minY, bb.minZ, c[ColorDirection.WEST.value].getRed(), c[ColorDirection.WEST.value].getGreen(), c[ColorDirection.WEST.value].getBlue(), c[ColorDirection.WEST.value].getAlpha());
addPosAndColor(buffer, bb.minX, bb.minY, bb.maxZ, c[ColorDirection.WEST.value].getRed(), c[ColorDirection.WEST.value].getGreen(), c[ColorDirection.WEST.value].getBlue(), c[ColorDirection.WEST.value].getAlpha());
addPosAndColor(buffer, bb.minX, bb.maxY, bb.maxZ, c[ColorDirection.WEST.value].getRed(), c[ColorDirection.WEST.value].getGreen(), c[ColorDirection.WEST.value].getBlue(), c[ColorDirection.WEST.value].getAlpha());
addPosAndColor(buffer, bb.minX, bb.maxY, bb.minZ, c[ColorDirection.WEST.value].getRed(), c[ColorDirection.WEST.value].getGreen(), c[ColorDirection.WEST.value].getBlue(), c[ColorDirection.WEST.value].getAlpha());
// east (facing +X)
addPosAndColor(buffer, bb.maxX, bb.maxY, bb.minZ, c[ColorDirection.EAST.value].getRed(), c[ColorDirection.EAST.value].getGreen(), c[ColorDirection.EAST.value].getBlue(), c[ColorDirection.EAST.value].getAlpha());
addPosAndColor(buffer, bb.maxX, bb.maxY, bb.maxZ, c[ColorDirection.EAST.value].getRed(), c[ColorDirection.EAST.value].getGreen(), c[ColorDirection.EAST.value].getBlue(), c[ColorDirection.EAST.value].getAlpha());
addPosAndColor(buffer, bb.maxX, bb.minY, bb.maxZ, c[ColorDirection.EAST.value].getRed(), c[ColorDirection.EAST.value].getGreen(), c[ColorDirection.EAST.value].getBlue(), c[ColorDirection.EAST.value].getAlpha());
addPosAndColor(buffer, bb.maxX, bb.minY, bb.minZ, c[ColorDirection.EAST.value].getRed(), c[ColorDirection.EAST.value].getGreen(), c[ColorDirection.EAST.value].getBlue(), c[ColorDirection.EAST.value].getAlpha());
}
@Override
public int getBufferMemoryForSingleLod(LodDetail detail)
public int getBufferMemoryForSingleNode(int maxVerticalData)
{
// (sidesOnACube * pointsInASquare * (positionPoints + colorPoints))) * howManyPointsPerLodChunk
return (6 * 4 * (3 + 4)) * detail.dataPointLengthCount * detail.dataPointLengthCount;
// TODO, what do these magic numbers mean
return 2 * 4 * (3 + 4) + 4 * 4 * Math.max((maxVerticalData+1)/2,1) * (3 + 4);
}
}
@@ -17,35 +17,37 @@
*/
package com.seibel.lod.builders.lodTemplates;
import com.seibel.lod.enums.LodDetail;
import com.seibel.lod.objects.LodChunk;
import com.seibel.lod.objects.LodDimension;
import java.util.Map;
import com.seibel.lod.enums.DebugMode;
import com.seibel.lod.proxy.ClientProxy;
import net.minecraft.client.renderer.BufferBuilder;
import net.minecraft.client.renderer.texture.NativeImage;
import net.minecraft.util.Direction;
import net.minecraft.util.math.BlockPos;
/**
* TODO DynamicLodTemplate
* Chunks smoothly transition between
* each other, unless a neighboring chunk
* is at a significantly different height.
*
*
* @author James Seibel
* @version 06-16-2021
*/
public class DynamicLodTemplate extends AbstractLodTemplate
{
@Override
public void addLodToBuffer(BufferBuilder buffer,
LodDimension lodDim, LodChunk lod,
double xOffset, double yOffset, double zOffset,
boolean debugging)
public void addLodToBuffer(BufferBuilder buffer, BlockPos bufferCenterBlockPos, long data, Map<Direction, long[]> adjData,
byte detailLevel, int posX, int posZ, Box box, DebugMode debugging, NativeImage lightMap)
{
System.err.println("DynamicLodTemplate not implemented!");
ClientProxy.LOGGER.error(DynamicLodTemplate.class.getSimpleName() + " is not implemented!");
}
@Override
public int getBufferMemoryForSingleLod(LodDetail detail) {
// TODO Auto-generated method stub
public int getBufferMemoryForSingleNode(int maxVerticalData)
{
return 0;
}
}
@@ -17,33 +17,35 @@
*/
package com.seibel.lod.builders.lodTemplates;
import com.seibel.lod.enums.LodDetail;
import com.seibel.lod.objects.LodChunk;
import com.seibel.lod.objects.LodDimension;
import java.util.Map;
import com.seibel.lod.enums.DebugMode;
import com.seibel.lod.proxy.ClientProxy;
import net.minecraft.client.renderer.BufferBuilder;
import net.minecraft.client.renderer.texture.NativeImage;
import net.minecraft.util.Direction;
import net.minecraft.util.math.BlockPos;
/**
* TODO #21 TriangularLodTemplate
* Builds each LOD chunk as a singular rectangular prism.
*
*
* @author James Seibel
* @version 06-16-2021
*/
public class TriangularLodTemplate extends AbstractLodTemplate
{
@Override
public void addLodToBuffer(BufferBuilder buffer,
LodDimension lodDim, LodChunk lod,
double xOffset, double yOffset, double zOffset,
boolean debugging)
public void addLodToBuffer(BufferBuilder buffer, BlockPos bufferCenterBlockPos, long data, Map<Direction, long[]> adjData,
byte detailLevel, int posX, int posZ, Box box, DebugMode debugging, NativeImage lightMap)
{
System.err.println("DynamicLodTemplate not implemented!");
ClientProxy.LOGGER.error(DynamicLodTemplate.class.getSimpleName() + " is not implemented!");
}
@Override
public int getBufferMemoryForSingleLod(LodDetail detail) {
// TODO Auto-generated method stub
public int getBufferMemoryForSingleNode(int maxVerticalData)
{
return 0;
}
}
@@ -26,24 +26,22 @@ import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.function.Supplier;
import com.seibel.lod.builders.LodBufferBuilder;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import com.seibel.lod.builders.LodBuilder;
import com.seibel.lod.builders.LodBuilderConfig;
import com.seibel.lod.builders.LodChunkBuilder;
import com.seibel.lod.config.LodConfig;
import com.seibel.lod.enums.DistanceGenerationMode;
import com.seibel.lod.handlers.LodConfig;
import com.seibel.lod.objects.LodChunk;
import com.seibel.lod.objects.LodDimension;
import com.seibel.lod.objects.LodRegion;
import com.seibel.lod.proxy.ClientProxy;
import com.seibel.lod.render.LodRenderer;
import com.seibel.lod.util.LodThreadFactory;
import com.seibel.lod.util.LodUtil;
import net.minecraft.block.Block;
import net.minecraft.block.BlockState;
import net.minecraft.util.WeightedList.Entry;
import net.minecraft.util.math.ChunkPos;
import net.minecraft.util.palette.UpgradeData;
import net.minecraft.util.registry.Registry;
import net.minecraft.world.biome.Biome;
import net.minecraft.world.biome.BiomeContainer;
import net.minecraft.world.chunk.ChunkPrimer;
import net.minecraft.world.chunk.ChunkStatus;
import net.minecraft.world.chunk.IChunk;
@@ -68,140 +66,176 @@ import net.minecraftforge.common.WorldWorkerManager.IWorker;
/**
* This is used to generate a LodChunk at a given ChunkPos.
*
*
* @author James Seibel
* @version 7-5-2021
* @version 9-7-2021
*/
public class LodChunkGenWorker implements IWorker
public class LodNodeGenWorker implements IWorker
{
public static final ExecutorService genThreads = Executors.newFixedThreadPool(LodConfig.CLIENT.numberOfWorldGenerationThreads.get());
private boolean threadStarted = false;
private LodChunkGenThread thread;
/** If a configured feature fails for whatever reason,
* add it to this list, this is to hopefully remove any
* features that could cause issues down the line. */
private static ConcurrentHashMap<Integer, ConfiguredFeature<?, ?>> configuredFeaturesToAvoid = new ConcurrentHashMap<>();
public LodChunkGenWorker(ChunkPos newPos, LodRenderer newLodRenderer,
LodChunkBuilder newLodBuilder, LodBufferBuilder newLodBufferBuilder,
LodDimension newLodDimension, ServerWorld newServerWorld,
BiomeContainer newBiomeContainer)
{
if (newServerWorld == null)
throw new IllegalArgumentException("LodChunkGenWorker must have a non-null ServerWorld");
thread = new LodChunkGenThread(newPos, newLodRenderer,
newLodBuilder, newLodBufferBuilder,
newLodDimension, newServerWorld);
}
@Override
public boolean doWork()
{
if (!threadStarted)
{
thread.lodBufferBuilder.numberOfChunksWaitingToGenerate.addAndGet(-1);
if (LodConfig.CLIENT.distanceGenerationMode.get() == DistanceGenerationMode.SERVER)
public static ExecutorService genThreads = Executors.newFixedThreadPool(LodConfig.CLIENT.threading.numberOfWorldGenerationThreads.get(), new ThreadFactoryBuilder().setNameFormat("Gen-Worker-Thread-%d").build());
private boolean threadStarted = false;
private LodChunkGenThread thread;
/** If a configured feature fails for whatever reason,
* add it to this list, this is to hopefully remove any
* features that could cause issues down the line. */
private static ConcurrentHashMap<Integer, ConfiguredFeature<?, ?>> configuredFeaturesToAvoid = new ConcurrentHashMap<>();
public LodNodeGenWorker(ChunkPos newPos, DistanceGenerationMode newGenerationMode,
LodBuilder newLodBuilder,
LodDimension newLodDimension, ServerWorld newServerWorld)
{
// just a few sanity checks
if (newPos == null)
throw new IllegalArgumentException("LodChunkGenWorker must have a non-null ChunkPos");
if (newLodBuilder == null)
throw new IllegalArgumentException("LodChunkGenThread requires a non-null LodChunkBuilder");
if (newLodDimension == null)
throw new IllegalArgumentException("LodChunkGenThread requires a non-null LodDimension");
if (newServerWorld == null)
throw new IllegalArgumentException("LodChunkGenThread requires a non-null ServerWorld");
thread = new LodChunkGenThread(newPos, newGenerationMode,
newLodBuilder,
newLodDimension, newServerWorld);
}
@Override
public boolean doWork()
{
if (!threadStarted)
{
if (LodConfig.CLIENT.worldGenerator.distanceGenerationMode.get() == DistanceGenerationMode.SERVER)
{
// if we are using SERVER generation that has to be done
// synchronously to prevent crashing and harmful
// interactions with the normal world generator
thread.run();
// if we are using SERVER generation that has to be done
// synchronously to prevent crashing and harmful
// interactions with the normal world generator
thread.run();
}
else
{
// Every other method can
// be done asynchronously
genThreads.execute(thread);
}
threadStarted = true;
// useful for debugging
else
{
// Every other method can
// be done asynchronously
Thread newThread = new Thread(thread);
newThread.setPriority(5);
genThreads.execute(newThread);
}
threadStarted = true;
// useful for debugging
// ClientProxy.LOGGER.info(thread.lodDim.getNumberOfLods());
}
return false;
}
@Override
public boolean hasWork()
{
return !threadStarted;
}
private class LodChunkGenThread implements Runnable
{
public final ServerWorld serverWorld;
public final LodDimension lodDim;
public final LodChunkBuilder lodChunkBuilder;
public final LodRenderer lodRenderer;
private LodBufferBuilder lodBufferBuilder;
private ChunkPos pos;
public LodChunkGenThread(ChunkPos newPos, LodRenderer newLodRenderer,
LodChunkBuilder newLodBuilder, LodBufferBuilder newLodBufferBuilder,
LodDimension newLodDimension, ServerWorld newServerWorld)
{
pos = newPos;
lodRenderer = newLodRenderer;
lodChunkBuilder = newLodBuilder;
lodBufferBuilder = newLodBufferBuilder;
lodDim = newLodDimension;
serverWorld = newServerWorld;
}
// ClientProxy.LOGGER.info(genThreads.toString());
}
return false;
}
@Override
public boolean hasWork()
{
return !threadStarted;
}
private class LodChunkGenThread implements Runnable
{
public final ServerWorld serverWorld;
public final LodDimension lodDim;
public final DistanceGenerationMode generationMode;
public final LodBuilder lodBuilder;
private ChunkPos pos;
public LodChunkGenThread(ChunkPos newPos, DistanceGenerationMode newGenerationMode,
LodBuilder newLodBuilder,
LodDimension newLodDimension, ServerWorld newServerWorld)
{
pos = newPos;
generationMode = newGenerationMode;
lodBuilder = newLodBuilder;
lodDim = newLodDimension;
serverWorld = newServerWorld;
}
@Override
public void run()
{
// only generate LodChunks if they can
// be added to the current LodDimension
if (lodDim.regionIsInRange(pos.x / LodRegion.SIZE, pos.z / LodRegion.SIZE))
try
{
// long startTime = System.currentTimeMillis();
// only generate LodChunks if they can
// be added to the current LodDimension
switch(LodConfig.CLIENT.distanceGenerationMode.get())
/**TODO i must disable this if, i will find a way to replace it*/
if (lodDim.regionIsInRange(pos.x / LodUtil.REGION_WIDTH_IN_CHUNKS, pos.z / LodUtil.REGION_WIDTH_IN_CHUNKS))
{
case BIOME_ONLY:
case BIOME_ONLY_SIMULATE_HEIGHT:
// fastest
generateUsingBiomesOnly();
break;
case SURFACE:
// faster
generateUsingSurface();
break;
case FEATURES:
// fast
generateUsingFeatures();
break;
case SERVER:
// very slow
generateWithServer();
break;
}
// long startTime = System.currentTimeMillis();
switch(generationMode)
{
case NONE:
// don't generate
break;
case BIOME_ONLY:
case BIOME_ONLY_SIMULATE_HEIGHT:
// fastest
generateUsingBiomesOnly();
break;
case SURFACE:
// faster
generateUsingSurface();
break;
case FEATURES:
// fast
generateUsingFeatures();
break;
case SERVER:
// very slow
generateWithServer();
break;
}
//lodRenderer.regenerateLODsNextFrame();
/*
boolean dataExistence = lodDim.doesDataExist(new LevelPos((byte) 3, pos.x, pos.z));
if (dataExistence)
ClientProxy.LOGGER.info(pos.x + " " + pos.z + " Success!");
else
ClientProxy.LOGGER.info(pos.x + " " + pos.z);
*/
// shows the pool size, active threads, queued tasks and completed tasks
// ClientProxy.LOGGER.info(genThreads.toString());
// long endTime = System.currentTimeMillis();
// System.out.println(endTime - startTime);
}// if in range
}
catch (Exception e)
{
ClientProxy.LOGGER.error(LodChunkGenThread.class.getSimpleName() + ": ran into an error: " + e.getMessage());
e.printStackTrace();
}
finally
{
// decrement how many threads are running
LodWorldGenerator.INSTANCE.numberOfChunksWaitingToGenerate.addAndGet(-1);
lodRenderer.regenerateLODsNextFrame();
// if (lodDim.getLodFromCoordinates(pos.x, pos.z) != null)
// ClientProxy.LOGGER.info(pos.x + " " + pos.z + " Success!");
// else
// ClientProxy.LOGGER.info(pos.x + " " + pos.z);
// long endTime = System.currentTimeMillis();
// System.out.println(endTime - startTime);
}// if in range
// this position is no longer being generated
LodWorldGenerator.INSTANCE.positionsWaitingToBeGenerated.remove(pos);
}
}// run
@@ -219,34 +253,33 @@ public class LodChunkGenWorker implements IWorker
ServerChunkProvider chunkSource = serverWorld.getChunkSource();
ChunkGenerator chunkGen = chunkSource.generator;
// generate the terrain (this is thread safe)
ChunkStatus.EMPTY.generate(serverWorld, chunkGen, serverWorld.getStructureManager(), (ServerWorldLightManager) serverWorld.getLightEngine(), null, chunkList);
// override the chunk status so we can run the next generator stage
chunk.setStatus(ChunkStatus.STRUCTURE_REFERENCES);
ChunkStatus.BIOMES.generate(serverWorld, chunkGen, serverWorld.getStructureManager(), (ServerWorldLightManager) serverWorld.getLightEngine(), null, chunkList);
chunkGen.createBiomes(serverWorld.registryAccess().registryOrThrow(Registry.BIOME_REGISTRY), chunk);
chunk.setStatus(ChunkStatus.STRUCTURE_REFERENCES);
// generate fake height data for this LOD
int seaLevel = serverWorld.getSeaLevel();
boolean simulateHeight = LodConfig.CLIENT.distanceGenerationMode.get() == DistanceGenerationMode.BIOME_ONLY_SIMULATE_HEIGHT;
boolean simulateHeight = generationMode == DistanceGenerationMode.BIOME_ONLY_SIMULATE_HEIGHT;
boolean inTheEnd = false;
// add fake heightmap data so our LODs aren't at height 0
Heightmap heightmap = new Heightmap(chunk, LodChunk.DEFAULT_HEIGHTMAP);
for(int x = 0; x < LodChunk.WIDTH && !inTheEnd; x++)
Heightmap heightmap = new Heightmap(chunk, LodUtil.DEFAULT_HEIGHTMAP);
for(int x = 0; x < LodUtil.CHUNK_WIDTH && !inTheEnd; x++)
{
for(int z = 0; z < LodChunk.WIDTH && !inTheEnd; z++)
for(int z = 0; z < LodUtil.CHUNK_WIDTH && !inTheEnd; z++)
{
if (simulateHeight)
{
// TODO use the biomes around each block to smooth out the transition
// these heights are of course aren't super accurate,
// they are just to simulate height data where there isn't any
switch(chunk.getBiomes().getNoiseBiome(x, seaLevel, z).getBiomeCategory())
switch(chunk.getBiomes().getNoiseBiome(x >> 2, seaLevel >> 2, z >> 2).getBiomeCategory())
{
case NETHER:
heightmap.setHeight(x, z, serverWorld.getHeight() / 2);
@@ -277,14 +310,14 @@ public class LodChunkGenWorker implements IWorker
inTheEnd = true;
break;
// DESERT
// FOREST
// ICY
// MUSHROOM
// SAVANNA
// SWAMP
// TAIGA
// PLAINS
// DESERT
// FOREST
// ICY
// MUSHROOM
// SAVANNA
// SWAMP
// TAIGA
// PLAINS
default:
heightmap.setHeight(x, z, seaLevel + 10);
break;
@@ -299,22 +332,25 @@ public class LodChunkGenWorker implements IWorker
}// z
}// x
chunk.setHeightmap(LodChunk.DEFAULT_HEIGHTMAP, heightmap.getRawData());
chunk.setHeightmap(LodUtil.DEFAULT_HEIGHTMAP, heightmap.getRawData());
LodChunk lod;
if (!inTheEnd)
{
lod = lodChunkBuilder.generateLodFromChunk(chunk, new LodBuilderConfig(true, true, false));
lodBuilder.generateLodNodeFromChunk(lodDim, chunk, new LodBuilderConfig(true, true, false));
}
else
{
// if we are in the end, don't generate any chunks.
// Since we don't know where the islands are, everything
// generates the same and it looks really bad.
lod = new LodChunk(chunk.getPos().x, chunk.getPos().z);
lodBuilder.generateLodNodeFromChunk(lodDim, chunk, new LodBuilderConfig(true, true, false));
}
lodDim.addLod(lod);
// long startTime = System.currentTimeMillis();
// long endTime = System.currentTimeMillis();
// System.out.println(endTime - startTime);
}
@@ -336,24 +372,25 @@ public class LodChunkGenWorker implements IWorker
ChunkStatus.EMPTY.generate(serverWorld, chunkGen, serverWorld.getStructureManager(), (ServerWorldLightManager) serverWorld.getLightEngine(), null, chunkList);
// override the chunk status so we can run the next generator stage
chunk.setStatus(ChunkStatus.STRUCTURE_REFERENCES);
ChunkStatus.BIOMES.generate(serverWorld, chunkGen, serverWorld.getStructureManager(), (ServerWorldLightManager) serverWorld.getLightEngine(), null, chunkList);
chunkGen.createBiomes(serverWorld.registryAccess().registryOrThrow(Registry.BIOME_REGISTRY), chunk);
ChunkStatus.NOISE.generate(serverWorld, chunkGen, serverWorld.getStructureManager(), (ServerWorldLightManager) serverWorld.getLightEngine(), null, chunkList);
ChunkStatus.SURFACE.generate(serverWorld, chunkGen, serverWorld.getStructureManager(), (ServerWorldLightManager) serverWorld.getLightEngine(), null, chunkList);
// this feature has proved to be thread safe
// this feature has been proven to be thread safe
// so we will add it
IceAndSnowFeature snowFeature = new IceAndSnowFeature(NoFeatureConfig.CODEC);
snowFeature.place(lodServerWorld, chunkGen, serverWorld.random, chunk.getPos().getWorldPosition(), null);
lodBuilder.generateLodNodeFromChunk(lodDim, chunk, new LodBuilderConfig(DistanceGenerationMode.SURFACE));
LodChunk lod = lodChunkBuilder.generateLodFromChunk(chunk);
lodDim.addLod(lod);
/**TODO if we want to use Biome utils and terrain utils for overworld
* lodBuilder.generateLodNodeFromChunk(lodDim, pos ,detailLevel, serverWorld.getSeed());*/
}
/**
* takes about 15 - 20 ms
*
*
* Causes concurrentModification Exceptions,
* which could cause instability or world generation bugs
*/
@@ -372,18 +409,18 @@ public class LodChunkGenWorker implements IWorker
ChunkStatus.EMPTY.generate(serverWorld, chunkGen, serverWorld.getStructureManager(), (ServerWorldLightManager) serverWorld.getLightEngine(), null, chunkList);
// override the chunk status so we can run the next generator stage
chunk.setStatus(ChunkStatus.STRUCTURE_REFERENCES);
ChunkStatus.BIOMES.generate(serverWorld, chunkGen, serverWorld.getStructureManager(), (ServerWorldLightManager) serverWorld.getLightEngine(), null, chunkList);
chunkGen.createBiomes(serverWorld.registryAccess().registryOrThrow(Registry.BIOME_REGISTRY), chunk);
ChunkStatus.NOISE.generate(serverWorld, chunkGen, serverWorld.getStructureManager(), (ServerWorldLightManager) serverWorld.getLightEngine(), null, chunkList);
ChunkStatus.SURFACE.generate(serverWorld, chunkGen, serverWorld.getStructureManager(), (ServerWorldLightManager) serverWorld.getLightEngine(), null, chunkList);
// get all the biomes in the chunk
HashSet<Biome> biomes = new HashSet<>();
for (int x = 0; x < LodChunk.WIDTH; x++)
for (int x = 0; x < LodUtil.CHUNK_WIDTH; x++)
{
for (int z = 0; z < LodChunk.WIDTH; z++)
for (int z = 0; z < LodUtil.CHUNK_WIDTH; z++)
{
Biome biome = chunk.getBiomes().getNoiseBiome(x, serverWorld.getSeaLevel(), z);
Biome biome = chunk.getBiomes().getNoiseBiome(x >> 2, serverWorld.getSeaLevel() >> 2, z >> 2);
// Issue #35
// For some reason Jungle biomes cause incredible lag
@@ -400,7 +437,7 @@ public class LodChunkGenWorker implements IWorker
}
}
boolean allowUnstableFeatures = LodConfig.CLIENT.allowUnstableFeatureGeneration.get();
boolean allowUnstableFeatures = LodConfig.CLIENT.worldGenerator.allowUnstableFeatureGeneration.get();
// generate all the features related to this chunk.
// this may or may not be thread safe
@@ -415,7 +452,7 @@ public class LodChunkGenWorker implements IWorker
ConfiguredFeature<?, ?> configuredFeature = featureSupplier.get();
if (!allowUnstableFeatures &&
configuredFeaturesToAvoid.containsKey(configuredFeature.hashCode()))
configuredFeaturesToAvoid.containsKey(configuredFeature.hashCode()))
continue;
@@ -435,7 +472,7 @@ public class LodChunkGenWorker implements IWorker
// path was blocked since I can't clone lambda methods.
// I tried using a deep cloning library and discovered
// the problem there.
// ( https://github.com/kostaskougios/cloning
// ( https://github.com/kostaskougios/cloning
// and
// https://github.com/EsotericSoftware/kryo )
@@ -457,11 +494,7 @@ public class LodChunkGenWorker implements IWorker
{
// I'm not sure what happened, print to the log
System.out.println();
System.out.println();
e.printStackTrace();
System.out.println();
System.out.println();
if (!allowUnstableFeatures)
configuredFeaturesToAvoid.put(configuredFeature.hashCode(), configuredFeature);
@@ -472,23 +505,22 @@ public class LodChunkGenWorker implements IWorker
}
// generate a Lod like normal
LodChunk lod = lodChunkBuilder.generateLodFromChunk(chunk);
lodDim.addLod(lod);
lodBuilder.generateLodNodeFromChunk(lodDim, chunk, new LodBuilderConfig(DistanceGenerationMode.FEATURES));
}
/**
* on pre generated chunks 0 - 1 ms
* on un generated chunks 0 - 50 ms
* on un generated chunks 0 - 50 ms
* with the median seeming to hover around 15 - 30 ms
* and outliers in the 100 - 200 ms range
*
*
* Note this should not be multithreaded and does cause server/simulation lag
* (Higher lag for generating than loading)
*/
private void generateWithServer()
{
lodChunkBuilder.generateLodChunkAsync(serverWorld.getChunk(pos.x, pos.z, ChunkStatus.FEATURES), ClientProxy.getLodWorld(), serverWorld);
lodBuilder.generateLodNodeAsync(serverWorld.getChunk(pos.x, pos.z, ChunkStatus.FEATURES), ClientProxy.getLodWorld(), serverWorld);
}
@@ -543,16 +575,11 @@ public class LodChunkGenWorker implements IWorker
private BlockClusterFeatureConfig cloneBlockClusterFeatureConfig(BlockClusterFeatureConfig config)
{
WeightedBlockStateProvider provider = new WeightedBlockStateProvider();
for(Entry<BlockState> state : ((WeightedBlockStateProvider) config.stateProvider).weightedList.entries)
provider.weightedList.entries.add(state);
provider.weightedList.entries.addAll(((WeightedBlockStateProvider) config.stateProvider).weightedList.entries);
HashSet<Block> whitelist = new HashSet<>();
for(Block block : config.whitelist)
whitelist.add(block);
HashSet<Block> whitelist = new HashSet<>(config.whitelist);
HashSet<BlockState> blacklist = new HashSet<>();
for(BlockState state : config.blacklist)
blacklist.add(state);
HashSet<BlockState> blacklist = new HashSet<>(config.blacklist);
BlockClusterFeatureConfig.Builder builder = new BlockClusterFeatureConfig.Builder(provider, config.blockPlacer);
@@ -570,16 +597,38 @@ public class LodChunkGenWorker implements IWorker
return builder.build();
}
}
/*
* performance/generation tests related to
* serverWorld.getChunk(x, z, ChunkStatus. *** )
}
/**
* Stops the current genThreads if they are running
* and then recreates the Executer service. <br><br>
*
* This is done to clear any outstanding tasks
* that may exist after the player leaves their current world.
* If this isn't done unfinished tasks may be left in the queue
* preventing new LodChunks form being generated.
*/
public static void restartExecuterService()
{
if (genThreads != null && !genThreads.isShutdown())
{
genThreads.shutdownNow();
}
genThreads = Executors.newFixedThreadPool(LodConfig.CLIENT.threading.numberOfWorldGenerationThreads.get(), new LodThreadFactory(LodNodeGenWorker.class.getSimpleName()));
}
/*
* performance/generation tests related to
* serverWorld.getChunk(x, z, ChunkStatus. *** )
true/false is whether they generated blocks or not
the time is how long it took to generate
ChunkStatus.EMPTY 0 - 1 ms false (empty, what did you expect? :P)
ChunkStatus.STRUCTURE_REFERENCES 1 - 2 ms false (no height, only generates some chunks)
ChunkStatus.BIOMES 1 - 10 ms false (no height)
@@ -592,11 +641,11 @@ public class LodChunkGenWorker implements IWorker
ChunkStatus.LIGHT 20 - 40 ms true
ChunkStatus.FULL 30 - 50 ms true
ChunkStatus.SPAWN 50 - 80 ms true
At this point I would suggest using FEATURES, as it generates snow and trees
(and any other object that is needed to make biomes distinct)
Otherwise if snow/trees aren't necessary SURFACE is the next fastest (although not by much)
*/
*/
}
@@ -23,7 +23,7 @@ import java.util.Random;
import java.util.function.Predicate;
import java.util.stream.Stream;
import com.seibel.lod.objects.LodChunk;
import com.seibel.lod.util.LodUtil;
import net.minecraft.block.Block;
import net.minecraft.block.BlockState;
@@ -65,257 +65,266 @@ import net.minecraft.world.storage.IWorldInfo;
* This allows us to keep each LodChunk generation independent
* of the actual ServerWorld, allowing us
* to multithread generation.
*
*
* @author James Seibel
* @version 7-4-2021
* @version 7-26-2021
*/
public class LodServerWorld implements ISeedReader {
public class LodServerWorld implements ISeedReader
{
public HashMap<Heightmap.Type, Heightmap> heightmaps = new HashMap<>();
public IChunk chunk;
public ServerWorld serverWorld;
public LodServerWorld(ServerWorld newServerWorld, IChunk newChunk)
{
chunk = newChunk;
serverWorld = newServerWorld;
}
@Override
public int getHeight(Type heightmapType, int x, int z)
{
// make sure the block position is set relative to the chunk
x = x % LodChunk.WIDTH;
x = x % LodUtil.CHUNK_WIDTH;
x = (x < 0) ? x + 16 : x;
z = z % LodChunk.WIDTH;
z = z % LodUtil.CHUNK_WIDTH;
z = (z < 0) ? z + 16 : z;
return chunk.getOrCreateHeightmapUnprimed(LodChunk.DEFAULT_HEIGHTMAP).getFirstAvailable(x, z);
return chunk.getOrCreateHeightmapUnprimed(LodUtil.DEFAULT_HEIGHTMAP).getFirstAvailable(x, z);
}
@Override
public Biome getBiome(BlockPos pos)
{
return chunk.getBiomes().getNoiseBiome(pos.getX(), pos.getY(), pos.getZ());
return chunk.getBiomes().getNoiseBiome(pos.getX() >> 2, pos.getY() >> 2, pos.getZ() >> 2);
}
@Override
public boolean setBlock(BlockPos pos, BlockState state, int flags, int recursionLeft)
{
return chunk.setBlockState(pos, state, false) == state;
}
@Override
public BlockState getBlockState(BlockPos pos)
{
return chunk.getBlockState(pos);
}
@Override
public FluidState getFluidState(BlockPos pos)
{
return chunk.getFluidState(pos);
}
@Override
public boolean isStateAtPosition(BlockPos pos, Predicate<BlockState> state)
{
return state.test(chunk.getBlockState(pos));
}
@Override
public ITickList<Block> getBlockTicks()
{
return EmptyTickList.empty();
}
@Override
public IChunk getChunk(int x, int z, ChunkStatus requiredStatus, boolean nonnull)
{
return chunk;
}
@Override
public Stream<? extends StructureStart<?>> startsForFeature(SectionPos p_241827_1_, Structure<?> p_241827_2_)
{
return serverWorld.startsForFeature(p_241827_1_, p_241827_2_);
}
@Override
public ITickList<Fluid> getLiquidTicks()
{
return EmptyTickList.empty();
}
@Override
public WorldLightManager getLightEngine()
{
return new WorldLightManager(null, false, false);
}
/**
*
* All methods below shouldn't be needed
* and thus have been left unimplemented.
*
*/
@Override
public Random getRandom() {
public long getSeed()
{
return serverWorld.getSeed();
}
@Override
public DynamicRegistries registryAccess()
{
return serverWorld.registryAccess();
}
/**
* All methods below shouldn't be needed
* and thus have been left unimplemented.
*/
@Override
public Random getRandom()
{
throw new UnsupportedOperationException("Not Implemented");
}
@Override
public void playSound(PlayerEntity player, BlockPos pos, SoundEvent soundIn, SoundCategory category, float volume,
float pitch) {
float pitch)
{
throw new UnsupportedOperationException("Not Implemented");
}
@Override
public void addParticle(IParticleData particleData, double x, double y, double z, double xSpeed, double ySpeed,
double zSpeed) {
throw new UnsupportedOperationException("Not Implemented");
}
@Override
public DynamicRegistries registryAccess() {
throw new UnsupportedOperationException("Not Implemented");
}
@Override
public BiomeManager getBiomeManager() {
throw new UnsupportedOperationException("Not Implemented");
}
@Override
public int getSeaLevel() {
throw new UnsupportedOperationException("Not Implemented");
}
@Override
public float getShade(Direction p_230487_1_, boolean p_230487_2_) {
throw new UnsupportedOperationException("Not Implemented");
}
@Override
public WorldBorder getWorldBorder() {
throw new UnsupportedOperationException("Not Implemented");
}
@Override
public boolean removeBlock(BlockPos pos, boolean isMoving) {
throw new UnsupportedOperationException("Not Implemented");
}
@Override
public boolean destroyBlock(BlockPos pos, boolean dropBlock, Entity entity, int recursionLeft) {
throw new UnsupportedOperationException("Not Implemented");
}
@Override
public long getSeed() {
double zSpeed)
{
throw new UnsupportedOperationException("Not Implemented");
}
@Override
public ServerWorld getLevel() {
public BiomeManager getBiomeManager()
{
throw new UnsupportedOperationException("Not Implemented");
}
@Override
public int getSeaLevel()
{
throw new UnsupportedOperationException("Not Implemented");
}
@Override
public float getShade(Direction p_230487_1_, boolean p_230487_2_)
{
throw new UnsupportedOperationException("Not Implemented");
}
@Override
public WorldBorder getWorldBorder()
{
throw new UnsupportedOperationException("Not Implemented");
}
@Override
public boolean removeBlock(BlockPos pos, boolean isMoving)
{
throw new UnsupportedOperationException("Not Implemented");
}
@Override
public boolean destroyBlock(BlockPos pos, boolean dropBlock, Entity entity, int recursionLeft)
{
throw new UnsupportedOperationException("Not Implemented");
}
@Override
public AbstractChunkProvider getChunkSource() {
public ServerWorld getLevel()
{
throw new UnsupportedOperationException("Not Implemented");
}
@Override
public DifficultyInstance getCurrentDifficultyAt(BlockPos arg0) {
public AbstractChunkProvider getChunkSource()
{
throw new UnsupportedOperationException("Not Implemented");
}
@Override
public IWorldInfo getLevelData() {
public DifficultyInstance getCurrentDifficultyAt(BlockPos arg0)
{
throw new UnsupportedOperationException("Not Implemented");
}
@Override
public void levelEvent(PlayerEntity arg0, int arg1, BlockPos arg2, int arg3) {
throw new UnsupportedOperationException("Not Implemented");
}
@Override
public List<Entity> getEntities(Entity arg0, AxisAlignedBB arg1, Predicate<? super Entity> arg2) {
public IWorldInfo getLevelData()
{
throw new UnsupportedOperationException("Not Implemented");
}
@Override
public void levelEvent(PlayerEntity arg0, int arg1, BlockPos arg2, int arg3)
{
throw new UnsupportedOperationException("Not Implemented");
}
@Override
public List<Entity> getEntities(Entity arg0, AxisAlignedBB arg1, Predicate<? super Entity> arg2)
{
throw new UnsupportedOperationException("Not Implemented");
}
@Override
public <T extends Entity> List<T> getEntitiesOfClass(Class<? extends T> arg0, AxisAlignedBB arg1,
Predicate<? super T> arg2) {
Predicate<? super T> arg2)
{
throw new UnsupportedOperationException("Not Implemented");
}
@Override
public List<? extends PlayerEntity> players() {
public List<? extends PlayerEntity> players()
{
throw new UnsupportedOperationException("Not Implemented");
}
@Override
public int getSkyDarken() {
public int getSkyDarken()
{
throw new UnsupportedOperationException("Not Implemented");
}
@Override
public Biome getUncachedNoiseBiome(int p_225604_1_, int p_225604_2_, int p_225604_3_) {
public Biome getUncachedNoiseBiome(int p_225604_1_, int p_225604_2_, int p_225604_3_)
{
throw new UnsupportedOperationException("Not Implemented");
}
@Override
public boolean isClientSide() {
public boolean isClientSide()
{
throw new UnsupportedOperationException("Not Implemented");
}
@Override
public DimensionType dimensionType() {
public DimensionType dimensionType()
{
throw new UnsupportedOperationException("Not Implemented");
}
@Override
public TileEntity getBlockEntity(BlockPos p_175625_1_) {
public TileEntity getBlockEntity(BlockPos p_175625_1_)
{
throw new UnsupportedOperationException("Not Implemented");
}
}
@@ -0,0 +1,190 @@
package com.seibel.lod.builders.worldGeneration;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicInteger;
import com.seibel.lod.builders.LodBuilder;
import com.seibel.lod.config.LodConfig;
import com.seibel.lod.enums.DistanceGenerationMode;
import com.seibel.lod.objects.LodDimension;
import com.seibel.lod.objects.PosToGenerateContainer;
import com.seibel.lod.render.LodRenderer;
import com.seibel.lod.util.DetailDistanceUtil;
import com.seibel.lod.util.LevelPosUtil;
import com.seibel.lod.util.LodThreadFactory;
import com.seibel.lod.util.LodUtil;
import com.seibel.lod.wrappers.MinecraftWrapper;
import net.minecraft.util.math.ChunkPos;
import net.minecraft.world.server.ServerWorld;
import net.minecraftforge.common.WorldWorkerManager;
/**
* A singleton that handles all long distance LOD world generation.
*
* @author James Seibel
* @version 9-25-2021
*/
public class LodWorldGenerator
{
public MinecraftWrapper mc = MinecraftWrapper.INSTANCE;
/** This holds the thread used to generate new LODs off the main thread. */
private ExecutorService mainGenThread = Executors.newSingleThreadExecutor(new LodThreadFactory(this.getClass().getSimpleName() + " world generator"));
/** we only want to queue up one generator thread at a time */
private boolean generatorThreadRunning = false;
/**
* How many chunks to generate outside of the player's view distance at one
* time. (or more specifically how many requests to make at one time). I
* multiply by 8 to make sure there is always a buffer of chunk requests, to
* make sure the CPU is always busy and we can generate LODs as quickly as
* possible.
*/
public int maxChunkGenRequests;
/**
* This keeps track of how many chunk generation requests are on going. This is
* to limit how many chunks are queued at once. To prevent chunks from being
* generated for a long time in an area the player is no longer in.
*/
public AtomicInteger numberOfChunksWaitingToGenerate = new AtomicInteger(0);
public Set<ChunkPos> positionsWaitingToBeGenerated = new HashSet<>();
/**
* Singleton copy of this object
*/
public static final LodWorldGenerator INSTANCE = new LodWorldGenerator();
private LodWorldGenerator()
{
}
/**
* Queues up LodNodeGenWorkers for the given lodDimension.
*
* @param renderer needed so the LodNodeGenWorkers can flag that the
* buffers need to be rebuilt.
*/
public void queueGenerationRequests(LodDimension lodDim, LodRenderer renderer, LodBuilder lodBuilder)
{
if (LodConfig.CLIENT.worldGenerator.distanceGenerationMode.get() != DistanceGenerationMode.NONE
&& !generatorThreadRunning
&& mc.hasSingleplayerServer())
{
// the thread is now running, don't queue up another thread
generatorThreadRunning = true;
// just in case the config changed
maxChunkGenRequests = LodConfig.CLIENT.threading.numberOfWorldGenerationThreads.get() * 8;
Thread generatorThread = new Thread(() ->
{
try
{
// round the player's block position down to the nearest chunk BlockPos
int playerPosX = mc.getPlayer().blockPosition().getX();
int playerPosZ = mc.getPlayer().blockPosition().getZ();
//=======================================//
// fill in positionsWaitingToBeGenerated //
//=======================================//
ServerWorld serverWorld = LodUtil.getServerWorldFromDimension(lodDim.dimension);
PosToGenerateContainer posToGenerate = lodDim.getDataToGenerate(
maxChunkGenRequests,
playerPosX,
playerPosZ);
byte detailLevel;
int posX;
int posZ;
int nearIndex = 0;
int farIndex = 0;
for (int i = 0; i < posToGenerate.getNumberOfPos(); i++)
{
// I wish there was a way to compress this code, but I'm not aware of
// a easy way to do so.
// add the near positions
if (posToGenerate.getNthDetail(nearIndex, true) != 0 && nearIndex < posToGenerate.getNumberOfNearPos())
{
detailLevel = (byte) (posToGenerate.getNthDetail(nearIndex, true) - 1);
posX = posToGenerate.getNthPosX(nearIndex, true);
posZ = posToGenerate.getNthPosZ(nearIndex, true);
nearIndex++;
ChunkPos chunkPos = new ChunkPos(LevelPosUtil.getChunkPos(detailLevel, posX), LevelPosUtil.getChunkPos(detailLevel, posZ));
if (numberOfChunksWaitingToGenerate.get() < maxChunkGenRequests)
{
// prevent generating the same chunk multiple times
if (positionsWaitingToBeGenerated.contains(chunkPos))
continue;
}
// don't add more to the generation queue then allowed
if (numberOfChunksWaitingToGenerate.get() >= maxChunkGenRequests)
break;
positionsWaitingToBeGenerated.add(chunkPos);
numberOfChunksWaitingToGenerate.addAndGet(1);
LodNodeGenWorker genWorker = new LodNodeGenWorker(chunkPos, DetailDistanceUtil.getDistanceGenerationMode(detailLevel), lodBuilder, lodDim, serverWorld);
WorldWorkerManager.addWorker(genWorker);
}
// add the far positions
if (posToGenerate.getNthDetail(farIndex, false) != 0 && farIndex < posToGenerate.getNumberOfFarPos())
{
detailLevel = (byte) (posToGenerate.getNthDetail(farIndex, false) - 1);
posX = posToGenerate.getNthPosX(farIndex, false);
posZ = posToGenerate.getNthPosZ(farIndex, false);
farIndex++;
ChunkPos chunkPos = new ChunkPos(LevelPosUtil.getChunkPos(detailLevel, posX), LevelPosUtil.getChunkPos(detailLevel, posZ));
if (numberOfChunksWaitingToGenerate.get() < maxChunkGenRequests)
{
// prevent generating the same chunk multiple times
if (positionsWaitingToBeGenerated.contains(chunkPos))
continue;
}
// don't add more to the generation queue then allowed
if (numberOfChunksWaitingToGenerate.get() >= maxChunkGenRequests)
break;
positionsWaitingToBeGenerated.add(chunkPos);
numberOfChunksWaitingToGenerate.addAndGet(1);
LodNodeGenWorker genWorker = new LodNodeGenWorker(chunkPos, DetailDistanceUtil.getDistanceGenerationMode(detailLevel), lodBuilder, lodDim, serverWorld);
WorldWorkerManager.addWorker(genWorker);
}
}
}
catch (Exception e)
{
// this shouldn't ever happen, but just in case
e.printStackTrace();
}
finally
{
generatorThreadRunning = false;
}
});
mainGenThread.execute(generatorThread);
} // if distanceGenerationMode != DistanceGenerationMode.NONE && !generatorThreadRunning
} // queueGenerationRequests
}
@@ -0,0 +1,473 @@
/*
* This file is part of the LOD Mod, licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.lod.config;
import java.nio.file.Path;
import java.nio.file.Paths;
import org.apache.commons.lang3.tuple.Pair;
import org.apache.logging.log4j.LogManager;
import com.electronwill.nightconfig.core.file.CommentedFileConfig;
import com.electronwill.nightconfig.core.io.WritingMode;
import com.seibel.lod.ModInfo;
import com.seibel.lod.enums.DebugMode;
import com.seibel.lod.enums.DetailDropOff;
import com.seibel.lod.enums.DistanceGenerationMode;
import com.seibel.lod.enums.FogDistance;
import com.seibel.lod.enums.FogDrawOverride;
import com.seibel.lod.enums.GenerationPriority;
import com.seibel.lod.enums.HorizontalQuality;
import com.seibel.lod.enums.HorizontalResolution;
import com.seibel.lod.enums.HorizontalScale;
import com.seibel.lod.enums.LodTemplate;
import com.seibel.lod.enums.VerticalQuality;
import com.seibel.lod.render.LodRenderer.FovTest;
import net.minecraftforge.common.ForgeConfigSpec;
import net.minecraftforge.eventbus.api.SubscribeEvent;
import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.config.ModConfig;
/**
* This handles any configuration the user has access to.
*
* @author James Seibel
* @version 9-26-2021
*/
@Mod.EventBusSubscriber
public class LodConfig
{
public static class Client
{
public final Graphics graphics;
public final WorldGenerator worldGenerator;
public final Threading threading;
public final Debugging debugging;
public final Buffers buffers;
public Client(ForgeConfigSpec.Builder builder)
{
builder.push("client");
{
graphics = new Graphics(builder);
worldGenerator = new WorldGenerator(builder);
threading = new Threading(builder);
debugging = new Debugging(builder);
buffers = new Buffers(builder);
}
builder.pop();
}
}
//================//
// Client Configs //
//================//
public static class Graphics
{
public ForgeConfigSpec.BooleanValue drawLods;
public ForgeConfigSpec.EnumValue<FogDistance> fogDistance;
public ForgeConfigSpec.EnumValue<FogDrawOverride> fogDrawOverride;
public ForgeConfigSpec.EnumValue<LodTemplate> lodTemplate;
public ForgeConfigSpec.EnumValue<HorizontalResolution> drawResolution;
// public ForgeConfigSpec.EnumValue<ShadingMode> shadingMode;
public ForgeConfigSpec.EnumValue<DetailDropOff> detailDropOff;
public ForgeConfigSpec.IntValue lodChunkRenderDistance;
public ForgeConfigSpec.BooleanValue disableDirectionalCulling;
public ForgeConfigSpec.BooleanValue alwaysDrawAtMaxQuality;
public ForgeConfigSpec.EnumValue<FovTest> useFovSetting;
Graphics(ForgeConfigSpec.Builder builder)
{
builder.comment("These settings control how the LODs look.").push(this.getClass().getSimpleName());
drawLods = builder
.comment("\n\n"
+ " If true, the mod is enabled and LODs will be drawn. \n"
+ " If false, the mod will still generate LODs, \n"
+ " but they won't be rendered. \n")
.define("drawLODs", true);
fogDistance = builder
.comment("\n\n"
+ " At what distance should Fog be drawn on the LODs? \n"
+ " If the fog cuts off ubruptly or you are using Optifine's \"fast\" fog option \n"
+ " set this to " + FogDistance.NEAR + " or " + FogDistance.FAR + ". \n")
.defineEnum("fogDistance", FogDistance.NEAR_AND_FAR);
fogDrawOverride = builder
.comment("\n\n"
+ " When should fog be drawn? \n"
+ " " + FogDrawOverride.USE_OPTIFINE_FOG_SETTING + ": Use whatever Fog setting Optifine is using. If Optifine isn't installed this defaults to " + FogDrawOverride.ALWAYS_DRAW_FOG_FANCY + ". \n"
+ " " + FogDrawOverride.NEVER_DRAW_FOG + ": Never draw fog on the LODs \n"
+ " " + FogDrawOverride.ALWAYS_DRAW_FOG_FAST + ": Always draw fast fog on the LODs \n"
+ " " + FogDrawOverride.ALWAYS_DRAW_FOG_FANCY + ": Always draw fancy fog on the LODs (if your graphics card supports it) \n")
.defineEnum("fogDrawOverride", FogDrawOverride.ALWAYS_DRAW_FOG_FANCY);
lodTemplate = builder
.comment("\n\n"
+ " How should the LODs be drawn? \n"
+ " NOTE: Currently only " + LodTemplate.CUBIC + " is implemented! \n"
+ " \n"
+ " " + LodTemplate.CUBIC + ": LOD Chunks are drawn as rectangular prisms (boxes). \n"
+ " " + LodTemplate.TRIANGULAR + ": LOD Chunks smoothly transition between other. \n"
+ " " + LodTemplate.DYNAMIC + ": LOD Chunks smoothly transition between other, \n"
+ " " + " unless a neighboring chunk is at a significantly different height. \n")
.defineEnum("lodTemplate", LodTemplate.CUBIC);
detailDropOff = builder
.comment("\n\n"
+ " How smooth should the detail transition for LODs be? \n"
+ DetailDropOff.FANCY + ": quality is determined per-block (best quality option, may cause stuttering when moving)\n"
+ DetailDropOff.FAST + ": quality is determined per-region (performance option)\n")
.defineEnum("detailDropOff", DetailDropOff.FANCY);
drawResolution = builder
.comment("\n\n"
+ " What is the maximum detail LODs should be drawn at? \n"
+ " " + HorizontalResolution.CHUNK + ": render 1 LOD for each Chunk. \n"
+ " " + HorizontalResolution.HALF_CHUNK + ": render 4 LODs for each Chunk. \n"
+ " " + HorizontalResolution.FOUR_BLOCKS + ": render 16 LODs for each Chunk. \n"
+ " " + HorizontalResolution.TWO_BLOCKS + ": render 64 LODs for each Chunk. \n"
+ " " + HorizontalResolution.BLOCK + ": render 256 LODs for each Chunk. \n")
.defineEnum("Draw resolution", HorizontalResolution.BLOCK);
lodChunkRenderDistance = builder
.comment("\n\n"
+ " The mod's render distance, measured in chunks. \n")
.defineInRange("lodChunkRenderDistance", 64, 32, 1024);
useFovSetting = builder
.comment("\n\n"
+ " Experimental text value. \n"
+ " " + FovTest.BOTH + ": is the normal value \n")
.defineEnum("useFovSetting", FovTest.BOTH);
disableDirectionalCulling = builder
.comment("\n\n"
+ " If false LODs that are behind the player's camera \n"
+ " aren't drawn, increasing performance. \n\n"
+ ""
+ " If true all LODs are drawn, even those behind \n"
+ " the player's camera, decreasing performance. \n\n"
+ ""
+ " Disable this if you see LODs disapearing. \n"
+ " (This may happen if you are using a camera mod) \n")
.define("disableDirectionalCulling", false);
// shadingMode = builder
// .comment("\n\n"
// + " What kind of shading should the LODs have? \n"
// + " \n"
// + " " + ShadingMode.NONE + " \n"
// + " " + "LODs will have the same lighting on every side. \n"
// + " " + "Can make large similarly colored areas hard to differentiate. \n"
// + "\n"
// + " " + ShadingMode.GAME_SHADING + " \n"
// + " " + "LODs will have darker sides and bottoms to simulate Minecraft's flat lighting.")
// .defineEnum("lightingMode", ShadingMode.GAME_SHADING);
alwaysDrawAtMaxQuality = builder
.comment("\n\n"
+ " Disable LOD quality falloff, "
+ " all LODs will be drawn at the highest "
+ " available detail level. "
+ " "
+ " WARNING "
+ " This could cause a Out Of Memory crash on render "
+ " distances higher than 128 \n")
.define("alwaysDrawAtMaxQuality", false);
builder.pop();
}
}
public static class WorldGenerator
{
public ForgeConfigSpec.EnumValue<VerticalQuality> lodQualityMode;
public ForgeConfigSpec.EnumValue<HorizontalResolution> generationResolution;
public ForgeConfigSpec.EnumValue<DistanceGenerationMode> distanceGenerationMode;
public ForgeConfigSpec.EnumValue<GenerationPriority> generationPriority;
public ForgeConfigSpec.BooleanValue allowUnstableFeatureGeneration;
public ForgeConfigSpec.EnumValue<HorizontalScale> horizontalScale;
public ForgeConfigSpec.EnumValue<HorizontalQuality> horizontalQuality;
WorldGenerator(ForgeConfigSpec.Builder builder)
{
builder.comment("These settings control how LODs outside your normal view range are generated.").push(this.getClass().getSimpleName());
lodQualityMode = builder
.comment("\n\n"
+ " Use 3d lods or 2d lods? \n"
+ " " + VerticalQuality.HEIGHTMAP + ": LODs are solid from the lowest point to the highest. Creates pillars for floating islands. Faster \n"
+ " " + VerticalQuality.VOXEL + ": LODs have gaps between vertical blocks. Good for floating islands and caves. Slower \n"
+ " " + "(Yes we know voxels are generally cubes, but voxel sounds better than rectangular_prism) \n")
.defineEnum("lodQualityMode", VerticalQuality.HEIGHTMAP);
generationResolution = builder
.comment("\n\n"
+ " What is the maximum detail level that LODs should be generated at? \n"
+ " " + HorizontalResolution.CHUNK + ": render 1 LOD for each Chunk. \n"
+ " " + HorizontalResolution.HALF_CHUNK + ": render 4 LODs for each Chunk. \n"
+ " " + HorizontalResolution.FOUR_BLOCKS + ": render 16 LODs for each Chunk. \n"
+ " " + HorizontalResolution.TWO_BLOCKS + ": render 64 LODs for each Chunk. \n"
+ " " + HorizontalResolution.BLOCK + ": render 256 LODs for each Chunk. \n")
.defineEnum("Generation Resolution", HorizontalResolution.BLOCK);
horizontalScale = builder
.comment("\n\n"
+ " This indicates how quickly LODs drop off in quality. \n"
+ " " + HorizontalScale.LOW + ": quality drops every " + HorizontalScale.LOW.distanceUnit / 16 + " chunks. \n"
+ " " + HorizontalScale.MEDIUM + ": quality drops every " + HorizontalScale.MEDIUM.distanceUnit / 16 + " chunks. \n"
+ " " + HorizontalScale.HIGH + ": quality drops every " + HorizontalScale.HIGH.distanceUnit / 16 + " chunks. \n")
.defineEnum("horizontal scale", HorizontalScale.MEDIUM);
horizontalQuality = builder
.comment("\n\n"
+ " This indicates the exponential base of the quadratic drop-off \n"
+ " " + HorizontalQuality.LINEAR + ": base " + HorizontalQuality.LINEAR.quadraticBase + ". \n"
+ " " + HorizontalQuality.LOW + ": base " + HorizontalQuality.LOW.quadraticBase + ". \n"
+ " " + HorizontalQuality.MEDIUM + ": base " + HorizontalQuality.MEDIUM.quadraticBase + ". \n"
+ " " + HorizontalQuality.HIGH + ": base " + HorizontalQuality.HIGH.quadraticBase + ". \n")
.defineEnum("horizontal quality", HorizontalQuality.MEDIUM);
generationPriority = builder
.comment("\n\n"
+ " " + GenerationPriority.FAR_FIRST + " \n"
+ " LODs are generated from low to high detail\n"
+ " with a small priority for far regions. \n"
+ " This fills in the world fastest. \n"
+ "\n"
+ " " + GenerationPriority.NEAR_FIRST + " \n"
+ " LODs are generated around the player \n"
+ " in a spiral, similar to vanilla minecraft. \n")
.defineEnum("Generation priority", GenerationPriority.NEAR_FIRST);
distanceGenerationMode = builder
.comment("\n\n"
+ " Note: The times listed here are the amount of time it took \n"
+ " one of the developer's PC to generate 1 chunk, \n"
+ " and are included so you can compare the \n"
+ " different generation options. Your mileage may vary. \n"
+ "\n"
+ " " + DistanceGenerationMode.NONE + " \n"
+ " Don't run the distance generator. \n"
+ " " + DistanceGenerationMode.BIOME_ONLY + " \n"
+ " Only generate the biomes and use the biome's \n"
+ " grass color, water color, or snow color. \n"
+ " Doesn't generate height, everything is shown at sea level. \n"
+ " Multithreaded - Fastest (2-5 ms) \n"
+ "\n"
+ " " + DistanceGenerationMode.BIOME_ONLY_SIMULATE_HEIGHT + " \n"
+ " Same as BIOME_ONLY, except instead \n"
+ " of always using sea level as the LOD height \n"
+ " different biome types (mountain, ocean, forest, etc.) \n"
+ " use predetermined heights to simulate having height data. \n"
+ " Multithreaded - Fastest (2-5 ms) \n"
+ "\n"
+ " " + DistanceGenerationMode.SURFACE + " \n"
+ " Generate the world surface, \n"
+ " this does NOT include caves, trees, \n"
+ " or structures. \n"
+ " Multithreaded - Faster (10-20 ms) \n"
+ "\n"
+ " " + DistanceGenerationMode.FEATURES + " \n"
+ " Generate everything except structures. \n"
+ " WARNING: This may cause world generation bugs or instability! \n"
+ " Multithreaded - Fast (15-20 ms) \n"
+ "\n"
+ " " + DistanceGenerationMode.SERVER + " \n"
+ " Ask the server to generate/load each chunk. \n"
+ " This is the most compatible, but causes server/simulation lag. \n"
+ " This will show player made structures, which can \n"
+ " be useful if you are adding the mod to a pre-existing world. \n"
+ " Singlethreaded - Slow (15-50 ms, with spikes up to 200 ms) \n")
.defineEnum("distanceGenerationMode", DistanceGenerationMode.SURFACE);
allowUnstableFeatureGeneration = builder
.comment("\n\n"
+ " When using the " + DistanceGenerationMode.FEATURES + " generation mode \n"
+ " some features may not be thread safe, which could \n"
+ " cause instability and crashes. \n"
+ " By default (false) those features are skipped, \n"
+ " improving stability, but decreasing how many features are \n"
+ " actually generated. \n"
+ " (for example: some tree generation is unstable, \n"
+ " so some trees may not be generated.) \n"
+ " By setting this to true, all features will be generated, \n"
+ " but your game will be more unstable and crashes may occur. \n"
+ " \n"
+ " I would love to remove this option and always generate everything, \n"
+ " but I'm not sure how to do that. \n"
+ " If you are a Java wizard, check out the git issue here: \n"
+ " https://gitlab.com/jeseibel/minecraft-lod-mod/-/issues/35 \n")
.define("allowUnstableFeatureGeneration", false);
builder.pop();
}
}
public static class Threading
{
public ForgeConfigSpec.IntValue numberOfWorldGenerationThreads;
public ForgeConfigSpec.IntValue numberOfBufferBuilderThreads;
Threading(ForgeConfigSpec.Builder builder)
{
builder.comment("These settings control how many CPU threads the mod uses for different tasks.").push(this.getClass().getSimpleName());
numberOfWorldGenerationThreads = builder
.comment("\n\n"
+ " This is how many threads are used when generating LODs outside \n"
+ " the normal render distance. \n"
+ " If you experience stuttering when generating distant LODs, decrease \n"
+ " this number. If you want to increase LOD generation speed, \n"
+ " increase this number. \n"
+ " \n"
+ " The maximum value is the number of logical processors on your CPU. \n"
+ " Requires a restart to take effect. \n")
.defineInRange("numberOfWorldGenerationThreads", Runtime.getRuntime().availableProcessors() / 2, 1, Runtime.getRuntime().availableProcessors());
numberOfBufferBuilderThreads = builder
.comment("\n\n"
+ " This is how many threads are used when building vertex buffers \n"
+ " (The things sent to your GPU to draw the LODs). \n"
+ " If you experience high CPU useage when NOT generating distant \n"
+ " LODs, lower this number. \n"
+ " \n"
+ " The maximum value is the number of logical processors on your CPU. \n"
+ " Requires a restart to take effect. \n")
.defineInRange("numberOfBufferBuilderThreads", Runtime.getRuntime().availableProcessors(), 1, Runtime.getRuntime().availableProcessors());
builder.pop();
}
}
public static class Debugging
{
public ForgeConfigSpec.EnumValue<DebugMode> debugMode;
public ForgeConfigSpec.BooleanValue enableDebugKeybindings;
Debugging(ForgeConfigSpec.Builder builder)
{
builder.comment("These settings can be used to look for bugs, or see how certain aspects of the mod work.").push(this.getClass().getSimpleName());
debugMode = builder
.comment("\n\n"
+ " " + DebugMode.OFF + ": LODs will draw with their normal colors. \n"
+ " " + DebugMode.SHOW_DETAIL + ": LOD colors will be based on their detail level. \n"
+ " " + DebugMode.SHOW_DETAIL_WIREFRAME + ": LOD colors will be based on their detail level, drawn as a wireframe. \n")
.defineEnum("debugMode", DebugMode.OFF);
enableDebugKeybindings = builder
.comment("\n\n"
+ " If true the F4 key can be used to cycle through the different debug modes. \n"
+ " and the F6 key can be used to enable and disable LOD rendering.")
.define("enableDebugKeybinding", false);
builder.pop();
}
}
public static class Buffers
{
public ForgeConfigSpec.IntValue bufferRebuildPlayerMoveTimeout;
public ForgeConfigSpec.IntValue bufferRebuildChunkChangeTimeout;
public ForgeConfigSpec.IntValue bufferRebuildLodChangeTimeout;
Buffers(ForgeConfigSpec.Builder builder)
{
builder.comment("These settings affect when Vertex Buffers are built.").push(this.getClass().getSimpleName());
bufferRebuildPlayerMoveTimeout = builder
.comment("\n\n"
+ " How long in milliseconds should we wait to \n"
+ " rebuild the vertex buffers when the player moves \n"
+ " a chunk or more? \n")
.defineInRange("bufferRebuildPlayerMoveTimeout", 2000, 1, 60000);
bufferRebuildChunkChangeTimeout = builder
.comment("\n\n"
+ " How long in milliseconds should we wait to \n"
+ " rebuild the vertex buffers when the vanilla rendered \n"
+ " chunks change? \n")
.defineInRange("bufferRebuildChunkChangeTimeout", 1000, 1, 60000);
bufferRebuildLodChangeTimeout = builder
.comment("\n\n"
+ " How long in milliseconds should we wait to \n"
+ " rebuild the vertex buffers when the LOD regions change? \n")
.defineInRange("bufferRebuildLodChangeTimeout", 5000, 1, 60000);
builder.pop();
}
}
/**
* {@link Path} to the configuration file of this mod
*/
private static final Path CONFIG_PATH = Paths.get("config", ModInfo.MODID + ".toml");
public static final ForgeConfigSpec CLIENT_SPEC;
public static final Client CLIENT;
static
{
final Pair<Client, ForgeConfigSpec> specPair = new ForgeConfigSpec.Builder().configure(Client::new);
CLIENT_SPEC = specPair.getRight();
CLIENT = specPair.getLeft();
CommentedFileConfig clientConfig = CommentedFileConfig.builder(CONFIG_PATH)
.writingMode(WritingMode.REPLACE)
.build();
clientConfig.load();
clientConfig.save();
CLIENT_SPEC.setConfig(clientConfig);
}
@SubscribeEvent
public static void onLoad(final ModConfig.Loading configEvent)
{
LogManager.getLogger().debug(ModInfo.MODNAME, "Loaded forge config file {}", configEvent.getConfig().getFileName());
}
@SubscribeEvent
public static void onFileChange(final ModConfig.Reloading configEvent)
{
LogManager.getLogger().debug(ModInfo.MODNAME, "Forge config just got changed on the file system!");
}
}
@@ -0,0 +1,51 @@
/*
* This file is part of the LOD Mod, licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.lod.enums;
/**
* off, detail, detail wireframe
*
* @author James Seibel
* @version 8-28-2021
*/
public enum DebugMode
{
/** LODs are rendered normally */
OFF,
/** LOD colors are based on their detail */
SHOW_DETAIL,
/** LOD colors are based on their detail, and draws in wireframe. */
SHOW_DETAIL_WIREFRAME;
/** used when cycling through the different modes */
private DebugMode next;
static
{
OFF.next = SHOW_DETAIL;
SHOW_DETAIL.next = SHOW_DETAIL_WIREFRAME;
SHOW_DETAIL_WIREFRAME.next = OFF;
}
/** returns the next debug mode */
public DebugMode getNext()
{
return this.next;
}
}
@@ -18,34 +18,18 @@
package com.seibel.lod.enums;
/**
* TOP, NORTH, SOUTH, EAST, WEST, BOTTOM
* By_Region_Fast, <br>
* By_Region_Fancy, <br>
* By_Chunk
*
* @author James Seibel
* @version 10-17-2020
* @author Leonardo Amato
* @version 9-25-2021
*/
public enum ColorDirection
public enum DetailDropOff
{
// used for colors
/** +Y */
TOP(0),
/** -Z */
NORTH(1),
/** +Z */
SOUTH(2),
/** +X */
EAST(3),
/** -X */
WEST(4),
/** -Y */
BOTTOM(5);
public final int value;
private ColorDirection(int newValue)
{
value = newValue;
}
/** quality is determined per-region, using the lowest quality that would be used in BY_CHUNK */
FAST,
/** quality is determined per-block (best quality option, may cause stuttering when moving) */
FANCY,
}
@@ -18,51 +18,76 @@
package com.seibel.lod.enums;
/**
* NONE <br>
* BIOME_ONLY <br>
* BIOME_ONLY_SIMULATE_HEIGHT <br>
* SURFACE <br>
* FEATURES <br>
* SERVER <br><br>
*
* <p>
* In order of fastest to slowest.
*
*
* @author James Seibel
* @version 6-27-2021
* @author Leonardo Amato
* @version 8-7-2021
*/
public enum DistanceGenerationMode
{
/** Only generate the biomes and use biome
/**
* Don't generate anything
*/
NONE((byte) 0),
/**
* Only generate the biomes and use biome
* grass/foliage color, water color, or ice color
* to generate the color.
* Doesn't generate height, everything is shown at sea level.
* Multithreaded - Fastest (2-5 ms) */
BIOME_ONLY,
* Doesn't generate height, everything is shown at sea level.
* Multithreaded - Fastest (2-5 ms)
*/
BIOME_ONLY((byte) 1),
/**
* Same as BIOME_ONLY, except instead
* of always using sea level as the LOD height
* different biome types (mountain, ocean, forest, etc.)
* use predetermined heights to simulate having height data.
*/
BIOME_ONLY_SIMULATE_HEIGHT,
/** Generate the world surface,
BIOME_ONLY_SIMULATE_HEIGHT((byte) 2),
/**
* Generate the world surface,
* this does NOT include caves, trees,
* or structures.
* Multithreaded - Faster (10-20 ms) */
SURFACE,
/** Generate everything except structures.
* or structures.
* Multithreaded - Faster (10-20 ms)
*/
SURFACE((byte) 3),
/**
* Generate everything except structures.
* NOTE: This may cause world generation bugs or instability,
* since some features cause concurrentModification exceptions.
* Multithreaded - Fast (15-20 ms) */
FEATURES,
/** Ask the server to generate/load each chunk.
* Multithreaded - Fast (15-20 ms)
*/
FEATURES((byte) 4),
/**
* Ask the server to generate/load each chunk.
* This is the most compatible, but causes server/simulation lag.
* This will also show player made structures if you
* are adding the mod to a pre-existing world.
* Singlethreaded - Slow (15-50 ms, with spikes up to 200 ms) */
SERVER;
* are adding the mod to a pre-existing world.
* Singlethreaded - Slow (15-50 ms, with spikes up to 200 ms)
*/
SERVER((byte) 5);
/**
* The higher the number the more complete the generation is.
*/
public final byte complexity;
DistanceGenerationMode(byte complexity)
{
this.complexity = complexity;
}
}
@@ -24,7 +24,7 @@ package com.seibel.lod.enums;
* ALWAYS_DRAW_FOG_FANCY <br>
*
* @author James Seibel
* @version 7-03-2021
* @version 7-3-2021
*/
public enum FogDrawOverride
{
@@ -18,26 +18,18 @@
package com.seibel.lod.enums;
/**
* NE, SE, SW, NW
* Near_First <br>
* Far_First <br>
* <br>
* Determines which LODs should have priority when generating
* outside the normal view distance.
*
* @author James Seibel
* @version 1-20-2020
* @author Leonardo Amato
* @version 9-25-2021
*/
public enum LodCorner
public enum GenerationPriority
{
/** -Z, +X */
NE(0),
/** +Z, +X */
SE(1),
/** +Z, -X */
SW(2),
/** -Z, -X */
NW(3);
NEAR_FIRST,
public final int value;
private LodCorner(int newValue)
{
value = newValue;
}
FAR_FIRST;
}
@@ -0,0 +1,50 @@
/*
* This file is part of the LOD Mod, licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.lod.enums;
/**
* Low <br>
* Medium <br>
* High <br>
* <br>
* this indicate the base of the quadratic function we use for the quality drop off
*
* @author Leonardo Amato
* @version 9-29-2021
*/
public enum HorizontalQuality
{
/** Lods are 2D with heightMap */
LINEAR(1.0f),
/** Lods are 2D with heightMap */
LOW(1.5f),
/** Lods expand in three dimension */
MEDIUM(2.0f),
/** Lods expand in three dimension */
HIGH(2.2f);
public double quadraticBase;
HorizontalQuality(double distanceUnit)
{
this.quadraticBase = distanceUnit;
}
}
@@ -0,0 +1,165 @@
/*
* This file is part of the LOD Mod, licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.lod.enums;
import java.util.ArrayList;
import java.util.Collections;
import com.seibel.lod.util.LodUtil;
/**
* chunk <Br>
* half_chunk <Br>
* four_blocks <br>
* two_blocks <Br>
* block <br>
*
* @author James Seibel
* @author Leonardo Amato
* @version 9-25-2021
*/
public enum HorizontalResolution
{
/** render 1 LOD for each chunk */
CHUNK(1, 4),
/** render 4 LODs for each chunk */
HALF_CHUNK(2, 3),
/** render 16 LODs for each chunk */
FOUR_BLOCKS(4, 2),
/** render 64 LODs for each chunk */
TWO_BLOCKS(8, 1),
/** render 256 LODs for each chunk */
BLOCK(16, 0);
/** How many DataPoints should
* be drawn per side per LodChunk */
public final int dataPointLengthCount;
/** How wide each LOD DataPoint is */
public final int dataPointWidth;
/** This is the same as detailLevel in LodQuadTreeNode,
* lowest is 0 highest is 9 */
public final byte detailLevel;
/* Start/End X/Z give the block positions
* for each individual dataPoint in a LodChunk */
public final int[] startX;
public final int[] startZ;
public final int[] endX;
public final int[] endZ;
/**
* 1st dimension: LodDetail.detailLevel <br>
* 2nd dimension: An array of all LodDetails that are less than or <br>
* equal to that detailLevel
*/
private static HorizontalResolution[][] lowerDetailArrays;
private HorizontalResolution(int newLengthCount, int newDetailLevel)
{
detailLevel = (byte) newDetailLevel;
dataPointLengthCount = newLengthCount;
dataPointWidth = 16 / dataPointLengthCount;
startX = new int[dataPointLengthCount * dataPointLengthCount];
endX = new int[dataPointLengthCount * dataPointLengthCount];
startZ = new int[dataPointLengthCount * dataPointLengthCount];
endZ = new int[dataPointLengthCount * dataPointLengthCount];
int index = 0;
for(int x = 0; x < newLengthCount; x++)
{
for(int z = 0; z < newLengthCount; z++)
{
startX[index] = x * dataPointWidth;
startZ[index] = z * dataPointWidth;
endX[index] = (x*dataPointWidth) + dataPointWidth;
endZ[index] = (z*dataPointWidth) + dataPointWidth;
index++;
}
}
}// constructor
/**
* Returns an array of all LodDetails that have a detail level
* that is less than or equal to the given LodDetail
*/
public static HorizontalResolution[] getSelfAndLowerDetails(HorizontalResolution detail)
{
if (lowerDetailArrays == null)
{
// run first time setup
lowerDetailArrays = new HorizontalResolution[HorizontalResolution.values().length][];
// go through each LodDetail
for(HorizontalResolution currentDetail : HorizontalResolution.values())
{
ArrayList<HorizontalResolution> lowerDetails = new ArrayList<>();
// find the details lower than currentDetail
for(HorizontalResolution compareDetail : HorizontalResolution.values())
{
if (currentDetail.detailLevel <= compareDetail.detailLevel)
{
lowerDetails.add(compareDetail);
}
}
// have the highest detail item first in the list
Collections.sort(lowerDetails);
Collections.reverse(lowerDetails);
lowerDetailArrays[currentDetail.detailLevel] = lowerDetails.toArray(new HorizontalResolution[lowerDetails.size()]);
}
}
return lowerDetailArrays[detail.detailLevel];
}
/** Returns what detail level should be used at a given distance and maxDistance. */
public static HorizontalResolution getDetailForDistance(HorizontalResolution maxDetailLevel, int distance, int maxDistance)
{
HorizontalResolution[] lowerDetails = getSelfAndLowerDetails(maxDetailLevel);
int distaneBetweenDetails = maxDistance / lowerDetails.length;
int index = LodUtil.clamp(0, distance / distaneBetweenDetails, lowerDetails.length - 1);
return lowerDetails[index];
}
}
@@ -15,31 +15,33 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.lod.objects;
import net.minecraft.client.renderer.BufferBuilder;
package com.seibel.lod.enums;
/**
* This object is just a replacement for an array
* to make things easier to understand in the LodRenderer
* and BuildBufferThread.
* Low <br>
* Medium <br>
* High <br>
* <br>
* this is a quality scale for the detail drop-off
*
* @author James Seibel
* @version 03-25-2021
* @author Leonardo Amato
* @version 9-25-2021
*/
public class NearFarBuffer
public enum HorizontalScale
{
public BufferBuilder nearBuffer;
/** Lods are 2D with heightMap */
LOW(64),
public BufferBuilder farBuffer;
/** Lods expand in three dimension */
MEDIUM(128),
/**
* @param newNearBuffer
* @param newFarBuffer
*/
public NearFarBuffer(BufferBuilder newNearBuffer, BufferBuilder newFarBuffer)
/** Lods expand in three dimension */
HIGH(256);
public int distanceUnit;
HorizontalScale(int distanceUnit)
{
nearBuffer = newNearBuffer;
farBuffer = newFarBuffer;
this.distanceUnit = distanceUnit;
}
}
}
@@ -1,114 +0,0 @@
/*
* This file is part of the LOD Mod, licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.lod.enums;
import com.seibel.lod.objects.LodChunk;
import com.seibel.lod.objects.LodDataPoint;
/**
* single, double, quad, half, full
*
* @author James Seibel
* @version 06-13-2021
*/
public enum LodDetail
{
/** render 1 LOD for each chunk */
SINGLE(1),
/** render 4 LODs for each chunk */
DOUBLE(2),
/** render 16 LODs for each chunk */
QUAD(4),
/** render 64 LODs for each chunk */
HALF(8),
/** render 256 LODs for each chunk */
FULL(16);
/** How many DataPoints should
* be drawn per side per LodChunk */
public final int dataPointLengthCount;
/** How wide each LOD DataPoint is */
public final int dataPointWidth;
/* Start/End X/Z give the block positions
* for each individual dataPoint in a LodChunk */
public final int[] startX;
public final int[] startZ;
public final int[] endX;
public final int[] endZ;
/** This is how many pieces of data should be expected
* when creating a LodChunk with this detail level */
public final int lodChunkStringDelimiterCount;
private LodDetail(int newLengthCount)
{
dataPointLengthCount = newLengthCount;
dataPointWidth = 16 / dataPointLengthCount;
if(newLengthCount == LodChunk.WIDTH)
{
// this is to prevent overflow
newLengthCount = LodChunk.WIDTH - 1;
}
startX = new int[dataPointLengthCount * dataPointLengthCount];
endX = new int[dataPointLengthCount * dataPointLengthCount];
startZ = new int[dataPointLengthCount * dataPointLengthCount];
endZ = new int[dataPointLengthCount * dataPointLengthCount];
int index = 0;
for(int x = 0; x < newLengthCount; x++)
{
for(int z = 0; z < newLengthCount; z++)
{
startX[index] = x * dataPointWidth;
startZ[index] = z * dataPointWidth;
// special case for FULL
if(dataPointWidth != 1)
{
endX[index] = (x*dataPointWidth) + dataPointWidth - 1;
endZ[index] = (z*dataPointWidth) + dataPointWidth - 1;
}
else
{
endX[index] = (x*dataPointWidth) + dataPointWidth;
endZ[index] = (z*dataPointWidth) + dataPointWidth;
}
index++;
}
}
lodChunkStringDelimiterCount = 2 + (dataPointLengthCount * dataPointLengthCount * LodDataPoint.NUMBER_OF_DELIMITERS);
}// constructor
}
@@ -26,22 +26,20 @@ import com.seibel.lod.builders.lodTemplates.TriangularLodTemplate;
* Cubic, Triangular, Dynamic
*
* @author James Seibel
* @version 06-16-2021
* @version 8-4-2021
*/
public enum LodTemplate
{
// used for position
/** Chunks are rendered as
/** LODs are rendered as
* rectangular prisms. */
CUBIC(new CubicLodTemplate()),
/** Chunks smoothly transition between
/** LODs smoothly transition between
* each other. */
TRIANGULAR(new TriangularLodTemplate()),
/** Chunks smoothly transition between
* each other, unless a neighboring chunk
/** LODs smoothly transition between
* each other, unless a neighboring LOD
* is at a significantly different height. */
DYNAMIC(new DynamicLodTemplate());
@@ -54,8 +52,8 @@ public enum LodTemplate
}
public int getBufferMemoryForSingleLod(LodDetail detail)
public int getBufferMemoryForSingleLod(int maxVerticalData)
{
return template.getBufferMemoryForSingleLod(detail);
return template.getBufferMemoryForSingleNode(maxVerticalData);
}
}
@@ -1,86 +0,0 @@
/*
* This file is part of the LOD Mod, licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.lod.enums;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.EnumSet;
/**
* NE, SE, SW, NW <br>
* NORTH, SOUTH, EAST, WEST, <br>
* CENTER
*
* @author James Seibel
* @version 05-30-2021
*/
public enum RelativeChunkPos
{
/** +X, -Z */
NE(0, 1,-1),
/** +X, +Z */
SE(1, 1,1),
/** -X, +Z */
SW(2, -1,1),
/** -X, -Z */
NW(3, -1,-1),
/** -Z */
NORTH(4, 0,-1),
/** +Z */
SOUTH(5, 0,1),
/** +X */
EAST(6, 1,0),
/** -X */
WEST(7, -1,0),
CENTER(8, 0,0);
/** index used when referencing objects in an array */
public final int index;
/** position relative to X CENTER */
public final int x;
/** position relative to Z CENTER */
public final int z;
/** NORTH, SOUTH, EAST, WEST */
public static EnumSet<RelativeChunkPos> ADJACENT = EnumSet.of(NORTH, SOUTH, EAST, WEST);
/** NE, NW, SE, SW */
public static EnumSet<RelativeChunkPos> DIAGONAL = EnumSet.of(NE, NW, SE, SW);
public static EnumSet<RelativeChunkPos> NW_CORNER = EnumSet.of(WEST, NW, NORTH);
public static EnumSet<RelativeChunkPos> NE_CORNER = EnumSet.of(EAST, NE, NORTH);
public static EnumSet<RelativeChunkPos> SW_CORNER = EnumSet.of(WEST, SW, SOUTH);
public static EnumSet<RelativeChunkPos> SE_CORNER = EnumSet.of(EAST, SE, SOUTH);
/** NW_CORNER, NE_CORNER, SW_CORNER, SE_CORNER <br>
* Contains the 3 points surrounding a corner. */
public static ArrayList<EnumSet<RelativeChunkPos>> CORNERS = new ArrayList<EnumSet<RelativeChunkPos>>( Arrays.asList(NW_CORNER, NE_CORNER, SW_CORNER, SE_CORNER) );
private RelativeChunkPos(int newIndex, int newX, int newZ)
{
index = newIndex;
x = newX;
z = newZ;
}
}
@@ -0,0 +1,22 @@
package com.seibel.lod.enums;
/**
* NONE, GAME_SHADING
*
* @author James Seibel
* @version 7-25-2020
*/
public enum ShadingMode
{
/**
* LODs will have the same lighting on every side.
* can make large similarly colored areas hard to differentiate
*/
NONE,
/**
* LODs will have darker sides and bottoms to simulate
* Minecraft's fast, top down lighting.
*/
GAME_SHADING;
}
@@ -0,0 +1,34 @@
/*
* This file is part of the LOD Mod, licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.lod.enums;
/**
* heightmap <br>
* multi_lod <br>
*
* @author Leonardo Amato
* @version 9-27-2021
*/
public enum VerticalQuality
{
/** Lods only have height and depth data */
HEIGHTMAP,
/** Lods expand in three dimensions */
VOXEL;
}
@@ -1,258 +0,0 @@
/*
* This file is part of the LOD Mod, licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.lod.handlers;
import java.nio.file.Path;
import java.nio.file.Paths;
import org.apache.commons.lang3.tuple.Pair;
import org.apache.logging.log4j.LogManager;
import com.electronwill.nightconfig.core.file.CommentedFileConfig;
import com.electronwill.nightconfig.core.io.WritingMode;
import com.seibel.lod.ModInfo;
import com.seibel.lod.enums.DistanceGenerationMode;
import com.seibel.lod.enums.FogDistance;
import com.seibel.lod.enums.FogDrawOverride;
import com.seibel.lod.enums.LodDetail;
import com.seibel.lod.enums.LodTemplate;
import net.minecraftforge.common.ForgeConfigSpec;
import net.minecraftforge.eventbus.api.SubscribeEvent;
import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.config.ModConfig;
/**
*
* @author James Seibel
* @version 7-5-2021
*/
@Mod.EventBusSubscriber
public class LodConfig
{
public static class Client
{
public ForgeConfigSpec.BooleanValue drawLODs;
public ForgeConfigSpec.EnumValue<FogDistance> fogDistance;
public ForgeConfigSpec.EnumValue<FogDrawOverride> fogDrawOverride;
public ForgeConfigSpec.BooleanValue debugMode;
public ForgeConfigSpec.EnumValue<LodTemplate> lodTemplate;
public ForgeConfigSpec.EnumValue<LodDetail> lodDetail;
public ForgeConfigSpec.EnumValue<DistanceGenerationMode> distanceGenerationMode;
public ForgeConfigSpec.BooleanValue allowUnstableFeatureGeneration;
public ForgeConfigSpec.IntValue numberOfWorldGenerationThreads;
/** this is multiplied by the default view distance
* to determine how far out to generate/render LODs */
public ForgeConfigSpec.IntValue lodChunkRadiusMultiplier;
Client(ForgeConfigSpec.Builder builder)
{
builder.comment(ModInfo.MODNAME + " configuration settings").push("client");
drawLODs = builder
.comment("\n\n"
+ " If false LODs will not be drawn, \n"
+ " however they will still be generated \n"
+ " and saved to file for later use. \n")
.define("drawLODs", true);
fogDistance = builder
.comment("\n\n"
+ " At what distance should Fog be drawn on the LODs? \n"
+ " If the fog cuts off ubruptly or you are using Optifine's \"fast\" \n"
+ " fog option set this to " + FogDistance.NEAR.toString() + " or " + FogDistance.FAR.toString() + ". \n")
.defineEnum("fogDistance", FogDistance.NEAR_AND_FAR);
fogDrawOverride = builder
.comment("\n\n"
+ " When should fog be drawn? \n"
+ " " + FogDrawOverride.USE_OPTIFINE_FOG_SETTING.toString() + ": Use whatever Fog setting Optifine is using. If Optifine isn't installed this defaults to " + FogDrawOverride.ALWAYS_DRAW_FOG_FANCY.toString() + ". \n"
+ " " + FogDrawOverride.NEVER_DRAW_FOG.toString() + ": Never draw fog on the LODs \n"
+ " " + FogDrawOverride.ALWAYS_DRAW_FOG_FAST.toString() + ": Always draw fast fog on the LODs \n"
+ " " + FogDrawOverride.ALWAYS_DRAW_FOG_FANCY.toString() + ": Always draw fancy fog on the LODs (if your graphics card supports it) \n")
.defineEnum("fogDrawOverride", FogDrawOverride.USE_OPTIFINE_FOG_SETTING);
debugMode = builder
.comment("\n\n"
+ " If false the LODs will draw with their normal world colors. \n"
+ " If true they will draw as a black and white checkerboard. \n"
+ " This can be used for debugging or imagining you are playing a \n"
+ " giant game of chess ;) \n")
.define("drawCheckerBoard", false);
lodTemplate = builder
.comment("\n\n"
+ " How should the LODs be drawn? \n"
+ " NOTE: Currently only " + LodTemplate.CUBIC.toString() + " is implemented! \n"
+ " \n"
+ " " + LodTemplate.CUBIC.toString() + ": LOD Chunks are drawn as rectangular prisms (boxes). \n"
+ " " + LodTemplate.TRIANGULAR.toString() + ": LOD Chunks smoothly transition between other. \n"
+ " " + LodTemplate.DYNAMIC.toString() + ": LOD Chunks smoothly transition between other, \n"
+ " " + " unless a neighboring chunk is at a significantly different height. \n")
.defineEnum("lodTemplate", LodTemplate.CUBIC);
lodDetail = builder
.comment("\n\n"
+ " How detailed should the LODs be? \n"
+ " " + LodDetail.SINGLE.toString() + ": render 1 LOD for each Chunk. \n"
+ " " + LodDetail.DOUBLE.toString() + ": render 4 LODs for each Chunk. \n"
+ " " + LodDetail.QUAD.toString() + ": render 16 LODs for each Chunk. \n"
+ " " + LodDetail.HALF.toString() + ": render 64 LODs for each Chunk. \n")
.defineEnum("lodGeometryQuality", LodDetail.DOUBLE);
lodChunkRadiusMultiplier = builder
.comment("\n\n"
+ " This is multiplied by the default view distance \n"
+ " to determine how far out to generate/render LODs. \n"
+ " A value of 2 means that there is 1 render distance worth \n"
+ " of LODs in each cardinal direction. \n")
.defineInRange("lodChunkRadiusMultiplier", 8, 2, 16);
distanceGenerationMode = builder
.comment("\n\n"
+ " Note: The times listed here are the amount of time it took \n"
+ " the developer's PC to generate 1 chunk, \n"
+ " and are included so you can compare the \n"
+ " different generation options. Your mileage may vary. \n"
+ "\n"
+ " " + DistanceGenerationMode.BIOME_ONLY.toString() + " \n"
+ " Only generate the biomes and use biome \n"
+ " grass/foliage color, water color, or snow color \n"
+ " to generate the color. \n"
+ " Doesn't generate height, everything is shown at sea level. \n"
+ " Multithreaded - Fastest (2-5 ms) \n"
+ "\n"
+ " " + DistanceGenerationMode.BIOME_ONLY_SIMULATE_HEIGHT.toString() + " \n"
+ " Same as BIOME_ONLY, except instead \n"
+ " of always using sea level as the LOD height \n"
+ " different biome types (mountain, ocean, forest, etc.) \n"
+ " use predetermined heights to simulate having height data. \n"
+ " Multithreaded - Fastest (2-5 ms) \n"
+ "\n"
+ " " + DistanceGenerationMode.SURFACE.toString() + " \n"
+ " Generate the world surface, \n"
+ " this does NOT include caves, trees, \n"
+ " or structures. \n"
+ " Multithreaded - Faster (10-20 ms) \n"
+ "\n"
+ " " + DistanceGenerationMode.FEATURES.toString() + " \n"
+ " Generate everything except structures. \n"
+ " WARNING: This may cause world generation bugs or instability! \n"
+ " Multithreaded - Fast (15-20 ms) \n"
+ "\n"
+ " " + DistanceGenerationMode.SERVER.toString() + " \n"
+ " Ask the server to generate/load each chunk. \n"
+ " This is the most compatible, but causes server/simulation lag. \n"
+ " This will also show player made structures if you \n"
+ " are adding the mod to a pre-existing world. \n"
+ " Singlethreaded - Slow (15-50 ms, with spikes up to 200 ms) \n")
.defineEnum("distanceBiomeOnlyGeneration", DistanceGenerationMode.SURFACE);
allowUnstableFeatureGeneration = builder
.comment("\n\n"
+ " When using the " + DistanceGenerationMode.FEATURES.toString() + "generation mode \n"
+ " some features may not be thread safe, which could \n"
+ " cause instability and crashes. \n"
+ " By default (false) those features are skipped, \n"
+ " improving stability, but decreasing how many features are \n"
+ " actually generated. \n"
+ " (for example: some tree generation is unstable, \n"
+ " so some trees may not be generated.) \n"
+ " By setting this to true, all features will be generated, \n"
+ " but your game will be more unstable and crashes may occur. \n"
+ " \n"
+ " I would love to remove this option and always generate everything, \n"
+ " but I'm not sure how to do that. \n"
+ " If you are a Java wizard, check out the git issue here: \n"
+ " https://gitlab.com/jeseibel/minecraft-lod-mod/-/issues/35 \n")
.define("allowUnstableFeatureGeneration", false);
numberOfWorldGenerationThreads = builder
.comment("\n\n"
+ " This is how many threads are used when generating terrain. \n"
+ " If you experience stuttering when generating terrain, decrease \n"
+ " this number. If you want to increase LOD generation speed, \n"
+ " increase the number. \n"
+ " The max is the number of processors on your CPU. \n"
+ "\n"
+ " Requires a restart to take effect. \n"
)
.defineInRange("numberOfWorldGenerationThreads", Runtime.getRuntime().availableProcessors(), 1, Runtime.getRuntime().availableProcessors());
builder.pop();
}
}
/**
* {@link Path} to the configuration file of this mod
*/
private static final Path CONFIG_PATH =
Paths.get("config", ModInfo.MODID + ".toml");
public static final ForgeConfigSpec clientSpec;
public static final Client CLIENT;
static {
final Pair<Client, ForgeConfigSpec> specPair = new ForgeConfigSpec.Builder().configure(Client::new);
clientSpec = specPair.getRight();
CLIENT = specPair.getLeft();
// setup the config file
CommentedFileConfig config = CommentedFileConfig.builder(CONFIG_PATH)
.writingMode(WritingMode.REPLACE)
.build();
config.load();
config.save();
clientSpec.setConfig(config);
}
@SubscribeEvent
public static void onLoad(final ModConfig.Loading configEvent)
{
LogManager.getLogger().debug(ModInfo.MODNAME, "Loaded forge config file {}", configEvent.getConfig().getFileName());
}
@SubscribeEvent
public static void onFileChange(final ModConfig.Reloading configEvent)
{
LogManager.getLogger().debug(ModInfo.MODNAME, "Forge config just got changed on the file system!");
}
}
@@ -17,19 +17,29 @@
*/
package com.seibel.lod.handlers;
import java.io.BufferedReader;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.file.Files;
import java.nio.file.StandardCopyOption;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import com.seibel.lod.enums.LodDetail;
import com.seibel.lod.objects.LodChunk;
import com.seibel.lod.enums.DistanceGenerationMode;
import com.seibel.lod.enums.VerticalQuality;
import com.seibel.lod.objects.LodDimension;
import com.seibel.lod.objects.LodRegion;
import com.seibel.lod.objects.RegionPos;
import com.seibel.lod.objects.SingleLevelContainer;
import com.seibel.lod.objects.VerticalLevelContainer;
import com.seibel.lod.proxy.ClientProxy;
import com.seibel.lod.util.LodThreadFactory;
import com.seibel.lod.util.LodUtil;
/**
* This object handles creating LodRegions
@@ -37,178 +47,177 @@ import com.seibel.lod.proxy.ClientProxy;
* to file.
*
* @author James Seibel
* @version 6-27-2021
* @author Cola
* @version 9-25-2021
*/
public class LodDimensionFileHandler
{
/** This is what separates each piece of data */
public static final char DATA_DELIMITER = ',';
private LodDimension loadedDimension = null;
public long regionLastWriteTime[][];
/** This is the dimension that owns this file handler */
private LodDimension lodDimension = null;
private File dimensionDataSaveFolder;
/** lod */
private final String FILE_NAME_PREFIX = "lod";
private static final String FILE_NAME_PREFIX = "lod";
/** .txt */
private final String FILE_EXTENSION = ".txt";
private static final String FILE_EXTENSION = ".dat";
/** detail- */
private static final String DETAIL_FOLDER_NAME_PREFIX = "detail-";
/** This is the file version currently accepted by this
/**
* .tmp <br>
* Added to the end of the file path when saving to prevent
* nulling a currently existing file. <br>
* After the file finishes saving it will end with
* FILE_EXTENSION.
*/
private static final String TMP_FILE_EXTENSION = ".tmp";
/**
* This is the file version currently accepted by this
* file handler, older versions (smaller numbers) will be deleted and overwritten,
* newer versions (larger numbers) will be ignored and won't be read. */
public static final int LOD_SAVE_FILE_VERSION = 2;
* newer versions (larger numbers) will be ignored and won't be read.
*/
public static final int LOD_SAVE_FILE_VERSION = 6;
/** This is the string written before the file version */
private static final String LOD_FILE_VERSION_PREFIX = "lod_save_file_version";
/** Allow saving asynchronously, but never try to save multiple regions
* at a time */
private ExecutorService fileWritingThreadPool = Executors.newSingleThreadExecutor();
/**
* Allow saving asynchronously, but never try to save multiple regions
* at a time
*/
private ExecutorService fileWritingThreadPool = Executors.newSingleThreadExecutor(new LodThreadFactory(this.getClass().getSimpleName()));
public LodDimensionFileHandler(File newSaveFolder, LodDimension newLoadedDimension)
public LodDimensionFileHandler(File newSaveFolder, LodDimension newLodDimension)
{
if (newSaveFolder == null)
throw new IllegalArgumentException("LodDimensionFileHandler requires a valid File location to read and write to.");
dimensionDataSaveFolder = newSaveFolder;
loadedDimension = newLoadedDimension;
// these two variable are used in sync with the LodDimension
regionLastWriteTime = new long[loadedDimension.getWidth()][loadedDimension.getWidth()];
for(int i = 0; i < loadedDimension.getWidth(); i++)
for(int j = 0; j < loadedDimension.getWidth(); j++)
regionLastWriteTime[i][j] = -1;
lodDimension = newLodDimension;
}
//================//
// read from file //
//================//
/**
* Return the LodRegion at the given coordinates.
* (null if the file doesn't exist)
* Returns the LodRegion at the given coordinates.
* Returns an empty region if the file doesn't exist.
*/
public LodRegion loadRegionFromFile(int regionX, int regionZ)
public LodRegion loadRegionFromFile(byte detailLevel, RegionPos regionPos, DistanceGenerationMode generationMode, VerticalQuality verticalQuality)
{
int regionX = regionPos.x;
int regionZ = regionPos.z;
LodRegion region = new LodRegion(LodUtil.REGION_DETAIL_LEVEL, regionPos, generationMode, verticalQuality);
String fileName = getFileNameAndPathForRegion(regionX, regionZ, LodConfig.CLIENT.lodDetail.get());
File f = new File(fileName);
if (!f.exists())
for (byte tempDetailLevel = LodUtil.REGION_DETAIL_LEVEL; tempDetailLevel >= detailLevel; tempDetailLevel--)
{
// there wasn't a file, don't
// return anything
return null;
}
LodRegion region = new LodRegion(regionX, regionZ);
try
{
BufferedReader br = new BufferedReader(new FileReader(f));
String s = br.readLine();
int fileVersion = -1;
String fileName = getFileNameAndPathForRegion(regionX, regionZ, generationMode, tempDetailLevel, verticalQuality);
try
{
// if the fileName was null that means the folder is inaccessible
// for some reason
if (fileName == null)
throw new IllegalArgumentException("Unable to read region [" + regionX + ", " + regionZ + "] file, no fileName.");
File file = new File(fileName);
if (!file.exists())
{
// there wasn't a file, don't
// return anything
continue;
}
// don't try parsing empty files
long dataSize = file.length();
dataSize -= 1;
if (dataSize > 0)
{
try (InputStream inputStream = new BufferedInputStream(new FileInputStream(file)))
{
int fileVersion = -1;
fileVersion = inputStream.read();
if(s != null && !s.isEmpty())
{
// try to get the file version
try
{
fileVersion = Integer.parseInt(s.substring(s.indexOf(' ')).trim());
}
catch(NumberFormatException | StringIndexOutOfBoundsException e)
{
// this file doesn't have a version
// keep the version as -1
fileVersion = -1;
}
// check if this file can be read by this file handler
if(fileVersion < LOD_SAVE_FILE_VERSION)
{
// the file we are reading is an older version,
// close the reader and delete the file.
br.close();
f.delete();
ClientProxy.LOGGER.info("Outdated LOD region file for region: (" + regionX + "," + regionZ + ") version: " + fileVersion +
", version requested: " + LOD_SAVE_FILE_VERSION +
" File was been deleted.");
return null;
}
else if(fileVersion > LOD_SAVE_FILE_VERSION)
{
// the file we are reading is a newer version,
// close the reader and ignore the file, we don't
// want to accidently delete anything the user may want.
br.close();
ClientProxy.LOGGER.info("Newer LOD region file for region: (" + regionX + "," + regionZ + ") version: " + fileVersion +
", version requested: " + LOD_SAVE_FILE_VERSION +
" this region will not be written to in order to protect the newer file.");
return null;
}
// check if this file can be read by this file handler
if (fileVersion < LOD_SAVE_FILE_VERSION)
{
// the file we are reading is an older version,
// close the reader and delete the file.
inputStream.close();
file.delete();
ClientProxy.LOGGER.info("Outdated LOD region file for region: (" + regionX + "," + regionZ + ")"
+ " version found: " + fileVersion
+ ", version requested: " + LOD_SAVE_FILE_VERSION
+ ". File was been deleted.");
break;
}
else if (fileVersion > LOD_SAVE_FILE_VERSION)
{
// the file we are reading is a newer version,
// close the reader and ignore the file, we don't
// want to accidently delete anything the user may want.
inputStream.close();
ClientProxy.LOGGER.info("Newer LOD region file for region: (" + regionX + "," + regionZ + ")"
+ " version found: " + fileVersion
+ ", version requested: " + LOD_SAVE_FILE_VERSION
+ " this region will not be written to in order to protect the newer file.");
break;
}
// this file is a readable version,
// read the file
byte[] data = new byte[(int) dataSize];
inputStream.read(data);
inputStream.close();
// add the data to our region
switch (region.getVerticalQuality())
{
default:
case HEIGHTMAP:
region.addLevelContainer(new SingleLevelContainer(data));
break;
case VOXEL:
region.addLevelContainer(new VerticalLevelContainer(data));
break;
}
}
catch (IOException ioEx)
{
ClientProxy.LOGGER.error("LOD file read error. Unable to read to [" + fileName + "] error [" + ioEx.getMessage() + "]: ");
ioEx.printStackTrace();
}
}// file datasize > 0
}
else
catch (Exception e)
{
// there is no data in this file
br.close();
return null;
// the buffered reader encountered a
// problem reading the file
ClientProxy.LOGGER.error("LOD file read error. Unable to read to [" + fileName + "] error [" + e.getMessage() + "]: ");
e.printStackTrace();
}
// this file is a readable version, begin reading the file
s = br.readLine();
while(s != null && !s.isEmpty())
{
try
{
// convert each line into an LOD object and add it to the region
LodChunk lod = new LodChunk(s, LodConfig.CLIENT.lodDetail.get());
region.addLod(lod);
}
catch(IllegalArgumentException e)
{
// we were unable to create this chunk
// for whatever reason.
// skip to the next chunk
ClientProxy.LOGGER.warn(e.getMessage());
}
s = br.readLine();
}
br.close();
}
catch (IOException e)
{
// the buffered reader encountered a
// problem reading the file
return null;
}
}// for each detail level
if (region.getMinDetailLevel() >= detailLevel)
region.growTree(detailLevel);
return region;
}
//==============//
// Save to File //
//==============//
@@ -221,21 +230,29 @@ public class LodDimensionFileHandler
fileWritingThreadPool.execute(saveDirtyRegionsThread);
}
private Thread saveDirtyRegionsThread = new Thread(() ->
private Thread saveDirtyRegionsThread = new Thread(() ->
{
for(int i = 0; i < loadedDimension.getWidth(); i++)
try
{
for(int j = 0; j < loadedDimension.getWidth(); j++)
for (int i = 0; i < lodDimension.getWidth(); i++)
{
if(loadedDimension.isRegionDirty[i][j] && loadedDimension.regions[i][j] != null)
for (int j = 0; j < lodDimension.getWidth(); j++)
{
saveRegionToDisk(loadedDimension.regions[i][j]);
loadedDimension.isRegionDirty[i][j] = false;
// TODO shouldn't this use lodDimension.isRegionDirty?
if (lodDimension.doesRegionNeedBufferRegen(i,j) && lodDimension.getRegionByArrayIndex(i,j) != null)
{
saveRegionToFile(lodDimension.getRegionByArrayIndex(i,j));
lodDimension.setRegenRegionBufferByArrayIndex(i, j,false);
}
}
}
}
catch (Exception e)
{
e.printStackTrace();
}
});
/**
* Save a specific region to disk.<br>
* Note: <br>
@@ -244,88 +261,91 @@ public class LodDimensionFileHandler
* 2. This will save to the LodDimension that this
* handler is associated with.
*/
private void saveRegionToDisk(LodRegion region)
private void saveRegionToFile(LodRegion region)
{
// convert chunk coordinates to region
// coordinates
int x = region.x;
int z = region.z;
File f = new File(getFileNameAndPathForRegion(x, z, LodConfig.CLIENT.lodDetail.get()));
try
for (byte detailLevel = region.getMinDetailLevel(); detailLevel <= LodUtil.REGION_DETAIL_LEVEL; detailLevel++)
{
// make sure the file and folder exists
if (!f.exists())
String fileName = getFileNameAndPathForRegion(region.regionPosX, region.regionPosZ, region.getGenerationMode(), detailLevel, region.getVerticalQuality());
File oldFile = new File(fileName);
// if the fileName was null that means the folder is inaccessible
// for some reason
if (fileName == null)
{
// the file doesn't exist,
// create it and the folder if need be
if(!f.getParentFile().exists())
f.getParentFile().mkdirs();
f.createNewFile();
}
else
{
// the file exists, make sure it
// is the correct version.
// (to make sure we don't overwrite a newer
// version file if it exists)
BufferedReader br = new BufferedReader(new FileReader(f));
String s = br.readLine();
int fileVersion = LOD_SAVE_FILE_VERSION;
if(s != null && !s.isEmpty())
{
// try to get the file version
try
{
fileVersion = Integer.parseInt(s.substring(s.indexOf(' ')).trim());
}
catch(NumberFormatException | StringIndexOutOfBoundsException e)
{
// this file doesn't have a correctly formated version
// just overwrite the file
}
}
br.close();
// check if this file can be written to by the file handler
if(fileVersion <= LOD_SAVE_FILE_VERSION)
{
// we are good to continue and overwrite the old file
}
else //if(fileVersion > LOD_SAVE_FILE_VERSION)
{
// the file we are reading is a newer version,
// don't write anything, we don't want to accidently
// delete anything the user may want.
return;
}
ClientProxy.LOGGER.warn("Unable to save region [" + region.regionPosX + ", " + region.regionPosZ + "] to file, file is inaccessible.");
return;
}
FileWriter fw = new FileWriter(f);
// add the version of this file
fw.write(LOD_FILE_VERSION_PREFIX + " " + LOD_SAVE_FILE_VERSION + "\n");
// add each LodChunk to the file
for(LodChunk[] chunkArray : region.getAllLods())
for(LodChunk chunk : chunkArray)
if(chunk != null && !chunk.isPlaceholder())
fw.write(chunk.toData() + "\n");
fw.close();
}
catch(Exception e)
{
ClientProxy.LOGGER.error("LOD file write error: " + e.getMessage());
try
{
// make sure the file and folder exists
if (!oldFile.exists())
{
// the file doesn't exist,
// create it and the folder if need be
if (!oldFile.getParentFile().exists())
oldFile.getParentFile().mkdirs();
oldFile.createNewFile();
}
else
{
// the file exists, make sure it
// is the correct version.
// (to make sure we don't overwrite a newer
// version file if it exists)
int fileVersion = LOD_SAVE_FILE_VERSION;
try (InputStream inputStream = new BufferedInputStream(new FileInputStream(oldFile)))
{
fileVersion = inputStream.read();
inputStream.close();
}
catch (IOException ex)
{
ex.printStackTrace();
}
// check if this file can be written to by the file handler
if (fileVersion > LOD_SAVE_FILE_VERSION)
{
// the file we are reading is a newer version,
// don't write anything, we don't want to accidently
// delete anything the user may want.
return;
}
// if we got this far then we are good
// to overwrite the old file
}
// the old file is good, now create a new temporary save file
File newFile = new File(fileName + TMP_FILE_EXTENSION);
try (OutputStream outputStream = new BufferedOutputStream(new FileOutputStream(newFile)))
{
// add the version of this file
outputStream.write(LOD_SAVE_FILE_VERSION);
// add each LodChunk to the file
outputStream.write(region.getLevel(detailLevel).toDataString());
outputStream.close();
// overwrite the old file with the new one
Files.move(newFile.toPath(), oldFile.toPath(), StandardCopyOption.ATOMIC_MOVE, StandardCopyOption.REPLACE_EXISTING);
}
catch (IOException ex)
{
ex.printStackTrace();
}
}
catch (Exception e)
{
ClientProxy.LOGGER.error("LOD file write error. Unable to write to [" + fileName + "] error [" + e.getMessage() + "]: ");
e.printStackTrace();
}
}
}
@@ -335,25 +355,32 @@ public class LodDimensionFileHandler
/**
* Return the name of the file that should contain the
* Return the name of the file that should contain the
* region at the given x and z. <br>
* Returns null if this object isn't ready to read and write. <br><br>
*
* example: "lod.FULL.0.0.txt"
* Returns null if this object isn't available to read and write. <br><br>
* <p>
* example: "lod.0.0.txt" <br>
* <p>
* Returns null if there is an IO or security Exception.
*/
private String getFileNameAndPathForRegion(int regionX, int regionZ, LodDetail detail)
private String getFileNameAndPathForRegion(int regionX, int regionZ, DistanceGenerationMode generationMode, byte detailLevel, VerticalQuality verticalQuality)
{
try
{
// saveFolder is something like
// ".\Super Flat\DIM-1\data"
// ".\Super Flat\DIM-1\data\"
// or
// ".\Super Flat\data"
return dimensionDataSaveFolder.getCanonicalPath() + File.separatorChar +
FILE_NAME_PREFIX + "." + detail.toString() + "." + regionX + "." + regionZ + FILE_EXTENSION;
// ".\Super Flat\data\"
return dimensionDataSaveFolder.getCanonicalPath() + File.separatorChar +
verticalQuality + File.separatorChar +
generationMode.toString() + File.separatorChar +
DETAIL_FOLDER_NAME_PREFIX + detailLevel + File.separatorChar +
FILE_NAME_PREFIX + "." + regionX + "." + regionZ + FILE_EXTENSION;
}
catch(IOException e)
catch (IOException | SecurityException e)
{
ClientProxy.LOGGER.warn("Unable to get the filename for the region [" + regionX + ", " + regionZ + "], error: [" + e.getMessage() + "], stacktrace: ");
e.printStackTrace();
return null;
}
}
@@ -18,44 +18,48 @@
package com.seibel.lod.handlers;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import com.seibel.lod.enums.FogQuality;
import net.minecraft.client.Minecraft;
import com.seibel.lod.proxy.ClientProxy;
import com.seibel.lod.wrappers.MinecraftWrapper;
/**
* This object is used to get variables from methods
* where they are private. Specifically the fog setting
* in Optifine.
*
*
* @author James Seibel
* @version 7-03-2021
* @version 9-25-2021
*/
public class ReflectionHandler
{
private Minecraft mc = Minecraft.getInstance();
public Field ofFogField = null;
public static final ReflectionHandler INSTANCE = new ReflectionHandler();
private MinecraftWrapper mc = MinecraftWrapper.INSTANCE;
public ReflectionHandler()
public Field ofFogField = null;
public Method vertexBufferUploadMethod = null;
private ReflectionHandler()
{
setupFogField();
}
/**
* Similar to setupFovMethod.
* finds the Optifine fog type field
*/
private void setupFogField()
{
// get every variable from the entity renderer
Field[] optionFields = mc.options.getClass().getDeclaredFields();
Field[] optionFields = mc.getOptions().getClass().getDeclaredFields();
// try and find the ofFogType variable in gameSettings
for(Field field : optionFields)
for (Field field : optionFields)
{
if(field.getName().equals("ofFogType"))
if (field.getName().equals("ofFogType"))
{
ofFogField = field;
return;
@@ -65,13 +69,12 @@ public class ReflectionHandler
// we didn't find the field,
// either optifine isn't installed, or
// optifine changed the name of the variable
ofFogField = null;
ClientProxy.LOGGER.info(ReflectionHandler.class.getSimpleName() + ": unable to find the Optifine fog field. If Optifine isn't installed this can be ignored.");
}
/**
* Get what type of fog optifine is currently set to render.
*/
@@ -89,7 +92,7 @@ public class ReflectionHandler
try
{
returnNum = (int)ofFogField.get(mc.options);
returnNum = (int) ofFogField.get(mc.getOptions());
}
catch (IllegalArgumentException | IllegalAccessException e)
{
@@ -98,22 +101,20 @@ public class ReflectionHandler
switch (returnNum)
{
default:
case 0:
// optifine's "default" option,
// it should never be called in this case
case 0:
return FogQuality.FAST;
return FogQuality.FAST;
// normal options
case 1:
return FogQuality.FAST;
case 2:
return FogQuality.FANCY;
case 3:
return FogQuality.OFF;
default:
return FogQuality.FAST;
case 1:
return FogQuality.FAST;
case 2:
return FogQuality.FANCY;
case 3:
return FogQuality.OFF;
}
}
}
@@ -24,7 +24,7 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import com.mojang.blaze3d.matrix.MatrixStack;
import com.seibel.lod.LodMain;
import com.seibel.lod.handlers.LodConfig;
import com.seibel.lod.config.LodConfig;
import net.minecraft.client.renderer.RenderType;
import net.minecraft.client.renderer.WorldRenderer;
@@ -32,11 +32,12 @@ import net.minecraft.client.renderer.WorldRenderer;
/**
* This class is used to mix in my rendering code
* before Minecraft starts rendering blocks.
* If this wasn't done the LODs would render on top
* If this wasn't done and we used Forge's
* render last event, the LODs would render on top
* of the normal terrain.
*
* @author James Seibel
* @version 05-29-2021
* @version 9-19-2021
*/
@Mixin(WorldRenderer.class)
public class MixinWorldRenderer
@@ -56,7 +57,7 @@ public class MixinWorldRenderer
{
// only render if LODs are enabled and
// only render before solid blocks
if (LodConfig.CLIENT.drawLODs.get() && renderType.equals(RenderType.solid()))
LodMain.client_proxy.renderLods(previousPartialTicks);
if (LodConfig.CLIENT.graphics.drawLods.get() && renderType.equals(RenderType.solid()))
LodMain.client_proxy.renderLods(matrixStackIn, previousPartialTicks);
}
}
@@ -0,0 +1,93 @@
package com.seibel.lod.objects;
public interface LevelContainer
{
public static final char VERTICAL_DATA_DELIMITER = '\t';
public static final char DATA_DELIMITER = ' ';
/**With this you can add data to the level container
*
* @param data actual data to add in a array of long format.
* @param posX x position in the detail level
* @param posZ z position in the detail level
* @param index z position in the detail level
* @return true if correctly added, false otherwise
*/
public boolean addData(long data, int posX, int posZ, int index);
/**With this you can add data to the level container
*
* @param data actual data to add in a array of long format.
* @param posX x position in the detail level
* @param posZ z position in the detail level
* @return true if correctly added, false otherwise
*/
public boolean addSingleData(long data, int posX, int posZ);
/**With this you can get data from the level container
*
* @param posX x position in the detail level
* @param posZ z position in the detail level
* @return the data in long array format
*/
public long getData(int posX, int posZ, int index);
/**With this you can get data from the level container
*
* @param posX x position in the detail level
* @param posZ z position in the detail level
* @return the data in long array format
*/
public long getSingleData(int posX, int posZ);
/**
* @param posX x position in the detail level
* @param posZ z position in the detail level
* @return true only if the data exist
*/
public boolean doesItExist(int posX, int posZ);
/**
* @return return the deatilLevel of this level container
*/
public byte getDetailLevel();
public int getMaxVerticalData();
/** Clears the dataPoint at the given array index */
public void clear(int posX, int posZ);
/**This return a level container with detail level lower than the current level.
* The new level container may use information of this level.
* @return the new level container
*/
public LevelContainer expand();
/**
*
* @param lowerLevelContainer lower level where we extract the data
* @param posX x position in the detail level to update
* @param posZ z position in the detail level to update
*/
public void updateData(LevelContainer lowerLevelContainer, int posX, int posZ);
/**
* This will give the data to save in the file
* @return data as a String
*/
public byte[] toDataString();
/**
* This will give the data to save in the file
* @return data as a String
*/
public int getMaxNumberOfLods();
/**
* This will give the data to save in the file
* @return data as a String
*/
public int getMaxMemoryUse();
}
@@ -1,503 +0,0 @@
/*
* This file is part of the LOD Mod, licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.lod.objects;
import java.awt.Color;
import com.seibel.lod.enums.LodDetail;
import com.seibel.lod.handlers.LodDimensionFileHandler;
import net.minecraft.util.math.ChunkPos;
import net.minecraft.world.gen.Heightmap;
/**
* This object contains position
* and color data for an LOD object.
*
* @author James Seibel
* @version 6-27-2021
*/
public class LodChunk
{
/** This is what separates each piece of data in the toData method */
private static final char DATA_DELIMITER = LodDimensionFileHandler.DATA_DELIMITER;
/** Width of a Minecraft Chunk */
public static final int WIDTH = 16;
/** alpha used when drawing chunks in debug mode */
private static final int DEBUG_ALPHA = 255; // 0 - 255
private static final Color DEBUG_BLACK = new Color(0, 0, 0, DEBUG_ALPHA);
private static final Color DEBUG_WHITE = new Color(255, 255, 255, DEBUG_ALPHA);
private static final Color INVISIBLE = new Color(0,0,0,0);
/** If we ever have to use a heightmap for any reason, use this one. */
public static final Heightmap.Type DEFAULT_HEIGHTMAP = Heightmap.Type.WORLD_SURFACE_WG;
public LodDetail detail = LodDetail.SINGLE;
/** If this is set to true then toData will return
* the empty string */
public boolean dontSave = false;
// TODO store the DistanceGenerationMethod used for this chunk (so we can upgrade old chunks if we want to)
/** The x coordinate of the chunk. */
public int x = 0;
/** The z coordinate of the chunk. */
public int z = 0;
/** This stores the height and color for each data point in the LodChunk */
public LodDataPoint dataPoints[][];
/** If true then this LodChunk contains no data */
private boolean empty = false;
/**
* Create an empty, invisible, non-saving LodChunk at (0,0)
*/
public LodChunk()
{
dontSave = true;
empty = true;
x = 0;
z = 0;
detail = LodDetail.SINGLE;
dataPoints = new LodDataPoint[detail.dataPointLengthCount][detail.dataPointLengthCount];
// fill with dummy data, to prevent null pointers
for(int i = 0; i < detail.dataPointLengthCount; i++)
for(int j = 0; j < detail.dataPointLengthCount; j++)
dataPoints[i][j] = new LodDataPoint();
}
/**
* Create an empty, invisible, non-saving LodChunk at the given ChunkPos
*/
public LodChunk(ChunkPos pos)
{
this();
x = pos.x;
z = pos.z;
}
/**
* Create an empty, invisible, non-saving LodChunk at the given ChunkPos
*/
public LodChunk(int newX, int newZ)
{
this();
x = newX;
z = newZ;
}
/**
* Creates an LodChunk from the string
* generated by the toData method.
*
* @throws IllegalArgumentException if the data isn't valid to create a LodChunk
* @throws NumberFormatException if any piece of data can't be converted at any point
*/
public LodChunk(String data, LodDetail newDetail) throws IllegalArgumentException, NumberFormatException
{
/*
* data format:
* x, z, dataPoint[0][0], dataPoint[0][1], ...
* x, z, height, depth, rgb color data, height, depth, rgb....
*
* example:
* 5,8, 4, 0, 255,255,255, 4, 0, 255, 255, 255, ...
*/
dontSave = false;
// make sure there are the correct number of entries
// in the data string
int count = 0;
detail = newDetail;
for(int i = 0; i < data.length(); i++)
if(data.charAt(i) == DATA_DELIMITER)
count++;
if(count != detail.lodChunkStringDelimiterCount)
throw new IllegalArgumentException("LodChunk constructor givin an invalid string. The data given had " + count + " delimiters when it should have had " + detail.lodChunkStringDelimiterCount + ".");
dataPoints = new LodDataPoint[detail.dataPointLengthCount][detail.dataPointLengthCount];
// index we will use when going through the String
int index = 0;
int lastIndex = 0;
// x and z position
index = data.indexOf(DATA_DELIMITER, 0);
this.x = Integer.parseInt(data.substring(0,index));
lastIndex = index;
index = data.indexOf(DATA_DELIMITER, lastIndex+1);
this.z = Integer.parseInt(data.substring(lastIndex+1,index));
// LodDataPoints
for(int blockX = 0; blockX < detail.dataPointLengthCount; blockX++)
{
for(int blockZ = 0; blockZ < detail.dataPointLengthCount; blockZ++)
{
// height
lastIndex = index;
index = data.indexOf(DATA_DELIMITER, lastIndex+1);
int height = Short.parseShort(data.substring(lastIndex+1,index));
// depth
lastIndex = index;
index = data.indexOf(DATA_DELIMITER, lastIndex+1);
int depth = Short.parseShort(data.substring(lastIndex+1,index));
// color
int red = 0;
int green = 0;
int blue = 0;
// get r,g,b
for(int i = 0; i < 3; i++)
{
lastIndex = index;
index = data.indexOf(DATA_DELIMITER, lastIndex + 1);
String raw = "";
switch(i)
{
case 0:
raw = data.substring(lastIndex+1,index);
red = Short.parseShort(raw);
break;
case 1:
raw = data.substring(lastIndex+1,index);
green = Short.parseShort(raw);
break;
case 2:
raw = data.substring(lastIndex+1,index);
blue = Short.parseShort(raw);
break;
}
}
dataPoints[blockX][blockZ] = new LodDataPoint((short)height, (short)depth, new Color(red, green, blue));
}
}
empty = determineIfEmtpy();
}
/**
* Create a LodChunk from the given values.
*/
public LodChunk(ChunkPos pos, LodDataPoint[][] newDataPoints, LodDetail newDetail)
{
x = pos.x;
z = pos.z;
dataPoints = newDataPoints;
dontSave = false;
detail = newDetail;
empty = determineIfEmtpy();
}
//================//
// misc functions //
//================//
/**
* Returns true if this LodChunk is an emptyPlaceholder
*/
public boolean isPlaceholder()
{
return empty;
}
public boolean isLodEmpty()
{
return empty;
}
/**
* Returns true if this LOD is either invisible
* from every direction or doesn't have a valid height.
*/
private boolean determineIfEmtpy()
{
for(LodDataPoint[] dataPointArray : dataPoints)
{
for(LodDataPoint dataPoint : dataPointArray)
{
if (dataPoint == null)
continue;
// we don't check the depth since the
// height should always be greater than or equal
// to the depth
if(dataPoint.height >= 0)
{
// the height is valid,
if (dataPoint.color == INVISIBLE)
continue;
// the color and height are valid
// this LodChunk isn't empty
return false;
}
}
}
// we checked everywhere, this LodChunk is empty
return true;
}
//========//
// output //
//========//
/**
* Returns the data point for the given relative block position.
*/
public LodDataPoint getDataPointForBlockPos(int blockX, int blockZ)
{
return dataPoints[blockX / detail.dataPointWidth][blockZ / detail.dataPointWidth];
}
public Color getColorForBlockPos(int blockX, int blockZ)
{
return getDataPointForBlockPos(blockX, blockZ).color;
}
public short getHeightForBlockPos(int blockX, int blockZ)
{
return getDataPointForBlockPos(blockX, blockZ).height;
}
public short getDepthForBlockPos(int blockX, int blockZ)
{
return getDataPointForBlockPos(blockX, blockZ).depth;
}
public Color getColor(int xIndex, int zIndex)
{
return dataPoints[xIndex][zIndex].color;
}
public short getHeight(int xIndex, int zIndex)
{
return dataPoints[xIndex][zIndex].height;
}
public short getDepth(int xIndex, int zIndex)
{
return dataPoints[xIndex][zIndex].depth;
}
public short calculateHighestPoint()
{
short highest = 0;
for(int x = 0; x < detail.dataPointLengthCount; x++)
{
for(int z = 0; z < detail.dataPointLengthCount; z++)
{
if (getHeight(x,z) > highest)
highest = getHeight(x,z);
}
}
return highest;
}
/**
* @param startX
* @param startZ
* @param endX
* @param endZ
* @return
*/
public short getAverageHeightOverArea(int startX, int startZ, int endX, int endZ)
{
if (startX == endX || startZ == endZ)
// we were given an area with 0 blocks in it
return getHeightForBlockPos(startX,startZ);
int average = 0;
for(int x = startX; x < endX; x++)
for(int z = startZ; z < endZ; z++)
average += getHeightForBlockPos(x,z);
return (short) (average / ((endX - startX) * (endZ - startZ)));
}
/**
* @param startX
* @param startZ
* @param endX
* @param endZ
* @return
*/
public short getAverageDepthOverArea(int startX, int startZ, int endX, int endZ)
{
if (startX == endX || startZ == endZ)
// we were given an area with 0 blocks in it
return getDepthForBlockPos(startX,startZ);
int average = 0;
for(int x = startX; x < endX; x++)
for(int z = startZ; z < endZ; z++)
average += getDepthForBlockPos(x,z);
return (short) (average / ((endX - startX) * (endZ - startZ)));
}
/**
* Determine the color of this LOD.
*/
public Color getAverageColorOverArea(int startX, int startZ, int endX, int endZ, boolean debugging)
{
if (startX == endX || startZ == endZ)
// we were given an area with 0 blocks in it
return getColorForBlockPos(startX,startZ);
int[] colorComponents = new int[3];
if (debugging)
{
// draw the squares as a black and white,
// like a checker board
// only return 1 color to prevent
// getting back gray
if ((startX + startZ) % 2 == 0)
return DEBUG_WHITE;
else
return DEBUG_BLACK;
}
for(int x = startX; x < endX; x++)
{
for(int z = startZ; z < endZ; z++)
{
colorComponents = addColorToColorAverages(colorComponents, getColorForBlockPos(x,z));
}
}
int numbPoints = ((endX - startX) * (endZ - startZ));
return new Color(colorComponents[0]/numbPoints, colorComponents[1]/numbPoints, colorComponents[2]/numbPoints);
}
private int[] addColorToColorAverages(int[] colorAverages, Color colorToAdd)
{
// convert the colorToAdd to an int array
float[] colorCompoments = new float[4];
colorCompoments = colorToAdd.getColorComponents(colorCompoments);
// add each color component to the array
for(int rgbIndex = 0; rgbIndex < 3; rgbIndex++)
{
// * 255 + 0.5 taken from the Color java class
colorAverages[rgbIndex] += (int) (colorCompoments[rgbIndex] * 255 + 0.5);
}
return colorAverages;
}
/**
* Outputs all data in a csv format
* with the given delimiter, if
* dontSave is false. <br><br>
*
* data format: <br>
* x, z, dataPoint[0][0], dataPoint[0][1], ... <br>
* x, z, height, depth, rgb color data, height, depth, rgb.... <br><br>
*
* example: <br>
* 5,8, 4, 0, 255,255,255, 4, 0, 255, 255, 255, ...
*/
public String toData()
{
if (dontSave)
return "";
String s = "";
s += Integer.toString(x) + DATA_DELIMITER + Integer.toString(z) + DATA_DELIMITER;
for (int i = 0; i < dataPoints.length; i++)
for (int j = 0; j < dataPoints[i].length; j++)
s += dataPoints[i][j].toData();
return s;
}
@Override
public String toString()
{
String s = "";
s += "x: " + x + " z: " + z + "\t";
return s;
}
}
@@ -1,112 +0,0 @@
/*
* This file is part of the LOD Mod, licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.lod.objects;
import java.awt.Color;
import com.seibel.lod.handlers.LodDimensionFileHandler;
/**
* This stores the height and color
* for a specific area in a LodChunk.
*
* @author James Seibel
* @version 6-19-2021
*/
public class LodDataPoint
{
/** This is what separates each piece of data in the toData method */
private static final char DATA_DELIMITER = LodDimensionFileHandler.DATA_DELIMITER;
/** this is how many pieces of data are exported when toData is called */
public static final int NUMBER_OF_DELIMITERS = 5;
private static final Color INVISIBLE = new Color(0,0,0,0);
/** highest point */
public short height;
/** lowest point */
public short depth;
/** The average color for the 6 cardinal directions */
public Color color;
/**
* Creates and empty LodDataPoint
*/
public LodDataPoint()
{
height = -1;
depth = -1;
color = INVISIBLE;
}
public LodDataPoint(short newHeight, short newDepth, Color newColor)
{
height = newHeight;
depth = newDepth;
color = newColor;
}
public LodDataPoint(int newHeight, int newDepth, Color newColor)
{
height = (short) newHeight;
depth = (short) newDepth;
color = newColor;
}
/**
* Outputs all data in a csv format
* with the given delimiter.
* <br>
* Exports data in the form:
* <br>
* height, depth, rgb color data
*
* <br>
* example output:
* <br>
* 4, 0, 255,255,255,
*/
public String toData()
{
String s = Short.toString(height) + DATA_DELIMITER;
s += Short.toString(depth) + DATA_DELIMITER;
s += Integer.toString(color.getRed()) + DATA_DELIMITER + Integer.toString(color.getGreen()) + DATA_DELIMITER + Integer.toString(color.getBlue()) + DATA_DELIMITER;
return s;
}
@Override
public String toString()
{
String s = Short.toString(height) + DATA_DELIMITER;
s += Short.toString(depth) + DATA_DELIMITER;
s += Integer.toString(color.getRed()) + DATA_DELIMITER + Integer.toString(color.getGreen()) + DATA_DELIMITER + Integer.toString(color.getBlue()) + DATA_DELIMITER;
return s;
}
}
File diff suppressed because it is too large Load Diff
@@ -1,104 +1,574 @@
/*
* This file is part of the LOD Mod, licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.lod.objects;
import com.seibel.lod.config.LodConfig;
import com.seibel.lod.enums.DistanceGenerationMode;
import com.seibel.lod.enums.LodTemplate;
import com.seibel.lod.enums.VerticalQuality;
import com.seibel.lod.util.DataPointUtil;
import com.seibel.lod.util.DetailDistanceUtil;
import com.seibel.lod.util.LevelPosUtil;
import com.seibel.lod.util.LodUtil;
/**
* A LodRegion is the a 32x32
* 2D array of LodChunk objects.
* Each LodRegion corresponds to
* one file in the file system.
*
* @author James Seibel
* @version 6-12-2021
* This object holds all loaded LevelContainers
* for a given region. <Br><Br>
*
* <strong>Coordinate Standard: </strong><br>
* Coordinate called posX or posZ are relative LevelPos coordinates <br>
* unless stated otherwise. <br>
*
* @author Leonardo Amato
* @version 9-27-2021
*/
public class LodRegion
{
/** number of chunks wide */
public static final int SIZE = 32;
/** X coordinate of this region */
public final int x;
/** Z coordinate of this region */
public final int z;
private LodChunk chunks[][];
public LodRegion(int regionX, int regionZ)
{
x = regionX;
z = regionZ;
chunks = new LodChunk[SIZE][SIZE];
}
/**
* Add the given LOD to this region at the coordinate
* stored in the LOD. If an LOD already exists at the given
* coordinates it will be overwritten.
* TODO what does this represent, and should it be defined here?
*/
public void addLod(LodChunk lod)
{
// we use ABS since LODs can be negative, but if they are
// the region will negative first, therefore we don't have to
// store the LOD chunks at negative indexes since we search
// LOD the region first
int xIndex = Math.abs(lod.x % SIZE);
int zIndex = Math.abs(lod.z % SIZE);
chunks[xIndex][zIndex] = lod;
}
private static final byte POSSIBLE_LOD = 10;
/**
* Get the LodChunk at the given X and Z coordinates
* in this region.
* <br>
* Returns null if the LodChunk doesn't exist or
* is outside the loaded area.
* Holds the lowest (least detailed) detail level in this region
* TODO is that correct?
*/
public LodChunk getLod(int chunkX, int chunkZ)
{
// since we add LOD's with ABS, we get them the same way
int arrayX = Math.abs(chunkX % SIZE);
int arrayZ = Math.abs(chunkZ % SIZE);
return chunks[arrayX][arrayZ];
}
private byte minDetailLevel;
/**
* Returns all LodChunks in this region
* This holds all data for this region
*/
public LodChunk[][] getAllLods()
private LevelContainer[] dataContainer;
/**
* the generation mode for this region
* TODO will this ever change through a region's life?
*/
private DistanceGenerationMode generationMode;
/**
* the vertical quality of this region
*/
private VerticalQuality verticalQuality;
/**
* this region's x RegionPos
*/
public final int regionPosX;
/**
* this region's z RegionPos
*/
public final int regionPosZ;
public LodRegion(RegionPos regionPos)
{
return chunks;
this.minDetailLevel = LodUtil.REGION_DETAIL_LEVEL;
this.regionPosX = regionPos.x;
this.regionPosZ = regionPos.z;
dataContainer = new LevelContainer[POSSIBLE_LOD];
}
public LodRegion(byte minDetailLevel, RegionPos regionPos, DistanceGenerationMode generationMode, VerticalQuality verticalQuality)
{
this.minDetailLevel = minDetailLevel;
this.regionPosX = regionPos.x;
this.regionPosZ = regionPos.z;
this.verticalQuality = verticalQuality;
this.generationMode = generationMode;
dataContainer = new LevelContainer[POSSIBLE_LOD];
// Initialize all the different matrices
for (byte lod = minDetailLevel; lod <= LodUtil.REGION_DETAIL_LEVEL; lod++)
{
switch (verticalQuality)
{
default:
case HEIGHTMAP:
dataContainer[lod] = new SingleLevelContainer(lod);
break;
case VOXEL:
dataContainer[lod] = new VerticalLevelContainer(lod);
break;
}
}
}
/**
* Inserts the data point into the region.
* <p>
* TODO this will always return true unless it has
*
* @return true if the data was added successfully
*/
public boolean addData(byte detailLevel, int posX, int posZ, int verticalIndex, long data)
{
posX = LevelPosUtil.getRegionModule(detailLevel, posX);
posZ = LevelPosUtil.getRegionModule(detailLevel, posZ);
// The dataContainer could have null entries if the
// detailLevel changes.
if (this.dataContainer[detailLevel] == null)
{
if (verticalQuality == VerticalQuality.HEIGHTMAP)
this.dataContainer[detailLevel] = new SingleLevelContainer(detailLevel);
else
this.dataContainer[detailLevel] = new VerticalLevelContainer(detailLevel);
}
this.dataContainer[detailLevel].addData(data, posX, posZ, verticalIndex);
return true;
}
/**
* Get the dataPoint at the given relative position.
*
* @return the data at the relative pos and detail level,
* 0 if the data doesn't exist.
*/
public long getData(byte detailLevel, int posX, int posZ, int verticalIndex)
{
return dataContainer[detailLevel].getData(posX, posZ, verticalIndex);
}
/**
* Get the dataPoint at the given relative position.
*
* @return the data at the relative pos and detail level,
* 0 if the data doesn't exist.
*/
public long getSingleData(byte detailLevel, int posX, int posZ)
{
return dataContainer[detailLevel].getSingleData(posX, posZ);
}
/**
* Clears the datapoint at the given relative position
*/
public void clear(byte detailLevel, int posX, int posZ)
{
dataContainer[detailLevel].clear(posX, posZ);
}
/**
* This method will fill the posToGenerate array with all levelPos that
* are render-able.
* <p>
* TODO why don't we return the posToGenerate, it would make this easier to understand
*/
public void getDataToGenerate(PosToGenerateContainer posToGenerate,
int playerBlockPosX, int playerBlockPosZ)
{
getDataToGenerate(posToGenerate, LodUtil.REGION_DETAIL_LEVEL, 0, 0, playerBlockPosX, playerBlockPosZ);
}
/**
* A recursive method that fills the posToGenerate array with all levelPos that
* need to be generated.
* <p>
* TODO why don't we return the posToGenerate, it would make this easier to understand
*/
private void getDataToGenerate(PosToGenerateContainer posToGenerate, byte detailLevel,
int childOffsetPosX, int childOffsetPosZ, int playerPosX, int playerPosZ)
{
// equivalent to 2^(...)
int size = 1 << (LodUtil.REGION_DETAIL_LEVEL - detailLevel);
// calculate what LevelPos are in range to generate
int maxDistance = LevelPosUtil.maxDistance(detailLevel, childOffsetPosX, childOffsetPosZ, playerPosX, playerPosZ, regionPosX, regionPosZ);
// determine this child's levelPos
byte childDetailLevel = (byte) (detailLevel - 1);
int childPosX = childOffsetPosX * 2;
int childPosZ = childOffsetPosZ * 2;
int childSize = 1 << (LodUtil.REGION_DETAIL_LEVEL - childDetailLevel);
byte targetDetailLevel = DetailDistanceUtil.getLodGenDetail(DetailDistanceUtil.getGenerationDetailFromDistance(maxDistance)).detailLevel;
if (targetDetailLevel > detailLevel)
{
// we have gone beyond the target Detail level
// we can stop generating
return;
} else if (targetDetailLevel == detailLevel)
{
if (!doesDataExist(detailLevel, childOffsetPosX, childOffsetPosZ))
posToGenerate.addPosToGenerate(detailLevel, childOffsetPosX + regionPosX * size, childOffsetPosZ + regionPosZ * size);
} else
{
// we want at max one request per chunk (since the world generator creates chunks).
// So for lod smaller than a chunk, only recurse down
// the top right child
if (detailLevel > LodUtil.CHUNK_DETAIL_LEVEL)
{
int ungeneratedChildren = 0;
// make sure all children are generated to this detailLevel
for (int x = 0; x <= 1; x++)
{
for (int z = 0; z <= 1; z++)
{
if (!doesDataExist(childDetailLevel, childPosX + x, childPosZ + z))
{
ungeneratedChildren++;
posToGenerate.addPosToGenerate(childDetailLevel, childPosX + x + regionPosX * childSize, childPosZ + z + regionPosZ * childSize);
}
}
}
// only if all the children are correctly generated
// should we go deeper
if (ungeneratedChildren == 0)
for (int x = 0; x <= 1; x++)
for (int z = 0; z <= 1; z++)
getDataToGenerate(posToGenerate, childDetailLevel, childPosX + x, childPosZ + z, playerPosX, playerPosZ);
} else
{
// The detail Level is smaller than a chunk.
// Only recurse down the top right child.
if (DetailDistanceUtil.getLodGenDetail(childDetailLevel).detailLevel <= (childDetailLevel))
{
if (!doesDataExist(childDetailLevel, childPosX, childPosZ))
posToGenerate.addPosToGenerate(childDetailLevel, childPosX + regionPosX * childSize, childPosZ + regionPosZ * childSize);
else
getDataToGenerate(posToGenerate, childDetailLevel, childPosX, childPosZ, playerPosX, playerPosZ);
}
}
}
}
/**
* This method will fill the posToRender array with all levelPos that
* are render-able.
* <p>
* TODO why don't we return the posToRender, it would make this easier to understand
*/
public void getDataToRender(PosToRenderContainer posToRender,
int playerPosX, int playerPosZ, boolean requireCorrectDetailLevel)
{
getDataToRender(posToRender, LodUtil.REGION_DETAIL_LEVEL, 0, 0, playerPosX, playerPosZ, requireCorrectDetailLevel);
}
/**
* This method will fill the posToRender array with all levelPos that
* are render-able.
* <p>
* TODO why don't we return the posToRender, it would make this easier to understand
* TODO this needs some more comments, James was only able to figure out part of it
*/
private void getDataToRender(PosToRenderContainer posToRender,
byte detailLevel, int posX, int posZ,
int playerPosX, int playerPosZ, boolean requireCorrectDetailLevel)
{
// equivalent to 2^(...)
int size = 1 << (LodUtil.REGION_DETAIL_LEVEL - detailLevel);
byte desiredLevel;
int maxDistance;
boolean stopNow = false;
int minDistance;
int childLevel;
// calculate the LevelPos that are in range
switch (LodConfig.CLIENT.graphics.detailDropOff.get())
{
case FAST:
int playerRegionX = LevelPosUtil.getRegion(LodUtil.BLOCK_DETAIL_LEVEL, playerPosX);
int playerRegionZ = LevelPosUtil.getRegion(LodUtil.BLOCK_DETAIL_LEVEL, playerPosZ);
if (playerRegionX == regionPosX && playerRegionZ == regionPosZ)
{
maxDistance = LevelPosUtil.maxDistance(detailLevel, posX, posZ, playerPosX, playerPosZ, regionPosX, regionPosZ);
desiredLevel = DetailDistanceUtil.getLodDrawDetail(DetailDistanceUtil.getDrawDetailFromDistance(maxDistance));
minDistance = LevelPosUtil.minDistance(detailLevel, posX, posZ, playerPosX, playerPosZ, regionPosX, regionPosZ);
childLevel = DetailDistanceUtil.getLodDrawDetail(DetailDistanceUtil.getDrawDetailFromDistance(minDistance));
stopNow = detailLevel == childLevel - 1;
break;
}
default:
case FANCY:
maxDistance = LevelPosUtil.maxDistance(detailLevel, posX, posZ, playerPosX, playerPosZ, regionPosX, regionPosZ);
desiredLevel = DetailDistanceUtil.getLodDrawDetail(DetailDistanceUtil.getDrawDetailFromDistance(maxDistance));
minDistance = LevelPosUtil.minDistance(detailLevel, posX, posZ, playerPosX, playerPosZ, regionPosX, regionPosZ);
childLevel = DetailDistanceUtil.getLodDrawDetail(DetailDistanceUtil.getDrawDetailFromDistance(minDistance));
stopNow = detailLevel == childLevel - 1;
break;
}
if (stopNow)
{
posToRender.addPosToRender(detailLevel,
posX + regionPosX * size,
posZ + regionPosZ * size);
} else if (desiredLevel > detailLevel)
{
// we have gone beyond the target Detail level
// we can stop generating
return;
} else if (desiredLevel == detailLevel)
{
posToRender.addPosToRender(detailLevel,
posX + regionPosX * size,
posZ + regionPosZ * size);
} else //case where (detailLevel > desiredLevel)
{
int childPosX = posX * 2;
int childPosZ = posZ * 2;
byte childDetailLevel = (byte) (detailLevel - 1);
int childrenCount = 0;
for (int x = 0; x <= 1; x++)
{
for (int z = 0; z <= 1; z++)
{
if (doesDataExist(childDetailLevel, childPosX + x, childPosZ + z))
{
if (!requireCorrectDetailLevel)
childrenCount++;
else
getDataToRender(posToRender, childDetailLevel, childPosX + x, childPosZ + z, playerPosX, playerPosZ, requireCorrectDetailLevel);
}
}
}
if (!requireCorrectDetailLevel)
{
// If all the four children exist go deeper
if (childrenCount == 4)
{
for (int x = 0; x <= 1; x++)
for (int z = 0; z <= 1; z++)
getDataToRender(posToRender, childDetailLevel, childPosX + x, childPosZ + z, playerPosX, playerPosZ, requireCorrectDetailLevel);
} else
{
posToRender.addPosToRender(detailLevel,
posX + regionPosX * size,
posZ + regionPosZ * size);
}
}
}
}
/**
* Updates all children.
* <p>
* TODO could this be renamed mergeArea?
*/
public void updateArea(byte detailLevel, int posX, int posZ)
{
int width;
int startX;
int startZ;
// TODO what are each of these loops updating?
for (byte down = (byte) (minDetailLevel + 1); down <= detailLevel; down++)
{
startX = LevelPosUtil.convert(detailLevel, posX, down);
startZ = LevelPosUtil.convert(detailLevel, posZ, down);
width = 1 << (detailLevel - down);
for (int x = 0; x < width; x++)
for (int z = 0; z < width; z++)
update(down, startX + x, startZ + z);
}
for (byte up = (byte) (detailLevel + 1); up <= LodUtil.REGION_DETAIL_LEVEL; up++)
{
update(up,
LevelPosUtil.convert(detailLevel, posX, up),
LevelPosUtil.convert(detailLevel, posZ, up));
}
}
/**
* Update the child at the given relative Pos
* <p>
* TODO could this be renamed mergeChildData?
*/
private void update(byte detailLevel, int posX, int posZ)
{
dataContainer[detailLevel].updateData(dataContainer[detailLevel - 1], posX, posZ);
}
/**
* Returns if data exists at the given relative Pos.
*/
public boolean doesDataExist(byte detailLevel, int posX, int posZ)
{
if (detailLevel < minDetailLevel)
return false;
posX = LevelPosUtil.getRegionModule(detailLevel, posX);
posZ = LevelPosUtil.getRegionModule(detailLevel, posZ);
if (dataContainer == null || dataContainer[detailLevel] == null)
return false;
return dataContainer[detailLevel].doesItExist(posX, posZ);
}
/**
* Gets the generation mode for the data point at the given relative pos.
*/
public byte getGenerationMode(byte detailLevel, int posX, int posZ)
{
if (dataContainer[detailLevel].doesItExist(posX, posZ))
// We take the bottom information always
// TODO what does that mean? bottom of what?
return DataPointUtil.getGenerationMode(dataContainer[detailLevel].getSingleData(posX, posZ));
else
return DistanceGenerationMode.NONE.complexity;
}
/**
* Returns the lowest detail level in this region
* TODO is that right?
*/
public byte getMinDetailLevel()
{
return minDetailLevel;
}
/**
* Returns the LevelContainer for the detailLevel
*
* @throws IllegalArgumentException if the detailLevel is less than minDetailLevel
*/
public LevelContainer getLevel(byte detailLevel)
{
if (detailLevel < minDetailLevel)
throw new IllegalArgumentException("getLevel asked for a detail level that does not exist: minimum: [" + minDetailLevel + "] level requested: [" + detailLevel + "]");
return dataContainer[detailLevel];
}
/**
* Add the levelContainer to this Region, updating the minDetailLevel
* if necessary.
*
* @throws IllegalArgumentException if the LevelContainer's detailLevel
* is 2 or more detail levels lower than the
* minDetailLevel of this region.
*/
public void addLevelContainer(LevelContainer levelContainer)
{
if (levelContainer.getDetailLevel() < minDetailLevel - 1)
{
throw new IllegalArgumentException(
"the LevelContainer's detailLevel was "
+ "[" + levelContainer.getDetailLevel() + "] but this region "
+ "only allows adding LevelContainers with a "
+ "detail level of [" + (minDetailLevel - 1) + "]");
}
if (levelContainer.getDetailLevel() == minDetailLevel - 1)
minDetailLevel = levelContainer.getDetailLevel();
dataContainer[levelContainer.getDetailLevel()] = levelContainer;
}
// TODO James thinks cutTree and growTree (which he renamed to match cutTree)
// should have more descriptive names, to make sure the "Tree" portion isn't
// confused with Minecraft trees (the plant).
/**
* Removes any dataContainers that are higher than
* the given detailLevel
*/
public void cutTree(byte detailLevel)
{
if (detailLevel > minDetailLevel)
{
for (byte detailLevelIndex = 0; detailLevelIndex < detailLevel; detailLevelIndex++)
dataContainer[detailLevelIndex] = null;
minDetailLevel = detailLevel;
}
}
/**
* Make this region more detailed to the detailLevel given.
* TODO is that correct?
*/
public void growTree(byte detailLevel)
{
if (detailLevel < minDetailLevel)
{
for (byte detailLevelIndex = (byte) (minDetailLevel - 1); detailLevelIndex >= detailLevel; detailLevelIndex--)
{
if (dataContainer[detailLevelIndex + 1] == null)
dataContainer[detailLevelIndex + 1] = new SingleLevelContainer((byte) (detailLevelIndex + 1));
dataContainer[detailLevelIndex] = dataContainer[detailLevelIndex + 1].expand();
}
minDetailLevel = detailLevel;
}
}
/**
* return RegionPos of this lod region
*/
public RegionPos getRegionPos()
{
return new RegionPos(regionPosX, regionPosZ);
}
/**
* Returns the minimum memory needed in bytes
*/
public int getMinMemoryNeeded(LodTemplate template)
{
int memory = 0;
for (byte detailLevelIndex = LodUtil.REGION_DETAIL_LEVEL; detailLevelIndex > minDetailLevel; detailLevelIndex--)
{
// TODO why are we multiplying the dataContainer's memory by the template's memory?
memory += dataContainer[detailLevelIndex].getMaxMemoryUse() * template.getBufferMemoryForSingleLod(dataContainer[detailLevelIndex].getMaxVerticalData());
}
return memory;
}
/**
* Returns how many LODs are in this region
*/
public int getNumberOfLods()
{
int count = 0;
for (LevelContainer container : dataContainer)
count += container.getMaxNumberOfLods();
return count;
}
public VerticalQuality getVerticalQuality()
{
return verticalQuality;
}
public DistanceGenerationMode getGenerationMode()
{
return generationMode;
}
public int getMaxVerticalData(byte detailLevel)
{
return dataContainer[detailLevel].getMaxVerticalData();
}
@Override
public String toString()
{
String s = "";
s += "x: " + x + " z: " + z + "\t";
return s;
return getLevel(LodUtil.REGION_DETAIL_LEVEL).toString();
}
}
@@ -20,37 +20,52 @@ package com.seibel.lod.objects;
import java.util.Hashtable;
import java.util.Map;
import com.seibel.lod.proxy.ClientProxy;
import net.minecraft.world.DimensionType;
/**
* This stores all LODs for a given world.
*
*
* @author James Seibel
* @version 04-01-2021
* @author Leonardo Amato
* @version 9-27-2021
*/
public class LodWorld
{
/** name of this world */
private String worldName;
/** dimensions in this world */
private Map<DimensionType, LodDimension> lodDimensions;
/** If true then the LOD world is setup and ready to use */
private boolean isWorldLoaded = false;
/** the name given to the world if it isn't loaded */
public static final String NO_WORLD_LOADED = "No world loaded";
public LodWorld()
{
worldName = NO_WORLD_LOADED;
}
/**
* Set up the LodWorld with the given newWorldName. <br>
* This should be done whenever loading a new world.
* @param newWorldName
* This should be done whenever loading a new world. <br><br>
*
* Note a System.gc() call may be in order after calling this <Br>
* since a lot of LOD data is now homeless. <br>
*
* @param newWorldName name of the world
*/
public void selectWorld(String newWorldName)
{
if(newWorldName.isEmpty())
if (newWorldName.isEmpty())
{
deselectWorld();
return;
@@ -69,7 +84,10 @@ public class LodWorld
/**
* Set the worldName to "No world loaded"
* and clear the lodDimensions Map. <br>
* This should be done whenever unloaded a world.
* This should be done whenever unloaded a world. <br><br>
*
* Note a System.gc() call may be in order after calling this <Br>
* since a lot of LOD data is now homeless. <br>
*/
public void deselectWorld()
{
@@ -79,33 +97,58 @@ public class LodWorld
}
public void addLodDimension(LodDimension newStorage)
/**
* Adds newDimension to this world, if a LodDimension
* already exists for the given dimension it is replaced.
*/
public void addLodDimension(LodDimension newDimension)
{
if (lodDimensions == null)
throw new IllegalStateException("LodWorld hasn't been given a world yet.");
return;
lodDimensions.put(newStorage.dimension, newStorage);
lodDimensions.put(newDimension.dimension, newDimension);
}
/**
* Returns null if no LodDimension exists for the given dimension
*/
public LodDimension getLodDimension(DimensionType dimension)
{
if (lodDimensions == null)
throw new IllegalStateException("LodWorld hasn't been given a world yet.");
return null;
return lodDimensions.get(dimension);
}
/**
* Resizes the max width in regions that each LodDimension
* should use.
* should use.
*/
public void resizeDimensionRegionWidth(int newWidth)
public void resizeDimensionRegionWidth(int newRegionWidth)
{
if (lodDimensions == null)
throw new IllegalStateException("LodWorld hasn't been given a world yet.");
return;
for(DimensionType key : lodDimensions.keySet())
lodDimensions.get(key).setRegionWidth(newWidth);
saveAllDimensions();
for (DimensionType key : lodDimensions.keySet())
lodDimensions.get(key).setRegionWidth(newRegionWidth);
}
/**
* Requests all dimensions save any dirty regions they may have.
*/
public void saveAllDimensions()
{
if (lodDimensions == null)
return;
// TODO we should only print this if lods were actually saved to file
// but that requires a LodDimension.hasDirtyRegions() method or something similar
ClientProxy.LOGGER.info("Saving LODs");
for (DimensionType key : lodDimensions.keySet())
lodDimensions.get(key).saveDirtyRegionsToFileAsync();
}
@@ -125,3 +168,4 @@ public class LodWorld
return "World name: " + worldName;
}
}
@@ -0,0 +1,200 @@
package com.seibel.lod.objects;
import com.seibel.lod.util.LevelPosUtil;
import javax.swing.*;
/**
* Holds the levelPos that need to be generated.
* TODO is that correct?
*
* @author Leonardo Amato
* @version 9-27-2021
*/
public class PosToGenerateContainer
{
private int playerPosX;
private int playerPosZ;
private byte farMinDetail;
private int nearSize;
private int farSize;
// TODO what is the format of these two arrays? [detailLevel][4-children]?
private int[][] nearPosToGenerate;
private int[][] farPosToGenerate;
public PosToGenerateContainer(byte farMinDetail, int maxDataToGenerate, int playerPosX, int playerPosZ)
{
this.playerPosX = playerPosX;
this.playerPosZ = playerPosZ;
this.farMinDetail = farMinDetail;
nearSize = 0;
farSize = 0;
nearPosToGenerate = new int[maxDataToGenerate][4];
farPosToGenerate = new int[maxDataToGenerate][4];
}
// TODO what is going on in this method?
public void addPosToGenerate(byte detailLevel, int posX, int posZ)
{
int distance = LevelPosUtil.minDistance(detailLevel, posX, posZ, playerPosX, playerPosZ);
int index;
if (detailLevel >= farMinDetail)
{
// We are introducing a position in the far array
if(farSize < farPosToGenerate.length)
farSize++;
index = farSize - 1;
while (index > 0 && LevelPosUtil.compareDistance(distance, farPosToGenerate[index - 1][3]) <= 0)
{
farPosToGenerate[index][0] = farPosToGenerate[index - 1][0];
farPosToGenerate[index][1] = farPosToGenerate[index - 1][1];
farPosToGenerate[index][2] = farPosToGenerate[index - 1][2];
farPosToGenerate[index][3] = farPosToGenerate[index - 1][3];
index--;
}
if (index != farSize-1 || farSize != farPosToGenerate.length)
{
farPosToGenerate[index][0] = detailLevel + 1;
farPosToGenerate[index][1] = posX;
farPosToGenerate[index][2] = posZ;
farPosToGenerate[index][3] = distance;
}
}
else
{
//We are introducing a position in the near array
if(nearSize < nearPosToGenerate.length)
nearSize++;
index = nearSize-1;
while (index > 0 && LevelPosUtil.compareDistance(distance, nearPosToGenerate[index - 1][3]) <= 0)
{
nearPosToGenerate[index][0] = nearPosToGenerate[index - 1][0];
nearPosToGenerate[index][1] = nearPosToGenerate[index - 1][1];
nearPosToGenerate[index][2] = nearPosToGenerate[index - 1][2];
nearPosToGenerate[index][3] = nearPosToGenerate[index - 1][3];
index--;
}
if (index != nearSize-1 || nearSize != nearPosToGenerate.length)
{
nearPosToGenerate[index][0] = detailLevel + 1;
nearPosToGenerate[index][1] = posX;
nearPosToGenerate[index][2] = posZ;
nearPosToGenerate[index][3] = distance;
}
}
}
public int getNumberOfPos()
{
return nearSize+farSize;
}
public int getNumberOfNearPos()
{
return nearSize;
}
public int getNumberOfFarPos()
{
return farSize;
}
// TODO what does getNth mean? could the name be more descriptive or is it just a index?
public int getNthDetail(int n, boolean near)
{
if (near)
return nearPosToGenerate[n][0];
else
return farPosToGenerate[n][0];
}
public int getNthPosX(int n, boolean near)
{
if (near)
return nearPosToGenerate[n][1];
else
return farPosToGenerate[n][1];
}
public int getNthPosZ(int n, boolean near)
{
if (near)
return nearPosToGenerate[n][2];
else
return farPosToGenerate[n][2];
}
public int getNthGeneration(int n, boolean near)
{
if (near)
return nearPosToGenerate[n][3];
else
return farPosToGenerate[n][3];
}
@Override
public String toString()
{
// TOOD is this commented code still useful?
// if so why have it commented out?
/*
StringBuilder builder = new StringBuilder();
builder.append("Number of pos to generate ");
builder.append(farSize + nearSize);
builder.append('\n');
builder.append("Number of near pos to generate ");
builder.append(nearSize);
builder.append('\n');
builder.append("Number of far pos to generate ");
builder.append(farSize);
builder.append('\n');
builder.append('\n');
builder.append("near pos to generate");
builder.append('\n');
for (int i = 0; i < nearSize; i++)
{
builder.append(posToGenerate[i][0]-1);
builder.append(" ");
builder.append(posToGenerate[i][1]);
builder.append(" ");
builder.append(posToGenerate[i][2]);
builder.append(" ");
builder.append(posToGenerate[i][3]);
builder.append('\n');
}
builder.append('\n');
builder.append("far pos to generate");
builder.append('\n');
for (int i = maxSize - 1; i >= maxSize - farSize; i--)
{
builder.append(posToGenerate[i][0]-1);
builder.append(" ");
builder.append(posToGenerate[i][1]);
builder.append(" ");
builder.append(posToGenerate[i][2]);
builder.append(" ");
builder.append(posToGenerate[i][3]);
builder.append('\n');
}
builder.append('\n');
return builder.toString();
*/
return " ";
}
}
@@ -0,0 +1,126 @@
package com.seibel.lod.objects;
import com.seibel.lod.proxy.ClientProxy;
import com.seibel.lod.util.LevelPosUtil;
import com.seibel.lod.util.LodUtil;
import java.util.Arrays;
/**
*
* @author Leonardo Amato
* @version 9-18-2021
*/
public class PosToRenderContainer
{
public byte minDetail;
private int size;
private int regionPosX;
private int regionPosZ;
private int numberOfPosToRender;
private int[] posToRender;
/*TODO this population matrix could be converted to boolean to improve memory use*/
private byte[][] population;
public PosToRenderContainer(byte minDetail, int regionPosX, int regionPosZ)
{
this.minDetail = minDetail;
this.numberOfPosToRender = 0;
this.regionPosX = regionPosX;
this.regionPosZ = regionPosZ;
this.size = 1 << (LodUtil.REGION_DETAIL_LEVEL - minDetail);
posToRender = new int[size*size*3];
population = new byte[size][size];
}
public void addPosToRender(byte detailLevel, int posX, int posZ)
{
// When rapidly changing dimensions the bufferBuidler can cause this,
// James isn't sure why, but this will prevent an exception at
// the very least (while stilling logging the problem).
if (numberOfPosToRender >= posToRender.length)
{
// This is might be due to dimensions having a different width
// when first loading in
ClientProxy.LOGGER.error("Unable to addPosToRender. numberOfPosToRender [" + numberOfPosToRender +"] detailLevel [" + detailLevel + "] Pos [" + posX + "," + posZ + "]");
numberOfPosToRender++; // incrementing so we can see how many pos over the limit we would go
return;
}
//if(numberOfPosToRender >= posToRender.length)
// posToRender = Arrays.copyOf(posToRender, posToRender.length*2);
posToRender[numberOfPosToRender*3 + 0] = detailLevel;
posToRender[numberOfPosToRender*3 + 1] = posX;
posToRender[numberOfPosToRender*3 + 2] = posZ;
numberOfPosToRender++;
population[LevelPosUtil.getRegionModule(minDetail, LevelPosUtil.convert(detailLevel,posX,minDetail))]
[LevelPosUtil.getRegionModule(minDetail, LevelPosUtil.convert(detailLevel,posZ,minDetail))] = (byte) (detailLevel + 1);
}
public boolean contains(byte detailLevel, int posX, int posZ)
{
if(LevelPosUtil.getRegion(detailLevel, posX) == regionPosX && LevelPosUtil.getRegion(detailLevel, posZ) == regionPosZ)
{
return (population[LevelPosUtil.getRegionModule(minDetail, LevelPosUtil.convert(detailLevel,posX,minDetail))]
[LevelPosUtil.getRegionModule(minDetail, LevelPosUtil.convert(detailLevel,posZ,minDetail))] == (detailLevel + 1));
}else
{
return false;
}
}
public void clear(byte minDetail, int regionPosX, int regionPosZ){
this.numberOfPosToRender = 0;
this.regionPosX = regionPosX;
this.regionPosZ = regionPosZ;
if(this.minDetail == minDetail)
{
Arrays.fill(posToRender, 0);
for(int i = 0; i< population.length; i++)
Arrays.fill(population[i], (byte) 0);
}else{
this.minDetail = minDetail;
int size = 1 << (LodUtil.REGION_DETAIL_LEVEL - minDetail);
posToRender = new int[size*size*3];
population = new byte[size][size];
}
}
public int getNumberOfPos()
{
return numberOfPosToRender;
}
public byte getNthDetailLevel(int n)
{
return (byte) posToRender[n*3 + 0];
}
public int getNthPosX(int n)
{
return posToRender[n*3 + 1];
}
public int getNthPosZ(int n)
{
return posToRender[n*3 + 2];
}
@Override
public String toString()
{
StringBuilder builder = new StringBuilder();
builder.append("To render ");
builder.append(numberOfPosToRender);
builder.append('\n');
for(int i = 0; i < numberOfPosToRender; i++)
{
builder.append(posToRender[i*3 + 0]);
builder.append(" ");
builder.append(posToRender[i*3 + 1]);
builder.append(" ");
builder.append(posToRender[i*3 + 2]);
builder.append('\n');
}
builder.append('\n');
return builder.toString();
}
}
@@ -17,11 +17,16 @@
*/
package com.seibel.lod.objects;
import com.seibel.lod.util.LodUtil;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.ChunkPos;
/**
* This object is similar to ChunkPos or BlockPos.
*
*
* @author James Seibel
* @version 03-19-2021
* @version 8-21-2021
*/
public class RegionPos
{
@@ -30,7 +35,7 @@ public class RegionPos
/**
* Default Constructor <br>
* Default Constructor <br><br>
*
* Sets x and z to 0
*/
@@ -40,9 +45,45 @@ public class RegionPos
z = 0;
}
/** simple constructor that sets x and z to new x and z. */
public RegionPos(int newX, int newZ)
{
x = newX;
z = newZ;
}
/** Converts from a BlockPos to a RegionPos */
public RegionPos(BlockPos pos)
{
this(new ChunkPos(pos));
}
/** Converts from a ChunkPos to a RegionPos */
public RegionPos(ChunkPos pos)
{
x = Math.floorDiv(pos.x, LodUtil.REGION_WIDTH_IN_CHUNKS);
z = Math.floorDiv(pos.z, LodUtil.REGION_WIDTH_IN_CHUNKS);
}
/** Returns the ChunkPos at the center of this region */
public ChunkPos chunkPos()
{
return new ChunkPos(
(x * LodUtil.REGION_WIDTH_IN_CHUNKS) + LodUtil.REGION_WIDTH_IN_CHUNKS / 2,
(z * LodUtil.REGION_WIDTH_IN_CHUNKS) + LodUtil.REGION_WIDTH_IN_CHUNKS / 2);
}
/** Returns the BlockPos at the center of this region */
public BlockPos blockPos()
{
return chunkPos().getWorldPosition()
.offset(LodUtil.CHUNK_WIDTH / 2, 0, LodUtil.CHUNK_WIDTH / 2);
}
@Override
public String toString()
{
return "(" + x + "," + z + ")";
}
}
@@ -0,0 +1,233 @@
package com.seibel.lod.objects;
import java.util.Arrays;
import com.seibel.lod.util.DataPointUtil;
import com.seibel.lod.util.LevelPosUtil;
import com.seibel.lod.util.LodUtil;
import com.seibel.lod.util.ThreadMapUtil;
/**
* This object holds the LOD data for a single dataPoint.
*
* @author Leonardo Amato
* @version 9-28-2021
*/
public class SingleLevelContainer implements LevelContainer
{
/** The detailLevel of this LevelContainer */
public final byte detailLevel;
/** How many dataPoints wide is this LevelContainer? */
public final int dataWidthCount;
/** This holds all the dataPoints for this LevelContainer */
public final long[][] dataContainer;
/** Constructor */
public SingleLevelContainer(byte newDetailLevel)
{
this.detailLevel = newDetailLevel;
// equivalent to 2^(...)
dataWidthCount = 1 << (LodUtil.REGION_DETAIL_LEVEL - newDetailLevel);
dataContainer = new long[dataWidthCount][dataWidthCount];
}
/** */
public SingleLevelContainer(byte[] inputData)
{
int tempIndex;
int index = 0;
long newData;
detailLevel = inputData[index];
index++;
dataWidthCount = (int) Math.pow(2, LodUtil.REGION_DETAIL_LEVEL - detailLevel);
this.dataContainer = new long[dataWidthCount][dataWidthCount];
for (int x = 0; x < dataWidthCount; x++)
{
for (int z = 0; z < dataWidthCount; z++)
{
newData = 0;
if (inputData[index] == 0)
{
index++;
}
else if (inputData[index] == 3)
{
newData = 3;
index++;
}
else if (index + 7 >= inputData.length)
{
break;
}
else
{
for (tempIndex = 0; tempIndex < 8; tempIndex++)
newData += (((long) inputData[index + tempIndex]) & 0xff) << (8 * tempIndex);
index = index + 8;
}
dataContainer[x][z] = newData;
}
}
}
@Override
public void clear(int posX, int posZ)
{
posX = LevelPosUtil.getRegionModule(detailLevel, posX);
posZ = LevelPosUtil.getRegionModule(detailLevel, posZ);
dataContainer[posX][posZ] = DataPointUtil.EMPTY_DATA;
}
@Override
public boolean addData(long data, int posX, int posZ, int index)
{
posX = LevelPosUtil.getRegionModule(detailLevel, posX);
posZ = LevelPosUtil.getRegionModule(detailLevel, posZ);
dataContainer[posX][posZ] = data;
return true;
}
@Override
public boolean addSingleData(long newData, int posX, int posZ)
{
posX = LevelPosUtil.getRegionModule(detailLevel, posX);
posZ = LevelPosUtil.getRegionModule(detailLevel, posZ);
dataContainer[posX][posZ] = newData;
return true;
}
@Override
public long getData(int posX, int posZ, int index)
{
posX = LevelPosUtil.getRegionModule(detailLevel, posX);
posZ = LevelPosUtil.getRegionModule(detailLevel, posZ);
// TODO Improve this using a thread map to long[]
return dataContainer[posX][posZ];
}
@Override
public long getSingleData(int posX, int posZ)
{
posX = LevelPosUtil.getRegionModule(detailLevel, posX);
posZ = LevelPosUtil.getRegionModule(detailLevel, posZ);
// TODO Improve this using a thread map to long[]
return dataContainer[posX][posZ];
}
@Override
public byte getDetailLevel()
{
return detailLevel;
}
@Override
public LevelContainer expand()
{
return new SingleLevelContainer((byte) (getDetailLevel() - 1));
}
/** TODO could this be renamed mergeData? */
@Override
public void updateData(LevelContainer lowerLevelContainer, int posX, int posZ)
{
//We reset the array
long[] dataToMerge = ThreadMapUtil.getSingleUpdateArray();
int childPosX;
int childPosZ;
long data;
posX = LevelPosUtil.getRegionModule(detailLevel, posX);
posZ = LevelPosUtil.getRegionModule(detailLevel, posZ);
for (int x = 0; x <= 1; x++)
{
for (int z = 0; z <= 1; z++)
{
childPosX = 2 * posX + x;
childPosZ = 2 * posZ + z;
dataToMerge[2 * x + z] = lowerLevelContainer.getSingleData(childPosX, childPosZ);
}
}
data = DataPointUtil.mergeSingleData(dataToMerge);
addSingleData(data, posX, posZ);
}
@Override
public int getMaxVerticalData()
{
return 1;
}
@Override
public boolean doesItExist(int posX, int posZ)
{
posX = LevelPosUtil.getRegionModule(detailLevel, posX);
posZ = LevelPosUtil.getRegionModule(detailLevel, posZ);
//Improve this using a thread map to long[]
return DataPointUtil.doesItExist(getSingleData(posX, posZ));
}
@Override
public byte[] toDataString()
{
int index = 0;
int tempIndex;
byte[] tempData = ThreadMapUtil.getFreshSaveContainer(1 + (dataWidthCount * dataWidthCount * 8));
tempData[index] = detailLevel;
index++;
for (int x = 0; x < dataWidthCount; x++)
{
for (int z = 0; z < dataWidthCount; z++)
{
if (dataContainer[x][z] == 0)
{
tempData[index] = 0;
index++;
} else if (dataContainer[x][z] == 3)
{
tempData[index] = 3;
index++;
} else
{
for (tempIndex = 0; tempIndex < 8; tempIndex++)
tempData[index + tempIndex] = (byte) (dataContainer[x][z] >>> (8 * tempIndex));
index += 8;
}
}
}
return Arrays.copyOfRange(tempData, 0, index);
}
@Override
public String toString()
{
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append(detailLevel);
return stringBuilder.toString();
}
@Override
public int getMaxNumberOfLods()
{
return dataWidthCount * dataWidthCount * getMaxVerticalData();
}
@Override
public int getMaxMemoryUse()
{
return getMaxNumberOfLods() * 2; //2 byte
}
}
@@ -0,0 +1,261 @@
package com.seibel.lod.objects;
import java.util.Arrays;
import com.seibel.lod.util.DataPointUtil;
import com.seibel.lod.util.DetailDistanceUtil;
import com.seibel.lod.util.LevelPosUtil;
import com.seibel.lod.util.LodUtil;
import com.seibel.lod.util.ThreadMapUtil;
public class VerticalLevelContainer implements LevelContainer
{
public final byte detailLevel;
public final int size;
public final int maxVerticalData;
public final long[] dataContainer;
public VerticalLevelContainer(byte detailLevel)
{
this.detailLevel = detailLevel;
size = 1 << (LodUtil.REGION_DETAIL_LEVEL - detailLevel);
maxVerticalData = DetailDistanceUtil.getMaxVerticalData(detailLevel);
dataContainer = new long[size * size * DetailDistanceUtil.getMaxVerticalData(detailLevel)];
}
@Override
public byte getDetailLevel()
{
return detailLevel;
}
@Override
public void clear(int posX, int posZ){
posX = LevelPosUtil.getRegionModule(detailLevel, posX);
posZ = LevelPosUtil.getRegionModule(detailLevel, posZ);
for(int verticalIndex = 0; verticalIndex < maxVerticalData; verticalIndex++){
dataContainer[posX*size*maxVerticalData + posZ*maxVerticalData + verticalIndex] = DataPointUtil.EMPTY_DATA;
}
}
@Override
public boolean addData(long data, int posX, int posZ, int verticalIndex){
posX = LevelPosUtil.getRegionModule(detailLevel, posX);
posZ = LevelPosUtil.getRegionModule(detailLevel, posZ);
dataContainer[posX*size*maxVerticalData + posZ*maxVerticalData + verticalIndex] = data;
return true;
}
@Override
public boolean addSingleData(long data, int posX, int posZ){
return addData(data, posX, posZ, 0);
}
@Override
public long getData(int posX, int posZ, int verticalIndex){
posX = LevelPosUtil.getRegionModule(detailLevel, posX);
posZ = LevelPosUtil.getRegionModule(detailLevel, posZ);
return dataContainer[posX*size*maxVerticalData + posZ*maxVerticalData + verticalIndex];
}
@Override
public long getSingleData(int posX, int posZ){
return getData(posX,posZ,0);
}
@Override
public int getMaxVerticalData(){
return maxVerticalData;
}
public int getSize(){
return size;
}
@Override
public boolean doesItExist(int posX, int posZ){
posX = LevelPosUtil.getRegionModule(detailLevel, posX);
posZ = LevelPosUtil.getRegionModule(detailLevel, posZ);
return DataPointUtil.doesItExist(getSingleData(posX,posZ));
}
public VerticalLevelContainer(byte[] inputData)
{
int tempIndex;
int index = 0;
int counter = -1;
long newData;
byte last = 0;
detailLevel = inputData[index];
index++;
maxVerticalData = inputData[index];
index++;
size = (int) Math.pow(2, LodUtil.REGION_DETAIL_LEVEL - detailLevel);
int x = size * size * maxVerticalData;
this.dataContainer = new long[x];
for ( int i = 0; i < x; i++)
{
newData = 0;
if (counter > -1)
{
dataContainer[i] = last;
if (last == 3)
{ //skip rest of void chunk
for (tempIndex = 1; tempIndex < maxVerticalData; tempIndex++) {
dataContainer[i + tempIndex] = 0;
}
i += maxVerticalData - 1;
}
counter--;
} else if ((inputData[index] & 0x3) == 0 || (inputData[index] & 0x3) == 3)
{
last = (byte)(inputData[index] & 0x3);
//recover counter
counter = (inputData[index] & 0x7c) >>> 2;
tempIndex = 0;
while ((inputData[index] & 0x80) == 0x80)
{ //overflow bit is on
index++;
counter += (inputData[index] & 0x7f) << (5 + 7 * tempIndex);
tempIndex++;
}
index++;
//since loop expects from us to put some data in, we just make it rerun it with new counter;
i--;
} else if (index + 7 >= inputData.length)
break;
else {
for (tempIndex = 0; tempIndex < 8; tempIndex++)
newData += (((long) inputData[index + tempIndex]) & 0xff) << (8 * tempIndex);
index = index + 8;
dataContainer[i] = newData;
}
}
}
@Override
public LevelContainer expand(){
return new VerticalLevelContainer((byte) (getDetailLevel() - 1));
}
@Override
public void updateData(LevelContainer lowerLevelContainer, int posX, int posZ)
{
//We reset the array
long[] dataToMerge = ThreadMapUtil.getFreshVerticalUpdateArray(4 * lowerLevelContainer.getMaxVerticalData(), detailLevel);
int lowerMaxVertical = dataToMerge.length/4;
int childPosX;
int childPosZ;
long[] data;
posX = LevelPosUtil.getRegionModule(detailLevel, posX);
posZ = LevelPosUtil.getRegionModule(detailLevel, posZ);
for (int x = 0; x <= 1; x++)
{
for (int z = 0; z <= 1; z++)
{
childPosX = 2 * posX + x;
childPosZ = 2 * posZ + z;
for(int verticalIndex = 0; verticalIndex < lowerMaxVertical; verticalIndex++)
dataToMerge[(z*2+x)*lowerMaxVertical + verticalIndex] = lowerLevelContainer.getData(childPosX, childPosZ, verticalIndex);
}
}
data = DataPointUtil.mergeMultiData(dataToMerge, lowerMaxVertical, getMaxVerticalData());
for(int verticalIndex = 0; (verticalIndex < data.length) && (verticalIndex < maxVerticalData); verticalIndex++)
{
addData(data[verticalIndex],
posX,
posZ,
verticalIndex);
}
}
@Override
public byte[] toDataString()
{
int index = 0;
int counter = -1;
byte last = -1;
int x = size * size * maxVerticalData;
int tempIndex;
long current;
byte[] tempData = ThreadMapUtil.getFreshSaveContainer(2 + (x * 8));
tempData[index] = detailLevel;
index++;
tempData[index] = (byte) maxVerticalData;
index++;
for (int i = 0; i < x; i++)
{
current = dataContainer[i];
if ((current & 0b11) == 0 || (current & 0b11) == 3)
{
current &= 0b11; //clean any garbage data after those two bits
last = (byte) current;
if (current == 3) //skip rest of void chunk
i += maxVerticalData - 1;
counter++;
} else {
for (tempIndex = 0; tempIndex < 8; tempIndex++)
tempData[index + tempIndex] = (byte) (current >>> (8 * tempIndex));
index += 8;
}
if (last != -1 && ( i == x - 1 || last != ((dataContainer[i + 1]) & 0b11)))
{ //save compressed data if next is different or if we reached onf of the data
tempData[index] = (byte)(0x7f & ((counter << 2) + last)); //save 5 bits of counter and compressed block
tempIndex = 0;
while ((counter >>> (5 + 7 * tempIndex)) != 0) //there is more of that counter
{
tempData[index] = (byte)(tempData[index] | 0x80); //set overflow bit to true
index++; // after setting overflow bit w can actually index++
tempData[index] = (byte)(0x7f & (counter >>> (5 + 7 * tempIndex))); // save 7 bits of counter
tempIndex++;
}
index++;
last = -1;
counter = -1;
}
}
return Arrays.copyOfRange(tempData, 0, index);
}
@Override
public String toString()
{
/*
StringBuilder stringBuilder = new StringBuilder();
int size = (int) Math.pow(2, LodUtil.REGION_DETAIL_LEVEL - detailLevel);
stringBuilder.append(detailLevel);
stringBuilder.append(DATA_DELIMITER);
for (int x = 0; x < size; x++)
{
for (int z = 0; z < size; z++)
{
//Converting the dataToHex
stringBuilder.append(Long.toHexString(dataContainer[x][z][0]));
stringBuilder.append(DATA_DELIMITER);
}
}
return stringBuilder.toString();
*/
return " ";
}
@Override
public int getMaxNumberOfLods(){
return size*size*getMaxVerticalData();
}
@Override
public int getMaxMemoryUse(){
return getMaxNumberOfLods() * 2; //2 byte
}
}
@@ -19,19 +19,29 @@ package com.seibel.lod.proxy;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.lwjgl.glfw.GLFW;
import com.mojang.blaze3d.matrix.MatrixStack;
import com.seibel.lod.builders.LodBufferBuilder;
import com.seibel.lod.builders.LodChunkBuilder;
import com.seibel.lod.handlers.LodConfig;
import com.seibel.lod.objects.LodChunk;
import com.seibel.lod.builders.LodBuilder;
import com.seibel.lod.builders.worldGeneration.LodNodeGenWorker;
import com.seibel.lod.builders.worldGeneration.LodWorldGenerator;
import com.seibel.lod.config.LodConfig;
import com.seibel.lod.enums.DistanceGenerationMode;
import com.seibel.lod.objects.LodDimension;
import com.seibel.lod.objects.LodRegion;
import com.seibel.lod.objects.LodWorld;
import com.seibel.lod.objects.RegionPos;
import com.seibel.lod.render.LodRenderer;
import com.seibel.lod.util.DataPointUtil;
import com.seibel.lod.util.DetailDistanceUtil;
import com.seibel.lod.util.LodUtil;
import com.seibel.lod.util.ThreadMapUtil;
import com.seibel.lod.wrappers.MinecraftWrapper;
import net.minecraft.client.Minecraft;
import net.minecraft.profiler.IProfiler;
import net.minecraft.util.text.StringTextComponent;
import net.minecraftforge.client.event.InputEvent;
import net.minecraftforge.event.TickEvent;
import net.minecraftforge.event.world.BlockEvent;
import net.minecraftforge.event.world.ChunkEvent;
import net.minecraftforge.event.world.WorldEvent;
@@ -39,22 +49,42 @@ import net.minecraftforge.eventbus.api.SubscribeEvent;
/**
* This handles all events sent to the client,
* and is the starting point for most of this program.
*
* and is the starting point for most of the mod.
*
* @author James_Seibel
* @version 06-27-2021
* @version 9-26-2021
*/
public class ClientProxy
{
public static final Logger LOGGER = LogManager.getLogger("LOD");
/**
* there is some setup that should only happen once,
* once this is true that setup has completed
*/
private boolean firstTimeSetupComplete = false;
private static LodWorld lodWorld = new LodWorld();
private static LodChunkBuilder lodChunkBuilder = new LodChunkBuilder();
private static LodBufferBuilder lodBufferBuilder = new LodBufferBuilder(lodChunkBuilder);
private static LodBuilder lodBuilder = new LodBuilder();
private static LodBufferBuilder lodBufferBuilder = new LodBufferBuilder();
private static LodRenderer renderer = new LodRenderer(lodBufferBuilder);
private static LodWorldGenerator lodWorldGenerator = LodWorldGenerator.INSTANCE;
Minecraft mc = Minecraft.getInstance();
private boolean configOverrideReminderPrinted = false;
private MinecraftWrapper mc = MinecraftWrapper.INSTANCE;
/** This is used to determine if the LODs should be regenerated */
public static int previousChunkRenderDistance = 0;
/** This is used to determine if the LODs should be regenerated */
public static int previousLodRenderDistance = 0;
/**
* can be set if we want to recalculate variables related
* to the LOD view distance
*/
private boolean recalculateWidths = false;
public ClientProxy()
{
@@ -62,8 +92,6 @@ public class ClientProxy
}
//==============//
// render event //
//==============//
@@ -71,72 +99,101 @@ public class ClientProxy
/**
* Do any setup that is required to draw LODs
* and then tell the LodRenderer to draw.
*
* @param mcMatrixStack
*/
public void renderLods(float partialTicks)
public void renderLods(MatrixStack mcMatrixStack, float partialTicks)
{
if (mc == null || mc.player == null || !lodWorld.getIsWorldLoaded())
return;
// comment out when creating a release
applyConfigOverrides();
// update each regions' width to match the new render distance
int newWidth = Math.max(4,
// TODO is this logic good?
(mc.options.renderDistance * LodChunk.WIDTH * 2 * LodConfig.CLIENT.lodChunkRadiusMultiplier.get()) / LodRegion.SIZE
);
if (lodChunkBuilder.regionWidth != newWidth)
// clear any out of date objects
mc.clearFrameObjectCache();
try
{
lodWorld.resizeDimensionRegionWidth(newWidth);
lodChunkBuilder.regionWidth = newWidth;
// only run the first time setup once
if (!firstTimeSetupComplete)
firstFrameSetup();
// skip this frame, hopefully the lodWorld
// should have everything set up by then
return;
if (mc == null || mc.getPlayer() == null || !lodWorld.getIsWorldLoaded())
return;
LodDimension lodDim = lodWorld.getLodDimension(mc.getCurrentDimension());
if (lodDim == null)
return;
DetailDistanceUtil.updateSettings();
viewDistanceChangedEvent();
playerMoveEvent(lodDim);
lodDim.cutRegionNodesAsync((int) mc.getPlayer().getX(), (int) mc.getPlayer().getZ());
lodDim.expandOrLoadRegionsAsync((int) mc.getPlayer().getX(), (int) mc.getPlayer().getZ());
// Note to self:
// if "unspecified" shows up in the pie chart, it is
// possibly because the amount of time between sections
// is too small for the profiler to measure
IProfiler profiler = mc.getProfiler();
profiler.pop(); // get out of "terrain"
profiler.push("LOD");
renderer.drawLODs(lodDim, mcMatrixStack, partialTicks, mc.getProfiler());
profiler.pop(); // end LOD
profiler.push("terrain"); // go back into "terrain"
// these can't be set until after the buffers are built (in renderer.drawLODs)
// otherwise the buffers may be set to the wrong size, or not changed at all
previousChunkRenderDistance = mc.getRenderDistance();
previousLodRenderDistance = LodConfig.CLIENT.graphics.lodChunkRenderDistance.get();
}
LodDimension lodDim = lodWorld.getLodDimension(mc.player.level.dimensionType());
if (lodDim == null)
return;
// offset the regions
double playerX = mc.player.getX();
double playerZ = mc.player.getZ();
int xOffset = ((int)playerX / (LodChunk.WIDTH * LodRegion.SIZE)) - lodDim.getCenterX();
int zOffset = ((int)playerZ / (LodChunk.WIDTH * LodRegion.SIZE)) - lodDim.getCenterZ();
if (xOffset != 0 || zOffset != 0)
catch (Exception e)
{
lodDim.move(xOffset, zOffset);
LOGGER.error("client proxy: " + e.getMessage());
e.printStackTrace();
}
}
private void applyConfigOverrides()
{
// remind the developer(s) that the config override is active
if (!configOverrideReminderPrinted)
{
//mc.getPlayer().sendMessage(new StringTextComponent("Debug settings enabled!"), mc.getPlayer().getUUID());
mc.getPlayer().sendMessage(new StringTextComponent("LOD experimental build 1.5.1"), mc.getPlayer().getUUID());
mc.getPlayer().sendMessage(new StringTextComponent("Here be dragons!"), mc.getPlayer().getUUID());
configOverrideReminderPrinted = true;
}
// LodConfig.CLIENT.graphics.drawResolution.set(HorizontalResolution.BLOCK);
// LodConfig.CLIENT.worldGenerator.generationResolution.set(HorizontalResolution.BLOCK);
// requires a world restart?
// LodConfig.CLIENT.worldGenerator.lodQualityMode.set(VerticalQuality.MULTI_LOD);
// for testing
// LodConfig.CLIENT.debugMode.set(false);
// LodConfig.CLIENT.graphics.fogDistance.set(FogDistance.FAR);
// LodConfig.CLIENT.graphics.fogDrawOverride.set(FogDrawOverride.ALWAYS_DRAW_FOG_FANCY);
// LodConfig.CLIENT.graphics.shadingMode.set(ShadingMode.DARKEN_SIDES);
// LodConfig.CLIENT.graphics.brightnessMultiplier.set(1.0);
// LodConfig.CLIENT.graphics.saturationMultiplier.set(1.0);
// LodConfig.CLIENT.lodDetail.set(LodDetail.DOUBLE);
// LodConfig.CLIENT.lodChunkRadiusMultiplier.set(12);
// LodConfig.CLIENT.fogDistance.set(FogDistance.FAR);
// LodConfig.CLIENT.fogDrawOverride.set(FogDrawOverride.ALWAYS_DRAW_FOG_FANCY);
// LodConfig.CLIENT.worldGenerator.distanceGenerationMode.set(DistanceGenerationMode.SURFACE);
// LodConfig.CLIENT.graphics.lodChunkRenderDistance.set(64);
// LodConfig.CLIENT.worldGenerator.lodDistanceCalculatorType.set(DistanceCalculatorType.LINEAR);
// LodConfig.CLIENT.worldGenerator.allowUnstableFeatureGeneration.set(false);
// LodConfig.CLIENT.distanceGenerationMode.set(DistanceGenerationMode.FEATURES);
// LodConfig.CLIENT.allowUnstableFeatureGeneration.set(false);
// LodConfig.CLIENT.buffers.bufferRebuildPlayerMoveTimeout.set(2000); // 2000
// LodConfig.CLIENT.buffers.bufferRebuildChunkChangeTimeout.set(1000); // 1000
// LodConfig.CLIENT.buffers.bufferRebuildLodChangeTimeout.set(5000); // 5000
// Note to self:
// if "unspecified" shows up in the pie chart, it is
// possibly because the amount of time between sections
// is too small for the profile to measure
IProfiler profiler = mc.getProfiler();
profiler.pop(); // get out of "terrain"
profiler.push("LOD");
renderer.drawLODs(lodDim, partialTicks, mc.getProfiler());
profiler.pop(); // end LOD
profiler.push("terrain"); // restart terrain
}
LodConfig.CLIENT.debugging.enableDebugKeybindings.set(true);
// LodConfig.CLIENT.debugging.debugMode.set(DebugMode.SHOW_DETAIL);
}
//==============//
@@ -144,17 +201,40 @@ public class ClientProxy
//==============//
@SubscribeEvent
public void chunkLoadEvent(ChunkEvent.Load event)
public void serverTickEvent(TickEvent.ServerTickEvent event)
{
lodChunkBuilder.generateLodChunkAsync(event.getChunk(), lodWorld, event.getWorld());
if (mc == null || mc.getPlayer() == null || !lodWorld.getIsWorldLoaded())
return;
LodDimension lodDim = lodWorld.getLodDimension(mc.getPlayer().level.dimensionType());
if (lodDim == null)
return;
lodWorldGenerator.queueGenerationRequests(lodDim, renderer, lodBuilder);
}
@SubscribeEvent
public void chunkLoadEvent(ChunkEvent.Load event)
{
lodBuilder.generateLodNodeAsync(event.getChunk(), lodWorld, event.getWorld(), DistanceGenerationMode.SERVER);
}
@SubscribeEvent
public void worldSaveEvent(WorldEvent.Save event)
{
if (lodWorld != null)
lodWorld.saveAllDimensions();
}
/** This is also called when a new dimension loads */
@SubscribeEvent
public void worldLoadEvent(WorldEvent.Load event)
{
DataPointUtil.worldHeight = event.getWorld().getHeight();
// the player just loaded a new world/dimension
lodWorld.selectWorld(LodUtil.getWorldID(event.getWorld()));
// make sure the correct LODs are being rendered
// (if this isn't done the previous world's LODs may be drawn)
renderer.regenerateLODsNextFrame();
@@ -164,33 +244,128 @@ public class ClientProxy
public void worldUnloadEvent(WorldEvent.Unload event)
{
// the player just unloaded a world/dimension
ThreadMapUtil.clearMaps();
if(mc.getConnection().getLevel() == null)
if (mc.getConnection().getLevel() == null)
{
lodBufferBuilder.numberOfChunksWaitingToGenerate.set(0);
// the player just left the server
// if this isn't done unfinished tasks may be left in the queue
// preventing new LodChunks form being generated
LodNodeGenWorker.restartExecuterService();
LodWorldGenerator.INSTANCE.numberOfChunksWaitingToGenerate.set(0);
// the player has disconnected from a server
lodWorld.deselectWorld();
// hopefully this should reduce issues related to the buffer builder
// breaking when changing worlds.
renderer.destroyBuffers();
recalculateWidths = true;
renderer = new LodRenderer(lodBufferBuilder);
// make sure the nulled objects are freed.
// (this prevents an out of memory error when
// changing worlds)
System.gc();
}
}
@SubscribeEvent
public void blockChangeEvent(BlockEvent event)
{
if (event.getClass() == BlockEvent.BreakEvent.class ||
event.getClass() == BlockEvent.EntityPlaceEvent.class ||
event.getClass() == BlockEvent.EntityMultiPlaceEvent.class ||
event.getClass() == BlockEvent.FluidPlaceBlockEvent.class ||
event.getClass() == BlockEvent.PortalSpawnEvent.class)
event.getClass() == BlockEvent.EntityPlaceEvent.class ||
event.getClass() == BlockEvent.EntityMultiPlaceEvent.class ||
event.getClass() == BlockEvent.FluidPlaceBlockEvent.class ||
event.getClass() == BlockEvent.PortalSpawnEvent.class)
{
// recreate the LOD where the blocks were changed
lodChunkBuilder.generateLodChunkAsync(event.getWorld().getChunk(event.getPos()), lodWorld, event.getWorld());
lodBuilder.generateLodNodeAsync(event.getWorld().getChunk(event.getPos()), lodWorld, event.getWorld());
}
}
@SubscribeEvent
public void onKeyInput(InputEvent.KeyInputEvent event)
{
if(LodConfig.CLIENT.debugging.enableDebugKeybindings.get()
&& event.getKey() == GLFW.GLFW_KEY_F4 && event.getAction() == GLFW.GLFW_PRESS)
{
LodConfig.CLIENT.debugging.debugMode.set(LodConfig.CLIENT.debugging.debugMode.get().getNext());
}
if(LodConfig.CLIENT.debugging.enableDebugKeybindings.get()
&& event.getKey() == GLFW.GLFW_KEY_F6 && event.getAction() == GLFW.GLFW_PRESS)
{
LodConfig.CLIENT.graphics.drawLods.set(!LodConfig.CLIENT.graphics.drawLods.get());
}
}
//============//
// LOD events //
//============//
/**
* Re-centers the given LodDimension if it needs to be.
*/
private void playerMoveEvent(LodDimension lodDim)
{
// make sure the dimension is centered
RegionPos playerRegionPos = new RegionPos(mc.getPlayer().blockPosition());
RegionPos worldRegionOffset = new RegionPos(playerRegionPos.x - lodDim.getCenterRegionPosX(), playerRegionPos.z - lodDim.getCenterRegionPosZ());
if (worldRegionOffset.x != 0 || worldRegionOffset.z != 0)
{
lodWorld.saveAllDimensions();
lodDim.move(worldRegionOffset);
//LOGGER.info("offset: " + worldRegionOffset.x + "," + worldRegionOffset.z + "\t center: " + lodDim.getCenterX() + "," + lodDim.getCenterZ());
}
}
/**
* Re-sizes all LodDimensions if they needs to be.
*/
private void viewDistanceChangedEvent()
{
// calculate how wide the dimension(s) should be in regions
int chunksWide = LodConfig.CLIENT.graphics.lodChunkRenderDistance.get() * 2 + 1;
int newWidth = (int) Math.ceil(chunksWide / (float) LodUtil.REGION_WIDTH_IN_CHUNKS);
newWidth = (newWidth % 2 == 0) ? (newWidth += 1) : (newWidth += 2); // make sure we have a odd number of regions
// do the dimensions need to change in size?
if (lodBuilder.defaultDimensionWidthInRegions != newWidth || recalculateWidths)
{
lodWorld.saveAllDimensions();
// update the dimensions to fit the new width
lodWorld.resizeDimensionRegionWidth(newWidth);
lodBuilder.defaultDimensionWidthInRegions = newWidth;
renderer.setupBuffers(lodWorld.getLodDimension(mc.getClientWorld().dimensionType()));
recalculateWidths = false;
//LOGGER.info("new dimension width in regions: " + newWidth + "\t potential: " + newWidth );
}
DetailDistanceUtil.updateSettings();
}
/**
* This event is called once during the first frame Minecraft renders in the world.
*/
public void firstFrameSetup()
{
// make sure the GlProxy is created before the LodBufferBuilder
GlProxy.getInstance();
// TODO shouldn't these already be empty?
ThreadMapUtil.clearMaps();
firstTimeSetupComplete = true;
}
//================//
// public getters //
//================//
@@ -200,9 +375,9 @@ public class ClientProxy
return lodWorld;
}
public static LodChunkBuilder getLodBuilder()
public static LodBuilder getLodBuilder()
{
return lodChunkBuilder;
return lodBuilder;
}
public static LodRenderer getRenderer()
@@ -0,0 +1,188 @@
package com.seibel.lod.proxy;
import org.lwjgl.opengl.GL;
import org.lwjgl.opengl.GLCapabilities;
import org.lwjgl.opengl.WGL;
import com.mojang.blaze3d.systems.RenderSystem;
/**
* A singleton that holds references to different openGL contexts
* and GPU capabilities.
*
* <p>
* Helpful OpenGL resources: <br><br>
*
* https://www.seas.upenn.edu/~pcozzi/OpenGLInsights/OpenGLInsights-AsynchronousBufferTransfers.pdf <br>
* https://learnopengl.com/Advanced-OpenGL/Advanced-Data <br>
* https://gamedev.stackexchange.com/questions/91995/edit-vbo-data-or-create-a-new-one <br><br>
*
*
* @author James Seibel
* @version 9-15-2021
*/
public class GlProxy
{
private static GlProxy instance = null;
public final long deviceContext;
public long minecraftGlContext;
public GLCapabilities minecraftGlCapabilities;
public long lodBuilderGlContext;
public GLCapabilities lodBuilderGlCapabilities;
/** This is just used for debugging, hopefuly it can be removed once
* the context switching is more stable. */
public Thread lodBuilderOwnerThread = null;
/**
* Does this computer's GPU support fancy fog?
*/
public final boolean fancyFogAvailable;
private GlProxy()
{
// getting Minecraft's context has to be done on the render thread,
// where the GL context is
if (!RenderSystem.isOnRenderThread())
throw new IllegalStateException(GlProxy.class.getSimpleName() + " was created outside the render thread!");
//============================//
// create the builder context //
//============================//
minecraftGlContext = WGL.wglGetCurrentContext();
minecraftGlCapabilities = GL.getCapabilities();
deviceContext = WGL.wglGetCurrentDC();
lodBuilderGlContext = WGL.wglCreateContext(deviceContext);
if (!WGL.wglShareLists(minecraftGlContext, lodBuilderGlContext))
throw new IllegalStateException("Unable to share lists between Minecraft and builder contexts.");
if (!WGL.wglMakeCurrent(deviceContext, lodBuilderGlContext))
throw new IllegalStateException("Unable to change OpenGL contexts! tried to change to [" + GlProxyContext.LOD_BUILDER.toString() + "] from [" + GlProxyContext.MINECRAFT.toString() + "]");
lodBuilderGlCapabilities = GL.createCapabilities();
WGL.wglMakeCurrent(deviceContext, 0L);
// Since this is called on the render thread, make sure the Minecraft context is used in the end
WGL.wglMakeCurrent(deviceContext, minecraftGlContext);
//==================================//
// get any GPU related capabilities //
//==================================//
// see if this GPU can run fancy fog
fancyFogAvailable = GL.getCapabilities().GL_NV_fog_distance;
if (!fancyFogAvailable)
{
ClientProxy.LOGGER.info("This GPU does not support GL_NV_fog_distance. This means that the fancy fog option will not be available.");
}
}
/**
* A simple wrapper function to make switching contexts easier
*
* @throws IllegalStateException if unable to change to newContext. <br>
* This expection should never be thrown if
* switching to GlProxyContext.NONE
*/
public void setGlContext(GlProxyContext newContext)
{
GlProxyContext currentContext = getGlContext();
// we don't have to change the context, we're already there.
if (currentContext == newContext)
return;
long contextPointer = 0L;
GLCapabilities newGlCapabilities = null;
switch(newContext)
{
case LOD_BUILDER:
contextPointer = lodBuilderGlContext;
newGlCapabilities = lodBuilderGlCapabilities;
break;
case MINECRAFT:
contextPointer = minecraftGlContext;
newGlCapabilities = minecraftGlCapabilities;
break;
case NONE:
contextPointer = 0L; // equivalent to null
newGlCapabilities = null;
break;
default:
// should never happen, here to make the compiler happy
}
if (!WGL.wglMakeCurrent(deviceContext, contextPointer))
throw new IllegalStateException("Unable to change OpenGL contexts! tried to change to [" + newContext.toString() + "] from [" + currentContext.toString() + "] on thread: [" + Thread.currentThread().getName() + "] lod builder owner thread: " + (lodBuilderOwnerThread != null ? lodBuilderOwnerThread.getName() : "null"));
if (newContext == GlProxyContext.LOD_BUILDER)
{
lodBuilderOwnerThread = Thread.currentThread();
}
else if (newContext == GlProxyContext.NONE && currentContext == GlProxyContext.LOD_BUILDER)
{
lodBuilderOwnerThread = null;
}
GL.setCapabilities(newGlCapabilities);
}
/**
* Returns this thread's OpenGL context.
*/
public GlProxyContext getGlContext()
{
long currentContext = WGL.wglGetCurrentContext();
if(currentContext == lodBuilderGlContext)
{
return GlProxyContext.LOD_BUILDER;
}
else if(currentContext == minecraftGlContext)
{
return GlProxyContext.MINECRAFT;
}
else
{
return GlProxyContext.NONE;
}
}
/** Minecraft, Alpha, Beta, None */
public enum GlProxyContext
{
MINECRAFT,
LOD_BUILDER,
/** used to un-bind threads */
NONE,
}
public static GlProxy getInstance()
{
try
{
if (instance == null)
instance = new GlProxy();
}catch (Exception e)
{
e.printStackTrace();
}
return instance;
}
}
File diff suppressed because it is too large Load Diff
@@ -17,23 +17,27 @@
*/
package com.seibel.lod.render;
import com.seibel.lod.enums.LodDetail;
import com.seibel.lod.config.LodConfig;
import com.seibel.lod.enums.LodTemplate;
import com.seibel.lod.handlers.LodConfig;
import com.seibel.lod.objects.LodRegion;
import com.seibel.lod.util.DetailDistanceUtil;
import com.seibel.lod.util.LodUtil;
import com.seibel.lod.wrappers.MinecraftWrapper;
import net.minecraft.client.Minecraft;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.ChunkPos;
import net.minecraft.util.math.vector.Vector3d;
/**
* This holds miscellaneous helper code
* to be used in the rendering process.
*
* @author James Seibel
* @version 6-17-2021
* @version 8-21-2021
*/
public class RenderUtil
{
private static final Minecraft mc = Minecraft.getInstance();
private static final MinecraftWrapper mc = MinecraftWrapper.INSTANCE;
/**
@@ -42,28 +46,24 @@ public class RenderUtil
*/
public static boolean isChunkPosInLoadedArea(ChunkPos pos, ChunkPos center)
{
Minecraft mc = Minecraft.getInstance();
return (pos.x >= center.x - mc.options.renderDistance
&& pos.x <= center.x + mc.options.renderDistance)
return (pos.x >= center.x - mc.getRenderDistance()
&& pos.x <= center.x + mc.getRenderDistance())
&&
(pos.z >= center.z - mc.options.renderDistance
&& pos.z <= center.z + mc.options.renderDistance);
(pos.z >= center.z - mc.getRenderDistance()
&& pos.z <= center.z + mc.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 i, int j, int centerCoordinate)
public static boolean isCoordinateInLoadedArea(int x, int z, int centerCoordinate)
{
Minecraft mc = Minecraft.getInstance();
return (i >= centerCoordinate - mc.options.renderDistance
&& i <= centerCoordinate + mc.options.renderDistance)
return (x >= centerCoordinate - mc.getRenderDistance()
&& x <= centerCoordinate + mc.getRenderDistance())
&&
(j >= centerCoordinate - mc.options.renderDistance
&& j <= centerCoordinate + mc.options.renderDistance);
(z >= centerCoordinate - mc.getRenderDistance()
&& z <= centerCoordinate + mc.getRenderDistance());
}
@@ -85,27 +85,68 @@ public class RenderUtil
/**
* Get how much buffer memory would be required for the given radius multiplier
*
* issue #62
* TODO check if this is actually returning the correct memory needed
* it is possible (albeit unlikely) to have a buffer indexOutOfBounds exception
* which is caused by the buffer not being big enough.
*/
public static int getBufferMemoryForRadiusMultiplier(int radiusMultiplier)
{
int numbChunksWide = mc.options.renderDistance *
radiusMultiplier * 2;
public static int getBufferMemoryForRegion(LodRegion region)
{
// calculate the max amount of buffer memory needed (in bytes)
return numbChunksWide * numbChunksWide *
LodConfig.CLIENT.lodTemplate.get().
getBufferMemoryForSingleLod(LodConfig.CLIENT.lodDetail.get());
return region.getMinMemoryNeeded(LodConfig.CLIENT.graphics.lodTemplate.get());
}
/**
* Returns the maxViewDistanceMultiplier for the given LodTemplate
* at the given LodDetail level.
*/
public static int getMaxRadiusMultiplierWithAvaliableMemory(LodTemplate lodTemplate, LodDetail lodDetail)
/*public static int getMaxRadiusMultiplierWithAvaliableMemory(LodTemplate lodTemplate, int detailLevel)
{
int maxNumberOfLods = LodRenderer.MAX_ALOCATEABLE_DIRECT_MEMORY / lodTemplate.getBufferMemoryForSingleLod(lodDetail);
int maxNumberOfLods = LodRenderer.MAX_ALOCATEABLE_DIRECT_MEMORY / lodTemplate.getBufferMemoryForSingleLod();
int numbLodsWide = (int) Math.sqrt(maxNumberOfLods);
return numbLodsWide / (2 * mc.options.renderDistance);
return numbLodsWide / (2 * mc.getRenderDistance());
}*/
/**
* Returns true if one of the region's 4 corners is in front
* of the camera.
*/
public static boolean isRegionInViewFrustum(BlockPos playerBlockPos, Vector3d cameraDir, BlockPos vboCenterPos)
{
// convert the vbo position into a direction vector
// starting from the player's position
Vector3d vboVec = new Vector3d(vboCenterPos.getX(), 0, vboCenterPos.getZ());
Vector3d playerVec = new Vector3d(playerBlockPos.getX(), playerBlockPos.getY(), playerBlockPos.getZ());
Vector3d vboCenterVec = vboVec.subtract(playerVec);
int halfRegionWidth = LodUtil.REGION_WIDTH / 2;
// calculate the 4 corners
Vector3d vboSeVec = new Vector3d(vboCenterVec.x + halfRegionWidth, vboCenterVec.y, vboCenterVec.z + halfRegionWidth);//.normalize();
Vector3d vboSwVec = new Vector3d(vboCenterVec.x - halfRegionWidth, vboCenterVec.y, vboCenterVec.z + halfRegionWidth);//.normalize();
Vector3d vboNwVec = new Vector3d(vboCenterVec.x - halfRegionWidth, vboCenterVec.y, vboCenterVec.z - halfRegionWidth);//.normalize();
Vector3d vboNeVec = new Vector3d(vboCenterVec.x + halfRegionWidth, vboCenterVec.y, vboCenterVec.z - halfRegionWidth);//.normalize();
// 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(Vector3d objectVector, Vector3d 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 disappearences
return objectVector.dot(cameraDir) > -0.1;
}
}
@@ -0,0 +1,114 @@
package com.seibel.lod.util;
import java.awt.*;
public class ColorUtil
{
public static int rgbToInt(int red, int green, int blue)
{
return (0xFF << 24) | (red << 16) | (green << 8) | blue;
}
public static int rgbToInt(int alpha, int red, int green, int blue)
{
return (alpha << 24) | (red << 16) | (green << 8) | blue;
}
public static int getAlpha(int color)
{
return (color >> 24) & 0xFF;
}
public static int getRed(int color)
{
return (color >> 16) & 0xFF;
}
public static int getGreen(int color)
{
return (color >> 8) & 0xFF;
}
public static int getBlue(int color)
{
return color & 0xFF;
}
public static int applyShade(int color, int shade)
{
if (shade < 0)
{
return (getAlpha(color) << 24) | (Math.max(getRed(color) + shade, 0) << 16) | (Math.max(getGreen(color) + shade, 0) << 8) | Math.max(getBlue(color) + shade, 0);
} else
{
return (getAlpha(color) << 24) | (Math.min(getRed(color) + shade, 255) << 16) | (Math.min(getGreen(color) + shade, 255) << 8) | Math.min(getBlue(color) + shade, 255);
}
}
public static int applyShade(int color, float shade)
{
if (shade < 1)
{
return (getAlpha(color) << 24) | ((int) Math.max(getRed(color) * shade, 0) << 16) | ((int) Math.max(getGreen(color) * shade, 0) << 8) | (int) Math.max(getBlue(color) * shade, 0);
} else
{
return (getAlpha(color) << 24) | ((int) Math.min(getRed(color) * shade, 255) << 16) | ((int) Math.min(getGreen(color) * shade, 255) << 8) | (int) Math.min(getBlue(color) * shade, 255);
}
}
/**
* Edit the given color as a HSV (Hue Saturation Value) color.
*/
public static int applySaturationAndBrightnessMultipliers(int color, float saturationMultiplier, float brightnessMultiplier)
{
float[] hsv = Color.RGBtoHSB(getRed(color), getGreen(color), getBlue(color), null);
return Color.getHSBColor(
hsv[0], // hue
LodUtil.clamp(0.0f, hsv[1] * saturationMultiplier, 1.0f),
LodUtil.clamp(0.0f, hsv[2] * brightnessMultiplier, 1.0f)).getRGB();
}
/**
* Edit the given color as a HSV (Hue Saturation Value) color.
*/
public static int changeBrightness(int color, float brightness)
{
float[] hsv = Color.RGBtoHSB(getRed(color), getGreen(color), getBlue(color), null);
return Color.getHSBColor(
hsv[0], // hue
hsv[1],
brightness).getRGB();
}
/**
* Edit the given color as a HSV (Hue Saturation Value) color.
*/
public static int changeBrightnessValue(int color, int brightnessColor)
{
float[] hsv = Color.RGBtoHSB(getRed(color), getGreen(color), getBlue(color), null);
float brightness = Color.RGBtoHSB(getRed(brightnessColor), getGreen(brightnessColor), getBlue(brightnessColor), null)[2];
return Color.getHSBColor(
hsv[0], // hue
hsv[1],
brightness).getRGB();
}
public static int multiplyRGBcolors(int color1, int color2)
{
/**TODO FIX the alpha*/
return 0xFF000000 | (((getRed(color1) * getRed(color2)) << 8) & 0xFF0000) | ((getGreen(color1) * getGreen(color2)) & 0xFF00) | (((getBlue(color1) * getBlue(color2)) >> 8) & 0xFF);
}
public static String toString(int color)
{
StringBuilder s = new StringBuilder();
s.append(Integer.toHexString(getAlpha(color)));
s.append(" ");
s.append(Integer.toHexString(getRed(color)));
s.append(" ");
s.append(Integer.toHexString(getGreen(color)));
s.append(" ");
s.append(Integer.toHexString(getBlue(color)));
return s.toString();
}
}
@@ -0,0 +1,432 @@
package com.seibel.lod.util;
import com.seibel.lod.enums.DistanceGenerationMode;
import net.minecraft.client.renderer.texture.NativeImage;
public class DataPointUtil
{
/*
|a |a |a |a |r |r |r |r |
|r |r |r |r |g |g |g |g |
|g |g |g |g |b |b |b |b |
|b |b |b |b |h |h |h |h |
|h |h |h |h |h |h |d |d |
|d |d |d |d |d |d |d |d |
|bl |bl |bl |bl |sl |sl |sl |sl |
|l |l |f |g |g |g |v |e |
*/
// Reminder: bytes have range of [-128, 127].
// When converting to or from a int a 128 should be added or removed.
// If there is a bug with color then it's probably caused by this.
//To be used in the future for negative value
//public final static int MIN_DEPTH = -64;
//public final static int MIN_HEIGHT = -64;
public final static int EMPTY_DATA = 0;
public static int worldHeight = 256;
public final static int ALPHA_DOWNSIZE_SHIFT = 4;
//public final static int BLUE_COLOR_SHIFT = 0;
//public final static int GREEN_COLOR_SHIFT = 8;
//public final static int RED_COLOR_SHIFT = 16;
//public final static int ALPHA_COLOR_SHIFT = 24;
public final static int BLUE_SHIFT = 36;
public final static int GREEN_SHIFT = BLUE_SHIFT + 8;
public final static int RED_SHIFT = BLUE_SHIFT + 16;
public final static int ALPHA_SHIFT = BLUE_SHIFT + 24;
public final static int COLOR_SHIFT = 36;
public final static int HEIGHT_SHIFT = 26;
public final static int DEPTH_SHIFT = 16;
public final static int BLOCK_LIGHT_SHIFT = 12;
public final static int SKY_LIGHT_SHIFT = 8;
//public final static int LIGHTS_SHIFT = SKY_LIGHT_SHIFT;
//public final static int VERTICAL_INDEX_SHIFT = 6;
//public final static int FLAG_SHIFT = 5;
public final static int GEN_TYPE_SHIFT = 2;
public final static int VOID_SHIFT = 1;
public final static int EXISTENCE_SHIFT = 0;
public final static long ALPHA_MASK = 0b1111;
public final static long RED_MASK = 0b1111_1111;
public final static long GREEN_MASK = 0b1111_1111;
public final static long BLUE_MASK = 0b1111_1111;
//public final static long COLOR_MASK = 0b11111111_11111111_11111111;
public final static long HEIGHT_MASK = 0b11_1111_1111;
public final static long DEPTH_MASK = 0b11_1111_1111;
//public final static long LIGHTS_MASK = 0b1111_1111;
public final static long BLOCK_LIGHT_MASK = 0b1111;
public final static long SKY_LIGHT_MASK = 0b1111;
//public final static long VERTICAL_INDEX_MASK = 0b11;
//public final static long FLAG_MASK = 0b1;
public final static long GEN_TYPE_MASK = 0b111;
public final static long VOID_MASK = 1;
public final static long EXISTENCE_MASK = 1;
public static long createVoidDataPoint(int generationMode)
{
long dataPoint = 0;
dataPoint += (generationMode & GEN_TYPE_MASK) << GEN_TYPE_SHIFT;
dataPoint += VOID_MASK << VOID_SHIFT;
dataPoint += EXISTENCE_MASK << EXISTENCE_SHIFT;
return dataPoint;
}
public static long createDataPoint(int height, int depth, int color, int lightSky, int lightBlock, int generationMode)
{
return createDataPoint(
ColorUtil.getAlpha(color),
ColorUtil.getRed(color),
ColorUtil.getGreen(color),
ColorUtil.getBlue(color),
height, depth, lightSky, lightBlock, generationMode);
}
public static long createDataPoint(int alpha, int red, int green, int blue, int height, int depth, int lightSky, int lightBlock, int generationMode)
{
long dataPoint = 0;
dataPoint += ((alpha >>> ALPHA_DOWNSIZE_SHIFT) & ALPHA_MASK) << ALPHA_SHIFT;
dataPoint += (red & RED_MASK) << RED_SHIFT;
dataPoint += (green & GREEN_MASK) << GREEN_SHIFT;
dataPoint += (blue & BLUE_MASK) << BLUE_SHIFT;
dataPoint += (height & HEIGHT_MASK) << HEIGHT_SHIFT;
dataPoint += (depth & DEPTH_MASK) << DEPTH_SHIFT;
dataPoint += (lightBlock & BLOCK_LIGHT_MASK) << BLOCK_LIGHT_SHIFT;
dataPoint += (lightSky & SKY_LIGHT_MASK) << SKY_LIGHT_SHIFT;
dataPoint += (generationMode & GEN_TYPE_MASK) << GEN_TYPE_SHIFT;
dataPoint += EXISTENCE_MASK << EXISTENCE_SHIFT;
return dataPoint;
}
public static short getHeight(long dataPoint)
{
return (short) ((dataPoint >>> HEIGHT_SHIFT) & HEIGHT_MASK);
}
public static short getDepth(long dataPoint)
{
return (short) ((dataPoint >>> DEPTH_SHIFT) & DEPTH_MASK);
}
public static short getAlpha(long dataPoint)
{
return (short) (((dataPoint >>> ALPHA_SHIFT) & ALPHA_MASK) << ALPHA_DOWNSIZE_SHIFT);
}
public static short getRed(long dataPoint)
{
return (short) ((dataPoint >>> RED_SHIFT) & RED_MASK);
}
public static short getGreen(long dataPoint)
{
return (short) ((dataPoint >>> GREEN_SHIFT) & GREEN_MASK);
}
public static short getBlue(long dataPoint)
{
return (short) ((dataPoint >>> BLUE_SHIFT) & BLUE_MASK);
}
public static int getLightSky(long dataPoint)
{
return (int) ((dataPoint >>> SKY_LIGHT_SHIFT) & SKY_LIGHT_MASK);
}
public static int getLightBlock(long dataPoint)
{
return (int) ((dataPoint >>> BLOCK_LIGHT_SHIFT) & BLOCK_LIGHT_MASK);
}
public static byte getGenerationMode(long dataPoint)
{
return (byte) ((dataPoint >>> GEN_TYPE_SHIFT) & GEN_TYPE_MASK);
}
public static boolean isVoid(long dataPoint)
{
return (((dataPoint >>> VOID_SHIFT) & VOID_MASK) == 1);
}
public static boolean doesItExist(long dataPoint)
{
return (((dataPoint >>> EXISTENCE_SHIFT) & EXISTENCE_MASK) == 1);
}
public static int getColor(long dataPoint)
{
//int color = getBlue(dataPoint) << BLUE_COLOR_SHIFT;
//color += getRed(dataPoint) << BLUE_COLOR_SHIFT;
return (int) (dataPoint >>> COLOR_SHIFT);
}
public static int getLightColor(long dataPoint, NativeImage lightMap)
{
int lightBlock = getLightBlock(dataPoint);
int lightSky = getLightSky(dataPoint);
int color = lightMap.getPixelRGBA(lightBlock, lightSky);
int red = ColorUtil.getBlue(color);
int green = ColorUtil.getGreen(color);
int blue = ColorUtil.getRed(color);
return ColorUtil.multiplyRGBcolors(getColor(dataPoint), ColorUtil.rgbToInt(red, green, blue));
}
public static String toString(long dataPoint)
{
StringBuilder s = new StringBuilder();
s.append(getHeight(dataPoint));
s.append(" ");
s.append(getDepth(dataPoint));
s.append(" ");
s.append(getAlpha(dataPoint));
s.append(" ");
s.append(getRed(dataPoint));
s.append(" ");
s.append(getBlue(dataPoint));
s.append(" ");
s.append(getGreen(dataPoint));
s.append(" ");
s.append(getLightBlock(dataPoint));
s.append(" ");
s.append(getLightSky(dataPoint));
s.append(" ");
s.append(getGenerationMode(dataPoint));
s.append(" ");
s.append(isVoid(dataPoint));
s.append(" ");
s.append(doesItExist(dataPoint));
s.append('\n');
return s.toString();
}
public static long mergeSingleData(long[] dataToMerge)
{
int numberOfChildren = 0;
int tempAlpha = 0;
int tempRed = 0;
int tempGreen = 0;
int tempBlue = 0;
int tempHeight = Integer.MIN_VALUE;
int tempDepth = Integer.MAX_VALUE;
int tempLightBlock = 0;
int tempLightSky = 0;
byte tempGenMode = DistanceGenerationMode.SERVER.complexity;
boolean allEmpty = true;
boolean allVoid = true;
for (long data : dataToMerge)
{
if (DataPointUtil.doesItExist(data))
{
allEmpty = false;
if (!(DataPointUtil.isVoid(data)))
{
numberOfChildren++;
allVoid = false;
tempAlpha += DataPointUtil.getAlpha(data);
tempRed += DataPointUtil.getRed(data);
tempGreen += DataPointUtil.getGreen(data);
tempBlue += DataPointUtil.getBlue(data);
tempHeight = Math.max(tempHeight, DataPointUtil.getHeight(data));
tempDepth = Math.min(tempDepth, DataPointUtil.getDepth(data));
tempLightBlock += DataPointUtil.getLightBlock(data);
tempLightSky += DataPointUtil.getLightSky(data);
}
tempGenMode = (byte) Math.min(tempGenMode, DataPointUtil.getGenerationMode(data));
} else
{
tempGenMode = (byte) Math.min(tempGenMode, DistanceGenerationMode.NONE.complexity);
}
}
if (allEmpty)
{
//no child has been initialized
return DataPointUtil.EMPTY_DATA;
} else if (allVoid)
{
//all the children are void
return DataPointUtil.createVoidDataPoint(tempGenMode);
} else
{
//we have at least 1 child
tempAlpha = tempAlpha / numberOfChildren;
tempRed = tempRed / numberOfChildren;
tempGreen = tempGreen / numberOfChildren;
tempBlue = tempBlue / numberOfChildren;
tempLightBlock = tempLightBlock / numberOfChildren;
tempLightSky = tempLightSky / numberOfChildren;
return DataPointUtil.createDataPoint(tempAlpha, tempRed, tempGreen, tempBlue, tempHeight, tempDepth, tempLightSky, tempLightBlock, tempGenMode);
}
}
public static long[] mergeMultiData(long[] dataToMerge, int inputVerticalData, int maxVerticalData)
{
int size = dataToMerge.length / inputVerticalData;
// We initialize the arrays that are going to be used
short[] projection = ThreadMapUtil.getFreshProjectionArray((worldHeight) / 16 + 1);
short[] heightAndDepth = ThreadMapUtil.getFreshHeightAndDepth((worldHeight + 1) * 2);
long[] singleDataToMerge = ThreadMapUtil.getFreshSingleAddDataToMerge(size);
long[] dataPoint = ThreadMapUtil.getFreshVerticalDataArray(worldHeight + 1);
int genMode = DistanceGenerationMode.SERVER.complexity;
boolean allEmpty = true;
boolean allVoid = true;
long singleData;
short depth;
short height;
//We collect the indexes of the data, ordered by the depth
for (int index = 0; index < size; index++)
{
for (int dataIndex = 0; dataIndex < inputVerticalData; dataIndex++)
{
singleData = dataToMerge[index * inputVerticalData + dataIndex];
if (doesItExist(singleData))
{
genMode = Math.min(genMode, getGenerationMode(singleData));
allEmpty = false;
if (!isVoid(singleData))
{
allVoid = false;
depth = getDepth(singleData);
height = getHeight(singleData);
for (int y = depth; y <= height; y++)
projection[y / 16] |= 1 << (y & 0xf);
}
}
}
}
//We check if there is any data that's not empty or void
if (allEmpty)
{
return dataPoint;
}
if (allVoid)
{
dataPoint[0] = createVoidDataPoint(genMode);
return dataPoint;
}
//We extract the merged data
int count = 0;
int i = 0;
int ii = 0;
while (i < projection.length)
{
while (i < projection.length && projection[i] == 0) i++;
if (i == projection.length)
break; //we reached end of WORLD_HEIGHT and it's nothing more here
while (ii < 15 && ((projection[i] >>> ii) & 1) == 0) ii++;
if (ii >= 15 && ((projection[i] >>> ii) & 1) == 0) //there is nothing more in this chunk
{
ii = 0;
i++;
continue;
}
depth = (short) (i * 16 + ii);
while (ii < 15 && ((projection[i] >>> ii) & 1) == 1) ii++;
if (ii >= 15 && ((projection[i] >>> ii) & 1) == 1) //if end is not in this chunk
{
ii = 0;
i++;
while (i < projection.length && ~(projection[i]) == 0) i++; //check for big solid blocks
if (i == projection.length) //solid to WORLD_HEIGHT
{
heightAndDepth[count * 2] = depth;
heightAndDepth[count * 2 + 1] = (short) (worldHeight - 1);
break;
}
while ((((projection[i] >>> ii) & 1) == 1)) ii++;
}
height = (short) (i * 16 + ii - 1);
heightAndDepth[count * 2] = depth;
heightAndDepth[count * 2 + 1] = height;
count++;
}
//we limit the vertical portion to maxVerticalData
int j = 0;
while (count > maxVerticalData)
{
ii = worldHeight;
for (i = 0; i < count - 1; i++)
{
if (heightAndDepth[(i + 1) * 2] - heightAndDepth[i * 2 + 1] < ii)
{
ii = heightAndDepth[(i + 1) * 2] - heightAndDepth[i * 2 + 1];
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
for (j = 0; j < count; j++)
{
depth = heightAndDepth[j * 2];
height = heightAndDepth[j * 2 + 1];
if ((depth == 0 && height == 0) || j >= heightAndDepth.length / 2)
break;
for (int k = 0; k < size; k++)
{
singleDataToMerge[k] = 0;
}
for (int index = 0; index < size; index++)
{
for (int dataIndex = 0; dataIndex < inputVerticalData; dataIndex++)
{
singleData = dataToMerge[index * inputVerticalData + dataIndex];
if (doesItExist(singleData) && !isVoid(singleData))
{
if ((depth <= getDepth(singleData) && getDepth(singleData) <= height)
|| (depth <= getHeight(singleData) && getHeight(singleData) <= height))
{
if (getHeight(singleData) > getHeight(singleDataToMerge[index]))
{
singleDataToMerge[index] = singleData;
}
}
}
}
}
long data = mergeSingleData(singleDataToMerge);
dataPoint[count - j - 1] = createDataPoint(height, depth, getColor(data), getLightSky(data), getLightBlock(data), getGenerationMode(data));
}
return dataPoint;
}
public static long[] compress(long[] data, byte detailLevel)
{
return null;
}
}
@@ -0,0 +1,173 @@
package com.seibel.lod.util;
import com.seibel.lod.config.LodConfig;
import com.seibel.lod.enums.DistanceGenerationMode;
import com.seibel.lod.enums.HorizontalResolution;
import com.seibel.lod.enums.VerticalQuality;
public class DetailDistanceUtil
{
private static double genMultiplier = 1.0;
private static double treeGenMultiplier = 1.0;
private static double treeCutMultiplier = 1.0;
private static int minGenDetail = LodConfig.CLIENT.worldGenerator.generationResolution.get().detailLevel;
private static int minDrawDetail = Math.max(LodConfig.CLIENT.graphics.drawResolution.get().detailLevel,LodConfig.CLIENT.worldGenerator.generationResolution.get().detailLevel);
private static int maxDetail = LodUtil.REGION_DETAIL_LEVEL + 1;
private static int minDistance = 0;
private static int maxDistance = LodConfig.CLIENT.graphics.lodChunkRenderDistance.get() * 16 * 2;
private static int[] maxVerticalData = {
4,
4,
4,
2,
2,
1,
1,
1,
1,
1,
1};
private static HorizontalResolution[] lodGenDetails = {
HorizontalResolution.BLOCK,
HorizontalResolution.TWO_BLOCKS,
HorizontalResolution.FOUR_BLOCKS,
HorizontalResolution.HALF_CHUNK,
HorizontalResolution.CHUNK,
HorizontalResolution.CHUNK,
HorizontalResolution.CHUNK,
HorizontalResolution.CHUNK,
HorizontalResolution.CHUNK,
HorizontalResolution.CHUNK,
HorizontalResolution.CHUNK};
public static void updateSettings(){
minGenDetail = LodConfig.CLIENT.worldGenerator.generationResolution.get().detailLevel;
minDrawDetail = Math.max(LodConfig.CLIENT.graphics.drawResolution.get().detailLevel,LodConfig.CLIENT.worldGenerator.generationResolution.get().detailLevel);
maxDistance = LodConfig.CLIENT.graphics.lodChunkRenderDistance.get() * 16 * 8;
}
public static int baseDistanceFunction(int detail)
{
if (detail <= minGenDetail)
return minDistance;
if (detail >= maxDetail)
return maxDistance;
int distanceUnit = LodConfig.CLIENT.worldGenerator.horizontalScale.get().distanceUnit;
switch (LodConfig.CLIENT.worldGenerator.horizontalQuality.get()){
case LINEAR:;
return (detail * distanceUnit);
default:
double base = LodConfig.CLIENT.worldGenerator.horizontalQuality.get().quadraticBase;
return (int) (Math.pow(base, detail) * distanceUnit);
}
}
public static int getDrawDistanceFromDetail(int detail)
{
return baseDistanceFunction(detail);
}
public static byte baseInverseFunction(int distance, int minDetail)
{
int detail = 0;
if (distance == 0)
return (byte) minDetail;
int distanceUnit = LodConfig.CLIENT.worldGenerator.horizontalScale.get().distanceUnit;
switch (LodConfig.CLIENT.worldGenerator.horizontalQuality.get()){
case LINEAR:
detail = (byte) Math.floorDiv(distance, distanceUnit);
break;
default:
double base = LodConfig.CLIENT.worldGenerator.horizontalQuality.get().quadraticBase;
double logBase = Math.log(base);
detail = (byte) (Math.log(Math.floorDiv(distance, distanceUnit))/logBase);
break;
}
return (byte) LodUtil.clamp(minDetail, detail, maxDetail-1);
}
public static byte getDrawDetailFromDistance(int distance)
{
return baseInverseFunction(distance, minDrawDetail);
}
public static byte getGenerationDetailFromDistance(int distance)
{
return baseInverseFunction((int) (distance * genMultiplier), minGenDetail);
}
public static byte getTreeCutDetailFromDistance(int distance)
{
return baseInverseFunction((int) (distance * treeCutMultiplier), minGenDetail);
}
public static byte getTreeGenDetailFromDistance(int distance)
{
return baseInverseFunction((int) (distance * treeGenMultiplier), minGenDetail);
}
public static DistanceGenerationMode getDistanceGenerationMode(int detail)
{
return LodConfig.CLIENT.worldGenerator.distanceGenerationMode.get();
}
public static byte getLodDrawDetail(int detail)
{
if (detail < minDrawDetail)
{
if(LodConfig.CLIENT.graphics.alwaysDrawAtMaxQuality.get())
return getLodGenDetail(minDrawDetail).detailLevel;
else
return (byte) minDrawDetail;
} else
{
if(LodConfig.CLIENT.graphics.alwaysDrawAtMaxQuality.get())
return getLodGenDetail(detail).detailLevel;
else
return (byte) detail;
}
}
public static HorizontalResolution getLodGenDetail(int detail)
{
if (detail < minGenDetail)
{
return lodGenDetails[minGenDetail];
} else
{
return lodGenDetails[detail];
}
}
public static byte getCutLodDetail(int detail)
{
if (detail < minGenDetail)
{
return lodGenDetails[minGenDetail].detailLevel;
} else if (detail == maxDetail)
{
return LodUtil.REGION_DETAIL_LEVEL;
} else
{
return lodGenDetails[detail].detailLevel;
}
}
public static int getMaxVerticalData(int detail)
{
if(LodConfig.CLIENT.worldGenerator.lodQualityMode.get() == VerticalQuality.HEIGHTMAP)
return 1;
return maxVerticalData[LodUtil.clamp(minGenDetail, detail, LodUtil.REGION_DETAIL_LEVEL)];
}
}
@@ -0,0 +1,229 @@
package com.seibel.lod.util;
import com.seibel.lod.config.LodConfig;
public class LevelPosUtil
{
public static int[] convert(int[] levelPos, byte newDetailLevel)
{
return convert(getDetailLevel(levelPos), getPosX(levelPos), getPosZ(levelPos), newDetailLevel);
}
public static int[] convert(byte detailLevel, int posX, int posZ, byte newDetailLevel)
{
int width;
if (newDetailLevel >= detailLevel)
{
width = 1 << (newDetailLevel - detailLevel);
return createLevelPos(
newDetailLevel,
Math.floorDiv(posX, width),
Math.floorDiv(posZ, width));
} else
{
width = 1 << (detailLevel - newDetailLevel);
return createLevelPos(
newDetailLevel,
posX * width,
posZ * width);
}
}
public static int[] createLevelPos(byte detailLevel, int posX, int posZ)
{
return new int[]{detailLevel, posX, posZ};
}
public static int convert(byte detailLevel, int pos, byte newDetailLevel)
{
int width;
if (newDetailLevel >= detailLevel)
{
width = 1 << (newDetailLevel - detailLevel);
return Math.floorDiv(pos, width);
} else
{
width = 1 << (detailLevel - newDetailLevel);
return pos * width;
}
}
public static int getRegion(byte detailLevel, int pos)
{
return Math.floorDiv(pos, 1 << (LodUtil.REGION_DETAIL_LEVEL - detailLevel));
}
public static int getRegionModule(byte detailLevel, int pos)
{
return Math.floorMod(pos, 1 << (LodUtil.REGION_DETAIL_LEVEL - detailLevel));
}
public static byte getDetailLevel(int[] levelPos)
{
return (byte) levelPos[0];
}
public static int getPosX(int[] levelPos)
{
return levelPos[1];
}
public static int getPosZ(int[] levelPos)
{
return levelPos[2];
}
public static int getDistance(int[] levelPos)
{
return levelPos[3];
}
public static int[] getRegionModule(int[] levelPos)
{
return getRegionModule(getDetailLevel(levelPos), getPosX(levelPos), getPosZ(levelPos));
}
public static int[] getRegionModule(byte detailLevel, int posX, int posZ)
{
int width = 1 << (LodUtil.REGION_DETAIL_LEVEL - detailLevel);
return createLevelPos(
detailLevel,
Math.floorMod(posX, width),
Math.floorMod(posZ, width));
}
public static int[] applyOffset(int[] levelPos, int xOffset, int zOffset)
{
return createLevelPos(
getDetailLevel(levelPos),
getPosX(levelPos) + xOffset,
getPosZ(levelPos) + zOffset);
}
public static int[] applyLevelOffset(int[] levelPos, byte detailOffset, int xOffset, int zOffset)
{
return createLevelPos(
getDetailLevel(levelPos),
getPosX(levelPos) + xOffset * (1 << detailOffset),
getPosZ(levelPos) + zOffset * (1 << detailOffset));
}
public static int getRegionPosX(int[] levelPos)
{
int width = 1 << (LodUtil.REGION_DETAIL_LEVEL - getDetailLevel(levelPos));
return Math.floorDiv(getPosX(levelPos), width);
}
public static int getRegionPosZ(int[] levelPos)
{
int width = 1 << (LodUtil.REGION_DETAIL_LEVEL - getDetailLevel(levelPos));
return Math.floorDiv(getPosZ(levelPos), width);
}
public static int getChunkPos(byte detailLevel, int pos)
{
return convert(detailLevel,pos, LodUtil.CHUNK_DETAIL_LEVEL);
}
public static int maxDistance(byte detailLevel, int posX, int posZ, int playerPosX, int playerPosZ)
{
int width = 1 << detailLevel;
int startPosX = posX * width;
int startPosZ = posZ * width;
int endPosX = startPosX + width;
int endPosZ = startPosZ + width;
int maxDistance = (int) Math.sqrt(Math.pow(playerPosX - startPosX, 2) + Math.pow(playerPosZ - startPosZ, 2));
maxDistance = Math.max(maxDistance, (int) Math.sqrt(Math.pow(playerPosX - startPosX, 2) + Math.pow(playerPosZ - endPosZ, 2)));
maxDistance = Math.max(maxDistance, (int) Math.sqrt(Math.pow(playerPosX - endPosX, 2) + Math.pow(playerPosZ - startPosZ, 2)));
maxDistance = Math.max(maxDistance, (int) Math.sqrt(Math.pow(playerPosX - endPosX, 2) + Math.pow(playerPosZ - endPosZ, 2)));
return maxDistance;
}
public static int maxDistance(byte detailLevel, int posX, int posZ, int playerPosX, int playerPosZ, int xRegion, int zRegion)
{
int width = 1 << (LodUtil.REGION_DETAIL_LEVEL - detailLevel);
int newPosX = xRegion * width + posX;
int newPosZ = zRegion * width + posZ;
return maxDistance(detailLevel, newPosX, newPosZ, playerPosX, playerPosZ);
}
public static int minDistance(byte detailLevel, int posX, int posZ, int playerPosX, int playerPosZ)
{
int width = 1 << detailLevel;
int startPosX = posX * width;
int startPosZ = posZ * width;
int endPosX = startPosX + width;
int endPosZ = startPosZ + width;
boolean inXArea = playerPosX >= startPosX && playerPosX <= endPosX;
boolean inZArea = playerPosZ >= startPosZ && playerPosZ <= endPosZ;
if (inXArea && inZArea)
{
return 0;
} else if (inXArea)
{
return Math.min(
Math.abs(playerPosZ - startPosZ),
Math.abs(playerPosZ - endPosZ)
);
} else if (inZArea)
{
return Math.min(
Math.abs(playerPosX - startPosX),
Math.abs(playerPosX - endPosX)
);
} else
{
int minDistance = (int) Math.sqrt(Math.pow(playerPosX - startPosX, 2) + Math.pow(playerPosZ - startPosZ, 2));
minDistance = Math.min(minDistance, (int) Math.sqrt(Math.pow(playerPosX - startPosX, 2) + Math.pow(playerPosZ - endPosZ, 2)));
minDistance = Math.min(minDistance, (int) Math.sqrt(Math.pow(playerPosX - endPosX, 2) + Math.pow(playerPosZ - startPosZ, 2)));
minDistance = Math.min(minDistance, (int) Math.sqrt(Math.pow(playerPosX - endPosX, 2) + Math.pow(playerPosZ - endPosZ, 2)));
return minDistance;
}
}
public static int minDistance(byte detailLevel, int posX, int posZ, int playerPosX, int playerPosZ, int xRegion, int zRegion)
{
int width = 1 << (LodUtil.REGION_DETAIL_LEVEL - detailLevel);
int newPosX = xRegion * width + posX;
int newPosZ = zRegion * width + posZ;
return minDistance(detailLevel, newPosX, newPosZ, playerPosX, playerPosZ);
}
public static int compareDistance(int firstDistance, int secondDistance)
{
return Integer.compare(
firstDistance,
secondDistance);
}
public static int compareLevelAndDistance(byte firstDetail, int firstDistance, byte secondDetail, int secondDistance)
{
int compareResult = Integer.compare(
secondDetail,
firstDetail);
if (compareResult == 0)
{
compareResult = Integer.compare(
firstDistance,
secondDistance);
}
return compareResult;
}
public static String toString(int[] levelPos)
{
return (getDetailLevel(levelPos) + " " + getPosX(levelPos) + " " + getPosZ(levelPos));
}
public static String toString(byte detailLevel, int posX, int posZ)
{
return (detailLevel + " " + posX + " " + posZ);
}
}
@@ -0,0 +1,28 @@
package com.seibel.lod.util;
import java.util.concurrent.ThreadFactory;
/**
* Just a simple ThreadFactory to name ExecutorService
* threads, which can be helpful when debugging.
*
* @author James Seibel
* @version 8-15-2021
*/
public class LodThreadFactory implements ThreadFactory
{
public String threadName;
public LodThreadFactory(String newThreadName)
{
threadName = newThreadName + " Thread";
}
@Override
public Thread newThread(Runnable r)
{
return new Thread(r, threadName);
}
}
+318 -99
View File
@@ -19,30 +19,110 @@ package com.seibel.lod.util;
import java.awt.Color;
import java.io.File;
import java.util.HashSet;
import com.seibel.lod.objects.LodRegion;
import com.seibel.lod.enums.LodTemplate;
import com.seibel.lod.objects.LodDimension;
import com.seibel.lod.objects.RegionPos;
import com.seibel.lod.wrappers.MinecraftWrapper;
import net.minecraft.client.Minecraft;
import net.minecraft.client.multiplayer.ServerData;
import net.minecraft.client.renderer.WorldRenderer;
import net.minecraft.client.renderer.chunk.ChunkRenderDispatcher.CompiledChunk;
import net.minecraft.server.integrated.IntegratedServer;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.ChunkPos;
import net.minecraft.world.DimensionType;
import net.minecraft.world.IWorld;
import net.minecraft.world.chunk.ChunkSection;
import net.minecraft.world.chunk.IChunk;
import net.minecraft.world.gen.Heightmap;
import net.minecraft.world.server.ServerChunkProvider;
import net.minecraft.world.server.ServerWorld;
/**
* This class holds methods that may be used in multiple places.
* This class holds methods and constants that may be used in multiple places.
*
* @author James Seibel
* @version 06-27-2021
* @version 9-7-2021
*/
public class LodUtil
{
private static Minecraft mc = Minecraft.getInstance();
private static MinecraftWrapper mc = MinecraftWrapper.INSTANCE;
/** alpha used when drawing chunks in debug mode */
public static final int DEBUG_ALPHA = 255; // 0 - 255
public static final Color COLOR_DEBUG_BLACK = new Color(0, 0, 0, DEBUG_ALPHA);
public static final Color COLOR_DEBUG_WHITE = new Color(255, 255, 255, DEBUG_ALPHA);
public static final Color COLOR_INVISIBLE = new Color(0,0,0,0);
/** a gray-purple color */
public static final int MYCELIUM_COLOR_INT = LodUtil.colorToInt(Color.decode("#6E6166"));
/** TODO, add a better way to override material colors
* and/or add a method to generate colors based on texture
* issue #64 */
public static final int STONE_COLOR_INT = LodUtil.colorToInt(new Color(150, 150, 150));
public static final int NETHERRACK_COLOR_INT = LodUtil.colorToInt(new Color(95, 38, 38));
public static final int WARPED_NYLIUM_COLOR_INT = LodUtil.colorToInt(new Color(34, 94, 85));
public static final int CRIMSON_NYLIUM_COLOR_INT = LodUtil.colorToInt(new Color(126, 27, 27));
/**
* In order of nearest to farthest: <br>
* Red, Orange, Yellow, Green, Cyan, Blue, Magenta, white, gray, black
*/
public static final Color DEBUG_DETAIL_LEVEL_COLORS[] = new Color[] { Color.RED, Color.ORANGE, Color.YELLOW, Color.GREEN, Color.CYAN, Color.BLUE, Color.MAGENTA, Color.WHITE, Color.GRAY, Color.BLACK };
/** 512 blocks wide */
public static final byte REGION_DETAIL_LEVEL = 9;
/** 16 blocks wide */
public static final byte CHUNK_DETAIL_LEVEL = 4;
/** 1 block wide */
public static final byte BLOCK_DETAIL_LEVEL = 0;
public static final byte DETAIL_OPTIONS = 10;
public static final short MAX_VERTICAL_DATA = 4;
/** measured in Blocks <br>
* detail level 9 */
public static final short REGION_WIDTH = 512;
/** measured in Blocks <br>
* detail level 4 */
public static final short CHUNK_WIDTH = 16;
/** measured in Blocks <br>
* detail level 0 */
public static final short BLOCK_WIDTH = 1;
/** number of chunks wide */
public static final int REGION_WIDTH_IN_CHUNKS = 32;
/** If we ever need to use a heightmap for any reason, use this one. */
public static final Heightmap.Type DEFAULT_HEIGHTMAP = Heightmap.Type.WORLD_SURFACE_WG;
/** This regex finds any characters that are invalid for use in a windows
* (and by extension mac and linux) file path */
public static final String INVALID_FILE_CHARACTERS_REGEX = "[\\\\\\/:*?\\\"<>|]";
/**
* 64 MB by default is the maximum amount of memory that
* can be directly allocated. <br><br>
* <p>
* I know there are commands to change that amount
* (specifically "-XX:MaxDirectMemorySize"), but
* I have no idea how to access that amount. <br>
* So I guess this will be the hard limit for now. <br><br>
* <p>
* https://stackoverflow.com/questions/50499238/bytebuffer-allocatedirect-and-xmx
*/
public static final int MAX_ALOCATEABLE_DIRECT_MEMORY = 64 * 1024 * 1024;
@@ -60,7 +140,7 @@ public class LodUtil
for (ServerWorld world : worlds)
return world;
return null;
}
@@ -86,32 +166,30 @@ public class LodUtil
break;
}
}
return returnWorld;
}
/**
* Convert the given ChunkPos into a RegionPos.
* Convert a 2D absolute position into a quad tree relative position.
*/
public static RegionPos convertChunkPosToRegionPos(ChunkPos pos)
public static RegionPos convertGenericPosToRegionPos(int x, int z, int detailLevel)
{
RegionPos rPos = new RegionPos();
rPos.x = pos.x / LodRegion.SIZE;
rPos.z = pos.z / LodRegion.SIZE;
int relativePosX = Math.floorDiv(x, (int) Math.pow(2, LodUtil.REGION_DETAIL_LEVEL - detailLevel));
int relativePosZ = Math.floorDiv(z, (int) Math.pow(2, LodUtil.REGION_DETAIL_LEVEL - detailLevel));
// prevent issues if X/Z is negative and less than 16
if (pos.x < 0)
{
rPos.x = (Math.abs(rPos.x) * -1) - 1;
}
if (pos.z < 0)
{
rPos.z = (Math.abs(rPos.z) * -1) - 1;
}
return rPos;
return new RegionPos(relativePosX, relativePosZ);
}
/**
* Convert a 2D absolute position into a quad tree relative position.
*/
public static int convertLevelPos(int pos, int currectDetailLevel, int targetDetailLevel)
{
int newPos = Math.floorDiv(pos, (int) Math.pow(2, targetDetailLevel - currectDetailLevel));
return newPos;
}
/**
* Return whether the given chunk
* has any data in it.
@@ -133,80 +211,9 @@ public class LodUtil
public static String getCurrentDimensionID()
{
Minecraft mc = Minecraft.getInstance();
if(mc.hasSingleplayerServer())
{
// this will return the world save location
// and the dimension folder
if(mc.level == null)
return "";
ServerWorld serverWorld = LodUtil.getServerWorldFromDimension(mc.level.dimensionType());
if(serverWorld == null)
return "";
ServerChunkProvider provider = serverWorld.getChunkSource();
if(provider == null)
return "";
return provider.dataStorage.dataFolder.toString();
}
else
{
ServerData server = mc.getCurrentServer();
return server.name + ", IP " +
server.ip + ", GameVersion " +
server.version.getString() + File.separatorChar
+ "dim_" + mc.level.dimensionType().effectsLocation().getPath() + File.separatorChar;
}
}
/**
* If on single player this will return the name of the user's
* world and the dimensional save folder, if in multiplayer
* it will return the server name, game version, and dimension.<br>
* <br>
* This can be used to determine where to save files for a given
* dimension.
*/
public static String getDimensionIDFromWorld(IWorld world)
{
Minecraft mc = Minecraft.getInstance();
if(mc.hasSingleplayerServer())
{
// this will return the world save location
// and the dimension folder
ServerWorld serverWorld = LodUtil.getServerWorldFromDimension(world.dimensionType());
if(serverWorld == null)
throw new NullPointerException("getDimensionIDFromWorld wasn't able to get the ServerWorld for the dimension " + world.dimensionType().effectsLocation().getPath());
ServerChunkProvider provider = serverWorld.getChunkSource();
if(provider == null)
throw new NullPointerException("getDimensionIDFromWorld wasn't able to get the ServerChunkProvider for the dimension " + world.dimensionType().effectsLocation().getPath());
return provider.dataStorage.dataFolder.toString();
}
else
{
ServerData server = mc.getCurrentServer();
return server.name + ", IP " +
server.ip + ", GameVersion " +
server.version.getString() + File.separatorChar
+ "dim_" + world.dimensionType().effectsLocation().getPath() + File.separatorChar;
}
}
/**
* If on single player this will return the name of the user's
* world, if in multiplayer it will return the server name
* world, if in multiplayer it will return the server name, IP,
* and game version.
*/
public static String getWorldID(IWorld world)
@@ -224,15 +231,60 @@ public class LodUtil
}
else
{
ServerData server = mc.getCurrentServer();
return server.name + ", IP " +
server.ip + ", GameVersion " +
server.version.getString();
return getServerId();
}
}
/**
* If on single player this will return the name of the user's
* world and the dimensional save folder, if in multiplayer
* it will return the server name, ip, game version, and dimension.<br>
* <br>
* This can be used to determine where to save files for a given
* dimension.
*/
public static String getDimensionIDFromWorld(IWorld world)
{
if(mc.hasSingleplayerServer())
{
// this will return the world save location
// and the dimension folder
ServerWorld serverWorld = LodUtil.getServerWorldFromDimension(world.dimensionType());
if(serverWorld == null)
throw new NullPointerException("getDimensionIDFromWorld wasn't able to get the ServerWorld for the dimension " + world.dimensionType().effectsLocation().getPath());
ServerChunkProvider provider = serverWorld.getChunkSource();
if(provider == null)
throw new NullPointerException("getDimensionIDFromWorld wasn't able to get the ServerChunkProvider for the dimension " + world.dimensionType().effectsLocation().getPath());
return provider.dataStorage.dataFolder.toString();
}
else
{
return getServerId() + File.separatorChar + "dim_" + world.dimensionType().effectsLocation().getPath() + File.separatorChar;
}
}
/**
* returns the server name, IP and game version.
*/
public static String getServerId()
{
ServerData server = mc.getCurrentServer();
String serverName = server.name.replaceAll(INVALID_FILE_CHARACTERS_REGEX, "");
String serverIp = server.ip.replaceAll(INVALID_FILE_CHARACTERS_REGEX, "");
String serverMcVersion = server.version.getString().replaceAll(INVALID_FILE_CHARACTERS_REGEX, "");
String serverId = serverName + ", IP " + serverIp + ", GameVersion " + serverMcVersion;
return serverId;
}
/**
* Convert a BlockColors int into a Color object.
*/
@@ -254,4 +306,171 @@ public class LodUtil
{
return color.getRGB();
}
/**
* Clamps the given value between the min and max values.
* May behave strangely if min > max.
*/
public static int clamp(int min, int value, int max)
{
return Math.min(max, Math.max(value, min));
}
/**
* Clamps the given value between the min and max values.
* May behave strangely if min > max.
*/
public static float clamp(float min, float value, float max)
{
return Math.min(max, Math.max(value, min));
}
/**
* Clamps the given value between the min and max values.
* May behave strangely if min > max.
*/
public static double clamp(double min, double value, double max)
{
return Math.min(max, Math.max(value, min));
}
/**
* This methods return the number of lods that are going to be rendered in a region in the worst case
* @param regionPosX x region position to check
* @param regionPosZ z region position to check
* @return number of lods in the region
*/
public static int regionRenderingMemoryUse(int regionPosX, int regionPosZ, LodTemplate template)
{
int xRegionSign = (int) Math.signum(regionPosX);
int zRegionSign = (int) Math.signum(regionPosZ);
//we first find the center of the circle which is one of the following X position in the center region
/*
X - X - X
| |
X X X
| |
X - X - X
*/
int circleCenterX = 256 + 256*xRegionSign;
int circleCenterZ = 256 + 256*zRegionSign;
int innerRadius;
int outerRadius;
int size;
int count;
int minDistance;
int maxDistance;
int memoryUse = 0;
int number = 0;
for(byte detailLevel = BLOCK_DETAIL_LEVEL; detailLevel <= REGION_DETAIL_LEVEL; detailLevel++)
{
//We find now the inner and outer detail of this area
innerRadius = DetailDistanceUtil.getDrawDistanceFromDetail(detailLevel);
outerRadius = DetailDistanceUtil.getDrawDistanceFromDetail(detailLevel + 1);
//we skip if the region does not intersect the two circles.
minDistance = LevelPosUtil.minDistance(REGION_DETAIL_LEVEL, regionPosX, regionPosZ, circleCenterX, circleCenterZ);
maxDistance = LevelPosUtil.maxDistance(REGION_DETAIL_LEVEL, regionPosX, regionPosZ, circleCenterX, circleCenterZ);
if (innerRadius > maxDistance || minDistance > outerRadius)
continue;
//we proceed to count all the position in the region that fall between these two circle
size = 1 << (REGION_DETAIL_LEVEL - detailLevel);
count = 0;
for (int x = 0; x < size; x++)
{
for (int z = 0; z < size; z++)
{
minDistance = LevelPosUtil.minDistance(detailLevel, x, z, circleCenterX, circleCenterZ, regionPosX, regionPosZ);
if (innerRadius < minDistance && minDistance < outerRadius)
count++;
}
}
//we multiply the data with the max vertical data of this detail level
int maxVerticalData = DetailDistanceUtil.getMaxVerticalData(detailLevel);
number += count;
count *= maxVerticalData;
memoryUse += template.getBufferMemoryForSingleLod(maxVerticalData) * count;
}
System.out.println(number);
return memoryUse;
}
/**
* Get a HashSet of all ChunkPos within the normal render distance
* that should not be rendered.
*/
public static HashSet<ChunkPos> getNearbyLodChunkPosToSkip(LodDimension lodDim, BlockPos playerPos)
{
int chunkRenderDist = mc.getRenderDistance();
ChunkPos centerChunk = new ChunkPos(playerPos);
// skip chunks that are already going to be rendered by Minecraft
HashSet<ChunkPos> posToSkip = getRenderedChunks();
// go through each chunk within the normal view distance
for (int x = centerChunk.x - chunkRenderDist; x < centerChunk.x + chunkRenderDist; x++)
{
for (int z = centerChunk.z - chunkRenderDist; z < centerChunk.z + chunkRenderDist; z++)
{
if (!lodDim.doesDataExist(LodUtil.CHUNK_DETAIL_LEVEL, x, z))
continue;
long data = lodDim.getSingleData(LodUtil.CHUNK_DETAIL_LEVEL, x, z);
short lodAverageHeight = DataPointUtil.getHeight(data);
if (playerPos.getY() <= lodAverageHeight)
{
// don't draw Lod's that are taller than the player
// to prevent LODs being drawn on top of the player
posToSkip.add(new ChunkPos(x, z));
}
}
}
return posToSkip;
}
/**
* This method returns the ChunkPos of all chunks that Minecraft
* is going to render this frame. <br><br>
* <p>
* Note: This isn't perfect. It will return some chunks that are outside
* the clipping plane. (For example, if you are high above the ground some chunks
* will be incorrectly added, even though they are outside render range).
*/
public static HashSet<ChunkPos> getRenderedChunks()
{
HashSet<ChunkPos> loadedPos = new HashSet<>();
// Wow those are some long names!
// go through every RenderInfo to get the compiled chunks
WorldRenderer renderer = mc.getLevelRenderer();
for (WorldRenderer.LocalRenderInformationContainer worldrenderer$localrenderinformationcontainer : renderer.renderChunks)
{
CompiledChunk compiledChunk = worldrenderer$localrenderinformationcontainer.chunk.getCompiledChunk();
if (!compiledChunk.hasNoRenderableLayers())
{
// add the ChunkPos for every rendered chunk
BlockPos bpos = worldrenderer$localrenderinformationcontainer.chunk.getOrigin();
loadedPos.add(new ChunkPos(bpos));
}
}
return loadedPos;
}
}
@@ -0,0 +1,251 @@
package com.seibel.lod.util;
import java.util.Arrays;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
/**
* Holds data used by specific threads so
* the data doesn't have to be recreated every
* time it is needed.
*
* @author Leonardo Amato
* @version 9-25-2021
*/
public class ThreadMapUtil
{
public static final ConcurrentMap<String, long[]> threadSingleUpdateMap = new ConcurrentHashMap<>();
public static final ConcurrentMap<String, long[][]> threadBuilderArrayMap = new ConcurrentHashMap<>();
public static final ConcurrentMap<String, long[][]> threadBuilderVerticalArrayMap = new ConcurrentHashMap<>();
public static final ConcurrentMap<String, long[]> threadVerticalAddDataMap = new ConcurrentHashMap<>();
public static final ConcurrentMap<String, byte[]> saveContainer = new ConcurrentHashMap<>();
public static final ConcurrentMap<String, short[]> projectionArrayMap = new ConcurrentHashMap<>();
public static final ConcurrentMap<String, short[]> heightAndDepthMap = new ConcurrentHashMap<>();
public static final ConcurrentMap<String, long[]> singleDataToMergeMap = new ConcurrentHashMap<>();
public static final ConcurrentMap<String, long[][]> verticalUpdate = new ConcurrentHashMap<>();
/** returns the array filled with 0's */
public static long[] getFreshSingleUpdateArray(int arrayLength)
{
long[] array = getSingleUpdateArray();
array = clearOrCreateArray(array, arrayLength);
return array;
}
public static long[] getSingleUpdateArray()
{
if (!threadSingleUpdateMap.containsKey(Thread.currentThread().getName()) || (threadSingleUpdateMap.get(Thread.currentThread().getName()) == null))
{
threadSingleUpdateMap.put(Thread.currentThread().getName(), new long[4]);
}
return threadSingleUpdateMap.get(Thread.currentThread().getName());
}
/** returns the array filled with 0's */
public static long[] getFreshBuilderArray(int arrayLength, int detailLevel)
{
long[] array = getBuilderArray(detailLevel);
array = clearOrCreateArray(array, arrayLength);
return array;
}
public static long[] getBuilderArray(int detailLevel)
{
if (!threadBuilderArrayMap.containsKey(Thread.currentThread().getName()) || (threadBuilderArrayMap.get(Thread.currentThread().getName()) == null))
{
long[][] array = new long[5][];
threadBuilderArrayMap.put(Thread.currentThread().getName(), array);
}
return threadBuilderArrayMap.get(Thread.currentThread().getName())[detailLevel];
}
/** returns the array filled with 0's */
public static long[] getFreshBuilderVerticalArray(int arrayLength, int detailLevel)
{
long[] array = getBuilderVerticalArray(detailLevel);
array = clearOrCreateArray(array, arrayLength);
return array;
}
public static long[] getBuilderVerticalArray(int detailLevel)
{
if (!threadBuilderVerticalArrayMap.containsKey(Thread.currentThread().getName()) || (threadBuilderVerticalArrayMap.get(Thread.currentThread().getName()) == null))
{
long[][] array = new long[5][];
threadBuilderVerticalArrayMap.put(Thread.currentThread().getName(), array);
}
return threadBuilderVerticalArrayMap.get(Thread.currentThread().getName())[detailLevel];
}
/** returns the array filled with 0's */
public static long[] getFreshVerticalDataArray(int arrayLength)
{
long[] array = getVerticalDataArray();
array = clearOrCreateArray(array, arrayLength);
return array;
}
public static long[] getVerticalDataArray()
{
if (!threadVerticalAddDataMap.containsKey(Thread.currentThread().getName()) || (threadVerticalAddDataMap.get(Thread.currentThread().getName()) == null))
{
threadVerticalAddDataMap.put(Thread.currentThread().getName(), new long[0]);
}
return threadVerticalAddDataMap.get(Thread.currentThread().getName());
}
/** returns the array filled with 0's */
public static short[] getFreshProjectionArray(int arrayLength)
{
short[] array = getProjectionArray();
array = clearOrCreateArray(array, arrayLength);
return array;
}
public static short[] getProjectionArray()
{
if (!projectionArrayMap.containsKey(Thread.currentThread().getName()) || (projectionArrayMap.get(Thread.currentThread().getName()) == null))
{
projectionArrayMap.put(Thread.currentThread().getName(), new short[0]);
}
return projectionArrayMap.get(Thread.currentThread().getName());
}
/** returns the array filled with 0's */
public static short[] getFreshHeightAndDepth(int arrayLength)
{
short[] array = getHeightAndDepth();
array = clearOrCreateArray(array, arrayLength);
return array;
}
public static short[] getHeightAndDepth()
{
if (!heightAndDepthMap.containsKey(Thread.currentThread().getName()) || (heightAndDepthMap.get(Thread.currentThread().getName()) == null))
{
heightAndDepthMap.put(Thread.currentThread().getName(), new short[0]);
}
return heightAndDepthMap.get(Thread.currentThread().getName());
}
/** returns the array filled with 0's */
public static byte[] getFreshSaveContainer(int arrayLength)
{
byte[] array = getSaveContainer();
array = clearOrCreateArray(array, arrayLength);
return array;
}
public static byte[] getSaveContainer()
{
if (!saveContainer.containsKey(Thread.currentThread().getName()) || (saveContainer.get(Thread.currentThread().getName()) == null))
{
saveContainer.put(Thread.currentThread().getName(), new byte[0]);
}
return saveContainer.get(Thread.currentThread().getName());
}
/** returns the array filled with 0's */
public static long[] getFreshVerticalUpdateArray(int arrayLength, int detailLevel)
{
long[] array = ThreadMapUtil.getVerticalUpdateArray(detailLevel);
array = clearOrCreateArray(array, arrayLength);
return array;
}
public static long[] getVerticalUpdateArray(int detailLevel)
{
if (!verticalUpdate.containsKey(Thread.currentThread().getName()) || (verticalUpdate.get(Thread.currentThread().getName()) == null))
{
long[][] array = new long[10][];
verticalUpdate.put(Thread.currentThread().getName(), array);
}
return verticalUpdate.get(Thread.currentThread().getName())[detailLevel];
}
/** returns the array filled with 0's */
public static long[] getFreshSingleAddDataToMerge(int arrayLength)
{
long[] array = getSingleAddDataToMerge();
array = clearOrCreateArray(array, arrayLength);
return array;
}
public static long[] getSingleAddDataToMerge()
{
if (!singleDataToMergeMap.containsKey(Thread.currentThread().getName()) || (singleDataToMergeMap.get(Thread.currentThread().getName()) == null))
{
singleDataToMergeMap.put(Thread.currentThread().getName(), new long[0]);
}
return singleDataToMergeMap.get(Thread.currentThread().getName());
}
/** clears all arrays so they will have to be rebuilt */
public static void clearMaps()
{
threadSingleUpdateMap.clear();
threadBuilderArrayMap.clear();
threadBuilderVerticalArrayMap.clear();
threadVerticalAddDataMap.clear();
saveContainer.clear();
projectionArrayMap.clear();
heightAndDepthMap.clear();
singleDataToMergeMap.clear();
verticalUpdate.clear();
}
/** returns an array filled with 0's */
private static long[] clearOrCreateArray(long[] array, int arrayLength)
{
if (array == null || array.length != arrayLength)
{
array = new long[arrayLength];
}
else
Arrays.fill(array, 0);
return array;
}
/** returns an array filled with 0's */
@SuppressWarnings("unused")
private static int[] clearOrCreateArray(int[] array, int arrayLength)
{
if (array == null || array.length != arrayLength)
array = new int[arrayLength];
else
Arrays.fill(array, 0);
return array;
}
/** returns an array filled with 0's */
private static short[] clearOrCreateArray(short[] array, int arrayLength)
{
if (array == null || array.length != arrayLength)
array = new short[arrayLength];
else
Arrays.fill(array, (short) 0);
return array;
}
/** returns an array filled with 0's */
private static byte[] clearOrCreateArray(byte[] array, int arrayLength)
{
if (array == null || array.length != arrayLength)
array = new byte[arrayLength];
else
Arrays.fill(array, (byte) 0);
return array;
}
}
@@ -0,0 +1,236 @@
package com.seibel.lod.wrappers;
import java.awt.Color;
import java.io.File;
import com.seibel.lod.util.LodUtil;
import net.minecraft.client.GameSettings;
import net.minecraft.client.MainWindow;
import net.minecraft.client.Minecraft;
import net.minecraft.client.entity.player.ClientPlayerEntity;
import net.minecraft.client.multiplayer.ServerData;
import net.minecraft.client.network.play.ClientPlayNetHandler;
import net.minecraft.client.renderer.GameRenderer;
import net.minecraft.client.renderer.LightTexture;
import net.minecraft.client.renderer.WorldRenderer;
import net.minecraft.client.renderer.model.ModelManager;
import net.minecraft.client.renderer.texture.NativeImage;
import net.minecraft.client.world.ClientWorld;
import net.minecraft.entity.Entity;
import net.minecraft.profiler.IProfiler;
import net.minecraft.server.integrated.IntegratedServer;
import net.minecraft.util.Direction;
import net.minecraft.world.DimensionType;
/**
* A singleton that wraps the Minecraft class
* to allow for easier movement between Minecraft versions.
*
* @author James Seibel
* @version 9-16-2021
*/
public class MinecraftWrapper
{
public static MinecraftWrapper INSTANCE = new MinecraftWrapper();
private Minecraft mc = Minecraft.getInstance();
/** The lightmap for the current:
* Time, dimension, brightness setting, etc. */
private NativeImage lightMap = null;
private MinecraftWrapper()
{
}
//================//
// helper methods //
//================//
/**
* This should be called at the beginning of every frame to
* clear any Minecraft data that becomes out of date after a frame. <br> <br>
*
* Lightmaps and other time sensitive objects fall in this category. <br> <br>
*
* This doesn't effect OpenGL objects in any way.
*/
public void clearFrameObjectCache()
{
lightMap = null;
}
//=================//
// method wrappers //
//=================//
public float getShading(Direction direction)
{
return mc.level.getShade(Direction.UP, true);
}
public boolean hasSingleplayerServer()
{
return mc.hasSingleplayerServer();
}
public DimensionType getCurrentDimension()
{
return mc.player.level.dimensionType();
}
public String getCurrentDimensionId()
{
return LodUtil.getDimensionIDFromWorld(mc.level);
}
/**
* This texture changes every frame
*/
public NativeImage getCurrentLightMap()
{
// get the current lightMap if the cache is empty
if (lightMap == null)
{
LightTexture tex = mc.gameRenderer.lightTexture();
NativeImage lightPixels = tex.lightPixels;
lightMap = lightPixels;
}
// // hotswap this to true, then back to false to write a file
// // (and not write a file every frame)
// if (false)
// {
// try
// {
// // obviously change the filepath to somewhere on your PC
// lightPixels.writeToFile(new File("C:\\Users\\James Seibel\\Desktop\\image.png"));
// }
// catch(Exception e)
// {
// e.printStackTrace();
// }
// }
return lightMap;
}
/**
* Returns the color int at the given pixel coordinates
* from the current lightmap.
*
* @param u x location in texture space
* @param v z location in texture space
*/
public int getColorIntFromLightMap(int u, int v)
{
if (lightMap == null)
{
// make sure the lightMap is up to date
getCurrentLightMap();
}
return lightMap.getPixelRGBA(u, v);
}
/**
* Returns the Color at the given pixel coordinates
* from the current lightmap.
*
* @param u x location in texture space
* @param v z location in texture space
*/
public Color getColorFromLightMap(int u, int v)
{
return LodUtil.intToColor(lightMap.getPixelRGBA(u, v));
}
//=============//
// Simple gets //
//=============//
public ClientPlayerEntity getPlayer()
{
return mc.player;
}
public GameSettings getOptions()
{
return mc.options;
}
public ModelManager getModelManager()
{
return mc.getModelManager();
}
public ClientWorld getClientWorld()
{
return mc.level;
}
/** Measured in chunks */
public int getRenderDistance()
{
return mc.options.renderDistance;
}
public File getGameDirectory()
{
return mc.gameDirectory;
}
public IProfiler getProfiler()
{
return mc.getProfiler();
}
public ClientPlayNetHandler getConnection()
{
return mc.getConnection();
}
public GameRenderer getGameRenderer()
{
return mc.gameRenderer;
}
public Entity getCameraEntity()
{
return mc.cameraEntity;
}
public MainWindow getWindow()
{
return mc.getWindow();
}
public float getSkyDarken(float partialTicks)
{
return mc.level.getSkyDarken(partialTicks);
}
public IntegratedServer getSingleplayerServer()
{
return mc.getSingleplayerServer();
}
public ServerData getCurrentServer()
{
return mc.getCurrentServer();
}
public WorldRenderer getLevelRenderer()
{
return mc.levelRenderer;
}
}
@@ -44,6 +44,13 @@ public net.minecraft.world.lighting.WorldLightManager field_215576_a # blockEngi
public net.minecraft.world.lighting.WorldLightManager field_215577_b # skyEngine
public net.minecraft.world.gen.feature.Feature field_236290_a_ # configuredCodec
# used for uploading vertex buffers off the render thread
public net.minecraft.client.renderer.vertex.VertexBuffer field_177365_a # id
public net.minecraft.client.renderer.vertex.VertexBuffer field_177363_b # format
public net.minecraft.client.renderer.vertex.VertexBuffer field_177364_c # vertexCount
# used for accessing the lightmap
public net.minecraft.client.renderer.LightTexture field_205111_b # lightPixels
#=====================#
+8 -10
View File
@@ -11,10 +11,10 @@ loaderVersion="[36,)" #mandatory This is typically bumped every Minecraft versio
#// The license for you mod. This is mandatory metadata and allows for easier comprehension of your redistributive properties.
#// Review your options at https://choosealicense.com/. All rights reserved is the default copyright stance, and is thus the default here.
license="All rights reserved"
license="GNU GPLv3"
#// A URL to refer people to when problems occur with this mod
#//issueTrackerURL="https://change.me.to.your.issue.tracker.example.invalid/" #optional
issueTrackerURL="https://gitlab.com/jeseibel/minecraft-lod-mod/-/issues" #optional
#// A list of mods - how many allowed here is determined by the individual mod loader
[[mods]] #mandatory
@@ -24,27 +24,25 @@ modId="lod" #mandatory
#// The version number of the mod - there's a few well known ${} variables useable here or just hardcode it
#//${file.jarVersion} will substitute the value of the Implementation-Version as read from the mod's JAR file metadata
#// see the associated build.gradle script for how to populate this completely automatically during a build
version="a1.3" #mandatory
version="a1.5.1-pre" #mandatory
#// A display name for the mod
displayName="Level of Detail" #mandatory
displayName="LOD" #mandatory
#// A URL to query for updates for this mod. See the JSON update specification https://mcforge.readthedocs.io/en/latest/gettingstarted/autoupdate/
#//updateJSONURL="https://change.me.example.invalid/updates.json" #optional
#// A URL for the "homepage" for this mod, displayed in the mod UI
#//displayURL="https://change.me.to.your.mods.homepage.example.invalid/" #optional
displayURL="https://www.curseforge.com/minecraft/mc-mods/lod-level-of-detail" #optional
#// A file name (in the root of the mod JAR) containing a logo for display
#//logoFile="examplemod.png" #optional
logoFile="logo.png" #optional
#// A text field displayed in the mod UI
credits="TechnoVision, Vike, and Darkhax for their modding tutorials." #optional
#// A text field displayed in the mod UI
authors="James Seibel" #optional
authors="James Seibel, Leonardo Amato, and Cola" #optional
#// The description text for the mod (multi line!) (#mandatory)
description='''
This mod generates and renders simplified chunks beyond the normal view distance, at a low performance cost.
'''
description='''This mod generates and renders simplified terrain beyond the normal view distance, at a low performance cost.'''
Binary file not shown.

After

Width:  |  Height:  |  Size: 172 KiB

-16
View File
@@ -1,16 +0,0 @@
[
{
"modid": "lod",
"name": "Level Of Detail",
"description": "Generates and renders simplified chunks beyond the normal view distance, at a low performance cost.",
"version": "a1.3",
"mcversion": "1.16.4",
"url": "",
"updateUrl": "",
"authorList": ["James Seibel"],
"credits": "TechnoVision, Vike, and Darkhax for their modding tutorials.",
"logoFile": "",
"screenshots": [],
"dependencies": []
}
]
+1 -1
View File
@@ -1,6 +1,6 @@
{
"pack": {
"description": "examplemod resources",
"description": "",
"pack_format": 6,
"_comment": "A pack_format of 6 requires json lang files and some texture changes from 1.16.2. Note: we require v6 pack meta for all mods."
}