Compare commits

...

207 Commits

Author SHA1 Message Date
Morippi d5072ed475 added new classes for the quadTree 2022-03-18 17:01:16 +01:00
Morippi ffee2141d4 added some new methods 2022-03-18 16:41:45 +01:00
Morippi 9f4b6b8709 added some comments 2022-03-18 16:39:43 +01:00
Morippi b05f074f4c added some comments 2022-03-18 16:24:19 +01:00
Morippi 06a549983b added methods names 2022-03-18 16:20:19 +01:00
Morippi 0b96ca8509 changed names of variables 2022-03-18 16:04:31 +01:00
Morippi edd50096d6 Added First concept of LodSection. 2022-03-16 23:03:54 +01:00
tom lee 607f3e8afe VertQuality: Fixed some stuff. Now High Qual loading works
Live config change still... a work in progress though
2021-12-30 16:07:49 +08:00
tom lee 8b3404e5f8 Cleanup for the saving in interface 2021-12-29 21:32:04 +08:00
tom lee df6253af39 LodUpdate: Make both cut and expand use spiral iteration 2021-12-29 20:36:40 +08:00
tom lee b1f3b23ba1 Load: Make it load via a spiral 2021-12-29 20:18:52 +08:00
tom lee c4708ed173 Save/Load: Slightly improved speed on loading 2021-12-29 19:40:34 +08:00
tom lee fcab0d3b20 Save/Load: Optimize Memory usage on save/load
It now directly read/write to file stream instead of via a temp byte[]
buffer.
2021-12-29 18:17:44 +08:00
tom lee 1034360b88 Renderer: Stored and reapplied more gl states to increase mod compat
Now at least iris shaders won't throw OpenGL errors. (Even though it
still aren't working correctly)
2021-12-29 13:44:37 +08:00
tom lee f92f656876 Changed some System.out to ClientApi.debug 2021-12-28 15:26:01 +08:00
coolGi2007 e052a0c96f Moved annotations to core 2021-12-28 05:31:56 +00:00
tom lee 8bb8217c7b GLProxy: Changed GL logger to new unique log file
Log file is: OpenGL-Lod-ProxyContext
and: OpenGL-LodWorkerContext
2021-12-28 00:09:10 +08:00
tom lee 0563cde3c2 GLProxy: Add temp debug enable via setting debugMode to SHOW_DETAIL` 2021-12-27 23:42:47 +08:00
tom lee 5fe192f4c5 BufferUpload: Changed/Improve Buffer Upload, and a new timeout
Now timeout length is based on how much data is uploaded.

Also, added LagSpikeCatcher in LodBufferBuilderFactory to debug catch
lag spike locations.
2021-12-27 16:07:55 +08:00
cola98765 428e12081c fixed void chunks... made it a bit more resilient 2021-12-26 17:22:56 +01:00
cola98765 ac102402cc disabled connected lods in hasCeiling dimensions 2021-12-26 16:19:06 +01:00
cola98765 966677b89e Add back face culling #109 2021-12-26 10:40:11 +01:00
cola98765 64e73b7d83 fixed light requiring Y offset 2021-12-25 13:19:01 +01:00
cola98765 52ea2e96b7 detect snow etc on connected lods; better comments to enable transparency 2021-12-25 12:25:48 +01:00
cola98765 c658697ecd fixed rogue faces when using connected lods 2021-12-25 11:32:35 +01:00
cola98765 a5b259f098 remove debug if 2021-12-25 10:45:01 +01:00
cola98765 e9da9c26f4 remove connected vertical faces 2021-12-25 10:44:12 +01:00
cola98765 2f6ff1a3ea fix avoided blocks with new method 2021-12-24 11:48:00 +01:00
cola98765 766e5a358d made connected lods configurable (TODO actually add it to config) 2021-12-24 09:29:48 +01:00
cola98765 a19189c2a8 commented the thing to enable colors 2021-12-23 23:43:55 +01:00
cola98765 0627f779d7 connected lods! 2021-12-23 19:19:32 +01:00
James Seibel 01bfb65d9e Add TestDiagram.drawio 2021-12-22 02:14:54 +00:00
James Seibel cccad08a61 Add a folder for wiki files 2021-12-22 02:13:32 +00:00
tom lee 96be86cacf RenderBuffer: Fixed BufferMapping OpenGL Errors
Thanks to nev at discord for pointing this issue out.
2021-12-21 21:12:15 +08:00
tom lee b75791006c General cleanup/fixup/improvement of render buffers 2021-12-21 15:48:25 +08:00
tom lee 9047ebb970 GLProxy: Now always log OpenGL supported version 2021-12-21 14:05:49 +08:00
tom lee 2d1c2d6efb GLProxy: Improved version not supported messages 2021-12-21 13:52:12 +08:00
tom lee 8577363438 added option for more WorldGenerater control 2021-12-19 00:29:50 +08:00
tom lee 4229ed75ae WorldGen: support 1.18 worldgen without locking the server up
Added getWorldGenerationCountPerThread() constant. Cleaned up the
LodWorldGenerator thread main lamda.
2021-12-17 15:57:37 +08:00
tom lee b8408bc6fa Renderer: Changed all to use GL32, set requirement to GL32
This should be the final commit on the renderer system rework if there are no
other bugs.
2021-12-17 14:52:24 +08:00
cola98765 56c4911316 changed where minHeight is set 2021-12-16 12:24:07 +01:00
tom lee 244ead9451 Renderer rework completed
The renderer rework is done. Fixed some non-issues and optimized away
some bind() calls. Also added some FIXME comments to some place that I
noticed needs to be fixed.
2021-12-16 00:16:09 +08:00
cola98765 5709a4c660 changed how minHeight is handled in VerticalLevelContainer 2021-12-15 12:09:47 +01:00
tom lee 3913b955be Renderer: Fixed and enabled GL43+ Vertex Attribute
Not sure if it's faster currently. It should in theory be.
2021-12-15 17:15:54 +08:00
tom lee a295dcafd4 Fix Init Null Exception (LodBuilder getMinHeight())
Fixed someone calling world.getMinHeight() on LodBuilder static init.
The world hasn't even been loaded... Also added it so that it updates on
world / dimension change.
2021-12-15 16:05:13 +08:00
James Seibel f9914f9336 slightly change some config wording 2021-12-14 23:27:46 -06:00
James Seibel 13e9df5b48 Change the sodium support to use a flat render distance
Newer versions of sodium store chunks in such a way that using reflection to get them is complicated at best. So for now we will just use the render distance and estimate which chunks should be loaded.
2021-12-14 23:27:27 -06:00
James Seibel 98c394bad1 Add a default createChunkPos(long) to IWrapperFactory 2021-12-14 20:51:29 -06:00
cola98765 444bf3b8bc made getMinHeight() default, so when it's not implemented it just returns 0 2021-12-14 21:40:33 +01:00
cola98765 455281a32d no longer using getMinimumWorldHeight() form version constants, added replacement in WorldWrapper. 2021-12-14 21:36:51 +01:00
cola98765 7b613ae8e3 rework for 1.18 y<0 2021-12-14 21:12:02 +01:00
cola98765 443d6165fa final fix for 1.18 y<0? 2021-12-14 20:45:38 +01:00
cola98765 a4ebe3e3c1 me stupid 2021-12-14 20:07:10 +01:00
cola98765 6ac3edf280 adding offset back 2021-12-14 19:59:33 +01:00
cola98765 a1163dc340 removed debug message 2021-12-14 19:41:01 +01:00
cola98765 38b7e66ef8 different debug message 2021-12-14 19:28:56 +01:00
cola98765 a843a0ed65 disable debug message 2021-12-14 19:24:20 +01:00
cola98765 e0d2d2530f disable debug message 2021-12-14 19:24:01 +01:00
cola98765 44e2936b68 made very logs for merge bug 2021-12-14 19:06:21 +01:00
cola98765 dfa717e0e3 revert everything around this merge bug 2021-12-14 19:04:06 +01:00
cola98765 69844417ce potential fix to that merge bug 2021-12-14 18:37:26 +01:00
cola98765 8370402dc1 potential fix to that merge bug 2021-12-14 18:27:01 +01:00
cola98765 cbb32bc996 potential fix to that merge bug 2021-12-14 18:24:42 +01:00
cola98765 ac876c0030 potential fix 2021-12-14 18:14:14 +01:00
cola98765 2176807a0a fixed lightmap 2021-12-14 18:09:39 +01:00
cola98765 47732b7f57 just playing around with <0 y 2021-12-14 17:57:27 +01:00
cola98765 ff9afee5b4 fixed loading data being corrupted 2021-12-14 17:29:55 +01:00
tom lee b5665a59c0 Renderer: Fix critical bug that black screen Minecraft 2021-12-15 00:04:04 +08:00
cola98765 483ecf2e4d fixed couple things around Leo's merge. 2021-12-14 14:57:49 +01:00
tom lee 30eab27a49 Removed build-breaking imports
Ha. Told me to always test the build before pushing. Turns out you also
didn't test it!
2021-12-14 21:38:14 +08:00
tom lee d1b5200fed Renderer: Fixup some stuff that maybe an issue 2021-12-14 21:28:09 +08:00
Morippi 4a4728d41e fixed some part and added the newUpdate for now (still unsuded and doesn't use the slice) 2021-12-14 14:11:11 +01:00
Morippi c9204a2094 fixed some part and made a splitted version (if we want to use it since it's possible) 2021-12-14 14:00:47 +01:00
Morippi c45f0b2414 completed the merge method 2021-12-14 13:46:10 +01:00
tom lee 97b2db84cd Quick Patch for GL43+
TODO: Check why GL43+ Vertex Attribute doesn't work
2021-12-14 18:21:40 +08:00
tom lee c389f3b391 Fix merge conflicts 2021-12-14 15:03:31 +08:00
tom lee 6049840aa6 MAJOR Renderer Rewrite
Rewriten almost all stuff in core.render. Changes made:

1. Added LightmapTexture object
2. Moved&Renamed LodShader & LodShaderProgram to be under render/objects/
3. All class under render/objects are actual objects for OpenGL
4. Add LodRenderProgram which is a ShaderProgram + VertexAttribute. If
   we change the method on how we render stuff (Like modifying
   input/output), we should change this file as this is the
   repesentation of the Rendering Program that we are using.
5. Add VertexAttribute, and a GL43+ vesion of it. I implemented both
   versions because the GL43+ will get a noticable speedup due to some
   additions to OpenGL standard. However I still included older version
   for backward compatibilities. To access VertexAttributes, it is
   recommended to access it though the VertexAttribute abstract class's
   method.
6. Rewritten the main renderLod() method. Now it will cache as much
   stuff as possible.
7. To do '6.', now the renderer has a setup() and a cleanup() method,
   both that must be called inside Render Thread. The renderer should
   automatically detects when it needs to call the setup() and
   cleanup(). However, you can expicitly request the renderer to do
   cleanup() on next rendering by calling markForCleanup().
8. ...I think that's all? Don't quite remember tbh. Hopefully I didn't
   miss any points.

Note: This overhaul of the renderer means that THERE WILL BE BUGS. I
can't test all versions on both Nvidia and AMD cards with all OPENGL
versions so please report any bugs to me straight away. Thanks in
advance!

Note 2: I will add more docs in the source code next.
2021-12-14 15:03:03 +08:00
tom lee 2dee6b0326 Add a try/catch and disable Renderer on error
Rendering exceptions usually happens every frame, which cause a lot of
spam. So I added a try/catch and a bool to check if Renderer has
encountered an error, in which the Renderer will be disabled until the
game restarts. (This could probibly be changed)
2021-12-14 14:51:00 +08:00
Morippi 27d9e6aeb7 Added slice in mergeAndAddData 2021-12-13 22:55:29 +01:00
Morippi c751b6fcc6 Added some other comment to the merge 2021-12-13 22:49:58 +01:00
Morippi 31f173c8e8 added the commented code to the mergeAndAddData to make the conversion to new system easier 2021-12-13 22:40:01 +01:00
Morippi 90ca3bd394 Added the position data merge 2021-12-13 22:37:40 +01:00
Morippi 30dd5526cd refactoring for future code 2021-12-13 22:12:13 +01:00
Morippi a9e31b8133 Added methods for the merge in the LevelContainer, to be implemented 2021-12-13 21:03:54 +01:00
Morippi e71cd864b4 Merge remote-tracking branch 'origin/main' into main 2021-12-13 18:59:52 +01:00
Morippi 8efc7d54e7 Changed the format 2021-12-13 18:59:42 +01:00
cola98765 e323e8d62e fixed wrong wrapper call 2021-12-13 18:38:01 +01:00
cola98765 b970ad0ab8 I hope I fixed what james broke 2021-12-13 16:13:43 +01:00
cola98765 c811e6bad6 another change to save format... now there is no static offset 2021-12-13 10:41:33 +01:00
James Seibel 8ef0d40f0c Merge branch 'main' of gitlab.com:jeseibel/distant-horizons-core 2021-12-12 17:42:25 -06:00
James Seibel 227f7d0a23 Closes #85 (Sodium Overdraw incompatibility)
One minor issue: Sodium returns chunks differently than vanilla MC or Optifine. Specifically as of 1.16.5 (12-12-2021) it also returns one layer of chunks further than what is currently rendered.
2021-12-12 17:42:23 -06:00
James Seibel b385b018f1 Remove a few unneeded lines 2021-12-12 17:32:38 -06:00
cola98765 4ece2de991 probable fix to a color bug that made no sense 2021-12-12 23:47:24 +01:00
cola98765 a172961112 probable fix to a color bug that made no sense 2021-12-12 21:26:40 +01:00
tom lee 9e882951ef WorldGen: Quick change to respect VersionConstants
Changed it so that even in FULL mode, it respects the VersionConstants
setting. Who knows if one day we might get even FULL chunk gen in
multithreading~
2021-12-12 23:14:52 +08:00
cola98765 1c9fe23633 fixed couple errors, but floating islands are still broken 2021-12-12 12:04:08 +01:00
James Seibel 92b6a9695d Improve the comment for the Horizontal Quality setting 2021-12-11 21:49:56 -06:00
James Seibel acc5e7af98 Add support for different Core behavior in different MC versions 2021-12-11 21:40:43 -06:00
coolGi2007 aa9e49b3e7 Please stop calling a forge wrapper from core 2021-12-12 01:30:40 +00:00
cola98765 7d86e24db5 attempt to fix generation below y=0 2021-12-12 00:25:51 +01:00
cola98765 71986d8818 attempt to fix generation below y=0 2021-12-12 00:16:34 +01:00
cola98765 e948b4dac1 attempt to fix generation below y=0 2021-12-11 23:48:04 +01:00
cola98765 0a5f7d0c11 reversed IF to attempt to avoid indexOutOfBounds 2021-12-11 23:30:11 +01:00
cola98765 1c65ef8323 made whatever leetom made build on my java 8 instance 2021-12-11 19:23:21 +01:00
cola98765 2c744dd22b made whatever leetom made build on my java 8 instance 2021-12-11 19:22:49 +01:00
tom lee 009d7ede1b WorldGen: Added support for single thread world gen
Reworked the world gen so that it no longer creates like 5 new threads
every server tick. And combined LodGenWorker into LodWorldGenerator as
being a seperate object is not needed.
2021-12-12 02:01:59 +08:00
cola98765 4199954843 fixed terraforged nullPointer. TODO fix the cause of the bug, bot it's symptoms 2021-12-11 18:06:58 +01:00
cola98765 c7ab36c6b7 since light no longer needs to be updated, I've reenabled partial regens 2021-12-11 16:58:37 +01:00
cola98765 1da6544550 actually fix FAR_FIRST 2021-12-11 16:33:27 +01:00
cola98765 c2896d1f73 now stuff should actually cull 2021-12-11 15:43:43 +01:00
cola98765 302abb1e57 fixed blocklight 2021-12-11 14:10:55 +01:00
cola98765 34c6f28173 fix nonFull and noCollision blocks not giving color 2021-12-11 10:54:26 +01:00
cola98765 75676df736 Merge remote-tracking branch 'origin/main' into main 2021-12-11 10:22:08 +01:00
coolGi2007 56b80f00e8 Fixed something that shouldnt be called 2021-12-11 09:13:12 +00:00
cola98765 c1651edb6a fix directional culling 2021-12-11 09:56:41 +01:00
Morippi c1375f7a10 small fixes, + changed variables name 2021-12-11 01:26:03 +01:00
Morippi a82ef3dcde Fixed position bug in the rendering 2021-12-11 00:42:08 +01:00
cola98765 74a97dab0d small fixes 2021-12-10 21:57:14 +01:00
cola98765 16621773af mall fix 2021-12-10 20:49:40 +01:00
Morippi c8988ad1b7 fixewd generation not working 2021-12-10 20:37:02 +01:00
Morippi 1f3f432fef Removed most of BlockPos and ChunkPos use 2021-12-10 20:36:51 +01:00
cola98765 56032b6d6b Merge remote-tracking branch 'origin/main' into main 2021-12-10 18:20:30 +01:00
cola98765 6157c659d2 fix fog for fixed render distance 2021-12-10 18:20:08 +01:00
cola98765 d2cae841e7 fix calculation of view range if it was k*32+x where k is int and 0<=x<16. To compare with old versions add 32 2021-12-10 18:02:07 +01:00
cola98765 d1711be390 fix calculation of view range. if it was k*32+x where k is int and 0<=x<16. To compare with old versions add 32 2021-12-10 18:01:19 +01:00
cola98765 93332d6256 changed debug key to F8 and added chat messages when using debug keys. 2021-12-10 17:45:54 +01:00
James Seibel 8292dc67c0 Remove Dynamic and Triangular Lod Templates 2021-12-09 21:49:48 -06:00
James Seibel 1b82acec9f Fix a incorrect merge 2021-12-09 18:17:50 -06:00
Morippi da4f423d10 Added maps to cache the biome blocks couples 2021-12-09 17:30:43 +01:00
Morippi 5a4c04b5a3 added getters and creators in the new format. 2021-12-09 17:30:18 +01:00
cola98765 cd50be6531 fix FAR_FIRST and AUTO options 2021-12-09 15:20:31 +01:00
cola98765 5d4698621c buffer rebuild due to time is no longer needed 2021-12-09 14:36:24 +01:00
Morippi aba392ace4 Fixed another small bug 2021-12-09 14:31:25 +01:00
Morippi d1c0ea123a Merge remote-tracking branch 'origin/main' into main 2021-12-09 14:30:12 +01:00
Morippi 816efb2837 Fixed a small bug 2021-12-09 14:30:06 +01:00
cola98765 0221a39819 wrong "-" sign 2021-12-09 14:20:30 +01:00
Morippi cc08707f32 Fixed some part of the new format 2021-12-09 13:50:59 +01:00
Morippi 8db253f886 Merge remote-tracking branch 'origin/main' into main 2021-12-09 13:50:48 +01:00
Morippi f59bdf15d7 Created the BlockBiomeCouple 2021-12-09 13:50:37 +01:00
cola98765 ac0d439b3f Merge remote-tracking branch 'origin/main' into main 2021-12-09 13:32:41 +01:00
cola98765 4e652c7573 just switch B and R and should be fine 2021-12-09 13:31:56 +01:00
Morippi 3686bfb4e0 switched two formats 2021-12-09 12:58:52 +01:00
Morippi fe1b2c2683 Added new format classes 2021-12-09 12:57:35 +01:00
cola98765 d5ed9a22fa more consistent skylight-blocklight order 2021-12-09 11:59:37 +01:00
cola98765 e3f9c974f8 partially fix shader based light 2021-12-09 11:57:05 +01:00
cola98765 daead98102 move where in region pos is calculated 2021-12-09 09:49:57 +01:00
James Seibel 5c31927d54 Add shaders files for shader based lighting (ops) 2021-12-08 23:07:10 -06:00
James Seibel f9fa1a5260 merge GLProxy 2021-12-08 23:05:14 -06:00
James Seibel d0472ee56d Add (buggy, unfinished) shader based lighting 2021-12-08 22:59:37 -06:00
Ran e4e21d2dc8 Merge branch 'main' into 'main'
Fix for WindowOS Render null Error

See merge request jeseibel/distant-horizons-core!3
2021-12-08 14:18:05 +00:00
TomTheFurry 79e4dce569 Fix for WindowOS Render null Error 2021-12-08 14:18:04 +00:00
cola98765 408f09c0f4 added and commented out time testing code 2021-12-07 13:05:34 +01:00
cola98765 65bfedc942 Merge remote-tracking branch 'origin/main' into main 2021-12-06 10:21:33 +01:00
cola98765 fab99cd4ec fix LOWEST HorizontalQuality 2021-12-06 10:21:15 +01:00
cola98765 019f4b7c1e fix LOWEST HorizontalQuality 2021-12-06 10:16:51 +01:00
cola98765 f3b6b15bcb reverted DrawResolutionOffset 2021-12-05 20:03:17 +01:00
cola98765 7c086cdc40 HorizontalScale is now a number 2-32 2021-12-05 17:03:38 +01:00
cola98765 5617e1312c updated DYNAMIC VanillaOverdraw setting. 2021-12-05 16:43:21 +01:00
cola98765 9dd51bfdde added DrawResolutionOffset to work with DrawResolution 2021-12-05 16:37:32 +01:00
James Seibel c9dc998eef Fix a few buffer building issues 2021-12-04 22:22:00 -06:00
Morippi 068df9d5e0 Change Box to VertexOptimizer and added the DataFormat folder with empty classes 2021-12-02 23:07:08 +01:00
cola98765 44dc7c96af UNTESTED: when loading and no save was found it will try to look in better DistanceGenerationMode AND VerticalQuality 2021-12-02 17:26:05 +01:00
cola98765 89cc3513f3 added save and load methods to be later used with server 2021-12-02 11:42:49 +01:00
cola98765 7a94db77ef small movements will no longer trigger buffer regen 2021-12-02 10:24:14 +01:00
James Seibel f2fc669b37 comment out configOverride for release a1.5.4 2021-12-01 22:41:56 -06:00
James Seibel a3d4163b67 Add GpuUpload auto selection and re-arrange the config options 2021-12-01 22:32:05 -06:00
James Seibel aea4542616 Add AUTO to generationPriority (chooses best based on current world) 2021-12-01 18:53:56 -06:00
Ran a489810d68 Remove @Nullable 2021-11-30 23:05:39 +06:00
cola98765 806a1e99db couple more warnings 2021-11-30 12:05:05 +01:00
cola98765 4843027e43 javadocs error (not really problem, but prevented me form scanning the code) 2021-11-30 11:50:36 +01:00
cola98765 af4ba453ca couple warnings 2021-11-30 11:45:47 +01:00
cola98765 adc5853f0b fix for potential bug with couple things at high altitudes 2021-11-30 11:21:03 +01:00
cola98765 c42ddd29a9 fix light on servers 2021-11-30 10:27:11 +01:00
James Seibel 9b7d3c083f Add buffer timeout and improve the uploading logic slightly 2021-11-29 21:23:45 -06:00
James Seibel 87bb4ae840 re-add applyConfigOverride 2021-11-29 20:18:14 -06:00
cola98765 b34f5e7f5f moved VERTICAL_OFFSET to DataPointUtil where WORLD_HEIGHT was 2021-11-29 09:09:24 +01:00
Eric a053a79d99 Simplify the createProjectionMatrix method and add VR support. 2021-11-28 21:33:12 -07:00
James Seibel 73c041e02f Fix a potential crash when teleporting 2021-11-28 18:14:28 -06:00
cola98765 f96a5fc794 potentially fix backwards compatibility 2021-11-29 00:20:47 +01:00
James Seibel d591458cd6 comment out the applyConfigOverrides for release a1.5.3 2021-11-28 16:54:11 -06:00
James Seibel 88305d8db3 Merge branch 'main' of gitlab.com:jeseibel/distant-horizons-core 2021-11-28 16:28:38 -06:00
James Seibel 40b0517656 Hopefully prevent a issue with linux file paths in GLProxy 2021-11-28 16:08:56 -06:00
cola98765 2289826363 implemented VERTICAL_OFFSET (-64) and change WORLD_HEIGHT (1024). those numbers mean the same as 1.17 "min_y" and "height" world settings.
TODO check if it's not significantly worse in performance.
2021-11-28 14:32:08 +01:00
James Seibel e1c2b2a0a9 Add a missing comment 2021-11-27 23:28:15 -06:00
James Seibel 70f7a2422b Add Legacy OpenGL vanilla fog removal 2021-11-27 23:16:45 -06:00
James Seibel 8ad6f184dd Add Black and white logo 2021-11-27 18:04:03 -06:00
James Seibel 4a93bde7be Improve the config descriptions 2021-11-27 16:17:01 -06:00
James Seibel b9dfd93b56 rename DistantHorizons_Server_Data -> Distant_Horizons_server_data 2021-11-27 14:48:34 -06:00
James Seibel 5e78c98f17 rename "lod server data" -> Distant_Horizons_Server_Data 2021-11-27 12:03:25 -06:00
James Seibel 31035ccb1e Add setUniform(color) 2021-11-27 10:32:04 -06:00
James Seibel e840a23d01 Add FogColorMode 2021-11-27 10:10:01 -06:00
James Seibel 08b8f8778a Merge branch 'skyfog' into 'main'
Added sky fog color option

See merge request jeseibel/distant-horizons-core!2
2021-11-27 15:38:59 +00:00
coolGi2007 044f87eef2 Added sky fog color option 2021-11-27 12:28:36 +00:00
James Seibel 30cd7fd4e0 remove a debug command 2021-11-26 21:20:32 -06:00
James Seibel fbf5dfaa9d Clean up Fog, remove Fast fog, close issue #77 (near-far incorrect center) 2021-11-26 21:13:54 -06:00
James Seibel 58d4bc7f0f Add fog as a fragment shader 2021-11-26 19:24:48 -06:00
James Seibel 511a771351 Add a few extra resource links to GLProxy 2021-11-25 12:00:52 -06:00
James Seibel e806098544 Fix rendering performance 2021-11-25 11:51:35 -06:00
James Seibel 4316cfbfd7 Merge branch 'ServerPatch' into 'main'
Fixed error when going into servers

See merge request jeseibel/distant-horizons-core!1
2021-11-25 14:02:51 +00:00
coolGi2007 6be4c0303f Fixed error when going into servers 2021-11-25 07:27:14 +00:00
James Seibel 7633c7bc70 Update Readme.md 2021-11-23 02:28:13 +00:00
James Seibel 0bc96a98cf Add support for disabling rendering 2021-11-21 22:14:46 -06:00
James Seibel a0529a310b Update Readme.md 2021-11-21 18:26:20 -06:00
James Seibel ace3c03019 Update IChunkWrapper.java 2021-11-21 15:28:19 -06:00
James Seibel 16b44695ec re-add the basic shaders 2021-11-21 15:27:33 -06:00
James Seibel 151ca3902f Replace MinecraftRenderWrapper with the interface 2021-11-21 15:27:23 -06:00
James Seibel 1ba24659bc Remove all Forge files 2021-11-21 14:01:39 -06:00
133 changed files with 6039 additions and 8063 deletions
+6 -56
View File
@@ -1,65 +1,15 @@
# 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:
<a href="https://www.youtube.com/watch?v=H2tnvEVbO1c" target="_blank">![Minecraft Level Of Detail (LOD) mod - Alpha 1.4](https://i.ytimg.com/vi_webp/H2tnvEVbO1c/mqdefault.webp)</a>
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)
This repo is for the Distant Horizons mod.
The purpose of this submodule is to isolate code that isn't tied to a specific version of minecraft. This prevents us from having duplicate code; reducing errors and potentially helping us port to different versions faster and easier.
Check out the mod's main GitLab page here:
https://gitlab.com/jeseibel/minecraft-lod-mod
## 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`
You shouldn't download this repo directly.
It should be automatically included when pulling the full mod.
## Open Source Acknowledgements
File diff suppressed because one or more lines are too long
Binary file not shown.

After

Width:  |  Height:  |  Size: 101 KiB

View File
+1
View File
@@ -0,0 +1 @@
<mxfile host="app.diagrams.net" modified="2021-12-22T02:14:44.485Z" agent="5.0 (Windows)" etag="8Lz4CpREcKLpQpROSPVl" version="16.0.3" type="gitlab"><diagram id="xLs7mM1S-vncSruOQYJG" name="Page-1">xZVNj5swEIZ/DcetAJdkc2yTbXvZVaQcuunNtSfgrsGRcRbor6+Jx4BDom3VSr1Enmc+7HnHOBFZl+1nTY/Fo+IgozTmbUQ2UZou71P724POAZIlDuRacIcmYCd+AsIY6UlwqINAo5Q04hhCpqoKmAkY1Vo1YdhByXDXI81hBnaMyjn9KrgpHL3P4pF/AZEXfuckRk9JfTCCuqBcNRNEHiKy1koZtyrbNcheO6+Ly/t0wzscTENlfichf/r2ohqemP2Pp+3dtn3s9tkdVnml8oQNCwOlJeioTeeV0OpUceiLxRH52BQ2cHekrPc2dvSWFaaU1krsEsuCNtDePG8yqGBvD6gSjO5sCCasUDe8OMl7tJtxDInXtpiMYIGM4uTzofIojl2gPn+gVXpLq/TvtDoIKddKKn3OJYcDLBizvDZavcDEw5er73H8b9QlSSjv4n+rS2bqzlSFin/oP2lrMUnrWrBQyFB1aIV5Rk+/3vf8XYbWpp2EbTpvVLaV56nhspaZt8e8s+UT3VmBzx6Ti3nYftRJM3j7kzRU52Deuo7z+U4GmF2Zn2caJDXiNTzutaHiDlslbCM3r89Q15dwbWLW9FW6LLS4+MxXF4WcDrNC5zs2tH3t2llzfFxd+PgPRR5+AQ==</diagram></mxfile>
-234
View File
@@ -1,234 +0,0 @@
buildscript {
repositories {
maven { url = 'https://files.minecraftforge.net' }
mavenCentral()
// potential replacement in case of problems:
// https://dist.creeper.host/Sponge/maven
maven { url = 'https://repo.spongepowered.org/maven/' }
// used to download and compile dependencies from git repos
maven { url 'https://jitpack.io' }
}
dependencies {
classpath group: 'net.minecraftforge.gradle', name: 'ForgeGradle', version: '3.+', changing: true
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'
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.3'
group = 'com.seibel.lod'
archivesBaseName = 'Distant-Horizons_1.16.5'
sourceCompatibility = targetCompatibility = compileJava.sourceCompatibility = compileJava.targetCompatibility = '1.8' // Need this here so eclipse task generates correctly.
println('Java: ' + System.getProperty('java.version') + ' JVM: ' + System.getProperty('java.vm.version') + '(' + System.getProperty('java.vendor') + ') Arch: ' + System.getProperty('os.arch'))
minecraft {
// The mappings can be changed at any time, and must be in the following format.
// snapshot_YYYYMMDD Snapshot are built nightly.
// stable_# Stables are built at the discretion of the MCP team.
// Use non-default mappings at your own risk. they may not always work.
// Simply re-run your setup task after changing the mappings to update your workspace.
mappings channel: 'official', version: '1.16.5'
// makeObfSourceJar = false // an Srg named sources jar is made by default. uncomment this to disable.
accessTransformer = file('src/main/resources/META-INF/accesstransformer.cfg')
// Default run configurations.
// These can be tweaked, removed, or duplicated as needed.
runs {
client {
workingDirectory project.file('run')
arg "-mixin.config=lod.mixins.json"
// Recommended logging data for a userdev environment
// The markers can be changed as needed.
// "SCAN": For mods scan.
// "REGISTRIES": For firing of registry events.
// "REGISTRYDUMP": For getting the contents of all registries.
property 'forge.logging.markers', 'REGISTRIES'
// Recommended logging level for the console
// You can set various levels here.
// Please read: https://stackoverflow.com/questions/2031163/when-to-use-the-different-log-levels
property 'forge.logging.console.level', 'debug'
mods {
examplemod {
source sourceSets.main
}
}
}
server {
workingDirectory project.file('run')
arg "-mixin.config=lod.mixins.json"
// Recommended logging data for a userdev environment
// The markers can be changed as needed.
// "SCAN": For mods scan.
// "REGISTRIES": For firing of registry events.
// "REGISTRYDUMP": For getting the contents of all registries.
property 'forge.logging.markers', 'REGISTRIES'
// Recommended logging level for the console
// You can set various levels here.
// Please read: https://stackoverflow.com/questions/2031163/when-to-use-the-different-log-levels
property 'forge.logging.console.level', 'debug'
mods {
examplemod {
source sourceSets.main
}
}
}
data {
workingDirectory project.file('run')
// Recommended logging data for a userdev environment
// The markers can be changed as needed.
// "SCAN": For mods scan.
// "REGISTRIES": For firing of registry events.
// "REGISTRYDUMP": For getting the contents of all registries.
property 'forge.logging.markers', 'REGISTRIES'
// Recommended logging level for the console
// You can set various levels here.
// Please read: https://stackoverflow.com/questions/2031163/when-to-use-the-different-log-levels
property 'forge.logging.console.level', 'debug'
// Specify the modid for data generation, where to output the resulting resource, and where to look for existing resources.
args '--mod', 'lod', '--all', '--output', file('src/generated/resources/'), '--existing', file('src/main/resources/')
mods {
examplemod {
source sourceSets.main
}
}
}
}
}
// Include resources generated by data generators.
sourceSets.main.resources { srcDir 'src/generated/resources' }
// this is required so that we can use
// jitpack in the dependencies section below
repositories {
mavenCentral()
// used to download and compile dependencies from git repos
maven { url 'https://jitpack.io' }
}
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'
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
// a way to duplicate lambda functions (which features use)
// so for now I'm not sure what to do.
//implementation 'io.github.kostaskougios:cloning:1.10.3'
//
//implementation ('com.esotericsoftware:kryo:5.1.1') {
// exclude group: "org.objenesis"
//}
//implementation 'org.objenesis:objenesis:3.2'
// You may put jars on which you depend on in ./libs or you may define them like so..
// compile "some.group:artifact:version:classifier"
// compile "some.group:artifact:version"
// Real examples
// compile 'com.mod-buildcraft:buildcraft:6.0.8:dev' // adds buildcraft to the dev env
// compile 'com.googlecode.efficient-java-matrix-library:ejml:0.24' // adds ejml to the dev env
// The 'provided' configuration is for optional dependencies that exist at compile-time but might not at runtime.
// provided 'com.mod-buildcraft:buildcraft:6.0.8:dev'
// These dependencies get remapped to your current MCP mappings
// deobf 'com.mod-buildcraft:buildcraft:6.0.8:dev'
// For more info...
// http://www.gradle.org/docs/current/userguide/artifact_dependencies_tutorial.html
// http://www.gradle.org/docs/current/userguide/dependency_management.html
}
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 {
attributes([
"Specification-Title": "LOD",
"Specification-Version": "1", // We are version 1 of ourselves
"Implementation-Title": project.name,
"Implementation-Version": "1",
"Implementation-Timestamp": new Date().format("yyyy-MM-dd'T'HH:mm:ssZ"),
"MixinConfigs": "lod.mixins.json",
])
}
}
// Example configuration to allow publishing using the maven-publish task
// This is the preferred method to reobfuscate your jar file
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')
publishing {
publications {
mavenJava(MavenPublication) {
artifact jar
}
}
repositories {
maven {
url "file:///${project.projectDir}/mcmodsrepo"
}
}
}
mixin {
add sourceSets.main, "lod.refmap.json"
}
-4
View File
@@ -1,4 +0,0 @@
# Sets default memory used for gradle commands. Can be overridden by user or command line properties.
# This is required to provide enough memory for the Minecraft decompilation process.
org.gradle.jvmargs=-Xmx3G
org.gradle.daemon=false
Binary file not shown.
-5
View File
@@ -1,5 +0,0 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.3-bin.zip
Vendored
-172
View File
@@ -1,172 +0,0 @@
#!/usr/bin/env sh
##############################################################################
##
## Gradle start up script for UN*X
##
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG=`dirname "$PRG"`"/$link"
fi
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >/dev/null
APP_HOME="`pwd -P`"
cd "$SAVED" >/dev/null
APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS=""
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
warn () {
echo "$*"
}
die () {
echo
echo "$*"
echo
exit 1
}
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "`uname`" in
CYGWIN* )
cygwin=true
;;
Darwin* )
darwin=true
;;
MINGW* )
msys=true
;;
NONSTOP* )
nonstop=true
;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
else
JAVACMD="$JAVA_HOME/bin/java"
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD="java"
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
MAX_FD="$MAX_FD_LIMIT"
fi
ulimit -n $MAX_FD
if [ $? -ne 0 ] ; then
warn "Could not set maximum file descriptor limit: $MAX_FD"
fi
else
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
fi
fi
# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
# For Cygwin, switch paths to Windows format before running java
if $cygwin ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=`cygpath --unix "$JAVACMD"`
# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
SEP=""
for dir in $ROOTDIRSRAW ; do
ROOTDIRS="$ROOTDIRS$SEP$dir"
SEP="|"
done
OURCYGPATTERN="(^($ROOTDIRS))"
# Add a user-defined pattern to the cygpath arguments
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
fi
# Now convert the arguments - kludge to limit ourselves to /bin/sh
i=0
for arg in "$@" ; do
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
else
eval `echo args$i`="\"$arg\""
fi
i=$((i+1))
done
case $i in
(0) set -- ;;
(1) set -- "$args0" ;;
(2) set -- "$args0" "$args1" ;;
(3) set -- "$args0" "$args1" "$args2" ;;
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
esac
fi
# Escape application args
save () {
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
echo " "
}
APP_ARGS=$(save "$@")
# Collect all arguments for the java command, following the shell quoting and substitution rules
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
cd "$(dirname "$0")"
fi
exec "$JAVACMD" "$@"
Vendored
-84
View File
@@ -1,84 +0,0 @@
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS=
@rem Find java.exe
if defined JAVA_MC_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto init
echo.
echo ERROR: JAVA_MC_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_MC_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_MC_HOME=%JAVA_MC_HOME:"=%
set JAVA_EXE=%JAVA_MC_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto init
echo.
echo ERROR: JAVA_MC_HOME is set to an invalid directory: %JAVA_MC_HOME%
echo.
echo Please set the JAVA_MC_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:init
@rem Get command-line arguments, handling Windows variants
if not "%OS%" == "Windows_NT" goto win9xME_args
:win9xME_args
@rem Slurp the command line arguments.
set CMD_LINE_ARGS=
set _SKIP=2
:win9xME_args_slurp
if "x%~1" == "x" goto execute
set CMD_LINE_ARGS=%*
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega
@@ -28,7 +28,7 @@ package com.seibel.lod.core;
* Pretty much all of the mod stems from there.
*
* @author James Seibel
* @version 11-13-2021
* @version 11-29-2021
*/
public final class ModInfo
{
@@ -38,5 +38,5 @@ public final class ModInfo
/** Human readable version of NAME */
public static final String READABLE_NAME = "Distant Horizons";
public static final String API = "LodAPI";
public static final String VERSION = "a1.5.3";
public static final String VERSION = "a1.5.4";
}
@@ -23,14 +23,12 @@ import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import com.seibel.lod.core.ModInfo;
import com.seibel.lod.core.builders.worldGeneration.LodGenWorker;
import com.seibel.lod.core.objects.lod.LodDimension;
import com.seibel.lod.core.objects.math.Mat4f;
import com.seibel.lod.core.render.GLProxy;
import com.seibel.lod.core.render.LodRenderer;
import com.seibel.lod.core.util.DetailDistanceUtil;
import com.seibel.lod.core.util.SingletonHandler;
import com.seibel.lod.core.util.ThreadMapUtil;
import com.seibel.lod.core.wrapperInterfaces.config.ILodConfigWrapperSingleton;
import com.seibel.lod.core.wrapperInterfaces.minecraft.IMinecraftRenderWrapper;
import com.seibel.lod.core.wrapperInterfaces.minecraft.IMinecraftWrapper;
@@ -42,7 +40,7 @@ import com.seibel.lod.core.wrapperInterfaces.minecraft.IProfilerWrapper;
* Specifically for the client.
*
* @author James Seibel
* @version 11-12-2021
* @version 12-8-2021
*/
public class ClientApi
{
@@ -63,6 +61,7 @@ public class ClientApi
private boolean firstTimeSetupComplete = false;
private boolean configOverrideReminderPrinted = false;
public boolean rendererDisabledBecauseOfExceptions = false;
private ClientApi()
@@ -103,19 +102,32 @@ public class ClientApi
lodDim.expandOrLoadRegionsAsync(MC.getPlayerBlockPos().getX(), MC.getPlayerBlockPos().getZ());
// Note to self:
// if "unspecified" shows up in the pie chart, it is
// possibly because the amount of time between sections
// is too small for the profiler to measure
IProfilerWrapper profiler = MC.getProfiler();
profiler.pop(); // get out of "terrain"
profiler.push("LOD");
if (CONFIG.client().advanced().debugging().getDrawLods())
{
// Note to self:
// if "unspecified" shows up in the pie chart, it is
// possibly because the amount of time between sections
// is too small for the profiler to measure
IProfilerWrapper profiler = MC.getProfiler();
profiler.pop(); // get out of "terrain"
profiler.push("LOD");
if (!rendererDisabledBecauseOfExceptions) {
try {
ClientApi.renderer.drawLODs(lodDim, mcModelViewMatrix, mcProjectionMatrix, partialTicks, MC.getProfiler());
} catch (RuntimeException e) {
rendererDisabledBecauseOfExceptions = true;
try {
//ClientApi.renderer.ma ();
} catch (RuntimeException welpLookLikeWeWillLeakResource) {}
throw e;
}
}
profiler.pop(); // end LOD
profiler.push("terrain"); // go back into "terrain"
}
ClientApi.renderer.drawLODs(lodDim, mcModelViewMatrix, mcProjectionMatrix, partialTicks, MC.getProfiler());
profiler.pop(); // end LOD
profiler.push("terrain"); // go back into "terrain"
// these can't be set until after the buffers are built (in renderer.drawLODs)
@@ -138,15 +150,26 @@ public class ClientApi
{
MC.sendChatMessage(ModInfo.READABLE_NAME + " experimental build " + ModInfo.VERSION);
MC.sendChatMessage("You are running a unsupported version of the mod!");
MC.sendChatMessage("==========================================");
MC.sendChatMessage("SEIZURE WARNING: Flashing lights expected!"); // remove this line when the lighting shaders are fixed
MC.sendChatMessage("==========================================");
MC.sendChatMessage("Here be dragons!");
configOverrideReminderPrinted = true;
}
// CONFIG.client().worldGenerator().setDistanceGenerationMode(DistanceGenerationMode.SURFACE);
// CONFIG.client().worldGenerator().setDistanceGenerationMode(DistanceGenerationMode.FULL);
// CONFIG.client().worldGenerator().setGenerationPriority(GenerationPriority.AUTO);
// CONFIG.client().graphics().advancedGraphics().setGpuUploadMethod(GpuUploadMethod.BUFFER_STORAGE);
// CONFIG.client().graphics().quality().setLodChunkRenderDistance(128);
// CONFIG.client().graphics().fogQuality().setFogDrawMode(FogDrawMode.FOG_ENABLED);
// CONFIG.client().graphics().fogQuality().setFogDistance(FogDistance.FAR);
// CONFIG.client().graphics().fogQuality().setDisableVanillaFog(true);
// CONFIG.client().advanced().buffers().setRebuildTimes(BufferRebuildTimes.FREQUENT);
CONFIG.client().advanced().debugging().setDebugKeybindingsEnabled(true);
@@ -159,6 +182,8 @@ public class ClientApi
// Lod maintenance //
//=================//
// FIXME: I need a onLastFrameCleanup() callback in Render Thread... Which calls renderer.cleanup()
/** This event is called once during the first frame Minecraft renders in the world. */
public void firstFrameSetup()
{
@@ -168,14 +193,6 @@ public class ClientApi
firstTimeSetupComplete = true;
}
/** this method reset some static data every time we change world */
private void resetMod()
{
// TODO when should this be used?
ThreadMapUtil.clearMaps();
LodGenWorker.restartExecutorService();
}
@@ -21,6 +21,7 @@ package com.seibel.lod.core.api;
import org.lwjgl.glfw.GLFW;
import com.seibel.lod.core.builders.lodBuilding.LodBuilder;
import com.seibel.lod.core.builders.worldGeneration.LodWorldGenerator;
import com.seibel.lod.core.enums.config.DistanceGenerationMode;
import com.seibel.lod.core.objects.lod.LodDimension;
@@ -80,7 +81,8 @@ public class EventApi
if (lodDim == null)
return;
LodWorldGenerator.INSTANCE.queueGenerationRequests(lodDim, ClientApi.renderer, ApiShared.lodBuilder);
// FIXME: This is in server thread. We shouldn't be accessing the client's renderer!
LodWorldGenerator.INSTANCE.queueGenerationRequests(lodDim, ApiShared.lodBuilder);
}
@@ -103,7 +105,9 @@ public class EventApi
/** This is also called when a new dimension loads */
public void worldLoadEvent(IWorldWrapper world)
{
DataPointUtil.worldHeight = world.getHeight();
DataPointUtil.WORLD_HEIGHT = world.getHeight();
LodBuilder.MIN_WORLD_HEIGHT = world.getMinHeight(); // This updates the World height
//LodNodeGenWorker.restartExecutorService();
//ThreadMapUtil.clearMaps();
@@ -115,13 +119,32 @@ public class EventApi
ClientApi.renderer.regenerateLODsNextFrame();
}
/** This is also called when the user disconnects from a server+ */
public void worldUnloadEvent()
{
// the player just unloaded a world/dimension
ThreadMapUtil.clearMaps();
// ClientApi.renderer.markForCleanup();
// ClientApi.renderer.destroyBuffers();
new Thread(() -> checkIfDisconnectedFromServer()).start();
}
private void checkIfDisconnectedFromServer()
{
try
{
// world unloading events are called before disconnecting from the server,
// so we need to wait a second for MC to disconnect
Thread.sleep(1000);
}
catch (InterruptedException e)
{
// this should never happen, but just in case
e.printStackTrace();
}
if (MC.getWrappedClientWorld() == null)
if (MC.getWrappedClientWorld() == null || (!MC.connectedToServer() && !MC.hasSinglePlayerServer()))
{
// the player just left the server
@@ -129,19 +152,20 @@ public class EventApi
// if this isn't done unfinished tasks may be left in the queue
// preventing new LodChunks form being generated
//LodNodeGenWorker.restartExecutorService(); // TODO why was this commented out? -James
//ThreadMapUtil.clearMaps();
LodWorldGenerator.INSTANCE.restartExecutorService();
LodWorldGenerator.INSTANCE.numberOfChunksWaitingToGenerate.set(0);
ApiShared.lodWorld.deselectWorld();
// prevent issues related to the buffer builder
// breaking when changing worlds.
// breaking or retaining previous data when changing worlds.
ClientApi.renderer.destroyBuffers();
ClientApi.renderer.requestCleanup();
recalculateWidths = true;
// TODO: Check if after the refactoring, is this still needed
ClientApi.renderer = new LodRenderer(ApiShared.lodBufferBuilderFactory);
ClientApi.INSTANCE.rendererDisabledBecauseOfExceptions = false;
// make sure the nulled objects are freed.
// (this prevents an out of memory error when
@@ -167,14 +191,16 @@ public class EventApi
{
if (CONFIG.client().advanced().debugging().getDebugKeybindingsEnabled())
{
if (key == GLFW.GLFW_KEY_F4 && keyAction == GLFW.GLFW_PRESS)
if (key == GLFW.GLFW_KEY_F8 && keyAction == GLFW.GLFW_PRESS)
{
CONFIG.client().advanced().debugging().setDebugMode(CONFIG.client().advanced().debugging().getDebugMode().getNext());
MC.sendChatMessage("F8: Set debug mode " + CONFIG.client().advanced().debugging().getDebugMode());
}
if (key == GLFW.GLFW_KEY_F6 && keyAction == GLFW.GLFW_PRESS)
{
CONFIG.client().advanced().debugging().setDrawLods(!CONFIG.client().advanced().debugging().getDrawLods());
MC.sendChatMessage("F6: Set rendering " + CONFIG.client().advanced().debugging().getDrawLods());
}
}
}
@@ -205,7 +231,7 @@ public class EventApi
int newWidth = (int) Math.ceil(chunksWide / (float) LodUtil.REGION_WIDTH_IN_CHUNKS);
// make sure we have an odd number of regions
newWidth += (newWidth & 1) == 0 ? 1 : 2;
newWidth += (newWidth & 1) == 0 ? 1 : 0;
// do the dimensions need to change in size?
if (ApiShared.lodBuilder.defaultDimensionWidthInRegions != newWidth || recalculateWidths)
@@ -0,0 +1,160 @@
/*
* 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.core.builders.bufferBuilding;
import java.util.Map;
import com.seibel.lod.core.enums.LodDirection;
import com.seibel.lod.core.enums.rendering.DebugMode;
import com.seibel.lod.core.objects.VertexOptimizer;
import com.seibel.lod.core.objects.opengl.LodBufferBuilder;
import com.seibel.lod.core.util.ColorUtil;
import com.seibel.lod.core.util.DataPointUtil;
import com.seibel.lod.core.util.LodUtil;
import static com.seibel.lod.core.builders.lodBuilding.LodBuilder.MIN_WORLD_HEIGHT;
/**
* Builds LODs as rectangular prisms.
* @author James Seibel
* @version 12-8-2021
*/
public class CubicLodTemplate
{
//TODO make it a config
static int cullingRange = 128;
public static void addLodToBuffer(LodBufferBuilder buffer, int playerX, int playerZ, long data, Map<LodDirection, long[]> adjData,
byte detailLevel, int posX, int posZ, VertexOptimizer vertexOptimizer, DebugMode debugging, boolean[] adjShadeDisabled)
{
if (vertexOptimizer == null)
return;
// equivalent to 2^detailLevel
int blockWidth = 1 << detailLevel;
int color;
if (debugging != DebugMode.OFF)
color = LodUtil.DEBUG_DETAIL_LEVEL_COLORS[detailLevel].getRGB();
else
color = DataPointUtil.getColor(data);
generateBoundingBox(
vertexOptimizer,
DataPointUtil.getHeight(data),
DataPointUtil.getDepth(data),
blockWidth,
posX * blockWidth, 0, posZ * blockWidth, // x, y, z offset
playerX,
playerZ,
adjData,
color,
DataPointUtil.getLightSkyAlt(data),
DataPointUtil.getLightBlock(data),
adjShadeDisabled);
addBoundingBoxToBuffer(buffer, vertexOptimizer);
}
/** add the given position and color to the buffer */
public static void addPosAndColor(LodBufferBuilder buffer,
float x, float y, float z,
int color, byte skyLightValue, byte blockLightValue)
{
// TODO transparency re-add by replacing the color 255 with "ColorUtil.getAlpha(color)"
buffer.position(x, y, z)
.color(ColorUtil.getRed(color), ColorUtil.getGreen(color), ColorUtil.getBlue(color), 255)
.minecraftLightValue(skyLightValue).minecraftLightValue(blockLightValue)
.endVertex();
}
private static void generateBoundingBox(VertexOptimizer vertexOptimizer,
int height, int depth, int width,
double xOffset, double yOffset, double zOffset,
int playerX, int playerZ,
Map<LodDirection, long[]> adjData,
int color, byte skyLight, byte 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 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 = -playerX;
double z = -playerZ;
vertexOptimizer.reset();
vertexOptimizer.setColor(color, adjShadeDisabled);
vertexOptimizer.setLights(skyLight, blockLight);
vertexOptimizer.setWidth(width, height - depth, width);
vertexOptimizer.setOffset((int) (xOffset + x), (int) (depth + yOffset), (int) (zOffset + z));
vertexOptimizer.setAdjData(adjData);
}
private static void addBoundingBoxToBuffer(LodBufferBuilder buffer, VertexOptimizer vertexOptimizer)
{
int color;
byte skyLight;
byte blockLight;
for (LodDirection lodDirection : VertexOptimizer.DIRECTIONS)
{
//if(vertexOptimizer.isCulled(lodDirection))
// continue;
// culling
if (lodDirection == LodDirection.NORTH && vertexOptimizer.getZ(lodDirection, 0) < -cullingRange
|| lodDirection == LodDirection.EAST && vertexOptimizer.getX(lodDirection, 0) > cullingRange
|| lodDirection == LodDirection.SOUTH && vertexOptimizer.getZ(lodDirection, 0) > cullingRange
|| lodDirection == LodDirection.WEST && vertexOptimizer.getX(lodDirection, 0) < -cullingRange)
continue;
int verticalFaceIndex = 0;
while (vertexOptimizer.shouldRenderFace(lodDirection, verticalFaceIndex))
{
for (int vertexIndex = 0; vertexIndex < 6; vertexIndex++)
{
skyLight = vertexOptimizer.getSkyLight(lodDirection, verticalFaceIndex);
blockLight = (byte) vertexOptimizer.getBlockLight();
color = vertexOptimizer.getColor(lodDirection);
addPosAndColor(buffer,
vertexOptimizer.getX(lodDirection, vertexIndex),
vertexOptimizer.getY(lodDirection, vertexIndex, verticalFaceIndex) + MIN_WORLD_HEIGHT,
vertexOptimizer.getZ(lodDirection, vertexIndex),
color, skyLight, blockLight );
}
verticalFaceIndex++;
}
}
}
}
File diff suppressed because it is too large Load Diff
@@ -1,52 +0,0 @@
/*
* 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.core.builders.bufferBuilding.lodTemplates;
import java.util.Map;
import com.seibel.lod.core.enums.LodDirection;
import com.seibel.lod.core.enums.rendering.DebugMode;
import com.seibel.lod.core.objects.Box;
import com.seibel.lod.core.objects.opengl.LodBufferBuilder;
import com.seibel.lod.core.util.ColorUtil;
import com.seibel.lod.core.wrapperInterfaces.block.AbstractBlockPosWrapper;
/**
* This is the abstract class used to create different
* BufferBuilders.
* @author James Seibel
* @version 11-13-2021
*/
public abstract class AbstractLodTemplate
{
/** Uploads the given LOD to the buffer. */
public abstract void addLodToBuffer(LodBufferBuilder buffer, AbstractBlockPosWrapper bufferCenterBlockPos, long data, Map<LodDirection, long[]> adjData,
byte detailLevel, int posX, int posZ, Box box, DebugMode debugging, boolean[] adjShadeDisabled);
/** add the given position and color to the buffer */
protected void addPosAndColor(LodBufferBuilder buffer,
float x, float y, float z,
int color)
{
// 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();
}
}
@@ -1,142 +0,0 @@
/*
* 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.core.builders.bufferBuilding.lodTemplates;
import java.util.Map;
import com.seibel.lod.core.enums.LodDirection;
import com.seibel.lod.core.enums.rendering.DebugMode;
import com.seibel.lod.core.objects.Box;
import com.seibel.lod.core.objects.opengl.LodBufferBuilder;
import com.seibel.lod.core.util.ColorUtil;
import com.seibel.lod.core.util.DataPointUtil;
import com.seibel.lod.core.util.LodUtil;
import com.seibel.lod.core.wrapperInterfaces.block.AbstractBlockPosWrapper;
/**
* Builds LODs as rectangular prisms.
* @author James Seibel
* @version 11-8-2021
*/
public class CubicLodTemplate extends AbstractLodTemplate
{
public CubicLodTemplate()
{
}
@Override
public void addLodToBuffer(LodBufferBuilder buffer, AbstractBlockPosWrapper bufferCenterBlockPos, long data, Map<LodDirection, long[]> adjData,
byte detailLevel, int posX, int posZ, Box box, DebugMode debugging, boolean[] adjShadeDisabled)
{
if (box == null)
return;
// equivalent to 2^detailLevel
int blockWidth = 1 << detailLevel;
int color;
if (debugging != DebugMode.OFF)
color = LodUtil.DEBUG_DETAIL_LEVEL_COLORS[detailLevel].getRGB();
else
color = DataPointUtil.getColor(data);
generateBoundingBox(
box,
DataPointUtil.getHeight(data),
DataPointUtil.getDepth(data),
blockWidth,
posX * blockWidth, 0, posZ * blockWidth, // x, y, z offset
bufferCenterBlockPos,
adjData,
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,
AbstractBlockPosWrapper bufferCenterBlockPos,
Map<LodDirection, 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 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, 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);
box.setAdjData(adjData);
}
private void addBoundingBoxToBuffer(LodBufferBuilder buffer, Box box)
{
int color;
int skyLight;
int blockLight;
for (LodDirection lodDirection : Box.DIRECTIONS)
{
if(box.isCulled(lodDirection))
continue;
int verticalFaceIndex = 0;
while (box.shouldRenderFace(lodDirection, verticalFaceIndex))
{
for (int vertexIndex = 0; vertexIndex < 6; vertexIndex++)
{
color = box.getColor(lodDirection);
skyLight = box.getSkyLight(lodDirection, verticalFaceIndex);
blockLight = box.getBlockLight();
color = ColorUtil.applyLightValue(color, skyLight, blockLight);
addPosAndColor(buffer,
box.getX(lodDirection, vertexIndex),
box.getY(lodDirection, vertexIndex, verticalFaceIndex),
box.getZ(lodDirection, vertexIndex),
color);
}
verticalFaceIndex++;
}
}
}
}
@@ -1,48 +0,0 @@
/*
* 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.core.builders.bufferBuilding.lodTemplates;
import java.util.Map;
import com.seibel.lod.core.api.ClientApi;
import com.seibel.lod.core.enums.LodDirection;
import com.seibel.lod.core.enums.rendering.DebugMode;
import com.seibel.lod.core.objects.Box;
import com.seibel.lod.core.objects.opengl.LodBufferBuilder;
import com.seibel.lod.core.wrapperInterfaces.block.AbstractBlockPosWrapper;
/**
* TODO DynamicLodTemplate
* Chunks smoothly transition between
* each other, unless a neighboring chunk
* is at a significantly different height.
* @author James Seibel
* @version 06-16-2021
*/
public class DynamicLodTemplate extends AbstractLodTemplate
{
@Override
public void addLodToBuffer(LodBufferBuilder buffer, AbstractBlockPosWrapper bufferCenterBlockPos, long data, Map<LodDirection, long[]> adjData,
byte detailLevel, int posX, int posZ, Box box, DebugMode debugging, boolean[] adjShadeDisabled)
{
ClientApi.LOGGER.error(DynamicLodTemplate.class.getSimpleName() + " is not implemented!");
}
}
@@ -1,46 +0,0 @@
/*
* 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.core.builders.bufferBuilding.lodTemplates;
import java.util.Map;
import com.seibel.lod.core.api.ClientApi;
import com.seibel.lod.core.enums.LodDirection;
import com.seibel.lod.core.enums.rendering.DebugMode;
import com.seibel.lod.core.objects.Box;
import com.seibel.lod.core.objects.opengl.LodBufferBuilder;
import com.seibel.lod.core.wrapperInterfaces.block.AbstractBlockPosWrapper;
/**
* TODO #21 TriangularLodTemplate
* Builds each LOD chunk as a singular rectangular prism.
* @author James Seibel
* @version 06-16-2021
*/
public class TriangularLodTemplate extends AbstractLodTemplate
{
@Override
public void addLodToBuffer(LodBufferBuilder buffer, AbstractBlockPosWrapper bufferCenterBlockPos, long data, Map<LodDirection, long[]> adjData,
byte detailLevel, int posX, int posZ, Box box, DebugMode debugging, boolean[] adjShadeDisabled)
{
ClientApi.LOGGER.error(DynamicLodTemplate.class.getSimpleName() + " is not implemented!");
}
}
@@ -35,12 +35,9 @@ import com.seibel.lod.core.util.LodThreadFactory;
import com.seibel.lod.core.util.LodUtil;
import com.seibel.lod.core.util.SingletonHandler;
import com.seibel.lod.core.util.ThreadMapUtil;
import com.seibel.lod.core.wrapperInterfaces.IWrapperFactory;
import com.seibel.lod.core.wrapperInterfaces.block.AbstractBlockPosWrapper;
import com.seibel.lod.core.wrapperInterfaces.block.IBlockColorSingletonWrapper;
import com.seibel.lod.core.wrapperInterfaces.block.IBlockColorWrapper;
import com.seibel.lod.core.wrapperInterfaces.block.IBlockShapeWrapper;
import com.seibel.lod.core.wrapperInterfaces.chunk.AbstractChunkPosWrapper;
import com.seibel.lod.core.wrapperInterfaces.chunk.IChunkWrapper;
import com.seibel.lod.core.wrapperInterfaces.config.ILodConfigWrapperSingleton;
import com.seibel.lod.core.wrapperInterfaces.minecraft.IMinecraftWrapper;
@@ -54,18 +51,18 @@ import com.seibel.lod.core.wrapperInterfaces.world.IWorldWrapper;
* @author Cola
* @author Leonardo Amato
* @author James Seibel
* @version 10-22-2021
* @version 12-11-2021
*/
@SuppressWarnings("GrazieInspection")
public class LodBuilder
{
private static final IMinecraftWrapper MC = SingletonHandler.get(IMinecraftWrapper.class);
private static final IBlockColorSingletonWrapper BLOCK_COLOR = SingletonHandler.get(IBlockColorSingletonWrapper.class);
private static final IWrapperFactory FACTORY = SingletonHandler.get(IWrapperFactory.class);
private static final IBlockColorSingletonWrapper BLOCK_COLOR = SingletonHandler.get(IBlockColorSingletonWrapper.class);
/** If no blocks are found in the area in determineBottomPointForArea return this */
public static final short DEFAULT_DEPTH = 0;
/** If no blocks are found in the area in determineHeightPointForArea return this */
public static final short DEFAULT_HEIGHT = 0;
/** This cannot be final! Different world have different height, and in menu, this causes Null Exceptions*/
//public static final short MIN_WORLD_HEIGHT = MC.getWrappedClientWorld().getMinHeight();
public static short MIN_WORLD_HEIGHT = 0; // Currently modified in EventApi.onWorldLoaded(...)
/** Minecraft's max light value */
public static final short DEFAULT_MAX_LIGHT = 15;
@@ -83,6 +80,7 @@ public class LodBuilder
//public static final boolean useExperimentalLighting = true;
private static int timesToEdgeDetect = 1;
@@ -109,8 +107,9 @@ public class LodBuilder
Thread thread = new Thread(() ->
{
try
{
//noinspection GrazieInspection
//try
//{
// we need a loaded client world in order to
// get the textures for blocks
if (MC.getWrappedClientWorld() == null)
@@ -133,14 +132,14 @@ public class LodBuilder
lodDim = lodWorld.getLodDimension(dim);
}
generateLodNodeFromChunk(lodDim, chunk, new LodBuilderConfig(generationMode));
}
catch (IllegalArgumentException | NullPointerException e)
{
e.printStackTrace();
// if the world changes while LODs are being generated
// they will throw errors as they try to access things that no longer
// exist.
}
//}
//catch (IllegalArgumentException | NullPointerException e)
//{
// e.printStackTrace();
// // if the world changes while LODs are being generated
// // they will throw errors as they try to access things that no longer
// // exist.
//}
});
lodGenThreadPool.execute(thread);
}
@@ -161,6 +160,7 @@ public class LodBuilder
public void generateLodNodeFromChunk(LodDimension lodDim, IChunkWrapper chunk, LodBuilderConfig config)
throws IllegalArgumentException
{
//long executeTime = System.currentTimeMillis();
if (chunk == null)
throw new IllegalArgumentException("generateLodFromChunk given a null chunk");
@@ -168,7 +168,7 @@ public class LodBuilder
int startZ;
LodRegion region = lodDim.getRegion(chunk.getPos().getRegionX(), chunk.getPos().getRegionZ());
LodRegion region = lodDim.getRegion(chunk.getRegionPosX(), chunk.getRegionPosZ());
if (region == null)
return;
@@ -196,18 +196,20 @@ public class LodBuilder
long[] data;
long[] dataToMergeVertical = createVerticalDataToMerge(detail, chunk, config, startX, startZ);
data = DataPointUtil.mergeMultiData(dataToMergeVertical, DataPointUtil.worldHeight / 2 + 1, DetailDistanceUtil.getMaxVerticalData(detailLevel));
data = DataPointUtil.mergeMultiData(dataToMergeVertical, DataPointUtil.WORLD_HEIGHT / 2 + 1, DetailDistanceUtil.getMaxVerticalData(detailLevel));
//lodDim.clear(detailLevel, posX, posZ);
if (data != null && data.length != 0)
{
posX = LevelPosUtil.convert((byte) 0, chunk.getPos().getX() * 16 + startX, detail.detailLevel);
posZ = LevelPosUtil.convert((byte) 0, chunk.getPos().getZ() * 16 + startZ, detail.detailLevel);
posX = LevelPosUtil.convert((byte) 0, chunk.getChunkPosX() * 16 + startX, detail.detailLevel);
posZ = LevelPosUtil.convert((byte) 0, chunk.getChunkPosZ() * 16 + startZ, detail.detailLevel);
lodDim.addVerticalData(detailLevel, posX, posZ, data, false);
}
}
lodDim.updateData(LodUtil.CHUNK_DETAIL_LEVEL, chunk.getPos().getX(), chunk.getPos().getZ());
lodDim.updateData(LodUtil.CHUNK_DETAIL_LEVEL, chunk.getChunkPosX(), chunk.getChunkPosZ());
//executeTime = System.currentTimeMillis() - executeTime;
//if (executeTime > 0) ClientApi.LOGGER.info("generateLodNodeFromChunk level: " + detailLevel + " time ms: " + executeTime);
}
/** creates a vertical DataPoint */
@@ -217,9 +219,7 @@ public class LodBuilder
int size = 1 << detail.detailLevel;
long[] dataToMerge = ThreadMapUtil.getBuilderVerticalArray(detail.detailLevel);
int verticalData = DataPointUtil.worldHeight / 2 + 1;
AbstractChunkPosWrapper chunkPos = chunk.getPos();
int verticalData = DataPointUtil.WORLD_HEIGHT / 2 + 1;
int height;
int depth;
int color;
@@ -236,49 +236,42 @@ public class LodBuilder
boolean hasCeiling = MC.getWrappedClientWorld().getDimensionType().hasCeiling();
boolean hasSkyLight = MC.getWrappedClientWorld().getDimensionType().hasSkyLight();
boolean isDefault;
AbstractBlockPosWrapper blockPos = FACTORY.createBlockPos();
int index;
for (index = 0; index < size * size; index++)
{
xRel = startX + index % size;
zRel = startZ + index / size;
xAbs = chunkPos.getMinBlockX() + xRel;
zAbs = chunkPos.getMinBlockZ() + zRel;
xAbs = chunk.getMinX() + xRel;
zAbs = chunk.getMinZ() + zRel;
//Calculate the height of the lod
yAbs = DataPointUtil.worldHeight + 1;
yAbs = chunk.getMaxY(xRel,zRel) - MIN_WORLD_HEIGHT;
int count = 0;
boolean topBlock = true;
if (yAbs <= 0);
dataToMerge[index * verticalData] = DataPointUtil.createVoidDataPoint(generation);
while (yAbs > 0)
{
height = determineHeightPointFrom(chunk, config, xRel, yAbs, zRel, blockPos);
height = determineHeightPointFrom(chunk, config, xAbs, yAbs, zAbs);
// If the lod is at the default height, it must be void data
if (height == DEFAULT_HEIGHT)
{
if (topBlock)
dataToMerge[index * verticalData] = DataPointUtil.createVoidDataPoint(generation);
if (height == 0)
break;
}
yAbs = height - 1;
// We search light on above air block
depth = determineBottomPointFrom(chunk, config, xRel, yAbs, zRel, blockPos);
depth = determineBottomPointFrom(chunk, config, xAbs, yAbs, zAbs, count < timesToEdgeDetect && !hasCeiling);
if (hasCeiling && topBlock)
{
yAbs = depth;
blockPos.set(xAbs, yAbs, zAbs);
light = getLightValue(chunk, blockPos, true, hasSkyLight, true);
color = generateLodColor(chunk, config, xAbs, yAbs, zAbs, blockPos);
blockPos.set(xAbs, yAbs - 1, zAbs);
light = getLightValue(chunk, xAbs,yAbs + MIN_WORLD_HEIGHT, zAbs, true, hasSkyLight, true);
color = generateLodColor(chunk, config, xAbs, yAbs, zAbs);
}
else
{
blockPos.set(xAbs, yAbs, zAbs);
light = getLightValue(chunk, blockPos, hasCeiling, hasSkyLight, topBlock);
color = generateLodColor(chunk, config, xRel, yAbs, zRel, blockPos);
blockPos.set(xAbs, yAbs + 1, zAbs);
light = getLightValue(chunk, xAbs, yAbs + MIN_WORLD_HEIGHT, zAbs, hasCeiling, hasSkyLight, topBlock);
color = generateLodColor(chunk, config, xAbs, yAbs, zAbs);
}
lightBlock = light & 0b1111;
lightSky = (light >> 4) & 0b1111;
@@ -297,34 +290,56 @@ public class LodBuilder
* Find the lowest valid point from the bottom.
* Used when creating a vertical LOD.
*/
private short determineBottomPointFrom(IChunkWrapper chunk, LodBuilderConfig config, int xAbs, int yAbs, int zAbs, AbstractBlockPosWrapper blockPos)
private short determineBottomPointFrom(IChunkWrapper chunk, LodBuilderConfig config, int xAbs, int yAbs, int zAbs, boolean strictEdge)
{
short depth = DEFAULT_DEPTH;
short depth = 0;
for (int y = yAbs; y >= 0; y--)
int colorOfBlock = 0;
if (strictEdge)
{
blockPos.set(xAbs, y, zAbs);
if (!isLayerValidLodPoint(chunk, blockPos))
colorOfBlock = chunk.getBlockColorWrapper(xAbs, yAbs, zAbs).getColor();
IBlockShapeWrapper block = chunk.getBlockShapeWrapper(xAbs, yAbs + 1, zAbs);
if (block != null && ((this.config.client().worldGenerator().getBlocksToAvoid().nonFull && block.isNonFull())
|| (this.config.client().worldGenerator().getBlocksToAvoid().noCollision && block.hasNoCollision())))
{
int aboveColorInt = chunk.getBlockColorWrapper(xAbs, yAbs + 1, zAbs).getColor();
if (aboveColorInt != 0)
colorOfBlock = aboveColorInt;
}
}
for (int y = yAbs - 1; y >= 0; y--)
{
if (!isLayerValidLodPoint(chunk, xAbs, y, zAbs))
{
depth = (short) (y + 1);
break;
}
if (strictEdge)
{
if (colorOfBlock != chunk.getBlockColorWrapper(xAbs, y, zAbs).getColor())
{
depth = (short) (y + 1);
break;
}
}
}
return depth;
}
/** Find the highest valid point from the Top */
private short determineHeightPointFrom(IChunkWrapper chunk, LodBuilderConfig config, int xAbs, int yAbs, int zAbs, AbstractBlockPosWrapper blockPos)
private short determineHeightPointFrom(IChunkWrapper chunk, LodBuilderConfig config, int xAbs, int yAbs, int zAbs)
{
short height = DEFAULT_HEIGHT;
//TODO find a way to skip bottom of the world
short height = 0;
if (config.useHeightmap)
height = (short) chunk.getHeightMapValue(xAbs, zAbs);
else
{
for (int y = yAbs; y >= 0; y--)
{
blockPos.set(xAbs, y, zAbs);
if (isLayerValidLodPoint(chunk, blockPos))
if (isLayerValidLodPoint(chunk, xAbs, y, zAbs))
{
height = (short) (y + 1);
break;
@@ -344,29 +359,27 @@ public class LodBuilder
* Generate the color for the given chunk using biome water color, foliage
* color, and grass color.
*/
private int generateLodColor(IChunkWrapper chunk, LodBuilderConfig builderConfig, int xRel, int yAbs, int zRel, AbstractBlockPosWrapper blockPos)
private int generateLodColor(IChunkWrapper chunk, LodBuilderConfig builderConfig, int x, int y, int z)
{
int colorInt;
if (builderConfig.useBiomeColors)
{
// I have no idea why I need to bit shift to the right, but
// if I don't the biomes don't show up correctly.
colorInt = chunk.getBiome(xRel, yAbs, zRel).getColorForBiome(xRel, zRel);
colorInt = chunk.getBiome(x, y, z).getColorForBiome(x, z);
}
else
{
blockPos.set(chunk.getPos().getMinBlockX() + xRel, yAbs, chunk.getPos().getMinBlockZ() + zRel);
colorInt = getColorForBlock(chunk, blockPos);
colorInt = getColorForBlock(chunk, x, y, z);
// 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 (config.client().worldGenerator().getBlocksToAvoid().nonFull || config.client().worldGenerator().getBlocksToAvoid().noCollision)
{
blockPos.set(chunk.getPos().getMinBlockX() + xRel, yAbs + 1, chunk.getPos().getMinBlockZ() + zRel);
aboveColorInt = getColorForBlock(chunk, blockPos);
}
IBlockShapeWrapper block = chunk.getBlockShapeWrapper(x, y + 1, z);
if (block != null && ((config.client().worldGenerator().getBlocksToAvoid().nonFull && block.isNonFull())
|| (config.client().worldGenerator().getBlocksToAvoid().noCollision && block.hasNoCollision())))
aboveColorInt = getColorForBlock(chunk, x, y + 1, z);
//if (colorInt == 0 && yAbs > 0)
// if this block is invisible, check the block below it
@@ -382,7 +395,7 @@ public class LodBuilder
}
/** Gets the light value for the given block position */
private int getLightValue(IChunkWrapper chunk, AbstractBlockPosWrapper blockPos, boolean hasCeiling, boolean hasSkyLight, boolean topBlock)
private int getLightValue(IChunkWrapper chunk, int x, int y, int z, boolean hasCeiling, boolean hasSkyLight, boolean topBlock)
{
int skyLight = 0;
int blockLight;
@@ -391,25 +404,25 @@ public class LodBuilder
IWorldWrapper world = MC.getWrappedServerWorld();
int blockBrightness = chunk.getEmittedBrightness(blockPos);
int blockBrightness = chunk.getEmittedBrightness(x, y, z);
// get the air block above or below this block
if (hasCeiling && topBlock)
blockPos.set(blockPos.getX(), blockPos.getY() - 1, blockPos.getZ());
y--;
else
blockPos.set(blockPos.getX(), blockPos.getY() + 1, blockPos.getZ());
y++;
if (world != null && !world.isEmpty())
if (world != null)
{
// server world sky light (always accurate)
blockLight = world.getBlockLight(blockPos);
blockLight = world.getBlockLight(x,y,z);
if (topBlock && !hasCeiling && hasSkyLight)
skyLight = DEFAULT_MAX_LIGHT;
else
{
if (hasSkyLight)
skyLight = world.getSkyLight(blockPos);
skyLight = world.getSkyLight(x,y,z);
//else
// skyLight = 0;
}
@@ -417,7 +430,7 @@ public class LodBuilder
{
// we are on predicted terrain, and we don't know what the light here is,
// lets just take a guess
if (blockPos.getY() >= MC.getWrappedClientWorld().getSeaLevel() - 5)
if (y >= MC.getWrappedClientWorld().getSeaLevel() - 5)
{
skyLight = 12;
isDefault = 1;
@@ -428,39 +441,42 @@ public class LodBuilder
}
else
{
world = MC.getWrappedServerWorld();
if (world.isEmpty())
return 0;
// client world sky light (almost never accurate)
blockLight = world.getBlockLight(blockPos);
// estimate what the lighting should be
if (hasSkyLight || !hasCeiling)
world = MC.getWrappedClientWorld();
if (world==null)
{
if (topBlock)
skyLight = DEFAULT_MAX_LIGHT;
else
blockLight = 0;
skyLight = 12;
isDefault = 1;
}
else
{
// client world sky light (almost never accurate)
blockLight = world.getBlockLight(x,y,z);
// estimate what the lighting should be
if (hasSkyLight || !hasCeiling)
{
if (hasSkyLight)
skyLight = world.getSkyLight(blockPos);
//else
// skyLight = 0;
if (!chunk.isLightCorrect() && (skyLight == 0 || skyLight == 15))
if (topBlock)
skyLight = DEFAULT_MAX_LIGHT;
else
{
// we don't know what the light here is,
// lets just take a guess
if (blockPos.getY() >= MC.getWrappedClientWorld().getSeaLevel() - 5)
if (hasSkyLight)
skyLight = world.getSkyLight(x,y,z);
//else
// skyLight = 0;
if (!chunk.isLightCorrect() && (skyLight == 0 || skyLight == 15))
{
skyLight = 12;
isDefault = 1;
// we don't know what the light here is,
// lets just take a guess
if (y >= MC.getWrappedClientWorld().getSeaLevel() - 5)
{
skyLight = 12;
isDefault = 1;
}
else
skyLight = 0;
}
else
skyLight = 0;
}
}
if (hasSkyLight)
skyLight = 0;
}
}
@@ -470,34 +486,31 @@ public class LodBuilder
}
/** Returns a color int for the given block. */
private int getColorForBlock(IChunkWrapper chunk, AbstractBlockPosWrapper blockPos)
private int getColorForBlock(IChunkWrapper chunk, int x, int y, int z)
{
int colorOfBlock;
int colorInt;
int xRel = blockPos.getX() - chunk.getPos().getMinBlockX();
int zRel = blockPos.getZ() - chunk.getPos().getMinBlockZ();
//int x = blockPos.getX();
int y = blockPos.getY();
//int z = blockPos.getZ();
IBlockShapeWrapper blockShapeWrapper = chunk.getBlockShapeWrapper(x, y, z);
if (blockShapeWrapper == null || blockShapeWrapper.isToAvoid())
return 0;
IBlockColorWrapper blockColorWrapper;
IBlockShapeWrapper blockShapeWrapper = chunk.getBlockShapeWrapper(blockPos);
if (chunk.isWaterLogged(blockPos))
if (chunk.isWaterLogged(x, y, z))
blockColorWrapper = BLOCK_COLOR.getWaterColor();
else
blockColorWrapper = chunk.getBlockColorWrapper(blockPos);
blockColorWrapper = chunk.getBlockColorWrapper(x, y, z);
if (blockShapeWrapper.isToAvoid())
return 0;
colorOfBlock = blockColorWrapper.getColor();
if (blockColorWrapper.hasTint())
{
IBiomeWrapper biome = chunk.getBiome(xRel, y, zRel);
IBiomeWrapper biome = chunk.getBiome(x, y, z);
int tintValue;
if (blockColorWrapper.hasGrassTint())
// grass and green plants
@@ -517,15 +530,16 @@ public class LodBuilder
/** Is the block at the given blockPos a valid LOD point? */
private boolean isLayerValidLodPoint(IChunkWrapper chunk, AbstractBlockPosWrapper blockPos)
private boolean isLayerValidLodPoint(IChunkWrapper chunk, int x, int y, int z)
{
if (chunk.isWaterLogged(blockPos))
if (chunk.isWaterLogged(x, y, z))
return true;
boolean nonFullAvoidance = config.client().worldGenerator().getBlocksToAvoid().nonFull;
boolean noCollisionAvoidance = config.client().worldGenerator().getBlocksToAvoid().noCollision;
IBlockShapeWrapper block = chunk.getBlockShapeWrapper(blockPos);
IBlockShapeWrapper block = chunk.getBlockShapeWrapper(x, y, z);
if (block == null) return false;
return !block.isToAvoid()
&& !(nonFullAvoidance && block.isNonFull())
&& !(noCollisionAvoidance && block.hasNoCollision());
@@ -1,212 +0,0 @@
/*
* 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.core.builders.worldGeneration;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import com.seibel.lod.core.api.ClientApi;
import com.seibel.lod.core.builders.lodBuilding.LodBuilder;
import com.seibel.lod.core.enums.config.DistanceGenerationMode;
import com.seibel.lod.core.objects.lod.LodDimension;
import com.seibel.lod.core.util.LodUtil;
import com.seibel.lod.core.util.SingletonHandler;
import com.seibel.lod.core.wrapperInterfaces.IWrapperFactory;
import com.seibel.lod.core.wrapperInterfaces.chunk.AbstractChunkPosWrapper;
import com.seibel.lod.core.wrapperInterfaces.config.ILodConfigWrapperSingleton;
import com.seibel.lod.core.wrapperInterfaces.world.IWorldWrapper;
import com.seibel.lod.core.wrapperInterfaces.worldGeneration.AbstractWorldGeneratorWrapper;
/**
* This is used to generate a LodChunk at a given ChunkPos.
*
* @author James Seibel
* @version 11-20-2021
*/
public class LodGenWorker
{
private static final ILodConfigWrapperSingleton CONFIG = SingletonHandler.get(ILodConfigWrapperSingleton.class);
private static final IWrapperFactory FACTORY = SingletonHandler.get(IWrapperFactory.class);
public static ExecutorService genThreads = Executors.newFixedThreadPool(CONFIG.client().advanced().threading().getNumberOfWorldGenerationThreads(), new ThreadFactoryBuilder().setNameFormat("Gen-Worker-Thread-%d").build());
private final LodChunkGenThread thread;
public LodGenWorker(AbstractChunkPosWrapper newPos, DistanceGenerationMode newGenerationMode,
LodBuilder newLodBuilder,
LodDimension newLodDimension, IWorldWrapper serverWorld)
{
// just a few sanity checks
if (newPos == null)
throw new IllegalArgumentException("LodChunkGenWorker must have a non-null ChunkPos");
if (newLodBuilder == null)
throw new IllegalArgumentException("LodChunkGenThread requires a non-null LodChunkBuilder");
if (newLodDimension == null)
throw new IllegalArgumentException("LodChunkGenThread requires a non-null LodDimension");
if (serverWorld == null)
throw new IllegalArgumentException("LodChunkGenThread requires a non-null ServerWorld");
thread = new LodChunkGenThread(newPos, newGenerationMode,
newLodBuilder,
newLodDimension, serverWorld);
}
public void queueWork()
{
if (CONFIG.client().worldGenerator().getDistanceGenerationMode() == DistanceGenerationMode.FULL)
{
// if we are using FULL generation there is no reason
// to queue up a bunch of generation requests,
// because MC's internal server (as of 1.16.5) only
// responds with a single thread. And we don't
// want to cause more lag then necessary or queue up
// requests that may end up being unneeded.
thread.run();
}
else
{
// Every other method can
// be done asynchronously
genThreads.execute(thread);
}
// useful for debugging
// ClientProxy.LOGGER.info(thread.lodDim.getNumberOfLods());
// ClientProxy.LOGGER.info(genThreads.toString());
}
private static class LodChunkGenThread implements Runnable
{
private AbstractWorldGeneratorWrapper worldGenWrapper;
public final LodDimension lodDim;
public final DistanceGenerationMode generationMode;
private final AbstractChunkPosWrapper pos;
public LodChunkGenThread(AbstractChunkPosWrapper newPos, DistanceGenerationMode newGenerationMode,
LodBuilder newLodBuilder,
LodDimension newLodDimension, IWorldWrapper worldWrapper)
{
worldGenWrapper = FACTORY.createWorldGenerator(newLodBuilder, newLodDimension, worldWrapper);
pos = newPos;
generationMode = newGenerationMode;
lodDim = newLodDimension;
}
@Override
public void run()
{
try
{
// only generate LodChunks if they can
// be added to the current LodDimension
if (lodDim.regionIsInRange(pos.getX() / LodUtil.REGION_WIDTH_IN_CHUNKS, pos.getZ() / LodUtil.REGION_WIDTH_IN_CHUNKS))
{
switch (generationMode)
{
case NONE:
// don't generate
break;
case BIOME_ONLY:
case BIOME_ONLY_SIMULATE_HEIGHT:
// fastest
worldGenWrapper.generateBiomesOnly(pos, generationMode);
break;
case SURFACE:
// faster
worldGenWrapper.generateSurface(pos);
break;
case FEATURES:
// fast
worldGenWrapper.generateFeatures(pos);
break;
case FULL:
// very slow
worldGenWrapper.generateFull(pos);
break;
}
// boolean dataExistence = lodDim.doesDataExist(new LevelPos((byte) 3, pos.x, pos.z));
// if (dataExistence)
// ClientProxy.LOGGER.info(pos.x + " " + pos.z + " Success!");
// else
// ClientProxy.LOGGER.info(pos.x + " " + pos.z);
// shows the pool size, active threads, queued tasks and completed tasks
// ClientProxy.LOGGER.info(genThreads.toString());
// long endTime = System.currentTimeMillis();
// System.out.println(endTime - startTime);
}// if in range
}
catch (Exception e)
{
ClientApi.LOGGER.error(LodChunkGenThread.class.getSimpleName() + ": ran into an error: " + e.getMessage());
e.printStackTrace();
}
finally
{
// decrement how many threads are running
LodWorldGenerator.INSTANCE.numberOfChunksWaitingToGenerate.addAndGet(-1);
// this position is no longer being generated
LodWorldGenerator.INSTANCE.positionsWaitingToBeGenerated.remove(pos);
}
}// run
}
/**
* Stops the current genThreads if they are running
* 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 restartExecutorService()
{
if (genThreads != null && !genThreads.isShutdown())
{
genThreads.shutdownNow();
}
genThreads = Executors.newFixedThreadPool(CONFIG.client().advanced().threading().getNumberOfWorldGenerationThreads(), new ThreadFactoryBuilder().setNameFormat("Gen-Worker-Thread-%d").build());
}
}
@@ -25,50 +25,47 @@ import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicInteger;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import com.seibel.lod.core.builders.lodBuilding.LodBuilder;
import com.seibel.lod.core.enums.config.DistanceGenerationMode;
import com.seibel.lod.core.objects.PosToGenerateContainer;
import com.seibel.lod.core.objects.lod.LodDimension;
import com.seibel.lod.core.render.LodRenderer;
import com.seibel.lod.core.util.DetailDistanceUtil;
import com.seibel.lod.core.util.LevelPosUtil;
import com.seibel.lod.core.util.LodThreadFactory;
import com.seibel.lod.core.util.LodUtil;
import com.seibel.lod.core.util.SingletonHandler;
import com.seibel.lod.core.wrapperInterfaces.IVersionConstants;
import com.seibel.lod.core.wrapperInterfaces.IWrapperFactory;
import com.seibel.lod.core.wrapperInterfaces.chunk.AbstractChunkPosWrapper;
import com.seibel.lod.core.wrapperInterfaces.config.ILodConfigWrapperSingleton;
import com.seibel.lod.core.wrapperInterfaces.minecraft.IMinecraftWrapper;
import com.seibel.lod.core.wrapperInterfaces.world.IWorldWrapper;
import com.seibel.lod.core.wrapperInterfaces.worldGeneration.AbstractExperimentalWorldGeneratorWrapper;
import com.seibel.lod.core.wrapperInterfaces.worldGeneration.AbstractWorldGeneratorWrapper;
/**
* A singleton that handles all long distance LOD world generation.
* @author Leonardo Amato
* @author James Seibel
* @version 9-25-2021
* @version 12-11-2021
*/
public class LodWorldGenerator
{
private static final IMinecraftWrapper MC = SingletonHandler.get(IMinecraftWrapper.class);
private static final ILodConfigWrapperSingleton CONFIG = SingletonHandler.get(ILodConfigWrapperSingleton.class);
private static final IWrapperFactory WRAPPER_FACTORY = SingletonHandler.get(IWrapperFactory.class);
private static final IVersionConstants VERSION_CONSTANTS = SingletonHandler.get(IVersionConstants.class);
/** This holds the thread used to create LOD generation requests off the main thread. */
private final ExecutorService mainGenThread = Executors.newSingleThreadExecutor(new LodThreadFactory(this.getClass().getSimpleName() + " world generator"));
private ExecutorService genSubThreads = Executors.newFixedThreadPool(CONFIG.client().advanced().threading().getNumberOfWorldGenerationThreads(),
new ThreadFactoryBuilder().setNameFormat("Gen-Worker-Thread-%d").build());
/** we only want to queue up one generator thread at a time */
private boolean generatorThreadRunning = false;
/**
* 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
* possible.
*/
public int maxChunkGenRequests;
/**
* This keeps track of how many chunk generation requests are on going. This is
* to limit how many chunks are queued at once. To prevent chunks from being
@@ -82,32 +79,64 @@ public class LodWorldGenerator
* Singleton copy of this object
*/
public static final LodWorldGenerator INSTANCE = new LodWorldGenerator();
public AbstractExperimentalWorldGeneratorWrapper experimentalWorldGenerator;
private LodWorldGenerator()
{
}
private LodWorldGenerator() {}
/**
* Queues up LodNodeGenWorkers for the given lodDimension.
* @param renderer needed so the LodNodeGenWorkers can flag that the
* renderer needed so the LodNodeGenWorkers can flag that the
* buffers need to be rebuilt.
*/
public void queueGenerationRequests(LodDimension lodDim, LodRenderer renderer, LodBuilder lodBuilder)
public void queueGenerationRequests(LodDimension lodDim, LodBuilder lodBuilder)
{
if (CONFIG.client().worldGenerator().getDistanceGenerationMode() != DistanceGenerationMode.NONE
IWorldWrapper world = LodUtil.getServerWorldFromDimension(lodDim.dimension);
// TODO: Rename the config option
if (CONFIG.client().worldGenerator().getAllowUnstableFeatureGeneration()) {
if (experimentalWorldGenerator == null) {
experimentalWorldGenerator = WRAPPER_FACTORY.createExperimentalWorldGenerator(lodBuilder, lodDim, world);
if (experimentalWorldGenerator == null) CONFIG.client().worldGenerator().setAllowUnstableFeatureGeneration(false);
}
} else {
if (experimentalWorldGenerator != null) {
experimentalWorldGenerator.stop();
experimentalWorldGenerator = null;
}
}
if (experimentalWorldGenerator != null) {
experimentalWorldGenerator.queueGenerationRequests(lodDim, lodBuilder);
return;
}
// TODO: This currently doesn't use the DetailDistanceUtil.getDistanceGenerationMode(int detail) to get the mode.
// This is fine currently since DistanceGenerationMode doesn't care about the detail level for now.
// However, If that was to be changed, This will need to be fixed.
DistanceGenerationMode mode = CONFIG.client().worldGenerator().getDistanceGenerationMode();
if (mode != DistanceGenerationMode.NONE
&& !generatorThreadRunning
&& MC.hasSinglePlayerServer())
{
// the thread is now running, don't queue up another thread
generatorThreadRunning = true;
/**
* 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
* possible.
*/
int genRequestPerThread = VERSION_CONSTANTS.getWorldGenerationCountPerThread();
int maxChunkGenRequests;
if (VERSION_CONSTANTS.isWorldGeneratorSingleThreaded(mode))
maxChunkGenRequests = CONFIG.client().advanced().threading().getNumberOfWorldGenerationThreads() * genRequestPerThread;
else maxChunkGenRequests = genRequestPerThread;
// just in case the config changed
maxChunkGenRequests = CONFIG.client().advanced().threading().getNumberOfWorldGenerationThreads() * 8;
Thread generatorThread = new Thread(() ->
Runnable generatorFunc = (() ->
{
try
{
@@ -127,7 +156,6 @@ public class LodWorldGenerator
playerPosX,
playerPosZ);
byte detailLevel;
int posX;
int posZ;
@@ -140,7 +168,7 @@ public class LodWorldGenerator
// an easy way to do so.
// add the near positions
if (posToGenerate.getNthDetail(nearIndex, true) != 0 && nearIndex < posToGenerate.getNumberOfNearPos())
if (nearIndex < posToGenerate.getNumberOfNearPos() && posToGenerate.getNthDetail(nearIndex, true) != 0)
{
detailLevel = (byte) (posToGenerate.getNthDetail(nearIndex, true) - 1);
posX = posToGenerate.getNthPosX(nearIndex, true);
@@ -159,13 +187,12 @@ public class LodWorldGenerator
positionsWaitingToBeGenerated.add(chunkPos);
numberOfChunksWaitingToGenerate.addAndGet(1);
LodGenWorker genWorker = new LodGenWorker(chunkPos, DetailDistanceUtil.getDistanceGenerationMode(detailLevel), lodBuilder, lodDim, serverWorld);
genWorker.queueWork();
queueWork(chunkPos, mode, lodBuilder, lodDim, serverWorld);
}
// add the far positions
if (posToGenerate.getNthDetail(farIndex, false) != 0 && farIndex < posToGenerate.getNumberOfFarPos())
if (farIndex < posToGenerate.getNumberOfFarPos() && posToGenerate.getNthDetail(farIndex, false) != 0)
{
detailLevel = (byte) (posToGenerate.getNthDetail(farIndex, false) - 1);
posX = posToGenerate.getNthPosX(farIndex, false);
@@ -185,13 +212,12 @@ public class LodWorldGenerator
positionsWaitingToBeGenerated.add(chunkPos);
numberOfChunksWaitingToGenerate.addAndGet(1);
LodGenWorker genWorker = new LodGenWorker(chunkPos, DetailDistanceUtil.getDistanceGenerationMode(detailLevel), lodBuilder, lodDim, serverWorld);
genWorker.queueWork();
queueWork(chunkPos, mode, lodBuilder, lodDim, serverWorld);
}
}
}
catch (Exception e)
catch (RuntimeException e)
{
// this shouldn't ever happen, but just in case
e.printStackTrace();
@@ -202,8 +228,146 @@ public class LodWorldGenerator
}
});
mainGenThread.execute(generatorThread);
if (VERSION_CONSTANTS.isWorldGeneratorSingleThreaded(mode))
{
generatorFunc.run();
}
else
{
mainGenThread.execute(generatorFunc);
}
} // if distanceGenerationMode != DistanceGenerationMode.NONE && !generatorThreadRunning
} // queueGenerationRequests
private void queueWork(AbstractChunkPosWrapper newPos, DistanceGenerationMode newGenerationMode,
LodBuilder newLodBuilder,
LodDimension newLodDimension, IWorldWrapper serverWorld)
{
// just a few sanity checks
if (newPos == null)
throw new IllegalArgumentException("LodChunkGenWorker must have a non-null ChunkPos");
if (newLodBuilder == null)
throw new IllegalArgumentException("LodChunkGenThread requires a non-null LodChunkBuilder");
if (newLodDimension == null)
throw new IllegalArgumentException("LodChunkGenThread requires a non-null LodDimension");
if (serverWorld == null)
throw new IllegalArgumentException("LodChunkGenThread requires a non-null ServerWorld");
Runnable method = (() -> {generateChunk(newPos, newGenerationMode,
newLodBuilder, newLodDimension, serverWorld);});
if (VERSION_CONSTANTS.isWorldGeneratorSingleThreaded(newGenerationMode))
{
// --Note: This is now using version constants--
// if we are using FULL generation there is no reason
// to queue up a bunch of generation requests,
// because MC's internal server (as of 1.16.5) only
// responds with a single thread. And we don't
// want to cause more lag than necessary or queue up
// requests that may end up being unneeded.
// In 1.17+, world generation becomes completely single
// threaded. So to allow that, we check the boolean for
// whether the wrapper requires single thread
method.run();
}
else
{
// Every other method can
// be done asynchronously
genSubThreads.execute(method);
}
// useful for debugging
// ClientProxy.LOGGER.info(thread.lodDim.getNumberOfLods());
// ClientProxy.LOGGER.info(genThreads.toString());
}
private void generateChunk(AbstractChunkPosWrapper pos, DistanceGenerationMode generationMode,
LodBuilder newLodBuilder, LodDimension lodDim, IWorldWrapper worldWrapper)
{
// try
{
AbstractWorldGeneratorWrapper worldGenWrapper = WRAPPER_FACTORY.createWorldGenerator(newLodBuilder, lodDim, worldWrapper);
// only generate LodChunks if they can
// be added to the current LodDimension
if (lodDim.regionIsInRange(pos.getX() / LodUtil.REGION_WIDTH_IN_CHUNKS, pos.getZ() / LodUtil.REGION_WIDTH_IN_CHUNKS))
{
switch (generationMode)
{
case NONE:
// don't generate
break;
case BIOME_ONLY:
case BIOME_ONLY_SIMULATE_HEIGHT:
// fastest
worldGenWrapper.generateBiomesOnly(pos, generationMode);
break;
case SURFACE:
// faster
worldGenWrapper.generateSurface(pos);
break;
case FEATURES:
// fast
worldGenWrapper.generateFeatures(pos);
break;
case FULL:
// very slow
worldGenWrapper.generateFull(pos);
break;
}
// boolean dataExistence = lodDim.doesDataExist(new LevelPos((byte) 3, pos.x, pos.z));
// if (dataExistence)
// ClientProxy.LOGGER.info(pos.x + " " + pos.z + " Success!");
// else
// ClientProxy.LOGGER.info(pos.x + " " + pos.z);
// shows the pool size, active threads, queued tasks and completed tasks
// ClientProxy.LOGGER.info(genThreads.toString());
}// if in range
}
// catch (Exception e)
// {
// ClientApi.LOGGER.error(LodWorldGenerator.class.getSimpleName() + ": ran into an error: " + e.getMessage());
// e.printStackTrace();
// }
// finally
{
// decrement how many threads are running
LodWorldGenerator.INSTANCE.numberOfChunksWaitingToGenerate.addAndGet(-1);
// this position is no longer being generated
LodWorldGenerator.INSTANCE.positionsWaitingToBeGenerated.remove(pos);
}
}// run
/**
* Stops the current genThreads if they are running
* 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 void restartExecutorService()
{
if (experimentalWorldGenerator != null) {
experimentalWorldGenerator.stop();
experimentalWorldGenerator = null;
}
if (genSubThreads != null && !genSubThreads.isShutdown())
{
genSubThreads.shutdownNow();
}
genSubThreads = Executors.newFixedThreadPool(CONFIG.client().advanced().threading().getNumberOfWorldGenerationThreads(),
new ThreadFactoryBuilder().setNameFormat("Gen-Worker-Thread-%d").build());
}
}
@@ -0,0 +1,56 @@
package com.seibel.lod.core.config;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Where the annotations for the config are defined
*
* @author coolGi2007
* @version 12-28-2021
*/
public class ConfigAnnotations {
/** a textField, button, etc. that can be interacted with */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Entry
{
String name() default "";
int width() default 150;
double minValue() default Double.MIN_NORMAL;
double maxValue() default Double.MAX_VALUE;
}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface ScreenEntry
{
String name() default "";
int width() default 100;
}
/** Used when sorting the configs in the menu */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Category
{
String value();
}
/** Makes text (looks like @Entry but dosnt save and has no button */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Comment
{
}
}
@@ -0,0 +1,502 @@
package com.seibel.lod.core.dataFormat;
import com.seibel.lod.core.enums.config.DistanceGenerationMode;
import com.seibel.lod.core.util.ColorUtil;
import com.seibel.lod.core.util.DetailDistanceUtil;
import com.seibel.lod.core.util.ThreadMapUtil;
import static com.seibel.lod.core.builders.bufferBuilding.LodBufferBuilderFactory.skyLightPlayer;
public class BlockDataFormat
{
/*
|a |a |a |a |r |r |r |r |
|r |r |r |r |g |g |g |g |
|g |g |g |g |b |b |b |b |
|b |b |b |b |h |h |h |h |
|h |h |h |h |h |h |d |d |
|d |d |d |d |d |d |d |d |
|bl |bl |bl |bl |sl |sl |sl |sl |
|l |l |f |g |g |g |v |e |
*/
// Reminder: bytes have range of [-128, 127].
// When converting to or from 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
//public final static int MIN_DEPTH = -64;
//public final static int MIN_HEIGHT = -64;
public final static int EMPTY_DATA = 0;
public static final short VERTICAL_OFFSET = -64;
public static int WORLD_HEIGHT = 1024;
public final static int ALPHA_DOWNSIZE_SHIFT = 4;
//public final static int BLUE_COLOR_SHIFT = 0;
//public final static int GREEN_COLOR_SHIFT = 8;
//public final static int RED_COLOR_SHIFT = 16;
//public final static int ALPHA_COLOR_SHIFT = 24;
public final static int BLUE_SHIFT = 36;
public final static int GREEN_SHIFT = BLUE_SHIFT + 8;
public final static int RED_SHIFT = BLUE_SHIFT + 16;
public final static int ALPHA_SHIFT = BLUE_SHIFT + 24;
public final static int COLOR_SHIFT = 36;
public final static int HEIGHT_SHIFT = 26;
public final static int DEPTH_SHIFT = 16;
public final static int BLOCK_LIGHT_SHIFT = 12;
public final static int SKY_LIGHT_SHIFT = 8;
//public final static int LIGHTS_SHIFT = SKY_LIGHT_SHIFT;
//public final static int VERTICAL_INDEX_SHIFT = 6;
public final static int FLAG_SHIFT = 5;
public final static int GEN_TYPE_SHIFT = 2;
public final static int VOID_SHIFT = 1;
public final static int EXISTENCE_SHIFT = 0;
public final static long ALPHA_MASK = 0b1111;
public final static long RED_MASK = 0b1111_1111;
public final static long GREEN_MASK = 0b1111_1111;
public final static long BLUE_MASK = 0b1111_1111;
public final static long COLOR_MASK = 0b11111111_11111111_11111111;
public final static long HEIGHT_MASK = 0b11_1111_1111;
public final static long DEPTH_MASK = 0b11_1111_1111;
//public final static long LIGHTS_MASK = 0b1111_1111;
public final static long BLOCK_LIGHT_MASK = 0b1111;
public final static long SKY_LIGHT_MASK = 0b1111;
//public final static long VERTICAL_INDEX_MASK = 0b11;
public final static long FLAG_MASK = 0b1;
public final static long GEN_TYPE_MASK = 0b111;
public final static long VOID_MASK = 1;
public final static long EXISTENCE_MASK = 1;
public static long createVoidDataPoint(int generationMode)
{
long dataPoint = 0;
dataPoint += (generationMode & GEN_TYPE_MASK) << GEN_TYPE_SHIFT;
dataPoint += VOID_MASK << VOID_SHIFT;
dataPoint += EXISTENCE_MASK << EXISTENCE_SHIFT;
return dataPoint;
}
public static long createDataPoint(int height, int depth, int color, int lightSky, int lightBlock, int generationMode, boolean flag)
{
return createDataPoint(
ColorUtil.getAlpha(color),
ColorUtil.getRed(color),
ColorUtil.getGreen(color),
ColorUtil.getBlue(color),
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, boolean flag)
{
long dataPoint = 0;
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;
dataPoint += (height & HEIGHT_MASK) << HEIGHT_SHIFT;
dataPoint += (depth & DEPTH_MASK) << DEPTH_SHIFT;
dataPoint += (lightBlock & BLOCK_LIGHT_MASK) << BLOCK_LIGHT_SHIFT;
dataPoint += (lightSky & SKY_LIGHT_MASK) << SKY_LIGHT_SHIFT;
dataPoint += (generationMode & GEN_TYPE_MASK) << GEN_TYPE_SHIFT;
if (flag)
dataPoint += FLAG_MASK << FLAG_SHIFT;
dataPoint += EXISTENCE_MASK << EXISTENCE_SHIFT;
return dataPoint;
}
public static short getHeight(long dataPoint)
{
return (short) ((dataPoint >>> HEIGHT_SHIFT) & HEIGHT_MASK);
}
public static short getDepth(long dataPoint)
{
return (short) ((dataPoint >>> DEPTH_SHIFT) & DEPTH_MASK);
}
public static short getAlpha(long dataPoint)
{
return (short) ((((dataPoint >>> ALPHA_SHIFT) & ALPHA_MASK) << ALPHA_DOWNSIZE_SHIFT) | 0b1111);
}
public static short getRed(long dataPoint)
{
return (short) ((dataPoint >>> RED_SHIFT) & RED_MASK);
}
public static short getGreen(long dataPoint)
{
return (short) ((dataPoint >>> GREEN_SHIFT) & GREEN_MASK);
}
public static short getBlue(long dataPoint)
{
return (short) ((dataPoint >>> BLUE_SHIFT) & BLUE_MASK);
}
public static byte getLightSky(long dataPoint)
{
return (byte) ((dataPoint >>> SKY_LIGHT_SHIFT) & SKY_LIGHT_MASK);
}
public static byte getLightSkyAlt(long dataPoint)
{
if (skyLightPlayer == 0 && ((dataPoint >>> FLAG_SHIFT) & FLAG_MASK) == 1)
return 0;
else
return (byte) ((dataPoint >>> SKY_LIGHT_SHIFT) & SKY_LIGHT_MASK);
}
public static byte getLightBlock(long dataPoint)
{
return (byte) ((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);
}
public static boolean isVoid(long dataPoint)
{
return (((dataPoint >>> VOID_SHIFT) & VOID_MASK) == 1);
}
public static boolean doesItExist(long dataPoint)
{
return (((dataPoint >>> EXISTENCE_SHIFT) & EXISTENCE_MASK) == 1);
}
public static int getColor(long dataPoint)
{
return (int) (((dataPoint >>> COLOR_SHIFT) & COLOR_MASK) | (/*((dataPoint >>> (ALPHA_SHIFT - ALPHA_DOWNSIZE_SHIFT)) | 0b1111)*/255 << 24));
}
/** This is used to convert a dataPoint to string (useful for the print function) */
@SuppressWarnings("unused")
public static String toString(long dataPoint)
{
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 void shrinkArray(short[] array, int packetSize, int start, int length, int arraySize)
{
start *= packetSize;
length *= packetSize;
arraySize *= packetSize;
for (int i = 0; i < arraySize - start; i++)
{
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[] heightAndDepth = ThreadMapUtil.getHeightAndDepth((WORLD_HEIGHT / 2 + 1) * 2);
long[] dataPoint = ThreadMapUtil.getVerticalDataArray(DetailDistanceUtil.getMaxVerticalData(0));
int genMode = DistanceGenerationMode.FULL.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 (dataIndex = 0; dataIndex < inputVerticalData; dataIndex++)
{
singleData = dataToMerge[index * inputVerticalData + dataIndex];
if (doesItExist(singleData))
{
genMode = Math.min(genMode, getGenerationMode(singleData));
allEmpty = false;
if (!isVoid(singleData))
{
allVoid = false;
depth = getDepth(singleData);
height = getHeight(singleData);
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 limit the vertical portion to maxVerticalData
int j = 0;
while (count > maxVerticalData)
{
ii = WORLD_HEIGHT - VERTICAL_OFFSET;
for (i = 0; i < count - 1; i++)
{
if (heightAndDepth[i * 2 + 1] - heightAndDepth[(i + 1) * 2] <= ii)
{
ii = heightAndDepth[i * 2 + 1] - heightAndDepth[(i + 1) * 2];
j = i;
}
}
heightAndDepth[j * 2 + 1] = heightAndDepth[(j + 1) * 2 + 1];
for (i = j + 1; i < count - 1; i++)
{
heightAndDepth[i * 2] = heightAndDepth[(i + 1) * 2];
heightAndDepth[i * 2 + 1] = heightAndDepth[(i + 1) * 2 + 1];
}
//System.arraycopy(heightAndDepth, j + 1, heightAndDepth, j, count - j - 1);
count--;
}
//As standard the vertical lods are ordered from top to bottom
for (j = count - 1; j >= 0; j--)
{
height = heightAndDepth[j * 2];
depth = heightAndDepth[j * 2 + 1];
if ((depth == 0 && height == 0) || j >= heightAndDepth.length / 2)
break;
int numberOfChildren = 0;
int tempAlpha = 0;
int tempRed = 0;
int tempGreen = 0;
int tempBlue = 0;
int tempLightBlock = 0;
int tempLightSky = 0;
byte tempGenMode = DistanceGenerationMode.FULL.complexity;
allEmpty = true;
allVoid = true;
allDefault = true;
long data = 0;
for (int index = 0; index < size; index++)
{
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(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);
}
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;
}
}
@@ -0,0 +1,45 @@
package com.seibel.lod.core.dataFormat;
public class ColorFormat
{
public final static int BLUE_SHIFT = 0;
public final static int GREEN_SHIFT = BLUE_SHIFT + 8;
public final static int RED_SHIFT = BLUE_SHIFT + 16;
public final static int ALPHA_SHIFT = BLUE_SHIFT + 24;
public final static long ALPHA_MASK = 0b1111;
public final static long RED_MASK = 0b1111_1111;
public final static long GREEN_MASK = 0b1111_1111;
public final static long BLUE_MASK = 0b1111_1111;
public static int createColorData(int alpha, int red, int green, int blue)
{
int colorData = 0;
colorData += (alpha & ALPHA_MASK) << ALPHA_SHIFT;
colorData += (red & RED_MASK) << RED_SHIFT;
colorData += (green & GREEN_MASK) << GREEN_SHIFT;
colorData += (blue & BLUE_MASK) << BLUE_SHIFT;
return colorData;
}
public static short getAlpha(long dataPoint)
{
return (short) ((dataPoint >>> ALPHA_SHIFT) & ALPHA_MASK);
}
public static short getRed(long dataPoint)
{
return (short) ((dataPoint >>> RED_SHIFT) & RED_MASK);
}
public static short getGreen(long dataPoint)
{
return (short) ((dataPoint >>> GREEN_SHIFT) & GREEN_MASK);
}
public static short getBlue(long dataPoint)
{
return (short) ((dataPoint >>> BLUE_SHIFT) & BLUE_MASK);
}
}
@@ -0,0 +1,29 @@
package com.seibel.lod.core.dataFormat;
public class DataMergeUtil
{
public static void shrinkArray(short[] array, int packetSize, int start, int length, int arraySize)
{
start *= packetSize;
length *= packetSize;
arraySize *= packetSize;
for (int i = 0; i < arraySize - start; i++)
{
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;
}
}
}
@@ -0,0 +1,39 @@
package com.seibel.lod.core.dataFormat;
public class LightFormat
{
public final static byte INT_BLOCK_LIGHT_SHIFT = 16;
public final static byte INT_SKY_LIGHT_SHIFT = 0;
public final static byte BYTE_BLOCK_LIGHT_SHIFT = 4;
public final static byte BYTE_SKY_LIGHT_SHIFT = 0;
public final static byte BLOCK_LIGHT_MASK = 0b1111;
public final static byte SKY_LIGHT_MASK = 0b1111;
public static byte formatLightAsByte(byte skyLight, byte blockLight)
{
return (byte) (((skyLight & SKY_LIGHT_MASK) << (BYTE_SKY_LIGHT_SHIFT + 4)) | ((blockLight & BLOCK_LIGHT_MASK) << (BYTE_BLOCK_LIGHT_SHIFT + 4)));
}
public static int formatLightAsInt(byte skyLight, byte blockLight)
{
return ((skyLight & SKY_LIGHT_MASK) << INT_SKY_LIGHT_SHIFT) | ((blockLight & BLOCK_LIGHT_MASK) << INT_BLOCK_LIGHT_SHIFT);
}
public static int convertByteToIntFormat(byte lights)
{
return formatLightAsInt((byte) ((lights >>> BYTE_SKY_LIGHT_SHIFT) & SKY_LIGHT_MASK), (byte) ((lights >>> BYTE_BLOCK_LIGHT_SHIFT) & BLOCK_LIGHT_MASK));
}
public static byte getSkyLight(byte lights)
{
return (byte) ((lights >>> BYTE_SKY_LIGHT_SHIFT) & SKY_LIGHT_MASK);
}
public static byte getBlockLight(byte lights)
{
return (byte) ((lights >>> BYTE_BLOCK_LIGHT_SHIFT) & BLOCK_LIGHT_MASK);
}
}
@@ -0,0 +1,85 @@
package com.seibel.lod.core.dataFormat;
public class PositionDataFormat
{
public final static byte LOD_COUNT_SHIFT = 6;
public final static byte CORRECT_LIGHT_SHIFT = 5;
public final static byte GEN_TYPE_SHIFT = 2;
public final static byte VOID_SHIFT = 1;
public final static byte EXISTENCE_SHIFT = 0;
//We are able to count up to 64 different lods in a column
public final static short LOD_COUNT_MASK = 0b11_1111;
public final static short CORRECT_LIGHT_MASK = 0b1;
public final static short GEN_TYPE_MASK = 0b111;
public final static short VOID_MASK = 0b1;
public final static short EXISTENCE_MASK = 0b1;
public final static int EMPTY_DATA = 0;
public final static int VOID_DATA = VOID_MASK<<VOID_SHIFT + EXISTENCE_MASK<<EXISTENCE_SHIFT;
public static short createVoidPositionData(byte generationMode)
{
short positionData = 0;
positionData |= (generationMode & GEN_TYPE_MASK) << GEN_TYPE_SHIFT;
positionData |= VOID_MASK << VOID_SHIFT;
positionData |= EXISTENCE_MASK << EXISTENCE_SHIFT;
return positionData;
}
public static short createPositionData(int lodCount, boolean correctLight, byte generationMode)
{
short positionData = 0;
positionData |= (lodCount & LOD_COUNT_MASK) << LOD_COUNT_SHIFT;
positionData |= (generationMode & GEN_TYPE_MASK) << GEN_TYPE_SHIFT;
if (correctLight)
positionData |= CORRECT_LIGHT_MASK << CORRECT_LIGHT_SHIFT;
positionData |= EXISTENCE_MASK << EXISTENCE_SHIFT;
return positionData;
}
public static byte getLodCount(short dataPoint)
{
return (byte) ((dataPoint >>> LOD_COUNT_SHIFT) & LOD_COUNT_MASK);
}
public static boolean getFlag(short dataPoint)
{
return ((dataPoint >>> CORRECT_LIGHT_SHIFT) & CORRECT_LIGHT_MASK) == 1;
}
public static byte getGenerationMode(short dataPoint)
{
return (byte) ((dataPoint >>> GEN_TYPE_SHIFT) & GEN_TYPE_MASK);
}
public static boolean isVoid(short dataPoint)
{
return (((dataPoint >>> VOID_SHIFT) & VOID_MASK) == 1);
}
public static boolean doesItExist(short dataPoint)
{
return (((dataPoint >>> EXISTENCE_SHIFT) & EXISTENCE_MASK) == 1);
}
public static short setLodCount(short dataPoint, short lodCount)
{
return (short) (dataPoint | ((lodCount & LOD_COUNT_MASK) << LOD_COUNT_SHIFT));
}
public static short setFlag(short dataPoint)
{
return (short) (dataPoint | ((CORRECT_LIGHT_MASK) << CORRECT_LIGHT_SHIFT));
}
public static short setGenerationMode(short dataPoint, byte generationMode)
{
return (short) (dataPoint | ((generationMode & GEN_TYPE_MASK) << GEN_TYPE_SHIFT));
}
public static short setVoid(short dataPoint)
{
return (short) (dataPoint | (VOID_MASK << VOID_SHIFT));
}
public static short setExistence(short dataPoint)
{
return (short) (dataPoint | (EXISTENCE_MASK << EXISTENCE_SHIFT));
}
}
@@ -0,0 +1,111 @@
package com.seibel.lod.core.dataFormat;
public class VerticalDataFormat
{
public final static short MIN_WORLD_HEIGHT = -2048;
public final static short MAX_WORLD_HEIGHT = 2047;
public final static byte HEIGHT_SHIFT = 20;
public final static byte DEPTH_SHIFT = 8;
public final static byte LEVEL_SHIFT = 3;
public final static byte BOTTOM_TYPE_SHIFT = 2;
public final static byte TRANSPARENCY_SHIFT = 1;
public final static byte EXISTENCE_SHIFT = 0;
public final static int FULL_MASK = ~0;
public final static int HEIGHT_MASK = 0b1111_1111_1111;
public final static int DEPTH_MASK = 0b1111_1111_1111;
public final static int LEVEL_MASK = 0b111;
public final static int TRANSPARENCY_MASK = 0b1;
public final static int BOTTOM_TYPE_MASK = 0b1;
public final static int EXISTENCE_MASK = 0b1;
public final static int HEIGHT_RESET = ~(HEIGHT_MASK << HEIGHT_SHIFT);
public final static int DEPTH_RESET = ~(DEPTH_MASK << DEPTH_SHIFT);
public final static int LEVEL_RESET = ~(LEVEL_MASK << LEVEL_SHIFT);
public final static int TRANSPARENCY_RESET = ~(TRANSPARENCY_MASK << BOTTOM_TYPE_SHIFT);
public final static int BOTTOM_TYPE_RESET = ~(BOTTOM_TYPE_MASK << TRANSPARENCY_SHIFT);
public final static int EXISTENCE_RESET = ~(EXISTENCE_MASK << EXISTENCE_SHIFT);
public final static int EMPTY_LOD = 0;
public static int createVerticalData(int height, int depth, int level, boolean transparent, boolean bottom)
{
int verticalData = 0;
verticalData |= (height & HEIGHT_MASK) << HEIGHT_SHIFT;
verticalData |= (depth & DEPTH_MASK) << DEPTH_SHIFT;
verticalData |= (level & LEVEL_MASK) << LEVEL_SHIFT;
if (bottom)
verticalData |= BOTTOM_TYPE_MASK << BOTTOM_TYPE_SHIFT;
if (transparent)
verticalData |= TRANSPARENCY_MASK << TRANSPARENCY_SHIFT;
verticalData |= EXISTENCE_MASK << EXISTENCE_SHIFT;
return verticalData;
}
public static short getHeight(int verticalData)
{
return (short) ((verticalData >>> HEIGHT_SHIFT) & HEIGHT_MASK);
}
public static short getDepth(int verticalData)
{
return (short) ((verticalData >>> DEPTH_SHIFT) & DEPTH_MASK);
}
public static byte getLevel(int verticalData)
{
return (byte) ((verticalData >>> LEVEL_SHIFT) & LEVEL_MASK);
}
public static boolean isTransparent(int verticalData)
{
return ((verticalData >>> TRANSPARENCY_SHIFT) & TRANSPARENCY_MASK) == 1;
}
public static boolean isBottom(int verticalData)
{
return ((verticalData >>> BOTTOM_TYPE_SHIFT) & BOTTOM_TYPE_MASK) == 1;
}
public static boolean doesItExist(int verticalData)
{
return (((verticalData >>> EXISTENCE_SHIFT) & EXISTENCE_MASK) == 1);
}
public static int setHeight(int verticalData, int height)
{
return verticalData | ((height & HEIGHT_MASK) << HEIGHT_SHIFT);
}
public static int setDepth(int verticalData, int depth)
{
return verticalData | ((depth & DEPTH_MASK) << DEPTH_SHIFT);
}
public static int setLevel(int verticalData, int level)
{
return verticalData | ((level & LEVEL_MASK) << LEVEL_SHIFT);
}
public static int setTransparency(int verticalData)
{
return verticalData | ((TRANSPARENCY_MASK) << TRANSPARENCY_SHIFT);
}
public static int setBottom(int verticalData)
{
return verticalData | ((BOTTOM_TYPE_MASK) << BOTTOM_TYPE_SHIFT);
}
public static int setExistence(int verticalData)
{
return verticalData | ((EXISTENCE_MASK) << EXISTENCE_SHIFT);
}
}
@@ -6,8 +6,6 @@ import java.util.Map;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import javax.annotation.Nullable;
import com.seibel.lod.core.objects.math.Vec3i;
/**
@@ -73,7 +71,7 @@ public enum LodDirection
private LodDirection(int p_i46016_3_, int p_i46016_4_, int p_i46016_5_, String p_i46016_6_, LodDirection.AxisDirection p_i46016_7_, LodDirection.Axis p_i46016_8_, Vec3i p_i46016_9_)
LodDirection(int p_i46016_3_, int p_i46016_4_, int p_i46016_5_, String p_i46016_6_, LodDirection.AxisDirection p_i46016_7_, LodDirection.Axis p_i46016_8_, Vec3i p_i46016_9_)
{
// this.data3d = p_i46016_3_;
// this.data2d = p_i46016_5_;
@@ -227,9 +225,8 @@ public enum LodDirection
{
return this.axis;
}
@Nullable
public static LodDirection byName(@Nullable String name)
public static LodDirection byName(String name)
{
return name == null ? null : BY_NAME.get(name.toLowerCase(Locale.ROOT));
}
@@ -328,7 +325,7 @@ public enum LodDirection
// return this.normal.getX() * f1 + this.normal.getZ() * f2 > 0.0F;
// }
public static enum Axis implements Predicate<LodDirection>
public enum Axis implements Predicate<LodDirection>
{
X("x")
{
@@ -381,12 +378,11 @@ public enum LodDirection
}));
private final String name;
private Axis(String name)
Axis(String name)
{
this.name = name;
}
@Nullable
public static LodDirection.Axis byName(String name)
{
return BY_NAME.get(name.toLowerCase(Locale.ROOT));
@@ -419,7 +415,7 @@ public enum LodDirection
// }
@Override
public boolean test(@Nullable LodDirection p_test_1_)
public boolean test(LodDirection p_test_1_)
{
return p_test_1_ != null && p_test_1_.getAxis() == this;
}
@@ -443,7 +439,7 @@ public enum LodDirection
public abstract double choose(double p_196051_1_, double p_196051_3_, double p_196051_5_);
}
public static enum AxisDirection
public enum AxisDirection
{
POSITIVE(1, "Towards positive"),
NEGATIVE(-1, "Towards negative");
@@ -451,7 +447,7 @@ public enum LodDirection
private final int step;
private final String name;
private AxisDirection(int newStep, String newName)
AxisDirection(int newStep, String newName)
{
this.step = newStep;
this.name = newName;
@@ -31,20 +31,22 @@ package com.seibel.lod.core.enums.config;
*/
public enum BufferRebuildTimes
{
FREQUENT(1000, 500, 2500),
FREQUENT(1000, 500, 2500, 1),
NORMAL(2000, 1000, 5000),
NORMAL(2000, 1000, 5000, 4),
RARE(5000, 2000, 10000);
RARE(5000, 2000, 10000, 16);
public final int playerMoveTimeout;
public final int renderedChunkTimeout;
public final int chunkChangeTimeout;
public final int playerMoveDistance;
BufferRebuildTimes(int playerMoveTimeout, int renderedChunkTimeout, int chunkChangeTimeout)
BufferRebuildTimes(int playerMoveTimeout, int renderedChunkTimeout, int chunkChangeTimeout, int playerMoveDistance)
{
this.playerMoveTimeout = playerMoveTimeout;
this.renderedChunkTimeout = renderedChunkTimeout;
this.chunkChangeTimeout = chunkChangeTimeout;
this.playerMoveDistance = playerMoveDistance;
}
}
@@ -19,6 +19,8 @@
package com.seibel.lod.core.enums.config;
import org.jetbrains.annotations.Nullable;
/**
* NONE <br>
* BIOME_ONLY <br>
@@ -92,4 +94,46 @@ public enum DistanceGenerationMode
{
this.complexity = complexity;
}
// Note: return null if out of range
@Nullable
public static DistanceGenerationMode previous(DistanceGenerationMode mode) {
switch (mode) {
case FULL:
return DistanceGenerationMode.FEATURES;
case FEATURES:
return DistanceGenerationMode.SURFACE;
case SURFACE:
return DistanceGenerationMode.BIOME_ONLY_SIMULATE_HEIGHT;
case BIOME_ONLY_SIMULATE_HEIGHT:
return DistanceGenerationMode.BIOME_ONLY;
case BIOME_ONLY:
return DistanceGenerationMode.NONE;
case NONE:
return null;
default:
return null;
}
}
// Note: return null if out of range
@Nullable
public static DistanceGenerationMode next(DistanceGenerationMode mode) {
switch (mode) {
case FULL:
return null;
case FEATURES:
return DistanceGenerationMode.FULL;
case SURFACE:
return DistanceGenerationMode.FEATURES;
case BIOME_ONLY_SIMULATE_HEIGHT:
return DistanceGenerationMode.SURFACE;
case BIOME_ONLY:
return DistanceGenerationMode.BIOME_ONLY_SIMULATE_HEIGHT;
case NONE:
return DistanceGenerationMode.BIOME_ONLY;
default:
return null;
}
}
}
@@ -20,6 +20,7 @@
package com.seibel.lod.core.enums.config;
/**
* AUTO <br>
* Near_First <br>
* Far_First <br>
* <br>
@@ -27,10 +28,13 @@ package com.seibel.lod.core.enums.config;
* outside the normal view distance.
*
* @author Leonardo Amato
* @version 9-25-2021
* @version 12-1-2021
*/
public enum GenerationPriority
{
/** NEAR_FIRST when connected to servers and FAR_FIRST when on single player */
AUTO,
NEAR_FIRST,
FAR_FIRST
@@ -20,22 +20,40 @@
package com.seibel.lod.core.enums.config;
/**
* Buffer_Storage, Sub_Data, Buffer_Mapping
* Auto, Buffer_Storage, Sub_Data, Buffer_Mapping, Data
*
* @author James Seibel
* @version 11-21-2021
* @version 12-1-2021
*/
public enum GpuUploadMethod
{
/** Default if OpenGL 4.5 is supported. Fast rendering, no stuttering. */
/** Picks the best option based on the GPU the user has. */
AUTO,
/**
* Default for NVIDIA if OpenGL 4.5 is supported. <br>
* Fast rendering, no stuttering.
*/
BUFFER_STORAGE,
/** Default if OpenGL 4.5 is NOT supported. Fast rendering but may stutter when uploading. */
/**
* Backup option for NVIDIA. <br>
* Fast rendering but may stutter when uploading.
*/
SUB_DATA,
/** Fast rendering but will stutter when uploading. */
/**
* Default option for AMD/Intel. <br>
* May end up storing buffers in System memory. <br>
* Fast rending if in GPU memory, slow if in system memory, <br>
* but won't stutter when uploading.
*/
BUFFER_MAPPING,
/**
* Backup option for AMD/Intel. <br>
* Fast rendering but may stutter when uploading.
*/
DATA,
/** May end up storing buffers in System memory. Slower rendering but won't stutter when uploading. */
BUFFER_MAPPING,
}
@@ -1,62 +0,0 @@
/*
* 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.core.enums.config;
import com.seibel.lod.core.builders.bufferBuilding.lodTemplates.AbstractLodTemplate;
import com.seibel.lod.core.builders.bufferBuilding.lodTemplates.CubicLodTemplate;
import com.seibel.lod.core.builders.bufferBuilding.lodTemplates.DynamicLodTemplate;
import com.seibel.lod.core.builders.bufferBuilding.lodTemplates.TriangularLodTemplate;
/**
* Cubic, Triangular, Dynamic
*
* @author James Seibel
* @version 10-10-2021
*/
public enum LodTemplate
{
/**
* LODs are rendered as
* rectangular prisms.
*/
CUBIC(new CubicLodTemplate()),
/**
* LODs smoothly transition between
* each other.
*/
TRIANGULAR(new TriangularLodTemplate()),
/**
* LODs smoothly transition between
* each other, unless a neighboring LOD
* is at a significantly different height.
*/
DYNAMIC(new DynamicLodTemplate());
public final AbstractLodTemplate template;
LodTemplate(AbstractLodTemplate newTemplate)
{
template = newTemplate;
}
}
@@ -19,6 +19,8 @@
package com.seibel.lod.core.enums.config;
import org.jetbrains.annotations.Nullable;
/**
* heightmap <br>
* multi_lod <br>
@@ -77,4 +79,34 @@ public enum VerticalQuality
{
this.maxVerticalData = maxVerticalData;
}
// Note: return null if out of range
@Nullable
public static VerticalQuality previous(VerticalQuality mode) {
switch (mode) {
case HIGH:
return VerticalQuality.MEDIUM;
case MEDIUM:
return VerticalQuality.LOW;
case LOW:
return null;
default:
return null;
}
}
// Note: return null if out of range
@Nullable
public static VerticalQuality next(VerticalQuality mode) {
switch (mode) {
case HIGH:
return null;
case MEDIUM:
return VerticalQuality.HIGH;
case LOW:
return VerticalQuality.MEDIUM;
default:
return null;
}
}
}
@@ -20,14 +20,23 @@
package com.seibel.lod.core.enums.rendering;
/**
* fast, fancy, or off
* USE_DEFAULT_FOG_COLOR, <br>
* USE_SKY_COLOR, <br>
*
* @author James Seibel
* @version 02-14-2021
* @version 11-27-2021
*/
public enum FogQuality
public enum FogColorMode
{
FAST,
FANCY,
OFF
/** Fog uses Minecraft's fog color. */
USE_WORLD_FOG_COLOR,
/**
* Replicates the effect of the clear sky mod.
* Making the fog blend in with the sky better
* https://www.curseforge.com/minecraft/mc-mods/clear-skies
* https://www.curseforge.com/minecraft/mc-mods/clear-skies-forge-port
* For it to look good you need one of those mods
*/
USE_SKY_COLOR,
}
@@ -23,16 +23,11 @@ package com.seibel.lod.core.enums.rendering;
* NEAR, FAR, or NEAR_AND_FAR.
*
* @author James Seibel
* @version 02-14-2021
* @version 11-26-2021
*/
public enum FogDistance
{
/** good for fast or fancy fog qualities. */
NEAR,
/** good for fast or fancy fog qualities. */
FAR,
/** only looks good if the fog quality is set to Fancy. */
NEAR_AND_FAR
}
@@ -21,27 +21,20 @@ package com.seibel.lod.core.enums.rendering;
/**
* USE_OPTIFINE_FOG_SETTING, <br>
* NEVER_DRAW_FOG, <br>
* ALWAYS_DRAW_FOG_FAST, <br>
* ALWAYS_DRAW_FOG_FANCY <br>
* FOG_ENABLED, <br>
* FOG_DISABLED <br>
*
* @author James Seibel
* @version 7-3-2021
* @version 11-27-2021
*/
public enum FogDrawOverride
public enum FogDrawMode
{
/**
* Use whatever Fog setting optifine is using.
* If optifine isn't installed this defaults to ALWAYS_DRAW_FOG.
*/
OPTIFINE_SETTING,
USE_OPTIFINE_SETTING,
/** Never draw fog on the LODs */
NO_FOG,
/** Always draw fast fog on the LODs */
FAST,
/** Always draw fancy fog on the LODs */
FANCY
}
FOG_ENABLED,
FOG_DISABLED
}
@@ -19,8 +19,7 @@
package com.seibel.lod.core.handlers;
import com.seibel.lod.core.enums.rendering.FogQuality;
import com.seibel.lod.core.objects.math.Mat4f;
import com.seibel.lod.core.enums.rendering.FogDrawMode;
/**
* A singleton used to get variables from methods
@@ -35,24 +34,16 @@ import com.seibel.lod.core.objects.math.Mat4f;
* different MC versions.
*
* @author James Seibel
* @version 11-20-2021
* @version 12-14-2021
*/
public interface IReflectionHandler
{
/** @returns the type of fog optifine is currently set to render. */
public FogQuality getFogQuality();
/** @returns Whether Optifine is set to render fog or not. */
FogDrawMode getFogDrawMode();
/** @returns if Vivecraft is present. Attempts to find the "VRRenderer" class. */
public boolean vivecraftPresent();
boolean vivecraftPresent();
/**
* Modifies the projection matrix's clip planes.
* The projection matrix must be in column-major format.
*
* @param projectionMatrix The projection matrix to be modified.
* @param newNearClipPlane the new near clip plane value.
* @param newFarClipPlane the new far clip plane value.
* @return The modified matrix.
*/
public Mat4f ModifyProjectionClipPlanes(Mat4f projectionMatrix, float newNearClipPlane, float newFarClipPlane);
/** @returns if Sodium (or a sodium like) mod is present. Attempts to find the "SodiumWorldRenderer" class. */
boolean sodiumPresent();
}
@@ -23,11 +23,13 @@ import java.io.*;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.util.Arrays;
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 org.jetbrains.annotations.Nullable;
import com.seibel.lod.core.api.ClientApi;
import com.seibel.lod.core.enums.config.DistanceGenerationMode;
@@ -77,7 +79,7 @@ public class LodDimensionFileHandler
* file handler, older versions (smaller numbers) will be deleted and overwritten,
* newer versions (larger numbers) will be ignored and won't be read.
*/
public static final int LOD_SAVE_FILE_VERSION = 6;
public static final int LOD_SAVE_FILE_VERSION = 8;
/**
* Allow saving asynchronously, but never try to save multiple regions
@@ -99,6 +101,8 @@ public class LodDimensionFileHandler
//================//
// read from file //
//================//
@@ -115,111 +119,65 @@ public class LodDimensionFileHandler
for (byte tempDetailLevel = LodUtil.REGION_DETAIL_LEVEL; tempDetailLevel >= detailLevel; tempDetailLevel--)
{
String fileName = getFileNameAndPathForRegion(regionX, regionZ, generationMode, tempDetailLevel, verticalQuality);
File file = getBestMatchingRegionFile(tempDetailLevel, regionX, regionZ, generationMode, verticalQuality);
if (file == null) continue; // Failed to find the file for this detail level. continue and try next one
try
long fileSize = file.length();
if (fileSize == 0) continue; // file is empty. Let's not try parsing empty files
try (XZCompressorInputStream inputStream = new XZCompressorInputStream(new FileInputStream(file)))
{
// if the fileName was null that means the folder is inaccessible
// for some reason
if (fileName == null)
throw new IllegalArgumentException("Unable to read region [" + regionX + ", " + regionZ + "] file, no fileName.");
int fileVersion;
fileVersion = inputStream.read();
File file = new File(fileName);
if (!file.exists())
// check if this file can be read by this file handler
if (fileVersion < 6)
{
//there is no file for current gen mode
//search others above current from the most to the least detailed
DistanceGenerationMode tempGenMode = DistanceGenerationMode.FULL;
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.FULL)
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;
// the file we are reading is an older version,
// close the reader and delete the file.
inputStream.close();
file.delete();
ClientApi.LOGGER.info("Outdated LOD region file for region: (" + regionX + "," + regionZ + ")"
+ " version found: " + fileVersion
+ ", version requested: " + LOD_SAVE_FILE_VERSION
+ ". File has been deleted.");
// This should not break, but be continue to see whether other detail levels can be loaded or updated
continue;
}
// don't try parsing empty files
long dataSize = file.length();
dataSize -= 1;
if (dataSize > 0)
else if (fileVersion > LOD_SAVE_FILE_VERSION)
{
try (XZCompressorInputStream inputStream = new XZCompressorInputStream(new FileInputStream(file)))
{
int fileVersion;
fileVersion = inputStream.read();
// check if this file can be read by this file handler
if (fileVersion < LOD_SAVE_FILE_VERSION)
{
// the file we are reading is an older version,
// close the reader and delete the file.
inputStream.close();
file.delete();
ClientApi.LOGGER.info("Outdated LOD region file for region: (" + regionX + "," + regionZ + ")"
+ " version found: " + fileVersion
+ ", version requested: " + LOD_SAVE_FILE_VERSION
+ ". File was been deleted.");
break;
}
else if (fileVersion > LOD_SAVE_FILE_VERSION)
{
// the file we are reading is a newer version,
// close the reader and ignore the file, we don't
// want to accidentally delete anything the user may want.
inputStream.close();
ClientApi.LOGGER.info("Newer LOD region file for region: (" + regionX + "," + regionZ + ")"
+ " version found: " + fileVersion
+ ", version requested: " + LOD_SAVE_FILE_VERSION
+ " this region will not be written to in order to protect the newer file.");
break;
}
// this file is a readable version,
// read the file
byte[] data = ThreadMapUtil.getSaveContainer(tempDetailLevel);
inputStream.read(data);
inputStream.close();
// add the data to our region
region.addLevelContainer(new VerticalLevelContainer(data));
}
catch (IOException ioEx)
{
ClientApi.LOGGER.error("LOD file read error. Unable to read to [" + fileName + "] error [" + ioEx.getMessage() + "]: ");
ioEx.printStackTrace();
}
// the file we are reading is a newer version,
// close the reader and ignore the file, we don't
// want to accidentally delete anything the user may want.
inputStream.close();
ClientApi.LOGGER.info("Newer LOD region file for region: (" + regionX + "," + regionZ + ")"
+ " version found: " + fileVersion
+ ", version requested: " + LOD_SAVE_FILE_VERSION
+ " this region will not be written to in order to protect the newer file.");
// This should not break, but be continue to see whether other detail levels can be loaded or updated
continue;
}
else if (fileVersion < LOD_SAVE_FILE_VERSION)
{
ClientApi.LOGGER.debug("Old LOD region file for region: (" + regionX + "," + regionZ + ")"
+ " version found: " + fileVersion
+ ", version requested: " + LOD_SAVE_FILE_VERSION
+ ". File will be loaded and updated to new format in next save.");
// this is old, but readable version
// read and add the data to our region
region.addLevelContainer(new VerticalLevelContainer(new DataInputStream(inputStream), fileVersion));
inputStream.close();
} else
{
// this file is a readable version,
// read and add the data to our region
region.addLevelContainer(new VerticalLevelContainer(new DataInputStream(inputStream), LOD_SAVE_FILE_VERSION));
inputStream.close();
}
}
catch (Exception e)
catch (IOException ioEx)
{
// the buffered reader encountered a
// problem reading the file
ClientApi.LOGGER.error("LOD file read error. Unable to read to [" + fileName + "] error [" + e.getMessage() + "]: ");
e.printStackTrace();
ClientApi.LOGGER.error("LOD file read error. Unable to read xz compressed file [" + file + "] error [" + ioEx.getMessage() + "]: ");
ioEx.printStackTrace();
}
}// for each detail level
@@ -274,121 +232,111 @@ 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());
// Get the old file
File oldFile = getRegionFile(region.regionPosX, region.regionPosZ, region.getGenerationMode(), detailLevel, region.getVerticalQuality());
ClientApi.LOGGER.debug("saving region [" + region.regionPosX + ", " + region.regionPosZ + "] to file.");
// if the fileName was null that means the folder is inaccessible
// for some reason
if (fileName == null)
boolean isFileFullyGened = false;
// make sure the file and folder exists
if (!oldFile.exists())
{
ClientApi.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.");
byte[] temp = region.getLevel(detailLevel).toDataString();
try
{
// make sure the file and folder exists
if (!oldFile.exists())
{
// the file doesn't exist,
// create it and the folder if need be
if (!oldFile.getParentFile().exists())
oldFile.getParentFile().mkdirs();
// the file doesn't exist,
// create it and the folder if need be
if (!oldFile.getParentFile().exists())
oldFile.getParentFile().mkdirs();
try {
oldFile.createNewFile();
}
else
{
// the file exists, make sure it
// is the correct version.
// (to make sure we don't overwrite a newer
// version file if it exists)
int fileVersion = LOD_SAVE_FILE_VERSION;
int isFull = 0;
try (XZCompressorInputStream inputStream = new XZCompressorInputStream(new FileInputStream(oldFile)))
{
fileVersion = inputStream.read();
inputStream.skip(1);
isFull = inputStream.read() & 0b10000000;
inputStream.close();
}
catch (IOException ex)
{
ex.printStackTrace();
}
// check if this file can be written to by the file handler
if (fileVersion > LOD_SAVE_FILE_VERSION)
{
// the file we are reading is a newer version,
// don't write anything, we don't want to accidentally
// delete anything the user may want.
return;
}
if ((temp[1] & 0b10000000) != 0b10000000 && isFull == 0b10000000)
{
// existing file is complete while new one is only partially generate
// this can happen is for some reason loading failed
// this doesn't fix the bug, but at least protects old data
ClientApi.LOGGER.error("LOD file write error. Attempted to overwrite complete region with incomplete one [" + fileName + "]");
return;
}
// if we got this far then we are good
// to overwrite the old file
}
// the old file is good, now create a new temporary save file
File newFile = new File(fileName + TMP_FILE_EXTENSION);
try (XZCompressorOutputStream outputStream = new XZCompressorOutputStream(new FileOutputStream(newFile), 3))
{
// add the version of this file
outputStream.write(LOD_SAVE_FILE_VERSION);
// add each LodChunk to the file
outputStream.write(temp);
outputStream.close();
// overwrite the old file with the new one
Files.move(newFile.toPath(), oldFile.toPath(), StandardCopyOption.ATOMIC_MOVE, StandardCopyOption.REPLACE_EXISTING);
}
catch (IOException ex)
{
ex.printStackTrace();
} catch (IOException e) {
ClientApi.LOGGER.error("LOD file write error. Unable to create parent directory for [" + oldFile + "] error [" + e.getMessage() + "]: ");
e.printStackTrace();
continue;
}
}
catch (Exception e)
else
{
ClientApi.LOGGER.error("LOD file write error. Unable to write to [" + fileName + "] error [" + e.getMessage() + "]: ");
// the file exists, make sure it
// is the correct version.
// (to make sure we don't overwrite a newer
// version file if it exists)
int fileVersion = LOD_SAVE_FILE_VERSION;
try (XZCompressorInputStream inputStream = new XZCompressorInputStream(new FileInputStream(oldFile)))
{
fileVersion = inputStream.read();
inputStream.skip(1);
isFileFullyGened = (inputStream.read() & 0b10000000) != 0;
inputStream.close();
}
catch (IOException e)
{
ClientApi.LOGGER.warn("LOD file write warning. Unable to read existing file [" + oldFile + "] version. Treating it as latest version. [" + e.getMessage() + "]: ");
e.printStackTrace();
}
// check if this file can be written to by the file handler
if (fileVersion > LOD_SAVE_FILE_VERSION)
{
// the file we are reading is a newer version,
// don't write anything, we don't want to accidentally
// delete anything the user may want.
continue;
}
// if we got this far then we are good
// to overwrite the old file
}
// Now create a new temporary save file
File tempFile;
try {
tempFile = File.createTempFile(oldFile.getName(), TMP_FILE_EXTENSION);
} catch (IOException e) {
ClientApi.LOGGER.error("LOD file write error. Unable to create temp file for [" + oldFile + "] error [" + e.getMessage() + "]: ");
e.printStackTrace();
continue;
}
tempFile.deleteOnExit(); // Mark it to be deleted on exit if any unexcepted terminations happen
try (XZCompressorOutputStream outputStream = new XZCompressorOutputStream(new FileOutputStream(tempFile), 3))
{
// add the version of this file
outputStream.write(LOD_SAVE_FILE_VERSION);
// add each LodChunk to the file
boolean isNewDataFullyGened = region.getLevel(detailLevel).writeData(new DataOutputStream(outputStream));
outputStream.close();
if (!isNewDataFullyGened && isFileFullyGened)
{
// existing file is complete while new one is only partially generate
// this can happen is for some reason loading failed
// this doesn't fix the bug, but at least protects old data
ClientApi.LOGGER.error("LOD file write error. Attempted to overwrite complete region with incomplete one [" + oldFile + "]");
try {
tempFile.delete();
} catch (SecurityException e) {
// Failed to delete temp file... just continue.
}
continue;
}
}
catch (IOException e)
{
ClientApi.LOGGER.error("LOD file write error. Unable to write to temp file [" + tempFile + "] error [" + e.getMessage() + "]: ");
e.printStackTrace();
continue;
}
// overwrite the old file with the new one
try {
Files.move(tempFile.toPath(), oldFile.toPath(), StandardCopyOption.ATOMIC_MOVE, StandardCopyOption.REPLACE_EXISTING);
} catch (IOException e) {
ClientApi.LOGGER.error("LOD file write error. Unable to update file [" + oldFile + "] error [" + e.getMessage() + "]: ");
e.printStackTrace();
}
}
}
//================//
// helper methods //
//================//
public byte[] getHashFromFile(byte detailLevel, RegionPos regionPos, DistanceGenerationMode generationMode, VerticalQuality verticalQuality)
{
int regionX = regionPos.x;
int regionZ = regionPos.z;
String fileName = getFileNameAndPathForRegion(regionX, regionZ, generationMode, detailLevel, verticalQuality);
try (InputStream is = Files.newInputStream(Paths.get(fileName))) {
return org.apache.commons.codec.digest.DigestUtils.md5(is);
}
catch (IOException ioEx)
{
ClientApi.LOGGER.error("LOD file read error. Unable to read to [" + fileName + "] error [" + ioEx.getMessage() + "]: ");
ioEx.printStackTrace();
}
return new byte[0];
}
/**
* Return the name of the file that should contain the
* region at the given x and z. <br>
@@ -398,26 +346,40 @@ public class LodDimensionFileHandler
* <p>
* Returns null if there is an IO or security Exception.
*/
private String getFileNameAndPathForRegion(int regionX, int regionZ, DistanceGenerationMode generationMode, byte detailLevel, VerticalQuality verticalQuality)
{
try
{
// saveFolder is something like
// ".\Super Flat\DIM-1\data\"
// or
// ".\Super Flat\data\"
return dimensionDataSaveFolder.getCanonicalPath() + File.separatorChar +
verticalQuality + File.separatorChar +
generationMode.toString() + File.separatorChar +
DETAIL_FOLDER_NAME_PREFIX + detailLevel + File.separatorChar +
FILE_NAME_PREFIX + "." + regionX + "." + regionZ + FILE_EXTENSION;
}
catch (IOException | SecurityException e)
{
ClientApi.LOGGER.warn("Unable to get the filename for the region [" + regionX + ", " + regionZ + "], error: [" + e.getMessage() + "], stacktrace: ");
e.printStackTrace();
return null;
private String getFileBasePath() {
try {
return dimensionDataSaveFolder.getCanonicalPath() + File.separatorChar;
} catch (IOException e) {
ClientApi.LOGGER.warn("Unable to get the base save file path. One possible cause is that"
+ " the process failed to read the current path location due to security configs.");
throw new RuntimeException("DistantHorizons Get Save File Path Failure");
}
}
private File getRegionFile(int regionX, int regionZ, DistanceGenerationMode genMode, byte detail, VerticalQuality vertQuality) {
return new File(getFileBasePath() + vertQuality + File.separatorChar +
genMode + File.separatorChar +
DETAIL_FOLDER_NAME_PREFIX + detail + File.separatorChar +
FILE_NAME_PREFIX + "." + regionX + "." + regionZ + FILE_EXTENSION);
}
// Return null if no file found
@Nullable
private File getBestMatchingRegionFile(byte detailLevel, int regionX, int regionZ, DistanceGenerationMode targetGenMode, VerticalQuality targetVertQuality) {
DistanceGenerationMode genMode = targetGenMode;
// Search from least GenMode to max GenMode, than least vertQuality to max vertQuality
do {
File file = getRegionFile(regionX, regionZ, genMode, detailLevel, targetVertQuality);
if (file.exists()) return file; // Found target file.
targetGenMode = DistanceGenerationMode.next(targetGenMode);
if (targetGenMode == null) { // Failed to find any files for this vertQuality. Try next one up.
targetGenMode = genMode;
targetVertQuality = VerticalQuality.next(targetVertQuality);
}
} while (targetVertQuality != null);
return null;
}
}
@@ -25,27 +25,29 @@ import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import com.seibel.lod.core.ModInfo;
import com.seibel.lod.core.enums.rendering.FogQuality;
import com.seibel.lod.core.objects.math.Mat4f;
import com.seibel.lod.core.enums.rendering.FogDrawMode;
/**
* A singleton used to get variables from methods
* where they are private or potentially absent.
* Specifically the fog setting in Optifine or the
* presence/absence of other mods.
* For example: the fog setting in Optifine or the
* presence/absence of Vivecraft.
*
* @author James Seibel
* @version 11-20-2021
* @version 12-14-2021
*/
public class ReflectionHandler implements IReflectionHandler
{
private static final Logger LOGGER = LogManager.getLogger(ModInfo.NAME + "-" + ReflectionHandler.class.getSimpleName());
private static ReflectionHandler instance;
public static ReflectionHandler instance;
private Field ofFogField = null;
private final Object mcOptionsObject;
private Boolean sodiumPresent = null;
private ReflectionHandler(Field[] optionFields, Object newMcOptionsObject)
@@ -75,6 +77,8 @@ public class ReflectionHandler implements IReflectionHandler
/** finds the Optifine fog type field */
private void setupFogField(Field[] optionFields)
{
@@ -100,14 +104,14 @@ public class ReflectionHandler implements IReflectionHandler
* @return the fog quality
*/
@Override
public FogQuality getFogQuality()
public FogDrawMode getFogDrawMode()
{
if (ofFogField == null)
{
// either optifine isn't installed,
// the variable name was changed, or
// the setup method wasn't called yet.
return FogQuality.FANCY;
return FogDrawMode.FOG_ENABLED;
}
int returnNum = 0;
@@ -129,12 +133,11 @@ public class ReflectionHandler implements IReflectionHandler
// it should never be called in this case
// normal options
case 1:
return FogQuality.FAST;
case 2:
return FogQuality.FANCY;
case 3:
return FogQuality.OFF;
case 1: // fast
case 2: // fancy
return FogDrawMode.FOG_ENABLED;
case 3: // off
return FogDrawMode.FOG_DISABLED;
}
}
@@ -156,41 +159,29 @@ public class ReflectionHandler implements IReflectionHandler
return false;
}
/**
* Modifies the projection matrix's clip planes.
* The projection matrix must be in column-major format.
*
* @param projectionMatrix The projection matrix to be modified.
* @param newNearClipPlane the new near clip plane value.
* @param newFarClipPlane the new far clip plane value.
* @return The modified matrix.
*/
@Override
public Mat4f ModifyProjectionClipPlanes(Mat4f projectionMatrix, float newNearClipPlane, float newFarClipPlane)
public boolean sodiumPresent()
{
// find the matrix values.
float nearMatrixValue = -((newFarClipPlane + newNearClipPlane) / (newFarClipPlane - newNearClipPlane));
float farMatrixValue = -((2 * newFarClipPlane * newNearClipPlane) / (newFarClipPlane - newNearClipPlane));
// we don't want to run a potentially expensive
// reflection search operation every time this method is called
if (sodiumPresent == null)
{
try
{
Class.forName("me.jellysquid.mods.sodium.client.render.SodiumWorldRenderer");
sodiumPresent = true;
}
catch (ClassNotFoundException e)
{
sodiumPresent = false;
}
}
try
{
// TODO this was originally created before we had the Mat4f object,
// so this doesn't need to be done with reflection anymore.
// And should be moved to RenderUtil
// get the fields of the projectionMatrix
Field[] fields = projectionMatrix.getClass().getDeclaredFields();
// bypass the security protections on the fields that encode near and far plane values.
fields[10].setAccessible(true);
fields[11].setAccessible(true);
// Change the values of the near and far plane.
fields[10].set(projectionMatrix, nearMatrixValue);
fields[11].set(projectionMatrix, farMatrixValue);
}
catch (Exception e)
{
e.printStackTrace();
}
return projectionMatrix;
return sodiumPresent;
}
}
@@ -0,0 +1,104 @@
package com.seibel.lod.core.objects;
import com.seibel.lod.core.wrapperInterfaces.block.IBlockColorWrapper;
import com.seibel.lod.core.wrapperInterfaces.world.IBiomeWrapper;
import java.util.HashMap;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
public class BlockBiomeCouple
{
public static ConcurrentMap<IBlockColorWrapper, BlockBiomeCouple> noBiomeIstanceCache = new ConcurrentHashMap<>();
public static ConcurrentMap<IBiomeWrapper, ConcurrentMap<IBlockColorWrapper, BlockBiomeCouple>> withBiomeIstanceCache = new ConcurrentHashMap<>();
String blockName;
String biomeName;
String coupleName;
IBiomeWrapper biomeColor;
IBlockColorWrapper blockColor;
public static void addBlockBiomeToCache(IBlockColorWrapper blockColor){
}
public static BlockBiomeCouple getBlockBiomeCouple(IBlockColorWrapper blockColor){
if(noBiomeIstanceCache.containsKey(blockColor))
{
return noBiomeIstanceCache.get(blockColor);
}
else
{
BlockBiomeCouple couple = new BlockBiomeCouple(blockColor);
noBiomeIstanceCache.put(blockColor,couple);
return couple;
}
}
public static BlockBiomeCouple getBlockBiomeCouple(IBiomeWrapper biomeColor, IBlockColorWrapper blockColor){
if(biomeColor == null)
{
return getBlockBiomeCouple(blockColor);
}
else
{
if(withBiomeIstanceCache.containsKey(biomeColor))
{
withBiomeIstanceCache.put(biomeColor, new ConcurrentHashMap<>());
}
ConcurrentMap<IBlockColorWrapper, BlockBiomeCouple> blockToCoupleMap = withBiomeIstanceCache.get(biomeColor);
if(blockToCoupleMap.containsKey(blockColor))
{
return blockToCoupleMap.get(blockColor);
}
else
{
BlockBiomeCouple couple = new BlockBiomeCouple(blockColor,biomeColor);
blockToCoupleMap.put(blockColor,couple);
return couple;
}
}
}
public BlockBiomeCouple(IBlockColorWrapper blockColor)
{
this.biomeColor = null;
this.blockColor = blockColor;
biomeName = "";
blockName = blockColor.getName();
coupleName = blockName;
}
public BlockBiomeCouple(IBlockColorWrapper blockColor, IBiomeWrapper biomeColor)
{
this.biomeColor = biomeColor;
this.blockColor = blockColor;
if(biomeColor == null)
biomeName = biomeColor.getName();
else
biomeName = "";
blockName = blockColor.getName();
coupleName = blockName + biomeName;
}
@Override public boolean equals(Object o)
{
if (this == o)
return true;
if (!(o instanceof BlockBiomeCouple))
return false;
BlockBiomeCouple that = (BlockBiomeCouple) o;
return Objects.equals(blockName, that.blockName) && Objects.equals(biomeName, that.biomeName);
}
@Override public int hashCode()
{
return Objects.hash(blockName, biomeName);
}
}
@@ -28,9 +28,9 @@ package com.seibel.lod.core.objects;
*/
public class MinDefaultMax<T>
{
public T minValue;
public T defaultValue;
public T maxValue;
public final T minValue;
public final T defaultValue;
public final T maxValue;
public MinDefaultMax(T newMinValue, T newDefaultValue, T newMaxValue)
{
@@ -35,12 +35,11 @@ import com.seibel.lod.core.wrapperInterfaces.config.ILodConfigWrapperSingleton;
import com.seibel.lod.core.wrapperInterfaces.minecraft.IMinecraftWrapper;
/**
* Similar to Minecraft's AxisAlignedBoundingBox.
*
* This class handles all the vertex optimization that's needed for a column of lods. W
* @author Leonardo Amato
* @version 10-2-2021
*/
public class Box
public class VertexOptimizer
{
private static final IMinecraftWrapper MC = SingletonHandler.get(IMinecraftWrapper.class);
private static final ILodConfigWrapperSingleton CONFIG = SingletonHandler.get(ILodConfigWrapperSingleton.class);
@@ -201,13 +200,14 @@ public class Box
public final Map<LodDirection, byte[]> skyLights;
public byte blockLight;
/** Holds if the given direction should be culled or not */
public final boolean[] culling;
boolean skipTop;
boolean skipBot;
/** creates an empty box */
@SuppressWarnings("serial")
public Box()
public VertexOptimizer()
{
boxOffset = new int[3];
boxWidth = new int[3];
@@ -236,8 +236,6 @@ public class Box
put(LodDirection.SOUTH, new int[LodUtil.MAX_NUMBER_OF_VERTICAL_LODS]);
put(LodDirection.NORTH, new int[LodUtil.MAX_NUMBER_OF_VERTICAL_LODS]);
}};
culling = new boolean[6];
}
/** Set the light of the columns */
@@ -310,31 +308,6 @@ public class Box
}
}
/** determine which faces should be culled */
public void setUpCulling(int cullingDistance, AbstractBlockPosWrapper playerPos)
{
for (LodDirection lodDirection : DIRECTIONS)
{
if (lodDirection == LodDirection.DOWN || lodDirection == LodDirection.WEST || lodDirection == LodDirection.NORTH)
culling[DIRECTION_INDEX.get(lodDirection)] = playerPos.get(lodDirection.getAxis()) > getFacePos(lodDirection) + cullingDistance;
else if (lodDirection == LodDirection.UP || lodDirection == LodDirection.EAST || lodDirection == LodDirection.SOUTH)
culling[DIRECTION_INDEX.get(lodDirection)] = playerPos.get(lodDirection.getAxis()) < getFacePos(lodDirection) - cullingDistance;
culling[DIRECTION_INDEX.get(lodDirection)] = false;
}
}
/**
* @param lodDirection 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(LodDirection lodDirection)
{
return culling[DIRECTION_INDEX.get(lodDirection)];
}
/**
* 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
@@ -347,15 +320,13 @@ public class Box
int maxY = getMaxY();
long singleAdjDataPoint;
/* TODO implement attached vertical face culling
// TODO transparency uncomment final condition to see ocean floor
//Up direction case
if(DataPointUtil.doesItExist(adjData.get(Direction.UP)))
{
height = DataPointUtil.getHeight(singleAdjDataPoint);
depth = DataPointUtil.getDepth(singleAdjDataPoint);
}*/
singleAdjDataPoint = adjData.get(LodDirection.UP)[0];
skipTop = DataPointUtil.doesItExist(singleAdjDataPoint) && DataPointUtil.getDepth(singleAdjDataPoint) == maxY;// && DataPointUtil.getAlpha(singleAdjDataPoint) == 255;
//Down direction case
singleAdjDataPoint = adjData.get(LodDirection.DOWN)[0];
skipBot = DataPointUtil.doesItExist(singleAdjDataPoint) && DataPointUtil.getHeight(singleAdjDataPoint) == minY;// && DataPointUtil.getAlpha(singleAdjDataPoint) == 255;
if(DataPointUtil.doesItExist(singleAdjDataPoint))
skyLights.get(LodDirection.DOWN)[0] = DataPointUtil.getLightSkyAlt(singleAdjDataPoint);
else
@@ -364,9 +335,6 @@ public class Box
//TODO clean some similar cases
for (LodDirection lodDirection : ADJ_DIRECTIONS)
{
if (isCulled(lodDirection))
continue;
long[] dataPoint = adjData.get(lodDirection);
if (dataPoint == null || DataPointUtil.isVoid(dataPoint[0]))
{
@@ -374,7 +342,7 @@ public class Box
adjDepth.get(lodDirection)[0] = minY;
adjHeight.get(lodDirection)[1] = VOID_FACE;
adjDepth.get(lodDirection)[1] = VOID_FACE;
skyLights.get(lodDirection)[0] = 15; //in void set full sky light
skyLights.get(lodDirection)[0] = 15; //in void set full skylight
continue;
}
@@ -384,17 +352,22 @@ public class Box
boolean toFinish = false;
int toFinishIndex = 0;
boolean allAbove = true;
// TODO transparency ocean floor fix
//boolean isOpaque = ((colorMap[0] >> 24) & 0xFF) == 255;
for (i = 0; i < dataPoint.length; i++)
{
singleAdjDataPoint = dataPoint[i];
if (DataPointUtil.isVoid(singleAdjDataPoint) || !DataPointUtil.doesItExist(singleAdjDataPoint))
break;
// TODO transparency ocean floor fix
//if (isOpaque && DataPointUtil.getAlpha(singleAdjDataPoint) != 255)
// continue;
height = DataPointUtil.getHeight(singleAdjDataPoint);
depth = DataPointUtil.getDepth(singleAdjDataPoint);
if (depth <= maxY)
if (depth < maxY)
{
allAbove = false;
if (height < minY)
@@ -448,7 +421,7 @@ public class Box
}
break;
}
else if (height >= maxY)//depth > minY &&
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
@@ -521,24 +494,14 @@ public class Box
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 lodDirection that we want to check
* @return position in the axis of the face
*/
public int getFacePos(LodDirection lodDirection)
{
return boxOffset[FACE_DIRECTION.get(lodDirection)[0]] + boxWidth[FACE_DIRECTION.get(lodDirection)[0]] * FACE_DIRECTION.get(lodDirection)[1];
}
/**
* returns true if the given direction should be rendered.
*/
/** returns true if the given direction should be rendered. */
public boolean shouldRenderFace(LodDirection lodDirection, int adjIndex)
{
if (lodDirection == LodDirection.UP || lodDirection == LodDirection.DOWN)
return adjIndex == 0;
if (lodDirection == LodDirection.UP)
return adjIndex == 0 && !skipTop;
if (lodDirection == LodDirection.DOWN)
return adjIndex == 0 && !skipBot;
return !(adjHeight.get(lodDirection)[adjIndex] == VOID_FACE && adjDepth.get(lodDirection)[adjIndex] == VOID_FACE);
}
@@ -19,6 +19,10 @@
package com.seibel.lod.core.objects.lod;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.OutputStream;
/**
* A level container is a quad tree level
*/
@@ -81,7 +85,7 @@ public interface LevelContainer
byte getDetailLevel();
int getMaxVerticalData();
int getVerticalSize();
/** Clears the dataPoint at the given array index */
void clear(int posX, int posZ);
@@ -99,13 +103,13 @@ public interface LevelContainer
* @param posZ z position in the detail level to update
*/
void updateData(LevelContainer lowerLevelContainer, int posX, int posZ);
/**
* This will give the data to save in the file
* @return data as a String
* This will write the raw data with metadata to the output stream
* @return isAllGenerated whether the data is all generated
* @throws IOException
*/
byte[] toDataString();
boolean writeData(DataOutputStream output) throws IOException;
/**
* This will give the data to save in the file
@@ -128,7 +128,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 + "Distant_Horizons_server_data" + File.separatorChar + MC.getCurrentDimensionId());
}
fileHandler = new LodDimensionFileHandler(saveDir, this);
@@ -310,6 +310,32 @@ public class LodDimension
regions[xIndex][zIndex] = newRegion;
}
public interface PosComsumer {
void run(int x, int z);
}
public void iterateWithSpiral(PosComsumer r) {
int ox,oy,dx,dy;
ox = oy = dx = 0;
dy = -1;
int len = regions.length;
int maxI = len*len;
int halfLen = len/2;
for(int i =0; i < maxI; i++){
if ((-halfLen <= ox) && (ox <= halfLen) && (-halfLen <= oy) && (oy <= halfLen)){
int x = ox+halfLen;
int z = oy+halfLen;
r.run(x, z);
}
if( (ox == oy) || ((ox < 0) && (ox == -oy)) || ((ox > 0) && (ox == 1-oy))){
int temp = dx;
dx = -dy;
dy = temp;
}
ox += dx;
oy += dy;
}
}
/**
@@ -325,116 +351,99 @@ public class LodDimension
// don't run the tree cutter multiple times
// for the same location
if (newPlayerChunk.getX() != lastCutChunk.getX() || newPlayerChunk.getZ() != lastCutChunk.getZ())
{
if (newPlayerChunk.getX() != lastCutChunk.getX() || newPlayerChunk.getZ() != lastCutChunk.getZ()) {
lastCutChunk = newPlayerChunk;
Thread thread = new Thread(() ->
{
int regionX;
int regionZ;
int minDistance;
byte detail;
byte minAllowedDetailLevel;
Runnable thread = () -> {
// go over every region in the dimension
for (int x = 0; x < regions.length; x++)
{
for (int z = 0; z < regions.length; z++)
{
regionX = (x + center.x) - halfWidth;
regionZ = (z + center.z) - halfWidth;
if (regions[x][z] != null)
{
// check what detail level this region should be
// and cut it if it is higher then that
minDistance = LevelPosUtil.minDistance(LodUtil.REGION_DETAIL_LEVEL, regionX, regionZ, playerPosX, playerPosZ);
detail = DetailDistanceUtil.getTreeCutDetailFromDistance(minDistance);
minAllowedDetailLevel = DetailDistanceUtil.getCutLodDetail(detail);
if (regions[x][z].getMinDetailLevel() > minAllowedDetailLevel)
{
regions[x][z].cutTree(minAllowedDetailLevel);
recreateRegionBuffer[x][z] = true;
}
iterateWithSpiral((int x, int z) -> {
int regionX;
int regionZ;
int minDistance;
byte detail;
byte minAllowedDetailLevel;
regionX = (x + center.x) - halfWidth;
regionZ = (z + center.z) - halfWidth;
if (regions[x][z] != null) {
// check what detail level this region should be
// and cut it if it is higher then that
minDistance = LevelPosUtil.minDistance(LodUtil.REGION_DETAIL_LEVEL, regionX, regionZ,
playerPosX, playerPosZ);
detail = DetailDistanceUtil.getTreeCutDetailFromDistance(minDistance);
minAllowedDetailLevel = DetailDistanceUtil.getCutLodDetail(detail);
if (regions[x][z].getMinDetailLevel() > minAllowedDetailLevel) {
regions[x][z].cutTree(minAllowedDetailLevel);
recreateRegionBuffer[x][z] = true;
}
}// region z
}// region z
});
}
});
};
cutAndExpandThread.execute(thread);
}
}
/** Either expands or loads all regions in the rendered LOD area */
public void expandOrLoadRegionsAsync(int playerPosX, int playerPosZ)
{
public void expandOrLoadRegionsAsync(int playerPosX, int playerPosZ) {
DistanceGenerationMode generationMode = CONFIG.client().worldGenerator().getDistanceGenerationMode();
AbstractChunkPosWrapper newPlayerChunk = FACTORY.createChunkPos(LevelPosUtil.getChunkPos((byte) 0, playerPosX), LevelPosUtil.getChunkPos((byte) 0, playerPosZ));
AbstractChunkPosWrapper newPlayerChunk = FACTORY.createChunkPos(LevelPosUtil.getChunkPos((byte) 0, playerPosX),
LevelPosUtil.getChunkPos((byte) 0, playerPosZ));
VerticalQuality verticalQuality = CONFIG.client().graphics().quality().getVerticalQuality();
if (lastExpandedChunk == null)
lastExpandedChunk = FACTORY.createChunkPos(newPlayerChunk.getX() + 1, newPlayerChunk.getZ() - 1);
// don't run the expander multiple times
// for the same location
if (newPlayerChunk.getX() != lastExpandedChunk.getX() || newPlayerChunk.getZ() != lastExpandedChunk.getZ())
{
if (newPlayerChunk.getX() != lastExpandedChunk.getX() || newPlayerChunk.getZ() != lastExpandedChunk.getZ()) {
lastExpandedChunk = newPlayerChunk;
Thread thread = new Thread(() ->
{
int regionX;
int regionZ;
LodRegion region;
int minDistance;
byte detail;
byte levelToGen;
for (int x = 0; x < regions.length; x++)
{
for (int z = 0; z < regions.length; z++)
{
regionX = (x + center.x) - halfWidth;
regionZ = (z + center.z) - halfWidth;
final RegionPos regionPos = new RegionPos(regionX, regionZ);
region = regions[x][z];
minDistance = LevelPosUtil.minDistance(LodUtil.REGION_DETAIL_LEVEL, regionX, regionZ, playerPosX, playerPosZ);
detail = DetailDistanceUtil.getTreeGenDetailFromDistance(minDistance);
levelToGen = DetailDistanceUtil.getLodGenDetail(detail).detailLevel;
// check that the region isn't null and at least this detail level
if (region == null || region.getGenerationMode() != generationMode)
{
// First case, region has to be created
// try to get the region from file
regions[x][z] = getRegionFromFile(regionPos, levelToGen, generationMode, verticalQuality);
// if there is no region file create an empty region
if (regions[x][z] == null)
regions[x][z] = new LodRegion(levelToGen, regionPos, generationMode, verticalQuality);
regenRegionBuffer[x][z] = true;
regenDimensionBuffers = true;
recreateRegionBuffer[x][z] = true;
}
else if (region.getMinDetailLevel() > levelToGen)
{
// Second case, the region exists at a higher detail level.
// Expand the region by introducing the missing layer
region.growTree(levelToGen);
regions[x][z] = getRegionFromFile(regionPos, levelToGen, generationMode, verticalQuality);
recreateRegionBuffer[x][z] = true;
}
Runnable thread = () -> {
iterateWithSpiral((int x, int z) -> {
int regionX;
int regionZ;
LodRegion region;
int minDistance;
byte detail;
byte levelToGen;
regionX = (x + center.x) - halfWidth;
regionZ = (z + center.z) - halfWidth;
final RegionPos regionPos = new RegionPos(regionX, regionZ);
region = regions[x][z];
minDistance = LevelPosUtil.minDistance(LodUtil.REGION_DETAIL_LEVEL, regionX, regionZ, playerPosX,
playerPosZ);
detail = DetailDistanceUtil.getTreeGenDetailFromDistance(minDistance);
levelToGen = DetailDistanceUtil.getLodGenDetail(detail).detailLevel;
// check that the region isn't null and at least this detail level
if (region == null || region.getGenerationMode() != generationMode) {
// First case, region has to be created
// try to get the region from file
regions[x][z] = getRegionFromFile(regionPos, levelToGen, generationMode, verticalQuality);
// if there is no region file create an empty region
if (regions[x][z] == null)
regions[x][z] = new LodRegion(levelToGen, regionPos, generationMode, verticalQuality);
regenRegionBuffer[x][z] = true;
regenDimensionBuffers = true;
recreateRegionBuffer[x][z] = true;
} else if (region.getMinDetailLevel() > levelToGen) {
// Second case, the region exists at a higher detail level.
// Expand the region by introducing the missing layer
region.growTree(levelToGen);
regions[x][z] = getRegionFromFile(regionPos, levelToGen, generationMode, verticalQuality);
recreateRegionBuffer[x][z] = true;
}
}
});
});
};
cutAndExpandThread.execute(thread);
}
}
@@ -551,7 +560,6 @@ public class LodDimension
// We can use two type of generation scheduling
switch (CONFIG.client().worldGenerator().getGenerationPriority())
{
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
@@ -637,7 +645,7 @@ public class LodDimension
}
break;
default:
case FAR_FIRST:
//in the FAR_FIRST generation we dedicate part of the generation process to the far region with really
//low detail quality.
@@ -680,8 +688,16 @@ public class LodDimension
int playerPosZ)
{
LodRegion region = getRegion(regionPos.x, regionPos.z);
// use FAR_FIRST on local worlds and NEAR_FIRST on servers
GenerationPriority generationPriority = CONFIG.client().worldGenerator().getGenerationPriority();
if (generationPriority == GenerationPriority.AUTO)
generationPriority = MC.hasSinglePlayerServer() ? GenerationPriority.FAR_FIRST : GenerationPriority.NEAR_FIRST;
boolean requireCorrectDetailLevel = generationPriority == GenerationPriority.NEAR_FIRST;
if (region != null)
region.getPosToRender(posToRender, playerPosX, playerPosZ, CONFIG.client().worldGenerator().getGenerationPriority() == GenerationPriority.NEAR_FIRST);
region.getPosToRender(posToRender, playerPosX, playerPosZ, requireCorrectDetailLevel);
}
/**
@@ -162,9 +162,7 @@ public class LodRegion
// The dataContainer could have null entries if the
// detailLevel changes.
if (this.dataContainer[detailLevel] == null)
{
this.dataContainer[detailLevel] = new VerticalLevelContainer(detailLevel);
}
this.dataContainer[detailLevel].addData(data, posX, posZ, verticalIndex);
@@ -179,9 +177,8 @@ public class LodRegion
*/
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);
posX = LevelPosUtil.getRegionModule(detailLevel, posX);
posZ = LevelPosUtil.getRegionModule(detailLevel, posZ);
// The dataContainer could have null entries if the
// detailLevel changes.
@@ -198,6 +195,8 @@ public class LodRegion
*/
public long getData(byte detailLevel, int posX, int posZ, int verticalIndex)
{
posX = LevelPosUtil.getRegionModule(detailLevel, posX);
posZ = LevelPosUtil.getRegionModule(detailLevel, posZ);
return dataContainer[detailLevel].getData(posX, posZ, verticalIndex);
}
@@ -208,6 +207,8 @@ public class LodRegion
*/
public long getSingleData(byte detailLevel, int posX, int posZ)
{
posX = LevelPosUtil.getRegionModule(detailLevel, posX);
posZ = LevelPosUtil.getRegionModule(detailLevel, posZ);
return dataContainer[detailLevel].getSingleData(posX, posZ);
}
@@ -216,6 +217,8 @@ public class LodRegion
*/
public void clear(byte detailLevel, int posX, int posZ)
{
posX = LevelPosUtil.getRegionModule(detailLevel, posX);
posZ = LevelPosUtil.getRegionModule(detailLevel, posZ);
dataContainer[detailLevel].clear(posX, posZ);
}
@@ -450,6 +453,8 @@ public class LodRegion
*/
private void update(byte detailLevel, int posX, int posZ)
{
posX = LevelPosUtil.getRegionModule(detailLevel, posX);
posZ = LevelPosUtil.getRegionModule(detailLevel, posZ);
dataContainer[detailLevel].updateData(dataContainer[detailLevel - 1], posX, posZ);
}
@@ -459,15 +464,12 @@ public class LodRegion
*/
public boolean doesDataExist(byte detailLevel, int posX, int posZ)
{
if (detailLevel < minDetailLevel)
if (detailLevel < minDetailLevel || dataContainer[detailLevel] == null)
return false;
posX = LevelPosUtil.getRegionModule(detailLevel, posX);
posZ = LevelPosUtil.getRegionModule(detailLevel, posZ);
if (dataContainer[detailLevel] == null)
return false;
return dataContainer[detailLevel].doesItExist(posX, posZ);
}
@@ -599,7 +601,7 @@ public class LodRegion
public int getMaxVerticalData(byte detailLevel)
{
return dataContainer[detailLevel].getMaxVerticalData();
return dataContainer[detailLevel].getVerticalSize();
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,13 @@
package com.seibel.lod.core.objects.lod.quadtree;
import com.seibel.lod.core.util.DetailDistanceUtil;
public class LodQuadTree
{
public LodSection[][][] quadTreeStructure;
public QuadTreeProperties properties;
public LodQuadTree()
{
}
}
@@ -0,0 +1,115 @@
package com.seibel.lod.core.objects.lod.quadtree;
/**
A lod section rappresent a distinct section in a precise level of the quadtree.
The section holds all the lods information as color (computed with the blockBiome object referenced by the ID), size, light...
The save and load of a section is handled by the section itself together with a handler class.
*/
public class LodSection
{
//level of detail of this section
public final int detail;
//level position of this section
public final int sectionPosX;
public final int sectionPosZ;
//horizontal size of this section
public final int horizontalSize;
//vertical size of this section
public final int verticalSize;
//how many id we save for each lod
public final int idPerLod;
//What generation mode should be used for chunk in this section
public final byte generationMode;
//if present in region file, use pregenerated chunk in lod creation
public boolean usePregeneratedChunk;
//Position data hold information about each level position like generation mode used, number of vertical lods in that area...
private byte[] positionData;
//Position data hold vertical information about each lod, like minY and maxY...
private int[] verticalData;
//Lights data hold lights information about each lod, like skylight and block light values...
private byte[] lightsData;
//BlockBiomeId hold a unique ID for each lod relative to a block-biome couple. This couple is capable of generating color
//We could just reference
private int[] BlockBiomeId;
//BlockBiomeFrequency for each lod BlockBiomeId
private byte[] BlockBiomeFrequency;
/**
*
* @param detail
* @param horizontalSize
* @param verticalSize
* @param levelPosX
* @param levelPosZ
* @param generationMode
* @param avoidPregeneratedChunk
* @param idPerLod
*/
public LodSection(int detail, int horizontalSize, int verticalSize, int levelPosX, int levelPosZ, byte generationMode, boolean avoidPregeneratedChunk, int idPerLod)
{
this.detail = detail;
this.sectionPosX = levelPosX;
this.sectionPosZ = levelPosZ;
this.horizontalSize = horizontalSize;
this.verticalSize = verticalSize;
this.generationMode = generationMode;
this.usePregeneratedChunk = avoidPregeneratedChunk;
this.idPerLod = idPerLod;
//Now we should search if there is a file with this values
//if so we load it and end the process
//Otherwise we initialize this section
positionData = new byte[horizontalSize*horizontalSize];
verticalData = new int[horizontalSize*horizontalSize*verticalSize];
lightsData = new byte[horizontalSize*horizontalSize*verticalSize];
BlockBiomeId = new int[horizontalSize*horizontalSize*verticalSize*idPerLod];
BlockBiomeFrequency = new byte[horizontalSize*horizontalSize*verticalSize*idPerLod];
}
/**
*
* @param otherSection
* @param inputStartX
* @param inputStartZ
* @param inputEndX
* @param inputEndZ
* @param targetStartX
* @param targetStartZ
* @param targetEndX
* @param targetEndZ
*/
public void mergeAndAdd(LodSection otherSection,
int inputStartX, int inputStartZ, int inputEndX, int inputEndZ,
int targetStartX, int targetStartZ, int targetEndX, int targetEndZ)
{
}
public short getPositionData(int posX, int posZ)
{
return 0;
}
public long[] getData(int posX, int posZ)
{
return null;
}
public void save()
{
}
public void tryload()
{
}
}
@@ -0,0 +1,11 @@
package com.seibel.lod.core.objects.lod.quadtree;
import com.seibel.lod.core.util.DetailDistanceUtil;
public class QuadTreeMover
{
public static void move(LodQuadTree lqt, RenderQuadTree rqt, int centerX, int centerZ)
{
}
}
@@ -0,0 +1,36 @@
package com.seibel.lod.core.objects.lod.quadtree;
import com.seibel.lod.core.util.DetailDistanceUtil;
public class QuadTreeProperties
{
public int MAX_NUMBER_OF_DETAIL=23;
public int SECTION_SIZE=128;
public int[] absoluteDetailCircleSize = new int[MAX_NUMBER_OF_DETAIL];
public int[] relativeDetailCircleSize = new int[MAX_NUMBER_OF_DETAIL];
public int[] generationModeOfDetail = new int[MAX_NUMBER_OF_DETAIL];
public LodQuadTree lodQuadTree;
public RenderQuadTree renderQuadTree;
public void update()
{
for(int detail = 0; detail < MAX_NUMBER_OF_DETAIL; detail++)
{
//Compute circle distance for this detail in term of blocks
absoluteDetailCircleSize[detail] = DetailDistanceUtil.getDrawDistanceFromDetail(detail);
//Compute circle distance in terms of number of section
relativeDetailCircleSize[detail] = (int) Math.ceil(absoluteDetailCircleSize[detail]/(SECTION_SIZE*2^detail));
}
updateGridSize();
}
private void updateGridSize()
{
for(int detail = 0; detail < MAX_NUMBER_OF_DETAIL; detail++)
{
//Use this value to change to update the size of the matrices
//relativeDetailCircleSize[detail];
}
}
}
@@ -0,0 +1,13 @@
package com.seibel.lod.core.objects.lod.quadtree;
import com.seibel.lod.core.util.DetailDistanceUtil;
public class RenderQuadTree
{
//public RenderSection[][][] quadTreeStructure;
public QuadTreeProperties properties;
public RenderQuadTree()
{
}
}
@@ -161,41 +161,11 @@ public class Mat4f
@Override
public String toString()
{
StringBuilder stringbuilder = new StringBuilder();
stringbuilder.append("Matrix4f:\n");
stringbuilder.append(this.m00);
stringbuilder.append(" ");
stringbuilder.append(this.m01);
stringbuilder.append(" ");
stringbuilder.append(this.m02);
stringbuilder.append(" ");
stringbuilder.append(this.m03);
stringbuilder.append("\n");
stringbuilder.append(this.m10);
stringbuilder.append(" ");
stringbuilder.append(this.m11);
stringbuilder.append(" ");
stringbuilder.append(this.m12);
stringbuilder.append(" ");
stringbuilder.append(this.m13);
stringbuilder.append("\n");
stringbuilder.append(this.m20);
stringbuilder.append(" ");
stringbuilder.append(this.m21);
stringbuilder.append(" ");
stringbuilder.append(this.m22);
stringbuilder.append(" ");
stringbuilder.append(this.m23);
stringbuilder.append("\n");
stringbuilder.append(this.m30);
stringbuilder.append(" ");
stringbuilder.append(this.m31);
stringbuilder.append(" ");
stringbuilder.append(this.m32);
stringbuilder.append(" ");
stringbuilder.append(this.m33);
stringbuilder.append("\n");
return stringbuilder.toString();
return "Matrix4f:\n" +
this.m00 + " " + this.m01 + " " + this.m02 + " " + this.m03 + "\n" +
this.m10 + " " + this.m11 + " " + this.m12 + " " + this.m13 + "\n" +
this.m20 + " " + this.m21 + " " + this.m22 + " " + this.m23 + "\n" +
this.m30 + " " + this.m31 + " " + this.m32 + " " + this.m33 + "\n";
}
@@ -553,4 +523,21 @@ public class Mat4f
this.m13 = y;
this.m23 = z;
}
/**
* Changes the values that store the clipping planes.
* Formula for calculating matrix values is the same that OpenGL uses when making matrices.
*
* @param nearClip New near clipping plane value.
* @param farClip New far clipping plane value.
*/
public void setClipPlanes(float nearClip,float farClip)
{
//convert to matrix values, formula copied from a textbook / openGL specification.
float matNearClip = -((farClip + nearClip) / (farClip - nearClip));
float matFarClip = -((2 * farClip * nearClip) / (farClip - nearClip));
//set new values for the clip planes.
this.m22 = matNearClip;
this.m23 = matFarClip;
}
}
@@ -26,7 +26,7 @@ import com.google.common.collect.ImmutableList;
* DefaultVertexFormats class.
*
* @author James Seibel
* @version 11-13-2021
* @version 12-8-2021
*/
public class DefaultLodVertexFormats
{
@@ -37,6 +37,8 @@ public class DefaultLodVertexFormats
public static final LodVertexFormatElement ELEMENT_NORMAL = new LodVertexFormatElement(0, LodVertexFormatElement.DataType.BYTE, 3);
public static final LodVertexFormatElement ELEMENT_PADDING = new LodVertexFormatElement(0, LodVertexFormatElement.DataType.BYTE, 1);
public static final LodVertexFormatElement ELEMENT_BLOCK_LIGHT = new LodVertexFormatElement(0, LodVertexFormatElement.DataType.UBYTE, 1);
public static final LodVertexFormat POSITION = new LodVertexFormat(ImmutableList.<LodVertexFormatElement>builder().add(ELEMENT_POSITION).build());
public static final LodVertexFormat POSITION_COLOR = new LodVertexFormat(ImmutableList.<LodVertexFormatElement>builder().add(ELEMENT_POSITION).add(ELEMENT_COLOR).build());
@@ -45,4 +47,5 @@ public class DefaultLodVertexFormats
public static final LodVertexFormat POSITION_COLOR_TEX = new LodVertexFormat(ImmutableList.<LodVertexFormatElement>builder().add(ELEMENT_POSITION).add(ELEMENT_COLOR).add(ELEMENT_UV).build());
public static final LodVertexFormat POSITION_COLOR_TEX_LIGHTMAP = new LodVertexFormat(ImmutableList.<LodVertexFormatElement>builder().add(ELEMENT_POSITION).add(ELEMENT_COLOR).add(ELEMENT_UV).add(ELEMENT_LIGHT_MAP_UV).build());
public static final LodVertexFormat POSITION_COLOR_BLOCK_LIGHT_SKY_LIGHT = new LodVertexFormat(ImmutableList.<LodVertexFormatElement>builder().add(ELEMENT_POSITION).add(ELEMENT_COLOR).add(ELEMENT_BLOCK_LIGHT).add(ELEMENT_BLOCK_LIGHT).build());
}
@@ -19,14 +19,11 @@
package com.seibel.lod.core.objects.opengl;
import java.nio.Buffer;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;
import java.util.List;
import javax.annotation.Nullable;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
@@ -40,7 +37,7 @@ import com.google.common.collect.Lists;
* OpenGL buffers.
*
* @author James Seibel
* @version 11-13-2021
* @version 12-9-2021
*/
public class LodBufferBuilder
{
@@ -53,7 +50,6 @@ public class LodBufferBuilder
private int nextElementByte = 0;
private int totalUploadedBytes = 0;
private int vertices;
@Nullable
private LodVertexFormatElement currentElement;
private int elementIndex;
private int mode;
@@ -87,7 +83,7 @@ public class LodBufferBuilder
/** make sure the buffer doesn't overflow when inserting new elements */
private void ensureVertexCapacity()
{
this.ensureCapacity(this.format.getVertexSize());
this.ensureCapacity(this.format.getByteSize());
}
private void ensureCapacity(int vertexSizeInBytes)
{
@@ -97,9 +93,9 @@ public class LodBufferBuilder
int j = i + roundUp(vertexSizeInBytes);
//LOGGER.debug("Needed to grow BufferBuilder buffer: Old size {} bytes, new size {} bytes.", i, j);
ByteBuffer bytebuffer = allocateByteBuffer(j);
((Buffer) this.buffer).position(0);
this.buffer.position(0);
bytebuffer.put(this.buffer);
((Buffer) bytebuffer).rewind();
bytebuffer.rewind();
this.buffer = bytebuffer;
}
}
@@ -272,7 +268,7 @@ public class LodBufferBuilder
this.switchFormat(LodVertexFormat);
this.currentElement = LodVertexFormat.getElements().get(0);
this.elementIndex = 0;
((Buffer) this.buffer).clear();
this.buffer.clear();
}
}
@@ -286,7 +282,7 @@ public class LodBufferBuilder
{
this.building = false;
this.vertexCounts.add(new LodBufferBuilder.DrawState(this.format, this.vertices, this.mode));
this.totalRenderedBytes += this.vertices * this.format.getVertexSize();
this.totalRenderedBytes += this.vertices * this.format.getByteSize();
this.vertices = 0;
this.currentElement = null;
this.elementIndex = 0;
@@ -326,8 +322,7 @@ public class LodBufferBuilder
ImmutableList<LodVertexFormatElement> immutablelist = this.format.getElements();
this.elementIndex = (this.elementIndex + 1) % immutablelist.size();
this.nextElementByte += this.currentElement.getByteSize();
LodVertexFormatElement LodVertexFormatelement = immutablelist.get(this.elementIndex);
this.currentElement = LodVertexFormatelement;
this.currentElement = immutablelist.get(this.elementIndex);
// if (LodVertexFormatelement.getUsage() == LodVertexFormatElement.Usage.PADDING)
// {
// this.nextElement();
@@ -358,7 +353,22 @@ public class LodBufferBuilder
}
}
public LodBufferBuilder vertex(float x, float y, float z)
public LodBufferBuilder minecraftLightValue(byte lightValue)
{
LodVertexFormatElement LodVertexFormatelement = this.currentElement();
if (LodVertexFormatelement.getType() != LodVertexFormatElement.DataType.UBYTE)
{
throw new IllegalStateException("Light Color must be stored as a UBYTE");
}
else
{
this.putByte(0, lightValue);
this.nextElement();
return this;
}
}
public LodBufferBuilder position(float x, float y, float z)
{
if (this.currentElement().getType() != LodVertexFormatElement.DataType.FLOAT)
{
@@ -439,17 +449,17 @@ public class LodBufferBuilder
public ByteBuffer getCleanedByteBuffer()
{
LodBufferBuilder.DrawState bufferbuilder$drawstate = this.vertexCounts.get(this.lastRenderedCountIndex++);
((Buffer) this.buffer).position(this.totalUploadedBytes);
this.totalUploadedBytes += bufferbuilder$drawstate.vertexCount() * bufferbuilder$drawstate.format().getVertexSize();
((Buffer) this.buffer).limit(this.totalUploadedBytes);
this.buffer.position(this.totalUploadedBytes);
this.totalUploadedBytes += bufferbuilder$drawstate.vertexCount() * bufferbuilder$drawstate.format().getByteSize();
this.buffer.limit(this.totalUploadedBytes);
if (this.lastRenderedCountIndex == this.vertexCounts.size() && this.vertices == 0)
{
this.clear();
}
ByteBuffer bytebuffer = this.buffer.slice();
bytebuffer.order(this.buffer.order()); // FORGE: Fix incorrect byte order
((Buffer) this.buffer).clear();
//bytebuffer.order(this.buffer.order()); // FORGE: Fix incorrect byte order
this.buffer.clear();
return bytebuffer; // the original method also returned bufferbuilder$drawstate
}
@@ -533,10 +543,10 @@ public class LodBufferBuilder
// Forge added methods
public void putBulkData(ByteBuffer buffer)
{
ensureCapacity(buffer.limit() + this.format.getVertexSize());
((Buffer) this.buffer).position(this.vertices * this.format.getVertexSize());
ensureCapacity(buffer.limit() + this.format.getByteSize());
this.buffer.position(this.vertices * this.format.getByteSize());
this.buffer.put(buffer);
this.vertices += buffer.limit() / this.format.getVertexSize();
this.vertices += buffer.limit() / this.format.getByteSize();
this.nextElementByte += buffer.limit();
}
@@ -19,7 +19,7 @@
package com.seibel.lod.core.objects.opengl;
import org.lwjgl.opengl.GL15;
import org.lwjgl.opengl.GL32;
import com.seibel.lod.core.enums.rendering.GLProxyContext;
import com.seibel.lod.core.render.GLProxy;
@@ -41,7 +41,7 @@ public class LodVertexBuffer implements AutoCloseable
if (GLProxy.getInstance().getGlContext() == GLProxyContext.NONE)
throw new IllegalStateException("Thread [" +Thread.currentThread().getName() + "] tried to create a [" + LodVertexBuffer.class.getSimpleName() + "] outside a OpenGL contex.");
this.id = GL15.glGenBuffers();
this.id = GL32.glGenBuffers();
}
@@ -50,7 +50,7 @@ public class LodVertexBuffer implements AutoCloseable
{
if (this.id >= 0)
{
GLProxy.getInstance().recordOpenGlCall(() -> GL15.glDeleteBuffers(this.id));
GLProxy.getInstance().recordOpenGlCall(() -> GL32.glDeleteBuffers(this.id));
this.id = -1;
}
}
@@ -35,13 +35,13 @@ import it.unimi.dsi.fastutil.ints.IntList;
* were commented out since we didn't need them.
*
* @author James Seibel
* @version 11-13-2021
* @version 12-9-2021
*/
public class LodVertexFormat
{
private final ImmutableList<LodVertexFormatElement> elements;
private final IntList offsets = new IntArrayList();
private final int vertexSize;
private final int byteSize;
public LodVertexFormat(ImmutableList<LodVertexFormatElement> elementList)
{
@@ -54,17 +54,12 @@ public class LodVertexFormat
i += LodVertexFormatElement.getByteSize();
}
this.vertexSize = i;
this.byteSize = i;
}
public int getIntegerSize()
public int getByteSize()
{
return this.getVertexSize() / 4;
}
public int getVertexSize()
{
return this.vertexSize;
return this.byteSize;
}
public ImmutableList<LodVertexFormatElement> getElements()
@@ -98,7 +93,7 @@ public class LodVertexFormat
else if (obj != null && this.getClass() == obj.getClass())
{
LodVertexFormat vertexformat = (LodVertexFormat) obj;
return this.vertexSize != vertexformat.vertexSize ? false : this.elements.equals(vertexformat.elements);
return this.byteSize == vertexformat.byteSize && this.elements.equals(vertexformat.elements);
}
else
{
@@ -71,7 +71,7 @@ public class LodVertexFormatElement
public static enum DataType
public enum DataType
{
FLOAT(4, "Float", GL11.GL_FLOAT),
UBYTE(1, "Unsigned Byte", GL11.GL_UNSIGNED_BYTE),
@@ -85,7 +85,7 @@ public class LodVertexFormatElement
private final String name;
private final int glType;
private DataType(int sizeInBytes, String newName, int openGlDataType)
DataType(int sizeInBytes, String newName, int openGlDataType)
{
this.size = sizeInBytes;
this.name = newName;
@@ -1,64 +0,0 @@
/*
* 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.core.objects.rending;
import com.seibel.lod.core.enums.rendering.FogDistance;
import com.seibel.lod.core.enums.rendering.FogQuality;
/**
* This object is just a replacement for an array
* to make things easier to understand in the LodRenderer.
*
* @author James Seibel
* @version 7-03-2021
*/
public class NearFarFogSettings
{
public final NearOrFarSetting near = new NearOrFarSetting(FogDistance.NEAR);
public final NearOrFarSetting far = new NearOrFarSetting(FogDistance.FAR);
/**
* If true that means Minecraft is
* rendering fog
*/
public boolean vanillaIsRenderingFog = true;
public NearFarFogSettings()
{
}
/**
* This holds all relevant data to rendering fog at either
* near or far distances.
*/
public static class NearOrFarSetting
{
public FogQuality quality = FogQuality.FANCY;
public FogDistance distance;
public NearOrFarSetting(FogDistance newFogDistance)
{
distance = newFogDistance;
}
}
}
@@ -2,7 +2,7 @@
* 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
* Copyright (C) 2021 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
@@ -19,23 +19,28 @@
package com.seibel.lod.core.render;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.PrintStream;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import org.lwjgl.glfw.GLFW;
import org.lwjgl.opengl.GL;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL20;
import org.lwjgl.opengl.GL30;
import org.lwjgl.opengl.GL32;
import org.lwjgl.opengl.GLCapabilities;
import org.lwjgl.opengl.GLUtil;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import com.seibel.lod.core.ModInfo;
import com.seibel.lod.core.api.ClientApi;
import com.seibel.lod.core.enums.config.GpuUploadMethod;
import com.seibel.lod.core.enums.rendering.DebugMode;
import com.seibel.lod.core.enums.rendering.GLProxyContext;
import com.seibel.lod.core.render.shader.LodShader;
import com.seibel.lod.core.render.shader.LodShaderProgram;
import com.seibel.lod.core.util.SingletonHandler;
import com.seibel.lod.core.wrapperInterfaces.config.ILodConfigWrapperSingleton;
import com.seibel.lod.core.wrapperInterfaces.minecraft.IMinecraftWrapper;
/**
@@ -43,21 +48,26 @@ import com.seibel.lod.core.wrapperInterfaces.minecraft.IMinecraftWrapper;
* and GPU capabilities.
*
* <p>
* Helpful OpenGL resources: <br><br>
* Helpful OpenGL resources:
* <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>
* https://www.slideshare.net/CassEveritt/approaching-zero-driver-overhead <br><br>
*
* https://gamedev.stackexchange.com/questions/91995/edit-vbo-data-or-create-a-new-one <br>
* https://stackoverflow.com/questions/63509735/massive-performance-loss-with-glmapbuffer <br><br>
*
* @author James Seibel
* @version 11-21-2021
* @version 12-9-2021
*/
public class GLProxy
{
private static final IMinecraftWrapper MC = SingletonHandler.get(IMinecraftWrapper.class);
private static ExecutorService workerThread = Executors.newSingleThreadExecutor(new ThreadFactoryBuilder().setNameFormat(GLProxy.class.getSimpleName() + "-Worker-Thread").build());
private static final ExecutorService workerThread = Executors.newSingleThreadExecutor(new ThreadFactoryBuilder().setNameFormat(GLProxy.class.getSimpleName() + "-Worker-Thread").build());
private static final ILodConfigWrapperSingleton CONFIG = SingletonHandler.get(ILodConfigWrapperSingleton.class);
private static GLProxy instance = null;
@@ -76,33 +86,94 @@ public class GLProxy
/** the proxyWorker's GL capabilities */
public final GLCapabilities proxyWorkerGlCapabilities;
/** This program contains all shaders required when rendering LODs */
public LodShaderProgram lodShaderProgram;
/** This is the VAO that is used when rendering */
public final int vertexArrayObjectId;
/** Requires OpenGL 4.5, and offers the best buffer uploading */
/** Requires OpenGL 4.4, and offers the best buffer uploading */
public final boolean bufferStorageSupported;
/** Requires OpenGL 3.0 */
public final boolean mapBufferRangeSupported;
/** Requires OpenGL 4.5 */
public final boolean namedObjectSupported;
/** Requires OpenGL 4.3 */
public final boolean VertexAttributeBufferBindingSupported;
/** Requires OpenGL 3.0, which will current min requirement as 3.3, should always be true */
@Deprecated
public final boolean mapBufferRangeSupported = true;
private final GpuUploadMethod preferredUploadMethod;
private String getFailedVersionInfo(GLCapabilities c) {
StringBuilder str = new StringBuilder("Your supported OpenGL version:\n");
str.append("1.1: "+c.OpenGL11+"\n");
str.append("1.2: "+c.OpenGL12+"\n");
str.append("1.3: "+c.OpenGL13+"\n");
str.append("1.4: "+c.OpenGL14+"\n");
str.append("1.5: "+c.OpenGL15+"\n");
str.append("2.0: "+c.OpenGL20+"\n");
str.append("2.1: "+c.OpenGL21+"\n");
str.append("3.0: "+c.OpenGL30+"\n");
str.append("3.1: "+c.OpenGL31+"\n");
str.append("3.2: "+c.OpenGL32+" <- REQUIRED\n");
str.append("3.3: "+c.OpenGL33+"\n");
str.append("4.0: "+c.OpenGL40+"\n");
str.append("4.1: "+c.OpenGL41+"\n");
str.append("4.2: "+c.OpenGL42+"\n");
str.append("4.3: "+c.OpenGL43+" <- optional improvement\n");
str.append("4.4: "+c.OpenGL44+" <- optional improvement\n");
str.append("4.5: "+c.OpenGL45+"\n");
str.append("4.6: "+c.OpenGL46+"\n");
str.append("If you noticed that your computer supports higher OpenGL versions"
+ " but not the required version, try running the game in compatibility mode."
+ " (How you turn that on, I have no clue~)");
return str.toString();
}
private String getVersionInfo(GLCapabilities c) {
StringBuilder str = new StringBuilder("Your supported OpenGL version:\n");
str.append("1.1: "+c.OpenGL11+"\n");
str.append("1.2: "+c.OpenGL12+"\n");
str.append("1.3: "+c.OpenGL13+"\n");
str.append("1.4: "+c.OpenGL14+"\n");
str.append("1.5: "+c.OpenGL15+"\n");
str.append("2.0: "+c.OpenGL20+"\n");
str.append("2.1: "+c.OpenGL21+"\n");
str.append("3.0: "+c.OpenGL30+"\n");
str.append("3.1: "+c.OpenGL31+"\n");
str.append("3.2: "+c.OpenGL32+" <- REQUIRED\n");
str.append("3.3: "+c.OpenGL33+"\n");
str.append("4.0: "+c.OpenGL40+"\n");
str.append("4.1: "+c.OpenGL41+"\n");
str.append("4.2: "+c.OpenGL42+"\n");
str.append("4.3: "+c.OpenGL43+" <- optional improvement\n");
str.append("4.4: "+c.OpenGL44+" <- optional improvement\n");
str.append("4.5: "+c.OpenGL45+"\n");
str.append("4.6: "+c.OpenGL46+"\n");
return str.toString();
}
/**
* @throws IllegalStateException
* @throws RuntimeException
* @throws FileNotFoundException
*/
private GLProxy()
{
ClientApi.LOGGER.error("Creating " + GLProxy.class.getSimpleName() + "... If this is the last message you see in the log there must have been a OpenGL error.");
boolean enableDebugLogging = CONFIG.client().advanced().debugging().getDebugMode() == DebugMode.SHOW_DETAIL;
// this must be created on minecraft's render context to work correctly
ClientApi.LOGGER.info("Creating " + GLProxy.class.getSimpleName() + "... If this is the last message you see in the log there must have been a OpenGL error.");
ClientApi.LOGGER.info("Lod Render OpenGL version [" + GL11.glGetString(GL11.GL_VERSION) + "].");
// getting Minecraft's context has to be done on the render thread,
// where the GL context is
if (GLFW.glfwGetCurrentContext() == 0L)
throw new IllegalStateException(GLProxy.class.getSimpleName() + " was created outside the render thread!");
//============================//
// create the builder context //
@@ -111,7 +182,20 @@ public class GLProxy
// get Minecraft's context
minecraftGlContext = GLFW.glfwGetCurrentContext();
minecraftGlCapabilities = GL.getCapabilities();
// crash the game if the GPU doesn't support OpenGL 3.2
if (!minecraftGlCapabilities.OpenGL32)
{
String supportedVersionInfo = getFailedVersionInfo(minecraftGlCapabilities);
// Note: as of MC 1.17 this shouldn't happen since MC
// requires OpenGL 3.2, but for older MC version this will warn the player.
String errorMessage = ModInfo.READABLE_NAME + " was initializing " + GLProxy.class.getSimpleName()
+ " and discovered this GPU doesn't support OpenGL 3.2." + " Sorry I couldn't tell you sooner :(\n"+
"Additional info:\n"+supportedVersionInfo;
MC.crashMinecraft(errorMessage, new UnsupportedOperationException("This GPU doesn't support OpenGL 3.2."));
}
ClientApi.LOGGER.info("minecraftGlCapabilities:\n"+getVersionInfo(minecraftGlCapabilities));
// context creation setup
GLFW.glfwDefaultWindowHints();
@@ -122,21 +206,24 @@ public class GLProxy
// GLFW.glfwWindowHint(GLFW.GLFW_CONTEXT_VERSION_MAJOR, 4);
// GLFW.glfwWindowHint(GLFW.GLFW_CONTEXT_VERSION_MINOR, 5);
// create the LodBuilder context
lodBuilderGlContext = GLFW.glfwCreateWindow(64, 48, "LOD Builder Window", 0L, minecraftGlContext);
GLFW.glfwMakeContextCurrent(lodBuilderGlContext);
lodBuilderGlCapabilities = GL.createCapabilities();
ClientApi.LOGGER.info("lodBuilderGlCapabilities:\n"+getVersionInfo(lodBuilderGlCapabilities));
// create the proxyWorker's context
proxyWorkerGlContext = GLFW.glfwCreateWindow(64, 48, "LOD proxy worker Window", 0L, minecraftGlContext);
GLFW.glfwMakeContextCurrent(proxyWorkerGlContext);
proxyWorkerGlCapabilities = GL.createCapabilities();
ClientApi.LOGGER.info("proxyWorkerGlCapabilities:\n"+getVersionInfo(lodBuilderGlCapabilities));
// Check if we can use the make-over version of Vertex Attribute, which is available in GL4.3 or after
VertexAttributeBufferBindingSupported = minecraftGlCapabilities.glBindVertexBuffer != 0L; // Nullptr
// UNUSED currently
// Check if we can use the named version of all calls, which is available in GL4.5 or after
namedObjectSupported = minecraftGlCapabilities.glNamedBufferData != 0L; //Nullptr
//==================================//
@@ -144,53 +231,62 @@ public class GLProxy
//==================================//
setGlContext(GLProxyContext.LOD_BUILDER);
ClientApi.LOGGER.info("Lod Render OpenGL version [" + GL11.glGetString(GL11.GL_VERSION) + "].");
// crash the game if the GPU doesn't support OpenGL 2.0
if (!minecraftGlCapabilities.OpenGL20)
{
// Note: as of MC 1.17 this shouldn't happen since MC
// requires OpenGL 3.3, but just in case.
String errorMessage = ModInfo.READABLE_NAME + " was initializing " + GLProxy.class.getSimpleName() + " and discoverd this GPU doesn't support OpenGL 2.0 or greater.";
MC.crashMinecraft(errorMessage + " Sorry I couldn't tell you sooner :(", new UnsupportedOperationException("This GPU doesn't support OpenGL 2.0 or greater."));
// TODO: Enable this but disable INFO logging
File proxyLog = new File("OpenGL-Lod-ProxyContext.log");
try {
proxyLog.createNewFile();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
if (enableDebugLogging)
try {
GLUtil.setupDebugMessageCallback(new PrintStream(proxyLog));
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
// get specific capabilities
// TODO re-add buffer storage support
bufferStorageSupported = lodBuilderGlCapabilities.glBufferStorage != 0;
mapBufferRangeSupported = lodBuilderGlCapabilities.glMapBufferRange != 0;
// Check if we can use the Buffer Storage, which is available in GL4.4 or after
bufferStorageSupported = minecraftGlCapabilities.glBufferStorage != 0L && lodBuilderGlCapabilities.glBufferStorage != 0L; // Nullptr
//bufferStorageSupported = true;
// display the capabilities
if (!bufferStorageSupported)
{
String fallBackVersion = mapBufferRangeSupported ? "3.0" : "1.5";
ClientApi.LOGGER.error("This GPU doesn't support Buffer Storage (OpenGL 4.5), falling back to OpenGL " + fallBackVersion + ". This may cause stuttering and reduced performance.");
ClientApi.LOGGER.warn("This GPU doesn't support Buffer Storage (OpenGL 4.4), falling back to using other methods.");
}
String vendor = GL32.glGetString(GL32.GL_VENDOR).toUpperCase(); // example return: "NVIDIA CORPORATION"
if (vendor.contains("NVIDIA") || vendor.contains("GEFORCE"))
{
// NVIDIA card
preferredUploadMethod = bufferStorageSupported ? GpuUploadMethod.BUFFER_STORAGE : GpuUploadMethod.SUB_DATA;
}
else
{
// AMD or Intel card
preferredUploadMethod = GpuUploadMethod.BUFFER_MAPPING;
}
//==============//
// shader setup //
//==============//
//setGlContext(GLProxyContext.LOD_RENDER);
setGlContext(GLProxyContext.MINECRAFT);
createShaderProgram();
// Note: VAO objects can not be shared between contexts,
// this must be created on minecraft's render context to work correctly
vertexArrayObjectId = GL30.glGenVertexArrays();
ClientApi.LOGGER.info("GPU Vendor [" + vendor + "], Preferred upload method is [" + preferredUploadMethod + "].");
setGlContext(GLProxyContext.PROXY_WORKER);
File workerLog = new File("OpenGL-Lod-WorkerContext.log");
try {
workerLog.createNewFile();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
if (enableDebugLogging)
try {
GLUtil.setupDebugMessageCallback(new PrintStream(workerLog));
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
//==========//
// clean up //
@@ -199,55 +295,10 @@ public class GLProxy
// Since this is created on the render thread, make sure the Minecraft context is used in the end
setGlContext(GLProxyContext.MINECRAFT);
// GLProxy creation success
ClientApi.LOGGER.error(GLProxy.class.getSimpleName() + " creation successful. OpenGL smiles upon you this day.");
ClientApi.LOGGER.info(GLProxy.class.getSimpleName() + " creation successful. OpenGL smiles upon you this day.");
}
/** Creates all required shaders */
public void createShaderProgram()
{
LodShader vertexShader = null;
LodShader fragmentShader = null;
try
{
// get the shaders from the resource folder
vertexShader = LodShader.loadShader(GL20.GL_VERTEX_SHADER, "shaders/unshaded.vert", false);
fragmentShader = LodShader.loadShader(GL20.GL_FRAGMENT_SHADER, "shaders/unshaded.frag", false);
// this can be used when testing shaders,
// since we can't hot swap the files in the resource folder
// vertexShader = LodShader.loadShader(GL20.GL_VERTEX_SHADER, "C:/Users/James Seibel/Desktop/shaders/unshaded.vert", true);
// fragmentShader = LodShader.loadShader(GL20.GL_FRAGMENT_SHADER, "C:/Users/James Seibel/Desktop/shaders/unshaded.frag", true);
// create the shaders
lodShaderProgram = new LodShaderProgram();
// Attach the compiled shaders to the program
lodShaderProgram.attachShader(vertexShader);
lodShaderProgram.attachShader(fragmentShader);
// activate the fragment shader output
GL30.glBindFragDataLocation(lodShaderProgram.id, 0, "fragColor");
// attach the shader program to the OpenGL context
lodShaderProgram.link();
// after the shaders have been attached to the program
// we don't need their OpenGL references anymore
GL20.glDeleteShader(vertexShader.id);
GL20.glDeleteShader(fragmentShader.id);
}
catch (Exception e)
{
ClientApi.LOGGER.error("Unable to compile shaders. Error: " + e.getMessage());
}
}
/**
* A wrapper function to make switching contexts easier. <br>
* Does nothing if the calling thread is already using newContext.
@@ -317,21 +368,29 @@ public class GLProxy
+ "no context [0].");
}
public static boolean hasInstance() {
return instance != null;
}
public static GLProxy getInstance()
{
if (instance == null)
instance = new GLProxy();
return instance;
}
public GpuUploadMethod getGpuUploadMethod() {
GpuUploadMethod method = CONFIG.client().advanced().buffers().getGpuUploadMethod();
if (!bufferStorageSupported && method == GpuUploadMethod.BUFFER_STORAGE)
{
// if buffer storage isn't supported
// default to SUB_DATA
method = GpuUploadMethod.SUB_DATA;
}
return method == GpuUploadMethod.AUTO ? preferredUploadMethod : method;
}
/**
* Asynchronously calls the given runnable on proxy's OpenGL context.
@@ -364,6 +423,31 @@ public class GLProxy
}
}
/**
* If called from a legacy OpenGL context this will
* set the fog end to infinity with a density of 0.
* Effectively removing the fog.
* <p>
* This only works with Legacy OpenGL because James hasn't
* looking into a way for it to work with Modern OpenGL.
*/
public boolean disableLegacyFog()
{
// make sure this is a legacy OpenGL context
if (minecraftGlCapabilities.glFogf != 0)
{
// glFogf should only have an address if the current OpenGL
// context can call it, and it should only be able to call it in
// legacy OpenGL contexts; since it is disabled in Modern
// OpenGL.
GL11.glFogf(GL11.GL_FOG_START, 0.0f);
GL11.glFogf(GL11.GL_FOG_END, Float.MAX_VALUE);
GL11.glFogf(GL11.GL_FOG_DENSITY, 0.0f);
return true;
}
return false;
}
}
@@ -0,0 +1,75 @@
/*
* 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.core.render;
import com.seibel.lod.core.enums.rendering.FogDistance;
import com.seibel.lod.core.enums.rendering.FogDrawMode;
import com.seibel.lod.core.handlers.IReflectionHandler;
import com.seibel.lod.core.wrapperInterfaces.config.ILodConfigWrapperSingleton;
/**
* This object is just a replacement for an array
* to make things easier to understand in the LodRenderer.
*
* @author James Seibel
* @version 11-26-2021
*/
public class LodFogConfig
{
public FogDrawMode fogDrawMode;
public FogDistance fogDistance;
public float nearFogStart = 0;
public float nearFogEnd = 0;
public float farFogStart = 0;
public float farFogEnd = 0;
public LodFogConfig(ILodConfigWrapperSingleton config, IReflectionHandler reflectionHandler, int farPlaneBlockDistance, int vanillaBlockRenderedDistance) {
fogDrawMode = config.client().graphics().fogQuality().getFogDrawMode();
if (fogDrawMode == FogDrawMode.USE_OPTIFINE_SETTING)
fogDrawMode = reflectionHandler.getFogDrawMode();
// how different distances are drawn depends on the quality set
fogDistance = config.client().graphics().fogQuality().getFogDistance();
// far fog //
if (config.client().graphics().fogQuality().getFogDistance() == FogDistance.NEAR_AND_FAR)
farFogStart = farPlaneBlockDistance * 0.9f;
else
// for more realistic fog when using FAR
farFogStart = Math.min(vanillaBlockRenderedDistance * 1.5f, farPlaneBlockDistance * 0.9f);
farFogEnd = farPlaneBlockDistance;
// near fog //
// the reason that I wrote fogEnd then fogStart backwards
// is because we are using fog backwards to how
// it is normally used, hiding near objects
// instead of far objects.
nearFogEnd = vanillaBlockRenderedDistance * 1.41f;
nearFogStart = vanillaBlockRenderedDistance * 1.6f;
}
}
@@ -0,0 +1,155 @@
/*
* This file is part of the Distant Horizon mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2021 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.core.render;
import java.awt.Color;
import com.seibel.lod.core.enums.rendering.FogDistance;
import com.seibel.lod.core.enums.rendering.FogDrawMode;
import com.seibel.lod.core.objects.math.Mat4f;
import com.seibel.lod.core.objects.math.Vec3f;
import com.seibel.lod.core.render.objects.ShaderProgram;
import com.seibel.lod.core.render.objects.VertexAttribute;
import com.seibel.lod.core.render.objects.VertexAttributePostGL43;
import com.seibel.lod.core.render.objects.VertexAttributePreGL43;
import com.seibel.lod.core.util.LodUtil;
public class LodRenderProgram extends ShaderProgram {
public static final String VERTEX_SHADER_PATH = "shaders/standard.vert";
public static final String FRAGMENT_SHADER_PATH = "shaders/flat_shaded.frag";
public final VertexAttribute vao;
// Attributes
public final int posAttrib;
public final int colAttrib;
public final int blockSkyLightAttrib;
public final int blockLightAttrib;
// Uniforms
public final int mvmUniform;
public final int projUniform;
public final int cameraUniform;
public final int fogColorUniform;
// public final int skyLightUniform; worldSkyLight is currently not used
public final int lightMapUniform;
// Fog Uniforms
public final int fogEnabledUniform;
public final int nearFogEnabledUniform;
public final int farFogEnabledUniform;
public final int nearFogStartUniform;
public final int nearFogEndUniform;
public final int farFogStartUniform;
public final int farFogEndUniform;
// This will bind VertexAttribute
public LodRenderProgram() {
super(VERTEX_SHADER_PATH, FRAGMENT_SHADER_PATH, "fragColor");
posAttrib = getAttributeLocation("vPosition");
colAttrib = getAttributeLocation("color");
blockSkyLightAttrib = getAttributeLocation("blockSkyLight");
blockLightAttrib = getAttributeLocation("blockLight");
mvmUniform = getUniformLocation("modelViewMatrix");
projUniform = getUniformLocation("projectionMatrix");
cameraUniform = getUniformLocation("cameraPos");
fogColorUniform = getUniformLocation("fogColor");
// skyLightUniform = getUniformLocation("worldSkyLight");
lightMapUniform = getUniformLocation("lightMap");
// Fog uniforms
fogEnabledUniform = getUniformLocation("fogEnabled");
nearFogEnabledUniform = getUniformLocation("nearFogEnabled");
farFogEnabledUniform = getUniformLocation("farFogEnabled");
// near
nearFogStartUniform = getUniformLocation("nearFogStart");
nearFogEndUniform = getUniformLocation("nearFogEnd");
// far
farFogStartUniform = getUniformLocation("farFogStart");
farFogEndUniform = getUniformLocation("farFogEnd");
// TODO: Add better use of the LODFormat thing
int vertexByteCount = LodUtil.LOD_VERTEX_FORMAT.getByteSize();
if (GLProxy.getInstance().VertexAttributeBufferBindingSupported)
vao = new VertexAttributePostGL43(); // also binds VertexAttribute
else
vao = new VertexAttributePreGL43(); // also binds VertexAttribute
//vao.bind();
vao.setVertexAttribute(0, posAttrib, VertexAttribute.VertexPointer.addVec3Pointer(false));
vao.setVertexAttribute(0, colAttrib, VertexAttribute.VertexPointer.addUnsignedBytesPointer(4, true));
vao.setVertexAttribute(0, blockSkyLightAttrib, VertexAttribute.VertexPointer.addUnsignedBytePointer(false));
vao.setVertexAttribute(0, blockLightAttrib, VertexAttribute.VertexPointer.addUnsignedBytePointer(false));
vao.completeAndCheck(vertexByteCount);
}
// Override ShaderProgram.bind()
public void bind() {
super.bind();
vao.bind();
}
// Override ShaderProgram.unbind()
public void unbind() {
super.unbind();
vao.unbind();
}
// Override ShaderProgram.free()
public void free() {
vao.free();
super.free();
}
public void bindVertexBuffer(int vbo) {
vao.bindBufferToAllBindingPoint(vbo);
}
public void unbindVertexBuffer() {
vao.unbindBuffersFromAllBindingPoint();
}
public void fillUniformData(Mat4f modelViewMatrix, Mat4f projectionMatrix, Vec3f cameraPos, Color fogColor, int skyLight, int lightmapBindPoint) {
super.bind();
// uniforms
setUniform(mvmUniform, modelViewMatrix);
setUniform(projUniform, projectionMatrix);
setUniform(cameraUniform, cameraPos);
setUniform(fogColorUniform, fogColor);
// setUniform(skyLightUniform, skyLight);
setUniform(lightMapUniform, lightmapBindPoint);
}
public void fillUniformDataForFog(LodFogConfig fogSettings) {
super.bind();
if (fogSettings.fogDrawMode != FogDrawMode.FOG_DISABLED) {
setUniform(fogEnabledUniform, true);
setUniform(nearFogEnabledUniform, fogSettings.fogDistance != FogDistance.FAR);
setUniform(farFogEnabledUniform, fogSettings.fogDistance != FogDistance.NEAR);
// near
setUniform(nearFogStartUniform, fogSettings.nearFogStart);
setUniform(nearFogEndUniform, fogSettings.nearFogEnd);
// far
setUniform(farFogStartUniform, fogSettings.farFogStart);
setUniform(farFogEndUniform, fogSettings.farFogEnd);
} else {
setUniform(fogEnabledUniform, false);
}
}
}
File diff suppressed because it is too large Load Diff
@@ -21,9 +21,10 @@ package com.seibel.lod.core.render;
import com.seibel.lod.core.objects.math.Vec3f;
import com.seibel.lod.core.util.LodUtil;
import com.seibel.lod.core.util.SingletonHandler;
import com.seibel.lod.core.wrapperInterfaces.block.AbstractBlockPosWrapper;
import com.seibel.lod.core.wrapperInterfaces.chunk.AbstractChunkPosWrapper;
import com.seibel.lod.forge.wrappers.minecraft.MinecraftRenderWrapper;
import com.seibel.lod.core.wrapperInterfaces.minecraft.IMinecraftRenderWrapper;
/**
* This holds miscellaneous helper code
@@ -34,7 +35,7 @@ import com.seibel.lod.forge.wrappers.minecraft.MinecraftRenderWrapper;
*/
public class RenderUtil
{
private static final MinecraftRenderWrapper MC_RENDER = MinecraftRenderWrapper.INSTANCE;
private static final IMinecraftRenderWrapper MC_RENDER = SingletonHandler.get(IMinecraftRenderWrapper.class);
/**
@@ -84,24 +85,23 @@ public class RenderUtil
* Returns true if one of the region's 4 corners is in front
* of the camera.
*/
public static boolean isRegionInViewFrustum(AbstractBlockPosWrapper playerBlockPos, Vec3f cameraDir, AbstractBlockPosWrapper vboCenterPos)
public static boolean isRegionInViewFrustum(AbstractBlockPosWrapper playerBlockPos, Vec3f cameraDir, int vboCenterPosX, int vboCenterPosZ)
{
// convert the vbo position into a direction vector
// starting from the player's position
Vec3f vboVec = new Vec3f(vboCenterPos.getX(), 0, vboCenterPos.getZ());
Vec3f vboVec = new Vec3f(vboCenterPosX, 0, vboCenterPosZ);
Vec3f playerVec = new Vec3f(playerBlockPos.getX(), playerBlockPos.getY(), playerBlockPos.getZ());
vboVec.subtract(playerVec);
Vec3f vboCenterVec = vboVec;
int halfRegionWidth = LodUtil.REGION_WIDTH / 2;
// calculate the 4 corners
Vec3f vboSeVec = new Vec3f(vboCenterVec.x + halfRegionWidth, vboCenterVec.y, vboCenterVec.z + halfRegionWidth);
Vec3f vboSwVec = new Vec3f(vboCenterVec.x - halfRegionWidth, vboCenterVec.y, vboCenterVec.z + halfRegionWidth);
Vec3f vboNwVec = new Vec3f(vboCenterVec.x - halfRegionWidth, vboCenterVec.y, vboCenterVec.z - halfRegionWidth);
Vec3f vboNeVec = new Vec3f(vboCenterVec.x + halfRegionWidth, vboCenterVec.y, vboCenterVec.z - halfRegionWidth);
Vec3f vboSeVec = new Vec3f(vboVec.x + halfRegionWidth, vboVec.y, vboVec.z + halfRegionWidth);
Vec3f vboSwVec = new Vec3f(vboVec.x - halfRegionWidth, vboVec.y, vboVec.z + halfRegionWidth);
Vec3f vboNwVec = new Vec3f(vboVec.x - halfRegionWidth, vboVec.y, vboVec.z - halfRegionWidth);
Vec3f vboNeVec = new Vec3f(vboVec.x + halfRegionWidth, vboVec.y, vboVec.z - halfRegionWidth);
// if any corner is visible, this region should be rendered
return isNormalizedVectorInViewFrustum(vboSeVec, cameraDir) ||
@@ -0,0 +1,59 @@
package com.seibel.lod.core.render.objects;
import org.lwjgl.opengl.GL32;
public class LightmapTexture {
public int id;
public LightmapTexture() {
id = GL32.glGenTextures();
bind();
}
public void bind() {
GL32.glBindTexture(GL32.GL_TEXTURE_2D, id);
}
public void unbind() {
GL32.glBindTexture(GL32.GL_TEXTURE_2D, 0);
}
public void free() {
GL32.glDeleteTextures(id);
}
// private int[] testArray;
public void fillData(int lightMapWidth, int lightMapHeight, int[] pixels) {
GL32.glDeleteTextures(id);
id = GL32.glGenTextures();
GL32.glBindTexture(GL32.GL_TEXTURE_2D, id);
if (pixels.length != lightMapWidth*lightMapHeight)
throw new RuntimeException("Lightmap Width*Height not equal to pixels provided!");
// comment me out to see when the lightmap is changing
/*
boolean same = true;
int badIndex = 0;
if (testArray != null && pixels != null)
for (int i = 0; i < pixels.length; i++)
{
if(pixels[i] != testArray[i])
{
same = false;
badIndex = i;
break;
}
}
testArray = pixels;
MC.sendChatMessage(same + " " + badIndex);
*/
// comment this line out to prevent uploading the new lightmap
GL32.glTexImage2D(GL32.GL_TEXTURE_2D, 0, GL32.GL_RGBA, lightMapWidth,
lightMapHeight, 0, GL32.GL_RGBA, GL32.GL_UNSIGNED_BYTE, pixels);
GL32.glTexParameteri(GL32.GL_TEXTURE_2D, GL32.GL_TEXTURE_WRAP_S, GL32.GL_CLAMP_TO_BORDER);
GL32.glTexParameteri(GL32.GL_TEXTURE_2D, GL32.GL_TEXTURE_WRAP_T, GL32.GL_CLAMP_TO_BORDER);
GL32.glTexParameteri(GL32.GL_TEXTURE_2D, GL32.GL_TEXTURE_MIN_FILTER, GL32.GL_NEAREST);
GL32.glTexParameteri(GL32.GL_TEXTURE_2D, GL32.GL_TEXTURE_MAG_FILTER, GL32.GL_NEAREST);
}
}
@@ -2,7 +2,7 @@
* 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
* Copyright (C) 2021 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
@@ -17,15 +17,16 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.lod.core.render.shader;
package com.seibel.lod.core.render.objects;
import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import org.lwjgl.opengl.GL20;
import org.lwjgl.opengl.GL32;
import com.seibel.lod.core.api.ClientApi;
@@ -36,37 +37,57 @@ import com.seibel.lod.core.api.ClientApi;
* @author James Seibel
* @version 11-8-2021
*/
public class LodShader
public class Shader
{
/** OpenGL shader ID */
public final int id;
/** Creates a shader with specified type. */
public LodShader(int type)
{
id = GL20.glCreateShader(type);
}
/**
* Loads a shader from file.
*
/** Creates a shader with specified type.
* @param type Either GL_VERTEX_SHADER or GL_FRAGMENT_SHADER.
* @param path File path of the shader
* @param absoluteFilePath If false the file path is relative to the resource jar folder.
* @throws Exception if the shader fails to compile
* @throws RuntimeException if the shader fails to compile
*/
public static LodShader loadShader(int type, String path, boolean absoluteFilePath) throws Exception
public Shader(int type, String path, boolean absoluteFilePath)
{
ClientApi.LOGGER.info("Loading shader at "+path);
// Create an empty shader object
id = GL32.glCreateShader(type);
StringBuilder source = loadFile(path, absoluteFilePath);
GL32.glShaderSource(id, source);
GL32.glCompileShader(id);
// check if the shader compiled
int status = GL32.glGetShaderi(id, GL32.GL_COMPILE_STATUS);
if (status != GL32.GL_TRUE) {
String message = "Shader compiler error. Details: "+GL32.glGetShaderInfoLog(id);
free(); // important!
throw new RuntimeException(message);
}
ClientApi.LOGGER.info("Shader at "+path+" loaded sucessfully.");
}
// REMEMBER to always free the resource!
public void free() {
GL32.glDeleteShader(id);
}
private StringBuilder loadFile(String path, boolean absoluteFilePath) {
StringBuilder stringBuilder = new StringBuilder();
try
{
// open the file
InputStream in = absoluteFilePath ? new FileInputStream(path) : LodShader.class.getClassLoader().getResourceAsStream(path);
InputStream in;
if (absoluteFilePath) {
// Throws FileNotFoundException
in = new FileInputStream(path); // Note: this should use OS path seperator
} else {
in = Shader.class.getClassLoader().getResourceAsStream(path); // Note: path seperator should be '/'
if (in == null) {
throw new FileNotFoundException("Shader file not found in resource: "+path);
}
}
BufferedReader reader = new BufferedReader(new InputStreamReader(in));
// read in the file
@@ -76,41 +97,8 @@ public class LodShader
}
catch (IOException e)
{
ClientApi.LOGGER.error("Unable to load shader from file [" + path + "]. Error: " + e.getMessage());
throw new RuntimeException("Unable to load shader from file [" + path + "]. Error: " + e.getMessage());
}
CharSequence shaderFileSource = stringBuilder.toString();
return createShader(type, shaderFileSource);
return stringBuilder;
}
/**
* Creates a shader with the specified type and source.
*
* @param type Either GL_VERTEX_SHADER or GL_FRAGMENT_SHADER.
* @param source Source of the shader
* @throws Exception if the shader fails to compile
*/
public static LodShader createShader(int type, CharSequence source) throws Exception
{
LodShader shader = new LodShader(type);
GL20.glShaderSource(shader.id, source);
shader.compile();
return shader;
}
/**
* Compiles the shader and checks it's status afterwards.
* @throws Exception if the shader fails to compile
*/
public void compile() throws Exception
{
GL20.glCompileShader(id);
// check if the shader compiled
int status = GL20.glGetShaderi(id, GL20.GL_COMPILE_STATUS);
if (status != GL20.GL_TRUE)
throw new Exception(GL20.glGetShaderInfoLog(id));
}
}
@@ -0,0 +1,169 @@
/*
* 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.core.render.objects;
import java.awt.Color;
import java.nio.FloatBuffer;
import org.lwjgl.opengl.GL32;
import org.lwjgl.system.MemoryStack;
import com.seibel.lod.core.objects.math.Mat4f;
import com.seibel.lod.core.objects.math.Vec3d;
import com.seibel.lod.core.objects.math.Vec3f;
/**
* This object holds the reference to a OpenGL shader program
* and contains a few methods that can be used with OpenGL shader programs.
* The reason for many of these simple wrapper methods is as reminders of what
* can (and needs to be) done with a shader program.
*
* @author James Seibel
* @version 11-26-2021
*/
public class ShaderProgram
{
/** Stores the handle of the program. */
public final int id;
// TODO: A better way to set the fragData output name
/** Creates a shader program.
* This will bind ShaderProgram */
public ShaderProgram(String vert, String frag, String fragDataOutputName)
{
Shader vertShader = new Shader(GL32.GL_VERTEX_SHADER, vert, false);
Shader fragShader = new Shader(GL32.GL_FRAGMENT_SHADER, frag, false);
id = GL32.glCreateProgram();
GL32.glAttachShader(this.id, vertShader.id);
GL32.glAttachShader(this.id, fragShader.id);
//GL32.glBindFragDataLocation(id, 0, fragDataOutputName);
GL32.glLinkProgram(this.id);
vertShader.free(); // important!
fragShader.free(); // important!
int status = GL32.glGetProgrami(this.id, GL32.GL_LINK_STATUS);
if (status != GL32.GL_TRUE) {
String message = "Shader Link Error. Details: "+GL32.glGetProgramInfoLog(this.id);
free(); // important!
throw new RuntimeException(message);
}
GL32.glUseProgram(id); // This HAVE to be a direct call to prevent calling the overloaded version
}
/** This will bind ShaderProgram */
public void bind()
{
GL32.glUseProgram(id);
}
/** This will unbind ShaderProgram */
public void unbind() {
GL32.glUseProgram(0);
}
// REMEMBER to always free the resource!
public void free()
{
GL32.glDeleteProgram(id);
}
/** WARNING: Slow native call! Cache it if possible!
* Gets the location of an attribute variable with specified name.
* Calls GL20.glGetAttribLocation(id, name)
*
* @param name Attribute name
* @throws RuntimeException if attribute not found
* @return Location of the attribute
*/
public int getAttributeLocation(CharSequence name)
{
int i = GL32.glGetAttribLocation(id, name);
if (i==-1) throw new RuntimeException("Attribute name not found: "+name);
return i;
}
/** WARNING: Slow native call! Cache it if possible!
* Gets the location of a uniform variable with specified name.
* Calls GL20.glGetUniformLocation(id, name)
*
* @param name Uniform name
* @throws RuntimeException if uniform not found
* @return Location of the Uniform
*/
public int getUniformLocation(CharSequence name)
{
int i = GL32.glGetUniformLocation(id, name);
if (i==-1) throw new RuntimeException("Uniform name not found: "+name);
return i;
}
/** Requires ShaderProgram binded. */
public void setUniform(int location, boolean value)
{
// This use -1 for false as that equals all one set
GL32.glUniform1i(location, value ? 1 : 0);
}
/** Requires ShaderProgram binded. */
public void setUniform(int location, int value)
{
GL32.glUniform1i(location, value);
}
/** Requires ShaderProgram binded. */
public void setUniform(int location, float value)
{
GL32.glUniform1f(location, value);
}
/** Requires ShaderProgram binded. */
public void setUniform(int location, Vec3f value)
{
GL32.glUniform3f(location, value.x, value.y, value.z);
}
/** Requires ShaderProgram binded. */
public void setUniform(int location, Vec3d value)
{
GL32.glUniform3f(location, (float) value.x, (float) value.y, (float) value.z);
}
/** Requires ShaderProgram binded. */
public void setUniform(int location, Mat4f value)
{
try (MemoryStack stack = MemoryStack.stackPush())
{
FloatBuffer buffer = stack.mallocFloat(4 * 4);
value.store(buffer);
GL32.glUniformMatrix4fv(location, false, buffer);
}
}
/** Converts the color's RGBA values into values between 0 and 1.
* Requires ShaderProgram binded. */
public void setUniform(int location, Color value)
{
GL32.glUniform4f(location, value.getRed() / 256.0f, value.getGreen() / 256.0f, value.getBlue() / 256.0f, value.getAlpha() / 256.0f);
}
}
@@ -0,0 +1,106 @@
/*
* This file is part of the Distant Horizon mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2021 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.core.render.objects;
import org.lwjgl.opengl.GL32;
public abstract class VertexAttribute {
public static final class VertexPointer {
public final int elementCount;
public final int glType;
public final boolean normalized;
public final int byteSize;
public VertexPointer(int elementCount, int glType, boolean normalized, int byteSize) {
this.elementCount = elementCount;
this.glType = glType;
this.normalized = normalized;
this.byteSize = byteSize;
}
public static VertexPointer addFloatPointer(boolean normalized) {
return new VertexPointer(1, GL32.GL_FLOAT, normalized, 4);
}
public static VertexPointer addVec2Pointer(boolean normalized) {
return new VertexPointer(2, GL32.GL_FLOAT, normalized, 8);
}
public static VertexPointer addVec3Pointer(boolean normalized) {
return new VertexPointer(3, GL32.GL_FLOAT, normalized, 12);
}
public static VertexPointer addVec4Pointer(boolean normalized) {
return new VertexPointer(1, GL32.GL_FLOAT, normalized, 16);
}
public static VertexPointer addUnsignedBytePointer(boolean normalized) {
return new VertexPointer(1, GL32.GL_UNSIGNED_BYTE, normalized, 1);
}
public static VertexPointer addUnsignedBytesPointer(int elementCount, boolean normalized) {
return new VertexPointer(elementCount, GL32.GL_UNSIGNED_BYTE, normalized, elementCount);
}
public static VertexPointer addIntPointer(boolean normalized) {
return new VertexPointer(1, GL32.GL_INT, normalized, 4);
}
public static VertexPointer addIvec2Pointer(boolean normalized) {
return new VertexPointer(2, GL32.GL_INT, normalized, 8);
}
public static VertexPointer addIvec3Pointer(boolean normalized) {
return new VertexPointer(3, GL32.GL_INT, normalized, 12);
}
public static VertexPointer addIvec4Pointer(boolean normalized) {
return new VertexPointer(4, GL32.GL_INT, normalized, 16);
}
}
/** Stores the handle of the VertexAttribute. */
public final int id;
// This will bind VertexAttribute
protected VertexAttribute() {
id = GL32.glGenVertexArrays();
GL32.glBindVertexArray(id);
}
// This will bind VertexAttribute
public void bind() {
GL32.glBindVertexArray(id);
}
// This will unbind VertexAttribute
public void unbind() {
GL32.glBindVertexArray(0);
}
// REMEMBER to always free the resource!
public void free() {
GL32.glDeleteVertexArrays(id);
}
// Requires VertexAttribute binded, VertexBuffer binded
public abstract void bindBufferToAllBindingPoint(int buffer);
// Requires VertexAttribute binded, VertexBuffer binded
public abstract void bindBufferToBindingPoint(int buffer, int bindingPoint);
// Requires VertexAttribute binded
public abstract void unbindBuffersFromAllBindingPoint();
// Requires VertexAttribute binded
public abstract void unbindBuffersFromBindingPoint(int bindingPoint);
// Requires VertexAttribute binded
public abstract void setVertexAttribute(int bindingPoint, int attributeIndex, VertexPointer attribute);
// Requires VertexAttribute binded
public abstract void completeAndCheck(int expectedStrideSize);
}
@@ -0,0 +1,73 @@
package com.seibel.lod.core.render.objects;
import org.lwjgl.opengl.GL43;
import com.seibel.lod.core.api.ClientApi;
// In OpenGL 4.3 and later, Vertex Attribute got a make-over.
// Now it provides support for buffer binding points natively.
// This means that setting up the VAO just use ONE native call when
// binding to a buffer.
//
// Since I no longer needs to implement binding points, I also no
// longer needs to keep track of Pointers.
public final class VertexAttributePostGL43 extends VertexAttribute {
int numberOfBindingPoints = 0;
int strideSize = 0;
// This will bind VertexAttribute
public VertexAttributePostGL43() {
super(); // also bind VertexAttribute
}
@Override
// Requires VertexAttribute binded, VertexBuffer binded
public void bindBufferToAllBindingPoint(int buffer) {
for (int i=0; i<numberOfBindingPoints; i++)
GL43.glBindVertexBuffer(i, buffer, 0, strideSize);
}
@Override
// Requires VertexAttribute binded, VertexBuffer binded
public void bindBufferToBindingPoint(int buffer, int bindingPoint) {
GL43.glBindVertexBuffer(bindingPoint, buffer, 0, strideSize);
}
@Override
// Requires VertexAttribute binded
public void unbindBuffersFromAllBindingPoint() {
for (int i=0; i<numberOfBindingPoints; i++)
GL43.glBindVertexBuffer(i, 0, 0, 0);
}
@Override
// Requires VertexAttribute binded
public void unbindBuffersFromBindingPoint(int bindingPoint) {
GL43.glBindVertexBuffer(bindingPoint, 0, 0, 0);
}
@Override
// Requires VertexAttribute binded
public void setVertexAttribute(int bindingPoint, int attributeIndex, VertexPointer attribute) {
GL43.glVertexAttribFormat(attributeIndex, attribute.elementCount, attribute.glType,
attribute.normalized, strideSize); // Here strideSize is new attrib offset
strideSize += attribute.byteSize;
if (numberOfBindingPoints <= bindingPoint) numberOfBindingPoints = bindingPoint+1;
GL43.glVertexAttribBinding(attributeIndex, bindingPoint);
GL43.glEnableVertexAttribArray(attributeIndex);
}
@Override
// Requires VertexAttribute binded
public void completeAndCheck(int expectedStrideSize) {
if (strideSize != expectedStrideSize) {
ClientApi.LOGGER.error("Vertex Attribute calculated stride size " + strideSize +
" does not match the provided expected stride size " + expectedStrideSize + "!");
}
ClientApi.LOGGER.info("Vertex Attribute (GL43+) completed. It contains "+numberOfBindingPoints
+" binding points and a stride size of "+strideSize);
}
}
@@ -0,0 +1,149 @@
package com.seibel.lod.core.render.objects;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.TreeMap;
import java.util.TreeSet;
import org.lwjgl.opengl.GL32;
import com.seibel.lod.core.api.ClientApi;
public final class VertexAttributePreGL43 extends VertexAttribute {
// I tried to use as much raw arrays as possible as those lookups
// happens every frame, and the speed directly effects fps
int strideSize = 0;
int[][] bindingPointsToIndex;
VertexPointer[] pointers;
int[] pointersOffset;
TreeMap<Integer, TreeSet<Integer>> bindingPointsToIndexBuilder;
ArrayList<VertexPointer> pointersBuilder;
// This will bind VertexAttribute
public VertexAttributePreGL43() {
super(); // also bind VertexAttribute
bindingPointsToIndexBuilder = new TreeMap<Integer, TreeSet<Integer>>();
pointersBuilder = new ArrayList<VertexPointer>();
}
@Override
// Requires VertexAttribute binded, VertexBuffer binded
public void bindBufferToAllBindingPoint(int buffer) {
for (int i=0; i<pointers.length; i++)
GL32.glEnableVertexAttribArray(i);
for (int i=0; i< pointers.length; i++) {
VertexPointer pointer = pointers[i];
if (pointer==null) continue;
GL32.glVertexAttribPointer(i, pointer.elementCount, pointer.glType,
pointer.normalized, strideSize, pointersOffset[i]);
}
}
@Override
// Requires VertexAttribute binded, VertexBuffer binded
public void bindBufferToBindingPoint(int buffer, int bindingPoint) {
int[] toBind = bindingPointsToIndex[bindingPoint];
for (int i=0; i<toBind.length; i++)
GL32.glEnableVertexAttribArray(toBind[i]);
for (int i=0; i< toBind.length; i++) {
VertexPointer pointer = pointers[toBind[i]];
if (pointer==null) continue;
GL32.glVertexAttribPointer(toBind[i], pointer.elementCount, pointer.glType,
pointer.normalized, strideSize, pointersOffset[toBind[i]]);
}
}
@Override
// Requires VertexAttribute binded
public void unbindBuffersFromAllBindingPoint() {
for (int i=0; i<pointers.length; i++)
GL32.glDisableVertexAttribArray(i);
}
@Override
// Requires VertexAttribute binded
public void unbindBuffersFromBindingPoint(int bindingPoint) {
int[] toBind = bindingPointsToIndex[bindingPoint];
for (int i=0; i<toBind.length; i++)
GL32.glDisableVertexAttribArray(toBind[i]);
}
@Override
// Requires VertexAttribute binded
public void setVertexAttribute(int bindingPoint, int attributeIndex, VertexPointer attribute) {
TreeSet<Integer> intArray = bindingPointsToIndexBuilder.get(bindingPoint);
if (intArray == null) {
intArray = new TreeSet<Integer>();
bindingPointsToIndexBuilder.put(bindingPoint, intArray);
}
intArray.add(attributeIndex);
while (pointersBuilder.size() <= attributeIndex) {
// This is dumb, but ArrayList doesn't have a resize, And this code
// should only be ran when it's building the Vertex Attribute anyways.
pointersBuilder.add(null);
}
pointersBuilder.set(attributeIndex, attribute);
}
@Override
// Requires VertexAttribute binded
public void completeAndCheck(int expectedStrideSize) {
int maxBindPointNumber = bindingPointsToIndexBuilder.lastKey();
bindingPointsToIndex = new int[maxBindPointNumber+1][];
bindingPointsToIndexBuilder.forEach((Integer i, TreeSet<Integer> set) -> {
bindingPointsToIndex[i] = new int[set.size()];
Iterator<Integer> iter = set.iterator();
for (int j = 0; j<set.size(); j++) {
bindingPointsToIndex[i][j] = iter.next();
}
});
pointers = pointersBuilder.toArray(new VertexPointer[pointersBuilder.size()]);
pointersOffset = new int[pointers.length];
pointersBuilder = null; // Release the builder
bindingPointsToIndexBuilder = null; // Release the builder
// Check if all pointers are valid
int currentOffset = 0;
for (int i = 0; i < pointers.length; i++) {
VertexPointer pointer = pointers[i];
if (pointer == null) {
ClientApi.LOGGER.warn("Vertex Attribute index "+i+" is not set! No index should be skipped normally!");
continue;
}
pointersOffset[i] = currentOffset;
currentOffset += pointer.byteSize;
}
if (currentOffset != expectedStrideSize)
ClientApi.LOGGER.error("Vertex Attribute calculated stride size " + currentOffset +
" does not match the provided expected stride size " + expectedStrideSize + "!");
strideSize = currentOffset;
ClientApi.LOGGER.info("Vertex Attribute (pre GL43) completed.");
// Debug logging
ClientApi.LOGGER.info("Vertex Attribute Debug Data:");
ClientApi.LOGGER.info("AttributeIndex: ElementCount, glType, normalized, strideSize, offset");
for (int i=0; i< pointers.length; i++) {
VertexPointer pointer = pointers[i];
if (pointer==null) {
ClientApi.LOGGER.warn(i + ": Null!!!!");
continue;
}
ClientApi.LOGGER.info(i + ": "+pointer.elementCount+", "+
pointer.glType+", "+pointer.normalized+", "+strideSize+", "+pointersOffset[i]);
}
}
}
@@ -1,184 +0,0 @@
/*
* 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.core.render.shader;
import java.nio.FloatBuffer;
import org.lwjgl.opengl.GL20;
import org.lwjgl.system.MemoryStack;
import com.seibel.lod.core.objects.math.Mat4f;
/**
* This object holds the reference to a OpenGL shader program
* and contains a few methods that can be used with OpenGL shader programs.
* The reason for many of these simple wrapper methods is as reminders of what
* can (and needs to be) done with a shader program.
*
* @author James Seibel
* @version 11-8-2021
*/
public class LodShaderProgram
{
/** Stores the handle of the program. */
public final int id;
/** Creates a shader program. */
public LodShaderProgram()
{
id = GL20.glCreateProgram();
}
/** Calls GL20.glUseProgram(this.id) */
public void use()
{
GL20.glUseProgram(id);
}
/**
* Calls GL20.glAttachShader(this.id, shader.id)
*
* @param shader Shader to get attached
*/
public void attachShader(LodShader shader)
{
GL20.glAttachShader(this.id, shader.id);
}
/**
* Links the shader program to the current OpenGL context.
* @throws Exception Exception if the program failed to link
*/
public void link() throws Exception
{
GL20.glLinkProgram(this.id);
checkLinkStatus();
}
/**
* Checks if the program was linked successfully.
* @throws Exception if the program failed to link
*/
public void checkLinkStatus() throws Exception
{
int status = GL20.glGetProgrami(this.id, GL20.GL_LINK_STATUS);
if (status != GL20.GL_TRUE)
throw new Exception(GL20.glGetProgramInfoLog(this.id));
}
/**
* Gets the location of an attribute variable with specified name.
* Calls GL20.glGetAttribLocation(id, name)
*
* @param name Attribute name
*
* @return Location of the attribute
*/
public int getAttributeLocation(CharSequence name)
{
return GL20.glGetAttribLocation(id, name);
}
/**
* Calls GL20.glEnableVertexAttribArray(location)
*
* @param location Location of the vertex attribute
*/
public void enableVertexAttribute(int location)
{
GL20.glEnableVertexAttribArray(location);
}
/**
* Calls GL20.glDisableVertexAttribArray(location)
*
* @param location Location of the vertex attribute
*/
public void disableVertexAttribute(int location)
{
GL20.glDisableVertexAttribArray(location);
}
/**
* Sets the vertex attribute pointer.
* Calls GL20.glVertexAttribPointer(...)
*
* @param location Location of the vertex attribute
* @param size Number of values per vertex
* @param stride Offset between consecutive generic vertex attributes in
* bytes
* @param offset Offset of the first component of the first generic vertex
* attribute in bytes
*/
public void pointVertexAttribute(int location, int size, int stride, int offset)
{
GL20.glVertexAttribPointer(location, size, GL20.GL_FLOAT, false, stride, offset);
}
/**
* Gets the location of an uniform variable with specified name.
* Calls GL20.glGetUniformLocation(id, name)
*
* @param name Uniform name
*
* @return -1 = error value, 0 = first value, 1 = second value, etc.
*/
public int getUniformLocation(CharSequence name)
{
return GL20.glGetUniformLocation(id, name);
}
/**
* Sets the uniform variable for specified location.
*
* @param location Uniform location
* @param value Value to set
*/
public void setUniform(int location, int value)
{
GL20.glUniform1i(location, value);
}
/**
* Sets the uniform variable for specified location.
*
* @param location Uniform location
* @param value Value to set
*/
public void setUniform(int location, Mat4f value)
{
try (MemoryStack stack = MemoryStack.stackPush())
{
FloatBuffer buffer = stack.mallocFloat(4 * 4);
value.store(buffer);
GL20.glUniformMatrix4fv(location, false, buffer);
}
}
}
@@ -23,6 +23,7 @@ import static com.seibel.lod.core.builders.bufferBuilding.LodBufferBuilderFactor
import com.seibel.lod.core.enums.config.DistanceGenerationMode;
/**
*
* @author Leonardo Amato
@@ -58,7 +59,8 @@ public class DataPointUtil
//public final static int MIN_DEPTH = -64;
//public final static int MIN_HEIGHT = -64;
public final static int EMPTY_DATA = 0;
public static int worldHeight = 256;
public static final short VERTICAL_OFFSET = -64;
public static int WORLD_HEIGHT = 1024;
public final static int ALPHA_DOWNSIZE_SHIFT = 4;
@@ -101,6 +103,9 @@ public class DataPointUtil
public final static long VOID_MASK = 1;
public final static long EXISTENCE_MASK = 1;
public final static long HEIGHT_SHIFTED_MASK = HEIGHT_MASK << HEIGHT_SHIFT;
public final static long DEPTH_SHIFTED_MASK = DEPTH_MASK << DEPTH_SHIFT;
public static long createVoidDataPoint(int generationMode)
{
@@ -139,6 +144,12 @@ public class DataPointUtil
return dataPoint;
}
public static long shiftHeightAndDepth(long dataPoint, short offset) {
long height = (dataPoint + (offset << HEIGHT_SHIFT)) & HEIGHT_SHIFTED_MASK;
long depth = (dataPoint + (offset << DEPTH_SHIFT)) & DEPTH_SHIFTED_MASK;
return dataPoint & ~(HEIGHT_SHIFTED_MASK | DEPTH_SHIFTED_MASK) | height | depth;
}
public static short getHeight(long dataPoint)
{
return (short) ((dataPoint >>> HEIGHT_SHIFT) & HEIGHT_MASK);
@@ -210,7 +221,8 @@ public class DataPointUtil
public static int getColor(long dataPoint)
{
return (int) (((dataPoint >>> COLOR_SHIFT) & COLOR_MASK) | (/*((dataPoint >>> (ALPHA_SHIFT - ALPHA_DOWNSIZE_SHIFT)) | 0b1111)*/255 << 24));
// TODO re-add transparency by replacing the color 255 with what is in comment
return (int) (((dataPoint >>> COLOR_SHIFT) & COLOR_MASK) | ((((dataPoint >>> ALPHA_SHIFT) & ALPHA_MASK) << ALPHA_DOWNSIZE_SHIFT) | 0b1111) << 24);
}
/** This is used to convert a dataPoint to string (useful for the print function) */
@@ -267,13 +279,13 @@ public class DataPointUtil
int size = dataToMerge.length / inputVerticalData;
// We initialize the arrays that are going to be used
short[] heightAndDepth = ThreadMapUtil.getHeightAndDepth((worldHeight / 2 + 1) * 2);
short[] heightAndDepth = ThreadMapUtil.getHeightAndDepth((WORLD_HEIGHT + 1) * 2);
long[] dataPoint = ThreadMapUtil.getVerticalDataArray(DetailDistanceUtil.getMaxVerticalData(0));
int genMode = DistanceGenerationMode.FULL.complexity;
boolean allEmpty = true;
boolean allVoid = true;
boolean limited = false;
boolean allDefault;
long singleData;
@@ -287,115 +299,139 @@ public class DataPointUtil
//We collect the indexes of the data, ordered by the depth
for (int index = 0; index < size; index++)
{
for (dataIndex = 0; dataIndex < inputVerticalData; dataIndex++)
if (index == 0)
{
singleData = dataToMerge[index * inputVerticalData + dataIndex];
if (doesItExist(singleData))
for (dataIndex = 0; dataIndex < inputVerticalData; dataIndex++)
{
genMode = Math.min(genMode, getGenerationMode(singleData));
allEmpty = false;
if (!isVoid(singleData))
singleData = dataToMerge[dataIndex];
if (doesItExist(singleData))
{
allVoid = false;
depth = getDepth(singleData);
height = getHeight(singleData);
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++)
genMode = Math.min(genMode, getGenerationMode(singleData));
allEmpty = false;
if (!isVoid(singleData))
{
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;
}
allVoid = false;
count++;
heightAndDepth[dataIndex * 2] = getHeight(singleData);
heightAndDepth[dataIndex * 2 +1] = getDepth(singleData);
}
for (i = 0; i < count; i++)
}
else
break;
}
}
else
{
for (dataIndex = 0; dataIndex < inputVerticalData; dataIndex++)
{
singleData = dataToMerge[index * inputVerticalData + dataIndex];
if (doesItExist(singleData))
{
genMode = Math.min(genMode, getGenerationMode(singleData));
allEmpty = false;
if (!isVoid(singleData))
{
if (height <= heightAndDepth[i * 2] && height >= heightAndDepth[i * 2 + 1])
allVoid = false;
depth = getDepth(singleData);
height = getHeight(singleData);
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++)
{
topPos = i;
break;
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;
}
}
else if (height < heightAndDepth[i * 2 + 1] && ((i + 1 < count && height > heightAndDepth[(i + 1) * 2]) || i + 1 == count))
for (i = 0; i < count; i++)
{
topPos = i;
topExtend = true;
break;
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)
if (topPos == -1)
{
//whole block falls above
extendArray(heightAndDepth, 2, 0, 1, count);
heightAndDepth[0] = height;
heightAndDepth[1] = depth;
count++;
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 (!botExtend)
else if (!topExtend)
{
//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];
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
{
//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;
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;
}
else
break;
}
}
@@ -412,7 +448,8 @@ public class DataPointUtil
int j = 0;
while (count > maxVerticalData)
{
ii = worldHeight;
limited = true;
ii = WORLD_HEIGHT;
for (i = 0; i < count - 1; i++)
{
if (heightAndDepth[i * 2 + 1] - heightAndDepth[(i + 1) * 2] <= ii)
@@ -431,88 +468,104 @@ public class DataPointUtil
count--;
}
//As standard the vertical lods are ordered from top to bottom
for (j = count - 1; j >= 0; j--)
if (!limited && size == 1)
{
height = heightAndDepth[j * 2];
depth = heightAndDepth[j * 2 + 1];
if ((depth == 0 && height == 0) || j >= heightAndDepth.length / 2)
break;
int numberOfChildren = 0;
int tempAlpha = 0;
int tempRed = 0;
int tempGreen = 0;
int tempBlue = 0;
int tempLightBlock = 0;
int tempLightSky = 0;
byte tempGenMode = DistanceGenerationMode.FULL.complexity;
allEmpty = true;
allVoid = true;
allDefault = true;
long data = 0;
for (int index = 0; index < size; index++)
for (j = 0; j < count; j++)
dataPoint[j] = dataToMerge[j];
}
else
{
for (j = 0; j < count; j++)
{
for (dataIndex = 0; dataIndex < inputVerticalData; dataIndex++)
height = heightAndDepth[j * 2];
depth = heightAndDepth[j * 2 + 1];
if ((depth == 0 && height == 0) || j >= heightAndDepth.length / 2)
break;
int numberOfChildren = 0;
int tempAlpha = 0;
int tempRed = 0;
int tempGreen = 0;
int tempBlue = 0;
int tempLightBlock = 0;
int tempLightSky = 0;
byte tempGenMode = DistanceGenerationMode.FULL.complexity;
allEmpty = true;
allVoid = true;
allDefault = true;
long data = 0;
for (int index = 0; index < size; index++)
{
singleData = dataToMerge[index * inputVerticalData + dataIndex];
if (doesItExist(singleData) && !isVoid(singleData))
for (dataIndex = 0; dataIndex < inputVerticalData; dataIndex++)
{
if ((depth <= getDepth(singleData) && getDepth(singleData) <= height)
|| (depth <= getHeight(singleData) && getHeight(singleData) <= height))
singleData = dataToMerge[index * inputVerticalData + dataIndex];
if (doesItExist(singleData) && !isVoid(singleData))
{
if (getHeight(singleData) > getHeight(data))
if ((depth <= getDepth(singleData) && getDepth(singleData) < height)
|| (depth < getHeight(singleData) && getHeight(singleData) <= height))
{
data = singleData;
break;
}
}
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
break;
}
if (!doesItExist(data))
{
singleData = dataToMerge[index * inputVerticalData];
data = createVoidDataPoint(getGenerationMode(singleData));
tempGenMode = (byte) Math.min(tempGenMode, DistanceGenerationMode.NONE.complexity);
}
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));
}
if (allEmpty)
//no child has been initialized
dataPoint[j] = EMPTY_DATA;
else if (allVoid)
//all the children are void
dataPoint[j] = createVoidDataPoint(tempGenMode);
else
tempGenMode = (byte) Math.min(tempGenMode, DistanceGenerationMode.NONE.complexity);
}
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);
{
//we have at least 1 child
if (size != 1)
{
tempAlpha = tempAlpha / numberOfChildren;
tempRed = tempRed / numberOfChildren;
tempGreen = tempGreen / numberOfChildren;
tempBlue = tempBlue / numberOfChildren;
tempLightBlock = tempLightBlock / numberOfChildren;
tempLightSky = tempLightSky / numberOfChildren;
}
//data = createDataPoint(tempAlpha, tempRed, tempGreen, tempBlue, height, depth, tempLightSky, tempLightBlock, tempGenMode, allDefault);
//if (j > 0 && getColor(data) == getColor(dataPoint[j]))
//{
// add simplification at the end due to color
//}
dataPoint[j] = createDataPoint(tempAlpha, tempRed, tempGreen, tempBlue, height, depth, tempLightSky, tempLightBlock, tempGenMode, allDefault);
}
}
}
return dataPoint;
@@ -19,11 +19,10 @@
package com.seibel.lod.core.util;
import com.seibel.lod.core.enums.config.DistanceGenerationMode;
import com.seibel.lod.core.enums.config.HorizontalQuality;
import com.seibel.lod.core.enums.config.HorizontalResolution;
import com.seibel.lod.core.wrapperInterfaces.config.ILodConfigWrapperSingleton;
import com.seibel.lod.forge.wrappers.minecraft.MinecraftRenderWrapper;
import com.seibel.lod.core.wrapperInterfaces.minecraft.IMinecraftRenderWrapper;
/**
*
@@ -33,13 +32,13 @@ import com.seibel.lod.forge.wrappers.minecraft.MinecraftRenderWrapper;
public class DetailDistanceUtil
{
private static final ILodConfigWrapperSingleton CONFIG = SingletonHandler.get(ILodConfigWrapperSingleton.class);
private static final MinecraftRenderWrapper MC_RENDER = MinecraftRenderWrapper.INSTANCE;
private static final IMinecraftRenderWrapper MC_RENDER = SingletonHandler.get(IMinecraftRenderWrapper.class);
private static final double genMultiplier = 1.0;
private static final double treeGenMultiplier = 1.0;
private static final double treeCutMultiplier = 1.0;
private static byte minGenDetail = CONFIG.client().graphics().quality().getDrawResolution().detailLevel;
private static byte minDrawDetail = (byte) Math.max(CONFIG.client().graphics().quality().getDrawResolution().detailLevel, CONFIG.client().graphics().quality().getDrawResolution().detailLevel);
private static byte minDrawDetail = CONFIG.client().graphics().quality().getDrawResolution().detailLevel;
private static final int maxDetail = LodUtil.REGION_DETAIL_LEVEL + 1;
private static final int minDistance = 0;
private static int minDetailDistance = (int) (MC_RENDER.getRenderDistance()*16 * 1.42f);
@@ -79,7 +78,7 @@ public class DetailDistanceUtil
if (CONFIG.client().graphics().advancedGraphics().getAlwaysDrawAtMaxQuality())
return detail * 0x10000; //if you want more you are doing wrong
int distanceUnit = CONFIG.client().graphics().quality().getHorizontalScale().distanceUnit;
int distanceUnit = CONFIG.client().graphics().quality().getHorizontalScale() * 16;
if (CONFIG.client().graphics().quality().getHorizontalQuality() == HorizontalQuality.LOWEST)
return (detail * distanceUnit);
else
@@ -96,14 +95,14 @@ public class DetailDistanceUtil
public static byte baseInverseFunction(int distance, byte minDetail, boolean useRenderMinDistance)
{
int detail;
byte detail;
if (distance == 0
|| (distance < minDetailDistance && useRenderMinDistance)
|| CONFIG.client().graphics().advancedGraphics().getAlwaysDrawAtMaxQuality())
return minDetail;
int distanceUnit = CONFIG.client().graphics().quality().getHorizontalScale().distanceUnit;
int distanceUnit = CONFIG.client().graphics().quality().getHorizontalScale() * 16;
if (CONFIG.client().graphics().quality().getHorizontalQuality() == HorizontalQuality.LOWEST)
detail = (byte) distance / distanceUnit;
detail = (byte) (distance / distanceUnit);
else
{
double base = CONFIG.client().graphics().quality().getHorizontalQuality().quadraticBase;
@@ -134,17 +133,21 @@ public class DetailDistanceUtil
return baseInverseFunction((int) (distance * treeGenMultiplier), minGenDetail, true);
}
// NOTE: The recent LodWorldGenerator changes assumes that this value doesn't change with 'detail'.
// If this is changed, LodWorldGenerator needs to be fixed!
/*
public static DistanceGenerationMode getDistanceGenerationMode(int detail)
{
return CONFIG.client().worldGenerator().getDistanceGenerationMode();
}
}*/
public static byte getLodDrawDetail(int detail)
public static byte getLodDrawDetail(byte detail)
{
if (detail < minDrawDetail)
return minDrawDetail;
else
return (byte) detail;
detail += minDrawDetail;
if (detail > 10)
detail = 10;
return detail;
}
public static HorizontalResolution getLodGenDetail(int detail)
@@ -26,7 +26,8 @@ import java.util.HashSet;
import com.seibel.lod.core.enums.LodDirection;
import com.seibel.lod.core.enums.config.HorizontalResolution;
import com.seibel.lod.core.enums.config.VanillaOverdraw;
import com.seibel.lod.core.objects.Box;
import com.seibel.lod.core.handlers.IReflectionHandler;
import com.seibel.lod.core.objects.VertexOptimizer;
import com.seibel.lod.core.objects.lod.LodDimension;
import com.seibel.lod.core.objects.lod.RegionPos;
import com.seibel.lod.core.objects.opengl.DefaultLodVertexFormats;
@@ -35,23 +36,24 @@ import com.seibel.lod.core.wrapperInterfaces.IWrapperFactory;
import com.seibel.lod.core.wrapperInterfaces.block.AbstractBlockPosWrapper;
import com.seibel.lod.core.wrapperInterfaces.chunk.AbstractChunkPosWrapper;
import com.seibel.lod.core.wrapperInterfaces.config.ILodConfigWrapperSingleton;
import com.seibel.lod.core.wrapperInterfaces.minecraft.IMinecraftRenderWrapper;
import com.seibel.lod.core.wrapperInterfaces.minecraft.IMinecraftWrapper;
import com.seibel.lod.core.wrapperInterfaces.world.IDimensionTypeWrapper;
import com.seibel.lod.core.wrapperInterfaces.world.IWorldWrapper;
import com.seibel.lod.forge.wrappers.minecraft.MinecraftRenderWrapper;
/**
* This class holds methods and constants that may be used in multiple places.
*
* @author James Seibel
* @version 11-13-2021
* @version 12-14-2021
*/
public class LodUtil
{
private static final IMinecraftWrapper MC = SingletonHandler.get(IMinecraftWrapper.class);
private static final MinecraftRenderWrapper MC_RENDER = MinecraftRenderWrapper.INSTANCE;
private static final IMinecraftRenderWrapper MC_RENDER = SingletonHandler.get(IMinecraftRenderWrapper.class);
private static final ILodConfigWrapperSingleton CONFIG = SingletonHandler.get(ILodConfigWrapperSingleton.class);
private static final IWrapperFactory FACTORY = SingletonHandler.get(IWrapperFactory.class);
private static final IReflectionHandler REFLECTION_HANDLER = SingletonHandler.get(IReflectionHandler.class);
/**
* Vanilla render distances less than or equal to this will not allow partial
@@ -140,7 +142,7 @@ public class LodUtil
public static final int MAX_ALLOCATABLE_DIRECT_MEMORY = 64 * 1024 * 1024;
/** the format of data stored in the GPU buffers */
public static final LodVertexFormat LOD_VERTEX_FORMAT = DefaultLodVertexFormats.POSITION_COLOR;
public static final LodVertexFormat LOD_VERTEX_FORMAT = DefaultLodVertexFormats.POSITION_COLOR_BLOCK_LIGHT_SKY_LIGHT;
@@ -342,18 +344,17 @@ public class LodUtil
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
// 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
// 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;
@@ -369,7 +370,7 @@ public class LodUtil
// get the chunks that are going to be rendered by Minecraft
HashSet<AbstractChunkPosWrapper> posToSkip = MC_RENDER.getRenderedChunks();
HashSet<AbstractChunkPosWrapper> posToSkip = REFLECTION_HANDLER.sodiumPresent() ? MC_RENDER.getSodiumRenderedChunks() : MC_RENDER.getVanillaRenderedChunks();
// remove everything outside the skipRadius,
@@ -382,7 +383,9 @@ public class LodUtil
{
if (x <= centerChunk.getX() - skipRadius || x >= centerChunk.getX() + skipRadius
|| z <= centerChunk.getZ() - skipRadius || z >= centerChunk.getZ() + skipRadius)
{
posToSkip.remove(FACTORY.createChunkPos(x, z));
}
}
}
}
@@ -403,10 +406,10 @@ public class LodUtil
return false;
int tempX;
int tempZ;
for (LodDirection lodDirection : Box.ADJ_DIRECTIONS)
for (LodDirection lodDirection : VertexOptimizer.ADJ_DIRECTIONS)
{
tempX = x + Box.DIRECTION_NORMAL_MAP.get(lodDirection).x;
tempZ = z + Box.DIRECTION_NORMAL_MAP.get(lodDirection).z;
tempX = x + VertexOptimizer.DIRECTION_NORMAL_MAP.get(lodDirection).x;
tempZ = z + VertexOptimizer.DIRECTION_NORMAL_MAP.get(lodDirection).z;
if (vanillaRenderedChunks[x][z] || (!(tempX < 0 || tempZ < 0 || tempX >= vanillaRenderedChunks.length || tempZ >= vanillaRenderedChunks[0].length)
&& !vanillaRenderedChunks[tempX][tempZ]))
return true;
@@ -40,7 +40,7 @@ public class SingletonHandler
/**
* Adds the given singleton so it can be referenced later.
*
* @param objectClass
* @param interfaceClass
* @param singletonReference
* @throws IllegalStateException
*/
@@ -26,7 +26,9 @@ import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import com.seibel.lod.core.enums.LodDirection;
import com.seibel.lod.core.objects.Box;
import com.seibel.lod.core.objects.VertexOptimizer;
// FIXME: Nuke this whole thing and use ThreadLocal instead. And no more redundant get() please!
/**
* Holds data used by specific threads so
@@ -42,7 +44,6 @@ 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, short[]> projectionArrayMap = new ConcurrentHashMap<>();
public static final ConcurrentMap<String, short[]> heightAndDepthMap = new ConcurrentHashMap<>();
public static final ConcurrentMap<String, long[]> singleDataToMergeMap = new ConcurrentHashMap<>();
@@ -55,7 +56,7 @@ public class ThreadMapUtil
public static final ConcurrentMap<String, boolean[]> adjShadeDisabled = new ConcurrentHashMap<>();
public static final ConcurrentMap<String, Map<LodDirection, long[]>> adjDataMap = new ConcurrentHashMap<>();
public static final ConcurrentMap<String, Box> boxMap = new ConcurrentHashMap<>();
public static final ConcurrentMap<String, VertexOptimizer> boxMap = new ConcurrentHashMap<>();
@@ -65,7 +66,7 @@ public class ThreadMapUtil
if (!adjShadeDisabled.containsKey(Thread.currentThread().getName())
|| (adjShadeDisabled.get(Thread.currentThread().getName()) == null))
{
adjShadeDisabled.put(Thread.currentThread().getName(), new boolean[Box.DIRECTIONS.length]);
adjShadeDisabled.put(Thread.currentThread().getName(), new boolean[VertexOptimizer.DIRECTIONS.length]);
}
Arrays.fill(adjShadeDisabled.get(Thread.currentThread().getName()), false);
return adjShadeDisabled.get(Thread.currentThread().getName());
@@ -82,24 +83,24 @@ public class ThreadMapUtil
adjDataMap.put(Thread.currentThread().getName(), new HashMap<>());
adjDataMap.get(Thread.currentThread().getName()).put(LodDirection.UP, new long[1]);
adjDataMap.get(Thread.currentThread().getName()).put(LodDirection.DOWN, new long[1]);
for (LodDirection lodDirection : Box.ADJ_DIRECTIONS)
for (LodDirection lodDirection : VertexOptimizer.ADJ_DIRECTIONS)
adjDataMap.get(Thread.currentThread().getName()).put(lodDirection, new long[verticalData]);
}
else
{
for (LodDirection lodDirection : Box.ADJ_DIRECTIONS)
for (LodDirection lodDirection : VertexOptimizer.ADJ_DIRECTIONS)
Arrays.fill(adjDataMap.get(Thread.currentThread().getName()).get(lodDirection), DataPointUtil.EMPTY_DATA);
}
return adjDataMap.get(Thread.currentThread().getName());
}
public static Box getBox()
public static VertexOptimizer getBox()
{
if (!boxMap.containsKey(Thread.currentThread().getName())
|| (boxMap.get(Thread.currentThread().getName()) == null))
{
boxMap.put(Thread.currentThread().getName(), new Box());
boxMap.put(Thread.currentThread().getName(), new VertexOptimizer());
}
boxMap.get(Thread.currentThread().getName()).reset();
return boxMap.get(Thread.currentThread().getName());
@@ -117,7 +118,8 @@ public class ThreadMapUtil
//________________________//
//TODO: Maybe use actual valid total world height instead of always assuming the worse and alloc 1024 blocks.
/** returns the array filled with 0's */
public static long[] getBuilderVerticalArray(int detailLevel)
{
@@ -128,7 +130,7 @@ public class ThreadMapUtil
for (int i = 0; i < 5; i++)
{
size = 1 << i;
array[i] = new long[size * size * (DataPointUtil.worldHeight / 2 + 1)];
array[i] = new long[size * size * (DataPointUtil.WORLD_HEIGHT / 2 + 1)];
}
threadBuilderVerticalArrayMap.put(Thread.currentThread().getName(), array);
}
@@ -136,41 +138,22 @@ public class ThreadMapUtil
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[LodUtil.DETAIL_OPTIONS][];
int size = 1;
for (int i = LodUtil.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[] getVerticalDataArray(int arrayLength)
{
if (!threadVerticalAddDataMap.containsKey(Thread.currentThread().getName()) || (threadVerticalAddDataMap.get(Thread.currentThread().getName()) == null))
long[] array = threadVerticalAddDataMap.get(Thread.currentThread().getName());
if (array == null || array.length != arrayLength)
{
threadVerticalAddDataMap.put(Thread.currentThread().getName(), new long[arrayLength]);
array = new long[arrayLength];
threadVerticalAddDataMap.put(Thread.currentThread().getName(), array);
}
else
{
Arrays.fill(threadVerticalAddDataMap.get(Thread.currentThread().getName()), 0);
}
return threadVerticalAddDataMap.get(Thread.currentThread().getName());
Arrays.fill(array, 0);
return array;
}
//FIXME: If the arrayLength change, this may return incorrect sized array
/** returns the array NOT cleared every time */
public static short[] getHeightAndDepth(int arrayLength)
{
@@ -185,18 +168,19 @@ public class ThreadMapUtil
/** returns the array filled with 0's */
public static long[] getVerticalUpdateArray(int detailLevel)
{
if (!verticalUpdate.containsKey(Thread.currentThread().getName()) || (verticalUpdate.get(Thread.currentThread().getName()) == null))
long[][] arrays = verticalUpdate.get(Thread.currentThread().getName());
if (arrays == null)
{
long[][] array = new long[LodUtil.DETAIL_OPTIONS][];
for (int i = 1; i < LodUtil.DETAIL_OPTIONS; i++)
array[i] = new long[DetailDistanceUtil.getMaxVerticalData(i - 1) * 4];
verticalUpdate.put(Thread.currentThread().getName(), array);
arrays = new long[LodUtil.DETAIL_OPTIONS][];
verticalUpdate.put(Thread.currentThread().getName(), arrays);
}
long[] array = arrays[detailLevel];
int arrayLength = DetailDistanceUtil.getMaxVerticalData(detailLevel) * 4;
if (array == null || array.length != arrayLength)
array = new long[arrayLength];
else
{
Arrays.fill(verticalUpdate.get(Thread.currentThread().getName())[detailLevel], 0);
}
return verticalUpdate.get(Thread.currentThread().getName())[detailLevel];
Arrays.fill(array, 0);
return array;
}
/** clears all arrays so they will have to be rebuilt */
@@ -209,7 +193,6 @@ public class ThreadMapUtil
threadBuilderArrayMap.clear();
threadBuilderVerticalArrayMap.clear();
threadVerticalAddDataMap.clear();
saveContainer.clear();
projectionArrayMap.clear();
heightAndDepthMap.clear();
singleDataToMergeMap.clear();
@@ -0,0 +1,32 @@
package com.seibel.lod.core.wrapperInterfaces;
import com.seibel.lod.core.enums.config.DistanceGenerationMode;
/**
* A singleton that contains variables specific to each version of Minecraft
* which can be used to change how DH-Core runs.
* For example: After MC 1.17 blocks can be negative, which changes how we generate LODs.
*
* @author James Seibel
* @version 12-11-2021
*/
public interface IVersionConstants
{
/** @returns the minimum height blocks can be generated */
int getMinimumWorldHeight();
/**
* @Returns True if the given DistanceGenerationMode can be run on our own thread. <br>
* False if the generation must be run on Minecraft's server thread.
*/
boolean isWorldGeneratorSingleThreaded(DistanceGenerationMode distanceGenerationMode);
/**
* @Returns the number of generations call per thread.
*/
default int getWorldGenerationCountPerThread() {
return 8;
}
}
@@ -24,25 +24,37 @@ import com.seibel.lod.core.objects.lod.LodDimension;
import com.seibel.lod.core.wrapperInterfaces.block.AbstractBlockPosWrapper;
import com.seibel.lod.core.wrapperInterfaces.chunk.AbstractChunkPosWrapper;
import com.seibel.lod.core.wrapperInterfaces.world.IWorldWrapper;
import com.seibel.lod.core.wrapperInterfaces.worldGeneration.AbstractExperimentalWorldGeneratorWrapper;
import com.seibel.lod.core.wrapperInterfaces.worldGeneration.AbstractWorldGeneratorWrapper;
/**
* This handles creating abstract wrapper objects.
*
* @author James Seibel
* @version 11-18-2021
* @version 12-14-2021
*/
public interface IWrapperFactory
{
public AbstractBlockPosWrapper createBlockPos();
public AbstractBlockPosWrapper createBlockPos(int x, int y, int z);
AbstractBlockPosWrapper createBlockPos();
AbstractBlockPosWrapper createBlockPos(int x, int y, int z);
public AbstractChunkPosWrapper createChunkPos();
public AbstractChunkPosWrapper createChunkPos(int x, int z);
public AbstractChunkPosWrapper createChunkPos(AbstractChunkPosWrapper newChunkPos);
public AbstractChunkPosWrapper createChunkPos(AbstractBlockPosWrapper blockPos);
AbstractChunkPosWrapper createChunkPos();
public default AbstractChunkPosWrapper createChunkPos(long xAndZPositionCombined)
{
int x = (int) (xAndZPositionCombined & Integer.MAX_VALUE);
int z = (int) (xAndZPositionCombined >> Long.SIZE / 2) & Integer.MAX_VALUE;
return createChunkPos(x, z);
}
AbstractChunkPosWrapper createChunkPos(int x, int z);
AbstractChunkPosWrapper createChunkPos(AbstractChunkPosWrapper newChunkPos);
AbstractChunkPosWrapper createChunkPos(AbstractBlockPosWrapper blockPos);
public AbstractWorldGeneratorWrapper createWorldGenerator(LodBuilder newLodBuilder, LodDimension newLodDimension, IWorldWrapper worldWrapper);
AbstractWorldGeneratorWrapper createWorldGenerator(LodBuilder newLodBuilder, LodDimension newLodDimension, IWorldWrapper worldWrapper);
// Return null to signal that there is no AbstractWorldGenerator
public default AbstractExperimentalWorldGeneratorWrapper createExperimentalWorldGenerator(LodBuilder newLodBuilder, LodDimension newLodDimension, IWorldWrapper worldWrapper) {
return null;
}
}
@@ -22,7 +22,7 @@ package com.seibel.lod.core.wrapperInterfaces.block;
import com.seibel.lod.core.enums.LodDirection;
/**
* BlockPos needs to be abstract instead of a interfaces
* BlockPos needs to be abstract instead of an interfaces
* so that we can define its constructors.
*
* @author James Seibel
@@ -30,6 +30,6 @@ package com.seibel.lod.core.wrapperInterfaces.block;
public interface IBlockColorSingletonWrapper
{
/** @returns the base color of water (grey) */
public IBlockColorWrapper getWaterColor();
IBlockColorWrapper getWaterColor();
}
@@ -29,22 +29,24 @@ public interface IBlockColorWrapper
//Colors getters//
//--------------//
public boolean hasColor();
boolean hasColor();
public int getColor();
String getName();
int getColor();
//------------//
//Tint getters//
//------------//
public boolean hasTint();
boolean hasTint();
public boolean hasGrassTint();
boolean hasGrassTint();
public boolean hasFolliageTint();
boolean hasFolliageTint();
public boolean hasWaterTint();
boolean hasWaterTint();
}
@@ -25,15 +25,15 @@ package com.seibel.lod.core.wrapperInterfaces.block;
*/
public interface IBlockShapeWrapper
{
public boolean ofBlockToAvoid();
boolean ofBlockToAvoid();
//-----------------//
//Avoidance getters//
//-----------------//
public boolean isNonFull();
boolean isNonFull();
public boolean hasNoCollision();
boolean hasNoCollision();
public boolean isToAvoid();
boolean isToAvoid();
}
@@ -23,7 +23,7 @@ import com.seibel.lod.core.wrapperInterfaces.block.AbstractBlockPosWrapper;
/**
* This is abstract instead of a interface so
* This is abstract instead of an interface, so
* we can define its constructors.
*
* @author James Seibel
@@ -22,7 +22,7 @@ package com.seibel.lod.core.wrapperInterfaces.chunk;
import com.seibel.lod.core.wrapperInterfaces.block.AbstractBlockPosWrapper;
import com.seibel.lod.core.wrapperInterfaces.block.IBlockColorWrapper;
import com.seibel.lod.core.wrapperInterfaces.block.IBlockShapeWrapper;
import com.seibel.lod.forge.wrappers.world.BiomeWrapper;
import com.seibel.lod.core.wrapperInterfaces.world.IBiomeWrapper;
/**
* @author James Seibel
@@ -30,23 +30,29 @@ import com.seibel.lod.forge.wrappers.world.BiomeWrapper;
*/
public interface IChunkWrapper
{
public int getHeight();
int getHeight();
public boolean isPositionInWater(AbstractBlockPosWrapper blockPos);
boolean isPositionInWater(int x, int y, int z);
public int getHeightMapValue(int xRel, int zRel);
int getHeightMapValue(int xRel, int zRel);
public BiomeWrapper getBiome(int xRel, int yAbs, int zRel);
IBiomeWrapper getBiome(int x, int y, int z);
IBlockColorWrapper getBlockColorWrapper(int x, int y, int z);
IBlockShapeWrapper getBlockShapeWrapper(int x, int y, int z);
public IBlockColorWrapper getBlockColorWrapper(AbstractBlockPosWrapper blockPos);
int getChunkPosX();
int getChunkPosZ();
int getRegionPosX();
int getRegionPosZ();
int getMaxY(int x, int z);
int getMaxX();
int getMaxZ();
int getMinX();
int getMinZ();
public IBlockShapeWrapper getBlockShapeWrapper(AbstractBlockPosWrapper blockPos);
boolean isLightCorrect();
public AbstractChunkPosWrapper getPos();
boolean isWaterLogged(int x, int y, int z);
public boolean isLightCorrect();
public boolean isWaterLogged(AbstractBlockPosWrapper blockPos);
public int getEmittedBrightness(AbstractBlockPosWrapper blockPos);
int getEmittedBrightness(int x, int y, int z);
}
@@ -26,15 +26,15 @@ import com.seibel.lod.core.enums.config.GenerationPriority;
import com.seibel.lod.core.enums.config.GpuUploadMethod;
import com.seibel.lod.core.enums.config.HorizontalQuality;
import com.seibel.lod.core.enums.config.HorizontalResolution;
import com.seibel.lod.core.enums.config.HorizontalScale;
import com.seibel.lod.core.enums.config.LodTemplate;
import com.seibel.lod.core.enums.config.VanillaOverdraw;
import com.seibel.lod.core.enums.config.VerticalQuality;
import com.seibel.lod.core.enums.rendering.DebugMode;
import com.seibel.lod.core.enums.rendering.FogColorMode;
import com.seibel.lod.core.enums.rendering.FogDistance;
import com.seibel.lod.core.enums.rendering.FogDrawOverride;
import com.seibel.lod.core.enums.rendering.FogDrawMode;
import com.seibel.lod.core.objects.MinDefaultMax;
import com.seibel.lod.core.util.LodUtil;
import com.seibel.lod.core.wrapperInterfaces.IVersionConstants;
/**
* This holds the config defaults, setters/getters
@@ -42,192 +42,196 @@ import com.seibel.lod.core.util.LodUtil;
* the options that should be implemented in a configWrapperSingleton.
*
* @author James Seibel
* @version 11-21-2021
* @version 12-14-2021
*/
public interface ILodConfigWrapperSingleton
{
public IClient client();
IClient client();
public interface IClient
interface IClient
{
public IGraphics graphics();
public IWorldGenerator worldGenerator();
public IAdvanced advanced();
IGraphics graphics();
IWorldGenerator worldGenerator();
IAdvanced advanced();
//==================//
// Graphics Configs //
//==================//
public interface IGraphics
interface IGraphics
{
public static final String DESC = "These settings control how the mod will look in game";
String DESC = "These settings control how the mod will look in game";
public IQuality quality();
public IFogQuality fogQuality();
public IAdvancedGraphics advancedGraphics();
IQuality quality();
IFogQuality fogQuality();
IAdvancedGraphics advancedGraphics();
public interface IQuality
interface IQuality
{
public static final String DESC = "These settings control how detailed the fake chunks will be.";
String DESC = "These settings control how detailed the fake chunks will be.";
HorizontalResolution DRAW_RESOLUTION_DEFAULT = HorizontalResolution.BLOCK;
public static final String DRAW_RESOLUTION_DESC = ""
String DRAW_RESOLUTION_DESC = ""
+ " What is the maximum detail fake chunks should be drawn at? \n"
+ " This setting will only affect closer chunks.\n"
+ " Higher settings will increase memory and GPU usage. \n"
+ "\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";
public HorizontalResolution getDrawResolution();
public void setDrawResolution(HorizontalResolution newHorizontalResolution);
+ " " + HorizontalResolution.BLOCK + ": render 256 LODs for each Chunk (width of one block). \n"
+ "\n"
+ " Lowest Quality: " + HorizontalResolution.CHUNK
+ " Highest Quality: " + HorizontalResolution.BLOCK;
HorizontalResolution getDrawResolution();
void setDrawResolution(HorizontalResolution newHorizontalResolution);
MinDefaultMax<Integer> LOD_CHUNK_RENDER_DISTANCE_MIN_DEFAULT_MAX = new MinDefaultMax<Integer>(16, 64, 1024);
String LOD_CHUNK_RENDER_DISTANCE_DESC = ""
+ " The mod's render distance, measured in chunks. \n";
public int getLodChunkRenderDistance();
public void setLodChunkRenderDistance(int newLodChunkRenderDistance);
+ " The radius of the mod's render distance. (measured in chunks) \n";
int getLodChunkRenderDistance();
void setLodChunkRenderDistance(int newLodChunkRenderDistance);
VerticalQuality VERTICAL_QUALITY_DEFAULT = VerticalQuality.MEDIUM;
String VERTICAL_QUALITY_DESC = ""
+ " 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"
+ " Higher options will make the world more accurate, but"
+ " will increase memory and GPU usage. \n"
+ "\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";
public VerticalQuality getVerticalQuality();
public void setVerticalQuality(VerticalQuality newVerticalQuality);
+ " " + VerticalQuality.HIGH + ": uses at max 8 columns per position. \n"
+ "\n"
+ " Lowest Quality: " + VerticalQuality.LOW
+ " Highest Quality: " + VerticalQuality.HIGH;
VerticalQuality getVerticalQuality();
void setVerticalQuality(VerticalQuality newVerticalQuality);
HorizontalScale HORIZONTAL_SCALE_DEFAULT = HorizontalScale.MEDIUM;
MinDefaultMax<Integer> HORIZONTAL_SCALE_MIN_DEFAULT_MAX = new MinDefaultMax<Integer>(2, 8, 32);
String HORIZONTAL_SCALE_DESC = ""
+ " 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";
public HorizontalScale getHorizontalScale();
public void setHorizontalScale(HorizontalScale newHorizontalScale);
+ " This indicates how quickly fake chunks decrease in quality the further away they are. \n"
+ " Higher settings will render higher quality fake chunks farther away, \n"
+ " but will increase memory and GPU usage.";
int getHorizontalScale();
void setHorizontalScale(int newHorizontalScale);
HorizontalQuality HORIZONTAL_QUALITY_DEFAULT = HorizontalQuality.MEDIUM;
String HORIZONTAL_QUALITY_DESC = ""
+ " 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";
public HorizontalQuality getHorizontalQuality();
public void setHorizontalQuality(HorizontalQuality newHorizontalQuality);
+ " This indicates how much farther away each drop in quality is. \n"
+ "\n"
+ " " + HorizontalQuality.LOWEST + ": each drop in quality is the same distance away. \n"
+ " " + HorizontalQuality.LOW + ": each drop in quality is " + HorizontalQuality.LOW.quadraticBase + " times farther away. \n"
+ " " + HorizontalQuality.MEDIUM + ": each drop in quality is " + HorizontalQuality.MEDIUM.quadraticBase + " times farther away. \n"
+ " " + HorizontalQuality.HIGH + ": each drop in quality is " + HorizontalQuality.HIGH.quadraticBase + " times farther away. \n"
+ "\n"
+ " Lowest Quality: " + HorizontalQuality.LOWEST
+ " Highest Quality: " + HorizontalQuality.HIGH;
HorizontalQuality getHorizontalQuality();
void setHorizontalQuality(HorizontalQuality newHorizontalQuality);
}
public interface IFogQuality
interface IFogQuality
{
String DESC = "These settings control the fog quality.";
FogDistance FOG_DISTANCE_DEFAULT = FogDistance.FAR;
String FOG_DISTANCE_DESC = ""
+ " 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";
public FogDistance getFogDistance();
public void setFogDistance(FogDistance newFogDistance);
+ "\n"
+ " This setting shouldn't affect performance.";
FogDistance getFogDistance();
void setFogDistance(FogDistance newFogDistance);
FogDrawOverride FOG_DRAW_OVERRIDE_DEFAULT = FogDrawOverride.FANCY;
String FOG_DRAW_OVERRIDE_DESC = ""
FogDrawMode FOG_DRAW_MODE_DEFAULT = FogDrawMode.FOG_ENABLED;
String FOG_DRAW_MODE_DESC = ""
+ " 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";
public FogDrawOverride getFogDrawOverride();
public void setFogDrawOverride(FogDrawOverride newFogDrawOverride);
+ "\n"
+ " " + FogDrawMode.USE_OPTIFINE_SETTING + ": Use whatever Fog setting Optifine is using. If Optifine isn't installed this defaults to " + FogDrawMode.FOG_ENABLED + ". \n"
+ " " + FogDrawMode.FOG_ENABLED + ": Never draw fog on the LODs \n"
+ " " + FogDrawMode.FOG_DISABLED + ": Always draw fast fog on the LODs \n"
+ "\n"
+ " Disabling fog will improve GPU performance.";
FogDrawMode getFogDrawMode();
void setFogDrawMode(FogDrawMode newFogDrawMode);
FogColorMode FOG_COLOR_MODE_DEFAULT = FogColorMode.USE_WORLD_FOG_COLOR;
String FOG_COLOR_MODE_DESC = ""
+ " What color should fog use? \n"
+ "\n"
+ " " + FogColorMode.USE_WORLD_FOG_COLOR + ": Use the world's fog color. \n"
+ " " + FogColorMode.USE_SKY_COLOR + ": Use the sky's color. \n"
+ "\n"
+ " This setting doesn't affect performance.";
FogColorMode getFogColorMode();
void setFogColorMode(FogColorMode newFogColorMode);
boolean DISABLE_VANILLA_FOG_DEFAULT = false;
String DISABLE_VANILLA_FOG_DESC = ""
+ " If true disable Minecraft's fog. \n\n"
+ ""
+ " Experimental! May cause issues with Sodium. \n\n"
+ ""
+ " Unlike Optifine or Sodium's fog disabling option this won't change \n"
+ " performance (we don't actually disable the fog, we just tell it to render a infinite distance away). \n"
+ " May or may not play nice with other mods that edit fog. \n";
public boolean getDisableVanillaFog();
public void setDisableVanillaFog(boolean newDisableVanillaFog);
+ " If true disable Minecraft's fog. \n"
+ "\n"
+ " Experimental! Will cause issues with Sodium and \n"
+ " may not play nice with other mods that edit fog. \n";
boolean getDisableVanillaFog();
void setDisableVanillaFog(boolean newDisableVanillaFog);
}
public interface IAdvancedGraphics
interface IAdvancedGraphics
{
String DESC = "Graphics options that are a bit more technical.";
LodTemplate LOD_TEMPLATE_DEFAULT = LodTemplate.CUBIC;
String LOD_TEMPLATE_DESC = ""
+ " 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";
public LodTemplate getLodTemplate();
public void setLodTemplate(LodTemplate newLodTemplate);
boolean DISABLE_DIRECTIONAL_CULLING_DEFAULT = false;
String DISABLE_DIRECTIONAL_CULLING_DESC = ""
+ " If false fake chunks behind the player's camera \n"
+ " aren't drawn, increasing performance. \n\n"
+ ""
+ " aren't drawn, increasing GPU 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";
public boolean getDisableDirectionalCulling();
public void setDisableDirectionalCulling(boolean newDisableDirectionalCulling);
+ " the player's camera, decreasing GPU performance. \n"
+ "\n"
+ " Disable this if you see LODs disappearing at the corners of your vision. \n";
boolean getDisableDirectionalCulling();
void setDisableDirectionalCulling(boolean newDisableDirectionalCulling);
boolean ALWAYS_DRAW_AT_MAD_QUALITY_DEFAULT = false;
String ALWAYS_DRAW_AT_MAD_QUALITY_DESC = ""
+ " Disable quality falloff, \n"
+ " all fake chunks will be drawn at the highest \n"
+ " available detail level. \n\n"
+ " "
+ " available detail level. \n"
+ "\n"
+ " WARNING: \n"
+ " This could cause a Out Of Memory crash on render \n"
+ " distances higher than 128 \n";
public boolean getAlwaysDrawAtMaxQuality();
public void setAlwaysDrawAtMaxQuality(boolean newAlwaysDrawAtMaxQuality);
+ " This could cause an Out Of Memory crash when using render \n"
+ " distances higher than 128 and will drastically increase GPU usage. \n";
boolean getAlwaysDrawAtMaxQuality();
void setAlwaysDrawAtMaxQuality(boolean newAlwaysDrawAtMaxQuality);
VanillaOverdraw VANILLA_OVERDRAW_DEFAULT = VanillaOverdraw.DYNAMIC;
String VANILLA_OVERDRAW_DESC = ""
+ " 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"
+ " 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.BORDER + ": LODs will render only on the border of vanilla chunks preventing only some holes in the world. \n"
+ " " + VanillaOverdraw.BORDER + ": LODs will render only on the border of vanilla chunks, preventing some holes in the world. \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";
public VanillaOverdraw getVanillaOverdraw();
public void setVanillaOverdraw(VanillaOverdraw newVanillaOverdraw);
GpuUploadMethod GPU_UPLOAD_METHOD_DEFAULT = GpuUploadMethod.BUFFER_STORAGE;
String GPU_UPLOAD_METHOD_DESC = ""
+ " What method should be used to upload geometry to the GPU? \n"
+ " Listed in the suggested order of best to worst. \n\n"
+ ""
+ " " + GpuUploadMethod.BUFFER_STORAGE + ": Default if OpenGL 4.5 is supported. Fast rendering, no stuttering. \n"
+ " " + GpuUploadMethod.SUB_DATA + ": Default if OpenGL 4.5 is NOT supported. Fast rendering but may stutter when uploading. \n"
+ " " + GpuUploadMethod.DATA + ": Fast rendering but will stutter when uploading. \n"
+ " " + GpuUploadMethod.BUFFER_MAPPING + ": Slow rendering but won't stutter when uploading. Possibly better than " + GpuUploadMethod.SUB_DATA + " if using a integrated GPU. \n";
public GpuUploadMethod getGpuUploadMethod();
public void setGpuUploadMethod(GpuUploadMethod newDisableVanillaFog);
+ " " + " " + VanillaOverdraw.NEVER + " or " + VanillaOverdraw.ALWAYS + " will be used depending on the dimension. \n"
+ " " + VanillaOverdraw.ALWAYS + ": LODs will render on all vanilla chunks preventing all holes in the world. \n"
+ "\n"
+ " This setting shouldn't affect performance. \n";
VanillaOverdraw getVanillaOverdraw();
void setVanillaOverdraw(VanillaOverdraw newVanillaOverdraw);
boolean USE_EXTENDED_NEAR_CLIP_PLANE_DEFAULT = false;
String USE_EXTENDED_NEAR_CLIP_PLANE_DESC = ""
+ " Will prevent some overdraw issues, but may cause nearby fake chunks to render incorrectly \n"
+ " especially when in/near an ocean. \n";
public boolean getUseExtendedNearClipPlane();
public void setUseExtendedNearClipPlane(boolean newUseExtendedNearClipPlane);
+ " especially when in/near an ocean. \n"
+ "\n"
+ " This setting shouldn't affect performance. \n";
boolean getUseExtendedNearClipPlane();
void setUseExtendedNearClipPlane(boolean newUseExtendedNearClipPlane);
}
}
@@ -237,65 +241,85 @@ public interface ILodConfigWrapperSingleton
//========================//
// WorldGenerator Configs //
//========================//
public interface IWorldGenerator
interface IWorldGenerator
{
String DESC = "These settings control how fake chunks outside your normal view range are generated.";
GenerationPriority GENERATION_PRIORITY_DEFAULT = GenerationPriority.FAR_FIRST;
GenerationPriority GENERATION_PRIORITY_DEFAULT = GenerationPriority.AUTO;
String GENERATION_PRIORITY_DESC = ""
+ " In what order should fake chunks be generated outside the vanilla render distance? \n"
+ "\n"
+ " " + GenerationPriority.FAR_FIRST + " \n"
+ " LODs are generated from low to high detail \n"
+ " Fake chunks are generated from lowest to highest detail \n"
+ " with a small priority for far away regions. \n"
+ " This fills in the world fastest. \n\n"
+ ""
+ " This fills in the world fastest, but you will have large low detail \n"
+ " blocks for a while while the generation happens. \n"
+ "\n"
+ " " + GenerationPriority.NEAR_FIRST + " \n"
+ " LODs are generated around the player \n"
+ " in a spiral, similar to vanilla minecraft. \n";
public GenerationPriority getGenerationPriority();
public void setGenerationPriority(GenerationPriority newGenerationPriority);
+ " Fake chunks are generated around the player \n"
+ " in a spiral, similar to vanilla minecraft. \n"
+ " Best used when on a server since we can't generate \n"
+ " fake chunks. \n"
+ "\n"
+ " " + GenerationPriority.AUTO + " \n"
+ " Uses " + GenerationPriority.FAR_FIRST + " when on a single player world \n"
+ " and " + GenerationPriority.NEAR_FIRST + " when connected to a server. \n"
+ "\n"
+ " This shouldn't affect performance.";
GenerationPriority getGenerationPriority();
void setGenerationPriority(GenerationPriority newGenerationPriority);
DistanceGenerationMode DISTANCE_GENERATION_MODE_DEFAULT = DistanceGenerationMode.SURFACE;
String DISTANCE_GENERATION_MODE_DESC = ""
+ " 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"
+ ""
public static String getDistanceGenerationModeDesc(IVersionConstants versionConstants)
{
return ""
+ " How detailed should fake chunks be generated outside the vanilla render distance? \n"
+ "\n"
+ " The times are the amount of time it took one of the developer's PC to generate \n"
+ " one chunk in Minecraft 1.16.5 and may be inaccurate for different Minecraft versions. \n"
+ " They are included to give a rough estimate as to how the different options \n"
+ " may perform in comparison to each other. \n"
+ "\n"
+ " " + DistanceGenerationMode.NONE + " \n"
+ " Don't run the distance generator. \n\n"
+ ""
+ " Don't run the distance generator. \n"
+ " No CPU usage - Fastest \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"
+ ""
+ " " + multiOrSingleThreadText(versionConstants, DistanceGenerationMode.BIOME_ONLY) + " - Fastest (2-5 ms) \n"
+ "\n"
+ " " + DistanceGenerationMode.BIOME_ONLY_SIMULATE_HEIGHT + " \n"
+ " Same as BIOME_ONLY, except instead \n"
+ " Same as " + DistanceGenerationMode.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"
+ ""
+ " " + multiOrSingleThreadText(versionConstants, DistanceGenerationMode.BIOME_ONLY_SIMULATE_HEIGHT) + " - 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"
+ ""
+ " " + multiOrSingleThreadText(versionConstants, DistanceGenerationMode.SURFACE) + " - 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"
+ ""
+ " " + multiOrSingleThreadText(versionConstants, DistanceGenerationMode.FEATURES) + " - Fast (15-20 ms) \n"
+ "\n"
+ " " + DistanceGenerationMode.FULL + " \n"
+ " Ask the local 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";
public DistanceGenerationMode getDistanceGenerationMode();
public void setDistanceGenerationMode(DistanceGenerationMode newDistanceGenerationMode);
+ " " + multiOrSingleThreadText(versionConstants, DistanceGenerationMode.FULL) + " - Slow (15-50 ms, with spikes up to 200 ms) \n"
+ "\n"
+ " The multithreaded options may increase CPU load significantly (while generating) \n"
+ " depending on how many world generation threads you have allocated. \n";
}
DistanceGenerationMode getDistanceGenerationMode();
void setDistanceGenerationMode(DistanceGenerationMode newDistanceGenerationMode);
boolean ALLOW_UNSTABLE_FEATURE_GENERATION_DEFAULT = false;
String ALLOW_UNSTABLE_FEATURE_GENERATION_DESC = ""
@@ -309,25 +333,36 @@ public interface ILodConfigWrapperSingleton
+ " 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"
+ "\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";
public boolean getAllowUnstableFeatureGeneration();
public void setAllowUnstableFeatureGeneration(boolean newAllowUnstableFeatureGeneration);
boolean getAllowUnstableFeatureGeneration();
void setAllowUnstableFeatureGeneration(boolean newAllowUnstableFeatureGeneration);
BlocksToAvoid BLOCKS_TO_AVOID_DEFAULT = BlocksToAvoid.BOTH;
String BLOCKS_TO_AVOID_DESC = ""
+ " " + BlocksToAvoid.NONE + ": Use all blocks when generating fake chunks \n\n"
+ ""
+ " " + BlocksToAvoid.NON_FULL + ": Only use full blocks when generating fake chunks (ignores slabs, lanterns, torches, grass, etc.) \n\n"
+ ""
+ " " + BlocksToAvoid.NO_COLLISION + ": Only use solid blocks when generating fake chunks (ignores grass, torches, etc.) \n"
+ ""
+ " " + BlocksToAvoid.BOTH + ": Only use full solid blocks when generating fake chunks \n";
public BlocksToAvoid getBlocksToAvoid();
public void setBlockToAvoid(BlocksToAvoid newBlockToAvoid);
+ " When generating fake chunks, what blocks should be ignored? \n"
+ " Ignored blocks don't affect the height of the fake chunk, but might affect the color. \n"
+ " So using " + BlocksToAvoid.BOTH + " will prevent snow covered blocks from appearing one block too tall, \n"
+ " but will still show the snow's color.\n"
+ "\n"
+ " " + BlocksToAvoid.NONE + ": Use all blocks when generating fake chunks \n"
+ " " + BlocksToAvoid.NON_FULL + ": Only use full blocks when generating fake chunks (ignores slabs, lanterns, torches, tall grass, etc.) \n"
+ " " + BlocksToAvoid.NO_COLLISION + ": Only use solid blocks when generating fake chunks (ignores tall grass, torches, etc.) \n"
+ " " + BlocksToAvoid.BOTH + ": Only use full solid blocks when generating fake chunks \n"
+ "\n"
+ " This wont't affect performance.";
BlocksToAvoid getBlocksToAvoid();
void setBlockToAvoid(BlocksToAvoid newBlockToAvoid);
/** description helper method */
static String multiOrSingleThreadText(IVersionConstants versionConstants, DistanceGenerationMode distanceGenerationMode)
{
return versionConstants.isWorldGeneratorSingleThreaded(distanceGenerationMode) ? "Singlethreaded" : "Multithreaded";
};
}
@@ -336,16 +371,16 @@ public interface ILodConfigWrapperSingleton
//============================//
// AdvancedModOptions Configs //
//============================//
public interface IAdvanced
interface IAdvanced
{
public static final String DESC = "Advanced mod settings";
String DESC = "Advanced mod settings";
public IThreading threading();
public IDebugging debugging();
public IBuffers buffers();
IThreading threading();
IDebugging debugging();
IBuffers buffers();
public interface IThreading
interface IThreading
{
String DESC = "These settings control how many CPU threads the mod uses for different tasks.";
@@ -354,34 +389,45 @@ public interface ILodConfigWrapperSingleton
Runtime.getRuntime().availableProcessors() / 2,
Runtime.getRuntime().availableProcessors());
String NUMBER_OF_WORLD_GENERATION_THREADS_DESC = ""
+ " This is how many threads are used when generating LODs outside \n"
+ " the normal render distance. \n"
+ " How many threads should be used when generating fake chunks outside \n"
+ " the normal render distance? \n"
+ "\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"
+ ""
+ " increase this number. \n"
+ "\n"
+ " This and the number of buffer builder threads are independent, \n"
+ " so if they add up to more threads than your CPU has cores, \n"
+ " that shouldn't cause an issue. \n"
+ "\n"
+ " The maximum value is the number of logical processors on your CPU. \n"
+ " Requires a restart to take effect. \n";
public int getNumberOfWorldGenerationThreads();
public void setNumberOfWorldGenerationThreads(int newNumberOfWorldGenerationThreads);
int getNumberOfWorldGenerationThreads();
void setNumberOfWorldGenerationThreads(int newNumberOfWorldGenerationThreads);
MinDefaultMax<Integer> NUMBER_OF_BUFFER_BUILDER_THREADS_MIN_DEFAULT_MAX
= new MinDefaultMax<Integer>(1,
Runtime.getRuntime().availableProcessors() / 2,
Runtime.getRuntime().availableProcessors());
String NUMBER_OF_BUFFER_BUILDER_THREADS_DESC = ""
+ " This is how many threads are used when building vertex buffers \n"
+ " How many threads are used when building vertex buffers? \n"
+ " (The things sent to your GPU to draw the fake chunks). \n"
+ "\n"
+ " If you experience high CPU usage when NOT generating distant \n"
+ " fake chunks, lower this number. \n"
+ " \n"
+ " fake chunks, lower this number. A higher number will make fake\n"
+ " fake chunks' transition faster when moving around the world. \n"
+ "\n"
+ " This and the number of world generator threads are independent, \n"
+ " so if they add up to more threads than your CPU has cores, \n"
+ " that shouldn't cause an issue. \n"
+ "\n"
+ " The maximum value is the number of logical processors on your CPU. \n"
+ " Requires a restart to take effect. \n";
public int getNumberOfBufferBuilderThreads();
public void setNumberOfBufferBuilderThreads(int newNumberOfWorldBuilderThreads);
int getNumberOfBufferBuilderThreads();
void setNumberOfBufferBuilderThreads(int newNumberOfWorldBuilderThreads);
}
public interface IDebugging
interface IDebugging
{
String DESC = "These settings can be used to look for bugs, or see how certain aspects of the mod work.";
@@ -389,36 +435,77 @@ public interface ILodConfigWrapperSingleton
String DRAW_LODS_DESC = ""
+ " 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";
public boolean getDrawLods();
public void setDrawLods(boolean newDrawLods);
+ " but they won't be rendered. \n"
+ "\n"
+ " Disabling rendering will reduce GPU usage \n";
boolean getDrawLods();
void setDrawLods(boolean newDrawLods);
DebugMode DEBUG_MODE_DEFAULT = DebugMode.OFF;
String DEBUG_MODE_DESC = ""
+ " Should specialized colors/rendering modes be used? \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";
public DebugMode getDebugMode();
public void setDebugMode(DebugMode newDebugMode);
DebugMode getDebugMode();
void setDebugMode(DebugMode newDebugMode);
boolean DEBUG_KEYBINDINGS_ENABLED_DEFAULT = true;
String DEBUG_KEYBINDINGS_ENABLED_DESC = ""
+ " If true the F4 key can be used to cycle through the different debug modes. \n"
+ " If true the F8 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.";
public boolean getDebugKeybindingsEnabled();
public void setDebugKeybindingsEnabled(boolean newEnableDebugKeybindings);
boolean getDebugKeybindingsEnabled();
void setDebugKeybindingsEnabled(boolean newEnableDebugKeybindings);
}
public interface IBuffers
interface IBuffers
{
String DESC = "These settings affect how often geometry is rebuilt.";
GpuUploadMethod GPU_UPLOAD_METHOD_DEFAULT = GpuUploadMethod.AUTO;
String GPU_UPLOAD_METHOD_DESC = ""
+ " What method should be used to upload geometry to the GPU? \n"
+ "\n"
+ " " + GpuUploadMethod.AUTO + ": Picks the best option based on the GPU you have. \n"
+ " " + GpuUploadMethod.BUFFER_STORAGE + ": Default for NVIDIA if OpenGL 4.5 is supported. \n"
+ " Fast rendering, no stuttering. \n"
+ " " + GpuUploadMethod.SUB_DATA + ": Backup option for NVIDIA. \n"
+ " Fast rendering but may stutter when uploading. \n"
+ " " + GpuUploadMethod.BUFFER_MAPPING + ": Slow rendering but won't stutter when uploading. Possibly the best option for integrated GPUs. \n"
+ " Default option for AMD/Intel. \n"
+ " May end up storing buffers in System memory. \n"
+ " Fast rendering if in GPU memory, slow if in system memory, \n"
+ " but won't stutter when uploading. \n"
+ " " + GpuUploadMethod.DATA + ": Fast rendering but will stutter when uploading. \n"
+ " Backup option for AMD/Intel. \n"
+ " Fast rendering but may stutter when uploading. \n"
+ "\n"
+ " If you don't see any difference when changing these settings, or the world looks corrupted: \n"
+ " Restart the game to clear the old buffers. \n";
GpuUploadMethod getGpuUploadMethod();
void setGpuUploadMethod(GpuUploadMethod newGpuUploadMethod);
MinDefaultMax<Integer> GPU_UPLOAD_PER_MEGABYTE_IN_MILLISECONDS_DEFAULT = new MinDefaultMax<Integer>(0, 10, 5000);
String GPU_UPLOAD_PER_MEGABYTE_IN_MILLISECONDS_DESC = ""
+ " How long should a buffer wait per Megabyte of data uploaded?\n"
+ " Helpful resource for frame times: https://fpstoms.com \n"
+ "\n"
+ " Longer times may reduce stuttering but will make fake chunks \n"
+ " transition and load slower. Change this to [0] for no timeout.\n"
+ "\n"
+ " NOTE:\n"
+ " Before changing this config, try changing \"GPU Upload methods\"\n"
+ " and determined the best method for your hardware first. \n";
int getGpuUploadPerMegabyteInMilliseconds();
void setGpuUploadPerMegabyteInMilliseconds(int newMilliseconds);
String REBUILD_TIMES_DESC = ""
+ " How frequently should geometry be rebuilt and sent to the GPU? \n"
+ " How frequently should vertex buffers (geometry) be rebuilt and sent to the GPU? \n"
+ " Higher settings may cause stuttering, but will prevent holes in the world \n";
BufferRebuildTimes REBUILD_TIMES_DEFAULT = BufferRebuildTimes.NORMAL;
public BufferRebuildTimes getRebuildTimes();
public void setRebuildTimes(BufferRebuildTimes newBufferRebuildTimes);
BufferRebuildTimes getRebuildTimes();
void setRebuildTimes(BufferRebuildTimes newBufferRebuildTimes);
}
}
}
@@ -19,11 +19,14 @@
package com.seibel.lod.core.wrapperInterfaces.minecraft;
import java.awt.Color;
import java.util.HashSet;
import com.seibel.lod.core.objects.math.Mat4f;
import com.seibel.lod.core.objects.math.Vec3d;
import com.seibel.lod.core.objects.math.Vec3f;
import com.seibel.lod.core.util.SingletonHandler;
import com.seibel.lod.core.wrapperInterfaces.IWrapperFactory;
import com.seibel.lod.core.wrapperInterfaces.block.AbstractBlockPosWrapper;
import com.seibel.lod.core.wrapperInterfaces.chunk.AbstractChunkPosWrapper;
@@ -32,33 +35,100 @@ import com.seibel.lod.core.wrapperInterfaces.chunk.AbstractChunkPosWrapper;
* rendering in Minecraft.
*
* @author James Seibel
* @version 11-18-2021
* @version 12-14-2021
*/
public interface IMinecraftRenderWrapper
{
public Vec3f getLookAtVector();
Vec3f getLookAtVector();
public AbstractBlockPosWrapper getCameraBlockPosition();
AbstractBlockPosWrapper getCameraBlockPosition();
public boolean playerHasBlindnessEffect();
boolean playerHasBlindnessEffect();
public Vec3d getCameraExactPosition();
Vec3d getCameraExactPosition();
public Mat4f getDefaultProjectionMatrix(float partialTicks);
Mat4f getDefaultProjectionMatrix(float partialTicks);
public double getGamma();
double getGamma();
public double getFov(float partialTicks);
Color getFogColor();
Color getSkyColor();
double getFov(float partialTicks);
/** Measured in chunks */
public int getRenderDistance();
int getRenderDistance();
public int getScreenWidth();
public int getScreenHeight();
int getScreenWidth();
int getScreenHeight();
/**
* This method returns the ChunkPos of all chunks that Minecraft
* is going to render this frame.
* <br>
* If not implemented this calls {@link #getMaximumRenderedChunks()}.
*/
public HashSet<AbstractChunkPosWrapper> getRenderedChunks();
public default HashSet<AbstractChunkPosWrapper> getVanillaRenderedChunks()
{
return getMaximumRenderedChunks();
}
/**
* This method returns the ChunkPos of every chunk that
* Sodium is going to render this frame.
* <br>
* If not implemented this calls {@link #getMaximumRenderedChunks()}.
*/
public default HashSet<AbstractChunkPosWrapper> getSodiumRenderedChunks()
{
return getMaximumRenderedChunks();
}
/**
* <strong>Doesn't need to be implemented.</strong> <br>
* Returns every chunk position within the vanilla render distance.
*/
public default HashSet<AbstractChunkPosWrapper> getMaximumRenderedChunks()
{
IMinecraftWrapper mcWrapper = SingletonHandler.get(IMinecraftWrapper.class);
IWrapperFactory factory = SingletonHandler.get(IWrapperFactory.class);
int chunkRenderDist = this.getRenderDistance();
// if we have a odd render distance, we'll have a empty gap. This way we'll overlap by 1 instead,
// which is preferable to having a hole in the world
chunkRenderDist = chunkRenderDist % 2 == 0 ? chunkRenderDist : chunkRenderDist - 1;
AbstractChunkPosWrapper centerChunkPos = mcWrapper.getPlayerChunkPos();
int startChunkX = centerChunkPos.getX() - chunkRenderDist;
int startChunkZ = centerChunkPos.getZ() - chunkRenderDist;
// add every position within render distance
HashSet<AbstractChunkPosWrapper> renderedPos = new HashSet<AbstractChunkPosWrapper>();
for (int chunkX = 0; chunkX < (chunkRenderDist * 2); chunkX++)
{
for(int chunkZ = 0; chunkZ < (chunkRenderDist * 2); chunkZ++)
{
renderedPos.add(factory.createChunkPos(startChunkX + chunkX, startChunkZ + chunkZ));
}
}
return renderedPos;
}
/** @returns null if there was a issue getting the lightmap */
int[] getLightmapPixels();
/** @returns -1 if there was an issue getting the lightmap */
int getLightmapTextureHeight();
/** @returns -1 if there was an issue getting the lightmap */
int getLightmapTextureWidth();
/** @returns -1 if there was an issue getting the lightmap */
public int getLightmapGLFormat();
/** Try and disable vanilla fog. Return true if successful, or false if not able to.
* If we are still using legacy fog, this method will not be called. */
public default boolean tryDisableVanillaFog() {
return false;
}
}
@@ -34,7 +34,7 @@ import com.seibel.lod.core.wrapperInterfaces.world.IWorldWrapper;
* Contains everything related to the Minecraft object.
*
* @author James Seibel
* @version 9-16-2021
* @version 12-8-2021
*/
public interface IMinecraftWrapper
{
@@ -50,7 +50,7 @@ public interface IMinecraftWrapper
* <p>
* This doesn't affect OpenGL objects in any way.
*/
public void clearFrameObjectCache();
void clearFrameObjectCache();
@@ -58,18 +58,18 @@ public interface IMinecraftWrapper
// method wrappers //
//=================//
public float getShade(LodDirection lodDirection);
float getShade(LodDirection lodDirection);
public boolean hasSinglePlayerServer();
boolean hasSinglePlayerServer();
public String getCurrentServerName();
public String getCurrentServerIp();
public String getCurrentServerVersion();
String getCurrentServerName();
String getCurrentServerIp();
String getCurrentServerVersion();
/** Returns the dimension the player is currently in */
public IDimensionTypeWrapper getCurrentDimension();
IDimensionTypeWrapper getCurrentDimension();
public String getCurrentDimensionId();
String getCurrentDimensionId();
/** This texture changes every frame */
ILightMapWrapper getCurrentLightMap();
@@ -77,18 +77,18 @@ public interface IMinecraftWrapper
/**
* 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
* @param blockLight x location in texture space
* @param skyLight z location in texture space
*/
public int getColorIntFromLightMap(int u, int v);
int getColorIntFromLightMap(int blockLight, int skyLight);
/**
* 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
* @param blockLight x location in texture space
* @param skyLight z location in texture space
*/
public Color getColorFromLightMap(int u, int v);
Color getColorFromLightMap(int blockLight, int skyLight);
@@ -97,35 +97,35 @@ public interface IMinecraftWrapper
// Simple gets //
//=============//
public boolean playerExists();
boolean playerExists();
public AbstractBlockPosWrapper getPlayerBlockPos();
AbstractBlockPosWrapper getPlayerBlockPos();
public AbstractChunkPosWrapper getPlayerChunkPos();
AbstractChunkPosWrapper getPlayerChunkPos();
/**
* Attempts to get the ServerWorld for the dimension
* the user is currently in.
* @returns null if no ServerWorld is available
*/
public IWorldWrapper getWrappedServerWorld();
IWorldWrapper getWrappedServerWorld();
public IWorldWrapper getWrappedClientWorld();
IWorldWrapper getWrappedClientWorld();
public File getGameDirectory();
File getGameDirectory();
public IProfilerWrapper getProfiler();
IProfilerWrapper getProfiler();
public float getSkyDarken(float partialTicks);
float getSkyDarken(float partialTicks);
boolean connectedToServer();
/** Returns all worlds available to the server */
public ArrayList<IWorldWrapper> getAllServerWorlds();
ArrayList<IWorldWrapper> getAllServerWorlds();
public void sendChatMessage(String string);
void sendChatMessage(String string);
/**
* Crashes Minecraft, displaying the given errorMessage <br> <br>
@@ -135,7 +135,7 @@ public interface IMinecraftWrapper
* Error: <strong>ExceptionClass: exceptionErrorMessage</strong> <br>
* Exit Code: -1 <br>
*/
public void crashMinecraft(String errorMessage, Throwable exception);
void crashMinecraft(String errorMessage, Throwable exception);
@@ -25,9 +25,9 @@ package com.seibel.lod.core.wrapperInterfaces.minecraft;
*/
public interface IProfilerWrapper
{
public void push(String newSection);
void push(String newSection);
public void popPush(String newSection);
void popPush(String newSection);
public void pop();
void pop();
}
@@ -25,5 +25,5 @@ package com.seibel.lod.core.wrapperInterfaces.misc;
*/
public interface ILightMapWrapper
{
public int getLightValue(int skyLight, int blockLight);
int getLightValue(int skyLight, int blockLight);
}
@@ -30,9 +30,9 @@ import com.seibel.lod.core.wrapperInterfaces.block.AbstractBlockPosWrapper;
*/
public interface IBiomeColorWrapperSingleton
{
public IBiomeColorWrapperSingleton getInstance();
IBiomeColorWrapperSingleton getInstance();
public int getGrassColor(IWorldWrapper world, AbstractBlockPosWrapper blockPos);
public int getWaterColor(IWorldWrapper world, AbstractBlockPosWrapper blockPos);
public int getFoliageColor(IWorldWrapper world, AbstractBlockPosWrapper blockPos);
int getGrassColor(IWorldWrapper world, AbstractBlockPosWrapper blockPos);
int getWaterColor(IWorldWrapper world, AbstractBlockPosWrapper blockPos);
int getFoliageColor(IWorldWrapper world, AbstractBlockPosWrapper blockPos);
}
@@ -26,12 +26,14 @@ package com.seibel.lod.core.wrapperInterfaces.world;
public interface IBiomeWrapper
{
/** Returns a color int for the given biome. */
public int getColorForBiome(int x, int z);
int getColorForBiome(int x, int z);
public int getGrassTint(int x, int z);
String getName();
public int getFolliageTint();
int getGrassTint(int x, int z);
public int getWaterTint();
int getFolliageTint();
int getWaterTint();
}
@@ -25,9 +25,9 @@ package com.seibel.lod.core.wrapperInterfaces.world;
*/
public interface IDimensionTypeWrapper
{
public String getDimensionName();
String getDimensionName();
public boolean hasCeiling();
boolean hasCeiling();
public boolean hasSkyLight();
boolean hasSkyLight();
}
@@ -32,28 +32,33 @@ import com.seibel.lod.core.wrapperInterfaces.block.AbstractBlockPosWrapper;
*/
public interface IWorldWrapper
{
public IDimensionTypeWrapper getDimensionType();
IDimensionTypeWrapper getDimensionType();
public WorldType getWorldType();
WorldType getWorldType();
public int getBlockLight(AbstractBlockPosWrapper blockPos);
int getBlockLight(int x, int y, int z);
public int getSkyLight(AbstractBlockPosWrapper blockPos);
int getSkyLight(int x, int y, int z);
public IBiomeWrapper getBiome(AbstractBlockPosWrapper blockPos);
boolean hasCeiling();
public boolean hasCeiling();
boolean hasSkyLight();
public boolean hasSkyLight();
// Pls don't use this
// If the world is null then this can't be called and gives an error
boolean isEmpty();
public boolean isEmpty();
int getHeight();
public int getHeight();
int getSeaLevel();
public int getSeaLevel();
default short getMinHeight()
{
return 0;
}
/** @throws UnsupportedOperationException if the WorldWrapper isn't for a ServerWorld */
public File getSaveFolder() throws UnsupportedOperationException;
File getSaveFolder() throws UnsupportedOperationException;
}
@@ -0,0 +1,11 @@
package com.seibel.lod.core.wrapperInterfaces.worldGeneration;
import com.seibel.lod.core.builders.lodBuilding.LodBuilder;
import com.seibel.lod.core.objects.lod.LodDimension;
import com.seibel.lod.core.wrapperInterfaces.world.IWorldWrapper;
public abstract class AbstractExperimentalWorldGeneratorWrapper {
public AbstractExperimentalWorldGeneratorWrapper(LodBuilder newLodBuilder, LodDimension newLodDimension, IWorldWrapper worldWrapper) { }
public abstract void queueGenerationRequests(LodDimension lodDim, LodBuilder lodBuilder);
public abstract void stop();
}
@@ -29,7 +29,7 @@ import com.seibel.lod.core.wrapperInterfaces.world.IWorldWrapper;
* This is used for generating chunks
* in a variety of detail and threading levels.
* <p>
* Abstract instead of a interface so
* Abstract instead of an interface, so
* we can define its constructors.
*
* @author James Seibel
@@ -1,105 +0,0 @@
/*
* 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.forge;
import com.seibel.lod.core.api.EventApi;
import com.seibel.lod.core.wrapperInterfaces.chunk.IChunkWrapper;
import com.seibel.lod.forge.wrappers.chunk.ChunkWrapper;
import com.seibel.lod.forge.wrappers.world.DimensionTypeWrapper;
import com.seibel.lod.forge.wrappers.world.WorldWrapper;
import net.minecraftforge.client.event.InputEvent;
import net.minecraftforge.event.TickEvent;
import net.minecraftforge.event.world.BlockEvent;
import net.minecraftforge.event.world.ChunkEvent;
import net.minecraftforge.event.world.WorldEvent;
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 11-12-2021
*/
public class ForgeClientProxy
{
private final EventApi eventApi = EventApi.INSTANCE;
@SubscribeEvent
public void serverTickEvent(TickEvent.ServerTickEvent event)
{
eventApi.serverTickEvent();
}
@SubscribeEvent
public void chunkLoadEvent(ChunkEvent.Load event)
{
eventApi.chunkLoadEvent(new ChunkWrapper(event.getChunk()), DimensionTypeWrapper.getDimensionTypeWrapper(event.getWorld().dimensionType()));
}
@SubscribeEvent
public void worldSaveEvent(WorldEvent.Save event)
{
eventApi.worldSaveEvent();
}
/** This is also called when a new dimension loads */
@SubscribeEvent
public void worldLoadEvent(WorldEvent.Load event)
{
eventApi.worldLoadEvent(WorldWrapper.getWorldWrapper(event.getWorld()));
}
@SubscribeEvent
public void worldUnloadEvent(WorldEvent.Unload event)
{
eventApi.worldUnloadEvent();
}
@SubscribeEvent
public void blockChangeEvent(BlockEvent event)
{
// we only care about certain block events
if (event.getClass() == BlockEvent.BreakEvent.class ||
event.getClass() == BlockEvent.EntityPlaceEvent.class ||
event.getClass() == BlockEvent.EntityMultiPlaceEvent.class ||
event.getClass() == BlockEvent.FluidPlaceBlockEvent.class ||
event.getClass() == BlockEvent.PortalSpawnEvent.class)
{
IChunkWrapper chunk = new ChunkWrapper(event.getWorld().getChunk(event.getPos()));
DimensionTypeWrapper dimType = DimensionTypeWrapper.getDimensionTypeWrapper(event.getWorld().dimensionType());
// recreate the LOD where the blocks were changed
eventApi.blockChangeEvent(chunk, dimType);
}
}
@SubscribeEvent
public void onKeyInput(InputEvent.KeyInputEvent event)
{
eventApi.onKeyInput(event.getKey(), event.getAction());
}
}
@@ -1,437 +0,0 @@
/*
* 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.forge;
import java.nio.file.Path;
import java.nio.file.Paths;
import org.apache.commons.lang3.tuple.Pair;
import org.apache.logging.log4j.LogManager;
import com.electronwill.nightconfig.core.file.CommentedFileConfig;
import com.electronwill.nightconfig.core.io.WritingMode;
import com.seibel.lod.core.ModInfo;
import com.seibel.lod.core.enums.config.BlocksToAvoid;
import com.seibel.lod.core.enums.config.BufferRebuildTimes;
import com.seibel.lod.core.enums.config.DistanceGenerationMode;
import com.seibel.lod.core.enums.config.GenerationPriority;
import com.seibel.lod.core.enums.config.GpuUploadMethod;
import com.seibel.lod.core.enums.config.HorizontalQuality;
import com.seibel.lod.core.enums.config.HorizontalResolution;
import com.seibel.lod.core.enums.config.HorizontalScale;
import com.seibel.lod.core.enums.config.LodTemplate;
import com.seibel.lod.core.enums.config.VanillaOverdraw;
import com.seibel.lod.core.enums.config.VerticalQuality;
import com.seibel.lod.core.enums.rendering.DebugMode;
import com.seibel.lod.core.enums.rendering.FogDistance;
import com.seibel.lod.core.enums.rendering.FogDrawOverride;
import com.seibel.lod.core.objects.MinDefaultMax;
import com.seibel.lod.core.wrapperInterfaces.config.ILodConfigWrapperSingleton.IClient.IAdvanced;
import com.seibel.lod.core.wrapperInterfaces.config.ILodConfigWrapperSingleton.IClient.IGraphics;
import com.seibel.lod.core.wrapperInterfaces.config.ILodConfigWrapperSingleton.IClient.IWorldGenerator;
import com.seibel.lod.core.wrapperInterfaces.config.ILodConfigWrapperSingleton.IClient.IAdvanced.IBuffers;
import com.seibel.lod.core.wrapperInterfaces.config.ILodConfigWrapperSingleton.IClient.IAdvanced.IDebugging;
import com.seibel.lod.core.wrapperInterfaces.config.ILodConfigWrapperSingleton.IClient.IAdvanced.IThreading;
import com.seibel.lod.core.wrapperInterfaces.config.ILodConfigWrapperSingleton.IClient.IGraphics.IAdvancedGraphics;
import com.seibel.lod.core.wrapperInterfaces.config.ILodConfigWrapperSingleton.IClient.IGraphics.IFogQuality;
import com.seibel.lod.core.wrapperInterfaces.config.ILodConfigWrapperSingleton.IClient.IGraphics.IQuality;
import net.minecraftforge.common.ForgeConfigSpec;
import net.minecraftforge.eventbus.api.SubscribeEvent;
import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.config.ModConfig;
/**
* This handles any configuration the user has access to.
* @author Leonardo Amato
* @author James Seibel
* @version 11-16-2021
*/
@Mod.EventBusSubscriber
public class ForgeConfig
{
// 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 AdvancedModOptions advancedModOptions;
//================//
// Client Configs //
//================//
public Client(ForgeConfigSpec.Builder builder)
{
builder.push(this.getClass().getSimpleName());
{
graphics = new Graphics(builder);
worldGenerator = new WorldGenerator(builder);
advancedModOptions = new AdvancedModOptions(builder);
}
builder.pop();
}
//==================//
// Graphics Configs //
//==================//
public static class Graphics
{
public final QualityOption qualityOption;
public final FogQualityOption fogQuality;
public final AdvancedGraphicsOption advancedGraphicsOption;
Graphics(ForgeConfigSpec.Builder builder)
{
builder.comment(IGraphics.DESC).push("Graphics");
{
qualityOption = new QualityOption(builder);
advancedGraphicsOption = new AdvancedGraphicsOption(builder);
fogQuality = new FogQualityOption(builder);
}
builder.pop();
}
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(IQuality.DESC).push(this.getClass().getSimpleName());
verticalQuality = builder
.comment("\n\n"
+ IQuality.VERTICAL_QUALITY_DESC)
.defineEnum("Vertical Quality", IQuality.VERTICAL_QUALITY_DEFAULT);
horizontalScale = builder
.comment("\n\n"
+ IQuality.HORIZONTAL_SCALE_DESC)
.defineEnum("Horizontal Scale", IQuality.HORIZONTAL_SCALE_DEFAULT);
horizontalQuality = builder
.comment("\n\n"
+ IQuality.HORIZONTAL_QUALITY_DESC)
.defineEnum("Horizontal Quality", IQuality.HORIZONTAL_QUALITY_DEFAULT);
drawResolution = builder
.comment("\n\n"
+ IQuality.DRAW_RESOLUTION_DESC)
.defineEnum("Block size", IQuality.DRAW_RESOLUTION_DEFAULT);
MinDefaultMax<Integer> minDefaultMax = IQuality.LOD_CHUNK_RENDER_DISTANCE_MIN_DEFAULT_MAX;
lodChunkRenderDistance = builder
.comment("\n\n"
+ IQuality.LOD_CHUNK_RENDER_DISTANCE_DESC)
.defineInRange("Lod Render Distance", minDefaultMax.defaultValue, minDefaultMax.minValue, minDefaultMax.maxValue);
builder.pop();
}
}
public static class FogQualityOption
{
public final ForgeConfigSpec.EnumValue<FogDistance> fogDistance;
public final ForgeConfigSpec.EnumValue<FogDrawOverride> fogDrawOverride;
public final ForgeConfigSpec.BooleanValue disableVanillaFog;
FogQualityOption(ForgeConfigSpec.Builder builder)
{
builder.comment(IFogQuality.DESC).push(this.getClass().getSimpleName());
fogDistance = builder
.comment("\n\n"
+ IFogQuality.FOG_DISTANCE_DESC)
.defineEnum("Fog Distance", IFogQuality.FOG_DISTANCE_DEFAULT);
fogDrawOverride = builder
.comment("\n\n"
+ IFogQuality.FOG_DRAW_OVERRIDE_DESC)
.defineEnum("Fog Draw Override", IFogQuality.FOG_DRAW_OVERRIDE_DEFAULT);
disableVanillaFog = builder
.comment("\n\n"
+ IFogQuality.DISABLE_VANILLA_FOG_DESC)
.define("Experimental Disable Vanilla Fog", IFogQuality.DISABLE_VANILLA_FOG_DEFAULT);
builder.pop();
}
}
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;
public final ForgeConfigSpec.EnumValue<GpuUploadMethod> gpuUploadMethod;
public final ForgeConfigSpec.BooleanValue useExtendedNearClipPlane;
AdvancedGraphicsOption(ForgeConfigSpec.Builder builder)
{
builder.comment(IAdvancedGraphics.DESC).push(this.getClass().getSimpleName());
lodTemplate = builder
.comment("\n\n"
+ IAdvancedGraphics.LOD_TEMPLATE_DESC)
.defineEnum("LOD Template", IAdvancedGraphics.LOD_TEMPLATE_DEFAULT);
disableDirectionalCulling = builder
.comment("\n\n"
+ IAdvancedGraphics.DISABLE_DIRECTIONAL_CULLING_DESC)
.define("Disable Directional Culling", IAdvancedGraphics.DISABLE_DIRECTIONAL_CULLING_DEFAULT);
alwaysDrawAtMaxQuality = builder
.comment("\n\n"
+ IAdvancedGraphics.ALWAYS_DRAW_AT_MAD_QUALITY_DESC)
.define("Always Use Max Quality", IAdvancedGraphics.ALWAYS_DRAW_AT_MAD_QUALITY_DEFAULT);
vanillaOverdraw = builder
.comment("\n\n"
+ IAdvancedGraphics.VANILLA_OVERDRAW_DESC)
.defineEnum("Vanilla Overdraw", IAdvancedGraphics.VANILLA_OVERDRAW_DEFAULT);
gpuUploadMethod = builder
.comment("\n\n"
+ IAdvancedGraphics.GPU_UPLOAD_METHOD_DESC)
.defineEnum("GPU Upload Method", IAdvancedGraphics.GPU_UPLOAD_METHOD_DEFAULT);
// This is a temporary fix (like vanilla overdraw)
// hopefully we can remove both once we get individual chunk rendering figured out
useExtendedNearClipPlane = builder
.comment("\n\n"
+ IAdvancedGraphics.USE_EXTENDED_NEAR_CLIP_PLANE_DESC)
.define("Use Extended Near Clip Plane", IAdvancedGraphics.USE_EXTENDED_NEAR_CLIP_PLANE_DEFAULT);
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<BlocksToAvoid> blocksToAvoid;
//public final ForgeConfigSpec.BooleanValue useExperimentalPreGenLoading;
WorldGenerator(ForgeConfigSpec.Builder builder)
{
builder.comment(IWorldGenerator.DESC).push("Generation");
generationPriority = builder
.comment("\n\n"
+ IWorldGenerator.GENERATION_PRIORITY_DESC)
.defineEnum("Generation Priority", IWorldGenerator.GENERATION_PRIORITY_DEFAULT);
distanceGenerationMode = builder
.comment("\n\n"
+ IWorldGenerator.DISTANCE_GENERATION_MODE_DESC)
.defineEnum("Distance Generation Mode", IWorldGenerator.DISTANCE_GENERATION_MODE_DEFAULT);
allowUnstableFeatureGeneration = builder
.comment("\n\n"
+ IWorldGenerator.ALLOW_UNSTABLE_FEATURE_GENERATION_DESC)
.define("Allow Unstable Feature Generation", IWorldGenerator.ALLOW_UNSTABLE_FEATURE_GENERATION_DEFAULT);
blocksToAvoid = builder
.comment("\n\n"
+ IWorldGenerator.BLOCKS_TO_AVOID_DESC)
.defineEnum("Blocks to avoid", IWorldGenerator.BLOCKS_TO_AVOID_DEFAULT);
/*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(IAdvanced.DESC).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(IThreading.DESC).push(this.getClass().getSimpleName());
MinDefaultMax<Integer> minDefaultMax = IThreading.NUMBER_OF_WORLD_GENERATION_THREADS_DEFAULT;
numberOfWorldGenerationThreads = builder
.comment("\n\n"
+ IThreading.NUMBER_OF_WORLD_GENERATION_THREADS_DESC)
.defineInRange("numberOfWorldGenerationThreads", minDefaultMax.defaultValue, minDefaultMax.minValue, minDefaultMax.maxValue);
minDefaultMax = IThreading.NUMBER_OF_BUFFER_BUILDER_THREADS_MIN_DEFAULT_MAX;
numberOfBufferBuilderThreads = builder
.comment("\n\n"
+ IThreading.NUMBER_OF_BUFFER_BUILDER_THREADS_MIN_DEFAULT_MAX)
.defineInRange("numberOfBufferBuilderThreads", minDefaultMax.defaultValue, minDefaultMax.minValue, minDefaultMax.maxValue);
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(IDebugging.DESC).push(this.getClass().getSimpleName());
drawLods = builder
.comment("\n\n"
+ IDebugging.DRAW_LODS_DESC)
.define("Enable Rendering", IDebugging.DRAW_LODS_DEFAULT);
debugMode = builder
.comment("\n\n"
+ IDebugging.DEBUG_MODE_DESC)
.defineEnum("Debug Mode", IDebugging.DEBUG_MODE_DEFAULT);
enableDebugKeybindings = builder
.comment("\n\n"
+ IDebugging.DEBUG_KEYBINDINGS_ENABLED_DESC)
.define("Enable Debug Keybinding", IDebugging.DEBUG_KEYBINDINGS_ENABLED_DEFAULT);
builder.pop();
}
}
public static class Buffers
{
public final ForgeConfigSpec.EnumValue<BufferRebuildTimes> rebuildTimes;
Buffers(ForgeConfigSpec.Builder builder)
{
builder.comment(IBuffers.DESC).push(this.getClass().getSimpleName());
rebuildTimes = builder
.comment("\n\n"
+ IBuffers.REBUILD_TIMES_DESC)
.defineEnum("rebuildFrequency", IBuffers.REBUILD_TIMES_DEFAULT);
builder.pop();
}
}
}
}
/** {@link Path} to the configuration file of this mod */
private static final Path CONFIG_PATH = Paths.get("config", ModInfo.NAME + ".toml");
public static final ForgeConfigSpec CLIENT_SPEC;
public static final Client CLIENT;
static
{
final Pair<Client, ForgeConfigSpec> specPair = new ForgeConfigSpec.Builder().configure(Client::new);
CLIENT_SPEC = specPair.getRight();
CLIENT = specPair.getLeft();
CommentedFileConfig clientConfig = CommentedFileConfig.builder(CONFIG_PATH)
.writingMode(WritingMode.REPLACE)
.build();
clientConfig.load();
clientConfig.save();
CLIENT_SPEC.setConfig(clientConfig);
}
@SubscribeEvent
public static void onLoad(final ModConfig.Loading configEvent)
{
LogManager.getLogger().debug(ModInfo.NAME, "Loaded forge config file {}", configEvent.getConfig().getFileName());
}
@SubscribeEvent
public static void onFileChange(final ModConfig.Reloading configEvent)
{
LogManager.getLogger().debug(ModInfo.NAME, "Forge config just got changed on the file system!");
}
}

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