Compare commits

...

161 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
70 changed files with 5198 additions and 2776 deletions
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>
@@ -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()
@@ -76,7 +75,7 @@ public class ClientApi
public void renderLods(Mat4f mcModelViewMatrix, Mat4f mcProjectionMatrix, float partialTicks)
{
// comment out when creating a release
// applyConfigOverrides();
applyConfigOverrides();
// clear any out of date objects
MC.clearFrameObjectCache();
@@ -114,9 +113,17 @@ public class ClientApi
profiler.pop(); // get out of "terrain"
profiler.push("LOD");
ClientApi.renderer.drawLODs(lodDim, mcModelViewMatrix, mcProjectionMatrix, partialTicks, MC.getProfiler());
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"
}
@@ -143,12 +150,16 @@ 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);
@@ -171,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()
{
@@ -180,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);
}
@@ -104,6 +106,8 @@ public class EventApi
public void worldLoadEvent(IWorldWrapper world)
{
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) + DataPointUtil.VERTICAL_OFFSET,
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
@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 @@ import com.seibel.lod.core.wrapperInterfaces.world.IWorldWrapper;
//public static final boolean useExperimentalLighting = true;
private static int timesToEdgeDetect = 1;
@@ -110,8 +108,8 @@ import com.seibel.lod.core.wrapperInterfaces.world.IWorldWrapper;
Thread thread = new Thread(() ->
{
//noinspection GrazieInspection
try
{
//try
//{
// we need a loaded client world in order to
// get the textures for blocks
if (MC.getWrappedClientWorld() == null)
@@ -134,14 +132,14 @@ import com.seibel.lod.core.wrapperInterfaces.world.IWorldWrapper;
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);
}
@@ -162,6 +160,7 @@ import com.seibel.lod.core.wrapperInterfaces.world.IWorldWrapper;
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");
@@ -169,7 +168,7 @@ import com.seibel.lod.core.wrapperInterfaces.world.IWorldWrapper;
int startZ;
LodRegion region = lodDim.getRegion(chunk.getPos().getRegionX(), chunk.getPos().getRegionZ());
LodRegion region = lodDim.getRegion(chunk.getRegionPosX(), chunk.getRegionPosZ());
if (region == null)
return;
@@ -203,12 +202,14 @@ import com.seibel.lod.core.wrapperInterfaces.world.IWorldWrapper;
//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 */
@@ -219,8 +220,6 @@ import com.seibel.lod.core.wrapperInterfaces.world.IWorldWrapper;
long[] dataToMerge = ThreadMapUtil.getBuilderVerticalArray(detail.detailLevel);
int verticalData = DataPointUtil.WORLD_HEIGHT / 2 + 1;
AbstractChunkPosWrapper chunkPos = chunk.getPos();
int height;
int depth;
int color;
@@ -237,55 +236,48 @@ import com.seibel.lod.core.wrapperInterfaces.world.IWorldWrapper;
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.WORLD_HEIGHT - DataPointUtil.VERTICAL_OFFSET + 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;
isDefault = ((light >> 8)) == 1;
dataToMerge[index * verticalData + count] = DataPointUtil.createDataPoint(height - DataPointUtil.VERTICAL_OFFSET, depth - DataPointUtil.VERTICAL_OFFSET, color, lightSky, lightBlock, generation, isDefault);
dataToMerge[index * verticalData + count] = DataPointUtil.createDataPoint(height, depth, color, lightSky, lightBlock, generation, isDefault);
topBlock = false;
yAbs = depth - 1;
count++;
@@ -298,34 +290,56 @@ import com.seibel.lod.core.wrapperInterfaces.world.IWorldWrapper;
* 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;
@@ -345,29 +359,27 @@ import com.seibel.lod.core.wrapperInterfaces.world.IWorldWrapper;
* 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
@@ -383,7 +395,7 @@ import com.seibel.lod.core.wrapperInterfaces.world.IWorldWrapper;
}
/** 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;
@@ -392,25 +404,25 @@ import com.seibel.lod.core.wrapperInterfaces.world.IWorldWrapper;
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)
{
// 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;
}
@@ -418,7 +430,7 @@ import com.seibel.lod.core.wrapperInterfaces.world.IWorldWrapper;
{
// 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;
@@ -439,7 +451,7 @@ import com.seibel.lod.core.wrapperInterfaces.world.IWorldWrapper;
else
{
// client world sky light (almost never accurate)
blockLight = world.getBlockLight(blockPos);
blockLight = world.getBlockLight(x,y,z);
// estimate what the lighting should be
if (hasSkyLight || !hasCeiling)
{
@@ -448,14 +460,14 @@ import com.seibel.lod.core.wrapperInterfaces.world.IWorldWrapper;
else
{
if (hasSkyLight)
skyLight = world.getSkyLight(blockPos);
skyLight = world.getSkyLight(x,y,z);
//else
// skyLight = 0;
if (!chunk.isLightCorrect() && (skyLight == 0 || skyLight == 15))
{
// we don't know what the light here is,
// lets just take a guess
if (blockPos.getY() >= MC.getWrappedClientWorld().getSeaLevel() - 5)
if (y >= MC.getWrappedClientWorld().getSeaLevel() - 5)
{
skyLight = 12;
isDefault = 1;
@@ -474,34 +486,31 @@ import com.seibel.lod.core.wrapperInterfaces.world.IWorldWrapper;
}
/** 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
@@ -521,15 +530,16 @@ import com.seibel.lod.core.wrapperInterfaces.world.IWorldWrapper;
/** 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 than 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 final 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);
}
}
@@ -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;
}
}
}
@@ -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,7 +20,6 @@
package com.seibel.lod.core.handlers;
import com.seibel.lod.core.enums.rendering.FogDrawMode;
import com.seibel.lod.core.objects.math.Mat4f;
/**
* A singleton used to get variables from methods
@@ -35,7 +34,7 @@ import com.seibel.lod.core.objects.math.Mat4f;
* different MC versions.
*
* @author James Seibel
* @version 11-26-2021
* @version 12-14-2021
*/
public interface IReflectionHandler
{
@@ -45,14 +44,6 @@ public interface IReflectionHandler
/** @returns if Vivecraft is present. Attempts to find the "VRRenderer" class. */
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.
*/
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();
}
@@ -29,6 +29,7 @@ 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;
@@ -78,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 = 7;
public static final int LOD_SAVE_FILE_VERSION = 8;
/**
* Allow saving asynchronously, but never try to save multiple regions
@@ -100,6 +101,8 @@ public class LodDimensionFileHandler
//================//
// read from file //
//================//
@@ -116,130 +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
VerticalQuality tempVerticalQuality = VerticalQuality.HIGH;
do {
DistanceGenerationMode tempGenMode = DistanceGenerationMode.FULL;
do {
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;
} while (tempGenMode != generationMode);
if (fileName != null)
{
file = new File(fileName);
if (file.exists())
break;
}
if (tempVerticalQuality == VerticalQuality.HIGH)
tempVerticalQuality = VerticalQuality.MEDIUM;
else if (tempVerticalQuality == VerticalQuality.MEDIUM)
tempVerticalQuality = VerticalQuality.LOW;
} while (tempVerticalQuality != verticalQuality);
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 < 6)
{
// 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;
}
else if (fileVersion == 6)
{
//this is old, but readable version
byte[] data = ThreadMapUtil.getSaveContainer(tempDetailLevel);
inputStream.read(data);
inputStream.close();
// add the data to our region
region.addLevelContainer(new VerticalLevelContainer(data, 6));
} else
{
// 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, LOD_SAVE_FILE_VERSION));
}
}
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
@@ -294,163 +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();
}
}
}
public void saveRegionFile (byte[] regionFile, RegionPos regionPos, DistanceGenerationMode generationMode, byte detailLevel, VerticalQuality verticalQuality)
{
int regionX = regionPos.x;
int regionZ = regionPos.z;
String fileName = getFileNameAndPathForRegion(regionX, regionZ, generationMode, detailLevel, verticalQuality);
if (fileName != null)
{
File oldFile = new File(fileName);
File newFile = new File(fileName + TMP_FILE_EXTENSION);
try (OutputStream os = new FileOutputStream(newFile))
{
os.write(regionFile);
Files.move(newFile.toPath(), oldFile.toPath(), StandardCopyOption.ATOMIC_MOVE, StandardCopyOption.REPLACE_EXISTING);
os.close();
}
catch (IOException ioEx)
{
ClientApi.LOGGER.error("LOD file write error. Unable to write to [" + fileName + "] error [" + ioEx.getMessage() + "]: ");
ioEx.printStackTrace();
}
}
}
public byte[] getRegionFile (RegionPos regionPos, DistanceGenerationMode generationMode, byte detailLevel, VerticalQuality verticalQuality)
{
int regionX = regionPos.x;
int regionZ = regionPos.z;
String fileName = getFileNameAndPathForRegion(regionX, regionZ, generationMode, detailLevel, verticalQuality);
if (fileName != null)
{
File file = new File(fileName);
try (InputStream is = new FileInputStream(file))
{
byte[] data = ThreadMapUtil.getSaveContainer(detailLevel);
is.read(data);
is.close();
return Arrays.copyOf(data, (int) file.length());
}
catch (IOException ioEx)
{
ClientApi.LOGGER.error("LOD file read error. Unable to read to [" + fileName + "] error [" + ioEx.getMessage() + "]: ");
ioEx.printStackTrace();
}
}
return new byte[0];
}
//================//
// helper methods //
//================//
public int getHashFromFile(RegionPos regionPos, DistanceGenerationMode generationMode, byte detailLevel, VerticalQuality verticalQuality)
{
int regionX = regionPos.x;
int regionZ = regionPos.z;
String fileName = getFileNameAndPathForRegion(regionX, regionZ, generationMode, detailLevel, verticalQuality);
if (fileName == null)
return 0;
File file = new File(fileName);
return file.hashCode();
}
/**
* Return the name of the file that should contain the
* region at the given x and z. <br>
@@ -460,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;
}
}
@@ -26,26 +26,28 @@ import org.apache.logging.log4j.Logger;
import com.seibel.lod.core.ModInfo;
import com.seibel.lod.core.enums.rendering.FogDrawMode;
import com.seibel.lod.core.objects.math.Mat4f;
/**
* 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-26-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)
{
@@ -155,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);
}
}
@@ -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]))
{
@@ -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
@@ -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.
@@ -682,7 +690,10 @@ public class LodDimension
LodRegion region = getRegion(regionPos.x, regionPos.z);
// use FAR_FIRST on local worlds and NEAR_FIRST on servers
GenerationPriority generationPriority = CONFIG.client().worldGenerator().getGenerationPriority() == GenerationPriority.AUTO && MC.hasSinglePlayerServer() ? GenerationPriority.FAR_FIRST : GenerationPriority.NEAR_FIRST;
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)
@@ -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()
{
}
}
@@ -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,7 +19,6 @@
package com.seibel.lod.core.objects.opengl;
import java.nio.Buffer;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;
@@ -38,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
{
@@ -84,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)
{
@@ -283,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;
@@ -354,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)
{
@@ -436,7 +450,7 @@ public class LodBufferBuilder
{
LodBufferBuilder.DrawState bufferbuilder$drawstate = this.vertexCounts.get(this.lastRenderedCountIndex++);
this.buffer.position(this.totalUploadedBytes);
this.totalUploadedBytes += bufferbuilder$drawstate.vertexCount() * bufferbuilder$drawstate.format().getVertexSize();
this.totalUploadedBytes += bufferbuilder$drawstate.vertexCount() * bufferbuilder$drawstate.format().getByteSize();
this.buffer.limit(this.totalUploadedBytes);
if (this.lastRenderedCountIndex == this.vertexCounts.size() && this.vertices == 0)
{
@@ -444,7 +458,7 @@ public class LodBufferBuilder
}
ByteBuffer bytebuffer = this.buffer.slice();
bytebuffer.order(this.buffer.order()); // FORGE: Fix incorrect byte order
//bytebuffer.order(this.buffer.order()); // FORGE: Fix incorrect byte order
this.buffer.clear();
return bytebuffer; // the original method also returned bufferbuilder$drawstate
}
@@ -529,10 +543,10 @@ public class LodBufferBuilder
// Forge added methods
public void putBulkData(ByteBuffer buffer)
{
ensureCapacity(buffer.limit() + this.format.getVertexSize());
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 && this.elements.equals(vertexformat.elements);
return this.byteSize == vertexformat.byteSize && this.elements.equals(vertexformat.elements);
}
else
{
@@ -1,43 +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.FogDrawMode;
/**
* 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;
}
@@ -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
@@ -20,24 +20,25 @@
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.GL15;
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;
@@ -57,15 +58,16 @@ import com.seibel.lod.core.wrapperInterfaces.minecraft.IMinecraftWrapper;
* https://stackoverflow.com/questions/63509735/massive-performance-loss-with-glmapbuffer <br><br>
*
* @author James Seibel
* @version 12-1-2021
* @version 12-9-2021
*/
public class GLProxy
{
private static final IMinecraftWrapper MC = SingletonHandler.get(IMinecraftWrapper.class);
private static final ILodConfigWrapperSingleton CONFIG = SingletonHandler.get(ILodConfigWrapperSingleton.class);
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;
@@ -84,34 +86,95 @@ 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 //
//============================//
@@ -119,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();
@@ -130,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
//==================================//
@@ -152,87 +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
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.");
}
// if using AUTO gpuUpload
// determine a good default for the GPU
if (CONFIG.client().advanced().buffers().getGpuUploadMethod() == GpuUploadMethod.AUTO)
String vendor = GL32.glGetString(GL32.GL_VENDOR).toUpperCase(); // example return: "NVIDIA CORPORATION"
if (vendor.contains("NVIDIA") || vendor.contains("GEFORCE"))
{
GpuUploadMethod uploadMethod;
String vendor = GL15.glGetString(GL15.GL_VENDOR).toUpperCase(); // example return: "NVIDIA CORPORATION"
if (vendor.contains("NVIDIA") || vendor.contains("GEFORCE"))
{
// NVIDIA card
if (bufferStorageSupported)
{
uploadMethod = GpuUploadMethod.BUFFER_STORAGE;
}
else
{
uploadMethod = GpuUploadMethod.SUB_DATA;
}
}
else
{
// AMD or Intel card
if (mapBufferRangeSupported)
{
uploadMethod = GpuUploadMethod.BUFFER_MAPPING;
}
else
{
uploadMethod = GpuUploadMethod.DATA;
}
}
CONFIG.client().advanced().buffers().setGpuUploadMethod(uploadMethod);
ClientApi.LOGGER.info("GPU Vendor [" + vendor + "], Upload method set to [" + uploadMethod + "].");
// NVIDIA card
preferredUploadMethod = bufferStorageSupported ? GpuUploadMethod.BUFFER_STORAGE : GpuUploadMethod.SUB_DATA;
}
else
{
// AMD or Intel card
preferredUploadMethod = GpuUploadMethod.BUFFER_MAPPING;
}
//==============//
// shader setup //
//==============//
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 //
@@ -241,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" + File.separator + "standard.vert", false);
fragmentShader = LodShader.loadShader(GL20.GL_FRAGMENT_SHADER, "shaders" + File.separator + "flat_shaded.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/standard.vert", true);
// fragmentShader = LodShader.loadShader(GL20.GL_FRAGMENT_SHADER, "C:/Users/James Seibel/Desktop/shaders/flat_shaded.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.
@@ -359,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.
@@ -414,7 +431,7 @@ public class GLProxy
* This only works with Legacy OpenGL because James hasn't
* looking into a way for it to work with Modern OpenGL.
*/
public void disableLegacyFog()
public boolean disableLegacyFog()
{
// make sure this is a legacy OpenGL context
if (minecraftGlCapabilities.glFogf != 0)
@@ -427,7 +444,9 @@ public class GLProxy
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);
}
}
}
@@ -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
@@ -22,33 +22,27 @@ package com.seibel.lod.core.render;
import java.awt.Color;
import java.util.HashSet;
import org.lwjgl.opengl.GL15;
import org.lwjgl.opengl.GL20;
import org.lwjgl.opengl.GL30;
import org.lwjgl.opengl.GL32;
import com.seibel.lod.core.api.ApiShared;
import com.seibel.lod.core.api.ClientApi;
import com.seibel.lod.core.builders.bufferBuilding.LodBufferBuilderFactory;
import com.seibel.lod.core.builders.bufferBuilding.LodBufferBuilderFactory.VertexBuffersAndOffset;
import com.seibel.lod.core.enums.config.GpuUploadMethod;
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.FogDrawMode;
import com.seibel.lod.core.handlers.IReflectionHandler;
import com.seibel.lod.core.objects.lod.LodDimension;
import com.seibel.lod.core.objects.lod.RegionPos;
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.objects.opengl.LodVertexBuffer;
import com.seibel.lod.core.objects.rending.LodFogConfig;
import com.seibel.lod.core.render.shader.LodShaderProgram;
import com.seibel.lod.core.render.objects.LightmapTexture;
import com.seibel.lod.core.util.DetailDistanceUtil;
import com.seibel.lod.core.util.LevelPosUtil;
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.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;
@@ -60,7 +54,7 @@ import com.seibel.lod.core.wrapperInterfaces.minecraft.IProfilerWrapper;
* This is where LODs are draw to the world.
*
* @author James Seibel
* @version 11-27-2021
* @version 12-12-2021
*/
public class LodRenderer
{
@@ -68,8 +62,6 @@ public class LodRenderer
private static final IMinecraftRenderWrapper MC_RENDER = SingletonHandler.get(IMinecraftRenderWrapper.class);
private static final ILodConfigWrapperSingleton CONFIG = SingletonHandler.get(ILodConfigWrapperSingleton.class);
private static final IReflectionHandler REFLECTION_HANDLER = SingletonHandler.get(IReflectionHandler.class);
private static final IWrapperFactory FACTORY = SingletonHandler.get(IWrapperFactory.class);
/**
* If true the LODs colors will be replaced with
@@ -77,8 +69,8 @@ public class LodRenderer
*/
public DebugMode previousDebugMode = DebugMode.OFF;
private int farPlaneBlockDistance;
// This tells us if the renderer is enabled or not. If in a world, it should be enabled.
private boolean isSetupComplete = false;
/** This is used to generate the buildable buffers */
private final LodBufferBuilderFactory lodBufferBuilderFactory;
@@ -90,18 +82,18 @@ public class LodRenderer
* These have to be separate because we can't override the
* buffers in the VBOs (and we don't want to)
*/
@SuppressWarnings("unused")
private int[][][] storageBufferIds;
private int[][][] storageBufferIds = null;
private AbstractChunkPosWrapper vbosCenter = FACTORY.createChunkPos();
// The shader program
LodRenderProgram shaderProgram = null;
private int vbosCenterX = 0;
private int vbosCenterZ = 0;
/** This is used to determine if the LODs should be regenerated */
private int[] previousPos = new int[] { 0, 0, 0 };
// these variables are used to determine if the buffers should be rebuilt
private float prevSkyBrightness = 0;
private double prevBrightness = 0;
private int prevRenderDistance = 0;
private long prevPlayerPosTime = 0;
private long prevVanillaChunkTime = 0;
@@ -117,6 +109,7 @@ public class LodRenderer
*/
private volatile boolean partialRegen = false;
private volatile boolean fullRegen = true;
private volatile boolean markToCleanup = false;
/**
* This HashSet contains every chunk that Vanilla Minecraft
@@ -125,22 +118,16 @@ public class LodRenderer
public boolean[][] vanillaRenderedChunks;
public boolean vanillaRenderedChunksChanged;
public boolean vanillaRenderedChunksEmptySkip = false;
public int vanillaBlockRenderedDistance;
private boolean canVanillaFogBeDisabled = true;
public void requestCleanup() {markToCleanup = true;}
public LodRenderer(LodBufferBuilderFactory newLodNodeBufferBuilder)
{
lodBufferBuilderFactory = newLodNodeBufferBuilder;
}
/**
* Besides drawing the LODs this method also starts
* the async process of generating the Buffers that hold those LODs.
@@ -162,6 +149,8 @@ public class LodRenderer
return;
}
if (MC_RENDER.playerHasBlindnessEffect())
{
// if the player is blind, don't render LODs,
@@ -169,16 +158,25 @@ public class LodRenderer
// which blindness relies on.
return;
}
// get MC's shader program
// Save all MC render state
int currentProgram = GL32.glGetInteger(GL32.GL_CURRENT_PROGRAM);
int currentVBO = GL32.glGetInteger(GL32.GL_ARRAY_BUFFER_BINDING);
int currentVAO = GL32.glGetInteger(GL32.GL_VERTEX_ARRAY_BINDING);
int currentActiveText = GL32.glGetInteger(GL32.GL_ACTIVE_TEXTURE);
boolean currentBlend = GL32.glGetBoolean(GL32.GL_BLEND);
if (CONFIG.client().graphics().fogQuality().getDisableVanillaFog())
GLProxy.getInstance().disableLegacyFog();
GLProxy glProxy = GLProxy.getInstance();
if (canVanillaFogBeDisabled && CONFIG.client().graphics().fogQuality().getDisableVanillaFog())
if (!glProxy.disableLegacyFog())
if (!MC_RENDER.tryDisableVanillaFog())
canVanillaFogBeDisabled = false;
// TODO move the buffer regeneration logic into its own class (probably called in the client api instead)
// starting here...
determineIfLodsShouldRegenerate(lodDim, partialTicks);
//=================//
// create the LODs //
@@ -192,7 +190,7 @@ public class LodRenderer
if ((partialRegen || fullRegen) && !lodBufferBuilderFactory.generatingBuffers && !lodBufferBuilderFactory.newBuffersAvailable())
{
// generate the LODs on a separate thread to prevent stuttering or freezing
lodBufferBuilderFactory.generateLodBuffersAsync(this, lodDim, MC.getPlayerBlockPos(), true);
lodBufferBuilderFactory.generateLodBuffersAsync(this, lodDim, MC.getPlayerBlockPos().getX(), MC.getPlayerBlockPos().getY(), MC.getPlayerBlockPos().getZ(), fullRegen);
// the regen process has been started,
// it will be done when lodBufferBuilder.newBuffersAvailable()
@@ -209,280 +207,169 @@ public class LodRenderer
swapBuffers();
}
if (vbos == null) {
// There is still no vbos, which means nothing needs to be drawn. So no rendering needed
// (Vbos should be setup by now)
return;
}
// FIXME: Currently, we check for last Lod Dimension so that we can trigger a cleanup() if dimension has changed
// The better thing to do is to call cleanup() on leaving dimensions in the EventApi, but only for client-side.
if (markToCleanup) {
markToCleanup = false;
cleanup(); // This will unset the isSetupComplete, causing a setup() call.
}
//===================//
// draw params setup //
//===================//
//===============//
// initial setup //
//===============//
profiler.push("LOD setup");
GLProxy glProxy = GLProxy.getInstance();
profiler.push("LOD draw setup");
/*---------Set GL State--------*/
// Make sure to unbind current VBO so we don't mess up vanilla settings
GL32.glBindBuffer(GL32.GL_ARRAY_BUFFER, 0);
// set the required open GL settings
if (CONFIG.client().advanced().debugging().getDebugMode() == DebugMode.SHOW_DETAIL_WIREFRAME)
GL15.glPolygonMode(GL15.GL_FRONT_AND_BACK, GL15.GL_LINE);
GL32.glPolygonMode(GL32.GL_FRONT_AND_BACK, GL32.GL_LINE);
else
GL15.glPolygonMode(GL15.GL_FRONT_AND_BACK, GL15.GL_FILL);
GL15.glEnable(GL15.GL_CULL_FACE);
GL15.glEnable(GL15.GL_DEPTH_TEST);
GL32.glPolygonMode(GL32.GL_FRONT_AND_BACK, GL32.GL_FILL);
GL32.glEnable(GL32.GL_CULL_FACE);
GL32.glEnable(GL32.GL_DEPTH_TEST);
// enable transparent rendering
GL15.glBlendFunc(GL15.GL_SRC_ALPHA, GL15.GL_ONE_MINUS_SRC_ALPHA);
GL15.glEnable(GL15.GL_BLEND);
// get MC's shader program
int currentProgram = GL20.glGetInteger(GL20.GL_CURRENT_PROGRAM);
// GL32.glBlendFunc(GL32.GL_SRC_ALPHA, GL32.GL_ONE_MINUS_SRC_ALPHA);
// GL32.glEnable(GL32.GL_BLEND);
/*---------Bind required objects--------*/
// Setup LodRenderProgram and the LightmapTexture if it has not yet been done
// also binds LightmapTexture, VAO, and ShaderProgram
if (!isSetupComplete) {
setup();
} else {
shaderProgram.bind();
}
GL32.glActiveTexture(GL32.GL_TEXTURE0);
LightmapTexture lightmapTexture = new LightmapTexture();
/*---------Get required data--------*/
// Get the matrixs for rendering
Mat4f modelViewMatrix = translateModelViewMatrix(mcModelViewMatrix, partialTicks);
vanillaBlockRenderedDistance = MC_RENDER.getRenderDistance() * LodUtil.CHUNK_WIDTH;
int vanillaBlockRenderedDistance = MC_RENDER.getRenderDistance() * LodUtil.CHUNK_WIDTH;
int farPlaneBlockDistance;
// required for setupFog and setupProjectionMatrix
if (MC.getWrappedClientWorld().getDimensionType().hasCeiling())
farPlaneBlockDistance = Math.min(CONFIG.client().graphics().quality().getLodChunkRenderDistance(), LodUtil.CEILED_DIMENSION_MAX_RENDER_DISTANCE) * LodUtil.CHUNK_WIDTH;
else
farPlaneBlockDistance = CONFIG.client().graphics().quality().getLodChunkRenderDistance() * LodUtil.CHUNK_WIDTH;
Mat4f projectionMatrix = createProjectionMatrix(mcProjectionMatrix, vanillaBlockRenderedDistance, farPlaneBlockDistance);
LodFogConfig fogSettings = new LodFogConfig(CONFIG, REFLECTION_HANDLER, farPlaneBlockDistance, vanillaBlockRenderedDistance);
/*---------Fill uniform data--------*/
// Fill the uniform data. Note: GL33.GL_TEXTURE0 == texture bindpoint 0
shaderProgram.fillUniformData(modelViewMatrix, projectionMatrix, getTranslatedCameraPos(),
getFogColor(), (int) (MC.getSkyDarken(partialTicks) * 15), 0);
// Previous guy said fog setting may be different from region to region, but the fogSettings never changed... soooooo...
shaderProgram.fillUniformDataForFog(fogSettings);
// Note: Since lightmapTexture is changing every frame, it's faster to recreate it than to reuse the old one.
lightmapTexture.fillData(MC_RENDER.getLightmapTextureWidth(), MC_RENDER.getLightmapTextureHeight(), MC_RENDER.getLightmapPixels());
//===========//
// rendering //
//===========//
profiler.popPush("LOD draw");
Mat4f projectionMatrix = createProjectionMatrix(mcProjectionMatrix, vanillaBlockRenderedDistance);
boolean cullingDisabled = CONFIG.client().graphics().advancedGraphics().getDisableDirectionalCulling();
boolean usingBufferStorage = glProxy.getGpuUploadMethod() == GpuUploadMethod.BUFFER_STORAGE;
LodFogConfig fogSettings = determineFogConfig();
if (vbos != null)
// where the center of the buffers is (needed when culling regions)
// render each of the buffers
for (int x = 0; x < vbos.length; x++)
{
//==============//
// shader setup //
//==============//
// can be used when testing shaders
// glProxy.createShaderProgram();
LodShaderProgram shaderProgram = glProxy.lodShaderProgram;
shaderProgram.use();
// determine the VertexArrayObject's element positions
int posAttrib = shaderProgram.getAttributeLocation("vPosition");
shaderProgram.enableVertexAttribute(posAttrib);
int colAttrib = shaderProgram.getAttributeLocation("color");
shaderProgram.enableVertexAttribute(colAttrib);
// global uniforms
int mvmUniform = shaderProgram.getUniformLocation("modelViewMatrix");
shaderProgram.setUniform(mvmUniform, modelViewMatrix);
int projUniform = shaderProgram.getUniformLocation("projectionMatrix");
shaderProgram.setUniform(projUniform, projectionMatrix);
int cameraUniform = shaderProgram.getUniformLocation("cameraPos");
shaderProgram.setUniform(cameraUniform, getTranslatedCameraPos());
int fogColorUniform = shaderProgram.getUniformLocation("fogColor");
shaderProgram.setUniform(fogColorUniform, getFogColor());
// region dependent uniforms
int fogEnabledUniform = shaderProgram.getUniformLocation("fogEnabled");
int nearFogEnabledUniform = shaderProgram.getUniformLocation("nearFogEnabled");
int farFogEnabledUniform = shaderProgram.getUniformLocation("farFogEnabled");
// near
int nearFogStartUniform = shaderProgram.getUniformLocation("nearFogStart");
int nearFogEndUniform = shaderProgram.getUniformLocation("nearFogEnd");
// far
int farFogStartUniform = shaderProgram.getUniformLocation("farFogStart");
int farFogEndUniform = shaderProgram.getUniformLocation("farFogEnd");
//===========//
// rendering //
//===========//
profiler.popPush("LOD draw");
boolean cullingDisabled = CONFIG.client().graphics().advancedGraphics().getDisableDirectionalCulling();
boolean renderBufferStorage = CONFIG.client().advanced().buffers().getGpuUploadMethod() == GpuUploadMethod.BUFFER_STORAGE && glProxy.bufferStorageSupported;
// where the center of the buffers is (needed when culling regions)
RegionPos vboCenterRegionPos = new RegionPos(vbosCenter);
RegionPos vboPos = new RegionPos();
// render each of the buffers
for (int x = 0; x < vbos.length; x++)
for (int z = 0; z < vbos.length; z++)
{
for (int z = 0; z < vbos.length; z++)
//int tempX = LodUtil.convertLevelPos(x + vbosCenterX - (lodDim.getWidth() / 2), LodUtil.REGION_DETAIL_LEVEL , LodUtil.BLOCK_DETAIL_LEVEL);
//int tempY = LodUtil.convertLevelPos(z + vbosCenterZ - (lodDim.getWidth() / 2), LodUtil.REGION_DETAIL_LEVEL, LodUtil.BLOCK_DETAIL_LEVEL);
if (cullingDisabled || RenderUtil.isRegionInViewFrustum(MC_RENDER.getCameraBlockPosition(),
MC_RENDER.getLookAtVector(),
vbosCenterX + LodUtil.convertLevelPos(x - (lodDim.getWidth() / 2), LodUtil.REGION_DETAIL_LEVEL , LodUtil.BLOCK_DETAIL_LEVEL),
vbosCenterZ + LodUtil.convertLevelPos(z - (lodDim.getWidth() / 2), LodUtil.REGION_DETAIL_LEVEL , LodUtil.BLOCK_DETAIL_LEVEL)))
{
vboPos.x = x + vboCenterRegionPos.x - (lodDim.getWidth() / 2);
vboPos.z = z + vboCenterRegionPos.z - (lodDim.getWidth() / 2);
if (cullingDisabled || RenderUtil.isRegionInViewFrustum(MC_RENDER.getCameraBlockPosition(), MC_RENDER.getLookAtVector(), vboPos.blockPos()))
// actual rendering
int bufferId = 0;
for (int i = 0; i < vbos[x][z].length; i++)
{
// fog may be different from region to region
applyFog(shaderProgram,
fogSettings, fogEnabledUniform, nearFogEnabledUniform, farFogEnabledUniform,
nearFogStartUniform, nearFogEndUniform, farFogStartUniform, farFogEndUniform);
// actual rendering
int bufferId = 0;
for (int i = 0; i < vbos[x][z].length; i++)
{
bufferId = (storageBufferIds != null && renderBufferStorage) ? storageBufferIds[x][z][i] : vbos[x][z][i].id;
drawArrays(bufferId, vbos[x][z][i].vertexCount, posAttrib, colAttrib);
}
bufferId = (storageBufferIds != null && usingBufferStorage) ? storageBufferIds[x][z][i] : vbos[x][z][i].id;
if (bufferId==0) continue;
GL32.glBindBuffer(GL32.GL_ARRAY_BUFFER, bufferId);
shaderProgram.bindVertexBuffer(bufferId);
GL32.glDrawArrays(GL32.GL_TRIANGLES, 0, vbos[x][z][i].vertexCount);
}
}
}
//================//
// render cleanup //
//================//
// if this cleanup isn't done MC may crash
// when trying to render its own terrain
GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
GL30.glBindVertexArray(0);
GL20.glDisableVertexAttribArray(posAttrib);
GL20.glDisableVertexAttribArray(colAttrib);
}
//=========//
// cleanup //
//=========//
//================//
// render cleanup //
//================//
profiler.popPush("LOD cleanup");
GL15.glPolygonMode(GL15.GL_FRONT_AND_BACK, GL15.GL_FILL);
GL15.glDisable(GL15.GL_BLEND); // TODO: what should this be reset to?
GL32.glBindBuffer(GL32.GL_ARRAY_BUFFER, 0);
shaderProgram.unbind();
lightmapTexture.free();
GL32.glPolygonMode(GL32.GL_FRONT_AND_BACK, GL32.GL_FILL);
if (currentBlend)
GL32.glEnable(GL32.GL_BLEND);
else
GL32.glDisable(GL32.GL_BLEND);
// if this cleanup isn't done MC will crash
// when trying to render its own terrain
// And may causes mod compat issue
GL32.glUseProgram(currentProgram);
GL32.glBindBuffer(GL32.GL_ARRAY_BUFFER, currentVBO);
GL32.glBindVertexArray(currentVAO);
GL32.glActiveTexture(currentActiveText);
GL20.glUseProgram(currentProgram);
// clear the depth buffer so everything is drawn over the LODs
GL32.glClear(GL32.GL_DEPTH_BUFFER_BIT);
// clear the depth buffer so everything is drawn
// over the LODs
GL15.glClear(GL15.GL_DEPTH_BUFFER_BIT);
// end of internal LOD profiling
profiler.pop();
}
/** This is where the actual drawing happens. */
private void drawArrays(int glBufferId, int vertexCount, int posAttrib, int colAttrib)
{
if (glBufferId == 0)
return;
// can be used to check for OpenGL errors
// int error = GL15.glGetError();
// ClientProxy.LOGGER.info(Integer.toHexString(error));
// bind the buffer we are going to draw
GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, glBufferId);
GL30.glBindVertexArray(GLProxy.getInstance().vertexArrayObjectId);
// let OpenGL know how our buffer is set up
int vertexByteCount = (Float.BYTES * 3) + (Byte.BYTES * 4);
GL20.glEnableVertexAttribArray(posAttrib);
GL20.glVertexAttribPointer(posAttrib, 3, GL15.GL_FLOAT, false, vertexByteCount, 0);
GL20.glEnableVertexAttribArray(colAttrib);
GL20.glVertexAttribPointer(colAttrib, 4, GL15.GL_UNSIGNED_BYTE, true, vertexByteCount, Float.BYTES * 3);
// draw the LODs
GL30.glDrawArrays(GL30.GL_TRIANGLES, 0, vertexCount);
}
//=================//
// Setup Functions //
//=================//
/** Setup all render objects - REQUIRES to be in render thread */
private void setup() {
if (isSetupComplete) {
ClientApi.LOGGER.warn("Renderer setup called but it has already completed setup!");
return;
}
if (!GLProxy.hasInstance()) {
ClientApi.LOGGER.warn("Renderer setup called but GLProxy has not yet been setup!");
return;
}
isSetupComplete = true;
shaderProgram = new LodRenderProgram();
}
/** Create all buffers that will be used. */
public void setupBuffers(LodDimension lodDim)
{
lodBufferBuilderFactory.setupBuffers(lodDim);
}
/** Return what fog settings should be used when rendering. */
private LodFogConfig determineFogConfig()
{
LodFogConfig fogConfig = new LodFogConfig();
fogConfig.fogDrawMode = CONFIG.client().graphics().fogQuality().getFogDrawMode();
if (fogConfig.fogDrawMode == FogDrawMode.USE_OPTIFINE_SETTING)
fogConfig.fogDrawMode = REFLECTION_HANDLER.getFogDrawMode();
// how different distances are drawn depends on the quality set
fogConfig.fogDistance = CONFIG.client().graphics().fogQuality().getFogDistance();
// far fog //
if (CONFIG.client().graphics().fogQuality().getFogDistance() == FogDistance.NEAR_AND_FAR)
fogConfig.farFogStart = farPlaneBlockDistance * 1.6f * 0.9f;
else
// for more realistic fog when using FAR
fogConfig.farFogStart = Math.min(vanillaBlockRenderedDistance * 1.5f, farPlaneBlockDistance * 0.9f * 1.6f);
fogConfig.farFogEnd = farPlaneBlockDistance * 1.6f;
// 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.
fogConfig.nearFogEnd = vanillaBlockRenderedDistance * 1.41f;
fogConfig.nearFogStart = vanillaBlockRenderedDistance * 1.6f;
return fogConfig;
}
private Color getFogColor()
{
Color fogColor;
@@ -510,9 +397,12 @@ public class LodRenderer
// translate the camera relative to the regions' center
// (AxisAlignedBoundingBoxes (LODs) use doubles and thus have a higher
// accuracy vs the model view matrix, which only uses floats)
AbstractBlockPosWrapper bufferPos = vbosCenter.getWorldPosition();
double xDiff = projectedView.x - bufferPos.getX();
double zDiff = projectedView.z - bufferPos.getZ();
//int bufferPosX = LevelPosUtil.convert(LodUtil.CHUNK_DETAIL_LEVEL, vbosCenterX, LodUtil.BLOCK_DETAIL_LEVEL);
//int bufferPosZ = LevelPosUtil.convert(LodUtil.CHUNK_DETAIL_LEVEL, vbosCenterZ, LodUtil.BLOCK_DETAIL_LEVEL);
int bufferPosX = vbosCenterX;
int bufferPosZ = vbosCenterZ;
double xDiff = projectedView.x - bufferPosX;
double zDiff = projectedView.z - bufferPosZ;
mcModelViewMatrix.multiplyTranslationMatrix(-xDiff, -projectedView.y, -zDiff);
return mcModelViewMatrix;
@@ -524,9 +414,12 @@ public class LodRenderer
*/
private Vec3f getTranslatedCameraPos()
{
AbstractBlockPosWrapper worldCenter = vbosCenter.getWorldPosition();
//int worldCenterX = LevelPosUtil.convert(LodUtil.CHUNK_DETAIL_LEVEL, vbosCenterX, LodUtil.BLOCK_DETAIL_LEVEL);
//int worldCenterZ = LevelPosUtil.convert(LodUtil.CHUNK_DETAIL_LEVEL, vbosCenterZ, LodUtil.BLOCK_DETAIL_LEVEL);
int worldCenterX = vbosCenterX;
int worldCenterZ = vbosCenterZ;
Vec3d cameraPos = MC_RENDER.getCameraExactPosition();
return new Vec3f((float)cameraPos.x - worldCenter.getX(), (float)cameraPos.y, (float)cameraPos.z - worldCenter.getZ());
return new Vec3f((float)cameraPos.x - worldCenterX, (float)cameraPos.y, (float)cameraPos.z - worldCenterZ);
}
/**
@@ -534,7 +427,7 @@ public class LodRenderer
* @param currentProjectionMatrix this is Minecraft's current projection matrix
* @param vanillaBlockRenderedDistance Minecraft's vanilla far plane distance
*/
private Mat4f createProjectionMatrix(Mat4f currentProjectionMatrix, float vanillaBlockRenderedDistance)
private static Mat4f createProjectionMatrix(Mat4f currentProjectionMatrix, float vanillaBlockRenderedDistance, int farPlaneBlockDistance)
{
//Create a copy of the current matrix, so the current matrix isn't modified.
Mat4f lodProj = currentProjectionMatrix.copy();
@@ -547,38 +440,37 @@ public class LodRenderer
return lodProj;
}
private void applyFog(LodShaderProgram shaderProgram,
LodFogConfig fogSettings, int fogEnabledUniform, int nearFogEnabledUniform, int farFogEnabledUniform,
int nearFogStartUniform, int nearFogEndUniform, int farFogStartUniform, int farFogEndUniform)
{
if (fogSettings.fogDrawMode != FogDrawMode.FOG_DISABLED)
{
shaderProgram.setUniform(fogEnabledUniform, true);
shaderProgram.setUniform(nearFogEnabledUniform, fogSettings.fogDistance != FogDistance.FAR);
shaderProgram.setUniform(farFogEnabledUniform, fogSettings.fogDistance != FogDistance.NEAR);
// near
shaderProgram.setUniform(nearFogStartUniform, fogSettings.nearFogStart);
shaderProgram.setUniform(nearFogEndUniform, fogSettings.nearFogEnd);
// far
shaderProgram.setUniform(farFogStartUniform, fogSettings.farFogStart);
shaderProgram.setUniform(farFogEndUniform, fogSettings.farFogEnd);
//======================//
// Cleanup Functions //
//======================//
/** cleanup and free all render objects. REQUIRES to be in render thread
* (Many objects are Native, outside of JVM, and need manual cleanup) */
private void cleanup() {
if (!isSetupComplete) {
ClientApi.LOGGER.warn("Renderer cleanup called but Renderer has not completed setup!");
return;
}
else
{
shaderProgram.setUniform(fogEnabledUniform, false);
if (!GLProxy.hasInstance()) {
ClientApi.LOGGER.warn("Renderer Cleanup called but the GLProxy has never been inited!");
return;
}
isSetupComplete = false;
ClientApi.LOGGER.info("Renderer Cleanup Started");
shaderProgram.free();
ClientApi.LOGGER.info("Renderer Cleanup Complete");
}
/** Calls the BufferBuilder's destroyBuffers method. */
public void destroyBuffers()
{
lodBufferBuilderFactory.destroyBuffers();
}
//======================//
// Other Misc Functions //
//======================//
/**
* If this is called then the next time "drawLODs" is called
* the LODs will be regenerated; the same as if the player moved.
@@ -602,17 +494,10 @@ public class LodRenderer
VertexBuffersAndOffset result = lodBufferBuilderFactory.getVertexBuffers();
vbos = result.vbos;
storageBufferIds = result.storageBufferIds;
vbosCenter = result.drawableCenterChunkPos;
vbosCenterX = result.drawableCenterBlockPosX;
vbosCenterZ = result.drawableCenterBlockPosZ;
}
/** Calls the BufferBuilder's destroyBuffers method. */
public void destroyBuffers()
{
lodBufferBuilderFactory.destroyBuffers();
}
/** Determines if the LODs should have a fullRegen or partialRegen */
private void determineIfLodsShouldRegenerate(LodDimension lodDim, float partialTicks)
{
@@ -661,52 +546,10 @@ public class LodRenderer
prevPlayerPosTime = newTime;
}
// determine how far the lighting has to
// change in order to rebuild the buffers
// the max brightness is 1 and the minimum is 0.2
float skyBrightness = lodDim.dimension.hasSkyLight() ? MC.getSkyDarken(partialTicks) : 0.2f;
float minLightingDifference;
switch (CONFIG.client().advanced().buffers().getRebuildTimes())
{
case FREQUENT:
minLightingDifference = 0.025f;
break;
case NORMAL:
minLightingDifference = 0.05f;
break;
default:
case RARE:
minLightingDifference = 0.1f;
break;
}
// check if the lighting changed
if (Math.abs(skyBrightness - prevSkyBrightness) > minLightingDifference
// make sure the lighting gets to the max/minimum value
// (just in case the minLightingDifference is too large to notice the change)
|| (skyBrightness == 1.0f && prevSkyBrightness != 1.0f) // noon
|| (skyBrightness == 0.2f && prevSkyBrightness != 0.2f) // midnight
|| MC_RENDER.getGamma() != prevBrightness)
{
fullRegen = true;
prevBrightness = MC_RENDER.getGamma();
prevSkyBrightness = skyBrightness;
}
/*if (lightMap != lastLightMap)
{
fullRegen = true;
lastLightMap = lightMap;
}*/
//================//
// partial regens //
//================//
// check if the vanilla rendered chunks changed
if (newTime - prevVanillaChunkTime > CONFIG.client().advanced().buffers().getRebuildTimes().renderedChunkTimeout)
{
@@ -718,7 +561,6 @@ public class LodRenderer
prevVanillaChunkTime = newTime;
}
// check if there is any newly generated terrain to show
if (newTime - prevChunkTime > CONFIG.client().advanced().buffers().getRebuildTimes().chunkChangeTimeout)
{
@@ -730,8 +572,6 @@ public class LodRenderer
prevChunkTime = newTime;
}
//==============//
// LOD skipping //
//==============//
@@ -773,5 +613,5 @@ public class LodRenderer
vanillaRenderedChunks = new boolean[vanillaRenderedChunksWidth][vanillaRenderedChunksWidth];
}
}
@@ -85,11 +85,11 @@ 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);
@@ -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 its 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,203 +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.awt.Color;
import java.nio.FloatBuffer;
import org.lwjgl.opengl.GL20;
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 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 a 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);
}
public void setUniform(int location, boolean value)
{
GL20.glUniform1i(location, value ? 1 : 0);
}
public void setUniform(int location, int value)
{
GL20.glUniform1i(location, value);
}
public void setUniform(int location, float value)
{
GL20.glUniform1f(location, value);
}
public void setUniform(int location, Vec3f value)
{
GL20.glUniform3f(location, value.x, value.y, value.z);
}
public void setUniform(int location, Vec3d value)
{
GL20.glUniform3f(location, (float) value.x, (float) value.y, (float) value.z);
}
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);
}
}
/** Converts the color's RGBA values into values between 0 and 1. */
public void setUniform(int location, Color value)
{
GL20.glUniform4f(location, value.getRed() / 256.0f, value.getGreen() / 256.0f, value.getBlue() / 256.0f, value.getAlpha() / 256.0f);
}
}
@@ -23,6 +23,7 @@ import static com.seibel.lod.core.builders.bufferBuilding.LodBufferBuilderFactor
import com.seibel.lod.core.enums.config.DistanceGenerationMode;
/**
*
* @author Leonardo Amato
@@ -102,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)
{
@@ -140,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);
@@ -211,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) */
@@ -268,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((WORLD_HEIGHT / 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;
@@ -288,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;
}
}
@@ -413,7 +448,8 @@ public class DataPointUtil
int j = 0;
while (count > maxVerticalData)
{
ii = WORLD_HEIGHT - VERTICAL_OFFSET;
limited = true;
ii = WORLD_HEIGHT;
for (i = 0; i < count - 1; i++)
{
if (heightAndDepth[i * 2 + 1] - heightAndDepth[(i + 1) * 2] <= ii)
@@ -432,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,7 +19,6 @@
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;
@@ -39,7 +38,7 @@ public class DetailDistanceUtil
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;
@@ -44,7 +45,7 @@ import com.seibel.lod.core.wrapperInterfaces.world.IWorldWrapper;
* 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
{
@@ -52,6 +53,7 @@ public class LodUtil
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;
@@ -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)
{
@@ -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,13 +24,14 @@ 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
{
@@ -39,10 +40,21 @@ public interface IWrapperFactory
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);
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;
}
}
@@ -31,6 +31,8 @@ public interface IBlockColorWrapper
boolean hasColor();
String getName();
int getColor();
@@ -32,21 +32,27 @@ public interface IChunkWrapper
{
int getHeight();
boolean isPositionInWater(AbstractBlockPosWrapper blockPos);
boolean isPositionInWater(int x, int y, int z);
int getHeightMapValue(int xRel, int zRel);
IBiomeWrapper 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);
IBlockColorWrapper getBlockColorWrapper(AbstractBlockPosWrapper blockPos);
IBlockShapeWrapper getBlockShapeWrapper(AbstractBlockPosWrapper blockPos);
AbstractChunkPosWrapper getPos();
int getChunkPosX();
int getChunkPosZ();
int getRegionPosX();
int getRegionPosZ();
int getMaxY(int x, int z);
int getMaxX();
int getMaxZ();
int getMinX();
int getMinZ();
boolean isLightCorrect();
boolean isWaterLogged(AbstractBlockPosWrapper blockPos);
boolean isWaterLogged(int x, int y, int z);
int getEmittedBrightness(AbstractBlockPosWrapper blockPos);
int getEmittedBrightness(int x, int y, int z);
}
@@ -26,8 +26,6 @@ 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;
@@ -36,6 +34,7 @@ import com.seibel.lod.core.enums.rendering.FogDistance;
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
@@ -43,7 +42,7 @@ import com.seibel.lod.core.util.LodUtil;
* the options that should be implemented in a configWrapperSingleton.
*
* @author James Seibel
* @version 12-1-2021
* @version 12-14-2021
*/
public interface ILodConfigWrapperSingleton
{
@@ -76,6 +75,7 @@ public interface ILodConfigWrapperSingleton
HorizontalResolution DRAW_RESOLUTION_DEFAULT = HorizontalResolution.BLOCK;
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"
@@ -111,29 +111,22 @@ public interface ILodConfigWrapperSingleton
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 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. \n"
+ "\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"
+ "\n"
+ " Lowest Quality: " + HorizontalScale.LOW
+ " Highest Quality: " + HorizontalScale.HIGH;
HorizontalScale getHorizontalScale();
void setHorizontalScale(HorizontalScale newHorizontalScale);
+ " 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"
+ " This indicates how much farther away each drop in quality is. \n"
+ "\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"
+ " " + 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;
@@ -190,18 +183,6 @@ public interface ILodConfigWrapperSingleton
{
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";
LodTemplate getLodTemplate();
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"
@@ -232,12 +213,12 @@ public interface ILodConfigWrapperSingleton
+ " 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 + " will be used depending on the dimension. \n"
+ " " + VanillaOverdraw.ALWAYS + ": LODs will render on all vanilla chunks preventing holes in the world. \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();
@@ -289,9 +270,16 @@ public interface ILodConfigWrapperSingleton
void setGenerationPriority(GenerationPriority newGenerationPriority);
DistanceGenerationMode DISTANCE_GENERATION_MODE_DEFAULT = DistanceGenerationMode.SURFACE;
String DISTANCE_GENERATION_MODE_DESC = ""
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"
+ " No CPU usage - Fastest \n"
@@ -300,35 +288,36 @@ public interface ILodConfigWrapperSingleton
+ " 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"
+ " " + multiOrSingleThreadText(versionConstants, DistanceGenerationMode.BIOME_ONLY) + " - Fastest (2-5 ms) \n"
+ "\n"
+ " " + DistanceGenerationMode.BIOME_ONLY_SIMULATE_HEIGHT + " \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"
+ " " + 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"
+ " " + 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"
+ " " + 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"
+ " " + 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);
@@ -367,6 +356,13 @@ public interface ILodConfigWrapperSingleton
+ " 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";
};
}
@@ -457,7 +453,7 @@ public interface ILodConfigWrapperSingleton
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.";
boolean getDebugKeybindingsEnabled();
void setDebugKeybindingsEnabled(boolean newEnableDebugKeybindings);
@@ -479,7 +475,7 @@ public interface ILodConfigWrapperSingleton
+ " " + 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 rending if in GPU memory, slow if 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"
@@ -490,20 +486,19 @@ public interface ILodConfigWrapperSingleton
GpuUploadMethod getGpuUploadMethod();
void setGpuUploadMethod(GpuUploadMethod newGpuUploadMethod);
MinDefaultMax<Integer> GPU_UPLOAD_TIMEOUT_IN_MILLISECONDS_DEFAULT = new MinDefaultMax<Integer>(0, 0, 5000);
String GPU_UPLOAD_TIMEOUT_IN_MILLISECONDS_DESC = ""
+ " How long should we wait before uploading a buffer to the GPU? \n"
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. \n"
+ " transition and load slower. Change this to [0] for no timeout.\n"
+ "\n"
+ " NOTE:\n"
+ " This should be a last resort option."
+ " Only change this from [0], after you have tried all of the \n"
+ " \"GPU Upload methods\" and determined even the best stutters with yoru hardware.";
int getGpuUploadTimeoutInMilliseconds();
void setGpuUploadTimeoutInMilliseconds(int newTimeoutInMilliseconds);
+ " 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 vertex buffers (geometry) be rebuilt and sent to the GPU? \n"
@@ -25,6 +25,8 @@ 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;
@@ -33,7 +35,7 @@ import com.seibel.lod.core.wrapperInterfaces.chunk.AbstractChunkPosWrapper;
* rendering in Minecraft.
*
* @author James Seibel
* @version 11-26-2021
* @version 12-14-2021
*/
public interface IMinecraftRenderWrapper
{
@@ -64,6 +66,69 @@ public interface IMinecraftRenderWrapper
/**
* This method returns the ChunkPos of all chunks that Minecraft
* is going to render this frame.
* <br>
* If not implemented this calls {@link #getMaximumRenderedChunks()}.
*/
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
{
@@ -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
*/
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
*/
Color getColorFromLightMap(int u, int v);
Color getColorFromLightMap(int blockLight, int skyLight);
@@ -28,6 +28,8 @@ public interface IBiomeWrapper
/** Returns a color int for the given biome. */
int getColorForBiome(int x, int z);
String getName();
int getGrassTint(int x, int z);
int getFolliageTint();
@@ -36,11 +36,9 @@ public interface IWorldWrapper
WorldType getWorldType();
int getBlockLight(AbstractBlockPosWrapper blockPos);
int getBlockLight(int x, int y, int z);
int getSkyLight(AbstractBlockPosWrapper blockPos);
IBiomeWrapper getBiome(AbstractBlockPosWrapper blockPos);
int getSkyLight(int x, int y, int z);
boolean hasCeiling();
@@ -54,6 +52,11 @@ public interface IWorldWrapper
int getSeaLevel();
default short getMinHeight()
{
return 0;
}
/** @throws UnsupportedOperationException if the WorldWrapper isn't for a ServerWorld */
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();
}
+1 -2
View File
@@ -2,13 +2,12 @@
in vec4 vertexColor;
in vec4 vertexWorldPos;
//in vec2 textureCoord;
out vec4 fragColor;
//uniform sampler2D texImage;
uniform vec3 cameraPos;
uniform bool fogEnabled;
+20 -4
View File
@@ -2,30 +2,46 @@
in vec3 vPosition;
in vec4 color;
in float blockSkyLight;
in float blockLight;
out vec4 vertexColor;
out vec4 vertexWorldPos;
//out vec2 textureCoord;
out float depth;
uniform mat4 modelViewMatrix;
uniform mat4 projectionMatrix;
uniform int worldSkyLight;
uniform sampler2D lightMap;
/**
* Vertex Shader
*
* author: James Seibel
* version: 11-26-2021
* version: 12-8-2021
*/
void main()
{
// just skylight
// good for sanity checks; but will cause OpenGL errors since we are binding unused data
// vertexColor = vec4(color.xyz * worldSkyLight / 16.0, color.w);
float blockLightTex = blockLight / 16.0;
float skyLightTex = blockSkyLight / 16.0;
// we don't really need alpha in the lightmap
// vertexColor = color * vec4(texture(lightMap, vec2(skyLightTex, blockLightTex)).xyz, 1);
vertexColor = color * texture(lightMap, vec2(skyLightTex, blockLightTex));
// TODO: add a simple white texture to support Optifine shaders
//textureCoord = textureCoord;
vertexColor = color;
vertexWorldPos = vec4(vPosition, 1);
// the vPosition needs to be converted to a vec4 so it can be multiplied