Compare commits

..

335 Commits

Author SHA1 Message Date
morippi c8b8817909 small change 2022-07-22 19:13:46 +02:00
morippi 8a70545439 small change 2022-07-22 19:13:06 +02:00
TomTheFurry d8e5cbda0a Update flat_shaded.frag, on phone... 2022-07-22 16:05:33 +00:00
morippi 6f4b0a54e9 Added some other comment 2022-07-22 14:37:37 +02:00
morippi 61ea29a6fd Improved comments 2022-07-22 14:37:19 +02:00
morippi 7d6c4fa1f9 small fix 2022-07-22 13:59:02 +02:00
morippi 108b4c7470 everything seems to work, new vertical merge 2022-07-22 13:33:39 +02:00
morippi 63fff244eb Some error are still present. Almost working (the games run, but doesn't generate chunk) 2022-07-22 01:46:02 +02:00
morippi 5e86aa1275 New vertical merge (could not be working) 2022-07-21 23:36:51 +02:00
morippi db72d1cdc2 Transparency should work but it doesn't 2022-07-21 18:46:08 +02:00
morippi 5e99efe093 block that touch trasparent surfaces are added in the building phase and added to the rendering phase. Transparenc is not working for some reason 2022-07-21 16:32:47 +02: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
174 changed files with 15378 additions and 8318 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>
@@ -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.7a-dev";
}
@@ -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, 16, 8, 8, 4, 4, 4, 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,13 +0,0 @@
package com.seibel.lod.core.objects.lod.quadtree;
import com.seibel.lod.core.util.DetailDistanceUtil;
public class LodQuadTree
{
public LodSection[][][] quadTreeStructure;
public QuadTreeProperties properties;
public LodQuadTree()
{
}
}
@@ -1,115 +0,0 @@
package com.seibel.lod.core.objects.lod.quadtree;
/**
A lod section rappresent a distinct section in a precise level of the quadtree.
The section holds all the lods information as color (computed with the blockBiome object referenced by the ID), size, light...
The save and load of a section is handled by the section itself together with a handler class.
*/
public class LodSection
{
//level of detail of this section
public final int detail;
//level position of this section
public final int sectionPosX;
public final int sectionPosZ;
//horizontal size of this section
public final int horizontalSize;
//vertical size of this section
public final int verticalSize;
//how many id we save for each lod
public final int idPerLod;
//What generation mode should be used for chunk in this section
public final byte generationMode;
//if present in region file, use pregenerated chunk in lod creation
public boolean usePregeneratedChunk;
//Position data hold information about each level position like generation mode used, number of vertical lods in that area...
private byte[] positionData;
//Position data hold vertical information about each lod, like minY and maxY...
private int[] verticalData;
//Lights data hold lights information about each lod, like skylight and block light values...
private byte[] lightsData;
//BlockBiomeId hold a unique ID for each lod relative to a block-biome couple. This couple is capable of generating color
//We could just reference
private int[] BlockBiomeId;
//BlockBiomeFrequency for each lod BlockBiomeId
private byte[] BlockBiomeFrequency;
/**
*
* @param detail
* @param horizontalSize
* @param verticalSize
* @param levelPosX
* @param levelPosZ
* @param generationMode
* @param avoidPregeneratedChunk
* @param idPerLod
*/
public LodSection(int detail, int horizontalSize, int verticalSize, int levelPosX, int levelPosZ, byte generationMode, boolean avoidPregeneratedChunk, int idPerLod)
{
this.detail = detail;
this.sectionPosX = levelPosX;
this.sectionPosZ = levelPosZ;
this.horizontalSize = horizontalSize;
this.verticalSize = verticalSize;
this.generationMode = generationMode;
this.usePregeneratedChunk = avoidPregeneratedChunk;
this.idPerLod = idPerLod;
//Now we should search if there is a file with this values
//if so we load it and end the process
//Otherwise we initialize this section
positionData = new byte[horizontalSize*horizontalSize];
verticalData = new int[horizontalSize*horizontalSize*verticalSize];
lightsData = new byte[horizontalSize*horizontalSize*verticalSize];
BlockBiomeId = new int[horizontalSize*horizontalSize*verticalSize*idPerLod];
BlockBiomeFrequency = new byte[horizontalSize*horizontalSize*verticalSize*idPerLod];
}
/**
*
* @param otherSection
* @param inputStartX
* @param inputStartZ
* @param inputEndX
* @param inputEndZ
* @param targetStartX
* @param targetStartZ
* @param targetEndX
* @param targetEndZ
*/
public void mergeAndAdd(LodSection otherSection,
int inputStartX, int inputStartZ, int inputEndX, int inputEndZ,
int targetStartX, int targetStartZ, int targetEndX, int targetEndZ)
{
}
public short getPositionData(int posX, int posZ)
{
return 0;
}
public long[] getData(int posX, int posZ)
{
return null;
}
public void save()
{
}
public void tryload()
{
}
}
@@ -1,11 +0,0 @@
package com.seibel.lod.core.objects.lod.quadtree;
import com.seibel.lod.core.util.DetailDistanceUtil;
public class QuadTreeMover
{
public static void move(LodQuadTree lqt, RenderQuadTree rqt, int centerX, int centerZ)
{
}
}
@@ -1,36 +0,0 @@
package com.seibel.lod.core.objects.lod.quadtree;
import com.seibel.lod.core.util.DetailDistanceUtil;
public class QuadTreeProperties
{
public int MAX_NUMBER_OF_DETAIL=23;
public int SECTION_SIZE=128;
public int[] absoluteDetailCircleSize = new int[MAX_NUMBER_OF_DETAIL];
public int[] relativeDetailCircleSize = new int[MAX_NUMBER_OF_DETAIL];
public int[] generationModeOfDetail = new int[MAX_NUMBER_OF_DETAIL];
public LodQuadTree lodQuadTree;
public RenderQuadTree renderQuadTree;
public void update()
{
for(int detail = 0; detail < MAX_NUMBER_OF_DETAIL; detail++)
{
//Compute circle distance for this detail in term of blocks
absoluteDetailCircleSize[detail] = DetailDistanceUtil.getDrawDistanceFromDetail(detail);
//Compute circle distance in terms of number of section
relativeDetailCircleSize[detail] = (int) Math.ceil(absoluteDetailCircleSize[detail]/(SECTION_SIZE*2^detail));
}
updateGridSize();
}
private void updateGridSize()
{
for(int detail = 0; detail < MAX_NUMBER_OF_DETAIL; detail++)
{
//Use this value to change to update the size of the matrices
//relativeDetailCircleSize[detail];
}
}
}
@@ -1,13 +0,0 @@
package com.seibel.lod.core.objects.lod.quadtree;
import com.seibel.lod.core.util.DetailDistanceUtil;
public class RenderQuadTree
{
//public RenderSection[][][] quadTreeStructure;
public QuadTreeProperties properties;
public RenderQuadTree()
{
}
}
@@ -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,317 @@
/*
* 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;
boolean isTransparent = ColorUtil.getAlpha(color)<255;
boolean isTopTransparent = DataPointUtil.isTransparent(topData);
boolean isBotTransparent = DataPointUtil.isTransparent(botData);
byte skyLightBot = DataPointUtil.doesItExist(botData) ? DataPointUtil.getLightSky(botData) : 0;
// Up direction case
boolean skipTop = DataPointUtil.doesItExist(topData) && (
(isTransparent && (DataPointUtil.getDepth(topData) == maxY)) ||
(!isTransparent && (DataPointUtil.getDepth(topData) == maxY) && !isTopTransparent));
boolean skipBot = DataPointUtil.doesItExist(botData) && (
(isTransparent && (DataPointUtil.getDepth(botData) == maxY)) ||
(!isTransparent && (DataPointUtil.getDepth(botData) == maxY) && !isBotTransparent));
// 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)
{
boolean isTransparent = ColorUtil.getAlpha(color)<255;
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];
boolean isAdjTransparent = DataPointUtil.isTransparent(adjPoint);
// TODO transparency ocean floor fix
if (!isTransparent && isAdjTransparent)
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,451 @@
/*
* 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
/**TODO REMOVE THIS UGLY TRANSPARENCY THING AND MAKE IT MORE SIMPLE*/
for (int transparency = 0; transparency <= 1; transparency++)
{
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 (DataPointUtil.isTransparent(data) == (transparency == 1))
continue;
// 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;
}
}
}

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