Compare commits

...

220 Commits

Author SHA1 Message Date
James Seibel d5466e0fda Fix a potential null pointer exception when leaving the world
The thread dealing with these should probably be killed and recreated when leaving the world instead of just catching the problem here.
2021-10-22 20:46:26 -05:00
James Seibel 7a0b95a105 minor refactor in LodGenWorker 2021-10-22 20:45:37 -05:00
James Seibel 7c59e33aee remove some commented out code 2021-10-22 20:42:02 -05:00
James Seibel 9907d2ddd5 Temporary null pointer fix 2021-10-22 18:48:32 -05:00
cola98765 1a838d4bd8 pushed FAR fog out by *1.6. I don't know why this number, but it works ok. 2021-10-22 20:07:02 +02:00
cola98765 dce3227bf1 Fixed compression on retail. Don't change your gradle tasks 2021-10-22 08:28:30 +02:00
cola98765 ea6f3e9881 now it's game crashes on launch when build using file made by "shadowJar" option 2021-10-21 16:42:30 +02:00
Leonardo 2f57b67bdc fixed underwater problem with always overdraw 2021-10-21 13:19:23 +02:00
Leonardo be024f524d Fixed regions wall not showing (which would cause holes in the rendering) and added border as possible overdraw config 2021-10-21 12:57:40 +02:00
cola98765 e0a176c0c4 tried to make XZ actually part of the jar 2021-10-21 12:16:39 +02:00
James Seibel b00795a0ab Merge branch '1.16.5' of gitlab.com:jeseibel/minecraft-lod-mod into 1.16.5 2021-10-20 22:34:26 -05:00
James Seibel 8bfa8e70a1 Create 1.5 release notes.txt 2021-10-20 22:34:14 -05:00
James Seibel 2a0653419f Update Readme.md
Ah so there is a way to preview this online, that's nice.
2021-10-21 02:21:28 +00:00
James Seibel cc1b9ea28e change the version to a1.5.0 and fix a line in the readme 2021-10-20 21:19:40 -05:00
James Seibel b32bdb3807 Add a acknowledgement for the use of the XZ data compression project 2021-10-20 21:14:15 -05:00
James Seibel 42bac2bd57 Merge branch '1.16.5' of gitlab.com:jeseibel/minecraft-lod-mod into 1.16.5 2021-10-20 21:07:21 -05:00
James Seibel 9bd89accd3 re-add dynamic overdraw 2021-10-20 21:07:18 -05:00
Leonardo 5493ef9033 removed a print 2021-10-21 02:36:57 +02:00
Leonardo 7d1df26cc5 Shading is only disabled for lod that should be transparent 2021-10-21 02:28:37 +02:00
Leonardo 63f6d4cc56 Fixed chorus plant 2021-10-21 02:17:57 +02:00
James Seibel 6066a572d9 finish two incomplete comments 2021-10-20 18:09:08 -05:00
Leonardo ea679992e8 Small change 2021-10-21 00:40:21 +02:00
Leonardo fedce10917 Overdraw should now be fixed 2021-10-21 00:39:37 +02:00
Leonardo 80448ce990 Small change to the buffer builder 2021-10-20 23:03:13 +02:00
Leonardo 89fac88b7e Added method to check water logged block 2021-10-20 23:02:35 +02:00
Leonardo 032b63208c Added IWaterLoggable blocks to block to use in the generation 2021-10-20 21:23:53 +02:00
Leonardo 9faed3a2c8 Commented the buffer builder and added a method 2021-10-20 20:18:42 +02:00
Leonardo 05800089cb Changed fog enum name 2021-10-20 19:30:42 +02:00
James Seibel 8585511007 Update two of the config overrides 2021-10-19 22:11:11 -05:00
James Seibel 890de58c72 Fix regions culled incorrectly if far from the buffer's center
This would happen if the buffer builder died or was slow
2021-10-19 22:10:28 -05:00
James Seibel 8def361f63 rename LodNodeGenWorker to LodGenWorker 2021-10-19 21:16:58 -05:00
James Seibel 58d5a8beda Update the License header to match the new mod name
LOD -> Distance Horizons
2021-10-19 21:14:57 -05:00
James Seibel 443bb64df0 Improve the wording of the config file 2021-10-19 20:43:43 -05:00
James Seibel 400e00f572 auto-indent 2021-10-19 20:11:31 -05:00
Leonardo 5c36a3df16 Small fix to push and pop of configs 2021-10-19 23:11:39 +02:00
Leonardo 8578a833b4 Fixed crash caused by config 2021-10-19 22:36:01 +02:00
Leonardo 974a9b4452 Small change to configs name + comments 2021-10-19 22:30:23 +02:00
Leonardo d35fbb0bb3 New configs 2021-10-19 22:24:21 +02:00
Leonardo 0551fbb2d4 Small fix to config 2021-10-19 19:15:07 +02:00
Leonardo fe8a847637 Reversed resolution enum order 2021-10-19 19:09:32 +02:00
Leonardo b04eec91bc Fully disabled pre-gen code 2021-10-19 18:50:11 +02:00
Leonardo 6eddef2fb0 simplified block to avoid config 2021-10-19 18:10:59 +02:00
Leonardo 10382342d8 Added some more pre-gen testing code (disabled) 2021-10-19 13:35:22 +02:00
Leonardo 9d80b81378 Now the game doesn't use Biome class to calculate colors 2021-10-19 01:21:25 +02:00
Leonardo 12abc4f018 Merge remote-tracking branch 'origin/1.16.5' into 1.16.5 2021-10-19 01:20:35 +02:00
Leonardo 21e774489c Added the ability to use pregenerated chunks, (bugged still to fix) 2021-10-19 01:20:29 +02:00
cola98765 f6f7f1043e cleaned out code after save compression refactor 2021-10-18 14:32:32 +02:00
cola98765 30e9dd1aac Merge remote-tracking branch 'origin/1.16.5' into 1.16.5 2021-10-18 11:51:14 +02:00
cola98765 219f3eba17 actually read old files as compressed 2021-10-18 11:50:50 +02:00
Leonardo d737500d95 Added a new section to the LodRegion which may be useful to identify pre-generated chunk in the future 2021-10-18 11:50:38 +02:00
Leonardo 2a402ef1d6 fully disabled the pregen code 2021-10-18 11:47:56 +02:00
Leonardo 84c0fd994d Merge remote-tracking branch 'origin/1.16.5' into 1.16.5 2021-10-18 11:46:33 +02:00
Leonardo cf782c5b6f changed default thread number 2021-10-18 11:46:23 +02:00
cola98765 96c19620cb now compressing using proper algorithm 2021-10-18 11:00:58 +02:00
cola98765 d0f93a2c89 Fixed light on predicted chunks in caveworld. 2021-10-17 22:15:06 +02:00
Leonardo 85157d0da0 Disabled pre-gen loading for now + small fix 2021-10-17 20:33:28 +02:00
Leonardo 33d150b090 Disabled pre-gen loading for now + small fix 2021-10-17 19:56:29 +02:00
Leonardo e6949dcd14 Fixed shadow with no tree bug 2021-10-17 19:37:06 +02:00
Leonardo b411f083e2 Added config for pre-generated chunk loading 2021-10-17 19:21:56 +02:00
Leonardo d3cd714f48 Added pre-generated chunk loading 2021-10-17 19:17:44 +02:00
Leonardo df0d1483c7 Merge remote-tracking branch 'origin/1.16.5' into 1.16.5 2021-10-17 18:52:40 +02:00
Leonardo 0e82afd6e1 Disabled config override 2021-10-17 18:52:34 +02:00
cola98765 d80253546f fixed chunks resetting after loading save 2021-10-17 16:37:48 +02:00
Leonardo 441e4ff1f2 SmallFix to generation 2021-10-17 14:41:28 +02:00
Leonardo cbdfabeaeb Real chunks are always converted in max quality (could make it a config) 2021-10-17 14:34:39 +02:00
cola98765 bdf7fb3466 fixed blocklight one more time 2021-10-17 13:34:15 +02:00
cola98765 7595248406 cleaned a bit of code 2021-10-17 12:13:17 +02:00
cola98765 b5bcab36cd cleaned a bit of code 2021-10-17 11:53:47 +02:00
cola98765 5ed1e6135b now blocklight also uses server world when applicable 2021-10-17 11:33:07 +02:00
James Seibel 636d4129bd Rename HorizontalQuality LINEAR to LOWEST 2021-10-16 21:38:28 -05:00
James Seibel a90dfb695d Make the config file more easily readable 2021-10-16 21:07:07 -05:00
James Seibel 15c1a64cfd Update the version number to b1.5.0 2021-10-16 20:58:10 -05:00
James Seibel c5f823c54e Add generation Resolution checking to Dynamic vanilla overdraw 2021-10-16 20:56:00 -05:00
James Seibel a7c2e8a6ed Update the config file to use mod name (Distant Horizons) vs the mod id (lod) 2021-10-16 18:48:25 -05:00
James Seibel cb496208f5 If avoiding non full/solid blocks use the above block's color
This fixes snow and flowers not being used.
2021-10-16 18:08:17 -05:00
James Seibel 4ff1815436 Clean up a few compiler warnings 2021-10-16 16:28:57 -05:00
cola98765 31ada4e5d2 No longer removing 1 block gaps as with new lighting it shouldn't look worse 2021-10-16 22:43:25 +02:00
cola98765 accf082309 No longer removing 1 block gaps as with new lighting it shouldn't look worse 2021-10-16 22:42:58 +02:00
cola98765 e1b1f26f37 made project actually build after removing experimental light option 2021-10-16 22:18:24 +02:00
James Seibel a0c14c86ca Improve the lighting on local worlds
ClientWorld apparently doesn't actually know what the lighting is anywhere, so we will have to find another way to determine shadows on servers.
2021-10-16 13:45:31 -05:00
Leonardo a122015f6d Experimental light system is a config now 2021-10-16 15:52:39 +02:00
Leonardo 164d407153 Experimental light system is a config now 2021-10-16 15:51:35 +02:00
cola98765 893bfa096f made new light optional with internal boolean 2021-10-16 11:25:13 +02:00
cola98765 37995af19e fixed flag setting, and changed code back as if isLightCorrect and getBrightness actually worked 2021-10-16 10:39:06 +02:00
James Seibel c637d23121 Merge branch 'CodeF53/minecraft-lod-mod-1.16.5' into 1.16.5 2021-10-15 21:00:54 -05:00
James Seibel d762508188 Only return Minecraft Chunks from the chunkLoadEvent 2021-10-15 20:26:56 -05:00
James Seibel f5437f00a2 slight refactoring 2021-10-15 20:25:33 -05:00
CodeF53 dec20962b2 Switch readme to markdown format, adding headers and misc formatting. 2021-10-15 21:24:32 +00:00
cola98765 30e796f5bf fixed side lighting 2021-10-15 23:20:54 +02:00
cola98765 4ab4dc59e7 now renders default Skylight as 0 when player is in Skylight 0 situation 2021-10-15 18:41:58 +02:00
cola98765 839a76c594 minor adjustment that was annoying me 2021-10-15 17:24:04 +02:00
Leonardo 3fb4d16e41 New sky light handling system (not complete) 2021-10-15 16:23:20 +02:00
Leonardo 002d86af9f Added comment + small refactor + removed TODO 2021-10-15 16:22:41 +02:00
cola98765 e1877f9149 Now using flag bit in data as "is light default" 2021-10-15 15:37:51 +02:00
cola98765 4da1fcc118 since new method of getting skyLight is objectively better, I've commented out old method and 'if' used to switch between those methods 2021-10-14 23:36:27 +02:00
cola98765 af123a8c80 added fast track when we want to add whole column of data, so it doesn't call 5 different methods for each lod block. 2021-10-14 23:33:14 +02:00
cola98765 94bc7fd011 now ignoring isLightCorrect completely, old method is still available 2021-10-14 19:29:50 +02:00
cola98765 905d8bddf3 made new sky lighting calculation optional with internal boolean 2021-10-14 18:56:51 +02:00
cola98765 5ee762cc78 now actually remove garbage vertical lods 2021-10-14 18:51:39 +02:00
cola98765 7c07a88b45 small fix to that light fix 2021-10-14 18:02:58 +02:00
cola98765 d8981af7aa possible fix to skylight... revert if worse than before 2021-10-14 15:11:36 +02:00
Leonardo 9906eb75f4 Changed the Far fog on fancy. Now fog start after the real game render distance 2021-10-14 14:06:11 +02:00
Leonardo d94fedc61e Changed the Far fog on fancy. Now fog start after the real game render distance 2021-10-14 14:02:47 +02:00
cola98765 3e3b979530 indents in LodConfig 2021-10-14 07:49:02 +02:00
James Seibel 6ad94d3362 Update the version number to 1.5.0 and replace the LOD name with Distant Horizons 2021-10-13 23:28:43 -05:00
James Seibel 31f8cecc92 Add the minimum OpenGL version to the readme
BufferStorage technically only requires OpenGL 4.4, but I already had 4.5 written everywhere. If it becomes a problem that going to 4.4 fixes I could probably roll back to that.
2021-10-13 22:04:41 -05:00
James Seibel b4106a8c3c Improve the buffer rebuild logic related to light change 2021-10-13 21:59:46 -05:00
James Seibel b6390c57ef auto-indent 2021-10-13 21:03:03 -05:00
James Seibel 5172c66b9b Merge branch 'CodeF53-1.16.5-patch-87033' into '1.16.5'
Update demo video to latest

See merge request jeseibel/minecraft-lod-mod!3
2021-10-14 01:59:33 +00:00
James Seibel 067fa92419 Improve the logic for vanilla overdraw to work better on different horizontal resolutions 2021-10-13 20:44:38 -05:00
Leonardo 15e80ed063 ceiled dimension now have 64 max render distance + various small changes 2021-10-14 02:29:42 +02:00
Leonardo 2c6549e815 Merge remote-tracking branch 'origin/1.16.5' into 1.16.5
# Conflicts:
#	src/main/java/com/seibel/lod/builders/lodBuilding/LodBuilder.java
#	src/main/java/com/seibel/lod/objects/VerticalLevelContainer.java
2021-10-14 02:28:24 +02:00
CodeF53 32423c07eb Update demo video to latest 2021-10-13 15:03:27 +00:00
cola98765 4a0640a219 cleaned out some code. 2021-10-13 14:27:27 +02:00
cola98765 5f9f20a064 cleaned out some code. 2021-10-13 13:45:23 +02:00
cola98765 ef4ba7208e cleaned out some code. 2021-10-13 13:31:47 +02:00
cola98765 80edf19b33 cleaned out some code. 2021-10-13 13:24:07 +02:00
cola98765 4baa649972 cleaned out some code. Applied autoformat everywhere 2021-10-13 12:45:21 +02:00
cola98765 c7dbc28a9a cleaned out some code 2021-10-13 12:08:47 +02:00
cola98765 707c55653a removed single level container 2021-10-13 12:01:56 +02:00
Leonardo 40cb7f4efc Merge remote-tracking branch 'origin/1.16.5' into 1.16.5
# Conflicts:
#	src/main/java/com/seibel/lod/objects/SingleLevelContainer.java
2021-10-13 11:35:24 +02:00
Leonardo df22a082a1 Added option to avoid block with no collision + removed SingleLevelContainer class use 2021-10-13 11:34:59 +02:00
cola98765 3a7f411aa4 cleaned java ocs in DataPointUtil 2021-10-13 10:36:29 +02:00
cola98765 11c419e503 updated some TODO's 2021-10-13 10:15:48 +02:00
James Seibel 30b82e8009 update dynamic vanilla overdraw in LodUtil 2021-10-12 22:31:51 -05:00
James Seibel 7df4a161dd Merge branch '1.16.5' of gitlab.com:jeseibel/minecraft-lod-mod into 1.16.5 2021-10-12 22:20:53 -05:00
James Seibel 56160db7ec comment out the buffer builder expanding console logging 2021-10-12 22:20:44 -05:00
James Seibel ac84c3707a Remove a illegal import 2021-10-12 22:20:27 -05:00
Leonardo d16f571467 Small changed to border shading + changed overdraw ratio 2021-10-12 17:12:31 +02:00
cola98765 d7bd0498a0 When there are no files for current gen mode, try to load more detailed ones instead. 2021-10-12 11:58:21 +02:00
James Seibel 57a2b956dd Improve the VanillaOverdraw config to work better in roofed dimensions 2021-10-11 23:05:49 -05:00
James Seibel 51aadc8d39 Maybe fix a OpenGL error? 2021-10-11 23:00:28 -05:00
James Seibel 70e2f7f3e6 prevent a potential out of bounds exception 2021-10-11 23:00:11 -05:00
James Seibel 0ef8615a98 Extract the main work of the buffer builder into a method for easier hot swapping 2021-10-11 22:12:35 -05:00
James Seibel 4c6412d09f comment out a debug override 2021-10-11 20:55:02 -05:00
Leonardo 27b8cfea09 Fixed the purple color bug 2021-10-11 23:17:20 +02:00
cola98765 b5ab06bfb6 resolved warning 2021-10-11 22:14:56 +02:00
James Seibel 732476b454 Add the overdraw
Overdraw renders LODs on top of vanilla chunks based with  three different settings.
2021-10-10 21:31:45 -05:00
James Seibel 9d1a9eb9f3 Remove the memory estimation code.
Buffers will now be purposely created smaller than expected and then expanded to fit the need.
2021-10-10 14:01:13 -05:00
James Seibel 6018449cbe Quick formatting change and remove a completed TODO 2021-10-10 08:10:05 -05:00
cola98765 8f43695a47 did someone say warnings and typos? 2021-10-10 13:07:23 +02:00
cola98765 30913a0c29 more typos 2021-10-10 11:36:10 +02:00
cola98765 d8cee2b10c Merge remote-tracking branch 'origin/1.16.5' into 1.16.5 2021-10-10 11:19:09 +02:00
cola98765 33a706f660 autoformat 2021-10-10 11:19:02 +02:00
cola98765 6c2b707c02 autoformat 2021-10-10 11:16:54 +02:00
cola98765 ecb01c731e Resolved "ALOT" of typos. 2021-10-10 11:09:15 +02:00
cola98765 72737a56c1 Resolved more warnings. 2021-10-10 10:38:00 +02:00
cola98765 1d1b5f6115 Resolved more warnings. Applied auto format in affected files. 2021-10-10 10:34:34 +02:00
cola98765 b67ab59a89 Resolved more warnings. Applied auto format in affected files. 2021-10-10 09:50:44 +02:00
cola98765 ca4597e3d9 Merge remote-tracking branch 'origin/1.16.5' into 1.16.5 2021-10-10 08:38:00 +02:00
cola98765 efd9a5a51b merge all 1 block gaps for better look o trees 2021-10-10 08:37:47 +02:00
cola98765 4c3d19a6c6 merge all 1 block gaps for better look o trees 2021-10-10 08:36:45 +02:00
cola98765 6c4faa9103 Merge remote-tracking branch 'origin/1.16.5' into 1.16.5 2021-10-10 08:24:05 +02:00
James Seibel 6066957584 Update LodConfig.java 2021-10-09 20:14:51 -05:00
James Seibel c3f8b0b677 Remove the experimental useFovSetting setting 2021-10-09 19:47:26 -05:00
James Seibel e2cdf1549a auto-indent 2021-10-09 19:42:02 -05:00
James Seibel fe9bc5e77e Delete Reference.java 2021-10-09 19:41:22 -05:00
cola98765 ea0ef9e394 boosted skylight prediction to 13 so most trees look better 2021-10-09 23:22:39 +02:00
James Seibel 27d74958bf Merge branch '1.16.5' of gitlab.com:jeseibel/minecraft-lod-mod into 1.16.5 2021-10-09 14:48:53 -05:00
James Seibel dc8db97f33 Fix the region at 0,0 (-X,-Z) rendering incorrectly 2021-10-09 14:48:47 -05:00
cola98765 3ec30d49be put guts of mergeSingleData into mergeMultiData and make dataPoint smaller to make it faster 2021-10-09 17:47:58 +02:00
cola98765 627327140e reversed logic in mergeMultiData to improve performance of initial compression to maxVerticalData 2021-10-09 17:07:38 +02:00
James Seibel 2ebbb6f591 Merge branch '1.16.5' of gitlab.com:jeseibel/minecraft-lod-mod into 1.16.5 2021-10-09 09:46:33 -05:00
James Seibel 126b581e97 Add BufferStorage recreation, improving memory usage
I still want the buffers to be created the right size at the start, but this will work for now.
2021-10-09 09:46:24 -05:00
cola98765 f625e714d4 made mergeMultiData a bit faster 2021-10-09 11:00:59 +02:00
cola98765 5db7e349b9 in mergeMultiData skip empty data 2021-10-09 08:16:58 +02:00
James Seibel 272546af4d Improve the buffer expansion console logging 2021-10-08 22:16:12 -05:00
James Seibel c0a9e3061c reformatting / commenting 2021-10-08 22:13:20 -05:00
James Seibel 5872dc3be5 Fix a memory leak when changing worlds 2021-10-07 22:47:16 -05:00
James Seibel 8fe6eff36c improve the buffer expanding logging 2021-10-07 22:45:48 -05:00
James Seibel d89f2dc9b8 Refactoring / reformatting 2021-10-07 21:34:34 -05:00
James Seibel e0ac03db6c Merge branch '1.16.5' of gitlab.com:jeseibel/minecraft-lod-mod into 1.16.5 2021-10-07 21:25:05 -05:00
James Seibel ee58f3d9dc Use OpenGL BufferStorage to improve upload performance
I need to change how the memory is determined, right now they fill to the maximum amount; which is very bad and only for testing.
2021-10-07 20:59:45 -05:00
cola98765 b02d58227c reworked mergeMultiData 2021-10-07 20:17:37 +02:00
Leonardo 61cb27020c small change to a print 2021-10-07 18:03:09 +02:00
Leonardo 443da5eede other config change, set roofed dimension max distance to 64 2021-10-07 17:37:02 +02:00
Leonardo 78ab4b8598 Changed configs 2021-10-07 17:20:39 +02:00
cola98765 028aed53f8 resolved some more warnings and applied autoformat in affected files 2021-10-07 11:17:02 +02:00
cola98765 1ed36ba6c4 resolved some more warnings and applied autoformat in affected files 2021-10-07 11:07:10 +02:00
cola98765 6deca8e235 resolved many warnings and applied autoformat in affected files 2021-10-07 10:36:30 +02:00
Leonardo a6f3c2478e Some refactor + fixed cpu problem with near_first 2021-10-07 00:19:32 +02:00
cola98765 a66554f2dd resolved todo 2021-10-06 11:45:36 +02:00
cola98765 28cf2eb450 removed debug message when saving 2021-10-06 10:37:36 +02:00
cola98765 e154f552c7 fixed bug where only some regions were saved when player was not moving 2021-10-06 10:36:37 +02:00
Leonardo 0c275e1495 Added back the toString in the PosToGenerateContainer 2021-10-05 19:06:51 +02:00
Leonardo 2b15c089f4 Added back the toString in the PosToGenerateContainer 2021-10-05 18:59:40 +02:00
Leonardo fca926bc8f Added some comment, fixed the thread conflict problem 2021-10-05 11:25:06 +02:00
James Seibel f6ea990ab0 Reduce buffer uploading stuttering
I also included commented out versions of several other buffer uploading options and comments on how well they perform.
2021-10-04 22:52:11 -05:00
James Seibel ee6c8d597c Hopefully prevent the buffer builder from dying if an exception is thrown 2021-10-03 14:45:38 -05:00
James Seibel 1e819bd555 temporarily disable transparency in the AbstractLodTemplate 2021-10-03 14:08:55 -05:00
James Seibel a0358dd298 Merge branch '1.16.5' of gitlab.com:jeseibel/minecraft-lod-mod into 1.16.5 2021-10-03 13:57:34 -05:00
James Seibel 90987fc389 Add a constant to LodUtil and remove a few unused ones 2021-10-03 13:57:24 -05:00
cola98765 f72c846fe5 removed old commented code 2021-10-03 09:42:50 +02:00
James Seibel 2ca8576ad7 auto-indent 2021-10-02 21:33:09 -05:00
James Seibel 0194cffaba auto indent 2021-10-02 21:28:52 -05:00
James Seibel cc52815315 update a override option in the clientProxy 2021-10-02 21:24:19 -05:00
James Seibel 900b67920c re-add transparency rendering 2021-10-02 21:24:04 -05:00
James Seibel 9c1e5c33a5 Merge branch '1.16.5' of gitlab.com:jeseibel/minecraft-lod-mod into 1.16.5 2021-10-02 21:08:22 -05:00
James Seibel d7a1d330d9 Re-add Linux and (hopefully) Mac support
I have tested Windows and Ubuntu (Linux). I didn't test Mac but I am reasonably confident that it should work.
2021-10-02 21:08:15 -05:00
cola98765 3ac05c6a2b fixed multiplyRGBcolors to not make off by 1 error 2021-10-02 15:34:02 +02:00
James Seibel eeff5437b5 Hopefully add Mac and Linux support 2021-10-01 23:25:51 -05:00
James Seibel ee89d6f512 Update the Readme to include IntelliJ install instructions 2021-10-01 07:19:55 -05:00
James Seibel a577851f6b Merge branch '1.16.5' of gitlab.com:jeseibel/minecraft-lod-mod into 1.16.5 2021-09-30 21:42:00 -05:00
James Seibel 536bff2fe7 Hopefully fix the OpenGL context switching error 2021-09-30 21:41:49 -05:00
James Seibel b1995445e2 auto-format 2021-09-30 19:24:30 -05:00
Leonardo 5d4d483b16 re-added the avoidNonFullBlock and avoidSmallBlock options 2021-10-01 01:26:11 +02:00
Leonardo c13abc3b7d Added thread data for the buffer builder 2021-09-30 19:50:05 +02:00
Leonardo 1cbb15045f Added a reset when unloading and loading world 2021-09-30 19:47:04 +02:00
Leonardo 48ea096ff1 Added comment and changed some small variables 2021-09-30 17:52:51 +02:00
cola98765 2e08b61672 Merge remote-tracking branch 'origin/1.16.5' into 1.16.5 2021-09-30 17:46:59 +02:00
cola98765 b0bb01fde4 reworked ThreadMapUtil to actually save arrays between uses. 2021-09-30 17:46:03 +02:00
Leonardo cc666e917e Now the border chunk only work for render distance higher than 4 2021-09-30 15:26:28 +02:00
Leonardo 9e0abd06fb Better fake chunk/ real chunk blending, less holes 2021-09-30 14:48:52 +02:00
cola98765 0f56a98499 attempt to fix grass colors by setting biome opacity to 255 2021-09-30 13:04:43 +02:00
cola98765 529c52b93f getBuilderArray wasn't initialised correctly causing a lot of new long[] 2021-09-30 12:28:04 +02:00
cola98765 8020b298fb fixed saving transparency and calculating it around biome colors 2021-09-30 11:11:23 +02:00
Leonardo 37db05d18f added a borderChunk finder function 2021-09-30 01:38:39 +02:00
Leonardo 555e5a78b5 simplified the culling (still disabled) 2021-09-30 01:37:48 +02:00
Leonardo 32d492f5f6 Changed some package and added a dimension to the buffer builders 2021-09-29 14:57:43 +02:00
James Seibel d9ee9135d7 revert the config override message 2021-09-29 07:55:46 -05:00
67 changed files with 5473 additions and 4546 deletions
Binary file not shown.
+68
View File
@@ -0,0 +1,68 @@
# Distant Horizons
This mod adds a Level Of Detail (LOD) system to Minecraft.\
This implementation renders simplified chunks outside the normal render distance\
allowing for an increased view distance without harming performance.
Or in other words: this mod lets you see farther without turning your game into a slide show.\
If you want to see a quick demo, check out a video covering the mod here:
[![Minecraft Level Of Detail (LOD) mod - Alpha 1.4](https://i.ytimg.com/vi_webp/MDWcEvdUGUE/mqdefault.webp)](https://www.youtube.com/watch?v=MDWcEvdUGUE)
Forge version: 1.16.5-36.1.0
Notes:\
This version has been confirmed to work in Eclipse and Retail Minecraft.\
(Retail running forge version 1.16.5-36.1.0)
## source code installation
See the Forge Documentation online for more detailed instructions:\
http://mcforge.readthedocs.io/en/latest/gettingstarted/
1. Create a system variable called "JAVA_MC_HOME" with the location of the JDK 1.8.0_251 (This is needed for gradle to work correctly)
2. replace JAVA_HOME with JAVA_MC_HOME in gradle.bat
3. open a command line in the project folder
**If using Ecplise:**
1. run the command: `./gradlew geneclipseruns`
2. run the command: `./gradlew eclipse`
3. Make sure eclipse has the JDK 1.8.0_251 installed. (This is needed so that eclipse can run minecraft)
4. Import the project into eclipse
**If using IntelliJ:**
1. open IDEA and import the build.gradle
2. run the command: `./gradlew genIntellijRuns`
3. refresh the Gradle project in IDEA if required
## Compiling
1. open a command line in the project folder
2. run the command: `./gradlew build`
3. the compiled jar file will be in the folder `build\libs`
## Other commands
`./gradlew --refresh-dependencies` to refresh local dependencies.
`./gradlew clean` to reset everything (this does not affect your code) and then start the process again.
## Note to self
The Minecraft source code is NOT added to your workspace in an editable way. Minecraft is treated like a normal Library. Sources are there for documentation and research purposes only.
Source code uses Mojang mappings.
The source code can be 'created' with the `./eclipse` command and can be found in the following path:\
`minecraft-lod-mod\build\fg_cache\mcp\ VERSION \joined\ RANDOM_STRING \patch\output.jar`
## Open Source Acknowledgements
XZ for Java (data compression)\
https://tukaani.org/xz/java.html
-70
View File
@@ -1,70 +0,0 @@
This mod adds a Level Of Detail (LOD) system to Minecraft.
This implementation renders simplified chunks outside the normal render distance
allowing for an increased view distance without harming performance.
Or in other words: this mod let's you see farther without turning your game into a slide show.
If you want to see a quick demo, check out the video I made here:
https://youtu.be/CCT-3s02tYA
Forge version: 1.16.5-36.1.0
Notes:
This version has been confirmed to work in Eclipse and Retail Minecraft.
(Retail running forge version 1.16.5-36.1.0)
========================
source code installation
========================
See the Forge Documentation online for more detailed instructions:
http://mcforge.readthedocs.io/en/latest/gettingstarted/
Step 1: Create a system variable called "JAVA_MC_HOME" with the location of the JDK 1.8.0_251 (This is needed for gradle to work correctly)
Step 2: replace JAVA_HOME with JAVA_MC_HOME in gradle.bat
Step 3: open a command line in the project folder
Step 4: run the command: "./gradlew geneclipseruns"
Step 5: run the command: "./gradlew eclipse"
Step 6: Make sure the eclipse has the JDK 1.8.0_251 installed. (This is needed so that eclipse can run minecraft)
Step 7: Import the project into eclipse
=========
compiling
=========
Step 1: open a command line in the project folder
Step 2: run the command: "./gradlew build"
Step 3: the compiled jar file will be in the folder "build\libs"
==============
Other commands
==============
"./gradlew --refresh-dependencies" to refresh local dependencies.
"./gradlew clean" to reset everything (this does not affect your code) and then start the process again.
============
Note to self
============
The Minecraft source code is NOT added to your workspace in a editable way. Minecraft is treated like a normal Library. Sources are there for documentation and research purposes only.
Source code uses mcp mappings not Mojangs.
The source code can be 'created' with the ./eclipse command and can be found in the following path:
minecraft-lod-mod\build\fg_cache\mcp\ VERSION \joined\ RANDOM_STRING \patch\output.jar
@@ -0,0 +1,50 @@
<code_scheme name="Eclipse (James' Edit)" version="173">
<option name="RIGHT_MARGIN" value="1200" />
<JavaCodeStyleSettings>
<option name="JD_ALIGN_PARAM_COMMENTS" value="false" />
<option name="JD_ALIGN_EXCEPTION_COMMENTS" value="false" />
<option name="JD_ADD_BLANK_AFTER_DESCRIPTION" value="false" />
<option name="JD_P_AT_EMPTY_LINES" value="false" />
<option name="JD_KEEP_INVALID_TAGS" value="false" />
<option name="JD_DO_NOT_WRAP_ONE_LINE_COMMENTS" value="true" />
<option name="JD_KEEP_EMPTY_PARAMETER" value="false" />
<option name="JD_KEEP_EMPTY_EXCEPTION" value="false" />
<option name="JD_KEEP_EMPTY_RETURN" value="false" />
</JavaCodeStyleSettings>
<codeStyleSettings language="JAVA">
<option name="KEEP_CONTROL_STATEMENT_IN_ONE_LINE" value="false" />
<option name="KEEP_BLANK_LINES_IN_DECLARATIONS" value="10" />
<option name="KEEP_BLANK_LINES_IN_CODE" value="10" />
<option name="KEEP_BLANK_LINES_BEFORE_RBRACE" value="10" />
<option name="BLANK_LINES_BEFORE_PACKAGE" value="1" />
<option name="BRACE_STYLE" value="2" />
<option name="CLASS_BRACE_STYLE" value="2" />
<option name="METHOD_BRACE_STYLE" value="2" />
<option name="ELSE_ON_NEW_LINE" value="true" />
<option name="WHILE_ON_NEW_LINE" value="true" />
<option name="CATCH_ON_NEW_LINE" value="true" />
<option name="FINALLY_ON_NEW_LINE" value="true" />
<option name="INDENT_CASE_FROM_SWITCH" value="false" />
<option name="ALIGN_MULTILINE_PARAMETERS" value="false" />
<option name="ALIGN_MULTILINE_RESOURCES" value="false" />
<option name="SPACE_WITHIN_ARRAY_INITIALIZER_BRACES" value="true" />
<option name="SPACE_BEFORE_ARRAY_INITIALIZER_LBRACE" value="true" />
<option name="CALL_PARAMETERS_WRAP" value="1" />
<option name="METHOD_PARAMETERS_WRAP" value="1" />
<option name="RESOURCE_LIST_WRAP" value="5" />
<option name="EXTENDS_LIST_WRAP" value="1" />
<option name="THROWS_LIST_WRAP" value="1" />
<option name="EXTENDS_KEYWORD_WRAP" value="1" />
<option name="THROWS_KEYWORD_WRAP" value="1" />
<option name="METHOD_CALL_CHAIN_WRAP" value="1" />
<option name="TERNARY_OPERATION_WRAP" value="5" />
<option name="ARRAY_INITIALIZER_WRAP" value="1" />
<option name="METHOD_ANNOTATION_WRAP" value="0" />
<option name="CLASS_ANNOTATION_WRAP" value="0" />
<option name="FIELD_ANNOTATION_WRAP" value="0" />
<indentOptions>
<option name="USE_TAB_CHARACTER" value="true" />
<option name="KEEP_INDENTS_ON_EMPTY_LINES" value="true" />
</indentOptions>
</codeStyleSettings>
</code_scheme>
+36 -25
View File
@@ -11,6 +11,7 @@ buildscript {
dependencies {
classpath group: 'net.minecraftforge.gradle', name: 'ForgeGradle', version: '3.+', changing: true
classpath group: 'org.spongepowered', name: 'mixingradle', version: '0.7-SNAPSHOT'
classpath 'com.github.jengelman.gradle.plugins:shadow:2.0.2'
}
}
apply plugin: 'net.minecraftforge.gradle'
@@ -18,10 +19,11 @@ 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: 'com.github.johnrengelman.shadow'
version = 'a1.5.1-pre'
version = 'a1.5.0'
group = 'com.seibel.lod'
archivesBaseName = 'lod_1.16.5'
archivesBaseName = 'Distant-Horizons_1.16.5'
sourceCompatibility = targetCompatibility = compileJava.sourceCompatibility = compileJava.targetCompatibility = '1.8' // Need this here so eclipse task generates correctly.
@@ -126,32 +128,23 @@ repositories {
maven { url 'https://jitpack.io' }
}
configurations {
shadowMe
compileOnly.extendsFrom(embed)
}
dependencies {
// Specify the version of Minecraft to use, If this is any group other then 'net.minecraft' it is assumed
// that the dep is a ForgeGradle 'patcher' dependency. And it's patches will be applied.
// The userdev artifact is a special name and will get all sorts of transformations applied to it.
minecraft 'net.minecraftforge:forge:1.16.5-36.1.0'
// these aren't needed right now
//implementation ('com.github.KaptainWutax:TerrainUtils:1.0.0'){
// transitive = false
//}
//implementation ('com.github.KaptainWutax:BiomeUtils:1.0.0'){
// transitive = false
//}
//implementation ('com.github.KaptainWutax:SeedUtils:-SNAPSHOT'){
// transitive = false
//}
//implementation ('com.github.KaptainWutax:MCUtils:1.0.0'){
// transitive = false
//}
//implementation ('com.github.KaptainWutax:NoiseUtils:1.0.0'){
// transitive = false
//}
//implementation ('com.github.KaptainWutax:MathUtils:-SNAPSHOT'){
// transitive = false
//}
compile 'org.tukaani:xz:1.9'
shadowMe 'org.tukaani:xz:1.9'
compile 'org.apache.commons:commons-compress:1.21'
shadowMe 'org.apache.commons:commons-compress:1.21'
// these were added to hopefully allow for cloning
// configuredFeatures to allow for safe
// multi threaded feature generation. Sadly I couldn't find
@@ -185,6 +178,25 @@ dependencies {
}
shadowJar {
duplicatesStrategy = DuplicatesStrategy.INCLUDE
configurations = [project.configurations.getByName("shadowMe")]
relocate 'org.tukaani', 'shaded.tukaani'
relocate 'org.apache.commons.compress', 'shaded.apache.commons.compress'
classifier = ''
}
reobf {
shadowJar {
dependsOn tasks.createMcpToSrg
mappings = tasks.createMcpToSrg.outputs.files.singleFile
}
}
artifacts {
archives tasks.shadowJar
}
// Example for how to get properties into the manifest for reading by the runtime..
jar {
manifest {
@@ -198,10 +210,9 @@ jar {
])
}
}
// Example configuration to allow publishing using the maven-publish task
// This is the preferred method to reobfuscate your jar file
jar.finalizedBy('reobfJar')
jar.finalizedBy('reobfJar')
// However if you are in a multi-project build, dev time needs unobfed jar files, so you can delay the obfuscation until publishing by doing
//publish.dependsOn('reobfJar')
Binary file not shown.
+26 -24
View File
@@ -1,5 +1,6 @@
/*
* This file is part of the LOD Mod, licensed under the GNU GPL v3 License.
* This file is part of the Distant Horizon mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020 James Seibel
*
@@ -15,6 +16,7 @@
* 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;
import com.seibel.lod.config.LodConfig;
@@ -53,28 +55,28 @@ public class LodMain
}
public LodMain()
{
// Register the methods
FMLJavaModLoadingContext.get().getModEventBus().addListener(this::init);
FMLJavaModLoadingContext.get().getModEventBus().addListener(this::onClientStart);
// Register ourselves for server and other game events we are interested in
MinecraftForge.EVENT_BUS.register(this);
}
private void onClientStart(final FMLClientSetupEvent event)
{
client_proxy = new ClientProxy();
public LodMain()
{
// Register the methods
FMLJavaModLoadingContext.get().getModEventBus().addListener(this::init);
FMLJavaModLoadingContext.get().getModEventBus().addListener(this::onClientStart);
// Register ourselves for server and other game events we are interested in
MinecraftForge.EVENT_BUS.register(this);
}
private void onClientStart(final FMLClientSetupEvent event)
{
client_proxy = new ClientProxy();
MinecraftForge.EVENT_BUS.register(client_proxy);
}
@SubscribeEvent
public void onServerStarting(FMLServerStartingEvent event)
{
// this is called when the server starts
}
}
@SubscribeEvent
public void onServerStarting(FMLServerStartingEvent event)
{
// this is called when the server starts
}
}
-43
View File
@@ -1,43 +0,0 @@
package com.seibel.lod;
import java.util.HashMap;
import java.util.Map;
import com.seibel.lod.builders.lodTemplates.Box;
import com.seibel.lod.config.LodConfig;
import com.seibel.lod.enums.HorizontalQuality;
import com.seibel.lod.enums.HorizontalScale;
import com.seibel.lod.util.DataPointUtil;
import net.minecraft.util.Direction;
public class Main
{
public static void main(String[] args)
{
for(byte detail = 0; detail < 13; detail++)
{
byte minGenDetail = 0;
byte maxDetail = 10;
int distance;
if (detail <= minGenDetail)
distance = 0;
else if (detail >= maxDetail)
distance = 10000;
else
{
int distanceUnit = HorizontalScale.LOW.distanceUnit;
switch (HorizontalQuality.HIGH)
{
case LINEAR:
;
distance = (detail * distanceUnit);
default:
double base = HorizontalQuality.HIGH.quadraticBase;
distance = (int) (Math.pow(base, detail) * distanceUnit);
}
}
System.out.println(distance);
}
}
}
+7 -6
View File
@@ -1,5 +1,6 @@
/*
* This file is part of the LOD Mod, licensed under the GNU GPL v3 License.
* This file is part of the Distant Horizon mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020 James Seibel
*
@@ -15,18 +16,18 @@
* 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;
/**
* This file is similar to mcmod.info
*
* @author James Seibel
* @version 08-29-2021
* @version 10-16-2021
*/
public final class ModInfo
{
{
public static final String MODID = "lod";
public static final String MODNAME = "LOD";
public static final String MODNAME = "DistantHorizons";
public static final String MODAPI = "LodAPI";
public static final String VERSION = "a1.5.1-pre";
public static final String VERSION = "a1.5.0";
}
@@ -1,588 +0,0 @@
/*
* This file is part of the LOD Mod, licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.lod.builders;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.locks.ReentrantLock;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL15;
import org.lwjgl.opengl.GL15C;
import com.seibel.lod.builders.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.proxy.ClientProxy;
import com.seibel.lod.proxy.GlProxy;
import com.seibel.lod.proxy.GlProxy.GlProxyContext;
import com.seibel.lod.render.LodRenderer;
import com.seibel.lod.util.DataPointUtil;
import com.seibel.lod.util.LevelPosUtil;
import com.seibel.lod.util.LodThreadFactory;
import com.seibel.lod.util.LodUtil;
import net.minecraft.client.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 9-25-2021
*/
public class LodBufferBuilder
{
/**
* This holds the thread used to generate new LODs off the main thread.
*/
public static ExecutorService mainGenThread = Executors.newSingleThreadExecutor(new LodThreadFactory(LodBufferBuilder.class.getSimpleName() + " - main"));
/**
* This holds the threads used to generate buffers.
*/
public static ExecutorService bufferBuilderThreads = Executors.newFixedThreadPool(LodConfig.CLIENT.threading.numberOfBufferBuilderThreads.get(), new LodThreadFactory(LodBufferBuilder.class.getSimpleName() + " - builder"));
/**
* 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 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);
public LodBufferBuilder()
{
}
/**
* Create a thread to asynchronously generate LOD buffers
* centered around the given camera X and Z.
* <br>
* This method will write to the drawable near and far buffers.
* <br>
* After the buildable buffers have been generated they must be
* swapped with the drawable buffers in the LodRenderer to be drawn.
*/
public void generateLodBuffersAsync(LodRenderer renderer, LodDimension lodDim,
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 startTime = System.currentTimeMillis();
ArrayList<Callable<Boolean>> nodeToRenderThreads = new ArrayList<>(lodDim.getWidth() * lodDim.getWidth());
startBuffers(fullRegen, lodDim);
RegionPos playerRegionPos = new RegionPos(playerChunkPos);
if (center == null)
center = playerRegionPos;
if (setsToRender == null)
setsToRender = new PosToRenderContainer[lodDim.getWidth()][lodDim.getWidth()];
if (setsToRender.length != lodDim.getWidth())
setsToRender = new PosToRenderContainer[lodDim.getWidth()][lodDim.getWidth()];
if (boxCache == null)
boxCache = new Box[lodDim.getWidth()][lodDim.getWidth()];
if (boxCache.length != lodDim.getWidth())
boxCache = new Box[lodDim.getWidth()][lodDim.getWidth()];
// this will be the center of the VBOs once they have been built
buildableCenterChunkPos = playerChunkPos;
//================================//
// create the nodeToRenderThreads //
//================================//
for (int xRegion = 0; xRegion < lodDim.getWidth(); xRegion++)
{
for (int zRegion = 0; zRegion < lodDim.getWidth(); zRegion++)
{
if (lodDim.doesRegionNeedBufferRegen(xRegion, zRegion) || fullRegen)
{
RegionPos regionPos = new RegionPos(
xRegion + lodDim.getCenterRegionPosX() - Math.floorDiv(lodDim.getWidth(), 2),
zRegion + lodDim.getCenterRegionPosZ() - Math.floorDiv(lodDim.getWidth(), 2));
// local position in the vbo and bufferBuilder arrays
BufferBuilder currentBuffer = buildableBuffers[xRegion][zRegion];
LodRegion region = lodDim.getRegion(regionPos.x, regionPos.z);
if (region == null)
continue;
// make sure the buffers weren't
// changed while we were running this method
if (currentBuffer == null || !currentBuffer.building())
return;
byte minDetail = region.getMinDetailLevel();
final int xR = xRegion;
final int zR = zRegion;
Callable<Boolean> dataToRenderThread = () ->
{
Map<Direction, long[]> adjData = new HashMap<>();
// determine how many LODs we can stack vertically
int maxVerticalData = 1;
if (LodConfig.CLIENT.worldGenerator.lodQualityMode.get() == VerticalQuality.VOXEL)
maxVerticalData = 256;
// create adjData's arrays
for (Direction direction : Box.ADJ_DIRECTIONS)
adjData.put(direction, new long[maxVerticalData]);
//previous setToRender cache
if (setsToRender[xR][zR] == null)
setsToRender[xR][zR] = new PosToRenderContainer(minDetail, regionPos.x, regionPos.z);
if (boxCache[xR][zR] == null)
boxCache[xR][zR] = new Box();
PosToRenderContainer posToRender = setsToRender[xR][zR];
posToRender.clear(minDetail, regionPos.x, regionPos.z);
lodDim.getDataToRender(
posToRender,
regionPos,
playerBlockPosRounded.getX(),
playerBlockPosRounded.getZ());
byte detailLevel;
int posX;
int posZ;
int xAdj;
int zAdj;
int chunkXdist;
int chunkZdist;
// keep a local version so we don't have to worry about indexOutOfBounds Exceptions
// if it changes in the LodRenderer while we are working here
boolean[][] vanillaRenderedChunks = renderer.vanillaRenderedChunks;
short gameChunkRenderDistance = (short) (vanillaRenderedChunks.length / 2 - 1);
for (int index = 0; index < posToRender.getNumberOfPos(); index++)
{
detailLevel = posToRender.getNthDetailLevel(index);
posX = posToRender.getNthPosX(index);
posZ = posToRender.getNthPosZ(index);
// skip any chunks that Minecraft is going to render
chunkXdist = LevelPosUtil.getChunkPos(detailLevel, posX) - playerChunkPos.x;
chunkZdist = LevelPosUtil.getChunkPos(detailLevel, posZ) - playerChunkPos.z;
if (gameChunkRenderDistance >= Math.abs(chunkXdist)
&& gameChunkRenderDistance >= Math.abs(chunkZdist)
&& detailLevel <= LodUtil.CHUNK_DETAIL_LEVEL
&& vanillaRenderedChunks[chunkXdist + gameChunkRenderDistance + 1][chunkZdist + gameChunkRenderDistance + 1])
{
continue;
}
// skip any chunks that Minecraft is going to render
for (Direction direction : Box.ADJ_DIRECTIONS)
{
xAdj = posX + direction.getNormal().getX();
zAdj = posZ + direction.getNormal().getZ();
chunkXdist = LevelPosUtil.getChunkPos(detailLevel, xAdj) - playerChunkPos.x;
chunkZdist = LevelPosUtil.getChunkPos(detailLevel, zAdj) - playerChunkPos.z;
boolean performFaceCulling = true;
if (performFaceCulling
&& posToRender.contains(detailLevel, xAdj, zAdj)
&& (gameChunkRenderDistance < Math.abs(chunkXdist)
|| gameChunkRenderDistance < Math.abs(chunkZdist)
|| !vanillaRenderedChunks[chunkXdist + gameChunkRenderDistance + 1][chunkZdist + gameChunkRenderDistance + 1]))
{
if (!adjData.containsKey(direction) || adjData.get(direction) == null)
adjData.put(direction, new long[maxVerticalData]);
for (int verticalIndex = 0; verticalIndex < lodDim.getMaxVerticalData(detailLevel, xAdj, zAdj); verticalIndex++)
{
long data = lodDim.getData(detailLevel, xAdj, zAdj, verticalIndex);
adjData.get(direction)[verticalIndex] = data;
}
}
else
{
adjData.put(direction, null);
}
}
long data;
for (int verticalIndex = 0; verticalIndex < lodDim.getMaxVerticalData(detailLevel, posX, posZ); verticalIndex++)
{
data = lodDim.getData(detailLevel, posX, posZ, verticalIndex);
if (DataPointUtil.isVoid(data) || !DataPointUtil.doesItExist(data))
break;
LodConfig.CLIENT.graphics.lodTemplate.get().template.addLodToBuffer(currentBuffer, playerBlockPosRounded, data, adjData,
detailLevel, posX, posZ, boxCache[xR][zR], renderer.previousDebugMode, renderer.lightMap);
}
} // for pos to in list to render
// the thread executed successfully
return true;
};
nodeToRenderThreads.add(dataToRenderThread);
}
} // region z
} // region z
long executeStart = System.currentTimeMillis();
// wait for all threads to finish
List<Future<Boolean>> futuresBuffer = bufferBuilderThreads.invokeAll(nodeToRenderThreads);
for (Future<Boolean> future : futuresBuffer)
{
// the future will be false if its thread failed
if (!future.get())
{
ClientProxy.LOGGER.warn("LodBufferBuilder ran into trouble and had to start over.");
closeBuffers(fullRegen, lodDim);
return;
}
}
long executeEnd = System.currentTimeMillis();
long endTime = System.currentTimeMillis();
@SuppressWarnings("unused")
long buildTime = endTime - startTime;
@SuppressWarnings("unused")
long executeTime = executeEnd - executeStart;
// ClientProxy.LOGGER.info("Thread Build time: " + buildTime + " ms" + '\n' +
// "thread execute time: " + executeTime + " ms");
// mark that the buildable buffers as ready to swap
switchVbos = true;
}
catch (Exception e)
{
ClientProxy.LOGGER.warn("\"LodNodeBufferBuilder.generateLodBuffersAsync\" ran into trouble: ");
e.printStackTrace();
}
finally
{
// regardless of if we successfully created the buffers
// we are done generating.
generatingBuffers = false;
// clean up any potentially open resources
if (buildableBuffers != null)
closeBuffers(fullRegen, lodDim);
// upload the new buffers
uploadBuffers(fullRegen, lodDim);
bufferLock.unlock();
}
});
mainGenThread.execute(thread);
}
//===============================//
// 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(LodDimension lodDimension)
{
bufferLock.lock();
int numbRegionsWide = lodDimension.getWidth();
int bufferMaxCapacity;
previousRegionWidth = numbRegionsWide;
buildableBuffers = new BufferBuilder[numbRegionsWide][numbRegionsWide];
buildableVbos = new VertexBuffer[numbRegionsWide][numbRegionsWide];
drawableVbos = new VertexBuffer[numbRegionsWide][numbRegionsWide];
for (int x = 0; x < numbRegionsWide; x++)
{
for (int z = 0; z < numbRegionsWide; z++)
{
bufferMaxCapacity = lodDimension.getMemoryRequired(x, z, LodConfig.CLIENT.graphics.lodTemplate.get());
buildableBuffers[x][z] = new BufferBuilder(bufferMaxCapacity);
buildableVbos[x][z] = new VertexBuffer(LodRenderer.LOD_VERTEX_FORMAT);
drawableVbos[x][z] = new VertexBuffer(LodRenderer.LOD_VERTEX_FORMAT);
}
}
bufferLock.unlock();
}
/**
* sets the buffers and Vbos to null, forcing them to be recreated. <br><br>
* <p>
* May have to wait for the bufferLock to open.
*/
public 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.doesRegionNeedBufferRegen(x, z))
{
// for some reason BufferBuilder.vertexCounts
// isn't reset unless this is called, which can cause
// a false indexOutOfBoundsException
buildableBuffers[x][z].discard();
buildableBuffers[x][z].begin(GL11.GL_QUADS, LodRenderer.LOD_VERTEX_FORMAT);
}
}
}
}
/**
* Calls end on each of the buildable BufferBuilders.
*/
private void closeBuffers(boolean fullRegen, LodDimension lodDim)
{
for (int x = 0; x < buildableBuffers.length; x++)
for (int z = 0; z < buildableBuffers.length; z++)
if (buildableBuffers[x][z] != null && buildableBuffers[x][z].building() && (fullRegen || lodDim.doesRegionNeedBufferRegen(x, z)))
buildableBuffers[x][z].end();
}
/**
* Upload all buildableBuffers to the GPU.
*/
private void uploadBuffers(boolean fullRegen, LodDimension lodDim)
{
GlProxy glProxy = GlProxy.getInstance();
try
{
// make sure we are uploading to a different OpenGL context,
// to prevent interference (IE stuttering) with the Minecraft context.
glProxy.setGlContext(GlProxyContext.LOD_BUILDER);
for (int x = 0; x < buildableVbos.length; x++)
{
for (int z = 0; z < buildableVbos.length; z++)
{
if (fullRegen || lodDim.doesRegionNeedBufferRegen(x, z))
{
ByteBuffer builderBuffer = buildableBuffers[x][z].popNextBuffer().getSecond();
vboUpload(buildableVbos[x][z], builderBuffer);
lodDim.setRegenRegionBufferByArrayIndex(x, z, false);
}
}
}
}
catch (IllegalStateException e)
{
ClientProxy.LOGGER.error(LodBufferBuilder.class.getSimpleName() + " - UploadBuffers failed: " + e.getMessage());
e.printStackTrace();
}
finally
{
// make sure the context is disabled
glProxy.setGlContext(GlProxyContext.NONE);
}
}
/**
* Uploads the uploadBuffer into the VBO in GPU memory.
*/
private void vboUpload(VertexBuffer vbo, ByteBuffer uploadBuffer)
{
// this shouldn't happen, but just to be safe
if (vbo.id != -1 && GlProxy.getInstance().getGlContext() == GlProxyContext.LOD_BUILDER)
{
// this is how many points will be rendered
vbo.vertexCount = (uploadBuffer.remaining() / vbo.format.getVertexSize());
GL15C.glBindBuffer(GL15.GL_ARRAY_BUFFER, vbo.id);
// subData only works if the memory is allocated beforehand.
GL15C.glBufferData(GL15.GL_ARRAY_BUFFER, uploadBuffer.remaining(), GL15C.GL_DYNAMIC_DRAW);
// interestingly bufferSubData renders faster than glMapBuffer
// even though OpenGLInsights-AsynchronousBufferTransfers says glMapBuffer
// is faster for transferring data. They must put the data in different memory
// or something.
GL15C.glBufferSubData(GL15.GL_ARRAY_BUFFER, 0, uploadBuffer);
GL15C.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
}
}
/**
* Get the newly created VBOs
*/
public VertexBuffersAndOffset getVertexBuffers()
{
// don't wait for the lock to open
// since this is called on the main render thread
if (bufferLock.tryLock())
{
VertexBuffer[][] tmpVbo = drawableVbos;
drawableVbos = buildableVbos;
buildableVbos = tmpVbo;
drawableCenterChunkPos = buildableCenterChunkPos;
// the vbos have been swapped
switchVbos = false;
bufferLock.unlock();
}
return new VertexBuffersAndOffset(drawableVbos, drawableCenterChunkPos);
}
/**
* A simple container to pass multiple objects back in the getVertexBuffers method.
*/
public class VertexBuffersAndOffset
{
public VertexBuffer[][] vbos;
public ChunkPos drawableCenterChunkPos;
public VertexBuffersAndOffset(VertexBuffer[][] newVbos, ChunkPos newDrawableCenterChunkPos)
{
vbos = newVbos;
drawableCenterChunkPos = newDrawableCenterChunkPos;
}
}
/**
* If this is true the buildable near and far
* buffers have been generated and are ready to be
* sent to the LodRenderer.
*/
public boolean newBuffersAvaliable()
{
return switchVbos;
}
}
@@ -0,0 +1,948 @@
/*
* This file is part of the Distant Horizon mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.lod.builders.bufferBuilding;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.locks.ReentrantLock;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL15;
import org.lwjgl.opengl.GL15C;
import org.lwjgl.opengl.GL45;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import com.mojang.blaze3d.systems.RenderSystem;
import com.seibel.lod.builders.bufferBuilding.lodTemplates.Box;
import com.seibel.lod.config.LodConfig;
import com.seibel.lod.enums.GlProxyContext;
import com.seibel.lod.enums.VanillaOverdraw;
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.proxy.ClientProxy;
import com.seibel.lod.proxy.GlProxy;
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 com.seibel.lod.util.ThreadMapUtil;
import com.seibel.lod.wrappers.MinecraftWrapper;
import net.minecraft.client.renderer.BufferBuilder;
import net.minecraft.client.renderer.vertex.VertexBuffer;
import net.minecraft.client.world.ClientWorld;
import net.minecraft.util.Direction;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.ChunkPos;
import net.minecraft.world.LightType;
/**
* This object is used to create NearFarBuffer objects.
* @author James Seibel
* @version 10-10-2021
*/
public class LodBufferBuilder
{
private static final MinecraftWrapper mc = MinecraftWrapper.INSTANCE;
/** The thread used to generate new LODs off the main thread. */
public static final ExecutorService mainGenThread = Executors.newSingleThreadExecutor(new LodThreadFactory(LodBufferBuilder.class.getSimpleName() + " - main"));
/** The threads used to generate buffers. */
public static final ExecutorService bufferBuilderThreads = Executors.newFixedThreadPool(LodConfig.CLIENT.advancedModOptions.threading.numberOfBufferBuilderThreads.get(), new ThreadFactoryBuilder().setNameFormat("Buffer-Builder-%d").build());
/**
* When uploading to a buffer that is too small,
* recreate it this many times bigger than the upload payload
*/
public static final double BUFFER_EXPANSION_MULTIPLIER = 1.5;
/**
* When buffers are first created they are allocated to this size (in Bytes).
* This size will be too small, more than likely. The buffers will be expanded
* when need be to fit the larger sizes.
*/
public static final int DEFAULT_MEMORY_ALLOCATION = 1024;
public static int skyLightPlayer = 15;
/**
* How many buffers there are for the given region. <Br>
* This is done because some regions may require more memory than
* can be directly allocated, so we split the regions into smaller sections. <Br>
* This keeps track of those sections.
*/
public volatile int[][] numberOfBuffersPerRegion;
/** Stores the vertices when building the VBOs */
public volatile BufferBuilder[][][] buildableBuffers;
/** The OpenGL IDs of the storage buffers used by the buildableVbos */
public int[][][] buildableStorageBufferIds;
/** The OpenGL IDs of the storage buffers used by the drawableVbos */
public int[][][] drawableStorageBufferIds;
/** used to debug how the buildableStorageBuffers are growing */
public int[][][] bufferPreviousCapacity;
/**
* This is toggled when the buffers are swapped, so we only
* display the expansion log for one set of buffers
*/
public boolean printExpansionLog = true;
/** 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 final ReentrantLock bufferLock = new ReentrantLock();
private volatile Box[][] boxCache;
private volatile PosToRenderContainer[][] setsToRender;
private volatile RegionPos center;
/**
* This is the ChunkPos the player was at the last time the buffers were built.
* IE the center of the buffers last time they were built
*/
private volatile ChunkPos drawableCenterChunkPos = new ChunkPos(0, 0);
private volatile ChunkPos buildableCenterChunkPos = new ChunkPos(0, 0);
public LodBufferBuilder()
{
}
/**
* Create a thread to asynchronously generate LOD buffers
* centered around the given camera X and Z.
* <br>
* This method will write to the drawable near and far buffers.
* <br>
* After the buildable buffers have been generated they must be
* swapped with the drawable buffers in the LodRenderer to be drawn.
*/
public void generateLodBuffersAsync(LodRenderer renderer, LodDimension lodDim,
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;
Thread thread = new Thread(() ->
generateLodBuffersThread(renderer, lodDim, playerBlockPos, fullRegen));
mainGenThread.execute(thread);
}
// this was pulled out as a separate method so that it could be
// more easily edited by hot swapping. Because, As far as James is aware
// you can't hot swap lambda expressions.
private void generateLodBuffersThread(LodRenderer renderer, LodDimension lodDim,
BlockPos playerBlockPos, boolean fullRegen)
{
bufferLock.lock();
try
{
// round the player's block position down to the nearest chunk BlockPos
ChunkPos playerChunkPos = new ChunkPos(playerBlockPos);
BlockPos playerBlockPosRounded = playerChunkPos.getWorldPosition();
long startTime = System.currentTimeMillis();
ArrayList<Callable<Boolean>> nodeToRenderThreads = new ArrayList<>(lodDim.getWidth() * lodDim.getWidth());
startBuffers(fullRegen, lodDim);
RegionPos playerRegionPos = new RegionPos(playerChunkPos);
if (center == null)
center = playerRegionPos;
if (setsToRender == null)
setsToRender = new PosToRenderContainer[lodDim.getWidth()][lodDim.getWidth()];
if (setsToRender.length != lodDim.getWidth())
setsToRender = new PosToRenderContainer[lodDim.getWidth()][lodDim.getWidth()];
if (boxCache == null)
boxCache = new Box[lodDim.getWidth()][lodDim.getWidth()];
if (boxCache.length != lodDim.getWidth())
boxCache = new Box[lodDim.getWidth()][lodDim.getWidth()];
// this will be the center of the VBOs once they have been built
buildableCenterChunkPos = playerChunkPos;
//================================//
// create the nodeToRenderThreads //
//================================//
ClientWorld world = mc.getClientWorld();
skyLightPlayer = world.getBrightness(LightType.SKY, playerBlockPos);
for (int xRegion = 0; xRegion < lodDim.getWidth(); xRegion++)
{
for (int zRegion = 0; zRegion < lodDim.getWidth(); zRegion++)
{
if (lodDim.doesRegionNeedBufferRegen(xRegion, zRegion) || fullRegen)
{
RegionPos regionPos = new RegionPos(
xRegion + lodDim.getCenterRegionPosX() - Math.floorDiv(lodDim.getWidth(), 2),
zRegion + lodDim.getCenterRegionPosZ() - Math.floorDiv(lodDim.getWidth(), 2));
// local position in the vbo and bufferBuilder arrays
BufferBuilder[] currentBuffers = 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 (currentBuffers == null || !currentBuffers[0].building())
return;
byte minDetail = region.getMinDetailLevel();
final int xR = xRegion;
final int zR = zRegion;
//we create the Callable to use for the buffer builder creation
Callable<Boolean> dataToRenderThread = () ->
{
//Variable initialization
byte detailLevel;
int posX;
int posZ;
int xAdj;
int zAdj;
int bufferIndex;
boolean posNotInPlayerChunk;
boolean adjPosInPlayerChunk;
Box box = ThreadMapUtil.getBox();
boolean[] adjShadeDisabled = ThreadMapUtil.getAdjShadeDisabledArray();
// determine how many LODs we can stack vertically
int maxVerticalData = DetailDistanceUtil.getMaxVerticalData((byte) 0);
//we get or create the map that will contain the adj data
Map<Direction, long[]> adjData = ThreadMapUtil.getAdjDataArray(maxVerticalData);
//previous setToRender cache
if (setsToRender[xR][zR] == null)
setsToRender[xR][zR] = new PosToRenderContainer(minDetail, regionPos.x, regionPos.z);
//We ask the lod dimension which block we have to render given the player position
PosToRenderContainer posToRender = setsToRender[xR][zR];
posToRender.clear(minDetail, regionPos.x, regionPos.z);
lodDim.getPosToRender(
posToRender,
regionPos,
playerBlockPosRounded.getX(),
playerBlockPosRounded.getZ());
// 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++)
{
bufferIndex = Math.floorMod(index, currentBuffers.length);
detailLevel = posToRender.getNthDetailLevel(index);
posX = posToRender.getNthPosX(index);
posZ = posToRender.getNthPosZ(index);
int chunkXdist = LevelPosUtil.getChunkPos(detailLevel, posX) - playerChunkPos.x;
int chunkZdist = LevelPosUtil.getChunkPos(detailLevel, posZ) - playerChunkPos.z;
//We don't want to render this fake block if
//The block is inside the render distance with, is not bigger than a chunk and is positioned in a chunk set as vanilla rendered
//
//The block is in the player chunk or in a chunk adjacent to the player
if(isThisPositionGoingToBeRendered(detailLevel, posX, posZ, playerChunkPos, vanillaRenderedChunks, gameChunkRenderDistance))
{
continue;
}
//we check if the block to render is not in player chunk
posNotInPlayerChunk = !(chunkXdist == 0 && chunkZdist == 0);
// We extract the adj data in the four cardinal direction
// we first reset the adjShadeDisabled. This is used to disable the shade on the border when we have transparent block like water or glass
// to avoid having a "darker border" underground
Arrays.fill(adjShadeDisabled, false);
//We check every adj block in each direction
for (Direction direction : Box.ADJ_DIRECTIONS)
{
xAdj = posX + Box.DIRECTION_NORMAL_MAP.get(direction).getX();
zAdj = posZ + Box.DIRECTION_NORMAL_MAP.get(direction).getZ();
long data;
chunkXdist = LevelPosUtil.getChunkPos(detailLevel, xAdj) - playerChunkPos.x;
chunkZdist = LevelPosUtil.getChunkPos(detailLevel, zAdj) - playerChunkPos.z;
adjPosInPlayerChunk = (chunkXdist == 0 && chunkZdist == 0);
//If the adj block is rendered in the same region and with same detail
// and is positioned in a place that is not going to be rendered by vanilla game
// then we can set this position as adj
// We avoid cases where the adjPosition is in player chunk while the position is not
// to always have a wall underwater
if(posToRender.contains(detailLevel, xAdj, zAdj)
&& !isThisPositionGoingToBeRendered(detailLevel, xAdj, zAdj, playerChunkPos, vanillaRenderedChunks, gameChunkRenderDistance)
&& !(posNotInPlayerChunk && adjPosInPlayerChunk))
{
for (int verticalIndex = 0; verticalIndex < lodDim.getMaxVerticalData(detailLevel, xAdj, zAdj); verticalIndex++)
{
data = lodDim.getData(detailLevel, xAdj, zAdj, verticalIndex);
adjShadeDisabled[Box.DIRECTION_INDEX.get(direction)] = false;
adjData.get(direction)[verticalIndex] = data;
}
}
else
{
//Other wise we check if this position is
data = lodDim.getSingleData(detailLevel, xAdj, zAdj);
adjData.get(direction)[0] = DataPointUtil.EMPTY_DATA;
if ((isThisPositionGoingToBeRendered(detailLevel, xAdj, zAdj, playerChunkPos, vanillaRenderedChunks, gameChunkRenderDistance) || (posNotInPlayerChunk && adjPosInPlayerChunk))
&& !DataPointUtil.isVoid(data))
{
adjShadeDisabled[Box.DIRECTION_INDEX.get(direction)] = DataPointUtil.getAlpha(data) < 255;
}
}
}
// We render every vertical lod present in this position
// We only stop when we find a block that is void or non existing block
long data;
for (int verticalIndex = 0; verticalIndex < lodDim.getMaxVerticalData(detailLevel, posX, posZ); verticalIndex++)
{
//we get the above block as adj UP
if (verticalIndex > 0)
{
adjData.get(Direction.UP)[0] = lodDim.getData(detailLevel, posX, posZ, verticalIndex - 1);
}
else
{
adjData.get(Direction.UP)[0] = DataPointUtil.EMPTY_DATA;
}
//we get the below block as adj DOWN
if (verticalIndex < lodDim.getMaxVerticalData(detailLevel, posX, posZ) - 1)
{
adjData.get(Direction.DOWN)[0] = lodDim.getData(detailLevel, posX, posZ, verticalIndex + 1);
}
else
{
adjData.get(Direction.DOWN)[0] = DataPointUtil.EMPTY_DATA;
}
//We extract the data to render
data = lodDim.getData(detailLevel, posX, posZ, verticalIndex);
//If the data is not renderable (Void or non existing) we stop since there is no data left in this position
if (DataPointUtil.isVoid(data) || !DataPointUtil.doesItExist(data))
break;
//We send the call to create the vertices
LodConfig.CLIENT.graphics.advancedGraphicsOption.lodTemplate.get().template.addLodToBuffer(currentBuffers[bufferIndex], playerBlockPosRounded, data, adjData,
detailLevel, posX, posZ, box, renderer.previousDebugMode, renderer.lightMap, adjShadeDisabled);
}
} // for pos to in list to render
// the thread executed successfully
return true;
};
nodeToRenderThreads.add(dataToRenderThread);
}
} // region z
} // region z
long executeStart = System.currentTimeMillis();
// wait for all threads to finish
List<Future<Boolean>> futuresBuffer = bufferBuilderThreads.invokeAll(nodeToRenderThreads);
for (Future<Boolean> future : futuresBuffer)
{
// the future will be false if its thread failed
if (!future.get())
{
ClientProxy.LOGGER.warn("LodBufferBuilder ran into trouble and had to start over.");
break;
}
}
long executeEnd = System.currentTimeMillis();
long endTime = System.currentTimeMillis();
@SuppressWarnings("unused")
long buildTime = endTime - startTime;
@SuppressWarnings("unused")
long executeTime = executeEnd - executeStart;
// ClientProxy.LOGGER.info("Thread Build time: " + buildTime + " ms" + '\n' +
// "thread execute time: " + executeTime + " ms");
// mark that the buildable buffers as ready to swap
switchVbos = true;
}
catch (Exception e)
{
ClientProxy.LOGGER.warn("\"LodNodeBufferBuilder.generateLodBuffersAsync\" ran into trouble: ");
e.printStackTrace();
}
finally
{
// clean up any potentially open resources
if (buildableBuffers != null)
closeBuffers(fullRegen, lodDim);
try
{
// upload the new buffers
uploadBuffers(fullRegen, lodDim);
}
catch (Exception e)
{
ClientProxy.LOGGER.warn("\"LodNodeBufferBuilder.generateLodBuffersAsync\" was unable to upload the buffers to the GPU: " + e.getMessage());
e.printStackTrace();
}
// regardless of whether we were able to successfully create
// the buffers, we are done generating.
generatingBuffers = false;
bufferLock.unlock();
}
}
private boolean isThisPositionGoingToBeRendered(byte detailLevel, int posX, int posZ, ChunkPos playerChunkPos, boolean[][] vanillaRenderedChunks, int gameChunkRenderDistance){
// skip any chunks that Minecraft is going to render
int chunkXdist = LevelPosUtil.getChunkPos(detailLevel, posX) - playerChunkPos.x;
int chunkZdist = LevelPosUtil.getChunkPos(detailLevel, posZ) - playerChunkPos.z;
// check if the chunk is on the border
boolean isItBorderPos;
if (LodConfig.CLIENT.graphics.advancedGraphicsOption.vanillaOverdraw.get() == VanillaOverdraw.BORDER)
isItBorderPos = LodUtil.isBorderChunk(vanillaRenderedChunks, chunkXdist + gameChunkRenderDistance + 1, chunkZdist + gameChunkRenderDistance + 1);
else
isItBorderPos = false;
//boolean smallRenderDistance = gameChunkRenderDistance <= LodUtil.MINIMUM_RENDER_DISTANCE_FOR_PARTIAL_OVERDRAW;
// get the positions that will be rendered
boolean vanillaRenderedPosition = gameChunkRenderDistance >= Math.abs(chunkXdist)
&& gameChunkRenderDistance >= Math.abs(chunkZdist)
&& detailLevel <= LodUtil.CHUNK_DETAIL_LEVEL
&& vanillaRenderedChunks[chunkXdist + gameChunkRenderDistance + 1][chunkZdist + gameChunkRenderDistance + 1];
return (vanillaRenderedPosition && (!(isItBorderPos)));
}
//===============================//
// 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(LodDimension lodDimension)
{
bufferLock.lock();
int numbRegionsWide = lodDimension.getWidth();
long regionMemoryRequired;
int numberOfBuffers;
previousRegionWidth = numbRegionsWide;
numberOfBuffersPerRegion = new int[numbRegionsWide][numbRegionsWide];
buildableBuffers = new BufferBuilder[numbRegionsWide][numbRegionsWide][];
buildableVbos = new VertexBuffer[numbRegionsWide][numbRegionsWide][];
drawableVbos = new VertexBuffer[numbRegionsWide][numbRegionsWide][];
buildableStorageBufferIds = new int[numbRegionsWide][numbRegionsWide][];
drawableStorageBufferIds = new int[numbRegionsWide][numbRegionsWide][];
bufferPreviousCapacity = new int[numbRegionsWide][numbRegionsWide][];
for (int x = 0; x < numbRegionsWide; x++)
{
for (int z = 0; z < numbRegionsWide; z++)
{
regionMemoryRequired = DEFAULT_MEMORY_ALLOCATION;
// if the memory required is greater than the max buffer
// capacity, divide the memory across multiple buffers
if (regionMemoryRequired > LodUtil.MAX_ALLOCATABLE_DIRECT_MEMORY)
{
numberOfBuffers = (int) Math.ceil(regionMemoryRequired / LodUtil.MAX_ALLOCATABLE_DIRECT_MEMORY) + 1;
// TODO shouldn't this be determined with regionMemoryRequired?
// always allocating the max memory is a bit expensive isn't it?
regionMemoryRequired = LodUtil.MAX_ALLOCATABLE_DIRECT_MEMORY;
numberOfBuffersPerRegion[x][z] = numberOfBuffers;
buildableBuffers[x][z] = new BufferBuilder[numberOfBuffers];
buildableVbos[x][z] = new VertexBuffer[numberOfBuffers];
drawableVbos[x][z] = new VertexBuffer[numberOfBuffers];
buildableStorageBufferIds[x][z] = new int[numberOfBuffers];
drawableStorageBufferIds[x][z] = new int[numberOfBuffers];
bufferPreviousCapacity[x][z] = new int[numberOfBuffers];
}
else
{
// we only need one buffer for this region
numberOfBuffersPerRegion[x][z] = 1;
buildableBuffers[x][z] = new BufferBuilder[1];
buildableVbos[x][z] = new VertexBuffer[1];
drawableVbos[x][z] = new VertexBuffer[1];
buildableStorageBufferIds[x][z] = new int[1];
drawableStorageBufferIds[x][z] = new int[1];
bufferPreviousCapacity[x][z] = new int[1];
}
for (int i = 0; i < numberOfBuffersPerRegion[x][z]; i++)
{
bufferPreviousCapacity[x][z][i] = (int) regionMemoryRequired;
buildableBuffers[x][z][i] = new BufferBuilder((int) regionMemoryRequired);
buildableVbos[x][z][i] = new VertexBuffer(LodUtil.LOD_VERTEX_FORMAT);
drawableVbos[x][z][i] = new VertexBuffer(LodUtil.LOD_VERTEX_FORMAT);
// create the initial mapped buffers (system memory)
GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, buildableVbos[x][z][i].id);
GL15.glBufferData(GL15.GL_ARRAY_BUFFER, regionMemoryRequired, GL45.GL_DYNAMIC_DRAW);
GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, drawableVbos[x][z][i].id);
GL15.glBufferData(GL15.GL_ARRAY_BUFFER, regionMemoryRequired, GL45.GL_DYNAMIC_DRAW);
GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
// create the buffer storage (GPU memory)
buildableStorageBufferIds[x][z][i] = GL45.glGenBuffers();
GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, buildableStorageBufferIds[x][z][i]);
GL45.glBufferStorage(GL15.GL_ARRAY_BUFFER, regionMemoryRequired, 0); // the 0 flag means to create the storage in the GPUs memory
GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
drawableStorageBufferIds[x][z][i] = GL45.glGenBuffers();
GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, drawableStorageBufferIds[x][z][i]);
GL45.glBufferStorage(GL15.GL_ARRAY_BUFFER, regionMemoryRequired, 0);
GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
}
}
}
bufferLock.unlock();
}
/**
* Sets the buffers and Vbos to null, forcing them to be recreated <br>
* and destroys any bound OpenGL objects. <br><br>
* <p>
* May have to wait for the bufferLock to open.
*/
public void destroyBuffers()
{
bufferLock.lock();
// destroy the buffer storages if they aren't already
if (buildableStorageBufferIds != null)
{
for (int x = 0; x < buildableStorageBufferIds.length; x++)
{
for (int z = 0; z < buildableStorageBufferIds.length; z++)
{
for (int i = 0; i < buildableStorageBufferIds[x][z].length; i++)
{
int buildableId = buildableStorageBufferIds[x][z][i];
int drawableId = drawableStorageBufferIds[x][z][i];
// Send this over to the render thread, if this is being
// called we aren't worried about stuttering anyway.
// This way we don't have to worry about what context this
// was called from (if any).
RenderSystem.recordRenderCall(() ->
{
GL45.glDeleteBuffers(buildableId);
GL45.glDeleteBuffers(drawableId);
});
}
}
}
}
buildableStorageBufferIds = null;
drawableStorageBufferIds = null;
// destroy the VBOs if they aren't already
if (buildableVbos != null)
{
for (int i = 0; i < buildableVbos.length; i++)
{
for (int j = 0; j < buildableVbos.length; j++)
{
for (int k = 0; k < buildableVbos[i][j].length; k++)
{
int buildableId;
int drawableId;
// variables passed into a lambda expression
// need to be effectively final, so we have
// to use an else statement here
if (buildableVbos[i][j][k] != null)
buildableId = buildableVbos[i][j][k].id;
else
buildableId = 0;
if (drawableVbos[i][j][k] != null)
drawableId = drawableVbos[i][j][k].id;
else
drawableId = 0;
RenderSystem.recordRenderCall(() ->
{
if (buildableId != 0)
GL45.glDeleteBuffers(buildableId);
if (drawableId != 0)
GL45.glDeleteBuffers(drawableId);
});
}
}
}
}
buildableVbos = null;
drawableVbos = null;
// these don't contain any OpenGL objects, so
// they don't require any special clean-up
buildableBuffers = null;
bufferLock.unlock();
}
/** Calls begin on each of the buildable BufferBuilders. */
private void startBuffers(boolean fullRegen, LodDimension lodDim)
{
for (int x = 0; x < buildableBuffers.length; x++)
{
for (int z = 0; z < buildableBuffers.length; z++)
{
if (fullRegen || lodDim.doesRegionNeedBufferRegen(x, z))
{
for (int i = 0; i < buildableBuffers[x][z].length; i++)
{
// for some reason BufferBuilder.vertexCounts
// isn't reset unless this is called, which can cause
// a false indexOutOfBoundsException
buildableBuffers[x][z][i].discard();
buildableBuffers[x][z][i].begin(GL11.GL_QUADS, LodUtil.LOD_VERTEX_FORMAT);
}
}
}
}
}
/** Calls end on each of the buildable BufferBuilders. */
private void closeBuffers(boolean fullRegen, LodDimension lodDim)
{
for (int x = 0; x < buildableBuffers.length; x++)
for (int z = 0; z < buildableBuffers.length; z++)
for (int i = 0; i < buildableBuffers[x][z].length; i++)
if (buildableBuffers[x][z][i] != null && buildableBuffers[x][z][i].building() && (fullRegen || lodDim.doesRegionNeedBufferRegen(x, z)))
buildableBuffers[x][z][i].end();
}
/** Upload all buildableBuffers to the GPU. */
private void uploadBuffers(boolean fullRegen, LodDimension lodDim)
{
GlProxy glProxy = GlProxy.getInstance();
try
{
// make sure we are uploading to the builder context,
// this helps prevent interference (IE stuttering) with the Minecraft context.
glProxy.setGlContext(GlProxyContext.LOD_BUILDER);
// actually upload the buffers
for (int x = 0; x < buildableVbos.length; x++)
{
for (int z = 0; z < buildableVbos.length; z++)
{
if (fullRegen || lodDim.doesRegionNeedBufferRegen(x, z))
{
for (int i = 0; i < buildableBuffers[x][z].length; i++)
{
ByteBuffer uploadBuffer = buildableBuffers[x][z][i].popNextBuffer().getSecond();
vboUpload(buildableVbos[x][z][i], buildableStorageBufferIds[x][z][i], uploadBuffer, x, z, i, true);
lodDim.setRegenRegionBufferByArrayIndex(x, z, false);
}
}
}
}
}
catch (Exception e)
{
// this doesn't appear to be necessary anymore, but just in case.
ClientProxy.LOGGER.error(LodBufferBuilder.class.getSimpleName() + " - UploadBuffers failed: " + e.getMessage());
e.printStackTrace();
}
finally
{
GL11.glFinish();
// close the context so it can be re-used later.
// I'm guessing we can't just leave it because the executor service
// does something that invalidates the OpenGL context.
glProxy.setGlContext(GlProxyContext.NONE);
}
}
/** Uploads the uploadBuffer into the VBO and then into GPU memory. */
private void vboUpload(VertexBuffer vbo, int storageBufferId, ByteBuffer uploadBuffer,
int xVboIndex, int zVboIndex, int iVboIndex, boolean allowBufferExpansion)
// x/zVboIndex are just used for the debugging console logging
// and should be removed when the logger is removed.
{
// 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.capacity() / vbo.format.getVertexSize());
GL45.glBindBuffer(GL45.GL_ARRAY_BUFFER, vbo.id);
try
{
// get a pointer to the buffer in system memory
ByteBuffer vboBuffer = GL45.glMapBufferRange(GL45.GL_ARRAY_BUFFER, 0, uploadBuffer.capacity(), GL45.GL_MAP_WRITE_BIT | GL45.GL_MAP_UNSYNCHRONIZED_BIT);
if (vboBuffer == null)
{
int previousCapacity = uploadBuffer.capacity();
// only expand the buffers if the uploadBuffer actually
// has something in it and expansion is allowed
if (previousCapacity != 0 && allowBufferExpansion)
{
// the buffer(s) aren't big enough, expand them.
// This does cause lag/stuttering, so it should be avoided!
// expand the buffer in system memory
GL45.glBufferData(GL45.GL_ARRAY_BUFFER, (int) (uploadBuffer.capacity() * BUFFER_EXPANSION_MULTIPLIER), GL45.GL_DYNAMIC_DRAW);
GL45.glBufferSubData(GL45.GL_ARRAY_BUFFER, 0, uploadBuffer);
// un-bind the system memory buffer
GL15.glUnmapBuffer(GL15.GL_ARRAY_BUFFER);
GL15C.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
// expand the buffer storage
GL45.glDeleteBuffers(storageBufferId);
GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, storageBufferId);
GL45.glBufferStorage(GL15.GL_ARRAY_BUFFER, (int) (uploadBuffer.capacity() * BUFFER_EXPANSION_MULTIPLIER), 0);
GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
// recursively try to upload into the newly created buffer storage
// but don't recurse again if that fails
// (we don't want an infinitely expanding buffer!)
vboUpload(vbo, storageBufferId, uploadBuffer, xVboIndex, zVboIndex, iVboIndex, false);
/*if (printExpansionLog)
{
// NOTE: this will display twice because we are double buffering
// (using 1 buffer to generate into and one to draw)
ClientProxy.LOGGER.info("vbo (" + xVboIndex + "," + zVboIndex + ") expanded: " + bufferPreviousCapacity[xVboIndex][zVboIndex][iVboIndex] + " -> " + (int)(uploadBuffer.capacity() * BUFFER_EXPANSION_MULTIPLIER));
bufferPreviousCapacity[xVboIndex][zVboIndex][iVboIndex] = (int) (uploadBuffer.capacity() * BUFFER_EXPANSION_MULTIPLIER);
}*/
}
}
else
{
// upload the buffer into system memory...
vboBuffer.put(uploadBuffer);
GL15.glUnmapBuffer(GL15.GL_ARRAY_BUFFER);
// ...then upload into GPU memory
// (uploading into GPU memory directly can only be done
// through the glCopyBufferSubData/glCopyNamed... methods)
GL45.glCopyNamedBufferSubData(vbo.id, storageBufferId, 0, 0, uploadBuffer.capacity());
}
}
catch (Exception e)
{
ClientProxy.LOGGER.error("vboUpload failed: " + e.getClass().getSimpleName());
e.printStackTrace();
}
finally
{
GL15C.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
}
}
}
/** Get the newly created VBOs */
public VertexBuffersAndOffset getVertexBuffers()
{
// don't wait for the lock to open,
// since this is called on the main render thread
if (bufferLock.tryLock())
{
VertexBuffer[][][] tmpVbo = drawableVbos;
drawableVbos = buildableVbos;
buildableVbos = tmpVbo;
int[][][] tmpStorage = drawableStorageBufferIds;
drawableStorageBufferIds = buildableStorageBufferIds;
buildableStorageBufferIds = tmpStorage;
// we only want to print the expansion log for
// one set of buffers, not both
printExpansionLog = !printExpansionLog;
drawableCenterChunkPos = buildableCenterChunkPos;
// the vbos have been swapped
switchVbos = false;
bufferLock.unlock();
}
return new VertexBuffersAndOffset(drawableVbos, drawableStorageBufferIds, drawableCenterChunkPos);
}
/** A simple container to pass multiple objects back in the getVertexBuffers method. */
public static class VertexBuffersAndOffset
{
public final VertexBuffer[][][] vbos;
public final int[][][] storageBufferIds;
public final ChunkPos drawableCenterChunkPos;
public VertexBuffersAndOffset(VertexBuffer[][][] newVbos, int[][][] newStorageBufferIds, ChunkPos newDrawableCenterChunkPos)
{
vbos = newVbos;
storageBufferIds = newStorageBufferIds;
drawableCenterChunkPos = newDrawableCenterChunkPos;
}
}
/**
* If this is true the buildable near and far
* buffers have been generated and are ready to be
* sent to the LodRenderer.
*/
public boolean newBuffersAvailable()
{
return switchVbos;
}
}
@@ -1,5 +1,6 @@
/*
* This file is part of the LOD Mod, licensed under the GNU GPL v3 License.
* This file is part of the Distant Horizon mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020 James Seibel
*
@@ -15,7 +16,8 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.lod.builders.lodTemplates;
package com.seibel.lod.builders.bufferBuilding.lodTemplates;
import java.util.Map;
@@ -30,32 +32,23 @@ import net.minecraft.util.math.BlockPos;
/**
* This is the abstract class used to create different
* BufferBuilders.
*
* @author James Seibel
* @version 8-8-2021
* @version 10-10-2021
*/
public abstract class AbstractLodTemplate
{
/**
* Uploads the given LOD to the buffer.
*/
/** Uploads the given LOD to the buffer. */
public abstract void addLodToBuffer(BufferBuilder buffer, BlockPos bufferCenterBlockPos, long data, Map<Direction, long[]> adjData,
byte detailLevel, int posX, int posZ, Box box, DebugMode debugging, NativeImage lightMap);
byte detailLevel, int posX, int posZ, Box box, DebugMode debugging, NativeImage lightMap, boolean[] adjShadeDisabled);
/**
* add the given position and color to the buffer
*/
/** add the given position and color to the buffer */
protected void addPosAndColor(BufferBuilder buffer,
double x, double y, double z,
int color)
{
buffer.vertex(x, y, z).color(ColorUtil.getRed(color), ColorUtil.getGreen(color), ColorUtil.getBlue(color), ColorUtil.getAlpha(color)).endVertex();
// TODO re-add transparency by replacing the 255 with "ColorUtil.getAlpha(color)"
buffer.vertex(x, y, z).color(ColorUtil.getRed(color), ColorUtil.getGreen(color), ColorUtil.getBlue(color), 255).endVertex();
}
/**
* Returns in bytes how much buffer memory is required
* for one LOD object
*/
public abstract int getBufferMemoryForSingleNode(int maxVerticalData);
}
@@ -0,0 +1,593 @@
/*
* This file is part of the Distant Horizon mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.lod.builders.bufferBuilding.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.util.LodUtil;
import com.seibel.lod.wrappers.MinecraftWrapper;
import net.minecraft.util.Direction;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.vector.Vector3i;
/**
* Similar to Minecraft's AxisAlignedBoundingBox.
* @author Leonardo Amato
* @version 10-2-2021
*/
public class Box
{
public static final int ADJACENT_HEIGHT_INDEX = 0;
public static final int ADJACENT_DEPTH_INDEX = 1;
public static final int X = 0;
public static final int Y = 1;
public static final int Z = 2;
public static final int MIN = 0;
public static final int MAX = 1;
public static final int VOID_FACE = 0;
/** The six cardinal directions */
public static final Direction[] DIRECTIONS = new Direction[] {
Direction.UP,
Direction.DOWN,
Direction.WEST,
Direction.EAST,
Direction.NORTH,
Direction.SOUTH };
/** North, South, East, West */
public static final Direction[] ADJ_DIRECTIONS = new Direction[] {
Direction.EAST,
Direction.WEST,
Direction.SOUTH,
Direction.NORTH };
/** All the faces and vertices of a cube. This is used to extract the vertex from the column */
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 } });
}};
/**
* This indicates which position is invariable in the DIRECTION_VERTEX_MAP.
* Is used to extract the vertex
*/
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 });
}};
/**
* This is a map from Direction to the relative normal vector
* we are using this since I'm not sure if the getNormal create new object at every call
*/
public static final Map<Direction, Vector3i> DIRECTION_NORMAL_MAP = new HashMap<Direction, Vector3i>()
{{
put(Direction.UP, Direction.UP.getNormal());
put(Direction.DOWN, Direction.DOWN.getNormal());
put(Direction.EAST, Direction.EAST.getNormal());
put(Direction.WEST, Direction.WEST.getNormal());
put(Direction.SOUTH, Direction.SOUTH.getNormal());
put(Direction.NORTH, Direction.NORTH.getNormal());
}};
/** We use this index for all array that are going to */
public static final Map<Direction, Integer> DIRECTION_INDEX = new HashMap<Direction, Integer>()
{{
put(Direction.UP, 0);
put(Direction.DOWN, 1);
put(Direction.EAST, 2);
put(Direction.WEST, 3);
put(Direction.SOUTH, 4);
put(Direction.NORTH, 5);
}};
public static final Map<Direction, Integer> ADJ_DIRECTION_INDEX = new HashMap<Direction, Integer>()
{{
put(Direction.EAST, 0);
put(Direction.WEST, 1);
put(Direction.SOUTH, 2);
put(Direction.NORTH, 3);
}};
/** holds the box's x, y, z offset */
public final int[] boxOffset;
/** holds the box's x, y, z width */
public final int[] boxWidth;
/** Holds each direction's color */
public final int[] colorMap;
/** The original color (before shading) of this box */
public int color;
/**
*
*/
public final Map<Direction, int[]> adjHeight;
public final Map<Direction, int[]> adjDepth;
public final Map<Direction, byte[]> skyLights;
public byte blockLight;
/** Holds if the given direction should be culled or not */
public final boolean[] culling;
/** creates an empty box */
public Box()
{
boxOffset = new int[3];
boxWidth = new int[3];
colorMap = new int[6];
skyLights = new HashMap<Direction, byte[]>()
{{
put(Direction.UP, new byte[1]);
put(Direction.DOWN, new byte[1]);
put(Direction.EAST, new byte[LodUtil.MAX_NUMBER_OF_VERTICAL_LODS]);
put(Direction.WEST, new byte[LodUtil.MAX_NUMBER_OF_VERTICAL_LODS]);
put(Direction.SOUTH, new byte[LodUtil.MAX_NUMBER_OF_VERTICAL_LODS]);
put(Direction.NORTH, new byte[LodUtil.MAX_NUMBER_OF_VERTICAL_LODS]);
}};
adjHeight = new HashMap<Direction, int[]>()
{{
put(Direction.EAST, new int[LodUtil.MAX_NUMBER_OF_VERTICAL_LODS]);
put(Direction.WEST, new int[LodUtil.MAX_NUMBER_OF_VERTICAL_LODS]);
put(Direction.SOUTH, new int[LodUtil.MAX_NUMBER_OF_VERTICAL_LODS]);
put(Direction.NORTH, new int[LodUtil.MAX_NUMBER_OF_VERTICAL_LODS]);
}};
adjDepth = new HashMap<Direction, int[]>()
{{
put(Direction.EAST, new int[LodUtil.MAX_NUMBER_OF_VERTICAL_LODS]);
put(Direction.WEST, new int[LodUtil.MAX_NUMBER_OF_VERTICAL_LODS]);
put(Direction.SOUTH, new int[LodUtil.MAX_NUMBER_OF_VERTICAL_LODS]);
put(Direction.NORTH, new int[LodUtil.MAX_NUMBER_OF_VERTICAL_LODS]);
}};
culling = new boolean[6];
}
/** Set the light of the columns */
public void setLights(int skyLight, int blockLight)
{
this.blockLight = (byte) blockLight;
skyLights.get(Direction.UP)[0] = (byte) skyLight;
}
/**
* Set the color of the columns
* @param color color to add
* @param adjShadeDisabled this array indicates which face does not need shading
*/
public void setColor(int color, boolean[] adjShadeDisabled)
{
this.color = color;
for (Direction direction : DIRECTIONS)
{
if (!adjShadeDisabled[DIRECTION_INDEX.get(direction)])
colorMap[DIRECTION_INDEX.get(direction)] = ColorUtil.applyShade(color, MinecraftWrapper.INSTANCE.getClientWorld().getShade(direction, true));
else
colorMap[DIRECTION_INDEX.get(direction)] = color;
}
}
/**
* @param direction of the face of which we want to get the color
* @return color of the face
*/
public int getColor(Direction direction)
{
if (LodConfig.CLIENT.advancedModOptions.debugging.debugMode.get() != DebugMode.SHOW_DETAIL)
return colorMap[DIRECTION_INDEX.get(direction)];
else
return ColorUtil.applyShade(color, MinecraftWrapper.INSTANCE.getClientWorld().getShade(direction, true));
}
/**
*/
public byte getSkyLight(Direction direction, int verticalIndex)
{
if(direction == Direction.UP || direction == Direction.DOWN)
return skyLights.get(direction)[0];
else
return skyLights.get(direction)[verticalIndex];
}
/**
*/
public int getBlockLight()
{
return blockLight;
}
/** clears this box, resetting everything to default values */
public void reset()
{
Arrays.fill(boxWidth, 0);
Arrays.fill(boxOffset, 0);
Arrays.fill(colorMap, 0);
blockLight = 0;
for (Direction direction : ADJ_DIRECTIONS)
{
for (int i = 0; i < adjHeight.get(direction).length; i++)
{
adjHeight.get(direction)[i] = VOID_FACE;
adjDepth.get(direction)[i] = VOID_FACE;
skyLights.get(direction)[i] = 0;
}
}
}
/** determine which faces should be culled */
public void setUpCulling(int cullingDistance, BlockPos playerPos)
{
for (Direction direction : DIRECTIONS)
{
if (direction == Direction.DOWN || direction == Direction.WEST || direction == Direction.NORTH)
culling[DIRECTION_INDEX.get(direction)] = playerPos.get(direction.getAxis()) > getFacePos(direction) + cullingDistance;
else if (direction == Direction.UP || direction == Direction.EAST || direction == Direction.SOUTH)
culling[DIRECTION_INDEX.get(direction)] = playerPos.get(direction.getAxis()) < getFacePos(direction) - cullingDistance;
culling[DIRECTION_INDEX.get(direction)] = false;
}
}
/**
* @param direction direction that we want to check if it's culled
* @return true if and only if the face of the direction is culled
*/
public boolean isCulled(Direction direction)
{
return culling[DIRECTION_INDEX.get(direction)];
}
/**
* This method create all the shared face culling based on the adjacent data
* @param adjData data adjacent to the column we are going to render
*/
public void setAdjData(Map<Direction, long[]> adjData)
{
int height;
int depth;
int minY = getMinY();
int maxY = getMaxY();
long singleAdjDataPoint;
/* TODO implement attached vertical face culling
//Up direction case
if(DataPointUtil.doesItExist(adjData.get(Direction.UP)))
{
height = DataPointUtil.getHeight(singleAdjDataPoint);
depth = DataPointUtil.getDepth(singleAdjDataPoint);
}*/
//Down direction case
singleAdjDataPoint = adjData.get(Direction.DOWN)[0];
if(DataPointUtil.doesItExist(singleAdjDataPoint))
skyLights.get(Direction.DOWN)[0] = (byte) DataPointUtil.getLightSkyAlt(singleAdjDataPoint);
else
skyLights.get(Direction.DOWN)[0] = skyLights.get(Direction.UP)[0];
//other sided
//TODO clean some similar cases
for (Direction direction : ADJ_DIRECTIONS)
{
if (isCulled(direction))
continue;
long[] dataPoint = adjData.get(direction);
if (dataPoint == null || DataPointUtil.isVoid(dataPoint[0]))
{
adjHeight.get(direction)[0] = maxY;
adjDepth.get(direction)[0] = minY;
adjHeight.get(direction)[1] = VOID_FACE;
adjDepth.get(direction)[1] = VOID_FACE;
skyLights.get(direction)[0] = 15; //in void set full sky light
continue;
}
int i;
int faceToDraw = 0;
boolean firstFace = true;
boolean toFinish = false;
int toFinishIndex = 0;
boolean allAbove = true;
for (i = 0; i < dataPoint.length; i++)
{
singleAdjDataPoint = dataPoint[i];
if (DataPointUtil.isVoid(singleAdjDataPoint) || !DataPointUtil.doesItExist(singleAdjDataPoint))
break;
height = DataPointUtil.getHeight(singleAdjDataPoint);
depth = DataPointUtil.getDepth(singleAdjDataPoint);
if (depth <= maxY)
{
allAbove = false;
if (height < minY)
{
// the adj data is lower than the current data
if (firstFace)
{
adjHeight.get(direction)[0] = getMaxY();
adjDepth.get(direction)[0] = getMinY();
skyLights.get(direction)[0] = skyLights.get(Direction.UP)[0];
}
else
{
adjDepth.get(direction)[faceToDraw] = getMinY();
skyLights.get(direction)[faceToDraw] = (byte) DataPointUtil.getLightSkyAlt(singleAdjDataPoint);
}
faceToDraw++;
toFinish = false;
// break since all the other data will be lower
break;
}
else if (depth <= minY && height >= maxY)
{
// the adj data is inside the current data
// don't draw the face
adjHeight.get(direction)[0] = VOID_FACE;
adjDepth.get(direction)[0] = VOID_FACE;
break;
}
else if (depth <= minY)//&& height < maxY
{
// the adj data intersects the lower part of the current data
// if this is the only face, use the maxY and break,
// if there was another face we finish the last one and break
if (firstFace)
{
adjHeight.get(direction)[0] = getMaxY();
adjDepth.get(direction)[0] = height;
skyLights.get(direction)[0] = skyLights.get(Direction.UP)[0];
}
else
{
adjDepth.get(direction)[faceToDraw] = height;
skyLights.get(direction)[faceToDraw] = (byte) DataPointUtil.getLightSkyAlt(singleAdjDataPoint);
}
toFinish = false;
faceToDraw++;
break;
}
else if (height >= maxY)//depth > minY &&
{
// the adj data intersects the higher part of the current data
// we start the creation of a new face
adjHeight.get(direction)[faceToDraw] = depth;
//skyLights.get(direction)[faceToDraw] = (byte) DataPointUtil.getLightSkyAlt(singleAdjDataPoint);
firstFace = false;
toFinish = true;
toFinishIndex = i + 1;
}
else
{
// if (depth > minY && height < maxY)
// the adj data is contained in the current data
if (firstFace)
{
adjHeight.get(direction)[0] = getMaxY();
}
adjDepth.get(direction)[faceToDraw] = height;
skyLights.get(direction)[faceToDraw] = (byte) DataPointUtil.getLightSkyAlt(singleAdjDataPoint);
faceToDraw++;
adjHeight.get(direction)[faceToDraw] = depth;
firstFace = false;
toFinish = true;
toFinishIndex = i + 1;
}
}
}
if (allAbove)
{
adjHeight.get(direction)[0] = getMaxY();
adjDepth.get(direction)[0] = getMinY();
skyLights.get(direction)[0] = skyLights.get(Direction.UP)[0];
faceToDraw++;
}
else if (toFinish)
{
adjDepth.get(direction)[faceToDraw] = minY;
if(toFinishIndex < dataPoint.length)
{
singleAdjDataPoint = dataPoint[toFinishIndex];
if (DataPointUtil.doesItExist(singleAdjDataPoint))
skyLights.get(direction)[faceToDraw] = (byte) DataPointUtil.getLightSkyAlt(singleAdjDataPoint);
else
skyLights.get(direction)[faceToDraw] = skyLights.get(Direction.UP)[0];
}
faceToDraw++;
}
adjHeight.get(direction)[faceToDraw] = VOID_FACE;
adjDepth.get(direction)[faceToDraw] = VOID_FACE;
}
}
/** We use this method to set the various width of the column */
public void setWidth(int xWidth, int yWidth, int zWidth)
{
boxWidth[X] = xWidth;
boxWidth[Y] = yWidth;
boxWidth[Z] = zWidth;
}
/** We use this method to set the various offset of the column */
public void setOffset(int xOffset, int yOffset, int zOffset)
{
boxOffset[X] = xOffset;
boxOffset[Y] = yOffset;
boxOffset[Z] = zOffset;
}
/**
* This method return the position of a face in the axis of the face
* This is useful for the face culling
* @param direction that we want to check
* @return position in the axis of the face
*/
public int getFacePos(Direction direction)
{
return boxOffset[FACE_DIRECTION.get(direction)[0]] + boxWidth[FACE_DIRECTION.get(direction)[0]] * FACE_DIRECTION.get(direction)[1];
}
/**
* returns true if the given direction should be rendered.
*/
public boolean shouldRenderFace(Direction direction, int adjIndex)
{
if (direction == Direction.UP || direction == Direction.DOWN)
return adjIndex == 0;
return !(adjHeight.get(direction)[adjIndex] == VOID_FACE && adjDepth.get(direction)[adjIndex] == VOID_FACE);
}
/**
* @param direction direction of the face we want to render
* @param vertexIndex index of the vertex of the face (0-1-2-3)
* @return position x of the relative vertex
*/
public int getX(Direction direction, int vertexIndex)
{
return boxOffset[X] + boxWidth[X] * DIRECTION_VERTEX_MAP.get(direction)[vertexIndex][X];
}
/**
* @param direction direction of the face we want to render
* @param vertexIndex index of the vertex of the face (0-1-2-3)
* @return position y of the relative vertex
*/
public int getY(Direction direction, int vertexIndex)
{
return boxOffset[Y] + boxWidth[Y] * DIRECTION_VERTEX_MAP.get(direction)[vertexIndex][Y];
}
/**
* @param direction direction of the face we want to render
* @param vertexIndex index of the vertex of the face (0-1-2-3)
* @param adjIndex, index of the n-th culled face of this direction
* @return position x of the relative vertex
*/
public int getY(Direction direction, int vertexIndex, int adjIndex)
{
if (direction == Direction.DOWN || direction == Direction.UP)
return boxOffset[Y] + boxWidth[Y] * DIRECTION_VERTEX_MAP.get(direction)[vertexIndex][Y];
else
{
// this could probably be cleaned up more,
// but it still works
if (1 - DIRECTION_VERTEX_MAP.get(direction)[vertexIndex][Y] == ADJACENT_HEIGHT_INDEX)
return adjHeight.get(direction)[adjIndex];
else
return adjDepth.get(direction)[adjIndex];
}
}
/**
* @param direction direction of the face we want to render
* @param vertexIndex index of the vertex of the face (0-1-2-3)
* @return position z of the relative vertex
*/
public int getZ(Direction direction, int vertexIndex)
{
return boxOffset[Z] + boxWidth[Z] * DIRECTION_VERTEX_MAP.get(direction)[vertexIndex][Z];
}
public int getMinX()
{
return boxOffset[X];
}
public int getMaxX()
{
return boxOffset[X] + boxWidth[X];
}
public int getMinY()
{
return boxOffset[Y];
}
public int getMaxY()
{
return boxOffset[Y] + boxWidth[Y];
}
public int getMinZ()
{
return boxOffset[Z];
}
public int getMaxZ()
{
return boxOffset[Z] + boxWidth[Z];
}
}
@@ -1,5 +1,6 @@
/*
* This file is part of the LOD Mod, licensed under the GNU GPL v3 License.
* This file is part of the Distant Horizon mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020 James Seibel
*
@@ -15,13 +16,16 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.lod.builders.lodTemplates;
package com.seibel.lod.builders.bufferBuilding.lodTemplates;
import java.util.Map;
import com.seibel.lod.enums.DebugMode;
import com.seibel.lod.util.ColorUtil;
import com.seibel.lod.util.DataPointUtil;
import com.seibel.lod.util.LodUtil;
import com.seibel.lod.wrappers.MinecraftWrapper;
import net.minecraft.client.renderer.BufferBuilder;
import net.minecraft.client.renderer.texture.NativeImage;
@@ -30,21 +34,20 @@ import net.minecraft.util.math.BlockPos;
/**
* Builds LODs as rectangular prisms.
*
* @author James Seibel
* @version 9-25-2021
* @version 10-10-2021
*/
public class CubicLodTemplate extends AbstractLodTemplate
{
public CubicLodTemplate()
{
}
@Override
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)
byte detailLevel, int posX, int posZ, Box box, DebugMode debugging, NativeImage lightMap, boolean[] adjShadeDisabled)
{
if (box == null)
return;
@@ -56,7 +59,7 @@ public class CubicLodTemplate extends AbstractLodTemplate
if (debugging != DebugMode.OFF)
color = LodUtil.DEBUG_DETAIL_LEVEL_COLORS[detailLevel].getRGB();
else
color = DataPointUtil.getLightColor(data, lightMap);
color = DataPointUtil.getColor(data);
generateBoundingBox(
@@ -67,36 +70,41 @@ public class CubicLodTemplate extends AbstractLodTemplate
posX * blockWidth, 0, posZ * blockWidth, // x, y, z offset
bufferCenterBlockPos,
adjData,
color);
color,
DataPointUtil.getLightSkyAlt(data),
DataPointUtil.getLightBlock(data),
adjShadeDisabled);
addBoundingBoxToBuffer(buffer, box);
}
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)
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,
int skyLight,
int blockLight,
boolean[] adjShadeDisabled)
{
// don't add an LOD if it is empty
if (height == -1 && depth == -1)
return;
if (depth == height)
{
// if the top and bottom points are at the same height
// render this LOD as 1 block thick
height++;
}
// offset the AABB by it's x/z position in the world since
// offset the AABB by its 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.setColor(color, adjShadeDisabled);
box.setLights(skyLight, blockLight);
box.setWidth(width, height - depth, width);
box.setOffset((int) (xOffset + x), (int) (depth + yOffset), (int) (zOffset + z));
box.setUpCulling(32, bufferCenterBlockPos);
@@ -105,30 +113,31 @@ public class CubicLodTemplate extends AbstractLodTemplate
private void addBoundingBoxToBuffer(BufferBuilder buffer, Box box)
{
int color;
int skyLight;
int blockLight;
for (Direction direction : Box.DIRECTIONS)
{
// TODO what does adjacentIndex mean?
int adjIndex = 0;
while (box.shouldRenderFace(direction, adjIndex))
if(box.isCulled(direction))
continue;
int verticalFaceIndex = 0;
while (box.shouldRenderFace(direction, verticalFaceIndex))
{
for (int vertexIndex = 0; vertexIndex < 4; vertexIndex++)
{
color = box.getColor(direction);
skyLight = box.getSkyLight(direction, verticalFaceIndex);
blockLight = box.getBlockLight();
color = ColorUtil.applyLightValue(color, skyLight, blockLight, MinecraftWrapper.INSTANCE.getCurrentLightMap());
addPosAndColor(buffer,
box.getX(direction, vertexIndex),
box.getY(direction, vertexIndex, adjIndex),
box.getY(direction, vertexIndex, verticalFaceIndex),
box.getZ(direction, vertexIndex),
box.getColor(direction));
color);
}
adjIndex++;
verticalFaceIndex++;
}
}
}
@Override
public int getBufferMemoryForSingleNode(int maxVerticalData)
{
// TODO, what do these magic numbers mean
return 2 * 4 * (3 + 4) + 4 * 4 * Math.max((maxVerticalData+1)/2,1) * (3 + 4);
}
}
@@ -1,5 +1,6 @@
/*
* This file is part of the LOD Mod, licensed under the GNU GPL v3 License.
* This file is part of the Distant Horizon mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020 James Seibel
*
@@ -15,7 +16,8 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.lod.builders.lodTemplates;
package com.seibel.lod.builders.bufferBuilding.lodTemplates;
import java.util.Map;
@@ -32,7 +34,6 @@ import net.minecraft.util.math.BlockPos;
* Chunks smoothly transition between
* each other, unless a neighboring chunk
* is at a significantly different height.
*
* @author James Seibel
* @version 06-16-2021
*/
@@ -40,14 +41,9 @@ public class DynamicLodTemplate extends AbstractLodTemplate
{
@Override
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)
byte detailLevel, int posX, int posZ, Box box, DebugMode debugging, NativeImage lightMap, boolean[] adjShadeDisabled)
{
ClientProxy.LOGGER.error(DynamicLodTemplate.class.getSimpleName() + " is not implemented!");
}
@Override
public int getBufferMemoryForSingleNode(int maxVerticalData)
{
return 0;
}
}
@@ -1,5 +1,6 @@
/*
* This file is part of the LOD Mod, licensed under the GNU GPL v3 License.
* This file is part of the Distant Horizon mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020 James Seibel
*
@@ -15,7 +16,8 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.lod.builders.lodTemplates;
package com.seibel.lod.builders.bufferBuilding.lodTemplates;
import java.util.Map;
@@ -30,7 +32,6 @@ import net.minecraft.util.math.BlockPos;
/**
* TODO #21 TriangularLodTemplate
* Builds each LOD chunk as a singular rectangular prism.
*
* @author James Seibel
* @version 06-16-2021
*/
@@ -38,14 +39,9 @@ public class TriangularLodTemplate extends AbstractLodTemplate
{
@Override
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)
byte detailLevel, int posX, int posZ, Box box, DebugMode debugging, NativeImage lightMap, boolean[] adjShadeDisabled)
{
ClientProxy.LOGGER.error(DynamicLodTemplate.class.getSimpleName() + " is not implemented!");
}
@Override
public int getBufferMemoryForSingleNode(int maxVerticalData)
{
return 0;
}
}
@@ -1,5 +1,6 @@
/*
* This file is part of the LOD Mod, licensed under the GNU GPL v3 License.
* This file is part of the Distant Horizon mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020 James Seibel
*
@@ -15,7 +16,8 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.lod.builders;
package com.seibel.lod.builders.lodBuilding;
import java.awt.Color;
import java.util.List;
@@ -50,12 +52,16 @@ import net.minecraft.block.BushBlock;
import net.minecraft.block.FlowerBlock;
import net.minecraft.block.GrassBlock;
import net.minecraft.block.IGrowable;
import net.minecraft.block.ILiquidContainer;
import net.minecraft.block.IWaterLoggable;
import net.minecraft.block.LeavesBlock;
import net.minecraft.block.SixWayBlock;
import net.minecraft.block.TallGrassBlock;
import net.minecraft.block.material.MaterialColor;
import net.minecraft.client.renderer.model.BakedQuad;
import net.minecraft.client.renderer.texture.TextureAtlasSprite;
import net.minecraft.client.world.ClientWorld;
import net.minecraft.state.properties.BlockStateProperties;
import net.minecraft.util.Direction;
import net.minecraft.util.math.AxisAlignedBB;
import net.minecraft.util.math.BlockPos;
@@ -66,33 +72,38 @@ import net.minecraft.world.IWorld;
import net.minecraft.world.LightType;
import net.minecraft.world.World;
import net.minecraft.world.biome.Biome;
import net.minecraft.world.biome.BiomeColors;
import net.minecraft.world.chunk.ChunkSection;
import net.minecraft.world.chunk.IChunk;
import net.minecraft.world.gen.Heightmap;
import net.minecraft.world.server.ServerWorld;
import net.minecraftforge.client.model.data.ModelDataMap;
/**
* This object is in charge of creating Lod related objects. (specifically: Lod
* World, Dimension, and Region objects)
*
* This object is in charge of creating Lod related objects.
*
* @author Cola
* @author Leonardo Amato
* @author James Seibel
* @version 9-25-2021
* @version 10-22-2021
*/
public class LodBuilder
{
private static MinecraftWrapper mc = MinecraftWrapper.INSTANCE;
private static final MinecraftWrapper mc = MinecraftWrapper.INSTANCE;
private ExecutorService lodGenThreadPool = Executors.newSingleThreadExecutor(new LodThreadFactory(this.getClass().getSimpleName()));
private final ExecutorService lodGenThreadPool = Executors.newSingleThreadExecutor(new LodThreadFactory(this.getClass().getSimpleName()));
public static final Direction[] directions = new Direction[] { Direction.UP, Direction.EAST, Direction.SOUTH, Direction.WEST, Direction.NORTH, Direction.DOWN };
public static final int CHUNK_DATA_WIDTH = LodUtil.CHUNK_WIDTH;
public static final int CHUNK_SECTION_HEIGHT = CHUNK_DATA_WIDTH;
public static final Heightmap.Type DEFAULT_HEIGHTMAP = Heightmap.Type.WORLD_SURFACE_WG;
public static final ConcurrentMap<Block, Integer> colorMap = new ConcurrentHashMap<>();
public static final ConcurrentMap<Block, Integer> tintColor = new ConcurrentHashMap<>();
public static final ConcurrentMap<Block, Boolean> toTint = new ConcurrentHashMap<>();
public static final ConcurrentMap<Block, VoxelShape> shapeMap = new ConcurrentHashMap<>();
public static final ConcurrentMap<Block, Boolean> notFullBlock = new ConcurrentHashMap<>();
public static final ConcurrentMap<Block, Boolean> smallBlock = new ConcurrentHashMap<>();
public static final ModelDataMap dataMap = new ModelDataMap.Builder().build();
/** If no blocks are found in the area in determineBottomPointForArea return this */
@@ -102,35 +113,31 @@ public class LodBuilder
/** Minecraft's max light value */
public static final short DEFAULT_MAX_LIGHT = 15;
/** TODO is this needed / used? */
public static final boolean onlyUseFullBlock = false;
/** TODO is this needed / used? */
public static final boolean avoidSmallBlock = false;
/** How wide LodDimensions should be in regions */
public int defaultDimensionWidthInRegions = 5;
/**
* How wide LodDimensions should be in regions <br>
* Is automatically set before the first frame in ClientProxy.
*/
public int defaultDimensionWidthInRegions = 0;
//public static final boolean useExperimentalLighting = true;
public LodBuilder()
{
}
public void generateLodNodeAsync(IChunk chunk, LodWorld lodWorld, IWorld world)
{
generateLodNodeAsync(chunk, lodWorld, world, DistanceGenerationMode.SERVER);
}
public void generateLodNodeAsync(IChunk chunk, LodWorld lodWorld, IWorld world, DistanceGenerationMode generationMode)
{
if (lodWorld == null || !lodWorld.getIsWorldLoaded())
if (lodWorld == null || lodWorld.getIsWorldNotLoaded())
return;
// don't try to create an LOD object
@@ -148,6 +155,11 @@ public class LodBuilder
if (mc.getClientWorld() == null)
return;
// don't try to generate LODs if the user isn't in the world anymore
// (this happens a lot when the user leaves a world/server)
if (mc.getSinglePlayerServer() == null && mc.getCurrentServer() == null)
return;
DimensionType dim = world.dimensionType();
// make sure the dimension exists
@@ -176,7 +188,6 @@ public class LodBuilder
/**
* Creates a LodNode for a chunk in the given world.
*
* @throws IllegalArgumentException thrown if either the chunk or world is null.
*/
public void generateLodNodeFromChunk(LodDimension lodDim, IChunk chunk) throws IllegalArgumentException
@@ -186,7 +197,6 @@ public class LodBuilder
/**
* Creates a LodNode for a chunk in the given world.
*
* @throws IllegalArgumentException thrown if either the chunk or world is null.
*/
public void generateLodNodeFromChunk(LodDimension lodDim, IChunk chunk, LodBuilderConfig config)
@@ -212,7 +222,7 @@ public class LodBuilder
// determine how many LODs to generate vertically
VerticalQuality verticalQuality = LodConfig.CLIENT.worldGenerator.lodQualityMode.get();
VerticalQuality verticalQuality = LodConfig.CLIENT.graphics.qualityOption.verticalQuality.get();
byte detailLevel = detail.detailLevel;
@@ -231,58 +241,24 @@ public class LodBuilder
long[] data;
switch (verticalQuality)
{
default:
case HEIGHTMAP:
long singleData;
long[] dataToMergeSingle = createSingleDataToMerge(detail, chunk, config, startX, startZ, endX, endZ);
singleData = DataPointUtil.mergeSingleData(dataToMergeSingle);
lodDim.addData(detailLevel,
posX,
posZ,
0,
singleData,
false);
break;
case VOXEL:
long[] dataToMergeVertical = createVerticalDataToMerge(detail, chunk, config, startX, startZ, endX, endZ);
data = DataPointUtil.mergeMultiData(dataToMergeVertical, DataPointUtil.worldHeight, DetailDistanceUtil.getMaxVerticalData(detailLevel));
//lodDim.clear(detailLevel, posX, posZ);
if (data != null && data.length != 0)
{
for (int verticalIndex = 0; verticalIndex < lodDim.getMaxVerticalData(detailLevel, posX, posZ); verticalIndex++)
{
if (!DataPointUtil.doesItExist(data[verticalIndex]))
break;
lodDim.addData(detailLevel,
posX,
posZ,
verticalIndex,
data[verticalIndex],
false);
}
}
break;
}
long[] dataToMergeVertical = createVerticalDataToMerge(detail, chunk, config, startX, startZ, endX, endZ);
data = DataPointUtil.mergeMultiData(dataToMergeVertical, DataPointUtil.worldHeight, DetailDistanceUtil.getMaxVerticalData(detailLevel));
//lodDim.clear(detailLevel, posX, posZ);
if (data != null && data.length != 0)
lodDim.addVerticalData(detailLevel, posX, posZ, data,false);
}
lodDim.updateData(LodUtil.CHUNK_DETAIL_LEVEL, chunk.getPos().x, chunk.getPos().z);
}
/**
* creates a vertical DataPoint
*/
/** creates a vertical DataPoint */
private long[] createVerticalDataToMerge(HorizontalResolution detail, IChunk chunk, LodBuilderConfig config, int startX, int startZ, int endX, int endZ)
{
// equivalent to 2^detailLevel
int size = 1 << detail.detailLevel;
long[] dataToMerge = ThreadMapUtil.getFreshBuilderVerticalArray(size * size * DataPointUtil.worldHeight + 1, detail.detailLevel);
long[] dataToMerge = ThreadMapUtil.getBuilderVerticalArray(detail.detailLevel);
int verticalData = DataPointUtil.worldHeight;
@@ -302,7 +278,7 @@ public class LodBuilder
int zAbs;
boolean hasCeiling = mc.getClientWorld().dimensionType().hasCeiling();
boolean hasSkyLight = mc.getClientWorld().dimensionType().hasSkyLight();
boolean isDefault;
BlockPos.Mutable blockPos = new BlockPos.Mutable(0, 0, 0);
int index;
@@ -349,9 +325,9 @@ public class LodBuilder
}
lightBlock = light & 0b1111;
lightSky = (light >> 4) & 0b1111;
isDefault = ((light >> 8)) == 1;
dataToMerge[index * verticalData + count] = DataPointUtil.createDataPoint(height, depth, color, lightSky, lightBlock, generation);
dataToMerge[index * verticalData + count] = DataPointUtil.createDataPoint(height, depth, color, lightSky, lightBlock, generation, isDefault);
topBlock = false;
yAbs = depth - 1;
count++;
@@ -367,48 +343,44 @@ public class LodBuilder
private short determineBottomPointFrom(IChunk chunk, LodBuilderConfig config, int xRel, int zRel, int yAbs, BlockPos.Mutable blockPos)
{
short depth = DEFAULT_DEPTH;
if (config.useHeightmap)
/*if (config.useHeightmap)
{
// when using the generated heightmap there is no data about the lowest point
depth = 0;
depth = 0; //DEFAULT_DEPTH == 0
}
else
{*/
boolean voidData = true;
ChunkSection[] chunkSections = chunk.getSections();
for (int sectionIndex = chunkSections.length - 1; sectionIndex >= 0; sectionIndex--)
{
boolean voidData = true;
ChunkSection[] chunkSections = chunk.getSections();
for (int sectionIndex = chunkSections.length - 1; sectionIndex >= 0; sectionIndex--)
for (int yRel = CHUNK_DATA_WIDTH - 1; yRel >= 0; yRel--)
{
for (int yRel = CHUNK_DATA_WIDTH - 1; yRel >= 0; yRel--)
{
if (sectionIndex * CHUNK_DATA_WIDTH + yRel > yAbs)
continue;
blockPos.set(chunk.getPos().getMinBlockX() + xRel, sectionIndex * CHUNK_DATA_WIDTH + yRel, chunk.getPos().getMinBlockZ() + zRel);
if (!isLayerValidLodPoint(chunk, blockPos))
{
depth = (short) (sectionIndex * CHUNK_DATA_WIDTH + yRel + 1);
voidData = false;
break;
}
}
if (sectionIndex * CHUNK_DATA_WIDTH + yRel > yAbs)
continue;
if (!voidData)
blockPos.set(chunk.getPos().getMinBlockX() + xRel, sectionIndex * CHUNK_DATA_WIDTH + yRel, chunk.getPos().getMinBlockZ() + zRel);
if (!isLayerValidLodPoint(chunk, blockPos))
{
depth = (short) (sectionIndex * CHUNK_DATA_WIDTH + yRel + 1);
voidData = false;
break;
}
}
if (!voidData)
break;
}
//}
return depth;
}
/**
* Find the highest valid point from the Top
*/
/** Find the highest valid point from the Top */
private short determineHeightPointFrom(IChunk chunk, LodBuilderConfig config, int xRel, int zRel, int yAbs, BlockPos.Mutable blockPos)
{
short height = DEFAULT_HEIGHT;
if (config.useHeightmap)
{
height = (short) chunk.getOrCreateHeightmapUnprimed(LodUtil.DEFAULT_HEIGHTMAP).getFirstAvailable(xRel, zRel);
}
else
{
boolean voidData = true;
@@ -434,149 +406,12 @@ public class LodBuilder
return height;
}
/**
* Creates a single HeightMap style LOD data point.
*/
private long[] createSingleDataToMerge(HorizontalResolution detail, IChunk chunk, LodBuilderConfig config, int startX, int startZ, int endX, int endZ)
{
long[] dataToMerge = ThreadMapUtil.getBuilderArray(detail.detailLevel);
ChunkPos chunkPos = chunk.getPos();
int size = 1 << detail.detailLevel;
int height = 0;
int depth = 0;
int color = 0;
int light = 0;
int generation = config.distanceGenerationMode.complexity;
int xRel;
int zRel;
int xAbs;
int yAbs;
int zAbs;
int lightBlock;
int lightSky;
boolean hasCeiling = mc.getClientWorld().dimensionType().hasCeiling();
boolean hasSkyLight = mc.getClientWorld().dimensionType().hasSkyLight();
BlockPos.Mutable blockPos = new BlockPos.Mutable(0, 0, 0);
int index = 0;
if (dataToMerge == null)
{
dataToMerge = new long[size * size];
}
for (index = 0; index < size * size; index++)
{
xRel = Math.floorMod(index, size) + startX;
zRel = Math.floorDiv(index, size) + startZ;
xAbs = chunkPos.getMinBlockX() + xRel;
zAbs = chunkPos.getMinBlockZ() + zRel;
//Calculate the height of the lod
height = determineHeightPoint(chunk, config, xRel, zRel, blockPos);
//If the lod is at the default height it must be void
if (height == DEFAULT_HEIGHT)
{
dataToMerge[index] = DataPointUtil.createVoidDataPoint(generation);
continue;
}
yAbs = height - 1;
color = generateLodColor(chunk, config, xRel, yAbs, zRel, blockPos);
depth = determineBottomPoint(chunk, config, xRel, zRel, blockPos);
// get the light value from the above air block
blockPos.set(xAbs, yAbs + 1, zAbs);
light = getLightValue(chunk, blockPos, hasCeiling, hasSkyLight, true);
lightBlock = light & 0b1111;
lightSky = DEFAULT_MAX_LIGHT;
dataToMerge[index] = DataPointUtil.createDataPoint(height, depth, color, lightSky, lightBlock, generation);
}
return dataToMerge;
}
// =====================//
// constructor helpers //
// =====================//
/**
* Find the lowest valid point from the bottom.
*/
private short determineBottomPoint(IChunk chunk, LodBuilderConfig config, int xRel, int zRel, BlockPos.Mutable blockPos)
{
ChunkSection[] chunkSections = chunk.getSections();
short depth = DEFAULT_DEPTH;
if (config.useHeightmap)
{
depth = 0;
}
else
{
boolean found = false;
for (int sectionIndex = 0; sectionIndex < chunkSections.length; sectionIndex++)
{
for (int yRel = 0; yRel < CHUNK_DATA_WIDTH; yRel++)
{
blockPos.set(chunk.getPos().getMinBlockX() + xRel, sectionIndex * CHUNK_DATA_WIDTH + yRel, chunk.getPos().getMinBlockZ() + zRel);
if (isLayerValidLodPoint(chunk, blockPos))
{
depth = (short) (sectionIndex * CHUNK_DATA_WIDTH + yRel);
found = true;
break;
}
}
if (found)
break;
}
}
return depth;
}
/**
* Find the highest valid point from the Top
*/
private short determineHeightPoint(IChunk chunk, LodBuilderConfig config, int xRel, int zRel, BlockPos.Mutable blockPos)
{
short height = DEFAULT_HEIGHT;
if (config.useHeightmap)
{
height = (short) chunk.getOrCreateHeightmapUnprimed(LodUtil.DEFAULT_HEIGHTMAP).getFirstAvailable(xRel, zRel);
}
else
{
boolean voidData = true;
ChunkSection[] chunkSections = chunk.getSections();
for (int sectionIndex = chunkSections.length - 1; sectionIndex >= 0; sectionIndex--)
{
for (int yRel = CHUNK_DATA_WIDTH - 1; yRel >= 0; yRel--)
{
blockPos.set(chunk.getPos().getMinBlockX() + xRel, sectionIndex * CHUNK_DATA_WIDTH + yRel, chunk.getPos().getMinBlockZ() + zRel);
if (isLayerValidLodPoint(chunk, blockPos))
{
height = (short) (sectionIndex * CHUNK_DATA_WIDTH + yRel + 1);
voidData = false;
break;
}
}
if (!voidData)
break;
}
}
return height;
}
/**
* Generate the color for the given chunk using biome water color, foliage
* color, and grass color.
@@ -602,65 +437,106 @@ public class LodBuilder
colorInt = getColorForBlock(chunk, blockPos);
}
// if we are skipping non-full and non-solid blocks that means we ignore
// snow, flowers, etc. Get the above block so we can still get the color
// of the snow, flower, etc. that may be above this block
int aboveColorInt = 0;
if (LodConfig.CLIENT.worldGenerator.blockToAvoid.get().nonFull || LodConfig.CLIENT.worldGenerator.blockToAvoid.get().noCollision)
{
blockPos.set(chunk.getPos().getMinBlockX() + xRel, sectionIndex * CHUNK_DATA_WIDTH + yRel + 1, chunk.getPos().getMinBlockZ() + zRel);
aboveColorInt = getColorForBlock(chunk, blockPos);
}
if (colorInt == 0 && yAbs > 0)
// if this block is invisible, check the block below it
colorInt = generateLodColor(chunk, config, xRel, yAbs - 1, zRel, blockPos);
// override this block's color if there was a block above this
// and we were avoiding non-full/non-solid blocks
if (aboveColorInt != 0)
colorInt = aboveColorInt;
}
return colorInt;
}
/**
* Gets the light value for the given block position
*/
/** Gets the light value for the given block position */
private int getLightValue(IChunk chunk, BlockPos.Mutable blockPos, boolean hasCeiling, boolean hasSkyLight, boolean topBlock)
{
int skyLight;
int blockLight = 0;
int skyLight = 0;
int blockLight;
// 1 means the lighting is a guess
int isDefault = 0;
if (mc.getClientWorld() == null)
ClientWorld clientWorld = mc.getClientWorld();
if (clientWorld == null)
return 0;
ClientWorld world = mc.getClientWorld();
ServerWorld serverWorld = LodUtil.getServerWorldFromDimension(clientWorld.dimensionType());
int blockBrightness = chunk.getLightEmission(blockPos);
// get the air block above or below this block
if (hasCeiling && topBlock)
blockPos.set(blockPos.getX(), blockPos.getY() - 1, blockPos.getZ());
else
blockPos.set(blockPos.getX(), blockPos.getY() + 1, blockPos.getZ());
if (!hasSkyLight && hasCeiling)
if (serverWorld != null)
{
skyLight = 0;
}
else if (topBlock)
{
skyLight = DEFAULT_MAX_LIGHT;
}
else
{
if (chunk.isLightCorrect())
{
skyLight = world.getBrightness(LightType.SKY, blockPos);
}
// server world sky light (always accurate)
blockLight = serverWorld.getBrightness(LightType.BLOCK, blockPos);
if(topBlock && !hasCeiling && hasSkyLight)
skyLight = DEFAULT_MAX_LIGHT;
else
skyLight = serverWorld.getBrightness(LightType.SKY, blockPos);
if (!topBlock && skyLight == 15)
{
// we don't know what the light here is,
// we are on predicted terrain, and we don't know what the light here is,
// lets just take a guess
if (blockPos.getY() >= mc.getClientWorld().getSeaLevel() - 5)
skyLight = 10;
{
skyLight = 12;
isDefault = 1;
}
else
skyLight = 0;
}
}
else
{
// client world sky light (almost never accurate)
blockLight = clientWorld.getBrightness(LightType.BLOCK, blockPos);
// estimate what the lighting should be
if (hasSkyLight || !hasCeiling)
{
if (topBlock)
skyLight = DEFAULT_MAX_LIGHT;
else
{
skyLight = clientWorld.getBrightness(LightType.SKY, blockPos);
if (!chunk.isLightCorrect() && (skyLight == 0 || skyLight == 15))
{
// we don't know what the light here is,
// lets just take a guess
if (blockPos.getY() >= mc.getClientWorld().getSeaLevel() - 5)
{
skyLight = 12;
isDefault = 1;
}
else
skyLight = 0;
}
}
}
}
blockLight = world.getBrightness(LightType.BLOCK, blockPos);
blockLight = LodUtil.clamp(0, blockLight + blockBrightness, DEFAULT_MAX_LIGHT);
blockLight = LodUtil.clamp(0, Math.max(blockLight, blockBrightness), DEFAULT_MAX_LIGHT);
return blockLight + (skyLight << 4);
return blockLight + (skyLight << 4) + (isDefault << 8);
}
@@ -679,31 +555,36 @@ public class LodBuilder
World world = mc.getClientWorld();
TextureAtlasSprite texture;
List<BakedQuad> quad = null;
List<BakedQuad> quads = null;
int tintIndex = Integer.MIN_VALUE;
boolean isTinted = false;
int listSize = 0;
// get the first quad we can for this block
for (Direction direction : directions)
{
quad = mc.getModelManager().getBlockModelShaper().getBlockModel(blockState).getQuads(blockState, direction, new Random(0), dataMap);
if (!quad.isEmpty())
quads = mc.getModelManager().getBlockModelShaper().getBlockModel(blockState).getQuads(blockState, direction, new Random(0), dataMap);
listSize = Math.max(listSize, quads.size());
for (BakedQuad bakedQuad : quads)
{
isTinted |= bakedQuad.isTinted();
tintIndex = Math.max(tintIndex, bakedQuad.getTintIndex());
}
}
toTint.put(block, isTinted);
tintColor.put(block, tintIndex);
for (Direction direction : directions)
{
quads = mc.getModelManager().getBlockModelShaper().getBlockModel(blockState).getQuads(blockState, direction, new Random(0), dataMap);
if (!quads.isEmpty())
break;
}
if (!quad.isEmpty())
toTint.put(block, quad.get(0).isTinted());
else
toTint.put(blockState.getBlock(), false);
if (useTopTexture && !quad.isEmpty())
texture = quad.get(0).getSprite();
if (useTopTexture && !quads.isEmpty())
texture = quads.get(0).getSprite();
else
texture = mc.getModelManager().getBlockModelShaper().getTexture(blockState, world, blockPos);
int count = 0;
int alpha = 0;
int red = 0;
@@ -742,7 +623,7 @@ public class LodBuilder
// add to the running averages
count = colorMultiplier + count; // TODO shouldn't colorMultiplier be multiplied by?
count += colorMultiplier;
alpha += ColorUtil.getAlpha(color) * colorMultiplier;
red += ColorUtil.getBlue(color) * colorMultiplier;
green += ColorUtil.getGreen(color) * colorMultiplier;
@@ -753,10 +634,8 @@ public class LodBuilder
if (count == 0)
{
// this block is entirely transparent
color = 0;
}
else
{
// determine the average color
@@ -768,12 +647,11 @@ public class LodBuilder
}
// determine if this block should use the biome color tint
if (block instanceof TallGrassBlock || (useGrassTint(block) || useLeafTint(block) || useWaterTint(block)) && numberOfGreyPixel / count > 0.75f)
if ((useGrassTint(block) || useLeafTint(block) || useWaterTint(block)) && (float) numberOfGreyPixel / count > 0.75f)
toTint.replace(block, true);
// add the newly generated block color to the map for later use
colorMap.put(block, color);
return color;
}
@@ -805,8 +683,10 @@ public class LodBuilder
/** Returns a color int for the given block. */
private int getColorForBlock(IChunk chunk, BlockPos blockPos)
{
int blockColor;
int colorInt = 0;
int colorInt;
int xRel = blockPos.getX() - chunk.getPos().getMinBlockX();
int zRel = blockPos.getZ() - chunk.getPos().getMinBlockZ();
@@ -814,10 +694,11 @@ public class LodBuilder
int y = blockPos.getY();
int z = blockPos.getZ();
Biome biome = chunk.getBiomes().getNoiseBiome(xRel >> 2, y >> 2, zRel >> 2);
//Biome biome = chunk.getBiomes().getNoiseBiome(xRel >> 2, y >> 2, zRel >> 2);
BlockState blockState = chunk.getBlockState(blockPos);
if(isInWater(blockState))
blockState = Blocks.WATER.defaultBlockState();
// block special cases
// TODO: this needs to be replaced by a config file of some sort
@@ -825,41 +706,82 @@ public class LodBuilder
|| blockState == Blocks.CAVE_AIR.defaultBlockState()
|| blockState == Blocks.BARRIER.defaultBlockState())
{
Color tmp = LodUtil.intToColor(biome.getGrassColor(x, z));
tmp = tmp.darker();
colorInt = LodUtil.colorToInt(tmp);
return 0;
}
blockColor = getColorTextureForBlock(blockState, blockPos, true);
if (toTint.get(blockState.getBlock()).booleanValue())
//if the blockColor is 0 we reset it and don't use the faceColor
if (blockColor == 0)
{
if (useLeafTint(blockState.getBlock()))
{
// leaves
colorInt = ColorUtil.multiplyRGBcolors(biome.getFoliageColor(), blockColor);
}
else if (useGrassTint(blockState.getBlock()))
tintColor.remove(blockState.getBlock());
toTint.remove(blockState.getBlock());
colorMap.remove(blockState.getBlock());
blockColor = getColorTextureForBlock(blockState, blockPos, false);
}
//if the blockColor is still 0 we use the default material color
if (blockColor == 0)
{
tintColor.replace(blockState.getBlock(), 0);
toTint.replace(blockState.getBlock(), false);
colorMap.replace(blockState.getBlock(), blockState.getBlock().defaultMaterialColor().col);
}
if (toTint.get(blockState.getBlock()))
{
Biome biome = chunk.getBiomes().getNoiseBiome(xRel >> 2, y >> 2, zRel >> 2);
ClientWorld clientWorld = mc.getClientWorld();
if (clientWorld == null)
return 0;
ServerWorld serverWorld = LodUtil.getServerWorldFromDimension(clientWorld.dimensionType());
int tintValue;
if (useGrassTint(blockState.getBlock()))
{
// grass and green plants
colorInt = ColorUtil.multiplyRGBcolors(biome.getGrassColor(x, z), blockColor);
try
{
tintValue = BiomeColors.getAverageGrassColor(serverWorld, blockPos);
}
catch(NullPointerException e)
{
tintValue = biome.getGrassColor(x, z);
}
}
else if (useWaterTint(blockState.getBlock()))
{
// water
colorInt = ColorUtil.multiplyRGBcolors(biome.getWaterColor(), blockColor);
try
{
tintValue = BiomeColors.getAverageWaterColor(serverWorld, blockPos);
}
catch(NullPointerException e)
{
tintValue = biome.getWaterColor();
}
}
else
{
// leaves
try
{
tintValue = BiomeColors.getAverageFoliageColor(serverWorld, blockPos);
}
catch(NullPointerException e)
{
tintValue = biome.getFoliageColor();
}
}
colorInt = ColorUtil.multiplyRGBcolors(tintValue | 0xFF000000, blockColor);
}
else
{
colorInt = blockColor;
}
return colorInt;
}
/**
* Returns a color int for the given biome.
*/
/** Returns a color int for the given biome. */
private int getColorForBiome(int x, int z, Biome biome)
{
int colorInt;
@@ -868,7 +790,7 @@ public class LodBuilder
{
case NETHER:
colorInt = LodUtil.NETHERRACK_COLOR_INT;
colorInt = Blocks.NETHERRACK.defaultBlockState().materialColor.col;
break;
case THEEND:
@@ -913,64 +835,108 @@ public class LodBuilder
tmp = tmp.darker();
colorInt = LodUtil.colorToInt(tmp);
break;
}
return colorInt;
}
private boolean isInWater(BlockState blockState)
{
//This type of block is always in water
if((blockState.getBlock() instanceof ILiquidContainer) && !(blockState.getBlock() instanceof IWaterLoggable))
return true;
//This type of block could be in water
if(blockState.getOptionalValue(BlockStateProperties.WATERLOGGED).isPresent() && blockState.getOptionalValue(BlockStateProperties.WATERLOGGED).get())
return true;
return false;
}
/** Is the block at the given blockPos a valid LOD point? */
private boolean isLayerValidLodPoint(IChunk chunk, BlockPos.Mutable blockPos)
{
BlockState blockState = chunk.getBlockState(blockPos);
if (isInWater(blockState))
return true;
boolean nonFullAvoidance = LodConfig.CLIENT.worldGenerator.blockToAvoid.get().nonFull;
boolean noCollisionAvoidance = LodConfig.CLIENT.worldGenerator.blockToAvoid.get().noCollision;
if (blockState != null)
{
// TODO this code is dead since avoidSmallBlock and onlyUseFullBlock
// are set to false and are never changed.
// should this code be changed?
if (avoidSmallBlock || onlyUseFullBlock)
if (nonFullAvoidance)
{
if (!blockState.getFluidState().isEmpty())
return true;
VoxelShape voxelShape;
if (shapeMap.containsKey(blockState.getBlock()))
if(!blockState.getFluidState().isEmpty() || blockState.getBlock() instanceof SixWayBlock)
{
voxelShape = shapeMap.get(blockState.getBlock());
notFullBlock.put(blockState.getBlock(), false);
}
if (!notFullBlock.containsKey(blockState.getBlock()) || notFullBlock.get(blockState.getBlock()) == null)
{
VoxelShape voxelShape = blockState.getBlock().defaultBlockState().getShape(chunk, blockPos);
}
else
{
voxelShape = blockState.getShape(chunk, blockPos);
shapeMap.put(blockState.getBlock(), voxelShape);
}
if (!voxelShape.isEmpty())
{
AxisAlignedBB bbox = voxelShape.bounds();
int xWidth = (int) (bbox.maxX - bbox.minX);
int yWidth = (int) (bbox.maxY - bbox.minY);
int zWidth = (int) (bbox.maxZ - bbox.minZ);
if (xWidth < 1 && zWidth < 1 && yWidth < 1 && onlyUseFullBlock)
if (!voxelShape.isEmpty())
{
return false;
AxisAlignedBB bbox = voxelShape.bounds();
double xWidth = (bbox.maxX - bbox.minX);
double yWidth = (bbox.maxY - bbox.minY);
double zWidth = (bbox.maxZ - bbox.minZ);
if (xWidth < 1 && zWidth < 1 && yWidth < 1)
notFullBlock.put(blockState.getBlock(), true);
else
notFullBlock.put(blockState.getBlock(), false);
}
if (xWidth < 0.7 && zWidth < 0.7 && yWidth < 1 && avoidSmallBlock)
else
{
return false;
notFullBlock.put(blockState.getBlock(), false);
}
}
else
if (notFullBlock.get(blockState.getBlock()))
{
return false;
}
}
if (noCollisionAvoidance)
{
if(!blockState.getFluidState().isEmpty() || blockState.getBlock() instanceof SixWayBlock)
smallBlock.put(blockState.getBlock(), false);
if (!smallBlock.containsKey(blockState.getBlock()) || smallBlock.get(blockState.getBlock()) == null)
{
VoxelShape voxelShape = blockState.getCollisionShape(chunk, blockPos);
if (!blockState.getFluidState().isEmpty())
{
smallBlock.put(blockState.getBlock(), false);
}
else
{
if (voxelShape.isEmpty())
{
smallBlock.put(blockState.getBlock(), true);
}
else
{
smallBlock.put(blockState.getBlock(), false);
}
}
}
if (smallBlock.get(blockState.getBlock()))
{
return false;
}
}
return blockState.getBlock() != Blocks.AIR
&& blockState.getBlock() != Blocks.CAVE_AIR
&& blockState.getBlock() != Blocks.BARRIER;
&& blockState.getBlock() != Blocks.CAVE_AIR
&& blockState.getBlock() != Blocks.BARRIER;
}
return false;
@@ -1,5 +1,6 @@
/*
* This file is part of the LOD Mod, licensed under the GNU GPL v3 License.
* This file is part of the Distant Horizon mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020 James Seibel
*
@@ -15,16 +16,16 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.lod.builders;
package com.seibel.lod.builders.lodBuilding;
import com.seibel.lod.enums.DistanceGenerationMode;
/**
* This is used to easily configure how LodChunks are generated.
* Generally this will only be used if we want to generate a
* LodChunk using a incomplete Chunk, otherwise the defaults
* LodChunk using an incomplete Chunk, otherwise the defaults
* work best for a fully generated chunk (IE has correct surface blocks).
*
* @author James Seibel
* @version 8-14-2021
*/
@@ -38,7 +39,7 @@ public class LodBuilderConfig
public boolean useSolidBlocksInColorGen;
/** default: server */
public DistanceGenerationMode distanceGenerationMode;
/**
* default settings for a normal chunk <br>
* useHeightmap = false <br>
@@ -53,27 +54,26 @@ public class LodBuilderConfig
useSolidBlocksInColorGen = true;
distanceGenerationMode = DistanceGenerationMode.SERVER;
}
/**
* @param newUseHeightmap default = false
* @param newUseBiomeColors default = false
* @param newUseHeightmap default = false
* @param newUseBiomeColors default = false
* @param newUseSolidBlocksInBiomeColor default = true
* @param newDistanceGenerationMode default = Server
* @param newDistanceGenerationMode default = Server
*/
public LodBuilderConfig(boolean newUseHeightmap, boolean newUseBiomeColors,
boolean newUseSolidBlocksInBiomeColor, DistanceGenerationMode newDistanceGenerationMode)
boolean newUseSolidBlocksInBiomeColor, DistanceGenerationMode newDistanceGenerationMode)
{
useHeightmap = newUseHeightmap;
useBiomeColors = newUseBiomeColors;
useSolidBlocksInColorGen = newUseSolidBlocksInBiomeColor;
distanceGenerationMode = newDistanceGenerationMode;
}
/**
* @param newUseHeightmap default = false
* @param newUseBiomeColors default = false
* @param newUseHeightmap default = false
* @param newUseBiomeColors default = false
* @param newUseSolidBlocksInBiomeColor default = true
* @param newDistanceGenerationMode default = Server
*/
public LodBuilderConfig(boolean newUseHeightmap, boolean newUseBiomeColors, boolean newUseSolidBlocksInBiomeColor)
{
@@ -81,14 +81,11 @@ public class LodBuilderConfig
useHeightmap = newUseHeightmap;
useBiomeColors = newUseBiomeColors;
useSolidBlocksInColorGen = newUseSolidBlocksInBiomeColor;
distanceGenerationMode = newUseHeightmap? DistanceGenerationMode.BIOME_ONLY_SIMULATE_HEIGHT : DistanceGenerationMode.BIOME_ONLY;
distanceGenerationMode = newUseHeightmap ? DistanceGenerationMode.BIOME_ONLY_SIMULATE_HEIGHT : DistanceGenerationMode.BIOME_ONLY;
}
/**
* @param newUseHeightmap default = false
* @param newUseBiomeColors default = false
* @param newUseSolidBlocksInBiomeColor default = true
* @param newDistanceGenerationMode default = Server
* @param newDistanceGenerationMode default = Server
*/
public LodBuilderConfig(DistanceGenerationMode newDistanceGenerationMode)
{
@@ -1,512 +0,0 @@
package com.seibel.lod.builders.lodTemplates;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import com.seibel.lod.config.LodConfig;
import com.seibel.lod.enums.DebugMode;
import com.seibel.lod.util.ColorUtil;
import com.seibel.lod.util.DataPointUtil;
import com.seibel.lod.wrappers.MinecraftWrapper;
import net.minecraft.util.Direction;
import net.minecraft.util.math.BlockPos;
/**
* Similar to Minecraft's AxisAlignedBoundingBox.
*
* @author Leonardo Amato
* @version 9-25-2021
*/
public class Box
{
public static final int ADJACENT_HEIGHT_INDEX = 0;
public static final int ADJACENT_DEPTH_INDEX = 1;
public static final int X = 0;
public static final int Y = 1;
public static final int Z = 2;
public static final int MIN = 0;
public static final int MAX = 1;
public static final int VOID_FACE = 0;
/** The six cardinal directions */
public static final Direction[] DIRECTIONS = new Direction[]{
Direction.UP,
Direction.DOWN,
Direction.WEST,
Direction.EAST,
Direction.NORTH,
Direction.SOUTH};
/** North, South, East, West */
public static final Direction[] ADJ_DIRECTIONS = new Direction[]{
Direction.EAST,
Direction.WEST,
Direction.SOUTH,
Direction.NORTH};
/** TODO what does this represent? I don't understand the name */
@SuppressWarnings("serial")
public static final Map<Direction, int[][]> DIRECTION_VERTEX_MAP = new HashMap<Direction, int[][]>()
{{
put(Direction.UP, new int[][]{
{0, 1, 0},
{0, 1, 1},
{1, 1, 1},
{1, 1, 0}});
put(Direction.DOWN, new int[][]{
{1, 0, 0},
{1, 0, 1},
{0, 0, 1},
{0, 0, 0}});
put(Direction.EAST, new int[][]{
{1, 1, 0},
{1, 1, 1},
{1, 0, 1},
{1, 0, 0}});
put(Direction.WEST, new int[][]{
{0, 0, 0},
{0, 0, 1},
{0, 1, 1},
{0, 1, 0}});
put(Direction.SOUTH, new int[][]{
{1, 0, 1},
{1, 1, 1},
{0, 1, 1},
{0, 0, 1}});
put(Direction.NORTH, new int[][]{
{0, 0, 0},
{0, 1, 0},
{1, 1, 0},
{1, 0, 0}});
}};
@SuppressWarnings("serial")
public static final Map<Direction, int[]> FACE_DIRECTION = new HashMap<Direction, int[]>()
{{
put(Direction.UP, new int[]{Y, MAX});
put(Direction.DOWN, new int[]{Y, MIN});
put(Direction.EAST, new int[]{X, MAX});
put(Direction.WEST, new int[]{X, MIN});
put(Direction.SOUTH, new int[]{Z, MAX});
put(Direction.NORTH, new int[]{Z, MIN});
}};
@SuppressWarnings("serial")
public static final Map<Direction, int[]> DIRECTION_NORMAL_MAP = new HashMap<Direction, int[]>()
{{
put(Direction.UP, new int[]{0, 1, 0});
put(Direction.DOWN, new int[]{0, -1, 0});
put(Direction.EAST, new int[]{1, 0, 0});
put(Direction.WEST, new int[]{-1, 0, 0});
put(Direction.SOUTH, new int[]{0, 0, 1});
put(Direction.NORTH, new int[]{0, 0, -1});
}};
/** holds the box's x, y, z offset */
public int[] boxOffset;
/** holds the box's x, y, z width */
public int[] boxWidth;
/** Holds each direction's color */
public Map<Direction, Integer> colorMap;
/** The original color (before shading) of this box */
public int color;
/** */
public Map<Direction, int[]> adjHeight;
public Map<Direction, int[]> adjDepth;
/** Holds if the given direction should be culled or not */
public Map<Direction, Boolean> culling;
/** creates a empty box */
@SuppressWarnings("serial")
public Box()
{
boxOffset = new int[3];
boxWidth = new int[3];
colorMap = new HashMap<Direction, Integer>()
{{
put(Direction.UP, 0);
put(Direction.DOWN, 0);
put(Direction.EAST, 0);
put(Direction.WEST, 0);
put(Direction.SOUTH, 0);
put(Direction.NORTH, 0);
}};
// TODO what does the 32 represent?
adjHeight = new HashMap<Direction, int[]>()
{{
put(Direction.EAST, new int[32]);
put(Direction.WEST, new int[32]);
put(Direction.SOUTH, new int[32]);
put(Direction.NORTH, new int[32]);
}};
adjDepth = new HashMap<Direction, int[]>()
{{
put(Direction.EAST, new int[32]);
put(Direction.WEST, new int[32]);
put(Direction.SOUTH, new int[32]);
put(Direction.NORTH, new int[32]);
}};
culling = new HashMap<Direction, Boolean>()
{{
put(Direction.UP, false);
put(Direction.DOWN, false);
put(Direction.EAST, false);
put(Direction.WEST, false);
put(Direction.SOUTH, false);
put(Direction.NORTH, false);
}};
}
public void setColor(int color)
{
this.color = color;
for (Direction direction : DIRECTIONS)
{
colorMap.put(direction, ColorUtil.applyShade(color, MinecraftWrapper.INSTANCE.getClientWorld().getShade(direction, true)));
}
}
public int getColor(Direction direction)
{
if (LodConfig.CLIENT.debugging.debugMode.get() != DebugMode.SHOW_DETAIL)
{
return colorMap.get(direction);
}
else
{
return ColorUtil.applyShade(color, MinecraftWrapper.INSTANCE.getClientWorld().getShade(direction, true));
}
}
/** clears this box, reseting everything to default values */
public void reset()
{
Arrays.fill(boxWidth, 0);
Arrays.fill(boxOffset, 0);
for (Direction direction : DIRECTIONS)
{
colorMap.put(direction, 0);
}
for (Direction direction : ADJ_DIRECTIONS)
{
// TODO wouldn't we want to set all adjHeightAndDepth
// to VOID_FACE regardless of the culled status?
if (isCulled(direction))
continue;
for (int i = 0; i < adjHeight.get(direction).length; i++)
{
adjHeight.get(direction)[i] = VOID_FACE;
adjDepth.get(direction)[i] = VOID_FACE;
}
}
}
/** determine which faces should be culled */
public void setUpCulling(int cullingDistance, BlockPos playerPos)
{
for (Direction direction : DIRECTIONS)
{
if (direction == Direction.DOWN)
culling.put(direction, playerPos.get(direction.getAxis()) > getFacePos(direction) + cullingDistance);
else if (direction == Direction.UP)
culling.put(direction, playerPos.get(direction.getAxis()) < getFacePos(direction) - cullingDistance);
else if (direction == Direction.WEST)
culling.put(direction, -playerPos.get(direction.getAxis()) > getFacePos(direction) + cullingDistance);
else if (direction == Direction.NORTH)
culling.put(direction, -playerPos.get(direction.getAxis()) > getFacePos(direction) + cullingDistance);
else if (direction == Direction.EAST)
culling.put(direction, -playerPos.get(direction.getAxis()) < getFacePos(direction) - cullingDistance);
else if (direction == Direction.SOUTH)
culling.put(direction, -playerPos.get(direction.getAxis()) < getFacePos(direction) - cullingDistance);
}
}
public boolean isCulled(Direction direction)
{
return culling.get(direction);
}
public void setAdjData(Map<Direction, long[]> adjData)
{
int height;
int depth;
int minY = getMinY();
int maxY = getMaxY();
for (Direction direction : ADJ_DIRECTIONS)
{
long[] dataPoint = adjData.get(direction);
if (dataPoint == null || DataPointUtil.isVoid(dataPoint[0]))
{
adjHeight.get(direction)[0] = maxY;
adjDepth.get(direction)[0] = minY;
adjHeight.get(direction)[1] = VOID_FACE;
adjDepth.get(direction)[1] = VOID_FACE;
continue;
}
//We order the adj list
/**TODO remove this if the order is maintained naturally*/
/*order[0] = 0;
int count = 0;
for (int i = 0; i < dataPoint.length; i++)
{
int j = i - 1;
if(DataPointUtil.isItVoid(dataPoint[i]) || !DataPointUtil.doesItExist(dataPoint[i]))
{
continue;
}
while (j >= 0 && DataPointUtil.getHeight(order[j]) < DataPointUtil.getHeight(dataPoint[i]))
{
order[j + 1] = order[j];
j = j - 1;
}
order[j + 1] = dataPoint[i];
count++;
}*/
int i;
int faceToDraw = 0;
boolean firstFace = true;
boolean toFinish = false;
boolean allAbove = true;
long singleAdjDataPoint;
for (i = 0; i < dataPoint.length; i++)
{
singleAdjDataPoint = dataPoint[i];
if(DataPointUtil.isVoid(singleAdjDataPoint) || !DataPointUtil.doesItExist(singleAdjDataPoint))
break;
height = DataPointUtil.getHeight(singleAdjDataPoint);
depth = DataPointUtil.getDepth(singleAdjDataPoint);
if (depth <= maxY)
{
allAbove = false;
if (height < minY)
{
// the adj data is lower than the current data
if (firstFace)
{
adjHeight.get(direction)[0] = getMaxY();
adjDepth.get(direction)[0] = getMinY();
}
else
{
adjDepth.get(direction)[faceToDraw] = getMinY();
}
faceToDraw++;
toFinish = false;
// break since all the other data will be lower
break;
}
else if (depth <= minY && height >= maxY)
{
// the adj data is inside the current data
// don't draw the face
adjHeight.get(direction)[0] = VOID_FACE;
adjDepth.get(direction)[0] = VOID_FACE;
break;
}
else if (depth <= minY)//&& height < maxY
{
// the adj data intersects the lower part of the current data
// if this is the only face, use the maxY and break,
// if there was another face we finish the last one and break
if (firstFace)
{
adjHeight.get(direction)[0] = getMaxY();
adjDepth.get(direction)[0] = height;
}
else
{
adjDepth.get(direction)[faceToDraw] = height;
}
toFinish = false;
faceToDraw++;
break;
}
else if (height >= maxY)//depth > minY &&
{
// the adj data intersects the higher part of the current data
// we start the creation of a new face
adjHeight.get(direction)[faceToDraw] = depth;
firstFace = false;
toFinish = true;
}
else
{
// if (depth > minY && height < maxY)
// the adj data is contained in the current data
if (firstFace)
{
adjHeight.get(direction)[0] = getMaxY();
}
adjDepth.get(direction)[faceToDraw] = height;
faceToDraw++;
adjHeight.get(direction)[faceToDraw] = depth;
firstFace = false;
toFinish = true;
}
}
//else
//{
// // the adj data is higher than the current data
// // we continue since there could be some other data that intersect the current
//}
}
if(allAbove)
{
adjHeight.get(direction)[0] = getMaxY();
adjDepth.get(direction)[0] = getMinY();
faceToDraw++;
}
else if (toFinish)
{
adjDepth.get(direction)[faceToDraw] = minY;
faceToDraw++;
}
adjHeight.get(direction)[faceToDraw] = VOID_FACE;
adjDepth.get(direction)[faceToDraw] = VOID_FACE;
}
}
public void setWidth(int xWidth, int yWidth, int zWidth)
{
boxWidth[X] = xWidth;
boxWidth[Y] = yWidth;
boxWidth[Z] = zWidth;
}
public void setOffset(int xOffset, int yOffset, int zOffset)
{
boxOffset[X] = xOffset;
boxOffset[Y] = yOffset;
boxOffset[Z] = zOffset;
}
// TODO what does this mean?
public int getFacePos(Direction direction)
{
return boxOffset[FACE_DIRECTION.get(direction)[0]] + boxWidth[FACE_DIRECTION.get(direction)[0]] * FACE_DIRECTION.get(direction)[1];
}
// TODO is this still needed?
// public int getCoord(Direction direction, int axis, int vertexIndex)
// {
// return box[OFFSET][axis] + boxWidth[axis] * DIRECTION_VERTEX_MAP.get(direction)[vertexIndex][axis];
// }
/**
* returns true if the given direction should be rendered.
*/
public boolean shouldRenderFace(Direction direction, int adjIndex)
{
if (direction == Direction.UP || direction == Direction.DOWN)
{
return adjIndex == 0;
}
return !(adjHeight.get(direction)[adjIndex] == VOID_FACE && adjDepth.get(direction)[adjIndex] == VOID_FACE);
}
// TODO what does vertexIndex mean, is it 0-3 and represent
// the 4 vertices in the quad we send to the bufferBuilder?
public int getX(Direction direction, int vertexIndex)
{
return boxOffset[X] + boxWidth[X] * DIRECTION_VERTEX_MAP.get(direction)[vertexIndex][X];
}
public int getY(Direction direction, int vertexIndex)
{
return boxOffset[Y] + boxWidth[Y] * DIRECTION_VERTEX_MAP.get(direction)[vertexIndex][Y];
}
public int getY(Direction direction, int vertexIndex, int adjIndex)
{
if (direction == Direction.DOWN || direction == Direction.UP)
{
return boxOffset[Y] + boxWidth[Y] * DIRECTION_VERTEX_MAP.get(direction)[vertexIndex][Y];
}
else
{
// this could probably be cleaned up more,
// but it still works
if (1 - DIRECTION_VERTEX_MAP.get(direction)[vertexIndex][Y] == ADJACENT_HEIGHT_INDEX)
{
return adjHeight.get(direction)[adjIndex];
}
else
{
return adjDepth.get(direction)[adjIndex];
}
}
}
public int getZ(Direction direction, int vertexIndex)
{
return boxOffset[Z] + boxWidth[Z] * DIRECTION_VERTEX_MAP.get(direction)[vertexIndex][Z];
}
public int getMinX()
{
return boxOffset[X];
}
public int getMaxX()
{
return boxOffset[X] + boxWidth[X];
}
public int getMinY()
{
return boxOffset[Y];
}
public int getMaxY()
{
return boxOffset[Y] + boxWidth[Y];
}
public int getMinZ()
{
return boxOffset[Z];
}
public int getMaxZ()
{
return boxOffset[Z] + boxWidth[Z];
}
}
@@ -1,5 +1,6 @@
/*
* This file is part of the LOD Mod, licensed under the GNU GPL v3 License.
* This file is part of the Distant Horizon mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020 James Seibel
*
@@ -15,6 +16,7 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.lod.builders.worldGeneration;
import java.util.ConcurrentModificationException;
@@ -27,13 +29,12 @@ 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.builders.lodBuilding.LodBuilder;
import com.seibel.lod.builders.lodBuilding.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.util.LodThreadFactory;
import com.seibel.lod.util.LodUtil;
import net.minecraft.block.Block;
@@ -55,6 +56,7 @@ import net.minecraft.world.gen.feature.FeatureSpread;
import net.minecraft.world.gen.feature.FeatureSpreadConfig;
import net.minecraft.world.gen.feature.IceAndSnowFeature;
import net.minecraft.world.gen.feature.NoFeatureConfig;
import net.minecraft.world.gen.feature.template.TemplateManager;
import net.minecraft.world.gen.placement.ConfiguredPlacement;
import net.minecraft.world.gen.placement.DecoratedPlacementConfig;
import net.minecraft.world.gen.placement.IPlacementConfig;
@@ -66,26 +68,28 @@ import net.minecraftforge.common.WorldWorkerManager.IWorker;
/**
* This is used to generate a LodChunk at a given ChunkPos.
*
*
* @author James Seibel
* @version 9-7-2021
* @version 10-22-2021
*/
public class LodNodeGenWorker implements IWorker
public class LodGenWorker implements IWorker
{
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.advancedModOptions.threading.numberOfWorldGenerationThreads.get(), new ThreadFactoryBuilder().setNameFormat("Gen-Worker-Thread-%d").build());
private boolean threadStarted = false;
private LodChunkGenThread thread;
private final LodChunkGenThread thread;
/** If a configured feature fails for whatever reason,
/**
* If a configured feature fails for whatever reason,
* add it to this list, this is to hopefully remove any
* features that could cause issues down the line. */
private static ConcurrentHashMap<Integer, ConfiguredFeature<?, ?>> configuredFeaturesToAvoid = new ConcurrentHashMap<>();
* features that could cause issues down the line.
*/
private static final ConcurrentHashMap<Integer, ConfiguredFeature<?, ?>> configuredFeaturesToAvoid = new ConcurrentHashMap<>();
public LodNodeGenWorker(ChunkPos newPos, DistanceGenerationMode newGenerationMode,
public LodGenWorker(ChunkPos newPos, DistanceGenerationMode newGenerationMode,
LodBuilder newLodBuilder,
LodDimension newLodDimension, ServerWorld newServerWorld)
{
@@ -149,14 +153,14 @@ public class LodNodeGenWorker implements IWorker
private class LodChunkGenThread implements Runnable
private static class LodChunkGenThread implements Runnable
{
public final ServerWorld serverWorld;
public final LodDimension lodDim;
public final DistanceGenerationMode generationMode;
public final LodBuilder lodBuilder;
private ChunkPos pos;
private final ChunkPos pos;
public LodChunkGenThread(ChunkPos newPos, DistanceGenerationMode newGenerationMode,
LodBuilder newLodBuilder,
@@ -177,34 +181,77 @@ public class LodNodeGenWorker implements IWorker
// only generate LodChunks if they can
// be added to the current LodDimension
/**TODO i must disable this if, i will find a way to replace it*/
/* TODO I must disable this 'if', if I will find a way to replace it */
if (lodDim.regionIsInRange(pos.x / LodUtil.REGION_WIDTH_IN_CHUNKS, pos.z / LodUtil.REGION_WIDTH_IN_CHUNKS))
{
// long startTime = System.currentTimeMillis();
switch(generationMode)
//
//{
// lodBuilder.generateLodNodeFromChunk(lodDim, loadedChunk, new LodBuilderConfig(DistanceGenerationMode.SERVER));
//}
//else
//{
/*
IChunk loadedChunk = null;
if (lodDim.isChunkPreGenerated(pos.x, pos.z) && LodConfig.CLIENT.worldGenerator.useExperimentalPreGenLoading.get())
{
case NONE:
// don't generate
break;
case BIOME_ONLY:
case BIOME_ONLY_SIMULATE_HEIGHT:
// fastest
generateUsingBiomesOnly();
break;
case SURFACE:
// faster
generateUsingSurface();
break;
case FEATURES:
// fast
generateUsingFeatures();
break;
case SERVER:
// very slow
generateWithServer();
break;
// generate a Lod like normal
loadedChunk = ChunkLoader.getChunkFromFile(pos);
if(loadedChunk != null)
lodBuilder.generateLodNodeFromChunk(lodDim, loadedChunk, new LodBuilderConfig(DistanceGenerationMode.SERVER));
else
{
switch (generationMode)
{
case NONE:
// don't generate
break;
case BIOME_ONLY:
case BIOME_ONLY_SIMULATE_HEIGHT:
// fastest
generateUsingBiomesOnly();
break;
case SURFACE:
// faster
generateUsingSurface();
break;
case FEATURES:
// fast
generateUsingFeatures();
break;
case SERVER:
// very slow
lodBuilder.generateLodNodeFromChunk(lodDim, serverWorld.getChunk(pos.x, pos.z, ChunkStatus.FEATURES), new LodBuilderConfig(DistanceGenerationMode.SERVER));
//generateWithServer();
break;
}
}
}
else
{*/
switch (generationMode)
{
case NONE:
// don't generate
break;
case BIOME_ONLY:
case BIOME_ONLY_SIMULATE_HEIGHT:
// fastest
generateUsingBiomesOnly();
break;
case SURFACE:
// faster
generateUsingSurface();
break;
case FEATURES:
// fast
generateUsingFeatures();
break;
case SERVER:
// very slow
generateWithServer();
break;
}
//}
//lodRenderer.regenerateLODsNextFrame();
@@ -217,7 +264,7 @@ public class LodNodeGenWorker implements IWorker
*/
// shows the pool size, active threads, queued tasks and completed tasks
// ClientProxy.LOGGER.info(genThreads.toString());
// long endTime = System.currentTimeMillis();
// System.out.println(endTime - startTime);
@@ -255,7 +302,7 @@ public class LodNodeGenWorker implements IWorker
// generate the terrain (this is thread safe)
ChunkStatus.EMPTY.generate(serverWorld, chunkGen, serverWorld.getStructureManager(), (ServerWorldLightManager) serverWorld.getLightEngine(), null, chunkList);
// override the chunk status so we can run the next generator stage
// override the chunk status, so we can run the next generator stage
chunk.setStatus(ChunkStatus.STRUCTURE_REFERENCES);
chunkGen.createBiomes(serverWorld.registryAccess().registryOrThrow(Registry.BIOME_REGISTRY), chunk);
chunk.setStatus(ChunkStatus.STRUCTURE_REFERENCES);
@@ -271,20 +318,20 @@ public class LodNodeGenWorker implements IWorker
// add fake heightmap data so our LODs aren't at height 0
Heightmap heightmap = new Heightmap(chunk, LodUtil.DEFAULT_HEIGHTMAP);
for(int x = 0; x < LodUtil.CHUNK_WIDTH && !inTheEnd; x++)
for (int x = 0; x < LodUtil.CHUNK_WIDTH && !inTheEnd; x++)
{
for(int z = 0; z < LodUtil.CHUNK_WIDTH && !inTheEnd; z++)
for (int z = 0; z < LodUtil.CHUNK_WIDTH && !inTheEnd; z++)
{
if (simulateHeight)
{
// these heights are of course aren't super accurate,
// they are just to simulate height data where there isn't any
switch(chunk.getBiomes().getNoiseBiome(x >> 2, seaLevel >> 2, z >> 2).getBiomeCategory())
switch (chunk.getBiomes().getNoiseBiome(x >> 2, seaLevel >> 2, z >> 2).getBiomeCategory())
{
case NETHER:
heightmap.setHeight(x, z, serverWorld.getHeight() / 2);
break;
case EXTREME_HILLS:
heightmap.setHeight(x, z, seaLevel + 30);
break;
@@ -300,24 +347,24 @@ public class LodNodeGenWorker implements IWorker
case NONE:
heightmap.setHeight(x, z, 0);
break;
case OCEAN:
case RIVER:
heightmap.setHeight(x, z, seaLevel);
break;
case THEEND:
inTheEnd = true;
break;
// DESERT
// FOREST
// ICY
// MUSHROOM
// SAVANNA
// SWAMP
// TAIGA
// PLAINS
// DESERT
// FOREST
// ICY
// MUSHROOM
// SAVANNA
// SWAMP
// TAIGA
// PLAINS
default:
heightmap.setHeight(x, z, seaLevel + 10);
break;
@@ -343,11 +390,12 @@ public class LodNodeGenWorker implements IWorker
{
// if we are in the end, don't generate any chunks.
// Since we don't know where the islands are, everything
// generates the same and it looks really bad.
// generates the same, and it looks awful.
//TODO it appears that 'if' can be collapsed, but comment says that it should not be a case
lodBuilder.generateLodNodeFromChunk(lodDim, chunk, new LodBuilderConfig(true, true, false));
}
// long startTime = System.currentTimeMillis();
// long endTime = System.currentTimeMillis();
// System.out.println(endTime - startTime);
@@ -365,32 +413,35 @@ public class LodNodeGenWorker implements IWorker
LodServerWorld lodServerWorld = new LodServerWorld(serverWorld, chunk);
ServerChunkProvider chunkSource = serverWorld.getChunkSource();
ServerWorldLightManager lightEngine = (ServerWorldLightManager) serverWorld.getLightEngine();
TemplateManager templateManager = serverWorld.getStructureManager();
ChunkGenerator chunkGen = chunkSource.generator;
// generate the terrain (this is thread safe)
ChunkStatus.EMPTY.generate(serverWorld, chunkGen, serverWorld.getStructureManager(), (ServerWorldLightManager) serverWorld.getLightEngine(), null, chunkList);
// override the chunk status so we can run the next generator stage
ChunkStatus.EMPTY.generate(serverWorld, chunkGen, templateManager, lightEngine, null, chunkList);
// override the chunk status, so we can run the next generator stage
chunk.setStatus(ChunkStatus.STRUCTURE_REFERENCES);
chunkGen.createBiomes(serverWorld.registryAccess().registryOrThrow(Registry.BIOME_REGISTRY), chunk);
ChunkStatus.NOISE.generate(serverWorld, chunkGen, serverWorld.getStructureManager(), (ServerWorldLightManager) serverWorld.getLightEngine(), null, chunkList);
ChunkStatus.SURFACE.generate(serverWorld, chunkGen, serverWorld.getStructureManager(), (ServerWorldLightManager) serverWorld.getLightEngine(), null, chunkList);
ChunkStatus.NOISE.generate(serverWorld, chunkGen, templateManager, lightEngine, null, chunkList);
ChunkStatus.SURFACE.generate(serverWorld, chunkGen, templateManager, lightEngine, null, chunkList);
// this feature has been proven to be thread safe
// this feature has been proven to be thread safe,
// so we will add it
IceAndSnowFeature snowFeature = new IceAndSnowFeature(NoFeatureConfig.CODEC);
snowFeature.place(lodServerWorld, chunkGen, serverWorld.random, chunk.getPos().getWorldPosition(), null);
lodBuilder.generateLodNodeFromChunk(lodDim, chunk, new LodBuilderConfig(DistanceGenerationMode.SURFACE));
/**TODO if we want to use Biome utils and terrain utils for overworld
/*TODO if we want to use Biome utils and terrain utils for overworld
* lodBuilder.generateLodNodeFromChunk(lodDim, pos ,detailLevel, serverWorld.getSeed());*/
}
/**
* takes about 15 - 20 ms
*
* <p>
* Causes concurrentModification Exceptions,
* which could cause instability or world generation bugs
*/
@@ -402,16 +453,18 @@ public class LodNodeGenWorker implements IWorker
LodServerWorld lodServerWorld = new LodServerWorld(serverWorld, chunk);
ServerChunkProvider chunkSource = serverWorld.getChunkSource();
ServerWorldLightManager lightEngine = (ServerWorldLightManager) serverWorld.getLightEngine();
TemplateManager templateManager = serverWorld.getStructureManager();
ChunkGenerator chunkGen = chunkSource.generator;
// generate the terrain (this is thread safe)
ChunkStatus.EMPTY.generate(serverWorld, chunkGen, serverWorld.getStructureManager(), (ServerWorldLightManager) serverWorld.getLightEngine(), null, chunkList);
// override the chunk status so we can run the next generator stage
ChunkStatus.EMPTY.generate(serverWorld, chunkGen, templateManager, lightEngine, null, chunkList);
// override the chunk status, so we can run the next generator stage
chunk.setStatus(ChunkStatus.STRUCTURE_REFERENCES);
chunkGen.createBiomes(serverWorld.registryAccess().registryOrThrow(Registry.BIOME_REGISTRY), chunk);
ChunkStatus.NOISE.generate(serverWorld, chunkGen, serverWorld.getStructureManager(), (ServerWorldLightManager) serverWorld.getLightEngine(), null, chunkList);
ChunkStatus.SURFACE.generate(serverWorld, chunkGen, serverWorld.getStructureManager(), (ServerWorldLightManager) serverWorld.getLightEngine(), null, chunkList);
ChunkStatus.NOISE.generate(serverWorld, chunkGen, templateManager, lightEngine, null, chunkList);
ChunkStatus.SURFACE.generate(serverWorld, chunkGen, templateManager, lightEngine, null, chunkList);
// get all the biomes in the chunk
@@ -445,14 +498,14 @@ public class LodNodeGenWorker implements IWorker
{
List<List<Supplier<ConfiguredFeature<?, ?>>>> featuresForState = biome.generationSettings.features();
for(int featureStateToGenerate = 0; featureStateToGenerate < featuresForState.size(); featureStateToGenerate++)
for (List<Supplier<ConfiguredFeature<?, ?>>> suppliers : featuresForState)
{
for(Supplier<ConfiguredFeature<?, ?>> featureSupplier : featuresForState.get(featureStateToGenerate))
for (Supplier<ConfiguredFeature<?, ?>> featureSupplier : suppliers)
{
ConfiguredFeature<?, ?> configuredFeature = featureSupplier.get();
if (!allowUnstableFeatures &&
configuredFeaturesToAvoid.containsKey(configuredFeature.hashCode()))
configuredFeaturesToAvoid.containsKey(configuredFeature.hashCode()))
continue;
@@ -460,10 +513,10 @@ public class LodNodeGenWorker implements IWorker
{
configuredFeature.place(lodServerWorld, chunkGen, serverWorld.random, chunk.getPos().getWorldPosition());
}
catch(ConcurrentModificationException e)
catch (ConcurrentModificationException e)
{
// This will happen. I'm not sure what to do about it
// except pray that it doesn't effect the normal world generation
// except pray that it doesn't affect the normal world generation
// in any harmful way.
// Update: this can cause crashes and high CPU usage.
@@ -480,7 +533,7 @@ public class LodNodeGenWorker implements IWorker
configuredFeaturesToAvoid.put(configuredFeature.hashCode(), configuredFeature);
// ClientProxy.LOGGER.info(configuredFeaturesToAvoid.mappingCount());
}
catch(UnsupportedOperationException e)
catch (UnsupportedOperationException e)
{
// This will happen when the LodServerWorld
// isn't able to return something that a feature
@@ -490,7 +543,7 @@ public class LodNodeGenWorker implements IWorker
configuredFeaturesToAvoid.put(configuredFeature.hashCode(), configuredFeature);
// ClientProxy.LOGGER.info(configuredFeaturesToAvoid.mappingCount());
}
catch(Exception e)
catch (Exception e)
{
// I'm not sure what happened, print to the log
@@ -512,15 +565,15 @@ public class LodNodeGenWorker implements IWorker
/**
* on pre generated chunks 0 - 1 ms
* on un generated chunks 0 - 50 ms
* with the median seeming to hover around 15 - 30 ms
* and outliers in the 100 - 200 ms range
*
* with the median seeming to hover around 15 - 30 ms
* and outliers in the 100 - 200 ms range
* <p>
* Note this should not be multithreaded and does cause server/simulation lag
* (Higher lag for generating than loading)
*/
private void generateWithServer()
{
lodBuilder.generateLodNodeAsync(serverWorld.getChunk(pos.x, pos.z, ChunkStatus.FEATURES), ClientProxy.getLodWorld(), serverWorld);
lodBuilder.generateLodNodeFromChunk(lodDim, serverWorld.getChunk(pos.x, pos.z, ChunkStatus.FEATURES), new LodBuilderConfig(DistanceGenerationMode.SERVER));
}
@@ -538,7 +591,7 @@ public class LodNodeGenWorker implements IWorker
@SuppressWarnings({ "rawtypes", "unchecked", "unused" })
private DecoratedFeatureConfig cloneDecoratedFeatureConfig(DecoratedFeatureConfig config)
{
IPlacementConfig placementConfig = null;
IPlacementConfig placementConfig;
Class oldConfigClass = config.decorator.config().getClass();
@@ -549,19 +602,19 @@ public class LodNodeGenWorker implements IWorker
placementConfig = new FeatureSpreadConfig(oldSpread);
}
else if(oldConfigClass == DecoratedPlacementConfig.class)
else if (oldConfigClass == DecoratedPlacementConfig.class)
{
DecoratedPlacementConfig oldPlacementConfig = (DecoratedPlacementConfig) config.decorator.config();
placementConfig = new DecoratedPlacementConfig(oldPlacementConfig.inner(), oldPlacementConfig.outer());
}
else if(oldConfigClass == NoiseDependant.class)
else if (oldConfigClass == NoiseDependant.class)
{
NoiseDependant oldPlacementConfig = (NoiseDependant) config.decorator.config();
placementConfig = new NoiseDependant(oldPlacementConfig.noiseLevel, oldPlacementConfig.belowNoise, oldPlacementConfig.aboveNoise);
}
else
{
// ClientProxy.LOGGER.debug("unkown decorated placement config: \"" + config.decorator.config().getClass() + "\"");
// ClientProxy.LOGGER.debug("unknown decorated placement config: \"" + config.decorator.config().getClass() + "\"");
return config;
}
@@ -588,9 +641,18 @@ public class LodNodeGenWorker implements IWorker
builder.xspread(config.xspread);
builder.yspread(config.yspread);
builder.zspread(config.zspread);
if(config.canReplace) { builder.canReplace(); }
if(config.needWater) { builder.needWater(); }
if(config.project) { builder.noProjection(); }
if (config.canReplace)
{
builder.canReplace();
}
if (config.needWater)
{
builder.needWater();
}
if (config.project)
{
builder.noProjection();
}
builder.tries(config.tries);
@@ -602,20 +664,20 @@ public class LodNodeGenWorker implements IWorker
/**
* Stops the current genThreads if they are running
* and then recreates the Executer service. <br><br>
*
* and then recreates the Executor service. <br><br>
* <p>
* This is done to clear any outstanding tasks
* that may exist after the player leaves their current world.
* If this isn't done unfinished tasks may be left in the queue
* preventing new LodChunks form being generated.
*/
public static void restartExecuterService()
public static void restartExecutorService()
{
if (genThreads != null && !genThreads.isShutdown())
{
genThreads.shutdownNow();
}
genThreads = Executors.newFixedThreadPool(LodConfig.CLIENT.threading.numberOfWorldGenerationThreads.get(), new LodThreadFactory(LodNodeGenWorker.class.getSimpleName()));
genThreads = Executors.newFixedThreadPool(LodConfig.CLIENT.advancedModOptions.threading.numberOfWorldGenerationThreads.get(), new ThreadFactoryBuilder().setNameFormat("Gen-Worker-Thread-%d").build());
}
@@ -646,6 +708,6 @@ public class LodNodeGenWorker implements IWorker
At this point I would suggest using FEATURES, as it generates snow and trees
(and any other object that is needed to make biomes distinct)
Otherwise if snow/trees aren't necessary SURFACE is the next fastest (although not by much)
Otherwise, if snow/trees aren't necessary SURFACE is the next fastest (although not by much)
*/
}
@@ -1,5 +1,6 @@
/*
* This file is part of the LOD Mod, licensed under the GNU GPL v3 License.
* This file is part of the Distant Horizon mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020 James Seibel
*
@@ -15,6 +16,7 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.lod.builders.worldGeneration;
import java.util.HashMap;
@@ -65,266 +67,265 @@ import net.minecraft.world.storage.IWorldInfo;
* This allows us to keep each LodChunk generation independent
* of the actual ServerWorld, allowing us
* to multithread generation.
*
* @author James Seibel
* @version 7-26-2021
*/
public class LodServerWorld implements ISeedReader
{
public HashMap<Heightmap.Type, Heightmap> heightmaps = new HashMap<>();
public IChunk chunk;
public ServerWorld serverWorld;
public final IChunk chunk;
public final ServerWorld serverWorld;
public LodServerWorld(ServerWorld newServerWorld, IChunk newChunk)
{
chunk = newChunk;
serverWorld = newServerWorld;
}
@Override
public int getHeight(Type heightmapType, int x, int z)
{
// make sure the block position is set relative to the chunk
x = x % LodUtil.CHUNK_WIDTH;
x = (x < 0) ? x + 16 : x;
z = z % LodUtil.CHUNK_WIDTH;
z = (z < 0) ? z + 16 : z;
return chunk.getOrCreateHeightmapUnprimed(LodUtil.DEFAULT_HEIGHTMAP).getFirstAvailable(x, z);
}
@Override
public Biome getBiome(BlockPos pos)
{
return chunk.getBiomes().getNoiseBiome(pos.getX() >> 2, pos.getY() >> 2, pos.getZ() >> 2);
}
@Override
public boolean setBlock(BlockPos pos, BlockState state, int flags, int recursionLeft)
{
return chunk.setBlockState(pos, state, false) == state;
}
@Override
public BlockState getBlockState(BlockPos pos)
{
return chunk.getBlockState(pos);
}
@Override
public FluidState getFluidState(BlockPos pos)
{
return chunk.getFluidState(pos);
}
@Override
public boolean isStateAtPosition(BlockPos pos, Predicate<BlockState> state)
{
return state.test(chunk.getBlockState(pos));
}
@Override
public ITickList<Block> getBlockTicks()
{
return EmptyTickList.empty();
}
@Override
public IChunk getChunk(int x, int z, ChunkStatus requiredStatus, boolean nonnull)
{
return chunk;
}
@Override
public Stream<? extends StructureStart<?>> startsForFeature(SectionPos p_241827_1_, Structure<?> p_241827_2_)
{
return serverWorld.startsForFeature(p_241827_1_, p_241827_2_);
}
@Override
public ITickList<Fluid> getLiquidTicks()
{
return EmptyTickList.empty();
}
@Override
public WorldLightManager getLightEngine()
{
return new WorldLightManager(null, false, false);
}
@Override
public long getSeed()
{
return serverWorld.getSeed();
}
@Override
public DynamicRegistries registryAccess()
{
return serverWorld.registryAccess();
}
/**
* All methods below shouldn't be needed
* and thus have been left unimplemented.
*/
@Override
public Random getRandom()
{
throw new UnsupportedOperationException("Not Implemented");
}
@Override
public void playSound(PlayerEntity player, BlockPos pos, SoundEvent soundIn, SoundCategory category, float volume,
float pitch)
float pitch)
{
throw new UnsupportedOperationException("Not Implemented");
}
@Override
public void addParticle(IParticleData particleData, double x, double y, double z, double xSpeed, double ySpeed,
double zSpeed)
double zSpeed)
{
throw new UnsupportedOperationException("Not Implemented");
}
@Override
public BiomeManager getBiomeManager()
{
throw new UnsupportedOperationException("Not Implemented");
}
@Override
public int getSeaLevel()
{
throw new UnsupportedOperationException("Not Implemented");
}
@Override
public float getShade(Direction p_230487_1_, boolean p_230487_2_)
{
throw new UnsupportedOperationException("Not Implemented");
}
@Override
public WorldBorder getWorldBorder()
{
throw new UnsupportedOperationException("Not Implemented");
}
@Override
public boolean removeBlock(BlockPos pos, boolean isMoving)
{
throw new UnsupportedOperationException("Not Implemented");
}
@Override
public boolean destroyBlock(BlockPos pos, boolean dropBlock, Entity entity, int recursionLeft)
{
throw new UnsupportedOperationException("Not Implemented");
}
@Override
public ServerWorld getLevel()
{
throw new UnsupportedOperationException("Not Implemented");
}
@Override
public AbstractChunkProvider getChunkSource()
{
throw new UnsupportedOperationException("Not Implemented");
}
@Override
public DifficultyInstance getCurrentDifficultyAt(BlockPos arg0)
{
throw new UnsupportedOperationException("Not Implemented");
}
@Override
public IWorldInfo getLevelData()
{
throw new UnsupportedOperationException("Not Implemented");
}
@Override
public void levelEvent(PlayerEntity arg0, int arg1, BlockPos arg2, int arg3)
{
throw new UnsupportedOperationException("Not Implemented");
}
@Override
public List<Entity> getEntities(Entity arg0, AxisAlignedBB arg1, Predicate<? super Entity> arg2)
{
throw new UnsupportedOperationException("Not Implemented");
}
@Override
public <T extends Entity> List<T> getEntitiesOfClass(Class<? extends T> arg0, AxisAlignedBB arg1,
Predicate<? super T> arg2)
Predicate<? super T> arg2)
{
throw new UnsupportedOperationException("Not Implemented");
}
@Override
public List<? extends PlayerEntity> players()
{
throw new UnsupportedOperationException("Not Implemented");
}
@Override
public int getSkyDarken()
{
throw new UnsupportedOperationException("Not Implemented");
}
@Override
public Biome getUncachedNoiseBiome(int p_225604_1_, int p_225604_2_, int p_225604_3_)
{
throw new UnsupportedOperationException("Not Implemented");
}
@Override
public boolean isClientSide()
{
throw new UnsupportedOperationException("Not Implemented");
}
@Override
public DimensionType dimensionType()
{
throw new UnsupportedOperationException("Not Implemented");
}
@Override
public TileEntity getBlockEntity(BlockPos p_175625_1_)
{
throw new UnsupportedOperationException("Not Implemented");
}
}
@@ -1,3 +1,22 @@
/*
* This file is part of the Distant Horizon mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.lod.builders.worldGeneration;
import java.util.HashSet;
@@ -6,7 +25,7 @@ import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicInteger;
import com.seibel.lod.builders.LodBuilder;
import com.seibel.lod.builders.lodBuilding.LodBuilder;
import com.seibel.lod.config.LodConfig;
import com.seibel.lod.enums.DistanceGenerationMode;
import com.seibel.lod.objects.LodDimension;
@@ -24,25 +43,24 @@ import net.minecraftforge.common.WorldWorkerManager;
/**
* A singleton that handles all long distance LOD world generation.
*
* @author James Seibel
* @version 9-25-2021
*/
public class LodWorldGenerator
{
public MinecraftWrapper mc = MinecraftWrapper.INSTANCE;
public final MinecraftWrapper mc = MinecraftWrapper.INSTANCE;
/** This holds the thread used to generate new LODs off the main thread. */
private ExecutorService mainGenThread = Executors.newSingleThreadExecutor(new LodThreadFactory(this.getClass().getSimpleName() + " world generator"));
private final ExecutorService mainGenThread = Executors.newSingleThreadExecutor(new LodThreadFactory(this.getClass().getSimpleName() + " world generator"));
/** we only want to queue up one generator thread at a time */
private boolean generatorThreadRunning = false;
/**
* How many chunks to generate outside of the player's view distance at one
* How many chunks to generate outside the player's view distance at one
* time. (or more specifically how many requests to make at one time). I
* multiply by 8 to make sure there is always a buffer of chunk requests, to
* make sure the CPU is always busy and we can generate LODs as quickly as
* make sure the CPU is always busy, and we can generate LODs as quickly as
* possible.
*/
public int maxChunkGenRequests;
@@ -52,9 +70,9 @@ public class LodWorldGenerator
* to limit how many chunks are queued at once. To prevent chunks from being
* generated for a long time in an area the player is no longer in.
*/
public AtomicInteger numberOfChunksWaitingToGenerate = new AtomicInteger(0);
public final AtomicInteger numberOfChunksWaitingToGenerate = new AtomicInteger(0);
public Set<ChunkPos> positionsWaitingToBeGenerated = new HashSet<>();
public final Set<ChunkPos> positionsWaitingToBeGenerated = new HashSet<>();
/**
* Singleton copy of this object
@@ -69,21 +87,20 @@ public class LodWorldGenerator
/**
* Queues up LodNodeGenWorkers for the given lodDimension.
*
* @param renderer needed so the LodNodeGenWorkers can flag that the
* buffers need to be rebuilt.
* buffers need to be rebuilt.
*/
public void queueGenerationRequests(LodDimension lodDim, LodRenderer renderer, LodBuilder lodBuilder)
{
if (LodConfig.CLIENT.worldGenerator.distanceGenerationMode.get() != DistanceGenerationMode.NONE
&& !generatorThreadRunning
&& mc.hasSingleplayerServer())
&& mc.hasSinglePlayerServer())
{
// the thread is now running, don't queue up another thread
generatorThreadRunning = true;
// just in case the config changed
maxChunkGenRequests = LodConfig.CLIENT.threading.numberOfWorldGenerationThreads.get() * 8;
maxChunkGenRequests = LodConfig.CLIENT.advancedModOptions.threading.numberOfWorldGenerationThreads.get() * 8;
Thread generatorThread = new Thread(() ->
{
@@ -100,7 +117,7 @@ public class LodWorldGenerator
ServerWorld serverWorld = LodUtil.getServerWorldFromDimension(lodDim.dimension);
PosToGenerateContainer posToGenerate = lodDim.getDataToGenerate(
PosToGenerateContainer posToGenerate = lodDim.getPosToGenerate(
maxChunkGenRequests,
playerPosX,
playerPosZ);
@@ -115,7 +132,7 @@ public class LodWorldGenerator
for (int i = 0; i < posToGenerate.getNumberOfPos(); i++)
{
// I wish there was a way to compress this code, but I'm not aware of
// a easy way to do so.
// an easy way to do so.
// add the near positions
if (posToGenerate.getNthDetail(nearIndex, true) != 0 && nearIndex < posToGenerate.getNumberOfNearPos())
@@ -126,12 +143,10 @@ public class LodWorldGenerator
nearIndex++;
ChunkPos chunkPos = new ChunkPos(LevelPosUtil.getChunkPos(detailLevel, posX), LevelPosUtil.getChunkPos(detailLevel, posZ));
if (numberOfChunksWaitingToGenerate.get() < maxChunkGenRequests)
{
// prevent generating the same chunk multiple times
if (positionsWaitingToBeGenerated.contains(chunkPos))
continue;
}
// prevent generating the same chunk multiple times
if (positionsWaitingToBeGenerated.contains(chunkPos))
continue;
// don't add more to the generation queue then allowed
if (numberOfChunksWaitingToGenerate.get() >= maxChunkGenRequests)
@@ -139,7 +154,7 @@ public class LodWorldGenerator
positionsWaitingToBeGenerated.add(chunkPos);
numberOfChunksWaitingToGenerate.addAndGet(1);
LodNodeGenWorker genWorker = new LodNodeGenWorker(chunkPos, DetailDistanceUtil.getDistanceGenerationMode(detailLevel), lodBuilder, lodDim, serverWorld);
LodGenWorker genWorker = new LodGenWorker(chunkPos, DetailDistanceUtil.getDistanceGenerationMode(detailLevel), lodBuilder, lodDim, serverWorld);
WorldWorkerManager.addWorker(genWorker);
}
@@ -153,20 +168,19 @@ public class LodWorldGenerator
farIndex++;
ChunkPos chunkPos = new ChunkPos(LevelPosUtil.getChunkPos(detailLevel, posX), LevelPosUtil.getChunkPos(detailLevel, posZ));
if (numberOfChunksWaitingToGenerate.get() < maxChunkGenRequests)
{
// prevent generating the same chunk multiple times
if (positionsWaitingToBeGenerated.contains(chunkPos))
continue;
}
// don't add more to the generation queue then allowed
if (numberOfChunksWaitingToGenerate.get() >= maxChunkGenRequests)
break;
continue;
//break;
// prevent generating the same chunk multiple times
if (positionsWaitingToBeGenerated.contains(chunkPos))
continue;
positionsWaitingToBeGenerated.add(chunkPos);
numberOfChunksWaitingToGenerate.addAndGet(1);
LodNodeGenWorker genWorker = new LodNodeGenWorker(chunkPos, DetailDistanceUtil.getDistanceGenerationMode(detailLevel), lodBuilder, lodDim, serverWorld);
LodGenWorker genWorker = new LodGenWorker(chunkPos, DetailDistanceUtil.getDistanceGenerationMode(detailLevel), lodBuilder, lodDim, serverWorld);
WorldWorkerManager.addWorker(genWorker);
}
}
+443 -364
View File
@@ -1,5 +1,6 @@
/*
* This file is part of the LOD Mod, licensed under the GNU GPL v3 License.
* This file is part of the Distant Horizon mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020 James Seibel
*
@@ -15,6 +16,7 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.lod.config;
import java.nio.file.Path;
@@ -26,8 +28,9 @@ import org.apache.logging.log4j.LogManager;
import com.electronwill.nightconfig.core.file.CommentedFileConfig;
import com.electronwill.nightconfig.core.io.WritingMode;
import com.seibel.lod.ModInfo;
import com.seibel.lod.enums.BlockToAvoid;
import com.seibel.lod.enums.BufferRebuildTimes;
import com.seibel.lod.enums.DebugMode;
import com.seibel.lod.enums.DetailDropOff;
import com.seibel.lod.enums.DistanceGenerationMode;
import com.seibel.lod.enums.FogDistance;
import com.seibel.lod.enums.FogDrawOverride;
@@ -36,8 +39,9 @@ import com.seibel.lod.enums.HorizontalQuality;
import com.seibel.lod.enums.HorizontalResolution;
import com.seibel.lod.enums.HorizontalScale;
import com.seibel.lod.enums.LodTemplate;
import com.seibel.lod.enums.VanillaOverdraw;
import com.seibel.lod.enums.VerticalQuality;
import com.seibel.lod.render.LodRenderer.FovTest;
import com.seibel.lod.util.LodUtil;
import net.minecraftforge.common.ForgeConfigSpec;
import net.minecraftforge.eventbus.api.SubscribeEvent;
@@ -46,401 +50,476 @@ import net.minecraftforge.fml.config.ModConfig;
/**
* This handles any configuration the user has access to.
*
* @author James Seibel
* @version 9-26-2021
* @version 10-19-2021
*/
@Mod.EventBusSubscriber
public class LodConfig
{
// CONFIG STRUCTURE
// -> Client
// |
// |-> Graphics
// | |-> QualityOption
// | |-> FogQualityOption
// | |-> AdvancedGraphicsOption
// |
// |-> World Generation
// |
// |-> Advanced Mod Option
// |-> Threads
// |-> Buffers
// |-> Debugging
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 final AdvancedModOptions advancedModOptions;
//================//
// Client Configs //
//================//
public Client(ForgeConfigSpec.Builder builder)
{
builder.push("client");
builder.push(this.getClass().getSimpleName());
{
graphics = new Graphics(builder);
worldGenerator = new WorldGenerator(builder);
threading = new Threading(builder);
debugging = new Debugging(builder);
buffers = new Buffers(builder);
advancedModOptions = new AdvancedModOptions(builder);
}
builder.pop();
}
}
//================//
// Client Configs //
//================//
public static class Graphics
{
public ForgeConfigSpec.BooleanValue drawLods;
public ForgeConfigSpec.EnumValue<FogDistance> fogDistance;
public ForgeConfigSpec.EnumValue<FogDrawOverride> fogDrawOverride;
public ForgeConfigSpec.EnumValue<LodTemplate> lodTemplate;
public ForgeConfigSpec.EnumValue<HorizontalResolution> drawResolution;
// public ForgeConfigSpec.EnumValue<ShadingMode> shadingMode;
public ForgeConfigSpec.EnumValue<DetailDropOff> detailDropOff;
public ForgeConfigSpec.IntValue lodChunkRenderDistance;
public ForgeConfigSpec.BooleanValue disableDirectionalCulling;
public ForgeConfigSpec.BooleanValue alwaysDrawAtMaxQuality;
public ForgeConfigSpec.EnumValue<FovTest> useFovSetting;
Graphics(ForgeConfigSpec.Builder builder)
//==================//
// Graphics Configs //
//==================//
public static class Graphics
{
builder.comment("These settings control how the LODs look.").push(this.getClass().getSimpleName());
drawLods = builder
.comment("\n\n"
+ " If true, the mod is enabled and LODs will be drawn. \n"
+ " If false, the mod will still generate LODs, \n"
+ " but they won't be rendered. \n")
.define("drawLODs", true);
public final QualityOption qualityOption;
public final FogQualityOption fogQualityOption;
public final AdvancedGraphicsOption advancedGraphicsOption;
fogDistance = builder
.comment("\n\n"
+ " At what distance should Fog be drawn on the LODs? \n"
+ " If the fog cuts off ubruptly or you are using Optifine's \"fast\" fog option \n"
+ " set this to " + FogDistance.NEAR + " or " + FogDistance.FAR + ". \n")
.defineEnum("fogDistance", FogDistance.NEAR_AND_FAR);
fogDrawOverride = builder
.comment("\n\n"
+ " When should fog be drawn? \n"
+ " " + FogDrawOverride.USE_OPTIFINE_FOG_SETTING + ": Use whatever Fog setting Optifine is using. If Optifine isn't installed this defaults to " + FogDrawOverride.ALWAYS_DRAW_FOG_FANCY + ". \n"
+ " " + FogDrawOverride.NEVER_DRAW_FOG + ": Never draw fog on the LODs \n"
+ " " + FogDrawOverride.ALWAYS_DRAW_FOG_FAST + ": Always draw fast fog on the LODs \n"
+ " " + FogDrawOverride.ALWAYS_DRAW_FOG_FANCY + ": Always draw fancy fog on the LODs (if your graphics card supports it) \n")
.defineEnum("fogDrawOverride", FogDrawOverride.ALWAYS_DRAW_FOG_FANCY);
lodTemplate = builder
.comment("\n\n"
+ " How should the LODs be drawn? \n"
+ " NOTE: Currently only " + LodTemplate.CUBIC + " is implemented! \n"
+ " \n"
+ " " + LodTemplate.CUBIC + ": LOD Chunks are drawn as rectangular prisms (boxes). \n"
+ " " + LodTemplate.TRIANGULAR + ": LOD Chunks smoothly transition between other. \n"
+ " " + LodTemplate.DYNAMIC + ": LOD Chunks smoothly transition between other, \n"
+ " " + " unless a neighboring chunk is at a significantly different height. \n")
.defineEnum("lodTemplate", LodTemplate.CUBIC);
detailDropOff = builder
.comment("\n\n"
+ " How smooth should the detail transition for LODs be? \n"
+ DetailDropOff.FANCY + ": quality is determined per-block (best quality option, may cause stuttering when moving)\n"
+ DetailDropOff.FAST + ": quality is determined per-region (performance option)\n")
.defineEnum("detailDropOff", DetailDropOff.FANCY);
drawResolution = builder
.comment("\n\n"
+ " What is the maximum detail LODs should be drawn at? \n"
+ " " + HorizontalResolution.CHUNK + ": render 1 LOD for each Chunk. \n"
+ " " + HorizontalResolution.HALF_CHUNK + ": render 4 LODs for each Chunk. \n"
+ " " + HorizontalResolution.FOUR_BLOCKS + ": render 16 LODs for each Chunk. \n"
+ " " + HorizontalResolution.TWO_BLOCKS + ": render 64 LODs for each Chunk. \n"
+ " " + HorizontalResolution.BLOCK + ": render 256 LODs for each Chunk. \n")
.defineEnum("Draw resolution", HorizontalResolution.BLOCK);
Graphics(ForgeConfigSpec.Builder builder)
{
builder.comment("These settings control how the mod will look in game").push("Graphics");
{
qualityOption = new QualityOption(builder);
advancedGraphicsOption = new AdvancedGraphicsOption(builder);
fogQualityOption = new FogQualityOption(builder);
}
builder.pop();
}
lodChunkRenderDistance = builder
.comment("\n\n"
+ " The mod's render distance, measured in chunks. \n")
.defineInRange("lodChunkRenderDistance", 64, 32, 1024);
public static class QualityOption
{
public final ForgeConfigSpec.EnumValue<HorizontalResolution> drawResolution;
public final ForgeConfigSpec.IntValue lodChunkRenderDistance;
public final ForgeConfigSpec.EnumValue<VerticalQuality> verticalQuality;
public final ForgeConfigSpec.EnumValue<HorizontalScale> horizontalScale;
public final ForgeConfigSpec.EnumValue<HorizontalQuality> horizontalQuality;
QualityOption(ForgeConfigSpec.Builder builder)
{
builder.comment("These settings control how detailed the fake chunks will be.").push(this.getClass().getSimpleName());
verticalQuality = builder
.comment("\n\n"
+ " This indicates how detailed fake chunks will represent \n"
+ " overhangs, caves, floating islands, ect. \n"
+ " Higher options will use more memory and increase GPU usage. \n"
+ " " + VerticalQuality.LOW + ": uses at max 2 columns per position. \n"
+ " " + VerticalQuality.MEDIUM + ": uses at max 4 columns per position. \n"
+ " " + VerticalQuality.HIGH + ": uses at max 8 columns per position. \n")
.defineEnum("Vertical Quality", VerticalQuality.MEDIUM);
horizontalScale = builder
.comment("\n\n"
+ " This indicates how quickly fake chunks drop off in quality. \n"
+ " " + HorizontalScale.LOW + ": quality drops every " + HorizontalScale.LOW.distanceUnit / 16 + " chunks. \n"
+ " " + HorizontalScale.MEDIUM + ": quality drops every " + HorizontalScale.MEDIUM.distanceUnit / 16 + " chunks. \n"
+ " " + HorizontalScale.HIGH + ": quality drops every " + HorizontalScale.HIGH.distanceUnit / 16 + " chunks. \n")
.defineEnum("Horizontal Scale", HorizontalScale.MEDIUM);
horizontalQuality = builder
.comment("\n\n"
+ " This indicates the exponential base of the quadratic drop-off \n"
+ " " + HorizontalQuality.LOWEST + ": base " + HorizontalQuality.LOWEST.quadraticBase + ". \n"
+ " " + HorizontalQuality.LOW + ": base " + HorizontalQuality.LOW.quadraticBase + ". \n"
+ " " + HorizontalQuality.MEDIUM + ": base " + HorizontalQuality.MEDIUM.quadraticBase + ". \n"
+ " " + HorizontalQuality.HIGH + ": base " + HorizontalQuality.HIGH.quadraticBase + ". \n")
.defineEnum("Horizontal Quality", HorizontalQuality.MEDIUM);
drawResolution = builder
.comment("\n\n"
+ " What is the maximum detail fake chunks should be drawn at? \n"
+ " " + HorizontalResolution.CHUNK + ": render 1 LOD for each Chunk. \n"
+ " " + HorizontalResolution.HALF_CHUNK + ": render 4 LODs for each Chunk. \n"
+ " " + HorizontalResolution.FOUR_BLOCKS + ": render 16 LODs for each Chunk. \n"
+ " " + HorizontalResolution.TWO_BLOCKS + ": render 64 LODs for each Chunk. \n"
+ " " + HorizontalResolution.BLOCK + ": render 256 LODs for each Chunk. \n")
.defineEnum("Block size", HorizontalResolution.BLOCK);
lodChunkRenderDistance = builder
.comment("\n\n"
+ " The mod's render distance, measured in chunks. \n")
.defineInRange("Lod Render Distance", 64, 32, 1024);
builder.pop();
}
}
useFovSetting = builder
.comment("\n\n"
+ " Experimental text value. \n"
+ " " + FovTest.BOTH + ": is the normal value \n")
.defineEnum("useFovSetting", FovTest.BOTH);
disableDirectionalCulling = builder
.comment("\n\n"
+ " If false LODs that are behind the player's camera \n"
+ " aren't drawn, increasing performance. \n\n"
+ ""
+ " If true all LODs are drawn, even those behind \n"
+ " the player's camera, decreasing performance. \n\n"
+ ""
+ " Disable this if you see LODs disapearing. \n"
+ " (This may happen if you are using a camera mod) \n")
.define("disableDirectionalCulling", false);
public static class FogQualityOption
{
public final ForgeConfigSpec.EnumValue<FogDistance> fogDistance;
public final ForgeConfigSpec.EnumValue<FogDrawOverride> fogDrawOverride;
FogQualityOption(ForgeConfigSpec.Builder builder)
{
builder.comment("These settings control the fog quality.").push(this.getClass().getSimpleName());
fogDistance = builder
.comment("\n\n"
+ " At what distance should Fog be drawn on the fake chunks? \n"
+ " If the fog cuts off abruptly or you are using Optifine's \"fast\" fog option \n"
+ " set this to " + FogDistance.NEAR + " or " + FogDistance.FAR + ". \n")
.defineEnum("Fog Distance", FogDistance.NEAR_AND_FAR);
fogDrawOverride = builder
.comment("\n\n"
+ " When should fog be drawn? \n"
+ " " + FogDrawOverride.OPTIFINE_SETTING + ": Use whatever Fog setting Optifine is using. If Optifine isn't installed this defaults to " + FogDrawOverride.FANCY + ". \n"
+ " " + FogDrawOverride.NO_FOG + ": Never draw fog on the LODs \n"
+ " " + FogDrawOverride.FAST + ": Always draw fast fog on the LODs \n"
+ " " + FogDrawOverride.FANCY + ": Always draw fancy fog on the LODs (if your graphics card supports it) \n")
.defineEnum("Fog Draw Override", FogDrawOverride.FANCY);
builder.pop();
}
}
// shadingMode = builder
// .comment("\n\n"
// + " What kind of shading should the LODs have? \n"
// + " \n"
// + " " + ShadingMode.NONE + " \n"
// + " " + "LODs will have the same lighting on every side. \n"
// + " " + "Can make large similarly colored areas hard to differentiate. \n"
// + "\n"
// + " " + ShadingMode.GAME_SHADING + " \n"
// + " " + "LODs will have darker sides and bottoms to simulate Minecraft's flat lighting.")
// .defineEnum("lightingMode", ShadingMode.GAME_SHADING);
alwaysDrawAtMaxQuality = builder
.comment("\n\n"
+ " Disable LOD quality falloff, "
+ " all LODs will be drawn at the highest "
+ " available detail level. "
+ " "
+ " WARNING "
+ " This could cause a Out Of Memory crash on render "
+ " distances higher than 128 \n")
.define("alwaysDrawAtMaxQuality", false);
public static class AdvancedGraphicsOption
{
public final ForgeConfigSpec.EnumValue<LodTemplate> lodTemplate;
public final ForgeConfigSpec.BooleanValue disableDirectionalCulling;
public final ForgeConfigSpec.BooleanValue alwaysDrawAtMaxQuality;
public final ForgeConfigSpec.EnumValue<VanillaOverdraw> vanillaOverdraw;
AdvancedGraphicsOption(ForgeConfigSpec.Builder builder)
{
builder.comment("Advanced graphics option for the mod").push(this.getClass().getSimpleName());
lodTemplate = builder
.comment("\n\n"
+ " How should the LODs be drawn? \n"
+ " NOTE: Currently only " + LodTemplate.CUBIC + " is implemented! \n"
+ " \n"
+ " " + LodTemplate.CUBIC + ": LOD Chunks are drawn as rectangular prisms (boxes). \n"
+ " " + LodTemplate.TRIANGULAR + ": LOD Chunks smoothly transition between other. \n"
+ " " + LodTemplate.DYNAMIC + ": LOD Chunks smoothly transition between each other, \n"
+ " " + " unless a neighboring chunk is at a significantly different height. \n")
.defineEnum("LOD Template", LodTemplate.CUBIC);
disableDirectionalCulling = builder
.comment("\n\n"
+ " If false fake chunks 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 disappearing. \n"
+ " (Which may happen if you are using a camera mod) \n")
.define("Disable Directional Culling", false);
alwaysDrawAtMaxQuality = builder
.comment("\n\n"
+ " Disable quality falloff, \n"
+ " all fake chunks will be drawn at the highest \n"
+ " available detail level. \n\n"
+ " "
+ " WARNING: \n"
+ " This could cause a Out Of Memory crash on render \n"
+ " distances higher than 128 \n")
.define("Always Use Max Quality", false);
vanillaOverdraw = builder
.comment("\n\n"
+ " How often should LODs be drawn on top of regular chunks? \n"
+ " HALF and ALWAYS will prevent holes in the world, but may look odd for transparent blocks or in caves. \n\n"
+ " " + VanillaOverdraw.NEVER + ": LODs won't render on top of vanilla chunks. \n"
+ " " + VanillaOverdraw.DYNAMIC + ": LODs will render on top of distant vanilla chunks to hide delayed loading. \n"
+ " " + " More effective on higher render distances. \n"
+ " " + " For vanilla render distances less than or equal to " + LodUtil.MINIMUM_RENDER_DISTANCE_FOR_PARTIAL_OVERDRAW + " \n"
+ " " + " " + VanillaOverdraw.NEVER + " or " + VanillaOverdraw.ALWAYS + " may be used depending on the dimension. \n"
+ " " + VanillaOverdraw.ALWAYS + ": LODs will render on all vanilla chunks preventing holes in the world. \n"
+ " " + VanillaOverdraw.BORDER + ": LODs will render only on the border of vanilla chunks preventing only some holes in the world. \n")
.defineEnum("Vanilla Overdraw", VanillaOverdraw.DYNAMIC);
builder.pop();
}
}
}
//========================//
// WorldGenerator Configs //
//========================//
public static class WorldGenerator
{
public final ForgeConfigSpec.EnumValue<GenerationPriority> generationPriority;
public final ForgeConfigSpec.EnumValue<DistanceGenerationMode> distanceGenerationMode;
public final ForgeConfigSpec.BooleanValue allowUnstableFeatureGeneration;
public final ForgeConfigSpec.EnumValue<BlockToAvoid> blockToAvoid;
//public final ForgeConfigSpec.BooleanValue useExperimentalPreGenLoading;
builder.pop();
WorldGenerator(ForgeConfigSpec.Builder builder)
{
builder.comment("These settings control how fake chunks outside your normal view range are generated.").push("Generation");
generationPriority = builder
.comment("\n\n"
+ " " + GenerationPriority.FAR_FIRST + " \n"
+ " LODs are generated from low to high detail \n"
+ " with a small priority for far away regions. \n"
+ " This fills in the world fastest. \n\n"
+ ""
+ " " + GenerationPriority.NEAR_FIRST + " \n"
+ " LODs are generated around the player \n"
+ " in a spiral, similar to vanilla minecraft. \n")
.defineEnum("Generation Priority", GenerationPriority.FAR_FIRST);
distanceGenerationMode = builder
.comment("\n\n"
+ " Note: The times listed here are the amount of time it took \n"
+ " one of the developer's PC to generate 1 chunk, \n"
+ " and are included so you can compare the \n"
+ " different generation options. Your mileage may vary. \n"
+ "\n"
+ " " + DistanceGenerationMode.NONE + " \n"
+ " Don't run the distance generator. \n"
+ "\n"
+ " " + DistanceGenerationMode.BIOME_ONLY + " \n"
+ " Only generate the biomes and use the biome's \n"
+ " grass color, water color, or snow color. \n"
+ " Doesn't generate height, everything is shown at sea level. \n"
+ " Multithreaded - Fastest (2-5 ms) \n"
+ "\n"
+ " " + DistanceGenerationMode.BIOME_ONLY_SIMULATE_HEIGHT + " \n"
+ " Same as BIOME_ONLY, except instead \n"
+ " of always using sea level as the LOD height \n"
+ " different biome types (mountain, ocean, forest, etc.) \n"
+ " use predetermined heights to simulate having height data. \n"
+ " Multithreaded - Fastest (2-5 ms) \n"
+ "\n"
+ " " + DistanceGenerationMode.SURFACE + " \n"
+ " Generate the world surface, \n"
+ " this does NOT include trees, \n"
+ " or structures. \n"
+ " Multithreaded - Faster (10-20 ms) \n"
+ "\n"
+ " " + DistanceGenerationMode.FEATURES + " \n"
+ " Generate everything except structures. \n"
+ " WARNING: This may cause world generation bugs or instability! \n"
+ " Multithreaded - Fast (15-20 ms) \n"
+ "\n"
+ " " + DistanceGenerationMode.SERVER + " \n"
+ " Ask the server to generate/load each chunk. \n"
+ " This will show player made structures, which can \n"
+ " be useful if you are adding the mod to a pre-existing world. \n"
+ " This is the most compatible, but causes server/simulation lag. \n"
+ " SingleThreaded - Slow (15-50 ms, with spikes up to 200 ms) \n")
.defineEnum("Distance Generation Mode", DistanceGenerationMode.SURFACE);
allowUnstableFeatureGeneration = builder
.comment("\n\n"
+ " When using the " + DistanceGenerationMode.FEATURES + " generation mode \n"
+ " some features may not be thread safe, which could \n"
+ " cause instability and crashes. \n"
+ " By default (false) those features are skipped, \n"
+ " improving stability, but decreasing how many features are \n"
+ " actually generated. \n"
+ " (for example: some tree generation is unstable, \n"
+ " so some trees may not be generated.) \n"
+ " By setting this to true, all features will be generated, \n"
+ " but your game will be more unstable and crashes may occur. \n"
+ " \n"
+ " I would love to remove this option and always generate everything, \n"
+ " but I'm not sure how to do that. \n"
+ " If you are a Java wizard, check out the git issue here: \n"
+ " https://gitlab.com/jeseibel/minecraft-lod-mod/-/issues/35 \n")
.define("Allow Unstable Feature Generation", false);
blockToAvoid = builder
.comment("\n\n"
+ " " + BlockToAvoid.NONE + ": Use all blocks when generating fake chunks \n\n"
+ ""
+ " " + BlockToAvoid.NON_FULL + ": Only use full blocks when generating fake chunks (ignores slabs, lanterns, torches, grass, etc.) \n\n"
+ ""
+ " " + BlockToAvoid.NO_COLLISION + ": Only use solid blocks when generating fake chunks (ignores grass, torches, etc.) \n"
+ ""
+ " " + BlockToAvoid.BOTH + ": Only use full solid blocks when generating fake chunks \n"
+ "\n")
.defineEnum("Block to avoid", BlockToAvoid.BOTH);
/*useExperimentalPreGenLoading = builder
.comment("\n\n"
+ " if a chunk has been pre-generated, then the mod would use the real chunk for the \n"
+ "fake chunk creation. May require a deletion of the lod file to see the result. \n")
.define("Use pre-generated chunks", false);*/
builder.pop();
}
}
//============================//
// AdvancedModOptions Configs //
//============================//
public static class AdvancedModOptions
{
public final Threading threading;
public final Debugging debugging;
public final Buffers buffers;
public AdvancedModOptions(ForgeConfigSpec.Builder builder)
{
builder.comment("Advanced mod settings").push(this.getClass().getSimpleName());
{
threading = new Threading(builder);
debugging = new Debugging(builder);
buffers = new Buffers(builder);
}
builder.pop();
}
public static class Threading
{
public final ForgeConfigSpec.IntValue numberOfWorldGenerationThreads;
public final ForgeConfigSpec.IntValue numberOfBufferBuilderThreads;
Threading(ForgeConfigSpec.Builder builder)
{
builder.comment("These settings control how many CPU threads the mod uses for different tasks.").push(this.getClass().getSimpleName());
numberOfWorldGenerationThreads = builder
.comment("\n\n"
+ " This is how many threads are used when generating LODs outside \n"
+ " the normal render distance. \n"
+ " If you experience stuttering when generating distant LODs, decrease \n"
+ " this number. If you want to increase LOD generation speed, \n"
+ " increase this number. \n\n"
+ ""
+ " The maximum value is the number of logical processors on your CPU. \n"
+ " Requires a restart to take effect. \n")
.defineInRange("numberOfWorldGenerationThreads", Math.max(1, Runtime.getRuntime().availableProcessors() / 2), 1, Runtime.getRuntime().availableProcessors());
numberOfBufferBuilderThreads = builder
.comment("\n\n"
+ " This is how many threads are used when building vertex buffers \n"
+ " (The things sent to your GPU to draw the fake chunks). \n"
+ " If you experience high CPU usage when NOT generating distant \n"
+ " fake chunks, lower this number. \n"
+ " \n"
+ " The maximum value is the number of logical processors on your CPU. \n"
+ " Requires a restart to take effect. \n")
.defineInRange("numberOfBufferBuilderThreads", Math.max(1, Runtime.getRuntime().availableProcessors() / 2), 1, Runtime.getRuntime().availableProcessors());
builder.pop();
}
}
//===============//
// Debug Options //
//===============//
public static class Debugging
{
public final ForgeConfigSpec.BooleanValue drawLods;
public final ForgeConfigSpec.EnumValue<DebugMode> debugMode;
public final ForgeConfigSpec.BooleanValue enableDebugKeybindings;
Debugging(ForgeConfigSpec.Builder builder)
{
builder.comment("These settings can be used to look for bugs, or see how certain aspects of the mod work.").push(this.getClass().getSimpleName());
drawLods = builder
.comment("\n\n"
+ " If true, the mod is enabled and fake chunks will be drawn. \n"
+ " If false, the mod will still generate fake chunks, \n"
+ " but they won't be rendered. \n")
.define("Disable Drawing", true);
debugMode = builder
.comment("\n\n"
+ " " + DebugMode.OFF + ": Fake chunks will be drawn with their normal colors. \n"
+ " " + DebugMode.SHOW_DETAIL + ": Fake chunks color will be based on their detail level. \n"
+ " " + DebugMode.SHOW_DETAIL_WIREFRAME + ": Fake chunks color will be based on their detail level, drawn as a wireframe. \n")
.defineEnum("Debug Mode", DebugMode.OFF);
enableDebugKeybindings = builder
.comment("\n\n"
+ " If true the F4 key can be used to cycle through the different debug modes. \n"
+ " and the F6 key can be used to enable and disable LOD rendering.")
.define("Enable Debug Keybinding", false);
builder.pop();
}
}
public static class Buffers
{
public final ForgeConfigSpec.EnumValue<BufferRebuildTimes> rebuildTimes;
Buffers(ForgeConfigSpec.Builder builder)
{
builder.comment("These settings affect how often geometry is are built.").push(this.getClass().getSimpleName());
rebuildTimes = builder
.comment("\n\n"
+ " How frequently should geometry be rebuilt and sent to the GPU? \n"
+ " Higher settings may cause stuttering, but will prevent holes in the world \n")
.defineEnum("rebuildFrequency", BufferRebuildTimes.NORMAL);
builder.pop();
}
}
}
}
public static class WorldGenerator
{
public ForgeConfigSpec.EnumValue<VerticalQuality> lodQualityMode;
public ForgeConfigSpec.EnumValue<HorizontalResolution> generationResolution;
public ForgeConfigSpec.EnumValue<DistanceGenerationMode> distanceGenerationMode;
public ForgeConfigSpec.EnumValue<GenerationPriority> generationPriority;
public ForgeConfigSpec.BooleanValue allowUnstableFeatureGeneration;
public ForgeConfigSpec.EnumValue<HorizontalScale> horizontalScale;
public ForgeConfigSpec.EnumValue<HorizontalQuality> horizontalQuality;
WorldGenerator(ForgeConfigSpec.Builder builder)
{
builder.comment("These settings control how LODs outside your normal view range are generated.").push(this.getClass().getSimpleName());
lodQualityMode = builder
.comment("\n\n"
+ " Use 3d lods or 2d lods? \n"
+ " " + VerticalQuality.HEIGHTMAP + ": LODs are solid from the lowest point to the highest. Creates pillars for floating islands. Faster \n"
+ " " + VerticalQuality.VOXEL + ": LODs have gaps between vertical blocks. Good for floating islands and caves. Slower \n"
+ " " + "(Yes we know voxels are generally cubes, but voxel sounds better than rectangular_prism) \n")
.defineEnum("lodQualityMode", VerticalQuality.HEIGHTMAP);
generationResolution = builder
.comment("\n\n"
+ " What is the maximum detail level that LODs should be generated at? \n"
+ " " + HorizontalResolution.CHUNK + ": render 1 LOD for each Chunk. \n"
+ " " + HorizontalResolution.HALF_CHUNK + ": render 4 LODs for each Chunk. \n"
+ " " + HorizontalResolution.FOUR_BLOCKS + ": render 16 LODs for each Chunk. \n"
+ " " + HorizontalResolution.TWO_BLOCKS + ": render 64 LODs for each Chunk. \n"
+ " " + HorizontalResolution.BLOCK + ": render 256 LODs for each Chunk. \n")
.defineEnum("Generation Resolution", HorizontalResolution.BLOCK);
horizontalScale = builder
.comment("\n\n"
+ " This indicates how quickly LODs drop off in quality. \n"
+ " " + HorizontalScale.LOW + ": quality drops every " + HorizontalScale.LOW.distanceUnit / 16 + " chunks. \n"
+ " " + HorizontalScale.MEDIUM + ": quality drops every " + HorizontalScale.MEDIUM.distanceUnit / 16 + " chunks. \n"
+ " " + HorizontalScale.HIGH + ": quality drops every " + HorizontalScale.HIGH.distanceUnit / 16 + " chunks. \n")
.defineEnum("horizontal scale", HorizontalScale.MEDIUM);
horizontalQuality = builder
.comment("\n\n"
+ " This indicates the exponential base of the quadratic drop-off \n"
+ " " + HorizontalQuality.LINEAR + ": base " + HorizontalQuality.LINEAR.quadraticBase + ". \n"
+ " " + HorizontalQuality.LOW + ": base " + HorizontalQuality.LOW.quadraticBase + ". \n"
+ " " + HorizontalQuality.MEDIUM + ": base " + HorizontalQuality.MEDIUM.quadraticBase + ". \n"
+ " " + HorizontalQuality.HIGH + ": base " + HorizontalQuality.HIGH.quadraticBase + ". \n")
.defineEnum("horizontal quality", HorizontalQuality.MEDIUM);
generationPriority = builder
.comment("\n\n"
+ " " + GenerationPriority.FAR_FIRST + " \n"
+ " LODs are generated from low to high detail\n"
+ " with a small priority for far regions. \n"
+ " This fills in the world fastest. \n"
+ "\n"
+ " " + GenerationPriority.NEAR_FIRST + " \n"
+ " LODs are generated around the player \n"
+ " in a spiral, similar to vanilla minecraft. \n")
.defineEnum("Generation priority", GenerationPriority.NEAR_FIRST);
distanceGenerationMode = builder
.comment("\n\n"
+ " Note: The times listed here are the amount of time it took \n"
+ " one of the developer's PC to generate 1 chunk, \n"
+ " and are included so you can compare the \n"
+ " different generation options. Your mileage may vary. \n"
+ "\n"
+ " " + DistanceGenerationMode.NONE + " \n"
+ " Don't run the distance generator. \n"
+ " " + DistanceGenerationMode.BIOME_ONLY + " \n"
+ " Only generate the biomes and use the biome's \n"
+ " grass color, water color, or snow color. \n"
+ " Doesn't generate height, everything is shown at sea level. \n"
+ " Multithreaded - Fastest (2-5 ms) \n"
+ "\n"
+ " " + DistanceGenerationMode.BIOME_ONLY_SIMULATE_HEIGHT + " \n"
+ " Same as BIOME_ONLY, except instead \n"
+ " of always using sea level as the LOD height \n"
+ " different biome types (mountain, ocean, forest, etc.) \n"
+ " use predetermined heights to simulate having height data. \n"
+ " Multithreaded - Fastest (2-5 ms) \n"
+ "\n"
+ " " + DistanceGenerationMode.SURFACE + " \n"
+ " Generate the world surface, \n"
+ " this does NOT include caves, trees, \n"
+ " or structures. \n"
+ " Multithreaded - Faster (10-20 ms) \n"
+ "\n"
+ " " + DistanceGenerationMode.FEATURES + " \n"
+ " Generate everything except structures. \n"
+ " WARNING: This may cause world generation bugs or instability! \n"
+ " Multithreaded - Fast (15-20 ms) \n"
+ "\n"
+ " " + DistanceGenerationMode.SERVER + " \n"
+ " Ask the server to generate/load each chunk. \n"
+ " This is the most compatible, but causes server/simulation lag. \n"
+ " This will show player made structures, which can \n"
+ " be useful if you are adding the mod to a pre-existing world. \n"
+ " Singlethreaded - Slow (15-50 ms, with spikes up to 200 ms) \n")
.defineEnum("distanceGenerationMode", DistanceGenerationMode.SURFACE);
allowUnstableFeatureGeneration = builder
.comment("\n\n"
+ " When using the " + DistanceGenerationMode.FEATURES + " generation mode \n"
+ " some features may not be thread safe, which could \n"
+ " cause instability and crashes. \n"
+ " By default (false) those features are skipped, \n"
+ " improving stability, but decreasing how many features are \n"
+ " actually generated. \n"
+ " (for example: some tree generation is unstable, \n"
+ " so some trees may not be generated.) \n"
+ " By setting this to true, all features will be generated, \n"
+ " but your game will be more unstable and crashes may occur. \n"
+ " \n"
+ " I would love to remove this option and always generate everything, \n"
+ " but I'm not sure how to do that. \n"
+ " If you are a Java wizard, check out the git issue here: \n"
+ " https://gitlab.com/jeseibel/minecraft-lod-mod/-/issues/35 \n")
.define("allowUnstableFeatureGeneration", false);
builder.pop();
}
}
public static class Threading
{
public ForgeConfigSpec.IntValue numberOfWorldGenerationThreads;
public ForgeConfigSpec.IntValue numberOfBufferBuilderThreads;
Threading(ForgeConfigSpec.Builder builder)
{
builder.comment("These settings control how many CPU threads the mod uses for different tasks.").push(this.getClass().getSimpleName());
numberOfWorldGenerationThreads = builder
.comment("\n\n"
+ " This is how many threads are used when generating LODs outside \n"
+ " the normal render distance. \n"
+ " If you experience stuttering when generating distant LODs, decrease \n"
+ " this number. If you want to increase LOD generation speed, \n"
+ " increase this number. \n"
+ " \n"
+ " The maximum value is the number of logical processors on your CPU. \n"
+ " Requires a restart to take effect. \n")
.defineInRange("numberOfWorldGenerationThreads", Runtime.getRuntime().availableProcessors() / 2, 1, Runtime.getRuntime().availableProcessors());
numberOfBufferBuilderThreads = builder
.comment("\n\n"
+ " This is how many threads are used when building vertex buffers \n"
+ " (The things sent to your GPU to draw the LODs). \n"
+ " If you experience high CPU useage when NOT generating distant \n"
+ " LODs, lower this number. \n"
+ " \n"
+ " The maximum value is the number of logical processors on your CPU. \n"
+ " Requires a restart to take effect. \n")
.defineInRange("numberOfBufferBuilderThreads", Runtime.getRuntime().availableProcessors(), 1, Runtime.getRuntime().availableProcessors());
builder.pop();
}
}
public static class Debugging
{
public ForgeConfigSpec.EnumValue<DebugMode> debugMode;
public ForgeConfigSpec.BooleanValue enableDebugKeybindings;
Debugging(ForgeConfigSpec.Builder builder)
{
builder.comment("These settings can be used to look for bugs, or see how certain aspects of the mod work.").push(this.getClass().getSimpleName());
debugMode = builder
.comment("\n\n"
+ " " + DebugMode.OFF + ": LODs will draw with their normal colors. \n"
+ " " + DebugMode.SHOW_DETAIL + ": LOD colors will be based on their detail level. \n"
+ " " + DebugMode.SHOW_DETAIL_WIREFRAME + ": LOD colors will be based on their detail level, drawn as a wireframe. \n")
.defineEnum("debugMode", DebugMode.OFF);
enableDebugKeybindings = builder
.comment("\n\n"
+ " If true the F4 key can be used to cycle through the different debug modes. \n"
+ " and the F6 key can be used to enable and disable LOD rendering.")
.define("enableDebugKeybinding", false);
builder.pop();
}
}
public static class Buffers
{
public ForgeConfigSpec.IntValue bufferRebuildPlayerMoveTimeout;
public ForgeConfigSpec.IntValue bufferRebuildChunkChangeTimeout;
public ForgeConfigSpec.IntValue bufferRebuildLodChangeTimeout;
Buffers(ForgeConfigSpec.Builder builder)
{
builder.comment("These settings affect when Vertex Buffers are built.").push(this.getClass().getSimpleName());
bufferRebuildPlayerMoveTimeout = builder
.comment("\n\n"
+ " How long in milliseconds should we wait to \n"
+ " rebuild the vertex buffers when the player moves \n"
+ " a chunk or more? \n")
.defineInRange("bufferRebuildPlayerMoveTimeout", 2000, 1, 60000);
bufferRebuildChunkChangeTimeout = builder
.comment("\n\n"
+ " How long in milliseconds should we wait to \n"
+ " rebuild the vertex buffers when the vanilla rendered \n"
+ " chunks change? \n")
.defineInRange("bufferRebuildChunkChangeTimeout", 1000, 1, 60000);
bufferRebuildLodChangeTimeout = builder
.comment("\n\n"
+ " How long in milliseconds should we wait to \n"
+ " rebuild the vertex buffers when the LOD regions change? \n")
.defineInRange("bufferRebuildLodChangeTimeout", 5000, 1, 60000);
builder.pop();
}
}
/**
* {@link Path} to the configuration file of this mod
*/
private static final Path CONFIG_PATH = Paths.get("config", ModInfo.MODID + ".toml");
/** {@link Path} to the configuration file of this mod */
private static final Path CONFIG_PATH = Paths.get("config", ModInfo.MODNAME + ".toml");
public static final ForgeConfigSpec CLIENT_SPEC;
public static final Client CLIENT;
@@ -0,0 +1,47 @@
/*
* This file is part of the Distant Horizon mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.lod.enums;
/**
* heightmap <br>
* multi_lod <br>
*
* @author Leonardo Amato
* @version 19-10-2021
*/
public enum BlockToAvoid
{
NONE(false, false),
NON_FULL(true, false),
NO_COLLISION(false, true),
BOTH(true, true);
public boolean nonFull;
public boolean noCollision;
BlockToAvoid(boolean nonFull, boolean noCollision)
{
this.nonFull = nonFull;
this.noCollision = noCollision;
}
}
@@ -0,0 +1,49 @@
/*
* This file is part of the Distant Horizon mod (formerly 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;
/**
* Near_First <br>
* Far_First <br>
* <br>
* Determines how fast the buffers need to be regenerated
*
* @author Leonardo Amato
* @version 9-25-2021
*/
public enum BufferRebuildTimes
{
FREQUENT(1000, 500, 2500),
NORMAL(2000, 1000, 5000),
RARE(5000, 2000, 10000);
public final int playerMoveTimeout;
public final int renderedChunkTimeout;
public final int chunkChangeTimeout;
BufferRebuildTimes(int playerMoveTimeout, int renderedChunkTimeout, int chunkChangeTimeout)
{
this.playerMoveTimeout = playerMoveTimeout;
this.renderedChunkTimeout = renderedChunkTimeout;
this.chunkChangeTimeout = chunkChangeTimeout;
}
}
@@ -1,5 +1,6 @@
/*
* This file is part of the LOD Mod, licensed under the GNU GPL v3 License.
* This file is part of the Distant Horizon mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020 James Seibel
*
@@ -15,6 +16,7 @@
* 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;
/**
@@ -36,7 +38,8 @@ public enum DebugMode
/** used when cycling through the different modes */
private DebugMode next;
static
static
{
OFF.next = SHOW_DETAIL;
SHOW_DETAIL.next = SHOW_DETAIL_WIREFRAME;
@@ -1,5 +1,6 @@
/*
* This file is part of the LOD Mod, licensed under the GNU GPL v3 License.
* This file is part of the Distant Horizon mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020 James Seibel
*
@@ -15,6 +16,7 @@
* 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;
/**
@@ -26,7 +28,7 @@ package com.seibel.lod.enums;
* SERVER <br><br>
* <p>
* In order of fastest to slowest.
*
*
* @author James Seibel
* @author Leonardo Amato
* @version 8-7-2021
@@ -37,7 +39,7 @@ public enum DistanceGenerationMode
* Don't generate anything
*/
NONE((byte) 0),
/**
* Only generate the biomes and use biome
* grass/foliage color, water color, or ice color
@@ -46,7 +48,7 @@ public enum DistanceGenerationMode
* Multithreaded - Fastest (2-5 ms)
*/
BIOME_ONLY((byte) 1),
/**
* Same as BIOME_ONLY, except instead
* of always using sea level as the LOD height
@@ -54,7 +56,7 @@ public enum DistanceGenerationMode
* use predetermined heights to simulate having height data.
*/
BIOME_ONLY_SIMULATE_HEIGHT((byte) 2),
/**
* Generate the world surface,
* this does NOT include caves, trees,
@@ -62,7 +64,7 @@ public enum DistanceGenerationMode
* Multithreaded - Faster (10-20 ms)
*/
SURFACE((byte) 3),
/**
* Generate everything except structures.
* NOTE: This may cause world generation bugs or instability,
@@ -70,7 +72,7 @@ public enum DistanceGenerationMode
* Multithreaded - Fast (15-20 ms)
*/
FEATURES((byte) 4),
/**
* Ask the server to generate/load each chunk.
* This is the most compatible, but causes server/simulation lag.
@@ -79,13 +81,13 @@ public enum DistanceGenerationMode
* Singlethreaded - Slow (15-50 ms, with spikes up to 200 ms)
*/
SERVER((byte) 5);
/**
* The higher the number the more complete the generation is.
*/
public final byte complexity;
DistanceGenerationMode(byte complexity)
{
this.complexity = complexity;
@@ -1,5 +1,6 @@
/*
* This file is part of the LOD Mod, licensed under the GNU GPL v3 License.
* This file is part of the Distant Horizon mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020 James Seibel
*
@@ -15,6 +16,7 @@
* 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;
/**
@@ -32,5 +34,5 @@ public enum FogDistance
FAR,
/** only looks good if the fog quality is set to Fancy. */
NEAR_AND_FAR;
NEAR_AND_FAR
}
@@ -1,5 +1,6 @@
/*
* This file is part of the LOD Mod, licensed under the GNU GPL v3 License.
* This file is part of the Distant Horizon mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020 James Seibel
*
@@ -15,6 +16,7 @@
* 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;
/**
@@ -28,16 +30,18 @@ package com.seibel.lod.enums;
*/
public enum FogDrawOverride
{
/** Use whatever Fog setting optifine is using.
* If optifine isn't installed this defaults to ALWAYS_DRAW_FOG. */
USE_OPTIFINE_FOG_SETTING,
/**
* Use whatever Fog setting optifine is using.
* If optifine isn't installed this defaults to ALWAYS_DRAW_FOG.
*/
OPTIFINE_SETTING,
/** Never draw fog on the LODs */
NEVER_DRAW_FOG,
NO_FOG,
/** Always draw fog on the LODs */
ALWAYS_DRAW_FOG_FAST,
/** Always draw fast fog on the LODs */
FAST,
/** Always draw fog on the LODs */
ALWAYS_DRAW_FOG_FANCY;
/** Always draw fancy fog on the LODs */
FANCY
}
@@ -1,5 +1,6 @@
/*
* This file is part of the LOD Mod, licensed under the GNU GPL v3 License.
* This file is part of the Distant Horizon mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020 James Seibel
*
@@ -15,6 +16,7 @@
* 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;
/**
@@ -27,5 +29,5 @@ public enum FogQuality
{
FAST,
FANCY,
OFF;
OFF
}
@@ -1,5 +1,6 @@
/*
* This file is part of the LOD Mod, licensed under the GNU GPL v3 License.
* This file is part of the Distant Horizon mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020 James Seibel
*
@@ -15,6 +16,7 @@
* 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;
/**
@@ -31,5 +33,5 @@ public enum GenerationPriority
{
NEAR_FIRST,
FAR_FIRST;
FAR_FIRST
}
@@ -1,5 +1,6 @@
/*
* This file is part of the LOD Mod, licensed under the GNU GPL v3 License.
* This file is part of the Distant Horizon mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020 James Seibel
*
@@ -15,21 +16,23 @@
* 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;
/**
* By_Region_Fast, <br>
* By_Region_Fancy, <br>
* By_Chunk
* Minecraft, Lod_Builder, None
*
* @author Leonardo Amato
* @version 9-25-2021
* @author James Seibel
* @version 10-1-2021
*/
public enum DetailDropOff
public enum GlProxyContext
{
/** quality is determined per-region, using the lowest quality that would be used in BY_CHUNK */
FAST,
/** quality is determined per-block (best quality option, may cause stuttering when moving) */
FANCY,
}
/** Minecraft's render thread */
MINECRAFT,
/** The context we send buffers to the GPU on */
LOD_BUILDER,
/** used to un-bind threads */
NONE,
}
@@ -1,5 +1,6 @@
/*
* This file is part of the LOD Mod, licensed under the GNU GPL v3 License.
* This file is part of the Distant Horizon mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020 James Seibel
*
@@ -15,34 +16,36 @@
* 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;
/**
* Lowest <br>
* Low <br>
* Medium <br>
* High <br>
* <br>
* this indicate the base of the quadratic function we use for the quality drop off
* this indicates the base of the quadratic function we use for the quality drop off
*
* @author Leonardo Amato
* @version 9-29-2021
*/
public enum HorizontalQuality
{
/** Lods are 2D with heightMap */
LINEAR(1.0f),
/** Lods are 2D with heightMap */
/** 1.0 AKA Linear */
LOWEST(1.0f),
/** exponent 1.5 */
LOW(1.5f),
/** Lods expand in three dimension */
/** exponent 2.0 */
MEDIUM(2.0f),
/** Lods expand in three dimension */
/** exponent 2.2 */
HIGH(2.2f);
public double quadraticBase;
public final double quadraticBase;
HorizontalQuality(double distanceUnit)
{
this.quadraticBase = distanceUnit;
@@ -1,5 +1,6 @@
/*
* This file is part of the LOD Mod, licensed under the GNU GPL v3 License.
* This file is part of the Distant Horizon mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020 James Seibel
*
@@ -15,6 +16,7 @@
* 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;
@@ -35,30 +37,38 @@ import com.seibel.lod.util.LodUtil;
*/
public enum HorizontalResolution
{
/** render 1 LOD for each chunk */
CHUNK(1, 4),
/** render 4 LODs for each chunk */
HALF_CHUNK(2, 3),
/** render 16 LODs for each chunk */
FOUR_BLOCKS(4, 2),
/** render 256 LODs for each chunk */
BLOCK(16, 0),
/** render 64 LODs for each chunk */
TWO_BLOCKS(8, 1),
/** render 256 LODs for each chunk */
BLOCK(16, 0);
/** How many DataPoints should
* be drawn per side per LodChunk */
/** render 16 LODs for each chunk */
FOUR_BLOCKS(4, 2),
/** render 4 LODs for each chunk */
HALF_CHUNK(2, 3),
/** render 1 LOD for each chunk */
CHUNK(1, 4);
/**
* How many DataPoints should
* be drawn per side, per LodChunk
*/
public final int dataPointLengthCount;
/** How wide each LOD DataPoint is */
public final int dataPointWidth;
/** This is the same as detailLevel in LodQuadTreeNode,
* lowest is 0 highest is 9 */
/**
* This is the same as detailLevel in LodQuadTreeNode,
* lowest is 0 highest is 9
*/
public final byte detailLevel;
/* Start/End X/Z give the block positions
@@ -68,19 +78,19 @@ public enum HorizontalResolution
public final int[] endX;
public final int[] endZ;
/**
/**
* 1st dimension: LodDetail.detailLevel <br>
* 2nd dimension: An array of all LodDetails that are less than or <br>
* equal to that detailLevel
* equal to that detailLevel
*/
private static HorizontalResolution[][] lowerDetailArrays;
private HorizontalResolution(int newLengthCount, int newDetailLevel)
HorizontalResolution(int newLengthCount, int newDetailLevel)
{
detailLevel = (byte) newDetailLevel;
dataPointLengthCount = newLengthCount;
@@ -94,15 +104,15 @@ public enum HorizontalResolution
int index = 0;
for(int x = 0; x < newLengthCount; x++)
for (int x = 0; x < newLengthCount; x++)
{
for(int z = 0; z < newLengthCount; z++)
for (int z = 0; z < newLengthCount; z++)
{
startX[index] = x * dataPointWidth;
startZ[index] = z * dataPointWidth;
endX[index] = (x*dataPointWidth) + dataPointWidth;
endZ[index] = (z*dataPointWidth) + dataPointWidth;
endX[index] = (x * dataPointWidth) + dataPointWidth;
endZ[index] = (z * dataPointWidth) + dataPointWidth;
index++;
}
@@ -115,7 +125,7 @@ public enum HorizontalResolution
/**
/**
* Returns an array of all LodDetails that have a detail level
* that is less than or equal to the given LodDetail
*/
@@ -127,12 +137,12 @@ public enum HorizontalResolution
lowerDetailArrays = new HorizontalResolution[HorizontalResolution.values().length][];
// go through each LodDetail
for(HorizontalResolution currentDetail : HorizontalResolution.values())
for (HorizontalResolution currentDetail : HorizontalResolution.values())
{
ArrayList<HorizontalResolution> lowerDetails = new ArrayList<>();
// find the details lower than currentDetail
for(HorizontalResolution compareDetail : HorizontalResolution.values())
for (HorizontalResolution compareDetail : HorizontalResolution.values())
{
if (currentDetail.detailLevel <= compareDetail.detailLevel)
{
@@ -155,8 +165,8 @@ public enum HorizontalResolution
public static HorizontalResolution getDetailForDistance(HorizontalResolution maxDetailLevel, int distance, int maxDistance)
{
HorizontalResolution[] lowerDetails = getSelfAndLowerDetails(maxDetailLevel);
int distaneBetweenDetails = maxDistance / lowerDetails.length;
int index = LodUtil.clamp(0, distance / distaneBetweenDetails, lowerDetails.length - 1);
int distanceBetweenDetails = maxDistance / lowerDetails.length;
int index = LodUtil.clamp(0, distance / distanceBetweenDetails, lowerDetails.length - 1);
return lowerDetails[index];
@@ -1,5 +1,6 @@
/*
* This file is part of the LOD Mod, licensed under the GNU GPL v3 License.
* This file is part of the Distant Horizon mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020 James Seibel
*
@@ -15,6 +16,7 @@
* 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;
/**
@@ -38,7 +40,7 @@ public enum HorizontalScale
/** Lods expand in three dimension */
HIGH(256);
public int distanceUnit;
public final int distanceUnit;
HorizontalScale(int distanceUnit)
{
@@ -1,5 +1,6 @@
/*
* This file is part of the LOD Mod, licensed under the GNU GPL v3 License.
* This file is part of the Distant Horizon mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020 James Seibel
*
@@ -15,45 +16,47 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.lod.enums;
import com.seibel.lod.builders.lodTemplates.AbstractLodTemplate;
import com.seibel.lod.builders.lodTemplates.CubicLodTemplate;
import com.seibel.lod.builders.lodTemplates.DynamicLodTemplate;
import com.seibel.lod.builders.lodTemplates.TriangularLodTemplate;
import com.seibel.lod.builders.bufferBuilding.lodTemplates.AbstractLodTemplate;
import com.seibel.lod.builders.bufferBuilding.lodTemplates.CubicLodTemplate;
import com.seibel.lod.builders.bufferBuilding.lodTemplates.DynamicLodTemplate;
import com.seibel.lod.builders.bufferBuilding.lodTemplates.TriangularLodTemplate;
/**
* Cubic, Triangular, Dynamic
*
* @author James Seibel
* @version 8-4-2021
* @version 10-10-2021
*/
public enum LodTemplate
{
/** LODs are rendered as
* rectangular prisms. */
/**
* LODs are rendered as
* rectangular prisms.
*/
CUBIC(new CubicLodTemplate()),
/** LODs smoothly transition between
* each other. */
/**
* LODs smoothly transition between
* each other.
*/
TRIANGULAR(new TriangularLodTemplate()),
/** LODs smoothly transition between
/**
* LODs smoothly transition between
* each other, unless a neighboring LOD
* is at a significantly different height. */
* is at a significantly different height.
*/
DYNAMIC(new DynamicLodTemplate());
public final AbstractLodTemplate template;
private LodTemplate(AbstractLodTemplate newTemplate)
LodTemplate(AbstractLodTemplate newTemplate)
{
template = newTemplate;
}
public int getBufferMemoryForSingleLod(int maxVerticalData)
{
return template.getBufferMemoryForSingleNode(maxVerticalData);
}
}
@@ -9,14 +9,14 @@ package com.seibel.lod.enums;
public enum ShadingMode
{
/**
* LODs will have the same lighting on every side.
* can make large similarly colored areas hard to differentiate
* LODs will have darker sides and bottoms to simulate
* Minecraft's fast lighting.
*/
NONE,
GAME_SHADING,
/**
* LODs will have darker sides and bottoms to simulate
* Minecraft's fast, top down lighting.
* LODs will use ambient occlusion to mimic Minecarft's
* Fancy lighting.
*/
GAME_SHADING;
AMBIENT_OCCLUSION
}
@@ -0,0 +1,45 @@
/*
* This file is part of the Distant Horizon mod (formerly 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;
/**
* None, Dynamic, Always
*
* <p>
* This represents how far the LODs should overlap with
* the vanilla Minecraft terrain.
*
* @author James Seibel
* @version 10-11-2021
*/
public enum VanillaOverdraw
{
/** Never draw LODs where a minecraft chunk could be. */
NEVER,
/** Draw LODs over the farther minecraft chunks. */
DYNAMIC,
/** Draw LODs over all minecraft chunks. */
ALWAYS,
/** Draw LODs over border chunks. */
BORDER,
}
@@ -1,5 +1,6 @@
/*
* This file is part of the LOD Mod, licensed under the GNU GPL v3 License.
* This file is part of the Distant Horizon mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020 James Seibel
*
@@ -15,6 +16,7 @@
* 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;
/**
@@ -22,13 +24,57 @@ package com.seibel.lod.enums;
* multi_lod <br>
*
* @author Leonardo Amato
* @version 9-27-2021
* @version 10-07-2021
*/
public enum VerticalQuality
{
/** Lods only have height and depth data */
HEIGHTMAP,
/** Lods expand in three dimensions */
VOXEL;
LOW(
new int[] { 2,
2,
2,
2,
1,
1,
1,
1,
1,
1,
1 }
),
MEDIUM(
new int[] { 4,
4,
2,
2,
2,
1,
1,
1,
1,
1,
1 }
),
HIGH(
new int[] {
8,
8,
4,
4,
2,
2,
2,
1,
1,
1,
1 }
);
public final int[] maxVerticalData;
VerticalQuality(int[] maxVerticalData)
{
this.maxVerticalData = maxVerticalData;
}
}
@@ -0,0 +1,74 @@
/*
* This file is part of the Distant Horizon mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.lod.handlers;
import java.io.File;
import com.seibel.lod.util.LodUtil;
import com.seibel.lod.wrappers.MinecraftWrapper;
import net.minecraft.client.world.ClientWorld;
import net.minecraft.util.math.ChunkPos;
import net.minecraft.world.chunk.IChunk;
import net.minecraft.world.chunk.storage.ChunkSerializer;
import net.minecraft.world.server.ServerWorld;
/**
*
* @author ??
* @version ??
*/
public class ChunkLoader
{
public static IChunk getChunkFromFile(ChunkPos pos){
ClientWorld clientWorld = MinecraftWrapper.INSTANCE.getClientWorld();
if (clientWorld == null)
return null;
ServerWorld serverWorld = LodUtil.getServerWorldFromDimension(clientWorld.dimensionType());
try
{
File file = new File(serverWorld.getChunkSource().getDataStorage().dataFolder.getParent() + File.separatorChar + "region", "r." + (pos.x >> 5) + "." + (pos.z >> 5) + ".mca");
if(!file.exists())
return null;
IChunk loadedChunk = ChunkSerializer.read(
serverWorld,
serverWorld.getStructureManager(),
serverWorld.getPoiManager(),
pos,
serverWorld.getChunkSource().chunkMap.read(pos)
);
boolean emptyChunk = true;
for(int i = 0; i < 16; i++){
for(int j = 0; j < 16; j++){
emptyChunk &= loadedChunk.isYSpaceEmpty(i,j);
}
}
if(emptyChunk)
return null;
else
return loadedChunk;
}
catch (Exception e)
{
return null;
}
}
}
@@ -1,5 +1,6 @@
/*
* This file is part of the LOD Mod, licensed under the GNU GPL v3 License.
* This file is part of the Distant Horizon mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020 James Seibel
*
@@ -15,31 +16,31 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.lod.handlers;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
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 org.apache.commons.compress.compressors.xz.XZCompressorInputStream;
import org.apache.commons.compress.compressors.xz.XZCompressorOutputStream;
import com.seibel.lod.enums.DistanceGenerationMode;
import com.seibel.lod.enums.VerticalQuality;
import com.seibel.lod.objects.LodDimension;
import com.seibel.lod.objects.LodRegion;
import com.seibel.lod.objects.RegionPos;
import com.seibel.lod.objects.SingleLevelContainer;
import com.seibel.lod.objects.VerticalLevelContainer;
import com.seibel.lod.proxy.ClientProxy;
import com.seibel.lod.util.LodThreadFactory;
import com.seibel.lod.util.LodUtil;
import com.seibel.lod.util.ThreadMapUtil;
/**
* This object handles creating LodRegions
@@ -53,14 +54,14 @@ import com.seibel.lod.util.LodUtil;
public class LodDimensionFileHandler
{
/** This is the dimension that owns this file handler */
private LodDimension lodDimension = null;
private LodDimension lodDimension;
private File dimensionDataSaveFolder;
private final File dimensionDataSaveFolder;
/** lod */
private static final String FILE_NAME_PREFIX = "lod";
/** .txt */
private static final String FILE_EXTENSION = ".dat";
private static final String FILE_EXTENSION = ".xz";
/** detail- */
private static final String DETAIL_FOLDER_NAME_PREFIX = "detail-";
@@ -84,7 +85,7 @@ public class LodDimensionFileHandler
* Allow saving asynchronously, but never try to save multiple regions
* at a time
*/
private ExecutorService fileWritingThreadPool = Executors.newSingleThreadExecutor(new LodThreadFactory(this.getClass().getSimpleName()));
private final ExecutorService fileWritingThreadPool = Executors.newSingleThreadExecutor(new LodThreadFactory(this.getClass().getSimpleName()));
@@ -125,14 +126,36 @@ public class LodDimensionFileHandler
if (fileName == null)
throw new IllegalArgumentException("Unable to read region [" + regionX + ", " + regionZ + "] file, no fileName.");
File file = new File(fileName);
if (!file.exists())
{
// there wasn't a file, don't
// return anything
continue;
//there is no file for current gen mode
//search others above current from the most to the least detailed
DistanceGenerationMode tempGenMode = DistanceGenerationMode.SERVER;
while (tempGenMode != generationMode)
{
fileName = getFileNameAndPathForRegion(regionX, regionZ, tempGenMode, tempDetailLevel, verticalQuality);
if (fileName != null)
{
file = new File(fileName);
if (file.exists())
break;
}
//decrease gen mode
if (tempGenMode == DistanceGenerationMode.SERVER)
tempGenMode = DistanceGenerationMode.FEATURES;
else if (tempGenMode == DistanceGenerationMode.FEATURES)
tempGenMode = DistanceGenerationMode.SURFACE;
else if (tempGenMode == DistanceGenerationMode.SURFACE)
tempGenMode = DistanceGenerationMode.BIOME_ONLY_SIMULATE_HEIGHT;
else if (tempGenMode == DistanceGenerationMode.BIOME_ONLY_SIMULATE_HEIGHT)
tempGenMode = DistanceGenerationMode.BIOME_ONLY;
else if (tempGenMode == DistanceGenerationMode.BIOME_ONLY)
tempGenMode = DistanceGenerationMode.NONE;
}
if (!file.exists())
//there wasn't a file, don't return anything
continue;
}
@@ -142,9 +165,9 @@ public class LodDimensionFileHandler
dataSize -= 1;
if (dataSize > 0)
{
try (InputStream inputStream = new BufferedInputStream(new FileInputStream(file)))
try (XZCompressorInputStream inputStream = new XZCompressorInputStream(new FileInputStream(file)))
{
int fileVersion = -1;
int fileVersion;
fileVersion = inputStream.read();
// check if this file can be read by this file handler
@@ -165,7 +188,7 @@ public class LodDimensionFileHandler
{
// 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.
// want to accidentally delete anything the user may want.
inputStream.close();
ClientProxy.LOGGER.info("Newer LOD region file for region: (" + regionX + "," + regionZ + ")"
+ " version found: " + fileVersion
@@ -178,29 +201,20 @@ public class LodDimensionFileHandler
// this file is a readable version,
// read the file
byte[] data = new byte[(int) dataSize];
byte[] data = ThreadMapUtil.getSaveContainer(tempDetailLevel);
inputStream.read(data);
inputStream.close();
// add the data to our region
switch (region.getVerticalQuality())
{
default:
case HEIGHTMAP:
region.addLevelContainer(new SingleLevelContainer(data));
break;
case VOXEL:
region.addLevelContainer(new VerticalLevelContainer(data));
break;
}
region.addLevelContainer(new VerticalLevelContainer(data));
}
catch (IOException ioEx)
{
ClientProxy.LOGGER.error("LOD file read error. Unable to read to [" + fileName + "] error [" + ioEx.getMessage() + "]: ");
ioEx.printStackTrace();
}
}// file datasize > 0
}
}
catch (Exception e)
{
@@ -230,7 +244,7 @@ public class LodDimensionFileHandler
fileWritingThreadPool.execute(saveDirtyRegionsThread);
}
private Thread saveDirtyRegionsThread = new Thread(() ->
private final Thread saveDirtyRegionsThread = new Thread(() ->
{
try
{
@@ -238,11 +252,10 @@ public class LodDimensionFileHandler
{
for (int j = 0; j < lodDimension.getWidth(); j++)
{
// TODO shouldn't this use lodDimension.isRegionDirty?
if (lodDimension.doesRegionNeedBufferRegen(i,j) && lodDimension.getRegionByArrayIndex(i,j) != null)
if (lodDimension.GetIsRegionDirty(i, j) && lodDimension.getRegionByArrayIndex(i, j) != null)
{
saveRegionToFile(lodDimension.getRegionByArrayIndex(i,j));
lodDimension.setRegenRegionBufferByArrayIndex(i, j,false);
saveRegionToFile(lodDimension.getRegionByArrayIndex(i, j));
lodDimension.SetIsRegionDirty(i, j, false);
}
}
}
@@ -266,7 +279,6 @@ public class LodDimensionFileHandler
for (byte detailLevel = region.getMinDetailLevel(); detailLevel <= LodUtil.REGION_DETAIL_LEVEL; detailLevel++)
{
String fileName = getFileNameAndPathForRegion(region.regionPosX, region.regionPosZ, region.getGenerationMode(), detailLevel, region.getVerticalQuality());
File oldFile = new File(fileName);
// if the fileName was null that means the folder is inaccessible
// for some reason
@@ -275,7 +287,8 @@ public class LodDimensionFileHandler
ClientProxy.LOGGER.warn("Unable to save region [" + region.regionPosX + ", " + region.regionPosZ + "] to file, file is inaccessible.");
return;
}
File oldFile = new File(fileName);
//ClientProxy.LOGGER.info("saving region [" + region.regionPosX + ", " + region.regionPosZ + "] to file.");
try
{
@@ -295,7 +308,7 @@ public class LodDimensionFileHandler
// (to make sure we don't overwrite a newer
// version file if it exists)
int fileVersion = LOD_SAVE_FILE_VERSION;
try (InputStream inputStream = new BufferedInputStream(new FileInputStream(oldFile)))
try (XZCompressorInputStream inputStream = new XZCompressorInputStream(new FileInputStream(oldFile)))
{
fileVersion = inputStream.read();
inputStream.close();
@@ -309,10 +322,10 @@ public class LodDimensionFileHandler
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
// don't write anything, we don't want to accidentally
// delete anything the user may want.
return;
}
}
// if we got this far then we are good
// to overwrite the old file
@@ -320,7 +333,7 @@ public class LodDimensionFileHandler
// the old file is good, now create a new temporary save file
File newFile = new File(fileName + TMP_FILE_EXTENSION);
try (OutputStream outputStream = new BufferedOutputStream(new FileOutputStream(newFile)))
try (XZCompressorOutputStream outputStream = new XZCompressorOutputStream(new FileOutputStream(newFile), 3))
{
// add the version of this file
outputStream.write(LOD_SAVE_FILE_VERSION);
@@ -1,5 +1,6 @@
/*
* This file is part of the LOD Mod, licensed under the GNU GPL v3 License.
* This file is part of the Distant Horizon mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020 James Seibel
*
@@ -15,6 +16,7 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.lod.handlers;
import java.lang.reflect.Field;
@@ -28,14 +30,14 @@ import com.seibel.lod.wrappers.MinecraftWrapper;
* This object is used to get variables from methods
* where they are private. Specifically the fog setting
* in Optifine.
*
*
* @author James Seibel
* @version 9-25-2021
*/
public class ReflectionHandler
{
public static final ReflectionHandler INSTANCE = new ReflectionHandler();
private MinecraftWrapper mc = MinecraftWrapper.INSTANCE;
private final MinecraftWrapper mc = MinecraftWrapper.INSTANCE;
public Field ofFogField = null;
public Method vertexBufferUploadMethod = null;
@@ -105,8 +107,6 @@ public class ReflectionHandler
case 0:
// optifine's "default" option,
// it should never be called in this case
return FogQuality.FAST;
// normal options
case 1:
@@ -1,5 +1,6 @@
/*
* This file is part of the LOD Mod, licensed under the GNU GPL v3 License.
* This file is part of the Distant Horizon mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020 James Seibel
*
@@ -15,6 +16,7 @@
* 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.mixin;
import org.spongepowered.asm.mixin.Mixin;
@@ -32,7 +34,7 @@ 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 and we used Forge's
* If this wasn't done, and we used Forge's
* render last event, the LODs would render on top
* of the normal terrain.
*
@@ -44,7 +46,7 @@ public class MixinWorldRenderer
{
private static float previousPartialTicks = 0;
@Inject(at = @At("RETURN"), method = "renderSky(Lcom/mojang/blaze3d/matrix/MatrixStack;F)V", cancellable = false)
@Inject(at = @At("RETURN"), method = "renderSky(Lcom/mojang/blaze3d/matrix/MatrixStack;F)V")
private void renderSky(MatrixStack matrixStackIn, float partialTicks, CallbackInfo callback)
{
// get the partial ticks since renderBlockLayer doesn't
@@ -52,12 +54,12 @@ public class MixinWorldRenderer
previousPartialTicks = partialTicks;
}
@Inject(at = @At("HEAD"), method = "renderChunkLayer(Lnet/minecraft/client/renderer/RenderType;Lcom/mojang/blaze3d/matrix/MatrixStack;DDD)V", cancellable = false)
@Inject(at = @At("HEAD"), method = "renderChunkLayer(Lnet/minecraft/client/renderer/RenderType;Lcom/mojang/blaze3d/matrix/MatrixStack;DDD)V")
private void renderChunkLayer(RenderType renderType, MatrixStack matrixStackIn, double xIn, double yIn, double zIn, CallbackInfo callback)
{
// only render if LODs are enabled and
// only render before solid blocks
if (LodConfig.CLIENT.graphics.drawLods.get() && renderType.equals(RenderType.solid()))
if (LodConfig.CLIENT.advancedModOptions.debugging.drawLods.get() && renderType.equals(RenderType.solid()))
LodMain.client_proxy.renderLods(matrixStackIn, previousPartialTicks);
}
}
@@ -1,93 +1,115 @@
/*
* This file is part of the Distant Horizon mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.lod.objects;
/**
* A level container is a quad tree level
*/
public interface LevelContainer
{
public static final char VERTICAL_DATA_DELIMITER = '\t';
public static final char DATA_DELIMITER = ' ';
/**With this you can add data to the level container
*
* @param data actual data to add in a array of long format.
/**
* With this you can add data to the level container
* @param data actual data to add in an 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);
boolean addData(long data, int posX, int posZ, int index);
/**With this you can add data to the level container
*
* @param data actual data to add in a array of long format.
/**
* With this you can add data to the level container
* @param data actual data to add in an 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);
boolean addVerticalData(long[] data, int posX, int posZ);
/**With this you can get data from the level container
*
/**
* With this you can add data to the level container
* @param data actual data to add in an 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
*/
boolean addSingleData(long data, int posX, int posZ);
/**
* With this you can get data from the level container
* @param posX x position in the detail level
* @param posZ z position in the detail level
* @return the data in long array format
*/
public long getData(int posX, int posZ, int index);
long getData(int posX, int posZ, int index);
/**With this you can get data from the level container
*
/**
* 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);
long getSingleData(int posX, int posZ);
/**
* @param posX x position in the detail level
* @param posZ z position in the detail level
* @return true only if the data exist
*/
public boolean doesItExist(int posX, int posZ);
boolean doesItExist(int posX, int posZ);
/**
* @return return the deatilLevel of this level container
* @return return the detailLevel of this level container
*/
public byte getDetailLevel();
byte getDetailLevel();
public int getMaxVerticalData();
int getMaxVerticalData();
/** Clears the dataPoint at the given array index */
public void clear(int posX, int posZ);
void clear(int posX, int posZ);
/**This return a level container with detail level lower than the current level.
/**
* 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();
LevelContainer expand();
/**
*
* @param lowerLevelContainer lower level where we extract the data
* @param posX x position in the detail level to update
* @param posZ z position in the detail level to update
*/
public void updateData(LevelContainer lowerLevelContainer, int posX, int posZ);
void updateData(LevelContainer lowerLevelContainer, int posX, int posZ);
/**
* This will give the data to save in the file
* @return data as a String
*/
public byte[] toDataString();
byte[] toDataString();
/**
* This will give the data to save in the file
* @return data as a String
*/
public int getMaxNumberOfLods();
/**
* This will give the data to save in the file
* @return data as a String
*/
public int getMaxMemoryUse();
int getMaxNumberOfLods();
}
@@ -1,5 +1,6 @@
/*
* This file is part of the LOD Mod, licensed under the GNU GPL v3 License.
* This file is part of the Distant Horizon mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020 James Seibel
*
@@ -15,6 +16,7 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.lod.objects;
import java.io.File;
@@ -25,7 +27,6 @@ import java.util.concurrent.Executors;
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.util.DataPointUtil;
@@ -44,14 +45,14 @@ import net.minecraft.world.server.ServerWorld;
/**
* This object holds all loaded LOD regions
* for a given dimension. <Br><Br>
*
*
* <strong>Coordinate Standard: </strong><br>
* Coordinate called posX or posZ are relative LevelPos coordinates <br>
* unless stated otherwise. <br>
*
* @author Leonardo Amato
* @author James Seibel
* @version 9-27-2021
* @version 10-10-2021
*/
public class LodDimension
{
@@ -82,17 +83,16 @@ public class LodDimension
private LodDimensionFileHandler fileHandler;
private volatile RegionPos center;
private final RegionPos center;
/** prevents the cutAndExpandThread from expanding at the same location multiple times */
private volatile ChunkPos lastExpandedChunk;
/** prevents the cutAndExpandThread from cutting at the same location multiple times */
private volatile ChunkPos lastCutChunk;
private ExecutorService cutAndExpandThread = Executors.newSingleThreadExecutor(new LodThreadFactory(this.getClass().getSimpleName() + " - Cut and Expand"));
private final ExecutorService cutAndExpandThread = Executors.newSingleThreadExecutor(new LodThreadFactory(this.getClass().getSimpleName() + " - Cut and Expand"));
/**
* Creates the dimension centered at (0,0)
*
* @param newWidth in regions
*/
public LodDimension(DimensionType newDimension, LodWorld lodWorld, int newWidth)
@@ -109,7 +109,7 @@ public class LodDimension
try
{
File saveDir;
if (mc.hasSingleplayerServer())
if (mc.hasSinglePlayerServer())
{
// local world
@@ -125,7 +125,7 @@ public class LodDimension
// connected to server
saveDir = new File(mc.getGameDirectory().getCanonicalFile().getPath() +
File.separatorChar + "lod server data" + File.separatorChar + mc.getCurrentDimensionId());
File.separatorChar + "lod server data" + File.separatorChar + mc.getCurrentDimensionId());
}
fileHandler = new LodDimensionFileHandler(saveDir, this);
@@ -243,27 +243,6 @@ public class LodDimension
}
/**
* return the minimum needed memory in bytes
*/
public int getMinMemoryNeeded()
{
int count = 0;
LodRegion region;
for (int x = 0; x < regions.length; x++)
{
for (int z = 0; z < regions.length; z++)
{
region = regions[x][z];
if (region != null)
count += region.getMinMemoryNeeded(LodConfig.CLIENT.graphics.lodTemplate.get());
}
}
return count;
}
/**
* Gets the region at the given LevelPos
* <br>
@@ -315,7 +294,6 @@ public class LodDimension
/**
* Overwrite the LodRegion at the location of newRegion with newRegion.
*
* @throws ArrayIndexOutOfBoundsException if newRegion is outside what can be stored in this LodDimension.
*/
public synchronized void addOrOverwriteRegion(LodRegion newRegion) throws ArrayIndexOutOfBoundsException
@@ -391,7 +369,7 @@ public class LodDimension
{
DistanceGenerationMode generationMode = LodConfig.CLIENT.worldGenerator.distanceGenerationMode.get();
ChunkPos newPlayerChunk = new ChunkPos(LevelPosUtil.getChunkPos((byte) 0, playerPosX), LevelPosUtil.getChunkPos((byte) 0, playerPosZ));
VerticalQuality verticalQuality = LodConfig.CLIENT.worldGenerator.lodQualityMode.get();
VerticalQuality verticalQuality = LodConfig.CLIENT.graphics.qualityOption.verticalQuality.get();
if (lastExpandedChunk == null)
@@ -433,7 +411,7 @@ public class LodDimension
// try to get the region from file
regions[x][z] = getRegionFromFile(regionPos, levelToGen, generationMode, verticalQuality);
// if there is no region file create a empty region
// if there is no region file create an empty region
if (regions[x][z] == null)
regions[x][z] = new LodRegion(levelToGen, regionPos, generationMode, verticalQuality);
@@ -447,6 +425,7 @@ public class LodDimension
// Expand the region by introducing the missing layer
region.growTree(levelToGen);
regions[x][z] = getRegionFromFile(regionPos, levelToGen, generationMode, verticalQuality);
recreateRegionBuffer[x][z] = true;
}
}
@@ -479,7 +458,7 @@ public class LodDimension
{
try
{
// mark the region as dirty so it will be saved to disk
// mark the region as dirty, so it will be saved to disk
int xIndex = (regionPosX - center.x) + halfWidth;
int zIndex = (regionPosZ - center.z) + halfWidth;
@@ -499,6 +478,48 @@ public class LodDimension
return nodeAdded;
}
/**
* Add whole column of LODs to this dimension at the coordinate
* stored in the LOD. If an LOD already exists at the given
* coordinate it will be overwritten.
*/
public Boolean addVerticalData(byte detailLevel, int posX, int posZ, long[] data, boolean dontSave)
{
int regionPosX = LevelPosUtil.getRegion(detailLevel, posX);
int regionPosZ = LevelPosUtil.getRegion(detailLevel, posZ);
// don't continue if the region can't be saved
LodRegion region = getRegion(regionPosX, regionPosZ);
if (region == null)
return false;
boolean nodeAdded = region.addVerticalData(detailLevel, posX, posZ, 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 = (regionPosX - center.x) + halfWidth;
int zIndex = (regionPosZ - center.z) + halfWidth;
isRegionDirty[xIndex][zIndex] = true;
regenRegionBuffer[xIndex][zIndex] = true;
regenDimensionBuffers = true;
}
catch (ArrayIndexOutOfBoundsException e)
{
e.printStackTrace();
// If this happens, the method was probably
// called when the dimension was changing size.
// Hopefully this shouldn't be an issue.
}
}
return nodeAdded;
}
/** marks the region at the given region position to have its buffer rebuilt */
public void markRegionBufferToRegen(int xRegion, int zRegion)
{
@@ -510,27 +531,32 @@ public class LodDimension
/**
* Returns every position that need to be generated based on the position of the player
*/
public PosToGenerateContainer getDataToGenerate(int maxDataToGenerate, int playerBlockPosX, int playerBlockPosZ)
public PosToGenerateContainer getPosToGenerate(int maxDataToGenerate, int playerBlockPosX, int playerBlockPosZ)
{
PosToGenerateContainer posToGenerate;
LodRegion region;
// TODO what are dx, dz, and t?
LodRegion lodRegion;
// all the following values are used for the spiral matrix visit
// x and z are the matrix coord
// dx and dz is the next move on the coordinate in the range -1 0 +1
int x, z, dx, dz, t;
x = 0;
z = 0;
dx = 0;
dz = -1;
// TODO please comment what this code is doing
// We can use two type of generation scheduling
switch (LodConfig.CLIENT.worldGenerator.generationPriority.get())
{
default:
case NEAR_FIRST:
//in the NEAR_FIRST generation scheduling we prioritize the nearest un-generated position to the player
//the chunk position to generate will be stored in a posToGenerate object
posToGenerate = new PosToGenerateContainer((byte) 10, maxDataToGenerate, playerBlockPosX, playerBlockPosZ);
int playerChunkX = LevelPosUtil.getChunkPos(LodUtil.BLOCK_DETAIL_LEVEL, playerBlockPosX);
int playerChunkZ = LevelPosUtil.getChunkPos(LodUtil.BLOCK_DETAIL_LEVEL, playerBlockPosZ);
int complexity;
int xChunkToCheck;
int zChunkToCheck;
byte detailLevel;
@@ -540,40 +566,62 @@ public class LodDimension
int numbChunksWide = (width) * 32;
int circleLimit = Integer.MAX_VALUE;
//posToGenerate is using an insertion sort algorithm which can become really fast if the
//original data order is almost ordered. For this reason we explore the matrix of the position to generate
//with a spiral matrix visit (a square spiral is almost ordered in the "nearest to farthest" order)
for (int i = 0; i < numbChunksWide * numbChunksWide; i++)
{
// use this for circular generation
if (maxDataToGenerate < 0)
//Firstly we check if the posToGenerate has been filled
if (maxDataToGenerate == 0)
{
maxDataToGenerate--;
//if it has been filled then we set a stop distance
//the stop distance will be current distance (generically x) per square root of 2
//this would guarantee a circular generation since (Math.abs(x) * 1.41f) is the
//radius of a circle that inscribe a square
circleLimit = (int) (Math.abs(x) * 1.41f);
}
//This second if check if we reached the circleLimit decided in the previous if
//if so we stop
else 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;
region = getRegion(LodUtil.CHUNK_DETAIL_LEVEL, xChunkToCheck, zChunkToCheck);
if (region == null)
//we get the lod region in which the chunk is present
lodRegion = getRegion(LodUtil.CHUNK_DETAIL_LEVEL, xChunkToCheck, zChunkToCheck);
if (lodRegion == null)
continue;
detailLevel = region.getMinDetailLevel();
//Now we check if the current chunk has been generated with the correct complexity
//if(lodRegion.isChunkPreGenerated(xChunkToCheck,zChunkToCheck))
// complexity = DistanceGenerationMode.SERVER.complexity;
//else
complexity = LodConfig.CLIENT.worldGenerator.distanceGenerationMode.get().complexity;
//we create the level position info of the chunk
detailLevel = lodRegion.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)
//we will generate the position only if the current generation complexity is lower than the target one.
//an un-generated area will always have 0 generation
if (DataPointUtil.getGenerationMode(data) < complexity)
{
posToGenerate.addPosToGenerate(detailLevel, posX, posZ);
if (maxDataToGenerate >= 0)
maxDataToGenerate--;
}
//with this code section we find the next chunk to check
if ((x == z) || ((x < 0) && (x == -z)) || ((x > 0) && (x == 1 - z)))
{
t = dx;
@@ -584,9 +632,12 @@ public class LodDimension
z += dz;
}
break;
case FAR_FIRST:
//in the FAR_FIRST generation we dedicate part of the generation process to the far region with really
//low detail quality.
posToGenerate = new PosToGenerateContainer((byte) 8, maxDataToGenerate, playerBlockPosX, playerBlockPosZ);
int xRegion;
@@ -597,11 +648,13 @@ public class LodDimension
xRegion = x + center.x;
zRegion = z + center.z;
region = getRegion(xRegion, zRegion);
if (region != null)
region.getDataToGenerate(posToGenerate, playerBlockPosX, playerBlockPosZ);
//All of this is handled directly by the region, which scan every pos from top to bottom of the quad tree
lodRegion = getRegion(xRegion, zRegion);
if (lodRegion != null)
lodRegion.getPosToGenerate(posToGenerate, playerBlockPosX, playerBlockPosZ);
//with this code section we find the next chunk to check
if ((x == z) || ((x < 0) && (x == -z)) || ((x > 0) && (x == 1 - z)))
{
t = dx;
@@ -617,20 +670,18 @@ public class LodDimension
}
/**
* Returns every node that should be rendered based on the position of the player.
*
* TODO why isn't posToRender returned? it would make it a bit more clear what is happening
* Fills the posToRender with the position to render for the regionPos given in input
*/
public void getDataToRender(PosToRenderContainer posToRender, RegionPos regionPos, int playerPosX,
public void getPosToRender(PosToRenderContainer posToRender, RegionPos regionPos, int playerPosX,
int playerPosZ)
{
LodRegion region = getRegion(regionPos.x, regionPos.z);
if (region != null)
region.getDataToRender(posToRender, playerPosX, playerPosZ, LodConfig.CLIENT.worldGenerator.generationPriority.get() == GenerationPriority.NEAR_FIRST);
region.getPosToRender(posToRender, playerPosX, playerPosZ, LodConfig.CLIENT.worldGenerator.generationPriority.get() == GenerationPriority.NEAR_FIRST);
}
/**
* Determines how many vertical LODs could be used
* Determines how many vertical LODs could be used
* for the given region at the given detail level
*/
public int getMaxVerticalData(byte detailLevel, int posX, int posZ)
@@ -706,17 +757,6 @@ public class LodDimension
return regenRegionBuffer[xIndex][zIndex];
}
/**
* TODO we aren't currently using this, is there a reason for that?
* is this significantly different than regenRegionBuffer?
*
* Returns if the buffer at the given array index needs
* to have its buffer resized.
*/
public boolean doesRegionNeedBufferResized(int xIndex, int zIndex)
{
return recreateRegionBuffer[xIndex][zIndex];
}
/**
* Sets if the buffer at the given array index needs
@@ -752,23 +792,17 @@ public class LodDimension
public boolean doesDataExist(byte detailLevel, int posX, int posZ)
{
LodRegion region = getRegion(detailLevel, posX, posZ);
if (region == null)
return false;
return region.doesDataExist(detailLevel, posX, posZ);
return region != null && region.doesDataExist(detailLevel, posX, posZ);
}
/**
* Loads the region at the given RegionPos from file,
* if a file exists for that region.
*/
public LodRegion getRegionFromFile(RegionPos regionPos, byte detailLevel,
public LodRegion getRegionFromFile(RegionPos regionPos, byte detailLevel,
DistanceGenerationMode generationMode, VerticalQuality verticalQuality)
{
if (fileHandler != null)
return fileHandler.loadRegionFromFile(detailLevel, regionPos, generationMode, verticalQuality);
else
return null;
return fileHandler != null ? fileHandler.loadRegionFromFile(detailLevel, regionPos, generationMode, verticalQuality) : null;
}
/**
@@ -780,6 +814,19 @@ public class LodDimension
}
/**
* Return true if the chunk has been pregenerated in game
*/
//public boolean isChunkPreGenerated(int xChunkPos, int zChunkPos)
//{
//
// LodRegion region = getRegion(LodUtil.CHUNK_DETAIL_LEVEL, xChunkPos, zChunkPos);
// if (region == null)
// return false;
//
// return region.isChunkPreGenerated(xChunkPos, zChunkPos);
//}
/**
* Returns whether the region at the given RegionPos
* is within the loaded range.
@@ -809,17 +856,10 @@ public class LodDimension
*/
public int getWidth()
{
if (regions != null)
{
// we want to get the length directly from the
// source to make sure it is in sync with region
// and isRegionDirty
return regions.length;
}
else
{
return width;
}
// we want to get the length directly from the
// source to make sure it is in sync with region
// and isRegionDirty
return regions != null ? regions.length : width;
}
/** Update the width of this dimension, in regions */
@@ -843,67 +883,30 @@ public class LodDimension
@Override
public String toString()
{
LodRegion region;
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append("Dimension : \n");
for (int x = 0; x < regions.length; x++)
for (LodRegion[] lodRegions : regions)
{
for (int z = 0; z < regions.length; z++)
for (LodRegion region : lodRegions)
{
region = regions[x][z];
if (region == null)
{
stringBuilder.append("n");
stringBuilder.append("\t");
}
else
{
stringBuilder.append(region.getMinDetailLevel());
stringBuilder.append("\t");
}
stringBuilder.append("\t");
}
stringBuilder.append("\n");
}
return stringBuilder.toString();
}
/** Returns the minimum memory required by the dimension in Bytes */
public int getMemoryRequired(int x, int z, LodTemplate template)
public boolean GetIsRegionDirty(int i, int j)
{
/*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:
}*/
/*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 memoryUse = LodUtil.regionRenderingMemoryUse(x,z,template);
System.out.println(detail + " " + memoryUse + " " + template.getBufferMemoryForSingleLod(maxVerticalData));
return memoryUse;
//return memoryUse;
return isRegionDirty[i][j];
}
public void SetIsRegionDirty(int i, int j, boolean val)
{
isRegionDirty[i][j] = val;
}
}
@@ -1,9 +1,25 @@
/*
* This file is part of the Distant Horizon mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.lod.objects;
import com.seibel.lod.config.LodConfig;
import com.seibel.lod.enums.DistanceGenerationMode;
import com.seibel.lod.enums.LodTemplate;
import com.seibel.lod.enums.VerticalQuality;
import com.seibel.lod.util.DataPointUtil;
import com.seibel.lod.util.DetailDistanceUtil;
@@ -11,45 +27,48 @@ import com.seibel.lod.util.LevelPosUtil;
import com.seibel.lod.util.LodUtil;
/**
* This object holds all loaded LevelContainers
* This object holds all loaded LevelContainers acting as a quad tree
* for a given region. <Br><Br>
*
* <strong>Coordinate Standard: </strong><br>
* Coordinate called posX or posZ are relative LevelPos coordinates <br>
* unless stated otherwise. <br>
*
*
* @author Leonardo Amato
* @version 9-27-2021
* @version 10-10-2021
*/
public class LodRegion
{
/**
* TODO what does this represent, and should it be defined here?
* Number of detail level supported by a region
*/
private static final byte POSSIBLE_LOD = 10;
/**
* Holds the lowest (least detailed) detail level in this region
* TODO is that correct?
*/
private byte minDetailLevel;
/**
* This holds all data for this region
*/
private LevelContainer[] dataContainer;
private final LevelContainer[] dataContainer;
/**
* This chunk Pos has been generated
*/
//private final boolean[] preGeneratedChunkPos;
/**
* the generation mode for this region
* TODO will this ever change through a region's life?
*/
private DistanceGenerationMode generationMode;
/**
* the vertical quality of this region
*/
private VerticalQuality verticalQuality;
/**
* this region's x RegionPos
*/
@@ -58,16 +77,7 @@ public class LodRegion
* this region's z RegionPos
*/
public final int regionPosZ;
public LodRegion(RegionPos regionPos)
{
this.minDetailLevel = LodUtil.REGION_DETAIL_LEVEL;
this.regionPosX = regionPos.x;
this.regionPosZ = regionPos.z;
dataContainer = new LevelContainer[POSSIBLE_LOD];
}
public LodRegion(byte minDetailLevel, RegionPos regionPos, DistanceGenerationMode generationMode, VerticalQuality verticalQuality)
{
this.minDetailLevel = minDetailLevel;
@@ -76,55 +86,127 @@ public class LodRegion
this.verticalQuality = verticalQuality;
this.generationMode = generationMode;
dataContainer = new LevelContainer[POSSIBLE_LOD];
// Initialize all the different matrices
for (byte lod = minDetailLevel; lod <= LodUtil.REGION_DETAIL_LEVEL; lod++)
{
switch (verticalQuality)
{
default:
case HEIGHTMAP:
dataContainer[lod] = new SingleLevelContainer(lod);
break;
case VOXEL:
dataContainer[lod] = new VerticalLevelContainer(lod);
break;
}
dataContainer[lod] = new VerticalLevelContainer(lod);
}
boolean fileFound = false;
/*
preGeneratedChunkPos = new boolean[32 * 32];
if (MinecraftWrapper.INSTANCE.hasSinglePlayerServer() && LodConfig.CLIENT.worldGenerator.useExperimentalPreGenLoading.get())
{
File regionFileDirHead;
File regionFileDirParent;
// local world
ServerWorld serverWorld = LodUtil.getServerWorldFromDimension(MinecraftWrapper.INSTANCE.getCurrentDimension());
// provider needs a separate variable to prevent
// the compiler from complaining
StringBuilder string = new StringBuilder();
try
{
ServerChunkProvider provider = serverWorld.getChunkSource();
//System.out.println(provider.dataStorage.dataFolder);
regionFileDirHead = new File(provider.dataStorage.dataFolder.getCanonicalFile().getParentFile().toPath().toAbsolutePath().toString() + File.separatorChar + "region", "r." + regionPosZ + "." + regionPosX + ".mca");
if (regionFileDirHead.exists())
{
regionFileDirParent = regionFileDirHead.getParentFile();
//string.append(regionFileDirParent.toString());
string.append(regionFileDirHead);
RegionFile regionFile = new RegionFile(regionFileDirHead, regionFileDirParent, true);
for (int x = 0; x < 32; x++)
{
for (int z = 0; z < 32; z++)
{
preGeneratedChunkPos[x * 32 + z] = regionFile.doesChunkExist(new ChunkPos(regionPosX * 32 + x, regionPosZ * 32 + z));
}
}
string.append("region " + regionPosX + " " + regionPosZ + "\n");
for (int x = 0; x < 32; x++)
{
for (int z = 0; z < 32; z++)
{
//regionFile.doesChunkExist()
string.append(preGeneratedChunkPos[x * 32 + z] + "\t");
}
string.append("\n");
}
regionFile.close();
}
}
catch (Exception e)
{
e.printStackTrace();
}
System.out.println(string);
}*/
}
/**
* Return true if the chunk has been pregenerated in game
*/
//public boolean isChunkPreGenerated(int xChunkPos, int zChunkPos)
//{
// xChunkPos = LevelPosUtil.getRegionModule(LodUtil.CHUNK_DETAIL_LEVEL, xChunkPos);
// zChunkPos = LevelPosUtil.getRegionModule(LodUtil.CHUNK_DETAIL_LEVEL, zChunkPos);
// return preGeneratedChunkPos[xChunkPos * 32 + zChunkPos];
//}
/**
* Inserts the data point into the region.
* <p>
* TODO this will always return true unless it has
*
* @return true if the data was added successfully
*/
public boolean addData(byte detailLevel, int posX, int posZ, int verticalIndex, long data)
{
posX = LevelPosUtil.getRegionModule(detailLevel, posX);
posZ = LevelPosUtil.getRegionModule(detailLevel, posZ);
// The dataContainer could have null entries if the
// detailLevel changes.
if (this.dataContainer[detailLevel] == null)
{
if (verticalQuality == VerticalQuality.HEIGHTMAP)
this.dataContainer[detailLevel] = new SingleLevelContainer(detailLevel);
else
this.dataContainer[detailLevel] = new VerticalLevelContainer(detailLevel);
this.dataContainer[detailLevel] = new VerticalLevelContainer(detailLevel);
}
this.dataContainer[detailLevel].addData(data, posX, posZ, verticalIndex);
return true;
}
/**
* Inserts the vertical data into the region.
* <p>
* TODO this will always return true unless it has
* @return true if the data was added successfully
*/
public boolean addVerticalData(byte detailLevel, int posX, int posZ, long[] data)
{
//position is already relative
//posX = LevelPosUtil.getRegionModule(detailLevel, posX);
//posZ = LevelPosUtil.getRegionModule(detailLevel, posZ);
// The dataContainer could have null entries if the
// detailLevel changes.
if (this.dataContainer[detailLevel] == null)
this.dataContainer[detailLevel] = new VerticalLevelContainer(detailLevel);
return this.dataContainer[detailLevel].addVerticalData(data, posX, posZ);
}
/**
* Get the dataPoint at the given relative position.
*
* @return the data at the relative pos and detail level,
* 0 if the data doesn't exist.
*/
@@ -132,10 +214,9 @@ public class LodRegion
{
return dataContainer[detailLevel].getData(posX, posZ, verticalIndex);
}
/**
* Get the dataPoint at the given relative position.
*
* @return the data at the relative pos and detail level,
* 0 if the data doesn't exist.
*/
@@ -143,7 +224,7 @@ public class LodRegion
{
return dataContainer[detailLevel].getSingleData(posX, posZ);
}
/**
* Clears the datapoint at the given relative position
*/
@@ -151,110 +232,113 @@ public class LodRegion
{
dataContainer[detailLevel].clear(posX, posZ);
}
/**
* This method will fill the posToGenerate array with all levelPos that
* are render-able.
* <p>
* TODO why don't we return the posToGenerate, it would make this easier to understand
*/
public void getDataToGenerate(PosToGenerateContainer posToGenerate,
int playerBlockPosX, int playerBlockPosZ)
public void getPosToGenerate(PosToGenerateContainer posToGenerate,
int playerBlockPosX, int playerBlockPosZ)
{
getDataToGenerate(posToGenerate, LodUtil.REGION_DETAIL_LEVEL, 0, 0, playerBlockPosX, playerBlockPosZ);
getPosToGenerate(posToGenerate, LodUtil.REGION_DETAIL_LEVEL, 0, 0, playerBlockPosX, playerBlockPosZ);
}
/**
* A recursive method that fills the posToGenerate array with all levelPos that
* need to be generated.
* <p>
* TODO why don't we return the posToGenerate, it would make this easier to understand
*/
private void getDataToGenerate(PosToGenerateContainer posToGenerate, byte detailLevel,
int childOffsetPosX, int childOffsetPosZ, int playerPosX, int playerPosZ)
private void getPosToGenerate(PosToGenerateContainer posToGenerate, byte detailLevel,
int childOffsetPosX, int childOffsetPosZ, int playerPosX, int playerPosZ)
{
// equivalent to 2^(...)
int size = 1 << (LodUtil.REGION_DETAIL_LEVEL - detailLevel);
// calculate what LevelPos are in range to generate
int maxDistance = LevelPosUtil.maxDistance(detailLevel, childOffsetPosX, childOffsetPosZ, playerPosX, playerPosZ, regionPosX, regionPosZ);
// determine this child's levelPos
byte childDetailLevel = (byte) (detailLevel - 1);
int childPosX = childOffsetPosX * 2;
int childPosZ = childOffsetPosZ * 2;
int childSize = 1 << (LodUtil.REGION_DETAIL_LEVEL - childDetailLevel);
byte targetDetailLevel = DetailDistanceUtil.getLodGenDetail(DetailDistanceUtil.getGenerationDetailFromDistance(maxDistance)).detailLevel;
if (targetDetailLevel > detailLevel)
if (targetDetailLevel <= detailLevel)
{
// we have gone beyond the target Detail level
// we can stop generating
return;
} else if (targetDetailLevel == detailLevel)
{
if (!doesDataExist(detailLevel, childOffsetPosX, childOffsetPosZ))
posToGenerate.addPosToGenerate(detailLevel, childOffsetPosX + regionPosX * size, childOffsetPosZ + regionPosZ * size);
} else
{
// we want at max one request per chunk (since the world generator creates chunks).
// So for lod smaller than a chunk, only recurse down
// the top right child
if (detailLevel > LodUtil.CHUNK_DETAIL_LEVEL)
if (targetDetailLevel == detailLevel)
{
int ungeneratedChildren = 0;
// make sure all children are generated to this detailLevel
for (int x = 0; x <= 1; x++)
if (!doesDataExist(detailLevel, childOffsetPosX, childOffsetPosZ))
posToGenerate.addPosToGenerate(detailLevel, childOffsetPosX + regionPosX * size, childOffsetPosZ + regionPosZ * size);
}
else
{
// we want at max one request per chunk (since the world generator creates chunks).
// So for lod smaller than a chunk, only recurse down
// the top right child
if (detailLevel > LodUtil.CHUNK_DETAIL_LEVEL)
{
for (int z = 0; z <= 1; z++)
int ungeneratedChildren = 0;
// make sure all children are generated to this detailLevel
for (int x = 0; x <= 1; x++)
{
if (!doesDataExist(childDetailLevel, childPosX + x, childPosZ + z))
for (int z = 0; z <= 1; z++)
{
ungeneratedChildren++;
posToGenerate.addPosToGenerate(childDetailLevel, childPosX + x + regionPosX * childSize, childPosZ + z + regionPosZ * childSize);
if (!doesDataExist(childDetailLevel, childPosX + x, childPosZ + z))
{
ungeneratedChildren++;
posToGenerate.addPosToGenerate(childDetailLevel, childPosX + x + regionPosX * childSize, childPosZ + z + regionPosZ * childSize);
}
}
}
// only if all the children are correctly generated
// should we go deeper
if (ungeneratedChildren == 0)
for (int x = 0; x <= 1; x++)
for (int z = 0; z <= 1; z++)
getPosToGenerate(posToGenerate, childDetailLevel, childPosX + x, childPosZ + z, playerPosX, playerPosZ);
}
// only if all the children are correctly generated
// should we go deeper
if (ungeneratedChildren == 0)
for (int x = 0; x <= 1; x++)
for (int z = 0; z <= 1; z++)
getDataToGenerate(posToGenerate, childDetailLevel, childPosX + x, childPosZ + z, playerPosX, playerPosZ);
} else
{
// The detail Level is smaller than a chunk.
// Only recurse down the top right child.
if (DetailDistanceUtil.getLodGenDetail(childDetailLevel).detailLevel <= (childDetailLevel))
else
{
if (!doesDataExist(childDetailLevel, childPosX, childPosZ))
posToGenerate.addPosToGenerate(childDetailLevel, childPosX + regionPosX * childSize, childPosZ + regionPosZ * childSize);
else
getDataToGenerate(posToGenerate, childDetailLevel, childPosX, childPosZ, playerPosX, playerPosZ);
// The detail Level is smaller than a chunk.
// Only recurse down the top right child.
if (DetailDistanceUtil.getLodGenDetail(childDetailLevel).detailLevel <= (childDetailLevel))
{
if (!doesDataExist(childDetailLevel, childPosX, childPosZ))
posToGenerate.addPosToGenerate(childDetailLevel, childPosX + regionPosX * childSize, childPosZ + regionPosZ * childSize);
else
getPosToGenerate(posToGenerate, childDetailLevel, childPosX, childPosZ, playerPosX, playerPosZ);
}
}
}
}
// we have gone beyond the target Detail level
// we can stop generating
}
/**
* This method will fill the posToRender array with all levelPos that
* are render-able.
* <p>
* TODO why don't we return the posToRender, it would make this easier to understand
*/
public void getDataToRender(PosToRenderContainer posToRender,
int playerPosX, int playerPosZ, boolean requireCorrectDetailLevel)
public void getPosToRender(PosToRenderContainer posToRender,
int playerPosX, int playerPosZ, boolean requireCorrectDetailLevel)
{
getDataToRender(posToRender, LodUtil.REGION_DETAIL_LEVEL, 0, 0, playerPosX, playerPosZ, requireCorrectDetailLevel);
getPosToRender(posToRender, LodUtil.REGION_DETAIL_LEVEL, 0, 0, playerPosX, playerPosZ, requireCorrectDetailLevel);
}
/**
* This method will fill the posToRender array with all levelPos that
* are render-able.
@@ -262,102 +346,85 @@ public class LodRegion
* TODO why don't we return the posToRender, it would make this easier to understand
* TODO this needs some more comments, James was only able to figure out part of it
*/
private void getDataToRender(PosToRenderContainer posToRender,
byte detailLevel, int posX, int posZ,
int playerPosX, int playerPosZ, boolean requireCorrectDetailLevel)
private void getPosToRender(PosToRenderContainer posToRender,
byte detailLevel, int posX, int posZ,
int playerPosX, int playerPosZ, boolean requireCorrectDetailLevel)
{
// equivalent to 2^(...)
int size = 1 << (LodUtil.REGION_DETAIL_LEVEL - detailLevel);
byte desiredLevel;
int maxDistance;
boolean stopNow = false;
int minDistance;
int childLevel;
// calculate the LevelPos that are in range
switch (LodConfig.CLIENT.graphics.detailDropOff.get())
{
case FAST:
int playerRegionX = LevelPosUtil.getRegion(LodUtil.BLOCK_DETAIL_LEVEL, playerPosX);
int playerRegionZ = LevelPosUtil.getRegion(LodUtil.BLOCK_DETAIL_LEVEL, playerPosZ);
if (playerRegionX == regionPosX && playerRegionZ == regionPosZ)
{
maxDistance = LevelPosUtil.maxDistance(detailLevel, posX, posZ, playerPosX, playerPosZ, regionPosX, regionPosZ);
desiredLevel = DetailDistanceUtil.getLodDrawDetail(DetailDistanceUtil.getDrawDetailFromDistance(maxDistance));
minDistance = LevelPosUtil.minDistance(detailLevel, posX, posZ, playerPosX, playerPosZ, regionPosX, regionPosZ);
childLevel = DetailDistanceUtil.getLodDrawDetail(DetailDistanceUtil.getDrawDetailFromDistance(minDistance));
stopNow = detailLevel == childLevel - 1;
break;
}
default:
case FANCY:
maxDistance = LevelPosUtil.maxDistance(detailLevel, posX, posZ, playerPosX, playerPosZ, regionPosX, regionPosZ);
desiredLevel = DetailDistanceUtil.getLodDrawDetail(DetailDistanceUtil.getDrawDetailFromDistance(maxDistance));
minDistance = LevelPosUtil.minDistance(detailLevel, posX, posZ, playerPosX, playerPosZ, regionPosX, regionPosZ);
childLevel = DetailDistanceUtil.getLodDrawDetail(DetailDistanceUtil.getDrawDetailFromDistance(minDistance));
stopNow = detailLevel == childLevel - 1;
break;
}
if (stopNow)
maxDistance = LevelPosUtil.maxDistance(detailLevel, posX, posZ, playerPosX, playerPosZ, regionPosX, regionPosZ);
desiredLevel = DetailDistanceUtil.getLodDrawDetail(DetailDistanceUtil.getDrawDetailFromDistance(maxDistance));
minDistance = LevelPosUtil.minDistance(detailLevel, posX, posZ, playerPosX, playerPosZ, regionPosX, regionPosZ);
childLevel = DetailDistanceUtil.getLodDrawDetail(DetailDistanceUtil.getDrawDetailFromDistance(minDistance));
if (detailLevel == childLevel - 1)
{
posToRender.addPosToRender(detailLevel,
posX + regionPosX * size,
posZ + regionPosZ * size);
} else if (desiredLevel > detailLevel)
{
}
else
//if (desiredLevel > detailLevel)
//{
// we have gone beyond the target Detail level
// we can stop generating
return;
} else if (desiredLevel == detailLevel)
{
posToRender.addPosToRender(detailLevel,
posX + regionPosX * size,
posZ + regionPosZ * size);
} else //case where (detailLevel > desiredLevel)
{
int childPosX = posX * 2;
int childPosZ = posZ * 2;
byte childDetailLevel = (byte) (detailLevel - 1);
int childrenCount = 0;
for (int x = 0; x <= 1; x++)
//} else
if (desiredLevel == detailLevel)
{
for (int z = 0; z <= 1; z++)
posToRender.addPosToRender(detailLevel,
posX + regionPosX * size,
posZ + regionPosZ * size);
}
else //case where (detailLevel > desiredLevel)
{
int childPosX = posX * 2;
int childPosZ = posZ * 2;
byte childDetailLevel = (byte) (detailLevel - 1);
int childrenCount = 0;
for (int x = 0; x <= 1; x++)
{
if (doesDataExist(childDetailLevel, childPosX + x, childPosZ + z))
for (int z = 0; z <= 1; z++)
{
if (!requireCorrectDetailLevel)
childrenCount++;
else
getDataToRender(posToRender, childDetailLevel, childPosX + x, childPosZ + z, playerPosX, playerPosZ, requireCorrectDetailLevel);
if (doesDataExist(childDetailLevel, childPosX + x, childPosZ + z))
{
if (!requireCorrectDetailLevel)
childrenCount++;
else
getPosToRender(posToRender, childDetailLevel, childPosX + x, childPosZ + z, playerPosX, playerPosZ, requireCorrectDetailLevel);
}
}
}
if (!requireCorrectDetailLevel)
{
// If all the four children exist go deeper
if (childrenCount == 4)
{
for (int x = 0; x <= 1; x++)
for (int z = 0; z <= 1; z++)
getPosToRender(posToRender, childDetailLevel, childPosX + x, childPosZ + z, playerPosX, playerPosZ, requireCorrectDetailLevel);
}
else
{
posToRender.addPosToRender(detailLevel,
posX + regionPosX * size,
posZ + regionPosZ * size);
}
}
}
if (!requireCorrectDetailLevel)
{
// If all the four children exist go deeper
if (childrenCount == 4)
{
for (int x = 0; x <= 1; x++)
for (int z = 0; z <= 1; z++)
getDataToRender(posToRender, childDetailLevel, childPosX + x, childPosZ + z, playerPosX, playerPosZ, requireCorrectDetailLevel);
} else
{
posToRender.addPosToRender(detailLevel,
posX + regionPosX * size,
posZ + regionPosZ * size);
}
}
}
}
/**
* Updates all children.
* <p>
@@ -368,20 +435,20 @@ public class LodRegion
int width;
int startX;
int startZ;
// TODO what are each of these loops updating?
for (byte down = (byte) (minDetailLevel + 1); down <= detailLevel; down++)
{
startX = LevelPosUtil.convert(detailLevel, posX, down);
startZ = LevelPosUtil.convert(detailLevel, posZ, down);
width = 1 << (detailLevel - down);
for (int x = 0; x < width; x++)
for (int z = 0; z < width; z++)
update(down, startX + x, startZ + z);
}
for (byte up = (byte) (detailLevel + 1); up <= LodUtil.REGION_DETAIL_LEVEL; up++)
{
update(up,
@@ -389,7 +456,7 @@ public class LodRegion
LevelPosUtil.convert(detailLevel, posZ, up));
}
}
/**
* Update the child at the given relative Pos
* <p>
@@ -399,8 +466,8 @@ public class LodRegion
{
dataContainer[detailLevel].updateData(dataContainer[detailLevel - 1], posX, posZ);
}
/**
* Returns if data exists at the given relative Pos.
*/
@@ -408,16 +475,16 @@ public class LodRegion
{
if (detailLevel < minDetailLevel)
return false;
posX = LevelPosUtil.getRegionModule(detailLevel, posX);
posZ = LevelPosUtil.getRegionModule(detailLevel, posZ);
if (dataContainer == null || dataContainer[detailLevel] == null)
return false;
return dataContainer[detailLevel].doesItExist(posX, posZ);
}
/**
* Gets the generation mode for the data point at the given relative pos.
*/
@@ -430,36 +497,34 @@ public class LodRegion
else
return DistanceGenerationMode.NONE.complexity;
}
/**
* Returns the lowest detail level in this region
* Returns the lowest (least detailed) detail level in this region
* TODO is that right?
*/
public byte getMinDetailLevel()
{
return minDetailLevel;
}
/**
* Returns the LevelContainer for the detailLevel
*
* @throws IllegalArgumentException if the detailLevel is less than minDetailLevel
*/
public LevelContainer getLevel(byte detailLevel)
{
if (detailLevel < minDetailLevel)
throw new IllegalArgumentException("getLevel asked for a detail level that does not exist: minimum: [" + minDetailLevel + "] level requested: [" + detailLevel + "]");
return dataContainer[detailLevel];
}
/**
* Add the levelContainer to this Region, updating the minDetailLevel
* if necessary.
*
* @throws IllegalArgumentException if the LevelContainer's detailLevel
* is 2 or more detail levels lower than the
* minDetailLevel of this region.
* is 2 or more detail levels lower than the
* minDetailLevel of this region.
*/
public void addLevelContainer(LevelContainer levelContainer)
{
@@ -471,17 +536,17 @@ public class LodRegion
+ "only allows adding LevelContainers with a "
+ "detail level of [" + (minDetailLevel - 1) + "]");
}
if (levelContainer.getDetailLevel() == minDetailLevel - 1)
minDetailLevel = levelContainer.getDetailLevel();
dataContainer[levelContainer.getDetailLevel()] = levelContainer;
}
// TODO James thinks cutTree and growTree (which he renamed to match cutTree)
// should have more descriptive names, to make sure the "Tree" portion isn't
// confused with Minecraft trees (the plant).
/**
* Removes any dataContainers that are higher than
* the given detailLevel
@@ -492,11 +557,11 @@ public class LodRegion
{
for (byte detailLevelIndex = 0; detailLevelIndex < detailLevel; detailLevelIndex++)
dataContainer[detailLevelIndex] = null;
minDetailLevel = detailLevel;
}
}
/**
* Make this region more detailed to the detailLevel given.
* TODO is that correct?
@@ -508,14 +573,14 @@ public class LodRegion
for (byte detailLevelIndex = (byte) (minDetailLevel - 1); detailLevelIndex >= detailLevel; detailLevelIndex--)
{
if (dataContainer[detailLevelIndex + 1] == null)
dataContainer[detailLevelIndex + 1] = new SingleLevelContainer((byte) (detailLevelIndex + 1));
dataContainer[detailLevelIndex + 1] = new VerticalLevelContainer((byte) (detailLevelIndex + 1));
dataContainer[detailLevelIndex] = dataContainer[detailLevelIndex + 1].expand();
}
minDetailLevel = detailLevel;
}
}
/**
* return RegionPos of this lod region
*/
@@ -523,21 +588,7 @@ public class LodRegion
{
return new RegionPos(regionPosX, regionPosZ);
}
/**
* Returns the minimum memory needed in bytes
*/
public int getMinMemoryNeeded(LodTemplate template)
{
int memory = 0;
for (byte detailLevelIndex = LodUtil.REGION_DETAIL_LEVEL; detailLevelIndex > minDetailLevel; detailLevelIndex--)
{
// TODO why are we multiplying the dataContainer's memory by the template's memory?
memory += dataContainer[detailLevelIndex].getMaxMemoryUse() * template.getBufferMemoryForSingleLod(dataContainer[detailLevelIndex].getMaxVerticalData());
}
return memory;
}
/**
* Returns how many LODs are in this region
*/
@@ -546,26 +597,26 @@ public class LodRegion
int count = 0;
for (LevelContainer container : dataContainer)
count += container.getMaxNumberOfLods();
return count;
}
public VerticalQuality getVerticalQuality()
{
return verticalQuality;
}
public DistanceGenerationMode getGenerationMode()
{
return generationMode;
}
public int getMaxVerticalData(byte detailLevel)
{
return dataContainer[detailLevel].getMaxVerticalData();
}
@Override
public String toString()
{
@@ -1,5 +1,6 @@
/*
* This file is part of the LOD Mod, licensed under the GNU GPL v3 License.
* This file is part of the Distant Horizon mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020 James Seibel
*
@@ -15,6 +16,7 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.lod.objects;
import java.util.Hashtable;
@@ -26,7 +28,7 @@ import net.minecraft.world.DimensionType;
/**
* This stores all LODs for a given world.
*
*
* @author James Seibel
* @author Leonardo Amato
* @version 9-27-2021
@@ -57,10 +59,9 @@ public class LodWorld
/**
* Set up the LodWorld with the given newWorldName. <br>
* This should be done whenever loading a new world. <br><br>
*
* <p>
* Note a System.gc() call may be in order after calling this <Br>
* since a lot of LOD data is now homeless. <br>
*
* @param newWorldName name of the world
*/
public void selectWorld(String newWorldName)
@@ -77,7 +78,7 @@ public class LodWorld
return;
worldName = newWorldName;
lodDimensions = new Hashtable<DimensionType, LodDimension>();
lodDimensions = new Hashtable<>();
isWorldLoaded = true;
}
@@ -85,7 +86,7 @@ public class LodWorld
* Set the worldName to "No world loaded"
* and clear the lodDimensions Map. <br>
* This should be done whenever unloaded a world. <br><br>
*
* <p>
* Note a System.gc() call may be in order after calling this <Br>
* since a lot of LOD data is now homeless. <br>
*/
@@ -152,9 +153,9 @@ public class LodWorld
}
public boolean getIsWorldLoaded()
public boolean getIsWorldNotLoaded()
{
return isWorldLoaded;
return !isWorldLoaded;
}
public String getWorldName()
@@ -1,5 +1,6 @@
/*
* This file is part of the LOD Mod, licensed under the GNU GPL v3 License.
* This file is part of the Distant Horizon mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020 James Seibel
*
@@ -15,6 +16,7 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.lod.objects;
import com.seibel.lod.enums.FogDistance;
@@ -29,11 +31,13 @@ import com.seibel.lod.enums.FogQuality;
*/
public class NearFarFogSettings
{
public NearOrFarSetting near = new NearOrFarSetting(FogDistance.NEAR);
public NearOrFarSetting far = new NearOrFarSetting(FogDistance.FAR);
public final NearOrFarSetting near = new NearOrFarSetting(FogDistance.NEAR);
public final NearOrFarSetting far = new NearOrFarSetting(FogDistance.FAR);
/** If true that means Minecraft is
* rendering fog along side us */
/**
* If true that means Minecraft is
* rendering fog alongside us
*/
public boolean vanillaIsRenderingFog = true;
public NearFarFogSettings()
@@ -47,13 +51,10 @@ public class NearFarFogSettings
* This holds all relevant data to rendering fog at either
* near or far distances.
*/
public class NearOrFarSetting
public static class NearOrFarSetting
{
public FogQuality quality = FogQuality.FANCY;
public FogDistance distance = FogDistance.FAR;
/** If true this section should render with fog */
public boolean enabled = true;
public FogDistance distance;
public NearOrFarSetting(FogDistance newFogDistance)
{
@@ -1,9 +1,26 @@
/*
* This file is part of the Distant Horizon mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.lod.objects;
import com.seibel.lod.util.LevelPosUtil;
import javax.swing.*;
/**
* Holds the levelPos that need to be generated.
* TODO is that correct?
@@ -13,15 +30,15 @@ import javax.swing.*;
*/
public class PosToGenerateContainer
{
private int playerPosX;
private int playerPosZ;
private byte farMinDetail;
private final int playerPosX;
private final int playerPosZ;
private final byte farMinDetail;
private int nearSize;
private int farSize;
// TODO what is the format of these two arrays? [detailLevel][4-children]?
private int[][] nearPosToGenerate;
private int[][] farPosToGenerate;
private final int[][] nearPosToGenerate;
private final int[][] farPosToGenerate;
@@ -49,9 +66,9 @@ public class PosToGenerateContainer
{
// We are introducing a position in the far array
if(farSize < farPosToGenerate.length)
if (farSize < farPosToGenerate.length)
farSize++;
index = farSize - 1;
while (index > 0 && LevelPosUtil.compareDistance(distance, farPosToGenerate[index - 1][3]) <= 0)
{
@@ -63,7 +80,7 @@ public class PosToGenerateContainer
}
if (index != farSize-1 || farSize != farPosToGenerate.length)
if (index != farSize - 1 || farSize != farPosToGenerate.length)
{
farPosToGenerate[index][0] = detailLevel + 1;
farPosToGenerate[index][1] = posX;
@@ -75,10 +92,10 @@ public class PosToGenerateContainer
{
//We are introducing a position in the near array
if(nearSize < nearPosToGenerate.length)
if (nearSize < nearPosToGenerate.length)
nearSize++;
index = nearSize-1;
index = nearSize - 1;
while (index > 0 && LevelPosUtil.compareDistance(distance, nearPosToGenerate[index - 1][3]) <= 0)
{
nearPosToGenerate[index][0] = nearPosToGenerate[index - 1][0];
@@ -89,7 +106,7 @@ public class PosToGenerateContainer
}
if (index != nearSize-1 || nearSize != nearPosToGenerate.length)
if (index != nearSize - 1 || nearSize != nearPosToGenerate.length)
{
nearPosToGenerate[index][0] = detailLevel + 1;
nearPosToGenerate[index][1] = posX;
@@ -103,7 +120,7 @@ public class PosToGenerateContainer
public int getNumberOfPos()
{
return nearSize+farSize;
return nearSize + farSize;
}
public int getNumberOfNearPos()
@@ -124,6 +141,7 @@ public class PosToGenerateContainer
else
return farPosToGenerate[n][0];
}
public int getNthPosX(int n, boolean near)
{
if (near)
@@ -131,6 +149,7 @@ public class PosToGenerateContainer
else
return farPosToGenerate[n][1];
}
public int getNthPosZ(int n, boolean near)
{
if (near)
@@ -138,6 +157,7 @@ public class PosToGenerateContainer
else
return farPosToGenerate[n][2];
}
public int getNthGeneration(int n, boolean near)
{
if (near)
@@ -149,52 +169,42 @@ public class PosToGenerateContainer
@Override
public String toString()
{
// TOOD is this commented code still useful?
// if so why have it commented out?
/*
StringBuilder builder = new StringBuilder();
builder.append("Number of pos to generate ");
builder.append(farSize + nearSize);
builder.append('\n');
builder.append("Number of near pos to generate ");
builder.append(nearSize);
builder.append('\n');
builder.append("Number of far pos to generate ");
builder.append(farSize);
builder.append('\n');
builder.append('\n');
builder.append("near pos to generate");
builder.append('\n');
for (int i = 0; i < nearSize; i++)
for (int[] ints : nearPosToGenerate)
{
builder.append(posToGenerate[i][0]-1);
if (ints[0] == 0)
break;
builder.append(ints[0] - 1);
builder.append(" ");
builder.append(posToGenerate[i][1]);
builder.append(ints[1]);
builder.append(" ");
builder.append(posToGenerate[i][2]);
builder.append(ints[2]);
builder.append(" ");
builder.append(posToGenerate[i][3]);
builder.append(ints[3]);
builder.append('\n');
}
builder.append('\n');
builder.append("far pos to generate");
builder.append('\n');
for (int i = maxSize - 1; i >= maxSize - farSize; i--)
for (int[] ints : farPosToGenerate)
{
builder.append(posToGenerate[i][0]-1);
if (ints[0] == 0)
break;
builder.append(ints[0] - 1);
builder.append(" ");
builder.append(posToGenerate[i][1]);
builder.append(ints[1]);
builder.append(" ");
builder.append(posToGenerate[i][2]);
builder.append(ints[2]);
builder.append(" ");
builder.append(posToGenerate[i][3]);
builder.append(ints[3]);
builder.append('\n');
}
builder.append('\n');
return builder.toString();
*/
return " ";
}
}
@@ -1,27 +1,46 @@
/*
* This file is part of the Distant Horizon mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.lod.objects;
import java.util.Arrays;
import com.seibel.lod.proxy.ClientProxy;
import com.seibel.lod.util.LevelPosUtil;
import com.seibel.lod.util.LodUtil;
import java.util.Arrays;
/**
*
* @author Leonardo Amato
* @version 9-18-2021
*/
public class PosToRenderContainer
{
public byte minDetail;
private int size;
private final int size;
private int regionPosX;
private int regionPosZ;
private int numberOfPosToRender;
private int[] posToRender;
/*TODO this population matrix could be converted to boolean to improve memory use*/
/*TODO this population matrix could be converted to boolean to improve memory use
* no since bools are stored as bytes anyway - cola*/
private byte[][] population;
public PosToRenderContainer(byte minDetail, int regionPosX, int regionPosZ)
{
this.minDetail = minDetail;
@@ -29,95 +48,98 @@ public class PosToRenderContainer
this.regionPosX = regionPosX;
this.regionPosZ = regionPosZ;
this.size = 1 << (LodUtil.REGION_DETAIL_LEVEL - minDetail);
posToRender = new int[size*size*3];
posToRender = new int[size * size * 3];
population = new byte[size][size];
}
public void addPosToRender(byte detailLevel, int posX, int posZ)
{
// When rapidly changing dimensions the bufferBuidler can cause this,
// When rapidly changing dimensions the bufferBuilder 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 + "]");
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;
posToRender[numberOfPosToRender * 3] = detailLevel;
posToRender[numberOfPosToRender * 3 + 1] = posX;
posToRender[numberOfPosToRender * 3 + 2] = posZ;
numberOfPosToRender++;
population[LevelPosUtil.getRegionModule(minDetail, LevelPosUtil.convert(detailLevel,posX,minDetail))]
[LevelPosUtil.getRegionModule(minDetail, LevelPosUtil.convert(detailLevel,posZ,minDetail))] = (byte) (detailLevel + 1);
population[LevelPosUtil.getRegionModule(minDetail, LevelPosUtil.convert(detailLevel, posX, minDetail))]
[LevelPosUtil.getRegionModule(minDetail, LevelPosUtil.convert(detailLevel, posZ, minDetail))] = (byte) (detailLevel + 1);
}
public boolean contains(byte detailLevel, int posX, int posZ)
{
if(LevelPosUtil.getRegion(detailLevel, posX) == regionPosX && LevelPosUtil.getRegion(detailLevel, posZ) == regionPosZ)
{
return (population[LevelPosUtil.getRegionModule(minDetail, LevelPosUtil.convert(detailLevel,posX,minDetail))]
[LevelPosUtil.getRegionModule(minDetail, LevelPosUtil.convert(detailLevel,posZ,minDetail))] == (detailLevel + 1));
}else
{
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){
public void clear(byte minDetail, int regionPosX, int regionPosZ)
{
this.numberOfPosToRender = 0;
this.regionPosX = regionPosX;
this.regionPosZ = regionPosZ;
if(this.minDetail == minDetail)
if (this.minDetail == minDetail)
{
Arrays.fill(posToRender, 0);
for(int i = 0; i< population.length; i++)
Arrays.fill(population[i], (byte) 0);
}else{
for (byte[] bytes : population)
Arrays.fill(bytes, (byte) 0);
}
else
{
this.minDetail = minDetail;
int size = 1 << (LodUtil.REGION_DETAIL_LEVEL - minDetail);
posToRender = new int[size*size*3];
posToRender = new int[size * size * 3];
population = new byte[size][size];
}
}
public int getNumberOfPos()
{
return numberOfPosToRender;
}
public byte getNthDetailLevel(int n)
{
return (byte) posToRender[n*3 + 0];
return (byte) posToRender[n * 3];
}
public int getNthPosX(int n)
{
return posToRender[n*3 + 1];
return posToRender[n * 3 + 1];
}
public int getNthPosZ(int n)
{
return posToRender[n*3 + 2];
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++)
for (int i = 0; i < numberOfPosToRender; i++)
{
builder.append(posToRender[i*3 + 0]);
builder.append(posToRender[i * 3]);
builder.append(" ");
builder.append(posToRender[i*3 + 1]);
builder.append(posToRender[i * 3 + 1]);
builder.append(" ");
builder.append(posToRender[i*3 + 2]);
builder.append(posToRender[i * 3 + 2]);
builder.append('\n');
}
builder.append('\n');
@@ -1,5 +1,6 @@
/*
* This file is part of the LOD Mod, licensed under the GNU GPL v3 License.
* This file is part of the Distant Horizon mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020 James Seibel
*
@@ -15,6 +16,7 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.lod.objects;
import com.seibel.lod.util.LodUtil;
@@ -24,7 +26,7 @@ import net.minecraft.util.math.ChunkPos;
/**
* This object is similar to ChunkPos or BlockPos.
*
*
* @author James Seibel
* @version 8-21-2021
*/
@@ -36,7 +38,7 @@ public class RegionPos
/**
* Default Constructor <br><br>
*
* <p>
* Sets x and z to 0
*/
public RegionPos()
@@ -69,7 +71,7 @@ public class RegionPos
public ChunkPos chunkPos()
{
return new ChunkPos(
(x * LodUtil.REGION_WIDTH_IN_CHUNKS) + LodUtil.REGION_WIDTH_IN_CHUNKS / 2,
(x * LodUtil.REGION_WIDTH_IN_CHUNKS) + LodUtil.REGION_WIDTH_IN_CHUNKS / 2,
(z * LodUtil.REGION_WIDTH_IN_CHUNKS) + LodUtil.REGION_WIDTH_IN_CHUNKS / 2);
}
@@ -1,233 +0,0 @@
package com.seibel.lod.objects;
import java.util.Arrays;
import com.seibel.lod.util.DataPointUtil;
import com.seibel.lod.util.LevelPosUtil;
import com.seibel.lod.util.LodUtil;
import com.seibel.lod.util.ThreadMapUtil;
/**
* This object holds the LOD data for a single dataPoint.
*
* @author Leonardo Amato
* @version 9-28-2021
*/
public class SingleLevelContainer implements LevelContainer
{
/** The detailLevel of this LevelContainer */
public final byte detailLevel;
/** How many dataPoints wide is this LevelContainer? */
public final int dataWidthCount;
/** This holds all the dataPoints for this LevelContainer */
public final long[][] dataContainer;
/** Constructor */
public SingleLevelContainer(byte newDetailLevel)
{
this.detailLevel = newDetailLevel;
// equivalent to 2^(...)
dataWidthCount = 1 << (LodUtil.REGION_DETAIL_LEVEL - newDetailLevel);
dataContainer = new long[dataWidthCount][dataWidthCount];
}
/** */
public SingleLevelContainer(byte[] inputData)
{
int tempIndex;
int index = 0;
long newData;
detailLevel = inputData[index];
index++;
dataWidthCount = (int) Math.pow(2, LodUtil.REGION_DETAIL_LEVEL - detailLevel);
this.dataContainer = new long[dataWidthCount][dataWidthCount];
for (int x = 0; x < dataWidthCount; x++)
{
for (int z = 0; z < dataWidthCount; z++)
{
newData = 0;
if (inputData[index] == 0)
{
index++;
}
else if (inputData[index] == 3)
{
newData = 3;
index++;
}
else if (index + 7 >= inputData.length)
{
break;
}
else
{
for (tempIndex = 0; tempIndex < 8; tempIndex++)
newData += (((long) inputData[index + tempIndex]) & 0xff) << (8 * tempIndex);
index = index + 8;
}
dataContainer[x][z] = newData;
}
}
}
@Override
public void clear(int posX, int posZ)
{
posX = LevelPosUtil.getRegionModule(detailLevel, posX);
posZ = LevelPosUtil.getRegionModule(detailLevel, posZ);
dataContainer[posX][posZ] = DataPointUtil.EMPTY_DATA;
}
@Override
public boolean addData(long data, int posX, int posZ, int index)
{
posX = LevelPosUtil.getRegionModule(detailLevel, posX);
posZ = LevelPosUtil.getRegionModule(detailLevel, posZ);
dataContainer[posX][posZ] = data;
return true;
}
@Override
public boolean addSingleData(long newData, int posX, int posZ)
{
posX = LevelPosUtil.getRegionModule(detailLevel, posX);
posZ = LevelPosUtil.getRegionModule(detailLevel, posZ);
dataContainer[posX][posZ] = newData;
return true;
}
@Override
public long getData(int posX, int posZ, int index)
{
posX = LevelPosUtil.getRegionModule(detailLevel, posX);
posZ = LevelPosUtil.getRegionModule(detailLevel, posZ);
// TODO Improve this using a thread map to long[]
return dataContainer[posX][posZ];
}
@Override
public long getSingleData(int posX, int posZ)
{
posX = LevelPosUtil.getRegionModule(detailLevel, posX);
posZ = LevelPosUtil.getRegionModule(detailLevel, posZ);
// TODO Improve this using a thread map to long[]
return dataContainer[posX][posZ];
}
@Override
public byte getDetailLevel()
{
return detailLevel;
}
@Override
public LevelContainer expand()
{
return new SingleLevelContainer((byte) (getDetailLevel() - 1));
}
/** TODO could this be renamed mergeData? */
@Override
public void updateData(LevelContainer lowerLevelContainer, int posX, int posZ)
{
//We reset the array
long[] dataToMerge = ThreadMapUtil.getSingleUpdateArray();
int childPosX;
int childPosZ;
long data;
posX = LevelPosUtil.getRegionModule(detailLevel, posX);
posZ = LevelPosUtil.getRegionModule(detailLevel, posZ);
for (int x = 0; x <= 1; x++)
{
for (int z = 0; z <= 1; z++)
{
childPosX = 2 * posX + x;
childPosZ = 2 * posZ + z;
dataToMerge[2 * x + z] = lowerLevelContainer.getSingleData(childPosX, childPosZ);
}
}
data = DataPointUtil.mergeSingleData(dataToMerge);
addSingleData(data, posX, posZ);
}
@Override
public int getMaxVerticalData()
{
return 1;
}
@Override
public boolean doesItExist(int posX, int posZ)
{
posX = LevelPosUtil.getRegionModule(detailLevel, posX);
posZ = LevelPosUtil.getRegionModule(detailLevel, posZ);
//Improve this using a thread map to long[]
return DataPointUtil.doesItExist(getSingleData(posX, posZ));
}
@Override
public byte[] toDataString()
{
int index = 0;
int tempIndex;
byte[] tempData = ThreadMapUtil.getFreshSaveContainer(1 + (dataWidthCount * dataWidthCount * 8));
tempData[index] = detailLevel;
index++;
for (int x = 0; x < dataWidthCount; x++)
{
for (int z = 0; z < dataWidthCount; z++)
{
if (dataContainer[x][z] == 0)
{
tempData[index] = 0;
index++;
} else if (dataContainer[x][z] == 3)
{
tempData[index] = 3;
index++;
} else
{
for (tempIndex = 0; tempIndex < 8; tempIndex++)
tempData[index + tempIndex] = (byte) (dataContainer[x][z] >>> (8 * tempIndex));
index += 8;
}
}
}
return Arrays.copyOfRange(tempData, 0, index);
}
@Override
public String toString()
{
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append(detailLevel);
return stringBuilder.toString();
}
@Override
public int getMaxNumberOfLods()
{
return dataWidthCount * dataWidthCount * getMaxVerticalData();
}
@Override
public int getMaxMemoryUse()
{
return getMaxNumberOfLods() * 2; //2 byte
}
}
@@ -1,6 +1,23 @@
package com.seibel.lod.objects;
/*
* This file is part of the Distant Horizon mod (formerly 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/>.
*/
import java.util.Arrays;
package com.seibel.lod.objects;
import com.seibel.lod.util.DataPointUtil;
import com.seibel.lod.util.DetailDistanceUtil;
@@ -8,15 +25,20 @@ import com.seibel.lod.util.LevelPosUtil;
import com.seibel.lod.util.LodUtil;
import com.seibel.lod.util.ThreadMapUtil;
/**
*
* @author Leonardo Amato
* @version ??
*/
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;
@@ -24,72 +46,90 @@ public class VerticalLevelContainer implements LevelContainer
maxVerticalData = DetailDistanceUtil.getMaxVerticalData(detailLevel);
dataContainer = new long[size * size * DetailDistanceUtil.getMaxVerticalData(detailLevel)];
}
@Override
public byte getDetailLevel()
{
return detailLevel;
}
@Override
public void clear(int posX, int posZ){
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;
for (int verticalIndex = 0; verticalIndex < maxVerticalData; verticalIndex++)
{
dataContainer[posX * size * maxVerticalData + posZ * maxVerticalData + verticalIndex] = DataPointUtil.EMPTY_DATA;
}
}
@Override
public boolean addData(long data, int posX, int posZ, int verticalIndex){
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;
dataContainer[posX * size * maxVerticalData + posZ * maxVerticalData + verticalIndex] = data;
return true;
}
@Override
public boolean addSingleData(long data, int posX, int posZ){
public boolean addVerticalData(long[] data, 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] = data[verticalIndex];
return true;
}
@Override
public boolean addSingleData(long data, int posX, int posZ)
{
return addData(data, posX, posZ, 0);
}
@Override
public long getData(int posX, int posZ, int verticalIndex){
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];
return dataContainer[posX * size * maxVerticalData + posZ * maxVerticalData + verticalIndex];
}
@Override
public long getSingleData(int posX, int posZ){
return getData(posX,posZ,0);
public long getSingleData(int posX, int posZ)
{
return getData(posX, posZ, 0);
}
@Override
public int getMaxVerticalData(){
public int getMaxVerticalData()
{
return maxVerticalData;
}
public int getSize(){
public int getSize()
{
return size;
}
@Override
public boolean doesItExist(int posX, int posZ){
public boolean doesItExist(int posX, int posZ)
{
posX = LevelPosUtil.getRegionModule(detailLevel, posX);
posZ = LevelPosUtil.getRegionModule(detailLevel, posZ);
return DataPointUtil.doesItExist(getSingleData(posX,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];
@@ -97,58 +137,29 @@ public class VerticalLevelContainer implements LevelContainer
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++)
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;
}
for (tempIndex = 0; tempIndex < 8; tempIndex++)
newData += (((long) inputData[index + tempIndex]) & 0xff) << (8 * tempIndex);
index += 8;
dataContainer[i] = newData;
}
}
@Override
public LevelContainer expand(){
public LevelContainer expand()
{
return new VerticalLevelContainer((byte) (getDetailLevel() - 1));
}
@Override
public void updateData(LevelContainer lowerLevelContainer, int posX, int posZ)
{
//We reset the array
long[] dataToMerge = ThreadMapUtil.getFreshVerticalUpdateArray(4 * lowerLevelContainer.getMaxVerticalData(), detailLevel);
int lowerMaxVertical = dataToMerge.length/4;
long[] dataToMerge = ThreadMapUtil.getVerticalUpdateArray(detailLevel);
int lowerMaxVertical = dataToMerge.length / 4;
int childPosX;
int childPosZ;
long[] data;
@@ -160,13 +171,13 @@ public class VerticalLevelContainer implements LevelContainer
{
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);
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++)
for (int verticalIndex = 0; (verticalIndex < data.length) && (verticalIndex < maxVerticalData); verticalIndex++)
{
addData(data[verticalIndex],
posX,
@@ -174,60 +185,34 @@ public class VerticalLevelContainer implements LevelContainer
verticalIndex);
}
}
@Override
public byte[] toDataString()
{
int index = 0;
int counter = -1;
byte last = -1;
int x = size * size * maxVerticalData;
int tempIndex;
long current;
byte[] tempData = ThreadMapUtil.getFreshSaveContainer(2 + (x * 8));
byte[] tempData = ThreadMapUtil.getSaveContainer(detailLevel);
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;
}
for (tempIndex = 0; tempIndex < 8; tempIndex++)
tempData[index + tempIndex] = (byte) (current >>> (8 * tempIndex));
index += 8;
}
return Arrays.copyOfRange(tempData, 0, index);
return tempData;
}
@Override
@SuppressWarnings("unused")
public String toString()
{
/*
@@ -248,14 +233,10 @@ public class VerticalLevelContainer implements LevelContainer
*/
return " ";
}
@Override
public int getMaxNumberOfLods(){
return size*size*getMaxVerticalData();
}
@Override
public int getMaxMemoryUse(){
return getMaxNumberOfLods() * 2; //2 byte
public int getMaxNumberOfLods()
{
return size * size * getMaxVerticalData();
}
}
@@ -1,5 +1,6 @@
/*
* This file is part of the LOD Mod, licensed under the GNU GPL v3 License.
* This file is part of the Distant Horizon mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020 James Seibel
*
@@ -15,6 +16,7 @@
* 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.proxy;
import org.apache.logging.log4j.LogManager;
@@ -22,9 +24,9 @@ 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.bufferBuilding.LodBufferBuilder;
import com.seibel.lod.builders.lodBuilding.LodBuilder;
import com.seibel.lod.builders.worldGeneration.LodGenWorker;
import com.seibel.lod.builders.worldGeneration.LodWorldGenerator;
import com.seibel.lod.config.LodConfig;
import com.seibel.lod.enums.DistanceGenerationMode;
@@ -50,29 +52,28 @@ import net.minecraftforge.eventbus.api.SubscribeEvent;
/**
* This handles all events sent to the client,
* and is the starting point for most of the mod.
*
* @author James_Seibel
* @version 9-26-2021
* @version 10-15-2021
*/
public class ClientProxy
{
public static final Logger LOGGER = LogManager.getLogger("LOD");
/**
/**
* there is some setup that should only happen once,
* once this is true that setup has completed
*/
private boolean firstTimeSetupComplete = false;
private static LodWorld lodWorld = new LodWorld();
private static LodBuilder lodBuilder = new LodBuilder();
private static LodBufferBuilder lodBufferBuilder = new LodBufferBuilder();
private static final LodWorld lodWorld = new LodWorld();
private static final LodBuilder lodBuilder = new LodBuilder();
private static final LodBufferBuilder lodBufferBuilder = new LodBufferBuilder();
private static LodRenderer renderer = new LodRenderer(lodBufferBuilder);
private static LodWorldGenerator lodWorldGenerator = LodWorldGenerator.INSTANCE;
private static final LodWorldGenerator lodWorldGenerator = LodWorldGenerator.INSTANCE;
private boolean configOverrideReminderPrinted = false;
private MinecraftWrapper mc = MinecraftWrapper.INSTANCE;
private final MinecraftWrapper mc = MinecraftWrapper.INSTANCE;
/** This is used to determine if the LODs should be regenerated */
@@ -96,16 +97,11 @@ public class ClientProxy
// render event //
//==============//
/**
* Do any setup that is required to draw LODs
* and then tell the LodRenderer to draw.
*
* @param mcMatrixStack
*/
/** Do any setup that is required to draw LODs and then tell the LodRenderer to draw. */
public void renderLods(MatrixStack mcMatrixStack, float partialTicks)
{
// comment out when creating a release
applyConfigOverrides();
// applyConfigOverrides();
// clear any out of date objects
mc.clearFrameObjectCache();
@@ -117,7 +113,7 @@ public class ClientProxy
firstFrameSetup();
if (mc == null || mc.getPlayer() == null || !lodWorld.getIsWorldLoaded())
if (mc == null || mc.getPlayer() == null || lodWorld.getIsWorldNotLoaded())
return;
LodDimension lodDim = lodWorld.getLodDimension(mc.getCurrentDimension());
@@ -140,7 +136,7 @@ public class ClientProxy
profiler.pop(); // get out of "terrain"
profiler.push("LOD");
renderer.drawLODs(lodDim, mcMatrixStack, partialTicks, mc.getProfiler());
renderer.drawLODs(lodDim, mcMatrixStack, partialTicks, mc.getProfiler());
profiler.pop(); // end LOD
profiler.push("terrain"); // go back into "terrain"
@@ -149,7 +145,7 @@ public class ClientProxy
// 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();
previousLodRenderDistance = LodConfig.CLIENT.graphics.qualityOption.lodChunkRenderDistance.get();
}
catch (Exception e)
{
@@ -158,40 +154,37 @@ public class ClientProxy
}
}
/** used in a development environment to change settings on the fly */
private void applyConfigOverrides()
{
// remind the developer(s) that the config override is active
if (!configOverrideReminderPrinted)
{
//mc.getPlayer().sendMessage(new StringTextComponent("Debug settings enabled!"), mc.getPlayer().getUUID());
// mc.getPlayer().sendMessage(new StringTextComponent("LOD experimental build 1.5.1"), mc.getPlayer().getUUID());
// mc.getPlayer().sendMessage(new StringTextComponent("Here be dragons!"), mc.getPlayer().getUUID());
mc.getPlayer().sendMessage(new StringTextComponent("LOD experimental build 1.5.1"), mc.getPlayer().getUUID());
mc.getPlayer().sendMessage(new StringTextComponent("Here be dragons!"), mc.getPlayer().getUUID());
mc.getPlayer().sendMessage(new StringTextComponent("Debug settings enabled!"), mc.getPlayer().getUUID());
configOverrideReminderPrinted = true;
}
// LodConfig.CLIENT.graphics.drawResolution.set(HorizontalResolution.BLOCK);
// LodConfig.CLIENT.worldGenerator.generationResolution.set(HorizontalResolution.BLOCK);
// requires a world restart?
// LodConfig.CLIENT.worldGenerator.lodQualityMode.set(VerticalQuality.MULTI_LOD);
// LodConfig.CLIENT.graphics.fogDistance.set(FogDistance.FAR);
// LodConfig.CLIENT.worldGenerator.lodQualityMode.set(VerticalQuality.VOXEL);
// LodConfig.CLIENT.graphics.fogQualityOption.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.vanillaOverdraw.set(VanillaOverdraw.HALF);
// LodConfig.CLIENT.worldGenerator.distanceGenerationMode.set(DistanceGenerationMode.SURFACE);
// LodConfig.CLIENT.graphics.lodChunkRenderDistance.set(64);
// LodConfig.CLIENT.graphics.qualityOption.lodChunkRenderDistance.set(128);
// 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.buffers.rebuildTimes.set(BufferRebuildTimes.FREQUENT);
LodConfig.CLIENT.debugging.enableDebugKeybindings.set(true);
LodConfig.CLIENT.advancedModOptions.debugging.enableDebugKeybindings.set(true);
// LodConfig.CLIENT.debugging.debugMode.set(DebugMode.SHOW_DETAIL);
}
@@ -203,7 +196,7 @@ public class ClientProxy
@SubscribeEvent
public void serverTickEvent(TickEvent.ServerTickEvent event)
{
if (mc == null || mc.getPlayer() == null || !lodWorld.getIsWorldLoaded())
if (mc == null || mc.getPlayer() == null || lodWorld.getIsWorldNotLoaded())
return;
LodDimension lodDim = lodWorld.getLodDimension(mc.getPlayer().level.dimensionType());
@@ -231,6 +224,8 @@ public class ClientProxy
public void worldLoadEvent(WorldEvent.Load event)
{
DataPointUtil.worldHeight = event.getWorld().getHeight();
//LodNodeGenWorker.restartExecutorService();
//ThreadMapUtil.clearMaps();
// the player just loaded a new world/dimension
lodWorld.selectWorld(LodUtil.getWorldID(event.getWorld()));
@@ -253,10 +248,10 @@ public class ClientProxy
// if this isn't done unfinished tasks may be left in the queue
// preventing new LodChunks form being generated
LodNodeGenWorker.restartExecuterService();
//LodNodeGenWorker.restartExecutorService();
//ThreadMapUtil.clearMaps();
LodWorldGenerator.INSTANCE.numberOfChunksWaitingToGenerate.set(0);
// the player has disconnected from a server
lodWorld.deselectWorld();
@@ -267,7 +262,7 @@ public class ClientProxy
renderer = new LodRenderer(lodBufferBuilder);
// make sure the nulled objects are freed.
// make sure the nilled objects are freed.
// (this prevents an out of memory error when
// changing worlds)
System.gc();
@@ -289,28 +284,29 @@ public class ClientProxy
}
@SubscribeEvent
public void onKeyInput(InputEvent.KeyInputEvent event)
public void onKeyInput(InputEvent.KeyInputEvent event)
{
if(LodConfig.CLIENT.debugging.enableDebugKeybindings.get()
if (LodConfig.CLIENT.advancedModOptions.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());
LodConfig.CLIENT.advancedModOptions.debugging.debugMode.set(LodConfig.CLIENT.advancedModOptions.debugging.debugMode.get().getNext());
}
if(LodConfig.CLIENT.debugging.enableDebugKeybindings.get()
if (LodConfig.CLIENT.advancedModOptions.debugging.enableDebugKeybindings.get()
&& event.getKey() == GLFW.GLFW_KEY_F6 && event.getAction() == GLFW.GLFW_PRESS)
{
LodConfig.CLIENT.graphics.drawLods.set(!LodConfig.CLIENT.graphics.drawLods.get());
LodConfig.CLIENT.advancedModOptions.debugging.drawLods.set(!LodConfig.CLIENT.advancedModOptions.debugging.drawLods.get());
}
}
//============//
// LOD events //
//============//
/**
* Re-centers the given LodDimension if it needs to be.
*/
/** Re-centers the given LodDimension if it needs to be. */
private void playerMoveEvent(LodDimension lodDim)
{
// make sure the dimension is centered
@@ -325,15 +321,19 @@ public class ClientProxy
}
/**
* Re-sizes all LodDimensions if they needs to be.
*/
/** Re-sizes all LodDimensions if they need to be. */
private void viewDistanceChangedEvent()
{
// calculate how wide the dimension(s) should be in regions
int chunksWide = LodConfig.CLIENT.graphics.lodChunkRenderDistance.get() * 2 + 1;
int chunksWide;
if (mc.getClientWorld().dimensionType().hasCeiling())
chunksWide = Math.min(LodConfig.CLIENT.graphics.qualityOption.lodChunkRenderDistance.get(), LodUtil.CEILED_DIMENSION_MAX_RENDER_DISTANCE) * 2 + 1;
else
chunksWide = LodConfig.CLIENT.graphics.qualityOption.lodChunkRenderDistance.get() * 2 + 1;
int newWidth = (int) Math.ceil(chunksWide / (float) LodUtil.REGION_WIDTH_IN_CHUNKS);
newWidth = (newWidth % 2 == 0) ? (newWidth += 1) : (newWidth += 2); // make sure we have a odd number of regions
// make sure we have an odd number of regions
newWidth += (newWidth & 1) == 0 ? 1 : 2;
// do the dimensions need to change in size?
if (lodBuilder.defaultDimensionWidthInRegions != newWidth || recalculateWidths)
@@ -352,20 +352,26 @@ public class ClientProxy
}
/**
* This event is called once during the first frame Minecraft renders in the world.
*/
/** 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
// make sure the GlProxy is created before the LodBufferBuilder needs it
GlProxy.getInstance();
// TODO shouldn't these already be empty?
ThreadMapUtil.clearMaps();
firstTimeSetupComplete = true;
}
/** this method reset some static data every time we change world */
private void resetMod()
{
ThreadMapUtil.clearMaps();
LodGenWorker.restartExecutorService();
}
//================//
// public getters //
//================//
+96 -73
View File
@@ -1,44 +1,66 @@
/*
* This file is part of the Distant Horizon mod (formerly 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.proxy;
import org.lwjgl.glfw.GLFW;
import org.lwjgl.opengl.GL;
import org.lwjgl.opengl.GLCapabilities;
import org.lwjgl.opengl.WGL;
import com.mojang.blaze3d.systems.RenderSystem;
import com.seibel.lod.enums.GlProxyContext;
/**
* A singleton that holds references to different openGL contexts
* and GPU capabilities.
*
*
* <p>
* Helpful OpenGL resources: <br><br>
*
* <p>
* 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
* @version 10-2-2021
*/
public class GlProxy
{
private static GlProxy instance = null;
public final long deviceContext;
/** Minecraft's GLFW window */
public final long minecraftGlContext;
/** Minecraft's GL context */
public final GLCapabilities minecraftGlCapabilities;
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;
/** the LodBuilder's GLFW window */
public final long lodBuilderGlContext;
/** the LodBuilder's GL context */
public final GLCapabilities lodBuilderGlCapabilities;
/**
* Does this computer's GPU support fancy fog?
* This is just used for debugging, hopefully 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;
@@ -57,21 +79,24 @@ public class GlProxy
// create the builder context //
//============================//
minecraftGlContext = WGL.wglGetCurrentContext();
// get Minecraft's context
minecraftGlContext = GLFW.glfwGetCurrentContext();
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() + "]");
// create the LodBuilder's context
// Hopefully this shouldn't cause any issues with other mods that need custom contexts
// (although the number that do should be relatively few)
GLFW.glfwWindowHint(GLFW.GLFW_VISIBLE, GLFW.GLFW_FALSE);
// create an invisible window to hold the context
lodBuilderGlContext = GLFW.glfwCreateWindow(640, 480, "LOD window", 0L, minecraftGlContext);
GLFW.glfwMakeContextCurrent(lodBuilderGlContext);
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);
GLFW.glfwMakeContextCurrent(minecraftGlContext);
GL.setCapabilities(minecraftGlCapabilities);
@@ -82,20 +107,17 @@ public class GlProxy
// 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
* A wrapper function to make switching contexts easier. <br>
* Does nothing if the calling thread is already using newContext.
*/
public void setGlContext(GlProxyContext newContext)
{
@@ -108,69 +130,61 @@ public class GlProxy
long contextPointer = 0L;
GLCapabilities newGlCapabilities = null;
switch(newContext)
// get the pointer(s) for this context
switch (newContext)
{
case LOD_BUILDER:
contextPointer = lodBuilderGlContext;
newGlCapabilities = lodBuilderGlCapabilities;
break;
case MINECRAFT:
contextPointer = minecraftGlContext;
newGlCapabilities = minecraftGlCapabilities;
break;
default: // default should never happen, it is just here to make the compiler happy
case NONE:
contextPointer = 0L; // equivalent to null
newGlCapabilities = null;
// 0L is equivalent to 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;
}
GLFW.glfwMakeContextCurrent(contextPointer);
GL.setCapabilities(newGlCapabilities);
// used for debugging
if (newContext == GlProxyContext.LOD_BUILDER)
lodBuilderOwnerThread = Thread.currentThread();
else if (newContext == GlProxyContext.NONE && currentContext == GlProxyContext.LOD_BUILDER)
lodBuilderOwnerThread = null;
}
/**
* Returns this thread's OpenGL context.
*/
/** Returns this thread's OpenGL context. */
public GlProxyContext getGlContext()
{
long currentContext = WGL.wglGetCurrentContext();
if(currentContext == lodBuilderGlContext)
{
long currentContext = GLFW.glfwGetCurrentContext();
if (currentContext == lodBuilderGlContext)
return GlProxyContext.LOD_BUILDER;
}
else if(currentContext == minecraftGlContext)
{
else if (currentContext == minecraftGlContext)
return GlProxyContext.MINECRAFT;
}
else
{
else if (currentContext == 0L)
return GlProxyContext.NONE;
}
else
// hopefully this shouldn't happen, but
// at least now we will be notified if it does happen
throw new IllegalStateException(Thread.currentThread().getName() + " has a unknown OpenGl context: [" + currentContext + "]. Minecraft context [" + minecraftGlContext + "], LodBuilder context [" + lodBuilderGlContext + "], no context [0].");
}
/** Minecraft, Alpha, Beta, None */
public enum GlProxyContext
{
MINECRAFT,
LOD_BUILDER,
/** used to un-bind threads */
NONE,
}
public static GlProxy getInstance()
{
@@ -178,11 +192,20 @@ public class GlProxy
{
if (instance == null)
instance = new GlProxy();
}catch (Exception e)
}
catch (Exception e)
{
e.printStackTrace();
}
return instance;
}
}
File diff suppressed because it is too large Load Diff
@@ -1,5 +1,6 @@
/*
* This file is part of the LOD Mod, licensed under the GNU GPL v3 License.
* This file is part of the Distant Horizon mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020 James Seibel
*
@@ -15,12 +16,9 @@
* 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.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;
@@ -33,7 +31,7 @@ import net.minecraft.util.math.vector.Vector3d;
* to be used in the rendering process.
*
* @author James Seibel
* @version 8-21-2021
* @version 10-19-2021
*/
public class RenderUtil
{
@@ -42,15 +40,15 @@ public class RenderUtil
/**
* Returns if the given ChunkPos is in the loaded area of the world.
* @param centerCoordinate the center of the loaded world (probably the player's ChunkPos)
* @param center the center of the loaded world (probably the player's ChunkPos)
*/
public static boolean isChunkPosInLoadedArea(ChunkPos pos, ChunkPos center)
{
return (pos.x >= center.x - mc.getRenderDistance()
&& pos.x <= center.x + mc.getRenderDistance())
&&
(pos.z >= center.z - mc.getRenderDistance()
&& pos.z <= center.z + mc.getRenderDistance());
return (pos.x >= center.x - mc.getRenderDistance()
&& pos.x <= center.x + mc.getRenderDistance())
&&
(pos.z >= center.z - mc.getRenderDistance()
&& pos.z <= center.z + mc.getRenderDistance());
}
/**
@@ -59,11 +57,11 @@ public class RenderUtil
*/
public static boolean isCoordinateInLoadedArea(int x, int z, int centerCoordinate)
{
return (x >= centerCoordinate - mc.getRenderDistance()
&& x <= centerCoordinate + mc.getRenderDistance())
&&
(z >= centerCoordinate - mc.getRenderDistance()
&& z <= centerCoordinate + mc.getRenderDistance());
return (x >= centerCoordinate - mc.getRenderDistance()
&& x <= centerCoordinate + mc.getRenderDistance())
&&
(z >= centerCoordinate - mc.getRenderDistance()
&& z <= centerCoordinate + mc.getRenderDistance());
}
@@ -75,78 +73,51 @@ public class RenderUtil
{
int halfRadius = lodRadius / 2;
return (i >= lodRadius - halfRadius
&& i <= lodRadius + halfRadius)
&&
return (i >= lodRadius - halfRadius
&& i <= lodRadius + halfRadius)
&&
(j >= lodRadius - halfRadius
&& j <= lodRadius + halfRadius);
&& j <= lodRadius + halfRadius);
}
/**
* Get how much buffer memory would be required for the given radius multiplier
*
* issue #62
* TODO check if this is actually returning the correct memory needed
* it is possible (albeit unlikely) to have a buffer indexOutOfBounds exception
* which is caused by the buffer not being big enough.
*/
public static int getBufferMemoryForRegion(LodRegion region)
{
// calculate the max amount of buffer memory needed (in bytes)
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)
{
int maxNumberOfLods = LodRenderer.MAX_ALOCATEABLE_DIRECT_MEMORY / lodTemplate.getBufferMemoryForSingleLod();
int numbLodsWide = (int) Math.sqrt(maxNumberOfLods);
return numbLodsWide / (2 * mc.getRenderDistance());
}*/
/**
* Returns true if one of the region's 4 corners is in front
* of the camera.
*/
public static boolean isRegionInViewFrustum(BlockPos playerBlockPos, Vector3d cameraDir, BlockPos vboCenterPos)
{
// convert the vbo position into a direction vector
// starting from the player's position
// convert the vbo position into a direction vector
// starting from the player's position
Vector3d vboVec = new Vector3d(vboCenterPos.getX(), 0, vboCenterPos.getZ());
Vector3d playerVec = new Vector3d(playerBlockPos.getX(), playerBlockPos.getY(), playerBlockPos.getZ());
Vector3d vboCenterVec = vboVec.subtract(playerVec);
int halfRegionWidth = LodUtil.REGION_WIDTH / 2;
// calculate the 4 corners
Vector3d vboSeVec = new Vector3d(vboCenterVec.x + halfRegionWidth, vboCenterVec.y, vboCenterVec.z + halfRegionWidth);//.normalize();
Vector3d vboSwVec = new Vector3d(vboCenterVec.x - halfRegionWidth, vboCenterVec.y, vboCenterVec.z + halfRegionWidth);//.normalize();
Vector3d vboNwVec = new Vector3d(vboCenterVec.x - halfRegionWidth, vboCenterVec.y, vboCenterVec.z - halfRegionWidth);//.normalize();
Vector3d vboNeVec = new Vector3d(vboCenterVec.x + halfRegionWidth, vboCenterVec.y, vboCenterVec.z - halfRegionWidth);//.normalize();
// if any corner is visible, this region should be rendered
return isNormalizedVectorInViewFrustum(vboSeVec, cameraDir) ||
isNormalizedVectorInViewFrustum(vboSwVec, cameraDir) ||
isNormalizedVectorInViewFrustum(vboNwVec, cameraDir) ||
isNormalizedVectorInViewFrustum(vboNeVec, cameraDir);
int halfRegionWidth = LodUtil.REGION_WIDTH / 2;
// calculate the 4 corners
Vector3d vboSeVec = new Vector3d(vboCenterVec.x + halfRegionWidth, vboCenterVec.y, vboCenterVec.z + halfRegionWidth);
Vector3d vboSwVec = new Vector3d(vboCenterVec.x - halfRegionWidth, vboCenterVec.y, vboCenterVec.z + halfRegionWidth);
Vector3d vboNwVec = new Vector3d(vboCenterVec.x - halfRegionWidth, vboCenterVec.y, vboCenterVec.z - halfRegionWidth);
Vector3d vboNeVec = new Vector3d(vboCenterVec.x + halfRegionWidth, vboCenterVec.y, vboCenterVec.z - halfRegionWidth);
// if any corner is visible, this region should be rendered
return isNormalizedVectorInViewFrustum(vboSeVec, cameraDir) ||
isNormalizedVectorInViewFrustum(vboSwVec, cameraDir) ||
isNormalizedVectorInViewFrustum(vboNwVec, cameraDir) ||
isNormalizedVectorInViewFrustum(vboNeVec, cameraDir);
}
/**
* Currently takes the dot product of the two vectors,
* but in the future could do more complicated frustum culling tests.
*/
private static boolean isNormalizedVectorInViewFrustum(Vector3d objectVector, Vector3d cameraDir)
private static boolean isNormalizedVectorInViewFrustum(Vector3d objectVector, Vector3d cameraDir)
{
// the -0.1 is to offer a slight buffer so we are
// more likely to render LODs and thus, hopefully prevent
// flickering or odd disappearences
return objectVector.dot(cameraDir) > -0.1;
// the -0.1 is to offer a slight buffer, so we are
// more likely to render LODs and thus, hopefully prevent
// flickering or odd disappearances
return objectVector.dot(cameraDir) > -0.1;
}
}
@@ -1,64 +1,94 @@
/*
* This file is part of the Distant Horizon mod (formerly 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.util;
import java.awt.*;
import java.awt.Color;
import net.minecraft.client.renderer.texture.NativeImage;
/**
*
* @author Cola
* @author Leonardo Amato
* @version ??
*/
public class ColorUtil
{
public static int rgbToInt(int red, int green, int blue)
{
return (0xFF << 24) | (red << 16) | (green << 8) | blue;
}
public static int rgbToInt(int alpha, int red, int green, int blue)
{
return (alpha << 24) | (red << 16) | (green << 8) | blue;
}
public static int getAlpha(int color)
{
return (color >> 24) & 0xFF;
}
public static int getRed(int color)
{
return (color >> 16) & 0xFF;
}
public static int getGreen(int color)
{
return (color >> 8) & 0xFF;
}
public static int getBlue(int color)
{
return color & 0xFF;
}
public static int applyShade(int color, int shade)
{
if (shade < 0)
{
return (getAlpha(color) << 24) | (Math.max(getRed(color) + shade, 0) << 16) | (Math.max(getGreen(color) + shade, 0) << 8) | Math.max(getBlue(color) + shade, 0);
} else
{
else
return (getAlpha(color) << 24) | (Math.min(getRed(color) + shade, 255) << 16) | (Math.min(getGreen(color) + shade, 255) << 8) | Math.min(getBlue(color) + shade, 255);
}
}
public static int applyShade(int color, float shade)
{
if (shade < 1)
{
return (getAlpha(color) << 24) | ((int) Math.max(getRed(color) * shade, 0) << 16) | ((int) Math.max(getGreen(color) * shade, 0) << 8) | (int) Math.max(getBlue(color) * shade, 0);
} else
{
else
return (getAlpha(color) << 24) | ((int) Math.min(getRed(color) * shade, 255) << 16) | ((int) Math.min(getGreen(color) * shade, 255) << 8) | (int) Math.min(getBlue(color) * shade, 255);
}
}
/**
* Edit the given color as a HSV (Hue Saturation Value) color.
*/
/** This method apply the lightmap to the color to use */
public static int applyLightValue(int color, int skyLight, int blockLight, NativeImage lightMap)
{
int lightColor = lightMap.getPixelRGBA(blockLight, skyLight);
int red = ColorUtil.getBlue(lightColor);
int green = ColorUtil.getGreen(lightColor);
int blue = ColorUtil.getRed(lightColor);
return ColorUtil.multiplyRGBcolors(color, ColorUtil.rgbToInt(red, green, blue));
}
/** Edit the given color as an HSV (Hue Saturation Value) color */
public static int applySaturationAndBrightnessMultipliers(int color, float saturationMultiplier, float brightnessMultiplier)
{
float[] hsv = Color.RGBtoHSB(getRed(color), getGreen(color), getBlue(color), null);
@@ -67,48 +97,19 @@ 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();
}
/** Multiply 2 RGB colors */
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);
return ((getAlpha(color1) * getAlpha(color2) / 255) << 24) | ((getRed(color1) * getRed(color2) / 255) << 16) | ((getGreen(color1) * getGreen(color2) / 255) << 8) | (getBlue(color1) * getBlue(color2) / 255);
}
@SuppressWarnings("unused")
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();
return Integer.toHexString(getAlpha(color)) + " " +
Integer.toHexString(getRed(color)) + " " +
Integer.toHexString(getGreen(color)) + " " +
Integer.toHexString(getBlue(color));
}
}
@@ -1,9 +1,35 @@
/*
* This file is part of the Distant Horizon mod (formerly 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.util;
import static com.seibel.lod.builders.bufferBuilding.LodBufferBuilder.skyLightPlayer;
import com.seibel.lod.enums.DistanceGenerationMode;
import net.minecraft.client.renderer.texture.NativeImage;
/**
*
* @author Leonardo Amato
* @version ??
*/
public class DataPointUtil
{
/*
@@ -27,7 +53,7 @@ public class DataPointUtil
*/
// Reminder: bytes have range of [-128, 127].
// When converting to or from a int a 128 should be added or removed.
// When converting to or from an int a 128 should be added or removed.
// If there is a bug with color then it's probably caused by this.
//To be used in the future for negative value
@@ -56,7 +82,7 @@ public class DataPointUtil
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 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;
@@ -65,14 +91,14 @@ public class DataPointUtil
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 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 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;
@@ -87,20 +113,20 @@ public class DataPointUtil
return dataPoint;
}
public static long createDataPoint(int height, int depth, int color, int lightSky, int lightBlock, int generationMode)
public static long createDataPoint(int height, int depth, int color, int lightSky, int lightBlock, int generationMode, boolean flag)
{
return createDataPoint(
ColorUtil.getAlpha(color),
ColorUtil.getRed(color),
ColorUtil.getGreen(color),
ColorUtil.getBlue(color),
height, depth, lightSky, lightBlock, generationMode);
height, depth, lightSky, lightBlock, generationMode, flag);
}
public static long createDataPoint(int alpha, int red, int green, int blue, int height, int depth, int lightSky, int lightBlock, int generationMode)
public static long createDataPoint(int alpha, int red, int green, int blue, int height, int depth, int lightSky, int lightBlock, int generationMode, boolean flag)
{
long dataPoint = 0;
dataPoint += ((alpha >>> ALPHA_DOWNSIZE_SHIFT) & ALPHA_MASK) << ALPHA_SHIFT;
dataPoint += (long) (alpha >>> ALPHA_DOWNSIZE_SHIFT) << ALPHA_SHIFT;
dataPoint += (red & RED_MASK) << RED_SHIFT;
dataPoint += (green & GREEN_MASK) << GREEN_SHIFT;
dataPoint += (blue & BLUE_MASK) << BLUE_SHIFT;
@@ -109,7 +135,9 @@ public class DataPointUtil
dataPoint += (lightBlock & BLOCK_LIGHT_MASK) << BLOCK_LIGHT_SHIFT;
dataPoint += (lightSky & SKY_LIGHT_MASK) << SKY_LIGHT_SHIFT;
dataPoint += (generationMode & GEN_TYPE_MASK) << GEN_TYPE_SHIFT;
if (flag) dataPoint += FLAG_MASK << FLAG_SHIFT;
dataPoint += EXISTENCE_MASK << EXISTENCE_SHIFT;
return dataPoint;
}
@@ -120,13 +148,12 @@ public class DataPointUtil
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);
return (short) ((((dataPoint >>> ALPHA_SHIFT) & ALPHA_MASK) << ALPHA_DOWNSIZE_SHIFT) | 0b1111);
}
public static short getRed(long dataPoint)
@@ -149,11 +176,24 @@ public class DataPointUtil
return (int) ((dataPoint >>> SKY_LIGHT_SHIFT) & SKY_LIGHT_MASK);
}
public static int getLightSkyAlt(long dataPoint)
{
if (skyLightPlayer == 0 && ((dataPoint >>> FLAG_SHIFT) & FLAG_MASK) == 1)
return 0;
else
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 boolean getFlag(long dataPoint)
{
return ((dataPoint >>> FLAG_SHIFT) & FLAG_MASK) == 1;
}
public static byte getGenerationMode(long dataPoint)
{
return (byte) ((dataPoint >>> GEN_TYPE_SHIFT) & GEN_TYPE_MASK);
@@ -172,11 +212,10 @@ public class DataPointUtil
public static int getColor(long dataPoint)
{
//int color = getBlue(dataPoint) << BLUE_COLOR_SHIFT;
//color += getRed(dataPoint) << BLUE_COLOR_SHIFT;
return (int) (dataPoint >>> COLOR_SHIFT);
return (int) (((dataPoint >>> COLOR_SHIFT) & COLOR_MASK) | (/*((dataPoint >>> (ALPHA_SHIFT - ALPHA_DOWNSIZE_SHIFT)) | 0b1111)*/255 << 24));
}
/** This method apply the lightmap to the color to use */
public static int getLightColor(long dataPoint, NativeImage lightMap)
{
int lightBlock = getLightBlock(dataPoint);
@@ -189,119 +228,81 @@ public class DataPointUtil
return ColorUtil.multiplyRGBcolors(getColor(dataPoint), ColorUtil.rgbToInt(red, green, blue));
}
/** This is used to convert a dataPoint to string (useful for the print function) */
@SuppressWarnings("unused")
public static String toString(long dataPoint)
{
StringBuilder s = new StringBuilder();
s.append(getHeight(dataPoint));
s.append(" ");
s.append(getDepth(dataPoint));
s.append(" ");
s.append(getAlpha(dataPoint));
s.append(" ");
s.append(getRed(dataPoint));
s.append(" ");
s.append(getBlue(dataPoint));
s.append(" ");
s.append(getGreen(dataPoint));
s.append(" ");
s.append(getLightBlock(dataPoint));
s.append(" ");
s.append(getLightSky(dataPoint));
s.append(" ");
s.append(getGenerationMode(dataPoint));
s.append(" ");
s.append(isVoid(dataPoint));
s.append(" ");
s.append(doesItExist(dataPoint));
s.append('\n');
return s.toString();
return getHeight(dataPoint) + " " +
getDepth(dataPoint) + " " +
getAlpha(dataPoint) + " " +
getRed(dataPoint) + " " +
getBlue(dataPoint) + " " +
getGreen(dataPoint) + " " +
getLightBlock(dataPoint) + " " +
getLightSky(dataPoint) + " " +
getGenerationMode(dataPoint) + " " +
isVoid(dataPoint) + " " +
doesItExist(dataPoint) + '\n';
}
public static long mergeSingleData(long[] dataToMerge)
public static void shrinkArray(short[] array, int packetSize, int start, int length, int arraySize)
{
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)
start *= packetSize;
length *= packetSize;
arraySize *= packetSize;
for (int i = 0; i < arraySize - start; i++)
{
if (DataPointUtil.doesItExist(data))
{
allEmpty = false;
if (!(DataPointUtil.isVoid(data)))
{
numberOfChildren++;
allVoid = false;
tempAlpha += DataPointUtil.getAlpha(data);
tempRed += DataPointUtil.getRed(data);
tempGreen += DataPointUtil.getGreen(data);
tempBlue += DataPointUtil.getBlue(data);
tempHeight = Math.max(tempHeight, DataPointUtil.getHeight(data));
tempDepth = Math.min(tempDepth, DataPointUtil.getDepth(data));
tempLightBlock += DataPointUtil.getLightBlock(data);
tempLightSky += DataPointUtil.getLightSky(data);
}
tempGenMode = (byte) Math.min(tempGenMode, DataPointUtil.getGenerationMode(data));
} else
{
tempGenMode = (byte) Math.min(tempGenMode, DistanceGenerationMode.NONE.complexity);
}
}
if (allEmpty)
{
//no child has been initialized
return DataPointUtil.EMPTY_DATA;
} else if (allVoid)
{
//all the children are void
return DataPointUtil.createVoidDataPoint(tempGenMode);
} else
{
//we have at least 1 child
tempAlpha = tempAlpha / numberOfChildren;
tempRed = tempRed / numberOfChildren;
tempGreen = tempGreen / numberOfChildren;
tempBlue = tempBlue / numberOfChildren;
tempLightBlock = tempLightBlock / numberOfChildren;
tempLightSky = tempLightSky / numberOfChildren;
return DataPointUtil.createDataPoint(tempAlpha, tempRed, tempGreen, tempBlue, tempHeight, tempDepth, tempLightSky, tempLightBlock, tempGenMode);
array[start + i] = array[start + length + i];
//remove comment to not leave garbage at the end
//array[start + packetSize + i] = 0;
}
}
public static void extendArray(short[] array, int packetSize, int start, int length, int arraySize)
{
start *= packetSize;
length *= packetSize;
arraySize *= packetSize;
for (int i = arraySize - start - 1; i >= 0; i--)
{
array[start + length + i] = array[start + i];
array[start + i] = 0;
}
}
/**
* This method merge column of multiple data together
* @param dataToMerge one or more columns of data
* @param inputVerticalData vertical size of an input data
* @param maxVerticalData max vertical size of the merged data
* @return one column of correctly parsed data
*/
public static long[] mergeMultiData(long[] dataToMerge, int inputVerticalData, int maxVerticalData)
{
int size = dataToMerge.length / inputVerticalData;
// We initialize the arrays that are going to be used
short[] projection = ThreadMapUtil.getFreshProjectionArray((worldHeight) / 16 + 1);
short[] heightAndDepth = ThreadMapUtil.getFreshHeightAndDepth((worldHeight + 1) * 2);
long[] singleDataToMerge = ThreadMapUtil.getFreshSingleAddDataToMerge(size);
long[] dataPoint = ThreadMapUtil.getFreshVerticalDataArray(worldHeight + 1);
short[] heightAndDepth = ThreadMapUtil.getHeightAndDepth((worldHeight + 1) * 2);
long[] dataPoint = ThreadMapUtil.getVerticalDataArray(DetailDistanceUtil.getMaxVerticalData(0));
int genMode = DistanceGenerationMode.SERVER.complexity;
boolean allEmpty = true;
boolean allVoid = true;
boolean allDefault;
long singleData;
short depth;
short height;
int count = 0;
int i;
int ii;
int dataIndex;
//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++)
for (dataIndex = 0; dataIndex < inputVerticalData; dataIndex++)
{
singleData = dataToMerge[index * inputVerticalData + dataIndex];
if (doesItExist(singleData))
@@ -313,59 +314,133 @@ public class DataPointUtil
allVoid = false;
depth = getDepth(singleData);
height = getHeight(singleData);
for (int y = depth; y <= height; y++)
projection[y / 16] |= 1 << (y & 0xf);
int botPos = -1;
int topPos = -1;
//values fall in between and possibly require extension of array
boolean botExtend = false;
boolean topExtend = false;
for (i = 0; i < count; i++)
{
if (depth <= heightAndDepth[i * 2] && depth >= heightAndDepth[i * 2 + 1])
{
botPos = i;
break;
}
else if (depth < heightAndDepth[i * 2 + 1] && ((i + 1 < count && depth > heightAndDepth[(i + 1) * 2]) || i + 1 == count))
{
botPos = i;
botExtend = true;
break;
}
}
for (i = 0; i < count; i++)
{
if (height <= heightAndDepth[i * 2] && height >= heightAndDepth[i * 2 + 1])
{
topPos = i;
break;
}
else if (height < heightAndDepth[i * 2 + 1] && ((i + 1 < count && height > heightAndDepth[(i + 1) * 2]) || i + 1 == count))
{
topPos = i;
topExtend = true;
break;
}
}
if (topPos == -1)
{
if (botPos == -1)
{
//whole block falls above
extendArray(heightAndDepth, 2, 0, 1, count);
heightAndDepth[0] = height;
heightAndDepth[1] = depth;
count++;
}
else if (!botExtend)
{
//only top falls above extending it there, while bottom is inside existing
shrinkArray(heightAndDepth, 2, 0, botPos, count);
heightAndDepth[0] = height;
count -= botPos;
}
else
{
//top falls between some blocks, extending those as well
shrinkArray(heightAndDepth, 2, 0, botPos, count);
heightAndDepth[0] = height;
heightAndDepth[1] = depth;
count -= botPos;
}
}
else if (!topExtend)
{
if (!botExtend)
//both top and bottom are within some exiting blocks, possibly merging them
heightAndDepth[topPos * 2 + 1] = heightAndDepth[botPos * 2 + 1];
else
//top falls between some blocks, extending it there
heightAndDepth[topPos * 2 + 1] = depth;
shrinkArray(heightAndDepth, 2, topPos + 1, botPos - topPos, count);
count -= botPos - topPos;
}
else
{
if (!botExtend)
{
//only top is within some exiting block, extending it
topPos++; //to make it easier
heightAndDepth[topPos * 2] = height;
heightAndDepth[topPos * 2 + 1] = heightAndDepth[botPos * 2 + 1];
shrinkArray(heightAndDepth, 2, topPos + 1, botPos - topPos, count);
count -= botPos - topPos;
}
else
{
//both top and bottom are outside existing blocks
shrinkArray(heightAndDepth, 2, topPos + 1, botPos - topPos, count);
count -= botPos - topPos;
extendArray(heightAndDepth, 2, topPos + 1, 1, count);
count++;
heightAndDepth[topPos * 2 + 2] = height;
heightAndDepth[topPos * 2 + 3] = depth;
}
}
}
}
else
break;
}
}
//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)
//we cut the 1 block gaps
/*int j;
for (i = 0; i < count - 1; i++)
{
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
if (heightAndDepth[i * 2 + 1] - heightAndDepth[(i + 1) * 2] == 1)
{
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[i * 2 + 1] = heightAndDepth[(i + 1) * 2 + 1];
for (j = i + 1; j < count - 1; j++)
{
heightAndDepth[count * 2] = depth;
heightAndDepth[count * 2 + 1] = (short) (worldHeight - 1);
break;
heightAndDepth[j * 2] = heightAndDepth[(j + 1) * 2];
heightAndDepth[j * 2 + 1] = heightAndDepth[(j + 1) * 2 + 1];
}
while ((((projection[i] >>> ii) & 1) == 1)) ii++;
count--;
i--;
}
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;
@@ -374,9 +449,9 @@ public class DataPointUtil
ii = worldHeight;
for (i = 0; i < count - 1; i++)
{
if (heightAndDepth[(i + 1) * 2] - heightAndDepth[i * 2 + 1] < ii)
if (heightAndDepth[i * 2 + 1] - heightAndDepth[(i + 1) * 2] < ii)
{
ii = heightAndDepth[(i + 1) * 2] - heightAndDepth[i * 2 + 1];
ii = heightAndDepth[i * 2 + 1] - heightAndDepth[(i + 1) * 2];
j = i;
}
}
@@ -386,47 +461,94 @@ public class DataPointUtil
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);
//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++)
for (j = count - 1; j >= 0; j--)
{
depth = heightAndDepth[j * 2];
height = heightAndDepth[j * 2 + 1];
height = heightAndDepth[j * 2];
depth = heightAndDepth[j * 2 + 1];
if ((depth == 0 && height == 0) || j >= heightAndDepth.length / 2)
break;
for (int k = 0; k < size; k++)
{
singleDataToMerge[k] = 0;
}
int numberOfChildren = 0;
int tempAlpha = 0;
int tempRed = 0;
int tempGreen = 0;
int tempBlue = 0;
int tempLightBlock = 0;
int tempLightSky = 0;
byte tempGenMode = DistanceGenerationMode.SERVER.complexity;
allEmpty = true;
allVoid = true;
allDefault = true;
long data = 0;
for (int index = 0; index < size; index++)
{
for (int dataIndex = 0; dataIndex < inputVerticalData; dataIndex++)
for (dataIndex = 0; dataIndex < inputVerticalData; dataIndex++)
{
singleData = dataToMerge[index * inputVerticalData + dataIndex];
if (doesItExist(singleData) && !isVoid(singleData))
{
if ((depth <= getDepth(singleData) && getDepth(singleData) <= height)
|| (depth <= getHeight(singleData) && getHeight(singleData) <= height))
{
if (getHeight(singleData) > getHeight(singleDataToMerge[index]))
{
singleDataToMerge[index] = singleData;
}
if (getHeight(singleData) > getHeight(data))
data = singleData;
}
}
else
break;
}
if (!doesItExist(data))
{
singleData = dataToMerge[index * inputVerticalData];
data = createVoidDataPoint(getGenerationMode(singleData));
}
if (doesItExist(data))
{
allEmpty = false;
if (!isVoid(data))
{
numberOfChildren++;
allVoid = false;
tempAlpha += getAlpha(data);
tempRed += getRed(data);
tempGreen += getGreen(data);
tempBlue += getBlue(data);
tempLightBlock += getLightBlock(data);
tempLightSky += getLightSky(data);
if (!getFlag(data)) allDefault = false;
}
tempGenMode = (byte) Math.min(tempGenMode, getGenerationMode(data));
}
else
tempGenMode = (byte) Math.min(tempGenMode, DistanceGenerationMode.NONE.complexity);
}
long data = mergeSingleData(singleDataToMerge);
dataPoint[count - j - 1] = createDataPoint(height, depth, getColor(data), getLightSky(data), getLightBlock(data), getGenerationMode(data));
if (allEmpty)
//no child has been initialized
dataPoint[j] = EMPTY_DATA;
else if (allVoid)
//all the children are void
dataPoint[j] = 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;
dataPoint[j] = createDataPoint(tempAlpha, tempRed, tempGreen, tempBlue, height, depth, tempLightSky, tempLightBlock, tempGenMode, allDefault);
}
}
return dataPoint;
}
public static long[] compress(long[] data, byte detailLevel)
{
return null;
}
}
@@ -1,36 +1,49 @@
/*
* This file is part of the Distant Horizon mod (formerly 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.util;
import com.seibel.lod.config.LodConfig;
import com.seibel.lod.enums.DistanceGenerationMode;
import com.seibel.lod.enums.HorizontalQuality;
import com.seibel.lod.enums.HorizontalResolution;
import com.seibel.lod.enums.VerticalQuality;
import com.seibel.lod.wrappers.MinecraftWrapper;
/**
*
* @author Leonardo Amato
* @version ??
*/
public class DetailDistanceUtil
{
private static double genMultiplier = 1.0;
private static double treeGenMultiplier = 1.0;
private static double treeCutMultiplier = 1.0;
private static int minGenDetail = LodConfig.CLIENT.worldGenerator.generationResolution.get().detailLevel;
private static int minDrawDetail = Math.max(LodConfig.CLIENT.graphics.drawResolution.get().detailLevel,LodConfig.CLIENT.worldGenerator.generationResolution.get().detailLevel);
private static int maxDetail = LodUtil.REGION_DETAIL_LEVEL + 1;
private static int minDistance = 0;
private static int maxDistance = LodConfig.CLIENT.graphics.lodChunkRenderDistance.get() * 16 * 2;
private static int[] maxVerticalData = {
4,
4,
4,
2,
2,
1,
1,
1,
1,
1,
1};
private static HorizontalResolution[] lodGenDetails = {
private static final double genMultiplier = 1.0;
private static final double treeGenMultiplier = 1.0;
private static final double treeCutMultiplier = 1.0;
private static int minGenDetail = LodConfig.CLIENT.graphics.qualityOption.drawResolution.get().detailLevel;
private static int minDrawDetail = Math.max(LodConfig.CLIENT.graphics.qualityOption.drawResolution.get().detailLevel, LodConfig.CLIENT.graphics.qualityOption.drawResolution.get().detailLevel);
private static final int maxDetail = LodUtil.REGION_DETAIL_LEVEL + 1;
private static final int minDistance = 0;
private static int minDetailDistance = (int) (MinecraftWrapper.INSTANCE.getRenderDistance()*16 * 1.42f);
private static int maxDistance = LodConfig.CLIENT.graphics.qualityOption.lodChunkRenderDistance.get() * 16 * 2;
private static final HorizontalResolution[] lodGenDetails = {
HorizontalResolution.BLOCK,
HorizontalResolution.TWO_BLOCKS,
HorizontalResolution.FOUR_BLOCKS,
@@ -41,133 +54,137 @@ public class DetailDistanceUtil
HorizontalResolution.CHUNK,
HorizontalResolution.CHUNK,
HorizontalResolution.CHUNK,
HorizontalResolution.CHUNK};
public static void updateSettings(){
minGenDetail = LodConfig.CLIENT.worldGenerator.generationResolution.get().detailLevel;
minDrawDetail = Math.max(LodConfig.CLIENT.graphics.drawResolution.get().detailLevel,LodConfig.CLIENT.worldGenerator.generationResolution.get().detailLevel);
maxDistance = LodConfig.CLIENT.graphics.lodChunkRenderDistance.get() * 16 * 8;
HorizontalResolution.CHUNK };
public static void updateSettings()
{
minDetailDistance = (int) (MinecraftWrapper.INSTANCE.getRenderDistance()*16 * 1.42f);
minGenDetail = LodConfig.CLIENT.graphics.qualityOption.drawResolution.get().detailLevel;
minDrawDetail = Math.max(LodConfig.CLIENT.graphics.qualityOption.drawResolution.get().detailLevel, LodConfig.CLIENT.graphics.qualityOption.drawResolution.get().detailLevel);
maxDistance = LodConfig.CLIENT.graphics.qualityOption.lodChunkRenderDistance.get() * 16 * 8;
}
public static int baseDistanceFunction(int detail)
{
if (detail <= minGenDetail)
return minDistance;
if (detail >= maxDetail)
return maxDistance;
int distanceUnit = LodConfig.CLIENT.worldGenerator.horizontalScale.get().distanceUnit;
switch (LodConfig.CLIENT.worldGenerator.horizontalQuality.get()){
case LINEAR:;
return (detail * distanceUnit);
default:
double base = LodConfig.CLIENT.worldGenerator.horizontalQuality.get().quadraticBase;
return (int) (Math.pow(base, detail) * distanceUnit);
int distanceUnit = LodConfig.CLIENT.graphics.qualityOption.horizontalScale.get().distanceUnit;
if (LodConfig.CLIENT.graphics.qualityOption.horizontalQuality.get() == HorizontalQuality.LOWEST)
return (detail * distanceUnit);
else
{
double base = LodConfig.CLIENT.graphics.qualityOption.horizontalQuality.get().quadraticBase;
return (int) (Math.pow(base, detail) * distanceUnit);
}
}
public static int getDrawDistanceFromDetail(int detail)
{
return baseDistanceFunction(detail);
}
public static byte baseInverseFunction(int distance, int minDetail)
public static byte baseInverseFunction(int distance, int minDetail, boolean useRenderMinDistance)
{
int detail = 0;
int detail;
if (distance == 0)
return (byte) minDetail;
int distanceUnit = LodConfig.CLIENT.worldGenerator.horizontalScale.get().distanceUnit;
switch (LodConfig.CLIENT.worldGenerator.horizontalQuality.get()){
case LINEAR:
detail = (byte) Math.floorDiv(distance, distanceUnit);
break;
default:
double base = LodConfig.CLIENT.worldGenerator.horizontalQuality.get().quadraticBase;
double logBase = Math.log(base);
detail = (byte) (Math.log(Math.floorDiv(distance, distanceUnit))/logBase);
break;
if (distance < minDetailDistance && useRenderMinDistance)
return (byte) minDetail;
int distanceUnit = LodConfig.CLIENT.graphics.qualityOption.horizontalScale.get().distanceUnit;
if (LodConfig.CLIENT.graphics.qualityOption.horizontalQuality.get() == HorizontalQuality.LOWEST)
detail = (byte) Math.floorDiv(distance, distanceUnit);
else
{
double base = LodConfig.CLIENT.graphics.qualityOption.horizontalQuality.get().quadraticBase;
double logBase = Math.log(base);
detail = (byte) (Math.log(Math.floorDiv(distance, distanceUnit)) / logBase);
}
return (byte) LodUtil.clamp(minDetail, detail, maxDetail-1);
return (byte) LodUtil.clamp(minDetail, detail, maxDetail - 1);
}
public static byte getDrawDetailFromDistance(int distance)
{
return baseInverseFunction(distance, minDrawDetail);
return baseInverseFunction(distance, minDrawDetail, false);
}
public static byte getGenerationDetailFromDistance(int distance)
{
return baseInverseFunction((int) (distance * genMultiplier), minGenDetail);
return baseInverseFunction((int) (distance * genMultiplier), minGenDetail, true);
}
public static byte getTreeCutDetailFromDistance(int distance)
{
return baseInverseFunction((int) (distance * treeCutMultiplier), minGenDetail);
return baseInverseFunction((int) (distance * treeCutMultiplier), minGenDetail, true);
}
public static byte getTreeGenDetailFromDistance(int distance)
{
return baseInverseFunction((int) (distance * treeGenMultiplier), minGenDetail);
return baseInverseFunction((int) (distance * treeGenMultiplier), minGenDetail, true);
}
public static DistanceGenerationMode getDistanceGenerationMode(int detail)
{
return LodConfig.CLIENT.worldGenerator.distanceGenerationMode.get();
}
public static byte getLodDrawDetail(int detail)
{
if (detail < minDrawDetail)
{
if(LodConfig.CLIENT.graphics.alwaysDrawAtMaxQuality.get())
if (LodConfig.CLIENT.graphics.advancedGraphicsOption.alwaysDrawAtMaxQuality.get())
return getLodGenDetail(minDrawDetail).detailLevel;
else
return (byte) minDrawDetail;
} else
}
else
{
if(LodConfig.CLIENT.graphics.alwaysDrawAtMaxQuality.get())
if (LodConfig.CLIENT.graphics.advancedGraphicsOption.alwaysDrawAtMaxQuality.get())
return getLodGenDetail(detail).detailLevel;
else
return (byte) detail;
}
}
public static HorizontalResolution getLodGenDetail(int detail)
{
if (detail < minGenDetail)
{
return lodGenDetails[minGenDetail];
} else
}
else
{
return lodGenDetails[detail];
}
}
public static byte getCutLodDetail(int detail)
{
if (detail < minGenDetail)
{
return lodGenDetails[minGenDetail].detailLevel;
} else if (detail == maxDetail)
}
else if (detail == maxDetail)
{
return LodUtil.REGION_DETAIL_LEVEL;
} else
}
else
{
return lodGenDetails[detail].detailLevel;
}
}
public static int getMaxVerticalData(int detail)
{
if(LodConfig.CLIENT.worldGenerator.lodQualityMode.get() == VerticalQuality.HEIGHTMAP)
return 1;
return maxVerticalData[LodUtil.clamp(minGenDetail, detail, LodUtil.REGION_DETAIL_LEVEL)];
return LodConfig.CLIENT.graphics.qualityOption.verticalQuality.get().maxVerticalData[LodUtil.clamp(minGenDetail, detail, LodUtil.REGION_DETAIL_LEVEL)];
}
}
@@ -1,14 +1,36 @@
/*
* This file is part of the Distant Horizon mod (formerly 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.util;
import com.seibel.lod.config.LodConfig;
/**
*
* @author Leonardo Amato
* @version ??
*/
public class LevelPosUtil
{
public static int[] convert(int[] levelPos, byte newDetailLevel)
{
return convert(getDetailLevel(levelPos), getPosX(levelPos), getPosZ(levelPos), newDetailLevel);
}
public static int[] convert(byte detailLevel, int posX, int posZ, byte newDetailLevel)
{
int width;
@@ -19,7 +41,8 @@ public class LevelPosUtil
newDetailLevel,
Math.floorDiv(posX, width),
Math.floorDiv(posZ, width));
} else
}
else
{
width = 1 << (detailLevel - newDetailLevel);
return createLevelPos(
@@ -28,12 +51,12 @@ public class LevelPosUtil
posZ * width);
}
}
public static int[] createLevelPos(byte detailLevel, int posX, int posZ)
{
return new int[]{detailLevel, posX, posZ};
return new int[] { detailLevel, posX, posZ };
}
public static int convert(byte detailLevel, int pos, byte newDetailLevel)
{
int width;
@@ -41,48 +64,49 @@ public class LevelPosUtil
{
width = 1 << (newDetailLevel - detailLevel);
return Math.floorDiv(pos, width);
} else
}
else
{
width = 1 << (detailLevel - newDetailLevel);
return pos * width;
}
}
public static int getRegion(byte detailLevel, int pos)
{
return Math.floorDiv(pos, 1 << (LodUtil.REGION_DETAIL_LEVEL - detailLevel));
}
public static int getRegionModule(byte detailLevel, int pos)
{
return Math.floorMod(pos, 1 << (LodUtil.REGION_DETAIL_LEVEL - detailLevel));
}
public static byte getDetailLevel(int[] levelPos)
{
return (byte) levelPos[0];
}
public static int getPosX(int[] levelPos)
{
return levelPos[1];
}
public static int getPosZ(int[] levelPos)
{
return levelPos[2];
}
public static int getDistance(int[] levelPos)
{
return levelPos[3];
}
public static int[] getRegionModule(int[] levelPos)
{
return getRegionModule(getDetailLevel(levelPos), getPosX(levelPos), getPosZ(levelPos));
}
public static int[] getRegionModule(byte detailLevel, int posX, int posZ)
{
int width = 1 << (LodUtil.REGION_DETAIL_LEVEL - detailLevel);
@@ -91,7 +115,7 @@ public class LevelPosUtil
Math.floorMod(posX, width),
Math.floorMod(posZ, width));
}
public static int[] applyOffset(int[] levelPos, int xOffset, int zOffset)
{
return createLevelPos(
@@ -99,7 +123,7 @@ public class LevelPosUtil
getPosX(levelPos) + xOffset,
getPosZ(levelPos) + zOffset);
}
public static int[] applyLevelOffset(int[] levelPos, byte detailOffset, int xOffset, int zOffset)
{
return createLevelPos(
@@ -107,42 +131,42 @@ public class LevelPosUtil
getPosX(levelPos) + xOffset * (1 << detailOffset),
getPosZ(levelPos) + zOffset * (1 << detailOffset));
}
public static int getRegionPosX(int[] levelPos)
{
int width = 1 << (LodUtil.REGION_DETAIL_LEVEL - getDetailLevel(levelPos));
return Math.floorDiv(getPosX(levelPos), width);
}
public static int getRegionPosZ(int[] levelPos)
{
int width = 1 << (LodUtil.REGION_DETAIL_LEVEL - getDetailLevel(levelPos));
return Math.floorDiv(getPosZ(levelPos), width);
}
public static int getChunkPos(byte detailLevel, int pos)
{
return convert(detailLevel,pos, LodUtil.CHUNK_DETAIL_LEVEL);
return convert(detailLevel, pos, LodUtil.CHUNK_DETAIL_LEVEL);
}
public static int maxDistance(byte detailLevel, int posX, int posZ, int playerPosX, int playerPosZ)
{
int width = 1 << detailLevel;
int startPosX = posX * width;
int startPosZ = posZ * width;
int endPosX = startPosX + width;
int endPosZ = startPosZ + width;
int maxDistance = (int) Math.sqrt(Math.pow(playerPosX - startPosX, 2) + Math.pow(playerPosZ - startPosZ, 2));
maxDistance = Math.max(maxDistance, (int) Math.sqrt(Math.pow(playerPosX - startPosX, 2) + Math.pow(playerPosZ - endPosZ, 2)));
maxDistance = Math.max(maxDistance, (int) Math.sqrt(Math.pow(playerPosX - endPosX, 2) + Math.pow(playerPosZ - startPosZ, 2)));
maxDistance = Math.max(maxDistance, (int) Math.sqrt(Math.pow(playerPosX - endPosX, 2) + Math.pow(playerPosZ - endPosZ, 2)));
return maxDistance;
}
public static int maxDistance(byte detailLevel, int posX, int posZ, int playerPosX, int playerPosZ, int xRegion, int zRegion)
{
int width = 1 << (LodUtil.REGION_DETAIL_LEVEL - detailLevel);
@@ -150,35 +174,36 @@ public class LevelPosUtil
int newPosZ = zRegion * width + posZ;
return maxDistance(detailLevel, newPosX, newPosZ, playerPosX, playerPosZ);
}
public static int minDistance(byte detailLevel, int posX, int posZ, int playerPosX, int playerPosZ)
{
int width = 1 << detailLevel;
int startPosX = posX * width;
int startPosZ = posZ * width;
int endPosX = startPosX + width;
int endPosZ = startPosZ + width;
boolean inXArea = playerPosX >= startPosX && playerPosX <= endPosX;
boolean inZArea = playerPosZ >= startPosZ && playerPosZ <= endPosZ;
if (inXArea && inZArea)
{
return 0;
} else if (inXArea)
else if (inXArea)
{
return Math.min(
Math.abs(playerPosZ - startPosZ),
Math.abs(playerPosZ - endPosZ)
);
} else if (inZArea)
}
else if (inZArea)
{
return Math.min(
Math.abs(playerPosX - startPosX),
Math.abs(playerPosX - endPosX)
);
} else
}
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)));
@@ -187,7 +212,7 @@ public class LevelPosUtil
return minDistance;
}
}
public static int minDistance(byte detailLevel, int posX, int posZ, int playerPosX, int playerPosZ, int xRegion, int zRegion)
{
int width = 1 << (LodUtil.REGION_DETAIL_LEVEL - detailLevel);
@@ -195,15 +220,15 @@ public class LevelPosUtil
int newPosZ = zRegion * width + posZ;
return minDistance(detailLevel, newPosX, newPosZ, playerPosX, playerPosZ);
}
public static int compareDistance(int firstDistance, int secondDistance)
{
return Integer.compare(
firstDistance,
secondDistance);
}
public static int compareLevelAndDistance(byte firstDetail, int firstDistance, byte secondDetail, int secondDistance)
{
int compareResult = Integer.compare(
@@ -217,11 +242,15 @@ public class LevelPosUtil
}
return compareResult;
}
@SuppressWarnings("unused")
public static String toString(int[] levelPos)
{
return (getDetailLevel(levelPos) + " " + getPosX(levelPos) + " " + getPosZ(levelPos));
return (getDetailLevel(levelPos) + " "
+ getPosX(levelPos) + " "
+ getPosZ(levelPos));
}
public static String toString(byte detailLevel, int posX, int posZ)
{
return (detailLevel + " " + posX + " " + posZ);
@@ -1,3 +1,22 @@
/*
* This file is part of the Distant Horizon mod (formerly 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.util;
import java.util.concurrent.ThreadFactory;
@@ -5,13 +24,12 @@ import java.util.concurrent.ThreadFactory;
/**
* Just a simple ThreadFactory to name ExecutorService
* threads, which can be helpful when debugging.
*
* @author James Seibel
* @version 8-15-2021
*/
public class LodThreadFactory implements ThreadFactory
{
public String threadName;
public final String threadName;
public LodThreadFactory(String newThreadName)
@@ -22,7 +40,7 @@ public class LodThreadFactory implements ThreadFactory
@Override
public Thread newThread(Runnable r)
{
return new Thread(r, threadName);
return new Thread(r, threadName);
}
}
+218 -182
View File
@@ -1,5 +1,6 @@
/*
* This file is part of the LOD Mod, licensed under the GNU GPL v3 License.
* This file is part of the Distant Horizon mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020 James Seibel
*
@@ -15,13 +16,17 @@
* 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.util;
import java.awt.Color;
import java.io.File;
import java.util.HashSet;
import com.seibel.lod.enums.LodTemplate;
import com.seibel.lod.builders.bufferBuilding.lodTemplates.Box;
import com.seibel.lod.config.LodConfig;
import com.seibel.lod.enums.HorizontalResolution;
import com.seibel.lod.enums.VanillaOverdraw;
import com.seibel.lod.objects.LodDimension;
import com.seibel.lod.objects.RegionPos;
import com.seibel.lod.wrappers.MinecraftWrapper;
@@ -29,7 +34,10 @@ import com.seibel.lod.wrappers.MinecraftWrapper;
import net.minecraft.client.multiplayer.ServerData;
import net.minecraft.client.renderer.WorldRenderer;
import net.minecraft.client.renderer.chunk.ChunkRenderDispatcher.CompiledChunk;
import net.minecraft.client.renderer.vertex.DefaultVertexFormats;
import net.minecraft.client.renderer.vertex.VertexFormat;
import net.minecraft.server.integrated.IntegratedServer;
import net.minecraft.util.Direction;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.ChunkPos;
import net.minecraft.world.DimensionType;
@@ -44,69 +52,89 @@ import net.minecraft.world.server.ServerWorld;
* This class holds methods and constants that may be used in multiple places.
*
* @author James Seibel
* @version 9-7-2021
* @version 10-20-2021
*/
public class LodUtil
{
private static MinecraftWrapper mc = MinecraftWrapper.INSTANCE;
private static final MinecraftWrapper mc = MinecraftWrapper.INSTANCE;
/**
* Vanilla render distances less than or equal to this will not allow partial
* overdraw. The VanillaOverdraw will either be ALWAYS or NEVER.
*/
public static final int MINIMUM_RENDER_DISTANCE_FOR_PARTIAL_OVERDRAW = 4;
/**
* Vanilla render distances less than or equal to this will cause the overdraw to
* run at a smaller fraction of the vanilla render distance.
*/
public static final int MINIMUM_RENDER_DISTANCE_FOR_FAR_OVERDRAW = 11;
/** alpha used when drawing chunks in debug mode */
/** The maximum number of LODs that can be rendered vertically */
public static final int MAX_NUMBER_OF_VERTICAL_LODS = 32;
/**
* alpha used when drawing chunks in debug mode
*/
public static final int DEBUG_ALPHA = 255; // 0 - 255
public static final Color COLOR_DEBUG_BLACK = new Color(0, 0, 0, DEBUG_ALPHA);
public static final Color COLOR_DEBUG_WHITE = new Color(255, 255, 255, DEBUG_ALPHA);
public static final Color COLOR_INVISIBLE = new Color(0,0,0,0);
public static final Color COLOR_INVISIBLE = new Color(0, 0, 0, 0);
public static final int CEILED_DIMENSION_MAX_RENDER_DISTANCE = 64; // 0 - 255
/** a gray-purple color */
public static final int MYCELIUM_COLOR_INT = LodUtil.colorToInt(Color.decode("#6E6166"));
/** TODO, add a better way to override material colors
* and/or add a method to generate colors based on texture
* issue #64 */
public static final int STONE_COLOR_INT = LodUtil.colorToInt(new Color(150, 150, 150));
public static final int NETHERRACK_COLOR_INT = LodUtil.colorToInt(new Color(95, 38, 38));
public static final int WARPED_NYLIUM_COLOR_INT = LodUtil.colorToInt(new Color(34, 94, 85));
public static final int CRIMSON_NYLIUM_COLOR_INT = LodUtil.colorToInt(new Color(126, 27, 27));
/**
* In order of nearest to farthest: <br>
* Red, Orange, Yellow, Green, Cyan, Blue, Magenta, white, gray, black
*/
public static final Color DEBUG_DETAIL_LEVEL_COLORS[] = new Color[] { Color.RED, Color.ORANGE, Color.YELLOW, Color.GREEN, Color.CYAN, Color.BLUE, Color.MAGENTA, Color.WHITE, Color.GRAY, Color.BLACK };
public static final Color[] DEBUG_DETAIL_LEVEL_COLORS = new Color[] { Color.RED, Color.ORANGE, Color.YELLOW, Color.GREEN, Color.CYAN, Color.BLUE, Color.MAGENTA, Color.WHITE, Color.GRAY, Color.BLACK };
public static final byte DETAIL_OPTIONS = 10;
/** 512 blocks wide */
public static final byte REGION_DETAIL_LEVEL = 9;
public static final byte REGION_DETAIL_LEVEL = DETAIL_OPTIONS - 1;
/** 16 blocks wide */
public static final byte CHUNK_DETAIL_LEVEL = 4;
/** 1 block wide */
public static final byte BLOCK_DETAIL_LEVEL = 0;
public static final byte DETAIL_OPTIONS = 10;
public static final short MAX_VERTICAL_DATA = 4;
/** measured in Blocks <br>
* detail level 9 */
public static final short REGION_WIDTH = 512;
/** measured in Blocks <br>
* detail level 4 */
/**
* measured in Blocks <br>
* detail level max - 1
*/
public static final short REGION_WIDTH = 1 << REGION_DETAIL_LEVEL;
/**
* measured in Blocks <br>
* detail level 4
*/
public static final short CHUNK_WIDTH = 16;
/** measured in Blocks <br>
* detail level 0 */
/**
* measured in Blocks <br>
* detail level 0
*/
public static final short BLOCK_WIDTH = 1;
/** number of chunks wide */
public static final int REGION_WIDTH_IN_CHUNKS = 32;
public static final int REGION_WIDTH_IN_CHUNKS = REGION_WIDTH / CHUNK_WIDTH;
/** If we ever need to use a heightmap for any reason, use this one. */
/**
* 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 = "[\\\\\\/:*?\\\"<>|]";
/**
* This regex finds any characters that are invalid for use in a windows
* (and by extension mac and linux) file path
*/
public static final String INVALID_FILE_CHARACTERS_REGEX = "[\\\\/:*?\"<>|]";
/**
* 64 MB by default is the maximum amount of memory that
@@ -119,24 +147,25 @@ public class LodUtil
* <p>
* https://stackoverflow.com/questions/50499238/bytebuffer-allocatedirect-and-xmx
*/
public static final int MAX_ALOCATEABLE_DIRECT_MEMORY = 64 * 1024 * 1024;
public static final int MAX_ALLOCATABLE_DIRECT_MEMORY = 64 * 1024 * 1024;
public static final VertexFormat LOD_VERTEX_FORMAT = DefaultVertexFormats.POSITION_COLOR;
/**
* Gets the first valid ServerWorld.
*
* @return null if there are no ServerWorlds
*/
public static ServerWorld getFirstValidServerWorld()
{
if (mc.hasSingleplayerServer())
if (mc.hasSinglePlayerServer())
return null;
Iterable<ServerWorld> worlds = mc.getSingleplayerServer().getAllLevels();
Iterable<ServerWorld> worlds = mc.getSinglePlayerServer().getAllLevels();
for (ServerWorld world : worlds)
return world;
@@ -146,12 +175,11 @@ public class LodUtil
/**
* Gets the ServerWorld for the relevant dimension.
*
* @return null if there is no ServerWorld for the given dimension
*/
public static ServerWorld getServerWorldFromDimension(DimensionType dimension)
{
IntegratedServer server = mc.getSingleplayerServer();
IntegratedServer server = mc.getSinglePlayerServer();
if (server == null)
return null;
@@ -160,7 +188,7 @@ public class LodUtil
for (ServerWorld world : worlds)
{
if(world.dimensionType() == dimension)
if (world.dimensionType() == dimension)
{
returnWorld = world;
break;
@@ -170,9 +198,7 @@ public class LodUtil
return returnWorld;
}
/**
* Convert a 2D absolute position into a quad tree relative position.
*/
/** Convert a 2D absolute position into a quad tree relative position. */
public static RegionPos convertGenericPosToRegionPos(int x, int z, int detailLevel)
{
int relativePosX = Math.floorDiv(x, (int) Math.pow(2, LodUtil.REGION_DETAIL_LEVEL - detailLevel));
@@ -181,15 +207,12 @@ public class LodUtil
return new RegionPos(relativePosX, relativePosZ);
}
/**
* Convert a 2D absolute position into a quad tree relative position.
*/
public static int convertLevelPos(int pos, int currectDetailLevel, int targetDetailLevel)
/** Convert a 2D absolute position into a quad tree relative position. */
public static int convertLevelPos(int pos, int currentDetailLevel, int targetDetailLevel)
{
int newPos = Math.floorDiv(pos, (int) Math.pow(2, targetDetailLevel - currectDetailLevel));
return newPos;
return Math.floorDiv(pos, (int) Math.pow(2, targetDetailLevel - currentDetailLevel));
}
/**
* Return whether the given chunk
* has any data in it.
@@ -198,9 +221,9 @@ public class LodUtil
{
ChunkSection[] blockStorage = chunk.getSections();
for(ChunkSection section : blockStorage)
for (ChunkSection section : blockStorage)
{
if(section != null && !section.isEmpty())
if (section != null && !section.isEmpty())
{
return true;
}
@@ -210,7 +233,6 @@ public class LodUtil
}
/**
* If on single player this will return the name of the user's
* world, if in multiplayer it will return the server name, IP,
@@ -218,7 +240,7 @@ public class LodUtil
*/
public static String getWorldID(IWorld world)
{
if(mc.hasSingleplayerServer())
if (mc.hasSinglePlayerServer())
{
// chop off the dimension ID as it is not needed/wanted
String dimId = getDimensionIDFromWorld(world);
@@ -236,10 +258,9 @@ 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
* world and the dimensional save folder, if in multiplayer
* it will return the server name, ip, game version, and dimension.<br>
* <br>
* This can be used to determine where to save files for a given
@@ -247,17 +268,17 @@ public class LodUtil
*/
public static String getDimensionIDFromWorld(IWorld world)
{
if(mc.hasSingleplayerServer())
if (mc.hasSinglePlayerServer())
{
// this will return the world save location
// and the dimension folder
ServerWorld serverWorld = LodUtil.getServerWorldFromDimension(world.dimensionType());
if(serverWorld == null)
if (serverWorld == null)
throw new NullPointerException("getDimensionIDFromWorld wasn't able to get the ServerWorld for the dimension " + world.dimensionType().effectsLocation().getPath());
ServerChunkProvider provider = serverWorld.getChunkSource();
if(provider == null)
if (provider == null)
throw new NullPointerException("getDimensionIDFromWorld wasn't able to get the ServerChunkProvider for the dimension " + world.dimensionType().effectsLocation().getPath());
return provider.dataStorage.dataFolder.toString();
@@ -268,40 +289,31 @@ public class LodUtil
}
}
/**
* returns the server name, IP and game version.
*/
/** 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;
return serverName + ", IP " + serverIp + ", GameVersion " + serverMcVersion;
}
/**
* Convert a BlockColors int into a Color object.
*/
/** Convert a BlockColors int into a Color object */
public static Color intToColor(int num)
{
int filter = 0b11111111;
int red = (num >> 16 ) & filter;
int green = (num >> 8 ) & filter;
int red = (num >> 16) & filter;
int green = (num >> 8) & filter;
int blue = num & filter;
return new Color(red, green, blue);
}
/**
* Convert a Color into a BlockColors object.
*/
/** Convert a Color into a BlockColors object. */
public static int colorToInt(Color color)
{
return color.getRGB();
@@ -334,135 +346,126 @@ public class LodUtil
{
return Math.min(max, Math.max(value, min));
}
/**
* This methods return the number of lods that are going to be rendered in a region in the worst case
* @param regionPosX x region position to check
* @param regionPosZ z region position to check
* @return number of lods in the region
* Get a HashSet of all ChunkPos within the normal render distance
* that should not be rendered.
*/
public static int regionRenderingMemoryUse(int regionPosX, int regionPosZ, LodTemplate template)
public static HashSet<ChunkPos> getNearbyLodChunkPosToSkip(LodDimension lodDim, BlockPos playerPos)
{
int xRegionSign = (int) Math.signum(regionPosX);
int zRegionSign = (int) Math.signum(regionPosZ);
//we first find the center of the circle which is one of the following X position in the center region
/*
X - X - X
| |
X X X
| |
X - X - X
*/
int circleCenterX = 256 + 256*xRegionSign;
int circleCenterZ = 256 + 256*zRegionSign;
int innerRadius;
int outerRadius;
int size;
int count;
int minDistance;
int maxDistance;
int memoryUse = 0;
int number = 0;
for(byte detailLevel = BLOCK_DETAIL_LEVEL; detailLevel <= REGION_DETAIL_LEVEL; detailLevel++)
int chunkRenderDist = mc.getRenderDistance();
ChunkPos centerChunk = new ChunkPos(playerPos);
int skipRadius;
VanillaOverdraw overdraw = LodConfig.CLIENT.graphics.advancedGraphicsOption.vanillaOverdraw.get();
HorizontalResolution drawRes = LodConfig.CLIENT.graphics.qualityOption.drawResolution.get();
// apply distance based rules for dynamic overdraw
if (overdraw == VanillaOverdraw.DYNAMIC
&& chunkRenderDist <= MINIMUM_RENDER_DISTANCE_FOR_PARTIAL_OVERDRAW)
{
//We find now the inner and outer detail of this area
innerRadius = DetailDistanceUtil.getDrawDistanceFromDetail(detailLevel);
outerRadius = DetailDistanceUtil.getDrawDistanceFromDetail(detailLevel + 1);
//we skip if the region does not intersect the two circles.
minDistance = LevelPosUtil.minDistance(REGION_DETAIL_LEVEL, regionPosX, regionPosZ, circleCenterX, circleCenterZ);
maxDistance = LevelPosUtil.maxDistance(REGION_DETAIL_LEVEL, regionPosX, regionPosZ, circleCenterX, circleCenterZ);
if (innerRadius > maxDistance || minDistance > outerRadius)
continue;
//we proceed to count all the position in the region that fall between these two circle
size = 1 << (REGION_DETAIL_LEVEL - detailLevel);
count = 0;
for (int x = 0; x < size; x++)
// The vanilla render distance isn't far enough
// for partial skipping to make sense...
if (!lodDim.dimension.hasCeiling() && (drawRes == HorizontalResolution.BLOCK))
{
for (int z = 0; z < size; z++)
// ...and the dimension is open, so we don't have to worry about
// LODs rendering on top of the player,
// and the user is using a high horizontal resolution,
// so the overdraw shouldn't be noticeable
overdraw = VanillaOverdraw.ALWAYS;
}
else
{
// ...but we are underground, so we don't want
// LODs rendering on top of the player,
// Or the user is using a LOW horizontal resolution
// and overdraw would be very noticeable.
overdraw = VanillaOverdraw.NEVER;
}
}
// determine the skipping type based
// on the overdraw type
switch (overdraw)
{
case ALWAYS:
// don't skip any positions
return new HashSet<>();
case DYNAMIC:
if (chunkRenderDist > MINIMUM_RENDER_DISTANCE_FOR_PARTIAL_OVERDRAW
&& chunkRenderDist <= MINIMUM_RENDER_DISTANCE_FOR_FAR_OVERDRAW)
{
// This is a small render distance (but greater than the minimum partial
// distance), skip positions that are greater than 2/3 the render distance
skipRadius = (int) Math.ceil(chunkRenderDist * (2.0/3.0));
}
else
{
// This is a large render distance. Skip positions that are greater than
// 4/5ths the render distance
skipRadius = (int) Math.ceil(chunkRenderDist * (4.0 / 5.0));
}
break;
default:
case BORDER:
case NEVER:
// skip chunks in render distance that are rendered
// by vanilla minecraft
skipRadius = 0;
break;
}
// get the chunks that are going to be rendered by Minecraft
HashSet<ChunkPos> posToSkip = getRenderedChunks();
// remove everything outside the skipRadius,
// if the skipRadius is being used
if (skipRadius != 0)
{
for (int x = centerChunk.x - chunkRenderDist; x < centerChunk.x + chunkRenderDist; x++)
{
for (int z = centerChunk.z - chunkRenderDist; z < centerChunk.z + chunkRenderDist; z++)
{
minDistance = LevelPosUtil.minDistance(detailLevel, x, z, circleCenterX, circleCenterZ, regionPosX, regionPosZ);
if (innerRadius < minDistance && minDistance < outerRadius)
count++;
if (x <= centerChunk.x - skipRadius || x >= centerChunk.x + skipRadius
|| z <= centerChunk.z - skipRadius || z >= centerChunk.z + skipRadius)
posToSkip.remove(new ChunkPos(x, z));
}
}
//we multiply the data with the max vertical data of this detail level
int maxVerticalData = DetailDistanceUtil.getMaxVerticalData(detailLevel);
number += count;
count *= maxVerticalData;
memoryUse += template.getBufferMemoryForSingleLod(maxVerticalData) * count;
}
System.out.println(number);
return memoryUse;
return posToSkip;
}
/**
* Get a HashSet of all ChunkPos within the normal render distance
* that should not be rendered.
*/
public static HashSet<ChunkPos> getNearbyLodChunkPosToSkip(LodDimension lodDim, BlockPos playerPos)
{
int chunkRenderDist = mc.getRenderDistance();
ChunkPos centerChunk = new ChunkPos(playerPos);
// skip chunks that are already going to be rendered by Minecraft
HashSet<ChunkPos> posToSkip = getRenderedChunks();
// go through each chunk within the normal view distance
for (int x = centerChunk.x - chunkRenderDist; x < centerChunk.x + chunkRenderDist; x++)
{
for (int z = centerChunk.z - chunkRenderDist; z < centerChunk.z + chunkRenderDist; z++)
{
if (!lodDim.doesDataExist(LodUtil.CHUNK_DETAIL_LEVEL, x, z))
continue;
long data = lodDim.getSingleData(LodUtil.CHUNK_DETAIL_LEVEL, x, z);
short lodAverageHeight = DataPointUtil.getHeight(data);
if (playerPos.getY() <= lodAverageHeight)
{
// don't draw Lod's that are taller than the player
// to prevent LODs being drawn on top of the player
posToSkip.add(new ChunkPos(x, z));
}
}
}
return posToSkip;
}
/**
* This method returns the ChunkPos of all chunks that Minecraft
* is going to render this frame. <br><br>
* <p>
* Note: This isn't perfect. It will return some chunks that are outside
* the clipping plane. (For example, if you are high above the ground some chunks
* will be incorrectly added, even though they are outside render range).
*/
* This method returns the ChunkPos of all chunks that Minecraft
* is going to render this frame. <br><br>
* <p>
* Note: This isn't perfect. It will return some chunks that are outside
* the clipping plane. (For example, if you are high above the ground some chunks
* will be incorrectly added, even though they are outside render range).
*/
public static HashSet<ChunkPos> getRenderedChunks()
{
HashSet<ChunkPos> loadedPos = new HashSet<>();
// Wow those are some long names!
// Wow, those are some long names!
// go through every RenderInfo to get the compiled chunks
WorldRenderer renderer = mc.getLevelRenderer();
for (WorldRenderer.LocalRenderInformationContainer worldrenderer$localrenderinformationcontainer : renderer.renderChunks)
for (WorldRenderer.LocalRenderInformationContainer worldRenderer$LocalRenderInformationContainer : renderer.renderChunks)
{
CompiledChunk compiledChunk = worldrenderer$localrenderinformationcontainer.chunk.getCompiledChunk();
CompiledChunk compiledChunk = worldRenderer$LocalRenderInformationContainer.chunk.getCompiledChunk();
if (!compiledChunk.hasNoRenderableLayers())
{
// add the ChunkPos for every rendered chunk
BlockPos bpos = worldrenderer$localrenderinformationcontainer.chunk.getOrigin();
BlockPos bpos = worldRenderer$LocalRenderInformationContainer.chunk.getOrigin();
loadedPos.add(new ChunkPos(bpos));
}
@@ -472,5 +475,38 @@ public class LodUtil
return loadedPos;
}
/**
* This method find if a given chunk is a border chunk of the renderable ones
* @param vanillaRenderedChunks matrix of the vanilla rendered chunks
* @param x relative (to the matrix) x chunk to check
* @param z relative (to the matrix) z chunk to check
* @return true if and only if the chunk is a border of the renderable chunks
*/
public static boolean isBorderChunk(boolean[][] vanillaRenderedChunks, int x, int z)
{
if (x < 0 || z < 0 || x >= vanillaRenderedChunks.length || z >= vanillaRenderedChunks[0].length)
return false;
int tempX;
int tempZ;
for (Direction direction : Box.ADJ_DIRECTIONS)
{
tempX = x + Box.DIRECTION_NORMAL_MAP.get(direction).getX();
tempZ = z + Box.DIRECTION_NORMAL_MAP.get(direction).getZ();
if (!(tempX < 0 || tempZ < 0 || tempX >= vanillaRenderedChunks.length || tempZ >= vanillaRenderedChunks[0].length))
{
if (!vanillaRenderedChunks[tempX][tempZ])
{
return true;
}
}
else
{
if (vanillaRenderedChunks[x][z])
{
return true;
}
}
}
return false;
}
}
@@ -1,43 +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.util;
/**
* This holds meta information about the mod.
*
* @author James Seibel
* @version 04-16-2020
*/
public class Reference
{
/** the mod's identifier */
public static final String MOD_ID = "lod";
/** the mod's name */
public static final String NAME = "LOD Mod";
/** the mod's version */
public static final String VERSION = "1.0";
/** the version of minecraft this mod is built for */
public static final String ACCEPTED_VERSIONS = "[1.16.4]";
/** where the client proxy class is */
public static final String CLIENT_PROXY_CLASS = "com.backsun.lod.proxy.ClientProxy";
/** where the common proxy class is*/
public static final String COMMON_PROXY_CLASS = "com.backsun.lod.proxy.CommonProxy";
}
@@ -1,11 +1,38 @@
/*
* This file is part of the Distant Horizon mod (formerly 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.util;
import static com.seibel.lod.util.LodUtil.DETAIL_OPTIONS;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import com.seibel.lod.builders.bufferBuilding.lodTemplates.Box;
import net.minecraft.util.Direction;
/**
* Holds data used by specific threads so
* Holds data used by specific threads so
* the data doesn't have to be recreated every
* time it is needed.
*
@@ -18,175 +45,167 @@ public class ThreadMapUtil
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, byte[][]> saveContainer = new ConcurrentHashMap<>();
public static final ConcurrentMap<String, short[]> projectionArrayMap = new ConcurrentHashMap<>();
public static final ConcurrentMap<String, short[]> heightAndDepthMap = new ConcurrentHashMap<>();
public static final ConcurrentMap<String, long[]> singleDataToMergeMap = new ConcurrentHashMap<>();
public static final ConcurrentMap<String, long[][]> verticalUpdate = new ConcurrentHashMap<>();
//________________________//
// used in BufferBuilder //
//________________________//
/** returns the array filled with 0's */
public static long[] getFreshSingleUpdateArray(int arrayLength)
public static final ConcurrentMap<String, boolean[]> adjShadeDisabled = new ConcurrentHashMap<>();
public static final ConcurrentMap<String, Map<Direction, long[]>> adjDataMap = new ConcurrentHashMap<>();
public static final ConcurrentMap<String, Box> boxMap = new ConcurrentHashMap<>();
/** returns the array NOT cleared every time */
public static boolean[] getAdjShadeDisabledArray()
{
long[] array = getSingleUpdateArray();
array = clearOrCreateArray(array, arrayLength);
return array;
}
public static long[] getSingleUpdateArray()
{
if (!threadSingleUpdateMap.containsKey(Thread.currentThread().getName()) || (threadSingleUpdateMap.get(Thread.currentThread().getName()) == null))
if (!adjShadeDisabled.containsKey(Thread.currentThread().getName())
|| (adjShadeDisabled.get(Thread.currentThread().getName()) == null))
{
threadSingleUpdateMap.put(Thread.currentThread().getName(), new long[4]);
adjShadeDisabled.put(Thread.currentThread().getName(), new boolean[Box.DIRECTIONS.length]);
}
return threadSingleUpdateMap.get(Thread.currentThread().getName());
Arrays.fill(adjShadeDisabled.get(Thread.currentThread().getName()), false);
return adjShadeDisabled.get(Thread.currentThread().getName());
}
/** returns the array filled with 0's */
public static long[] getFreshBuilderArray(int arrayLength, int detailLevel)
/** returns the array NOT cleared every time */
public static Map<Direction, long[]> getAdjDataArray(int verticalData)
{
long[] array = getBuilderArray(detailLevel);
array = clearOrCreateArray(array, arrayLength);
return array;
}
public static long[] getBuilderArray(int detailLevel)
{
if (!threadBuilderArrayMap.containsKey(Thread.currentThread().getName()) || (threadBuilderArrayMap.get(Thread.currentThread().getName()) == null))
if (!adjDataMap.containsKey(Thread.currentThread().getName())
|| (adjDataMap.get(Thread.currentThread().getName()) == null)
|| (adjDataMap.get(Thread.currentThread().getName()).get(Direction.NORTH) == null)
|| (adjDataMap.get(Thread.currentThread().getName()).get(Direction.NORTH).length != verticalData))
{
long[][] array = new long[5][];
threadBuilderArrayMap.put(Thread.currentThread().getName(), array);
adjDataMap.put(Thread.currentThread().getName(), new HashMap<Direction, long[]>());
adjDataMap.get(Thread.currentThread().getName()).put(Direction.UP, new long[1]);
adjDataMap.get(Thread.currentThread().getName()).put(Direction.DOWN, new long[1]);
for (Direction direction : Box.ADJ_DIRECTIONS)
adjDataMap.get(Thread.currentThread().getName()).put(direction, new long[verticalData]);
}
return threadBuilderArrayMap.get(Thread.currentThread().getName())[detailLevel];
else
{
for (Direction direction : Box.ADJ_DIRECTIONS)
Arrays.fill(adjDataMap.get(Thread.currentThread().getName()).get(direction), DataPointUtil.EMPTY_DATA);
}
return adjDataMap.get(Thread.currentThread().getName());
}
public static Box getBox()
{
if (!boxMap.containsKey(Thread.currentThread().getName())
|| (boxMap.get(Thread.currentThread().getName()) == null))
{
boxMap.put(Thread.currentThread().getName(), new Box());
}
boxMap.get(Thread.currentThread().getName()).reset();
return boxMap.get(Thread.currentThread().getName());
}
//________________________//
// used in DataPointUtil //
// mergeVerticalData //
//________________________//
//________________________//
// used in DataPointUtil //
// mergeSingleData //
//________________________//
/** returns the array filled with 0's */
public static long[] getFreshBuilderVerticalArray(int arrayLength, int detailLevel)
{
long[] array = getBuilderVerticalArray(detailLevel);
array = clearOrCreateArray(array, arrayLength);
return array;
}
public static long[] getBuilderVerticalArray(int detailLevel)
{
if (!threadBuilderVerticalArrayMap.containsKey(Thread.currentThread().getName()) || (threadBuilderVerticalArrayMap.get(Thread.currentThread().getName()) == null))
{
long[][] array = new long[5][];
int size = 1;
for (int i = 0; i < 5; i++)
{
array[i] = new long[size * size * DataPointUtil.worldHeight + 1];
size = size << 1;
}
threadBuilderVerticalArrayMap.put(Thread.currentThread().getName(), array);
}
Arrays.fill(threadBuilderVerticalArrayMap.get(Thread.currentThread().getName())[detailLevel], 0);
return threadBuilderVerticalArrayMap.get(Thread.currentThread().getName())[detailLevel];
}
/** returns the array NOT cleared every time */
public static byte[] getSaveContainer(int detailLevel)
{
if (!saveContainer.containsKey(Thread.currentThread().getName()) || (saveContainer.get(Thread.currentThread().getName()) == null))
{
byte[][] array = new byte[DETAIL_OPTIONS][];
int size = 1;
for (int i = DETAIL_OPTIONS - 1; i >= 0; i--)
{
array[i] = new byte[2 + 8 * size * size * DetailDistanceUtil.getMaxVerticalData(i)];
size = size << 1;
}
saveContainer.put(Thread.currentThread().getName(), array);
}
//Arrays.fill(threadBuilderVerticalArrayMap.get(Thread.currentThread().getName())[detailLevel], 0);
return saveContainer.get(Thread.currentThread().getName())[detailLevel];
}
/** returns the array filled with 0's */
public static long[] getFreshVerticalDataArray(int arrayLength)
{
long[] array = getVerticalDataArray();
array = clearOrCreateArray(array, arrayLength);
return array;
}
public static long[] getVerticalDataArray()
public static long[] getVerticalDataArray(int arrayLength)
{
if (!threadVerticalAddDataMap.containsKey(Thread.currentThread().getName()) || (threadVerticalAddDataMap.get(Thread.currentThread().getName()) == null))
{
threadVerticalAddDataMap.put(Thread.currentThread().getName(), new long[0]);
threadVerticalAddDataMap.put(Thread.currentThread().getName(), new long[arrayLength]);
}
else
{
Arrays.fill(threadVerticalAddDataMap.get(Thread.currentThread().getName()), 0);
}
return threadVerticalAddDataMap.get(Thread.currentThread().getName());
}
/** returns the array filled with 0's */
public static short[] getFreshProjectionArray(int arrayLength)
{
short[] array = getProjectionArray();
array = clearOrCreateArray(array, arrayLength);
return array;
}
public static short[] getProjectionArray()
{
if (!projectionArrayMap.containsKey(Thread.currentThread().getName()) || (projectionArrayMap.get(Thread.currentThread().getName()) == null))
{
projectionArrayMap.put(Thread.currentThread().getName(), new short[0]);
}
return projectionArrayMap.get(Thread.currentThread().getName());
}
/** returns the array filled with 0's */
public static short[] getFreshHeightAndDepth(int arrayLength)
{
short[] array = getHeightAndDepth();
array = clearOrCreateArray(array, arrayLength);
return array;
}
public static short[] getHeightAndDepth()
/** returns the array NOT cleared every time */
public static short[] getHeightAndDepth(int arrayLength)
{
if (!heightAndDepthMap.containsKey(Thread.currentThread().getName()) || (heightAndDepthMap.get(Thread.currentThread().getName()) == null))
{
heightAndDepthMap.put(Thread.currentThread().getName(), new short[0]);
heightAndDepthMap.put(Thread.currentThread().getName(), new short[arrayLength]);
}
return heightAndDepthMap.get(Thread.currentThread().getName());
}
/** returns the array filled with 0's */
public static byte[] getFreshSaveContainer(int arrayLength)
{
byte[] array = getSaveContainer();
array = clearOrCreateArray(array, arrayLength);
return array;
}
public static byte[] getSaveContainer()
{
if (!saveContainer.containsKey(Thread.currentThread().getName()) || (saveContainer.get(Thread.currentThread().getName()) == null))
{
saveContainer.put(Thread.currentThread().getName(), new byte[0]);
}
return saveContainer.get(Thread.currentThread().getName());
}
/** returns the array filled with 0's */
public static long[] getFreshVerticalUpdateArray(int arrayLength, int detailLevel)
{
long[] array = ThreadMapUtil.getVerticalUpdateArray(detailLevel);
array = clearOrCreateArray(array, arrayLength);
return array;
}
public static long[] getVerticalUpdateArray(int detailLevel)
{
if (!verticalUpdate.containsKey(Thread.currentThread().getName()) || (verticalUpdate.get(Thread.currentThread().getName()) == null))
{
long[][] array = new long[10][];
long[][] array = new long[DETAIL_OPTIONS][];
for (int i = 1; i < DETAIL_OPTIONS; i++)
array[i] = new long[DetailDistanceUtil.getMaxVerticalData(i - 1) * 4];
verticalUpdate.put(Thread.currentThread().getName(), array);
}
else
{
Arrays.fill(verticalUpdate.get(Thread.currentThread().getName())[detailLevel], 0);
}
return verticalUpdate.get(Thread.currentThread().getName())[detailLevel];
}
/** returns the array filled with 0's */
public static long[] getFreshSingleAddDataToMerge(int arrayLength)
{
long[] array = getSingleAddDataToMerge();
array = clearOrCreateArray(array, arrayLength);
return array;
}
public static long[] getSingleAddDataToMerge()
{
if (!singleDataToMergeMap.containsKey(Thread.currentThread().getName()) || (singleDataToMergeMap.get(Thread.currentThread().getName()) == null))
{
singleDataToMergeMap.put(Thread.currentThread().getName(), new long[0]);
}
return singleDataToMergeMap.get(Thread.currentThread().getName());
}
/** clears all arrays so they will have to be rebuilt */
public static void clearMaps()
{
adjShadeDisabled.clear();
adjDataMap.clear();
boxMap.clear();
threadSingleUpdateMap.clear();
threadBuilderArrayMap.clear();
threadBuilderVerticalArrayMap.clear();
@@ -197,55 +216,4 @@ public class ThreadMapUtil
singleDataToMergeMap.clear();
verticalUpdate.clear();
}
/** returns an array filled with 0's */
private static long[] clearOrCreateArray(long[] array, int arrayLength)
{
if (array == null || array.length != arrayLength)
{
array = new long[arrayLength];
}
else
Arrays.fill(array, 0);
return array;
}
/** returns an array filled with 0's */
@SuppressWarnings("unused")
private static int[] clearOrCreateArray(int[] array, int arrayLength)
{
if (array == null || array.length != arrayLength)
array = new int[arrayLength];
else
Arrays.fill(array, 0);
return array;
}
/** returns an array filled with 0's */
private static short[] clearOrCreateArray(short[] array, int arrayLength)
{
if (array == null || array.length != arrayLength)
array = new short[arrayLength];
else
Arrays.fill(array, (short) 0);
return array;
}
/** returns an array filled with 0's */
private static byte[] clearOrCreateArray(byte[] array, int arrayLength)
{
if (array == null || array.length != arrayLength)
array = new byte[arrayLength];
else
Arrays.fill(array, (byte) 0);
return array;
}
}
@@ -1,3 +1,22 @@
/*
* This file is part of the Distant Horizon mod (formerly 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.wrappers;
import java.awt.Color;
@@ -32,12 +51,14 @@ import net.minecraft.world.DimensionType;
*/
public class MinecraftWrapper
{
public static MinecraftWrapper INSTANCE = new MinecraftWrapper();
public static final MinecraftWrapper INSTANCE = new MinecraftWrapper();
private Minecraft mc = Minecraft.getInstance();
private final Minecraft mc = Minecraft.getInstance();
/** The lightmap for the current:
* Time, dimension, brightness setting, etc. */
/**
* The lightmap for the current:
* Time, dimension, brightness setting, etc.
*/
private NativeImage lightMap = null;
private MinecraftWrapper()
@@ -54,10 +75,10 @@ public class MinecraftWrapper
/**
* 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.
* <p>
* LightMaps and other time sensitive objects fall in this category. <br> <br>
* <p>
* This doesn't affect OpenGL objects in any way.
*/
public void clearFrameObjectCache()
{
@@ -75,7 +96,7 @@ public class MinecraftWrapper
return mc.level.getShade(Direction.UP, true);
}
public boolean hasSingleplayerServer()
public boolean hasSinglePlayerServer()
{
return mc.hasSingleplayerServer();
}
@@ -90,7 +111,7 @@ public class MinecraftWrapper
return LodUtil.getDimensionIDFromWorld(mc.level);
}
/**
/**
* This texture changes every frame
*/
public NativeImage getCurrentLightMap()
@@ -99,32 +120,14 @@ public class MinecraftWrapper
if (lightMap == null)
{
LightTexture tex = mc.gameRenderer.lightTexture();
NativeImage lightPixels = tex.lightPixels;
lightMap = lightPixels;
lightMap = tex.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
*/
@@ -132,7 +135,7 @@ public class MinecraftWrapper
{
if (lightMap == null)
{
// make sure the lightMap is up to date
// make sure the lightMap is up-to-date
getCurrentLightMap();
}
@@ -142,7 +145,6 @@ public class MinecraftWrapper
/**
* 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
*/
@@ -167,7 +169,7 @@ public class MinecraftWrapper
{
return mc.options;
}
public ModelManager getModelManager()
{
return mc.getModelManager();
@@ -175,9 +177,9 @@ public class MinecraftWrapper
public ClientWorld getClientWorld()
{
return mc.level;
return mc.level;
}
/** Measured in chunks */
public int getRenderDistance()
{
@@ -219,7 +221,7 @@ public class MinecraftWrapper
return mc.level.getSkyDarken(partialTicks);
}
public IntegratedServer getSingleplayerServer()
public IntegratedServer getSinglePlayerServer()
{
return mc.getSingleplayerServer();
}
+2 -2
View File
@@ -24,10 +24,10 @@ 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.5.1-pre" #mandatory
version="a1.5.0" #mandatory
#// A display name for the mod
displayName="LOD" #mandatory
displayName="Distant Horizons" #mandatory
#// A URL to query for updates for this mod. See the JSON update specification https://mcforge.readthedocs.io/en/latest/gettingstarted/autoupdate/
#//updateJSONURL="https://change.me.example.invalid/updates.json" #optional