Compare commits

...

332 Commits

Author SHA1 Message Date
coolGi b1cbc99a40 Removed ununsed injector 2023-06-17 13:00:39 +09:30
coolGi 8e4240ef9a Updated mod version 2023-06-08 17:27:49 +09:30
coolGi 131ba164cf Upped version number 2023-03-15 11:41:26 +00:00
coolGi acc681c535 Fixed up the build, buildsystem and the transparent lod button. 2023-03-15 21:45:57 +10:30
coolGi 1a3f9b82c6 Hack to make core shadow and relocate stuff 2022-12-20 18:52:38 +10:30
coolGi ddd6b3b817 Updated version number 2022-12-20 17:09:15 +10:30
James Seibel 5f116f0ea7 update the version number 1.6.8a -> 1.6.9a 2022-08-07 14:25:41 -05:00
James Seibel f00a1db9c2 update the version number 1.6.7a -> 1.6.8a 2022-07-31 12:06:54 -05:00
coolGi 9557912101 Changed version numbers to 1.6.7 2022-07-12 23:41:52 +09:30
James Seibel ca2b09c2c8 Update the version number to 1.6.6a-dev 2022-07-09 19:48:50 -05:00
cola98765 e1b2c62854 fixed int overflow with pow2 2022-06-23 14:33:06 +02:00
coolGi c3bb079b42 Added version to f3 screen and fixed some stuff in readme 2022-06-13 14:22:11 +09:30
coolGi 9670cbbb74 Updated version number, licence & readme 2022-06-13 13:14:25 +09:30
TomTheFurry 3ca0757358 Fixs: DimFinder nullPtr error on saving PlayerData before player loads in 2022-06-13 00:14:24 +08:00
TomTheFurry 4f2076b48e Fixs: Config Enum Error not caught, GLLogger not disabled, DimFinder Move crash on colliding with existing files, slience the rendering concurrency error 2022-06-13 00:02:42 +08:00
TomTheFurry e91bcb7964 Dummy commit to get git reconise a new core branch. 2022-06-12 23:37:26 +08:00
James Seibel f0f18993d1 Update the mod version in ModInfo 2022-04-22 21:17:29 -05:00
James Seibel 026816053c Remove the experimental build warning 2022-04-22 21:08:04 -05:00
James Seibel 0d3c005e0f Slightly improve the wording in en_us 2022-04-22 20:38:08 -05:00
TomTheFurry a93c04a654 Updated some config defaults and min/maxs 2022-04-22 21:11:35 +08:00
TomTheFurry 1601f0d19f Add the cave culling switch logic 2022-04-22 21:07:10 +08:00
TomTheFurry 6ad6ecc731 Ops. 2022-04-18 16:11:38 +08:00
TomTheFurry 29fab65ee9 Improve overlapped quads handling + fix minLevel being used to clamp getMaxVerticalData(), causing invalid sized containers being added to incorrect detail level slot in a region. 2022-04-18 15:57:14 +08:00
TomTheFurry a3022d2c64 Fix critical mem leak in BlockDetail + Add spaced out worldGenThread + make worldGenUpdate update once per 10 ticks + Semi-impl proper EarthCurveRatio limits + make worldGenThreads terminate faster + impl temp bypass to stop deadlocks on BufferFactory destroy() + fix chat logging messages with Throwable twice 2022-04-17 18:07:50 +08:00
TomTheFurry d2fef22719 Add config settings for earthCurve thingy 2022-04-15 19:15:55 +08:00
TomTheFurry 1cb27f2f78 Fix dumb mistake + change a bit of stuff 2022-04-15 18:46:38 +08:00
TomTheFurry 892570a442 Add curve shader + fix/change ibo upload 2022-04-15 18:25:58 +08:00
TomTheFurry 705060fa97 Fix multiple little bugs 2022-04-15 16:48:49 +08:00
James Seibel b98082980b Merge 2022-04-14 19:37:35 -05:00
James Seibel d3b1635538 Reformat LodFogConfig 2022-04-14 19:36:19 -05:00
TomTheFurry 73f9edc091 Fix GpuUploadMethod that use mapping to use DynamicDraw instead of StaticDraw 2022-04-14 18:17:39 +08:00
TomTheFurry 7f6d123292 '*Fixed*' the T-junction pixel issue... kinda. 2022-04-14 17:00:21 +08:00
TomTheFurry ca64027b0c Rework cortex's ibo, and the buffer objects. 2022-04-14 15:42:37 +08:00
James Seibel 175c48a49e auto-format 2022-04-13 21:57:48 -05:00
James Seibel 6f75197085 auto-format 2022-04-13 21:46:31 -05:00
James Seibel c1d6541729 auto-format and fix version date formats 2022-04-13 21:23:05 -05:00
James Seibel 0905f60034 Merge branch 'MCRcortex/distant-horizons-core-IBO' 2022-04-13 21:07:50 -05:00
James Seibel f012ac44a7 Reformat comment and optimize the bind/unbind calls 2022-04-13 21:06:47 -05:00
mcrcortex c19e2613b4 Swapped GL 4.3 direct state accesses with GL 3.1 core features 2022-04-13 23:24:36 +10:00
mcrcortex 351decad34 Removed accidental imports 2022-04-13 23:18:13 +10:00
mcrcortex d22057e1b1 Merge branch 'main' of https://gitlab.com/jeseibel/distant-horizons-core into IBO 2022-04-13 23:12:04 +10:00
mcrcortex a7e174cf74 Changed to using IBO drawElements for quads 2022-04-13 23:07:02 +10:00
TomTheFurry d88e0f5e9c Make far fog start at vanilla render distance + make fog multiply caps with inverse near fog if near fog is disabled 2022-04-13 15:00:29 +08:00
TomTheFurry 743e504ccc Fix Sodium non-fabulious causing lightmap flicker + add more log for load/unload world + no longer unloading world 3 times on exit due to sub-dim stuff 2022-04-13 14:14:02 +08:00
TomTheFurry 633b229d2e More logging & fix 1.16 getminbuildheight & improve blockdetailmap caching for waterlogged blocks 2022-04-13 13:38:36 +08:00
TomTheFurry ee803e10b5 Ops. Shouldn't comment out this two lines 2022-04-13 12:36:29 +08:00
TomTheFurry 3df603a0e2 Make the glProxy OpenGL version logging a bit better 2022-04-13 12:10:11 +08:00
James Seibel ce7217bad5 update the version number in ModInfo 2022-04-11 07:15:51 -05:00
James Seibel 3e42541fe9 Refactor LodQuadBuilder 2022-04-09 21:28:30 -05:00
James Seibel ff1c6cad2a auto-format 2022-04-09 11:08:15 -05:00
James Seibel 2bba421a5b auto-format 2022-04-09 10:54:13 -05:00
TomTheFurry 1fd1082a80 Add back 1.16.5 TerraForged basic support 2022-04-09 15:47:47 +08:00
TomTheFurry 25d2b9df7b Fix lightmap, again. 2022-04-09 15:21:59 +08:00
James Seibel 7eb0d62f75 Fix a typo in the license header 2022-04-06 22:28:34 -05:00
James Seibel 4fc816838d Update the license years 2022-04-06 22:24:32 -05:00
James Seibel 22bc6b5c9a Add the copyright header to files missing it 2022-04-06 22:21:41 -05:00
James Seibel 2e282433cc replace unnecessary error logs with info logs 2022-04-06 21:08:57 -05:00
James Seibel e76735a509 re-add the dev build warning 2022-04-06 21:04:44 -05:00
James Seibel 29aa95437e Replace the config button icon
Thanks Sjmarf!
2022-04-04 22:06:56 -05:00
coolGi2007 8296b006b4 Fixed stuff and made core use mc 1.16 version of log4j 2022-04-04 18:49:45 +09:30
TomTheFurry 02b0637adc Maybe fixed the light again? 2022-04-04 12:01:00 +08:00
TomTheFurry 9f12cc9528 Fix dumb stuff costing performance on world gen 2022-04-03 16:01:53 +08:00
TomTheFurry 0981870ea3 Fix up big blocks missing side faces 2022-04-03 13:06:08 +08:00
TomTheFurry 347383f440 Disabled framebuffer fix for now making optifine shaders to work again 2022-04-02 22:24:16 +08:00
TomTheFurry 4209937c35 Fix coolGi's java-8 port 2022-04-02 22:18:40 +08:00
TomTheFurry f877030eca Make lightmap fix works with Optifine shaders? 2022-04-02 21:09:52 +08:00
coolGi2007 a12f2e9e01 Made lots of things use java 8 and added access wideners for different versions 2022-04-02 19:29:01 +10:30
TomTheFurry 6d12acc30a Fix flickering lightmap by redo lightmap system 2022-04-02 15:25:10 +08:00
TomTheFurry 4bac38c99f MacOS Patch!??????? 2022-04-01 19:36:50 +08:00
James Seibel 3259f502e8 Fix the default folder selection if sub-dimensions are disabled 2022-03-31 21:23:31 -05:00
James Seibel aa3cde0537 Fix invalid files that use the server port 2022-03-30 21:18:53 -05:00
James Seibel e3018485ed Add additional logging to getFileBasePath 2022-03-30 19:56:58 -05:00
TomTheFurry 5a58fa92b3 Add debugging info for lodWorld 2022-03-30 19:52:27 +08:00
TomTheFurry 8a76c8cadc Make DataPointUtil's WORLD HEIGHT a final again 2022-03-30 18:56:10 +08:00
TomTheFurry 7f9b035665 Fix race & stuck playerData in sub-dim, add dataPoint verify 2022-03-30 18:38:48 +08:00
cola98765 368238821b change XZ setting 3 -> 1 made it easier to change it with static at the start of the file 2022-03-30 11:55:01 +02:00
James Seibel 4c984c5c10 Close #140 (no rendering if world named "No World Loaded" ) 2022-03-29 20:48:59 -05:00
TomTheFurry 7c935bb581 I think test renderer is now more correct? 2022-03-29 13:09:29 +08:00
CodeF53 3e825c11c4 small nitpicky changes to Lang Files 2022-03-28 19:24:28 -06:00
TomTheFurry 11982d701c Add RendererType & Debug Renderer + fix Logger bug 2022-03-28 17:04:06 +08:00
James Seibel 21d8f1124c Fix a null pointer error when moving empty folders 2022-03-27 21:49:20 -05:00
James Seibel b4009336a3 Fix old folders not moving if sub-dims are disabled 2022-03-27 00:09:32 -05:00
James Seibel a2d2e5d87b Make multi-Dim similarity = 0 disable the new system
By default the old system of 1 world per dimension is used
2022-03-26 23:15:47 -05:00
James Seibel f97815cac7 Update a log message in Sub-Dim 2022-03-26 21:34:36 -05:00
James Seibel 53c518b307 Fix incorrect files/folders crashing the sub-dim system 2022-03-26 21:27:16 -05:00
James Seibel 03f5a086f0 Automatically move old files to the new sub-dim system 2022-03-26 20:50:57 -05:00
James Seibel bc72142659 Merge for SubDim 2022-03-26 11:39:17 -05:00
TomTheFurry de51efc866 Fix logger bug, vertQual.next/pre missing ULTRA, subdim array compare 2022-03-26 23:31:04 +08:00
James Seibel 0658106822 Partially fix SubDimensionFinder 2022-03-26 10:20:01 -05:00
TomTheFurry 5178aa7def Overhaul Logging system to support runtime config switching 2022-03-26 18:55:02 +08:00
James Seibel a80f69f522 Use a SubDimCompare object to compare subDims
This still needs cleaning, but it is going in the right direction.
2022-03-24 22:56:06 -05:00
James Seibel 665a5a8bee Lower the subDim vertical quality back to LOW 2022-03-23 21:14:40 -05:00
James Seibel 8a851a70c7 rename LodDimFileHelper -> LodSubDimFolderFinder 2022-03-23 20:59:48 -05:00
James Seibel 621bf7341d Improve logging and potentially fix a few substring crashes 2022-03-23 20:58:50 -05:00
James Seibel 4820c11f8e Move a few objects to the objects.opengl package 2022-03-22 21:51:26 -05:00
James Seibel ebc4ee2ab7 auto-format before editing 2022-03-22 21:50:22 -05:00
TomTheFurry 95c5854d8f Add CaveCullingHeight + 'Fun' mode into config 2022-03-22 18:23:52 +08:00
CodeF53 fe798bf90c Skylight culling below Y
https://canary.discord.com/channels/881614130614767666/881748253228531772/955696258754961429
2022-03-22 00:21:49 -06:00
TomTheFurry 4a2a6fb4bd Fix 0 size quad bug + improve black face filling 2022-03-22 13:05:22 +08:00
James Seibel ef80271f09 Fix an incorrect array index 2022-03-21 21:21:09 -05:00
tom lee 6a5263a8f4 Add overdraw offset setting 2022-03-21 17:29:10 +08:00
tom lee 260ba1a70e Fix color mixing issue 2022-03-21 16:55:36 +08:00
tom lee ca625f9f8b Remove Manifold from core. Remove use of awt.Colors.
Removing awt.Colors due to it being just slow. And unneeded to import
entire awt lib just because of Color.

Removed Manifold from core due to Manifold java compiler has quite some
bugs, and it is causing crashes on inferring a generic class args on
inline Iterator class def. Core arn't needing those preprocessor
features anyways.
2022-03-21 15:13:26 +08:00
tom lee 4f2bf9b834 Add overdraw offset 2022-03-20 23:18:08 +08:00
tom lee 467f4a260f Forgot to turn back on the greedy meshing 2022-03-20 16:30:28 +08:00
tom lee 21253d1308 Add Cave Culling setting + cleanup + fix leo's adjData 2022-03-20 16:26:50 +08:00
tom lee 8f534fb51c Fix/Improve Near Clip Plane + add pow2 function 2022-03-20 13:41:28 +08:00
James Seibel 6cd0281d0e Merge in multi-dim support 2022-03-19 12:38:42 -05:00
James Seibel df73d1d275 Re-Add the brightness/saturation config 2022-03-19 11:00:28 -05:00
tom lee 804738cfe5 Accedentally moved a folder. Ops 2022-03-19 22:33:36 +08:00
tom lee 254d908807 Nuke old WorldGen, reorganize and improve all GridList util 2022-03-19 22:30:27 +08:00
James Seibel 9fa7fa843d Merge branch 'main' of https://gitlab.com/jeseibel/distant-horizons-core 2022-03-18 23:56:29 -05:00
James Seibel 82065cd1ce Add a config for the MultiDimension Similarity 2022-03-18 23:56:25 -05:00
James Seibel d0da822813 make the dimFileHelper run async 2022-03-18 22:01:29 -05:00
James Seibel 595aa79eb6 Fix crashing in singleplayer 2022-03-18 22:01:05 -05:00
James Seibel 8cdc0fc830 Remove a few unneeded imports 2022-03-17 23:08:52 -05:00
James Seibel c869047b30 Closes #134 (Add multi dimension support)
I still need to add a config and multithreading (to prevent stuttering when changing dimensions).
2022-03-17 23:08:42 -05:00
TomTheFurry 2907c8e3e7 Fixed a deadlock on shutting down BufferBuilders 2022-03-16 18:52:55 +08:00
TomTheFurry 4097cf7619 ADVANCED FOGGGGGGGGGGGGGGGG~~~~~~~~~~~~~~~~ 2022-03-16 18:52:55 +08:00
TomTheFurry cf519c02be Pushed the stuff leetom did on my macbook 2022-03-15 21:17:07 +10:30
TomTheFurry 4f563c2be5 Add proper multi FrameBuffer support(And fix MacOS?) 2022-03-15 16:38:39 +08:00
TomTheFurry e8de59a226 Fixup MixinUtilBackgroudThread+more render log 2022-03-15 18:48:27 +10:30
TomTheFurry 323da0b12c Improve GLMessage Parser 2022-03-15 12:37:51 +08:00
TomTheFurry 1c0e7839c0 Add Advanced Fog config entries. Actual impl is a todo. 2022-03-15 00:00:35 +08:00
TomTheFurry d2056d824f Slight cleanup of lodRenderer 2022-03-14 16:22:19 +08:00
TomTheFurry 7da2b90611 Fix hasCliffFace() not crossing chunk boundaries.
Also temp added cave culling. Need logic to provide better cave culling.
2022-03-14 15:10:04 +08:00
TomTheFurry f1eb06bbb1 Fix nullptr exception on calling clear(d) with d being null (Thx @HyperSoop) 2022-03-14 14:36:42 +08:00
James Seibel 78a1cc3330 Auto Format 2022-03-13 21:08:29 -05:00
TomTheFurry fbd8f48433 Improved getMaximumRenderedChunk and fix GLMessage on forge 2022-03-13 22:57:06 +08:00
TomTheFurry 49cc46dc25 Update Config defaults 2022-03-13 16:15:28 +08:00
TomTheFurry 1c7d87b9d3 Add new config: Biome Blending 2022-03-13 15:39:34 +08:00
James Seibel bca2b61800 Close #207 (re-add brightness/contrast configs) 2022-03-09 23:06:15 -06:00
TomTheFurry c9cabd8a32 Rework BlockDetail and stuff. 2022-03-08 23:26:41 +08:00
TomTheFurry d731424e93 Fix it so I think java8 now works...? (Javadoc won't tell me versions) 2022-03-08 16:04:38 +08:00
James Seibel 510058b7df Closes #217 (Change Server Folder Name)
Adds multiple options for formatting the server folder name.
If LODs have already been generated with a previous setting the files will have to be transferred to the new folder.
2022-03-07 22:15:52 -06:00
James Seibel 05e70416b7 Manual refactoring 2022-03-07 06:34:06 -06:00
James Seibel d5eb094256 Replace constant values with their enum representations 2022-03-07 06:28:48 -06:00
James Seibel b7aa341961 Auto Format 2022-03-07 06:28:23 -06:00
James Seibel 3f091af3f8 Rename MinecraftWrapper -> MinecraftClientWrapper 2022-03-05 18:26:47 -06:00
James Seibel 566eb3651f remove WorldWrapper.isEmpty()
It wasn't used and there was a comment saying not to use it
2022-03-05 18:14:03 -06:00
James Seibel 58392a8ac1 Update the DependencyHandler to support circular references 2022-03-05 17:34:19 -06:00
James Seibel 2149da59df Replace string.strip() with string.trim() 2022-03-03 19:34:09 -06:00
coolGi2007 e41b19ba2e Fixed 1.16 (tough someone needs to fix core/objects/opengl/RenderRegion to work with java8) 2022-03-03 17:39:14 +10:30
James Seibel d84d535896 Refactor the dependency injectors 2022-03-01 21:19:45 -06:00
tom lee e6f8c0d65f Cleanup Error logging + Polish OpenGL Error/Warning/Message handling 2022-02-27 17:01:03 +08:00
tom lee 7db3789bc2 Fix Overdraw Circles & Void chunk error spam due to genMode+1 2022-02-26 16:15:11 +08:00
tom lee 8d18ba861e Fix missing move. Also moved needRegenBuffer flag to RenderRegion 2022-02-25 15:13:39 +08:00
tom lee 6e63e39cd8 Patch the merge commit cause I used wrong files and also -1 level bug 2022-02-24 22:27:21 +08:00
tom lee 1dcc973a24 Overhaul Rendering Buffer Management
+ Merging with Leo's fix to adjData and stuff
2022-02-24 21:59:20 +08:00
Morippi 7ad2e82646 Added comments 2022-02-23 18:26:35 +01:00
Morippi f32dfbcbe7 Added comments 2022-02-23 18:24:59 +01:00
Morippi 18b7835c4c Small fix to the region circle border 2022-02-23 17:16:05 +01:00
Morippi c5f23dabb2 Removed border from details circles and regions 2022-02-23 16:38:19 +01:00
tom lee d6ba5205ad Too many changes. See details:
- Fix new block color with tint system slowdown
- Fix leaves block color
- Fix rgba not translating to rgb properly
  - Which changed water color to be darker
- Optimized quad merging to be faster
- Fix All types of gray color texture issue
- Fix Textures that rely on BlockState
- Fix All types of tint
- (?)Create Model perhaps no longer purple
- Fixed LodBufferBuilder always in single thread mode
2022-02-22 23:10:49 +08:00
Morippi 6948e4e437 Alpha now uses max value in merge and not average value 2022-02-22 11:54:24 +01:00
tom lee d39912e235 Fix init error by moving bufferFactory to ClientApi 2022-02-22 11:35:42 +08:00
Ran fa3d07edb5 Put logger in ApiShared.java 2022-02-21 17:34:51 +06:00
Ran bf4eb954d3 Merge remote-tracking branch 'origin/main' into main
# Conflicts:
#	src/main/java/com/seibel/lod/core/builders/lodBuilding/LodBuilder.java
2022-02-21 17:29:40 +06:00
Ran 1ddf2091b1 Put logger in ApiShared.java 2022-02-21 17:27:53 +06:00
tom lee 4708da8e8f Rework block color/shape stuff 2022-02-21 19:19:44 +08:00
Ran 59fa6eed73 Add protocol version 2022-02-21 09:21:27 +00:00
coolGi2007 0edca2402d Renamed RunMain to JarMain 2022-02-20 22:23:03 +00:00
cola98765 59bf66ec58 wireframe again hides the culled faces, to maintain parity as to what is actually rendered in different modes. 2022-02-20 14:09:03 +01:00
coolGi2007 a14046ef88 Added something so you can run the jar(dosnt do anything yet) 2022-02-20 11:43:41 +00:00
cola98765 cff317937f removed dummy enum. made genmode a byte where it's not an enum 2022-02-20 11:50:58 +01:00
cola98765 cead0346ad probable fix 2022-02-20 11:03:29 +01:00
cola98765 f3f3c3d54d gen mode + 1 of what was before 2022-02-20 10:54:17 +01:00
tom lee 9765da97a6 Reorder lodData, now this is the new Version 9 2022-02-20 17:34:24 +08:00
tom lee 57f5ad5ea0 Fix fog using vert instead of per frag. Oh and impl greedy meshing 2022-02-20 16:11:31 +08:00
Morippi 3cd71c1c69 Fixed down (and Up even if not visible) shade 2022-02-20 00:48:55 +01:00
cola98765 d79c873145 reorganised datapoint. added full 4096 world height support 2022-02-20 00:21:25 +01:00
tom lee 401cbbd8eb Fix resource leaks from missing close() on stream (Thx Boston!!!!!) 2022-02-20 01:00:32 +08:00
tom lee a1652fe68a Add and improve buffer upload methods 2022-02-20 00:42:27 +08:00
tom lee c450f5b960 I think render fps pref improved??? 2022-02-19 22:14:24 +08:00
coolGi2007 bcf8237131 Removed new config stuff 2022-02-19 08:14:49 +00:00
tom lee 3e7fed7ad4 Start rework to allow individual render region swapping 2022-02-18 14:56:54 +08:00
James Seibel fd81a8e067 Update the version number to 1.6.2a 2022-02-17 20:11:00 -06:00
cola98765 891cfdf4a9 added squared color averaging to merge 2022-02-17 18:05:45 +01:00
TomTheFurry b1795079b8 Merge branch 'render_test' into 'main'
Merge the Render test branch with improvements

See merge request jeseibel/distant-horizons-core!4
2022-02-17 14:20:12 +00:00
TomTheFurry 8e347959e1 Merge the Render test branch with improvements 2022-02-17 14:20:12 +00:00
cola98765 acd5116223 close #188; fix lang parts in #189 2022-02-15 11:21:22 +01:00
James Seibel 2ba7c6be6e Merge branch 'main' of https://gitlab.com/jeseibel/distant-horizons-core 2022-02-13 07:37:19 -06:00
coolGi2007 77492a8abf Updated config and lang 2022-02-13 03:32:51 +00:00
James Seibel a0c7ebedcf Prepare for 1.6.1a 2022-02-12 12:01:20 -06:00
tom lee ae752c6f63 Fixed it so it works with Java 8 2022-02-12 22:47:49 +08:00
tom lee fbbccf4739 Add a DummyRunExecutorService 2022-02-12 21:57:54 +08:00
coolGi2007 cf1c371311 Re added some stuff so the gui still works 2022-02-12 10:24:00 +00:00
coolGi2007 87ff857b59 Added some stuff for new config (not redy yet so dont use) 2022-02-12 09:24:52 +00:00
James Seibel 50819c30da Update the version number to 1.6.1a-pre and re-enable dev code 2022-02-11 22:07:23 -06:00
James Seibel 67f12c136c I accidently removed the "a" 2022-02-11 20:00:32 -06:00
James Seibel b35db97abc Change the version number and comment out the debug code for a1.6.0 2022-02-11 19:58:18 -06:00
coolGi2007 241447d55a Updated lang 2022-02-11 09:29:08 +00:00
coolGi2007 5d72d321db Removed 1 line from the lang 2022-02-11 05:36:28 +00:00
tom lee 595b8ecbb5 Fixed hash bug for ChunkPos & incorrect shink causing overdraw issue 2022-02-11 13:34:04 +08:00
tom lee f422df6280 Cleanup debug logging and get rid of unused functions in vertContainer 2022-02-11 12:39:40 +08:00
coolGi2007 14d3418651 Removed cloud stuff (forgot to remove before) 2022-02-11 04:12:01 +00:00
James Seibel f7da53622c Slightly improve the tooltips for the config GUI 2022-02-10 22:05:58 -06:00
Morippi e5a636fccb Added Ultra setting to vertical quality and changed the others 2022-02-10 15:10:41 +01:00
tom lee 080d33baf1 Changed defaults and removed unneeded configs 2022-02-10 21:40:15 +08:00
tom lee d8c082ba42 Fixed stuck futures if BufferBuild throws exception in the Runnable 2022-02-10 17:05:48 +08:00
tom lee b169e246b4 Make BufferUpload & BufferBuild use CompletableFuture Pipeline 2022-02-10 16:13:49 +08:00
tom lee 07d4050502 Fix many stuff. See list below:
1. Fixed seeing previous dim's lod after going though a portal
2. Fixed changing VertQual live causes many issues (now force a reload)
3. Fixed loading old lod files with smaller than expected vert data
   causing the data container to be incorrectly sized
4. Improved BufferBuilder to use less RAM and cleanup
5. Shink BufferBuilder on calling end()
2022-02-10 14:50:33 +08:00
tom lee c70d2a27f2 Make alwaysDrawAtMaxQuality not do anything in here 2022-02-10 11:31:21 +08:00
tom lee 4919c0cabb Impl a better fix for Leo's mentioned issue 2022-02-10 11:21:53 +08:00
coolGi2007 40a8f228ac Removed config and saved it locally till after 1.6 gets released 2022-02-10 02:27:27 +00:00
cola98765 1b711e2b66 fixed what Leo started 2022-02-09 22:48:36 +01:00
Morippi 104d4a7400 Tried to fix the render distance min detail bug 2022-02-09 22:01:17 +01:00
cola98765 01870e4875 changed hasCliffFace so it works better with chunk borders 2022-02-09 18:32:34 +01:00
cola98765 7203acf628 fix connected lods 2022-02-09 18:25:06 +01:00
tom lee cbcb7ca6ac General update/polish. (Add more pref dumpping) 2022-02-09 18:03:49 +08:00
tom lee 1b27161518 Make Far Pos gen no longer saves all detail level, speed up far gen tons 2022-02-08 13:41:19 +08:00
tom lee b51cb7ca51 Fixed detail level stuck at 0 in far pos caused by overflow issues 2022-02-07 14:01:33 +08:00
James Seibel 1e08fc51ec Delete a1.4_Rendering_Flow_Diagram.drawio
It wasn't turning out how I was hoping.
2022-02-06 23:13:47 +00:00
James Seibel c2d4475d44 Add a few missing items to a1.6_Flow_Diagram_Overview.drawio 2022-02-06 23:08:11 +00:00
James Seibel ddefb5f67a Improve the coloring in a1.6_Flow_Diagram_Overview.drawio 2022-02-06 22:44:44 +00:00
James Seibel 8bdb3146ed Added a1.6_Flow_Diagram_Overview.drawio 2022-02-06 22:39:41 +00:00
tom lee 8860e46d9b Patch up stuff so changing DETAIL_OPTIONS should work without errors 2022-02-06 22:54:21 +08:00
tom lee a73da4102a Add DebugMode: DRAW_WIREFRAME 2022-02-06 22:07:37 +08:00
tom lee bb8df761bb Added dump memory stats for Buffers 2022-02-06 21:33:13 +08:00
coolGi2007 e459994d6d Getting config ready for after 1.6 gets released 2022-02-06 12:41:29 +00:00
tom lee 65f16463ec Added Memory Stat Dumping via pressing p 2022-02-06 18:07:53 +08:00
tom lee 31717ad202 Changed how key events work 2022-02-06 14:57:43 +08:00
tom lee de4dd44209 Clean up generics 2022-02-06 13:38:36 +08:00
coolGi2007 6c73f8b723 What is T??????????????????? 2022-02-06 05:19:23 +00:00
coolGi2007 67a171da56 Update ConfigEntry 2022-02-06 05:00:25 +00:00
coolGi2007 09b2d952b6 Removed the actual texture (Add these back when doing clouds) 2022-02-05 09:31:28 +00:00
coolGi2007 255d2adbb0 Removed cloud stuff 2022-02-05 09:21:52 +00:00
tom lee 069978ee1d Removed one default in wrapper. 2022-02-05 16:06:46 +08:00
tom lee aff7e90e37 Tweeked values for RAM check to make it actaully run 2022-02-05 14:35:27 +08:00
tom lee 1032f550ed Optimize some performance. Now less lag spikes and stuff
Also fixed the WorldGen Disable not working issue
2022-02-04 14:32:04 +08:00
tom lee ac32697204 Make the expend low RAM skip no longer skip the updates. 2022-02-04 00:31:45 +08:00
tom lee 95220d4fd7 Add failsafe for low RAM issue. 2022-02-04 00:11:53 +08:00
tom lee 068622895f BatchGen: Refactor the experGen into proper generator 2022-02-03 16:46:49 +08:00
coolGi2007 a0dd0d5aca Added some stuff to get ready for the new config 2022-02-03 03:41:54 +00:00
tom lee ca81ed1efe Solve most race issue around the move(), making world hole less likely 2022-02-02 15:55:16 +08:00
coolGi2007 35ab1ce47f Maybe fixed something? 2022-02-02 02:39:04 +00:00
James Seibel c5aeac5091 Remove the Seizure warning 2022-02-02 01:11:17 +00:00
tom lee 834cfe2e10 Add stack dump for threadFactory 2022-01-31 00:23:15 +08:00
tom lee 99ca5f6bc6 Fix crash on exit due to no shutdown for cutAndExpend thread
Also tweeked the values of vertQual on larger blocks to better support floating
islands.
2022-01-30 15:30:09 +08:00
tom lee 4bcb6c0acd Fixed bug on incorrect walls for overdraw prevention thingy 2022-01-27 14:07:09 +08:00
coolGi2007 4f63fbb14d Fixed issue 162 (renamed stuff) 2022-01-27 02:36:35 +00:00
cola98765 522a74e6e3 fixed mergeMultiData and lodBuilder with connected lods 2022-01-26 09:25:33 +01:00
cola98765 6ef789c087 removed /2 in merge multi data. now that we support a lot of connected lods it's needed 2022-01-26 09:07:00 +01:00
tom lee 410fe684ec Changed how blockUpdate works 2022-01-25 18:25:46 +08:00
tom lee 43feb2d7fe Forgot about GenPriority. Updated it + fixed now BALANCED mode issue 2022-01-25 17:08:01 +08:00
tom lee 7fa99ea070 WorldGen Config Overhaul + General cleanup 2022-01-25 16:26:34 +08:00
coolGi2007 bdd877abe8 Added FileComment 2022-01-25 04:54:53 +00:00
tom lee b2d5e18fe4 Raised distance limits to 8192. HYPER EXPERIMENTAL!!! 2022-01-24 23:36:40 +08:00
tom lee 880be5ed60 Fixed a NEAR_FIRST bug I introduced 2022-01-24 23:02:31 +08:00
tom lee 90440f3e06 Tweaked a bit around the FAR_FIRST gen stuff 2022-01-24 22:56:35 +08:00
tom lee c3abb9c46b Errr....
1. Make VerticalQuality overall is higher.
2. Rework how FAR_FIRST works.
3. Clean up some stuff.
2022-01-24 21:02:55 +08:00
tom lee ab3880a5e5 Trash ThreadMapUtil replaced by ThreadLocal. Trash an unused class 2022-01-23 23:15:08 +08:00
tom lee f939839941 Added Thread Priorities. Hopefully no more stealing TPS. 2022-01-23 21:19:08 +08:00
tom lee e9e2af2807 Add optimization on getPosToGenerate. 2022-01-23 19:09:09 +08:00
James Seibel a5a4a3e6e2 Set the default Generation Priority to NEAR_FIRST closes #160 2022-01-22 22:03:52 -06:00
tom lee 9e7703ac53 Fixed void chunks. Changed thread defaults. Added LagCatchers in render 2022-01-22 18:10:07 +08:00
tom lee 9580335692 Fixed Buffer leaks, cleanup Debug logging 2022-01-22 16:09:58 +08:00
tom lee 608dc443dc Removed using the slice() func, but haven't found the mem leaks 2022-01-21 23:55:40 +08:00
tom lee 14e72c68cb Added sub buffers. Greatly reducing fps stutters 2022-01-20 22:53:17 +08:00
tom lee dc0e48ae2a Renamed stuff so the underWaterFog also applies for other special fog 2022-01-20 21:54:10 +08:00
tom lee 0bfb47cc68 Fixed issue in FAR_FIRST where it can overlaod Ram resources 2022-01-20 20:02:55 +08:00
tom lee c69569c95f Fixed Critical Vanilla Save corrupt issue. Fixed? world gen not starting 2022-01-20 19:27:32 +08:00
tom lee 914a76297d New save structure. No more multi folders for Generation Mode
This also includes a converter that runs on "Joining World..." text. It
may stuck on that screen for a while, but it is indeed merging and
updating the old saves.

The old saves file after update is not deleted. Instead, the folders are
renamed so that you still have a way to recover old saves in case the
converter failed.
2022-01-19 23:28:56 +08:00
tom lee 22e47b9734 Reworked lodBuilder. Make it faster. 2022-01-19 18:59:55 +08:00
tom lee 930113a6f9 Add support for OptifineAccessor, and decrease light issue on forge 2022-01-19 13:50:53 +08:00
tom lee 6c77164a65 Fixed PosToGenerate issue 2022-01-18 22:34:23 +08:00
cola98765 546d60f1fc resolved warnings. part 3 2022-01-18 11:29:01 +01:00
cola98765 6b6d011cd5 resolved warnings. part 2 2022-01-18 11:00:07 +01:00
cola98765 7bf54d20c0 resolved warnings. now more carefully. 2022-01-18 10:38:31 +01:00
jas35484 bba7f34d46 Add the minor version number 1.6a -> 1.6.0a
Why do I always remember these things after I have done all of them?
2022-01-17 20:33:00 -06:00
jas35484 ade5500c6d I forgot CurseForge doesn't like a1.X.Y versioning, changed to 1.X.Ya 2022-01-17 20:29:31 -06:00
jas35484 a3bd2ed70b Increment the version from 'a1.5.4' to 'a1.6-pre' 2022-01-17 20:23:47 -06:00
cola98765 43304da80b small fix 2022-01-17 16:30:09 +01:00
cola98765 7d53da40ec Revert "resolved couple warnings. If it causes problems just revert it."
This reverts commit 4702aa7888.
2022-01-17 16:27:36 +01:00
cola98765 bd489a66c7 whatever... deprecation is too hard for me 2022-01-17 15:50:24 +01:00
tom lee a50ef74a5e Fixed a bug in posToGenerate and the buffer now reset on too far away 2022-01-17 22:03:35 +08:00
cola98765 4702aa7888 resolved couple warnings. If it causes problems just revert it. 2022-01-17 14:18:12 +01:00
cola98765 57a6483aec cleaned up code around connected lods. not sure if fixed anything, but it's working for me. 2022-01-17 12:43:18 +01:00
cola98765 31cb1ef401 cleaned up code around light 2022-01-17 10:53:36 +01:00
tom lee be5b5de170 Fix issues with generation filling up RAM 2022-01-16 17:33:20 +08:00
tom lee f665a8b8f6 Add empty StarlightAccessor for now 2022-01-15 21:54:35 +08:00
tom lee 0c4de076ca Add/Fix issue with writeData and Cut causing data loss 2022-01-15 18:59:02 +08:00
coolGi2007 4c122512ac Added more languages 2022-01-14 04:53:15 +00:00
tom lee 976874e7a3 Nuked the old removeLegacyFog due to it failing on Sodium 2022-01-13 22:24:39 +08:00
tom lee bb22ad58bc Fix critical crash on Multiplayer. 2022-01-12 18:01:15 +08:00
tom lee 0b2be5580f Changed ClientChunkLoad to work properly 2022-01-12 16:15:26 +08:00
coolGi2007 02143d0951 Why dont you have a .java? 2022-01-11 08:44:03 +00:00
coolGi2007 3ebdfd6e87 Added a mod checker to check if mod exists 2022-01-11 08:23:14 +00:00
James Seibel 3caf8facf9 Merge branch 'main' of https://gitlab.com/jeseibel/distant-horizons-core 2022-01-10 18:08:06 -06:00
tom lee efd1d67f3f Render: Fixed mistakes and added more GridList stuff 2022-01-10 22:04:52 +08:00
coolGi2007 ee2c6e2a06 Added a cloud warning to the lang 2022-01-10 04:50:05 +00:00
tom lee d1e1970c18 Added ModAccessor sturcture for adding mod compats 2022-01-09 14:56:49 +08:00
tom lee 943a2d5cad Remove default light hack. Try the first patch to get light work 2022-01-09 00:25:58 +08:00
tom lee e5f5d33db9 Add missing default 2022-01-08 22:53:07 +08:00
tom lee 8b761ca31a Made IChunkWrapper also has the ability to return light data 2022-01-08 22:51:23 +08:00
tom lee a402fa5f0b Update lang file 2022-01-07 19:08:18 +08:00
tom lee d4123a44ed Add DropoffQuality 2022-01-07 18:58:29 +08:00
tom lee cba75123c7 Fixed Overdraw, mergeVertData error, wrong vanillaDist math. 2022-01-07 15:50:42 +08:00
tom lee 5ac51dd2a5 Fixed accidentally using java 9+ features. Now java 7 should work 2022-01-07 13:28:12 +08:00
coolGi2007 a8d52c1059 Updated lang 2022-01-06 09:58:00 +00:00
tom lee c664564fb0 Fixed FAR_FIRST gen. Impl proper cleanups on many place. 2022-01-06 16:27:28 +08:00
tom lee 1a5fd87346 Fixed saving. Guarrantee save is complete on exit. Trashed some logging
Also, tried (keyword: tried) fixing the FAR_FIRST generation.
2022-01-06 00:07:19 +08:00
tom lee e71660eb41 Fixed generation issues and buffer now update more aggresively 2022-01-05 19:18:23 +08:00
cola98765 d9dc33a105 added gen mode debug view 2022-01-05 10:53:56 +01:00
James Seibel 4bf004ae6b Add class_diagram.drawio 2022-01-05 05:26:39 +00:00
tom lee f9871ef16d WorldGen: Now no longer gen all chunks in higher than chunk details 2022-01-04 18:57:25 +08:00
tom lee 054988851d CutExpend: Fixed Cut bugs and some general fix to LodFileHandler stuff. 2022-01-04 16:43:21 +08:00
coolGi2007 44bcc5ae01 Added lower qulity clouds 2022-01-04 07:50:02 +00:00
tom lee 3b475886ef Buffers: Fixed critical render bugs and vertex buffer leaks 2022-01-04 15:10:41 +08:00
James Seibel ff6edec461 Merge branch 'main' of https://gitlab.com/jeseibel/distant-horizons-core 2022-01-03 21:23:31 -06:00
James Seibel ae7e416810 Add a missing comment 2022-01-03 21:20:46 -06:00
tom lee d72805d1fe Buffers: Basically redid the buffer management
Note: Backface Culling currently disabled
2022-01-03 00:04:54 +08:00
coolGi2007 f80c43385a Removed category annotation 2022-01-02 06:13:41 +00:00
coolGi2007 219ad9c45a Turned off custom clouds till it is fixed 2022-01-02 03:07:05 +00:00
James Seibel 662a1ec8bc Add a partially completed rendering flow diagram 2022-01-01 15:46:10 +00:00
cola98765 e60a0526ca even tho it is not used yet, updated texture so patterns are closer in size to vanilla 2022-01-01 15:05:21 +01:00
cola98765 1266ed6dc1 fixed updateData 2022-01-01 13:31:21 +01:00
tom lee 816c4fa76a BufferFactory: Added static bool for enabling time logging 2022-01-01 16:48:19 +08:00
tom lee 975c24c8a9 AMD: Fixed AMD render issue due to vertex data alignments 2022-01-01 15:25:37 +08:00
tom lee 4db3b7b729 LodRenderer: Add support for underwater fog 2022-01-01 13:50:59 +08:00
tom lee 32e341fe4f Reordered and chopped up some config desc 2022-01-01 12:12:08 +08:00
tom lee 0ce249ab17 Fixed up cloud configs and removed @nullable for 1.16.5 build 2021-12-31 21:44:46 +08:00
tom lee f9372c6a07 Save/Load: Fixed locking up files due to try-catch-resoure issue 2021-12-31 20:59:14 +08:00
coolGi2007 6ea7ecd215 Moved some resources to core 2021-12-31 10:40:40 +00:00
tom lee 6a828ee931 Load: Fixed load error on missing middle level data 2021-12-31 17:25:08 +08:00
tom lee e8d25daabf BufferUpload: Fixed bug on 0 size data not updating vert count 2021-12-31 17:01:40 +08:00
tom lee 7c8b073b02 Save: Fixed critical bug causing 0KB files on new world 2021-12-31 16:09:29 +08:00
coolGi2007 b623fc530f Abstracting clouds 2021-12-31 07:20:59 +00:00
tom lee 5fdf09209c Fixed Config names 2021-12-30 22:03:53 +08:00
cola98765 515afe4d45 moved where last pos is saved 2021-12-30 14:58:05 +01:00
cola98765 7f5eea865e made config for minimum back side culling distance. actual value is using prev player pos so on long flights it works better. 2021-12-30 13:19:46 +01:00
cola98765 13d6232790 made connected lods dependant on Vertical Quality config. 2021-12-30 12:04:40 +01:00
170 changed files with 15242 additions and 7961 deletions
+3
View File
@@ -16,3 +16,6 @@ It should be automatically included when pulling the full mod.
XZ for Java (data compression)\
https://tukaani.org/xz/java.html
Toml for Java (config handeling)\
https://github.com/TheElectronWill/night-config
-1
View File
@@ -1 +0,0 @@
<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>
@@ -0,0 +1,221 @@
<mxfile host="app.diagrams.net" modified="2022-02-06T23:07:56.707Z" agent="5.0 (Windows)" etag="McdhDnDn_b3JSW_6sAfD" version="16.5.3" type="gitlab">
<diagram id="xLs7mM1S-vncSruOQYJG" name="Page-1">
<mxGraphModel dx="2854" dy="971" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="850" pageHeight="1100" background="none" math="0" shadow="0">
<root>
<mxCell id="0" />
<object label="Background" id="1">
<mxCell parent="0" />
</object>
<mxCell id="bW8zysbPbXfA33rAzGtO-46" value="" style="rounded=0;whiteSpace=wrap;html=1;labelBackgroundColor=none;fillColor=#FFE9CC;strokeColor=#d79b00;shadow=0;" vertex="1" parent="1">
<mxGeometry x="400" y="300" width="720" height="320" as="geometry" />
</mxCell>
<mxCell id="ZgqzLkNpqH_WLmXpxmUH-3" value="" style="rounded=0;whiteSpace=wrap;html=1;labelBackgroundColor=none;fillColor=#FFE9CC;strokeColor=#d79b00;" parent="1" vertex="1">
<mxGeometry x="30" y="10" width="1090" height="290" as="geometry" />
</mxCell>
<mxCell id="lUieYn43trCVNQSoQYE8-16" value="" style="rounded=0;whiteSpace=wrap;html=1;labelBackgroundColor=none;fillColor=#CBF2CB;strokeColor=#06962D;gradientColor=none;" parent="1" vertex="1">
<mxGeometry x="-680" y="70" width="200" height="220" as="geometry" />
</mxCell>
<mxCell id="lUieYn43trCVNQSoQYE8-18" value="Minecraft &lt;br&gt;Version Specific" style="rounded=0;whiteSpace=wrap;html=1;labelBackgroundColor=none;fillColor=#CBF2CB;strokeColor=#06962D;gradientColor=none;" parent="1" vertex="1">
<mxGeometry x="-680" y="10" width="200" height="60" as="geometry" />
</mxCell>
<mxCell id="gNZkowd1tYjNP-PxMyY5-1" value="Minecraft" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#A5E8A5;strokeColor=#82b366;" parent="1" vertex="1">
<mxGeometry x="-650" y="205" width="140" height="50" as="geometry" />
</mxCell>
<mxCell id="XNAtI1EKQKx7pIlif8ke-27" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;strokeWidth=3;" parent="1" source="lUieYn43trCVNQSoQYE8-14" target="XNAtI1EKQKx7pIlif8ke-22" edge="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="lUieYn43trCVNQSoQYE8-14" value="Mod API&lt;br&gt;(Forge or Fabric)" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#A5E8A5;strokeColor=#82b366;" parent="1" vertex="1">
<mxGeometry x="-650" y="90" width="140" height="60" as="geometry" />
</mxCell>
<mxCell id="ZgqzLkNpqH_WLmXpxmUH-2" value="Distant Horizons Core&lt;br&gt;(Version Independent)" style="rounded=0;whiteSpace=wrap;html=1;labelBackgroundColor=none;fillColor=#FFE9CC;strokeColor=#d79b00;" parent="1" vertex="1">
<mxGeometry x="450" y="10" width="220" height="60" as="geometry" />
</mxCell>
<mxCell id="XNAtI1EKQKx7pIlif8ke-16" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0;entryY=0.25;entryDx=0;entryDy=0;strokeWidth=3;" parent="1" source="XNAtI1EKQKx7pIlif8ke-1" target="XNAtI1EKQKx7pIlif8ke-11" edge="1">
<mxGeometry relative="1" as="geometry">
<Array as="points">
<mxPoint x="10" y="120" />
<mxPoint x="10" y="176" />
</Array>
</mxGeometry>
</mxCell>
<mxCell id="XNAtI1EKQKx7pIlif8ke-72" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=0.5;entryY=0;entryDx=0;entryDy=0;fontColor=#FFFFFF;strokeWidth=3;" parent="1" source="XNAtI1EKQKx7pIlif8ke-11" target="bW8zysbPbXfA33rAzGtO-39" edge="1">
<mxGeometry relative="1" as="geometry">
<Array as="points">
<mxPoint x="134" y="90" />
<mxPoint x="1000" y="90" />
</Array>
<mxPoint x="997.5" y="165.0000000000001" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="XNAtI1EKQKx7pIlif8ke-1" value="Wrappers&lt;br&gt;Or&lt;br&gt;&lt;div&gt;DH-Object&lt;/div&gt;" style="shape=step;perimeter=stepPerimeter;whiteSpace=wrap;html=1;fixedSize=1;fillColor=#FFD7B0;strokeColor=#d79b00;rounded=0;labelBackgroundColor=none;" parent="1" vertex="1">
<mxGeometry x="-170" y="80" width="120" height="80" as="geometry" />
</mxCell>
<mxCell id="XNAtI1EKQKx7pIlif8ke-23" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;strokeWidth=3;" parent="1" source="XNAtI1EKQKx7pIlif8ke-3" target="XNAtI1EKQKx7pIlif8ke-21" edge="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="XNAtI1EKQKx7pIlif8ke-3" value="Mixins" style="shape=step;perimeter=stepPerimeter;whiteSpace=wrap;html=1;fixedSize=1;fillColor=#A5E8A5;strokeColor=#82b366;rounded=0;labelBackgroundColor=none;" parent="1" vertex="1">
<mxGeometry x="-470" y="190" width="120" height="80" as="geometry" />
</mxCell>
<mxCell id="XNAtI1EKQKx7pIlif8ke-4" value="" style="endArrow=classic;html=1;rounded=0;strokeWidth=3;entryX=0;entryY=0.5;entryDx=0;entryDy=0;exitX=1;exitY=0.5;exitDx=0;exitDy=0;" parent="1" source="gNZkowd1tYjNP-PxMyY5-1" target="XNAtI1EKQKx7pIlif8ke-3" edge="1">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="-420" y="130" as="sourcePoint" />
<mxPoint x="-220" y="-25" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="XNAtI1EKQKx7pIlif8ke-17" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0;entryY=0.75;entryDx=0;entryDy=0;strokeWidth=3;" parent="1" source="XNAtI1EKQKx7pIlif8ke-5" target="XNAtI1EKQKx7pIlif8ke-11" edge="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="XNAtI1EKQKx7pIlif8ke-5" value="Wrappers&lt;br&gt;Or&lt;br&gt;&lt;div&gt;DH-Object&lt;/div&gt;" style="shape=step;perimeter=stepPerimeter;whiteSpace=wrap;html=1;fixedSize=1;fillColor=#FFD7B0;strokeColor=#d79b00;rounded=0;labelBackgroundColor=none;" parent="1" vertex="1">
<mxGeometry x="-170" y="190" width="120" height="80" as="geometry" />
</mxCell>
<mxCell id="XNAtI1EKQKx7pIlif8ke-60" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;fontColor=#FFFFFF;strokeWidth=3;" parent="1" source="XNAtI1EKQKx7pIlif8ke-11" target="XNAtI1EKQKx7pIlif8ke-33" edge="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="XNAtI1EKQKx7pIlif8ke-63" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0.5;exitY=1;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;fontColor=#FFFFFF;strokeWidth=3;" parent="1" source="XNAtI1EKQKx7pIlif8ke-11" target="lUieYn43trCVNQSoQYE8-19" edge="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="XNAtI1EKQKx7pIlif8ke-11" value="core.api" style="rounded=0;whiteSpace=wrap;html=1;labelBackgroundColor=none;fillColor=#FFD7B0;strokeColor=#d79b00;" parent="1" vertex="1">
<mxGeometry x="60" y="165" width="147.5" height="45" as="geometry" />
</mxCell>
<mxCell id="XNAtI1EKQKx7pIlif8ke-25" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;strokeWidth=3;" parent="1" source="XNAtI1EKQKx7pIlif8ke-21" target="XNAtI1EKQKx7pIlif8ke-5" edge="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="XNAtI1EKQKx7pIlif8ke-21" value="" style="shape=step;perimeter=stepPerimeter;whiteSpace=wrap;html=1;fixedSize=1;fillColor=#C7C7C7;strokeColor=#666666;fontColor=#333333;rounded=0;labelBackgroundColor=none;" parent="1" vertex="1">
<mxGeometry x="-320" y="175" width="120" height="85" as="geometry" />
</mxCell>
<mxCell id="XNAtI1EKQKx7pIlif8ke-26" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;strokeWidth=3;" parent="1" source="XNAtI1EKQKx7pIlif8ke-22" target="XNAtI1EKQKx7pIlif8ke-1" edge="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="XNAtI1EKQKx7pIlif8ke-22" value="" style="shape=step;perimeter=stepPerimeter;whiteSpace=wrap;html=1;fixedSize=1;fillColor=#C7C7C7;strokeColor=#666666;fontColor=#333333;rounded=0;labelBackgroundColor=none;" parent="1" vertex="1">
<mxGeometry x="-320" y="90" width="120" height="85" as="geometry" />
</mxCell>
<mxCell id="XNAtI1EKQKx7pIlif8ke-28" value="&lt;div&gt;Wrapper&lt;/div&gt;&lt;div&gt;Factory&lt;br&gt;&lt;/div&gt;" style="rounded=0;whiteSpace=wrap;html=1;labelBackgroundColor=none;fillColor=#FFD7B0;strokeColor=#d79b00;fontColor=default;" parent="1" vertex="1">
<mxGeometry x="-290" y="110" width="60" height="130" as="geometry" />
</mxCell>
<mxCell id="bW8zysbPbXfA33rAzGtO-44" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;strokeWidth=3;" edge="1" parent="1" source="XNAtI1EKQKx7pIlif8ke-31" target="bW8zysbPbXfA33rAzGtO-39">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="XNAtI1EKQKx7pIlif8ke-31" value="GPU Buffer building" style="rounded=0;whiteSpace=wrap;html=1;labelBackgroundColor=none;fillColor=#FFD7B0;strokeColor=#d79b00;" parent="1" vertex="1">
<mxGeometry x="710" y="165" width="130" height="45" as="geometry" />
</mxCell>
<mxCell id="XNAtI1EKQKx7pIlif8ke-67" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0;entryY=0.25;entryDx=0;entryDy=0;fontColor=#FFFFFF;strokeWidth=3;" parent="1" source="XNAtI1EKQKx7pIlif8ke-33" target="XNAtI1EKQKx7pIlif8ke-54" edge="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="XNAtI1EKQKx7pIlif8ke-33" value="Lod Building" style="rounded=0;whiteSpace=wrap;html=1;labelBackgroundColor=none;fillColor=#FFD7B0;strokeColor=#d79b00;" parent="1" vertex="1">
<mxGeometry x="290" y="165" width="120" height="45" as="geometry" />
</mxCell>
<mxCell id="XNAtI1EKQKx7pIlif8ke-52" value="" style="group" parent="1" vertex="1" connectable="0">
<mxGeometry x="460" y="110" width="200" height="175" as="geometry" />
</mxCell>
<mxCell id="bW8zysbPbXfA33rAzGtO-25" value="" style="group" vertex="1" connectable="0" parent="XNAtI1EKQKx7pIlif8ke-52">
<mxGeometry width="200" height="175" as="geometry" />
</mxCell>
<mxCell id="XNAtI1EKQKx7pIlif8ke-53" value="LOD Storage" style="rounded=0;whiteSpace=wrap;html=1;labelBackgroundColor=none;fillColor=#ffe6cc;strokeColor=#d79b00;" parent="bW8zysbPbXfA33rAzGtO-25" vertex="1">
<mxGeometry width="200" height="45" as="geometry" />
</mxCell>
<mxCell id="XNAtI1EKQKx7pIlif8ke-54" value="" style="rounded=0;whiteSpace=wrap;html=1;labelBackgroundColor=none;fillColor=#FFF4E8;strokeColor=#d79b00;" parent="bW8zysbPbXfA33rAzGtO-25" vertex="1">
<mxGeometry y="45" width="200" height="130" as="geometry" />
</mxCell>
<mxCell id="XNAtI1EKQKx7pIlif8ke-55" value="Memory Storage" style="rounded=0;whiteSpace=wrap;html=1;labelBackgroundColor=none;fillColor=#FFD7B0;strokeColor=#d79b00;" parent="bW8zysbPbXfA33rAzGtO-25" vertex="1">
<mxGeometry x="10" y="115" width="180" height="45" as="geometry" />
</mxCell>
<mxCell id="XNAtI1EKQKx7pIlif8ke-56" value="&lt;div&gt;File Storage&lt;/div&gt;" style="rounded=0;whiteSpace=wrap;html=1;labelBackgroundColor=none;fillColor=#FFD7B0;strokeColor=#d79b00;" parent="bW8zysbPbXfA33rAzGtO-25" vertex="1">
<mxGeometry x="10" y="60" width="180" height="45" as="geometry" />
</mxCell>
<mxCell id="XNAtI1EKQKx7pIlif8ke-61" value="" style="group" parent="1" vertex="1" connectable="0">
<mxGeometry x="130" y="310" width="220" height="200" as="geometry" />
</mxCell>
<mxCell id="lUieYn43trCVNQSoQYE8-13" value="" style="rounded=0;whiteSpace=wrap;html=1;labelBackgroundColor=none;fillColor=#C7C7C7;strokeColor=#666666;fontColor=#333333;" parent="XNAtI1EKQKx7pIlif8ke-61" vertex="1">
<mxGeometry y="70" width="220" height="130" as="geometry" />
</mxCell>
<mxCell id="lUieYn43trCVNQSoQYE8-19" value="Distant Horizons&lt;br&gt;(Version dependent)" style="rounded=0;whiteSpace=wrap;html=1;labelBackgroundColor=none;fillColor=#C7C7C7;strokeColor=#666666;fontColor=#333333;" parent="XNAtI1EKQKx7pIlif8ke-61" vertex="1">
<mxGeometry width="220" height="70" as="geometry" />
</mxCell>
<mxCell id="XNAtI1EKQKx7pIlif8ke-19" value="Chunk Generation" style="rounded=0;whiteSpace=wrap;html=1;labelBackgroundColor=none;fillColor=#EBEBEB;strokeColor=#666666;fontColor=#333333;" parent="XNAtI1EKQKx7pIlif8ke-61" vertex="1">
<mxGeometry x="12.5" y="80" width="195" height="45" as="geometry" />
</mxCell>
<mxCell id="XNAtI1EKQKx7pIlif8ke-29" value="&lt;div&gt;Chunk loading from file&lt;/div&gt;" style="rounded=0;whiteSpace=wrap;html=1;labelBackgroundColor=none;fillColor=#EBEBEB;strokeColor=#666666;fontColor=#333333;" parent="XNAtI1EKQKx7pIlif8ke-61" vertex="1">
<mxGeometry x="12.5" y="140" width="195" height="45" as="geometry" />
</mxCell>
<mxCell id="XNAtI1EKQKx7pIlif8ke-64" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0.5;entryY=1;entryDx=0;entryDy=0;fontColor=#FFFFFF;strokeWidth=3;" parent="1" source="lUieYn43trCVNQSoQYE8-19" target="XNAtI1EKQKx7pIlif8ke-33" edge="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="XNAtI1EKQKx7pIlif8ke-66" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.25;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;fontColor=#FFFFFF;strokeWidth=3;" parent="1" source="XNAtI1EKQKx7pIlif8ke-54" target="XNAtI1EKQKx7pIlif8ke-31" edge="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="bW8zysbPbXfA33rAzGtO-23" value="" style="group" vertex="1" connectable="0" parent="1">
<mxGeometry x="455" y="324" width="210" height="270" as="geometry" />
</mxCell>
<mxCell id="bW8zysbPbXfA33rAzGtO-16" value="Memory Storage" style="rounded=0;whiteSpace=wrap;html=1;labelBackgroundColor=none;fillColor=#ffe6cc;strokeColor=#d79b00;" vertex="1" parent="bW8zysbPbXfA33rAzGtO-23">
<mxGeometry width="210" height="45" as="geometry" />
</mxCell>
<mxCell id="bW8zysbPbXfA33rAzGtO-17" value="" style="rounded=0;whiteSpace=wrap;html=1;labelBackgroundColor=none;fillColor=#FFF4E8;strokeColor=#d79b00;" vertex="1" parent="bW8zysbPbXfA33rAzGtO-23">
<mxGeometry y="45" width="210" height="225" as="geometry" />
</mxCell>
<mxCell id="bW8zysbPbXfA33rAzGtO-18" value="dimension" style="rounded=0;whiteSpace=wrap;html=1;labelBackgroundColor=none;fillColor=#FFD7B0;strokeColor=#d79b00;" vertex="1" parent="bW8zysbPbXfA33rAzGtO-23">
<mxGeometry x="10.5" y="110" width="189" height="40" as="geometry" />
</mxCell>
<mxCell id="bW8zysbPbXfA33rAzGtO-19" value="World" style="rounded=0;whiteSpace=wrap;html=1;labelBackgroundColor=none;fillColor=#FFD7B0;strokeColor=#d79b00;" vertex="1" parent="bW8zysbPbXfA33rAzGtO-23">
<mxGeometry x="10.5" y="60" width="189" height="40" as="geometry" />
</mxCell>
<mxCell id="bW8zysbPbXfA33rAzGtO-21" value="Region" style="rounded=0;whiteSpace=wrap;html=1;labelBackgroundColor=none;fillColor=#FFD7B0;strokeColor=#d79b00;" vertex="1" parent="bW8zysbPbXfA33rAzGtO-23">
<mxGeometry x="10.5" y="160" width="189" height="40" as="geometry" />
</mxCell>
<mxCell id="bW8zysbPbXfA33rAzGtO-22" value="Level Container&lt;br&gt;(Long primitive)" style="rounded=0;whiteSpace=wrap;html=1;labelBackgroundColor=none;fillColor=#FFD7B0;strokeColor=#d79b00;" vertex="1" parent="bW8zysbPbXfA33rAzGtO-23">
<mxGeometry x="10.5" y="210" width="189" height="40" as="geometry" />
</mxCell>
<mxCell id="bW8zysbPbXfA33rAzGtO-26" value="" style="group" vertex="1" connectable="0" parent="1">
<mxGeometry x="680" y="419" width="200" height="175" as="geometry" />
</mxCell>
<mxCell id="bW8zysbPbXfA33rAzGtO-27" value="Config System" style="rounded=0;whiteSpace=wrap;html=1;labelBackgroundColor=none;fillColor=#ffe6cc;strokeColor=#d79b00;" vertex="1" parent="bW8zysbPbXfA33rAzGtO-26">
<mxGeometry width="200" height="45" as="geometry" />
</mxCell>
<mxCell id="bW8zysbPbXfA33rAzGtO-28" value="" style="rounded=0;whiteSpace=wrap;html=1;labelBackgroundColor=none;fillColor=#FFF4E8;strokeColor=#d79b00;" vertex="1" parent="bW8zysbPbXfA33rAzGtO-26">
<mxGeometry y="45" width="200" height="130" as="geometry" />
</mxCell>
<mxCell id="bW8zysbPbXfA33rAzGtO-29" value="Config GUI" style="rounded=0;whiteSpace=wrap;html=1;labelBackgroundColor=none;fillColor=#FFD7B0;strokeColor=#d79b00;" vertex="1" parent="bW8zysbPbXfA33rAzGtO-26">
<mxGeometry x="10" y="115" width="180" height="45" as="geometry" />
</mxCell>
<mxCell id="bW8zysbPbXfA33rAzGtO-30" value="Config File" style="rounded=0;whiteSpace=wrap;html=1;labelBackgroundColor=none;fillColor=#FFD7B0;strokeColor=#d79b00;" vertex="1" parent="bW8zysbPbXfA33rAzGtO-26">
<mxGeometry x="10" y="60" width="180" height="45" as="geometry" />
</mxCell>
<mxCell id="bW8zysbPbXfA33rAzGtO-38" value="" style="group" vertex="1" connectable="0" parent="1">
<mxGeometry x="900" y="165" width="200" height="175" as="geometry" />
</mxCell>
<mxCell id="bW8zysbPbXfA33rAzGtO-39" value="Rendering" style="rounded=0;whiteSpace=wrap;html=1;labelBackgroundColor=none;fillColor=#ffe6cc;strokeColor=#d79b00;" vertex="1" parent="bW8zysbPbXfA33rAzGtO-38">
<mxGeometry width="200" height="45" as="geometry" />
</mxCell>
<mxCell id="bW8zysbPbXfA33rAzGtO-40" value="" style="rounded=0;whiteSpace=wrap;html=1;labelBackgroundColor=none;fillColor=#FFF4E8;strokeColor=#d79b00;" vertex="1" parent="bW8zysbPbXfA33rAzGtO-38">
<mxGeometry y="45" width="200" height="130" as="geometry" />
</mxCell>
<mxCell id="bW8zysbPbXfA33rAzGtO-41" value="Cloud Rendering" style="rounded=0;whiteSpace=wrap;html=1;labelBackgroundColor=none;fillColor=#FFD7B0;strokeColor=#d79b00;" vertex="1" parent="bW8zysbPbXfA33rAzGtO-38">
<mxGeometry x="10" y="115" width="180" height="45" as="geometry" />
</mxCell>
<mxCell id="bW8zysbPbXfA33rAzGtO-42" value="LOD Rendering" style="rounded=0;whiteSpace=wrap;html=1;labelBackgroundColor=none;fillColor=#FFD7B0;strokeColor=#d79b00;" vertex="1" parent="bW8zysbPbXfA33rAzGtO-38">
<mxGeometry x="10" y="60" width="180" height="45" as="geometry" />
</mxCell>
<mxCell id="bW8zysbPbXfA33rAzGtO-47" value="" style="group" vertex="1" connectable="0" parent="1">
<mxGeometry x="900" y="419" width="200" height="175" as="geometry" />
</mxCell>
<mxCell id="bW8zysbPbXfA33rAzGtO-32" value="Mod Support" style="rounded=0;whiteSpace=wrap;html=1;labelBackgroundColor=none;fillColor=#ffe6cc;strokeColor=#d79b00;" vertex="1" parent="bW8zysbPbXfA33rAzGtO-47">
<mxGeometry width="200" height="45" as="geometry" />
</mxCell>
<mxCell id="bW8zysbPbXfA33rAzGtO-33" value="" style="rounded=0;whiteSpace=wrap;html=1;labelBackgroundColor=none;fillColor=#FFF4E8;strokeColor=#d79b00;" vertex="1" parent="bW8zysbPbXfA33rAzGtO-47">
<mxGeometry y="45" width="200" height="130" as="geometry" />
</mxCell>
<mxCell id="bW8zysbPbXfA33rAzGtO-34" value="Mod API" style="rounded=0;whiteSpace=wrap;html=1;labelBackgroundColor=none;fillColor=#FFD7B0;strokeColor=#d79b00;" vertex="1" parent="bW8zysbPbXfA33rAzGtO-47">
<mxGeometry x="10" y="115" width="180" height="45" as="geometry" />
</mxCell>
<mxCell id="bW8zysbPbXfA33rAzGtO-35" value="&lt;div&gt;Mod Accessors&lt;/div&gt;" style="rounded=0;whiteSpace=wrap;html=1;labelBackgroundColor=none;fillColor=#FFD7B0;strokeColor=#d79b00;" vertex="1" parent="bW8zysbPbXfA33rAzGtO-47">
<mxGeometry x="10" y="60" width="180" height="45" as="geometry" />
</mxCell>
<mxCell id="bW8zysbPbXfA33rAzGtO-20" value="" style="shape=flexArrow;endArrow=classic;html=1;rounded=0;exitX=0.5;exitY=1;exitDx=0;exitDy=0;entryX=0.5;entryY=0;entryDx=0;entryDy=0;" edge="1" parent="1" source="XNAtI1EKQKx7pIlif8ke-55" target="bW8zysbPbXfA33rAzGtO-16">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="410" y="-36" as="sourcePoint" />
<mxPoint x="360" y="-86" as="targetPoint" />
</mxGeometry>
</mxCell>
</root>
</mxGraphModel>
</diagram>
</mxfile>
+1
View File
@@ -0,0 +1 @@
<mxfile host="app.diagrams.net" modified="2022-01-04T16:15:44.618Z" agent="5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.110 Safari/537.36 Edg/96.0.1054.62" etag="-Vdo5wDmcYQvA_9MRiBE" version="16.0.0" type="google"><diagram id="C5RBs43oDa-KdzZeNtuy" name="Page-1">7V1db5s6GP41kbaLTHxDLpt063aWHfWs2zrtzgtOwhnBOcZpm/36Y2M7CRg60mGoNEuVGt44Bvw876eNGbmzzcMVBtv1BxTDdORY8cPIvRw5jmPZDv3HJHsuCQIhWOEk5iL7KLhJfkIhtIR0l8QwLzUkCKUk2ZaFC5RlcEFKMoAxui83W6K0fNYtWEFFcLMAqSq9TWKy5tLICY/ytzBZreWZ7WDCv9kA2VjcSb4GMbo/EbmvR+4MI0T4p83DDKZs8OS43L7b36bzH8HVX//k/4HP0/ef/v4y5p29Oecnh1vAMCNP7prcfA4vxsv3wY+3YTb+mb27Dq/GchhyspcDBmM6fuIQYbJGK5SB9PVROsVol8WQdWvRo2ObOUJbKrSp8F9IyF6QAewIoqI12aTi25xg9APOUIowlcRwCXYpvbEpzOILBjkVZiiDXPImSVNxKn6t7AIr2P5iYES7HO3wAj4yGq4vCArwCpLHGk4O+FPFgWgDCd7TH2KYApLclS8PCAavDu0OP71GCb1wxxLa5kqqCV1z/KjcBb8D8atTrNWOXlmu51Ga+1HgWGGp28NpZLf8fpVu6YeTuzuKCmKdQTJpCvol2eBkmrQkk+f2QiYvsp5MplJHftAzffw/kT6cFS3oE9i90Cd0PB22KLLakYliAfYnzbasQa6DbtIJ3IF0JwZvjuJbhGngUeVhfp9sUlCwY4kyIinJ+APSZJXRzwtKB0jd3PQOYpLQuORCfEEYEaeLdZLGc7BHO4ZxTsDihzyarhFOftJuwdFvAkwEY52g1OKG/VJQE8OctrmWTLQrog/godRwDnIiBAuUpmCbJ98Pt7GhcCTZFBGCNiXms9uBD0/gvspUGWt6Fe8nqXF/jNtsaXvWJzFbFDSTu8SOs6kQKFQYOVMWhSaQhaEXTD/3W6jwgo4MUeIcYUaW1IZURJIrKVySRqbkW7BIstW8aHPpHSUfxUgwEaK/XaaF1VoncQyzwhQSQACHlOEnVIdeqD+lf3RAZ9Yrf+TTC5/RY/t4TP9Yc0xmKKP3ApICXUj5cg8ZZ9pR4REtU7kgsQ/aQe9ogz6sgb6CcpoU6HGUZXJhPwniDQWr8FsC00+FlxrbCu6uirtbg3EKvsP0GuUJSRDrH/O2FewHg9d3Wmq2LnSjBsWm3a9R/IIp9Uuj3/oIELQ07fr0e1Ln5S+TDcxypjLG0/fo6d22dLBlNapzPniqRRjzqtQyWeXcFMzhHUzzWSEyJuF8k9BQNHkuLt92nRoOzJzRhZUy5GdM/ajLxydsOMoMHzrmQ9sYQRsfPNuEgPrgDaNhQ0BPVXYTAvZJgGhoex+5CqjPoK7YNDcyei71Rr+TcnVzPa++iuhblXKkVZnt+kX7wPcqhOFX0GnpUI7haVLxeJRg0ooO04qD+ThEEGpacShrl0yMdAbdexmvNq0o4skbuGCuWSQXH9AdM+BXOInnCR1PJyjUfn7SkspWhSngjtt4pHM9UtNMW3MGUk8XbRGnOvVgIs7O4K1JKGrh1RZxNs0m7LYxIHBOg84YUj+RFjpPFWKL8q/83zcRid4hegaj9x0To6b61K/eR6qX4MygkZjhxCCcqMlO+uWEbzVyIvuEoUlIO4f8UFweDvOmmsRiRwzmWjB31TJUv5jbVlNYkEPyEWYxxHTs36Rglb8wFl8nFQJvaCrYdYsOBBWuYAYxYJG14YJ+LkyGDgn9usmHggrgDl4mmOwPRQEDfsdlAHvw2K+mjohYkRi+BRlN2fGIndq1j8U0U1LUU1L0K6tgA9tuxw1taxL9unDB1Ija6n7T7MWZJUBdNSJfXZbEzX6KQGwsvibU3aEjv6Ap2Wfe3sCuCfbB636BGuSV53uMa+9vtjDyWtLBtxxdfFDrP2y28GTYCzBY3icfjzEG4RyD0PSs1HOZAAxVgzAWt8qgMfBrhb/tBKE+f6DmfeOi+lO4g2s29WPQ14V+zYLEntFXY/8y+t8M+trQt62hc4Cw3vkL+C9PZoELFnzfEzMf1D0N3KFzglBNBRkNtqJkckmHlBMgp2EhMUvBNPHAHzwUVNeCnYaCRx6wQTEs0MOCaOiIMKxfP7pgyBkK9JESWkOHhWF9UpCyUxsK9EGB4cvDdesDzZxPZ1WfljGfrkmfqGmuny8MrqwA5cHg18Onb/wTVTSIT6vHL40l6J4pQzuDoO4JAbFS+PokRXhh0O8e/cngfqBpqSBF/8tJYmDQ7x59t/XDZNrQb1odSNGfyYTAQK8Bem/wYkDTfkUU+rlMBAz0GqCXVdnhng1qXBpiPL529CdDx3th03qw3Hh83eh7ztDxXvjI8wDG4+uE3h882Gvy+Lnx+HqhD4cO9iaBguofvC+NXEjf05785+5L41U3LRY5etO+NNX2bmhVCKNhX5pIrRs8upehWWd6PpVbP0JyeDPKrwyMvs0uIzWuYFNKG/AgY8rySkMxsdT8zJHxOr/yOlwFn+/K00n9cgO+8WWCFyn8COIE7XLDiv5YMfiC1Kgu/TQTjx3B23bFqa55x0lTesnnHc1Gx3pgH3xrGds2GcapGkQtMwxNL2ryJuUA0ZfHT34lTv15qlti+uIdF2230JTvfdKaqkzUusccxdMdTSfM9pn9Jiqtt890K2+G6nBvHLMF9+84Iq5Mv5106Io/bEsNQKiy802PjLbr1faxUzHvrRc46FN3WRkx6v4kdRf69Hz1XTLHxJ18OKy+As/fhE1Vy+luuYTYhGVDGOogGtxQuyYu+y1DzTVqAENND48vLOc52/G17+7r/wE=</diagram></mxfile>
+37
View File
@@ -0,0 +1,37 @@
plugins {
id "com.github.johnrengelman.shadow" version "7.1.0"
}
configurations {
common
shadowMe
implementation.extendsFrom shadowMe
}
dependencies {
// Toml
shadowMe("com.electronwill.night-config:toml:${rootProject.toml_version}") {}
// Compression
common('org.tukaani:xz:1.9')
common('org.apache.commons:commons-compress:1.21')
shadowMe 'org.tukaani:xz:1.9'
shadowMe 'org.apache.commons:commons-compress:1.21'
}
shadowJar {
configurations = [project.configurations.shadowMe]
relocate 'org.tukaani', 'shaded.tukaani'
relocate 'org.apache.commons.compress', 'shaded.apache.commons.compress'
relocate 'com.electronwill.nightconfig', 'shaded.electronwill.nightconfig'
relocate 'com.seibel.lod.common', 'fabric.com.seibel.lod.common'
classifier "dev-shadow"
}
//remapJar {
// input.set shadowJar.archiveFile
// dependsOn shadowJar
// classifier null
//}
@@ -0,0 +1,26 @@
/*
* This file is part of the Distant Horizons mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020-2022 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;
public class JarMain {
public static void main(String[] args){
System.out.println("Why are you running the jar, this isn't done yet >:(");
}
}
@@ -1,8 +1,8 @@
/*
* This file is part of the Distant Horizon mod (formerly the LOD Mod),
* This file is part of the Distant Horizons mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020 James Seibel
* Copyright (C) 2020-2022 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
@@ -18,7 +18,6 @@
*/
package com.seibel.lod.core;
/**
* This file is similar to mcmod.info
* <br>
@@ -28,15 +27,18 @@ package com.seibel.lod.core;
* Pretty much all of the mod stems from there.
*
* @author James Seibel
* @author Ran
* @version 11-29-2021
*/
public final class ModInfo
{
public static final String ID = "lod";
/** The internal protocol version used for networking */
public static final int PROTOCOL_VERSION = 1;
/** The internal mod name */
public static final String NAME = "DistantHorizons";
/** Human readable version of NAME */
public static final String READABLE_NAME = "Distant Horizons";
public static final String API = "LodAPI";
public static final String VERSION = "a1.5.4";
}
public static final String VERSION = "1.6.12a";
}
@@ -1,8 +1,8 @@
/*
* This file is part of the Distant Horizon mod (formerly the LOD Mod),
* This file is part of the Distant Horizons mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020 James Seibel
* Copyright (C) 2020-2022 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -19,9 +19,12 @@
package com.seibel.lod.core.api;
import com.seibel.lod.core.builders.bufferBuilding.LodBufferBuilderFactory;
import com.seibel.lod.core.ModInfo;
import com.seibel.lod.core.builders.lodBuilding.LodBuilder;
import com.seibel.lod.core.enums.config.VerticalQuality;
import com.seibel.lod.core.objects.lod.LodWorld;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
/**
* This stores objects and variables that
@@ -32,9 +35,9 @@ import com.seibel.lod.core.objects.lod.LodWorld;
*/
public class ApiShared
{
public static final Logger LOGGER = LogManager.getLogger(ModInfo.NAME);
public ApiShared INSTANCE = new ApiShared();
public static final LodBufferBuilderFactory lodBufferBuilderFactory = new LodBufferBuilderFactory();
public static final LodWorld lodWorld = new LodWorld();
public static final LodBuilder lodBuilder = new LodBuilder();
@@ -42,12 +45,14 @@ public class ApiShared
public static int previousChunkRenderDistance = 0;
/** Used to determine if the LODs should be regenerated */
public static int previousLodRenderDistance = 0;
public static VerticalQuality previousVertQual = null;
/** Signal whether a world is shutting down */
public static volatile boolean isShuttingDown = false;
private ApiShared()
{
}
}
@@ -1,8 +1,8 @@
/*
* This file is part of the Distant Horizon mod (formerly the LOD Mod),
* This file is part of the Distant Horizons mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020 James Seibel
* Copyright (C) 2020-2022 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -19,20 +19,37 @@
package com.seibel.lod.core.api;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.time.Duration;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import com.seibel.lod.core.builders.lodBuilding.LodBuilder;
import com.seibel.lod.core.enums.rendering.RendererType;
import com.seibel.lod.core.logging.ConfigBasedLogger;
import com.seibel.lod.core.logging.ConfigBasedSpamLogger;
import com.seibel.lod.core.render.RenderSystemTest;
import com.seibel.lod.core.wrapperInterfaces.chunk.AbstractChunkPosWrapper;
import org.apache.logging.log4j.Level;
import com.seibel.lod.core.handlers.LodDimensionFinder;
import org.lwjgl.glfw.GLFW;
import com.seibel.lod.core.ModInfo;
import com.seibel.lod.core.builders.lodBuilding.bufferBuilding.LodBufferBuilderFactory;
import com.seibel.lod.core.enums.config.DistanceGenerationMode;
import com.seibel.lod.core.handlers.dependencyInjection.SingletonHandler;
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.logging.SpamReducedLogger;
import com.seibel.lod.core.wrapperInterfaces.IWrapperFactory;
import com.seibel.lod.core.wrapperInterfaces.chunk.IChunkWrapper;
import com.seibel.lod.core.wrapperInterfaces.config.ILodConfigWrapperSingleton;
import com.seibel.lod.core.wrapperInterfaces.minecraft.IMinecraftRenderWrapper;
import com.seibel.lod.core.wrapperInterfaces.minecraft.IMinecraftWrapper;
import com.seibel.lod.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper;
import com.seibel.lod.core.wrapperInterfaces.minecraft.IProfilerWrapper;
import com.seibel.lod.core.wrapperInterfaces.world.IWorldWrapper;
/**
* This holds the methods that should be called
@@ -40,19 +57,43 @@ import com.seibel.lod.core.wrapperInterfaces.minecraft.IProfilerWrapper;
* Specifically for the client.
*
* @author James Seibel
* @version 12-8-2021
* @version 2022-3-26
*/
public class ClientApi
{
{
public static boolean prefLoggerEnabled = false;
public static final ClientApi INSTANCE = new ClientApi();
public static final Logger LOGGER = LogManager.getLogger(ModInfo.NAME);
public static final LodBufferBuilderFactory lodBufferBuilderFactory = new LodBufferBuilderFactory();
public static LodRenderer renderer = new LodRenderer(lodBufferBuilderFactory);
public static RenderSystemTest testRenderer = new RenderSystemTest();
public static LodRenderer renderer = new LodRenderer(ApiShared.lodBufferBuilderFactory);
private static final IMinecraftWrapper MC = SingletonHandler.get(IMinecraftWrapper.class);
private static final IMinecraftClientWrapper MC = SingletonHandler.get(IMinecraftClientWrapper.class);
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 EventApi EVENT_API = EventApi.INSTANCE;
public static final boolean ENABLE_LAG_SPIKE_LOGGING = false;
public static final long LAG_SPIKE_THRESOLD_NS = TimeUnit.NANOSECONDS.convert(16, TimeUnit.MILLISECONDS);
public static final long SPAM_LOGGER_FLUSH_NS = TimeUnit.NANOSECONDS.convert(1, TimeUnit.SECONDS);
public static LodDimensionFinder DIMENSION_FINDER = new LodDimensionFinder();;
public static class LagSpikeCatcher {
long timer = System.nanoTime();
public LagSpikeCatcher() {}
public void end(String source) {
if (!ENABLE_LAG_SPIKE_LOGGING) return;
timer = System.nanoTime() - timer;
if (timer > LAG_SPIKE_THRESOLD_NS) {
ApiShared.LOGGER.info("LagSpikeCatcher: "+source+" took "+Duration.ofNanos(timer)+"!");
}
}
}
/**
* there is some setup that should only happen once,
@@ -62,48 +103,161 @@ public class ClientApi
private boolean configOverrideReminderPrinted = false;
public boolean rendererDisabledBecauseOfExceptions = false;
private ClientApi()
{
}
public static void logToChat(Level logLevel, String str) {
String prefix = "["+ModInfo.READABLE_NAME+"] ";
if (logLevel == Level.ERROR) {
prefix += "\u00A74";
} else if (logLevel == Level.WARN) {
prefix += "\u00A76";
} else if (logLevel == Level.INFO) {
prefix += "\u00A7f";
} else if (logLevel == Level.DEBUG) {
prefix += "\u00A77";
} else if (logLevel == Level.TRACE) {
prefix += "\u00A78";
} else {
prefix += "\u00A7f";
}
prefix += "\u00A7l\u00A7u";
prefix += logLevel.name();
prefix += ":\u00A7r ";
if (MC != null) MC.sendChatMessage(prefix + str);
}
private final ConcurrentHashMap.KeySetView<Long,Boolean> generating = ConcurrentHashMap.newKeySet();
public final ConcurrentHashMap.KeySetView<Long,Boolean> toBeLoaded = ConcurrentHashMap.newKeySet();
public void clientChunkLoadEvent(IChunkWrapper chunk, IWorldWrapper world)
{
LagSpikeCatcher clientChunkLoad = new LagSpikeCatcher();
//ApiShared.LOGGER.info("Lod Generating add: "+chunk.getLongChunkPos());
toBeLoaded.add(chunk.getLongChunkPos());
clientChunkLoad.end("clientChunkLoad");
}
private long lastFlush = 0;
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();
try
{
boolean doFlush = System.nanoTime() - lastFlush >= SPAM_LOGGER_FLUSH_NS;
if (doFlush) {
lastFlush = System.nanoTime();
SpamReducedLogger.flushAll();
}
ConfigBasedLogger.updateAll();
ConfigBasedSpamLogger.updateAll(doFlush);
if (ApiShared.previousVertQual != CONFIG.client().graphics().quality().getVerticalQuality()) {
ApiShared.previousVertQual = CONFIG.client().graphics().quality().getVerticalQuality();
EventApi.INSTANCE.worldUnloadEvent(MC.getWrappedServerWorld());
EventApi.INSTANCE.worldLoadEvent(MC.getWrappedClientWorld());
return;
}
// only run the first time setup once
if (!firstTimeSetupComplete)
firstFrameSetup();
if (!MC.playerExists() || ApiShared.lodWorld.getIsWorldNotLoaded())
return;
LodDimension lodDim = ApiShared.lodWorld.getLodDimension(MC.getCurrentDimension());
if (lodDim == null)
IWorldWrapper world = MC.getWrappedClientWorld();
if (world == null)
return;
LodDimension lodDim = ApiShared.lodWorld.getLodDimension(world.getDimensionType());
// Make sure the player's data is up-to-date
DIMENSION_FINDER.updatePlayerData();
// Make the LodDim if it does not exist
if (lodDim == null)
{
if (DIMENSION_FINDER.isDone())
{
lodDim = DIMENSION_FINDER.getAndClearFoundLodDimension();
ApiShared.lodWorld.addLodDimension(lodDim);
}
else
{
DIMENSION_FINDER.AttemptToDetermineSubDimensionAsync(MC.getCurrentDimension());
return;
}
}
if (prefLoggerEnabled) {
lodDim.dumpRamUsage();
lodBufferBuilderFactory.dumpBufferMemoryUsage();
}
LagSpikeCatcher updateToBeLoadedChunk = new LagSpikeCatcher();
for (long pos : toBeLoaded) {
if (generating.size() >= 1) {
//ApiShared.LOGGER.info("Lod Generating Full! Remining: "+toBeLoaded.size());
break;
}
IChunkWrapper chunk = world.tryGetChunk(FACTORY.createChunkPos(pos));
if (chunk == null) {
toBeLoaded.remove(pos);
LodBuilder.EVENT_LOGGER.debug("Manual Chunk: {} not ready. Remaining queue: {}", FACTORY.createChunkPos(pos), toBeLoaded.size());
continue;
}
if (!chunk.isLightCorrect()) continue;
if (!chunk.doesNearbyChunksExist()) continue;
toBeLoaded.remove(pos);
generating.add(pos);
//ApiShared.LOGGER.info("Lod Generation trying "+pos+". Remining: " +toBeLoaded.size());
ApiShared.lodBuilder.generateLodNodeAsync(chunk, ApiShared.lodWorld,
world.getDimensionType(), DistanceGenerationMode.FULL, true, true, () -> {
generating.remove(pos);
LodBuilder.EVENT_LOGGER.debug("Manual Chunk: {} done. Remaining queue: {}", FACTORY.createChunkPos(pos), toBeLoaded.size());
}, () -> {
generating.remove(pos);
toBeLoaded.add(pos);
LodBuilder.EVENT_LOGGER.debug("Manual Chunk: {} not ready. Remaining queue: {}", FACTORY.createChunkPos(pos), toBeLoaded.size());
});
}
updateToBeLoadedChunk.end("updateToBeLoadedChunk");
LagSpikeCatcher updateSettings = new LagSpikeCatcher();
DetailDistanceUtil.updateSettings();
EVENT_API.viewDistanceChangedEvent();
updateSettings.end("updateSettings");
LagSpikeCatcher updatePlayerMove = new LagSpikeCatcher();
EVENT_API.playerMoveEvent(lodDim);
updatePlayerMove.end("updatePlayerMove");
LagSpikeCatcher cutAndExpendAsync = new LagSpikeCatcher();
lodDim.cutRegionNodesAsync(MC.getPlayerBlockPos().getX(), MC.getPlayerBlockPos().getZ());
lodDim.expandOrLoadRegionsAsync(MC.getPlayerBlockPos().getX(), MC.getPlayerBlockPos().getZ());
cutAndExpendAsync.end("cutAndExpendAsync");
if (CONFIG.client().advanced().debugging().getDrawLods())
if (CONFIG.client().advanced().debugging().getRendererType() == RendererType.DEFAULT)
{
// Note to self:
// if "unspecified" shows up in the pie chart, it is
@@ -118,18 +272,26 @@ public class ClientApi
ClientApi.renderer.drawLODs(lodDim, mcModelViewMatrix, mcProjectionMatrix, partialTicks, MC.getProfiler());
} catch (RuntimeException e) {
rendererDisabledBecauseOfExceptions = true;
ApiShared.LOGGER.error("Renderer thrown an uncaught exception: ",e);
try {
//ClientApi.renderer.ma ();
} catch (RuntimeException welpLookLikeWeWillLeakResource) {}
throw e;
MC.sendChatMessage("\u00A74\u00A7l\u00A7uERROR: Distant Horizons"
+ " renderer has encountered an exception!");
MC.sendChatMessage("\u00A74Renderer is now disabled to prevent futher issues.");
MC.sendChatMessage("\u00A74Exception detail: "+e.toString());
} catch (RuntimeException ignored) {}
}
}
profiler.pop(); // end LOD
profiler.push("terrain"); // go back into "terrain"
} else if (CONFIG.client().advanced().debugging().getRendererType() == RendererType.DEBUG) {
IProfilerWrapper profiler = MC.getProfiler();
profiler.pop(); // get out of "terrain"
profiler.push("LODTestRendering");
ClientApi.testRenderer.render();
profiler.pop(); // end LODTestRendering
profiler.push("terrain"); // go back into "terrain"
}
// these can't be set until after the buffers are built (in renderer.drawLODs)
// otherwise the buffers may be set to the wrong size, or not changed at all
ApiShared.previousChunkRenderDistance = MC_RENDER.getRenderDistance();
@@ -137,8 +299,7 @@ public class ClientApi
}
catch (Exception e)
{
ClientApi.LOGGER.error("client proxy: " + e.getMessage());
e.printStackTrace();
ApiShared.LOGGER.error("client proxy uncaught exception: ", e);
}
}
@@ -149,10 +310,7 @@ public class ClientApi
if (!configOverrideReminderPrinted)
{
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("You are running an unsupported version of the mod!");
MC.sendChatMessage("Here be dragons!");
configOverrideReminderPrinted = true;
@@ -171,10 +329,35 @@ public class ClientApi
// CONFIG.client().advanced().buffers().setRebuildTimes(BufferRebuildTimes.FREQUENT);
CONFIG.client().advanced().debugging().setDebugKeybindingsEnabled(true);
// CONFIG.client().advanced().debugging().setDebugKeybindingsEnabled(true);
}
//=================//
// DUBUG USE //
//=================//
// Trigger once on key press, with CLIENT PLAYER.
public void keyPressedEvent(int glfwKey) {
if (!CONFIG.client().advanced().debugging().getDebugKeybindingsEnabled()) return;
if (glfwKey == GLFW.GLFW_KEY_F8) {
CONFIG.client().advanced().debugging()
.setDebugMode(CONFIG.client().advanced().debugging().getDebugMode().getNext());
MC.sendChatMessage("F8: Set debug mode to " + CONFIG.client().advanced().debugging().getDebugMode());
}
if (glfwKey == GLFW.GLFW_KEY_F6) {
CONFIG.client().advanced().debugging()
.setRendererType(RendererType.next(CONFIG.client().advanced().debugging().getRendererType()));
MC.sendChatMessage("F6: Set rendering to " + CONFIG.client().advanced().debugging().getRendererType());
}
if (glfwKey == GLFW.GLFW_KEY_P) {
prefLoggerEnabled = !prefLoggerEnabled;
MC.sendChatMessage("P: Debug Pref Logger is " + (prefLoggerEnabled ? "enabled" : "disabled"));
}
}
@@ -1,8 +1,8 @@
/*
* This file is part of the Distant Horizon mod (formerly the LOD Mod),
* This file is part of the Distant Horizons mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020 James Seibel
* Copyright (C) 2020-2022 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -19,203 +19,206 @@
package com.seibel.lod.core.api;
import org.lwjgl.glfw.GLFW;
import com.seibel.lod.core.api.ClientApi.LagSpikeCatcher;
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.builders.worldGeneration.BatchGenerator;
import com.seibel.lod.core.enums.WorldType;
import com.seibel.lod.core.handlers.dependencyInjection.SingletonHandler;
import com.seibel.lod.core.objects.lod.LodDimension;
import com.seibel.lod.core.objects.lod.RegionPos;
import com.seibel.lod.core.render.GLProxy;
import com.seibel.lod.core.render.LodRenderer;
import com.seibel.lod.core.util.DataPointUtil;
import com.seibel.lod.core.util.DetailDistanceUtil;
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.IVersionConstants;
import com.seibel.lod.core.wrapperInterfaces.chunk.IChunkWrapper;
import com.seibel.lod.core.wrapperInterfaces.config.ILodConfigWrapperSingleton;
import com.seibel.lod.core.wrapperInterfaces.minecraft.IMinecraftWrapper;
import com.seibel.lod.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper;
import com.seibel.lod.core.wrapperInterfaces.world.IDimensionTypeWrapper;
import com.seibel.lod.core.wrapperInterfaces.world.IWorldWrapper;
/**
* This holds the methods that should be called
* by the host mod loader (Fabric, Forge, etc.).
* Specifically server and client events.
*
* This holds the methods that should be called by the host mod loader (Fabric,
* Forge, etc.). Specifically server and client events.
* @author James Seibel
* @version 11-12-2021
*/
public class EventApi
{
public static final boolean ENABLE_STACK_DUMP_LOGGING = false;
public static final EventApi INSTANCE = new EventApi();
private static final IMinecraftWrapper MC = SingletonHandler.get(IMinecraftWrapper.class);
private static final IMinecraftClientWrapper MC = SingletonHandler.get(IMinecraftClientWrapper.class);
private static final ILodConfigWrapperSingleton CONFIG = SingletonHandler.get(ILodConfigWrapperSingleton.class);
private static final IVersionConstants VERSION_CONSTANTS = SingletonHandler.get(IVersionConstants.class);
/**
* can be set if we want to recalculate variables related
* to the LOD view distance
* can be set if we want to recalculate variables related to the LOD view
* distance
*/
private boolean recalculateWidths = false;
private boolean isCurrentlyOnSinglePlayerServer = false;
private EventApi()
{
}
//=============//
// =============//
// tick events //
//=============//
// =============//
public BatchGenerator batchGenerator = null;
private int lastWorldGenTickDelta = 0;
public void serverTickEvent()
{
lastWorldGenTickDelta--;
if (!MC.playerExists() || ApiShared.lodWorld.getIsWorldNotLoaded())
return;
LodDimension lodDim = ApiShared.lodWorld.getLodDimension(MC.getCurrentDimension());
if (lodDim == null)
return;
// FIXME: This is in server thread. We shouldn't be accessing the client's renderer!
LodWorldGenerator.INSTANCE.queueGenerationRequests(lodDim, ApiShared.lodBuilder);
if (ApiShared.isShuttingDown)
return;
if (CONFIG.client().worldGenerator().getEnableDistantGeneration())
{
if (lastWorldGenTickDelta <= 0) {
lastWorldGenTickDelta = 20; // 20 ticks is 1 second. We don't need to refresh world gen status every tick.
try {
if (batchGenerator == null)
batchGenerator = new BatchGenerator(ApiShared.lodBuilder, lodDim);
batchGenerator.queueGenerationRequests(lodDim, ApiShared.lodBuilder);
} catch (Exception e) {
// Exception may happen if world got unloaded unorderly
e.printStackTrace();
}
}
}
else
{
if (batchGenerator != null)
{
batchGenerator.stop(false);
batchGenerator = null;
}
}
}
//==============//
// ==============//
// world events //
//==============//
public void chunkLoadEvent(IChunkWrapper chunk, IDimensionTypeWrapper dimType)
{
ApiShared.lodBuilder.generateLodNodeAsync(chunk, ApiShared.lodWorld, dimType, DistanceGenerationMode.FULL);
}
// ==============//
public void worldSaveEvent()
{
ApiShared.lodWorld.saveAllDimensions();
ApiShared.lodWorld.saveAllDimensions(false); // Do an async save.
}
/** This is also called when a new dimension loads */
public void worldLoadEvent(IWorldWrapper world)
{
DataPointUtil.WORLD_HEIGHT = world.getHeight();
if (ENABLE_STACK_DUMP_LOGGING)
ApiShared.LOGGER.info(
"WorldLoadEvent called here for "
+ (world.getWorldType() == WorldType.ClientWorld ? "clientLevel" : "serverLevel"),
new RuntimeException());
// Always ignore ServerWorld event
if (world.getWorldType() == WorldType.ServerWorld)
return;
isCurrentlyOnSinglePlayerServer = MC.hasSinglePlayerServer();
if (!ApiShared.isShuttingDown) ApiShared.LOGGER.warn("WorldLoadEvent called on {} while another world is loaded!",
(world.getWorldType() == WorldType.ClientWorld ? "clientLevel" : "serverLevel"));
ApiShared.isShuttingDown = false;
//DataPointUtil.WORLD_HEIGHT = world.getHeight();
LodBuilder.MIN_WORLD_HEIGHT = world.getMinHeight(); // This updates the World height
//LodNodeGenWorker.restartExecutorService();
//ThreadMapUtil.clearMaps();
// LodNodeGenWorker.restartExecutorService();
// ThreadMapUtil.clearMaps();
// the player just loaded a new world/dimension
ApiShared.lodWorld.selectWorld(LodUtil.getWorldID(world));
String worldID = LodUtil.getWorldID(world);
ApiShared.LOGGER.info("Loading new world/dimension: {}",worldID);
ApiShared.lodWorld.selectWorld(worldID);
ApiShared.LOGGER.info("World/dimension loaded: {}",worldID);
// make sure the correct LODs are being rendered
// (if this isn't done the previous world's LODs may be drawn)
ClientApi.renderer.regenerateLODsNextFrame();
ApiShared.previousVertQual = CONFIG.client().graphics().quality().getVerticalQuality();
}
/** This is also called when the user disconnects from a server+ */
public void worldUnloadEvent()
public void worldUnloadEvent(IWorldWrapper world)
{
// the player just unloaded a world/dimension
ThreadMapUtil.clearMaps();
// ClientApi.renderer.markForCleanup();
// ClientApi.renderer.destroyBuffers();
if (ENABLE_STACK_DUMP_LOGGING)
ApiShared.LOGGER.info(
"WorldUnloadEvent called here for "
+ (world.getWorldType() == WorldType.ClientWorld ? "clientLevel" : "serverLevel"),
new RuntimeException());
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 it's single player, ignore the client side world unload event
// Note: using isCurrentlyOnSinglePlayerServer as often API call unload event
// AFTER setting MC to not be in a singlePlayerServer
if (isCurrentlyOnSinglePlayerServer && world.getWorldType() == WorldType.ClientWorld)
return;
// if this isn't done unfinished tasks may be left in the queue
// preventing new LodChunks form being generated
if (ApiShared.isShuttingDown) return; // Don't do this if we're already shutting down
ApiShared.isShuttingDown = true;
// TODO Better report on when world gen is stuck and timeout
if (batchGenerator != null)
batchGenerator.stop(true);
batchGenerator = null;
if (MC.getWrappedClientWorld() == null || (!MC.connectedToServer() && !MC.hasSinglePlayerServer()))
{
// the player just left the server
// TODO should "resetMod()" be called here? -James
// if this isn't done unfinished tasks may be left in the queue
// preventing new LodChunks form being generated
LodWorldGenerator.INSTANCE.restartExecutorService();
LodWorldGenerator.INSTANCE.numberOfChunksWaitingToGenerate.set(0);
ApiShared.lodWorld.deselectWorld();
// prevent issues related to the buffer builder
// 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
// changing worlds)
System.gc();
}
ApiShared.lodWorld.deselectWorld(); // This force a save and shutdown lodDim properly
// prevent issues related to the buffer builder
// breaking or retaining previous data when changing worlds.
ClientApi.renderer.destroyBuffers();
ClientApi.renderer.requestCleanup();
GLProxy.ensureAllGLJobCompleted();
recalculateWidths = true;
ApiShared.previousVertQual = null;
// TODO: Check if after the refactoring, is this still needed
ClientApi.renderer = new LodRenderer(ClientApi.lodBufferBuilderFactory);
ClientApi.INSTANCE.rendererDisabledBecauseOfExceptions = false;
ApiShared.LOGGER.info("Distant Horizon unloaded");
}
public void blockChangeEvent(IChunkWrapper chunk, IDimensionTypeWrapper dimType)
{
if (dimType != MC.getCurrentDimension())
return;
// recreate the LOD where the blocks were changed
ApiShared.lodBuilder.generateLodNodeAsync(chunk, ApiShared.lodWorld, dimType);
LagSpikeCatcher blockChangeUpdate = new LagSpikeCatcher();
ClientApi.INSTANCE.toBeLoaded.add(chunk.getLongChunkPos());
blockChangeUpdate.end("clientChunkLoad");
}
//=============//
// =============//
// Misc Events //
//=============//
// =============//
public void onKeyInput(int key, int keyAction)
{
if (CONFIG.client().advanced().debugging().getDebugKeybindingsEnabled())
{
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());
}
}
}
// NOTE: This is being called from Render Thread.
/** Re-centers the given LodDimension if it needs to be. */
public void playerMoveEvent(LodDimension lodDim)
{
// make sure the dimension is centered
RegionPos playerRegionPos = new RegionPos(MC.getPlayerBlockPos());
RegionPos worldRegionOffset = new RegionPos(playerRegionPos.x - lodDim.getCenterRegionPosX(), playerRegionPos.z - lodDim.getCenterRegionPosZ());
RegionPos center = lodDim.getCenterRegionPos();
RegionPos worldRegionOffset = new RegionPos(playerRegionPos.x - center.x, playerRegionPos.z - center.z);
if (worldRegionOffset.x != 0 || worldRegionOffset.z != 0)
{
ApiShared.lodWorld.saveAllDimensions();
lodDim.move(worldRegionOffset);
//LOGGER.info("offset: " + worldRegionOffset.x + "," + worldRegionOffset.z + "\t center: " + lodDim.getCenterX() + "," + lodDim.getCenterZ());
// LOGGER.info("offset: " + worldRegionOffset.x + "," + worldRegionOffset.z +
// "\t center: " + lodDim.getCenterX() + "," + lodDim.getCenterZ());
}
}
@@ -225,7 +228,8 @@ public class EventApi
// calculate how wide the dimension(s) should be in regions
int chunksWide;
if (MC.getWrappedClientWorld().getDimensionType().hasCeiling())
chunksWide = Math.min(CONFIG.client().graphics().quality().getLodChunkRenderDistance(), LodUtil.CEILED_DIMENSION_MAX_RENDER_DISTANCE) * 2 + 1;
chunksWide = Math.min(CONFIG.client().graphics().quality().getLodChunkRenderDistance(),
LodUtil.CEILED_DIMENSION_MAX_RENDER_DISTANCE) * 2 + 1;
else
chunksWide = CONFIG.client().graphics().quality().getLodChunkRenderDistance() * 2 + 1;
@@ -236,18 +240,16 @@ public class EventApi
// do the dimensions need to change in size?
if (ApiShared.lodBuilder.defaultDimensionWidthInRegions != newWidth || recalculateWidths)
{
ApiShared.lodWorld.saveAllDimensions();
// update the dimensions to fit the new width
ApiShared.lodWorld.resizeDimensionRegionWidth(newWidth);
ApiShared.lodBuilder.defaultDimensionWidthInRegions = newWidth;
ClientApi.renderer.setupBuffers(ApiShared.lodWorld.getLodDimension(MC.getCurrentDimension()));
ClientApi.renderer.setupBuffers();
recalculateWidths = false;
//LOGGER.info("new dimension width in regions: " + newWidth + "\t potential: " + newWidth );
// LOGGER.info("new dimension width in regions: " + newWidth + "\t potential: "
// + newWidth );
}
DetailDistanceUtil.updateSettings();
}
}
@@ -1,160 +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;
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++;
}
}
}
}
@@ -1,8 +1,8 @@
/*
* This file is part of the Distant Horizon mod (formerly the LOD Mod),
* This file is part of the Distant Horizons mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020 James Seibel
* Copyright (C) 2020-2022 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -19,31 +19,31 @@
package com.seibel.lod.core.builders.lodBuilding;
import java.util.ConcurrentModificationException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import com.seibel.lod.core.enums.LodDirection;
import com.seibel.lod.core.enums.config.BlocksToAvoid;
import com.seibel.lod.core.enums.config.DistanceGenerationMode;
import com.seibel.lod.core.enums.config.HorizontalResolution;
import com.seibel.lod.core.handlers.dependencyInjection.SingletonHandler;
import com.seibel.lod.core.logging.ConfigBasedLogger;
import com.seibel.lod.core.objects.lod.LodDimension;
import com.seibel.lod.core.objects.lod.LodRegion;
import com.seibel.lod.core.objects.lod.LodWorld;
import com.seibel.lod.core.util.ColorUtil;
import com.seibel.lod.core.util.DataPointUtil;
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.util.ThreadMapUtil;
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.IWrapperFactory;
import com.seibel.lod.core.wrapperInterfaces.block.IBlockDetailWrapper;
import com.seibel.lod.core.wrapperInterfaces.chunk.IChunkWrapper;
import com.seibel.lod.core.wrapperInterfaces.config.ILodConfigWrapperSingleton;
import com.seibel.lod.core.wrapperInterfaces.minecraft.IMinecraftWrapper;
import com.seibel.lod.core.wrapperInterfaces.world.IBiomeWrapper;
import com.seibel.lod.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper;
import com.seibel.lod.core.wrapperInterfaces.world.IDimensionTypeWrapper;
import com.seibel.lod.core.wrapperInterfaces.world.IWorldWrapper;
import org.apache.logging.log4j.LogManager;
/**
* This object is in charge of creating Lod related objects.
@@ -56,9 +56,12 @@ import com.seibel.lod.core.wrapperInterfaces.world.IWorldWrapper;
@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 IMinecraftClientWrapper MC = SingletonHandler.get(IMinecraftClientWrapper.class);
private static final IWrapperFactory FACTORY = SingletonHandler.get(IWrapperFactory.class);
private static final ILodConfigWrapperSingleton config = SingletonHandler.get(ILodConfigWrapperSingleton.class);
public static final ConfigBasedLogger EVENT_LOGGER = new ConfigBasedLogger(LogManager.getLogger(LodBuilder.class),
() -> config.client().advanced().debugging().debugSwitch().getLogLodBuilderEvent());
/** 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();
@@ -66,9 +69,10 @@ public class LodBuilder
/** Minecraft's max light value */
public static final short DEFAULT_MAX_LIGHT = 15;
private final ExecutorService lodGenThreadPool = Executors.newSingleThreadExecutor(new LodThreadFactory(this.getClass().getSimpleName()));
private final ILodConfigWrapperSingleton config = SingletonHandler.get(ILodConfigWrapperSingleton.class);
//public static final ExecutorService lodGenThreadPool = Executors.newFixedThreadPool(8, new ThreadFactoryBuilder().setNameFormat("Lod-Builder-%d").build());
private final ExecutorService lodGenThreadPool = Executors.newSingleThreadExecutor(
new LodThreadFactory(this.getClass().getSimpleName(), Thread.NORM_PRIORITY-1));
@@ -76,11 +80,10 @@ public class LodBuilder
* How wide LodDimensions should be in regions <br>
* Is automatically set before the first frame in ClientProxy.
*/
public int defaultDimensionWidthInRegions = 0;
public int defaultDimensionWidthInRegions = 1;
//public static final boolean useExperimentalLighting = true;
private static int timesToEdgeDetect = 1;
@@ -89,27 +92,33 @@ public class LodBuilder
}
public void generateLodNodeAsync(IChunkWrapper chunk, LodWorld lodWorld, IDimensionTypeWrapper dim)
public void generateLodNodeAsync(IChunkWrapper chunk, LodWorld lodWorld, IDimensionTypeWrapper dim, boolean genAll)
{
generateLodNodeAsync(chunk, lodWorld, dim, DistanceGenerationMode.FULL);
// Block change event
generateLodNodeAsync(chunk, lodWorld, dim, DistanceGenerationMode.FULL, true, genAll, ()->{},
()->{generateLodNodeAsync(chunk,lodWorld,dim, genAll);});
}
public void generateLodNodeAsync(IChunkWrapper chunk, LodWorld lodWorld, IDimensionTypeWrapper dim, DistanceGenerationMode generationMode)
public void generateLodNodeAsync(IChunkWrapper chunk, LodWorld lodWorld, IDimensionTypeWrapper dim,
DistanceGenerationMode generationMode, boolean override, boolean genAll, Runnable endCallback, Runnable retryCallback)
{
if (lodWorld == null || lodWorld.getIsWorldNotLoaded())
if (lodWorld == null || lodWorld.getIsWorldNotLoaded()) {
endCallback.run();
return;
}
// don't try to create an LOD object
// if for some reason we aren't
// given a valid chunk object
if (chunk == null)
if (chunk == null) {
endCallback.run();
return;
}
Thread thread = new Thread(() ->
Runnable thread = () ->
{
//noinspection GrazieInspection
//try
//{
boolean retryNeeded = false;
try
{
// we need a loaded client world in order to
// get the textures for blocks
if (MC.getWrappedClientWorld() == null)
@@ -121,26 +130,25 @@ public class LodBuilder
return;
// make sure the dimension exists
LodDimension lodDim;
if (lodWorld.getLodDimension(dim) == null)
{
lodDim = new LodDimension(dim, lodWorld, defaultDimensionWidthInRegions);
lodWorld.addLodDimension(lodDim);
}
// if not, it prob means that player left
LodDimension lodDim = lodWorld.getLodDimension(dim);
if (lodDim == null) return;
retryNeeded = !generateLodNodeFromChunk(lodDim, chunk, new LodBuilderConfig(generationMode), override, genAll);
}
catch (RuntimeException e)
{
EVENT_LOGGER.error("LodBuilder Thread Uncaught Exception: ", e);
// if the world changes while LODs are being generated
// they will throw errors as they try to access things that no longer
// exist.
} finally {
if (!retryNeeded)
endCallback.run();
else
{
lodDim = lodWorld.getLodDimension(dim);
}
generateLodNodeFromChunk(lodDim, chunk, new LodBuilderConfig(generationMode));
//}
//catch (IllegalArgumentException | NullPointerException e)
//{
// e.printStackTrace();
// // if the world changes while LODs are being generated
// // they will throw errors as they try to access things that no longer
// // exist.
//}
});
retryCallback.run();
}
};
lodGenThreadPool.execute(thread);
}
@@ -148,202 +156,275 @@ public class LodBuilder
* Creates a LodNode for a chunk in the given world.
* @throws IllegalArgumentException thrown if either the chunk or world is null.
*/
public void generateLodNodeFromChunk(LodDimension lodDim, IChunkWrapper chunk) throws IllegalArgumentException
public boolean generateLodNodeFromChunk(LodDimension lodDim, IChunkWrapper chunk, LodBuilderConfig config, boolean override, boolean genAll)
{
generateLodNodeFromChunk(lodDim, chunk, new LodBuilderConfig());
try {
if (chunk == null)
throw new IllegalArgumentException("generateLodFromChunk given a null chunk");
LodRegion region = lodDim.getRegion(chunk.getRegionPosX(), chunk.getRegionPosZ());
if (region == null)
return false;
// this happens if a LOD is generated after the user leaves the world.
if (MC.getWrappedClientWorld() == null)
return false;
if (!canGenerateLodFromChunk(chunk))
return false;
// generate the LODs
int maxVerticalData = DetailDistanceUtil.getMaxVerticalData((byte)0);
long[] data = new long[maxVerticalData*16*16];
boolean isAllVoid = true;
if (!config.quickFillWithVoid) {
for (int i = 0; i < 16*16; i++)
{
int subX = i/16;
int subZ = i%16;
writeVerticalData(data, i*maxVerticalData, maxVerticalData, chunk, config, subX, subZ);
isAllVoid &= DataPointUtil.isVoid(data[i*maxVerticalData]);
if (!DataPointUtil.doesItExist(data[i*maxVerticalData]))
throw new RuntimeException("writeVerticalData result: Datapoint does not exist at "+ chunk.getMinX()+subX +", "+ chunk.getMinZ()+subZ);
if (DataPointUtil.getGenerationMode(data[i*maxVerticalData]) != config.distanceGenerationMode.complexity)
throw new RuntimeException("writeVerticalData result: Datapoint invalid at "+ chunk.getMinX()+subX +", "+ chunk.getMinZ()+subZ);
}
} else {
for (int i = 0; i < 16*16; i++)
{
data[i*maxVerticalData] = DataPointUtil.createVoidDataPoint(config.distanceGenerationMode.complexity);
}
}
if (isAllVoid) EVENT_LOGGER.debug("The chunk {} is completely void.", chunk);
// This MUST be done after the data is generated, to ensure that during the generation, the data is valid.
if (!canGenerateLodFromChunk(chunk)) // TODO Why are we calling this again? - James
return false; // Answer: Because concurrency change may cause the chunk to have invalid data, like light.
if (genAll) {
return writeAllLodNodeData(lodDim, region, chunk.getChunkPosX(), chunk.getChunkPosZ(), data, config, override);
} else {
return writePartialLodNodeData(lodDim, region, chunk.getChunkPosX(), chunk.getChunkPosZ(), data, config, override);
}
} catch (RuntimeException e) {
EVENT_LOGGER.error("LodBuilder encountered an error on building lod: ", e);
return false;
}
}
public static boolean canGenerateLodFromChunk(IChunkWrapper chunk)
{
return chunk != null && chunk.isLightCorrect() && chunk.doesNearbyChunksExist();
}
/**
* Creates a LodNode for a chunk in the given world.
* @throws IllegalArgumentException thrown if either the chunk or world is null.
*/
public void generateLodNodeFromChunk(LodDimension lodDim, IChunkWrapper chunk, LodBuilderConfig config)
throws IllegalArgumentException
{
//long executeTime = System.currentTimeMillis();
if (chunk == null)
throw new IllegalArgumentException("generateLodFromChunk given a null chunk");
int startX;
int startZ;
LodRegion region = lodDim.getRegion(chunk.getRegionPosX(), chunk.getRegionPosZ());
if (region == null)
return;
// this happens if a LOD is generated after the user leaves the world.
if (MC.getWrappedClientWorld() == null)
return;
// determine how many LODs to generate horizontally
byte minDetailLevel = region.getMinDetailLevel();
HorizontalResolution detail = DetailDistanceUtil.getLodGenDetail(minDetailLevel);
// determine how many LODs to generate vertically
//VerticalQuality verticalQuality = LodConfig.CLIENT.graphics.qualityOption.verticalQuality.get();
byte detailLevel = detail.detailLevel;
// generate the LODs
int posX;
int posZ;
for (int i = 0; i < detail.dataPointLengthCount * detail.dataPointLengthCount; i++)
{
startX = detail.startX[i];
startZ = detail.startZ[i];
long[] data;
long[] dataToMergeVertical = createVerticalDataToMerge(detail, chunk, config, startX, startZ);
data = DataPointUtil.mergeMultiData(dataToMergeVertical, DataPointUtil.WORLD_HEIGHT / 2 + 1, DetailDistanceUtil.getMaxVerticalData(detailLevel));
//lodDim.clear(detailLevel, posX, posZ);
if (data != null && data.length != 0)
{
posX = LevelPosUtil.convert((byte) 0, chunk.getChunkPosX() * 16 + startX, detail.detailLevel);
posZ = LevelPosUtil.convert((byte) 0, chunk.getChunkPosZ() * 16 + startZ, detail.detailLevel);
lodDim.addVerticalData(detailLevel, posX, posZ, data, false);
private boolean writeAllLodNodeData(LodDimension lodDim, LodRegion region, int chunkX, int chunkZ,
long[] data, LodBuilderConfig config, boolean override)
{
region.isWriting.incrementAndGet();
try {
if (region.getMinDetailLevel()!= 0) {
if (!LodUtil.checkRamUsage(0.05, 16)) {
EVENT_LOGGER.debug("LodBuilder: Not enough RAM avalible for loading files to build lods! Returning...");
return false;
}
LodRegion newRegion = lodDim.getRegionFromFile(region, (byte)0, region.getVerticalQuality());
if (region!=newRegion)
throw new RuntimeException();
}
//ApiShared.LOGGER.info("Generate chunk: {}, {} ({}, {}) at genMode {}",
// chunk.getChunkPosX(), chunk.getChunkPosZ(), chunk.getMinX(), chunk.getMinZ(), config.distanceGenerationMode);
region.addChunkOfData((byte)0, chunkX*16, chunkZ*16, 16, 16, data, data.length/16/16, override);
region.regenerateLodFromArea((byte)0, chunkX*16, chunkZ*16, 16, 16);
if (!region.doesDataExist((byte)0, chunkX*16, chunkZ*16, config.distanceGenerationMode))
throw new RuntimeException("data at detail 0 is still null after writes to it!");
if (!region.doesDataExist(LodUtil.CHUNK_DETAIL_LEVEL, chunkX, chunkZ, config.distanceGenerationMode))
throw new RuntimeException("data at chunk detail level is still null after writes to it!");
} finally {
region.isWriting.decrementAndGet();
}
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);
return true;
}
private boolean writePartialLodNodeData(LodDimension lodDim, LodRegion region, int chunkX, int chunkZ,
long[] data, LodBuilderConfig config, boolean override)
{
region.isWriting.incrementAndGet();
try {
byte targetLevel = region.getMinDetailLevel();
int vertQual = DetailDistanceUtil.getMaxVerticalData(targetLevel);
int lodCount = (targetLevel >= LodUtil.CHUNK_DETAIL_LEVEL) ?
1 : 1 << (LodUtil.CHUNK_DETAIL_LEVEL - targetLevel);
if (targetLevel != 0) {
int lodWidth = 16/lodCount;
int inputVertQual = data.length/16/16;
long[] mergedData = new long[vertQual*lodCount*lodCount];
for (int subX=0; subX<lodCount; subX++) {
for (int subZ=0; subZ<lodCount; subZ++) {
long[] toBeMerged = DataPointUtil.extractDataArray(
data, 16, 16, subX*lodWidth, subZ*lodWidth, lodWidth, lodWidth);
if(toBeMerged.length != lodWidth*lodWidth*inputVertQual) throw new RuntimeException();
long[] merged = DataPointUtil.mergeMultiData(toBeMerged, inputVertQual, vertQual);
if (merged.length != vertQual) throw new RuntimeException();
if (!DataPointUtil.doesItExist(merged[0]) ||
DataPointUtil.getGenerationMode(merged[0]) != config.distanceGenerationMode.complexity)
throw new RuntimeException();
System.arraycopy(merged, 0, mergedData, (subZ+subX*lodCount)*vertQual, vertQual);
}
}
data = mergedData;
}
if (lodCount*lodCount*vertQual != data.length) throw new RuntimeException();
for (int i=0; i<data.length; i+=vertQual) {
if (!DataPointUtil.doesItExist(data[i]) ||
DataPointUtil.getGenerationMode(data[i]) != config.distanceGenerationMode.complexity) {
EVENT_LOGGER.error("NULL data at {}, detail {}, vertQual {}, lodCount {}, chunkPos [{},{}]\n"
+ "Data: {}",
i, targetLevel, vertQual, lodCount, chunkX, chunkZ, DataPointUtil.toString(data[i]));
throw new RuntimeException("Null data!");
}
}
//ApiShared.LOGGER.info("Generate chunk: {}, {} ({}, {}) at genMode {}",
// chunk.getChunkPosX(), chunk.getChunkPosZ(), chunk.getMinX(), chunk.getMinZ(), config.distanceGenerationMode);
if (targetLevel != region.getMinDetailLevel()) {
//Concurrency issues happened.
throw new ConcurrentModificationException("Min detail level changed while writing data");
}
region.addChunkOfData(targetLevel,
LevelPosUtil.convert(LodUtil.CHUNK_DETAIL_LEVEL, chunkX, targetLevel),
LevelPosUtil.convert(LodUtil.CHUNK_DETAIL_LEVEL, chunkZ, targetLevel),
lodCount, lodCount, data, vertQual, override);
region.regenerateLodFromArea(targetLevel,
LevelPosUtil.convert(LodUtil.CHUNK_DETAIL_LEVEL, chunkX, targetLevel),
LevelPosUtil.convert(LodUtil.CHUNK_DETAIL_LEVEL, chunkZ, targetLevel),
lodCount, lodCount);
if (!region.doesDataExist(targetLevel,
LevelPosUtil.convert(LodUtil.CHUNK_DETAIL_LEVEL, chunkX, targetLevel),
LevelPosUtil.convert(LodUtil.CHUNK_DETAIL_LEVEL, chunkZ, targetLevel),
config.distanceGenerationMode))
throw new RuntimeException("data at detail "+ targetLevel+" is still null after writes to it!");
} catch (Exception e) {
EVENT_LOGGER.error("LodBuilder encountered an error on writePartialLodNodeData: ", e);
} finally {
region.isWriting.decrementAndGet();
}
return true;
}
/** creates a vertical DataPoint */
private long[] createVerticalDataToMerge(HorizontalResolution detail, IChunkWrapper chunk, LodBuilderConfig config, int startX, int startZ)
private void writeVerticalData(long[] data, int dataOffset, int maxVerticalData,
IChunkWrapper chunk, LodBuilderConfig config, int chunkSubPosX, int chunkSubPosZ)
{
// equivalent to 2^detailLevel
int size = 1 << detail.detailLevel;
int totalVerticalData = (chunk.getHeight());
long[] dataToMerge = new long[totalVerticalData];
long[] dataToMerge = ThreadMapUtil.getBuilderVerticalArray(detail.detailLevel);
int verticalData = DataPointUtil.WORLD_HEIGHT / 2 + 1;
int height;
int depth;
int color;
int light;
int lightSky;
int lightBlock;
int generation = config.distanceGenerationMode.complexity;
int xRel;
int zRel;
int xAbs;
int yAbs;
int zAbs;
boolean hasCeiling = MC.getWrappedClientWorld().getDimensionType().hasCeiling();
boolean hasSkyLight = MC.getWrappedClientWorld().getDimensionType().hasSkyLight();
boolean isDefault;
int index;
byte generation = config.distanceGenerationMode.complexity;
int count = 0;
// FIXME: This yAbs is just messy!
int x = chunk.getMinX() + chunkSubPosX;
int z = chunk.getMinZ() + chunkSubPosZ;
int y = chunk.getMaxY(x, z);
for (index = 0; index < size * size; index++)
{
xRel = startX + index % size;
zRel = startZ + index / size;
xAbs = chunk.getMinX() + xRel;
zAbs = chunk.getMinZ() + zRel;
//Calculate the height of the lod
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, xAbs, yAbs, zAbs);
// If the lod is at the default height, it must be void data
if (height == 0)
break;
yAbs = height - 1;
// We search light on above air block
depth = determineBottomPointFrom(chunk, config, xAbs, yAbs, zAbs, count < timesToEdgeDetect && !hasCeiling);
if (hasCeiling && topBlock)
{
yAbs = depth;
light = getLightValue(chunk, xAbs,yAbs + MIN_WORLD_HEIGHT, zAbs, true, hasSkyLight, true);
color = generateLodColor(chunk, config, xAbs, yAbs, zAbs);
}
else
{
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, depth, color, lightSky, lightBlock, generation, isDefault);
topBlock = false;
yAbs = depth - 1;
count++;
boolean topBlock = true;
if (y < chunk.getMinBuildHeight())
dataToMerge[0] = DataPointUtil.createVoidDataPoint(generation);
int maxConnectedLods = LodBuilder.config.client().graphics().quality().getVerticalQuality().maxVerticalData[0];
while (y >= chunk.getMinBuildHeight()) {
int height = determineHeightPointFrom(chunk, config, x, y, z);
// If the lod is at the default height, it must be void data
if (height < chunk.getMinBuildHeight()) {
if (topBlock) dataToMerge[0] = DataPointUtil.createVoidDataPoint(generation);
break;
}
y = height - 1;
// We search light on above air block
int depth = determineBottomPointFrom(chunk, config, x, y, z,
count < maxConnectedLods && (!hasCeiling || !topBlock));
if (hasCeiling && topBlock)
y = depth;
int light = getLightValue(chunk, x, y, z, hasCeiling, hasSkyLight, topBlock);
int color = generateLodColor(chunk, config, x, y, z);
int lightBlock = light & 0b1111;
int lightSky = (light >> 4) & 0b1111;
dataToMerge[count] = DataPointUtil.createDataPoint(height-chunk.getMinBuildHeight(), depth-chunk.getMinBuildHeight(),
color, lightSky, lightBlock, generation);
topBlock = false;
y = depth - 1;
count++;
}
return dataToMerge;
long[] result = DataPointUtil.mergeMultiData(dataToMerge, totalVerticalData, maxVerticalData);
if (result.length != maxVerticalData) throw new ArrayIndexOutOfBoundsException();
System.arraycopy(result, 0, data, dataOffset, maxVerticalData);
}
public static final LodDirection[] DIRECTIONS = new LodDirection[] {
LodDirection.UP,
LodDirection.DOWN,
LodDirection.WEST,
LodDirection.EAST,
LodDirection.NORTH,
LodDirection.SOUTH };
private boolean hasCliffFace(IChunkWrapper chunk, int x, int y, int z) {
for (LodDirection dir : DIRECTIONS) {
IBlockDetailWrapper block = chunk.getBlockDetailAtFace(x, y, z, dir);
if (block == null || !block.hasFaceCullingFor(LodDirection.OPPOSITE_DIRECTIONS[dir.ordinal()]))
return true;
}
return false;
}
/**
* 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, boolean strictEdge)
private int determineBottomPointFrom(IChunkWrapper chunk, LodBuilderConfig builderConfig, int xAbs, int yAbs, int zAbs, boolean strictEdge)
{
short depth = 0;
int colorOfBlock = 0;
int depth = chunk.getMinBuildHeight();
IBlockDetailWrapper currentBlockDetail = null;
if (strictEdge)
{
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;
IBlockDetailWrapper blockAbove = chunk.getBlockDetail(xAbs, yAbs + 1, zAbs);
if (blockAbove != null && !blockAbove.shouldRender(config.client().worldGenerator().getBlocksToAvoid()))
{ // The above block is skipped. Lets use its skipped color for currrent block
currentBlockDetail = blockAbove;
}
if (currentBlockDetail == null) currentBlockDetail = chunk.getBlockDetail(xAbs, yAbs, zAbs);
}
for (int y = yAbs - 1; y >= 0; y--)
for (int y = yAbs - 1; y >= chunk.getMinBuildHeight(); 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;
}
IBlockDetailWrapper nextBlock = chunk.getBlockDetail(xAbs, y, zAbs);
if (isLayerValidLodPoint(nextBlock)) {
if (!strictEdge) continue;
if (currentBlockDetail.equals(nextBlock)) continue;
if (!hasCliffFace(chunk, xAbs, y, zAbs)) continue;
}
depth = (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)
private int determineHeightPointFrom(IChunkWrapper chunk, LodBuilderConfig config, int xAbs, int yAbs, int zAbs)
{
//TODO find a way to skip bottom of the world
short height = 0;
if (config.useHeightmap)
height = (short) chunk.getHeightMapValue(xAbs, zAbs);
else
int height = chunk.getMinBuildHeight()-1;
for (int y = yAbs; y >= chunk.getMinBuildHeight(); y--)
{
for (int y = yAbs; y >= 0; y--)
if (isLayerValidLodPoint(chunk, xAbs, y, zAbs))
{
if (isLayerValidLodPoint(chunk, xAbs, y, zAbs))
{
height = (short) (y + 1);
break;
}
height = (y + 1);
break;
}
}
return height;
@@ -370,25 +451,24 @@ public class LodBuilder
}
else
{
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;
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
// colorInt = generateLodColor(chunk, config, xRel, yAbs - 1, zRel, blockPos);
colorInt = 0;
if (chunk.blockPosInsideChunk(x, y+1, z)) {
IBlockDetailWrapper blockAbove = chunk.getBlockDetail(x, y+1, z);
if (blockAbove != null && !blockAbove.shouldRender(config.client().worldGenerator().getBlocksToAvoid()))
{ // The above block is skipped. Lets use its skipped color for currrent block
colorInt = blockAbove.getAndResolveFaceColor(null, chunk, FACTORY.createBlockPos(x, y+1, z));
}
}
// override this block's color if there was a block above this
// and we were avoiding non-full/non-solid blocks
if (aboveColorInt != 0)
colorInt = aboveColorInt;
if (colorInt == 0) {
IBlockDetailWrapper detail = chunk.getBlockDetail(x, y, z);
colorInt = detail.getAndResolveFaceColor(null, chunk, FACTORY.createBlockPos(x, y, z));
}
}
return colorInt;
@@ -397,12 +477,8 @@ public class LodBuilder
/** Gets the light value for the given block position */
private int getLightValue(IChunkWrapper chunk, int x, int y, int z, boolean hasCeiling, boolean hasSkyLight, boolean topBlock)
{
int skyLight = 0;
int skyLight;
int blockLight;
// 1 means the lighting is a guess
int isDefault = 0;
IWorldWrapper world = MC.getWrappedServerWorld();
int blockBrightness = chunk.getEmittedBrightness(x, y, z);
// get the air block above or below this block
@@ -411,69 +487,60 @@ public class LodBuilder
else
y++;
blockLight = chunk.getBlockLight(x, y, z);
skyLight = hasSkyLight ? chunk.getSkyLight(x, y, z) : 0;
if (world != null)
if (blockLight == -1 || skyLight == -1)
{
// server world sky light (always accurate)
blockLight = world.getBlockLight(x,y,z);
if (topBlock && !hasCeiling && hasSkyLight)
skyLight = DEFAULT_MAX_LIGHT;
IWorldWrapper world = MC.getWrappedServerWorld();
if (world != null)
{
// server world sky light (always accurate)
blockLight = world.getBlockLight(x, y, z);
if (topBlock && !hasCeiling && hasSkyLight)
skyLight = DEFAULT_MAX_LIGHT;
else
skyLight = hasSkyLight ? world.getSkyLight(x, y, z) : 0;
if (!topBlock && skyLight == 15)
{
// we are on predicted terrain, and we don't know what the light here is,
// lets just take a guess
skyLight = 12;
}
}
else
{
if (hasSkyLight)
skyLight = world.getSkyLight(x,y,z);
//else
// skyLight = 0;
}
if (!topBlock && skyLight == 15)
{
// we are on predicted terrain, and we don't know what the light here is,
// lets just take a guess
if (y >= MC.getWrappedClientWorld().getSeaLevel() - 5)
world = MC.getWrappedClientWorld();
if (world == null)
{
blockLight = 0;
skyLight = 12;
isDefault = 1;
}
else
skyLight = 0;
}
}
else
{
world = MC.getWrappedClientWorld();
if (world==null)
{
blockLight = 0;
skyLight = 12;
isDefault = 1;
}
else
{
// client world sky light (almost never accurate)
blockLight = world.getBlockLight(x,y,z);
// estimate what the lighting should be
if (hasSkyLight || !hasCeiling)
{
if (topBlock)
skyLight = DEFAULT_MAX_LIGHT;
else
// client world sky light (almost never accurate)
blockLight = world.getBlockLight(x, y, z);
// estimate what the lighting should be
if (hasSkyLight || !hasCeiling)
{
if (hasSkyLight)
skyLight = world.getSkyLight(x,y,z);
//else
// skyLight = 0;
if (!chunk.isLightCorrect() && (skyLight == 0 || skyLight == 15))
if (topBlock)
skyLight = DEFAULT_MAX_LIGHT;
else
{
// we don't know what the light here is,
// lets just take a guess
if (y >= MC.getWrappedClientWorld().getSeaLevel() - 5)
if (hasSkyLight)
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
skyLight = 12;
isDefault = 1;
}
else
skyLight = 0;
}
}
}
@@ -481,68 +548,21 @@ public class LodBuilder
}
blockLight = LodUtil.clamp(0, Math.max(blockLight, blockBrightness), DEFAULT_MAX_LIGHT);
return blockLight + (skyLight << 4) + (isDefault << 8);
return blockLight + (skyLight << 4);
}
/** Returns a color int for the given block. */
private int getColorForBlock(IChunkWrapper chunk, int x, int y, int z)
/** Is the block at the given blockPos a valid LOD point? */
private boolean isLayerValidLodPoint(IBlockDetailWrapper blockDetail)
{
int colorOfBlock;
int colorInt;
IBlockShapeWrapper blockShapeWrapper = chunk.getBlockShapeWrapper(x, y, z);
if (blockShapeWrapper == null || blockShapeWrapper.isToAvoid())
return 0;
IBlockColorWrapper blockColorWrapper;
if (chunk.isWaterLogged(x, y, z))
blockColorWrapper = BLOCK_COLOR.getWaterColor();
else
blockColorWrapper = chunk.getBlockColorWrapper(x, y, z);
colorOfBlock = blockColorWrapper.getColor();
if (blockColorWrapper.hasTint())
{
IBiomeWrapper biome = chunk.getBiome(x, y, z);
int tintValue;
if (blockColorWrapper.hasGrassTint())
// grass and green plants
tintValue = biome.getGrassTint(0,0);
else if (blockColorWrapper.hasFolliageTint())
tintValue = biome.getFolliageTint();
else
//we can reintroduce this with the wrappers
tintValue = biome.getWaterTint();
colorInt = ColorUtil.multiplyRGBcolors(tintValue | 0xFF000000, colorOfBlock);
}
else
colorInt = colorOfBlock;
return colorInt;
BlocksToAvoid avoid = config.client().worldGenerator().getBlocksToAvoid();
return blockDetail != null && blockDetail.shouldRender(avoid);
}
/** Is the block at the given blockPos a valid LOD point? */
private boolean isLayerValidLodPoint(IChunkWrapper chunk, int x, int y, int z)
{
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(x, y, z);
if (block == null) return false;
return !block.isToAvoid()
&& !(nonFullAvoidance && block.isNonFull())
&& !(noCollisionAvoidance && block.hasNoCollision());
BlocksToAvoid avoid = config.client().worldGenerator().getBlocksToAvoid();
IBlockDetailWrapper block = chunk.getBlockDetail(x, y, z);
return block != null && block.shouldRender(avoid);
}
}
@@ -1,8 +1,8 @@
/*
* This file is part of the Distant Horizon mod (formerly the LOD Mod),
* This file is part of the Distant Horizons mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020 James Seibel
* Copyright (C) 2020-2022 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
@@ -39,6 +39,7 @@ public class LodBuilderConfig
public boolean useSolidBlocksInColorGen;
/** default: server */
public DistanceGenerationMode distanceGenerationMode;
public boolean quickFillWithVoid;
/**
* default settings for a normal chunk <br>
@@ -47,49 +48,18 @@ public class LodBuilderConfig
* useSolidBlocksInColorGen = true <br>
* generationMode = Server <br>
*/
public LodBuilderConfig()
public LodBuilderConfig(DistanceGenerationMode newDistanceGenerationMode)
{
useHeightmap = false;
useBiomeColors = false;
useSolidBlocksInColorGen = true;
distanceGenerationMode = DistanceGenerationMode.FULL;
}
/**
* @param newUseHeightmap default = false
* @param newUseBiomeColors default = false
* @param newUseSolidBlocksInBiomeColor default = true
* @param newDistanceGenerationMode default = Server
*/
public LodBuilderConfig(boolean newUseHeightmap, boolean newUseBiomeColors,
boolean newUseSolidBlocksInBiomeColor, DistanceGenerationMode newDistanceGenerationMode)
{
useHeightmap = newUseHeightmap;
useBiomeColors = newUseBiomeColors;
useSolidBlocksInColorGen = newUseSolidBlocksInBiomeColor;
quickFillWithVoid = false;
distanceGenerationMode = newDistanceGenerationMode;
}
/**
* @param newUseHeightmap default = false
* @param newUseBiomeColors default = false
* @param newUseSolidBlocksInBiomeColor default = true
*/
public LodBuilderConfig(boolean newUseHeightmap, boolean newUseBiomeColors, boolean newUseSolidBlocksInBiomeColor)
{
this();
useHeightmap = newUseHeightmap;
useBiomeColors = newUseBiomeColors;
useSolidBlocksInColorGen = newUseSolidBlocksInBiomeColor;
distanceGenerationMode = newUseHeightmap ? DistanceGenerationMode.BIOME_ONLY_SIMULATE_HEIGHT : DistanceGenerationMode.BIOME_ONLY;
}
/**
* @param newDistanceGenerationMode default = Server
*/
public LodBuilderConfig(DistanceGenerationMode newDistanceGenerationMode)
{
this();
distanceGenerationMode = newDistanceGenerationMode;
public static LodBuilderConfig getFillVoidConfig() {
LodBuilderConfig config = new LodBuilderConfig(DistanceGenerationMode.NONE);
config.quickFillWithVoid = true;
return config;
}
}
@@ -1,8 +1,8 @@
/*
* This file is part of the Distant Horizon mod (formerly the LOD Mod),
* This file is part of the Distant Horizons mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020 James Seibel
* Copyright (C) 2020-2022 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,23 +17,19 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.lod.core.wrapperInterfaces.block;
package com.seibel.lod.core.builders.lodBuilding.bufferBuilding;
/**
* EastWest <Br>
* NorthSouthOrUpDown
*
* @author James Seibel
* @version 11-20-2021
* @version 2022-4-9
*/
public interface IBlockShapeWrapper
public enum BufferMergeDirectionEnum
{
boolean ofBlockToAvoid();
EastWest,
//-----------------//
//Avoidance getters//
//-----------------//
boolean isNonFull();
boolean hasNoCollision();
boolean isToAvoid();
}
/** NorthSouth and UpDown are merged since */
NorthSouthOrUpDown
}
@@ -0,0 +1,281 @@
/*
* This file is part of the Distant Horizons mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020-2022 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.lodBuilding.bufferBuilding;
import com.seibel.lod.core.enums.LodDirection;
import com.seibel.lod.core.util.ColorUtil;
import static com.seibel.lod.core.render.LodRenderer.EVENT_LOGGER;
/**
* Represents a renderable quad.
*
* @author James Seibel
* @author ?
* @version 4-9-2022
*/
public final class BufferQuad
{
final short x;
final short y;
final short z;
short widthEastWest;
/** This is both North/South and Up/Down since the merging logic is the same either way */
short widthNorthSouthOrUpDown;
final int color;
final byte skyLight;
final byte blockLight;
final LodDirection direction;
boolean hasError = false;
BufferQuad(short x, short y, short z, short widthEastWest, short widthNorthSouthOrUpDown,
int color, byte skylight, byte blocklight,
LodDirection direction)
{
if (widthEastWest == 0 || widthNorthSouthOrUpDown == 0)
throw new IllegalArgumentException("Size 0 quad!");
if (widthEastWest < 0 || widthNorthSouthOrUpDown < 0)
throw new IllegalArgumentException("Negative sized quad!");
this.x = x;
this.y = y;
this.z = z;
this.widthEastWest = widthEastWest;
this.widthNorthSouthOrUpDown = widthNorthSouthOrUpDown;
this.color = color;
this.skyLight = skylight;
this.blockLight = blocklight;
this.direction = direction;
}
/** a rough but fast calculation */
double calculateDistance(double relativeX, double relativeY, double relativeZ)
{
return Math.pow(relativeX - x, 2) + Math.pow(relativeY - y, 2) + Math.pow(relativeZ - z, 2);
}
/** compares this quad's position to the given quad */
public int compare(BufferQuad quad, BufferMergeDirectionEnum compareDirection)
{
if (direction != quad.direction)
throw new IllegalArgumentException("The other quad is not in the same direction: " + quad.direction + " vs " + direction);
if (compareDirection == BufferMergeDirectionEnum.EastWest)
{
switch (direction.getAxis())
{
case X:
return threeDimensionalCompare(x, y, z, quad.x, quad.y, quad.z);
case Y:
return threeDimensionalCompare(y, z, x, quad.y, quad.z, quad.x);
case Z:
return threeDimensionalCompare(z, y, x, quad.z, quad.y, quad.x);
default:
throw new IllegalArgumentException("Invalid Axis enum: " + direction.getAxis());
}
}
else // if ()
{
switch (direction.getAxis())
{
case X:
return threeDimensionalCompare(x, z, y, quad.x, quad.z, quad.y);
case Y:
return threeDimensionalCompare(y, x, z, quad.y, quad.x, quad.z);
case Z:
return threeDimensionalCompare(z, x, y, quad.z, quad.x, quad.y);
default:
throw new IllegalArgumentException("Invalid Axis enum: " + direction.getAxis());
}
}
}
/**
* Compares two 3D points A and B. <br>
* The X, Y, and Z coordinates can be passed into parameters 0, 1, and 2 in any order
* provided they are in the same order for both A and B. <br>
* With the 0th parameter being the most significant when comparing.
*/
private static int threeDimensionalCompare(short a0, short a1, short a2, short b0, short b1, short b2)
{
long a = (long) a0 << 48 | (long) a1 << 32 | (long) a2 << 16;
long b = (long) b0 << 48 | (long) b1 << 32 | (long) b2 << 16;
return Long.compare(a, b);
}
/**
* Attempts to merge the given quad into this one.
* @returns true if the quads were merged, false otherwise.
*/
public boolean tryMerge(BufferQuad quad, BufferMergeDirectionEnum mergeDirection)
{
if (quad.hasError || this.hasError) return false;
// only merge quads that are in the same direction
if (direction != quad.direction)
return false;
// make sure these quads share the same perpendicular axis
if ((mergeDirection == BufferMergeDirectionEnum.EastWest && this.y != quad.y) ||
(mergeDirection == BufferMergeDirectionEnum.NorthSouthOrUpDown && this.x != quad.x))
{
return false;
}
// get the position of each quad to compare against
short thisPerpendicularCompareStartPos; // edge perpendicular to the merge direction
short thisParallelCompareStartPos; // edge parallel to the merge direction
short otherPerpendicularCompareStartPos;
short otherParallelCompareStartPos;
switch (this.direction.getAxis())
{
default: // shouldn't normally happen, just here to make the compiler happy
case X:
if (mergeDirection == BufferMergeDirectionEnum.EastWest)
{
thisPerpendicularCompareStartPos = this.z;
thisParallelCompareStartPos = this.x;
otherPerpendicularCompareStartPos = quad.z;
otherParallelCompareStartPos = quad.x;
}
else //if (mergeDirection == MergeDirection.NorthSouthOrUpDown)
{
thisPerpendicularCompareStartPos = this.y;
thisParallelCompareStartPos = this.z;
otherPerpendicularCompareStartPos = quad.y;
otherParallelCompareStartPos = quad.z;
}
break;
case Y:
if (mergeDirection == BufferMergeDirectionEnum.EastWest)
{
thisPerpendicularCompareStartPos = this.x;
thisParallelCompareStartPos = this.z;
otherPerpendicularCompareStartPos = quad.x;
otherParallelCompareStartPos = quad.z;
}
else //if (mergeDirection == MergeDirection.NorthSouthOrUpDown)
{
thisPerpendicularCompareStartPos = this.z;
thisParallelCompareStartPos = this.y;
otherPerpendicularCompareStartPos = quad.z;
otherParallelCompareStartPos = quad.y;
}
break;
case Z:
if (mergeDirection == BufferMergeDirectionEnum.EastWest)
{
thisPerpendicularCompareStartPos = this.x;
thisParallelCompareStartPos = this.z;
otherPerpendicularCompareStartPos = quad.x;
otherParallelCompareStartPos = quad.z;
}
else //if (mergeDirection == MergeDirection.NorthSouthOrUpDown)
{
thisPerpendicularCompareStartPos = this.y;
thisParallelCompareStartPos = this.z;
otherPerpendicularCompareStartPos = quad.y;
otherParallelCompareStartPos = quad.z;
}
break;
}
// get the width of this quad in the relevant axis
short thisPerpendicularCompareWidth;
short thisParallelCompareWidth;
short otherParallelCompareWidth;
if (mergeDirection == BufferMergeDirectionEnum.EastWest)
{
thisPerpendicularCompareWidth = this.widthEastWest;
thisParallelCompareWidth = this.widthNorthSouthOrUpDown;
otherParallelCompareWidth = quad.widthNorthSouthOrUpDown;
}
else
{
thisPerpendicularCompareWidth = this.widthNorthSouthOrUpDown;
thisParallelCompareWidth = this.widthEastWest;
otherParallelCompareWidth = quad.widthEastWest;
}
// FIXME: TEMP: Hard limit for width
if (thisPerpendicularCompareWidth >= 16) return false;
if (Math.floorDiv(otherPerpendicularCompareStartPos, 16)
!= Math.floorDiv(thisPerpendicularCompareStartPos, 16)) return false;
// check if these quads are adjacent
if (thisPerpendicularCompareStartPos + thisPerpendicularCompareWidth < otherPerpendicularCompareStartPos ||
thisParallelCompareStartPos != otherParallelCompareStartPos)
{
// these quads aren't adjacent, they can't be merged
return false;
}
else if (thisPerpendicularCompareStartPos + thisPerpendicularCompareWidth > otherPerpendicularCompareStartPos)
{
// these quads are overlapping, they can't be merged
EVENT_LOGGER.warn("Overlapping quads detected!");
quad.hasError = true;
this.hasError = true;
return false;
}
// only merge quads that have the same width edges
if (thisParallelCompareWidth != otherParallelCompareWidth)
{
return false;
}
// do the quads' color, light, etc. match?
if (color != quad.color ||
skyLight != quad.skyLight ||
blockLight != quad.blockLight)
{
// we can only merge identically colored/lit quads
return false;
}
// merge the two quads
if (mergeDirection == BufferMergeDirectionEnum.NorthSouthOrUpDown)
{
widthNorthSouthOrUpDown += quad.widthNorthSouthOrUpDown;
}
else // if (mergeDirection == MergeDirection.EastWest)
{
widthEastWest += quad.widthEastWest;
}
// merge successful
return true;
}
}
@@ -0,0 +1,106 @@
/*
* This file is part of the Distant Horizons mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020-2022 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.lodBuilding.bufferBuilding;
import com.seibel.lod.core.enums.rendering.DebugMode;
import com.seibel.lod.core.handlers.dependencyInjection.SingletonHandler;
import com.seibel.lod.core.objects.opengl.LodBox;
import com.seibel.lod.core.util.ColorUtil;
import com.seibel.lod.core.util.DataPointUtil;
import com.seibel.lod.core.util.LevelPosUtil;
import com.seibel.lod.core.util.LodUtil;
import com.seibel.lod.core.wrapperInterfaces.config.ILodConfigWrapperSingleton;
/**
* Builds LODs as rectangular prisms.
* @author James Seibel
* @version 3-19-2022
*/
public class CubicLodTemplate
{
private static final ILodConfigWrapperSingleton CONFIG = SingletonHandler.get(ILodConfigWrapperSingleton.class);
public static void addLodToBuffer(long data, long topData, long botData, long[][][] adjData,
boolean[] adjFillBlack, byte detailLevel,int offsetPosX, int offsetOosZ, LodQuadBuilder quadBuilder, DebugMode debugging)
{
short width = (short) (1 << detailLevel);
short x = (short) LevelPosUtil.convert(detailLevel, offsetPosX, LodUtil.BLOCK_DETAIL_LEVEL);
short y = DataPointUtil.getDepth(data);
short z = (short) LevelPosUtil.convert(detailLevel, offsetOosZ, LodUtil.BLOCK_DETAIL_LEVEL);
short dy = (short) (DataPointUtil.getHeight(data) - y);
if (dy == 0)
return;
if (dy < 0)
{
throw new IllegalArgumentException("Negative y size for the data! Data: " + DataPointUtil.toString(data));
}
int color;
boolean fullBright = false;
switch (debugging) {
case OFF:
case SHOW_WIREFRAME:
{
float saturationMultiplier = (float)CONFIG.client().graphics().advancedGraphics().getSaturationMultiplier();
float brightnessMultiplier = (float)CONFIG.client().graphics().advancedGraphics().getBrightnessMultiplier();
if (saturationMultiplier == 1.0 && brightnessMultiplier == 1.0) {
color = DataPointUtil.getColor(data);
} else {
float[] ahsv = ColorUtil.argbToAhsv(DataPointUtil.getColor(data));
color = ColorUtil.ahsvToArgb(ahsv[0], ahsv[1], ahsv[2] * saturationMultiplier, ahsv[3] * brightnessMultiplier);
//ApiShared.LOGGER.info("Raw color:[{}], AHSV:{}, Out color:[{}]",
// ColorUtil.toString(DataPointUtil.getColor(data)),
// ahsv, ColorUtil.toString(color));
}
break;
}
case SHOW_DETAIL:
case SHOW_DETAIL_WIREFRAME:
{
color = LodUtil.DEBUG_DETAIL_LEVEL_COLORS[detailLevel];
fullBright = true;
break;
}
case SHOW_GENMODE:
case SHOW_GENMODE_WIREFRAME:
{
color = LodUtil.DEBUG_DETAIL_LEVEL_COLORS[DataPointUtil.getGenerationMode(data)];
fullBright = true;
break;
}
case SHOW_OVERLAPPING_QUADS:
case SHOW_OVERLAPPING_QUADS_WIREFRAME:
{
color = ColorUtil.WHITE;
fullBright = true;
break;
}
default:
throw new IllegalArgumentException("Unknown debug mode: " + debugging);
}
LodBox.addBoxQuadsToBuilder(quadBuilder, // buffer
width, dy, width, // setWidth
x, y, z, // setOffset
color, // setColor
DataPointUtil.getLightSky(data), // setSkyLights
fullBright ? 15 : DataPointUtil.getLightBlock(data), // setBlockLights
topData, botData, adjData, adjFillBlack); // setAdjData
}
}
@@ -0,0 +1,355 @@
/*
* This file is part of the Distant Horizons mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020-2022 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.lodBuilding.bufferBuilding;
import java.time.Duration;
import java.util.concurrent.*;
import java.util.concurrent.locks.ReentrantLock;
import com.seibel.lod.core.api.ApiShared;
import com.seibel.lod.core.handlers.dependencyInjection.SingletonHandler;
import com.seibel.lod.core.logging.SpamReducedLogger;
import com.seibel.lod.core.objects.Pos2D;
import com.seibel.lod.core.objects.lod.LodDimension;
import com.seibel.lod.core.objects.lod.RegionPos;
import com.seibel.lod.core.objects.opengl.RenderRegion;
import com.seibel.lod.core.render.LodRenderer;
import com.seibel.lod.core.render.objects.GLBuffer;
import com.seibel.lod.core.util.*;
import com.seibel.lod.core.util.gridList.MovableGridRingList;
import com.seibel.lod.core.wrapperInterfaces.config.ILodConfigWrapperSingleton;
import com.seibel.lod.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper;
/**
* This object creates the buffers that are rendered by the LodRenderer.
*
* @author James Seibel
* @version 12-9-2021
*/
public class LodBufferBuilderFactory {
// TODO: Do some Perf logging of Buffer Building
public static final boolean ENABLE_BUFFER_PERF_LOGGING = false;
public static final boolean ENABLE_EVENT_LOGGING = false;
public static final boolean ENABLE_LAG_SPIKE_LOGGING = false;
public static final long LAG_SPIKE_THRESOLD_NS = TimeUnit.NANOSECONDS.convert(16, TimeUnit.MILLISECONDS);
public static class LagSpikeCatcher {
long timer = System.nanoTime();
public LagSpikeCatcher() {
}
public void end(String source) {
if (!ENABLE_LAG_SPIKE_LOGGING)
return;
timer = System.nanoTime() - timer;
if (timer > LAG_SPIKE_THRESOLD_NS) {
ApiShared.LOGGER.info("LagSpikeCatcher: " + source + " took " + Duration.ofNanos(timer) + "!");
}
}
}
private static final ILodConfigWrapperSingleton CONFIG = SingletonHandler.get(ILodConfigWrapperSingleton.class);
private static final IMinecraftClientWrapper MC = SingletonHandler.get(IMinecraftClientWrapper.class);
/** The thread used to generate new LODs off the main thread. */
private static LodThreadFactory mainGenThreadFactory = new LodThreadFactory(
LodBufferBuilderFactory.class.getSimpleName() + " - main", Thread.NORM_PRIORITY - 2);
public static ExecutorService mainGenThread = Executors.newSingleThreadExecutor(mainGenThreadFactory);
/** The threads used to generate buffers. */
private static LodThreadFactory bufferBuilderThreadFactory = new LodThreadFactory("BufferBuilder",
Thread.NORM_PRIORITY - 2);
private static int previousBufferBuilderThreads = CONFIG.client().advanced().threading().getNumberOfBufferBuilderThreads();
public static ExecutorService bufferBuilderThreads = Executors.newFixedThreadPool(previousBufferBuilderThreads, bufferBuilderThreadFactory);
/** The thread used to upload buffers. */
private static LodThreadFactory bufferUploadThreadFactory = new LodThreadFactory(
LodBufferBuilderFactory.class.getSimpleName() + " - upload", Thread.NORM_PRIORITY - 1);
public static ExecutorService bufferUploadThread = Executors.newSingleThreadExecutor(bufferUploadThreadFactory);
/**
* When buffers are first created they are allocated to this size (in Bytes).
* This size will be too small, more than likely. The buffers will be expanded
* when need be to fit the larger sizes.
*/
public static final int DEFAULT_MEMORY_ALLOCATION = (LodUtil.LOD_VERTEX_FORMAT.getByteSize() * 3) * 8;
public static final int QUADS_BYTE_SIZE = LodUtil.LOD_VERTEX_FORMAT.getByteSize() * (LodRenderer.ENABLE_IBO ? 4 : 6);
public static final int MAX_TRIANGLES_PER_BUFFER = (1024 * 1024 * 1)
/ (LodUtil.LOD_VERTEX_FORMAT.getByteSize() * 3);
public static final int MAX_QUADS_PER_BUFFER = (1024 * 1024 * 1) / QUADS_BYTE_SIZE;
public static final int FULL_SIZED_BUFFER = MAX_QUADS_PER_BUFFER * QUADS_BYTE_SIZE;
public static int skyLightPlayer = 15;
public MovableGridRingList<RenderRegion> renderRegions = null;
/** Size of the buffer builders in bytes last time we created them */
public int previousBufferSize = 0;
/** Width of the dimension in regions last time we created the buffers */
public int previousRegionWidth = 0;
private boolean builderThreadRunning = false;
public ReentrantLock regionsListLock = new ReentrantLock();
public LodBufferBuilderFactory() {
}
public void setRegionNeedRegen(int regionX, int regionZ) {
MovableGridRingList<RenderRegion> r = renderRegions;
if (r==null) return;
RenderRegion rr = r.get(regionX, regionZ);
if (rr==null) return;
rr.setNeedRegen();
}
/**
* Create a thread to asynchronously generate LOD buffers centered around the
* given camera X and Z. <br>
* This method will write to the drawable near and far buffers. <br>
* After the buildable buffers have been generated they must be swapped with the
* drawable buffers in the LodRenderer to be drawn.
*
* @return whether it has started a generation task or is blocked
*/
public boolean updateAndSwapLodBuffersAsync(LodRenderer renderer, LodDimension lodDim, int playerX, int playerY,
int playerZ, boolean fullRegen) {
// only allow one generation process to happen at a time
if (builderThreadRunning) return false;
builderThreadRunning = true;
Runnable thread = () -> generateLodBuffersThread(renderer, lodDim, playerX, playerY, playerZ, fullRegen);
mainGenThread.execute(thread);
return true;
}
private void updateRingList(int playerX, int playerZ, int regionWidth) throws InterruptedException {
if (renderRegions != null && regionWidth != renderRegions.getSize()) {
renderRegions.clear(RenderRegion::close);
renderRegions = null;
}
LodUtil.checkInterrupts();
if (renderRegions == null) {
renderRegions = new MovableGridRingList<RenderRegion>(regionWidth/2,
LevelPosUtil.getRegion(LodUtil.BLOCK_DETAIL_LEVEL, playerX),
LevelPosUtil.getRegion(LodUtil.BLOCK_DETAIL_LEVEL, playerZ));
ApiShared.LOGGER.info("============Render Regions rebuilt============");
} else {
renderRegions.move(LevelPosUtil.getRegion(LodUtil.BLOCK_DETAIL_LEVEL, playerX),
LevelPosUtil.getRegion(LodUtil.BLOCK_DETAIL_LEVEL, playerZ), RenderRegion::close);
}
}
private void resetThreadPools(boolean dumpThread) {
if (dumpThread) {
bufferBuilderThreadFactory.dumpAllThreadStacks();
bufferUploadThreadFactory.dumpAllThreadStacks();
}
bufferBuilderThreads.shutdownNow();
bufferUploadThread.shutdownNow();
previousBufferBuilderThreads = CONFIG.client().advanced().threading().getNumberOfBufferBuilderThreads();
bufferBuilderThreadFactory = new LodThreadFactory("BufferBuilder", Thread.NORM_PRIORITY - 2);
bufferBuilderThreads = Executors.newFixedThreadPool(previousBufferBuilderThreads, bufferBuilderThreadFactory);
bufferUploadThreadFactory = new LodThreadFactory(
LodBufferBuilderFactory.class.getSimpleName() + " - upload", Thread.NORM_PRIORITY - 1);
bufferUploadThread = Executors.newSingleThreadExecutor(bufferUploadThreadFactory);
}
private void generateLodBuffersThread(LodRenderer renderer, LodDimension lodDim, int playerX, int playerY,
int playerZ, boolean fullRegen) {
//ArrayList<RenderRegion> regionsToCleanup = new ArrayList<RenderRegion>();
try {
if (previousBufferBuilderThreads != CONFIG.client().advanced().threading().getNumberOfBufferBuilderThreads())
resetThreadPools(false);
regionsListLock.lockInterruptibly();
if (ENABLE_EVENT_LOGGING)
ApiShared.LOGGER.info("BufferBuilderStarter locked the region lock! LodDim: [{}], RenderRegion: [{}]",
lodDim, renderRegions==null ? "NULL" : renderRegions.toString());
long startTime = System.currentTimeMillis();
boolean doCaveCulling = CONFIG.client().graphics().advancedGraphics().getEnableCaveCulling();
doCaveCulling &= !lodDim.dimension.hasCeiling();
doCaveCulling &= lodDim.dimension.hasSkyLight();
doCaveCulling &= playerY > CONFIG.client().graphics().advancedGraphics().getCaveCullingHeight() + 5;
int playerSkylight = MC.getPlayerSkylight(); // if fail returns -1.
doCaveCulling &= playerSkylight > 7;
try {
updateRingList(playerX, playerZ, lodDim.getWidth());
// ================================//
// create the nodeToRenderThreads //
// ================================//
skyLightPlayer = MC.getWrappedClientWorld().getSkyLight(playerX, playerY, playerZ);
// int minCullingRange =
// SingletonHandler.get(ILodConfigWrapperSingleton.class).client().graphics().advancedGraphics().getBacksideCullingRange();
// int cullingRangeX = Math.max((int)(1.5 * Math.abs(lastX - playerX)),
// minCullingRange);
// int cullingRangeZ = Math.max((int)(1.5 * Math.abs(lastZ - playerZ)),
// minCullingRange);
Pos2D minPos = renderRegions.getMinInRange();
Pos2D maxPos = renderRegions.getMaxInRange();
CompletableFuture<Void> future = CompletableFuture.completedFuture(null);
try {
int numOfJobs = 0;
for (int regX = minPos.x; regX < maxPos.x; regX++) {
for (int regZ = minPos.y; regZ < maxPos.y; regZ++) {
RenderRegion r = renderRegions.get(regX, regZ);
RegionPos regPos = new RegionPos(regX, regZ);
if (r != null && !r.canRender(lodDim, regPos)) {
renderRegions.set(regX, regZ, null);
r.close();
r = null;
}
if (r == null) {
r = new RenderRegion(regPos, lodDim);
renderRegions.set(regX, regZ, r);
}
CompletableFuture<Void> newFuture =
r.updateStatus(bufferUploadThread, bufferBuilderThreads, fullRegen,
playerX, playerZ, doCaveCulling).orElse(null);
if (newFuture != null) {
future = CompletableFuture.allOf(future, newFuture);
numOfJobs++;
}
}
}
// ================================//
// wait on completion //
// ================================//
long executeStart = System.currentTimeMillis();
try {
future.get(1, TimeUnit.MINUTES);
} catch (CancellationException ce) {
throw new InterruptedException("Future interrupted");
} catch (ExecutionException ee) {
ApiShared.LOGGER.error("LodBufferBuilder ran into trouble: ", ee.getCause());
}
long executeEnd = System.currentTimeMillis();
long endTime = System.currentTimeMillis();
long buildTime = endTime - startTime;
long executeTime = executeEnd - executeStart;
if (ENABLE_BUFFER_PERF_LOGGING)
ApiShared.LOGGER.info("Thread Build&Upload(" + numOfJobs + "/"
+ (lodDim.getWidth() * lodDim.getWidth()) + (fullRegen ? "FULL" : "") + ") time: " + buildTime
+ " ms" + '\n' + "thread execute time: " + executeTime + " ms");
} catch (InterruptedException ie) {
resetThreadPools(false);
try {
future.get();
} catch (Throwable ignored) {
}
} catch (TimeoutException te) {
ApiShared.LOGGER.error("LodBufferBuilder timed out: ", te);
resetThreadPools(true);
}
}
catch (RuntimeException e) {
ApiShared.LOGGER.error("\"LodNodeBufferBuilder.generateLodBuffersAsync\" ran into trouble: ", e);
}
}
catch (InterruptedException ignored) { }
finally {
regionsListLock.unlock();
if (ENABLE_EVENT_LOGGING) ApiShared.LOGGER.info("BufferBuilderStarter unlocked the region lock!");
builderThreadRunning = false;
}
}
private final SpamReducedLogger ramLogger = new SpamReducedLogger(1);
public void dumpBufferMemoryUsage() {
if (!ramLogger.canMaybeLog())
return;
ramLogger.info("Dumping Ram Usage for buffer usage...");
StatsMap statsMap = new StatsMap();
if (renderRegions == null) {
ramLogger.info("Buildable VBOs are null!");
} else {
for (RenderRegion buffers : renderRegions) {
if (buffers == null)
continue;
buffers.debugDumpStats(statsMap);
}
}
statsMap.incStat("Total Buffers", GLBuffer.count.get());
ramLogger.info("================================================");
ramLogger.info("Stats: {}", statsMap);
ramLogger.info("================================================");
ramLogger.incLogTries();
}
// ===============================//
// BufferBuilder related methods //
// ===============================//
/**
* Sets the buffers and Vbos to null, forcing them to be recreated <br>
* and destroys any bound OpenGL objects. <br>
* <br>
* <p>
* May have to wait for the bufferLock to open.
*/
public void destroyBuffers() {
ApiShared.LOGGER.info("Destroying LodBufferBuilder...");
mainGenThread.shutdownNow();
mainGenThread = Executors.newSingleThreadExecutor(mainGenThreadFactory);
boolean locked = false;
try {
locked = regionsListLock.tryLock(5, TimeUnit.SECONDS); // FIXME: For some reason we get a deadlock here sometimes
} catch (InterruptedException ignored) {}
try {
if (renderRegions != null) renderRegions.clear(RenderRegion::close);
renderRegions = null;
} finally {
if (locked) regionsListLock.unlock();
}
ApiShared.LOGGER.info("LodBufferBuilder destroyed.");
}
/** Get the newly created VBOs
* Note: SHOULD NEVER MODIFY THE LIST */
public MovableGridRingList<RenderRegion> getRenderRegions() {
return renderRegions;
}
@Deprecated
public void triggerReset() {
}
}
@@ -0,0 +1,539 @@
/*
* This file is part of the Distant Horizons mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020-2022 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.lodBuilding.bufferBuilding;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.ListIterator;
import com.seibel.lod.core.builders.lodBuilding.LodBuilder;
import com.seibel.lod.core.enums.LodDirection;
import com.seibel.lod.core.enums.LodDirection.Axis;
import com.seibel.lod.core.enums.config.GpuUploadMethod;
import com.seibel.lod.core.handlers.dependencyInjection.SingletonHandler;
import com.seibel.lod.core.render.LodRenderer;
import com.seibel.lod.core.render.objects.GLVertexBuffer;
import com.seibel.lod.core.util.ColorUtil;
import com.seibel.lod.core.util.LodUtil;
import com.seibel.lod.core.wrapperInterfaces.config.ILodConfigWrapperSingleton;
import static com.seibel.lod.core.render.LodRenderer.EVENT_LOGGER;
/**
* Used to create the quads before they are converted to renderable buffers.
*
* @version 2022-4-9
*/
public class LodQuadBuilder
{
static final ILodConfigWrapperSingleton CONFIG = SingletonHandler.get(ILodConfigWrapperSingleton.class);
public final boolean skipQuadsWithZeroSkylight;
public final short skyLightCullingBelow;
final ArrayList<BufferQuad>[] quads = (ArrayList<BufferQuad>[]) new ArrayList[6];
public static final int[][][] DIRECTION_VERTEX_IBO_QUAD = new int[][][]
{
// X,Z //
{ // UP
{ 1, 0 }, // 0
{ 1, 1 }, // 1
{ 0, 1 }, // 2
{ 0, 0 }, // 3
},
{ // DOWN
{ 0, 0 }, // 0
{ 0, 1 }, // 1
{ 1, 1 }, // 2
{ 1, 0 }, // 3
},
// X,Y //
{ // NORTH
{ 0, 0 }, // 0
{ 0, 1 }, // 1
{ 1, 1 }, // 2
{ 1, 0 }, // 3
},
{ // SOUTH
{ 1, 0 }, // 0
{ 1, 1 }, // 1
{ 0, 1 }, // 2
{ 0, 0 }, // 3
},
// Z,Y //
{ // WEST
{ 0, 0 }, // 0
{ 1, 0 }, // 1
{ 1, 1 }, // 2
{ 0, 1 }, // 3
},
{ // EAST
{ 0, 1 }, // 0
{ 1, 1 }, // 1
{ 1, 0 }, // 2
{ 0, 0 }, // 3
},
};
public static final int[][][] DIRECTION_VERTEX_QUAD = new int[][][]
{
// X,Z //
{ // UP
{ 1, 0 }, // 0
{ 1, 1 }, // 1
{ 0, 1 }, // 2
{ 1, 0 }, // 0
{ 0, 1 }, // 2
{ 0, 0 }, // 3
},
{ // DOWN
{ 0, 0 }, // 0
{ 0, 1 }, // 1
{ 1, 1 }, // 2
{ 0, 0 }, // 0
{ 1, 1 }, // 2
{ 1, 0 }, // 3
},
// X,Y //
{ // NORTH
{ 0, 0 }, // 0
{ 0, 1 }, // 1
{ 1, 1 }, // 2
{ 0, 0 }, // 0
{ 1, 1 }, // 2
{ 1, 0 }, // 3
},
{ // SOUTH
{ 1, 0 }, // 0
{ 1, 1 }, // 1
{ 0, 1 }, // 2
{ 1, 0 }, // 0
{ 0, 1 }, // 2
{ 0, 0 }, // 3
},
// Z,Y //
{ // WEST
{ 0, 0 }, // 0
{ 1, 0 }, // 1
{ 1, 1 }, // 2
{ 0, 0 }, // 0
{ 1, 1 }, // 2
{ 0, 1 }, // 3
},
{ // EAST
{ 0, 1 }, // 0
{ 1, 1 }, // 1
{ 1, 0 }, // 2
{ 0, 1 }, // 0
{ 1, 0 }, // 2
{ 0, 0 }, // 3
},
};
public LodQuadBuilder(boolean enableSkylightCulling, int skyLightCullingBelow)
{
for (int i = 0; i < 6; i++)
quads[i] = new ArrayList<>();
this.skipQuadsWithZeroSkylight = enableSkylightCulling;
this.skyLightCullingBelow = (short) (skyLightCullingBelow - LodBuilder.MIN_WORLD_HEIGHT);
}
public void addQuadAdj(LodDirection dir, short x, short y, short z,
short widthEastWest, short widthNorthSouthOrUpDown,
int color, byte skylight, byte blocklight)
{
if (dir.ordinal() <= LodDirection.DOWN.ordinal())
throw new IllegalArgumentException("addQuadAdj() is only for adj direction! Not UP or Down!");
if (skipQuadsWithZeroSkylight && skylight == 0 && y < skyLightCullingBelow)
return;
quads[dir.ordinal()].add(new BufferQuad(x, y, z, widthEastWest, widthNorthSouthOrUpDown, color, skylight, blocklight, dir));
}
// XZ
public void addQuadUp(short x, short y, short z, short width, short wz, int color, byte skylight, byte blocklight)
{
if (skipQuadsWithZeroSkylight && skylight == 0 && y < skyLightCullingBelow)
return;
quads[LodDirection.UP.ordinal()].add(new BufferQuad(x, y, z, width, wz, color, skylight, blocklight, LodDirection.UP));
}
public void addQuadDown(short x, short y, short z, short width, short wz, int color, byte skylight, byte blocklight)
{
if (skipQuadsWithZeroSkylight && skylight == 0 && y < skyLightCullingBelow)
return;
quads[LodDirection.DOWN.ordinal()].add(new BufferQuad(x, y, z, width, wz, color, skylight, blocklight, LodDirection.DOWN));
}
// XY
public void addQuadN(short x, short y, short z, short width, short wy, int color, byte skylight, byte blocklight)
{
if (skipQuadsWithZeroSkylight && skylight == 0 && y < skyLightCullingBelow)
return;
quads[LodDirection.NORTH.ordinal()].add(new BufferQuad(x, y, z, width, wy, color, skylight, blocklight, LodDirection.NORTH));
}
public void addQuadS(short x, short y, short z, short width, short wy, int color, byte skylight, byte blocklight)
{
if (skipQuadsWithZeroSkylight && skylight == 0 && y < skyLightCullingBelow)
return;
quads[LodDirection.SOUTH.ordinal()].add(new BufferQuad(x, y, z, width, wy, color, skylight, blocklight, LodDirection.SOUTH));
}
// ZY
public void addQuadW(short x, short y, short z, short width, short wy, int color, byte skylight, byte blocklight)
{
if (skipQuadsWithZeroSkylight && skylight == 0 && y < skyLightCullingBelow)
return;
quads[LodDirection.WEST.ordinal()].add(new BufferQuad(x, y, z, width, wy, color, skylight, blocklight, LodDirection.WEST));
}
public void addQuadE(short x, short y, short z, short width, short wy, int color, byte skylight, byte blocklight)
{
if (skipQuadsWithZeroSkylight && skylight == 0 && y < skyLightCullingBelow)
return;
quads[LodDirection.EAST.ordinal()].add(new BufferQuad(x, y, z, width, wy, color, skylight, blocklight, LodDirection.EAST));
}
private static void putVertex(ByteBuffer bb, short x, short y, short z, int color, byte skylight, byte blocklight, int mx, int my, int mz)
{
skylight %= 16;
blocklight %= 16;
bb.putShort(x);
bb.putShort(y);
bb.putShort(z);
short meta = 0;
meta |= (skylight | (blocklight << 4));
byte mirco = 0;
// mirco offset which is a xyz 2bit value
// 0b00 = no offset
// 0b01 = positive offset
// 0b11 = negative offset
// format is: 0b00zzyyxx
if (mx != 0) mirco |= mx > 0 ? 0b01 : 0b11;
if (my != 0) mirco |= my > 0 ? 0b0100 : 0b1100;
if (mz != 0) mirco |= mz > 0 ? 0b010000 : 0b110000;
meta |= mirco << 8;
bb.putShort(meta);
byte r = (byte) ColorUtil.getRed(color);
byte g = (byte) ColorUtil.getGreen(color);
byte b = (byte) ColorUtil.getBlue(color);
byte a = (byte) ColorUtil.getAlpha(color);
bb.put(r);
bb.put(g);
bb.put(b);
bb.put(a);
}
private static void putQuad(ByteBuffer bb, BufferQuad quad)
{
int[][] quadBase = LodRenderer.ENABLE_IBO ? DIRECTION_VERTEX_IBO_QUAD[quad.direction.ordinal()] : DIRECTION_VERTEX_QUAD[quad.direction.ordinal()];
short widthEastWest = quad.widthEastWest;
short widthNorthSouth = quad.widthNorthSouthOrUpDown;
Axis axis = quad.direction.getAxis();
for (int i = 0; i < quadBase.length; i++)
{
short dx, dy, dz;
int mx, my, mz;
switch (axis)
{
case X: // ZY
dx = 0;
dy = quadBase[i][1] == 1 ? widthNorthSouth : 0;
dz = quadBase[i][0] == 1 ? widthEastWest : 0;
mx = 0;
my = quadBase[i][1] == 1 ? 1 : -1;
mz = quadBase[i][0] == 1 ? 1 : -1;
break;
case Y: // XZ
dx = quadBase[i][0] == 1 ? widthEastWest : 0;
dy = 0;
dz = quadBase[i][1] == 1 ? widthNorthSouth : 0;
mx = quadBase[i][0] == 1 ? 1 : -1;
my = 0;
mz = quadBase[i][1] == 1 ? 1 : -1;
break;
case Z: // XY
dx = quadBase[i][0] == 1 ? widthEastWest : 0;
dy = quadBase[i][1] == 1 ? widthNorthSouth : 0;
dz = 0;
mx = quadBase[i][0] == 1 ? 1 : -1;
my = quadBase[i][1] == 1 ? 1 : -1;
mz = 0;
break;
default:
throw new IllegalArgumentException("Invalid Axis enum: " + axis);
}
putVertex(bb, (short) (quad.x + dx), (short) (quad.y + dy), (short) (quad.z + dz),
quad.hasError ? ColorUtil.RED : quad.color,
quad.hasError ? 15 : quad.skyLight,
quad.hasError ? 15 : quad.blockLight,
mx, my, mz);
}
}
/** Uses Greedy meshing to merge this builder's Quads. */
public void mergeQuads()
{
long mergeCount = 0;
long preQuadsCount = getCurrentQuadsCount();
if (preQuadsCount <= 1)
return;
for (int directionIndex = 0; directionIndex < 6; directionIndex++)
{
mergeCount += mergeQuadsInternal(directionIndex, BufferMergeDirectionEnum.EastWest);
// only merge after the top has been merged
if (directionIndex == 1)
{
long pass2 = mergeQuadsInternal(directionIndex, BufferMergeDirectionEnum.NorthSouthOrUpDown);
mergeCount += pass2;
}
}
long postQuadsCount = getCurrentQuadsCount();
//if (mergeCount != 0)
EVENT_LOGGER.debug("Merged {}/{}({}) quads", mergeCount, preQuadsCount, mergeCount / (double) preQuadsCount);
}
/** Merges all of this builder's quads for the given directionIndex (up, down, left, etc.) in the given direction */
private long mergeQuadsInternal(int directionIndex, BufferMergeDirectionEnum mergeDirection)
{
if (quads[directionIndex].size() <= 1)
return 0;
quads[directionIndex].sort( (objOne, objTwo) -> objOne.compare(objTwo, mergeDirection) );
long mergeCount = 0;
ListIterator<BufferQuad> iter = quads[directionIndex].listIterator();
BufferQuad currentQuad = iter.next();
while (iter.hasNext())
{
BufferQuad nextQuad = iter.next();
if (currentQuad.tryMerge(nextQuad, mergeDirection))
{
// merge successful, attempt to merge the next quad
mergeCount++;
iter.set(null);
}
else
{
// merge fail, move on to the next quad
currentQuad = nextQuad;
}
}
quads[directionIndex].removeIf(o -> o == null);
return mergeCount;
}
public Iterator<ByteBuffer> makeVertexBuffers()
{
return new Iterator<ByteBuffer>()
{
final ByteBuffer bb = ByteBuffer.allocateDirect(LodBufferBuilderFactory.FULL_SIZED_BUFFER)
.order(ByteOrder.nativeOrder());
int dir = skipEmpty(0);
int quad = 0;
private int skipEmpty(int d)
{
while (d < 6 && quads[d].isEmpty())
d++;
return d;
}
@Override
public boolean hasNext()
{
return dir < 6;
}
@Override
public ByteBuffer next()
{
if (dir >= 6)
{
return null;
}
bb.clear();
bb.limit(LodBufferBuilderFactory.FULL_SIZED_BUFFER);
while (bb.hasRemaining() && dir < 6)
{
writeData();
}
bb.limit(bb.position());
bb.rewind();
return bb;
}
private void writeData()
{
int i = quad;
for (; i < quads[dir].size(); i++)
{
if (!bb.hasRemaining())
{
break;
}
putQuad(bb, quads[dir].get(i));
}
if (i >= quads[dir].size())
{
quad = 0;
dir++;
dir = skipEmpty(dir);
}
else
{
quad = i;
}
}
};
}
public interface BufferFiller
{
/** If true: more data needs to be filled */
boolean fill(GLVertexBuffer vbo);
}
public BufferFiller makeBufferFiller(GpuUploadMethod method)
{
return new BufferFiller()
{
int dir = 0;
int quad = 0;
public boolean fill(GLVertexBuffer vbo)
{
if (dir >= 6)
{
vbo.setVertexCount(0);
return false;
}
int numOfQuads = _countRemainingQuads();
if (numOfQuads > LodBufferBuilderFactory.MAX_QUADS_PER_BUFFER)
numOfQuads = LodBufferBuilderFactory.MAX_QUADS_PER_BUFFER;
if (numOfQuads == 0)
{
vbo.setVertexCount(0);
return false;
}
ByteBuffer bb = vbo.mapBuffer(numOfQuads * LodBufferBuilderFactory.QUADS_BYTE_SIZE, method,
LodBufferBuilderFactory.FULL_SIZED_BUFFER);
if (bb == null)
throw new NullPointerException("mapBuffer returned null");
bb.clear();
bb.limit(numOfQuads * LodBufferBuilderFactory.QUADS_BYTE_SIZE);
while (bb.hasRemaining() && dir < 6)
{
writeData(bb);
}
bb.rewind();
vbo.unmapBuffer();
vbo.setVertexCount(LodRenderer.ENABLE_IBO ? numOfQuads*4 : numOfQuads*6);
return dir < 6;
}
private int _countRemainingQuads()
{
int a = quads[dir].size() - quad;
for (int i = dir + 1; i < quads.length; i++)
{
a += quads[i].size();
}
return a;
}
private void writeData(ByteBuffer bb)
{
int startQ = quad;
int i = startQ;
for (i = startQ; i < quads[dir].size(); i++)
{
if (!bb.hasRemaining())
{
break;
}
putQuad(bb, quads[dir].get(i));
}
if (i >= quads[dir].size())
{
quad = 0;
dir++;
while (dir < 6 && quads[dir].isEmpty())
dir++;
}
else
{
quad = i;
}
}
};
}
public int getCurrentQuadsCount()
{
int i = 0;
for (ArrayList<BufferQuad> qs : quads)
i += qs.size();
return i;
}
/** Returns how many Buffers will be needed to render everything in this builder. */
public int getCurrentNeededVertexBufferCount()
{
return LodUtil.ceilDiv(getCurrentQuadsCount(), LodBufferBuilderFactory.MAX_QUADS_PER_BUFFER);
}
}
@@ -0,0 +1,266 @@
/*
* This file is part of the Distant Horizons mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2021 Tom Lee (TomTheFurry) & James Seibel (Original code)
*
* 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 com.seibel.lod.core.api.ApiShared;
import com.seibel.lod.core.builders.lodBuilding.LodBuilder;
import com.seibel.lod.core.enums.config.DistanceGenerationMode;
import com.seibel.lod.core.enums.config.GenerationPriority;
import com.seibel.lod.core.handlers.dependencyInjection.SingletonHandler;
import com.seibel.lod.core.objects.PosToGenerateContainer;
import com.seibel.lod.core.objects.lod.LodDimension;
import com.seibel.lod.core.util.LevelPosUtil;
import com.seibel.lod.core.util.LodUtil;
import com.seibel.lod.core.wrapperInterfaces.IWrapperFactory;
import com.seibel.lod.core.wrapperInterfaces.config.ILodConfigWrapperSingleton;
import com.seibel.lod.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper;
import com.seibel.lod.core.wrapperInterfaces.world.IWorldWrapper;
import com.seibel.lod.core.wrapperInterfaces.worldGeneration.AbstractBatchGenerationEnvionmentWrapper;
import com.seibel.lod.core.wrapperInterfaces.worldGeneration.AbstractBatchGenerationEnvionmentWrapper.Steps;
public class BatchGenerator {
public static final boolean ENABLE_GENERATOR_STATS_LOGGING = false;
private static final IMinecraftClientWrapper MC = SingletonHandler.get(IMinecraftClientWrapper.class);
private static final ILodConfigWrapperSingleton CONFIG = SingletonHandler.get(ILodConfigWrapperSingleton.class);
private static final IWrapperFactory FACTORY = SingletonHandler.get(IWrapperFactory.class);
public AbstractBatchGenerationEnvionmentWrapper generationGroup;
public LodDimension targetLodDim;
public static final int generationGroupSize = 4;
public static int previousThreadCount = CONFIG.client().advanced().threading()._getWorldGenerationThreadPoolSize();
private int estimatedSampleNeeded = 128;
private int estimatedPointsToQueue = 1;
public BatchGenerator(LodBuilder newLodBuilder, LodDimension newLodDimension) {
IWorldWrapper world = LodUtil.getServerWorldFromDimension(newLodDimension.dimension);
targetLodDim = newLodDimension;
generationGroup = FACTORY.createBatchGenerator(newLodBuilder, newLodDimension, world);
MC.sendChatMessage("NOTE: You are currently using Distant Horizon's Batch Chunk Pre-Generator.");
ApiShared.LOGGER.info("Batch Chunk Generator initialized");
}
@SuppressWarnings("unused")
public void queueGenerationRequests(LodDimension lodDim, LodBuilder lodBuilder) {
if (lodDim != targetLodDim) {
stop(false);
IWorldWrapper dim = LodUtil.getServerWorldFromDimension(lodDim.dimension);
generationGroup = FACTORY.createBatchGenerator(lodBuilder, lodDim, dim);
targetLodDim = lodDim;
ApiShared.LOGGER.info("1.18 Experimental Chunk Generator reinitialized");
}
DistanceGenerationMode mode = CONFIG.client().worldGenerator().getDistanceGenerationMode();
int newThreadCount = CONFIG.client().advanced().threading()._getWorldGenerationThreadPoolSize();
if (newThreadCount != previousThreadCount) {
generationGroup.resizeThreadPool(newThreadCount);
previousThreadCount = newThreadCount;
}
if (estimatedPointsToQueue < newThreadCount)
estimatedPointsToQueue = newThreadCount;
GenerationPriority priority = CONFIG.client().worldGenerator().getGenerationPriority();
if (priority == GenerationPriority.AUTO)
priority = MC.hasSinglePlayerServer() ? GenerationPriority.FAR_FIRST : GenerationPriority.NEAR_FIRST;
generationGroup.updateAllFutures();
if (!MC.hasSinglePlayerServer())
return;
if (!LodUtil.checkRamUsage(0.1, 64)) return;
int eventsCount = generationGroup.getEventCount();
// If we still all jobs running, return.
if (eventsCount >= estimatedPointsToQueue) {
estimatedPointsToQueue--;
if (estimatedPointsToQueue < newThreadCount)
estimatedPointsToQueue = newThreadCount;
return;
}
final int targetToGenerate = estimatedPointsToQueue - eventsCount;
int toGenerate = targetToGenerate;
int positionGoneThough = 0;
// round the player's block position down to the nearest chunk BlockPos
int playerPosX = MC.getPlayerBlockPos().getX();
int playerPosZ = MC.getPlayerBlockPos().getZ();
double runTimeRatio = CONFIG.client().advanced().threading()._getWorldGenerationPartialRunTime();
PosToGenerateContainer posToGenerate = lodDim.getPosToGenerate(estimatedSampleNeeded, playerPosX, playerPosZ,
priority, mode);
if (eventsCount == 0 && posToGenerate.getNumberOfPos() >= estimatedSampleNeeded) {
estimatedPointsToQueue++;
if (estimatedPointsToQueue > newThreadCount * 10)
estimatedPointsToQueue = newThreadCount * 10;
}
// ApiShared.LOGGER.info("PosToGenerate: {}", posToGenerate);
// Find the max number of iterations we need to go though.
// We are checking one FarPos, and one NearPos per iterations. This ensure we
// aren't just
// always picking one or the other.
Steps targetStep;
switch (mode) {
case NONE:
targetStep = Steps.Empty; // NOTE: Only load in existing chunks. No new chunk generation
break;
case BIOME_ONLY:
targetStep = Steps.Biomes; // NOTE: No block. Require fake height in LodBuilder
break;
case BIOME_ONLY_SIMULATE_HEIGHT:
targetStep = Steps.Noise; // NOTE: Stone only. Require fake surface
break;
case SURFACE:
targetStep = Steps.Surface; // Carvers or Surface???
break;
case FEATURES:
case FULL:
targetStep = Steps.Features;
break;
default:
assert false;
return;
}
if (ENABLE_GENERATOR_STATS_LOGGING)
ApiShared.LOGGER.info("WorldGen. Near:" + posToGenerate.getNumberOfNearPos() + " Far:"
+ posToGenerate.getNumberOfFarPos());
if (priority == GenerationPriority.FAR_FIRST || priority == GenerationPriority.BALANCED) {
int nearCount = posToGenerate.getNumberOfNearPos();
int farCount = posToGenerate.getNumberOfFarPos();
if (ENABLE_GENERATOR_STATS_LOGGING)
ApiShared.LOGGER.info("WorldGen. Near:" + nearCount + " Far:" + farCount);
int maxIteration = Math.max(nearCount, farCount);
for (int i = 0; i < maxIteration; i++) {
// We have farPos to go though
if (i < farCount && posToGenerate.getNthDetail(i, false) != 0) {
positionGoneThough++;
byte detailLevel = (byte) (posToGenerate.getNthDetail(i, false) - 1);
int chunkX = LevelPosUtil.getChunkPos(detailLevel, posToGenerate.getNthPosX(i, false));
int chunkZ = LevelPosUtil.getChunkPos(detailLevel, posToGenerate.getNthPosZ(i, false));
int genSize = detailLevel > LodUtil.CHUNK_DETAIL_LEVEL ? 0 : generationGroupSize;
if (generationGroup.tryAddPoint(chunkX, chunkZ, genSize, targetStep, false, runTimeRatio)) {
toGenerate--;
}
}
if (toGenerate <= 0)
break;
// We have nearPos to go though
if (i < nearCount && posToGenerate.getNthDetail(i, true) != 0) {
positionGoneThough++;
byte detailLevel = (byte) (posToGenerate.getNthDetail(i, true) - 1);
int chunkX = LevelPosUtil.getChunkPos(detailLevel, posToGenerate.getNthPosX(i, true));
int chunkZ = LevelPosUtil.getChunkPos(detailLevel, posToGenerate.getNthPosZ(i, true));
int genSize = detailLevel > LodUtil.CHUNK_DETAIL_LEVEL ? 0 : generationGroupSize;
if (generationGroup.tryAddPoint(chunkX, chunkZ, genSize, targetStep, true, runTimeRatio)) {
toGenerate--;
}
}
if (toGenerate <= 0)
break;
}
} else {
int nearCount = posToGenerate.getNumberOfNearPos();
for (int i = 0; i < nearCount; i++) {
// We have nearPos to go though
if (posToGenerate.getNthDetail(i, true) != 0) {
positionGoneThough++;
byte detailLevel = (byte) (posToGenerate.getNthDetail(i, true) - 1);
int chunkX = LevelPosUtil.getChunkPos(detailLevel, posToGenerate.getNthPosX(i, true));
int chunkZ = LevelPosUtil.getChunkPos(detailLevel, posToGenerate.getNthPosZ(i, true));
int genSize = detailLevel > LodUtil.CHUNK_DETAIL_LEVEL ? 0 : generationGroupSize;
if (generationGroup.tryAddPoint(chunkX, chunkZ, genSize, targetStep, true, runTimeRatio)) {
toGenerate--;
}
if (toGenerate <= 0)
break;
}
}
// Only do far gen if toGenerate is non 0 and that we have requested all samples
// we can get.
if (toGenerate > 0 && estimatedSampleNeeded > posToGenerate.getNumberOfPos()) {
int farCount = posToGenerate.getNumberOfFarPos();
for (int i = 0; i < farCount; i++) {
// We have farPos to go though
if (posToGenerate.getNthDetail(i, false) != 0) {
positionGoneThough++;
byte detailLevel = (byte) (posToGenerate.getNthDetail(i, false) - 1);
int chunkX = LevelPosUtil.getChunkPos(detailLevel, posToGenerate.getNthPosX(i, false));
int chunkZ = LevelPosUtil.getChunkPos(detailLevel, posToGenerate.getNthPosZ(i, false));
int genSize = detailLevel > LodUtil.CHUNK_DETAIL_LEVEL ? 0 : generationGroupSize;
if (generationGroup.tryAddPoint(chunkX, chunkZ, genSize, targetStep, false, runTimeRatio)) {
toGenerate--;
}
}
if (toGenerate <= 0)
break;
}
}
}
if (targetToGenerate != toGenerate && ENABLE_GENERATOR_STATS_LOGGING) {
if (toGenerate <= 0) {
ApiShared.LOGGER.info(
"WorldGenerator: Sampled " + posToGenerate.getNumberOfPos() + " out of " + estimatedSampleNeeded
+ " points, started all targeted " + targetToGenerate + " generations.");
} else {
ApiShared.LOGGER.info("WorldGenerator: Sampled " + posToGenerate.getNumberOfPos() + " out of "
+ estimatedSampleNeeded + " points, started " + (targetToGenerate - toGenerate)
+ " out of targeted " + targetToGenerate + " generations.");
}
}
if (toGenerate > 0 && estimatedSampleNeeded <= posToGenerate.getNumberOfPos()) {
// We failed to generate enough points from the samples.
// Let's increase the estimatedSampleNeeded.
estimatedSampleNeeded *= 1.3;
// Ensure wee don't go to basically infinity
if (estimatedSampleNeeded > 32768)
estimatedSampleNeeded = 32768;
if (ENABLE_GENERATOR_STATS_LOGGING)
ApiShared.LOGGER.info("WorldGenerator: Increasing estimatedSampleNeeeded to " + estimatedSampleNeeded);
} else if (toGenerate <= 0 && positionGoneThough * 1.5 < posToGenerate.getNumberOfPos()) {
// We haven't gone though half of them and it's already enough.
// Let's shink the estimatedSampleNeeded.
estimatedSampleNeeded /= 1.2;
// Ensure we don't go to near zero.
if (estimatedSampleNeeded < 4)
estimatedSampleNeeded = 4;
if (ENABLE_GENERATOR_STATS_LOGGING)
ApiShared.LOGGER.info("WorldGenerator: Decreasing estimatedSampleNeeeded to " + estimatedSampleNeeded);
}
}
public void stop(boolean blocking) {
ApiShared.LOGGER.info("1.18 Experimental Chunk Generator shutting down...");
generationGroup.stop(blocking);
}
}
@@ -1,373 +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.HashSet;
import java.util.Set;
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.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 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;
/**
* This keeps track of how many chunk generation requests are on going. This is
* to limit how many chunks are queued at once. To prevent chunks from being
* generated for a long time in an area the player is no longer in.
*/
public final AtomicInteger numberOfChunksWaitingToGenerate = new AtomicInteger(0);
public final Set<AbstractChunkPosWrapper> positionsWaitingToBeGenerated = new HashSet<>();
/**
* Singleton copy of this object
*/
public static final LodWorldGenerator INSTANCE = new LodWorldGenerator();
public AbstractExperimentalWorldGeneratorWrapper experimentalWorldGenerator;
private LodWorldGenerator() {}
/**
* Queues up LodNodeGenWorkers for the given lodDimension.
* renderer needed so the LodNodeGenWorkers can flag that the
* buffers need to be rebuilt.
*/
public void queueGenerationRequests(LodDimension lodDim, LodBuilder lodBuilder)
{
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;
Runnable generatorFunc = (() ->
{
try
{
// round the player's block position down to the nearest chunk BlockPos
int playerPosX = MC.getPlayerBlockPos().getX();
int playerPosZ = MC.getPlayerBlockPos().getZ();
//=======================================//
// fill in positionsWaitingToBeGenerated //
//=======================================//
IWorldWrapper serverWorld = LodUtil.getServerWorldFromDimension(lodDim.dimension);
PosToGenerateContainer posToGenerate = lodDim.getPosToGenerate(
maxChunkGenRequests,
playerPosX,
playerPosZ);
byte detailLevel;
int posX;
int posZ;
int nearIndex = 0;
int farIndex = 0;
for (int i = 0; i < posToGenerate.getNumberOfPos(); i++)
{
// I wish there was a way to compress this code, but I'm not aware of
// an easy way to do so.
// add the near positions
if (nearIndex < posToGenerate.getNumberOfNearPos() && posToGenerate.getNthDetail(nearIndex, true) != 0)
{
detailLevel = (byte) (posToGenerate.getNthDetail(nearIndex, true) - 1);
posX = posToGenerate.getNthPosX(nearIndex, true);
posZ = posToGenerate.getNthPosZ(nearIndex, true);
nearIndex++;
AbstractChunkPosWrapper chunkPos = WRAPPER_FACTORY.createChunkPos(LevelPosUtil.getChunkPos(detailLevel, posX), LevelPosUtil.getChunkPos(detailLevel, posZ));
// prevent generating the same chunk multiple times
if (positionsWaitingToBeGenerated.contains(chunkPos))
continue;
// don't add more to the generation queue then allowed
if (numberOfChunksWaitingToGenerate.get() >= maxChunkGenRequests)
break;
positionsWaitingToBeGenerated.add(chunkPos);
numberOfChunksWaitingToGenerate.addAndGet(1);
queueWork(chunkPos, mode, lodBuilder, lodDim, serverWorld);
}
// add the far positions
if (farIndex < posToGenerate.getNumberOfFarPos() && posToGenerate.getNthDetail(farIndex, false) != 0)
{
detailLevel = (byte) (posToGenerate.getNthDetail(farIndex, false) - 1);
posX = posToGenerate.getNthPosX(farIndex, false);
posZ = posToGenerate.getNthPosZ(farIndex, false);
farIndex++;
AbstractChunkPosWrapper chunkPos = WRAPPER_FACTORY.createChunkPos(LevelPosUtil.getChunkPos(detailLevel, posX), LevelPosUtil.getChunkPos(detailLevel, posZ));
// don't add more to the generation queue then allowed
if (numberOfChunksWaitingToGenerate.get() >= maxChunkGenRequests)
continue;
//break;
// prevent generating the same chunk multiple times
if (positionsWaitingToBeGenerated.contains(chunkPos))
continue;
positionsWaitingToBeGenerated.add(chunkPos);
numberOfChunksWaitingToGenerate.addAndGet(1);
queueWork(chunkPos, mode, lodBuilder, lodDim, serverWorld);
}
}
}
catch (RuntimeException e)
{
// this shouldn't ever happen, but just in case
e.printStackTrace();
}
finally
{
generatorThreadRunning = false;
}
});
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());
}
}
@@ -1,3 +1,22 @@
/*
* This file is part of the Distant Horizons mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020-2022 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.config;
import java.lang.annotation.ElementType;
@@ -7,44 +26,40 @@ import java.lang.annotation.Target;
/**
* Where the annotations for the config are defined
* If there is no annotation then the config will not touch it
*
* @author coolGi2007
* @version 12-28-2021
* REMOVED IN a1.7
*
* @author coolGi
* @version 02-07-2022
*/
@Deprecated
public class ConfigAnnotations {
/** a textField, button, etc. that can be interacted with */
/** A textField, button, etc. that can be interacted with */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Entry
{
String name() default "";
@Deprecated
int width() default 150;
@Deprecated
double minValue() default Double.MIN_NORMAL;
@Deprecated
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 */
/** For making categories */
@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)
@@ -53,4 +68,26 @@ public class ConfigAnnotations {
{
}
/**
* Adds a comment to the file,
* This should only be used in special cases where comments from an entry cant reach
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface FileComment
{
}
/** DONT USE AS IT WILL BE REMOVED IN THE REWORK OF THE CONFIG */
@Deprecated
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface ScreenEntry
{
String name() default "";
int width() default 100;
}
}
@@ -1,502 +0,0 @@
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;
}
}
@@ -1,3 +1,22 @@
/*
* This file is part of the Distant Horizons mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020-2022 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.dataFormat;
public class ColorFormat
@@ -1,5 +1,25 @@
/*
* This file is part of the Distant Horizons mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020-2022 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.dataFormat;
@Deprecated //Unused
public class DataMergeUtil
{
public static void shrinkArray(short[] array, int packetSize, int start, int length, int arraySize)
@@ -1,3 +1,22 @@
/*
* This file is part of the Distant Horizons mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020-2022 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.dataFormat;
public class LightFormat
@@ -1,3 +1,22 @@
/*
* This file is part of the Distant Horizons mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020-2022 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.dataFormat;
public class PositionDataFormat
@@ -1,3 +1,22 @@
/*
* This file is part of the Distant Horizons mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020-2022 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.dataFormat;
public class VerticalDataFormat
@@ -1,3 +1,22 @@
/*
* This file is part of the Distant Horizons mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020-2022 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;
import java.util.Arrays;
@@ -9,11 +28,18 @@ import java.util.stream.Collectors;
import com.seibel.lod.core.objects.math.Vec3i;
/**
* A (almost) exact copy of Minecraft's
* An (almost) exact copy of Minecraft's
* Direction enum.
*
*
* Up <Br>
* Down <Br>
* North <Br>
* South <Br>
* East <Br>
* West <Br>
*
* @author James Seibel
* @version 11-13-2021
* @version 2021-11-13
*/
public enum LodDirection
{
@@ -23,7 +49,27 @@ public enum LodDirection
SOUTH(3, 2, 0, "south", LodDirection.AxisDirection.POSITIVE, LodDirection.Axis.Z, new Vec3i(0, 0, 1)),
WEST(4, 5, 1, "west", LodDirection.AxisDirection.NEGATIVE, LodDirection.Axis.X, new Vec3i(-1, 0, 0)),
EAST(5, 4, 3, "east", LodDirection.AxisDirection.POSITIVE, LodDirection.Axis.X, new Vec3i(1, 0, 0));
public static final LodDirection[] DIRECTIONS = new LodDirection[] {
LodDirection.UP,
LodDirection.DOWN,
LodDirection.WEST,
LodDirection.EAST,
LodDirection.NORTH,
LodDirection.SOUTH };
public static final LodDirection[] OPPOSITE_DIRECTIONS = new LodDirection[] {
LodDirection.UP,
LodDirection.DOWN,
LodDirection.SOUTH,
LodDirection.NORTH,
LodDirection.EAST,
LodDirection.WEST };
/** North, South, East, West */
public static final LodDirection[] ADJ_DIRECTIONS = new LodDirection[] {
LodDirection.EAST,
LodDirection.WEST,
LodDirection.SOUTH,
LodDirection.NORTH };
// private final int data3d;
// private final int oppositeIndex;
// private final int data2d;
@@ -1,8 +1,8 @@
/*
* This file is part of the Distant Horizon mod (formerly the LOD Mod),
* This file is part of the Distant Horizons mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020 James Seibel
* Copyright (C) 2020-2022 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
@@ -1,8 +1,8 @@
/*
* This file is part of the Distant Horizon mod (formerly the LOD Mod),
* This file is part of the Distant Horizons mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020 James Seibel
* Copyright (C) 2020-2022 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
@@ -1,8 +1,8 @@
/*
* This file is part of the Distant Horizon mod (formerly the LOD Mod),
* This file is part of the Distant Horizons mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020 James Seibel
* Copyright (C) 2020-2022 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
@@ -31,6 +31,8 @@ package com.seibel.lod.core.enums.config;
*/
public enum BufferRebuildTimes
{
CONSTANT(0, 0, 0, 1),
FREQUENT(1000, 500, 2500, 1),
NORMAL(2000, 1000, 5000, 4),
@@ -1,8 +1,8 @@
/*
* This file is part of the Distant Horizon mod (formerly the LOD Mod),
* This file is part of the Distant Horizons mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020 James Seibel
* Copyright (C) 2020-2022 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -19,8 +19,6 @@
package com.seibel.lod.core.enums.config;
import org.jetbrains.annotations.Nullable;
/**
* NONE <br>
* BIOME_ONLY <br>
@@ -38,9 +36,9 @@ import org.jetbrains.annotations.Nullable;
public enum DistanceGenerationMode
{
/**
* Don't generate anything
* Don't generate anything except just load in already existing chunks
*/
NONE((byte) 0),
NONE((byte) 1),
/**
* Only generate the biomes and use biome
@@ -49,7 +47,7 @@ public enum DistanceGenerationMode
* Doesn't generate height, everything is shown at sea level.
* Multithreaded - Fastest (2-5 ms)
*/
BIOME_ONLY((byte) 1),
BIOME_ONLY((byte) 2),
/**
* Same as BIOME_ONLY, except instead
@@ -57,7 +55,7 @@ public enum DistanceGenerationMode
* different biome types (mountain, ocean, forest, etc.)
* use predetermined heights to simulate having height data.
*/
BIOME_ONLY_SIMULATE_HEIGHT((byte) 2),
BIOME_ONLY_SIMULATE_HEIGHT((byte) 3),
/**
* Generate the world surface,
@@ -65,7 +63,7 @@ public enum DistanceGenerationMode
* or structures.
* Multithreaded - Faster (10-20 ms)
*/
SURFACE((byte) 3),
SURFACE((byte) 4),
/**
* Generate everything except structures.
@@ -73,7 +71,7 @@ public enum DistanceGenerationMode
* since some features cause concurrentModification exceptions.
* Multithreaded - Fast (15-20 ms)
*/
FEATURES((byte) 4),
FEATURES((byte) 5),
/**
* Ask the server to generate/load each chunk.
@@ -82,8 +80,9 @@ public enum DistanceGenerationMode
* are adding the mod on a pre-existing world.
* Singlethreaded - Slow (15-50 ms, with spikes up to 200 ms)
*/
FULL((byte) 5);
FULL((byte) 6);
public static DistanceGenerationMode RENDERABLE = DistanceGenerationMode.BIOME_ONLY;
/**
* The higher the number the more complete the generation is.
@@ -96,7 +95,6 @@ public enum DistanceGenerationMode
}
// Note: return null if out of range
@Nullable
public static DistanceGenerationMode previous(DistanceGenerationMode mode) {
switch (mode) {
case FULL:
@@ -110,18 +108,14 @@ public enum DistanceGenerationMode
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:
@@ -132,6 +126,7 @@ public enum DistanceGenerationMode
return DistanceGenerationMode.BIOME_ONLY_SIMULATE_HEIGHT;
case NONE:
return DistanceGenerationMode.BIOME_ONLY;
case FULL:
default:
return null;
}
@@ -0,0 +1,49 @@
/*
* This file is part of the Distant Horizons mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2022 Tom Lee (TomTheFurry)
* Copyright (C) 2020-2022 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;
/**
* AUTO <br>
* SMOOTH_DROPOFF <br>
* PERFORMANCE_FOCUSED <br>
* <br>
* Determines how lod level drop off should be done
*
* @author Tom Lee
* @version 7-1-2022
*/
public enum DropoffQuality {
/** SMOOTH_DROPOFF when <128 lod view distance, or PERFORMANCE_FOCUSED otherwise */
AUTO(-1),
SMOOTH_DROPOFF(10),
PERFORMANCE_FOCUSED(0);
public final int fastModeSwitch;
DropoffQuality(int fastModeSwitch) {
this.fastModeSwitch = fastModeSwitch;
}
}
@@ -1,8 +1,8 @@
/*
* This file is part of the Distant Horizon mod (formerly the LOD Mod),
* This file is part of the Distant Horizons mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020 James Seibel
* Copyright (C) 2020-2022 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
@@ -32,10 +32,12 @@ package com.seibel.lod.core.enums.config;
*/
public enum GenerationPriority
{
/** NEAR_FIRST when connected to servers and FAR_FIRST when on single player */
/** NEAR_FIRST when connected to servers and BALANCED when on single player */
AUTO,
NEAR_FIRST,
BALANCED,
FAR_FIRST
}
@@ -1,8 +1,8 @@
/*
* This file is part of the Distant Horizon mod (formerly the LOD Mod),
* This file is part of the Distant Horizons mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020 James Seibel
* Copyright (C) 2020-2022 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,7 +20,7 @@
package com.seibel.lod.core.enums.config;
/**
* Auto, Buffer_Storage, Sub_Data, Buffer_Mapping, Data
* Auto, BUFFER_STORAGE_MAPPING, Buffer_Storage, Sub_Data, Buffer_Mapping, Data
*
* @author James Seibel
* @version 12-1-2021
@@ -28,19 +28,23 @@ package com.seibel.lod.core.enums.config;
public enum GpuUploadMethod
{
/** Picks the best option based on the GPU the user has. */
AUTO,
AUTO(false, false),
/*
*/
BUFFER_STORAGE_MAPPING(true, true),
/**
* Default for NVIDIA if OpenGL 4.5 is supported. <br>
* Fast rendering, no stuttering.
*/
BUFFER_STORAGE,
BUFFER_STORAGE(false, true),
/**
* Backup option for NVIDIA. <br>
* Fast rendering but may stutter when uploading.
*/
SUB_DATA,
SUB_DATA(false, false),
/**
* Default option for AMD/Intel. <br>
@@ -48,12 +52,19 @@ public enum GpuUploadMethod
* Fast rending if in GPU memory, slow if in system memory, <br>
* but won't stutter when uploading.
*/
BUFFER_MAPPING,
BUFFER_MAPPING(true, false),
/**
* Backup option for AMD/Intel. <br>
* Fast rendering but may stutter when uploading.
*/
DATA,
DATA(false, false);
public final boolean useEarlyMapping;
public final boolean useBufferStorage;
GpuUploadMethod(boolean useEarlyMapping, boolean useBufferStorage) {
this.useEarlyMapping = useEarlyMapping;
this.useBufferStorage = useBufferStorage;
}
}
@@ -1,8 +1,8 @@
/*
* This file is part of the Distant Horizon mod (formerly the LOD Mod),
* This file is part of the Distant Horizons mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020 James Seibel
* Copyright (C) 2020-2022 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
@@ -1,8 +1,8 @@
/*
* This file is part of the Distant Horizon mod (formerly the LOD Mod),
* This file is part of the Distant Horizons mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020 James Seibel
* Copyright (C) 2020-2022 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
@@ -1,8 +1,8 @@
/*
* This file is part of the Distant Horizon mod (formerly the LOD Mod),
* This file is part of the Distant Horizons mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020 James Seibel
* Copyright (C) 2020-2022 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
@@ -0,0 +1,30 @@
/*
* This file is part of the Distant Horizons mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020-2022 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;
public enum LightGenerationMode
{
// Fake in light values based on height maps
FAST,
// Run the light engine though the chunk to generate proper light values
FANCY
}
@@ -0,0 +1,44 @@
/*
* This file is part of the Distant Horizons mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020-2022 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 org.apache.logging.log4j.Level;
public enum LoggerMode {
DISABLED(Level.OFF, Level.OFF),
LOG_ALL_TO_FILE(Level.ALL, Level.OFF),
LOG_ERROR_TO_CHAT(Level.ALL, Level.ERROR),
LOG_WARNING_TO_CHAT(Level.ALL, Level.WARN),
LOG_INFO_TO_CHAT(Level.ALL, Level.INFO),
LOG_DEBUG_TO_CHAT(Level.ALL, Level.DEBUG),
LOG_ALL_TO_CHAT(Level.ALL, Level.ALL),
LOG_ERROR_TO_CHAT_AND_FILE(Level.ERROR, Level.ERROR),
LOG_WARNING_TO_CHAT_AND_FILE(Level.WARN, Level.WARN),
LOG_INFO_TO_CHAT_AND_FILE(Level.INFO, Level.INFO),
LOG_DEBUG_TO_CHAT_AND_FILE(Level.DEBUG, Level.DEBUG),
LOG_WARNING_TO_CHAT_AND_INFO_TO_FILE(Level.INFO, Level.WARN),
LOG_ERROR_TO_CHAT_AND_INFO_TO_FILE(Level.INFO, Level.ERROR),
;
public final Level levelForFile;
public final Level levelForChat;
LoggerMode(Level levelForFile, Level levelForChat) {
this.levelForFile = levelForFile;
this.levelForChat = levelForChat;
}
}
@@ -0,0 +1,69 @@
/*
* This file is part of the Distant Horizons mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2022 Tom Lee (TomTheFurry)
* Copyright (C) 2020-2022 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;
/**
* AUTO <br>
* NAME_ONLY <br>
* NAME_IP <br>
* NAME_IP_PORT <br>
* <br>
* Determines how the multiplayer folders should be named.
*
* @author James Seibel
* @version 3-7-2022
*/
public enum ServerFolderNameMode
{
/**
* NAME_IP for LAN connections <Br>
* NAME_IP_PORT for all others
*/
AUTO,
/** Only use the server name */
NAME_ONLY,
/**
* {SERVER_NAME} IP {IP} <br>
* Minecraft Server IP 192.168.1.40
*/
NAME_IP,
/**
* {SERVER_NAME} IP {IP} <br>
* Minecraft Server IP 192.168.1.40:25565
*/
NAME_IP_PORT,
/**
* {SERVER_NAME} IP {IP} <br>
* Minecraft Server IP 192.168.1.40:25565 GameVersion 1.16.5 <Br> <br>
*
* Not normally recommended, since the game version can change if the
* server installs paper or some other jar. <br>
* This is just here to provide backwards compatibility.
*
* TODO add this to config desc
*/
NAME_IP_PORT_MC_VERSION;
}
@@ -1,3 +1,22 @@
/*
* This file is part of the Distant Horizons mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020-2022 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;
/**
@@ -1,8 +1,8 @@
/*
* This file is part of the Distant Horizon mod (formerly the LOD Mod),
* This file is part of the Distant Horizons mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020 James Seibel
* Copyright (C) 2020-2022 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
@@ -31,15 +31,12 @@ package com.seibel.lod.core.enums.config;
*/
public enum VanillaOverdraw
{
/** Never draw LODs where a minecraft chunk could be. */
/** Dont draw LODs where a minecraft chunk could be. Use Overdraw Offset to tweak the border thickness */
NEVER,
/** Draw LODs over the farther minecraft chunks. */
/** Draw LODs over the farther minecraft chunks. Dynamically decides the border thickness */
DYNAMIC,
/** Draw LODs over all minecraft chunks. */
ALWAYS,
/** Draw LODs over border chunks. */
BORDER,
}
@@ -1,8 +1,8 @@
/*
* This file is part of the Distant Horizon mod (formerly the LOD Mod),
* This file is part of the Distant Horizons mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020 James Seibel
* Copyright (C) 2020-2022 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -19,94 +19,101 @@
package com.seibel.lod.core.enums.config;
import org.jetbrains.annotations.Nullable;
/**
* heightmap <br>
* multi_lod <br>
*
*
* @author Leonardo Amato
* @version 10-07-2021
* @version 2022-3-26
*/
public enum VerticalQuality
{
LOW(
new int[] { 2,
2,
2,
2,
1,
1,
1,
1,
1,
1,
1 }
new int[] { 4, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1 },
2
),
MEDIUM(
new int[] { 4,
4,
2,
2,
2,
1,
1,
1,
1,
1,
1 }
new int[] { 6, 4, 3, 2, 2, 1, 1, 1, 1, 1, 1 },
4
),
HIGH(
new int[] {
8,
8,
4,
4,
2,
2,
2,
1,
1,
1,
1 }
new int[] { 8, 6, 4, 2, 2, 2, 2, 1, 1, 1, 1 },
6
),
ULTRA(
new int[] { 16, 8, 4, 2, 2, 2, 2, 1, 1, 1, 1 },
12
);
public final int[] maxVerticalData;
VerticalQuality(int[] maxVerticalData)
@Deprecated // Will find other ways to optimize
public final int maxConnectedLods;
VerticalQuality(int[] maxVerticalData, int maxConnectedLods)
{
this.maxVerticalData = maxVerticalData;
this.maxConnectedLods = maxConnectedLods;
}
// Note: return null if out of range
@Nullable
public static VerticalQuality previous(VerticalQuality mode) {
switch (mode) {
/** returns null if out of range */
public static VerticalQuality previous(VerticalQuality mode)
{
switch (mode)
{
case ULTRA:
return VerticalQuality.HIGH;
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;
/** returns null if out of range */
public static VerticalQuality next(VerticalQuality mode)
{
switch (mode)
{
case MEDIUM:
return VerticalQuality.HIGH;
case LOW:
return VerticalQuality.MEDIUM;
case HIGH:
return VerticalQuality.ULTRA;
case ULTRA:
default:
return null;
}
}
/**
* Returns the value with the given name, case-insensitive. <br>
* Returns null if no enums match the name. <br>
* Similar to valueOf(String value)
*/
public static VerticalQuality getByName(String name)
{
switch (name.toUpperCase())
{
case "ULTRA":
return VerticalQuality.ULTRA;
case "HIGH":
return VerticalQuality.HIGH;
case "MEDIUM":
return VerticalQuality.MEDIUM;
case "LOW":
return VerticalQuality.LOW;
default:
return null;
}
}
}
@@ -1,8 +1,8 @@
/*
* This file is part of the Distant Horizon mod (formerly the LOD Mod),
* This file is part of the Distant Horizons mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020 James Seibel
* Copyright (C) 2020-2022 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
@@ -29,21 +29,41 @@ public enum DebugMode
{
/** LODs are rendered normally */
OFF,
/** LOD draws in wireframe. */
SHOW_WIREFRAME,
/** LOD colors are based on their detail */
SHOW_DETAIL,
/** LOD colors are based on their detail, and draws in wireframe. */
SHOW_DETAIL_WIREFRAME;
SHOW_DETAIL_WIREFRAME,
/** LOD colors are based on their gen mode. */
SHOW_GENMODE,
/** LOD colors are based on their gen mode, and draws in wireframe. */
SHOW_GENMODE_WIREFRAME,
/** Only draw overlapping LOD quads. */
SHOW_OVERLAPPING_QUADS,
/** Only draw overlapping LOD quads, and draws in wireframe. */
SHOW_OVERLAPPING_QUADS_WIREFRAME;
/** used when cycling through the different modes */
private DebugMode next;
static
{
OFF.next = SHOW_DETAIL;
OFF.next = SHOW_WIREFRAME;
SHOW_WIREFRAME.next = SHOW_DETAIL;
SHOW_DETAIL.next = SHOW_DETAIL_WIREFRAME;
SHOW_DETAIL_WIREFRAME.next = OFF;
SHOW_DETAIL_WIREFRAME.next = SHOW_GENMODE;
SHOW_GENMODE.next = SHOW_GENMODE_WIREFRAME;
SHOW_GENMODE_WIREFRAME.next = SHOW_OVERLAPPING_QUADS;
SHOW_OVERLAPPING_QUADS.next = SHOW_OVERLAPPING_QUADS_WIREFRAME;
SHOW_OVERLAPPING_QUADS_WIREFRAME.next = OFF;
}
/** returns the next debug mode */
@@ -1,8 +1,8 @@
/*
* This file is part of the Distant Horizon mod (formerly the LOD Mod),
* This file is part of the Distant Horizons mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020 James Seibel
* Copyright (C) 2020-2022 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
@@ -1,8 +1,8 @@
/*
* This file is part of the Distant Horizon mod (formerly the LOD Mod),
* This file is part of the Distant Horizons mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020 James Seibel
* Copyright (C) 2020-2022 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
@@ -1,8 +1,8 @@
/*
* This file is part of the Distant Horizon mod (formerly the LOD Mod),
* This file is part of the Distant Horizons mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020 James Seibel
* Copyright (C) 2020-2022 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
@@ -0,0 +1,78 @@
/*
* This file is part of the Distant Horizons mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020-2022 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.rendering;
import java.util.Objects;
/**
* Contains all configurable options related to fog.
*
* @version 2022-4-13
*/
public class FogSetting
{
/** a FogSetting object with 0 for every value */
public static final FogSetting EMPTY = new FogSetting(0, 0, 0, 0,0, FogSetting.FogType.LINEAR);
public final double start;
public final double end;
public final double min;
public final double max;
public final double density;
public final FogType fogType;
public FogSetting(double start, double end, double min, double max, double density, FogType fogType)
{
this.start = start;
this.end = end;
this.min = min;
this.max = max;
this.density = density;
this.fogType = fogType;
}
@Override
public boolean equals(Object o)
{
if (this == o)
return true;
if (o == null || getClass() != o.getClass())
return false;
FogSetting that = (FogSetting) o;
return Double.compare(that.start, start) == 0 && Double.compare(that.end, end) == 0 && Double.compare(that.min, min) == 0 && Double.compare(that.max, max) == 0 && Double.compare(that.density, density) == 0 && fogType == that.fogType;
}
@Override
public int hashCode()
{
return Objects.hash(start, end, min, max, density, fogType);
}
public enum FogType
{
LINEAR,
EXPONENTIAL,
EXPONENTIAL_SQUARED,
// TEXTURE_BASED, // TODO: Impl this
}
}
@@ -1,8 +1,8 @@
/*
* This file is part of the Distant Horizon mod (formerly the LOD Mod),
* This file is part of the Distant Horizons mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020 James Seibel
* Copyright (C) 2020-2022 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
@@ -0,0 +1,49 @@
/*
* This file is part of the Distant Horizons mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020-2022 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.rendering;
/**
* basic <br>
* Ignore_Height <br>
* Addition <br>
* Max <br>
* Multiply <br>
* Inverse_Multiply <br>
* Limited_Addition <br>
* Multiply_Addition <br>
* Inverse_Multiply_Addition <br>
* Average <br>
*
* @author Leetom
* @version 2022-4-14
*/
public enum HeightFogMixMode
{
BASIC,
IGNORE_HEIGHT,
ADDITION,
MAX,
MULTIPLY,
INVERSE_MULTIPLY,
LIMITED_ADDITION,
MULTIPLY_ADDITION,
INVERSE_MULTIPLY_ADDITION,
AVERAGE,
}
@@ -0,0 +1,41 @@
/*
* This file is part of the Distant Horizons mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020-2022 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.rendering;
public enum HeightFogMode
{
ABOVE_CAMERA(true, true, false),
BELOW_CAMERA(true, false, true),
ABOVE_AND_BELOW_CAMERA(true, true, true),
ABOVE_SET_HEIGHT(false, true, false),
BELOW_SET_HEIGHT(false, false, true),
ABOVE_AND_BELOW_SET_HEIGHT(false, true, true);
public final boolean basedOnCamera;
public final boolean above;
public final boolean below;
HeightFogMode(boolean basedOnCamera, boolean above, boolean below)
{
this.basedOnCamera = basedOnCamera;
this.above = above;
this.below = below;
}
}
@@ -0,0 +1,43 @@
/*
* This file is part of the Distant Horizons mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020-2022 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.rendering;
public enum RendererType {
DEFAULT,
DEBUG,
DISABLED,
;
public static RendererType next(RendererType type) {
switch (type) {
case DEFAULT: return DEBUG;
case DEBUG: return DISABLED;
default: return DEFAULT;
}
}
public static RendererType previous(RendererType type) {
switch (type) {
case DEFAULT: return DISABLED;
case DEBUG: return DEFAULT;
default: return DEBUG;
}
}
}
@@ -1,8 +1,8 @@
/*
* This file is part of the Distant Horizon mod (formerly the LOD Mod),
* This file is part of the Distant Horizons mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020 James Seibel
* Copyright (C) 2020-2022 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
@@ -1,8 +1,8 @@
/*
* This file is part of the Distant Horizon mod (formerly the LOD Mod),
* This file is part of the Distant Horizons mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020 James Seibel
* Copyright (C) 2020-2022 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,6 +20,7 @@
package com.seibel.lod.core.handlers;
import com.seibel.lod.core.enums.rendering.FogDrawMode;
import com.seibel.lod.core.handlers.dependencyInjection.IBindable;
/**
* A singleton used to get variables from methods
@@ -34,9 +35,9 @@ import com.seibel.lod.core.enums.rendering.FogDrawMode;
* different MC versions.
*
* @author James Seibel
* @version 12-14-2021
* @version 3-5-2022
*/
public interface IReflectionHandler
public interface IReflectionHandler extends IBindable
{
/** @returns Whether Optifine is set to render fog or not. */
FogDrawMode getFogDrawMode();
@@ -46,4 +47,6 @@ public interface IReflectionHandler
/** @returns if Sodium (or a sodium like) mod is present. Attempts to find the "SodiumWorldRenderer" class. */
boolean sodiumPresent();
boolean optifinePresent();
}
@@ -1,8 +1,8 @@
/*
* This file is part of the Distant Horizon mod (formerly the LOD Mod),
* This file is part of the Distant Horizons mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020 James Seibel
* Copyright (C) 2020-2022 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
@@ -21,26 +21,38 @@ package com.seibel.lod.core.handlers;
import java.io.*;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.util.Arrays;
import java.time.Duration;
import java.time.Instant;
import java.util.ArrayList;
import java.util.ConcurrentModificationException;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.locks.ReentrantLock;
import com.seibel.lod.core.handlers.dependencyInjection.SingletonHandler;
import com.seibel.lod.core.logging.ConfigBasedLogger;
import com.seibel.lod.core.wrapperInterfaces.config.ILodConfigWrapperSingleton;
import com.seibel.lod.core.api.ClientApi;
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;
import com.seibel.lod.core.enums.config.VerticalQuality;
import com.seibel.lod.core.objects.lod.LevelContainer;
import com.seibel.lod.core.objects.lod.LodDimension;
import com.seibel.lod.core.objects.lod.LodRegion;
import com.seibel.lod.core.objects.lod.RegionPos;
import com.seibel.lod.core.objects.lod.VerticalLevelContainer;
import com.seibel.lod.core.util.LodThreadFactory;
import com.seibel.lod.core.util.LodUtil;
import com.seibel.lod.core.util.ThreadMapUtil;
import com.seibel.lod.core.logging.SpamReducedLogger;
import com.seibel.lod.core.util.UnitBytes;
import org.apache.logging.log4j.LogManager;
/**
* This object handles creating LodRegions
@@ -49,22 +61,31 @@ import com.seibel.lod.core.util.ThreadMapUtil;
*
* @author James Seibel
* @author Cola
* @version 9-25-2021
* @version 2022-3-30
*/
public class LodDimensionFileHandler
{
/** This is the dimension that owns this file handler */
private LodDimension lodDimension;
private static final ILodConfigWrapperSingleton config = SingletonHandler.get(ILodConfigWrapperSingleton.class);
public static final ConfigBasedLogger LOGGER = new ConfigBasedLogger(LogManager.getLogger(LodDimensionFileHandler.class),
() -> config.client().advanced().debugging().debugSwitch().getLogFileReadWriteEvent());
private final File dimensionDataSaveFolder;
public static final boolean ENABLE_SAVE_THREAD_LOGGING = true;
public static final boolean ENABLE_SAVE_REGION_LOGGING = false;
/** This is the dimension that owns this file handler */
private final LodDimension lodDimension;
public final File dimensionDataSaveFolder;
/** lod */
private static final String FILE_NAME_PREFIX = "lod";
/** .txt */
/** .xz */
private static final String FILE_EXTENSION = ".xz";
/** detail- */
private static final String DETAIL_FOLDER_NAME_PREFIX = "detail-";
public static final String MULTIPLAYER_FOLDER_NAME = "Distant_Horizons_server_data";
/**
* .tmp <br>
* Added to the end of the file path when saving to prevent
@@ -74,20 +95,29 @@ public class LodDimensionFileHandler
*/
private static final String TMP_FILE_EXTENSION = ".tmp";
/**
* This is compression level set in XZ
* lower values are faster
* difference in size should be only 2x
*/
private static final int COMPRESSION_LEVEL = 1;
/**
* This is the file version currently accepted by this
* file handler, older versions (smaller numbers) will be deleted and overwritten,
* newer versions (larger numbers) will be ignored and won't be read.
*/
public static final int LOD_SAVE_FILE_VERSION = 8;
public static final int LOD_SAVE_FILE_VERSION = 9;
/**
* Allow saving asynchronously, but never try to save multiple regions
* at a time
*/
private final ExecutorService fileWritingThreadPool = Executors.newSingleThreadExecutor(new LodThreadFactory(this.getClass().getSimpleName()));
private final AtomicBoolean isFileWritingThreadRunning = new AtomicBoolean(false);
private ExecutorService fileWritingThreadPool = Executors.newSingleThreadExecutor(
new LodThreadFactory(this.getClass().getSimpleName(), Thread.NORM_PRIORITY + 1));
private final ConcurrentHashMap<RegionPos, LodRegion> regionToSave = new ConcurrentHashMap<RegionPos, LodRegion>();
public LodDimensionFileHandler(File newSaveFolder, LodDimension newLodDimension)
@@ -97,9 +127,67 @@ public class LodDimensionFileHandler
dimensionDataSaveFolder = newSaveFolder;
lodDimension = newLodDimension;
checkForOldSaveStructure();
}
private ReentrantLock mergeOldFileLock = new ReentrantLock();
private void checkForOldSaveStructure()
{
File file = new File(getFileBasePath());
if (!file.exists())
return;
File[] vertQualFiles = file.listFiles();
for (File vertQualFile : vertQualFiles)
{
if (!vertQualFile.isDirectory())
continue;
if (vertQualFile.getName().equals(VerticalQuality.HIGH.toString()) ||
vertQualFile.getName().equals(VerticalQuality.MEDIUM.toString()) ||
vertQualFile.getName().equals(VerticalQuality.LOW.toString()))
{
File[] subFiles = vertQualFile.listFiles();
for (File subFile : subFiles)
{
if (!subFile.isDirectory())
continue;
if (subFile.getName().equals(DistanceGenerationMode.FULL.toString()) ||
subFile.getName().equals(DistanceGenerationMode.FEATURES.toString()) ||
subFile.getName().equals(DistanceGenerationMode.SURFACE.toString()) ||
subFile.getName().equals(DistanceGenerationMode.BIOME_ONLY_SIMULATE_HEIGHT.toString()) ||
subFile.getName().equals(DistanceGenerationMode.BIOME_ONLY.toString()) ||
subFile.getName().equals(DistanceGenerationMode.NONE.toString()))
{
LOGGER.info("Noticed old save structure files. Starting merge process...");
LodDimensionOldFileStructureHandler oldFileStructHandler = new LodDimensionOldFileStructureHandler(this);
if (mergeOldFileLock.tryLock())
{
// I got the lock to merge file.
LOGGER.info("Updating VerticalQuality LOW...");
oldFileStructHandler.mergeOldFileStructureForVertQuality(VerticalQuality.LOW);
LOGGER.info("Updating VerticalQuality MEDIUM...");
oldFileStructHandler.mergeOldFileStructureForVertQuality(VerticalQuality.MEDIUM);
LOGGER.info("Updating VerticalQuality HIGH...");
oldFileStructHandler.mergeOldFileStructureForVertQuality(VerticalQuality.HIGH);
LOGGER.info("Update completed.");
}
else
{
// Someone is already doing it. I just need to wait until they are done.
mergeOldFileLock.lock();
mergeOldFileLock.unlock();
}
LOGGER.info("Merge process completed.");
return;
}
}
}
}
}
@@ -108,24 +196,53 @@ public class LodDimensionFileHandler
//================//
/**
* Returns the LodRegion at the given coordinates.
* Returns a new LodRegion at the given coordinates.
* Returns an empty region if the file doesn't exist.
*/
public LodRegion loadRegionFromFile(byte detailLevel, RegionPos regionPos, DistanceGenerationMode generationMode, VerticalQuality verticalQuality)
public LodRegion loadRegionFromFile(byte detailLevel, RegionPos regionPos, VerticalQuality verticalQuality)
{
int regionX = regionPos.x;
int regionZ = regionPos.z;
LodRegion region = new LodRegion(LodUtil.REGION_DETAIL_LEVEL, regionPos, generationMode, verticalQuality);
for (byte tempDetailLevel = LodUtil.REGION_DETAIL_LEVEL; tempDetailLevel >= detailLevel; tempDetailLevel--)
// Get one from the region hot cache
LodRegion region = regionToSave.get(regionPos);
if (region != null && region.getMinDetailLevel() <= detailLevel &&
region.getVerticalQuality().compareTo(verticalQuality) >= 0)
return region; // The current hot cache to-be-saved region match our requirement.
region = new LodRegion((byte) (LodUtil.REGION_DETAIL_LEVEL + 1), regionPos, verticalQuality);
return loadRegionFromFile(detailLevel, region, verticalQuality);
}
/**
* Returns the LodRegion that is filled at the given coordinates.
* Returns an empty region if the file doesn't exist.
*/
public LodRegion loadRegionFromFile(byte detailLevel, LodRegion region, VerticalQuality verticalQuality)
{
if (region.getVerticalQuality().compareTo(verticalQuality) < 0)
{
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
regionToSave.put(region.getRegionPos(), region); //FIXME: The hashMap key should prob be a {regionPos,VertQual} pair.
region = new LodRegion((byte) (LodUtil.REGION_DETAIL_LEVEL + 1), region.getRegionPos(), verticalQuality);
}
int regionX = region.regionPosX;
int regionZ = region.regionPosZ;
for (byte tempDetailLevel = (byte) (region.getMinDetailLevel() - 1); tempDetailLevel >= detailLevel; tempDetailLevel--)
{
File file = getBestMatchingRegionFile(tempDetailLevel, regionX, regionZ, verticalQuality);
if (file == null)
{
region.addLevelContainer(new VerticalLevelContainer(tempDetailLevel));
continue; // Failed to find the file for this detail level. continue and try next one
}
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 (fileSize == 0)
{
region.addLevelContainer(new VerticalLevelContainer(tempDetailLevel));
continue; // file is empty. Let's not try parsing empty files
}
try (FileInputStream fileInStream = new FileInputStream(file))
{
XZCompressorInputStream inputStream = new XZCompressorInputStream(fileInStream);
int fileVersion;
fileVersion = inputStream.read();
@@ -136,12 +253,12 @@ public class LodDimensionFileHandler
// close the reader and delete the file.
inputStream.close();
file.delete();
ClientApi.LOGGER.info("Outdated LOD region file for region: (" + regionX + "," + regionZ + ")"
LOGGER.info("Outdated LOD region file for region: (" + regionX + "," + regionZ + ")[" + tempDetailLevel + "]"
+ " 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;
region.addLevelContainer(new VerticalLevelContainer(tempDetailLevel));
}
else if (fileVersion > LOD_SAVE_FILE_VERSION)
{
@@ -149,40 +266,43 @@ public class LodDimensionFileHandler
// 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 + ")"
LOGGER.info("Newer LOD region file for region: (" + regionX + "," + regionZ + ")[" + tempDetailLevel + "]"
+ " 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;
region.addLevelContainer(new VerticalLevelContainer(tempDetailLevel));
}
else if (fileVersion < LOD_SAVE_FILE_VERSION)
{
ClientApi.LOGGER.debug("Old LOD region file for region: (" + regionX + "," + regionZ + ")"
LOGGER.info("Old LOD region file for region: (" + regionX + "," + regionZ + ")[" + tempDetailLevel + "]"
+ " 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));
DataInputStream dataStream = new DataInputStream(inputStream);
region.addLevelContainer(new VerticalLevelContainer(dataStream, fileVersion, tempDetailLevel));
dataStream.close();
inputStream.close();
} else
}
else
{
LOGGER.debug("Loading LOD region file for region: (" + regionX + "," + regionZ + ")[" + tempDetailLevel + "]");
// 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));
DataInputStream dataStream = new DataInputStream(inputStream);
region.addLevelContainer(new VerticalLevelContainer(dataStream, LOD_SAVE_FILE_VERSION, tempDetailLevel));
dataStream.close();
inputStream.close();
}
}
catch (IOException ioEx)
{
ClientApi.LOGGER.error("LOD file read error. Unable to read xz compressed file [" + file + "] error [" + ioEx.getMessage() + "]: ");
ioEx.printStackTrace();
LOGGER.error("LOD file read error. Unable to read xz compressed file [" + file + "]: ", ioEx);
region.addLevelContainer(new VerticalLevelContainer(tempDetailLevel));
}
}// for each detail level
if (region.getMinDetailLevel() >= detailLevel)
region.growTree(detailLevel);
} // for each detail level
return region;
}
@@ -192,33 +312,226 @@ public class LodDimensionFileHandler
// Save to File //
//==============//
/** Save all dirty regions in this LodDimension to file */
public void saveDirtyRegionsToFileAsync()
{
fileWritingThreadPool.execute(saveDirtyRegionsThread);
}
private final Thread saveDirtyRegionsThread = new Thread(() ->
public void saveDirect(int posX, int posZ, VerticalQuality vertQual, VerticalLevelContainer dataContainer)
{
File file = new File(getFileBasePath() + vertQual + File.separatorChar +
DETAIL_FOLDER_NAME_PREFIX + dataContainer.detailLevel + File.separatorChar +
FILE_NAME_PREFIX + "." + posX + "." + posZ + FILE_EXTENSION);
if (file.exists())
{
LOGGER.warn("LOD file write warn. Unable to write [" + file + "] because the newer version file already exist! Skipping this position...");
return;
}
if (!file.getParentFile().exists())
file.getParentFile().mkdirs();
try
{
for (int i = 0; i < lodDimension.getWidth(); i++)
file.createNewFile();
}
catch (IOException e)
{
LOGGER.error("LOD file write error. Unable to create parent directory for [" + file + "]: ", e);
return;
}
try (FileOutputStream fileOutStream = new FileOutputStream(file))
{
XZCompressorOutputStream outputStream = new XZCompressorOutputStream(fileOutStream, COMPRESSION_LEVEL);
// add the version of this file
outputStream.write(LOD_SAVE_FILE_VERSION);
// add each LodChunk to the file
DataOutputStream dataStream = new DataOutputStream(outputStream);
dataContainer.writeData(dataStream);
dataStream.close();
outputStream.close();
}
catch (IOException e)
{
LOGGER.error("LOD file write error. Unable to write to temp file [" + file + "]: ", e);
}
}
public void addRegionsToSave(LodRegion r)
{
regionToSave.put(r.getRegionPos(), r);
}
private final SpamReducedLogger ramLogger = new SpamReducedLogger(1);
public void dumpBufferMemoryUsage()
{
if (!ramLogger.canMaybeLog())
return;
ArrayList<LodRegion> regions = new ArrayList<LodRegion>(regionToSave.values());
ramLogger.info("Dumping Ram Usage for file writer for {} with {} regions...",
lodDimension.dimension.getDimensionName(), regions.size());
int nonNullRegionCount = 0;
int nonDirtiedRegionCount = 0;
int writingRegionCount = 0;
long totalUsage = 0;
int[] detailCount = new int[LodUtil.DETAIL_OPTIONS];
long[] detailUsage = new long[LodUtil.DETAIL_OPTIONS];
for (LodRegion r : regions)
{
if (r == null)
continue;
nonNullRegionCount++;
if (!r.needSaving)
nonDirtiedRegionCount++;
if (r.isWriting.get() != 0)
writingRegionCount++;
LevelContainer[] container = r.debugGetDataContainers().clone();
if (container == null || container.length != LodUtil.DETAIL_OPTIONS)
{
for (int j = 0; j < lodDimension.getWidth(); j++)
LOGGER.error("DumpRamUsage encountered an invalid region!");
continue;
}
for (int i = 0; i < LodUtil.DETAIL_OPTIONS; i++)
{
if (container[i] == null)
continue;
detailCount[i]++;
long byteUsage = container[i].getRoughRamUsage();
detailUsage[i] += byteUsage;
totalUsage += byteUsage;
}
}
ramLogger.info("================================================");
ramLogger.info("Non Null Regions: [{}], Non-Dirtied Regions: [{}], Writing Regions: [{}], Bytes: [{}]",
nonNullRegionCount, nonDirtiedRegionCount, writingRegionCount, new UnitBytes(totalUsage));
ramLogger.info("------------------------------------------------");
for (int i = 0; i < LodUtil.DETAIL_OPTIONS; i++)
{
ramLogger.info("DETAIL {}: Containers: [{}], Bytes: [{}]", i, detailCount[i], new UnitBytes(detailUsage[i]));
}
ramLogger.info("================================================");
ramLogger.incLogTries();
}
/** Save all dirty regions in this LodDimension to file */
public void saveDirtyRegionsToFile(boolean blockUntilFinished)
{
// determine the regions to save
for (int i = 0; i < lodDimension.getWidth(); i++)
{
for (int j = 0; j < lodDimension.getWidth(); j++)
{
LodRegion r = lodDimension.getRegionByArrayIndex(i, j);
// FIXME: Note that the isWriting is a crude attempt at syncing. It won't work.
// It just reduces the chance of a race condition
if (r != null && r.needSaving)
{
if (lodDimension.GetIsRegionDirty(i, j) && lodDimension.getRegionByArrayIndex(i, j) != null)
{
saveRegionToFile(lodDimension.getRegionByArrayIndex(i, j));
lodDimension.SetIsRegionDirty(i, j, false);
}
regionToSave.put(r.getRegionPos(), r);
}
}
}
catch (Exception e)
// save the dimension data
ClientApi.DIMENSION_FINDER.saveDimensionPlayerData(this.dimensionDataSaveFolder);
trySaveRegionsToBeSaved();
// wait for the saving to finish if requested
if (blockUntilFinished)
{
e.printStackTrace();
if (ENABLE_SAVE_THREAD_LOGGING)
LOGGER.info("Blocking until lod file save finishes!");
try
{
fileWritingThreadPool.shutdown();
boolean worked = fileWritingThreadPool.awaitTermination(30, TimeUnit.SECONDS);
if (!worked)
LOGGER.error("File writing timed out! File data may not be saved correctly and may cause corruptions!!!");
}
catch (InterruptedException e)
{
LOGGER.error("File writing wait is interrupted! File data may not be saved correctly and may cause corruptions!!!: ", e);
}
finally
{
fileWritingThreadPool = Executors.newSingleThreadExecutor(new LodThreadFactory(this.getClass().getSimpleName(), Thread.NORM_PRIORITY + 1));
}
}
});
}
public void trySaveRegionsToBeSaved()
{
if (regionToSave.isEmpty())
return;
// Use Memory order Acquire to acquire any memory changes on getting this boolean
// (Corresponding call is the this::writerMain(...)::...setRelease(false);)
//boolean haventStarted = !isFileWritingThreadRunning.compareAndExchangeAcquire(false, true);
// The above needs java 9!
boolean haventStarted = isFileWritingThreadRunning.compareAndSet(false, true);
if (haventStarted)
{
// We acquired the atomic lock.
fileWritingThreadPool.execute(this::writerMain);
}
}
private void writerMain()
{
// Use Memory order Relaxed as no additional memory changes needed to be visible.
// (This is just a safety checks)
// boolean isStarted = isFileWritingThreadRunning.getPlain();
// The above needs java 9!
boolean isStarted = isFileWritingThreadRunning.get();
if (!isStarted)
throw new ConcurrentModificationException("WriterMain Triggered but the thead state is not started!?");
if (ENABLE_SAVE_THREAD_LOGGING)
LOGGER.info("Lod File Writer started. To-be-written-regions: " + regionToSave.size());
Instant start = Instant.now();
// Note: Since regionToSave is a ConcurrentHashMap, and the .values() return one that support concurrency,
// this for loop should be safe and loop until all values are gone.
while (!regionToSave.isEmpty())
{
for (LodRegion r : regionToSave.values())
{
try
{
if (r.isWriting.getAndIncrement() > 0)
continue;
//Check if the data has been swapped out right under me. Otherwise remove it from the entry
if (!regionToSave.remove(r.getRegionPos(), r))
continue;
r.needSaving = false;
Instant i = Instant.now();
if (ENABLE_SAVE_REGION_LOGGING)
LOGGER.info("Lod: Saving Region " + r.getRegionPos());
saveRegionToFile(r);
Instant j = Instant.now();
Duration d = Duration.between(i, j);
if (ENABLE_SAVE_REGION_LOGGING)
LOGGER.info("Lod: Region " + r.getRegionPos() + " save finish. Took " + d);
}
catch (Exception e)
{
LOGGER.error("Lod: UNCAUGHT exception when saving region " + r.getRegionPos() + ": ", e);
}
finally
{
r.isWriting.decrementAndGet();
}
}
}
Instant end = Instant.now();
if (ENABLE_SAVE_THREAD_LOGGING)
LOGGER.info("Lod File Writer completed. Took " + Duration.between(start, end));
// Use Memory order Release to release any memory changes on setting this boolean
// (Corresponding call is the this::saveRegions(...)::...compareAndExchangeAcquire(false, true);)
// isFileWritingThreadRunning.setRelease(false);
// The above needs java 9!
isFileWritingThreadRunning.set(false);
}
/**
* Save a specific region to disk.<br>
@@ -233,8 +546,9 @@ public class LodDimensionFileHandler
for (byte detailLevel = region.getMinDetailLevel(); detailLevel <= LodUtil.REGION_DETAIL_LEVEL; detailLevel++)
{
// 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.");
File oldFile = getRegionFile(region.regionPosX, region.regionPosZ, detailLevel, region.getVerticalQuality());
if (ENABLE_SAVE_REGION_LOGGING)
LOGGER.debug("saving region [" + region.regionPosX + ", " + region.regionPosZ + "] detail " + detailLevel + " to file.");
boolean isFileFullyGened = false;
// make sure the file and folder exists
@@ -244,10 +558,14 @@ public class LodDimensionFileHandler
// create it and the folder if need be
if (!oldFile.getParentFile().exists())
oldFile.getParentFile().mkdirs();
try {
try
{
oldFile.createNewFile();
} catch (IOException e) {
ClientApi.LOGGER.error("LOD file write error. Unable to create parent directory for [" + oldFile + "] error [" + e.getMessage() + "]: ");
}
catch (IOException e)
{
LOGGER.error("LOD file write error. Unable to create parent directory for [" + oldFile + "] error [" + e.getMessage() + "]: ");
e.printStackTrace();
continue;
}
@@ -259,8 +577,9 @@ public class LodDimensionFileHandler
// (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)))
try (FileInputStream fileInStream = new FileInputStream(oldFile))
{
XZCompressorInputStream inputStream = new XZCompressorInputStream(fileInStream);
fileVersion = inputStream.read();
inputStream.skip(1);
isFileFullyGened = (inputStream.read() & 0b10000000) != 0;
@@ -268,7 +587,7 @@ public class LodDimensionFileHandler
}
catch (IOException e)
{
ClientApi.LOGGER.warn("LOD file write warning. Unable to read existing file [" + oldFile + "] version. Treating it as latest version. [" + e.getMessage() + "]: ");
LOGGER.warn("LOD file write warning. Unable to read existing file [" + oldFile + "] version. Treating it as latest version. [" + e.getMessage() + "]: ");
e.printStackTrace();
}
@@ -285,21 +604,16 @@ public class LodDimensionFileHandler
}
// 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))
File tempFile = new File(oldFile.getPath() + TMP_FILE_EXTENSION);
try (FileOutputStream fileOutStream = new FileOutputStream(tempFile))
{
XZCompressorOutputStream outputStream = new XZCompressorOutputStream(fileOutStream, 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));
DataOutputStream dataStream = new DataOutputStream(outputStream);
boolean isNewDataFullyGened = region.getLevel(detailLevel).writeData(dataStream);
dataStream.close();
outputStream.close();
if (!isNewDataFullyGened && isFileFullyGened)
@@ -307,10 +621,13 @@ public class LodDimensionFileHandler
// 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 {
LOGGER.error("LOD file write error. Attempted to overwrite complete region with incomplete one [" + oldFile + "]");
try
{
tempFile.delete();
} catch (SecurityException e) {
}
catch (SecurityException e)
{
// Failed to delete temp file... just continue.
}
continue;
@@ -318,17 +635,18 @@ public class LodDimensionFileHandler
}
catch (IOException e)
{
ClientApi.LOGGER.error("LOD file write error. Unable to write to temp file [" + tempFile + "] error [" + e.getMessage() + "]: ");
e.printStackTrace();
LOGGER.error("LOD file write error. Unable to write to temp file [" + tempFile + "]: ", e);
continue;
}
// overwrite the old file with the new one
try {
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();
}
catch (IOException e)
{
LOGGER.error("LOD file write error. Unable to update file [" + oldFile + "]: ", e);
}
}
}
@@ -337,6 +655,24 @@ public class LodDimensionFileHandler
// helper methods //
//================//
/**
* Returns the save folder used for this dimension.
*
* @throws RuntimeException if there was an error getting the folder
*/
private String getFileBasePath() throws RuntimeException
{
try
{
return dimensionDataSaveFolder.getCanonicalPath() + File.separatorChar;
}
catch (IOException e)
{
LOGGER.warn("Unable to get the base save file path. Error: " + e.getMessage(), e);
throw new RuntimeException("DistantHorizons Get Save File Path Failure");
}
}
/**
* Return the name of the file that should contain the
* region at the given x and z. <br>
@@ -346,38 +682,27 @@ public class LodDimensionFileHandler
* <p>
* Returns null if there is an IO or security Exception.
*/
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) {
private File getRegionFile(int regionX, int regionZ, 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);
/** Returns null if no file is found */
private File getBestMatchingRegionFile(byte detailLevel, int regionX, int regionZ, VerticalQuality targetVertQuality)
{
// Search from least vertQuality to max vertQuality
do
{
File file = getRegionFile(regionX, regionZ, detailLevel, targetVertQuality);
if (file.exists())
return file; // Found target file.
targetVertQuality = VerticalQuality.next(targetVertQuality);
}
while (targetVertQuality != null);
return null;
}
@@ -0,0 +1,585 @@
/*
* This file is part of the Distant Horizons mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020-2022 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.handlers;
import com.electronwill.nightconfig.core.file.CommentedFileConfig;
import com.seibel.lod.core.api.ApiShared;
import com.seibel.lod.core.handlers.dimensionFinder.PlayerData;
import com.seibel.lod.core.handlers.dimensionFinder.SubDimCompare;
import com.seibel.lod.core.builders.lodBuilding.LodBuilder;
import com.seibel.lod.core.builders.lodBuilding.LodBuilderConfig;
import com.seibel.lod.core.enums.config.DistanceGenerationMode;
import com.seibel.lod.core.enums.config.VerticalQuality;
import com.seibel.lod.core.handlers.dependencyInjection.SingletonHandler;
import com.seibel.lod.core.logging.ConfigBasedLogger;
import com.seibel.lod.core.objects.lod.LodDimension;
import com.seibel.lod.core.objects.lod.LodRegion;
import com.seibel.lod.core.objects.lod.RegionPos;
import com.seibel.lod.core.util.DataPointUtil;
import com.seibel.lod.core.util.LodUtil;
import com.seibel.lod.core.wrapperInterfaces.IWrapperFactory;
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.IMinecraftClientWrapper;
import com.seibel.lod.core.wrapperInterfaces.world.IDimensionTypeWrapper;
import com.seibel.lod.core.wrapperInterfaces.world.IWorldWrapper;
import org.apache.logging.log4j.LogManager;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.StandardCopyOption;
import java.util.Arrays;
import java.util.Comparator;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicBoolean;
/**
* Used to guess the world folder for the player's current dimension.
*
* @author James Seibel
* @version 2022-3-31
*/
public class LodDimensionFinder
{
private static final IMinecraftClientWrapper MC = SingletonHandler.get(IMinecraftClientWrapper.class);
private static final ILodConfigWrapperSingleton CONFIG = SingletonHandler.get(ILodConfigWrapperSingleton.class);
private static final IWrapperFactory FACTORY = SingletonHandler.get(IWrapperFactory.class);
public static final ConfigBasedLogger LOGGER = new ConfigBasedLogger(LogManager.getLogger(LodDimensionFinder.class),
() -> CONFIG.client().advanced().debugging().debugSwitch().getLogFileSubDimEvent());
/** Increasing this will increase accuracy but increase calculation time */
private static final VerticalQuality VERTICAL_QUALITY_TO_TEST_WITH = VerticalQuality.LOW;
public static final String THREAD_NAME = "Sub-Dimension-Finder";
public static final String DEFAULT_SAVE_DIMENSION_FOLDER = "_Default-Sub-Dimension";
private PlayerData playerData = new PlayerData(MC);
private PlayerData firstSeenPlayerData = null;
private volatile LodDimension foundLodDimension = null;
/** If true the LodDimensionFileHelper is attempting to determine the folder for this dimension */
private AtomicBoolean determiningWorldFolder = new AtomicBoolean(false);
public LodDimensionFinder()
{
}
/** Returns true if a LodDimension has been found */
public boolean isDone()
{
return foundLodDimension != null;
}
/** Returns the found LodDimension */
public LodDimension getAndClearFoundLodDimension()
{
// clear the found dimension
LodDimension returnDim = this.foundLodDimension;
this.foundLodDimension = null;
return returnDim;
}
public void AttemptToDetermineSubDimensionAsync(IDimensionTypeWrapper dimensionTypeWrapper)
{
// prevent multiple threads running at the same time
if (determiningWorldFolder.getAndSet(true))
return;
// run asynchronously since this could take a while
Thread thread = new Thread(() ->
{
try
{
// attempt to get the file handler
File saveDir;
if (CONFIG.client().multiplayer().getMultiDimensionRequiredSimilarity() == 0)
{
// only allow 1 sub dimension per world
saveDir = getDefaultSubDimensionFolder(dimensionTypeWrapper);
}
else
{
saveDir = attemptToDetermineSubDimensionFolder();
}
if (saveDir == null)
return;
foundLodDimension = new LodDimension(dimensionTypeWrapper, ApiShared.lodBuilder.defaultDimensionWidthInRegions, saveDir);
}
catch (IOException e)
{
ApiShared.LOGGER.error("Unable to set the dimension file handler for dimension type [" + dimensionTypeWrapper.getDimensionName() + "]. Error: " + e.getMessage(), e);
}
finally
{
// make sure we unlock this method
determiningWorldFolder.set(false);
}
});
thread.setName(THREAD_NAME);
thread.start();
}
/**
* Returns the default save folder if it exists
* otherwise the first valid subDimension folder lexicographically.
*
* @throws IOException if the folder doesn't exist or can't be accessed
*/
public File getDefaultSubDimensionFolder(IDimensionTypeWrapper dimensionTypeWrapper) throws IOException
{
File subDimFolder = null;
LOGGER.info("Attempting to determine default sub-dimension for [" + MC.getCurrentDimension().getDimensionName() + "]");
// move any old data folders if they exist
File dimensionFolder = GetDimensionFolder(MC.getCurrentDimension(), "");
moveOldSaveFoldersIfNecessary(dimensionFolder, MC.getCurrentDimension(), DEFAULT_SAVE_DIMENSION_FOLDER);
// check if a sub dimension folder exists
if (dimensionFolder.listFiles() != null)
{
// at least one folder exists
LOGGER.info("Potential Sub Dimension folders: [" + dimensionFolder.listFiles(File::isDirectory).length + "]");
// search for a valid sub dimension folder
File[] potentialSubDimFolders = dimensionFolder.listFiles();
Arrays.sort(potentialSubDimFolders); // listFiles isn't necessarily sorted
for (File potentialSubDim : potentialSubDimFolders)
{
if (isValidSubDimensionDirectory(potentialSubDim))
{
if (potentialSubDim.getName().equals(DEFAULT_SAVE_DIMENSION_FOLDER))
{
// use the default save folder if possible
subDimFolder = potentialSubDim;
break;
}
else if (subDimFolder == null)
{
// only get the first non-default sub folder
subDimFolder = potentialSubDim;
}
}
}
}
// if no valid sub dimension was found, create a new one
if (subDimFolder == null)
{
subDimFolder = GetDimensionFolder(dimensionTypeWrapper, DEFAULT_SAVE_DIMENSION_FOLDER);
LOGGER.info("Default Sub Dimension not found. Creating: [" + DEFAULT_SAVE_DIMENSION_FOLDER + "]");
}
else
{
LOGGER.info("Default Sub Dimension set to: [" + LodUtil.shortenString(subDimFolder.getName(), 8) + "...]");
}
return subDimFolder;
}
/**
* Currently this method checks a single chunk (where the player is)
* and compares it against the same chunk position in the other dimension worlds to
* guess which world the player is in.
*
* @throws IOException if the folder doesn't exist or can't be accessed
*/
public File attemptToDetermineSubDimensionFolder() throws IOException
{
if (firstSeenPlayerData == null)
{
firstSeenPlayerData = playerData;
playerData = new PlayerData(MC);
}
// relevant positions
AbstractChunkPosWrapper playerChunkPos = FACTORY.createChunkPos(playerData.playerBlockPos);
int startingBlockPosX = playerChunkPos.getMinBlockX();
int startingBlockPosZ = playerChunkPos.getMinBlockZ();
RegionPos playerRegionPos = new RegionPos(playerChunkPos);
// chunk from the newly loaded dimension
IChunkWrapper newlyLoadedChunk = MC.getWrappedClientWorld().tryGetChunk(playerChunkPos);
// check if this chunk is valid to test
if (!CanDetermineDimensionFolder(newlyLoadedChunk))
return null;
// create a temporary dimension to store the test LOD
LodDimension newlyLoadedDim = new LodDimension(MC.getCurrentDimension(), 1, null, false);
newlyLoadedDim.move(playerRegionPos);
newlyLoadedDim.regions.set(playerRegionPos.x, playerRegionPos.z, new LodRegion(LodUtil.BLOCK_DETAIL_LEVEL, playerRegionPos, VERTICAL_QUALITY_TO_TEST_WITH));
// generate a LOD to test against
boolean lodGenerated = ApiShared.lodBuilder.generateLodNodeFromChunk(newlyLoadedDim, newlyLoadedChunk, new LodBuilderConfig(DistanceGenerationMode.FULL), true, true);
if (!lodGenerated)
return null;
// log the start of this attempt
LOGGER.info("Attempting to determine sub-dimension for [" + MC.getCurrentDimension().getDimensionName() + "]");
LOGGER.info("Player block pos in dimension: [" + playerData.playerBlockPos.getX() + "," + playerData.playerBlockPos.getY() + "," + playerData.playerBlockPos.getZ() + "]");
// new chunk data
long[][][] newChunkData = new long[LodUtil.CHUNK_WIDTH][LodUtil.CHUNK_WIDTH][];
for (int x = 0; x < LodUtil.CHUNK_WIDTH; x++)
{
for (int z = 0; z < LodUtil.CHUNK_WIDTH; z++)
{
long[] array = newlyLoadedDim.getRegion(playerRegionPos.x, playerRegionPos.z).getAllData(LodUtil.BLOCK_DETAIL_LEVEL, x + startingBlockPosX, z + startingBlockPosZ);
newChunkData[x][z] = array;
}
}
boolean newChunkHasData = !isDataEmpty(newChunkData);
// check if the chunk is actually empty
if (!newChunkHasData)
{
if (newlyLoadedChunk.getHeight() != 0)
{
// the chunk isn't empty but the LOD is...
String message = "Error: the chunk at (" + playerChunkPos.getX() + "," + playerChunkPos.getZ() + ") has a height of [" + newlyLoadedChunk.getHeight() + "] but the LOD generated is empty!";
LOGGER.error(message);
}
else
{
String message = "Warning: The chunk at (" + playerChunkPos.getX() + "," + playerChunkPos.getZ() + ") is empty.";
LOGGER.warn(message);
}
return null;
}
// get every folder (world) we have for this dimension
File dimensionFolder = GetDimensionFolder(newlyLoadedDim.dimension, "");
// check if the folder exists
if (dimensionFolder.listFiles() == null)
{
if (!dimensionFolder.exists())
{
// create the directory since it doesn't exist
dimensionFolder.mkdirs();
}
}
// move any old data folders if they exist
moveOldSaveFoldersIfNecessary(dimensionFolder, MC.getCurrentDimension(), UUID.randomUUID().toString());
// compare each world with the newly loaded one
SubDimCompare mostSimilarSubDim = null;
LOGGER.info("Potential Sub Dimension folders: [" + dimensionFolder.listFiles(File::isDirectory).length + "]");
for (File testDimFolder : dimensionFolder.listFiles())
{
if (!testDimFolder.isDirectory())
continue;
LOGGER.info("Testing sub dimension: [" + LodUtil.shortenString(testDimFolder.getName(), 8) + "]");
try
{
// get a LOD from this dimension folder
LodDimension tempLodDim = new LodDimension(null, 1, null, false);
tempLodDim.move(playerRegionPos);
LodDimensionFileHandler tempFileHandler = new LodDimensionFileHandler(testDimFolder, tempLodDim);
LodRegion testRegion = tempFileHandler.loadRegionFromFile(LodUtil.BLOCK_DETAIL_LEVEL, playerRegionPos, VERTICAL_QUALITY_TO_TEST_WITH);
// get data from this LOD
long[][][] testChunkData = new long[LodUtil.CHUNK_WIDTH][LodUtil.CHUNK_WIDTH][];
for (int x = 0; x < LodUtil.CHUNK_WIDTH; x++)
{
for (int z = 0; z < LodUtil.CHUNK_WIDTH; z++)
{
long[] array = testRegion.getAllData(LodUtil.BLOCK_DETAIL_LEVEL, x + startingBlockPosX, z + startingBlockPosZ);
testChunkData[x][z] = array;
}
}
// get the player data for this dimension folder
PlayerData testPlayerData = new PlayerData(testDimFolder);
LOGGER.info("Last known player pos: [" + testPlayerData.playerBlockPos.getX() + "," + testPlayerData.playerBlockPos.getY() + "," + testPlayerData.playerBlockPos.getZ() + "]");
// check if the block positions are close
int playerBlockDist = testPlayerData.playerBlockPos.getManhattanDistance(playerData.playerBlockPos);
ApiShared.LOGGER.info("Player block position distance between saved sub dimension and first seen is [" + playerBlockDist + "]");
// check if the chunk is actually empty
if (isDataEmpty(testChunkData))
{
String message = "The test chunk for dimension folder [" + LodUtil.shortenString(testDimFolder.getName(), 8) + "] and chunk pos (" + playerChunkPos.getX() + "," + playerChunkPos.getZ() + ") is empty. This is expected if the position is outside the sub-dimension's generated area.";
LOGGER.info(message);
continue;
}
// compare the two LODs
int equalDataPoints = 0;
int totalDataPointCount = 0;
for (int x = 0; x < LodUtil.CHUNK_WIDTH; x++)
{
for (int z = 0; z < LodUtil.CHUNK_WIDTH; z++)
{
for (int y = 0; y < newChunkData[x][z].length; y++)
{
if (newChunkData[x][z][y] == testChunkData[x][z][y])
{
equalDataPoints++;
}
totalDataPointCount++;
if (!DataPointUtil.doesItExist(newChunkData[x][z][y]) || !DataPointUtil.doesItExist(testChunkData[x][z][y]))
break;
}
}
}
// determine if this world is closer to the newly loaded world
SubDimCompare subDimCompare = new SubDimCompare(equalDataPoints, totalDataPointCount, playerBlockDist, testDimFolder);
if (mostSimilarSubDim == null || subDimCompare.compareTo(mostSimilarSubDim) > 0)
{
mostSimilarSubDim = subDimCompare;
}
LOGGER.info("Sub dimension [" + LodUtil.shortenString(testDimFolder.getName(), 8) + "...] is current dimension probability: " + LodUtil.shortenString(subDimCompare.getPercentEqual() + "", 5) + " (" + equalDataPoints + "/" + totalDataPointCount + ")");
}
catch (Exception e)
{
// this sub dimension isn't formatted correctly
// for now we are just assuming it is an unrelated file
}
}
// TODO if two sub dimensions contain the same LODs merge them
// the first seen player data is no longer needed, the sub dimension has been determined
firstSeenPlayerData = null;
if (mostSimilarSubDim != null && mostSimilarSubDim.isValidSubDim())
{
// we found a world folder that is similar, use it
LOGGER.info("Sub Dimension set to: [" + LodUtil.shortenString(mostSimilarSubDim.folder.getName(), 8) + "...] with an equality of [" + mostSimilarSubDim.getPercentEqual() + "]");
return mostSimilarSubDim.folder;
}
else
{
// no world folder was found, create a new one
double highestEqualityPercent = mostSimilarSubDim != null ? mostSimilarSubDim.getPercentEqual() : 0;
String newId = UUID.randomUUID().toString();
String message = "No suitable sub dimension found. The highest equality was [" + LodUtil.shortenString(highestEqualityPercent + "", 5) + "]. Creating a new sub dimension with ID: " + LodUtil.shortenString(newId, 8) + "...";
LOGGER.info(message);
return GetDimensionFolder(newlyLoadedDim.dimension, newId);
}
}
/**
* Returns the dimension folder with the specific ID if specified. <br>
* If the worldId is empty or null this returns the dimension parent folder <br>
* Example folder names: "dim_overworld/worldId", "dim_the_nether/worldId"
*/
public static File GetDimensionFolder(IDimensionTypeWrapper newDimensionType, String worldId)
{
// prevent null pointers
if (worldId == null)
worldId = "";
// make sure the ID is a valid path
worldId = worldId.replaceAll(LodUtil.INVALID_FILE_CHARACTERS_REGEX, "");
try
{
if (MC.hasSinglePlayerServer())
{
// local world
IWorldWrapper serverWorld = LodUtil.getServerWorldFromDimension(newDimensionType);
return new File(serverWorld.getSaveFolder().getCanonicalFile().getPath() + File.separatorChar + "lod" + File.separatorChar + worldId);
}
else
{
// multiplayer
return new File(MC.getGameDirectory().getCanonicalFile().getPath() +
File.separatorChar + "Distant_Horizons_server_data" + File.separatorChar + MC.getCurrentDimensionId() + File.separatorChar + worldId);
}
}
catch (IOException e)
{
LOGGER.error("Unable to get dimension folder for dimension [" + newDimensionType.getDimensionName() + "]", e);
return null;
}
}
/** Returns true if the given chunk is valid to test */
public boolean CanDetermineDimensionFolder(IChunkWrapper chunk)
{
// we can only guess if the given chunk can be converted into a LOD
return LodBuilder.canGenerateLodFromChunk(chunk);
}
/** Used for debugging, returns true if every data point is 0 */
private static boolean isDataEmpty(long[][][] chunkData)
{
for (long[][] xArray : chunkData)
{
for (long[] zArray : xArray)
{
for (long dataPoint : zArray)
{
if (dataPoint != 0)
{
return false;
}
}
}
}
return true;
}
/** Returns true if the given folder holds valid Lod Dimension data */
public static boolean isValidSubDimensionDirectory(File potentialFolder)
{
if (!potentialFolder.isDirectory())
// it needs to be a folder
return false;
if (potentialFolder.listFiles() == null)
// it needs to have folders in it
return false;
// check if there is at least one VerticalQuality folder in this directory
for (File internalFolder : potentialFolder.listFiles())
{
if (VerticalQuality.getByName(internalFolder.getName()) != null)
{
// one of the internal folders is a VerticalQuality folder
return true;
}
}
return false;
}
/**
* Moves any folders from the old save location
* (directly under the dimension type)
* to a sub-dimension folder with the given name.
*/
private void moveOldSaveFoldersIfNecessary(File dimensionFolder, IDimensionTypeWrapper dimensionType, String subDimensionName) throws IOException
{
// if there are no files this will return null
if (dimensionFolder.listFiles() == null)
return;
for (File folder : dimensionFolder.listFiles())
{
if (VerticalQuality.getByName(folder.getName()) != null)
{
// this is a LOD save folder
// create a new sub dimension and move the data into it
File newDimension = GetDimensionFolder(dimensionType, subDimensionName);
newDimension.mkdirs();
File oldDataNewPath = new File(newDimension.getPath() + File.separatorChar + folder.getName());
Files.move(folder.toPath(), oldDataNewPath.toPath(), StandardCopyOption.REPLACE_EXISTING);
}
else
{
// ignore this folder
}
}
}
public void updatePlayerData()
{
playerData.updateData(MC);
}
/** saves any necessary player data to the given world folder */
public void saveDimensionPlayerData(File worldFolder)
{
// get and create the file and path if they don't exist
File file = PlayerData.getFileForDimensionFolder(worldFolder);
if (!file.exists())
{
try
{
file.getParentFile().mkdirs();
file.createNewFile();
}
catch (IOException e)
{
LOGGER.error("Unable to save player dimension data for world folder [" + worldFolder.getPath() + "].", e);
return;
}
}
// determine the playerData
IMinecraftClientWrapper mc = SingletonHandler.get(IMinecraftClientWrapper.class);
PlayerData playerdata = new PlayerData(mc);
// write the data to file
CommentedFileConfig toml = CommentedFileConfig.builder(file).build();
playerdata.toTomlFile(toml);
}
}
@@ -0,0 +1,277 @@
/*
* This file is part of the Distant Horizons mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020-2022 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.handlers;
import java.io.DataInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.locks.ReentrantLock;
import com.seibel.lod.core.api.ApiShared;
import org.apache.commons.compress.compressors.xz.XZCompressorInputStream;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import com.seibel.lod.core.api.ClientApi;
import com.seibel.lod.core.enums.config.DistanceGenerationMode;
import com.seibel.lod.core.enums.config.VerticalQuality;
import com.seibel.lod.core.objects.lod.RegionPos;
import com.seibel.lod.core.objects.lod.VerticalLevelContainer;
import com.seibel.lod.core.util.LodUtil;
public class LodDimensionOldFileStructureHandler
{
/** This is the dimension that owns this file handler */
private final File dimensionDataSaveFolder;
private final LodDimensionFileHandler newFileHandler;
enum OldDistanceGenerationMode {
NONE,
BIOME_ONLY,
BIOME_ONLY_SIMULATE_HEIGHT,
SURFACE,
FEATURES,
FULL
}
/** lod */
private static final String FILE_NAME_PREFIX = "lod";
/** .txt */
private static final String FILE_EXTENSION = ".xz";
/** detail- */
private static final String DETAIL_FOLDER_NAME_PREFIX = "detail-";
private static final String RETIRED_OLD_STRUCT_POSTFIX = "-RETIRED-CAN-BE-DELETED";
public static final int LOD_SAVE_FILE_VERSION = 8;
public static final ExecutorService mergerThreads = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
private static class TempLodRegion {
final VerticalLevelContainer[] containers;
final VerticalQuality vertQual;
final int posX;
final int posZ;
TempLodRegion(VerticalQuality vertQual, RegionPos pos) {
this.vertQual = vertQual;
posX = pos.x;
posZ = pos.z;
containers = new VerticalLevelContainer[LodUtil.REGION_DETAIL_LEVEL+1];
}
}
public LodDimensionOldFileStructureHandler(LodDimensionFileHandler fileHandler)
{
dimensionDataSaveFolder = fileHandler.dimensionDataSaveFolder;
newFileHandler = fileHandler;
}
private void loadGenModeToRegion(TempLodRegion region, OldDistanceGenerationMode genMode)
{
int regionX = region.posX;
int regionZ = region.posZ;
for (byte detail = LodUtil.REGION_DETAIL_LEVEL; detail >= 0; detail--) {
File file = new File(getFileBasePath() + region.vertQual + File.separatorChar +
genMode + File.separatorChar + DETAIL_FOLDER_NAME_PREFIX + detail + File.separatorChar +
FILE_NAME_PREFIX + "." + regionX + "." + regionZ + FILE_EXTENSION);
if (!file.exists()) continue;
if (!file.isFile()) continue;
long fileSize = file.length();
if (fileSize == 0) continue;
try (FileInputStream fileInStream = new FileInputStream(file))
{
XZCompressorInputStream inputStream = new XZCompressorInputStream(fileInStream);
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 too old.
// close the reader and delete the file.
inputStream.close();
ApiShared.LOGGER.info("Outdated LOD region file for region: (" + regionX + "," + regionZ + ")"
+ " version found: " + fileVersion
+ ", version requested: " + LOD_SAVE_FILE_VERSION
+ ". this region file will not be read and merged into the new save structure.");
continue;
}
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();
ApiShared.LOGGER.info("Unexpected newer LOD region file for region: (" + regionX + "," + regionZ + ")"
+ " version found: " + fileVersion
+ ", version requested: " + LOD_SAVE_FILE_VERSION
+ " this region file will not be read and merged into the new save structure.");
continue;
}
else if (fileVersion < LOD_SAVE_FILE_VERSION)
{
ApiShared.LOGGER.debug("Old LOD region file for region: (" + regionX + "," + regionZ + ")"
+ " version found: " + fileVersion
+ ", version requested: " + LOD_SAVE_FILE_VERSION
+ ". this region file be read, updated, and merged into the new save structure.");
}
VerticalLevelContainer data = new VerticalLevelContainer(new DataInputStream(inputStream), fileVersion, detail);
if (region.containers[detail] == null) {
region.containers[detail] = data;
} else {
region.containers[detail].addChunkOfData(data.dataContainer, 0, 0, data.size, data.size, false);
}
inputStream.close();
}
catch (IOException ioEx)
{
ApiShared.LOGGER.error("LOD file read error. Unable to read xz compressed file [" + file + "] error [" + ioEx.getMessage() + "]: ");
ioEx.printStackTrace();
}
}
}
private void saveRegion(TempLodRegion region) {
for (int detail=0; detail<=LodUtil.REGION_DETAIL_LEVEL; detail++) {
if (region.containers[detail] == null) continue;
newFileHandler.saveDirect(region.posX, region.posZ, region.vertQual, region.containers[detail]);
}
}
private void loadAndMergeAndSaveRegion(VerticalQuality verticalQuality, RegionPos regionPos)
{
ApiShared.LOGGER.info("Merging region "+regionPos+" at "+verticalQuality+"...");
TempLodRegion region = new TempLodRegion(verticalQuality, regionPos);
ApiShared.LOGGER.info("Reading data...");
loadGenModeToRegion(region, OldDistanceGenerationMode.FULL);
loadGenModeToRegion(region, OldDistanceGenerationMode.FEATURES);
loadGenModeToRegion(region, OldDistanceGenerationMode.SURFACE);
loadGenModeToRegion(region, OldDistanceGenerationMode.BIOME_ONLY_SIMULATE_HEIGHT);
loadGenModeToRegion(region, OldDistanceGenerationMode.BIOME_ONLY);
loadGenModeToRegion(region, OldDistanceGenerationMode.NONE);
ApiShared.LOGGER.info("Writing data...");
saveRegion(region);
ApiShared.LOGGER.info("region "+regionPos+" at "+verticalQuality+" merged");
}
private RegionPos parseFileName(String fileName) {
if (!fileName.endsWith(FILE_EXTENSION)) return null;
if (!fileName.startsWith(FILE_NAME_PREFIX)) return null;
String[] array = fileName.split("\\."); // Array content: "lod", "-1", "1", ".xz"
if (array.length!=4) return null;
try {
return new RegionPos(Integer.parseInt(array[1]), Integer.parseInt(array[2]));
} catch (NumberFormatException e) {
return null;
}
}
private HashSet<RegionPos> scanOldRegionFiles(VerticalQuality vertQual, OldDistanceGenerationMode genMode) {
HashSet<RegionPos> result = new HashSet<RegionPos>();
File baseBaseFolder = new File(getFileBasePath() + vertQual + File.separatorChar + genMode);
if (!baseBaseFolder.exists()) return result;
for (byte detail=0; detail <= LodUtil.REGION_DETAIL_LEVEL; detail++) {
File baseFolder = new File(getFileBasePath() + vertQual + File.separatorChar +
genMode + File.separatorChar + DETAIL_FOLDER_NAME_PREFIX + detail);
if (!baseFolder.exists()) continue;
if (!baseFolder.isDirectory()) continue;
File[] subFiles = baseFolder.listFiles();
for (File subFile : subFiles) {
if (!subFile.isFile()) continue;
if (!subFile.canRead()) continue;
RegionPos pos = parseFileName(subFile.getName());
if (pos != null) result.add(pos);
}
}
return result;
}
private void renameOldFileStructure(VerticalQuality vertQual, OldDistanceGenerationMode genMode) {
File baseBaseFolder = new File(getFileBasePath() + vertQual + File.separatorChar + genMode);
if (!baseBaseFolder.exists()) return;
baseBaseFolder.renameTo(new File(getFileBasePath() + vertQual + File.separatorChar + genMode + RETIRED_OLD_STRUCT_POSTFIX));
}
public void mergeOldFileStructureForVertQuality(VerticalQuality vertQual) {
File baseFile = new File(getFileBasePath() + vertQual);
if (!baseFile.exists()) return;
if (!baseFile.isDirectory()) return;
HashSet<RegionPos> totalPos = new HashSet<RegionPos>();
totalPos.addAll(scanOldRegionFiles(vertQual, OldDistanceGenerationMode.NONE));
totalPos.addAll(scanOldRegionFiles(vertQual, OldDistanceGenerationMode.BIOME_ONLY));
totalPos.addAll(scanOldRegionFiles(vertQual, OldDistanceGenerationMode.BIOME_ONLY_SIMULATE_HEIGHT));
totalPos.addAll(scanOldRegionFiles(vertQual, OldDistanceGenerationMode.SURFACE));
totalPos.addAll(scanOldRegionFiles(vertQual, OldDistanceGenerationMode.FEATURES));
totalPos.addAll(scanOldRegionFiles(vertQual, OldDistanceGenerationMode.FULL));
ArrayList<Future<?>> futures = new ArrayList<Future<?>>();
for (RegionPos pos : totalPos) {
futures.add(mergerThreads.submit(() -> {
loadAndMergeAndSaveRegion(vertQual, pos);
return true;
}));
}
futures.forEach(t ->
{
try
{
t.get();
}
catch (Exception e)
{
e.printStackTrace();
}
});
renameOldFileStructure(vertQual, OldDistanceGenerationMode.NONE);
renameOldFileStructure(vertQual, OldDistanceGenerationMode.BIOME_ONLY);
renameOldFileStructure(vertQual, OldDistanceGenerationMode.BIOME_ONLY_SIMULATE_HEIGHT);
renameOldFileStructure(vertQual, OldDistanceGenerationMode.SURFACE);
renameOldFileStructure(vertQual, OldDistanceGenerationMode.FEATURES);
renameOldFileStructure(vertQual, OldDistanceGenerationMode.FULL);
}
private String getFileBasePath()
{
try
{
return dimensionDataSaveFolder.getCanonicalPath() + File.separatorChar;
}
catch (IOException e)
{
ApiShared.LOGGER.warn("Unable to get the base save file path. Error: " + e.getMessage(), e);
throw new RuntimeException("DistantHorizons Get Save File Path Failure");
}
}
}
@@ -1,8 +1,8 @@
/*
* This file is part of the Distant Horizon mod (formerly the LOD Mod),
* This file is part of the Distant Horizons mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020 James Seibel
* Copyright (C) 2020-2022 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
@@ -46,6 +46,7 @@ public class ReflectionHandler implements IReflectionHandler
private final Object mcOptionsObject;
private Boolean sodiumPresent = null;
private boolean optifinePresent = false;
@@ -87,6 +88,7 @@ public class ReflectionHandler implements IReflectionHandler
{
if (field.getName().equals("ofFogType"))
{
optifinePresent = true;
ofFogField = field;
return;
}
@@ -158,7 +160,11 @@ public class ReflectionHandler implements IReflectionHandler
}
return false;
}
@Override
public boolean optifinePresent()
{
return optifinePresent;
}
@@ -0,0 +1,164 @@
/*
* This file is part of the Distant Horizons mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020-2022 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.handlers.dependencyInjection;
import java.util.HashMap;
import java.util.Map;
/**
* This class takes care of tracking objects used in dependency injection.
*
* @author James Seibel
* @version 3-4-2022
*/
public class DependencyHandler
{
private final Map<Class<?>, Object> dependencies = new HashMap<Class<?>, Object>();
private boolean bindingFinished = false;
/**
* Links the given implementation object to an interface so it can be referenced later.
*
* @param depenencyInterface The interface the implementation object should implement.
* @param dependencyImplementation A object that implements the depenencyInterface interface.
* @throws IllegalStateException if the implementation object doesn't implement
* the interface or the interface has already been bound.
*/
public void bind(Class<?> depenencyInterface, Object dependencyImplementation) throws IllegalStateException
{
// only allow binding before the finishBinding method is called
if (bindingFinished)
{
throw new IllegalStateException("The dependency [" + depenencyInterface.getSimpleName() + "] cannot be bound, Binding is finished for [" + this.getClass().getSimpleName() + "]. Make sure your bindings are happening before the [bindingFinished] method is being called.");
}
// make sure we haven't already bound this dependency
if (dependencies.containsKey(depenencyInterface))
{
throw new IllegalStateException("The dependency [" + depenencyInterface.getSimpleName() + "] has already been bound.");
}
// make sure the given dependency implements the necessary interfaces
boolean implementsInterface = checkIfClassImplements(dependencyImplementation.getClass(), depenencyInterface);
boolean implementsBindable = checkIfClassImplements(dependencyImplementation.getClass(), IBindable.class);
// display any errors
if (!implementsInterface)
{
throw new IllegalStateException("The dependency [" + dependencyImplementation.getClass().getSimpleName() + "] doesn't implement the interface [" + depenencyInterface.getSimpleName() + "].");
}
if (!implementsBindable)
{
throw new IllegalStateException("The dependency [" + dependencyImplementation.getClass().getSimpleName() + "] doesn't implement the interface [" + IBindable.class.getSimpleName() + "].");
}
dependencies.put(depenencyInterface, dependencyImplementation);
}
/**
* Checks if classToTest (or one of its ancestors)
* implements the given interface.
*/
private boolean checkIfClassImplements(Class<?> classToTest, Class<?> interfaceToLookFor)
{
// check the parent class (if applicable)
if (classToTest.getSuperclass() != Object.class && classToTest.getSuperclass() != null)
{
if (checkIfClassImplements(classToTest.getSuperclass(), interfaceToLookFor))
{
return true;
}
}
// check interfaces
for (Class<?> implementationInterface : classToTest.getInterfaces())
{
// recurse to check interface parents if necessary
if (implementationInterface.getInterfaces().length != 0)
{
if (checkIfClassImplements(implementationInterface, interfaceToLookFor))
{
return true;
}
}
if (implementationInterface.equals(interfaceToLookFor))
{
return true;
}
}
return false;
}
/**
* Returns a dependency of type T if one has been bound.
* Returns null otherwise.
*
* @param <T> class of the dependency
* (inferred from the objectClass parameter)
* @param interfaceClass Interface of the dependency
* @return the dependency of type T
* @throws ClassCastException If the dependency isn't able to be cast to type T.
* (this shouldn't normally happen, unless the bound object changed somehow)
*/
@SuppressWarnings("unchecked")
public <T extends IBindable> T get(Class<?> interfaceClass) throws ClassCastException
{
// getting dependencies should only happen after everything has been bound
if (!bindingFinished)
{
throw new IllegalStateException("Binding hasn't been finished for [" + this.getClass().getSimpleName() + "]. Make sure you are calling the [bindingFinished] method before calling [get].");
}
return (T) dependencies.get(interfaceClass);
}
/**
* Should only be called after all Binds have been done.
* Calls the delayedSetup method for each dependency. <br> <br>
*
* This is done so we can have circular dependencies.
*/
public void finishBinding()
{
// (yes technically the binding isn't finished,
// but this needs to be set to "true" so we can use "get")
bindingFinished = true;
for (Class<?> interfaceKey : dependencies.keySet())
{
IBindable concreteObject = get(interfaceKey);
concreteObject.finishDelayedSetup();
}
}
/** returns whether the finishBinding method has been called */
public boolean getBindingFinished()
{
return bindingFinished;
}
}
@@ -0,0 +1,44 @@
/*
* This file is part of the Distant Horizons mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020-2022 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.handlers.dependencyInjection;
/**
* Necessary for all singletons that can be dependency injected.
*
* @author James Seibel
* @version 3-4-2022
*/
public interface IBindable
{
/**
* Finish initializing this object. <br> <br>
*
* Generally this should just used for getting other objects through
* dependency injection and is specifically designed to allow
* for circular references. <br><br>
*
* If no circular dependencies are required this method
* doesn't have to be implemented.
*/
public default void finishDelayedSetup()
{
}
}
@@ -0,0 +1,91 @@
/*
* This file is part of the Distant Horizons mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020-2022 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.handlers.dependencyInjection;
import com.seibel.lod.core.api.ApiShared;
import com.seibel.lod.core.wrapperInterfaces.modAccessor.IModAccessor;
/**
* This class takes care of dependency injection for mods accessors. (for mod compatibility
* support).
*
* This is basically the same as the SingletonHandler, except it can return null.
* Getting null either means the mod isn't loaded in the game
* or it hasn't been implemented for the given Minecraft version.
*
* @author James Seibel
* @author Leetom
* @version 3-1-2022
*/
public class ModAccessorHandler
{
private static final DependencyHandler dependencyHandler = new DependencyHandler();
/**
* Links the given mod accessor to an interface so it can be referenced later.
*
* @param interfaceClass The interface the mod accessor should implement.
* @param modAccessor An object that implements the interfaceClass interface.
* @throws IllegalStateException if the mod accessor doesn't implement
* the interface or the interface has already been bound.
*/
public static void bind(Class<? extends IModAccessor> interfaceClass, IModAccessor modAccessor)
throws IllegalStateException
{
dependencyHandler.bind(interfaceClass, modAccessor);
ApiShared.LOGGER.info("Registored mod comatibility accessor for " + modAccessor.getModName());
}
/**
* Returns a mod accessor of type T if one has been bound.
* Returns null otherwise.
*
* @param <T> class of the mod accessor
* (inferred from the objectClass parameter)
* @param interfaceClass Interface of the mod accessor
* @return the dependency of type T
* @throws ClassCastException If the mod accessor isn't able to be cast to type T.
* (this shouldn't normally happen, unless the bound object changed somehow)
*/
public static <T extends IModAccessor> T get(Class<T> objectClass) throws ClassCastException
{
return dependencyHandler.get(objectClass);
}
/**
* Should only be called after all Binds have been done.
* Calls the delayedSetup method for each dependency. <br> <br>
*
* This is done so we can have circular dependencies.
*/
public static void finishBinding()
{
dependencyHandler.finishBinding();
}
/** returns whether the finishBinding method has been called */
public static boolean bindingFinished()
{
return dependencyHandler.getBindingFinished();
}
}
@@ -0,0 +1,90 @@
/*
* This file is part of the Distant Horizons mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020-2022 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.handlers.dependencyInjection;
/**
* This class takes care of dependency injection
* for singletons.
*
* @author James Seibel
* @version 3-5-2022
*/
public class SingletonHandler
{
private static final DependencyHandler dependencyHandler = new DependencyHandler();
/**
* Links the given implementation object to an interface so it can be referenced later.
*
* @param depenencyInterface The interface the implementation object should implement.
* @param dependencyImplementation A object that implements the depenencyInterface interface.
* @throws IllegalStateException if the implementation object doesn't implement
* the interface or the interface has already been bound.
*/
public static void bind(Class<?> interfaceClass, Object singletonReference) throws IllegalStateException
{
dependencyHandler.bind(interfaceClass, singletonReference);
}
/**
* Returns a dependency of type T if one has been bound.
* Returns null otherwise.
*
* @param <T> class of the dependency
* (inferred from the objectClass parameter)
* @param interfaceClass Interface of the dependency
* @return the dependency of type T
* @throws NullPointerException If no dependency was bound.
* @throws ClassCastException If the dependency isn't able to be cast to type T.
* (this shouldn't normally happen, unless the bound object changed somehow)
*/
public static <T> T get(Class<T> interfaceClass) throws NullPointerException, ClassCastException
{
T foundObject = dependencyHandler.get(interfaceClass);
// throw an error if the given singleton doesn't exist.
if (foundObject == null)
{
throw new NullPointerException("The singleton [" + interfaceClass.getSimpleName() + "] was never bound. If you are calling [bind], make sure it is happening before you call [get].");
}
return foundObject;
}
/**
* Should only be called after all Binds have been done.
* Calls the delayedSetup method for each dependency. <br> <br>
*
* This is done so we can have circular dependencies.
*/
public static void finishBinding()
{
dependencyHandler.finishBinding();
}
/** returns whether the finishBinding method has been called */
public static boolean getBindingFinished()
{
return dependencyHandler.getBindingFinished();
}
}
@@ -0,0 +1,131 @@
/*
* This file is part of the Distant Horizons mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020-2022 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.handlers.dimensionFinder;
import com.electronwill.nightconfig.core.file.CommentedFileConfig;
import com.seibel.lod.core.handlers.dependencyInjection.SingletonHandler;
import com.seibel.lod.core.wrapperInterfaces.IWrapperFactory;
import com.seibel.lod.core.wrapperInterfaces.block.AbstractBlockPosWrapper;
import com.seibel.lod.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper;
import java.io.File;
/**
* Data container for any player data we can use to differentiate one dimension from another.
*
* @author James Seibel
* @version 2022-3-26
*/
public class PlayerData
{
public static final IWrapperFactory FACTORY = SingletonHandler.get(IWrapperFactory.class);
private static final String playerDataFileName = "_playerData.toml";
public static final String PLAYER_BLOCK_POS_X_PATH = "playerBlockPosX";
public static final String PLAYER_BLOCK_POS_Y_PATH = "playerBlockPosY";
public static final String PLAYER_BLOCK_POS_Z_PATH = "playerBlockPosZ";
public AbstractBlockPosWrapper playerBlockPos;
// not implemented yet
public static final String WORLD_SPAWN_POS_X_PATH = "worldSpawnBlockPosX";
public static final String WORLD_SPAWN_POS_Y_PATH = "worldSpawnBlockPosY";
public static final String WORLD_SPAWN_POS_Z_PATH = "worldSpawnBlockPosZ";
/**
* The client world has access to a spawn point, so this should be possible to fill in.
* I'm not sure what this will look like for worlds that don't have a spawn point.
*/
public AbstractBlockPosWrapper worldSpawnPointBlockPos;
public PlayerData(IMinecraftClientWrapper mc)
{
updateData(mc);
}
public PlayerData(File dimensionFolder)
{
File file = getFileForDimensionFolder(dimensionFolder);
CommentedFileConfig toml = CommentedFileConfig.builder(file).build();
toml.load();
// get the player block pos if it is specified
if (toml.contains(PLAYER_BLOCK_POS_X_PATH)
&& toml.contains(PLAYER_BLOCK_POS_Y_PATH)
&& toml.contains(PLAYER_BLOCK_POS_Z_PATH))
{
int x = toml.getIntOrElse(PLAYER_BLOCK_POS_X_PATH, 0);
int y = toml.getIntOrElse(PLAYER_BLOCK_POS_Y_PATH, 0);
int z = toml.getIntOrElse(PLAYER_BLOCK_POS_Z_PATH, 0);
this.playerBlockPos = FACTORY.createBlockPos(x, y, z);
}
else
{
this.playerBlockPos = FACTORY.createBlockPos(0, 0, 0);
}
}
public static File getFileForDimensionFolder(File file)
{
return new File(file.getPath() + File.separatorChar + playerDataFileName);
}
/** Should be called often to make sure this object is up to date with the player's info */
public void updateData(IMinecraftClientWrapper mc)
{
if (mc.playerExists())
{
this.playerBlockPos = mc.getPlayerBlockPos();
}
}
/** Writes everything from this object to the file given. */
public void toTomlFile(CommentedFileConfig toml)
{
if (playerBlockPos == null)
{
toml.remove(PLAYER_BLOCK_POS_X_PATH);
toml.remove(PLAYER_BLOCK_POS_Y_PATH);
toml.remove(PLAYER_BLOCK_POS_Z_PATH);
} else {
// player block pos
toml.add(PLAYER_BLOCK_POS_X_PATH, playerBlockPos.getX());
toml.add(PLAYER_BLOCK_POS_Y_PATH, playerBlockPos.getY());
toml.add(PLAYER_BLOCK_POS_Z_PATH, playerBlockPos.getZ());
}
toml.save();
}
@Override
public String toString()
{
return "PlayerBlockPos: [" + playerBlockPos.getX() + "," + playerBlockPos.getY() + "," + playerBlockPos.getZ() + "]";
}
}
@@ -0,0 +1,85 @@
/*
* This file is part of the Distant Horizons mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020-2022 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.handlers.dimensionFinder;
import com.seibel.lod.core.handlers.dependencyInjection.SingletonHandler;
import com.seibel.lod.core.wrapperInterfaces.config.ILodConfigWrapperSingleton;
import org.jetbrains.annotations.NotNull;
import java.io.File;
/**
* Contains data used to compare different sub LodDimensions.
* Sub Dimensions are the different folders under a dimension.
* For example: "\Distant_Horizons_server_data\server_1\dim_the_nether\6fb97c01-e4c7-4634-87db-36b1e490e4c3"
* is a sub dimension for the server "server_1" in the nether.
*
* @author James Seibel
* @version 2022-3-26
*/
public class SubDimCompare implements Comparable<SubDimCompare>
{
private static final ILodConfigWrapperSingleton CONFIG = SingletonHandler.get(ILodConfigWrapperSingleton.class);
public int equalDataPoints = 0;
public int totalDataPoints = 0;
public int playerPosDist = 0;
public File folder = null;
public SubDimCompare(int newEqualDataPoints, int newTotalDataPoints, int newPlayerPosDistance, File newSubDimFolder)
{
this.equalDataPoints = newEqualDataPoints;
this.totalDataPoints = newTotalDataPoints;
this.playerPosDist = newPlayerPosDistance;
this.folder = newSubDimFolder;
}
/** returns a number between 0 (not equal) and 1 (totally equal) */
public double getPercentEqual()
{
return (double) equalDataPoints / (double) totalDataPoints;
}
@Override
public int compareTo(@NotNull SubDimCompare other)
{
if (this.equalDataPoints != other.equalDataPoints)
{
// compare based on data points
return Integer.compare(this.equalDataPoints, other.equalDataPoints);
}
else
{
// break ties based on player position
return Integer.compare(this.playerPosDist, other.playerPosDist);
}
}
/** Returns true if this sub dimension is close enough to be considered a valid sub dimension */
public boolean isValidSubDim()
{
double minimumSimilarityRequired = CONFIG.client().multiplayer().getMultiDimensionRequiredSimilarity();
return this.getPercentEqual() >= minimumSimilarityRequired || this.playerPosDist <= 3;
}
}
@@ -0,0 +1,92 @@
/*
* This file is part of the Distant Horizons mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020-2022 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.logging;
import com.seibel.lod.core.api.ClientApi;
import com.seibel.lod.core.enums.config.LoggerMode;
import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.message.Message;
import java.lang.ref.WeakReference;
import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.function.Supplier;
public class ConfigBasedLogger {
public static final List<WeakReference<ConfigBasedLogger>> loggers
= Collections.synchronizedList(new LinkedList<WeakReference<ConfigBasedLogger>>());
public static synchronized void updateAll() {
loggers.removeIf((logger) -> logger.get()==null);
loggers.forEach((logger) -> {
ConfigBasedLogger l = logger.get();
if (l!=null) l.update();
});
}
private LoggerMode mode;
private final Supplier<LoggerMode> getter;
private final Logger logger;
public ConfigBasedLogger(Logger logger, Supplier<LoggerMode> configQuery) {
getter = configQuery;
mode = getter.get();
this.logger = logger;
loggers.add(new WeakReference<>(this));
}
public void update() {
mode = getter.get();
}
public boolean canMaybeLog() {return mode != LoggerMode.DISABLED;}
public void log(Level level, String str, Object... param) {
Message msg = logger.getMessageFactory().newMessage(str, param);
String msgStr = msg.getFormattedMessage();
if (mode.levelForFile.isLessSpecificThan(level)) {
Level logLevel = level.isLessSpecificThan(Level.INFO) ? Level.INFO : level;
if (param.length > 0 && param[param.length-1] instanceof Throwable)
logger.log(logLevel, msgStr, (Throwable)param[param.length-1]);
else logger.log(logLevel, msgStr);
}
if (mode.levelForChat.isLessSpecificThan(level)) {
if (param.length > 0 && param[param.length-1] instanceof Throwable)
ClientApi.logToChat(level, msgStr + "\nat\n" + Arrays.toString(((Throwable) param[param.length - 1]).getStackTrace()));
else ClientApi.logToChat(level, msgStr);
}
}
public void error(String str, Object... param) {
log(Level.ERROR, str, param);
}
public void warn(String str, Object... param) {
log(Level.WARN, str, param);
}
public void info(String str, Object... param) {
log(Level.INFO, str, param);
}
public void debug(String str, Object... param) {
log(Level.DEBUG, str, param);
}
public void trace(String str, Object... param) {
log(Level.TRACE, str, param);
}
}
@@ -0,0 +1,139 @@
/*
* This file is part of the Distant Horizons mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020-2022 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.logging;
import com.seibel.lod.core.api.ApiShared;
import com.seibel.lod.core.api.ClientApi;
import com.seibel.lod.core.enums.config.LoggerMode;
import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.message.Message;
import java.lang.ref.WeakReference;
import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Supplier;
public class ConfigBasedSpamLogger {
public static final List<WeakReference<ConfigBasedSpamLogger>> loggers
= Collections.synchronizedList(new LinkedList<WeakReference<ConfigBasedSpamLogger>>());
public static synchronized void updateAll(boolean flush) {
loggers.removeIf((logger) -> logger.get()==null);
loggers.forEach((logger) -> {
ConfigBasedSpamLogger l = logger.get();
if (l!=null) l.update();
if (l!=null && flush) l.reset();
});
}
private LoggerMode mode;
private final Supplier<LoggerMode> getter;
private final int maxLogCount;
private final AtomicInteger logTries = new AtomicInteger(0);
private final Logger logger;
public ConfigBasedSpamLogger(Logger logger, Supplier<LoggerMode> configQuery, int maxLogPerSec) {
getter = configQuery;
mode = getter.get();
maxLogCount = maxLogPerSec;
this.logger = logger;
loggers.add(new WeakReference<>(this));
}
public void reset() {logTries.set(0);}
public boolean canMaybeLog() {return mode != LoggerMode.DISABLED && logTries.get() < maxLogCount;}
public void update() {
mode = getter.get();
}
public void log(Level level, String str, Object... param) {
if (logTries.get() >= maxLogCount) return;
Message msg = logger.getMessageFactory().newMessage(str, param);
String msgStr = msg.getFormattedMessage();
if (mode.levelForFile.isLessSpecificThan(level)) {
Level logLevel = level.isLessSpecificThan(Level.INFO) ? Level.INFO : level;
if (param.length > 0 && param[param.length-1] instanceof Throwable)
logger.log(logLevel, msgStr, (Throwable)param[param.length-1]);
else logger.log(logLevel, msgStr);
}
if (mode.levelForChat.isLessSpecificThan(level)) {
if (param.length > 0 && param[param.length - 1] instanceof Throwable)
ClientApi.logToChat(level, msgStr + "\nat\n" + Arrays.toString(((Throwable) param[param.length - 1]).getStackTrace()));
else ClientApi.logToChat(level, msgStr);
}
}
public void error(String str, Object... param) {
log(Level.ERROR, str, param);
}
public void warn(String str, Object... param) {
log(Level.WARN, str, param);
}
public void info(String str, Object... param) {
log(Level.INFO, str, param);
}
public void debug(String str, Object... param) {
log(Level.DEBUG, str, param);
}
public void trace(String str, Object... param) {
log(Level.TRACE, str, param);
}
public void incLogTries() {
logTries.getAndIncrement();
}
public void logInc(Level level, String str, Object... param) {
if (logTries.getAndIncrement() >= maxLogCount) return;
Message msg = logger.getMessageFactory().newMessage(str, param);
String msgStr = msg.getFormattedMessage();
if (mode.levelForFile.isLessSpecificThan(level)) {
Level logLevel = level.isLessSpecificThan(Level.INFO) ? Level.INFO : level;
if (param.length > 0 && param[param.length-1] instanceof Throwable)
logger.log(logLevel, msgStr, (Throwable)param[param.length-1]);
else logger.log(logLevel, msgStr);
}
if (mode.levelForChat.isLessSpecificThan(level)) {
if (param.length > 0 && param[param.length - 1] instanceof Throwable)
ClientApi.logToChat(level, msgStr + "\nat\n" + Arrays.toString(((Throwable) param[param.length - 1]).getStackTrace()));
else ClientApi.logToChat(level, msgStr);
}
}
public void errorInc(String str, Object... param) {
logInc(Level.ERROR, str, param);
}
public void warnInc(String str, Object... param) {
logInc(Level.WARN, str, param);
}
public void infoInc(String str, Object... param) {
logInc(Level.INFO, str, param);
}
public void debugInc(String str, Object... param) {
logInc(Level.DEBUG, str, param);
}
public void traceInc(String str, Object... param) {
logInc(Level.TRACE, str, param);
}
}
@@ -0,0 +1,97 @@
/*
* This file is part of the Distant Horizons mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020-2022 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.logging;
import java.lang.ref.WeakReference;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import com.seibel.lod.core.api.ApiShared;
import org.apache.logging.log4j.Level;
public class SpamReducedLogger {
public static final List<WeakReference<SpamReducedLogger>> loggers
= Collections.synchronizedList(new LinkedList<WeakReference<SpamReducedLogger>>());
public static synchronized void flushAll() {
loggers.removeIf((logger) -> logger.get()==null);
loggers.forEach((logger) -> {
SpamReducedLogger l = logger.get();
if (l!=null) l.reset();
});
}
private final int maxLogCount;
private final AtomicInteger logTries = new AtomicInteger(0);
public SpamReducedLogger(int maxLogPerSec) {
maxLogCount = maxLogPerSec;
loggers.add(new WeakReference<SpamReducedLogger>(this));
}
public void reset() {logTries.set(0);}
public boolean canMaybeLog() {return logTries.get() < maxLogCount;}
public void log(Level level, String str, Object... param) {
if (logTries.get() >= maxLogCount) return;
ApiShared.LOGGER.log(level.isLessSpecificThan(Level.INFO) ? Level.INFO : level, str, param);
}
public void error(String str, Object... param) {
log(Level.ERROR, str, param);
}
public void warn(String str, Object... param) {
log(Level.WARN, str, param);
}
public void info(String str, Object... param) {
log(Level.INFO, str, param);
}
public void debug(String str, Object... param) {
log(Level.DEBUG, str, param);
}
public void trace(String str, Object... param) {
log(Level.TRACE, str, param);
}
public void incLogTries() {
logTries.getAndIncrement();
}
public void logInc(Level level, String str, Object... param) {
if (logTries.getAndIncrement() >= maxLogCount) return;
ApiShared.LOGGER.log(level.isLessSpecificThan(Level.INFO) ? Level.INFO : level, str, param);
}
public void errorInc(String str, Object... param) {
logInc(Level.ERROR, str, param);
}
public void warnInc(String str, Object... param) {
logInc(Level.WARN, str, param);
}
public void infoInc(String str, Object... param) {
logInc(Level.INFO, str, param);
}
public void debugInc(String str, Object... param) {
logInc(Level.DEBUG, str, param);
}
public void traceInc(String str, Object... param) {
logInc(Level.TRACE, str, param);
}
}
@@ -1,24 +1,43 @@
/*
* This file is part of the Distant Horizons mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020-2022 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;
import com.seibel.lod.core.wrapperInterfaces.block.IBlockColorWrapper;
/*
import com.seibel.lod.core.wrapperInterfaces.block.BlockDetail;
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<>();
public static final ConcurrentMap<BlockDetail, BlockBiomeCouple> noBiomeIstanceCache = new ConcurrentHashMap<>();
public static ConcurrentMap<IBiomeWrapper, ConcurrentMap<BlockDetail, BlockBiomeCouple>> withBiomeIstanceCache = new ConcurrentHashMap<>();
String blockName;
String biomeName;
String coupleName;
IBiomeWrapper biomeColor;
IBlockColorWrapper blockColor;
BlockDetail blockColor;
public static void addBlockBiomeToCache(IBlockColorWrapper blockColor){
}
@@ -102,3 +121,4 @@ public class BlockBiomeCouple
}
*/
@@ -0,0 +1,26 @@
/*
* This file is part of the Distant Horizons mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020-2022 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;
public final class BoolType {
public static final BoolType TRUE = new BoolType();
public static final BoolType FALSE = null;
private BoolType() {}
}
@@ -1,8 +1,8 @@
/*
* This file is part of the Distant Horizon mod (formerly the LOD Mod),
* This file is part of the Distant Horizons mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020 James Seibel
* Copyright (C) 2020-2022 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
@@ -0,0 +1,109 @@
/*
* This file is part of the Distant Horizons mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020-2022 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;
/**
* Represents an IP and includes a couple helper methods.
*
* @author James Seibel
* @version 3-7-2022
*/
public class ParsedIp
{
/** can be used to find numeric IPs, IE: "192.168.1.19" */
public static final String NUMERIC_IP_REGEX = "^[0-9]*\\.[0-9]*\\.[0-9]*\\.[0-9]*(:[0-9]*)?$";
/**
* Can be used to find if a numeric IP is a LAN IP
*
* Ip list source: <br>
* https://networkengineering.stackexchange.com/questions/5825/why-192-168-for-local-addresses
*/
public static final String LAN_IP_REGEX = "(10|172\\.16|192\\.168).*";
/** Examples: "192.168.1.19", "mc.hypixel.net", or "localhost" */
public final String ip;
/**
* null if the ip isn't numeric (IE: "mc.hypixel.net" or "localhost") <br>
* Example: "25586"
*/
public final String port;
public final boolean isNumeric;
/** parses a standard IP string */
public ParsedIp(String fullIp)
{
fullIp = fullIp.trim();
isNumeric = fullIp.matches(NUMERIC_IP_REGEX);
if (isNumeric)
{
// attempt to separate the IP and the Port
String[] list = fullIp.split(":");
if (list.length == 2)
{
// IP and Port successfully separated
ip = list[0];
port = list[1];
}
else
{
// this IP must not have a port
ip = fullIp;
port = null;
}
}
else
{
// text based IP, IE: "localhost"
ip = fullIp;
port = null;
}
}
public ParsedIp(String newIp, String newPort)
{
ip = newIp;
port = newPort;
isNumeric = ip.matches(NUMERIC_IP_REGEX);
}
/** Returns if this IP is for a Local Area Network connection */
public boolean isLan()
{
return ip.toLowerCase().equals("localhost") || ip.matches(LAN_IP_REGEX);
}
@Override
public String toString()
{
return ip +
// only print the ":port" if a port is present
(port != null ? (":" + port) : "");
}
}
@@ -0,0 +1,29 @@
/*
* This file is part of the Distant Horizons mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020-2022 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;
public final class Pos2D {
public final int x;
public final int y;
public Pos2D(int x, int y) {
this.x = x;
this.y = y;
}
}
@@ -1,8 +1,8 @@
/*
* This file is part of the Distant Horizon mod (formerly the LOD Mod),
* This file is part of the Distant Horizons mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020 James Seibel
* Copyright (C) 2020-2022 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
@@ -31,7 +31,6 @@ public class PosToGenerateContainer
{
private final int playerPosX;
private final int playerPosZ;
private final byte farMinDetail;
private int nearSize;
private int farSize;
@@ -39,14 +38,10 @@ public class PosToGenerateContainer
private final int[][] nearPosToGenerate;
private final int[][] farPosToGenerate;
public PosToGenerateContainer(byte farMinDetail, int maxDataToGenerate, int playerPosX, int playerPosZ)
public PosToGenerateContainer(int maxDataToGenerate, int playerPosX, int playerPosZ)
{
this.playerPosX = playerPosX;
this.playerPosZ = playerPosZ;
this.farMinDetail = farMinDetail;
nearSize = 0;
farSize = 0;
nearPosToGenerate = new int[maxDataToGenerate][4];
@@ -56,46 +51,24 @@ public class PosToGenerateContainer
// TODO what is going on in this method?
public void addPosToGenerate(byte detailLevel, int posX, int posZ)
public void addNearPosToGenerate(byte detailLevel, int posX, int posZ, boolean sort)
{
int distance = LevelPosUtil.minDistance(detailLevel, posX, posZ, playerPosX, playerPosZ);
// FIXME: This is a cast from double to int!!! OVERFLOW MAY HAPPEN!
int distance = (int)LevelPosUtil.minDistance(detailLevel, posX, posZ, playerPosX, playerPosZ);
int index;
if (detailLevel >= farMinDetail)
{
// We are introducing a position in the far array
if (farSize < farPosToGenerate.length)
farSize++;
index = farSize - 1;
while (index > 0 && LevelPosUtil.compareDistance(distance, farPosToGenerate[index - 1][3]) <= 0)
{
farPosToGenerate[index][0] = farPosToGenerate[index - 1][0];
farPosToGenerate[index][1] = farPosToGenerate[index - 1][1];
farPosToGenerate[index][2] = farPosToGenerate[index - 1][2];
farPosToGenerate[index][3] = farPosToGenerate[index - 1][3];
index--;
//We are introducing a position in the near array
index = nearSize;
if (index == nearPosToGenerate.length) {
if (Integer.compare(distance, nearPosToGenerate[index - 1][3]) > 0) {
return;
}
if (index != farSize - 1 || farSize != farPosToGenerate.length)
{
farPosToGenerate[index][0] = detailLevel + 1;
farPosToGenerate[index][1] = posX;
farPosToGenerate[index][2] = posZ;
farPosToGenerate[index][3] = distance;
}
}
else
{
//We are introducing a position in the near array
if (nearSize < nearPosToGenerate.length)
nearSize++;
index = nearSize - 1;
while (index > 0 && LevelPosUtil.compareDistance(distance, nearPosToGenerate[index - 1][3]) <= 0)
index--;
} else nearSize++;
if (sort) {
while (index > 0 && Integer.compare(distance, nearPosToGenerate[index - 1][3]) <= 0)
{
nearPosToGenerate[index][0] = nearPosToGenerate[index - 1][0];
nearPosToGenerate[index][1] = nearPosToGenerate[index - 1][1];
@@ -103,16 +76,48 @@ public class PosToGenerateContainer
nearPosToGenerate[index][3] = nearPosToGenerate[index - 1][3];
index--;
}
if (index != nearSize - 1 || nearSize != nearPosToGenerate.length)
}
nearPosToGenerate[index][0] = detailLevel + 1;
nearPosToGenerate[index][1] = posX;
nearPosToGenerate[index][2] = posZ;
nearPosToGenerate[index][3] = distance;
}
// TODO what is going on in this method?
public void addFarPosToGenerate(byte detailLevel, int posX, int posZ, boolean sort)
{
// FIXME: This is a cast from double to int!!! OVERFLOW MAY HAPPEN!
int distance = (int)LevelPosUtil.minDistance(detailLevel, posX, posZ, playerPosX, playerPosZ);
int index;
// We are introducing a position in the far array
index = farSize;
if (index == farPosToGenerate.length) {
if (Integer.compare(distance, farPosToGenerate[index - 1][3]) > 0) {
return;
}
index--;
} else farSize++;
if (sort) {
while (index > 0 && Integer.compare(distance, farPosToGenerate[index - 1][3]) <= 0)
{
nearPosToGenerate[index][0] = detailLevel + 1;
nearPosToGenerate[index][1] = posX;
nearPosToGenerate[index][2] = posZ;
nearPosToGenerate[index][3] = distance;
farPosToGenerate[index][0] = farPosToGenerate[index - 1][0];
farPosToGenerate[index][1] = farPosToGenerate[index - 1][1];
farPosToGenerate[index][2] = farPosToGenerate[index - 1][2];
farPosToGenerate[index][3] = farPosToGenerate[index - 1][3];
index--;
}
}
farPosToGenerate[index][0] = detailLevel + 1;
farPosToGenerate[index][1] = posX;
farPosToGenerate[index][2] = posZ;
farPosToGenerate[index][3] = distance;
}
public boolean isFull() {
return nearSize == nearPosToGenerate.length && farSize == farPosToGenerate.length;
}
@@ -132,6 +137,16 @@ public class PosToGenerateContainer
return farSize;
}
public int getMaxNumberOfNearPos()
{
return nearPosToGenerate.length;
}
public int getMaxNumberOfFarPos()
{
return farPosToGenerate.length;
}
// TODO what does getNth mean? could the name be more descriptive or is it just a index?
public int getNthDetail(int n, boolean near)
{
@@ -1,8 +1,8 @@
/*
* This file is part of the Distant Horizon mod (formerly the LOD Mod),
* This file is part of the Distant Horizons mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020 James Seibel
* Copyright (C) 2020-2022 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
@@ -21,7 +21,7 @@ package com.seibel.lod.core.objects;
import java.util.Arrays;
import com.seibel.lod.core.api.ClientApi;
import com.seibel.lod.core.api.ApiShared;
import com.seibel.lod.core.util.LevelPosUtil;
import com.seibel.lod.core.util.LodUtil;
@@ -60,7 +60,7 @@ public class PosToRenderContainer
{
// This is might be due to dimensions having a different width
// when first loading in
ClientApi.LOGGER.error("Unable to addPosToRender. numberOfPosToRender [" + numberOfPosToRender + "] detailLevel [" + detailLevel + "] Pos [" + posX + "," + posZ + "]");
ApiShared.LOGGER.error("Unable to addPosToRender. numberOfPosToRender [" + numberOfPosToRender + "] detailLevel [" + detailLevel + "] Pos [" + posX + "," + posZ + "]");
numberOfPosToRender++; // incrementing so we can see how many pos over the limit we would go
return;
}
@@ -1,590 +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;
import java.util.Arrays;
import java.util.HashMap;
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.math.Vec3i;
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.util.SingletonHandler;
import com.seibel.lod.core.wrapperInterfaces.block.AbstractBlockPosWrapper;
import com.seibel.lod.core.wrapperInterfaces.config.ILodConfigWrapperSingleton;
import com.seibel.lod.core.wrapperInterfaces.minecraft.IMinecraftWrapper;
/**
* 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 VertexOptimizer
{
private static final IMinecraftWrapper MC = SingletonHandler.get(IMinecraftWrapper.class);
private static final ILodConfigWrapperSingleton CONFIG = SingletonHandler.get(ILodConfigWrapperSingleton.class);
public static final int ADJACENT_HEIGHT_INDEX = 0;
public static final int ADJACENT_DEPTH_INDEX = 1;
public static final int X = 0;
public static final int Y = 1;
public static final int Z = 2;
public static final int MIN = 0;
public static final int MAX = 1;
public static final int VOID_FACE = 0;
/** The six cardinal directions */
public static final LodDirection[] DIRECTIONS = new LodDirection[] {
LodDirection.UP,
LodDirection.DOWN,
LodDirection.WEST,
LodDirection.EAST,
LodDirection.NORTH,
LodDirection.SOUTH };
/** North, South, East, West */
public static final LodDirection[] ADJ_DIRECTIONS = new LodDirection[] {
LodDirection.EAST,
LodDirection.WEST,
LodDirection.SOUTH,
LodDirection.NORTH };
/** All the faces and vertices of a cube. This is used to extract the vertex from the column */
@SuppressWarnings("serial")
public static final Map<LodDirection, int[][]> DIRECTION_VERTEX_MAP = new HashMap<LodDirection, int[][]>()
{{
put(LodDirection.UP, new int[][] {
{ 0, 1, 0 }, // 0
{ 0, 1, 1 }, // 1
{ 1, 1, 1 }, // 2
{ 0, 1, 0 }, // 0
{ 1, 1, 1 }, // 2
{ 1, 1, 0 } // 3
});
put(LodDirection.DOWN, new int[][] {
{ 1, 0, 0 }, // 0
{ 1, 0, 1 }, // 1
{ 0, 0, 1 }, // 2
{ 1, 0, 0 }, // 0
{ 0, 0, 1 }, // 2
{ 0, 0, 0 } // 3
});
put(LodDirection.EAST, new int[][] {
{ 1, 1, 0 }, // 0
{ 1, 1, 1 }, // 1
{ 1, 0, 1 }, // 2
{ 1, 1, 0 }, // 0
{ 1, 0, 1 }, // 2
{ 1, 0, 0 } }); // 3
put(LodDirection.WEST, new int[][] {
{ 0, 0, 0 }, // 0
{ 0, 0, 1 }, // 1
{ 0, 1, 1 }, // 2
{ 0, 0, 0 }, // 0
{ 0, 1, 1 }, // 2
{ 0, 1, 0 } // 3
});
put(LodDirection.SOUTH, new int[][] {
{ 1, 0, 1 }, // 0
{ 1, 1, 1 }, // 1
{ 0, 1, 1 }, // 2
{ 1, 0, 1 }, // 0
{ 0, 1, 1 }, // 2
{ 0, 0, 1 } // 3
});
put(LodDirection.NORTH, new int[][] {
{ 0, 0, 0 }, // 0
{ 0, 1, 0 }, // 1
{ 1, 1, 0 }, // 2
{ 0, 0, 0 }, // 0
{ 1, 1, 0 }, // 2
{ 1, 0, 0 } // 3
});
}};
/**
* This indicates which position is invariable in the DIRECTION_VERTEX_MAP.
* Is used to extract the vertex
*/
@SuppressWarnings("serial")
public static final Map<LodDirection, int[]> FACE_DIRECTION = new HashMap<LodDirection, int[]>()
{{
put(LodDirection.UP, new int[] { Y, MAX });
put(LodDirection.DOWN, new int[] { Y, MIN });
put(LodDirection.EAST, new int[] { X, MAX });
put(LodDirection.WEST, new int[] { X, MIN });
put(LodDirection.SOUTH, new int[] { Z, MAX });
put(LodDirection.NORTH, new int[] { Z, MIN });
}};
/**
* This is a map from Direction to the relative normal vector
* we are using this since I'm not sure if the getNormal create new object at every call
*/
@SuppressWarnings("serial")
public static final Map<LodDirection, Vec3i> DIRECTION_NORMAL_MAP = new HashMap<LodDirection, Vec3i>()
{{
put(LodDirection.UP, LodDirection.UP.getNormal());
put(LodDirection.DOWN, LodDirection.DOWN.getNormal());
put(LodDirection.EAST, LodDirection.EAST.getNormal());
put(LodDirection.WEST, LodDirection.WEST.getNormal());
put(LodDirection.SOUTH, LodDirection.SOUTH.getNormal());
put(LodDirection.NORTH, LodDirection.NORTH.getNormal());
}};
/** We use this index for all array that are going to */
@SuppressWarnings("serial")
public static final Map<LodDirection, Integer> DIRECTION_INDEX = new HashMap<LodDirection, Integer>()
{{
put(LodDirection.UP, 0);
put(LodDirection.DOWN, 1);
put(LodDirection.EAST, 2);
put(LodDirection.WEST, 3);
put(LodDirection.SOUTH, 4);
put(LodDirection.NORTH, 5);
}};
@SuppressWarnings("serial")
public static final Map<LodDirection, Integer> ADJ_DIRECTION_INDEX = new HashMap<LodDirection, Integer>()
{{
put(LodDirection.EAST, 0);
put(LodDirection.WEST, 1);
put(LodDirection.SOUTH, 2);
put(LodDirection.NORTH, 3);
}};
/** holds the box's x, y, z offset */
public final int[] boxOffset;
/** holds the box's x, y, z width */
public final int[] boxWidth;
/** Holds each direction's color */
public final int[] colorMap;
/** The original color (before shading) of this box */
public int color;
/**
*
*/
public final Map<LodDirection, int[]> adjHeight;
public final Map<LodDirection, int[]> adjDepth;
public final Map<LodDirection, byte[]> skyLights;
public byte blockLight;
boolean skipTop;
boolean skipBot;
/** creates an empty box */
@SuppressWarnings("serial")
public VertexOptimizer()
{
boxOffset = new int[3];
boxWidth = new int[3];
colorMap = new int[6];
skyLights = new HashMap<LodDirection, byte[]>()
{{
put(LodDirection.UP, new byte[1]);
put(LodDirection.DOWN, new byte[1]);
put(LodDirection.EAST, new byte[LodUtil.MAX_NUMBER_OF_VERTICAL_LODS]);
put(LodDirection.WEST, new byte[LodUtil.MAX_NUMBER_OF_VERTICAL_LODS]);
put(LodDirection.SOUTH, new byte[LodUtil.MAX_NUMBER_OF_VERTICAL_LODS]);
put(LodDirection.NORTH, new byte[LodUtil.MAX_NUMBER_OF_VERTICAL_LODS]);
}};
adjHeight = new HashMap<LodDirection, int[]>()
{{
put(LodDirection.EAST, new int[LodUtil.MAX_NUMBER_OF_VERTICAL_LODS]);
put(LodDirection.WEST, new int[LodUtil.MAX_NUMBER_OF_VERTICAL_LODS]);
put(LodDirection.SOUTH, new int[LodUtil.MAX_NUMBER_OF_VERTICAL_LODS]);
put(LodDirection.NORTH, new int[LodUtil.MAX_NUMBER_OF_VERTICAL_LODS]);
}};
adjDepth = new HashMap<LodDirection, int[]>()
{{
put(LodDirection.EAST, new int[LodUtil.MAX_NUMBER_OF_VERTICAL_LODS]);
put(LodDirection.WEST, new int[LodUtil.MAX_NUMBER_OF_VERTICAL_LODS]);
put(LodDirection.SOUTH, new int[LodUtil.MAX_NUMBER_OF_VERTICAL_LODS]);
put(LodDirection.NORTH, new int[LodUtil.MAX_NUMBER_OF_VERTICAL_LODS]);
}};
}
/** Set the light of the columns */
public void setLights(int skyLight, int blockLight)
{
this.blockLight = (byte) blockLight;
skyLights.get(LodDirection.UP)[0] = (byte) skyLight;
}
/**
* Set the color of the columns
* @param color color to add
* @param adjShadeDisabled this array indicates which face does not need shading
*/
public void setColor(int color, boolean[] adjShadeDisabled)
{
this.color = color;
for (LodDirection lodDirection : DIRECTIONS)
{
if (!adjShadeDisabled[DIRECTION_INDEX.get(lodDirection)])
colorMap[DIRECTION_INDEX.get(lodDirection)] = ColorUtil.applyShade(color, MC.getShade(lodDirection));
else
colorMap[DIRECTION_INDEX.get(lodDirection)] = color;
}
}
/**
* @param lodDirection of the face of which we want to get the color
* @return color of the face
*/
public int getColor(LodDirection lodDirection)
{
if (CONFIG.client().advanced().debugging().getDebugMode() != DebugMode.SHOW_DETAIL)
return colorMap[DIRECTION_INDEX.get(lodDirection)];
else
return ColorUtil.applyShade(color, MC.getShade(lodDirection));
}
/**
*/
public byte getSkyLight(LodDirection lodDirection, int verticalIndex)
{
if(lodDirection == LodDirection.UP || lodDirection == LodDirection.DOWN)
return skyLights.get(lodDirection)[0];
else
return skyLights.get(lodDirection)[verticalIndex];
}
/**
*/
public int getBlockLight()
{
return blockLight;
}
/** clears this box, resetting everything to default values */
public void reset()
{
Arrays.fill(boxWidth, 0);
Arrays.fill(boxOffset, 0);
Arrays.fill(colorMap, 0);
blockLight = 0;
for (LodDirection lodDirection : ADJ_DIRECTIONS)
{
for (int i = 0; i < adjHeight.get(lodDirection).length; i++)
{
adjHeight.get(lodDirection)[i] = VOID_FACE;
adjDepth.get(lodDirection)[i] = VOID_FACE;
skyLights.get(lodDirection)[i] = 0;
}
}
}
/**
* This method create all the shared face culling based on the adjacent data
* @param adjData data adjacent to the column we are going to render
*/
public void setAdjData(Map<LodDirection, long[]> adjData)
{
int height;
int depth;
int minY = getMinY();
int maxY = getMaxY();
long singleAdjDataPoint;
// TODO transparency uncomment final condition to see ocean floor
//Up direction case
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
skyLights.get(LodDirection.DOWN)[0] = skyLights.get(LodDirection.UP)[0];
//other sided
//TODO clean some similar cases
for (LodDirection lodDirection : ADJ_DIRECTIONS)
{
long[] dataPoint = adjData.get(lodDirection);
if (dataPoint == null || DataPointUtil.isVoid(dataPoint[0]))
{
adjHeight.get(lodDirection)[0] = maxY;
adjDepth.get(lodDirection)[0] = minY;
adjHeight.get(lodDirection)[1] = VOID_FACE;
adjDepth.get(lodDirection)[1] = VOID_FACE;
skyLights.get(lodDirection)[0] = 15; //in void set full skylight
continue;
}
int i;
int faceToDraw = 0;
boolean firstFace = true;
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)
{
allAbove = false;
if (height < minY)
{
// the adj data is lower than the current data
if (firstFace)
{
adjHeight.get(lodDirection)[0] = getMaxY();
adjDepth.get(lodDirection)[0] = getMinY();
skyLights.get(lodDirection)[0] = DataPointUtil.getLightSkyAlt(singleAdjDataPoint); //skyLights.get(Direction.UP)[0];
}
else
{
adjDepth.get(lodDirection)[faceToDraw] = getMinY();
skyLights.get(lodDirection)[faceToDraw] = DataPointUtil.getLightSkyAlt(singleAdjDataPoint);
}
faceToDraw++;
toFinish = false;
// break since all the other data will be lower
break;
}
else if (depth <= minY)
{
if (height >= maxY)
{
// the adj data is inside the current data
// don't draw the face
adjHeight.get(lodDirection)[0] = VOID_FACE;
adjDepth.get(lodDirection)[0] = VOID_FACE;
}
else // height < maxY
{
// the adj data intersects the lower part of the current data
// if this is the only face, use the maxY and break,
// if there was another face we finish the last one and break
if (firstFace)
{
adjHeight.get(lodDirection)[0] = getMaxY();
adjDepth.get(lodDirection)[0] = height;
skyLights.get(lodDirection)[0] = DataPointUtil.getLightSkyAlt(singleAdjDataPoint); //skyLights.get(Direction.UP)[0];
}
else
{
adjDepth.get(lodDirection)[faceToDraw] = height;
skyLights.get(lodDirection)[faceToDraw] = DataPointUtil.getLightSkyAlt(singleAdjDataPoint);
}
toFinish = false;
faceToDraw++;
}
break;
}
else if (height >= maxY)// && depth > minY
{
// the adj data intersects the higher part of the current data
// we start the creation of a new face
adjHeight.get(lodDirection)[faceToDraw] = depth;
//skyLights.get(direction)[faceToDraw] = (byte) DataPointUtil.getLightSkyAlt(singleAdjDataPoint);
firstFace = false;
toFinish = true;
toFinishIndex = i + 1;
}
else
{
// if (depth > minY && height < maxY)
// the adj data is contained in the current data
if (firstFace)
{
adjHeight.get(lodDirection)[0] = getMaxY();
}
adjDepth.get(lodDirection)[faceToDraw] = height;
skyLights.get(lodDirection)[faceToDraw] = DataPointUtil.getLightSkyAlt(singleAdjDataPoint);
faceToDraw++;
adjHeight.get(lodDirection)[faceToDraw] = depth;
firstFace = false;
toFinish = true;
toFinishIndex = i + 1;
}
}
}
if (allAbove)
{
adjHeight.get(lodDirection)[0] = getMaxY();
adjDepth.get(lodDirection)[0] = getMinY();
skyLights.get(lodDirection)[0] = skyLights.get(LodDirection.UP)[0];
faceToDraw++;
}
else if (toFinish)
{
adjDepth.get(lodDirection)[faceToDraw] = minY;
if(toFinishIndex < dataPoint.length)
{
singleAdjDataPoint = dataPoint[toFinishIndex];
if (DataPointUtil.doesItExist(singleAdjDataPoint))
skyLights.get(lodDirection)[faceToDraw] = DataPointUtil.getLightSkyAlt(singleAdjDataPoint);
else
skyLights.get(lodDirection)[faceToDraw] = skyLights.get(LodDirection.UP)[0];
}
faceToDraw++;
}
adjHeight.get(lodDirection)[faceToDraw] = VOID_FACE;
adjDepth.get(lodDirection)[faceToDraw] = VOID_FACE;
}
}
/** We use this method to set the various width of the column */
public void setWidth(int xWidth, int yWidth, int zWidth)
{
boxWidth[X] = xWidth;
boxWidth[Y] = yWidth;
boxWidth[Z] = zWidth;
}
/** We use this method to set the various offset of the column */
public void setOffset(int xOffset, int yOffset, int zOffset)
{
boxOffset[X] = xOffset;
boxOffset[Y] = yOffset;
boxOffset[Z] = zOffset;
}
/** returns true if the given direction should be rendered. */
public boolean shouldRenderFace(LodDirection lodDirection, int adjIndex)
{
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);
}
/**
* @param lodDirection direction of the face we want to render
* @param vertexIndex index of the vertex of the face (0-1-2-3)
* @return position x of the relative vertex
*/
public int getX(LodDirection lodDirection, int vertexIndex)
{
return boxOffset[X] + boxWidth[X] * DIRECTION_VERTEX_MAP.get(lodDirection)[vertexIndex][X];
}
/**
* @param lodDirection direction of the face we want to render
* @param vertexIndex index of the vertex of the face (0-1-2-3)
* @return position y of the relative vertex
*/
public int getY(LodDirection lodDirection, int vertexIndex)
{
return boxOffset[Y] + boxWidth[Y] * DIRECTION_VERTEX_MAP.get(lodDirection)[vertexIndex][Y];
}
/**
* @param lodDirection direction of the face we want to render
* @param vertexIndex index of the vertex of the face (0-1-2-3)
* @param adjIndex, index of the n-th culled face of this direction
* @return position x of the relative vertex
*/
public int getY(LodDirection lodDirection, int vertexIndex, int adjIndex)
{
if (lodDirection == LodDirection.DOWN || lodDirection == LodDirection.UP)
return boxOffset[Y] + boxWidth[Y] * DIRECTION_VERTEX_MAP.get(lodDirection)[vertexIndex][Y];
else
{
// this could probably be cleaned up more,
// but it still works
if (1 - DIRECTION_VERTEX_MAP.get(lodDirection)[vertexIndex][Y] == ADJACENT_HEIGHT_INDEX)
return adjHeight.get(lodDirection)[adjIndex];
else
return adjDepth.get(lodDirection)[adjIndex];
}
}
/**
* @param lodDirection direction of the face we want to render
* @param vertexIndex index of the vertex of the face (0-1-2-3)
* @return position z of the relative vertex
*/
public int getZ(LodDirection lodDirection, int vertexIndex)
{
return boxOffset[Z] + boxWidth[Z] * DIRECTION_VERTEX_MAP.get(lodDirection)[vertexIndex][Z];
}
public int getMinX()
{
return boxOffset[X];
}
public int getMaxX()
{
return boxOffset[X] + boxWidth[X];
}
public int getMinY()
{
return boxOffset[Y];
}
public int getMaxY()
{
return boxOffset[Y] + boxWidth[Y];
}
public int getMinZ()
{
return boxOffset[Z];
}
public int getMaxZ()
{
return boxOffset[Z] + boxWidth[Z];
}
}
@@ -1,8 +1,8 @@
/*
* This file is part of the Distant Horizon mod (formerly the LOD Mod),
* This file is part of the Distant Horizons mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020 James Seibel
* Copyright (C) 2020-2022 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
@@ -21,7 +21,6 @@ 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
@@ -43,9 +42,15 @@ public interface LevelContainer
* @param data actual data to add in an array of long[] format.
* @param posX x position in the detail level
* @param posZ z position in the detail level
* @return true if correctly added, false otherwise
* @return true if correctly changed, false otherwise
*/
boolean addVerticalData(long[] data, int posX, int posZ);
boolean addVerticalData(long[] data, int posX, int posZ, boolean override);
/**
* With this you can add a square of data to the level container
* @return true if anything changed, false otherwise
*/
boolean addChunkOfData(long[] data, int posX, int posZ, int widthX, int widthZ, boolean override);
/**
* With this you can add data to the level container
@@ -63,6 +68,14 @@ public interface LevelContainer
* @return the data in long array format
*/
long getData(int posX, int posZ, int index);
/**
* With this you can get data from the level container
* @param posX x position in the detail level
* @param posZ z position in the detail level
* @return the data in long array format
*/
long[] getAllData(int posX, int posZ);
/**
* With this you can get data from the level container
@@ -116,4 +129,11 @@ public interface LevelContainer
* @return data as a String
*/
int getMaxNumberOfLods();
/**
* This will return a ram usage estimation of this object
* @return long as byte
*/
long getRoughRamUsage();
}
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -1,8 +1,8 @@
/*
* This file is part of the Distant Horizon mod (formerly the LOD Mod),
* This file is part of the Distant Horizons mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020 James Seibel
* Copyright (C) 2020-2022 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,15 +22,14 @@ package com.seibel.lod.core.objects.lod;
import java.util.Hashtable;
import java.util.Map;
import com.seibel.lod.core.api.ClientApi;
import com.seibel.lod.core.api.ApiShared;
import com.seibel.lod.core.wrapperInterfaces.world.IDimensionTypeWrapper;
/**
* This stores all LODs for a given world.
*
* @author James Seibel
* @author Leonardo Amato
* @version 9-27-2021
* @version 2022-3-29
*/
public class LodWorld
{
@@ -44,7 +43,7 @@ public class LodWorld
private boolean isWorldLoaded = false;
/** the name given to the world if it isn't loaded */
public static final String NO_WORLD_LOADED = "No world loaded";
public static final String NO_WORLD_LOADED = "";
@@ -65,25 +64,20 @@ public class LodWorld
*/
public void selectWorld(String newWorldName)
{
if (newWorldName.isEmpty())
{
deselectWorld();
return;
}
ApiShared.LOGGER.info("Selecting world {} while in world {}", newWorldName, worldName);
if (worldName.equals(newWorldName))
// don't recreate everything if we
// didn't actually change worlds
return;
deselectWorld();
worldName = newWorldName;
lodDimensions = new Hashtable<>();
isWorldLoaded = true;
}
/**
* Set the worldName to "No world loaded"
* and clear the lodDimensions Map. <br>
* Clear the lodDimensions Map. <br>
* This should be done whenever unloaded a world. <br><br>
* <p>
* Note a System.gc() call may be in order after calling this <Br>
@@ -91,7 +85,9 @@ public class LodWorld
*/
public void deselectWorld()
{
ApiShared.LOGGER.info("Deselecting world {}", worldName);
worldName = NO_WORLD_LOADED;
saveAllDimensions(true); // Make sure all dims are saved. This will block threads
lodDimensions = null;
isWorldLoaded = false;
}
@@ -105,8 +101,11 @@ public class LodWorld
{
if (lodDimensions == null)
return;
ApiShared.LOGGER.info("Adding dim {} to world {}", newDimension, worldName);
lodDimensions.put(newDimension.dimension, newDimension);
LodDimension oldDim = lodDimensions.put(newDimension.dimension, newDimension);
if (oldDim != null)
oldDim.saveDirtyRegionsToFile(true);
}
/**
@@ -129,7 +128,7 @@ public class LodWorld
if (lodDimensions == null)
return;
saveAllDimensions();
saveAllDimensions(true); //block until saving is done
for (IDimensionTypeWrapper key : lodDimensions.keySet())
lodDimensions.get(key).setRegionWidth(newRegionWidth);
@@ -138,17 +137,35 @@ public class LodWorld
/**
* Requests all dimensions save any dirty regions they may have.
*/
public void saveAllDimensions()
public void saveAllDimensions(boolean isBlocking)
{
if (lodDimensions == null)
return;
// TODO we should only print this if lods were actually saved to file
// but that requires a LodDimension.hasDirtyRegions() method or something similar
ClientApi.LOGGER.info("Saving LODs");
ApiShared.LOGGER.info("Saving LODs");
for (IDimensionTypeWrapper key : lodDimensions.keySet())
lodDimensions.get(key).saveDirtyRegionsToFileAsync();
{
lodDimensions.get(key).saveDirtyRegionsToFile(isBlocking);
}
//FIXME: This should block until file is saved.
}
/**
* Requests all dimensions to shutdown
*/
public void shutdownAllDimensions()
{
if (lodDimensions == null)
return;
// TODO: Add parallel shutdowns.
for (IDimensionTypeWrapper key : lodDimensions.keySet())
{
lodDimensions.get(key).shutdown();
}
}
@@ -1,8 +1,8 @@
/*
* This file is part of the Distant Horizon mod (formerly the LOD Mod),
* This file is part of the Distant Horizons mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020 James Seibel
* Copyright (C) 2020-2022 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -19,8 +19,8 @@
package com.seibel.lod.core.objects.lod;
import com.seibel.lod.core.handlers.dependencyInjection.SingletonHandler;
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;
@@ -82,10 +82,39 @@ public class RegionPos
.offset(LodUtil.CHUNK_WIDTH / 2, 0, LodUtil.CHUNK_WIDTH / 2);
}
@Override
public boolean equals(Object o) {
// If the object is compared with itself then return true
if (o == this) {
return true;
}
// Check if o is an instance of RegionPos or not
if (!(o instanceof RegionPos)) {
return false;
}
RegionPos c = (RegionPos) o;
return c.x==x &&c.z==z;
}
@Override
public String toString()
{
return "(" + x + "," + z + ")";
}
public static long asLong(int i, int j) {
return (long)i & 0xFFFFFFFFL | ((long)j & 0xFFFFFFFFL) << 32;
}
public static int getX(long l) {
return (int)(l & 0xFFFFFFFFL);
}
public static int getZ(long l) {
return (int)(l >>> 32 & 0xFFFFFFFFL);
}
@Override
public int hashCode() {
return Long.hashCode(asLong(x,z));
}
}
File diff suppressed because it is too large Load Diff
@@ -1,8 +1,8 @@
/*
* This file is part of the Distant Horizon mod (formerly the LOD Mod),
* This file is part of the Distant Horizons mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020 James Seibel
* Copyright (C) 2020-2022 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
@@ -397,6 +397,7 @@ public class Mat4f
/**
* TODO: what kind of translation is this?
* and how is this different from "multiplyTranslationMatrix"?
* Answer: This is faster and direct (but only if this is pure translation matrix without rotate)
*/
public void translate(Vec3f vec)
{
@@ -1,8 +1,8 @@
/*
* This file is part of the Distant Horizon mod (formerly the LOD Mod),
* This file is part of the Distant Horizons mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020 James Seibel
* Copyright (C) 2020-2022 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
@@ -1,8 +1,8 @@
/*
* This file is part of the Distant Horizon mod (formerly the LOD Mod),
* This file is part of the Distant Horizons mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020 James Seibel
* Copyright (C) 2020-2022 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
@@ -1,8 +1,8 @@
/*
* This file is part of the Distant Horizon mod (formerly the LOD Mod),
* This file is part of the Distant Horizons mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020 James Seibel
* Copyright (C) 2020-2022 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
@@ -1,8 +1,8 @@
/*
* This file is part of the Distant Horizon mod (formerly the LOD Mod),
* This file is part of the Distant Horizons mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020 James Seibel
* Copyright (C) 2020-2022 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
@@ -30,14 +30,14 @@ import com.google.common.collect.ImmutableList;
*/
public class DefaultLodVertexFormats
{
public static final LodVertexFormatElement ELEMENT_POSITION = new LodVertexFormatElement(0, LodVertexFormatElement.DataType.FLOAT, 3);
public static final LodVertexFormatElement ELEMENT_COLOR = new LodVertexFormatElement(0, LodVertexFormatElement.DataType.UBYTE, 4);
public static final LodVertexFormatElement ELEMENT_UV = new LodVertexFormatElement(0, LodVertexFormatElement.DataType.FLOAT, 2);
public static final LodVertexFormatElement ELEMENT_LIGHT_MAP_UV = new LodVertexFormatElement(1, LodVertexFormatElement.DataType.SHORT, 2);
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_POSITION = new LodVertexFormatElement(3, LodVertexFormatElement.DataType.USHORT, 3, false);
public static final LodVertexFormatElement ELEMENT_COLOR = new LodVertexFormatElement(0, LodVertexFormatElement.DataType.UBYTE, 4, false);
public static final LodVertexFormatElement ELEMENT_UV = new LodVertexFormatElement(0, LodVertexFormatElement.DataType.FLOAT, 2, false);
public static final LodVertexFormatElement ELEMENT_LIGHT_MAP_UV = new LodVertexFormatElement(1, LodVertexFormatElement.DataType.SHORT, 2, false);
public static final LodVertexFormatElement ELEMENT_NORMAL = new LodVertexFormatElement(0, LodVertexFormatElement.DataType.BYTE, 3, false);
public static final LodVertexFormatElement ELEMENT_PADDING = new LodVertexFormatElement(0, LodVertexFormatElement.DataType.BYTE, 1, true);
public static final LodVertexFormatElement ELEMENT_BLOCK_LIGHT = new LodVertexFormatElement(0, LodVertexFormatElement.DataType.UBYTE, 1);
public static final LodVertexFormatElement ELEMENT_LIGHT = new LodVertexFormatElement(0, LodVertexFormatElement.DataType.UBYTE, 1, false);
public static final LodVertexFormat POSITION = new LodVertexFormat(ImmutableList.<LodVertexFormatElement>builder().add(ELEMENT_POSITION).build());
@@ -47,5 +47,7 @@ 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());
public static final LodVertexFormat POSITION_COLOR_BLOCK_LIGHT_SKY_LIGHT = new LodVertexFormat(ImmutableList.<LodVertexFormatElement>builder()
.add(ELEMENT_POSITION).add(ELEMENT_PADDING).add(ELEMENT_LIGHT)
.add(ELEMENT_COLOR).build());
}
@@ -0,0 +1,309 @@
/*
* This file is part of the Distant Horizons mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020-2022 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.opengl;
import com.seibel.lod.core.builders.lodBuilding.bufferBuilding.LodQuadBuilder;
import com.seibel.lod.core.enums.LodDirection;
import com.seibel.lod.core.handlers.dependencyInjection.SingletonHandler;
import com.seibel.lod.core.util.ColorUtil;
import com.seibel.lod.core.util.DataPointUtil;
import com.seibel.lod.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper;
public class LodBox
{
private static final IMinecraftClientWrapper MC = SingletonHandler.get(IMinecraftClientWrapper.class);
public static void addBoxQuadsToBuilder(LodQuadBuilder builder, short xSize, short ySize, short zSize, short x,
short y, short z, int color, byte skyLight, byte blockLight, long topData, long botData, long[][][] adjData,
boolean[] adjFillBlack)
{
short maxX = (short) (x + xSize);
short maxY = (short) (y + ySize);
short maxZ = (short) (z + zSize);
byte skyLightTop = skyLight;
byte skyLightBot = DataPointUtil.doesItExist(botData) ? DataPointUtil.getLightSky(botData) : 0;
// Up direction case
boolean skipTop = DataPointUtil.doesItExist(topData) && DataPointUtil.getDepth(topData) == maxY;// &&
// DataPointUtil.getAlpha(singleAdjDataPoint)
// == 255;
boolean skipBot = DataPointUtil.doesItExist(botData) && DataPointUtil.getHeight(botData) == y;// &&
// DataPointUtil.getAlpha(singleAdjDataPoint)
// == 255;
if (!skipTop)
builder.addQuadUp(x, maxY, z, xSize, zSize, ColorUtil.applyShade(color, MC.getShade(LodDirection.UP)), skyLightTop, blockLight);
if (!skipBot)
builder.addQuadDown(x, y, z, xSize, zSize, ColorUtil.applyShade(color, MC.getShade(LodDirection.DOWN)), skyLightBot, blockLight);
//If the adj pos is at the same level we cull the faces normally, otherwise we divide the face in two and cull the two part separately
//NORTH face vertex creation
{
long[][] adjDataNorth = adjData[LodDirection.NORTH.ordinal() - 2];
int adjOverlapNorth = adjFillBlack[LodDirection.NORTH.ordinal() - 2] ? ColorUtil.BLACK : ColorUtil.TRANSPARENT;
if (adjDataNorth == null)
{
builder.addQuadAdj(LodDirection.NORTH, x, y, z, xSize, ySize, color, (byte) 15, blockLight);
}
else if (adjDataNorth.length == 1)
{
makeAdjQuads(builder, adjDataNorth[0], LodDirection.NORTH, x, y, z, xSize, ySize,
color, adjOverlapNorth, skyLightTop, blockLight);
}
else
{
makeAdjQuads(builder, adjDataNorth[0], LodDirection.NORTH, x, y, z, (short) (xSize / 2), ySize,
color, adjOverlapNorth, skyLightTop, blockLight);
makeAdjQuads(builder, adjDataNorth[1], LodDirection.NORTH, (short) (x + xSize / 2), y, z, (short) (xSize / 2), ySize,
color, adjOverlapNorth, skyLightTop, blockLight);
}
}
//SOUTH face vertex creation
{
long[][] adjDataSouth = adjData[LodDirection.SOUTH.ordinal() - 2];
int adjOverlapSouth = adjFillBlack[LodDirection.SOUTH.ordinal() - 2] ? ColorUtil.BLACK : ColorUtil.TRANSPARENT;
if (adjDataSouth == null)
{
builder.addQuadAdj(LodDirection.SOUTH, x, y, maxZ, xSize, ySize, color, (byte) 15, blockLight);
}
else if (adjDataSouth.length == 1)
{
makeAdjQuads(builder, adjDataSouth[0], LodDirection.SOUTH, x, y, maxZ, xSize, ySize,
color, adjOverlapSouth, skyLightTop, blockLight);
}
else
{
makeAdjQuads(builder, adjDataSouth[0], LodDirection.SOUTH, x, y, maxZ, (short) (xSize / 2), ySize,
color, adjOverlapSouth, skyLightTop, blockLight);
makeAdjQuads(builder, adjDataSouth[1], LodDirection.SOUTH, (short) (x + xSize / 2), y, maxZ, (short) (xSize / 2), ySize,
color, adjOverlapSouth, skyLightTop, blockLight);
}
}
//WEST face vertex creation
{
long[][] adjDataWest = adjData[LodDirection.WEST.ordinal() - 2];
int adjOverlapWest = adjFillBlack[LodDirection.WEST.ordinal() - 2] ? ColorUtil.BLACK : ColorUtil.TRANSPARENT;
if (adjDataWest == null)
{
builder.addQuadAdj(LodDirection.WEST, x, y, z, zSize, ySize, color, (byte) 15, blockLight);
}
else if (adjDataWest.length == 1)
{
makeAdjQuads(builder, adjDataWest[0], LodDirection.WEST, x, y, z, zSize, ySize,
color, adjOverlapWest, skyLightTop, blockLight);
}
else
{
makeAdjQuads(builder, adjDataWest[0], LodDirection.WEST, x, y, z, (short) (zSize / 2), ySize,
color, adjOverlapWest, skyLightTop, blockLight);
makeAdjQuads(builder, adjDataWest[1], LodDirection.WEST, x, y, (short) (z + zSize / 2), (short) (zSize / 2), ySize,
color, adjOverlapWest, skyLightTop, blockLight);
}
}
//EAST face vertex creation
{
long[][] adjDataEast = adjData[LodDirection.EAST.ordinal() - 2];
int adjOverlapEast = adjFillBlack[LodDirection.EAST.ordinal() - 2] ? ColorUtil.BLACK : ColorUtil.TRANSPARENT;
if (adjData[LodDirection.EAST.ordinal() - 2] == null)
{
builder.addQuadAdj(LodDirection.EAST, maxX, y, z, zSize, ySize, color, (byte) 15, blockLight);
}
else if (adjDataEast.length == 1)
{
makeAdjQuads(builder, adjDataEast[0], LodDirection.EAST, maxX, y, z, zSize, ySize,
color, adjOverlapEast, skyLightTop, blockLight);
}
else
{
makeAdjQuads(builder, adjDataEast[0], LodDirection.EAST, maxX, y, z, (short) (zSize / 2), ySize,
color, adjOverlapEast, skyLightTop, blockLight);
makeAdjQuads(builder, adjDataEast[1], LodDirection.EAST, maxX, y, (short) (z + zSize / 2), (short) (zSize / 2), ySize,
color, adjOverlapEast, skyLightTop, blockLight);
}
}
}
private static void makeAdjQuads(LodQuadBuilder builder, long[] adjData, LodDirection direction, short x, short y,
short z, short w0, short wy, int color, int overlapColor, byte upSkyLight, byte blockLight)
{
color = ColorUtil.applyShade(color, MC.getShade(direction));
long[] dataPoint = adjData;
if (dataPoint == null || DataPointUtil.isVoid(dataPoint[0]))
{
builder.addQuadAdj(direction, x, y, z, w0, wy, color, (byte) 15, blockLight);
return;
}
int i;
boolean firstFace = true;
boolean allAbove = true;
short previousDepth = -1;
byte nextSkyLight = upSkyLight;
// TODO transparency ocean floor fix
// boolean isOpaque = ((colorMap[0] >> 24) & 0xFF) == 255;
for (i = 0; i < dataPoint.length && DataPointUtil.doesItExist(adjData[i])
&& !DataPointUtil.isVoid(adjData[i]); i++)
{
long adjPoint = adjData[i];
// TODO transparency ocean floor fix
// if (isOpaque && DataPointUtil.getAlpha(singleAdjDataPoint) != 255)
// continue;
short height = DataPointUtil.getHeight(adjPoint);
short depth = DataPointUtil.getDepth(adjPoint);
// If the depth of said block is higher then our max Y, continue
// Basically: y < maxY <= _____ height
// _______&&: y < maxY <= depth
if (y + wy <= depth)
continue;
// Now: depth < maxY
allAbove = false;
if (height < y)
{
// Basically: _____ height < y < maxY
// _______&&: depth ______ < y < maxY
if (firstFace)
{
builder.addQuadAdj(direction, x, y, z, w0, wy, color, DataPointUtil.getLightSky(adjPoint),
blockLight);
}
else
{
// Now: depth < height < y < previousDepth < maxY
if (previousDepth == -1)
throw new RuntimeException("Loop error");
builder.addQuadAdj(direction, x, y, z, w0, (short) (previousDepth - y), color,
DataPointUtil.getLightSky(adjPoint), blockLight);
previousDepth = -1;
}
break;
}
if (depth <= y)
{ // AND y <= height
if (y + wy <= height)
{
// Basically: ________ y < maxY <= height
// _______&&: depth <= y < maxY
// The face is inside adj face completely.
if (overlapColor != 0)
{
builder.addQuadAdj(direction, x, y, z, w0, wy, overlapColor, (byte) 15, (byte) 15);
}
break;
}
// Otherwise: ________ y <= Height < maxY
// _______&&: depth <= y _________ < maxY
// the adj data intersects the lower part of the current data
if (height > y && overlapColor != 0)
{
builder.addQuadAdj(direction, x, y, z, w0, (short) (height - y), overlapColor, (byte) 15, (byte) 15);
}
// if this is the only face, use the maxY and break,
// if there was another face we finish the last one and break
if (firstFace)
{
builder.addQuadAdj(direction, x, height, z, w0, (short) (y + wy - height), color,
DataPointUtil.getLightSky(adjPoint), blockLight);
}
else
{
// Now: depth <= y <= height <= previousDepth < maxY
if (previousDepth == -1)
throw new RuntimeException("Loop error");
if (previousDepth > height)
{
builder.addQuadAdj(direction, x, height, z, w0, (short) (previousDepth - height), color,
DataPointUtil.getLightSky(adjPoint), blockLight);
}
previousDepth = -1;
}
break;
}
// In here always true: y < depth < maxY
// _________________&&: y < _____ (height and maxY)
if (y + wy <= height)
{
// Basically: y _______ < maxY <= height
// _______&&: y < depth < maxY
// the adj data intersects the higher part of the current data
if (overlapColor != 0)
{
builder.addQuadAdj(direction, x, depth, z, w0, (short) (y + wy - depth), overlapColor, (byte) 15, (byte) 15);
}
// we start the creation of a new face
}
else
{
// Otherwise: y < _____ height < maxY
// _______&&: y < depth ______ < maxY
if (overlapColor != 0)
{
builder.addQuadAdj(direction, x, depth, z, w0, (short) (height - depth), overlapColor, (byte) 15, (byte) 15);
}
if (firstFace)
{
builder.addQuadAdj(direction, x, height, z, w0, (short) (y + wy - height), color,
DataPointUtil.getLightSky(adjPoint), blockLight);
}
else
{
// Now: y < depth < height <= previousDepth < maxY
if (previousDepth == -1)
throw new RuntimeException("Loop error");
if (previousDepth > height)
{
builder.addQuadAdj(direction, x, height, z, w0, (short) (previousDepth - height), color,
DataPointUtil.getLightSky(adjPoint), blockLight);
}
previousDepth = -1;
}
}
// set next top as current depth
previousDepth = depth;
firstFace = false;
nextSkyLight = upSkyLight;
if (i + 1 < adjData.length && DataPointUtil.doesItExist(adjData[i + 1]))
nextSkyLight = DataPointUtil.getLightSky(adjData[i + 1]);
}
if (allAbove)
{
builder.addQuadAdj(direction, x, y, z, w0, wy, color, upSkyLight, blockLight);
}
else if (previousDepth != -1)
{
// We need to finish the last quad.
builder.addQuadAdj(direction, x, y, z, w0, (short) (previousDepth - y), color, nextSkyLight,
blockLight);
}
}
}
@@ -1,557 +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.opengl;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;
import java.util.List;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
/**
* A (almost) exact copy of Minecraft's
* BufferBuilder object. <br>
* Which allows for creating and filling
* OpenGL buffers.
*
* @author James Seibel
* @version 12-9-2021
*/
public class LodBufferBuilder
{
private static final Logger LOGGER = LogManager.getLogger();
public ByteBuffer buffer;
private final List<LodBufferBuilder.DrawState> vertexCounts = Lists.newArrayList();
private int lastRenderedCountIndex = 0;
private int totalRenderedBytes = 0;
private int nextElementByte = 0;
private int totalUploadedBytes = 0;
private int vertices;
private LodVertexFormatElement currentElement;
private int elementIndex;
private int mode;
private LodVertexFormat format;
private boolean building;
public LodBufferBuilder(int bufferSizeInBytes)
{
this.buffer = allocateByteBuffer(bufferSizeInBytes * 4);
}
/** originally from MC's GLAllocation class */
private ByteBuffer allocateByteBuffer(int bufferSizeInBytes)
{
return ByteBuffer.allocateDirect(bufferSizeInBytes).order(ByteOrder.nativeOrder());
}
/** originally from MC's GLAllocation class */
@SuppressWarnings("unused")
private FloatBuffer allocateFloatBuffer(int bufferSizeInBytes)
{
return allocateByteBuffer(bufferSizeInBytes).asFloatBuffer();
}
/** make sure the buffer doesn't overflow when inserting new elements */
private void ensureVertexCapacity()
{
this.ensureCapacity(this.format.getByteSize());
}
private void ensureCapacity(int vertexSizeInBytes)
{
if (this.nextElementByte + vertexSizeInBytes > this.buffer.capacity())
{
int i = this.buffer.capacity();
int j = i + roundUp(vertexSizeInBytes);
//LOGGER.debug("Needed to grow BufferBuilder buffer: Old size {} bytes, new size {} bytes.", i, j);
ByteBuffer bytebuffer = allocateByteBuffer(j);
this.buffer.position(0);
bytebuffer.put(this.buffer);
bytebuffer.rewind();
this.buffer = bytebuffer;
}
}
private static int roundUp(int vertexSizeInBytes)
{
int i = 2097152; // 2 ^ 21
if (vertexSizeInBytes == 0)
{
return i;
}
else
{
if (vertexSizeInBytes < 0)
{
i *= -1;
}
int j = vertexSizeInBytes % i;
return j == 0 ? vertexSizeInBytes : vertexSizeInBytes + i - j;
}
}
/* not currently needed sortQuads()
// the x,y,z location is a blockPos
public void sortQuads(float x, float y, float z)
{
((Buffer) this.buffer).clear();
FloatBuffer floatbuffer = this.buffer.asFloatBuffer();
int i = this.vertices / 4;
float[] afloat = new float[i];
for (int j = 0; j < i; ++j)
{
afloat[j] = getQuadDistanceFromPlayer(floatbuffer, x, y, z, this.format.getIntegerSize(), this.totalRenderedBytes / 4 + j * this.format.getVertexSize());
}
int[] aint = new int[i];
for (int k = 0; k < aint.length; aint[k] = k++)
{
}
IntArrays.mergeSort(aint, (p_227830_1_, p_227830_2_) ->
{
return Floats.compare(afloat[p_227830_2_], afloat[p_227830_1_]);
});
BitSet bitset = new BitSet();
FloatBuffer floatbuffer1 = allocateFloatBuffer(this.format.getIntegerSize() * 4);
for (int l = bitset.nextClearBit(0); l < aint.length; l = bitset.nextClearBit(l + 1))
{
int i1 = aint[l];
if (i1 != l)
{
this.limitToVertex(floatbuffer, i1);
((Buffer) floatbuffer1).clear();
floatbuffer1.put(floatbuffer);
int j1 = i1;
for (int k1 = aint[i1]; j1 != l; k1 = aint[k1])
{
this.limitToVertex(floatbuffer, k1);
FloatBuffer floatbuffer2 = floatbuffer.slice();
this.limitToVertex(floatbuffer, j1);
floatbuffer.put(floatbuffer2);
bitset.set(j1);
j1 = k1;
}
this.limitToVertex(floatbuffer, l);
((Buffer) floatbuffer1).flip();
floatbuffer.put(floatbuffer1);
}
bitset.set(l);
}
}
private void limitToVertex(FloatBuffer p_227829_1_, int p_227829_2_)
{
int i = this.format.getIntegerSize() * 4;
((Buffer) p_227829_1_).limit(this.totalRenderedBytes / 4 + (p_227829_2_ + 1) * i);
((Buffer) p_227829_1_).position(this.totalRenderedBytes / 4 + p_227829_2_ * i);
}
*/
/* not curerntly needed getState()
public LodBufferBuilder.State getState()
{
((Buffer) this.buffer).limit(this.nextElementByte);
((Buffer) this.buffer).position(this.totalRenderedBytes);
ByteBuffer bytebuffer = ByteBuffer.allocate(this.vertices * this.format.getVertexSize());
bytebuffer.put(this.buffer);
((Buffer) this.buffer).clear();
return new LodBufferBuilder.State(bytebuffer, this.format);
}
*/
/* not currently needed getQuadDistanceFromPlayer()
private static float getQuadDistanceFromPlayer(FloatBuffer floatBuffer, float x, float y, float z, int p_181665_4_, int p_181665_5_)
{
float f = floatBuffer.get(p_181665_5_ + p_181665_4_ * 0 + 0);
float f1 = floatBuffer.get(p_181665_5_ + p_181665_4_ * 0 + 1);
float f2 = floatBuffer.get(p_181665_5_ + p_181665_4_ * 0 + 2);
float f3 = floatBuffer.get(p_181665_5_ + p_181665_4_ * 1 + 0);
float f4 = floatBuffer.get(p_181665_5_ + p_181665_4_ * 1 + 1);
float f5 = floatBuffer.get(p_181665_5_ + p_181665_4_ * 1 + 2);
float f6 = floatBuffer.get(p_181665_5_ + p_181665_4_ * 2 + 0);
float f7 = floatBuffer.get(p_181665_5_ + p_181665_4_ * 2 + 1);
float f8 = floatBuffer.get(p_181665_5_ + p_181665_4_ * 2 + 2);
float f9 = floatBuffer.get(p_181665_5_ + p_181665_4_ * 3 + 0);
float f10 = floatBuffer.get(p_181665_5_ + p_181665_4_ * 3 + 1);
float f11 = floatBuffer.get(p_181665_5_ + p_181665_4_ * 3 + 2);
float f12 = (f + f3 + f6 + f9) * 0.25F - x;
float f13 = (f1 + f4 + f7 + f10) * 0.25F - y;
float f14 = (f2 + f5 + f8 + f11) * 0.25F - z;
return f12 * f12 + f13 * f13 + f14 * f14;
}
*/
/* not currently needed restoreState()
public void restoreState(LodBufferBuilder.State bufferState)
{
((Buffer) bufferState.data).clear();
int i = bufferState.data.capacity();
this.ensureCapacity(i);
((Buffer) this.buffer).limit(this.buffer.capacity());
((Buffer) this.buffer).position(this.totalRenderedBytes);
this.buffer.put(bufferState.data);
((Buffer) this.buffer).clear();
LodVertexFormat LodVertexFormat = bufferState.format;
this.switchFormat(LodVertexFormat);
this.vertices = i / LodVertexFormat.getVertexSize();
this.nextElementByte = this.totalRenderedBytes + this.vertices * LodVertexFormat.getVertexSize();
}
*/
private void switchFormat(LodVertexFormat newFormat)
{
format = newFormat;
}
//========================================//
// methods for actually building a buffer //
//========================================//
/**
* @param openGlLodVertexFormat GL11.GL_QUADS, GL11.GL_TRIANGLES, etc.
* @param LodVertexFormat
*/
public void begin(int openGlLodVertexFormat, LodVertexFormat LodVertexFormat)
{
if (this.building)
{
throw new IllegalStateException("Already building!");
}
else
{
this.building = true;
this.mode = openGlLodVertexFormat;
this.switchFormat(LodVertexFormat);
this.currentElement = LodVertexFormat.getElements().get(0);
this.elementIndex = 0;
this.buffer.clear();
}
}
public void end()
{
if (!this.building)
{
throw new IllegalStateException("Not building!");
}
else
{
this.building = false;
this.vertexCounts.add(new LodBufferBuilder.DrawState(this.format, this.vertices, this.mode));
this.totalRenderedBytes += this.vertices * this.format.getByteSize();
this.vertices = 0;
this.currentElement = null;
this.elementIndex = 0;
}
}
public void putByte(int index, byte newByte)
{
this.buffer.put(this.nextElementByte + index, newByte);
}
public void putShort(int index, short newShort)
{
this.buffer.putShort(this.nextElementByte + index, newShort);
}
public void putFloat(int index, float newFloat)
{
this.buffer.putFloat(this.nextElementByte + index, newFloat);
}
public void endVertex()
{
if (this.elementIndex != 0)
{
throw new IllegalStateException("Not filled all elements of the vertex");
}
else
{
++this.vertices;
this.ensureVertexCapacity();
}
}
public void nextElement()
{
ImmutableList<LodVertexFormatElement> immutablelist = this.format.getElements();
this.elementIndex = (this.elementIndex + 1) % immutablelist.size();
this.nextElementByte += this.currentElement.getByteSize();
this.currentElement = immutablelist.get(this.elementIndex);
// if (LodVertexFormatelement.getUsage() == LodVertexFormatElement.Usage.PADDING)
// {
// this.nextElement();
// }
// if (this.defaultColorSet && this.currentElement.getUsage() == LodVertexFormatElement.Usage.COLOR)
// {
// color(this.defaultR, this.defaultG, this.defaultB, this.defaultA);
// }
}
public LodBufferBuilder color(int red, int green, int blue, int alpha)
{
LodVertexFormatElement LodVertexFormatelement = this.currentElement();
if (LodVertexFormatelement.getType() != LodVertexFormatElement.DataType.UBYTE)
{
throw new IllegalStateException("Color must be stored as a UBYTE");
}
else
{
this.putByte(0, (byte) red);
this.putByte(1, (byte) green);
this.putByte(2, (byte) blue);
this.putByte(3, (byte) alpha);
this.nextElement();
return this;
}
}
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)
{
throw new IllegalStateException("Position verticies must be stored as a FLOAT");
}
else
{
this.putFloat(0, x);
this.putFloat(4, y);
this.putFloat(8, z);
this.nextElement();
return this;
}
}
/* not currently needed fullVertex()
* TODO James isn't sure about these names
public void vertex(float blockPosX, float blockPosY, float blockPosZ,
float red, float green, float blue, float alpha,
float textureU, float textureV,
int p_225588_10_, int p_225588_11_,
float p_225588_12_, float p_225588_13_, float p_225588_14_)
{
if (this.defaultColorSet)
{
throw new IllegalStateException();
}
else if (this.fastFormat)
{
this.putFloat(0, blockPosX);
this.putFloat(4, blockPosY);
this.putFloat(8, blockPosZ);
this.putByte(12, (byte) ((int) (red * 255.0F)));
this.putByte(13, (byte) ((int) (green * 255.0F)));
this.putByte(14, (byte) ((int) (blue * 255.0F)));
this.putByte(15, (byte) ((int) (alpha * 255.0F)));
this.putFloat(16, textureU);
this.putFloat(20, textureV);
int i;
if (this.fullFormat)
{
this.putShort(24, (short) (p_225588_10_ & '\uffff'));
this.putShort(26, (short) (p_225588_10_ >> 16 & '\uffff'));
i = 28;
}
else
{
i = 24;
}
this.putShort(i + 0, (short) (p_225588_11_ & '\uffff'));
this.putShort(i + 2, (short) (p_225588_11_ >> 16 & '\uffff'));
this.putByte(i + 4, IVertexConsumer.normalIntValue(p_225588_12_));
this.putByte(i + 5, IVertexConsumer.normalIntValue(p_225588_13_));
this.putByte(i + 6, IVertexConsumer.normalIntValue(p_225588_14_));
this.nextElementByte += i + 8;
this.endVertex();
}
else
{
super.vertex(blockPosX, blockPosY, blockPosZ, red, green, blue, alpha, textureU, textureV, p_225588_10_, p_225588_11_, p_225588_12_, p_225588_13_, p_225588_14_);
}
}
*/
/**
* James isn't sure what the difference between
* using this method and just directly getting the buffer would be.
* But this was what was being used before, so it will stay for now.
*
* If anyone figures out what is special about this, please replace this comment.
*/
public ByteBuffer getCleanedByteBuffer()
{
LodBufferBuilder.DrawState bufferbuilder$drawstate = this.vertexCounts.get(this.lastRenderedCountIndex++);
this.buffer.position(this.totalUploadedBytes);
this.totalUploadedBytes += bufferbuilder$drawstate.vertexCount() * bufferbuilder$drawstate.format().getByteSize();
this.buffer.limit(this.totalUploadedBytes);
if (this.lastRenderedCountIndex == this.vertexCounts.size() && this.vertices == 0)
{
this.clear();
}
ByteBuffer bytebuffer = this.buffer.slice();
//bytebuffer.order(this.buffer.order()); // FORGE: Fix incorrect byte order
this.buffer.clear();
return bytebuffer; // the original method also returned bufferbuilder$drawstate
}
public void clear()
{
if (this.totalRenderedBytes != this.totalUploadedBytes)
{
LOGGER.warn("Bytes mismatch " + this.totalRenderedBytes + " " + this.totalUploadedBytes);
}
this.discard();
}
public void discard()
{
this.totalRenderedBytes = 0;
this.totalUploadedBytes = 0;
this.nextElementByte = 0;
this.vertexCounts.clear();
this.lastRenderedCountIndex = 0;
}
public LodVertexFormatElement currentElement()
{
if (this.currentElement == null)
{
throw new IllegalStateException("BufferBuilder not started");
}
else
{
return this.currentElement;
}
}
public boolean building()
{
return this.building;
}
//==================//
// internal classes //
//==================//
public static final class DrawState
{
private final LodVertexFormat format;
private final int vertexCount;
private final int mode;
private DrawState(LodVertexFormat p_i225905_1_, int p_i225905_2_, int p_i225905_3_)
{
this.format = p_i225905_1_;
this.vertexCount = p_i225905_2_;
this.mode = p_i225905_3_;
}
public LodVertexFormat format()
{
return this.format;
}
public int vertexCount()
{
return this.vertexCount;
}
public int mode()
{
return this.mode;
}
}
// Forge added methods
public void putBulkData(ByteBuffer buffer)
{
ensureCapacity(buffer.limit() + this.format.getByteSize());
this.buffer.position(this.vertices * this.format.getByteSize());
this.buffer.put(buffer);
this.vertices += buffer.limit() / this.format.getByteSize();
this.nextElementByte += buffer.limit();
}
public LodVertexFormat getLodVertexFormat()
{
return this.format;
}
}
@@ -1,8 +1,8 @@
/*
* This file is part of the Distant Horizon mod (formerly the LOD Mod),
* This file is part of the Distant Horizons mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020 James Seibel
* Copyright (C) 2020-2022 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
@@ -1,8 +1,8 @@
/*
* This file is part of the Distant Horizon mod (formerly the LOD Mod),
* This file is part of the Distant Horizons mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020 James Seibel
* Copyright (C) 2020-2022 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
@@ -39,13 +39,19 @@ public class LodVertexFormatElement
private final int index;
private final int count;
private final int byteSize;
private final boolean isPadding;
public LodVertexFormatElement(int newIndex, LodVertexFormatElement.DataType newType, int newCount)
public LodVertexFormatElement(int newIndex, LodVertexFormatElement.DataType newType, int newCount, boolean isPadding)
{
this.dataType = newType;
this.index = newIndex;
this.count = newCount;
this.byteSize = newType.getSize() * this.count;
this.isPadding = isPadding;
}
public final boolean getIsPadding() {
return isPadding;
}
public final LodVertexFormatElement.DataType getType()
@@ -0,0 +1,143 @@
/*
* This file is part of the Distant Horizons mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020-2022 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.opengl;
import java.util.ConcurrentModificationException;
import com.seibel.lod.core.builders.lodBuilding.bufferBuilding.LodQuadBuilder;
import com.seibel.lod.core.enums.config.GpuUploadMethod;
import com.seibel.lod.core.render.LodRenderProgram;
import com.seibel.lod.core.util.StatsMap;
public abstract class RenderBuffer implements AutoCloseable
{
private enum State {
None,
Building,
Uploading,
Closed,
}
private State owner = State.None;
private State nextOwner = State.None;
final private void _lockThread(State newOwner) {
if (owner != State.None || (nextOwner != State.None && nextOwner != newOwner))
throw new ConcurrentModificationException("RenderMethod Illegal state!");
owner = newOwner;
nextOwner = State.None;
}
final private void _unlockThread(State oldOwner) {
if (owner != oldOwner)
throw new ConcurrentModificationException("RenderMethod Illegal state!");
owner = State.None;
}
final private void _unlockThreadTo(State oldOwner, State newOwner) {
if (owner != oldOwner)
throw new ConcurrentModificationException("RenderMethod Illegal state!");
owner = State.None;
nextOwner = newOwner;
}
final public void build(Runnable r) {
_lockThread(State.Building);
try {
r.run();
} finally {
_unlockThread(State.Building);
}
}
/* Return false if current renderMethod is not suited for current builder
* This will auto close the object if returning false. */
final public boolean tryUploadBuffers(LodQuadBuilder builder, GpuUploadMethod uploadMethod) {
_lockThread(State.Uploading);
boolean successful = false;
try {
successful = uploadBuffers(builder, uploadMethod);
return successful;
} finally {
if (!successful) {
_unlockThreadTo(State.Uploading, State.Closed);
close();
} else {
_unlockThread(State.Uploading);
}
}
}
// ======================================================================
// ====================== Methods for implementations ===================
// ======================================================================
// =========== Called by build starter thread ==========
/* Called on being reused after the object is swapped to the back
* and a new build event is triggered. Used for cleaning up non
* reusable objects sooner.
* Note: This is ran on BUILDER thread, and does not have access to
* GL Context, Use GLProxy.recordOpenGlCall() to access GL Context
* instead! */
public void onReuse() {}
// =========== Called by buffer upload thread ==========
/* Return false if current renderMethod is not suited for current builder
* If false, close call will be automatically triggered.
* If true, the object will be used (by first calling the swapBufferToFront())
* on tick render. */
protected abstract boolean uploadBuffers(LodQuadBuilder builder, GpuUploadMethod uploadMethod);
// ========== Called by render thread ==========
/* Called on buffer first being used by a render thread. */
public void onSwapToFront() {}
/* Called on buffer no longer being used. (Life ended)
* Return false if current object cannot be reused.
* Note: This should not do too much stuff as it is ran on render thread!
* The corresponding cleanups should be done using the onReuse() to prevent
* lag spikes! If you want this buffer to not be reused, but cleanup is
* expensive, use onReuse() instead!
* Note 2: This may not be triggered on some siturations like renderer being
* terminated, or dimension changed. So implementation should NEVER assume
* that onSwapToFront() will link to a call of onSwapToBack()! */
public boolean onSwapToBack() {return true;}
/* Called on... well... rendering.
* Return false if nothing rendered. (Optional) */
public abstract boolean render(LodRenderProgram shaderProgram);
// ========== Called by any thread. (thread safe) ==========
/* Called by anyone. This method is allowed to throw exceptions, but
* are never allowed to modify any values. This should behave the same
* to other methods as if the method have never been called.
* Note: This method is PURELY for debug or stats logging ONLY! */
public abstract void debugDumpStats(StatsMap statsMap);
// ========= Called only when 1 thread is using it =======
/* This method is called when object is no longer in use.
* Called either after uploadBuffers() returned false (On buffer Upload
* thread), or by others when the object is not being used. (not in build,
* upload, or render state). */
public abstract void close();
}
@@ -0,0 +1,445 @@
/*
* This file is part of the Distant Horizons mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020-2022 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.opengl;
import java.util.ConcurrentModificationException;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import com.seibel.lod.core.api.ClientApi;
import com.seibel.lod.core.builders.lodBuilding.bufferBuilding.CubicLodTemplate;
import com.seibel.lod.core.builders.lodBuilding.LodBuilder;
import com.seibel.lod.core.builders.lodBuilding.bufferBuilding.LodQuadBuilder;
import com.seibel.lod.core.enums.LodDirection;
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.handlers.dependencyInjection.SingletonHandler;
import com.seibel.lod.core.objects.BoolType;
import com.seibel.lod.core.objects.PosToRenderContainer;
import com.seibel.lod.core.objects.lod.LodDimension;
import com.seibel.lod.core.objects.lod.LodRegion;
import com.seibel.lod.core.objects.lod.RegionPos;
import com.seibel.lod.core.objects.math.Vec3d;
import com.seibel.lod.core.objects.math.Vec3f;
import com.seibel.lod.core.render.GLProxy;
import com.seibel.lod.core.render.LodRenderProgram;
import com.seibel.lod.core.render.RenderUtil;
import com.seibel.lod.core.util.DataPointUtil;
import com.seibel.lod.core.util.LevelPosUtil;
import com.seibel.lod.core.util.LodUtil;
import com.seibel.lod.core.util.StatsMap;
import com.seibel.lod.core.util.gridList.PosArrayGridList;
import com.seibel.lod.core.wrapperInterfaces.block.AbstractBlockPosWrapper;
import com.seibel.lod.core.wrapperInterfaces.config.ILodConfigWrapperSingleton;
import static com.seibel.lod.core.render.LodRenderer.EVENT_LOGGER;
public class RenderRegion implements AutoCloseable
{
private static final ILodConfigWrapperSingleton CONFIG = SingletonHandler.get(ILodConfigWrapperSingleton.class);
/** stores if the region at the given x and z index needs to be regenerated */
// Use int because I need Tri state:
private final AtomicInteger needRegen = new AtomicInteger(2);
private enum BackState {
Unused,
Building,
Complete,
}
private enum FrontState {
Unused,
Rendering,
Invalidated,
}
final RegionPos regionPos;
RenderBuffer renderBufferBack = null;
AtomicReference<BackState> backState =
new AtomicReference<BackState>(BackState.Unused);
AtomicReference<FrontState> frontState =
new AtomicReference<FrontState>(FrontState.Unused);
RenderBuffer renderBufferFront = null;
final LodDimension lodDim;
public RenderRegion(RegionPos regPos, LodDimension lodDim) {
regionPos = regPos;
this.lodDim = lodDim;
}
public boolean canRender(LodDimension lodDim, RegionPos regPos) {
return lodDim == this.lodDim && regPos.equals(regionPos);
}
public void setNeedRegen() {
needRegen.set(2);
}
public Optional<CompletableFuture<Void>> updateStatus(Executor bufferUploader, Executor bufferBuilder, boolean alwaysRegen, int playerPosX, int playerPosZ, boolean doCaveCulling) {
if (alwaysRegen) setNeedRegen();
BackState state = backState.get();
if (state != BackState.Unused) {
EVENT_LOGGER.trace("{}: UpdateStatus rejected. Cause: BackState is {}", regionPos, state);
return Optional.empty();
}
LodRegion r = lodDim.getRegion(regionPos.x, regionPos.z);
if (r==null) {
EVENT_LOGGER.trace("{}: UpdateStatus rejected. Cause: Region is null", regionPos);
return Optional.empty();
}
if (needRegen.get() == 0) {
EVENT_LOGGER.trace("{}: UpdateStatus rejected. Cause: Region doesn't need regen", regionPos);
return Optional.empty();
}
if (!backState.compareAndSet(BackState.Unused, BackState.Building)) {
EVENT_LOGGER.trace("{}: UpdateStatus rejected. Cause: CAS on BackState failed: ", backState.get());
return Optional.empty();
}
needRegen.decrementAndGet();
return Optional.of(startBuid(bufferUploader, bufferBuilder, r, lodDim, playerPosX, playerPosZ, doCaveCulling));
}
public boolean render(LodDimension renderDim,
Vec3d cameraPos, AbstractBlockPosWrapper cameraBlockPos, Vec3f cameraDir,
boolean enableDirectionalCulling, LodRenderProgram program) {
if (!frontState.compareAndSet(FrontState.Unused, FrontState.Rendering)) return false;
try {
if (renderDim != lodDim) return false;
if (enableDirectionalCulling &&
!RenderUtil.isRegionInViewFrustum(cameraBlockPos,
cameraDir, regionPos.x, regionPos.z)) return false;
BackState state = backState.get();
if (state == BackState.Complete) {
if (renderBufferBack != null) {
EVENT_LOGGER.debug("RenderRegion swap @ {}", regionPos);
boolean shouldKeep = renderBufferFront != null && renderBufferFront.onSwapToBack();
RenderBuffer temp = shouldKeep ? renderBufferFront : null;
renderBufferFront = renderBufferBack;
renderBufferBack = temp;
if (renderBufferFront != null) renderBufferFront.onSwapToFront();
}
if (!backState.compareAndSet(BackState.Complete, BackState.Unused)) {
EVENT_LOGGER.error("RenderRegion.render() got illegal state on swapping buffer!");
}
}
if (renderBufferFront == null) return false;
program.setModelPos(new Vec3f(
(float) ((regionPos.x * LodUtil.REGION_WIDTH) - cameraPos.x),
(float) (LodBuilder.MIN_WORLD_HEIGHT - cameraPos.y),
(float) ((regionPos.z * LodUtil.REGION_WIDTH) - cameraPos.z)));
return renderBufferFront.render(program);
} finally {
frontState.compareAndSet(FrontState.Rendering, FrontState.Unused);
}
}
private void recreateBuffer(LodQuadBuilder builder) {
if (renderBufferBack != null) throw new RuntimeException("Assert Error");
boolean useSimpleBuffer = (builder.getCurrentNeededVertexBufferCount() <= 6) || true;
renderBufferBack = useSimpleBuffer ?
new SimpleRenderBuffer()
: null; //new ComplexRenderRegion(regPos);
}
private CompletableFuture<Void> startBuid(Executor bufferUploader, Executor bufferBuilder, LodRegion region, LodDimension lodDim, int playerPosX, int playerPosZ, boolean doCaveCulling) {
EVENT_LOGGER.trace("RenderRegion startBuild @ {}", regionPos);
LodRegion[] adjRegions = new LodRegion[4];
try {
if (renderBufferBack != null) renderBufferBack.onReuse();
for (LodDirection dir : LodDirection.ADJ_DIRECTIONS) {
adjRegions[dir.ordinal() - 2] = lodDim.getRegion(regionPos.x+dir.getNormal().x, regionPos.z+dir.getNormal().z);
}
} catch (NullPointerException | ArrayIndexOutOfBoundsException e) { // HOTFIX: Error ignoring for a concurrency caused issue
setNeedRegen();
if (!backState.compareAndSet(BackState.Building, BackState.Unused)) {
EVENT_LOGGER.error("\"Lod Builder Starter\""
+ " encountered error on catching exceptions and fallback on starting build task: ",
new ConcurrentModificationException("RenderRegion Illegal State"));
}
EVENT_LOGGER.info("\"Lod Builder Starter\" failed due to possible known concurrency issue: ", e);
return CompletableFuture.completedFuture(null);
} catch (Throwable t) {
setNeedRegen();
if (!backState.compareAndSet(BackState.Building, BackState.Unused)) {
EVENT_LOGGER.error("\"Lod Builder Starter\""
+ " encountered error on catching exceptions and fallback on starting build task: ",
new ConcurrentModificationException("RenderRegion Illegal State"));
}
throw t;
}
return CompletableFuture.supplyAsync(() -> {
try {
EVENT_LOGGER.trace("RenderRegion start QuadBuild @ {}", regionPos);
int skyLightCullingBelow = CONFIG.client().graphics().advancedGraphics().getCaveCullingHeight();
// FIXME: Clamp also to the max world height.
skyLightCullingBelow = Math.max(skyLightCullingBelow, LodBuilder.MIN_WORLD_HEIGHT);
LodQuadBuilder builder = new LodQuadBuilder(doCaveCulling, skyLightCullingBelow);
Runnable buildRun = ()->{
makeLodRenderData(builder, region, adjRegions, playerPosX, playerPosZ);
};
if (renderBufferBack != null)
renderBufferBack.build(buildRun);
else
buildRun.run();
EVENT_LOGGER.trace("RenderRegion end QuadBuild @ {}", regionPos);
return builder;
} catch (NullPointerException | ArrayIndexOutOfBoundsException e) {
EVENT_LOGGER.info("\"LodNodeBufferBuilder\" failed due to possible known concurrency issue: ", e);
throw e; // HOTFIX: Error ignoring for a concurrency caused issue
} catch (Throwable e3) {
EVENT_LOGGER.error("\"LodNodeBufferBuilder\" was unable to build quads: ", e3);
throw e3;
}
}, bufferBuilder)
.thenAcceptAsync((builder) -> {
try {
EVENT_LOGGER.trace("RenderRegion start Upload @ {}", regionPos);
GLProxy glProxy = GLProxy.getInstance();
GpuUploadMethod method = GLProxy.getInstance().getGpuUploadMethod();
GLProxyContext oldContext = glProxy.getGlContext();
glProxy.setGlContext(GLProxyContext.LOD_BUILDER);
try {
if (renderBufferBack == null) recreateBuffer(builder);
if (!renderBufferBack.tryUploadBuffers(builder, method)) {
renderBufferBack = null;
recreateBuffer(builder);
if (!renderBufferBack.tryUploadBuffers(builder, method)) {
throw new RuntimeException("Newly created renderBuffer "
+ "is still returning false on tryUploadBuffers!");
}
}
} finally {
glProxy.setGlContext(oldContext);
}
EVENT_LOGGER.trace("RenderRegion end Upload @ {}", regionPos);
} catch (Throwable e3) {
EVENT_LOGGER.error("\"LodNodeBufferBuilder\" was unable to upload buffer: ", e3);
throw e3;
}
}, bufferUploader).handle((v, e) -> {
if (e != null) {
setNeedRegen();
if (!backState.compareAndSet(BackState.Building, BackState.Unused)) {
EVENT_LOGGER.error("\"LodNodeBufferBuilder\""
+ " encountered error on exit: ",
new ConcurrentModificationException("RenderRegion Illegal State"));
}
} else {
if (!backState.compareAndSet(BackState.Building, BackState.Complete)) {
EVENT_LOGGER.error("\"LodNodeBufferBuilder\""
+ " encountered error on exit: ",
new ConcurrentModificationException("RenderRegion Illegal State"));
}
}
return (Void) null;
});
}
private static final int ADJACENT8[][] = {
{-1,-1},
{-1, 0},
{-1, 1},
{ 0,-1},
//{ 0, 0},
{ 0, 1},
{ 1,-1},
{ 1, 0},
{ 1, 1}
};
private static void makeLodRenderData(LodQuadBuilder quadBuilder, LodRegion region, LodRegion[] adjRegions, int playerX,
int playerZ) {
byte minDetail = region.getMinDetailLevel();
// Variable initialization
DebugMode debugMode = CONFIG.client().advanced().debugging().getDebugMode();
// We ask the lod dimension which block we have to render given the player
// position
PosToRenderContainer posToRender = new PosToRenderContainer(minDetail, region.regionPosX, region.regionPosZ);
region.getPosToRender(posToRender, playerX, playerZ);
PosArrayGridList<BoolType> chunkGrid = ClientApi.renderer.vanillaChunks;
for (int index = 0; index < posToRender.getNumberOfPos(); index++) {
byte detailLevel = posToRender.getNthDetailLevel(index);
int posX = posToRender.getNthPosX(index);
int posZ = posToRender.getNthPosZ(index);
// TODO: In the future, We don't need to ignore rendered chunks! Just build it
// and leave it for the renderer to decide!
// We don't want to render this fake block if
// The block is inside the render distance with, is not bigger than a chunk and
// is positioned in a chunk set as vanilla rendered
// The block is in the player chunk or in a chunk adjacent to the player
if (detailLevel <= LodUtil.CHUNK_DETAIL_LEVEL) {
int chunkX = LevelPosUtil.getChunkPos(detailLevel, posX);
int chunkZ = LevelPosUtil.getChunkPos(detailLevel, posZ);
// skip any chunks that Minecraft is going to render
if (chunkGrid != null && chunkGrid.get(chunkX, chunkZ) != null) continue;
}
long[] posData = region.getAllData(detailLevel, posX, posZ);
if (posData == null || posData.length == 0 || !DataPointUtil.doesItExist(posData[0])
|| DataPointUtil.isVoid(posData[0]))
continue;
long[][][] adjData = new long[4][][];
boolean[] adjUseBlack = new boolean[4];
// We extract the adj data in the four cardinal direction
// we first reset the adjShadeDisabled. This is used to disable the shade on the
// border when we have transparent block like water or glass
// to avoid having a "darker border" underground
// Arrays.fill(adjShadeDisabled, false);
// We check every adj block in each direction
// If the adj block is rendered in the same region and with same detail
// and is positioned in a place that is not going to be rendered by vanilla game
// then we can set this position as adj
// We avoid cases where the adjPosition is in player chunk while the position is
// not
// to always have a wall underwater
for (LodDirection lodDirection : LodDirection.ADJ_DIRECTIONS) {
try {
int xAdj = posX + lodDirection.getNormal().x;
int zAdj = posZ + lodDirection.getNormal().z;
int chunkXAdj = LevelPosUtil.getChunkPos(detailLevel, xAdj);
int chunkZAdj = LevelPosUtil.getChunkPos(detailLevel, zAdj);
if (chunkGrid != null && chunkGrid.get(chunkXAdj, chunkZAdj)!=null) {
adjUseBlack[lodDirection.ordinal()-2] = true;
}
boolean isCrossRegionBoundary = LevelPosUtil.getRegion(detailLevel, xAdj) != region.regionPosX ||
LevelPosUtil.getRegion(detailLevel, zAdj) != region.regionPosZ;
LodRegion adjRegion;
byte adjDetail;
int childXAdj = xAdj*2 + (lodDirection.getNormal().x<0 ? 1 : 0);
int childZAdj = zAdj*2 + (lodDirection.getNormal().z<0 ? 1 : 0);
//we check if the detail of the adjPos is equal to the correct one (region border fix)
//or if the detail is wrong by 1 value (region+circle border fix)
if (isCrossRegionBoundary) {
//we compute at which detail that position should be rendered
adjRegion = adjRegions[lodDirection.ordinal()-2];
if(adjRegion == null) continue;
adjDetail = adjRegion.getRenderDetailLevelAt(playerX, playerZ, detailLevel, xAdj, zAdj);
} else {
adjRegion = region;
if (posToRender.contains(detailLevel, xAdj, zAdj)) adjDetail = detailLevel;
else if (detailLevel>0 &&
posToRender.contains((byte) (detailLevel-1), childXAdj, childZAdj))
adjDetail = (byte) (detailLevel-1);
else if (detailLevel<LodUtil.REGION_DETAIL_LEVEL &&
posToRender.contains((byte) (detailLevel+1), xAdj/2, zAdj/2))
adjDetail = (byte) (detailLevel+1);
else continue;
}
if (adjDetail < detailLevel-1 || adjDetail > detailLevel+1) {
continue;
}
if (adjDetail == detailLevel || adjDetail > detailLevel) {
adjData[lodDirection.ordinal() - 2] = new long[1][];
adjData[lodDirection.ordinal() - 2][0] = adjRegion.getAllData(adjDetail,
LevelPosUtil.convert(detailLevel, xAdj, adjDetail),
LevelPosUtil.convert(detailLevel, zAdj, adjDetail));
} else {
adjData[lodDirection.ordinal() - 2] = new long[2][];
adjData[lodDirection.ordinal() - 2][0] = adjRegion.getAllData(adjDetail,
childXAdj, childZAdj);
adjData[lodDirection.ordinal() - 2][1] = adjRegion.getAllData(adjDetail,
childXAdj + (lodDirection.getAxis()==LodDirection.Axis.X ? 0 : 1),
childZAdj + (lodDirection.getAxis()==LodDirection.Axis.Z ? 0 : 1));
}
} catch (RuntimeException e) {
EVENT_LOGGER.warn("Failed to get adj data for [{}:{},{}] at [{}]", detailLevel, posX, posZ, lodDirection);
EVENT_LOGGER.warn("Detail exception: ", e);
}
}
// We render every vertical lod present in this position
// We only stop when we find a block that is void or non-existing block
for (int i = 0; i < posData.length; i++) {
long data = posData[i];
// If the data is not renderable (Void or non-existing) we stop since there is
// no data left in this position
if (DataPointUtil.isVoid(data) || !DataPointUtil.doesItExist(data))
break;
long adjDataTop = i - 1 >= 0 ? posData[i - 1] : DataPointUtil.EMPTY_DATA;
long adjDataBot = i + 1 < posData.length ? posData[i + 1] : DataPointUtil.EMPTY_DATA;
// We send the call to create the vertices
CubicLodTemplate.addLodToBuffer(data, adjDataTop, adjDataBot, adjData, adjUseBlack, detailLevel,
LevelPosUtil.getRegionModule(detailLevel, posX),
LevelPosUtil.getRegionModule(detailLevel, posZ), quadBuilder, debugMode);
}
} // for pos to in list to render
// the thread executed successfully
// Merge all quads
quadBuilder.mergeQuads();
}
@Override
public void close()
{
if (renderBufferBack != null) renderBufferBack.close();
while (frontState.get() != FrontState.Invalidated && !frontState.compareAndSet(FrontState.Unused, FrontState.Invalidated)) {
Thread.yield(); //FIXME: If on java 9, use Thread.onSpinWait();
}
if (renderBufferFront != null) renderBufferFront.close();
}
public void debugDumpStats(StatsMap statsMap)
{
statsMap.incStat("RenderRegions");
RenderBuffer front = renderBufferFront;
if (front!=null) {
statsMap.incStat("FrontBuffers");
front.debugDumpStats(statsMap);
}
RenderBuffer back = renderBufferBack;
if (back!=null) {
statsMap.incStat("BackBuffers");
back.debugDumpStats(statsMap);
}
}
}
@@ -0,0 +1,198 @@
/*
* This file is part of the Distant Horizons mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020-2022 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.opengl;
import java.nio.ByteBuffer;
import java.util.Iterator;
import java.util.concurrent.TimeUnit;
import com.seibel.lod.core.api.ClientApi;
import com.seibel.lod.core.builders.lodBuilding.bufferBuilding.LodQuadBuilder;
import com.seibel.lod.core.render.LodRenderer;
import com.seibel.lod.core.render.objects.GLVertexBuffer;
import org.lwjgl.opengl.GL32;
import com.seibel.lod.core.api.ApiShared;
import com.seibel.lod.core.builders.lodBuilding.bufferBuilding.LodBufferBuilderFactory;
import com.seibel.lod.core.enums.config.GpuUploadMethod;
import com.seibel.lod.core.handlers.dependencyInjection.SingletonHandler;
import com.seibel.lod.core.builders.lodBuilding.bufferBuilding.LodQuadBuilder.BufferFiller;
import com.seibel.lod.core.render.GLProxy;
import com.seibel.lod.core.render.LodRenderProgram;
import com.seibel.lod.core.util.LodUtil;
import com.seibel.lod.core.util.StatsMap;
import com.seibel.lod.core.wrapperInterfaces.config.ILodConfigWrapperSingleton;
import static com.seibel.lod.core.render.GLProxy.GL_LOGGER;
public class SimpleRenderBuffer extends RenderBuffer
{
private static final ILodConfigWrapperSingleton CONFIG = SingletonHandler.get(ILodConfigWrapperSingleton.class);
private static final long MAX_BUFFER_UPLOAD_TIMEOUT_NANOSECONDS = 1_000_000;
GLVertexBuffer[] vbos;
// public void onReuse() {}
public SimpleRenderBuffer() {
vbos = new GLVertexBuffer[0];
}
@Override
protected boolean uploadBuffers(LodQuadBuilder builder, GpuUploadMethod method)
{
// if (builder.getCurrentNeededVertexBuffers()>6) return false;
if (method.useEarlyMapping) {
_uploadBuffersMapped(builder, method);
} else {
_uploadBuffersDirect(builder, method);
}
return true;
}
// public void onSwapToFront() {}
// public void onSwapToBack() {}
@Override
public boolean render(LodRenderProgram shaderProgram)
{
boolean hasRendered = false;
for (GLVertexBuffer vbo : vbos) {
if (vbo == null) continue;
if (vbo.getVertexCount() == 0) continue;
hasRendered = true;
vbo.bind();
shaderProgram.bindVertexBuffer(vbo.getId());
if (LodRenderer.ENABLE_IBO) {
GL32.glDrawElements(GL32.GL_TRIANGLES, (vbo.getVertexCount()/4)*6, ClientApi.renderer.quadIBO.getType(), 0);
} else {
GL32.glDrawArrays(GL32.GL_TRIANGLES, 0, vbo.getVertexCount());
}
//LodRenderer.tickLogger.info("Vertex buffer: {}", vbo);
}
return hasRendered;
}
@Override
public void debugDumpStats(StatsMap statsMap)
{
statsMap.incStat("RenderBuffers");
statsMap.incStat("SimpleRenderBuffers");
for (GLVertexBuffer b : vbos) {
if (b == null) continue;
statsMap.incStat("VBOs");
if (b.getSize() == LodBufferBuilderFactory.FULL_SIZED_BUFFER) {
statsMap.incStat("FullsizedVBOs");
}
if (b.getSize() == 0) GL_LOGGER.warn("VBO with size 0");
statsMap.incBytesStat("TotalUsage", b.getSize());
}
}
@Override
public void close()
{
GLProxy.getInstance().recordOpenGlCall(() -> {
for (GLVertexBuffer b : vbos) {
b.destroy(false);
}
});
}
private void _uploadBuffersDirect(LodQuadBuilder builder, GpuUploadMethod method) {
resize(builder.getCurrentNeededVertexBufferCount());
long remainingNS = 0;
long BPerNS = CONFIG.client().advanced().buffers().getGpuUploadPerMegabyteInMilliseconds();
int i = 0;
Iterator<ByteBuffer> iter = builder.makeVertexBuffers();
while (iter.hasNext()) {
if (i >= vbos.length) {
throw new RuntimeException("Too many vertex buffers!!");
}
ByteBuffer bb = iter.next();
GLVertexBuffer vbo = getOrMakeVbo(i++, method.useBufferStorage);
int size = bb.limit() - bb.position();
try {
vbo.bind();
vbo.uploadBuffer(bb, size/LodUtil.LOD_VERTEX_FORMAT.getByteSize(), method, LodBufferBuilderFactory.FULL_SIZED_BUFFER);
} catch (Exception e) {
vbos[i-1] = null;
vbo.close();
ApiShared.LOGGER.error("Failed to upload buffer: ", e);
}
if (BPerNS<=0) continue;
// upload buffers over an extended period of time
// to hopefully prevent stuttering.
remainingNS += size * BPerNS;
if (remainingNS >= TimeUnit.NANOSECONDS.convert(1000 / 60, TimeUnit.MILLISECONDS)) {
if (remainingNS > MAX_BUFFER_UPLOAD_TIMEOUT_NANOSECONDS)
remainingNS = MAX_BUFFER_UPLOAD_TIMEOUT_NANOSECONDS;
try {
Thread.sleep(remainingNS / 1000000, (int) (remainingNS % 1000000));
} catch (InterruptedException e) {
}
remainingNS = 0;
}
}
if (i < vbos.length) {
throw new RuntimeException("Too few vertex buffers!!");
}
}
private void _uploadBuffersMapped(LodQuadBuilder builder, GpuUploadMethod method)
{
resize(builder.getCurrentNeededVertexBufferCount());
for (int i=0; i<vbos.length; i++) {
if (vbos[i]==null) vbos[i] = new GLVertexBuffer(method.useBufferStorage);
}
BufferFiller func = builder.makeBufferFiller(method);
int i = 0;
while (i < vbos.length && func.fill(vbos[i++])) {}
}
private GLVertexBuffer getOrMakeVbo(int iIndex, boolean useBuffStorage) {
if (vbos[iIndex] == null) {
vbos[iIndex] = new GLVertexBuffer(useBuffStorage);
}
return vbos[iIndex];
}
private void resize(int size) {
if (vbos.length != size) {
GLVertexBuffer[] newVbos = new GLVertexBuffer[size];
if (vbos.length > size) {
for (int i=size; i<vbos.length; i++) {
if (vbos[i]!=null) vbos[i].close();
vbos[i] = null;
}
}
for (int i=0; i<newVbos.length && i<vbos.length; i++) {
newVbos[i] = vbos[i];
vbos[i] = null;
}
for (GLVertexBuffer b : vbos) {
if (b != null) throw new RuntimeException("LEAKING VBO!");
}
vbos = newVbos;
}
}
}
@@ -0,0 +1,14 @@
package com.seibel.lod.core.render;
import com.seibel.lod.core.ModInfo;
import java.util.Arrays;
import java.util.List;
public class F3Screen {
public static List<String> f3List = Arrays.asList(
"",
ModInfo.READABLE_NAME + " version: " + ModInfo.VERSION
);
public static boolean renderCustomF3 = false;
}
@@ -1,5 +1,5 @@
/*
* This file is part of the Distant Horizon mod (formerly the LOD Mod),
* This file is part of the Distant Horizons mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2021 James Seibel
@@ -19,13 +19,15 @@
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 java.util.concurrent.TimeUnit;
import com.seibel.lod.core.api.ApiShared;
import com.seibel.lod.core.logging.ConfigBasedLogger;
import org.apache.logging.log4j.LogManager;
import org.lwjgl.glfw.GLFW;
import org.lwjgl.opengl.GL;
import org.lwjgl.opengl.GL11;
@@ -35,13 +37,13 @@ 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.util.SingletonHandler;
import com.seibel.lod.core.handlers.dependencyInjection.SingletonHandler;
import com.seibel.lod.core.util.GLMessage;
import com.seibel.lod.core.util.GLMessageOutputStream;
import com.seibel.lod.core.wrapperInterfaces.config.ILodConfigWrapperSingleton;
import com.seibel.lod.core.wrapperInterfaces.minecraft.IMinecraftWrapper;
import com.seibel.lod.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper;
/**
* A singleton that holds references to different openGL contexts
@@ -62,13 +64,17 @@ import com.seibel.lod.core.wrapperInterfaces.minecraft.IMinecraftWrapper;
*/
public class GLProxy
{
public static final boolean OVERWIDE_VANILLA_GL_LOGGER = false;
private static final IMinecraftWrapper MC = SingletonHandler.get(IMinecraftWrapper.class);
private static final ExecutorService workerThread = Executors.newSingleThreadExecutor(new ThreadFactoryBuilder().setNameFormat(GLProxy.class.getSimpleName() + "-Worker-Thread").build());
private static final IMinecraftClientWrapper MC = SingletonHandler.get(IMinecraftClientWrapper.class);
private ExecutorService workerThread = Executors.newSingleThreadExecutor(new ThreadFactoryBuilder().setNameFormat(GLProxy.class.getSimpleName() + "-Worker-Thread").build());
private static final ILodConfigWrapperSingleton CONFIG = SingletonHandler.get(ILodConfigWrapperSingleton.class);
public static final ConfigBasedLogger GL_LOGGER = new ConfigBasedLogger(LogManager.getLogger(GLProxy.class),
() -> CONFIG.client().advanced().debugging().debugSwitch().getLogRendererGLEvent());
private static GLProxy instance = null;
/** Minecraft's GLFW window */
@@ -85,72 +91,68 @@ public class GLProxy
public final long proxyWorkerGlContext;
/** the proxyWorker's GL capabilities */
public final GLCapabilities proxyWorkerGlCapabilities;
/** Requires OpenGL 4.4, and offers the best buffer uploading */
public final boolean bufferStorageSupported;
/** 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;
public boolean namedObjectSupported = false; // ~OpenGL 4.5 (UNUSED CURRENTLY)
public boolean bufferStorageSupported = false; // ~OpenGL 4.4
public boolean VertexAttributeBufferBindingSupported = false; // ~OpenGL 4.3
private final GpuUploadMethod preferredUploadMethod;
public final GLMessage.Builder vanillaDebugMessageBuilder;
public final GLMessage.Builder lodBuilderDebugMessageBuilder;
public final GLMessage.Builder proxyWorkerDebugMessageBuilder;
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"
return "Your OpenGL support:\n" +
"openGL version 3.2+: " + c.OpenGL32 + " <- REQUIRED\n" +
"Vertex Attribute Buffer Binding: " + (c.glVertexAttribBinding!=0) + " <- optional improvement\n" +
"Buffer Storage: " + (c.glBufferStorage!=0) + " <- optional improvement\n" +
"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();
+ " (How you turn that on, I have no clue~)";
}
private boolean checkCapabilities(GLCapabilities c) {
if (!c.OpenGL32) {
return false;
}
namedObjectSupported = c.glNamedBufferStorage!=0;
bufferStorageSupported = c.glBufferStorage!=0;
VertexAttributeBufferBindingSupported = c.glVertexAttribBinding!=0;
return true;
}
private String getVersionInfo(GLCapabilities c) {
StringBuilder str = new StringBuilder("Your supported OpenGL version:\n");
return "Your OpenGL support:\n" +
"openGL version 3.2+: " + c.OpenGL32 + " <- REQUIRED\n" +
"Vertex Attribute Buffer Binding: " + (c.glVertexAttribBinding!=0) + " <- optional improvement\n" +
"Buffer Storage: " + (c.glBufferStorage!=0) + " <- optional improvement\n";
}
private static void logMessage(GLMessage msg) {
GLMessage.Severity s = msg.severity;
if (msg.type == GLMessage.Type.ERROR ||
msg.type == GLMessage.Type.UNDEFINED_BEHAVIOR) {
GL_LOGGER.error("GL ERROR {} from {}: {}", msg.id, msg.source, msg.message);
throw new RuntimeException("GL ERROR: "+msg.toString());
}
RuntimeException e = new RuntimeException("GL MESSAGE: "+msg.toString());
switch (s) {
case HIGH:
GL_LOGGER.error("{}", e);
break;
case MEDIUM:
GL_LOGGER.warn("{}", e);
break;
case LOW:
GL_LOGGER.info("{}", e);
break;
case NOTIFICATION:
GL_LOGGER.debug("{}", e);
break;
}
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();
}
@@ -161,14 +163,52 @@ public class GLProxy
*/
private GLProxy()
{
lodBuilderDebugMessageBuilder = new GLMessage.Builder(
(type) -> {
if (type == GLMessage.Type.POP_GROUP) return false;
if (type == GLMessage.Type.PUSH_GROUP) return false;
if (type == GLMessage.Type.MARKER) return false;
// if (type == GLMessage.Type.PERFORMANCE) return false;
return true;
}
,(severity) -> {
if (severity == GLMessage.Severity.NOTIFICATION) return false;
return true;
},null
);
proxyWorkerDebugMessageBuilder = new GLMessage.Builder(
(type) -> {
if (type == GLMessage.Type.POP_GROUP) return false;
if (type == GLMessage.Type.PUSH_GROUP) return false;
if (type == GLMessage.Type.MARKER) return false;
// if (type == GLMessage.Type.PERFORMANCE) return false;
return true;
}
,(severity) -> {
if (severity == GLMessage.Severity.NOTIFICATION) return false;
return true;
},null
);
vanillaDebugMessageBuilder = new GLMessage.Builder(
(type) -> {
if (type == GLMessage.Type.POP_GROUP) return false;
if (type == GLMessage.Type.PUSH_GROUP) return false;
if (type == GLMessage.Type.MARKER) return false;
// if (type == GLMessage.Type.PERFORMANCE) return false;
return true;
}
,(severity) -> {
if (severity == GLMessage.Severity.NOTIFICATION) return false;
return true;
},null
);
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) + "].");
GL_LOGGER.info("Creating " + GLProxy.class.getSimpleName() + "... If this is the last message you see in the log there must have been a OpenGL error.");
GL_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
@@ -187,15 +227,19 @@ public class GLProxy
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.
// See full requirement at above.
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"+
+ " and discovered this GPU doesn't meet the OpenGL requirement." + " 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."));
MC.crashMinecraft(errorMessage, new UnsupportedOperationException("Distant Horizon OpenGL requirements not met"));
}
ClientApi.LOGGER.info("minecraftGlCapabilities:\n"+getVersionInfo(minecraftGlCapabilities));
GL_LOGGER.info("minecraftGlCapabilities:\n"+getVersionInfo(minecraftGlCapabilities));
if (OVERWIDE_VANILLA_GL_LOGGER)
GLUtil.setupDebugMessageCallback(new PrintStream(new GLMessageOutputStream(GLProxy::logMessage, vanillaDebugMessageBuilder), true));
GLFW.glfwMakeContextCurrent(0L);
// context creation setup
GLFW.glfwDefaultWindowHints();
@@ -205,18 +249,37 @@ public class GLProxy
// but this can be explicitly set for testing
// GLFW.glfwWindowHint(GLFW.GLFW_CONTEXT_VERSION_MAJOR, 4);
// GLFW.glfwWindowHint(GLFW.GLFW_CONTEXT_VERSION_MINOR, 5);
// DO NOT comment out below 2 lines. It's needed for mac and also for creating forward compatible contexts
GLFW.glfwWindowHint(GLFW.GLFW_CONTEXT_VERSION_MAJOR, 3);
GLFW.glfwWindowHint(GLFW.GLFW_CONTEXT_VERSION_MINOR, 2);
GLFW.glfwWindowHint(GLFW.GLFW_OPENGL_FORWARD_COMPAT, GLFW.GLFW_TRUE);
GLFW.glfwWindowHint(GLFW.GLFW_OPENGL_PROFILE, GLFW.GLFW_OPENGL_CORE_PROFILE);
GLFW.glfwWindowHint(GLFW.GLFW_OPENGL_DEBUG_CONTEXT, GLFW.GLFW_TRUE);
// create the LodBuilder context
lodBuilderGlContext = GLFW.glfwCreateWindow(64, 48, "LOD Builder Window", 0L, minecraftGlContext);
if (lodBuilderGlContext == 0) {
GL_LOGGER.error("ERROR: Failed to create GLFW context for OpenGL 3.2 with"
+ " Forward Compat Core Profile! Your OS may have not been able to support it!");
throw new UnsupportedOperationException("Forward Compat Core Profile 3.2 creation failure");
}
GLFW.glfwMakeContextCurrent(lodBuilderGlContext);
lodBuilderGlCapabilities = GL.createCapabilities();
ClientApi.LOGGER.info("lodBuilderGlCapabilities:\n"+getVersionInfo(lodBuilderGlCapabilities));
GL_LOGGER.info("lodBuilderGlCapabilities:\n"+getVersionInfo(lodBuilderGlCapabilities));
GLFW.glfwMakeContextCurrent(0L);
// create the proxyWorker's context
proxyWorkerGlContext = GLFW.glfwCreateWindow(64, 48, "LOD proxy worker Window", 0L, minecraftGlContext);
if (proxyWorkerGlContext == 0) {
GL_LOGGER.error("ERROR: Failed to create GLFW context for OpenGL 3.2 with"
+ " Forward Compat Core Profile! Your OS may have not been able to support it!");
throw new UnsupportedOperationException("Forward Compat Core Profile 3.2 creation failure");
}
GLFW.glfwMakeContextCurrent(proxyWorkerGlContext);
proxyWorkerGlCapabilities = GL.createCapabilities();
ClientApi.LOGGER.info("proxyWorkerGlCapabilities:\n"+getVersionInfo(lodBuilderGlCapabilities));
GL_LOGGER.info("proxyWorkerGlCapabilities:\n"+getVersionInfo(lodBuilderGlCapabilities));
GLFW.glfwMakeContextCurrent(0L);
// 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
@@ -231,22 +294,8 @@ public class GLProxy
//==================================//
setGlContext(GLProxyContext.LOD_BUILDER);
// 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();
}
GLUtil.setupDebugMessageCallback(new PrintStream(new GLMessageOutputStream(GLProxy::logMessage, lodBuilderDebugMessageBuilder), true));
// get specific capabilities
// Check if we can use the Buffer Storage, which is available in GL4.4 or after
@@ -255,7 +304,7 @@ public class GLProxy
// display the capabilities
if (!bufferStorageSupported)
{
ClientApi.LOGGER.warn("This GPU doesn't support Buffer Storage (OpenGL 4.4), falling back to using other methods.");
GL_LOGGER.warn("This GPU doesn't support Buffer Storage (OpenGL 4.4), falling back to using other methods.");
}
String vendor = GL32.glGetString(GL32.GL_VENDOR).toUpperCase(); // example return: "NVIDIA CORPORATION"
@@ -269,24 +318,12 @@ public class GLProxy
// AMD or Intel card
preferredUploadMethod = GpuUploadMethod.BUFFER_MAPPING;
}
ClientApi.LOGGER.info("GPU Vendor [" + vendor + "], Preferred upload method is [" + preferredUploadMethod + "].");
GL_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();
}
GLUtil.setupDebugMessageCallback(new PrintStream(new GLMessageOutputStream(GLProxy::logMessage, proxyWorkerDebugMessageBuilder), true));
//==========//
// clean up //
@@ -296,7 +333,7 @@ public class GLProxy
setGlContext(GLProxyContext.MINECRAFT);
// GLProxy creation success
ClientApi.LOGGER.info(GLProxy.class.getSimpleName() + " creation successful. OpenGL smiles upon you this day.");
GL_LOGGER.info(GLProxy.class.getSimpleName() + " creation successful. OpenGL smiles upon you this day.");
}
/**
@@ -310,8 +347,7 @@ public class GLProxy
// we don't have to change the context, we are already there.
if (currentContext == newContext)
return;
long contextPointer;
GLCapabilities newGlCapabilities = null;
@@ -400,9 +436,10 @@ public class GLProxy
*/
public void recordOpenGlCall(Runnable renderCall)
{
workerThread.execute(new Thread(() -> { runnableContainer(renderCall); }));
StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
workerThread.execute(() -> runnableContainer(renderCall, stackTrace));
}
private void runnableContainer(Runnable renderCall)
private void runnableContainer(Runnable renderCall, StackTraceElement[] stackTrace)
{
try
{
@@ -413,8 +450,9 @@ public class GLProxy
}
catch (Exception e)
{
ClientApi.LOGGER.error(Thread.currentThread().getName() + " ran into a issue: " + e.getMessage());
e.printStackTrace();
RuntimeException error = new RuntimeException("Uncaught Exception during execution:", e);
error.setStackTrace(stackTrace);
GL_LOGGER.error(Thread.currentThread().getName() + " ran into a issue: ", error);
}
finally
{
@@ -423,31 +461,20 @@ public class GLProxy
}
}
/**
* If called from a legacy OpenGL context this will
* set the fog end to infinity with a density of 0.
* Effectively removing the fog.
* <p>
* This only works with Legacy OpenGL because James hasn't
* looking into a way for it to work with Modern OpenGL.
*/
public boolean disableLegacyFog()
{
// make sure this is a legacy OpenGL context
if (minecraftGlCapabilities.glFogf != 0)
{
// glFogf should only have an address if the current OpenGL
// context can call it, and it should only be able to call it in
// legacy OpenGL contexts; since it is disabled in Modern
// OpenGL.
GL11.glFogf(GL11.GL_FOG_START, 0.0f);
GL11.glFogf(GL11.GL_FOG_END, Float.MAX_VALUE);
GL11.glFogf(GL11.GL_FOG_DENSITY, 0.0f);
return true;
public static void ensureAllGLJobCompleted() { // Uses global logger since it's a cleanup method
if (!hasInstance()) return;
ApiShared.LOGGER.info("Blocking until GL jobs finished...");
try {
instance.workerThread.shutdown();
boolean worked = instance.workerThread.awaitTermination(30, TimeUnit.SECONDS);
if (!worked)
ApiShared.LOGGER.error("GLWorkerThread shutdown timed out! Game may crash on exit due to cleanup failure!");
} catch (InterruptedException e) {
ApiShared.LOGGER.error("GLWorkerThread shutdown is interrupted! Game may crash on exit due to cleanup failure!");
e.printStackTrace();
} finally {
instance.workerThread = Executors.newSingleThreadExecutor(new ThreadFactoryBuilder().setNameFormat(GLProxy.class.getSimpleName() + "-Worker-Thread").build());
}
return false;
ApiShared.LOGGER.info("All GL jobs finished!");
}
}
@@ -1,8 +1,8 @@
/*
* This file is part of the Distant Horizon mod (formerly the LOD Mod),
* This file is part of the Distant Horizons mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020 James Seibel
* Copyright (C) 2020-2022 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -19,57 +19,415 @@
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.enums.rendering.*;
import com.seibel.lod.core.handlers.IReflectionHandler;
import com.seibel.lod.core.handlers.dependencyInjection.SingletonHandler;
import com.seibel.lod.core.render.objects.Shader;
import com.seibel.lod.core.wrapperInterfaces.config.ILodConfigWrapperSingleton;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.Objects;
import static com.seibel.lod.core.render.GLProxy.GL_LOGGER;
/**
* This object is just a replacement for an array
* to make things easier to understand in the LodRenderer.
*
* This holds fog related settings and
* creates the fog related shader code.
*
* @author Leetom
* @author James Seibel
* @version 11-26-2021
* @version 2022-4-14
*/
public class LodFogConfig
{
public FogDrawMode fogDrawMode;
public FogDistance fogDistance;
private static final IReflectionHandler REFLECTION_HANDLER = SingletonHandler.get(IReflectionHandler.class);
private static final ILodConfigWrapperSingleton CONFIG = SingletonHandler.get(ILodConfigWrapperSingleton.class);
public float nearFogStart = 0;
public float nearFogEnd = 0;
public static final boolean DEBUG_DUMP_GENERATED_CODE = false;
public float farFogStart = 0;
public float farFogEnd = 0;
public final FogSetting farFogSetting;
public final FogSetting heightFogSetting;
public final HeightFogMixMode heightFogMixMode;
public final HeightFogMode heightFogMode;
public final float heightFogHeight;
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;
final boolean drawNearFog;
final int earthCurveRatio; // FIXME: Move this out of here
public static LodFogConfig generateFogConfig()
{
FogDrawMode fogMode = CONFIG.client().graphics().fogQuality().getFogDrawMode();
if (fogMode == FogDrawMode.USE_OPTIFINE_SETTING)
fogMode = REFLECTION_HANDLER.getFogDrawMode();
// 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;
return new LodFogConfig(fogMode);
}
/** sets all fog options from the config */
private LodFogConfig(FogDrawMode fogDrawMode)
{
earthCurveRatio = CONFIG.client().graphics().advancedGraphics().getEarthCurveRatio(); //FIXME: Move this out of here
if (fogDrawMode != FogDrawMode.FOG_DISABLED)
{
ILodConfigWrapperSingleton.IClient.IGraphics.IFogQuality fogSettings = CONFIG.client().graphics().fogQuality();
FogDistance fogDistance = fogSettings.getFogDistance();
drawNearFog = (fogDistance == FogDistance.NEAR || fogDistance == FogDistance.NEAR_AND_FAR);
if (fogDistance == FogDistance.FAR || fogDistance == FogDistance.NEAR_AND_FAR)
{
// far fog should be drawn
farFogSetting = fogSettings.advancedFog().computeFarFogSetting();
heightFogMixMode = fogSettings.advancedFog().heightFog().getHeightFogMixMode();
if (heightFogMixMode == HeightFogMixMode.IGNORE_HEIGHT || heightFogMixMode == HeightFogMixMode.BASIC)
{
// basic fog mixing
heightFogSetting = null;
heightFogMode = null;
heightFogHeight = 0.f;
}
else
{
// advanced fog mixing
heightFogSetting = fogSettings.advancedFog().heightFog().computeHeightFogSetting();
heightFogMode = fogSettings.advancedFog().heightFog().getHeightFogMode();
if (heightFogMode.basedOnCamera)
{
heightFogHeight = 0.f;
}
else
{
heightFogHeight = (float) fogSettings.advancedFog().heightFog().getHeightFogHeight();
}
}
}
else
{
// far fog should not be drawn
farFogSetting = null;
heightFogSetting = null;
heightFogMode = null;
heightFogMixMode = null;
heightFogHeight = 0.f;
}
}
else
{
// fog disabled
drawNearFog = false;
farFogSetting = null;
heightFogMixMode = null;
heightFogMode = null;
heightFogSetting = null;
heightFogHeight = 0.f;
}
}
public StringBuilder loadAndProcessFragShader(String path, boolean absoluteFilePath)
{
StringBuilder str = makeRuntimeDefine();
generateRuntimeShaderCode(Shader.loadFile(path, absoluteFilePath, str));
if (DEBUG_DUMP_GENERATED_CODE)
{
try (FileOutputStream file = new FileOutputStream("debugGenerated.frag", false))
{
file.write(str.toString().getBytes(StandardCharsets.UTF_8));
GL_LOGGER.info("Debug dumped generated code to debugGenerated.frag for {}", path);
}
catch (IOException e)
{
GL_LOGGER.warn("Failed to debug dump generated code to file for {}", path);
}
}
return str;
}
/** Generates the necessary constants for a fragment shader */
private void generateRuntimeShaderCode(StringBuilder str)
{
str.append("// =======RUNTIME GENERATED CODE SECTION======== //\n");
// Generate method: float getNearFogThickness(float dist);
str.append("" +
"float getNearFogThickness(float dist) \n" +
"{ \n" +
" return linearFog(dist, nearFogStart, nearFogLength, 0.0, 1.0); \n" +
"} \n");
if (farFogSetting == null)
{
str.append("\n" +
"float getFarFogThickness(float dist) { return 0.0; } \n" +
"float getHeightFogThickness(float dist) { return 0.0; } \n" +
"float calculateFarFogDepth(float horizontal, float dist, float nearFogStart) { return 0.0; } \n" +
"float calculateHeightFogDepth(float vertical, float realY) { return 0.0; } \n" +
"float mixFogThickness(float near, float far, float height) \n" +
"{ \n" +
(drawNearFog ? "return 1.0-near;" : "return 0.0;") +
"} \n\n");
}
else
{
// Generate method: float getFarFogThickness(float dist);
str.append("" +
"float getFarFogThickness(float dist) \n" +
"{ \n" +
getFarFogMethod(farFogSetting.fogType) + "\n" +
"} \n");
// Generate method: float getHeightFogThickness(float dist);
str.append("" +
"float getHeightFogThickness(float dist) \n" +
"{ \n"+
(heightFogSetting != null ? getHeightFogMethod(heightFogSetting.fogType) : " return 0.0;") + "\n" +
"} \n");
// Generate method: float calculateHeightFogDepth(float vertical, float realY);
str.append("" +
"float calculateHeightFogDepth(float vertical, float realY) \n" +
"{ \n" +
(heightFogSetting != null ? getHeightDepthMethod(heightFogMode, heightFogHeight) : " return 0.0;") + "\n" +
"} \n");
// Generate method: calculateFarFogDepth(float horizontal, float dist, float nearFogStart);
str.append("" +
"float calculateFarFogDepth(float horizontal, float dist, float nearFogStart) \n" +
"{ \n" +
" return " + (heightFogMixMode == HeightFogMixMode.BASIC ?
"(dist - nearFogStart)/(1.0 - nearFogStart);" :
"(horizontal - nearFogStart)/(1.0 - nearFogStart);") +
"} \n");
// Generate method: float mixFogThickness(float near, float far, float height);
str.append("" +
"float mixFogThickness(float near, float far, float height) \n" +
"{ \n" +
getMixFogLine(heightFogMixMode, drawNearFog) + "\n" +
"} \n");
}
}
//=================//
// shader creation //
// helper methods //
//=================//
private StringBuilder makeRuntimeDefine()
{
StringBuilder str = new StringBuilder();
str.append("// =======RUNTIME GENERATED DEFINE SECTION======== //\n");
str.append("#version 150 core\n");
FogSetting activeFarFogSetting = this.farFogSetting != null ? this.farFogSetting : FogSetting.EMPTY;
FogSetting activeHeightFogSetting = this.heightFogSetting != null ? this.heightFogSetting : FogSetting.EMPTY;
str.append("\n" +
"#define farFogStart " + activeFarFogSetting.start + "\n" +
"#define farFogLength " + (activeFarFogSetting.end - activeFarFogSetting.start) + "\n" +
"#define farFogMin " + activeFarFogSetting.min + "\n" +
"#define farFogRange " + (activeFarFogSetting.max - activeFarFogSetting.min) + "\n" +
"#define farFogDensity " + activeFarFogSetting.density + "\n" +
"\n" +
"#define heightFogStart " + activeHeightFogSetting.start + "\n" +
"#define heightFogLength " + (activeHeightFogSetting.end - activeHeightFogSetting.start) + "\n" +
"#define heightFogMin " + activeHeightFogSetting.min + "\n" +
"#define heightFogRange " + (activeHeightFogSetting.max - activeHeightFogSetting.min) + "\n" +
"#define heightFogDensity " + activeHeightFogSetting.density + "\n" +
"\n");
str.append("// =======RUNTIME END======== //\n");
return str;
}
private static String getFarFogMethod(FogSetting.FogType fogType)
{
switch (fogType)
{
case LINEAR:
return "return linearFog(dist, farFogStart, farFogLength, farFogMin, farFogRange);\n";
case EXPONENTIAL:
return "return exponentialFog(dist, farFogStart, farFogLength, farFogMin, farFogRange, farFogDensity);\n";
case EXPONENTIAL_SQUARED:
return "return exponentialSquaredFog(dist, farFogStart, farFogLength, farFogMin, farFogRange, farFogDensity);\n";
default:
throw new IllegalArgumentException("FogType [" + fogType + "] not implemented for [getFarFogMethod].");
}
}
private static String getHeightDepthMethod(HeightFogMode heightMode, float heightFogHeight)
{
String str = "";
if (!heightMode.basedOnCamera)
{
str = " vertical = realY - (" + heightFogHeight + ");\n";
}
if (heightMode.below && heightMode.above)
{
str += " return abs(vertical);\n";
}
else if (heightMode.below)
{
str += " return -vertical;\n";
}
else if (heightMode.above)
{
str += " return vertical;\n";
}
else
{
str += " return 0;\n";
}
return str;
}
/**
* Returns the method call for the given fog type. <br>
* Example: <br>
* <code>" return linearFog(dist, heightFogStart, heightFogLength, heightFogMin, heightFogRange);"</code>
*/
private static String getHeightFogMethod(FogSetting.FogType fogType)
{
switch (fogType)
{
case LINEAR:
return " return linearFog(dist, heightFogStart, heightFogLength, heightFogMin, heightFogRange);\n";
case EXPONENTIAL:
return " return exponentialFog(dist, heightFogStart, heightFogLength, heightFogMin, heightFogRange, heightFogDensity);\n";
case EXPONENTIAL_SQUARED:
return " return exponentialSquaredFog(dist, heightFogStart, heightFogLength, heightFogMin, heightFogRange, heightFogDensity);\n";
default:
throw new IllegalArgumentException("FogType [" + fogType + "] not implemented for [getHeightFogMethod].");
}
}
/**
* creates a line in the format <br>
* <code>" return max(1.0-near, far);" </code>
*/
private static String getMixFogLine(HeightFogMixMode heightFogMode, boolean drawNearFog)
{
String str = " return ";
switch (heightFogMode)
{
case BASIC:
case IGNORE_HEIGHT:
if (drawNearFog)
str += "max(1.0-near, far);\n";
else
str += "near * far;\n";
break;
case ADDITION:
if (drawNearFog)
str += "max(1.0-near, far + height);\n";
else
str += "near * (far + height);\n";
break;
case MAX:
if (drawNearFog)
str += "max(1.0-near, max(far, height));\n";
else
str += "near * max(far, height);\n";
break;
case INVERSE_MULTIPLY:
if (drawNearFog)
str += "max(1.0-near, 1.0 - (1.0-far)*(1.0-height));\n";
else
str += "near * (1.0 - (1.0-far)*(1.0-height));\n";
break;
case MULTIPLY:
if (drawNearFog)
str += "max(1.0-near, far*height);\n";
else
str += "near * far * height;\n";
break;
case LIMITED_ADDITION:
if (drawNearFog)
str += "max(1.0-near, far + max(far, height));\n";
else
str += "near * (far + max(far, height));\n";
break;
case MULTIPLY_ADDITION:
if (drawNearFog)
str += "max(1.0-near, far + far*height);\n";
else
str += "near * (far + far*height);\n";
break;
case INVERSE_MULTIPLY_ADDITION:
if (drawNearFog)
str += "max(1.0-near, far + 1.0 - (1.0-far)*(1.0-height));\n";
else
str += "near * (far + 1.0 - (1.0-far)*(1.0-height));\n";
break;
case AVERAGE:
if (drawNearFog)
str += "max(1.0-near, far*0.5 + height*0.5);\n";
else
str += "near * (far*0.5 + height*0.5);\n";
break;
default:
throw new IllegalArgumentException("FogType [" + heightFogMode + "] not implemented for [getMixFogMethod].");
}
return str;
}
//========================//
// default object methods //
//========================//
@Override
public boolean equals(Object o)
{
if (this == o)
return true;
if (o == null || getClass() != o.getClass())
return false;
LodFogConfig that = (LodFogConfig) o;
return Float.compare(that.heightFogHeight, heightFogHeight) == 0 &&
drawNearFog == that.drawNearFog && Objects.equals(farFogSetting, that.farFogSetting) &&
Objects.equals(heightFogSetting, that.heightFogSetting) && heightFogMixMode == that.heightFogMixMode &&
heightFogMode == that.heightFogMode && earthCurveRatio == that.earthCurveRatio;
}
@Override
public int hashCode()
{
return Objects.hash(farFogSetting, heightFogSetting, heightFogMixMode, heightFogMode, heightFogHeight, drawNearFog, earthCurveRatio);
}
}
@@ -1,5 +1,5 @@
/*
* This file is part of the Distant Horizon mod (formerly the LOD Mod),
* This file is part of the Distant Horizons mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2021 James Seibel
@@ -21,82 +21,94 @@ 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.handlers.dependencyInjection.SingletonHandler;
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.render.objects.*;
import com.seibel.lod.core.util.LodUtil;
import com.seibel.lod.core.wrapperInterfaces.IVersionConstants;
public class LodRenderProgram extends ShaderProgram {
public static final String VERTEX_SHADER_PATH = "shaders/standard.vert";
public static final String VERTEX_CURVE_SHADER_PATH = "shaders/curve.vert";
public static final String FRAGMENT_SHADER_PATH = "shaders/flat_shaded.frag";
private static final IVersionConstants VERSION_CONSTANTS = SingletonHandler.get(IVersionConstants.class);
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 combinedMatUniform;
public final int modelOffsetUniform;
public final int worldYOffsetUniform;
public final int mircoOffsetUniform;
public final int earthRadiusUniform;
public final int lightMapUniform;
// Fog Uniforms
public final int fogEnabledUniform;
public final int nearFogEnabledUniform;
public final int farFogEnabledUniform;
public final int fogColorUniform;
public final int fogScaleUniform;
public final int fogVerticalScaleUniform;
public final int nearFogStartUniform;
public final int nearFogEndUniform;
public final int farFogStartUniform;
public final int farFogEndUniform;
public final int nearFogLengthUniform;;
public final int fullFogModeUniform;
public final LodFogConfig fogConfig;
// 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");
public LodRenderProgram(LodFogConfig fogConfig) {
super(() -> Shader.loadFile(fogConfig.earthCurveRatio!=0 ? VERTEX_CURVE_SHADER_PATH : VERTEX_SHADER_PATH,
false, new StringBuilder()).toString(),
() -> fogConfig.loadAndProcessFragShader(FRAGMENT_SHADER_PATH, false).toString(),
"fragColor", new String[] { "vPosition", "color" });
this.fogConfig = fogConfig;
combinedMatUniform = getUniformLocation("combinedMatrix");
modelOffsetUniform = getUniformLocation("modelOffset");
worldYOffsetUniform = tryGetUniformLocation("worldYOffset");
mircoOffsetUniform = getUniformLocation("mircoOffset");
earthRadiusUniform = tryGetUniformLocation("earthRadius");
lightMapUniform = getUniformLocation("lightMap");
// Fog uniforms
fogEnabledUniform = getUniformLocation("fogEnabled");
nearFogEnabledUniform = getUniformLocation("nearFogEnabled");
farFogEnabledUniform = getUniformLocation("farFogEnabled");
fullFogModeUniform = getUniformLocation("fullFogMode");
fogColorUniform = getUniformLocation("fogColor");
fogScaleUniform = tryGetUniformLocation("fogScale");
fogVerticalScaleUniform = tryGetUniformLocation("fogVerticalScale");
// near
nearFogStartUniform = getUniformLocation("nearFogStart");
nearFogEndUniform = getUniformLocation("nearFogEnd");
// far
farFogStartUniform = getUniformLocation("farFogStart");
farFogEndUniform = getUniformLocation("farFogEnd");
nearFogStartUniform = tryGetUniformLocation("nearFogStart");
nearFogLengthUniform = tryGetUniformLocation("nearFogLength");
// 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.bind();
// Now a pos+light.
vao.setVertexAttribute(0, 0, VertexAttribute.VertexPointer.addUnsignedShortsPointer(4, false, true)); // 2+2+2+2
//vao.setVertexAttribute(0, posAttrib, VertexAttribute.VertexPointer.addVec3Pointer(false)); // 4+4+4
vao.setVertexAttribute(0, 1, VertexAttribute.VertexPointer.addUnsignedBytesPointer(4, true, false)); // +4
//vao.setVertexAttribute(0, lightAttrib, VertexAttribute.VertexPointer.addUnsignedBytesPointer(2, false)); // +4 due to how it aligns
try {
vao.completeAndCheck(vertexByteCount);
} catch (RuntimeException e) {
System.out.println(LodUtil.LOD_VERTEX_FORMAT);
throw e;
}
if (earthRadiusUniform != -1) setUniform(earthRadiusUniform,
/*6371KM*/ 6371000.0f / fogConfig.earthCurveRatio);
}
// If not usable, return a new LodFogConfig to be constructed
public LodFogConfig isShaderUsable() {
LodFogConfig newConfig = LodFogConfig.generateFogConfig();
if (fogConfig.equals(newConfig)) return null;
return newConfig;
}
// Override ShaderProgram.bind()
@@ -124,32 +136,34 @@ public class LodRenderProgram extends ShaderProgram {
vao.unbindBuffersFromAllBindingPoint();
}
public void fillUniformData(Mat4f modelViewMatrix, Mat4f projectionMatrix, Vec3f cameraPos, Color fogColor, int skyLight, int lightmapBindPoint) {
public void fillUniformData(Mat4f combinedMatrix, Color fogColor,
int lightmapBindPoint, int worldHeight, int worldYOffset, int lodDrawDistance,
int vanillaDrawDistance, boolean fullFogMode) {
super.bind();
vanillaDrawDistance += 32; // Give it a 2 chunk boundary for near fog.
// uniforms
setUniform(mvmUniform, modelViewMatrix);
setUniform(projUniform, projectionMatrix);
setUniform(cameraUniform, cameraPos);
setUniform(fogColorUniform, fogColor);
setUniform(combinedMatUniform, combinedMatrix);
setUniform(mircoOffsetUniform, 0.01f); // 0.01 block offset
// setUniform(skyLightUniform, skyLight);
setUniform(lightMapUniform, lightmapBindPoint);
if (worldYOffsetUniform != -1) setUniform(worldYOffsetUniform, (float)worldYOffset);
// Fog
setUniform(fullFogModeUniform, fullFogMode ? 1 : 0);
setUniform(fogColorUniform, fogColor);
float nearFogLen = vanillaDrawDistance * 0.2f / lodDrawDistance;
float nearFogStart = vanillaDrawDistance * (VERSION_CONSTANTS.isVanillaRenderedChunkSquare() ? (float)Math.sqrt(2.) : 1.f) / lodDrawDistance;
if (nearFogStartUniform != -1) setUniform(nearFogStartUniform, nearFogStart);
if (nearFogLengthUniform != -1) setUniform(nearFogLengthUniform, nearFogLen);
if (fogScaleUniform != -1) setUniform(fogScaleUniform, 1.f/lodDrawDistance);
if (fogVerticalScaleUniform != -1) setUniform(fogVerticalScaleUniform, 1.f/worldHeight);
}
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);
}
public void setModelPos(Vec3f modelPos) {
setUniform(modelOffsetUniform, modelPos);
}
}

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