Compare commits

...

220 Commits

Author SHA1 Message Date
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
51 changed files with 4815 additions and 3145 deletions
Binary file not shown.
+5 -13
View File
@@ -1,7 +1,6 @@
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
@@ -19,20 +18,13 @@ apply plugin: 'org.spongepowered.mixin'
// Only edit below this line, the above code adds and enables the necessary things for Forge to be setup.
apply plugin: 'eclipse'
apply plugin: 'maven-publish'
apply plugin: 'java' // needed for compileJava
version = 'a1.4'
version = 'a1.5.0-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.
compileJava {
// release 8 is needed because otherwise FloatBuffer.flip() will crash
// on some machines
// example thread: https://github.com/eclipse/jetty.project/issues/3244
options.compilerArgs.addAll(['--release', '8', '-Xlint:unchecked', '-Xlint:deprecation'])
}
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'))
minecraft {
@@ -200,7 +192,7 @@ jar {
"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",
])
@@ -228,4 +220,4 @@ publishing {
mixin {
add sourceSets.main, "lod.refmap.json"
}
}
+55
View File
@@ -0,0 +1,55 @@
package com.seibel.lod;
import java.util.HashMap;
import java.util.Map;
import com.seibel.lod.builders.lodTemplates.Box;
import com.seibel.lod.util.DataPointUtil;
import net.minecraft.util.Direction;
public class Main
{
public static void main(String[] args)
{
/*
try
{
@SuppressWarnings("serial")
Map<Direction, long[]> adjData = new HashMap<Direction, long[]>()
{{
put(Direction.EAST, new long[]{DataPointUtil.createDataPoint(60, 30, 0, 0, 0, 0), DataPointUtil.createDataPoint(25, 10, 0, 0, 0, 0)});
put(Direction.WEST, new long[]{DataPointUtil.createDataPoint(60, 10, 0, 0, 0, 0)});
put(Direction.NORTH, new long[]{DataPointUtil.createDataPoint(40, 20, 0, 0, 0, 0)});
put(Direction.SOUTH, new long[]{DataPointUtil.createDataPoint(40, 20, 0, 0, 0, 0)});
}};
Box box = new Box();
int height = 60;
int depth = 20;
box.set(10, height - depth, 10);
box.move(0, depth, 0);
box.setAdjData(adjData);
for(Direction direction : Box.DIRECTIONS)
{
int adjIndex = 0;
while (box.shouldContinue(direction, adjIndex))
{
System.out.println(direction.toString());
for (int i = 0; i < 4; i++)
{
System.out.println(box.getX(direction, i) + " " + box.getY(direction, i, adjIndex) + " " + box.getZ(direction, i));
}
adjIndex++;
}
}
} catch (Exception e)
{
e.printStackTrace();
}
*/
}
}
+1 -1
View File
@@ -28,5 +28,5 @@ public final class ModInfo
public static final String MODID = "lod";
public static final String MODNAME = "LOD";
public static final String MODAPI = "LodAPI";
public static final String VERSION = "a1.4";
public static final String VERSION = "a1.5.0-pre";
}
@@ -1,28 +0,0 @@
package com.seibel.lod.builders;
import com.seibel.lod.enums.DistanceGenerationMode;
import com.seibel.lod.objects.LevelPos.LevelPos;
import com.seibel.lod.util.LodUtil;
import net.minecraft.util.math.ChunkPos;
/**
* @author Leonardo Amato
* @version 22-08-2021
*/
public class GenerationRequest
{
public final LevelPos levelPos;
public final DistanceGenerationMode generationMode;
public GenerationRequest(LevelPos levelPos, DistanceGenerationMode generationMode)
{
this.levelPos = levelPos;
this.generationMode = generationMode;
}
public ChunkPos getChunkPos()
{
LevelPos chunkLevelPos = levelPos.getConvertedLevelPos(LodUtil.CHUNK_DETAIL_LEVEL);
return new ChunkPos(chunkLevelPos.posX, chunkLevelPos.posZ);
}
}
@@ -17,103 +17,123 @@
*/
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.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.locks.ReentrantLock;
import org.apache.commons.lang3.mutable.MutableBoolean;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL15;
import org.lwjgl.opengl.GL15C;
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.LodRegion;
import com.seibel.lod.objects.PosToRenderContainer;
import com.seibel.lod.objects.RegionPos;
import com.seibel.lod.objects.LevelPos.LevelPos;
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.DetailDistanceUtil;
import com.seibel.lod.util.LevelPosUtil;
import com.seibel.lod.util.LodThreadFactory;
import com.seibel.lod.util.LodUtil;
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;
/**
* This object is used to create NearFarBuffer objects.
*
* @author James Seibel
* @version 8-24-2021
* @version 9-22-2021
*/
public class LodBufferBuilder
{
/**
* This holds the thread used to generate new LODs off the main thread.
*/
private ExecutorService mainGenThread = Executors.newSingleThreadExecutor(new LodThreadFactory(this.getClass().getSimpleName() + " - main"));
public static ExecutorService mainGenThread = Executors.newSingleThreadExecutor(new LodThreadFactory(LodBufferBuilder.class.getSimpleName() + " - main"));
/**
* This holds the threads used to generate buffers.
*/
private ExecutorService bufferBuilderThreads = Executors.newFixedThreadPool(LodConfig.CLIENT.threading.numberOfBufferBuilderThreads.get(), new LodThreadFactory(this.getClass().getSimpleName() + " - builder"));
public static ExecutorService bufferBuilderThreads = Executors.newFixedThreadPool(LodConfig.CLIENT.threading.numberOfBufferBuilderThreads.get(), new LodThreadFactory(LodBufferBuilder.class.getSimpleName() + " - builder"));
/**
* The buffers that are used to create LODs using far fog
*/
public volatile BufferBuilder[][] buildableBuffers;
/**
* Used when building new VBOs
*/
public volatile VertexBuffer[][] buildableVbos;
/**
* VBOs that are sent over to the LodNodeRenderer
*/
public volatile VertexBuffer[][] drawableVbos;
/**
* 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 boolean switchVbos = false;
/**
* 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 Object[][] setsToRender;
// private static final int NUMBER_OF_DIRECTION = 4;
//in order -x, +x, -z, +z
// private static final int[][] ADJ_VECTOR = new int[][]{{1, 0}, {-1, 0}, {0, 1}, {0, -1}};
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);
private volatile boolean firstSetup = true;
public LodBufferBuilder()
{
}
/**
* Create a thread to asynchronously generate LOD buffers
* centered around the given camera X and Z.
@@ -124,193 +144,202 @@ public class LodBufferBuilder
* swapped with the drawable buffers in the LodRenderer to be drawn.
*/
public void generateLodBuffersAsync(LodRenderer renderer, LodDimension lodDim,
BlockPos playerBlockPos, boolean fullRegen)
BlockPos playerBlockPos, boolean fullRegen)
{
// only allow one generation process to happen at a time
if (generatingBuffers)
return;
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();
Thread thread = new Thread(() ->
{
bufferLock.lock();
try
{
long treeStart = System.currentTimeMillis();
long treeEnd = System.currentTimeMillis();
long startTime = System.currentTimeMillis();
ArrayList<Callable<Boolean>> nodeToRenderThreads = new ArrayList<>(lodDim.regions.length * lodDim.regions.length);
ArrayList<Callable<Boolean>> nodeToRenderThreads = new ArrayList<>(lodDim.getWidth() * lodDim.getWidth());
//setupBuffers(lodDim);
startBuffers(fullRegen, lodDim);
// =====================//
// RENDERING PART //
// =====================//
RegionPos playerRegionPos = new RegionPos(playerChunkPos);
if (center == null)
center = playerRegionPos;
if (setsToRender == null)
setsToRender = new Object[lodDim.regions.length][lodDim.regions.length];
if (setsToRender.length != lodDim.regions.length)
setsToRender = new Object[lodDim.regions.length][lodDim.regions.length];
RegionPos worldRegionOffset = new RegionPos(playerRegionPos.x - lodDim.getCenterX(), playerRegionPos.z - lodDim.getCenterZ());
if (worldRegionOffset.x != 0 || worldRegionOffset.z != 0)
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;
for (int xRegion = 0; xRegion < lodDim.getWidth(); xRegion++)
{
move(worldRegionOffset, Math.floorDiv(lodDim.getWidth(), 2));
}
for (int xRegion = 0; xRegion < lodDim.regions.length; xRegion++)
{
for (int zRegion = 0; zRegion < lodDim.regions.length; zRegion++)
for (int zRegion = 0; zRegion < lodDim.getWidth(); zRegion++)
{
if (lodDim.regen[xRegion][zRegion] || fullRegen)
if (lodDim.isRegionToRegen(xRegion, zRegion) || fullRegen)
{
RegionPos regionPos = new RegionPos(
xRegion + lodDim.getCenterX() - Math.floorDiv(lodDim.getWidth(), 2),
zRegion + lodDim.getCenterZ() - 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 != null && !currentBuffer.building()))
if (currentBuffer == null || !currentBuffer.building())
return;
//previous setToRender chache
if (setsToRender[xRegion][zRegion] == null)
{
setsToRender[xRegion][zRegion] = new ConcurrentHashMap<LevelPos, MutableBoolean>();
}
ConcurrentMap<LevelPos, MutableBoolean> nodeToRender = (ConcurrentMap<LevelPos, MutableBoolean>) setsToRender[xRegion][zRegion];
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.MULTI_LOD)
{
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(
nodeToRender,
posToRender,
regionPos,
playerBlockPosRounded.getX(),
playerBlockPosRounded.getZ());
byte detailLevel;
int posX;
int posZ;
byte detailLevel;
int xAdj;
int zAdj;
int chunkXdist;
int chunkZdist;
short gameChunkRenderDistance = (short) (renderer.vanillaRenderedChunks.length/2 - 1);
for (LevelPos posToRender : nodeToRender.keySet())
// 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++)
{
if (!nodeToRender.get(posToRender).booleanValue())
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])
{
nodeToRender.remove(posToRender);
continue;
}
nodeToRender.get(posToRender).setFalse();
// skip any chunks that Minecraft is going to render
chunkXdist = posToRender.getChunkPosX() - playerChunkPos.x;
chunkZdist = posToRender.getChunkPosZ() - playerChunkPos.z;
if(gameChunkRenderDistance >= Math.abs(chunkXdist) && gameChunkRenderDistance >= Math.abs(chunkZdist) && posToRender.detailLevel <= LodUtil.CHUNK_DETAIL_LEVEL)
for (Direction direction : Box.ADJ_DIRECTIONS)
{
if (renderer.vanillaRenderedChunks[chunkXdist + gameChunkRenderDistance + 1][chunkZdist + gameChunkRenderDistance + 1])
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]))
{
continue;
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);
}
}
posX = posToRender.posX;
posZ = posToRender.posZ;
detailLevel = posToRender.detailLevel;
// skip any chunks that Minecraft is going to render
try
long data;
for (int verticalIndex = 0; verticalIndex < lodDim.getMaxVerticalData(detailLevel, posX, posZ); verticalIndex++)
{
boolean disableFix = false;
if (lodDim.doesDataExist(posToRender.clone()))
{
short[] lodData = lodDim.getData(posToRender);
short[][][] adjData = new short[2][2][];
/**TODO The following two for are too complex*/
for (int x : new int[]{0, 1})
{
posToRender.changeParameters(detailLevel, posX + x * 2 - 1, posZ);
chunkXdist = posToRender.getChunkPosX() - playerChunkPos.x;
chunkZdist = posToRender.getChunkPosZ() - playerChunkPos.z;
if(gameChunkRenderDistance >= Math.abs(chunkXdist) && gameChunkRenderDistance >= Math.abs(chunkZdist))
{
if (!renderer.vanillaRenderedChunks[chunkXdist + gameChunkRenderDistance + 1][chunkZdist + gameChunkRenderDistance + 1]
&& (nodeToRender.containsKey(posToRender) || disableFix))
{
adjData[0][x] = lodDim.getData(posToRender);
}
}else{
if (nodeToRender.containsKey(posToRender) || disableFix)
{
adjData[0][x] = lodDim.getData(posToRender);
}
}
}
for (int z : new int[]{0, 1})
{
posToRender.changeParameters(detailLevel, posX, posZ + z * 2 - 1);
chunkXdist = posToRender.getChunkPosX() - playerChunkPos.x;
chunkZdist = posToRender.getChunkPosZ() - playerChunkPos.z;
if(gameChunkRenderDistance >= Math.abs(chunkXdist) && gameChunkRenderDistance >= Math.abs(chunkZdist))
{
if (!renderer.vanillaRenderedChunks[chunkXdist + gameChunkRenderDistance + 1][chunkZdist + gameChunkRenderDistance+ 1]
&& (nodeToRender.containsKey(posToRender) || disableFix))
{
adjData[1][z] = lodDim.getData(posToRender);
}
}else{
if (nodeToRender.containsKey(posToRender) || disableFix)
{
adjData[1][z] = lodDim.getData(posToRender);
}
}
}
posToRender.changeParameters(detailLevel, posX, posZ);
LodConfig.CLIENT.graphics.lodTemplate.get().template.addLodToBuffer(currentBuffer, playerBlockPos, lodData, adjData,
posToRender, renderer.previousDebugMode);
}
} catch (ArrayIndexOutOfBoundsException e)
{
return false;
data = lodDim.getData(detailLevel, posX, posZ, verticalIndex);
if (DataPointUtil.isItVoid(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
} // for pos to in list to render
// the thread executed successfully
return true;
};
nodeToRenderThreads.add(dataToRenderThread);
}
}// region z
}// region z
} // region z
} // region z
long renderStart = System.currentTimeMillis();
// wait for all threads to finish
List<Future<Boolean>> futuresBuffer = bufferBuilderThreads.invokeAll(nodeToRenderThreads);
@@ -325,8 +354,7 @@ public class LodBufferBuilder
}
}
long renderEnd = System.currentTimeMillis();
long endTime = System.currentTimeMillis();
@SuppressWarnings("unused")
long buildTime = endTime - startTime;
@@ -334,11 +362,11 @@ public class LodBufferBuilder
long treeTime = treeEnd - treeStart;
@SuppressWarnings("unused")
long renderingTime = renderEnd - renderStart;
// ClientProxy.LOGGER.info("Buffer Build time: " + buildTime + " ms" + '\n' +
// "Tree cutting time: " + treeTime + " ms" + '\n' +
// "Rendering time: " + renderingTime + " ms");
// ClientProxy.LOGGER.info("Buffer Build time: " + buildTime + " ms" + '\n' +
// "Tree cutting time: " + treeTime + " ms" + '\n' +
// "Rendering time: " + renderingTime + " ms");
// mark that the buildable buffers as ready to swap
switchVbos = true;
} catch (Exception e)
@@ -350,158 +378,64 @@ public class LodBufferBuilder
// 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();
}
});
mainGenThread.execute(thread);
return;
}
/**
* Move the center of this LodDimension and move all owned
* regions over by the given x and z offset. <br><br>
* <p>
* Synchronized to prevent multiple moves happening on top of each other.
*/
public void move(RegionPos regionOffset, int width)
{
int xOffset = regionOffset.x;
int zOffset = regionOffset.z;
// if the x or z offset is equal to or greater than
// the total size, just delete the current data
// and update the centerX and/or centerZ
if (Math.abs(xOffset) >= width || Math.abs(zOffset) >= width)
{
for (int x = 0; x < width; x++)
{
for (int z = 0; z < width; z++)
{
setsToRender[x][z] = null;
}
}
// update the new center
center.x += xOffset;
center.z += zOffset;
return;
}
// X
if (xOffset > 0)
{
// move everything over to the left (as the center moves to the right)
for (int x = 0; x < width; x++)
{
for (int z = 0; z < width; z++)
{
if (x + xOffset < width)
setsToRender[x][z] = setsToRender[x + xOffset][z];
else
setsToRender[x][z] = null;
}
}
} else
{
// move everything over to the right (as the center moves to the left)
for (int x = width - 1; x >= 0; x--)
{
for (int z = 0; z < width; z++)
{
if (x + xOffset >= 0)
setsToRender[x][z] = setsToRender[x + xOffset][z];
else
setsToRender[x][z] = null;
}
}
}
// Z
if (zOffset > 0)
{
// move everything up (as the center moves down)
for (int x = 0; x < width; x++)
{
for (int z = 0; z < width; z++)
{
if (z + zOffset < width)
setsToRender[x][z] = setsToRender[x][z + zOffset];
else
setsToRender[x][z] = null;
}
}
} else
{
// move everything down (as the center moves up)
for (int x = 0; x < width; x++)
{
for (int z = width - 1; z >= 0; z--)
{
if (z + zOffset >= 0)
setsToRender[x][z] = setsToRender[x][z + zOffset];
else
setsToRender[x][z] = null;
}
}
}
// update the new center
center.x += xOffset;
center.z += zOffset;
}
//===============================//
// BufferBuilder related methods //
//===============================//
/**
* Called from the LodRenderer to create the
* BufferBuilders. <br><br>
* <p>
* May have to wait for the bufferLock to open.
*/
public void setupBuffers(int numbRegionsWide, int bufferMaxCapacity)
public void setupBuffers(LodDimension lodDimension)
{
bufferLock.lock();
int numbRegionsWide = lodDimension.getWidth();
int bufferMaxCapacity;
//if(previousRegionWidth != numbRegionsWide || firstSetup)
//{
firstSetup = false;
previousRegionWidth = numbRegionsWide;
previousBufferSize = bufferMaxCapacity;
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++)
{
//if(lodDimension.isBufferToSetup(x,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();
}
/**
* sets the buffers and Vbos to null, forcing them to be recreated. <br><br>
* <p>
@@ -510,30 +444,31 @@ public class LodBufferBuilder
public void destroyBuffers()
{
bufferLock.lock();
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.regen[x][z])
if (fullRegen || lodDim.isRegionToRegen(x, z))
{
buildableBuffers[x][z].begin(GL11.GL_QUADS, LodRenderer.LOD_VERTEX_FORMAT);
}
}
}
}
/**
* Calls end on each of the buildable BufferBuilders.
*/
@@ -541,54 +476,118 @@ public class LodBufferBuilder
{
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.regen[x][z]))
{
if (buildableBuffers[x][z] != null && buildableBuffers[x][z].building() && (fullRegen || lodDim.isRegionToRegen(x, z)))
buildableBuffers[x][z].end();
}
}
/**
* Called from the LodRenderer to create the
* BufferBuilders at the right size.
* Upload all buildableBuffers to the GPU.
*/
private void uploadBuffers(boolean fullRegen, LodDimension lodDim)
{
for (int x = 0; x < buildableVbos.length; x++)
GlProxy glProxy = GlProxy.getInstance();
try
{
for (int z = 0; z < buildableVbos.length; z++)
// 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++)
{
if (fullRegen || lodDim.regen[x][z])
for (int z = 0; z < buildableVbos.length; z++)
{
buildableVbos[x][z].upload(buildableBuffers[x][z]);
lodDim.regen[x][z] = false;
if (fullRegen || lodDim.isRegionToRegen(x, z))
{
ByteBuffer builderBuffer = buildableBuffers[x][z].popNextBuffer().getSecond();
vboUpload(buildableVbos[x][z], builderBuffer);
lodDim.setRegenByArrayIndex(x, z, false);
}
}
}
}
catch (IllegalStateException e)
{
ClientProxy.LOGGER.error(LodBufferBuilder.class.getSimpleName() + " - UploadBuffers failed: " + e.getMessage());
e.printStackTrace();
}
finally
{
// make sure no buffer is bound
if (glProxy.getGlContext() == GlProxyContext.LOD_BUILDER)
{
GL15C.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
}
// 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 VertexBuffer[][] getVertexBuffers()
public VertexBuffersAndOffset getVertexBuffers()
{
// don't wait for the lock to open
// since this is called on the main render thread
if (bufferLock.tryLock())
{
VertexBuffer[][] tmp = drawableVbos;
VertexBuffer[][] tmpVbo = drawableVbos;
drawableVbos = buildableVbos;
buildableVbos = tmp;
buildableVbos = tmpVbo;
drawableCenterChunkPos = buildableCenterChunkPos;
// the vbos have been swapped
switchVbos = false;
bufferLock.unlock();
}
return drawableVbos;
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
@@ -598,6 +597,5 @@ public class LodBufferBuilder
{
return switchVbos;
}
}
File diff suppressed because it is too large Load Diff
@@ -17,10 +17,14 @@
*/
package com.seibel.lod.builders.lodTemplates;
import java.util.Map;
import com.seibel.lod.enums.DebugMode;
import com.seibel.lod.objects.LevelPos.LevelPos;
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;
/**
@@ -32,22 +36,25 @@ import net.minecraft.util.math.BlockPos;
*/
public abstract class AbstractLodTemplate
{
public abstract void addLodToBuffer(BufferBuilder buffer, BlockPos playerBlockPos, short[] data, short[][][] adjData,
LevelPos levelPos, DebugMode debugging);
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 red, int green, int blue, int alpha)
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 getBufferMemoryForSingleNode();
public abstract int getBufferMemoryForSingleNode(int maxVerticalData);
}
@@ -0,0 +1,448 @@
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;
public class Box
{
public static final int OFFSET = 0;
public static final int WIDTH = 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;
public static final Direction[] DIRECTIONS = new Direction[]{
Direction.UP,
Direction.DOWN,
Direction.WEST,
Direction.EAST,
Direction.NORTH,
Direction.SOUTH};
public static final Direction[] ADJ_DIRECTIONS = new Direction[]{
Direction.EAST,
Direction.WEST,
Direction.SOUTH,
Direction.NORTH};
@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});
}};
public int[][] box;
public long[] order;
public Map<Direction, int[]> colorMap;
public int debugColor;
public Map<Direction, int[][]> adjHeightAndDepth;
public Map<Direction, boolean[]> culling;
@SuppressWarnings("serial")
public Box()
{
box = new int[2][3];
//order = new long[DetailDistanceUtil.getMaxVerticalData(0)];
colorMap = new HashMap<Direction, int[]>()
{{
put(Direction.UP, new int[1]);
put(Direction.DOWN, new int[1]);
put(Direction.EAST, new int[1]);
put(Direction.WEST, new int[1]);
put(Direction.SOUTH, new int[1]);
put(Direction.NORTH, new int[1]);
}};
adjHeightAndDepth = new HashMap<Direction, int[][]>()
{{
put(Direction.EAST, new int[32][2]);
put(Direction.WEST, new int[32][2]);
put(Direction.SOUTH, new int[32][2]);
put(Direction.NORTH, new int[32][2]);
}};
culling = new HashMap<Direction, boolean[]>()
{{
put(Direction.UP, new boolean[1]);
put(Direction.DOWN, new boolean[1]);
put(Direction.EAST, new boolean[1]);
put(Direction.WEST, new boolean[1]);
put(Direction.SOUTH, new boolean[1]);
put(Direction.NORTH, new boolean[1]);
}};
}
public void setColor(int color)
{
this.debugColor = color;
for (Direction direction : DIRECTIONS)
{
colorMap.get(direction)[0] = 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)[0];
}
else
{
return ColorUtil.applyShade(debugColor, MinecraftWrapper.INSTANCE.getClientWorld().getShade(direction, true));
}
}
public void reset()
{
for (int i = 0; i < box.length; i++)
{
Arrays.fill(box[i], 0);
}
for (Direction direction : DIRECTIONS)
{
colorMap.get(direction)[0] = 0;
}
//Arrays.fill(order, DataPointUtil.EMPTY_DATA);
for (Direction direction : ADJ_DIRECTIONS)
{
if(isCulled(direction)){
continue;
}
for (int i = 0; i < adjHeightAndDepth.get(direction).length; i++)
{
adjHeightAndDepth.get(direction)[i][0] = VOID_FACE;
adjHeightAndDepth.get(direction)[i][1] = VOID_FACE;
}
}
}
public void setUpCulling(int cullingDistance, BlockPos playerPos)
{
for (Direction direction : DIRECTIONS)
{
if(direction == Direction.DOWN)
culling.get(direction)[0] = playerPos.get(direction.getAxis()) > getFacePos(direction) + cullingDistance;
else if(direction == Direction.UP)
culling.get(direction)[0] = playerPos.get(direction.getAxis()) < getFacePos(direction) - cullingDistance;
else if(direction == Direction.WEST)
culling.get(direction)[0] = -playerPos.get(direction.getAxis()) > getFacePos(direction) + cullingDistance;
else if(direction == Direction.NORTH)
culling.get(direction)[0] = -playerPos.get(direction.getAxis()) > getFacePos(direction) + cullingDistance;
else if(direction == Direction.EAST)
culling.get(direction)[0] = -playerPos.get(direction.getAxis()) < getFacePos(direction) - cullingDistance;
else if(direction == Direction.SOUTH)
culling.get(direction)[0] = -playerPos.get(direction.getAxis()) < getFacePos(direction) - cullingDistance;
}
}
public boolean isCulled(Direction direction)
{
return culling.get(direction)[0];
}
public void setAdjData(Map<Direction, long[]> adjData)
{
int height;
int depth;
int minY = getMinY();
int maxY = getMaxY();
for (Direction direction : ADJ_DIRECTIONS)
{
/*if(isCulled(direction)){
continue;
}*/
long[] dataPoint = adjData.get(direction);
if (dataPoint == null || DataPointUtil.isItVoid(dataPoint[0]))
{
adjHeightAndDepth.get(direction)[0][0] = maxY;
adjHeightAndDepth.get(direction)[0][1] = minY;
adjHeightAndDepth.get(direction)[1][0] = VOID_FACE;
adjHeightAndDepth.get(direction)[1][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];
/*for (i = 0; i < count; i++)
{
singleAdjDataPoint = order[i];*/
if(DataPointUtil.isItVoid(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
//we break since all the other data will be lower
if (firstFace)
{
adjHeightAndDepth.get(direction)[0][0] = getMaxY();
adjHeightAndDepth.get(direction)[0][1] = getMinY();
} else
{
adjHeightAndDepth.get(direction)[faceToDraw][1] = getMinY();
}
faceToDraw++;
toFinish = false;
break;
} else if (depth <= minY && height >= maxY)
{//the adj data contains the current
//we do not draw the face
adjHeightAndDepth.get(direction)[0][0] = VOID_FACE;
adjHeightAndDepth.get(direction)[0][1] = VOID_FACE;
break;
} else if (depth <= minY)//&& height < maxY
{//the adj data intersect the lower part of the current data
//if this is the only face we use the maxY and break
//if there was other face we finish the last one and break
if (firstFace)
{
adjHeightAndDepth.get(direction)[0][0] = getMaxY();
adjHeightAndDepth.get(direction)[0][1] = height;
} else
{
adjHeightAndDepth.get(direction)[faceToDraw][1] = height;
}
toFinish = false;
faceToDraw++;
break;
} else if (height >= maxY)//depth > minY &&
{//the adj data intersect the higher part of the current data
//we start the creation of a new face
adjHeightAndDepth.get(direction)[faceToDraw][0] = depth;
firstFace = false;
toFinish = true;
} else {//if (depth > minY && height < maxY)
//the adj data is contained in the current data
if (firstFace)
{
adjHeightAndDepth.get(direction)[0][0] = getMaxY();
}
adjHeightAndDepth.get(direction)[faceToDraw][1] = height;
faceToDraw++;
adjHeightAndDepth.get(direction)[faceToDraw][0] = 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){
adjHeightAndDepth.get(direction)[0][0] = getMaxY();
adjHeightAndDepth.get(direction)[0][1] = getMinY();
faceToDraw++;
}
else if (toFinish)
{
adjHeightAndDepth.get(direction)[faceToDraw][1] = minY;
faceToDraw++;
}
adjHeightAndDepth.get(direction)[faceToDraw][0] = VOID_FACE;
adjHeightAndDepth.get(direction)[faceToDraw][1] = VOID_FACE;
}
}
public void set(int xWidth, int yWidth, int zWidth)
{
box[WIDTH][X] = xWidth;
box[WIDTH][Y] = yWidth;
box[WIDTH][Z] = zWidth;
}
public void move(int xOffset, int yOffset, int zOffset)
{
box[OFFSET][X] = xOffset;
box[OFFSET][Y] = yOffset;
box[OFFSET][Z] = zOffset;
}
public int getFacePos(Direction direction)
{
return box[OFFSET][FACE_DIRECTION.get(direction)[0]] + box[WIDTH][FACE_DIRECTION.get(direction)[0]] * FACE_DIRECTION.get(direction)[1];
}
public int getCoord(Direction direction, int axis, int vertexIndex)
{
return box[OFFSET][axis] + box[WIDTH][axis] * DIRECTION_VERTEX_MAP.get(direction)[vertexIndex][axis];
}
public int getX(Direction direction, int vertexIndex)
{
return box[OFFSET][X] + box[WIDTH][X] * DIRECTION_VERTEX_MAP.get(direction)[vertexIndex][X];
}
public int getY(Direction direction, int vertexIndex)
{
return box[OFFSET][Y] + box[WIDTH][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 box[OFFSET][Y] + box[WIDTH][Y] * DIRECTION_VERTEX_MAP.get(direction)[vertexIndex][Y];
} else
{
return adjHeightAndDepth.get(direction)[adjIndex][1 - DIRECTION_VERTEX_MAP.get(direction)[vertexIndex][Y]];
}
}
public int getZ(Direction direction, int vertexIndex)
{
return box[OFFSET][Z] + box[WIDTH][Z] * DIRECTION_VERTEX_MAP.get(direction)[vertexIndex][Z];
}
/**
* returns true if the given direction should be rendered.
* TODO what does adjIndex represent?
*/
public boolean shouldRenderFace(Direction direction, int adjIndex)
{
if (direction == Direction.UP || direction == Direction.DOWN)
{
return adjIndex == 0;
}
return !(adjHeightAndDepth.get(direction)[adjIndex][0] == VOID_FACE && adjHeightAndDepth.get(direction)[adjIndex][1] == VOID_FACE);
}
public int getMinX()
{
return box[OFFSET][X];
}
public int getMaxX()
{
return box[OFFSET][X] + box[WIDTH][X];
}
public int getMinY()
{
return box[OFFSET][Y];
}
public int getMaxY()
{
return box[OFFSET][Y] + box[WIDTH][Y];
}
public int getMinZ()
{
return box[OFFSET][Z];
}
public int getMaxZ()
{
return box[OFFSET][Z] + box[WIDTH][Z];
}
}
@@ -17,18 +17,15 @@
*/
package com.seibel.lod.builders.lodTemplates;
import com.seibel.lod.config.LodConfig;
import java.util.Map;
import com.seibel.lod.enums.DebugMode;
import com.seibel.lod.enums.ShadingMode;
import com.seibel.lod.objects.DataPoint;
import com.seibel.lod.objects.LevelPos.LevelPos;
import com.seibel.lod.util.ColorUtil;
import com.seibel.lod.util.DataPointUtil;
import com.seibel.lod.util.LodUtil;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.BufferBuilder;
import net.minecraft.client.renderer.texture.NativeImage;
import net.minecraft.util.Direction;
import net.minecraft.util.math.AxisAlignedBB;
import net.minecraft.util.math.BlockPos;
/**
@@ -39,7 +36,6 @@ import net.minecraft.util.math.BlockPos;
*/
public class CubicLodTemplate extends AbstractLodTemplate
{
private final int CULL_OFFSET = 16;
public CubicLodTemplate()
{
@@ -47,40 +43,41 @@ public class CubicLodTemplate extends AbstractLodTemplate
}
@Override
public void addLodToBuffer(BufferBuilder buffer, BlockPos playerBlockPos, short[] data, short[][][] adjData,
LevelPos levelPos, DebugMode 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;
int width = 1 << detailLevel;
int width = 1 << levelPos.detailLevel;
// add each LOD for the detail level
bbox = generateBoundingBox(
DataPoint.getHeight(data),
DataPoint.getDepth(data),
width,
levelPos.posX * width,
0,
levelPos.posZ * width);
int color = DataPoint.getColor(data);
int color = DataPointUtil.getLightColor(data, lightMap);
if (debugging != DebugMode.OFF)
{
color = LodUtil.DEBUG_DETAIL_LEVEL_COLORS[levelPos.detailLevel].getRGB();
color = LodUtil.DEBUG_DETAIL_LEVEL_COLORS[detailLevel].getRGB();
}
if (bbox != null)
generateBoundingBox(
box,
DataPointUtil.getHeight(data),
DataPointUtil.getDepth(data),
width,
posX * width,
0,
posZ * width,
bufferCenterBlockPos,
adjData,
color);
if (box != null)
{
addBoundingBoxToBuffer(buffer, bbox, color, playerBlockPos, adjData);
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)
{
@@ -89,240 +86,44 @@ 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.set(width, height - depth, width);
box.move((int) (xOffset + x), (int) (depth + yOffset), (int) (zOffset + z));
box.setUpCulling(32, bufferCenterBlockPos);
box.setAdjData(adjData);
}
private void addBoundingBoxToBuffer(BufferBuilder buffer, AxisAlignedBB bb, int c, BlockPos playerBlockPos, short[][][] adjData)
private void addBoundingBoxToBuffer(BufferBuilder buffer, Box box)
{
int topColor = c;
int bottomColor = c;
int northColor = c;
int southColor = c;
int westColor = c;
int eastColor = c;
// darken the bottom and side colors if requested
if (LodConfig.CLIENT.graphics.shadingMode.get() == ShadingMode.DARKEN_SIDES)
for (Direction direction : Box.DIRECTIONS)
{
// the side colors are different because
// when using fast lighting in Minecraft the north/south
// and east/west sides are different in a similar way
/**TODO OPTIMIZE THIS STEP*/
Minecraft mc = Minecraft.getInstance();
topColor = ColorUtil.applyShade(c, mc.level.getShade(Direction.UP, true));
bottomColor = ColorUtil.applyShade(c, mc.level.getShade(Direction.DOWN, true));
northColor = ColorUtil.applyShade(c, mc.level.getShade(Direction.NORTH, true));
southColor = ColorUtil.applyShade(c, mc.level.getShade(Direction.SOUTH, true));
westColor = ColorUtil.applyShade(c, mc.level.getShade(Direction.WEST, true));
eastColor = ColorUtil.applyShade(c, mc.level.getShade(Direction.EAST, true));
}
// apply the user specified saturation and brightness
float saturationMultiplier = LodConfig.CLIENT.graphics.saturationMultiplier.get().floatValue();
float brightnessMultiplier = LodConfig.CLIENT.graphics.brightnessMultiplier.get().floatValue();
if (saturationMultiplier != 1 || brightnessMultiplier != 1)
{
topColor = ColorUtil.applySaturationAndBrightnessMultipliers(topColor, saturationMultiplier, brightnessMultiplier);
bottomColor = ColorUtil.applySaturationAndBrightnessMultipliers(bottomColor, saturationMultiplier, brightnessMultiplier);
northColor = ColorUtil.applySaturationAndBrightnessMultipliers(northColor, saturationMultiplier, brightnessMultiplier);
southColor = ColorUtil.applySaturationAndBrightnessMultipliers(southColor, saturationMultiplier, brightnessMultiplier);
westColor = ColorUtil.applySaturationAndBrightnessMultipliers(westColor, saturationMultiplier, brightnessMultiplier);
eastColor = ColorUtil.applySaturationAndBrightnessMultipliers(eastColor, saturationMultiplier, brightnessMultiplier);
}
int minY;
int maxY;
short[] data;
int red;
int green;
int blue;
int alpha;
boolean disableCulling = true;
/**TODO make all of this more automatic if possible*/
if (playerBlockPos.getY() > bb.maxY - CULL_OFFSET || disableCulling)
{
red = ColorUtil.getRed(topColor);
green = ColorUtil.getGreen(topColor);
blue = ColorUtil.getBlue(topColor);
alpha = ColorUtil.getAlpha(topColor);
// top (facing up)
addPosAndColor(buffer, bb.minX, bb.maxY, bb.minZ, red, green, blue, alpha);
addPosAndColor(buffer, bb.minX, bb.maxY, bb.maxZ, red, green, blue, alpha);
addPosAndColor(buffer, bb.maxX, bb.maxY, bb.maxZ, red, green, blue, alpha);
addPosAndColor(buffer, bb.maxX, bb.maxY, bb.minZ, red, green, blue, alpha);
}
if (playerBlockPos.getY() < bb.minY + CULL_OFFSET || disableCulling)
{
red = ColorUtil.getRed(bottomColor);
green = ColorUtil.getGreen(bottomColor);
blue = ColorUtil.getBlue(bottomColor);
alpha = ColorUtil.getAlpha(bottomColor);
// bottom (facing down)
addPosAndColor(buffer, bb.maxX, bb.minY, bb.minZ, red, green, blue, alpha);
addPosAndColor(buffer, bb.maxX, bb.minY, bb.maxZ, red, green, blue, alpha);
addPosAndColor(buffer, bb.minX, bb.minY, bb.maxZ, red, green, blue, alpha);
addPosAndColor(buffer, bb.minX, bb.minY, bb.minZ, red, green, blue, alpha);
}
if (playerBlockPos.getZ() > bb.minZ - CULL_OFFSET || disableCulling)
{
red = ColorUtil.getRed(northColor);
green = ColorUtil.getGreen(northColor);
blue = ColorUtil.getBlue(northColor);
alpha = ColorUtil.getAlpha(northColor);
// south (facing -Z)
data = adjData[1][1];
if (data == null)
int adjIndex = 0;
while (box.shouldRenderFace(direction, adjIndex))
{
addPosAndColor(buffer, bb.maxX, bb.minY, bb.maxZ, red, green, blue, alpha);
addPosAndColor(buffer, bb.maxX, bb.maxY, bb.maxZ, red, green, blue, alpha);
addPosAndColor(buffer, bb.minX, bb.maxY, bb.maxZ, red, green, blue, alpha);
addPosAndColor(buffer, bb.minX, bb.minY, bb.maxZ, red, green, blue, alpha);
}
else
{
maxY = DataPoint.getHeight(data);
if (maxY < bb.maxY)
for (int vertexIndex = 0; vertexIndex < 4; vertexIndex++)
{
minY = (int) Math.max(maxY, bb.minY);
addPosAndColor(buffer, bb.maxX, minY, bb.maxZ, red, green, blue, alpha);
addPosAndColor(buffer, bb.maxX, bb.maxY, bb.maxZ, red, green, blue, alpha);
addPosAndColor(buffer, bb.minX, bb.maxY, bb.maxZ, red, green, blue, alpha);
addPosAndColor(buffer, bb.minX, minY, bb.maxZ, red, green, blue, alpha);
}
minY = DataPoint.getDepth(data);
if (minY > bb.minY)
{
maxY = (int) Math.min(minY, bb.maxY);
addPosAndColor(buffer, bb.maxX, bb.minY, bb.maxZ, red, green, blue, alpha);
addPosAndColor(buffer, bb.maxX, maxY, bb.maxZ, red, green, blue, alpha);
addPosAndColor(buffer, bb.minX, maxY, bb.maxZ, red, green, blue, alpha);
addPosAndColor(buffer, bb.minX, bb.minY, bb.maxZ, red, green, blue, alpha);
}
}
}
if (playerBlockPos.getZ() < bb.maxZ + CULL_OFFSET || disableCulling)
{
red = ColorUtil.getRed(southColor);
green = ColorUtil.getGreen(southColor);
blue = ColorUtil.getBlue(southColor);
alpha = ColorUtil.getAlpha(southColor);
data = adjData[1][0];
// north (facing +Z)
if (data == null)
{
addPosAndColor(buffer, bb.minX, bb.minY, bb.minZ, red, green, blue, alpha);
addPosAndColor(buffer, bb.minX, bb.maxY, bb.minZ, red, green, blue, alpha);
addPosAndColor(buffer, bb.maxX, bb.maxY, bb.minZ, red, green, blue, alpha);
addPosAndColor(buffer, bb.maxX, bb.minY, bb.minZ, red, green, blue, alpha);
}
else
{
maxY = DataPoint.getHeight(data);
if (maxY < bb.maxY)
{
minY = (int) Math.max(maxY, bb.minY);
addPosAndColor(buffer, bb.minX, minY, bb.minZ, red, green, blue, alpha);
addPosAndColor(buffer, bb.minX, bb.maxY, bb.minZ, red, green, blue, alpha);
addPosAndColor(buffer, bb.maxX, bb.maxY, bb.minZ, red, green, blue, alpha);
addPosAndColor(buffer, bb.maxX, minY, bb.minZ, red, green, blue, alpha);
}
minY = DataPoint.getDepth(data);
if (minY > bb.minY)
{
maxY = (int) Math.min(minY, bb.maxY);
addPosAndColor(buffer, bb.minX, bb.minY, bb.minZ, red, green, blue, alpha);
addPosAndColor(buffer, bb.minX, maxY, bb.minZ, red, green, blue, alpha);
addPosAndColor(buffer, bb.maxX, maxY, bb.minZ, red, green, blue, alpha);
addPosAndColor(buffer, bb.maxX, bb.minY, bb.minZ, red, green, blue, alpha);
}
}
}
if (playerBlockPos.getX() < bb.maxX + CULL_OFFSET || disableCulling)
{
red = ColorUtil.getRed(westColor);
green = ColorUtil.getGreen(westColor);
blue = ColorUtil.getBlue(westColor);
alpha = ColorUtil.getAlpha(westColor);
// west (facing -X)
data = adjData[0][0];
if (data == null)
{
addPosAndColor(buffer, bb.minX, bb.minY, bb.minZ, red, green, blue, alpha);
addPosAndColor(buffer, bb.minX, bb.minY, bb.maxZ, red, green, blue, alpha);
addPosAndColor(buffer, bb.minX, bb.maxY, bb.maxZ, red, green, blue, alpha);
addPosAndColor(buffer, bb.minX, bb.maxY, bb.minZ, red, green, blue, alpha);
}
else
{
maxY = DataPoint.getHeight(data);
if (maxY < bb.maxY)
{
minY = (int) Math.max(maxY, bb.minY);
addPosAndColor(buffer, bb.minX, minY, bb.minZ, red, green, blue, alpha);
addPosAndColor(buffer, bb.minX, minY, bb.maxZ, red, green, blue, alpha);
addPosAndColor(buffer, bb.minX, bb.maxY, bb.maxZ, red, green, blue, alpha);
addPosAndColor(buffer, bb.minX, bb.maxY, bb.minZ, red, green, blue, alpha);
}
minY = DataPoint.getDepth(data);
if (minY > bb.minY)
{
maxY = (int) Math.min(minY, bb.maxY);
addPosAndColor(buffer, bb.minX, bb.minY, bb.minZ, red, green, blue, alpha);
addPosAndColor(buffer, bb.minX, bb.minY, bb.maxZ, red, green, blue, alpha);
addPosAndColor(buffer, bb.minX, maxY, bb.maxZ, red, green, blue, alpha);
addPosAndColor(buffer, bb.minX, maxY, bb.minZ, red, green, blue, alpha);
}
}
}
if (playerBlockPos.getX() > bb.minX - CULL_OFFSET || disableCulling)
{
red = ColorUtil.getRed(eastColor);
green = ColorUtil.getGreen(eastColor);
blue = ColorUtil.getBlue(eastColor);
alpha = ColorUtil.getAlpha(eastColor);
// east (facing +X)
data = adjData[0][1];
if (data == null)
{
addPosAndColor(buffer, bb.maxX, bb.maxY, bb.minZ, red, green, blue, alpha);
addPosAndColor(buffer, bb.maxX, bb.maxY, bb.maxZ, red, green, blue, alpha);
addPosAndColor(buffer, bb.maxX, bb.minY, bb.maxZ, red, green, blue, alpha);
addPosAndColor(buffer, bb.maxX, bb.minY, bb.minZ, red, green, blue, alpha);
}
else
{
maxY = DataPoint.getHeight(data);
if (maxY < bb.maxY)
{
minY = (int) Math.max(maxY, bb.minY);
addPosAndColor(buffer, bb.maxX, bb.maxY, bb.minZ, red, green, blue, alpha);
addPosAndColor(buffer, bb.maxX, bb.maxY, bb.maxZ, red, green, blue, alpha);
addPosAndColor(buffer, bb.maxX, minY, bb.maxZ, red, green, blue, alpha);
addPosAndColor(buffer, bb.maxX, minY, bb.minZ, red, green, blue, alpha);
}
minY = DataPoint.getDepth(data);
if (minY > bb.minY)
{
maxY = (int) Math.min(minY, bb.maxY);
addPosAndColor(buffer, bb.maxX, maxY, bb.minZ, red, green, blue, alpha);
addPosAndColor(buffer, bb.maxX, maxY, bb.maxZ, red, green, blue, alpha);
addPosAndColor(buffer, bb.maxX, bb.minY, bb.maxZ, red, green, blue, alpha);
addPosAndColor(buffer, bb.maxX, bb.minY, bb.minZ, red, green, blue, alpha);
addPosAndColor(buffer,
box.getX(direction, vertexIndex),
box.getY(direction, vertexIndex, adjIndex),
box.getZ(direction, vertexIndex),
box.getColor(direction));
}
adjIndex++;
}
}
}
@Override
public int getBufferMemoryForSingleNode()
public int getBufferMemoryForSingleNode(int maxVerticalData)
{
// (sidesOnACube * pointsInASquare * (positionPoints + colorPoints)))
return (6 * 4 * (3 + 4));
// TODO, someone please comment what these magic numbers mean
return 2 * 4 * (3 + 4) + 4 * 4 * Math.max((maxVerticalData+1)/2,1) * (3 + 4);
}
}
@@ -17,10 +17,14 @@
*/
package com.seibel.lod.builders.lodTemplates;
import java.util.Map;
import com.seibel.lod.enums.DebugMode;
import com.seibel.lod.objects.LevelPos.LevelPos;
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;
/**
@@ -35,14 +39,14 @@ import net.minecraft.util.math.BlockPos;
public class DynamicLodTemplate extends AbstractLodTemplate
{
@Override
public void addLodToBuffer(BufferBuilder buffer, BlockPos playerBlockPos, short[] data, short[][][] adjData,
LevelPos levelPos, DebugMode 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 getBufferMemoryForSingleNode()
public int getBufferMemoryForSingleNode(int maxVerticalData)
{
return 0;
}
@@ -17,10 +17,14 @@
*/
package com.seibel.lod.builders.lodTemplates;
import java.util.Map;
import com.seibel.lod.enums.DebugMode;
import com.seibel.lod.objects.LevelPos.LevelPos;
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;
/**
@@ -33,14 +37,14 @@ import net.minecraft.util.math.BlockPos;
public class TriangularLodTemplate extends AbstractLodTemplate
{
@Override
public void addLodToBuffer(BufferBuilder buffer, BlockPos playerBlockPos, short[] data, short[][][] adjData,
LevelPos levelPos, DebugMode 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 getBufferMemoryForSingleNode()
public int getBufferMemoryForSingleNode(int maxVerticalData)
{
return 0;
}
@@ -26,13 +26,13 @@ import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.function.Supplier;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import com.seibel.lod.builders.LodBuilder;
import com.seibel.lod.builders.LodBuilderConfig;
import com.seibel.lod.config.LodConfig;
import com.seibel.lod.enums.DistanceGenerationMode;
import com.seibel.lod.objects.LodDimension;
import com.seibel.lod.proxy.ClientProxy;
import com.seibel.lod.render.LodRenderer;
import com.seibel.lod.util.LodThreadFactory;
import com.seibel.lod.util.LodUtil;
@@ -69,11 +69,12 @@ import net.minecraftforge.common.WorldWorkerManager.IWorker;
* This is used to generate a LodChunk at a given ChunkPos.
*
* @author James Seibel
* @version 8-26-2021
* @version 9-7-2021
*/
public class LodNodeGenWorker implements IWorker
{
public static ExecutorService genThreads = Executors.newFixedThreadPool(LodConfig.CLIENT.threading.numberOfWorldGenerationThreads.get(), new LodThreadFactory(LodNodeGenWorker.class.getSimpleName()));
public static ExecutorService genThreads = Executors.newFixedThreadPool(LodConfig.CLIENT.threading.numberOfWorldGenerationThreads.get(), new ThreadFactoryBuilder().setNameFormat("Gen-Worker-Thread-%d").build());
//public static ExecutorService genThreads = Executors.newFixedThreadPool(LodConfig.CLIENT.threading.numberOfWorldGenerationThreads.get(), new LodThreadFactory(LodNodeGenWorker.class.getSimpleName()));
private boolean threadStarted = false;
private LodChunkGenThread thread;
@@ -86,7 +87,7 @@ public class LodNodeGenWorker implements IWorker
public LodNodeGenWorker(ChunkPos newPos, DistanceGenerationMode newGenerationMode, LodRenderer newLodRenderer,
public LodNodeGenWorker(ChunkPos newPos, DistanceGenerationMode newGenerationMode,
LodBuilder newLodBuilder,
LodDimension newLodDimension, ServerWorld newServerWorld)
{
@@ -94,9 +95,6 @@ public class LodNodeGenWorker implements IWorker
if (newPos == null)
throw new IllegalArgumentException("LodChunkGenWorker must have a non-null ChunkPos");
if (newLodRenderer == null)
throw new IllegalArgumentException("LodChunkGenWorker must have a non-null LodRenderer");
if (newLodBuilder == null)
throw new IllegalArgumentException("LodChunkGenThread requires a non-null LodChunkBuilder");
@@ -108,11 +106,15 @@ public class LodNodeGenWorker implements IWorker
thread = new LodChunkGenThread(newPos, newGenerationMode, newLodRenderer,
thread = new LodChunkGenThread(newPos, newGenerationMode,
newLodBuilder,
newLodDimension, newServerWorld);
}
public static void resetGenerator(){
ExecutorService genThreads = Executors.newFixedThreadPool(LodConfig.CLIENT.threading.numberOfWorldGenerationThreads.get(), new ThreadFactoryBuilder().setNameFormat("Gen-Worker-Thread-%d").build());
}
@Override
public boolean doWork()
{
@@ -130,7 +132,7 @@ public class LodNodeGenWorker implements IWorker
// Every other method can
// be done asynchronously
Thread newThread = new Thread(thread);
newThread.setPriority(3);
newThread.setPriority(5);
genThreads.execute(newThread);
}
@@ -159,17 +161,15 @@ public class LodNodeGenWorker implements IWorker
public final LodDimension lodDim;
public final DistanceGenerationMode generationMode;
public final LodBuilder lodBuilder;
public final LodRenderer lodRenderer;
private ChunkPos pos;
public LodChunkGenThread(ChunkPos newPos, DistanceGenerationMode newGenerationMode, LodRenderer newLodRenderer,
public LodChunkGenThread(ChunkPos newPos, DistanceGenerationMode newGenerationMode,
LodBuilder newLodBuilder,
LodDimension newLodDimension, ServerWorld newServerWorld)
{
pos = newPos;
generationMode = newGenerationMode;
lodRenderer = newLodRenderer;
lodBuilder = newLodBuilder;
lodDim = newLodDimension;
serverWorld = newServerWorld;
@@ -228,9 +228,9 @@ public class LodNodeGenWorker implements IWorker
// System.out.println(endTime - startTime);
}// if in range
else{
//else{
}
//}
}
catch (Exception e)
{
@@ -588,16 +588,11 @@ public class LodNodeGenWorker 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);
@@ -1,33 +1,23 @@
package com.seibel.lod.builders.worldGeneration;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicInteger;
import org.apache.commons.lang3.mutable.MutableBoolean;
import com.seibel.lod.builders.GenerationRequest;
import com.seibel.lod.builders.LodBuilder;
import com.seibel.lod.config.LodConfig;
import com.seibel.lod.enums.DistanceGenerationMode;
import com.seibel.lod.util.LevelPosUtil;
import com.seibel.lod.objects.LodDimension;
import com.seibel.lod.objects.LevelPos.LevelPos;
import com.seibel.lod.objects.PosToGenerateContainer;
import com.seibel.lod.render.LodRenderer;
import com.seibel.lod.util.DetailDistanceUtil;
import com.seibel.lod.util.LodThreadFactory;
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.world.server.ServerWorld;
import net.minecraftforge.common.WorldWorkerManager;
@@ -40,7 +30,7 @@ import net.minecraftforge.common.WorldWorkerManager;
*/
public class LodWorldGenerator
{
public Minecraft mc = Minecraft.getInstance();
public MinecraftWrapper mc = MinecraftWrapper.INSTANCE;
/**
* This holds the thread used to generate new LODs off the main thread.
@@ -75,10 +65,6 @@ public class LodWorldGenerator
*/
public static final LodWorldGenerator INSTANCE = new LodWorldGenerator();
public volatile ConcurrentMap<LevelPos, MutableBoolean> nodeToGenerate;
SortedSet<LevelPos> nodeToGenerateListNear;
SortedSet<LevelPos> nodeToGenerateListFar;
private LodWorldGenerator()
{
@@ -108,14 +94,9 @@ public class LodWorldGenerator
try
{
// round the player's block position down to the nearest chunk BlockPos
ChunkPos playerChunkPos = new ChunkPos(mc.player.blockPosition());
BlockPos playerBlockPosRounded = playerChunkPos.getWorldPosition();
int playerPosX = mc.getPlayer().blockPosition().getX();
int playerPosZ = mc.getPlayer().blockPosition().getZ();
// used when determining which chunks are closer when queuing distance
// generation
int minChunkDist = Integer.MAX_VALUE;
ArrayList<GenerationRequest> chunksToGen = new ArrayList<>(maxChunkGenRequests);
// if we don't have a full number of chunks to generate in chunksToGen
// we can top it off from this reserve
@@ -123,126 +104,81 @@ public class LodWorldGenerator
//=======================================//
// create the generation Request objects //
//=======================================//
List<GenerationRequest> generationRequestList = new ArrayList<>(maxChunkGenRequests);
if (nodeToGenerate == null)
nodeToGenerate = new ConcurrentHashMap<>();
Comparator<LevelPos> posNearComparator = LevelPos.getPosComparator(
playerBlockPosRounded.getX(),
playerBlockPosRounded.getZ());
Comparator<LevelPos> posFarComparator = LevelPos.getPosAndDetailComparator(
playerBlockPosRounded.getX(),
playerBlockPosRounded.getZ());
nodeToGenerateListNear = new TreeSet(posNearComparator);
nodeToGenerateListFar = new TreeSet(posFarComparator);
lodDim.getDataToGenerate(
nodeToGenerate,
playerBlockPosRounded.getX(),
playerBlockPosRounded.getZ());
ServerWorld serverWorld = LodUtil.getServerWorldFromDimension(lodDim.dimension);
PosToGenerateContainer posToGenerate = lodDim.getDataToGenerate(
maxChunkGenRequests,
playerPosX,
playerPosZ);
//System.out.println(posToGenerate);
//here we prepare two sorted set
//the first contains the near pos to render
//the second contain the far pos to render
byte farDetail = (byte) 7;
for (LevelPos pos : nodeToGenerate.keySet())
{
if (!nodeToGenerate.get(pos).booleanValue())
{
nodeToGenerate.remove(pos);
} else
{
if (pos.detailLevel > farDetail){
nodeToGenerateListFar.add(pos);
}
nodeToGenerateListNear.add(pos);
nodeToGenerate.get(pos).setFalse();
}
}
byte detailLevel;
int posX;
int posZ;
boolean nearOrFar = true;
boolean stopSwitch = false;
int near = 0;
int far = 0;
int maxDistance;
byte circle;
LevelPos levelPos;
int requesting = maxChunkGenRequests;
int requestingFar = maxChunkGenRequests / 4;
while (requesting > 0 && !nodeToGenerateListNear.isEmpty())
for (int index = 0; index < posToGenerate.getNumberOfPos(); index++)
{
levelPos = nodeToGenerateListNear.first();
//.out.println(levelPos);
nodeToGenerate.remove(levelPos);
nodeToGenerateListNear.remove(levelPos);
nodeToGenerateListFar.remove(levelPos);
//maxDistance = levelPos.maxDistance(
// playerBlockPosRounded.getX(),
// playerBlockPosRounded.getZ());
//circle = DetailDistanceUtil.getDistanceGenerationInverse(maxDistance);
generationRequestList.add(new GenerationRequest(levelPos, DetailDistanceUtil.getDistanceGenerationMode(levelPos.detailLevel)));
requesting--;
if (requestingFar > 0 && !nodeToGenerateListFar.isEmpty())
if (posToGenerate.getNthDetail(near, true) != 0 && near < posToGenerate.getNumberOfNearPos())
{
levelPos = nodeToGenerateListFar.first();
nodeToGenerate.remove(levelPos);
nodeToGenerateListNear.remove(levelPos);
nodeToGenerateListFar.remove(levelPos);
if (levelPos.detailLevel >= farDetail)
detailLevel = (byte) (posToGenerate.getNthDetail(near, true) - 1);
posX = posToGenerate.getNthPosX(near, true);
posZ = posToGenerate.getNthPosZ(near, true);
near++;
ChunkPos chunkPos = new ChunkPos(LevelPosUtil.getChunkPos(detailLevel, posX), LevelPosUtil.getChunkPos(detailLevel, posZ));
if (numberOfChunksWaitingToGenerate.get() < maxChunkGenRequests)
{
//maxDistance = levelPos.maxDistance( playerBlockPosRounded.getX(), playerBlockPosRounded.getZ());
//circle = DetailDistanceUtil.getDistanceGenerationInverse(maxDistance);
generationRequestList.add(new GenerationRequest(levelPos, DetailDistanceUtil.getDistanceGenerationMode(levelPos.detailLevel)));
requestingFar--;
requesting--;
// prevent generating the same chunk multiple times
if (positionWaitingToBeGenerated.contains(chunkPos))
{
continue;
}
}
}
}
//====================================//
// get the closet generation requests //
//====================================//
// determine which points in the posListToGenerate
// should actually be queued to generate
for (GenerationRequest generationRequest : generationRequestList)
{
ChunkPos chunkPos = generationRequest.getChunkPos();
if (numberOfChunksWaitingToGenerate.get() < maxChunkGenRequests)
{
// prevent generating the same chunk multiple times
if (positionWaitingToBeGenerated.contains(chunkPos))
{
// don't add null chunkPos (which shouldn't happen anyway)
// or add more to the generation queue
if (chunkPos == null || numberOfChunksWaitingToGenerate.get() >= maxChunkGenRequests)
continue;
positionWaitingToBeGenerated.add(chunkPos);
numberOfChunksWaitingToGenerate.addAndGet(1);
LodNodeGenWorker genWorker = new LodNodeGenWorker(chunkPos, DetailDistanceUtil.getDistanceGenerationMode(detailLevel), lodBuilder, lodDim, serverWorld);
WorldWorkerManager.addWorker(genWorker);
}
if (posToGenerate.getNthDetail(far, false) != 0 && far < posToGenerate.getNumberOfFarPos())
{
detailLevel = (byte) (posToGenerate.getNthDetail(far, false) - 1);
posX = posToGenerate.getNthPosX(far, false);
posZ = posToGenerate.getNthPosZ(far, false);
far++;
ChunkPos chunkPos = new ChunkPos(LevelPosUtil.getChunkPos(detailLevel, posX), LevelPosUtil.getChunkPos(detailLevel, posZ));
if (numberOfChunksWaitingToGenerate.get() < maxChunkGenRequests)
{
// prevent generating the same chunk multiple times
if (positionWaitingToBeGenerated.contains(chunkPos))
{
continue;
}
}
chunksToGen.add(generationRequest);
} // lod null and can generate more chunks
} // positions to generate
// don't add null chunkPos (which shouldn't happen anyway)
// or add more to the generation queue
if (chunkPos == null || numberOfChunksWaitingToGenerate.get() >= maxChunkGenRequests)
continue;
//=============================//
// start the LodNodeGenWorkers //
//=============================//
// issue #19
// TODO add a way for a server side mod to generate chunks requested here
ServerWorld serverWorld = LodUtil.getServerWorldFromDimension(lodDim.dimension);
// start chunk generation
for (GenerationRequest generationRequest : generationRequestList)
{
// don't add null chunkPos (which shouldn't happen anyway)
// or add more to the generation queue
ChunkPos chunkPos = generationRequest.getChunkPos();
if (chunkPos == null || numberOfChunksWaitingToGenerate.get() >= maxChunkGenRequests)
continue;
positionWaitingToBeGenerated.add(chunkPos);
numberOfChunksWaitingToGenerate.addAndGet(1);
LodNodeGenWorker genWorker = new LodNodeGenWorker(chunkPos, generationRequest.generationMode, renderer, lodBuilder, lodDim, serverWorld);
WorldWorkerManager.addWorker(genWorker);
positionWaitingToBeGenerated.add(chunkPos);
numberOfChunksWaitingToGenerate.addAndGet(1);
LodNodeGenWorker genWorker = new LodNodeGenWorker(chunkPos, DetailDistanceUtil.getDistanceGenerationMode(detailLevel), lodBuilder, lodDim, serverWorld);
WorldWorkerManager.addWorker(genWorker);
}
}
} catch (Exception e)
+158 -140
View File
@@ -27,13 +27,16 @@ 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.DistanceCalculatorType;
import com.seibel.lod.enums.DetailDropOff;
import com.seibel.lod.enums.DistanceGenerationMode;
import com.seibel.lod.enums.DistanceQualityDropOff;
import com.seibel.lod.enums.FogDistance;
import com.seibel.lod.enums.FogDrawOverride;
import com.seibel.lod.enums.LodDetail;
import com.seibel.lod.enums.GenerationPriority;
import com.seibel.lod.enums.HorizontalQuality;
import com.seibel.lod.enums.HorizontalResolution;
import com.seibel.lod.enums.LodTemplate;
import com.seibel.lod.enums.ShadingMode;
import com.seibel.lod.enums.VerticalQuality;
import net.minecraftforge.common.ForgeConfigSpec;
import net.minecraftforge.eventbus.api.SubscribeEvent;
@@ -44,34 +47,32 @@ import net.minecraftforge.fml.config.ModConfig;
* This handles any configuration the user has access to.
*
* @author James Seibel
* @version 9-1-2021
* @version 9-24-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();
}
}
{
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 //
@@ -79,112 +80,122 @@ public class LodConfig
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<LodDetail> maxDrawDetail;
public ForgeConfigSpec.EnumValue<HorizontalResolution> drawResolution;
public ForgeConfigSpec.EnumValue<ShadingMode> shadingMode;
// public ForgeConfigSpec.EnumValue<ShadingMode> shadingMode;
public ForgeConfigSpec.IntValue lodQuality;
public ForgeConfigSpec.EnumValue<HorizontalQuality> horizontalQuality;
public ForgeConfigSpec.EnumValue<DetailDropOff> detailDropOff;
public ForgeConfigSpec.IntValue lodChunkRenderDistance;
public ForgeConfigSpec.DoubleValue brightnessMultiplier;
public ForgeConfigSpec.DoubleValue saturationMultiplier;
public ForgeConfigSpec.BooleanValue disableDirectionalCulling;
public ForgeConfigSpec.BooleanValue alwaysDrawAtMaxQuality;
Graphics(ForgeConfigSpec.Builder builder)
{
builder.comment("These settings control how the LODs look.").push(this.getClass().getSimpleName());
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\" fog option \n"
+ " set this to " + FogDistance.NEAR.toString() + " or " + FogDistance.FAR.toString() + ". \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.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);
+ " " + 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.toString() + " is implemented! \n"
+ " NOTE: Currently only " + LodTemplate.CUBIC + " 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"
+ " " + 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);
maxDrawDetail = builder
detailDropOff = builder
.comment("\n\n"
+ " How smooth should the detail transition for LODs be? \n"
+ DetailDropOff.BY_CHUNK + ": quality is determined per-chunk (best quality option, may cause stuttering when moving)\n"
+ DetailDropOff.BY_REGION_FANCY + ": quality is determiend per-region (quality option)\n"
+ DetailDropOff.BY_REGION_FAST + ": quality is determiend per-region (performance option)\n")
.defineEnum("detailDropOff", DetailDropOff.BY_CHUNK);
drawResolution = builder
.comment("\n\n"
+ " What is the maximum detail level that LODs should be drawn at? \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"
+ " " + LodDetail.FULL.toString() + ": render 256 LODs for each Chunk. \n")
.defineEnum("lodDrawQuality", LodDetail.FULL);
+ " " + 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);
lodQuality = builder
horizontalQuality = builder
.comment("\n\n"
+ " this value is multiplied by 128 and determine \n"
+ " how much the quality decrease over distance \n")
.defineInRange("lodQuality", 1, 1, 4);
+ " This indicates how quickly LODs drop off in quality. \n"
+ " " + HorizontalQuality.LOW + ": quality drops every 4 chunks. \n"
+ " " + HorizontalQuality.MEDIUM + ": quality drops every 8 chunks. \n"
+ " " + HorizontalQuality.HIGH + ": quality drops every 16 chunks. \n")
.defineEnum("lodDrawQuality", HorizontalQuality.MEDIUM);
lodChunkRenderDistance = builder
.comment("\n\n"
+ " This is the render distance of the mod \n")
.defineInRange("lodChunkRenderDistane", 64, 32, 512);
+ " The mod's render distance, measured in chunks. \n")
.defineInRange("lodChunkRenderDistance", 64, 32, 1024);
shadingMode = builder
disableDirectionalCulling = builder
.comment("\n\n"
+ " What kind of shading should the LODs have? \n"
+ " \n"
+ " " + ShadingMode.NONE.toString() + " \n"
+ " " + "LODs will have the same lighting on every side. \n"
+ " " + "Can make large similarly colored areas hard to differentiate. \n"
+ "\n"
+ " " + ShadingMode.DARKEN_SIDES.toString() + " \n"
+ " " + "LODs will have darker sides and bottoms to simulate Minecraft's flat lighting.")
.defineEnum("lightingMode", ShadingMode.DARKEN_SIDES);
+ " 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);
brightnessMultiplier = builder
// 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"
+ " Change how bright LOD colors are. \n"
+ " 0 = black \n"
+ " 1 = normal color value \n"
+ " 2 = washed out colors \n")
.defineInRange("brightnessMultiplier", 1.0, 0, 2);
saturationMultiplier = builder
.comment("\n\n"
+ " Change how saturated LOD colors are. \n"
+ " 0 = black and white \n"
+ " 1 = normal saturation \n"
+ " 2 = very saturated \n")
.defineInRange("saturationMultiplier", 1.0, 0, 2);
+ " 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();
}
@@ -192,63 +203,78 @@ public class LodConfig
public static class WorldGenerator
{
public ForgeConfigSpec.EnumValue<LodDetail> maxGenerationDetail;
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<DistanceCalculatorType> lodDistanceCalculatorType;
public ForgeConfigSpec.EnumValue<DistanceQualityDropOff> lodDistanceCalculatorType;
WorldGenerator(ForgeConfigSpec.Builder builder)
{
builder.comment("These settings control how LODs outside your normal view range are generated.").push(this.getClass().getSimpleName());
maxGenerationDetail = builder
lodQualityMode = builder
.comment("\n\n"
+ " Use 3d lods or 2d lods? \n"
+ " " + VerticalQuality.HEIGHTMAP + ": LODs are solid from the lowest world point to the highest. Not good for floating islands or caves. Faster \n"
+ " " + VerticalQuality.MULTI_LOD + ": LODs have gaps between vertical blocks. Good for floating islands and caves. Slower \n")
.defineEnum("lodQualityMode", VerticalQuality.HEIGHTMAP);
generationResolution = builder
.comment("\n\n"
+ " What is the maximum detail level that LODs should be generated at? \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"
+ " " + LodDetail.FULL.toString() + ": render 256 LODs for each Chunk. \n")
.defineEnum("lodGenerationQuality", LodDetail.HALF);
+ " " + 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);
lodDistanceCalculatorType = builder
.comment("\n\n"
+ " " + DistanceCalculatorType.LINEAR + " \n"
+ " " + DistanceQualityDropOff.LINEAR + " \n"
+ " with LINEAR calculator the quality of block decrease \n"
+ " linearly to the distance of the player \n"
+ "\n"
+ " " + DistanceCalculatorType.QUADRATIC + " \n"
+ " with LINEAR calculator the quality of block decrease \n"
+ " quadratically to the distance of the player \n"
+ "\n"
+ " " + DistanceQualityDropOff.QUADRATIC + " \n"
+ " with QUADRATIC calculator the quality of block decrease \n"
+ " quadratically to the distance of the player \n")
.defineEnum("lodDistanceComputation", DistanceQualityDropOff.LINEAR);
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"
+ " " + DistanceCalculatorType.RENDER_DEPENDANT + " \n"
+ " with LINEAR calculator the quality of block decrease \n"
+ " quadratically to the distance of the player \n")
.defineEnum("lodDistanceComputation", DistanceCalculatorType.LINEAR);
+ "\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"
+ " the developer's PC to generate 1 chunk, \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.toString() + " \n"
+ " " + DistanceGenerationMode.NONE + " \n"
+ " Don't run the distance generator. \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"
+ " " + 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.toString() + " \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"
@@ -256,30 +282,30 @@ public class LodConfig
+ " Multithreaded - Fastest (2-5 ms) \n"
+ "\n"
+ " " + DistanceGenerationMode.SURFACE.toString() + " \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.toString() + " \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.toString() + " \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 also show player made structures if you \n"
+ " are adding the mod to a pre-existing world. \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.toString() + " generation mode \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"
@@ -296,7 +322,6 @@ public class LodConfig
+ " https://gitlab.com/jeseibel/minecraft-lod-mod/-/issues/35 \n")
.define("allowUnstableFeatureGeneration", false);
builder.pop();
}
}
@@ -318,7 +343,7 @@ public class LodConfig
+ " this number. If you want to increase LOD generation speed, \n"
+ " increase this number. \n"
+ " \n"
+ " The maximum value is the number of processors on your CPU. \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());
@@ -329,7 +354,7 @@ public class LodConfig
+ " If you experience high CPU useage when NOT generating distant \n"
+ " LODs, lower this number. \n"
+ " \n"
+ " The maximum value is the number of processors on your CPU. \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());
@@ -340,22 +365,23 @@ public class LodConfig
public static class Debugging
{
public ForgeConfigSpec.EnumValue<DebugMode> debugMode;
public ForgeConfigSpec.BooleanValue enableDebugKeybinding;
public ForgeConfigSpec.BooleanValue enableDebugKeybindings;
Debugging(ForgeConfigSpec.Builder builder)
{
builder.comment("These settings can be used by to look for bugs, or see how certain parts of the mod are working.").push(this.getClass().getSimpleName());
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.toString() + ": LODs will draw with their normal colors. \n"
+ " " + DebugMode.SHOW_DETAIL.toString() + ": LOD colors will be based on their detail. \n"
+ " " + DebugMode.SHOW_DETAIL_WIREFRAME.toString() + ": LOD colors will be based on their detail, drawn with wireframe. \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);
enableDebugKeybinding = builder
enableDebugKeybindings = builder
.comment("\n\n"
+ " If true the F4 key can be used to cycle through the different debug modes. \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();
@@ -392,18 +418,10 @@ public class LodConfig
+ " 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
*/
@@ -0,0 +1,28 @@
/*
* 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;
/**
*
*/
public enum DetailDropOff
{
BY_REGION_FAST,
BY_REGION_FANCY,
BY_CHUNK,
}
@@ -4,7 +4,7 @@ package com.seibel.lod.enums;
* @author Leonardo Amato
* @version 22-08-2021
*/
public enum DistanceCalculatorType
public enum DistanceQualityDropOff
{
/**
* different Lod detail render and generate linearly to the distance
@@ -15,9 +15,4 @@ public enum DistanceCalculatorType
* different Lod detail render and generate quadratically to the distance
*/
QUADRATIC,
/**
* we calculate the distance based on game render distance and mod render distance
*/
RENDER_DEPENDANT;
}
@@ -18,26 +18,12 @@
package com.seibel.lod.enums;
/**
* NE, SE, SW, NW
*
* @author James Seibel
* @version 1-20-2020
* @author Leonardo Amato
*/
public enum LodCorner
public enum GenerationPriority
{
/** -Z, +X */
NE(0),
/** +Z, +X */
SE(1),
/** +Z, -X */
SW(2),
/** -Z, -X */
NW(3);
public final int value;
private LodCorner(int newValue)
{
value = newValue;
}
NEAR_FIRST,
FAR_FIRST;
}
@@ -18,33 +18,29 @@
package com.seibel.lod.enums;
/**
* NONE <br>
* BIOME_ONLY <br>
* BIOME_ONLY_SIMULATE_HEIGHT <br>
* SURFACE <br>
* FEATURES <br>
* SERVER <br><br>
*
* In order of fastest to slowest.
*
* USE_OPTIFINE_FOG_SETTING, <br>
* NEVER_DRAW_FOG, <br>
* ALWAYS_DRAW_FOG_FAST, <br>
* ALWAYS_DRAW_FOG_FANCY <br>
*
* @author James Seibel
* @author Leonardo Amato
* @version 8-7-2021
* @version 7-03-2021
*/
public enum LodRenderDistance
public enum HorizontalQuality
{
SHORT(32),
/** Lods are 2D with heightMap */
LOW(64),
MEDIUM(64),
/** Lods expand in three dimension */
MEDIUM(128),
FAR(128);
/** Lods expand in three dimension */
HIGH(256);
public int distanceUnit;
/** The higher the number the more complete the generation is. */
public final int renderDistance;
LodRenderDistance(int complexity)
HorizontalQuality(int distanceUnit)
{
this.renderDistance = complexity;
this.distanceUnit = distanceUnit;
}
}
}
@@ -28,24 +28,23 @@ import com.seibel.lod.util.LodUtil;
* @author James Seibel
* @version 8-11-2021
*/
public enum LodDetail
public enum HorizontalResolution
{
/** render 1 LOD for each chunk */
SINGLE(1, 4),
CHUNK(1, 4),
/** render 4 LODs for each chunk */
DOUBLE(2, 3),
HALF_CHUNK(2, 3),
/** render 16 LODs for each chunk */
QUAD(4, 2),
FOUR_BLOCKS(4, 2),
/** render 64 LODs for each chunk */
HALF(8, 1),
TWO_BLOCKS(8, 1),
/** render 256 LODs for each chunk */
FULL(16, 0);
BLOCK(16, 0);
/** How many DataPoints should
* be drawn per side per LodChunk */
public final int dataPointLengthCount;
@@ -69,12 +68,12 @@ public enum LodDetail
* 2nd dimension: An array of all LodDetails that are less than or <br>
* equal to that detailLevel
*/
private static LodDetail[][] lowerDetailArrays;
private static HorizontalResolution[][] lowerDetailArrays;
private LodDetail(int newLengthCount, int newDetailLevel)
private HorizontalResolution(int newLengthCount, int newDetailLevel)
{
detailLevel = (byte) newDetailLevel;
dataPointLengthCount = newLengthCount;
@@ -113,20 +112,20 @@ public enum LodDetail
* Returns an array of all LodDetails that have a detail level
* that is less than or equal to the given LodDetail
*/
public static LodDetail[] getSelfAndLowerDetails(LodDetail detail)
public static HorizontalResolution[] getSelfAndLowerDetails(HorizontalResolution detail)
{
if (lowerDetailArrays == null)
{
// run first time setup
lowerDetailArrays = new LodDetail[LodDetail.values().length][];
lowerDetailArrays = new HorizontalResolution[HorizontalResolution.values().length][];
// go through each LodDetail
for(LodDetail currentDetail : LodDetail.values())
for(HorizontalResolution currentDetail : HorizontalResolution.values())
{
ArrayList<LodDetail> lowerDetails = new ArrayList<>();
ArrayList<HorizontalResolution> lowerDetails = new ArrayList<>();
// find the details lower than currentDetail
for(LodDetail compareDetail : LodDetail.values())
for(HorizontalResolution compareDetail : HorizontalResolution.values())
{
if (currentDetail.detailLevel <= compareDetail.detailLevel)
{
@@ -138,7 +137,7 @@ public enum LodDetail
Collections.sort(lowerDetails);
Collections.reverse(lowerDetails);
lowerDetailArrays[currentDetail.detailLevel] = lowerDetails.toArray(new LodDetail[lowerDetails.size()]);
lowerDetailArrays[currentDetail.detailLevel] = lowerDetails.toArray(new HorizontalResolution[lowerDetails.size()]);
}
}
@@ -146,9 +145,9 @@ public enum LodDetail
}
/** Returns what detail level should be used at a given distance and maxDistance. */
public static LodDetail getDetailForDistance(LodDetail maxDetailLevel, int distance, int maxDistance)
public static HorizontalResolution getDetailForDistance(HorizontalResolution maxDetailLevel, int distance, int maxDistance)
{
LodDetail[] lowerDetails = getSelfAndLowerDetails(maxDetailLevel);
HorizontalResolution[] lowerDetails = getSelfAndLowerDetails(maxDetailLevel);
int distaneBetweenDetails = maxDistance / lowerDetails.length;
int index = LodUtil.clamp(0, distance / distaneBetweenDetails, lowerDetails.length - 1);
@@ -54,8 +54,8 @@ public enum LodTemplate
}
public int getBufferMemoryForSingleLod()
public int getBufferMemoryForSingleLod(int maxVerticalData)
{
return template.getBufferMemoryForSingleNode();
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;
}
}
@@ -14,5 +14,5 @@ public enum ShadingMode
/** LODs will have darker sides and bottoms to simulate top down lighting.
Fastest */
DARKEN_SIDES;
GAME_SHADING;
}
@@ -18,34 +18,19 @@
package com.seibel.lod.enums;
/**
* TOP, NORTH, SOUTH, EAST, WEST, BOTTOM
*
* USE_OPTIFINE_FOG_SETTING, <br>
* NEVER_DRAW_FOG, <br>
* ALWAYS_DRAW_FOG_FAST, <br>
* ALWAYS_DRAW_FOG_FANCY <br>
*
* @author James Seibel
* @version 10-17-2020
* @version 7-03-2021
*/
public enum ColorDirection
public enum VerticalQuality
{
// 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;
}
}
/** Lods are 2D with heightMap */
HEIGHTMAP,
/** Lods expand in three dimension */
MULTI_LOD;
}
@@ -17,21 +17,22 @@
*/
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.DistanceGenerationMode;
import com.seibel.lod.objects.LevelContainer;
import com.seibel.lod.objects.LodDimension;
import com.seibel.lod.objects.LodRegion;
import com.seibel.lod.objects.RegionPos;
import com.seibel.lod.enums.VerticalQuality;
import com.seibel.lod.objects.*;
import com.seibel.lod.proxy.ClientProxy;
import com.seibel.lod.util.LodThreadFactory;
import com.seibel.lod.util.LodUtil;
@@ -42,15 +43,10 @@ import com.seibel.lod.util.LodUtil;
* to file.
*
* @author James Seibel
* @version 8-24-2021
* @version 9-7-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[][];
@@ -64,11 +60,7 @@ public class LodDimensionFileHandler
/**
* .txt
*/
private static final String FILE_EXTENSION = ".txt";
/**
* lod/
*/
private static final String LOD_FOLDER_NAME = "lod";
private static final String FILE_EXTENSION = ".dat";
/**
* detail-#
*/
@@ -88,12 +80,7 @@ public class LodDimensionFileHandler
* 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 = 4;
/**
* This is the string written before the file version
*/
private static final String LOD_FILE_VERSION_PREFIX = "lod_save_file_version";
public static final int LOD_SAVE_FILE_VERSION = 6;
/**
* Allow saving asynchronously, but never try to save multiple regions
@@ -126,21 +113,21 @@ public class LodDimensionFileHandler
* Return the LodRegion region at the given coordinates.
* (null if the file doesn't exist)
*/
public LodRegion loadRegionFromFile(byte detailLevel, RegionPos regionPos, DistanceGenerationMode generationMode)
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);
LodRegion region = new LodRegion(LodUtil.REGION_DETAIL_LEVEL,regionPos, generationMode, verticalQuality);
for (byte tempDetailLevel = LodUtil.REGION_DETAIL_LEVEL; tempDetailLevel >= detailLevel; tempDetailLevel--)
{
String fileName = getFileNameAndPathForRegion(regionX, regionZ, generationMode, tempDetailLevel, verticalQuality);
try
{
String fileName = getFileNameAndPathForRegion(regionX, regionZ, generationMode, tempDetailLevel);
// if the fileName was null that means the folder is inaccessible
// for some reason
if (fileName == null)
throw new IllegalArgumentException("Game folder is not accessible");
throw new IllegalArgumentException("Unable to read region [" + regionX + ", " + regionZ + "] file, no fileName.");
File f = new File(fileName);
@@ -151,65 +138,58 @@ public class LodDimensionFileHandler
// return anything
continue;
}
String data = "";
BufferedReader bufferedReader = new BufferedReader(new FileReader(f));
data = bufferedReader.readLine();
int fileVersion = -1;
byte[] data = {0};
long dataSize = f.length();
dataSize -= 1;
if (dataSize > 0) {
try (InputStream inputStream = new BufferedInputStream(new FileInputStream(f))) {
int fileVersion = -1;
fileVersion = inputStream.read();
// 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();
f.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.");
if (data != null && !data.isEmpty())
{
// try to get the file version
try
{
fileVersion = Integer.parseInt(data.substring(data.indexOf(' ')).trim());
} catch (NumberFormatException | StringIndexOutOfBoundsException e)
{
// this file doesn't have a version
// keep the version as -1
fileVersion = -1;
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, begin reading the file
data = new byte[(int) dataSize];
inputStream.read(data);
inputStream.close();
} catch (IOException ex) {
ex.printStackTrace();
}
// 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.
bufferedReader.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.");
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.
bufferedReader.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.");
break;
}
} else
{
// there is no data in this file
bufferedReader.close();
break;
}
// this file is a readable version, begin reading the file
data = bufferedReader.readLine();
bufferedReader.close();
region.addLevel(new LevelContainer(data));
switch (region.getLodQualityMode()){
default:
case HEIGHTMAP:
region.addLevel(new SingleLevelContainer(data));
break;
case MULTI_LOD:
region.addLevel(new VerticalLevelContainer(data));
break;
}
//region.addLevel(new SingleLevelContainer(data));
} catch (Exception e)
{
// 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();
}
}
@@ -239,10 +219,10 @@ public class LodDimensionFileHandler
{
for (int j = 0; j < loadedDimension.getWidth(); j++)
{
if (loadedDimension.isRegionDirty[i][j] && loadedDimension.regions[i][j] != null)
if (loadedDimension.isRegionToRegen(i,j) && loadedDimension.getRegionByArrayIndex(i,j) != null)
{
saveRegionToFile(loadedDimension.regions[i][j]);
loadedDimension.isRegionDirty[i][j] = false;
saveRegionToFile(loadedDimension.getRegionByArrayIndex(i,j));
loadedDimension.setRegenByArrayIndex(i, j,false);
}
}
}
@@ -267,14 +247,14 @@ public class LodDimensionFileHandler
int z = region.regionPosZ;
for (byte detailLevel = region.getMinDetailLevel(); detailLevel <= LodUtil.REGION_DETAIL_LEVEL; detailLevel++)
{
String fileName = getFileNameAndPathForRegion(x, z, region.getGenerationMode(), detailLevel);
String fileName = getFileNameAndPathForRegion(x, z, region.getGenerationMode(), detailLevel, region.getLodQualityMode());
File oldFile = new File(fileName);
// if the fileName was null that means the folder is inaccessible
// for some reason
if (fileName == null)
{
ClientProxy.LOGGER.warn("Unable to save region [" + x + ", " + z + "] to file.");
ClientProxy.LOGGER.warn("Unable to save region [" + x + ", " + z + "] to file, no fileName.");
return;
}
@@ -295,54 +275,45 @@ public class LodDimensionFileHandler
// is the correct version.
// (to make sure we don't overwrite a newer
// version file if it exists)
BufferedReader br = new BufferedReader(new FileReader(oldFile));
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
}
try (InputStream inputStream = new BufferedInputStream(new FileInputStream(oldFile))) {
fileVersion = inputStream.read();
inputStream.close();
} catch (IOException ex) {
ex.printStackTrace();
}
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)
{
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(fileVersion > LOD_SAVE_FILE_VERSION)
//else {
// we are good to continue and overwrite the old file
//}
}
// the old file is good, now create a new save file
File newFile = new File(fileName + TMP_FILE_EXTENSION);
FileWriter fw = new FileWriter(newFile);
try (OutputStream outputStream = new BufferedOutputStream(new FileOutputStream(newFile))) {
// add the version of this file
fw.write(LOD_FILE_VERSION_PREFIX + " " + LOD_SAVE_FILE_VERSION + "\n");
// add the version of this file
outputStream.write(LOD_SAVE_FILE_VERSION);
// add each LodChunk to the file
fw.write(region.getLevel(detailLevel).toString());
fw.close();
// 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);
// 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: ");
ClientProxy.LOGGER.error("LOD file write error. Unable to write to [" + fileName + "] error [" + e.getMessage() + "]: ");
e.printStackTrace();
}
}
@@ -363,7 +334,7 @@ public class LodDimensionFileHandler
* <p>
* Returns null if there is an IO Exception.
*/
private String getFileNameAndPathForRegion(int regionX, int regionZ, DistanceGenerationMode generationMode, byte detailLevel)
private String getFileNameAndPathForRegion(int regionX, int regionZ, DistanceGenerationMode generationMode, byte detailLevel, VerticalQuality verticalQuality)
{
try
{
@@ -372,9 +343,10 @@ public class LodDimensionFileHandler
// or
// ".\Super Flat\data"
return dimensionDataSaveFolder.getCanonicalPath() + File.separatorChar +
generationMode.toString() + File.separatorChar +
DETAIL_FOLDER_NAME_PREFIX + detailLevel + File.separatorChar +
FILE_NAME_PREFIX + "." + regionX + "." + regionZ + FILE_EXTENSION;
verticalQuality + File.separatorChar +
generationMode.toString() + File.separatorChar +
DETAIL_FOLDER_NAME_PREFIX + detailLevel + File.separatorChar +
FILE_NAME_PREFIX + "." + regionX + "." + regionZ + FILE_EXTENSION;
} catch (IOException | SecurityException e)
{
ClientProxy.LOGGER.warn("Unable to get the filename for the region [" + regionX + ", " + regionZ + "], error: [" + e.getMessage() + "], stacktrace: ");
@@ -18,10 +18,10 @@
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.wrappers.MinecraftWrapper;
/**
* This object is used to get variables from methods
@@ -29,27 +29,32 @@ import net.minecraft.client.Minecraft;
* in Optifine.
*
* @author James Seibel
* @version 7-03-2021
* @version 9-7-2021
*/
public class ReflectionHandler
{
private Minecraft mc = Minecraft.getInstance();
public static final ReflectionHandler INSTANCE = new ReflectionHandler();
private MinecraftWrapper mc = MinecraftWrapper.INSTANCE;
public Field ofFogField = null;
public ReflectionHandler()
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)
{
@@ -59,14 +64,15 @@ public class ReflectionHandler
return;
}
}
// we didn't find the field,
// either optifine isn't installed, or
// optifine changed the name of the variable
ofFogField = null;
}
/**
* Get what type of fog optifine is currently set to render.
*/
@@ -79,35 +85,34 @@ public class ReflectionHandler
// the setup method wasn't called yet.
return FogQuality.FANCY;
}
int returnNum = 0;
try
{
returnNum = (int) ofFogField.get(mc.options);
returnNum = (int) ofFogField.get(mc.getOptions());
} catch (IllegalArgumentException | IllegalAccessException e)
{
e.printStackTrace();
}
switch (returnNum)
{
// optifine's "default" option,
// it should never be called in this case
case 0:
return FogQuality.FAST;
// optifine's "default" option,
// it should never be called in this case
case 0:
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;
default:
return FogQuality.FAST;
}
}
}
@@ -17,26 +17,25 @@
*/
package com.seibel.lod.mixin;
import com.mojang.blaze3d.matrix.MatrixStack;
import com.seibel.lod.LodMain;
import com.seibel.lod.proxy.ClientProxy;
import net.minecraft.client.renderer.RenderType;
import net.minecraft.client.renderer.WorldRenderer;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import com.mojang.blaze3d.matrix.MatrixStack;
import com.seibel.lod.LodMain;
import com.seibel.lod.config.LodConfig;
import net.minecraft.client.renderer.RenderType;
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 +55,7 @@ public class MixinWorldRenderer
{
// only render if LODs are enabled and
// only render before solid blocks
if (LodConfig.CLIENT.graphics.drawLODs.get() && renderType.equals(RenderType.solid()))
LodMain.client_proxy.renderLods(previousPartialTicks);
if (ClientProxy.drawLods && renderType.equals(RenderType.solid()))
LodMain.client_proxy.renderLods(matrixStackIn, previousPartialTicks);
}
}
@@ -1,48 +0,0 @@
package com.seibel.lod.objects;
public class DataPoint
{
public static short[] createDataPoint(int height, int depth, int red, int green, int blue)
{
return new short[]{(short) height, (short) depth, (short) red, (short) green, (short) blue};
}
public static short getHeight(short[] dataPoint)
{
return dataPoint[0];
}
public static short getDepth(short[] dataPoint)
{
return dataPoint[1];
}
public static short getRed(short[] dataPoint)
{
return dataPoint[2];
}
public static short getGreen(short[] dataPoint)
{
return dataPoint[3];
}
public static short getBlue(short[] dataPoint)
{
return dataPoint[4];
}
public static short[] getHeightDepth(short[] dataPoint)
{
return new short[]{dataPoint[0], dataPoint[1]};
}
public static int getColor(short[] dataPoint)
{
int R = (dataPoint[2] << 16) & 0x00FF0000;
int G = (dataPoint[3] << 8) & 0x0000FF00;
int B = dataPoint[4] & 0x000000FF;
return 0xFF000000 | R | G | B;
}
}
@@ -1,100 +1,92 @@
package com.seibel.lod.objects;
import java.io.Serializable;
import com.seibel.lod.util.LodUtil;
public class LevelContainer implements Serializable
public interface LevelContainer
{
public static final char VERTICAL_DATA_DELIMITER = '\t';
public static final char DATA_DELIMITER = ' ';
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);
public final byte detailLevel;
/**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);
public final byte[][][] colors;
/**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);
public final short[][] height;
/**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);
public final short[][] depth;
/**
* @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);
public final boolean[][] dataExistence;
public LevelContainer(byte detailLevel, byte[][][] colors, short[][] height, short[][] depth, boolean[][] dataExistence)
{
this.detailLevel = detailLevel;
this.colors = colors;
this.height = height;
this.depth = depth;
this.dataExistence = dataExistence;
}
public LevelContainer(String data)
{
int index = 0;
int lastIndex = 0;
/**
* @return return the deatilLevel of this level container
*/
public byte getDetailLevel();
index = data.indexOf(DATA_DELIMITER, 0);
this.detailLevel = (byte) Integer.parseInt(data.substring(0, index));
int size = (int) Math.pow(2, LodUtil.REGION_DETAIL_LEVEL - detailLevel);
public int getMaxVerticalData();
this.colors = new byte[size][size][3];
this.height = new short[size][size];
this.depth = new short[size][size];
this.dataExistence = new boolean[size][size];
int intCol;
for (int x = 0; x < size; x++)
{
for (int z = 0; z < size; z++)
{
lastIndex = index;
index = data.indexOf(DATA_DELIMITER, lastIndex + 1);
intCol = Integer.parseInt(data.substring(lastIndex + 1, index), 16);
colors[x][z][0] = (byte) ((intCol >> 16) - 128);
colors[x][z][1] = (byte) ((intCol >> 8) - 128);
colors[x][z][2] = (byte) (intCol - 128);
public void clear(int posX, int posZ);
lastIndex = index;
index = data.indexOf(DATA_DELIMITER, lastIndex + 1);
height[x][z] = Short.parseShort(data.substring(lastIndex + 1, index), 16);
/**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();
lastIndex = index;
index = data.indexOf(DATA_DELIMITER, lastIndex + 1);
depth[x][z] = Short.parseShort(data.substring(lastIndex + 1, index), 16);
/**
*
* @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);
lastIndex = index;
index = data.indexOf(DATA_DELIMITER, lastIndex + 1);
dataExistence[x][z] = Boolean.parseBoolean(data.substring(lastIndex + 1, index));
}
}
/**
* This will give the data to save in the file
* @return data as a String
*/
public byte[] toDataString();
}
@Override
public String toString()
{
StringBuilder stringBuilder = new StringBuilder();
int combinedCol;
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 colors to intColor and then to HEX
combinedCol = ((colors[x][z][0] + 128) << 16) | ((colors[x][z][1] + 128) << 8) | ((colors[x][z][2] + 128));
stringBuilder.append(Integer.toHexString(combinedCol));
stringBuilder.append(DATA_DELIMITER);
stringBuilder.append(Integer.toHexString(height[x][z] & 0xffff));
stringBuilder.append(DATA_DELIMITER);
stringBuilder.append(Integer.toHexString(depth[x][z] & 0xffff));
stringBuilder.append(DATA_DELIMITER);
stringBuilder.append(dataExistence[x][z]);
stringBuilder.append(DATA_DELIMITER);
}
}
return stringBuilder.toString();
}
/**
* 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,7 +0,0 @@
package com.seibel.lod.objects.LevelPos;
public interface ImmutableLevelPos
{
public LevelPos getConvertedLevelPos(byte newDetailLevel);
public LevelPos getRegionModuleLevelPos();
}
@@ -1,415 +0,0 @@
package com.seibel.lod.objects.LevelPos;
import com.seibel.lod.objects.RegionPos;
import com.seibel.lod.util.LodUtil;
import net.minecraft.util.math.ChunkPos;
import java.util.Comparator;
public class LevelPos implements Cloneable, ImmutableLevelPos, MutableLevelPos, Comparable<LevelPos>
{
public byte detailLevel;
public int posX;
public int posZ;
public boolean flag;
public LevelPos()
{
}
public LevelPos(byte detailLevel, int posX, int posZ)
{
this.posX = posX;
this.posZ = posZ;
this.detailLevel = detailLevel;
}
/**
* this operation does not change the state
*/
public LevelPos getConvertedLevelPos(byte newDetailLevel)
{
if (newDetailLevel >= detailLevel)
{
int width = 1 << (newDetailLevel - detailLevel);
return new LevelPos(
newDetailLevel,
Math.floorDiv(posX, width),
Math.floorDiv(posZ, width));
} else
{
int width = 1 << (detailLevel - newDetailLevel);
return new LevelPos(
newDetailLevel,
posX * width,
posZ * width);
}
}
/**
* this operation does not change the state
*/
public LevelPos getRegionModuleLevelPos()
{
int width = 1 << (LodUtil.REGION_DETAIL_LEVEL - detailLevel);
return new LevelPos(
detailLevel,
Math.floorMod(posX, width),
Math.floorMod(posZ, width));
}
/**
* this operation changes the state
*/
public void convert(byte newDetailLevel)
{
if (newDetailLevel >= detailLevel)
{
int width = 1 << (newDetailLevel - detailLevel);
detailLevel = newDetailLevel;
posX = Math.floorDiv(posX, width);
posZ = Math.floorDiv(posZ, width);
} else
{
int width = 1 << (detailLevel - newDetailLevel);
detailLevel = newDetailLevel;
posX = posX * width;
posZ = posZ * width;
}
}
/**
* this operation changes the state
*/
public void performRegionModule()
{
int width = 1 << (LodUtil.REGION_DETAIL_LEVEL - detailLevel);
posX = Math.floorMod(posX, width);
posZ = Math.floorMod(posZ, width);
}
/**
* this operation changes the state
*/
public void applyOffset(int xOffset, int zOffset)
{
posX = posX + xOffset;
posX = posZ + zOffset;
}
/**
* this operation changes the state
*/
public void changeParameters(byte newDetailLevel, int newPosX, int newPosZ)
{
detailLevel = newDetailLevel;
posX = newPosX;
posZ = newPosZ;
}
public RegionPos getRegionPos()
{
int width = 1 << (LodUtil.REGION_DETAIL_LEVEL - detailLevel);
return new RegionPos(
Math.floorDiv(posX, width),
Math.floorDiv(posZ, width));
}
public int getRegionPosX()
{
int width = 1 << (LodUtil.REGION_DETAIL_LEVEL - detailLevel);
return Math.floorDiv(posX, width);
}
public int getRegionPosZ()
{
int width = 1 << (LodUtil.REGION_DETAIL_LEVEL - detailLevel);
return Math.floorDiv(posZ, width);
}
public int getChunkPosX()
{
if (LodUtil.CHUNK_DETAIL_LEVEL >= detailLevel)
{
int width = 1 << (LodUtil.CHUNK_DETAIL_LEVEL - detailLevel);
return Math.floorDiv(posX, width);
} else
{
int width = 1 << (detailLevel - LodUtil.CHUNK_DETAIL_LEVEL);
return posX * width;
}
}
public int getChunkPosZ()
{
if (LodUtil.CHUNK_DETAIL_LEVEL >= detailLevel)
{
int width = 1 << (LodUtil.CHUNK_DETAIL_LEVEL - detailLevel);
return Math.floorDiv(posZ, width);
} else
{
int width = 1 << (detailLevel - LodUtil.CHUNK_DETAIL_LEVEL);
return posZ * width;
}
}
public ChunkPos getChunkPos()
{
if (LodUtil.CHUNK_DETAIL_LEVEL >= detailLevel)
{
int width = 1 << (LodUtil.CHUNK_DETAIL_LEVEL - detailLevel);
return new ChunkPos(
Math.floorDiv(posX, width),
Math.floorDiv(posZ, width));
} else
{
int width = 1 << (detailLevel - LodUtil.CHUNK_DETAIL_LEVEL);
return new ChunkPos(
posX * width,
posZ * width);
}
}
/**
* TODO fix the region disappearing for a second
*/
public int maxDistance(int playerPosX, int playerPosZ, int regionPosX, int regionPosZ)
{
int width = 1 << detailLevel;
int startPosX = regionPosX * 512 + posX * width;
int startPosZ = regionPosZ * 512 + 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 int maxDistance(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 int minDistance(int playerPosX, int playerPosZ, int regionPosX, int regionPosZ)
{
int width = 1 << detailLevel;
int startPosX = regionPosX * 512 + posX * width;
int startPosZ = regionPosZ * 512 + 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 int minDistance(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 LevelPosDistanceComparator getPosComparator(int playerPosX, int playerPosZ)
{
return new LevelPosDistanceComparator(playerPosX, playerPosZ);
}
public static LevelPosDetailComparator getPosAndDetailComparator(int playerPosX, int playerPosZ)
{
return new LevelPosDetailComparator(playerPosX, playerPosZ);
}
public static class LevelPosDistanceComparator implements Comparator<LevelPos>
{
int playerPosX;
int playerPosZ;
public LevelPosDistanceComparator(int playerPosX, int playerPosZ)
{
this.playerPosX = playerPosX;
this.playerPosZ = playerPosZ;
}
@Override
public int compare(LevelPos first, LevelPos second)
{
return Integer.compare(
first.minDistance(playerPosX, playerPosZ),
second.minDistance(playerPosX, playerPosZ));
}
}
public static class LevelPosDetailComparator implements Comparator<LevelPos>
{
int playerPosX;
int playerPosZ;
public LevelPosDetailComparator(int playerPosX, int playerPosZ)
{
this.playerPosX = playerPosX;
this.playerPosZ = playerPosZ;
}
@Override
public int compare(LevelPos first, LevelPos second)
{
int compareResult = Integer.compare(second.detailLevel, first.detailLevel);
if (compareResult == 0)
{
compareResult = Integer.compare(
first.minDistance(playerPosX, playerPosZ),
second.minDistance(playerPosX, playerPosZ));
}
return compareResult;
}
}
public static LevelPosComparator getComparator()
{
return new LevelPosComparator();
}
public static class LevelPosComparator implements Comparator<LevelPos>
{
@Override
public int compare(LevelPos first, LevelPos second)
{
int compareResult = Integer.compare(first.detailLevel, second.detailLevel);
if (compareResult == 0)
{
compareResult = Integer.compare(
first.posX,
second.posX);
}
if (compareResult == 0)
{
compareResult = Integer.compare(
first.posZ,
second.posZ);
}
return compareResult;
}
}
@Override
public int compareTo(LevelPos other)
{
int compareResult = Integer.compare(this.detailLevel, other.detailLevel);
if (compareResult == 0)
{
compareResult = Integer.compare(
this.posX,
other.posX);
}
if (compareResult == 0)
{
compareResult = Integer.compare(
this.posZ,
other.posZ);
}
return compareResult;
}
@Override
public LevelPos clone()
{
return new LevelPos(detailLevel, posX, posZ);
}
@Override
public int hashCode()
{
int hash = 7;
hash = 31 * hash + (int) detailLevel;
hash = 31 * hash + posX;
hash = 31 * hash + posZ;
return hash;
}
@Override
public boolean equals(Object other)
{
return (this.detailLevel == ((LevelPos) other).detailLevel &&
this.posX == ((LevelPos) other).posX &&
this.posZ == ((LevelPos) other).posZ);
}
@Override
public String toString()
{
String s = (detailLevel + " " + posX + " " + posZ);
return s;
}
}
@@ -1,14 +0,0 @@
package com.seibel.lod.objects.LevelPos;
import com.seibel.lod.util.LodUtil;
public interface MutableLevelPos
{
public void convert(byte newDetailLevel);
public void performRegionModule();
public void applyOffset(int xOffset, int zOffset);
public void changeParameters(byte newDetailLevel, int newPosX, int newPosZ);
}
@@ -19,22 +19,18 @@ package com.seibel.lod.objects;
import java.io.File;
import java.io.IOException;
import java.security.InvalidParameterException;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import org.apache.commons.lang3.mutable.MutableBoolean;
import com.seibel.lod.config.LodConfig;
import com.seibel.lod.enums.DistanceGenerationMode;
import com.seibel.lod.enums.GenerationPriority;
import com.seibel.lod.enums.LodTemplate;
import com.seibel.lod.enums.VerticalQuality;
import com.seibel.lod.handlers.LodDimensionFileHandler;
import com.seibel.lod.objects.LevelPos.LevelPos;
import com.seibel.lod.util.DetailDistanceUtil;
import com.seibel.lod.util.LodThreadFactory;
import com.seibel.lod.util.LodUtil;
import com.seibel.lod.util.*;
import com.seibel.lod.wrappers.MinecraftWrapper;
import net.minecraft.client.Minecraft;
import net.minecraft.util.math.ChunkPos;
import net.minecraft.world.DimensionType;
import net.minecraft.world.server.ServerChunkProvider;
@@ -47,7 +43,7 @@ import net.minecraft.world.server.ServerWorld;
*
* @author Leonardo Amato
* @author James Seibel
* @version 8-8-2021
* @version 9-18-2021
*/
public class LodDimension
{
@@ -63,12 +59,30 @@ public class LodDimension
*/
private volatile int halfWidth;
// these three variables are private to force use of the getWidth() method
// which is a safer way to get the width then directly asking the arrays
/**
* stores all the regions in this dimension
*/
public volatile LodRegion[][] regions;
public volatile LodRegion regions[][];
public volatile boolean isRegionDirty[][];
public volatile boolean regen[][];
/** if true that means there are regions in this dimension
* that need to have their buffers rebuilt. */
/**
* stores if the region at the given x and z index needs to be saved to disk
*/
private volatile boolean[][] isRegionDirty;
/**
* stores if the region at the given x and z index needs to be regenerated
*/
private volatile boolean[][] regionNeedsRegen;
/**
* stores if the buffer size at the given x and z index needs to be changed
*/
private volatile boolean[][] setupBuffer;
/**
* if true that means there are regions in this dimension
* that need to have their buffers rebuilt.
*/
public volatile boolean regenDimension = false;
private volatile RegionPos center;
@@ -88,8 +102,8 @@ public class LodDimension
lastGenChunk = null;
dimension = newDimension;
width = newWidth;
halfWidth = (int) Math.floor(width / 2);
Minecraft mc = Minecraft.getInstance();
halfWidth = width / 2;
MinecraftWrapper mc = MinecraftWrapper.INSTANCE;
if (newDimension != null && lodWorld != null)
{
try
@@ -110,8 +124,8 @@ public class LodDimension
{
// connected to server
saveDir = new File(mc.gameDirectory.getCanonicalFile().getPath() +
File.separatorChar + "lod server data" + File.separatorChar + LodUtil.getDimensionIDFromWorld(mc.level));
saveDir = new File(mc.getGameDirectory().getCanonicalFile().getPath() +
File.separatorChar + "lod server data" + File.separatorChar + mc.getCurrentDimensionId());
}
fileHandler = new LodDimensionFileHandler(saveDir, this);
@@ -125,7 +139,8 @@ public class LodDimension
regions = new LodRegion[width][width];
isRegionDirty = new boolean[width][width];
regen = new boolean[width][width];
regionNeedsRegen = new boolean[width][width];
setupBuffer = new boolean[width][width];
//treeGenerator((int) mc.player.getX(),(int) mc.player.getZ());
@@ -239,12 +254,10 @@ public class LodDimension
/**
* return needed memory in byte
* return needed memory in bytes
*/
public int getMinMemoryNeeded()
{
int regionX;
int regionZ;
int count = 0;
LodRegion region;
@@ -255,32 +268,36 @@ public class LodDimension
region = regions[x][z];
if (region != null)
{
count += region.getMinMemoryNeeded();
count += region.getMinMemoryNeeded(LodConfig.CLIENT.graphics.lodTemplate.get());
}
}
}
return count;
}
/**
* Gets the region at the given X and Z
* <br>
* Returns null if the region doesn't exist
* or is outside the loaded area.
*/
public LodRegion getRegion(LevelPos levelPos)
public LodRegion getRegion(byte detailLevel, int posX, int posZ)
{
int xRegion = LevelPosUtil.getRegion(detailLevel, posX);
int zRegion = LevelPosUtil.getRegion(detailLevel, posZ);
int xIndex = (xRegion - center.x) + halfWidth;
int zIndex = (zRegion - center.z) + halfWidth;
RegionPos regionPos = levelPos.getRegionPos();
int xIndex = (regionPos.x - center.x) + halfWidth;
int zIndex = (regionPos.z - center.z) + halfWidth;
if (!regionIsInRange(regionPos.x, regionPos.z))
throw new ArrayIndexOutOfBoundsException("Region for level pos " + levelPos + " out of range");
if (!regionIsInRange(xRegion, zRegion))
return null;
//throw new ArrayIndexOutOfBoundsException("Region for level pos " + LevelPosUtil.toString(detailLevel, posX, posZ) + " out of range");
else if (regions[xIndex][zIndex] == null)
throw new InvalidParameterException("Region for level pos " + levelPos + " not currently initialized");
else if (regions[xIndex][zIndex].getMinDetailLevel() > levelPos.detailLevel)
throw new InvalidParameterException("Region for level pos " + levelPos + " currently only reach level " + regions[xIndex][zIndex].getMinDetailLevel());
return null;
//throw new InvalidParameterException("Region for level pos " + LevelPosUtil.toString(detailLevel, posX, posZ) + " not currently initialized");
else if (regions[xIndex][zIndex].getMinDetailLevel() > detailLevel)
return null;
//throw new InvalidParameterException("Region for level pos " + LevelPosUtil.toString(detailLevel, posX, posZ) + " currently only reach level " + regions[xIndex][zIndex].getMinDetailLevel());
return regions[xIndex][zIndex];
}
@@ -290,15 +307,25 @@ public class LodDimension
* Returns null if the region doesn't exist
* or is outside the loaded area.
*/
public LodRegion getRegion(RegionPos regionPos)
public LodRegion getRegion(int regionPosX, int regionPosZ)
{
int xIndex = (regionPos.x - center.x) + halfWidth;
int zIndex = (regionPos.z - center.z) + halfWidth;
int xIndex = (regionPosX - center.x) + halfWidth;
int zIndex = (regionPosZ - center.z) + halfWidth;
if (!regionIsInRange(regionPos.x, regionPos.z))
throw new ArrayIndexOutOfBoundsException("Region " + regionPos + " out of range");
if (!regionIsInRange(regionPosX, regionPosZ))
return null;
//throw new ArrayIndexOutOfBoundsException("Region " + regionPosX + " " + regionPosZ + " out of range");
else if (regions[xIndex][zIndex] == null)
throw new InvalidParameterException("Region " + regionPos + " not currently initialized");
return null;
//throw new InvalidParameterException("Region " + regionPosX + " " + regionPosZ + " not currently initialized");
return regions[xIndex][zIndex];
}
/**
* Useful when needing to iterate over every region.
*/
public LodRegion getRegionByArrayIndex(int xIndex, int zIndex)
{
return regions[xIndex][zIndex];
}
@@ -310,7 +337,7 @@ public class LodDimension
public synchronized void addOrOverwriteRegion(LodRegion newRegion) throws ArrayIndexOutOfBoundsException
{
int xIndex = (newRegion.regionPosX - center.x) + halfWidth;
int zIndex = (center.z - newRegion.regionPosZ) + halfWidth;
int zIndex = (newRegion.regionPosZ - center.z) + halfWidth;
if (!regionIsInRange(newRegion.regionPosX, newRegion.regionPosZ))
// out of range
@@ -325,7 +352,7 @@ public class LodDimension
*/
public void treeCutter(int playerPosX, int playerPosZ)
{
ChunkPos newPlayerChunk = (new LevelPos((byte) 0, playerPosX, playerPosZ)).getChunkPos();
ChunkPos newPlayerChunk = new ChunkPos(LevelPosUtil.getChunkPos((byte) 0, playerPosX), LevelPosUtil.getChunkPos((byte) 0, playerPosZ));
if (lastCutChunk == null)
lastCutChunk = new ChunkPos(newPlayerChunk.x + 1, newPlayerChunk.z - 1);
if (newPlayerChunk.x != lastCutChunk.x || newPlayerChunk.z != lastCutChunk.z)
@@ -338,7 +365,6 @@ public class LodDimension
int minDistance;
byte detail;
byte levelToCut;
LevelPos levelPos = new LevelPos();
for (int x = 0; x < regions.length; x++)
{
@@ -351,13 +377,13 @@ public class LodDimension
//if this is not the case w
if (regions[x][z] != null)
{
levelPos.changeParameters(LodUtil.REGION_DETAIL_LEVEL, regionX, regionZ);
minDistance = levelPos.minDistance(playerPosX, playerPosZ);
detail = DetailDistanceUtil.getDistanceTreeCutInverse(minDistance);
minDistance = LevelPosUtil.minDistance(LodUtil.REGION_DETAIL_LEVEL, regionX, regionZ, playerPosX, playerPosZ);
detail = DetailDistanceUtil.getTreeCutDetailFromDistance(minDistance);
levelToCut = DetailDistanceUtil.getCutLodDetail(detail);
if (regions[x][z].getMinDetailLevel() > levelToCut)
{
regions[x][z].cutTree(levelToCut);
setupBuffer[x][z] = true;
}
}
@@ -375,7 +401,8 @@ public class LodDimension
public void treeGenerator(int playerPosX, int playerPosZ)
{
DistanceGenerationMode generationMode = LodConfig.CLIENT.worldGenerator.distanceGenerationMode.get();
ChunkPos newPlayerChunk = (new LevelPos((byte) 0, playerPosX, playerPosZ)).getChunkPos();
ChunkPos newPlayerChunk = new ChunkPos(LevelPosUtil.getChunkPos((byte) 0, playerPosX), LevelPosUtil.getChunkPos((byte) 0, playerPosZ));
VerticalQuality verticalQuality = LodConfig.CLIENT.worldGenerator.lodQualityMode.get();
if (lastGenChunk == null)
lastGenChunk = new ChunkPos(newPlayerChunk.x + 1, newPlayerChunk.z - 1);
@@ -390,42 +417,41 @@ public class LodDimension
int minDistance;
byte detail;
byte levelToGen;
LevelPos levelPos = new LevelPos();
for (int x = 0; x < regions.length; x++)
{
for (int z = 0; z < regions.length; z++)
{
regionX = (x + center.x) - halfWidth;
regionZ = (z + center.z) - halfWidth;
levelPos.changeParameters(LodUtil.REGION_DETAIL_LEVEL, regionX, regionZ);
final RegionPos regionPos = new RegionPos(regionX, regionZ);
region = regions[x][z];
//We require that the region we are checking is loaded with at least this level
levelPos.changeParameters(LodUtil.REGION_DETAIL_LEVEL, regionX, regionZ);
minDistance = levelPos.minDistance(playerPosX, playerPosZ);
detail = DetailDistanceUtil.getDistanceTreeGenInverse(minDistance);
minDistance = LevelPosUtil.minDistance(LodUtil.REGION_DETAIL_LEVEL, regionX, regionZ, playerPosX, playerPosZ);
detail = DetailDistanceUtil.getTreeGenDetailFromDistance(minDistance);
levelToGen = DetailDistanceUtil.getLodGenDetail(detail).detailLevel;
if (region == null || region.getGenerationMode() != generationMode)
{
//First case, region has to be initialized
//We check if there is a file at the target level
regions[x][z] = getRegionFromFile(regionPos, levelToGen, generationMode);
regions[x][z] = getRegionFromFile(regionPos, levelToGen, generationMode, verticalQuality);
//if there is no file we initialize the region
if (regions[x][z] == null)
{
regions[x][z] = new LodRegion(levelToGen, regionPos, generationMode);
regions[x][z] = new LodRegion(levelToGen, regionPos, generationMode, verticalQuality);
}
regen[x][z] = true;
regionNeedsRegen[x][z] = true;
regenDimension = true;
setupBuffer[x][z] = true;
} else if (region.getMinDetailLevel() > levelToGen)
{
//Second case, region has been initialized but at a higher level
//We expand the region by introducing the missing layer
region.expand(levelToGen);
setupBuffer[x][z] = true;
}
}
}
@@ -439,32 +465,31 @@ public class LodDimension
* stored in the LOD. If an LOD already exists at the given
* coordinates it will be overwritten.
*/
public synchronized Boolean addData(LevelPos levelPos, short[] lodDataPoint, boolean dontSave, boolean serverQuality)
public Boolean addData(byte detailLevel, int posX, int posZ, int verticalIndex, long data, boolean dontSave)
{
// don't continue if the region can't be saved
RegionPos regionPos = levelPos.getRegionPos();
if (!regionIsInRange(regionPos.x, regionPos.z))
{
int regionPosX = LevelPosUtil.getRegion(detailLevel, posX);
int regionPosZ = LevelPosUtil.getRegion(detailLevel, posZ);
LodRegion region = getRegion(regionPosX, regionPosZ);
if (region == null)
return false;
}
LodRegion region = getRegion(levelPos);
boolean nodeAdded = region.addData(levelPos, lodDataPoint, serverQuality);
boolean nodeAdded = region.addData(detailLevel, posX, posZ, verticalIndex, data);
// only save valid LODs to disk
if (!dontSave && fileHandler != null)
{
try
{
// mark the region as dirty so it will be saved to disk
int xIndex = (regionPos.x - center.x) + halfWidth;
int zIndex = (regionPos.z - center.z) + halfWidth;
int xIndex = (regionPosX - center.x) + halfWidth;
int zIndex = (regionPosZ - center.z) + halfWidth;
isRegionDirty[xIndex][zIndex] = true;
regen[xIndex][zIndex] = true;
regionNeedsRegen[xIndex][zIndex] = true;
regenDimension = true;
} catch (ArrayIndexOutOfBoundsException e)
{
e.printStackTrace();
// This method was probably called when the dimension was changing size.
// Hopefully this shouldn't be an issue.
}
@@ -472,10 +497,11 @@ public class LodDimension
return nodeAdded;
}
public void setToRegen(int xRegion, int zRegion){
public void setToRegen(int xRegion, int zRegion)
{
int xIndex = (xRegion - center.x) + halfWidth;
int zIndex = (zRegion - center.z) + halfWidth;
regen[xIndex][zIndex] = true;
regionNeedsRegen[xIndex][zIndex] = true;
}
/**
@@ -483,32 +509,97 @@ public class LodDimension
*
* @return list of quadTrees
*/
public void getDataToGenerate(ConcurrentMap<LevelPos, MutableBoolean> dataToGenerate, int playerPosX, int playerPosZ)
public PosToGenerateContainer getDataToGenerate(int maxDataToGenerate, int playerPosX, int playerPosZ)
{
int n = regions.length;
int xIndex;
int zIndex;
PosToGenerateContainer posToGenerate;
LodRegion region;
RegionPos regionPos;
for (int xRegion = 0; xRegion < n; xRegion++)
int x, z, dx, dz, t;
x = 0;
z = 0;
dx = 0;
dz = -1;
switch (LodConfig.CLIENT.worldGenerator.generationPriority.get())
{
for (int zRegion = 0; zRegion < n; zRegion++)
{
try
default:
case NEAR_FIRST:
posToGenerate = new PosToGenerateContainer((byte) 10, maxDataToGenerate, 0, playerPosX, playerPosZ);
int playerChunkX = LevelPosUtil.getChunkPos(LodUtil.BLOCK_DETAIL_LEVEL, playerPosX);
int playerChunkZ = LevelPosUtil.getChunkPos(LodUtil.BLOCK_DETAIL_LEVEL, playerPosZ);
int xChunkToCheck;
int zChunkToCheck;
byte detailLevel;
int posX;
int posZ;
long data;
int numbChunksWide = (width) * 32;
int circleLimit = Integer.MAX_VALUE;
for (int i = 0; i < numbChunksWide * numbChunksWide; i++)
{
xIndex = (xRegion + center.x) - halfWidth;
zIndex = (zRegion + center.z) - halfWidth;
regionPos = new RegionPos(xIndex, zIndex);
region = getRegion(regionPos);
region.getDataToGenerate(dataToGenerate, playerPosX, playerPosZ);
// use this for square generation
} catch (Exception e)
{
//e.printStackTrace();
// use this for circular generation
if (maxDataToGenerate < 0)
{
if (circleLimit < Math.abs(x) && circleLimit < Math.abs(z))
break;
} else if (maxDataToGenerate == 0)
{
maxDataToGenerate--;
circleLimit = (int) (Math.abs(x) * 1.41f);
}
xChunkToCheck = x + playerChunkX;
zChunkToCheck = z + playerChunkZ;
//distance = LevelPosUtil.maxDistance(LodUtil.CHUNK_DETAIL_LEVEL, xChunkToCheck, zChunkToCheck, playerChunkX, playerChunkZ);
region = getRegion(LodUtil.CHUNK_DETAIL_LEVEL, xChunkToCheck, zChunkToCheck);
if (region == null)
continue;
detailLevel = region.getMinDetailLevel();
posX = LevelPosUtil.convert(LodUtil.CHUNK_DETAIL_LEVEL, xChunkToCheck, detailLevel);
posZ = LevelPosUtil.convert(LodUtil.CHUNK_DETAIL_LEVEL, zChunkToCheck, detailLevel);
data = getSingleData(detailLevel, posX, posZ);
if (DataPointUtil.getGenerationMode(data) < LodConfig.CLIENT.worldGenerator.distanceGenerationMode.get().complexity)
{
posToGenerate.addPosToGenerate(detailLevel, posX, posZ);
if (maxDataToGenerate >= 0)
maxDataToGenerate--;
}
if ((x == z) || ((x < 0) && (x == -z)) || ((x > 0) && (x == 1 - z)))
{
t = dx;
dx = -dz;
dz = t;
}
x += dx;
z += dz;
}
}
break;
case FAR_FIRST:
posToGenerate = new PosToGenerateContainer((byte) 8, maxDataToGenerate, (int) (maxDataToGenerate * 0.25), playerPosX, playerPosZ);
int n = regions.length;
int xRegion;
int zRegion;
for (int i = 0; i < width * width; i++)
{
xRegion = x + center.x;
zRegion = z + center.z;
region = getRegion(xRegion, zRegion);
if (region != null)
region.getDataToGenerate(posToGenerate, playerPosX, playerPosZ);
if ((x == z) || ((x < 0) && (x == -z)) || ((x > 0) && (x == 1 - z)))
{
t = dx;
dx = -dz;
dz = t;
}
x += dx;
z += dz;
}
break;
}
return posToGenerate;
}
/**
@@ -516,33 +607,27 @@ public class LodDimension
*
* @return list of nodes
*/
public void getDataToRender(ConcurrentMap<LevelPos, MutableBoolean> dataToRender, RegionPos regionPos, int playerPosX, int playerPosZ)
public void getDataToRender(PosToRenderContainer posToRender, RegionPos regionPos, int playerPosX,
int playerPosZ)
{
try
{
LodRegion region = getRegion(regionPos);
region.getDataToRender(dataToRender, playerPosX, playerPosZ);
} catch (NullPointerException e)
{
System.out.println(regionPos);
e.printStackTrace();
} catch (Exception e)
{
//e.printStackTrace();
}
LodRegion region = getRegion(regionPos.x, regionPos.z);
if (region != null)
region.getDataToRender(posToRender, playerPosX, playerPosZ, LodConfig.CLIENT.worldGenerator.generationPriority.get() == GenerationPriority.NEAR_FIRST);
}
/**
* Get the LodNodeData at the given X and Z coordinates
* in this dimension.
* <br>
* Returns null if the LodChunk doesn't exist or
* is outside the loaded area.
*/
public short[] getData(ChunkPos chunkPos)
public int getMaxVerticalData(byte detailLevel, int posX, int posZ)
{
LevelPos levelPos = new LevelPos(LodUtil.CHUNK_DETAIL_LEVEL, chunkPos.x, chunkPos.z);
return getData(levelPos);
if (detailLevel > LodUtil.REGION_DETAIL_LEVEL)
throw new IllegalArgumentException("getLodFromCoordinates given a level of \"" + detailLevel + "\" when \"" + LodUtil.REGION_DETAIL_LEVEL + "\" is the max.");
LodRegion region = getRegion(detailLevel, posX, posZ);
if (region == null)
{
return 0;
}
return region.getMaxVerticalData(detailLevel);
}
/**
@@ -552,26 +637,19 @@ public class LodDimension
* Returns null if the LodChunk doesn't exist or
* is outside the loaded area.
*/
public short[] getData(LevelPos levelPos)
public long getData(byte detailLevel, int posX, int posZ, int verticalIndex)
{
if (levelPos.detailLevel > LodUtil.REGION_DETAIL_LEVEL)
throw new IllegalArgumentException("getLodFromCoordinates given a level of \"" + levelPos.detailLevel + "\" when \"" + LodUtil.REGION_DETAIL_LEVEL + "\" is the max.");
if (detailLevel > LodUtil.REGION_DETAIL_LEVEL)
throw new IllegalArgumentException("getLodFromCoordinates given a level of \"" + detailLevel + "\" when \"" + LodUtil.REGION_DETAIL_LEVEL + "\" is the max.");
try
LodRegion region = getRegion(detailLevel, posX, posZ);
if (region == null)
{
LodRegion region = getRegion(levelPos);
if (region == null)
{
return null;
}
return region.getData(levelPos);
} catch (Exception e)
{
return null;
return DataPointUtil.EMPTY_DATA;
}
return region.getData(detailLevel, posX, posZ, verticalIndex);
}
@@ -582,50 +660,97 @@ public class LodDimension
* Returns null if the LodChunk doesn't exist or
* is outside the loaded area.
*/
public void updateData(LevelPos levelPos)
public long getSingleData(byte detailLevel, int posX, int posZ)
{
if (levelPos.detailLevel > LodUtil.REGION_DETAIL_LEVEL)
throw new IllegalArgumentException("getLodFromCoordinates given a level of \"" + levelPos.detailLevel + "\" when \"" + LodUtil.REGION_DETAIL_LEVEL + "\" is the max.");
if (detailLevel > LodUtil.REGION_DETAIL_LEVEL)
throw new IllegalArgumentException("getLodFromCoordinates given a level of \"" + detailLevel + "\" when \"" + LodUtil.REGION_DETAIL_LEVEL + "\" is the max.");
LodRegion region = getRegion(levelPos);
LodRegion region = getRegion(detailLevel, posX, posZ);
if (region == null)
{
return DataPointUtil.EMPTY_DATA;
}
return region.getSingleData(detailLevel, posX, posZ);
}
public void clear(byte detailLevel, int posX, int posZ)
{
if (detailLevel > LodUtil.REGION_DETAIL_LEVEL)
throw new IllegalArgumentException("getLodFromCoordinates given a level of \"" + detailLevel + "\" when \"" + LodUtil.REGION_DETAIL_LEVEL + "\" is the max.");
LodRegion region = getRegion(detailLevel, posX, posZ);
if (region == null)
{
return;
}
region.clear(detailLevel, posX, posZ);
}
public boolean isRegionToRegen(int xIndex, int zIndex)
{
return regionNeedsRegen[xIndex][zIndex];
}
public boolean isBufferToSetup(int xIndex, int zIndex)
{
return setupBuffer[xIndex][zIndex];
}
public void setRegenByArrayIndex(int xIndex, int zIndex, boolean newRegen)
{
regionNeedsRegen[xIndex][zIndex] = newRegen;
}
/**
* Get the data point at the given X and Z coordinates
* in this dimension.
* <br>
* Returns null if the LodChunk doesn't exist or
* is outside the loaded area.
*/
public void updateData(byte detailLevel, int posX, int posZ)
{
if (detailLevel > LodUtil.REGION_DETAIL_LEVEL)
throw new IllegalArgumentException("getLodFromCoordinates given a level of \"" + detailLevel + "\" when \"" + LodUtil.REGION_DETAIL_LEVEL + "\" is the max.");
LodRegion region = getRegion(detailLevel, posX, posZ);
if (region == null)
{
return;
}
region.updateArea(levelPos);
region.updateArea(detailLevel, posX, posZ);
}
/**
* return true if and only if the node at that position exist
*/
public boolean doesDataExist(LevelPos levelPos)
public boolean doesDataExist(byte detailLevel, int posX, int posZ)
{
try
{
LodRegion region = getRegion(levelPos);
LodRegion region = getRegion(detailLevel, posX, posZ);
if (region == null)
{
return false;
}
return region.doesDataExist(levelPos.clone());
} catch (Exception e)
if (region == null)
{
return false;
}
return region.doesDataExist(detailLevel, posX, posZ);
}
/**
* Get the region at the given X and Z coordinates from the
* RegionFileHandler.
*/
public LodRegion getRegionFromFile(RegionPos regionPos, byte detailLevel, DistanceGenerationMode generationMode)
public LodRegion getRegionFromFile(RegionPos regionPos, byte detailLevel, DistanceGenerationMode
generationMode, VerticalQuality verticalQuality)
{
if (fileHandler != null)
return fileHandler.loadRegionFromFile(detailLevel, regionPos, generationMode);
return fileHandler.loadRegionFromFile(detailLevel, regionPos, generationMode, verticalQuality);
else
return null;
}
@@ -662,7 +787,9 @@ public class LodDimension
return center.z;
}
/**
* returns the width of the dimension in regions
*/
public int getWidth()
{
if (regions != null)
@@ -677,6 +804,7 @@ public class LodDimension
}
}
public void setRegionWidth(int newWidth)
{
width = newWidth;
@@ -684,7 +812,8 @@ public class LodDimension
regions = new LodRegion[width][width];
isRegionDirty = new boolean[width][width];
regen = new boolean[width][width];
regionNeedsRegen = new boolean[width][width];
setupBuffer = new boolean[width][width];
// populate isRegionDirty
for (int i = 0; i < width; i++)
@@ -696,9 +825,6 @@ public class LodDimension
@Override
public String toString()
{
int regionX;
int regionZ;
LevelPos levelPos;
LodRegion region;
StringBuilder stringBuilder = new StringBuilder();
@@ -721,7 +847,31 @@ public class LodDimension
}
stringBuilder.append("\n");
}
System.out.println(stringBuilder);
return stringBuilder.toString();
}
public int getMemoryRequired(int x, int z, LodTemplate template)
{
/*return regions[x][z].getMinMemoryNeeded(template);*/
/*TODO add memory use calculated with the following cases
switch (LodConfig.CLIENT.graphics.detailDropOff.get())
{
default:
case BY_BLOCK:
break;
case BY_REGION_FANCY:
break;
case BY_REGION_FAST:
}*/
int minDistance = LevelPosUtil.minDistance(LodUtil.REGION_DETAIL_LEVEL, x, z, halfWidth, halfWidth);
int detail = DetailDistanceUtil.getTreeCutDetailFromDistance(minDistance);
int levelToGen = DetailDistanceUtil.getLodDrawDetail(detail);
int size = 1 << (LodUtil.REGION_DETAIL_LEVEL - levelToGen);
int maxVerticalData = DetailDistanceUtil.getMaxVerticalData(detail);
int numberOfLods = size * size * maxVerticalData;
int memoryUse = numberOfLods * template.getBufferMemoryForSingleLod(maxVerticalData);
System.out.println(detail + " " + memoryUse + " " + numberOfLods + " " + template.getBufferMemoryForSingleLod(maxVerticalData));
return memoryUse;
}
}
@@ -1,15 +1,13 @@
package com.seibel.lod.objects;
import java.io.Serializable;
import java.util.concurrent.ConcurrentMap;
import org.apache.commons.lang3.mutable.MutableBoolean;
import com.seibel.lod.builders.LodBuilder;
import com.seibel.lod.config.LodConfig;
import com.seibel.lod.enums.DistanceGenerationMode;
import com.seibel.lod.objects.LevelPos.LevelPos;
import com.seibel.lod.proxy.ClientProxy;
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;
/**
@@ -19,148 +17,122 @@ import com.seibel.lod.util.LodUtil;
* 0 for x, 1 for z in 2D
* 0 for x, 1 for y, 2 for z in 3D
*/
public class LodRegion implements Serializable
public class LodRegion
{
//x coord,
private byte minDetailLevel;
private static final byte POSSIBLE_LOD = 10;
private int numberOfPoints;
private DistanceGenerationMode generationMode;
//private int numberOfPoints;
//For each of the following field the first slot is for the level of detail
//Important: byte have a [-128, 127] range. When converting from or to int a 128 should be added or removed
//If there is a bug with color then it's probably caused by this.
//in the future other fields like transparency and light level could be added
private byte[][][][] colors;
private short[][][] height;
private LevelContainer[] dataContainer;
private short[][][] depth;
private boolean[][][] dataExistence;
private DistanceGenerationMode generationMode;
private VerticalQuality verticalQuality;
public final int regionPosX;
public final int regionPosZ;
public LodRegion(LevelContainer levelContainer, RegionPos regionPos, DistanceGenerationMode generationMode)
public LodRegion(RegionPos regionPos)
{
this.generationMode = generationMode;
this.minDetailLevel = LodUtil.REGION_DETAIL_LEVEL;
this.regionPosX = regionPos.x;
this.regionPosZ = regionPos.z;
this.minDetailLevel = levelContainer.detailLevel;
dataContainer = new LevelContainer[POSSIBLE_LOD];
}
//Array of matrices of arrays
colors = new byte[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];
//Arrays of matrices
height = new short[POSSIBLE_LOD][][];
depth = new short[POSSIBLE_LOD][][];
dataExistence = new boolean[POSSIBLE_LOD][][];
colors[minDetailLevel] = levelContainer.colors;
height[minDetailLevel] = levelContainer.height;
depth[minDetailLevel] = levelContainer.depth;
dataExistence[minDetailLevel] = levelContainer.dataExistence;
//Initialize all the different matrices
for (byte lod = (byte) (minDetailLevel + 1); lod <= LodUtil.REGION_DETAIL_LEVEL; lod++)
// Initialize all the different matrices
for (byte lod = minDetailLevel; lod <= LodUtil.REGION_DETAIL_LEVEL; lod++)
{
int size = (short) Math.pow(2, LodUtil.REGION_DETAIL_LEVEL - lod);
colors[lod] = new byte[size][size][3];
height[lod] = new short[size][size];
depth[lod] = new short[size][size];
dataExistence[lod] = new boolean[size][size];
}
int width;
LevelPos levelPos = new LevelPos();
for (byte tempLod = (byte) (minDetailLevel + 1); tempLod <= LodUtil.REGION_DETAIL_LEVEL; tempLod++)
{
width = 1 << (LodUtil.REGION_DETAIL_LEVEL - tempLod);
for (int x = 0; x < width; x++)
switch (verticalQuality)
{
for (int z = 0; z < width; z++)
{
levelPos.changeParameters(tempLod, x, z);
update(levelPos);
}
default:
case HEIGHTMAP:
dataContainer[lod] = new SingleLevelContainer(lod);
break;
case MULTI_LOD:
dataContainer[lod] = new VerticalLevelContainer(lod);
break;
}
}
}
public LodRegion(byte minDetailLevel, RegionPos regionPos, DistanceGenerationMode generationMode)
public VerticalQuality getLodQualityMode()
{
this.generationMode = generationMode;
this.minDetailLevel = minDetailLevel;
this.regionPosX = regionPos.x;
this.regionPosZ = regionPos.z;
return verticalQuality;
}
//Array of matrices of arrays
colors = new byte[POSSIBLE_LOD][][][];
public DistanceGenerationMode getGenerationMode()
{
return generationMode;
}
//Arrays of matrices
height = new short[POSSIBLE_LOD][][];
depth = new short[POSSIBLE_LOD][][];
dataExistence = new boolean[POSSIBLE_LOD][][];
//Initialize all the different matrices
for (byte lod = minDetailLevel; lod <= LodUtil.REGION_DETAIL_LEVEL; lod++)
{
int size = (short) Math.pow(2, LodUtil.REGION_DETAIL_LEVEL - lod);
colors[lod] = new byte[size][size][3];
height[lod] = new short[size][size];
depth[lod] = new short[size][size];
dataExistence[lod] = new boolean[size][size];
}
public int getMaxVerticalData(byte detailLevel)
{
return dataContainer[detailLevel].getMaxVerticalData();
}
/**
* This method can be used to insert data into the LodRegion
*
* @param levelPos
* @param dataPoint
* @return
* @return if the data was added successfully
*/
public boolean addData(LevelPos levelPos, short[] dataPoint, boolean serverQuality)
public boolean addData(byte detailLevel, int posX, int posZ, int verticalIndex, long data)
{
levelPos.performRegionModule();
if (!doesDataExist(levelPos) || serverQuality)
{
posX = LevelPosUtil.getRegionModule(detailLevel, posX);
posZ = LevelPosUtil.getRegionModule(detailLevel, posZ);
//update the number of node present
if (this.dataExistence[levelPos.detailLevel][levelPos.posX][levelPos.posZ]) numberOfPoints++;
//add the node data
this.height[levelPos.detailLevel][levelPos.posX][levelPos.posZ] = DataPoint.getHeight(dataPoint);
this.depth[levelPos.detailLevel][levelPos.posX][levelPos.posZ] = DataPoint.getDepth(dataPoint);
this.colors[levelPos.detailLevel][levelPos.posX][levelPos.posZ][0] = (byte) (DataPoint.getRed(dataPoint) - 128);
this.colors[levelPos.detailLevel][levelPos.posX][levelPos.posZ][1] = (byte) (DataPoint.getGreen(dataPoint) - 128);
this.colors[levelPos.detailLevel][levelPos.posX][levelPos.posZ][2] = (byte) (DataPoint.getBlue(dataPoint) - 128);
this.dataExistence[levelPos.detailLevel][levelPos.posX][levelPos.posZ] = true;
return true;
} else
// For some reason the dataContainer can contain null entries
if (this.dataContainer[detailLevel] == null)
{
return false;
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;
}
/**
* This method will return the data in the position relative to the level of detail
*
* @param levelPos
* @return the data at the relative pos and level
*/
public short[] getData(LevelPos levelPos)
public long getData(byte detailLevel, int posX, int posZ, int verticalIndex)
{
levelPos = levelPos.getRegionModuleLevelPos();
return new short[]{height[levelPos.detailLevel][levelPos.posX][levelPos.posZ],
depth[levelPos.detailLevel][levelPos.posX][levelPos.posZ],
(short) (colors[levelPos.detailLevel][levelPos.posX][levelPos.posZ][0] + 128),
(short) (colors[levelPos.detailLevel][levelPos.posX][levelPos.posZ][1] + 128),
(short) (colors[levelPos.detailLevel][levelPos.posX][levelPos.posZ][2] + 128)
};
return dataContainer[detailLevel].getData(posX, posZ, verticalIndex);
}
/**
* This method will return the data in the position relative to the level of detail
*
* @return the data at the relative pos and level
*/
public long getSingleData(byte detailLevel, int posX, int posZ)
{
return dataContainer[detailLevel].getSingleData(posX, posZ);
}
public void clear(byte detailLevel, int posX, int posZ)
{
dataContainer[detailLevel].clear(posX, posZ);
}
/**
@@ -168,47 +140,36 @@ public class LodRegion implements Serializable
*
* @return
*/
public void getDataToGenerate(ConcurrentMap<LevelPos, MutableBoolean> dataToGenerate, int playerPosX, int playerPosZ)
public void getDataToGenerate(PosToGenerateContainer posToGenerate, int playerPosX, int playerPosZ)
{
LevelPos levelPos = new LevelPos(LodUtil.REGION_DETAIL_LEVEL, 0, 0);
getDataToGenerate(dataToGenerate, levelPos, playerPosX, playerPosZ);
getDataToGenerate(posToGenerate, LodUtil.REGION_DETAIL_LEVEL, 0, 0, playerPosX, playerPosZ);
}
private void getDataToGenerate(ConcurrentMap<LevelPos, MutableBoolean> dataToGenerate, LevelPos levelPos, int playerPosX, int playerPosZ)
private void getDataToGenerate(PosToGenerateContainer posToGenerate, byte detailLevel, int posX, int posZ, int playerPosX, int playerPosZ)
{
int size = 1 << (LodUtil.REGION_DETAIL_LEVEL - levelPos.detailLevel);
int size = 1 << (LodUtil.REGION_DETAIL_LEVEL - detailLevel);
//here i calculate the the LevelPos is in range
//This is important to avoid any kind of hole in the generation
int minDistance = levelPos.minDistance(playerPosX, playerPosZ, regionPosX, regionPosZ);
//nt minDistance = LevelPosUtil.minDistance(detailLevel, posX + regionPosX * size, posZ + regionPosZ * size, playerPosX, playerPosZ);
int maxDistance = LevelPosUtil.maxDistance(detailLevel, posX, posZ, playerPosX, playerPosZ, regionPosX, regionPosZ);
int posX = levelPos.posX;
int posZ = levelPos.posZ;
byte detailLevel = levelPos.detailLevel;
byte childDetailLevel = (byte) (detailLevel - 1);
int childPosX = posX * 2;
int childPosZ = posZ * 2;
int childSize = 1 << (LodUtil.REGION_DETAIL_LEVEL - childDetailLevel);
//we have reached the target detail level
if (DetailDistanceUtil.getDistanceGenerationInverse(minDistance) > detailLevel)
byte targetDetailLevel = DetailDistanceUtil.getLodGenDetail(DetailDistanceUtil.getGenerationDetailFromDistance(maxDistance)).detailLevel;
if (targetDetailLevel > detailLevel)
{
return;
} else if (DetailDistanceUtil.getDistanceGenerationInverse(minDistance) == detailLevel)
} else if (targetDetailLevel == detailLevel)
{
if (!doesDataExist(levelPos))
if (!doesDataExist(detailLevel, posX, posZ))
{
levelPos.changeParameters(detailLevel, posX + regionPosX * size, posZ + regionPosZ * size);
if (dataToGenerate.containsKey(levelPos))
{
dataToGenerate.get(levelPos).setTrue();
} else
{
dataToGenerate.put(levelPos.clone(), new MutableBoolean(true));
}
posToGenerate.addPosToGenerate(detailLevel, posX + regionPosX * size, posZ + regionPosZ * size);
}
} else
{
@@ -221,19 +182,11 @@ public class LodRegion implements Serializable
{
for (int z = 0; z <= 1; z++)
{
levelPos.changeParameters((byte) (detailLevel - 1), childPosX + x, childPosZ + z);
if (!doesDataExist(levelPos))
if (!doesDataExist(childDetailLevel, childPosX + x, childPosZ + z))
{
num++;
levelPos.changeParameters((byte) (detailLevel - 1), childPosX + x + regionPosX * childSize, childPosZ + z + regionPosZ * childSize);
if (dataToGenerate.containsKey(levelPos))
{
dataToGenerate.get(levelPos).setTrue();
} else
{
dataToGenerate.put(levelPos.clone(), new MutableBoolean(true));
}
posToGenerate.addPosToGenerate(childDetailLevel, childPosX + x + regionPosX * childSize, childPosZ + z + regionPosZ * childSize);
}
}
}
@@ -245,8 +198,7 @@ public class LodRegion implements Serializable
{
for (int z = 0; z <= 1; z++)
{
levelPos.changeParameters((byte) (detailLevel - 1), childPosX + x, childPosZ + z);
getDataToGenerate(dataToGenerate, levelPos, playerPosX, playerPosZ);
getDataToGenerate(posToGenerate, childDetailLevel, childPosX + x, childPosZ + z, playerPosX, playerPosZ);
}
}
}
@@ -255,21 +207,12 @@ public class LodRegion implements Serializable
{
if (DetailDistanceUtil.getLodGenDetail(childDetailLevel).detailLevel <= (childDetailLevel))
{
levelPos.changeParameters(detailLevel, posX, posZ);
levelPos.convert(childDetailLevel);
if (!doesDataExist(levelPos))
if (!doesDataExist(childDetailLevel, childPosX, childPosZ))
{
levelPos.changeParameters(levelPos.detailLevel, levelPos.posX + regionPosX * childSize, levelPos.posZ + regionPosZ * childSize);
if (dataToGenerate.containsKey(levelPos))
{
dataToGenerate.get(levelPos).setTrue();
} else
{
dataToGenerate.put(levelPos.clone(), new MutableBoolean(true));
}
posToGenerate.addPosToGenerate(childDetailLevel, childPosX + regionPosX * childSize, childPosZ + regionPosZ * childSize);
} else
{
getDataToGenerate(dataToGenerate, levelPos, playerPosX, playerPosZ);
getDataToGenerate(posToGenerate, childDetailLevel, childPosX, childPosZ, playerPosX, playerPosZ);
}
}
}
@@ -280,219 +223,180 @@ public class LodRegion implements Serializable
/**
* @return
*/
public void getDataToRender(ConcurrentMap<LevelPos, MutableBoolean> dataToRender, int playerPosX, int playerPosZ)
public void getDataToRender(PosToRenderContainer posToRender, int playerPosX, int playerPosZ, boolean requireCorrectDetailLevel)
{
LevelPos levelPos = new LevelPos(LodUtil.REGION_DETAIL_LEVEL, 0, 0);
getDataToRender(dataToRender, levelPos, playerPosX, playerPosZ);
getDataToRender(posToRender, LodUtil.REGION_DETAIL_LEVEL, 0, 0, playerPosX, playerPosZ, requireCorrectDetailLevel);
}
/**
* @return
*/
private void getDataToRender(ConcurrentMap<LevelPos, MutableBoolean> dataToRender, LevelPos levelPos, int playerPosX, int playerPosZ)
private void getDataToRender(PosToRenderContainer posToRender, byte detailLevel, int posX, int posZ, int playerPosX, int playerPosZ, boolean requireCorrectDetailLevel)
{
int size = 1 << (LodUtil.REGION_DETAIL_LEVEL - levelPos.detailLevel);
int posX = levelPos.posX;
int posZ = levelPos.posZ;
byte detailLevel = levelPos.detailLevel;
int size = 1 << (LodUtil.REGION_DETAIL_LEVEL - detailLevel);
//here i calculate the the LevelPos is in range
//This is important to avoid any kind of hole in the rendering
int maxDistance = levelPos.maxDistance(playerPosX, playerPosZ, regionPosX, regionPosZ);
byte supposedLevel = DetailDistanceUtil.getLodDrawDetail(DetailDistanceUtil.getDistanceRenderingInverse(maxDistance));
if (supposedLevel > detailLevel)
return;
else if (supposedLevel == detailLevel)
byte supposedLevel;
int maxDistance;
boolean stopNow = false;
int minDistance;
int childLevel;
switch (LodConfig.CLIENT.graphics.detailDropOff.get())
{
if (dataToRender.containsKey(levelPos))
{
levelPos.changeParameters(detailLevel, posX + regionPosX * size, posZ + regionPosZ * size);
try
default:
case BY_CHUNK:
maxDistance = LevelPosUtil.maxDistance(detailLevel, posX, posZ, playerPosX, playerPosZ, regionPosX, regionPosZ);
supposedLevel = 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;
case BY_REGION_FANCY:
supposedLevel = minDetailLevel;
break;
case BY_REGION_FAST:
int playerRegionX = LevelPosUtil.getRegion(LodUtil.BLOCK_DETAIL_LEVEL, playerPosX);
int playerRegionZ = LevelPosUtil.getRegion(LodUtil.BLOCK_DETAIL_LEVEL, playerPosZ);
if (playerRegionX == regionPosX && playerRegionZ == regionPosZ)
{
dataToRender.get(levelPos).setTrue();
}catch (Exception e){
/*TODO Fix this exception*/
// This seems to happen more often when using an elytra in an amplified world
// maybe it has something to do with the dimensions moving?
ClientProxy.LOGGER.error("getDataToRender had a error at " + levelPos.getRegionPos() + ". Exception: " + e.getMessage());
dataToRender.put(new LevelPos(detailLevel, posX + regionPosX * size, posZ + regionPosZ * size), new MutableBoolean(true));
maxDistance = LevelPosUtil.maxDistance(detailLevel, posX, posZ, playerPosX, playerPosZ, regionPosX, regionPosZ);
supposedLevel = 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;
} else
{
maxDistance = LevelPosUtil.maxDistance(LodUtil.REGION_DETAIL_LEVEL, regionPosX, regionPosZ, playerRegionX * 512 + 256, playerRegionZ * 512 + 256);
supposedLevel = DetailDistanceUtil.getLodDrawDetail(DetailDistanceUtil.getDrawDetailFromDistance(maxDistance));
}
} else
{
dataToRender.put(new LevelPos(detailLevel, posX + regionPosX * size, posZ + regionPosZ * size), new MutableBoolean(true));
}
break;
}
if (stopNow)
{
posToRender.addPosToRender(detailLevel,
posX + regionPosX * size,
posZ + regionPosZ * size);
} else if (supposedLevel > detailLevel)
{
return;
} else if (supposedLevel == detailLevel)
{
posToRender.addPosToRender(detailLevel,
posX + regionPosX * size,
posZ + regionPosZ * size);
} else //case where (detailLevel > supposedLevel)
{
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++)
{
levelPos.changeParameters((byte) (detailLevel - 1), childPosX + x, childPosZ + z);
if (doesDataExist(levelPos)) childrenCount++;
if (doesDataExist(childDetailLevel, childPosX + x, childPosZ + z))
{
if (!requireCorrectDetailLevel)
{
childrenCount++;
} else
{
getDataToRender(posToRender, childDetailLevel, childPosX + x, childPosZ + z, playerPosX, playerPosZ, requireCorrectDetailLevel);
}
}
}
}
//If all the four children exist we go deeper
if (childrenCount == 4)
{
for (int x = 0; x <= 1; x++)
{
for (int z = 0; z <= 1; z++)
{
levelPos.changeParameters((byte) (detailLevel - 1), childPosX + x, childPosZ + z);
getDataToRender(dataToRender, levelPos, playerPosX, playerPosZ);
}
}
} else
{
levelPos.changeParameters(detailLevel, posX + regionPosX * size, posZ + regionPosZ * size);
if (dataToRender.containsKey(levelPos))
if (!requireCorrectDetailLevel)
{
if (childrenCount == 4)
{
if (dataToRender.get(levelPos) == null)
dataToRender.replace(levelPos, new MutableBoolean());
dataToRender.get(levelPos).setTrue();
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
{
dataToRender.put(new LevelPos(detailLevel, posX + regionPosX * size, posZ + regionPosZ * size), new MutableBoolean(true));
posToRender.addPosToRender(detailLevel,
posX + regionPosX * size,
posZ + regionPosZ * size);
}
}
}
}
/**
* @param levelPos
*
*/
public void updateArea(LevelPos levelPos)
public void updateArea(byte detailLevel, int posX, int posZ)
{
int width;
int startX;
int startZ;
byte detailLevel = levelPos.detailLevel;
int posX = levelPos.posX;
int posZ = levelPos.posZ;
for (byte bottom = (byte) (minDetailLevel + 1); bottom <= detailLevel; bottom++)
{
levelPos.convert(bottom);
startX = levelPos.posX;
startZ = levelPos.posZ;
startX = LevelPosUtil.convert(detailLevel, posX, bottom);
startZ = LevelPosUtil.convert(detailLevel, posZ, bottom);
width = 1 << (detailLevel - bottom);
for (int x = 0; x < width; x++)
{
for (int z = 0; z < width; z++)
{
levelPos.changeParameters(bottom, startX + x, startZ + z);
update(levelPos);
update(bottom, startX + x, startZ + z);
}
}
levelPos.changeParameters(detailLevel, posX, posZ);
}
for (byte tempLod = (byte) (detailLevel + 1); tempLod <= LodUtil.REGION_DETAIL_LEVEL; tempLod++)
for (byte up = (byte) (detailLevel + 1); up <= LodUtil.REGION_DETAIL_LEVEL; up++)
{
levelPos.convert(tempLod);
update(levelPos);
update(up,
LevelPosUtil.convert(detailLevel, posX, up),
LevelPosUtil.convert(detailLevel, posZ, up));
}
}
/**
* @param levelPos
*
*/
private void update(LevelPos levelPos)
private void update(byte detailLevel, int posX, int posZ)
{
levelPos.performRegionModule();
int numberOfChildren = 0;
int numberOfVoidChildren = 0;
int tempRed = 0;
int tempGreen = 0;
int tempBlue = 0;
int tempHeight = 0;
int tempDepth = 0;
int newPosX;
int newPosZ;
byte newDetailLevel;
int detailLevel = levelPos.detailLevel;
int posX = levelPos.posX;
int posZ = levelPos.posZ;
for (int x = 0; x <= 1; x++)
{
for (int z = 0; z <= 1; z++)
{
newPosX = 2 * posX + x;
newPosZ = 2 * posZ + z;
newDetailLevel = (byte) (detailLevel - 1);
levelPos.changeParameters(newDetailLevel, newPosX, newPosZ);
if (doesDataExist(levelPos))
{
if (height[newDetailLevel][newPosX][newPosZ] != LodBuilder.DEFAULT_HEIGHT
&& depth[newDetailLevel][newPosX][newPosZ] != LodBuilder.DEFAULT_DEPTH)
{
numberOfChildren++;
tempRed += colors[newDetailLevel][newPosX][newPosZ][0];
tempGreen += colors[newDetailLevel][newPosX][newPosZ][1];
tempBlue += colors[newDetailLevel][newPosX][newPosZ][2];
tempHeight += height[newDetailLevel][newPosX][newPosZ];
tempDepth += depth[newDetailLevel][newPosX][newPosZ];
} else
{
// void children have the default height (most likely -1)
// and represent a LOD with no blocks in it
numberOfVoidChildren++;
}
}
}
}
if (numberOfChildren > 0)
{
colors[detailLevel][posX][posZ][0] = (byte) (tempRed / numberOfChildren);
colors[detailLevel][posX][posZ][1] = (byte) (tempGreen / numberOfChildren);
colors[detailLevel][posX][posZ][2] = (byte) (tempBlue / numberOfChildren);
height[detailLevel][posX][posZ] = (short) (tempHeight / numberOfChildren);
depth[detailLevel][posX][posZ] = (short) (tempDepth / numberOfChildren);
dataExistence[detailLevel][posX][posZ] = true;
} else if (numberOfVoidChildren > 0)
{
colors[detailLevel][posX][posZ][0] = (byte) 0;
colors[detailLevel][posX][posZ][1] = (byte) 0;
colors[detailLevel][posX][posZ][2] = (byte) 0;
height[detailLevel][posX][posZ] = LodBuilder.DEFAULT_HEIGHT;
depth[detailLevel][posX][posZ] = LodBuilder.DEFAULT_DEPTH;
dataExistence[detailLevel][posX][posZ] = true;
}
dataContainer[detailLevel].updateData(dataContainer[detailLevel - 1], posX, posZ);
}
/**
* @param levelPos
* @return
*/
public boolean doesDataExist(LevelPos levelPos)
public boolean doesDataExist(byte detailLevel, int posX, int posZ)
{
try
{
levelPos = levelPos.getRegionModuleLevelPos();
return dataExistence[levelPos.detailLevel][levelPos.posX][levelPos.posZ];
} catch (NullPointerException e)
{
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);
}
/**
* @return
*/
public DistanceGenerationMode getGenerationMode()
public byte getGenerationMode(byte detailLevel, int posX, int posZ)
{
return generationMode;
if (dataContainer[detailLevel].doesItExist(posX, posZ))
{
//We take the bottom information always
return DataPointUtil.getGenerationMode(dataContainer[detailLevel].getSingleData(posX, posZ));
} else
{
return DistanceGenerationMode.NONE.complexity;
}
}
public byte getMinDetailLevel()
@@ -512,7 +416,7 @@ public class LodRegion implements Serializable
{
throw new IllegalArgumentException("getLevel asked for a level that does not exist: minimum " + minDetailLevel + " level requested " + detailLevel);
}
return new LevelContainer(detailLevel, colors[detailLevel], height[detailLevel], depth[detailLevel], dataExistence[detailLevel]);
return dataContainer[detailLevel];
}
/**
@@ -520,15 +424,12 @@ public class LodRegion implements Serializable
*/
public void addLevel(LevelContainer levelContainer)
{
if (levelContainer.detailLevel < minDetailLevel - 1)
if (levelContainer.getDetailLevel() < minDetailLevel - 1)
{
throw new IllegalArgumentException("addLevel requires a level that is at least the minimum level of the region -1 ");
}
if (levelContainer.detailLevel == minDetailLevel - 1) minDetailLevel = levelContainer.detailLevel;
colors[levelContainer.detailLevel] = levelContainer.colors;
height[levelContainer.detailLevel] = levelContainer.height;
depth[levelContainer.detailLevel] = levelContainer.depth;
dataExistence[levelContainer.detailLevel] = levelContainer.dataExistence;
if (levelContainer.getDetailLevel() == minDetailLevel - 1) minDetailLevel = levelContainer.getDetailLevel();
dataContainer[levelContainer.getDetailLevel()] = levelContainer;
}
@@ -541,10 +442,7 @@ public class LodRegion implements Serializable
{
for (byte tempLod = 0; tempLod < detailLevel; tempLod++)
{
colors[tempLod] = new byte[0][0][0];
height[tempLod] = new short[0][0];
depth[tempLod] = new short[0][0];
dataExistence[tempLod] = new boolean[0][0];
dataContainer[tempLod] = null;
}
minDetailLevel = detailLevel;
}
@@ -557,13 +455,13 @@ public class LodRegion implements Serializable
{
if (detailLevel < minDetailLevel)
{
for (byte tempLod = detailLevel; tempLod < minDetailLevel; tempLod++)
for (byte tempLod = (byte) (minDetailLevel - 1); tempLod >= detailLevel; tempLod--)
{
int size = (short) Math.pow(2, LodUtil.REGION_DETAIL_LEVEL - tempLod);
colors[tempLod] = new byte[size][size][3];
height[tempLod] = new short[size][size];
depth[tempLod] = new short[size][size];
dataExistence[tempLod] = new boolean[size][size];
if (dataContainer[tempLod + 1] == null)
{
dataContainer[tempLod + 1] = new SingleLevelContainer((byte) (tempLod + 1));
}
dataContainer[tempLod] = dataContainer[tempLod + 1].expand();
}
minDetailLevel = detailLevel;
}
@@ -579,8 +477,10 @@ public class LodRegion implements Serializable
/**
* return needed memory in byte
*
* @param template
*/
public int getMinMemoryNeeded()
public int getMinMemoryNeeded(LodTemplate template)
{
int count = 0;
for (byte tempLod = LodUtil.REGION_DETAIL_LEVEL; tempLod > minDetailLevel; tempLod--)
@@ -588,7 +488,7 @@ public class LodRegion implements Serializable
//i'm doing a upper limit of the minimum
//Color should be just 3 byte but i'm gonna calculate as 12 byte
//Height and depth should be just 4 byte but i'm gonna calculate as 8 byte
count += Math.pow(2, LodUtil.REGION_DETAIL_LEVEL - tempLod) * (8 + 3 + 2 + 2 + 1);
count += dataContainer[tempLod].getMaxMemoryUse() * template.getBufferMemoryForSingleLod(dataContainer[tempLod].getMaxVerticalData());
//count += Math.pow(2, LodUtil.REGION_DETAIL_LEVEL - tempLod) * (24 + 8 + 8 + 8 + 8);
}
return count;
@@ -599,4 +499,12 @@ public class LodRegion implements Serializable
{
return getLevel(LodUtil.REGION_DETAIL_LEVEL).toString();
}
public int getNumberOfLods()
{
int count = 0;
for (LevelContainer container : dataContainer)
count += container.getMaxNumberOfLods();
return count;
}
}
@@ -1,6 +1,6 @@
package com.seibel.lod.objects;
import org.lwjgl.system.CallbackI;
import com.seibel.lod.util.LevelPosUtil;
public class PosToGenerateContainer
{
@@ -12,90 +12,119 @@ public class PosToGenerateContainer
private int maxFarSize;
private int nearSize;
private int farSize;
private int[][] posToGenerate;
private int[][] nearPosToGenerate;
private int[][] farPosToGenerate;
public PosToGenerateContainer(byte farMinDetail, int maxDataToGenerate, int maxFarDataToGenerate, int playerPosX, int playerPosZ)
{
this.playerPosX = playerPosX;
this.playerPosZ = playerPosZ;
this.farMinDetail = farMinDetail;
maxNearSize = maxDataToGenerate;
maxNearSize = maxDataToGenerate-maxFarDataToGenerate;
maxFarSize = maxFarDataToGenerate;
maxSize = maxDataToGenerate;
nearSize = 0;
farSize = 0;
posToGenerate = new int[maxDataToGenerate][4];
}
public void addPosToGenerate(int[] levelPos)
{
addPosToGenerate(LevelPosUtil.getDetailLevel(levelPos), LevelPosUtil.getPosX(levelPos), LevelPosUtil.getPosZ(levelPos));
nearPosToGenerate = new int[maxDataToGenerate][4];
farPosToGenerate = new int[maxDataToGenerate][4];
}
public void addPosToGenerate(byte detailLevel, int posX, int posZ)
{
int distance = LevelPosUtil.minDistance(detailLevel, posX, posZ, playerPosX, playerPosZ);
int index;
int[] tempPos = new int[]{
detailLevel,
posX,
posZ,
distance};
if (detailLevel >= farMinDetail)
{//We are introducing a position in the far array
if (farSize < maxFarSize)
{
farSize++;
if (nearSize == maxNearSize)
{
nearSize--;
}
maxNearSize--;
}
index = posToGenerate.length - farSize;
while (index < posToGenerate.length - 1 && LevelPosUtil.compareLevelAndDistance(tempPos, posToGenerate[index + 1]) <= 0)
{
posToGenerate[index] = posToGenerate[index + 1];
index++;
}
if (index <= posToGenerate.length - 1)
posToGenerate[index] = tempPos;
} else
{//We are introducing a position in the near array
if (nearSize < maxNearSize)
nearSize++;
index = nearSize - 1;
while (index > 0 && LevelPosUtil.compareDistance(tempPos, posToGenerate[index - 1]) <= 0)
if(farSize < farPosToGenerate.length)
farSize++;
index = farSize;
//while (index > 0 && LevelPosUtil.compareDistance(distance, farPosToGenerate[index - 1][3]) <= 0)
while (index > 0 && LevelPosUtil.compareDistance(distance, farPosToGenerate[index - 1][3]) <= 0)
{
posToGenerate[index] = posToGenerate[index - 1];
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 >= 0)
posToGenerate[index] = tempPos;
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 farSize + nearSize;
return nearSize+farSize;
}
public int[] getNthPos(int n)
public int getNumberOfNearPos()
{
int index;
if (n > farSize * 2)
index = n - farSize;
else if (n % 2 == 0)
index = n / 2;
return nearSize;
}
public int getNumberOfFarPos()
{
return farSize;
}
public int getNthDetail(int n, boolean near)
{
if (near)
return nearPosToGenerate[n][0];
else
index = posToGenerate.length - n / 2 - 1;
return posToGenerate[index];
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];
}
public String toString()
{
{/*
StringBuilder builder = new StringBuilder();
builder.append("Number of pos to generate ");
builder.append(farSize + nearSize);
@@ -111,7 +140,7 @@ public class PosToGenerateContainer
builder.append('\n');
for (int i = 0; i < nearSize; i++)
{
builder.append(posToGenerate[i][0]);
builder.append(posToGenerate[i][0]-1);
builder.append(" ");
builder.append(posToGenerate[i][1]);
builder.append(" ");
@@ -125,7 +154,7 @@ public class PosToGenerateContainer
builder.append('\n');
for (int i = maxSize - 1; i >= maxSize - farSize; i--)
{
builder.append(posToGenerate[i][0]);
builder.append(posToGenerate[i][0]-1);
builder.append(" ");
builder.append(posToGenerate[i][1]);
builder.append(" ");
@@ -135,6 +164,7 @@ public class PosToGenerateContainer
builder.append('\n');
}
builder.append('\n');
return builder.toString();
return builder.toString();*/
return " ";
}
}
@@ -1,40 +1,88 @@
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
{
private byte minDetail;
public byte minDetail;
private int size;
private int regionPosX;
private int regionPosZ;
private int numberOfPosToRender;
private int[][] posToRender;
private int[] posToRender;
/*TODO this population matrix could be converted to boolean to improve memory use*/
private byte[][] population;
public PosToRenderContainer(byte minDetail)
public PosToRenderContainer(byte minDetail, int regionPosX, int regionPosZ)
{
this.minDetail = minDetail;
this.numberOfPosToRender = 0;
posToRender = new int[1][4];
int size = 1 << (LodUtil.REGION_DETAIL_LEVEL - minDetail);
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(int[] levelPos)
public void addPosToRender(byte detailLevel, int posX, int posZ)
{
if(numberOfPosToRender >= posToRender.length)
posToRender = Arrays.copyOf(posToRender, posToRender.length*2);
posToRender[numberOfPosToRender] = levelPos;
// 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++;
int[] newLevelPos = LevelPosUtil.getRegionModule(LevelPosUtil.convert(levelPos, minDetail));
population[LevelPosUtil.getPosZ(newLevelPos)][LevelPosUtil.getPosZ(newLevelPos)] = (byte) (LevelPosUtil.getDetailLevel(levelPos) + 1);
population[LevelPosUtil.getRegionModule(minDetail, LevelPosUtil.convert(detailLevel,posX,minDetail))]
[LevelPosUtil.getRegionModule(minDetail, LevelPosUtil.convert(detailLevel,posZ,minDetail))] = (byte) (detailLevel + 1);
}
public boolean contains(int[] levelPos)
public boolean contains(byte detailLevel, int posX, int posZ)
{
int[] newLevelPos = LevelPosUtil.convert(LevelPosUtil.getRegionModule(levelPos), minDetail);
return (population[LevelPosUtil.getPosZ(newLevelPos)][LevelPosUtil.getPosZ(newLevelPos)] == (LevelPosUtil.getDetailLevel(levelPos) + 1));
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()
@@ -42,22 +90,34 @@ public class PosToRenderContainer
return numberOfPosToRender;
}
public int[] getNthPos(int n)
public byte getNthDetailLevel(int n)
{
return posToRender[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][0]);
builder.append(posToRender[i*3 + 0]);
builder.append(" ");
builder.append(posToRender[i][1]);
builder.append(posToRender[i*3 + 1]);
builder.append(" ");
builder.append(posToRender[i][2]);
builder.append(posToRender[i*3 + 2]);
builder.append('\n');
}
builder.append('\n');
@@ -0,0 +1,193 @@
package com.seibel.lod.objects;
import com.seibel.lod.util.*;
import javax.xml.crypto.Data;
import java.util.Arrays;
public class SingleLevelContainer implements LevelContainer
{
public final byte detailLevel;
public final int size;
public final long[][] dataContainer;
public SingleLevelContainer(byte detailLevel)
{
this.detailLevel = detailLevel;
size = 1 << (LodUtil.REGION_DETAIL_LEVEL - detailLevel);
dataContainer = new long[size][size];
}
public void clear(int posX, int posZ)
{
posX = LevelPosUtil.getRegionModule(detailLevel, posX);
posZ = LevelPosUtil.getRegionModule(detailLevel, posZ);
dataContainer[posX][posZ] = DataPointUtil.EMPTY_DATA;
}
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;
}
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;
}
public long getData(int posX, int posZ, int index)
{
posX = LevelPosUtil.getRegionModule(detailLevel, posX);
posZ = LevelPosUtil.getRegionModule(detailLevel, posZ);
//Improve this using a thread map to long[]
return dataContainer[posX][posZ];
}
public long getSingleData(int posX, int posZ)
{
posX = LevelPosUtil.getRegionModule(detailLevel, posX);
posZ = LevelPosUtil.getRegionModule(detailLevel, posZ);
//Improve this using a thread map to long[]
return dataContainer[posX][posZ];
}
public byte getDetailLevel()
{
return detailLevel;
}
public LevelContainer expand()
{
return new SingleLevelContainer((byte) (getDetailLevel() - 1));
}
public SingleLevelContainer(byte[] inputData)
{
int tempIndex;
int index = 0;
long newData;
detailLevel = inputData[index];
index++;
size = (int) Math.pow(2, LodUtil.REGION_DETAIL_LEVEL - detailLevel);
this.dataContainer = new long[size][size];
for (int x = 0; x < size; x++)
{
for (int z = 0; z < size; 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;
}
}
}
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);
}
public int getMaxVerticalData()
{
return 1;
}
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));
}
public byte[] toDataString()
{
int index = 0;
int tempIndex;
byte[] tempData = ThreadMapUtil.getSaveContainer();
if (tempData == null || tempData.length != (1 + (size * size * 8)))
tempData = new byte[1 + (size * size * 8)];
else
Arrays.fill(tempData, (byte) 0);
tempData[index] = detailLevel;
index++;
for (int x = 0; x < size; x++)
{
for (int z = 0; z < size; 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();
}
public int getMaxNumberOfLods()
{
return size * size * getMaxVerticalData();
}
public int getMaxMemoryUse()
{
return getMaxNumberOfLods() * 2; //2 byte
}
}
@@ -0,0 +1,253 @@
package com.seibel.lod.objects;
import com.seibel.lod.util.*;
import java.util.Arrays;
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;
}
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;
}
}
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;
}
public boolean addSingleData(long data, int posX, int posZ){
return addData(data, posX, posZ, 0);
}
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];
}
public long getSingleData(int posX, int posZ){
return getData(posX,posZ,0);
}
public int getMaxVerticalData(){
return maxVerticalData;
}
public int getSize(){
return size;
}
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;
}
}
}
public LevelContainer expand(){
return new VerticalLevelContainer((byte) (getDetailLevel() - 1));
}
public void updateData(LevelContainer lowerLevelContainer, int posX, int posZ)
{
//We reset the array
long[] dataToMerge = ThreadMapUtil.getVerticalUpdateArray()[detailLevel];
if(dataToMerge == null || dataToMerge.length != 4*lowerLevelContainer.getMaxVerticalData())
dataToMerge = new long[4 * lowerLevelContainer.getMaxVerticalData()];
else
Arrays.fill(dataToMerge, DataPointUtil.EMPTY_DATA);
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);
}
}
public byte[] toDataString()
{
int index = 0;
int counter = -1;
byte last = -1;
int x = size * size * maxVerticalData;
int tempIndex;
byte[] tempData = ThreadMapUtil.getSaveContainer();
long current;
if(tempData == null || tempData.length != (2 + (x * 8)))
tempData = new byte[2 + (x * 8)];
else
Arrays.fill(tempData, (byte) 0);
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 " ";
}
public int getMaxNumberOfLods(){
return size*size*getMaxVerticalData();
}
public int getMaxMemoryUse(){
return getMaxNumberOfLods() * 2; //2 byte
}
}
@@ -21,27 +21,26 @@ 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.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.DistanceCalculatorType;
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.ShadingMode;
import com.seibel.lod.objects.LodDimension;
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.minecraft.world.DimensionType;
import net.minecraftforge.client.event.InputEvent;
import net.minecraftforge.event.TickEvent;
import net.minecraftforge.event.world.BlockEvent;
@@ -51,15 +50,18 @@ 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 8-24-2021
* @version 9-23-2021
*/
public class ClientProxy
{
public static final Logger LOGGER = LogManager.getLogger("LOD");
private boolean firstTimeSetupComplete = false;
public static boolean drawLods = true;
private static LodWorld lodWorld = new LodWorld();
private static LodBuilder lodBuilder = new LodBuilder();
private static LodBufferBuilder lodBufferBuilder = new LodBufferBuilder();
@@ -68,7 +70,7 @@ public class ClientProxy
private boolean configOverrideReminderPrinted = false;
Minecraft mc = Minecraft.getInstance();
private MinecraftWrapper mc = MinecraftWrapper.INSTANCE;
/**
@@ -85,7 +87,7 @@ public class ClientProxy
* to the LOD view distance
*/
private boolean recalculateWidths = false;
private DimensionType currentDimension = null;
public ClientProxy()
{
@@ -100,84 +102,112 @@ 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)
{
DetailDistanceUtil.updateSettings();
if (mc == null || mc.player == null || !lodWorld.getIsWorldLoaded())
return;
viewDistanceChangedEvent();
LodDimension lodDim = lodWorld.getLodDimension(mc.player.level.dimensionType());
if (lodDim == null)
return;
playerMoveEvent(lodDim);
//System.out.println("memory needed " + lodDim.getMinMemoryNeeded() + " byte");
//System.out.println(lodDim);
lodDim.treeCutter((int) mc.player.getX(), (int) mc.player.getZ());
lodDim.treeGenerator((int) mc.player.getX(), (int) mc.player.getZ());
// comment out when creating a release
//applyConfigOverrides();
applyConfigOverrides();
// clear any out of date objects
mc.clearFrameObjectCache();
// 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, partialTicks, mc.getProfiler());
profiler.pop(); // end LOD
profiler.push("terrain"); // restart "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.options.renderDistance;
previousLodRenderDistance = LodConfig.CLIENT.graphics.lodChunkRenderDistance.get();
try
{
// only run the first time setup once
if (!firstTimeSetupComplete)
{
ThreadMapUtil.clearMaps();
firstFrameSetup();
}
if(mc.getCurrentDimension() != currentDimension)
{
currentDimension = mc.getCurrentDimension();
reset();
}
DetailDistanceUtil.updateSettings();
if (mc == null || mc.getPlayer() == null || !lodWorld.getIsWorldLoaded())
return;
LodDimension lodDim = lodWorld.getLodDimension(mc.getCurrentDimension());
if (lodDim == null)
return;
viewDistanceChangedEvent();
playerMoveEvent(lodDim);
lodDim.treeCutter((int) mc.getPlayer().getX(), (int) mc.getPlayer().getZ());
lodDim.treeGenerator((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");
if(drawLods)
{
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();
}
catch (Exception e)
{
LOGGER.error("client proxy: " + e.getMessage());
e.printStackTrace();
}
}
private void applyConfigOverrides()
{
// remind the developer(s). that config override is active
// remind the developer(s) that the config override is active
if (!configOverrideReminderPrinted)
{
mc.player.sendMessage(new StringTextComponent("Debug settings enabled!"), mc.player.getUUID());
//mc.getPlayer().sendMessage(new StringTextComponent("Debug settings enabled!"), mc.getPlayer().getUUID());
// this was changed just for the buggy pre-release, since the code was already here
mc.getPlayer().sendMessage(new StringTextComponent("Warning: LOD mod 1.5 buggy pre-release"), mc.getPlayer().getUUID());
mc.getPlayer().sendMessage(new StringTextComponent("Hic sunt dracones"), mc.getPlayer().getUUID()); // Here be dragons
configOverrideReminderPrinted = true;
}
//LodConfig.CLIENT.drawLODs.set(true);
//LodConfig.CLIENT.debugMode.set(true);
LodConfig.CLIENT.graphics.maxDrawDetail.set(LodDetail.FULL);
LodConfig.CLIENT.worldGenerator.maxGenerationDetail.set(LodDetail.FULL);
// 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);
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.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.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.buffers.bufferRebuildPlayerMoveTimeout.set(2000); // 2000
// LodConfig.CLIENT.buffers.bufferRebuildChunkChangeTimeout.set(1000); // 1000
// LodConfig.CLIENT.buffers.bufferRebuildLodChangeTimeout.set(5000); // 5000
LodConfig.CLIENT.worldGenerator.distanceGenerationMode.set(DistanceGenerationMode.SURFACE);
LodConfig.CLIENT.graphics.lodChunkRenderDistance.set(256);
LodConfig.CLIENT.worldGenerator.lodDistanceCalculatorType.set(DistanceCalculatorType.LINEAR);
LodConfig.CLIENT.graphics.lodQuality.set(3);
LodConfig.CLIENT.worldGenerator.allowUnstableFeatureGeneration.set(false);
LodConfig.CLIENT.buffers.bufferRebuildPlayerMoveTimeout.set(2000); // 2000
LodConfig.CLIENT.buffers.bufferRebuildChunkChangeTimeout.set(1000); // 1000
LodConfig.CLIENT.buffers.bufferRebuildLodChangeTimeout.set(50); // 5000
LodConfig.CLIENT.debugging.enableDebugKeybinding.set(true);
// LodConfig.CLIENT.debugging.enableDebugKeybindings.set(true);
// LodConfig.CLIENT.debugging.debugMode.set(DebugMode.SHOW_DETAIL);
}
@@ -188,10 +218,10 @@ public class ClientProxy
@SubscribeEvent
public void serverTickEvent(TickEvent.ServerTickEvent event)
{
if (mc == null || mc.player == null || !lodWorld.getIsWorldLoaded())
if (mc == null || mc.getPlayer() == null || !lodWorld.getIsWorldLoaded())
return;
LodDimension lodDim = lodWorld.getLodDimension(mc.player.level.dimensionType());
LodDimension lodDim = lodWorld.getLodDimension(mc.getPlayer().level.dimensionType());
if (lodDim == null)
return;
@@ -214,6 +244,7 @@ public class ClientProxy
@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
@@ -225,7 +256,6 @@ public class ClientProxy
public void worldUnloadEvent(WorldEvent.Unload event)
{
// the player just unloaded a world/dimension
if (mc.getConnection().getLevel() == null)
{
// if this isn't done unfinished tasks may be left in the queue
@@ -241,6 +271,12 @@ public class ClientProxy
// breaking when changing worlds.
renderer.destroyBuffers();
recalculateWidths = true;
// make sure the nulled objects are freed.
// (this prevents a out of memory error when
// changing worlds)
System.gc();
}
}
@@ -261,18 +297,22 @@ public class ClientProxy
@SubscribeEvent
public void onKeyInput(InputEvent.KeyInputEvent event)
{
if(LodConfig.CLIENT.debugging.enableDebugKeybinding.get()
&& event.getKey() == GLFW.GLFW_KEY_F4 && event.getAction() == GLFW.GLFW_PRESS)
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)
{
drawLods = !drawLods;
}
}
//==================//
// frame LOD events //
//==================//
//============//
// LOD events //
//============//
/**
* Re-centers the given LodDimension if it needs to be.
@@ -280,7 +320,7 @@ public class ClientProxy
private void playerMoveEvent(LodDimension lodDim)
{
// make sure the dimension is centered
RegionPos playerRegionPos = new RegionPos(mc.player.blockPosition());
RegionPos playerRegionPos = new RegionPos(mc.getPlayer().blockPosition());
RegionPos worldRegionOffset = new RegionPos(playerRegionPos.x - lodDim.getCenterX(), playerRegionPos.z - lodDim.getCenterZ());
if (worldRegionOffset.x != 0 || worldRegionOffset.z != 0)
{
@@ -309,7 +349,7 @@ public class ClientProxy
// update the dimensions to fit the new width
lodWorld.resizeDimensionRegionWidth(newWidth);
lodBuilder.defaultDimensionWidthInRegions = newWidth;
renderer.setupBuffers(newWidth);
renderer.setupBuffers(lodWorld.getLodDimension(mc.getClientWorld().dimensionType()));
recalculateWidths = false;
//LOGGER.info("new dimension width in regions: " + newWidth + "\t potential: " + newWidth );
@@ -318,6 +358,26 @@ public class ClientProxy
}
/**
* 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();
firstTimeSetupComplete = true;
}
public static void reset()
{
renderer = new LodRenderer(lodBufferBuilder);
LodNodeGenWorker.resetGenerator();
ThreadMapUtil.clearMaps();
}
//================//
// public getters //
@@ -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;
}
}
@@ -17,22 +17,21 @@
*/
package com.seibel.lod.render;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;
import java.util.HashSet;
import java.util.Iterator;
import org.lwjgl.opengl.GL;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL15;
import org.lwjgl.opengl.GL15C;
import org.lwjgl.opengl.NVFogDistance;
import com.mojang.blaze3d.matrix.MatrixStack;
import com.mojang.blaze3d.platform.GlStateManager;
import com.mojang.blaze3d.systems.RenderSystem;
import com.seibel.lod.builders.LodBufferBuilder;
import com.seibel.lod.builders.LodBufferBuilder.VertexBuffersAndOffset;
import com.seibel.lod.config.LodConfig;
import com.seibel.lod.enums.DebugMode;
import com.seibel.lod.enums.DetailDropOff;
import com.seibel.lod.enums.FogDistance;
import com.seibel.lod.enums.FogDrawOverride;
import com.seibel.lod.enums.FogQuality;
@@ -40,26 +39,25 @@ import com.seibel.lod.handlers.ReflectionHandler;
import com.seibel.lod.objects.LodDimension;
import com.seibel.lod.objects.NearFarFogSettings;
import com.seibel.lod.objects.RegionPos;
import com.seibel.lod.objects.LevelPos.LevelPos;
import com.seibel.lod.proxy.ClientProxy;
import com.seibel.lod.proxy.GlProxy;
import com.seibel.lod.util.DetailDistanceUtil;
import com.seibel.lod.util.LevelPosUtil;
import com.seibel.lod.util.LodUtil;
import com.seibel.lod.wrappers.MinecraftWrapper;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.ActiveRenderInfo;
import net.minecraft.client.renderer.FogRenderer;
import net.minecraft.client.renderer.GameRenderer;
import net.minecraft.client.renderer.texture.NativeImage;
import net.minecraft.client.renderer.vertex.DefaultVertexFormats;
import net.minecraft.client.renderer.vertex.VertexBuffer;
import net.minecraft.client.renderer.vertex.VertexFormat;
import net.minecraft.potion.EffectInstance;
import net.minecraft.potion.Effects;
import net.minecraft.profiler.IProfiler;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.ChunkPos;
import net.minecraft.util.math.MathHelper;
import net.minecraft.util.math.vector.Matrix4f;
import net.minecraft.util.math.vector.Vector3d;
import net.minecraft.util.math.vector.Vector3f;
/**
@@ -67,7 +65,7 @@ import net.minecraft.util.math.vector.Vector3f;
* This is where LODs are draw to the world.
*
* @author James Seibel
* @version 8-31-2021
* @version 9-23-2021
*/
public class LodRenderer
{
@@ -89,24 +87,17 @@ public class LodRenderer
* https://stackoverflow.com/questions/50499238/bytebuffer-allocatedirect-and-xmx
*/
public static final int MAX_ALOCATEABLE_DIRECT_MEMORY = 64 * 1024 * 1024;
/**
* Does this computer's GPU support fancy fog?
*/
private static Boolean fancyFogAvailable = null;
/**
* If true the LODs colors will be replaced with
* a checkerboard, this can be used for debugging.
*/
public DebugMode previousDebugMode = DebugMode.OFF;
private Minecraft mc;
private MinecraftWrapper mc;
private GameRenderer gameRender;
private IProfiler profiler;
private int farPlaneBlockDistance;
private ReflectionHandler reflectionHandler;
/**
@@ -119,12 +110,18 @@ public class LodRenderer
*/
private VertexBuffer[][] vbos;
public static final VertexFormat LOD_VERTEX_FORMAT = DefaultVertexFormats.POSITION_COLOR;
private ChunkPos vbosCenter = new ChunkPos(0,0);
/**
* This is used to determine if the LODs should be regenerated
*/
private LevelPos previousPos = new LevelPos((byte) 0, 0, 0);
private int[] previousPos = new int[]{0,0,0};
public NativeImage lightMap = null;
// these variables are used to determine if the buffers should be rebuilt
private long prevDayTime = 0;
private double prevBrightness = 0;
private int prevRenderDistance = 0;
private long prevPlayerPosTime = 0;
private long prevVanillaChunkTime = 0;
@@ -153,10 +150,9 @@ public class LodRenderer
public LodRenderer(LodBufferBuilder newLodNodeBufferBuilder)
{
mc = Minecraft.getInstance();
gameRender = mc.gameRenderer;
mc = MinecraftWrapper.INSTANCE;
gameRender = mc.getGameRenderer();
reflectionHandler = new ReflectionHandler();
lodBufferBuilder = newLodNodeBufferBuilder;
}
@@ -166,9 +162,10 @@ public class LodRenderer
* the async process of generating the Buffers that hold those LODs.
*
* @param lodDim The dimension to draw, if null doesn't replace the current dimension.
* @param mcMatrixStack This matrix stack should come straight from MC's renderChunkLayer (or future equivalent) method
* @param partialTicks how far into the current tick this method was called.
*/
public void drawLODs(LodDimension lodDim, float partialTicks, IProfiler newProfiler)
public void drawLODs(LodDimension lodDim, MatrixStack mcMatrixStack, float partialTicks, IProfiler newProfiler)
{
if (lodDim == null)
{
@@ -184,20 +181,7 @@ public class LodRenderer
profiler = newProfiler;
profiler.push("LOD setup");
// only check the GPU capability's once
if (fancyFogAvailable == null)
{
// 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 fancy fog options will not be available.");
}
}
// TODO move the buffer regeneration logic into its own class (probably called in the client proxy instead)
// starting here...
@@ -215,7 +199,7 @@ public class LodRenderer
if ((partialRegen || fullRegen) && !lodBufferBuilder.generatingBuffers && !lodBufferBuilder.newBuffersAvaliable())
{
// generate the LODs on a separate thread to prevent stuttering or freezing
lodBufferBuilder.generateLodBuffersAsync(this, lodDim, mc.player.blockPosition(), true);
lodBufferBuilder.generateLodBuffersAsync(this, lodDim, mc.getPlayer().blockPosition(), true);
// the regen process has been started,
// it will be done when lodBufferBuilder.newBuffersAvaliable()
@@ -223,31 +207,28 @@ public class LodRenderer
fullRegen = false;
partialRegen = false;
}
// TODO move the buffer regeneration logic into its own class (probably called in the client proxy instead)
// ...ending here
// replace the buffers used to draw and build,
// this is only done when the createLodBufferGenerationThread
// has finished executing on a parallel thread.
if (lodBufferBuilder.newBuffersAvaliable())
{
swapBuffers();
}
//===========================//
// GL settings for rendering //
//===========================//
// set the required open GL settings
if (LodConfig.CLIENT.debugging.debugMode.get() == DebugMode.SHOW_DETAIL_WIREFRAME)
GL11.glPolygonMode(GL11.GL_FRONT_AND_BACK, GL11.GL_LINE);
else
GL11.glPolygonMode(GL11.GL_FRONT_AND_BACK, GL11.GL_FILL);
GL11.glDisable(GL11.GL_TEXTURE_2D);
GL11.glEnable(GL11.GL_CULL_FACE);
GL11.glEnable(GL11.GL_COLOR_MATERIAL);
@@ -259,19 +240,24 @@ public class LodRenderer
// get the default projection matrix so we can
// reset it after drawing the LODs
float[] defaultProjMatrix = new float[16];
GL11.glGetFloatv(GL11.GL_PROJECTION_MATRIX, defaultProjMatrix);
Matrix4f modelViewMatrix = generateModelViewMatrix(partialTicks);
float[] mcProjMatrixRaw = new float[16];
GL11.glGetFloatv(GL11.GL_PROJECTION_MATRIX, mcProjMatrixRaw);
Matrix4f mcProjectionMatrix = new Matrix4f(mcProjMatrixRaw);
// OpenGl outputs their matricies in col,row form instead of row,col
// (or maybe vice versa I have no idea :P)
mcProjectionMatrix.transpose();
Matrix4f modelViewMatrix = offsetTheModelViewMatrix(mcMatrixStack, partialTicks);
// required for setupFog and setupProjectionMatrix
farPlaneBlockDistance = LodConfig.CLIENT.graphics.lodChunkRenderDistance.get() * LodUtil.CHUNK_WIDTH;
setupProjectionMatrix(partialTicks);
setupLighting(lodDim, partialTicks);
setupProjectionMatrix(mcProjectionMatrix, partialTicks);
// commented out until we can add shaders to handle lighting
//setupLighting(lodDim, partialTicks);
NearFarFogSettings fogSettings = determineFogSettings();
// determine the current fog settings so they can be
// reset after drawing the LODs
float defaultFogStartDist = GL11.glGetFloat(GL11.GL_FOG_START);
@@ -283,14 +269,16 @@ public class LodRenderer
//===========//
// rendering //
//===========//
profiler.popPush("LOD draw");
if (vbos != null)
{
Vector3d cameraDir = mc.cameraEntity.getLookAngle().normalize();
cameraDir = mc.options.getCameraType().isMirrored() ? cameraDir.reverse() : cameraDir;
ActiveRenderInfo renderInfo = mc.getGameRenderer().getMainCamera();
Vector3d cameraDir = new Vector3d(renderInfo.getLookVector());
boolean cullingDisabled = LodConfig.CLIENT.graphics.disableDirectionalCulling.get();
// used to determine what type of fog to render
int halfWidth = vbos.length / 2;
int quarterWidth = vbos.length / 4;
@@ -300,7 +288,7 @@ public class LodRenderer
for (int j = 0; j < vbos.length; j++)
{
RegionPos vboPos = new RegionPos(i + lodDim.getCenterX() - lodDim.getWidth() / 2, j + lodDim.getCenterZ() - lodDim.getWidth() / 2);
if (RenderUtil.isRegionInViewFrustum(mc.cameraEntity.blockPosition(), cameraDir, vboPos.blockPos()))
if (cullingDisabled || RenderUtil.isRegionInViewFrustum(renderInfo.getBlockPosition(), cameraDir, vboPos.blockPos()))
{
if ((i > halfWidth - quarterWidth && i < halfWidth + quarterWidth) && (j > halfWidth - quarterWidth && j < halfWidth + quarterWidth))
setupFog(fogSettings.near.distance, fogSettings.near.quality);
@@ -313,7 +301,7 @@ public class LodRenderer
}
}
}
//=========//
// cleanup //
@@ -335,20 +323,18 @@ public class LodRenderer
// reset the projection matrix so anything drawn after
// the LODs will use the correct projection matrix
Matrix4f mvm = new Matrix4f(defaultProjMatrix);
mvm.transpose();
gameRender.resetProjectionMatrix(mvm);
gameRender.resetProjectionMatrix(mcProjectionMatrix);
// clear the depth buffer so anything drawn is drawn
// over the LODs
GL11.glClear(GL11.GL_DEPTH_BUFFER_BIT);
// end of internal LOD profiling
profiler.pop();
}
/**
* This is where the actual drawing happens.
*/
@@ -356,14 +342,14 @@ public class LodRenderer
{
if (vbo == null)
return;
vbo.bind();
GL15C.glBindBuffer(GL15.GL_ARRAY_BUFFER, vbo.id);
// 0L is the starting pointer
LOD_VERTEX_FORMAT.setupBufferState(0L);
vbo.draw(modelViewMatrix, GL11.GL_QUADS);
VertexBuffer.unbind();
GL15C.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
LOD_VERTEX_FORMAT.clearBufferState();
}
@@ -381,12 +367,12 @@ public class LodRenderer
RenderSystem.disableFog();
return;
}
if (fogDistance == FogDistance.NEAR_AND_FAR)
{
throw new IllegalArgumentException("setupFog doesn't accept the NEAR_AND_FAR fog distance.");
}
// determine the fog distance mode to use
int glFogDistanceMode;
if (fogQuality == FogQuality.FANCY)
@@ -399,7 +385,7 @@ public class LodRenderer
// fast fog (frustum distance based fog)
glFogDistanceMode = NVFogDistance.GL_EYE_PLANE_ABSOLUTE_NV;
}
// the multipliers are percentages
// of the regular view distance.
if (fogDistance == FogDistance.FAR)
@@ -408,7 +394,7 @@ public class LodRenderer
// is because we are using fog backwards to how
// it is normally used, with it hiding near objects
// instead of far objects.
if (fogQuality == FogQuality.FANCY)
{
// for more realistic fog when using FAR
@@ -434,16 +420,16 @@ public class LodRenderer
{
if (fogQuality == FogQuality.FANCY)
{
RenderSystem.fogEnd(mc.options.renderDistance * 16 * 1.41f);
RenderSystem.fogStart(mc.options.renderDistance * 16 * 1.6f);
RenderSystem.fogEnd(mc.getRenderDistance() * 16 * 1.41f);
RenderSystem.fogStart(mc.getRenderDistance() * 16 * 1.6f);
}
else if (fogQuality == FogQuality.FAST)
{
RenderSystem.fogEnd(mc.options.renderDistance * 16 * 1.0f);
RenderSystem.fogStart(mc.options.renderDistance * 16 * 1.5f);
RenderSystem.fogEnd(mc.getRenderDistance() * 16 * 1.0f);
RenderSystem.fogStart(mc.getRenderDistance() * 16 * 1.5f);
}
}
GL11.glEnable(GL11.GL_FOG);
RenderSystem.enableFog();
RenderSystem.setupNvFogDistance();
@@ -466,8 +452,8 @@ public class LodRenderer
// disable fog if Minecraft wasn't rendering fog
// but we were
if (!fogSettings.vanillaIsRenderingFog &&
(fogSettings.near.quality != FogQuality.OFF ||
fogSettings.far.quality != FogQuality.OFF))
(fogSettings.near.quality != FogQuality.OFF ||
fogSettings.far.quality != FogQuality.OFF))
{
GL11.glDisable(GL11.GL_FOG);
}
@@ -475,99 +461,93 @@ public class LodRenderer
/**
* Create the model view matrix to move the LODs
* from object space into world space.
* Translate the camera relative to the LodDimension's center,
* this is done since all LOD buffers are created in world space
* instead of object space.
* (since AxisAlignedBoundingBoxes (LODs) use doubles and thus have a higher
* accuracy vs the model view matrix, which only uses floats)
*/
private Matrix4f generateModelViewMatrix(float partialTicks)
private Matrix4f offsetTheModelViewMatrix(MatrixStack mcMatrixStack, float partialTicks)
{
// duplicate the last matrix
mcMatrixStack.pushPose();
// get all relevant camera info
ActiveRenderInfo renderInfo = mc.gameRenderer.getMainCamera();
ActiveRenderInfo renderInfo = mc.getGameRenderer().getMainCamera();
Vector3d projectedView = renderInfo.getPosition();
// generate the model view matrix
MatrixStack matrixStack = new MatrixStack();
matrixStack.pushPose();
// translate and rotate to the current camera location
matrixStack.mulPose(Vector3f.XP.rotationDegrees(renderInfo.getXRot()));
matrixStack.mulPose(Vector3f.YP.rotationDegrees(renderInfo.getYRot() + 180));
matrixStack.translate(-projectedView.x, -projectedView.y, -projectedView.z);
return matrixStack.last().pose();
// translate the camera relative to the regions' center
// (AxisAlignedBoundingBoxes (LODs) use doubles and thus have a higher
// accuracy vs the model view matrix, which only uses floats)
BlockPos bufferPos = vbosCenter.getWorldPosition();
Vector3d eyePos = mc.getPlayer().getEyePosition(partialTicks);
double xDiff = eyePos.x - bufferPos.getX();
double zDiff = eyePos.z - bufferPos.getZ();
mcMatrixStack.translate(-xDiff, -projectedView.y, -zDiff);
// get the modified model view matrix
Matrix4f lodModelViewMatrix = mcMatrixStack.last().pose();
// remove the lod ModelViewMatrix
mcMatrixStack.popPose();
return lodModelViewMatrix;
}
/**
* create a new projection matrix and send it over to the GPU
* <br><br>
* A lot of this code is copied from renderLevel (line 567)
* in the GameRender class. The code copied is anything with
* a matrixStack and is responsible for making sure the LOD
* objects distort correctly relative to the rest of the world.
* Distortions are caused by: standing in a nether portal,
* nausea potion effect, walking bobbing.
*
*
* @param currentProjectionMatrix this is Minecraft's current projection matrix
* @param partialTicks how many ticks into the frame we are
*/
private void setupProjectionMatrix(float partialTicks)
private void setupProjectionMatrix(Matrix4f currentProjectionMatrix, float partialTicks)
{
// Note: if the LOD objects don't distort correctly
// compared to regular minecraft terrain, make sure
// all the transformations in renderWorld are here too
MatrixStack matrixStack = new MatrixStack();
matrixStack.pushPose();
gameRender.bobHurt(matrixStack, partialTicks);
if (this.mc.options.bobView)
{
gameRender.bobView(matrixStack, partialTicks);
}
// potion and nausea effects
float f = MathHelper.lerp(partialTicks, this.mc.player.oPortalTime, this.mc.player.portalTime) * this.mc.options.screenEffectScale * this.mc.options.screenEffectScale;
if (f > 0.0F)
{
int i = this.mc.player.hasEffect(Effects.CONFUSION) ? 7 : 20;
float f1 = 5.0F / (f * f + 5.0F) - f * 0.04F;
f1 = f1 * f1;
Vector3f vector3f = new Vector3f(0.0F, MathHelper.SQRT_OF_TWO / 2.0F, MathHelper.SQRT_OF_TWO / 2.0F);
matrixStack.mulPose(vector3f.rotationDegrees((gameRender.tick + partialTicks) * i));
matrixStack.scale(1.0F / f1, 1.0F, 1.0F);
float f2 = -(gameRender.tick + partialTicks) * i;
matrixStack.mulPose(vector3f.rotationDegrees(f2));
}
// this projection matrix allows us to see past the normal
// world render distance
Matrix4f projectionMatrix =
Matrix4f.perspective(
getFov(partialTicks, true),
(float) this.mc.getWindow().getScreenWidth() / (float) this.mc.getWindow().getScreenHeight(),
// it is possible to see the near clip plane, but
// you have to be flying quickly in spectator mode through ungenerated
// terrain, so I don't think it is much of an issue.
mc.options.renderDistance,
farPlaneBlockDistance * LodUtil.CHUNK_WIDTH * 2);
// add the screen space distortions
projectionMatrix.multiply(matrixStack.last().pose());
gameRender.resetProjectionMatrix(projectionMatrix);
return;
// create the new projection matrix
Matrix4f lodPoj =
Matrix4f.perspective(
getFov(partialTicks, true),
(float) this.mc.getWindow().getScreenWidth() / (float) this.mc.getWindow().getScreenHeight(),
mc.getRenderDistance()/2,
farPlaneBlockDistance * LodUtil.CHUNK_WIDTH * 2 / 4);
// get Minecraft's un-edited projection matrix
// (this is before it is zoomed, distorted, etc.)
Matrix4f defaultMcProj = mc.getGameRenderer().getProjectionMatrix(mc.getGameRenderer().getMainCamera(), partialTicks, true);
// true here means use "use fov setting" (probably)
// this logic strips away the defaultMcProj matrix so we
// can get the distortionMatrix, which represents all
// transformations, zooming, distortions, etc. done
// to Minecraft's Projection matrix
Matrix4f defaultMcProjInv = defaultMcProj.copy();
defaultMcProjInv.invert();
Matrix4f distortionMatrix = defaultMcProjInv.copy();
distortionMatrix.multiply(currentProjectionMatrix);
// edit the lod projection to match Minecraft's
// (so the LODs line up with the real world)
lodPoj.multiply(distortionMatrix);
// send the projection over to the GPU
gameRender.resetProjectionMatrix(lodPoj);
}
/**
* setup the lighting to be used for the LODs
*/
private void setupLighting(LodDimension lodDimension, float partialTicks)
/*private void setupLighting(LodDimension lodDimension, float partialTicks)
{
// Determine if the player has night vision
boolean playerHasNightVision = false;
if (this.mc.player != null)
if (this.mc.getPlayer() != null)
{
Iterator<EffectInstance> iterator = this.mc.player.getActiveEffects().iterator();
Iterator<EffectInstance> iterator = this.mc.getPlayer().getActiveEffects().iterator();
while (iterator.hasNext())
{
EffectInstance instance = iterator.next();
@@ -579,13 +559,15 @@ public class LodRenderer
}
}
float sunBrightness = lodDimension.dimension.hasSkyLight() ? mc.level.getSkyDarken(partialTicks) : 0.2f;
float sunBrightness = lodDimension.dimension.hasSkyLight() ? mc.getSkyDarken(partialTicks) : 0.2f;
sunBrightness = playerHasNightVision ? 1.0f : sunBrightness;
float gammaMultiplyer = (float) mc.options.gamma - 0.5f;
float lightStrength = ((sunBrightness / 2f) - 0.2f) + (gammaMultiplyer * 0.3f);
float gamma = (float) mc.getOptions().gamma - 0.0f;
float dayEffect = (sunBrightness - 0.2f) * 1.25f;
float lightStrength = (gamma * 0.34f - 0.01f) * (1.0f - dayEffect) + dayEffect - 0.20f; //gamma * 0.2980392157f + 0.1647058824f
float blueLightStrength = (gamma * 0.44f + 0.12f) * (1.0f - dayEffect) + dayEffect - 0.20f; //gamma * 0.4235294118f + 0.2784313725f
float[] lightAmbient = {lightStrength, lightStrength, blueLightStrength, 1.0f};
float lightAmbient[] = {lightStrength, lightStrength, lightStrength, 1.0f};
// can be used for debugging
// if (partialTicks < 0.005)
@@ -597,26 +579,14 @@ public class LodRenderer
GL11.glEnable(LOD_GL_LIGHT_NUMBER); // Enable the above lighting
RenderSystem.enableLighting();
}
}*/
/**
* Create all buffers that will be used.
*/
public void setupBuffers(int numbRegionsWide)
public void setupBuffers(LodDimension lodDim)
{
// calculate the max amount of memory needed (in bytes)
int bufferMemory = RenderUtil.getBufferMemoryForRegion();
// if the required memory is greater than the
// MAX_ALOCATEABLE_DIRECT_MEMORY lower the lodChunkRadiusMultiplier
// to fit.
if (bufferMemory > MAX_ALOCATEABLE_DIRECT_MEMORY)
{
ClientProxy.LOGGER.warn("setupBuffers tried to allocate too much memory for the BufferBuilders."
+ " It tried to allocate \"" + bufferMemory + "\" bytes, when \"" + MAX_ALOCATEABLE_DIRECT_MEMORY + "\" is the max.");
}
lodBufferBuilder.setupBuffers(numbRegionsWide, bufferMemory);
lodBufferBuilder.setupBuffers(lodDim);
}
@@ -636,13 +606,18 @@ public class LodRenderer
/**
* Replace the current Vertex Buffers with the newly
* created buffers from the lodBufferBuilder.
* created buffers from the lodBufferBuilder. <br><br>
*
* For some reason this has to be called after the frame has been rendered,
* otherwise visual stuttering/rubber banding may happen. I'm not sure why...
*/
private void swapBuffers()
{
// replace the drawable buffers with
// the newly created buffers from the lodBufferBuilder
vbos = lodBufferBuilder.getVertexBuffers();
VertexBuffersAndOffset result = lodBufferBuilder.getVertexBuffers();
vbos = result.vbos;
vbosCenter = result.drawableCenterChunkPos;
}
/**
@@ -656,7 +631,7 @@ public class LodRenderer
private double getFov(float partialTicks, boolean useFovSetting)
{
return mc.gameRenderer.getFov(mc.gameRenderer.getMainCamera(), partialTicks, useFovSetting);
return mc.getGameRenderer().getFov(mc.getGameRenderer().getMainCamera(), partialTicks, useFovSetting);
}
@@ -668,14 +643,11 @@ public class LodRenderer
NearFarFogSettings fogSettings = new NearFarFogSettings();
FogQuality quality = reflectionHandler.getFogQuality();
FogQuality quality = ReflectionHandler.INSTANCE.getFogQuality();
FogDrawOverride override = LodConfig.CLIENT.graphics.fogDrawOverride.get();
if (quality == FogQuality.OFF)
fogSettings.vanillaIsRenderingFog = false;
else
fogSettings.vanillaIsRenderingFog = true;
fogSettings.vanillaIsRenderingFog = quality != FogQuality.OFF;
// use any fog overrides the user may have set
@@ -700,7 +672,7 @@ public class LodRenderer
// only use fancy fog if the user's GPU can deliver
if (!fancyFogAvailable && quality == FogQuality.FANCY)
if (!GlProxy.getInstance().fancyFogAvailable && quality == FogQuality.FANCY)
{
quality = FogQuality.FAST;
}
@@ -770,65 +742,69 @@ public class LodRenderer
return fogSettings;
}
/**
* Determines if the LODs should have a fullRegen or partialRegen
*/
@SuppressWarnings("unchecked")
private void determineIfLodsShouldRegenerate(LodDimension lodDim)
{
short renderDistance = (short) mc.options.renderDistance;
short chunkRenderDistance = (short) mc.getRenderDistance();
int vanillaRenderedChunksWidth = chunkRenderDistance*2+2;
vanillaRenderedChunks = new boolean[vanillaRenderedChunksWidth][vanillaRenderedChunksWidth];
//=============//
// full regens //
//=============//
// check if the view distance changed
if (ClientProxy.previousLodRenderDistance != LodConfig.CLIENT.graphics.lodChunkRenderDistance.get()
|| mc.options.renderDistance != prevRenderDistance
|| prevFogDistance != LodConfig.CLIENT.graphics.fogDistance.get())
|| mc.getRenderDistance() != prevRenderDistance
|| prevFogDistance != LodConfig.CLIENT.graphics.fogDistance.get())
{
DetailDistanceUtil.updateSettings();
fullRegen = true;
previousPos.changeParameters((byte) 4, mc.player.xChunk, mc.player.zChunk);
previousPos = LevelPosUtil.createLevelPos((byte) 4, mc.getPlayer().xChunk, mc.getPlayer().zChunk);
prevFogDistance = LodConfig.CLIENT.graphics.fogDistance.get();
prevRenderDistance = mc.options.renderDistance;
//should use this when it's ready
vanillaRenderedChunks = new boolean[renderDistance*2+2][renderDistance*2+2];
prevRenderDistance = mc.getRenderDistance();
}
// did the user change the debug setting?
if (LodConfig.CLIENT.debugging.debugMode.get() != previousDebugMode)
{
previousDebugMode = LodConfig.CLIENT.debugging.debugMode.get();
fullRegen = true;
}
long newTime = System.currentTimeMillis();
// check if the player has moved
if (newTime - prevPlayerPosTime > LodConfig.CLIENT.buffers.bufferRebuildPlayerMoveTimeout.get())
if(LodConfig.CLIENT.graphics.detailDropOff.get() == DetailDropOff.BY_CHUNK)
{
if (previousPos.detailLevel == 0
|| mc.player.xChunk != previousPos.posX
|| mc.player.zChunk != previousPos.posZ)
// check if the player has moved
if (newTime - prevPlayerPosTime > LodConfig.CLIENT.buffers.bufferRebuildPlayerMoveTimeout.get())
{
fullRegen = true;
previousPos.changeParameters((byte) 4, mc.player.xChunk, mc.player.zChunk);
//should use this when it's ready
vanillaRenderedChunks = new boolean[renderDistance*2+2][renderDistance*2+2];
if (LevelPosUtil.getDetailLevel(previousPos) == 0
|| mc.getPlayer().xChunk != LevelPosUtil.getPosX(previousPos)
|| mc.getPlayer().zChunk != LevelPosUtil.getPosZ(previousPos))
{
fullRegen = true;
previousPos = LevelPosUtil.createLevelPos((byte) 4, mc.getPlayer().xChunk, mc.getPlayer().zChunk);
}
prevPlayerPosTime = newTime;
}
prevPlayerPosTime = newTime;
}
//================//
// partial regens //
//================//
// check if the vanilla rendered chunks changed
if (newTime - prevVanillaChunkTime > LodConfig.CLIENT.buffers.bufferRebuildChunkChangeTimeout.get())
{
@@ -840,8 +816,8 @@ public class LodRenderer
}
prevVanillaChunkTime = newTime;
}
// check if there is any newly generated terrain to show
if (newTime - prevChunkTime > LodConfig.CLIENT.buffers.bufferRebuildLodChangeTimeout.get())
{
@@ -852,43 +828,54 @@ public class LodRenderer
}
prevChunkTime = newTime;
}
// check if the lighting has changed
if (mc.getClientWorld().getDayTime() - prevDayTime > 1000 || mc.getOptions().gamma != prevBrightness || lightMap == null)
{
fullRegen = true;
lightMap = mc.getCurrentLightMap();
prevBrightness = mc.getOptions().gamma;
prevDayTime = mc.getClientWorld().getDayTime();
}
//==============//
// LOD skipping //
//==============//
// determine which LODs should not be rendered close to the player
HashSet<ChunkPos> chunkPosToSkip = LodUtil.getNearbyLodChunkPosToSkip(lodDim, mc.player.blockPosition());
int chunkX;
int chunkZ;
HashSet<ChunkPos> chunkPosToSkip = LodUtil.getNearbyLodChunkPosToSkip(lodDim, mc.getPlayer().blockPosition());
int xIndex;
int zIndex;
for (ChunkPos pos : chunkPosToSkip)
{
chunkX = pos.x - mc.player.xChunk + renderDistance + 1;
chunkZ = pos.z - mc.player.zChunk + renderDistance + 1;
try
xIndex = (pos.x - mc.getPlayer().xChunk) + (chunkRenderDistance + 1);
zIndex = (pos.z - mc.getPlayer().zChunk) + (chunkRenderDistance + 1);
// sometimes we are given chunks that are outside the render distance,
// This prevents index out of bounds exceptions
if (xIndex >= 0 && zIndex >= 0
&& xIndex < vanillaRenderedChunks.length
&& zIndex < vanillaRenderedChunks.length)
{
if (!vanillaRenderedChunks[chunkX][chunkZ])
if (!vanillaRenderedChunks[xIndex][zIndex])
{
vanillaRenderedChunks[chunkX][chunkZ] = true;
vanillaRenderedChunks[xIndex][zIndex] = true;
vanillaRenderedChunksChanged = true;
lodDim.setToRegen(pos.getRegionX(), pos.getRegionZ());
}
}catch (Exception e){
System.out.println(vanillaRenderedChunks.length);
e.printStackTrace();
}
}
// if the player is high enough, draw all LODs
if(chunkPosToSkip.isEmpty() && mc.player.position().y > 256)
if(chunkPosToSkip.isEmpty() && mc.getPlayer().position().y > 256)
{
vanillaRenderedChunks = new boolean[renderDistance*2+2][renderDistance*2+2];
vanillaRenderedChunks = new boolean[vanillaRenderedChunksWidth][vanillaRenderedChunksWidth];
vanillaRenderedChunksChanged = true;
}
}
}
}
@@ -19,9 +19,11 @@ package com.seibel.lod.render;
import com.seibel.lod.config.LodConfig;
import com.seibel.lod.enums.LodTemplate;
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;
@@ -35,7 +37,7 @@ import net.minecraft.util.math.vector.Vector3d;
*/
public class RenderUtil
{
private static final Minecraft mc = Minecraft.getInstance();
private static final MinecraftWrapper mc = MinecraftWrapper.INSTANCE;
/**
@@ -44,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());
}
@@ -93,24 +91,23 @@ public class RenderUtil
* it is possible (albeit unlikely) to have a buffer indexOutOfBounds exception
* which is caused by the buffer not being big enough.
*/
public static int getBufferMemoryForRegion()
public static int getBufferMemoryForRegion(LodRegion region)
{
// calculate the max amount of buffer memory needed (in bytes)
return LodUtil.REGION_WIDTH_IN_CHUNKS * LodUtil.REGION_WIDTH_IN_CHUNKS *
LodConfig.CLIENT.graphics.lodTemplate.get().getBufferMemoryForSingleLod();
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, int detailLevel)
/*public static int getMaxRadiusMultiplierWithAvaliableMemory(LodTemplate lodTemplate, int detailLevel)
{
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());
}*/
/**
@@ -67,4 +67,48 @@ public class ColorUtil
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,448 @@
package com.seibel.lod.util;
import java.util.Arrays;
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 |
*/
//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 isItVoid(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(isItVoid(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.isItVoid(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 initialise the arrays that are going to be used
short[] projection = ThreadMapUtil.getProjectionShort();
short[] heightAndDepth = ThreadMapUtil.getHeightAndDepth();
long[] singleDataToMerge = ThreadMapUtil.getSingleAddDataToMerge();
long[] dataPoint = ThreadMapUtil.verticalDataArray();
if (projection == null || projection.length != (worldHeight) / 16 + 1)
projection = new short[(worldHeight) / 16 + 1];
else
Arrays.fill(projection, (short) 0);
if (heightAndDepth == null || heightAndDepth.length != (worldHeight + 1) * 2)
heightAndDepth = new short[(worldHeight + 1) * 2];
else
Arrays.fill(heightAndDepth, (short) 0);
if (singleDataToMerge == null || singleDataToMerge.length != size)
singleDataToMerge = new long[size];
else
Arrays.fill(singleDataToMerge, EMPTY_DATA);
if (dataPoint == null || dataPoint.length != worldHeight + 1)
dataPoint = new long[worldHeight + 1];
else
Arrays.fill(dataPoint, EMPTY_DATA);
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 (!isItVoid(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) && !isItVoid(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;
}
}
@@ -2,48 +2,59 @@ package com.seibel.lod.util;
import com.seibel.lod.config.LodConfig;
import com.seibel.lod.enums.DistanceGenerationMode;
import com.seibel.lod.enums.LodDetail;
import com.seibel.lod.objects.RegionPos;
import net.minecraft.client.Minecraft;
import com.seibel.lod.enums.HorizontalResolution;
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.maxGenerationDetail.get().detailLevel;
private static int minDrawDetail = Math.max(LodConfig.CLIENT.graphics.maxDrawDetail.get().detailLevel,LodConfig.CLIENT.worldGenerator.maxGenerationDetail.get().detailLevel);
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 base = 2;
private static double logBase = Math.log(2);
private static LodDetail[] lodDetails = {
LodDetail.FULL,
LodDetail.HALF,
LodDetail.QUAD,
LodDetail.DOUBLE,
LodDetail.SINGLE,
LodDetail.SINGLE,
LodDetail.SINGLE,
LodDetail.SINGLE,
LodDetail.SINGLE,
LodDetail.SINGLE,
LodDetail.SINGLE};
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.maxGenerationDetail.get().detailLevel;
minDrawDetail = Math.max(LodConfig.CLIENT.graphics.maxDrawDetail.get().detailLevel,LodConfig.CLIENT.worldGenerator.maxGenerationDetail.get().detailLevel);
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 * 2;
}
public static int getDistanceRendering(int detail)
public static int baseDistanceFunction(int detail)
{
int initial;
int distance = 0;
int distanceUnit = LodConfig.CLIENT.graphics.horizontalQuality.get().distanceUnit;
if (detail <= minGenDetail)
return minDistance;
if (detail == maxDetail)
@@ -52,30 +63,17 @@ public class DetailDistanceUtil
return maxDistance;
switch (LodConfig.CLIENT.worldGenerator.lodDistanceCalculatorType.get())
{
case LINEAR:
initial = LodConfig.CLIENT.graphics.lodQuality.get() * 128;
return (detail * initial);
case LINEAR:;
return (detail * distanceUnit);
default:
case QUADRATIC:
initial = LodConfig.CLIENT.graphics.lodQuality.get() * 128;
return (int) (Math.pow(base, detail) * initial);
case RENDER_DEPENDANT:
int realRenderDistance = Minecraft.getInstance().options.renderDistance * 16;
int border = 64;
byte detailAtBorder = (byte) 4;
if (detail > detailAtBorder)
{
return (detail * (border - realRenderDistance) / detailAtBorder + realRenderDistance);
} else
{
return ((maxDetail - detail) * (maxDistance - border) / (maxDetail - detailAtBorder) + border);
}
return (int) (Math.pow(base, detail) * distanceUnit);
}
return distance;
}
public static byte baseInverse(int distance, int minDetail)
public static byte baseInverseFunction(int distance, int minDetail)
{
int initial;
int distanceUnit = LodConfig.CLIENT.graphics.horizontalQuality.get().distanceUnit;
byte detail = 0;
if (distance == 0)
detail = (byte) minDetail;
@@ -84,60 +82,34 @@ public class DetailDistanceUtil
switch (LodConfig.CLIENT.worldGenerator.lodDistanceCalculatorType.get())
{
case LINEAR:
initial = LodConfig.CLIENT.graphics.lodQuality.get() * 128;
detail = (byte) Math.floorDiv(distance, initial);
detail = (byte) Math.floorDiv(distance, distanceUnit);
break;
case QUADRATIC:
initial = LodConfig.CLIENT.graphics.lodQuality.get() * 128;
detail = (byte) (Math.log(Math.floorDiv(distance, initial))/logBase);
break;
case RENDER_DEPENDANT:
detail = (byte) 9;
detail = (byte) (Math.log(Math.floorDiv(distance, distanceUnit))/logBase);
break;
}
return (byte) Math.min(detail, LodUtil.REGION_DETAIL_LEVEL);
}
public static byte getDistanceRenderingInverse(int distance)
public static byte getDrawDetailFromDistance(int distance)
{
return baseInverse(distance, minDrawDetail);
return baseInverseFunction(distance, minDrawDetail);
}
public static byte getDistanceGenerationInverse(int distance)
public static byte getGenerationDetailFromDistance(int distance)
{
return baseInverse((int) (distance * genMultiplier), minGenDetail);
return baseInverseFunction((int) (distance * genMultiplier), minGenDetail);
}
public static byte getDistanceTreeCutInverse(int distance)
public static byte getTreeCutDetailFromDistance(int distance)
{
return baseInverse((int) (distance * treeCutMultiplier), minGenDetail);
return baseInverseFunction((int) (distance * treeCutMultiplier), minGenDetail);
}
public static byte getDistanceTreeGenInverse(int distance)
public static byte getTreeGenDetailFromDistance(int distance)
{
return baseInverse((int) (distance * treeGenMultiplier), minGenDetail);
}
public static int getDistanceGeneration(int detail)
{
if (detail == maxDetail)
return maxDistance;
return (int) (getDistanceRendering(detail) * genMultiplier);
}
public static int getDistanceTreeCut(int detail)
{
if (detail == maxDetail)
return maxDistance;
return (int) (getDistanceRendering(detail) * treeCutMultiplier);
}
public static int getDistanceTreeGen(int detail)
{
if (detail == maxDetail)
return maxDistance;
return (int) (getDistanceRendering(detail) * treeGenMultiplier);
return baseInverseFunction((int) (distance * treeGenMultiplier), minGenDetail);
}
public static DistanceGenerationMode getDistanceGenerationMode(int detail)
@@ -147,17 +119,29 @@ public class DetailDistanceUtil
public static byte getLodDrawDetail(int detail)
{
return (byte) Math.max(detail, minDrawDetail);
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 LodDetail getLodGenDetail(int detail)
public static HorizontalResolution getLodGenDetail(int detail)
{
if (detail < minGenDetail)
{
return lodDetails[minGenDetail];
return lodGenDetails[minGenDetail];
} else
{
return lodDetails[detail];
return lodGenDetails[detail];
}
}
@@ -166,24 +150,19 @@ public class DetailDistanceUtil
{
if (detail < minGenDetail)
{
return lodDetails[minGenDetail].detailLevel;
return lodGenDetails[minGenDetail].detailLevel;
} else if (detail == maxDetail)
{
return LodUtil.REGION_DETAIL_LEVEL;
} else
{
return lodDetails[detail].detailLevel;
return lodGenDetails[detail].detailLevel;
}
}
public static boolean regionInView(int playerPosX, int playerPosY, int playerPosZ, int xRot, int yRot, int fov, RegionPos regionPos)
public static int getMaxVerticalData(int detail)
{
//System.out.println(Math.floorMod((int) mc.player.xRot,360) + " " + Math.floorMod((int) mc.player.yRot,360) + " " + mc.options.fov);
//System.out.println(mc.player.xRotO + " " + mc.player.yRotO);
//mc.options.fov;
return false;
return maxVerticalData[LodUtil.clamp(minGenDetail, detail, LodUtil.REGION_DETAIL_LEVEL)];
}
}
@@ -1,9 +1,4 @@
package com.seibel.lod.objects;
import com.seibel.lod.util.LodUtil;
import net.minecraft.util.math.ChunkPos;
import java.util.Comparator;
package com.seibel.lod.util;
public class LevelPosUtil
{
@@ -37,11 +32,29 @@ public class LevelPosUtil
return new int[]{detailLevel, posX, posZ};
}
public static int[] createLevelPos(byte detailLevel, int posX, int posZ, int distance)
public static int convert(byte detailLevel, int pos, byte newDetailLevel)
{
return new int[]{detailLevel, posX, posZ, distance};
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)
{
@@ -105,22 +118,11 @@ public class LevelPosUtil
return Math.floorDiv(getPosZ(levelPos), width);
}
public static int getChunkPosX(int[] levelPos)
public static int getChunkPos(byte detailLevel, int pos)
{
levelPos = convert(levelPos, LodUtil.CHUNK_DETAIL_LEVEL);
return getPosX(levelPos);
return convert(detailLevel,pos, LodUtil.CHUNK_DETAIL_LEVEL);
}
public static int getChunkPosZ(int[] levelPos)
{
levelPos = convert(levelPos, LodUtil.CHUNK_DETAIL_LEVEL);
return getPosZ(levelPos);
}
public static int maxDistance(int[] levelPos, int playerPosX, int playerPosZ)
{
return maxDistance(getDetailLevel(levelPos), getPosX(levelPos), getPosZ(levelPos), playerPosX, playerPosZ);
}
public static int maxDistance(byte detailLevel, int posX, int posZ, int playerPosX, int playerPosZ)
{
@@ -139,12 +141,24 @@ public class LevelPosUtil
return maxDistance;
}
public static int minDistance(int[] levelPos, int playerPosX, int playerPosZ)
public static int maxDistance(byte detailLevel, int posX, int posZ, int playerPosX, int playerPosZ, int xRegion, int zRegion)
{
return minDistance(getDetailLevel(levelPos), getPosX(levelPos), getPosZ(levelPos), playerPosX, playerPosZ);
int width = 1 << detailLevel;
int startPosX = xRegion * 512 + posX * width;
int startPosZ = zRegion * 512 + 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 minDistance(byte detailLevel, int posX, int posZ, int playerPosX, int playerPosZ)
{
int width = 1 << detailLevel;
@@ -181,40 +195,60 @@ public class LevelPosUtil
}
}
public static int compareDistance(int posX, int posZ, int[] first, int[] second)
public static int minDistance(byte detailLevel, int posX, int posZ, int playerPosX, int playerPosZ, int xRegion, int zRegion)
{
return Integer.compare(
minDistance(first, posX, posZ),
minDistance(second, posX, posZ));
}
int width = 1 << detailLevel;
public static int compareDistance(int[] first, int[] second)
{
return Integer.compare(
getDistance(first),
getDistance(second));
}
int startPosX = xRegion * 512 + posX * width;
int startPosZ = zRegion * 512 + posZ * width;
int endPosX = startPosX + width;
int endPosZ = startPosZ + width;
public static int compareLevelAndDistance(int[] first, int[] second)
{
int compareResult = Integer.compare(getDetailLevel(second), getDetailLevel(first));
if (compareResult == 0)
boolean inXArea = playerPosX >= startPosX && playerPosX <= endPosX;
boolean inZArea = playerPosZ >= startPosZ && playerPosZ <= endPosZ;
if (inXArea && inZArea)
{
compareResult = Integer.compare(
getDistance(first),
getDistance(second));
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;
}
return compareResult;
}
public static int compareLevelAndDistance(int posX, int posZ, int[] first, int[] second)
public static int compareDistance(int firstDistance, int secondDistance)
{
int compareResult = Integer.compare(getDetailLevel(second), getDetailLevel(first));
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(
minDistance(first, posX, posZ),
minDistance(second, posX, posZ));
firstDistance,
secondDistance);
}
return compareResult;
}
@@ -223,4 +257,8 @@ public class LevelPosUtil
{
return (getDetailLevel(levelPos) + " " + getPosX(levelPos) + " " + getPosZ(levelPos));
}
public static String toString(byte detailLevel, int posX, int posZ)
{
return (detailLevel + " " + posX + " " + posZ);
}
}
+71 -55
View File
@@ -23,11 +23,11 @@ import java.util.HashSet;
import com.seibel.lod.objects.LodDimension;
import com.seibel.lod.objects.RegionPos;
import com.seibel.lod.objects.LevelPos.LevelPos;
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;
@@ -43,11 +43,11 @@ import net.minecraft.world.server.ServerWorld;
* This class holds methods and constants that may be used in multiple places.
*
* @author James Seibel
* @version 8-29-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 */
@@ -62,7 +62,10 @@ public class LodUtil
* 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
@@ -79,7 +82,9 @@ public class LodUtil
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;
@@ -98,6 +103,9 @@ public class LodUtil
/** 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 = "[\\\\\\/:*?\\\"<>|]";
@@ -207,10 +215,7 @@ public class LodUtil
}
else
{
ServerData server = mc.getCurrentServer();
return server.name + ", IP " +
server.ip + ", GameVersion " +
server.version.getString();
return getServerId();
}
}
@@ -219,15 +224,13 @@ public class LodUtil
/**
* 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>
* 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)
{
Minecraft mc = Minecraft.getInstance();
if(mc.hasSingleplayerServer())
{
// this will return the world save location
@@ -245,14 +248,25 @@ public class LodUtil
}
else
{
ServerData server = mc.getCurrentServer();
return server.name + ", IP " +
server.ip + ", GameVersion " +
server.version.getString() + File.separatorChar
+ "dim_" + world.dimensionType().effectsLocation().getPath() + File.separatorChar;
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;
}
/**
@@ -311,39 +325,40 @@ public class LodUtil
* 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.options.renderDistance;
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++)
{
LevelPos levelPos = new LevelPos(LodUtil.CHUNK_DETAIL_LEVEL, x, z);
if (!lodDim.doesDataExist(levelPos.clone()))
continue;
short[] data = lodDim.getData(levelPos);
short lodAverageHeight = data[0];
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;
}
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>
@@ -355,22 +370,23 @@ public class LodUtil
{
HashSet<ChunkPos> loadedPos = new HashSet<>();
Minecraft mc = Minecraft.getInstance();
// Wow those are some long names!
// go through every RenderInfo to get the compiled chunks
for (WorldRenderer.LocalRenderInformationContainer worldrenderer$localrenderinformationcontainer : mc.levelRenderer.renderChunks)
WorldRenderer renderer = mc.getLevelRenderer();
for (WorldRenderer.LocalRenderInformationContainer worldrenderer$localrenderinformationcontainer : renderer.renderChunks)
{
if (!worldrenderer$localrenderinformationcontainer.chunk.getCompiledChunk().hasNoRenderableLayers())
CompiledChunk compiledChunk = worldrenderer$localrenderinformationcontainer.chunk.getCompiledChunk();
if (!compiledChunk.hasNoRenderableLayers())
{
// add the ChunkPos for every empty compiled chunk
// add the ChunkPos for every rendered chunk
BlockPos bpos = worldrenderer$localrenderinformationcontainer.chunk.getOrigin();
loadedPos.add(new ChunkPos(bpos));
}
}
return loadedPos;
}
@@ -0,0 +1,108 @@
package com.seibel.lod.util;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
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[]> projectionShortMap = 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<>();
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());
}
public static long[][] getBuilderArray()
{
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());
}
public static long[][] getBuilderVerticalArray()
{
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());
}
public static long[] verticalDataArray()
{
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());
}
public static short[] getProjectionShort(){
if(!projectionShortMap.containsKey(Thread.currentThread().getName()) || (projectionShortMap.get(Thread.currentThread().getName()) == null))
{
projectionShortMap.put(Thread.currentThread().getName(), new short[0]);
}
return projectionShortMap.get(Thread.currentThread().getName());
}
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());
}
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());
}
public static long[][] getVerticalUpdateArray(){
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());
}
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());
}
public static void clearMaps(){
threadSingleUpdateMap.clear();
threadBuilderArrayMap.clear();
threadBuilderVerticalArrayMap.clear();
threadVerticalAddDataMap.clear();
saveContainer.clear();
projectionShortMap.clear();
heightAndDepthMap.clear();
singleDataToMergeMap.clear();
verticalUpdate.clear();
}
}
@@ -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
#=====================#
+2 -2
View File
@@ -24,7 +24,7 @@ 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.4" #mandatory
version="a1.5.0-pre" #mandatory
#// A display name for the mod
displayName="LOD" #mandatory
@@ -42,7 +42,7 @@ logoFile="logo.png" #optional
credits="TechnoVision, Vike, and Darkhax for their modding tutorials." #optional
#// A text field displayed in the mod UI
authors="James Seibel and Leonardo Amato" #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 terrain beyond the normal view distance, at a low performance cost.'''