Compare commits
515 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| ebe2bd97bc | |||
| a3ac3386d0 | |||
| 69ba83d34f | |||
| e048d277f8 | |||
| 0bca47a92b | |||
| ce84b30ddb | |||
| 1843f96526 | |||
| e46864431b | |||
| 152a3fa108 | |||
| 3420133bd3 | |||
| c79bf7c3f7 | |||
| 2219da4050 | |||
| a1aa90cccb | |||
| d19abaef7b | |||
| 77625c65c7 | |||
| 91ba48ad4a | |||
| 32e587d536 | |||
| e3dfe658f5 | |||
| 4e249e943a | |||
| 1360edb459 | |||
| 4c7ca395c6 | |||
| a1c720d588 | |||
| 7b09840d02 | |||
| e65579f346 | |||
| 78d6481a49 | |||
| 1428e72d46 | |||
| 2c2c6d6785 | |||
| d5d48f2448 | |||
| 536de1a22d | |||
| 193c579712 | |||
| cdba7b20f5 | |||
| 5467d007b8 | |||
| b24d691d8a | |||
| f68e6a9a13 | |||
| 2dcdc854e3 | |||
| 42da213ea9 | |||
| dfea2f561a | |||
| d8b4730ee0 | |||
| 74cbf794a1 | |||
| 812fa65054 | |||
| ba63b44288 | |||
| b19a80f411 | |||
| 450f15ad36 | |||
| a6ed4968f5 | |||
| 65dc629771 | |||
| 80aa49e28b | |||
| 2c3149ef30 | |||
| cd792d7045 | |||
| 3ade43651f | |||
| f4c3ad8bb5 | |||
| 13d2c7b421 | |||
| c54c2c6612 | |||
| 13878b7387 | |||
| 2f20fc6b77 | |||
| d4ada067c5 | |||
| 9277b3ad38 | |||
| c48d409015 | |||
| ccdebb4242 | |||
| 18c94cea46 | |||
| fe381fc207 | |||
| acc73af666 | |||
| 685284f131 | |||
| f803bd58af | |||
| 26fbdfc92c | |||
| e3e8ef705a | |||
| e1a425dbcc | |||
| d8bd4e2347 | |||
| 49cdd5702f | |||
| 32c19eab7f | |||
| a32b66aaab | |||
| 425f4948dc | |||
| d05908af32 | |||
| 822a088096 | |||
| 36079e1624 | |||
| 8db782a406 | |||
| dd018f90d3 | |||
| af4e30588f | |||
| 2630d50147 | |||
| db2410e7fe | |||
| 9990132db2 | |||
| 21069d6479 | |||
| b27afbb18c | |||
| 783c0c97a1 | |||
| 3dfa2d778a | |||
| b44a641d96 | |||
| 8139b83a01 | |||
| 456ba183da | |||
| 7525c1b257 | |||
| 5d4d634537 | |||
| e1149be7c1 | |||
| fc0aaac69f | |||
| 9786a12273 | |||
| c169cb6b7f | |||
| afcdce4667 | |||
| ab7ed9a4c5 | |||
| 120e8e5f6d | |||
| 65824a6fed | |||
| 114aaf9fe4 | |||
| 347b149037 | |||
| 70317a37cb | |||
| 74e4744ff7 | |||
| 98cbc30709 | |||
| b44900e3f0 | |||
| 1755a252b1 | |||
| afcedd0c6d | |||
| 525005bcb6 | |||
| 360294d37c | |||
| ea05e3f9e6 | |||
| 70a75f0543 | |||
| 488d520b8f | |||
| 30bb175ea0 | |||
| 500a68e0fc | |||
| 0cd9bfaef2 | |||
| a464176a25 | |||
| a0dcde48ae | |||
| 8ae81d3aac | |||
| c4f864f0e3 | |||
| cddd239fc5 | |||
| 189b0ec878 | |||
| bd305a0269 | |||
| bbf99ed145 | |||
| cac4807986 | |||
| 181539b83b | |||
| 4795ddd1ff | |||
| c1c6680d04 | |||
| 81cbc7a12b | |||
| 4c6185556f | |||
| b0ed460230 | |||
| 38e323a12f | |||
| 9840c594e6 | |||
| fdd8204a46 | |||
| fb188e4819 | |||
| 9b3d9cbba3 | |||
| a2aa1dd081 | |||
| cb889d7430 | |||
| 2a88b2e746 | |||
| 93dd441708 | |||
| a81ce6c28a | |||
| 967627c57e | |||
| 31cb684bec | |||
| d5112be385 | |||
| 547d54aab1 | |||
| 397573bc08 | |||
| b52d7e4f20 | |||
| a450f44aa7 | |||
| a4e8e9ef60 | |||
| 3c28ed5601 | |||
| c5ec41eb2d | |||
| d1bb96c2dc | |||
| d72ba5bb82 | |||
| baba54bdb8 | |||
| aa7ea85cde | |||
| a1f01bef78 | |||
| 1a7655e752 | |||
| e2a983e4ac | |||
| e2f0263643 | |||
| b280303f8f | |||
| ff3b2aee6b | |||
| 804f830a10 | |||
| 68b5978135 | |||
| 9ebb84bf9c | |||
| a08e08856a | |||
| 8d5c8bf7b8 | |||
| 168eb537cb | |||
| ca57c15d46 | |||
| 5aea9877e0 | |||
| c20ec4ef59 | |||
| 32fb349621 | |||
| c5b4e20787 | |||
| a246dd7561 | |||
| 615e29451a | |||
| bcac2bd7ba | |||
| a02c232f5f | |||
| d27dbbb64e | |||
| fdfbee108a | |||
| de54c37cf1 | |||
| d4580806d1 | |||
| 5a856c65fa | |||
| 75d6da98dc | |||
| 7654cac01d | |||
| cbe242cd2f | |||
| 7f69b11ae4 | |||
| 426c9edb05 | |||
| 88fb9b5b21 | |||
| 163f8ca979 | |||
| ee416a10ec | |||
| c5ef03aa0e | |||
| 25cb08f541 | |||
| fcd06bde87 | |||
| 00f50a2e60 | |||
| 550c0f1bf2 | |||
| 0f0179bd19 | |||
| 7885d14a91 | |||
| bd9c3bba3f | |||
| 04f8cd653a | |||
| 02bfc20ae6 | |||
| d65017366a | |||
| 05d17ba9fa | |||
| 967aab3b3b | |||
| 9f77bf4e61 | |||
| 6aa096de57 | |||
| d312d60cd9 | |||
| 65fde550cb | |||
| 736b5ba625 | |||
| b2227688bc | |||
| cbe8b647b1 | |||
| 0b0f0eab2f | |||
| ffb63ce8ef | |||
| 0e6f5d9805 | |||
| a0bd9648dc | |||
| aa1778cf82 | |||
| 10d66a4775 | |||
| 4082c97e0e | |||
| 665f979e82 | |||
| 911205ce0a | |||
| 51078fde5b | |||
| adde7e8d67 | |||
| 582e38eec5 | |||
| e987e679d0 | |||
| 6d776407a4 | |||
| 8a61a5f0e7 | |||
| 49a9ff8640 | |||
| 47ebdb4aee | |||
| 2b10a9d977 | |||
| 247b7d5633 | |||
| ae9eba0608 | |||
| 7983681b70 | |||
| 85d4106f28 | |||
| 22d329b8aa | |||
| 004d36ffa7 | |||
| 231340d979 | |||
| f55f5b881f | |||
| 03ac090e06 | |||
| 3b375e9c1b | |||
| e44157fae5 | |||
| 997513231c | |||
| 64c3ba297b | |||
| 3ae9c49de4 | |||
| 5692f9fa69 | |||
| 72ba9f5699 | |||
| 696be6f796 | |||
| 5198335948 | |||
| 8102063850 | |||
| 86bf551de8 | |||
| ec2933f23c | |||
| f6daf62c7d | |||
| 50988db28c | |||
| 047cef184d | |||
| ee5b8662d1 | |||
| 9470ee2507 | |||
| 8f63e755be | |||
| b0d62da7fe | |||
| 7a4347b288 | |||
| bbaa583a6d | |||
| c3709f726c | |||
| d1417069d9 | |||
| 412f1bead0 | |||
| 1095f63832 | |||
| 89d0317c7f | |||
| 4f489a5272 | |||
| 8c843ba4b5 | |||
| 77401cbfa0 | |||
| b983692fc1 | |||
| e1a40c6868 | |||
| 9c2add4e3b | |||
| 8cede4760e | |||
| bceab238d5 | |||
| 121676e661 | |||
| 9ec3be8a48 | |||
| bb61cdad70 | |||
| 2e32e4d768 | |||
| d4ee641362 | |||
| 953c2b2e92 | |||
| 2cc78c874b | |||
| f6f0ecb21b | |||
| 190fef934c | |||
| 52608a9f3f | |||
| 54e5fd30ab | |||
| 89115fd5d5 | |||
| 5631ae52be | |||
| 6520cdb184 | |||
| ce5f8708cd | |||
| 270a83ddbc | |||
| 64b8cb3556 | |||
| 7f7884c130 | |||
| 5a95613cee | |||
| 9a58800499 | |||
| d68e46a2d0 | |||
| 20d1ff6d49 | |||
| ed188448f2 | |||
| 87c803e4c6 | |||
| 58f51aae91 | |||
| 97a773176d | |||
| f6d92021ed | |||
| 026cbd5da3 | |||
| 3538e79a6b | |||
| d520fb8535 | |||
| d7dbf8b994 | |||
| 2d70e8ebe2 | |||
| 5eafe8f818 | |||
| e875c664fd | |||
| e313a03410 | |||
| 781aa339bc | |||
| 73e318ee38 | |||
| 337853cdfa | |||
| afe4e111c3 | |||
| 3675c5da46 | |||
| a4cc39dbac | |||
| e3787079b7 | |||
| b2f98e2d92 | |||
| e1edd01069 | |||
| eb3455452b | |||
| 34baf923b2 | |||
| 499308870d | |||
| f852a79a0e | |||
| fa98454151 | |||
| 286fc91db6 | |||
| 99209f920d | |||
| 19fd1aff98 | |||
| 9ebc5aeb32 | |||
| 7c890a4627 | |||
| ccc661225e | |||
| d7b13a2e0b | |||
| c53ce15f7d | |||
| 35ac6fc756 | |||
| 6bcf58959b | |||
| 5051508e42 | |||
| 196e02e61a | |||
| bc8188beb0 | |||
| 0fc4c5532b | |||
| 98586d6af9 | |||
| bdd1e2410a | |||
| c0240c71bf | |||
| 65ffc93e0f | |||
| 01183be383 | |||
| 359791ea58 | |||
| 5b3aa82817 | |||
| d77bb48da1 | |||
| 8b941cb95c | |||
| b9d991db63 | |||
| 1a8a7be494 | |||
| d054ac91b1 | |||
| e40ee20460 | |||
| 29b3e9fadc | |||
| 3cc78c62a0 | |||
| 2c719c41d9 | |||
| cd06b42b02 | |||
| e8b46a6fd2 | |||
| 814ac5df84 | |||
| 7854f659a3 | |||
| 6cb1d2f8f4 | |||
| f95d57ab6d | |||
| 55f9d142e6 | |||
| 58607ab1fc | |||
| 433ab4f92f | |||
| 8840973a1e | |||
| f91479829d | |||
| 829c9531fa | |||
| f9bb248eef | |||
| 03ce4d9c42 | |||
| f181d073a3 | |||
| 63b01f3ec7 | |||
| 14f1b6db54 | |||
| 604089cfa8 | |||
| 712d9db2fa | |||
| 2b5b023472 | |||
| 4a9bc7ca74 | |||
| 804f910584 | |||
| d5b2512e27 | |||
| 280d281604 | |||
| 95bcc71d23 | |||
| b623f1581f | |||
| 62b6d9ea30 | |||
| 6c8d0f98a6 | |||
| dc353cd029 | |||
| 86e4ab7e83 | |||
| c6a96ae710 | |||
| 9e24ee0ef7 | |||
| 4460789ab1 | |||
| a232f449ed | |||
| 1c5988fbfb | |||
| b386547616 | |||
| 03ea7ee460 | |||
| 752a2e4a69 | |||
| 3e3e30c4b1 | |||
| f5cc5c2846 | |||
| 609d471346 | |||
| 5f921ffcef | |||
| 4aebf36f02 | |||
| 0b1fdba6ea | |||
| 475324ed73 | |||
| d36f97d765 | |||
| ee5dc0a6b3 | |||
| 05ed61b0a1 | |||
| 2ef6c59dfc | |||
| fd90f761a1 | |||
| 72bcc87de7 | |||
| 7ec7d6bdc5 | |||
| f9ed9a472c | |||
| b542738097 | |||
| bceefb5717 | |||
| f977687ad6 | |||
| 31ef1d4959 | |||
| 86d07eb62b | |||
| 39c7ae8d17 | |||
| 5c369fd515 | |||
| 2b0926a097 | |||
| 9b6b67b178 | |||
| bb07e3db9a | |||
| 46e65704d7 | |||
| d5ec17551e | |||
| 8390cfc6fb | |||
| 2711fbf01a | |||
| e4f64df660 | |||
| a6c9d0df98 | |||
| 136dd5b47a | |||
| 277def2acc | |||
| e941b553e7 | |||
| 92e651c779 | |||
| 2b82eb88c8 | |||
| 13fecf9d1d | |||
| cdd1529a13 | |||
| 7915444713 | |||
| 2135ae769d | |||
| b98cfb9666 | |||
| a28da86d44 | |||
| 799b0e2481 | |||
| e380c7c069 | |||
| 5a3b8f5baa | |||
| a1c547b72e | |||
| bb5613e3fe | |||
| a29060bf4f | |||
| 79ee23e0b0 | |||
| d3231a480d | |||
| 3694dcba4c | |||
| bf6db89a4b | |||
| 38c644739a | |||
| a0fe977976 | |||
| 739d6d5856 | |||
| 84125735a1 | |||
| 884f6811a1 | |||
| 402abb0963 | |||
| 2f7f489e14 | |||
| 4de5c287fc | |||
| ccb58024a1 | |||
| 0381d56511 | |||
| 7bbd3fd815 | |||
| 4e243252e9 | |||
| b488d21a14 | |||
| 3e8dbf7ac7 | |||
| 695b73f9d3 | |||
| 6e37bce38a | |||
| 06232f65b2 | |||
| bebe4b7436 | |||
| 7d9f04d54c | |||
| 70336acc75 | |||
| e1ef08a783 | |||
| 648a70097a | |||
| 280892f5f4 | |||
| 3c57b124d5 | |||
| 04d6bae479 | |||
| 98096a5db7 | |||
| 7e4f3a3185 | |||
| 9fe6be10f6 | |||
| 76b356d38e | |||
| 811e24ee5e | |||
| 5002db15d6 | |||
| a3357c1193 | |||
| af36224a57 | |||
| 630ff361d3 | |||
| 34c6f29a18 | |||
| 909718e491 | |||
| 8a3d199247 | |||
| fa13c981b7 | |||
| 815b00c3ca | |||
| ae9144a6c4 | |||
| 359fde3b6b | |||
| 92fa904cc6 | |||
| 2583ae34d4 | |||
| e36b3394f4 | |||
| 7af38df92c | |||
| 51add24110 | |||
| 9e5aac3bf7 | |||
| 5738a5b7cd | |||
| e1216966a3 | |||
| 222c0de7f1 | |||
| b4f1fb6d28 | |||
| a32082ad20 | |||
| eeb5fb6c3c | |||
| cb50f24c86 | |||
| 5ca5764c0e | |||
| fccd1db045 | |||
| 6c515350bc | |||
| 50aee9dfb2 | |||
| 0649504770 | |||
| 29068f9550 | |||
| 18c08ccd88 | |||
| b71d6a5e3f | |||
| 8f619f3fa1 | |||
| eab16ff20a | |||
| d913ed9621 | |||
| a649cf179f | |||
| cf5de39250 | |||
| bab3cd9656 | |||
| cafd4f0c47 | |||
| e20833225f | |||
| 14a06c220b | |||
| e5a5ba327e | |||
| fedc8f7b66 | |||
| 7a3497d44c | |||
| 873034f7e4 | |||
| a151054b48 | |||
| 14c69971f6 | |||
| edc3858699 | |||
| bdaf33b80b |
@@ -22,4 +22,5 @@ eclipse
|
||||
run
|
||||
|
||||
# Files from Forge MDK
|
||||
logs
|
||||
forge*changelog.txt
|
||||
|
||||
@@ -1,65 +0,0 @@
|
||||
Minecraft Forge: Credits/Thank You
|
||||
|
||||
Forge is a set of tools and modifications to the Minecraft base game code to assist
|
||||
mod developers in creating new and exciting content. It has been in development for
|
||||
several years now, but I would like to take this time thank a few people who have
|
||||
helped it along it's way.
|
||||
|
||||
First, the people who originally created the Forge projects way back in Minecraft
|
||||
alpha. Eloraam of RedPower, and SpaceToad of Buildcraft, without their acceptiance
|
||||
of me taking over the project, who knows what Minecraft modding would be today.
|
||||
|
||||
Secondly, someone who has worked with me, and developed some of the core features
|
||||
that allow modding to be as functional, and as simple as it is, cpw. For developing
|
||||
FML, which stabelized the client and server modding ecosystem. As well as the base
|
||||
loading system that allows us to modify Minecraft's code as elegently as possible.
|
||||
|
||||
Mezz, who has stepped up as the issue and pull request manager. Helping to keep me
|
||||
sane as well as guiding the community into creating better additions to Forge.
|
||||
|
||||
Searge, Bspks, Fesh0r, ProfMobious, and all the rest over on the MCP team {of which
|
||||
I am a part}. For creating some of the core tools needed to make Minecraft modding
|
||||
both possible, and as stable as can be.
|
||||
On that note, here is some specific information of the MCP data we use:
|
||||
* Minecraft Coder Pack (MCP) *
|
||||
Forge Mod Loader and Minecraft Forge have permission to distribute and automatically
|
||||
download components of MCP and distribute MCP data files. This permission is not
|
||||
transitive and others wishing to redistribute the Minecraft Forge source independently
|
||||
should seek permission of MCP or remove the MCP data files and request their users
|
||||
to download MCP separately.
|
||||
|
||||
And lastly, the countless community members who have spent time submitting bug reports,
|
||||
pull requests, and just helping out the community in general. Thank you.
|
||||
|
||||
--LexManos
|
||||
|
||||
=========================================================================
|
||||
|
||||
This is Forge Mod Loader.
|
||||
|
||||
You can find the source code at all times at https://github.com/MinecraftForge/MinecraftForge/tree/1.12.x/src/main/java/net/minecraftforge/fml
|
||||
|
||||
This minecraft mod is a clean open source implementation of a mod loader for minecraft servers
|
||||
and minecraft clients.
|
||||
|
||||
The code is authored by cpw.
|
||||
|
||||
It began by partially implementing an API defined by the client side ModLoader, authored by Risugami.
|
||||
http://www.minecraftforum.net/topic/75440-
|
||||
This support has been dropped as of Minecraft release 1.7, as Risugami no longer maintains ModLoader.
|
||||
|
||||
It also contains suggestions and hints and generous helpings of code from LexManos, author of MinecraftForge.
|
||||
http://www.minecraftforge.net/
|
||||
|
||||
Additionally, it contains an implementation of topological sort based on that
|
||||
published at http://keithschwarz.com/interesting/code/?dir=topological-sort
|
||||
|
||||
It also contains code from the Maven project for performing versioned dependency
|
||||
resolution. http://maven.apache.org/
|
||||
|
||||
It also contains a partial repackaging of the javaxdelta library from http://sourceforge.net/projects/javaxdelta/
|
||||
with credit to it's authors.
|
||||
|
||||
Forge Mod Loader downloads components from the Minecraft Coder Pack
|
||||
(http://mcp.ocean-labs.de/index.php/Main_Page) with kind permission from the MCP team.
|
||||
|
||||
@@ -2,15 +2,16 @@ This mod adds a Level Of Detail (LOD) system to Minecraft.
|
||||
This implementation renders simplified chunks outside the normal render distance
|
||||
allowing for an increased view distance without harming performance.
|
||||
|
||||
Or in other words: this mod let's you see farther without turning your game into a slide show.
|
||||
If you want to see a quick demo, check out the video I made here:
|
||||
https://youtu.be/CCT-3s02tYA
|
||||
|
||||
Forge version: 1.16.4-35.1.4
|
||||
|
||||
Forge version: 1.16.5-36.1.0
|
||||
|
||||
Notes:
|
||||
This version has been confirmed to work in Eclipse and retail Minecraft.
|
||||
(retail running forge 1.16.4-35.1.37)
|
||||
That being said only singleplayer is currently supported; connecting
|
||||
to servers (local or otherwise) will cause no LODs to be drawn and
|
||||
may cause instibility.
|
||||
This version has been confirmed to work in Eclipse and Retail Minecraft.
|
||||
(Retail running forge version 1.16.5-36.1.0)
|
||||
|
||||
|
||||
========================
|
||||
@@ -63,3 +64,7 @@ Note to self
|
||||
|
||||
The Minecraft source code is NOT added to your workspace in a editable way. Minecraft is treated like a normal Library. Sources are there for documentation and research purposes only.
|
||||
|
||||
Source code uses mcp mappings not Mojangs.
|
||||
|
||||
The source code can be 'created' with the ./eclipse command and can be found in the following path:
|
||||
minecraft-lod-mod\build\fg_cache\mcp\ VERSION \joined\ RANDOM_STRING \patch\output.jar
|
||||
|
||||
|
After Width: | Height: | Size: 113 KiB |
|
After Width: | Height: | Size: 83 KiB |
|
After Width: | Height: | Size: 81 KiB |
|
After Width: | Height: | Size: 191 KiB |
|
After Width: | Height: | Size: 172 KiB |
|
After Width: | Height: | Size: 194 KiB |
|
After Width: | Height: | Size: 174 KiB |
@@ -3,7 +3,11 @@ buildscript {
|
||||
maven { url = 'https://files.minecraftforge.net/maven' }
|
||||
jcenter()
|
||||
mavenCentral()
|
||||
maven { url='https://dist.creeper.host/Sponge/maven' }
|
||||
// potential replacement in case of problems:
|
||||
// https://dist.creeper.host/Sponge/maven
|
||||
maven { url = 'https://repo.spongepowered.org/maven/' }
|
||||
// used to download and compile dependencies from git repos
|
||||
maven { url 'https://jitpack.io' }
|
||||
}
|
||||
dependencies {
|
||||
classpath group: 'net.minecraftforge.gradle', name: 'ForgeGradle', version: '3.+', changing: true
|
||||
@@ -15,13 +19,21 @@ apply plugin: 'org.spongepowered.mixin'
|
||||
// Only edit below this line, the above code adds and enables the necessary things for Forge to be setup.
|
||||
apply plugin: 'eclipse'
|
||||
apply plugin: 'maven-publish'
|
||||
apply plugin: 'java' // needed for compileJava
|
||||
|
||||
version = 'a1'
|
||||
group = 'com.backsun.lod'
|
||||
archivesBaseName = 'lod_1.16.4'
|
||||
version = 'a1.4'
|
||||
group = 'com.seibel.lod'
|
||||
archivesBaseName = 'lod_1.16.5'
|
||||
|
||||
sourceCompatibility = targetCompatibility = compileJava.sourceCompatibility = compileJava.targetCompatibility = '1.8' // Need this here so eclipse task generates correctly.
|
||||
|
||||
compileJava {
|
||||
// release 8 is needed because otherwise FloatBuffer.flip() will crash
|
||||
// on some machines
|
||||
// example thread: https://github.com/eclipse/jetty.project/issues/3244
|
||||
options.compilerArgs.addAll(['--release', '8', '-Xlint:unchecked', '-Xlint:deprecation'])
|
||||
}
|
||||
|
||||
println('Java: ' + System.getProperty('java.version') + ' JVM: ' + System.getProperty('java.vm.version') + '(' + System.getProperty('java.vendor') + ') Arch: ' + System.getProperty('os.arch'))
|
||||
minecraft {
|
||||
// The mappings can be changed at any time, and must be in the following format.
|
||||
@@ -29,7 +41,7 @@ minecraft {
|
||||
// stable_# Stables are built at the discretion of the MCP team.
|
||||
// Use non-default mappings at your own risk. they may not always work.
|
||||
// Simply re-run your setup task after changing the mappings to update your workspace.
|
||||
mappings channel: 'snapshot', version: '20201028-1.16.3'
|
||||
mappings channel: 'official', version: '1.16.5'
|
||||
|
||||
// makeObfSourceJar = false // an Srg named sources jar is made by default. uncomment this to disable.
|
||||
|
||||
@@ -43,9 +55,15 @@ minecraft {
|
||||
arg "-mixin.config=lod.mixins.json"
|
||||
|
||||
// Recommended logging data for a userdev environment
|
||||
property 'forge.logging.markers', 'SCAN,REGISTRIES,REGISTRYDUMP'
|
||||
// The markers can be changed as needed.
|
||||
// "SCAN": For mods scan.
|
||||
// "REGISTRIES": For firing of registry events.
|
||||
// "REGISTRYDUMP": For getting the contents of all registries.
|
||||
property 'forge.logging.markers', 'REGISTRIES'
|
||||
|
||||
// Recommended logging level for the console
|
||||
// You can set various levels here.
|
||||
// Please read: https://stackoverflow.com/questions/2031163/when-to-use-the-different-log-levels
|
||||
property 'forge.logging.console.level', 'debug'
|
||||
|
||||
mods {
|
||||
@@ -60,9 +78,15 @@ minecraft {
|
||||
arg "-mixin.config=lod.mixins.json"
|
||||
|
||||
// Recommended logging data for a userdev environment
|
||||
property 'forge.logging.markers', 'SCAN,REGISTRIES,REGISTRYDUMP'
|
||||
// The markers can be changed as needed.
|
||||
// "SCAN": For mods scan.
|
||||
// "REGISTRIES": For firing of registry events.
|
||||
// "REGISTRYDUMP": For getting the contents of all registries.
|
||||
property 'forge.logging.markers', 'REGISTRIES'
|
||||
|
||||
// Recommended logging level for the console
|
||||
// You can set various levels here.
|
||||
// Please read: https://stackoverflow.com/questions/2031163/when-to-use-the-different-log-levels
|
||||
property 'forge.logging.console.level', 'debug'
|
||||
|
||||
mods {
|
||||
@@ -76,9 +100,15 @@ minecraft {
|
||||
workingDirectory project.file('run')
|
||||
|
||||
// Recommended logging data for a userdev environment
|
||||
property 'forge.logging.markers', 'SCAN,REGISTRIES,REGISTRYDUMP'
|
||||
// The markers can be changed as needed.
|
||||
// "SCAN": For mods scan.
|
||||
// "REGISTRIES": For firing of registry events.
|
||||
// "REGISTRYDUMP": For getting the contents of all registries.
|
||||
property 'forge.logging.markers', 'REGISTRIES'
|
||||
|
||||
// Recommended logging level for the console
|
||||
// You can set various levels here.
|
||||
// Please read: https://stackoverflow.com/questions/2031163/when-to-use-the-different-log-levels
|
||||
property 'forge.logging.console.level', 'debug'
|
||||
|
||||
// Specify the modid for data generation, where to output the resulting resource, and where to look for existing resources.
|
||||
@@ -96,11 +126,52 @@ minecraft {
|
||||
// Include resources generated by data generators.
|
||||
sourceSets.main.resources { srcDir 'src/generated/resources' }
|
||||
|
||||
// this is required so that we can use
|
||||
// jitpack in the dependencies section below
|
||||
repositories {
|
||||
mavenCentral()
|
||||
// used to download and compile dependencies from git repos
|
||||
maven { url 'https://jitpack.io' }
|
||||
}
|
||||
|
||||
dependencies {
|
||||
// Specify the version of Minecraft to use, If this is any group other then 'net.minecraft' it is assumed
|
||||
// that the dep is a ForgeGradle 'patcher' dependency. And it's patches will be applied.
|
||||
// The userdev artifact is a special name and will get all sorts of transformations applied to it.
|
||||
minecraft 'net.minecraftforge:forge:1.16.4-35.1.4'
|
||||
minecraft 'net.minecraftforge:forge:1.16.5-36.1.0'
|
||||
|
||||
// these aren't needed right now
|
||||
//implementation ('com.github.KaptainWutax:TerrainUtils:1.0.0'){
|
||||
// transitive = false
|
||||
//}
|
||||
//implementation ('com.github.KaptainWutax:BiomeUtils:1.0.0'){
|
||||
// transitive = false
|
||||
//}
|
||||
//implementation ('com.github.KaptainWutax:SeedUtils:-SNAPSHOT'){
|
||||
// transitive = false
|
||||
//}
|
||||
//implementation ('com.github.KaptainWutax:MCUtils:1.0.0'){
|
||||
// transitive = false
|
||||
//}
|
||||
//implementation ('com.github.KaptainWutax:NoiseUtils:1.0.0'){
|
||||
// transitive = false
|
||||
//}
|
||||
//implementation ('com.github.KaptainWutax:MathUtils:-SNAPSHOT'){
|
||||
// transitive = false
|
||||
//}
|
||||
|
||||
// these were added to hopefully allow for cloning
|
||||
// configuredFeatures to allow for safe
|
||||
// multi threaded feature generation. Sadly I couldn't find
|
||||
// a way to duplicate lambda functions (which features use)
|
||||
// so for now I'm not sure what to do.
|
||||
//implementation 'io.github.kostaskougios:cloning:1.10.3'
|
||||
//
|
||||
//implementation ('com.esotericsoftware:kryo:5.1.1') {
|
||||
// exclude group: "org.objenesis"
|
||||
//}
|
||||
//implementation 'org.objenesis:objenesis:3.2'
|
||||
|
||||
|
||||
// You may put jars on which you depend on in ./libs or you may define them like so..
|
||||
// compile "some.group:artifact:version:classifier"
|
||||
@@ -126,10 +197,10 @@ dependencies {
|
||||
jar {
|
||||
manifest {
|
||||
attributes([
|
||||
"Specification-Title": "Levels of Detail",
|
||||
"Specification-Title": "LOD",
|
||||
"Specification-Version": "1", // We are version 1 of ourselves
|
||||
"Implementation-Title": project.name,
|
||||
"Implementation-Version": "1.0",
|
||||
"Implementation-Version": "{version}",
|
||||
"Implementation-Timestamp": new Date().format("yyyy-MM-dd'T'HH:mm:ssZ"),
|
||||
"MixinConfigs": "lod.mixins.json",
|
||||
])
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
package com.backsun.lod;
|
||||
|
||||
/**
|
||||
* This file is similar to mcmod.info
|
||||
*
|
||||
* @author James Seibel
|
||||
* @version 02-17-2021
|
||||
*/
|
||||
public final class ModInfo
|
||||
{
|
||||
public static final String MODID = "lod";
|
||||
public static final String MODNAME = "Levels of Detail";
|
||||
public static final String MODAPI = "LodAPI";
|
||||
public static final String VERSION = "1.0";
|
||||
}
|
||||
@@ -1,189 +0,0 @@
|
||||
package com.backsun.lod.builders;
|
||||
import java.awt.Color;
|
||||
|
||||
import org.lwjgl.opengl.GL11;
|
||||
|
||||
import com.backsun.lod.objects.NearFarBuffer;
|
||||
import com.backsun.lod.renderer.LodRenderer;
|
||||
import com.backsun.lod.util.enums.FogDistance;
|
||||
|
||||
import net.minecraft.client.renderer.BufferBuilder;
|
||||
import net.minecraft.util.math.AxisAlignedBB;
|
||||
|
||||
/**
|
||||
* This object is used to create NearFarBuffer objects.
|
||||
*
|
||||
* @author James Seibel
|
||||
* @version 02-27-2021
|
||||
*/
|
||||
public class LodBufferBuilder
|
||||
{
|
||||
public BufferBuilder nearBuffer;
|
||||
public BufferBuilder farBuffer;
|
||||
public FogDistance distanceMode;
|
||||
public AxisAlignedBB[][] lods;
|
||||
public Color[][] colors;
|
||||
|
||||
|
||||
|
||||
public LodBufferBuilder()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
public NearFarBuffer createBuffers(
|
||||
BufferBuilder newNearBufferBuilder, BufferBuilder newFarBufferBuilder,
|
||||
FogDistance newDistanceMode,
|
||||
AxisAlignedBB[][] newLods, Color[][] newColors)
|
||||
{
|
||||
nearBuffer = newNearBufferBuilder;
|
||||
farBuffer = newFarBufferBuilder;
|
||||
distanceMode = newDistanceMode;
|
||||
lods = newLods;
|
||||
colors = newColors;
|
||||
|
||||
|
||||
nearBuffer.begin(GL11.GL_QUADS, LodRenderer.LOD_VERTEX_FORMAT);
|
||||
farBuffer.begin(GL11.GL_QUADS, LodRenderer.LOD_VERTEX_FORMAT);
|
||||
|
||||
int numbChunksWide = lods.length;
|
||||
|
||||
BufferBuilder currentBuffer;
|
||||
AxisAlignedBB bb;
|
||||
int red;
|
||||
int green;
|
||||
int blue;
|
||||
int alpha;
|
||||
|
||||
|
||||
// x axis
|
||||
for (int i = 0; i < numbChunksWide; i++)
|
||||
{
|
||||
// z axis
|
||||
for (int j = 0; j < numbChunksWide; j++)
|
||||
{
|
||||
if (lods[i][j] == null || colors[i][j] == null)
|
||||
continue;
|
||||
|
||||
bb = lods[i][j];
|
||||
|
||||
// get the color of this LOD object
|
||||
red = colors[i][j].getRed();
|
||||
green = colors[i][j].getGreen();
|
||||
blue = colors[i][j].getBlue();
|
||||
alpha = colors[i][j].getAlpha();
|
||||
|
||||
|
||||
if (isCoordinateInNearFogArea(i, j, numbChunksWide / 2))
|
||||
currentBuffer = nearBuffer;
|
||||
else
|
||||
currentBuffer = farBuffer;
|
||||
|
||||
|
||||
if (bb.minY != bb.maxY)
|
||||
{
|
||||
// top (facing up)
|
||||
addPosAndColor(currentBuffer, bb.minX, bb.maxY, bb.minZ, red, green, blue, alpha);
|
||||
addPosAndColor(currentBuffer, bb.minX, bb.maxY, bb.maxZ, red, green, blue, alpha);
|
||||
addPosAndColor(currentBuffer, bb.maxX, bb.maxY, bb.maxZ, red, green, blue, alpha);
|
||||
addPosAndColor(currentBuffer, bb.maxX, bb.maxY, bb.minZ, red, green, blue, alpha);
|
||||
// bottom (facing down)
|
||||
addPosAndColor(currentBuffer, bb.maxX, bb.minY, bb.minZ, red, green, blue, alpha);
|
||||
addPosAndColor(currentBuffer, bb.maxX, bb.minY, bb.maxZ, red, green, blue, alpha);
|
||||
addPosAndColor(currentBuffer, bb.minX, bb.minY, bb.maxZ, red, green, blue, alpha);
|
||||
addPosAndColor(currentBuffer, bb.minX, bb.minY, bb.minZ, red, green, blue, alpha);
|
||||
|
||||
// south (facing -Z)
|
||||
addPosAndColor(currentBuffer, bb.maxX, bb.minY, bb.maxZ, red, green, blue, alpha);
|
||||
addPosAndColor(currentBuffer, bb.maxX, bb.maxY, bb.maxZ, red, green, blue, alpha);
|
||||
addPosAndColor(currentBuffer, bb.minX, bb.maxY, bb.maxZ, red, green, blue, alpha);
|
||||
addPosAndColor(currentBuffer, bb.minX, bb.minY, bb.maxZ, red, green, blue, alpha);
|
||||
// north (facing +Z)
|
||||
addPosAndColor(currentBuffer, bb.minX, bb.minY, bb.minZ, red, green, blue, alpha);
|
||||
addPosAndColor(currentBuffer, bb.minX, bb.maxY, bb.minZ, red, green, blue, alpha);
|
||||
addPosAndColor(currentBuffer, bb.maxX, bb.maxY, bb.minZ, red, green, blue, alpha);
|
||||
addPosAndColor(currentBuffer, bb.maxX, bb.minY, bb.minZ, red, green, blue, alpha);
|
||||
|
||||
// west (facing -X)
|
||||
addPosAndColor(currentBuffer, bb.minX, bb.minY, bb.minZ, red, green, blue, alpha);
|
||||
addPosAndColor(currentBuffer, bb.minX, bb.minY, bb.maxZ, red, green, blue, alpha);
|
||||
addPosAndColor(currentBuffer, bb.minX, bb.maxY, bb.maxZ, red, green, blue, alpha);
|
||||
addPosAndColor(currentBuffer, bb.minX, bb.maxY, bb.minZ, red, green, blue, alpha);
|
||||
// east (facing +X)
|
||||
addPosAndColor(currentBuffer, bb.maxX, bb.maxY, bb.minZ, red, green, blue, alpha);
|
||||
addPosAndColor(currentBuffer, bb.maxX, bb.maxY, bb.maxZ, red, green, blue, alpha);
|
||||
addPosAndColor(currentBuffer, bb.maxX, bb.minY, bb.maxZ, red, green, blue, alpha);
|
||||
addPosAndColor(currentBuffer, bb.maxX, bb.minY, bb.minZ, red, green, blue, alpha);
|
||||
}
|
||||
else
|
||||
{
|
||||
// render this LOD as one block thick
|
||||
|
||||
// top (facing up)
|
||||
addPosAndColor(currentBuffer, bb.minX, bb.minY+1, bb.minZ, red, green, blue, alpha);
|
||||
addPosAndColor(currentBuffer, bb.minX, bb.minY+1, bb.maxZ, red, green, blue, alpha);
|
||||
addPosAndColor(currentBuffer, bb.maxX, bb.minY+1, bb.maxZ, red, green, blue, alpha);
|
||||
addPosAndColor(currentBuffer, bb.maxX, bb.minY+1, bb.minZ, red, green, blue, alpha);
|
||||
// bottom (facing down)
|
||||
addPosAndColor(currentBuffer, bb.maxX, bb.minY, bb.minZ, red, green, blue, alpha);
|
||||
addPosAndColor(currentBuffer, bb.maxX, bb.minY, bb.maxZ, red, green, blue, alpha);
|
||||
addPosAndColor(currentBuffer, bb.minX, bb.minY, bb.maxZ, red, green, blue, alpha);
|
||||
addPosAndColor(currentBuffer, bb.minX, bb.minY, bb.minZ, red, green, blue, alpha);
|
||||
|
||||
// south (facing -Z)
|
||||
addPosAndColor(currentBuffer, bb.maxX, bb.minY, bb.maxZ, red, green, blue, alpha);
|
||||
addPosAndColor(currentBuffer, bb.maxX, bb.minY+1, bb.maxZ, red, green, blue, alpha);
|
||||
addPosAndColor(currentBuffer, bb.minX, bb.minY+1, bb.maxZ, red, green, blue, alpha);
|
||||
addPosAndColor(currentBuffer, bb.minX, bb.minY, bb.maxZ, red, green, blue, alpha);
|
||||
// north (facing +Z)
|
||||
addPosAndColor(currentBuffer, bb.minX, bb.minY, bb.minZ, red, green, blue, alpha);
|
||||
addPosAndColor(currentBuffer, bb.minX, bb.minY+1, bb.minZ, red, green, blue, alpha);
|
||||
addPosAndColor(currentBuffer, bb.maxX, bb.minY+1, bb.minZ, red, green, blue, alpha);
|
||||
addPosAndColor(currentBuffer, bb.maxX, bb.minY, bb.minZ, red, green, blue, alpha);
|
||||
|
||||
// west (facing -X)
|
||||
addPosAndColor(currentBuffer, bb.minX, bb.minY, bb.minZ, red, green, blue, alpha);
|
||||
addPosAndColor(currentBuffer, bb.minX, bb.minY, bb.maxZ, red, green, blue, alpha);
|
||||
addPosAndColor(currentBuffer, bb.minX, bb.minY+1, bb.maxZ, red, green, blue, alpha);
|
||||
addPosAndColor(currentBuffer, bb.minX, bb.minY+1, bb.minZ, red, green, blue, alpha);
|
||||
// east (facing +X)
|
||||
addPosAndColor(currentBuffer, bb.maxX, bb.minY+1, bb.minZ, red, green, blue, alpha);
|
||||
addPosAndColor(currentBuffer, bb.maxX, bb.minY+1, bb.maxZ, red, green, blue, alpha);
|
||||
addPosAndColor(currentBuffer, bb.maxX, bb.minY, bb.maxZ, red, green, blue, alpha);
|
||||
addPosAndColor(currentBuffer, bb.maxX, bb.minY, bb.minZ, red, green, blue, alpha);
|
||||
}
|
||||
|
||||
} // z axis
|
||||
} // x axis
|
||||
|
||||
nearBuffer.finishDrawing();
|
||||
farBuffer.finishDrawing();
|
||||
|
||||
return new NearFarBuffer(nearBuffer, farBuffer);
|
||||
}
|
||||
|
||||
private void addPosAndColor(BufferBuilder buffer, double x, double y, double z, int red, int green, int blue, int alpha)
|
||||
{
|
||||
buffer.pos(x, y, z).color(red, green, blue, alpha).endVertex();
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Find the coordinates that are in the center half of the given
|
||||
* 2D matrix, starting at (0,0) and going to (2 * lodRadius, 2 * lodRadius).
|
||||
*/
|
||||
private static boolean isCoordinateInNearFogArea(int chunkX, int chunkZ, int lodRadius)
|
||||
{
|
||||
int halfRadius = lodRadius / 2;
|
||||
|
||||
return (chunkX >= lodRadius - halfRadius
|
||||
&& chunkX <= lodRadius + halfRadius)
|
||||
&&
|
||||
(chunkZ >= lodRadius - halfRadius
|
||||
&& chunkZ <= lodRadius + halfRadius);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,124 +0,0 @@
|
||||
package com.backsun.lod.builders;
|
||||
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
import com.backsun.lod.handlers.LodDimensionFileHandler;
|
||||
import com.backsun.lod.objects.LodChunk;
|
||||
import com.backsun.lod.objects.LodDimension;
|
||||
import com.backsun.lod.objects.LodWorld;
|
||||
import com.backsun.lod.util.LodUtils;
|
||||
|
||||
import net.minecraft.world.DimensionType;
|
||||
import net.minecraft.world.chunk.Chunk;
|
||||
import net.minecraft.world.chunk.ChunkSection;
|
||||
import net.minecraft.world.server.ServerWorld;
|
||||
|
||||
/**
|
||||
* This object is in charge of creating Lod
|
||||
* related objects.
|
||||
* (specifically: Lod World, Dimension, Region, and Chunk objects)
|
||||
*
|
||||
* @author James Seibel
|
||||
* @version 2-22-2021
|
||||
*/
|
||||
public class LodBuilder
|
||||
{
|
||||
private ExecutorService lodGenThreadPool = Executors.newSingleThreadExecutor();
|
||||
public volatile LodWorld lodWorld;
|
||||
|
||||
/** Default size of any LOD regions we use */
|
||||
public int regionWidth = 5;
|
||||
|
||||
public LodBuilder()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Returns LodWorld so that it can be passed
|
||||
* to the LodRenderer.
|
||||
*/
|
||||
public LodWorld generateLodChunkAsync(Chunk chunk)
|
||||
{
|
||||
if (lodWorld != null)
|
||||
// is this chunk from the same world as the lodWorld?
|
||||
if (!lodWorld.worldName.equals(LodDimensionFileHandler.getCurrentWorldID()))
|
||||
// we are not in the same world anymore,
|
||||
// remove the old world so it can be recreated later
|
||||
lodWorld = null;
|
||||
|
||||
// don't try to create an LOD object
|
||||
// if for some reason we aren't
|
||||
// given a valid chunk object
|
||||
// (Minecraft often gives back empty
|
||||
// or null chunks in this method)
|
||||
if (chunk == null || !isValidChunk(chunk))
|
||||
return lodWorld;
|
||||
|
||||
|
||||
DimensionType dim = chunk.getWorld().getDimensionType();
|
||||
ServerWorld world = LodUtils.getServerWorldFromDimension(dim);
|
||||
|
||||
|
||||
if (world == null)
|
||||
return lodWorld;
|
||||
|
||||
Thread thread = new Thread(() ->
|
||||
{
|
||||
try
|
||||
{
|
||||
LodChunk lod = new LodChunk(chunk, world);
|
||||
LodDimension lodDim;
|
||||
|
||||
if (lodWorld == null)
|
||||
{
|
||||
lodWorld = new LodWorld(LodDimensionFileHandler.getCurrentWorldID());
|
||||
}
|
||||
|
||||
|
||||
if (lodWorld.getLodDimension(dim) == null)
|
||||
{
|
||||
lodDim = new LodDimension(dim, regionWidth);
|
||||
lodWorld.addLodDimension(lodDim);
|
||||
}
|
||||
else
|
||||
{
|
||||
lodDim = lodWorld.getLodDimension(dim);
|
||||
}
|
||||
|
||||
lodDim.addLod(lod);
|
||||
}
|
||||
catch(IllegalArgumentException | NullPointerException e)
|
||||
{
|
||||
// if the world changes while LODs are being generated
|
||||
// they will throw errors as they try to access things that no longer
|
||||
// exist.
|
||||
}
|
||||
});
|
||||
lodGenThreadPool.execute(thread);
|
||||
|
||||
return lodWorld;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return whether the given chunk
|
||||
* has any data in it.
|
||||
*/
|
||||
public boolean isValidChunk(Chunk chunk)
|
||||
{
|
||||
ChunkSection[] blockStorage = chunk.getSections();
|
||||
|
||||
for(ChunkSection section : blockStorage)
|
||||
{
|
||||
if(section != null && !section.isEmpty())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -1,289 +0,0 @@
|
||||
package com.backsun.lod.handlers;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.File;
|
||||
import java.io.FileReader;
|
||||
import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
import com.backsun.lod.objects.LodChunk;
|
||||
import com.backsun.lod.objects.LodDimension;
|
||||
import com.backsun.lod.objects.LodRegion;
|
||||
import com.backsun.lod.util.LodUtils;
|
||||
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.world.server.ServerChunkProvider;
|
||||
import net.minecraft.world.server.ServerWorld;
|
||||
|
||||
/**
|
||||
* This object handles creating LodRegions
|
||||
* from files and saving LodRegion objects
|
||||
* to file.
|
||||
*
|
||||
* @author James Seibel
|
||||
* @version 01-30-2021
|
||||
*/
|
||||
public class LodDimensionFileHandler
|
||||
{
|
||||
private LodDimension loadedDimension = null;
|
||||
public long regionLastWriteTime[][];
|
||||
|
||||
private File dimensionDataSaveFolder;
|
||||
|
||||
private final String FILE_NAME_PREFIX = "lod";
|
||||
private final String FILE_EXTENSION = ".txt";
|
||||
|
||||
private ExecutorService fileWritingThreadPool = Executors.newFixedThreadPool(1);
|
||||
|
||||
|
||||
public LodDimensionFileHandler(File newSaveFolder, LodDimension newLoadedDimension)
|
||||
{
|
||||
dimensionDataSaveFolder = newSaveFolder;
|
||||
|
||||
loadedDimension = newLoadedDimension;
|
||||
// these two variable are used in sync with the LodDimension
|
||||
regionLastWriteTime = new long[loadedDimension.getWidth()][loadedDimension.getWidth()];
|
||||
for(int i = 0; i < loadedDimension.getWidth(); i++)
|
||||
for(int j = 0; j < loadedDimension.getWidth(); j++)
|
||||
regionLastWriteTime[i][j] = -1;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
//================//
|
||||
// read from file //
|
||||
//================//
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Return the LodRegion at the given coordinates.
|
||||
* (null if the file doesn't exist)
|
||||
*/
|
||||
public LodRegion loadRegionFromFile(int regionX, int regionZ)
|
||||
{
|
||||
// we don't currently support reading or writing
|
||||
// files when connected to a server
|
||||
if (!Minecraft.getInstance().isIntegratedServerRunning())
|
||||
return null;
|
||||
|
||||
if (!readyToReadAndWrite())
|
||||
return null;
|
||||
|
||||
String fileName = getFileNameForRegion(regionX, regionZ);
|
||||
|
||||
File f = new File(fileName);
|
||||
|
||||
if (!f.exists())
|
||||
{
|
||||
// there wasn't a file, don't
|
||||
// return anything
|
||||
return null;
|
||||
}
|
||||
|
||||
LodRegion region = new LodRegion(regionX, regionZ);
|
||||
|
||||
try
|
||||
{
|
||||
BufferedReader br = new BufferedReader(new FileReader(f));
|
||||
String s = br.readLine();
|
||||
|
||||
while(s != null && !s.isEmpty())
|
||||
{
|
||||
try
|
||||
{
|
||||
// convert each line into an LOD object and add it to the region
|
||||
LodChunk lod = new LodChunk(s);
|
||||
|
||||
region.addLod(lod);
|
||||
}
|
||||
catch(IllegalArgumentException e)
|
||||
{
|
||||
// we were unable to create this chunk
|
||||
// for whatever reason.
|
||||
// skip to the next chunk
|
||||
}
|
||||
|
||||
s = br.readLine();
|
||||
}
|
||||
|
||||
br.close();
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
// File not found
|
||||
|
||||
// or the buffered reader encountered a
|
||||
// problem reading the file
|
||||
return null;
|
||||
}
|
||||
|
||||
return region;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
//==============//
|
||||
// Save to File //
|
||||
//==============//
|
||||
|
||||
/**
|
||||
* Save all dirty regions in this LodDimension to file.
|
||||
*/
|
||||
public synchronized void saveDirtyRegionsToFileAsync()
|
||||
{
|
||||
// we don't currently support reading or writing
|
||||
// files when connected to a server
|
||||
if (!Minecraft.getInstance().isIntegratedServerRunning())
|
||||
return;
|
||||
|
||||
if (!readyToReadAndWrite())
|
||||
// we aren't ready to read and write yet
|
||||
return;
|
||||
|
||||
fileWritingThreadPool.execute(saveDirtyRegionsThread);
|
||||
}
|
||||
private Thread saveDirtyRegionsThread = new Thread(() ->
|
||||
{
|
||||
for(int i = 0; i < loadedDimension.getWidth(); i++)
|
||||
{
|
||||
for(int j = 0; j < loadedDimension.getWidth(); j++)
|
||||
{
|
||||
if(loadedDimension.isRegionDirty[i][j])
|
||||
{
|
||||
saveRegionToDisk(loadedDimension.regions[i][j]);
|
||||
loadedDimension.isRegionDirty[i][j] = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Save a specific region to disk.<br>
|
||||
* Note: it will save to the LodDimension that this
|
||||
* handler is associated with.
|
||||
*/
|
||||
private void saveRegionToDisk(LodRegion region)
|
||||
{
|
||||
if (!readyToReadAndWrite() || region == null)
|
||||
return;
|
||||
|
||||
// convert chunk coordinates to region
|
||||
// coordinates
|
||||
int x = region.x;
|
||||
int z = region.z;
|
||||
|
||||
File f = new File(getFileNameForRegion(x, z));
|
||||
|
||||
try
|
||||
{
|
||||
// make sure the file and folder exists
|
||||
if (!f.exists())
|
||||
if(!f.getParentFile().exists())
|
||||
f.getParentFile().mkdirs();
|
||||
f.createNewFile();
|
||||
|
||||
FileWriter fw = new FileWriter(f);
|
||||
|
||||
for(LodChunk[] chunkArray : region.getAllLods())
|
||||
for(LodChunk chunk : chunkArray)
|
||||
if(chunk != null)
|
||||
fw.write(chunk.toData() + "\n");
|
||||
|
||||
fw.close();
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
System.err.println("LOD file write error: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
//================//
|
||||
// helper methods //
|
||||
//================//
|
||||
|
||||
|
||||
/**
|
||||
* Return the name of the file that should contain the
|
||||
* region at the given x and z. <br>
|
||||
* Returns null if this object isn't ready to read and write.
|
||||
*/
|
||||
private String getFileNameForRegion(int regionX, int regionZ)
|
||||
{
|
||||
if (!readyToReadAndWrite())
|
||||
return null;
|
||||
|
||||
try
|
||||
{
|
||||
// saveFolder is something like
|
||||
// ".\Super Flat\DIM-1\data"
|
||||
// or
|
||||
// ".\Super Flat\data"
|
||||
return dimensionDataSaveFolder.getCanonicalPath() + "\\lod\\" +
|
||||
FILE_NAME_PREFIX + "." + regionX + "." + regionZ + FILE_EXTENSION;
|
||||
}
|
||||
catch(IOException e)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns if this FileHandler is ready to read
|
||||
* and write files.
|
||||
* <br>
|
||||
* This returns true when the world save directory is known.
|
||||
*/
|
||||
public boolean readyToReadAndWrite()
|
||||
{
|
||||
return dimensionDataSaveFolder != null;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* If on single player this will return the name of the user's
|
||||
* world, if in multiplayer it will return the server name
|
||||
* and game version.
|
||||
*/
|
||||
public static String getCurrentWorldID()
|
||||
{
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
|
||||
if(mc.isIntegratedServerRunning())
|
||||
{
|
||||
ServerWorld serverWorld = LodUtils.getFirstValidServerWorld();
|
||||
if (serverWorld == null)
|
||||
return "";
|
||||
|
||||
ServerChunkProvider provider = serverWorld.getChunkProvider();
|
||||
if(provider != null)
|
||||
return provider.getSavedData().folder.toString();
|
||||
|
||||
return "";
|
||||
}
|
||||
else
|
||||
{
|
||||
return mc.getCurrentServerData().serverName + "_version_" + mc.getCurrentServerData().gameVersion;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
package com.backsun.lod.mixin;
|
||||
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
|
||||
import com.backsun.lod.renderer.RenderGlobalHook;
|
||||
import com.mojang.blaze3d.matrix.MatrixStack;
|
||||
|
||||
import net.minecraft.client.renderer.RenderType;
|
||||
import net.minecraft.client.renderer.WorldRenderer;
|
||||
|
||||
@Mixin(WorldRenderer.class)
|
||||
public class MixinWorldRenderer
|
||||
{
|
||||
@Inject(at = @At("HEAD"), method = "renderBlockLayer(Lnet/minecraft/client/renderer/RenderType;Lcom/mojang/blaze3d/matrix/MatrixStack;DDD)V", cancellable = false)
|
||||
private void renderBlockLayer(RenderType blockLayerIn, MatrixStack matrixStackIn, double xIn, double yIn, double zIn, CallbackInfo callback)
|
||||
{
|
||||
RenderGlobalHook.startRenderingStencil(blockLayerIn);
|
||||
}
|
||||
}
|
||||
@@ -1,776 +0,0 @@
|
||||
package com.backsun.lod.objects;
|
||||
|
||||
import java.awt.Color;
|
||||
|
||||
import com.backsun.lod.util.enums.ColorDirection;
|
||||
import com.backsun.lod.util.enums.LodCorner;
|
||||
import com.backsun.lod.util.enums.LodLocation;
|
||||
|
||||
import net.minecraft.block.Blocks;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.renderer.color.BlockColors;
|
||||
import net.minecraft.world.World;
|
||||
import net.minecraft.world.chunk.Chunk;
|
||||
import net.minecraft.world.chunk.ChunkSection;
|
||||
|
||||
/**
|
||||
* This object contains position
|
||||
* and color data for an LOD object.
|
||||
*
|
||||
* @author James Seibel
|
||||
* @version 02-13-2021
|
||||
*/
|
||||
public class LodChunk
|
||||
{
|
||||
/** how many different pieces of data are in one line */
|
||||
private static final int DATA_DELIMITER_COUNT = 28;
|
||||
|
||||
/** This is what separates each piece of data in the toData method */
|
||||
public static final char DATA_DELIMITER = ',';
|
||||
|
||||
public static final int WIDTH = 16;
|
||||
|
||||
private static final int CHUNK_DATA_WIDTH = WIDTH;
|
||||
private static final int CHUNK_DATA_HEIGHT = WIDTH;
|
||||
|
||||
/**
|
||||
* This is how many blocks are
|
||||
* required at a specific y-value
|
||||
* to constitute a LOD point
|
||||
*/
|
||||
private final int LOD_BLOCK_REQ = 16;
|
||||
// the max number of blocks per layer = 64 (8*8)
|
||||
// since each layer is 1/4 the chunk
|
||||
|
||||
|
||||
/** The x coordinate of the chunk. */
|
||||
public int x;
|
||||
/** The z coordinate of the chunk. */
|
||||
public int z;
|
||||
|
||||
// each short is the height of
|
||||
// that 8th of the chunk.
|
||||
public short top[];
|
||||
public short bottom[];
|
||||
|
||||
/** The average color of each 6 cardinal directions */
|
||||
public Color colors[];
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
//==============//
|
||||
// constructors //
|
||||
//==============//
|
||||
|
||||
/**
|
||||
* Create an empty invisible LodChunk at (0,0)
|
||||
*/
|
||||
public LodChunk()
|
||||
{
|
||||
x = 0;
|
||||
z = 0;
|
||||
|
||||
top = new short[4];
|
||||
bottom = new short[4];
|
||||
colors = new Color[6];
|
||||
|
||||
// by default have the colors invisible
|
||||
for(ColorDirection dir : ColorDirection.values())
|
||||
{
|
||||
colors[dir.value] = new Color(0, 0, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an LodChunk from the string
|
||||
* generated by the toData method.
|
||||
*
|
||||
* @throws IllegalArgumentException if the data isn't valid to create a LodChunk
|
||||
* @throws NumberFormatException if the data can't be converted into an int at any point
|
||||
*/
|
||||
public LodChunk(String data) throws IllegalArgumentException, NumberFormatException
|
||||
{
|
||||
/*
|
||||
* data format:
|
||||
* x, z, top data, bottom data, rgb color data
|
||||
*
|
||||
* example:
|
||||
* 5,8, 4,4,4,4, 0,0,0,0, 255,255,255, 255,255,255, 255,255,255, 255,255,255, 255,255,255, 255,255,255,
|
||||
*/
|
||||
|
||||
// make sure there are the correct number of entries
|
||||
// in the data string (28)
|
||||
int count = 0;
|
||||
|
||||
for(int i = 0; i < data.length(); i++)
|
||||
{
|
||||
if(data.charAt(i) == DATA_DELIMITER)
|
||||
{
|
||||
count++;
|
||||
}
|
||||
}
|
||||
|
||||
if(count != DATA_DELIMITER_COUNT)
|
||||
{
|
||||
throw new IllegalArgumentException("LodChunk constructor givin an invalid string. The data given had " + count + " delimiters when it should have had " + DATA_DELIMITER_COUNT + ".");
|
||||
}
|
||||
|
||||
|
||||
|
||||
// index we will use when going through the String
|
||||
int index = 0;
|
||||
int lastIndex = 0;
|
||||
|
||||
|
||||
|
||||
// x and z position
|
||||
index = data.indexOf(DATA_DELIMITER, 0);
|
||||
x = Integer.parseInt(data.substring(0,index));
|
||||
|
||||
lastIndex = index;
|
||||
index = data.indexOf(DATA_DELIMITER, lastIndex + 1);
|
||||
z = Integer.parseInt(data.substring(lastIndex+1,index));
|
||||
|
||||
|
||||
|
||||
// top
|
||||
top = new short[4];
|
||||
for(LodLocation loc : LodLocation.values())
|
||||
{
|
||||
lastIndex = index;
|
||||
index = data.indexOf(DATA_DELIMITER, lastIndex + 1);
|
||||
|
||||
top[loc.value] = Short.parseShort(data.substring(lastIndex+1,index));
|
||||
}
|
||||
|
||||
|
||||
// bottom
|
||||
bottom = new short[4];
|
||||
for(LodLocation loc : LodLocation.values())
|
||||
{
|
||||
lastIndex = index;
|
||||
index = data.indexOf(DATA_DELIMITER, lastIndex + 1);
|
||||
|
||||
bottom[loc.value] = Short.parseShort(data.substring(lastIndex+1,index));
|
||||
}
|
||||
|
||||
|
||||
// color
|
||||
colors = new Color[6];
|
||||
for(ColorDirection dir : ColorDirection.values())
|
||||
{
|
||||
int red = 0;
|
||||
int green = 0;
|
||||
int blue = 0;
|
||||
|
||||
// get r,g,b
|
||||
for(int i = 0; i < 3; i++)
|
||||
{
|
||||
lastIndex = index;
|
||||
index = data.indexOf(DATA_DELIMITER, lastIndex + 1);
|
||||
|
||||
String raw = "";
|
||||
switch(i)
|
||||
{
|
||||
case 0:
|
||||
raw = data.substring(lastIndex+1,index);
|
||||
red = Short.parseShort(raw);
|
||||
break;
|
||||
case 1:
|
||||
raw = data.substring(lastIndex+1,index);
|
||||
green = Short.parseShort(raw);
|
||||
break;
|
||||
case 2:
|
||||
raw = data.substring(lastIndex+1,index);
|
||||
blue = Short.parseShort(raw);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
colors[dir.value] = new Color(red, green, blue);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a LodChunk for a chunk in the given world. <br>
|
||||
* Note: The world is required to determine each block's color
|
||||
*
|
||||
* @throws IllegalArgumentException
|
||||
* thrown if either the chunk or world is null.
|
||||
*/
|
||||
public LodChunk(Chunk chunk, World world) throws IllegalArgumentException
|
||||
{
|
||||
if(chunk == null)
|
||||
{
|
||||
throw new IllegalArgumentException("LodChunk constructor given a null chunk");
|
||||
}
|
||||
if(world == null)
|
||||
{
|
||||
throw new IllegalArgumentException("LodChunk constructor given a null world");
|
||||
}
|
||||
|
||||
|
||||
x = chunk.getPos().x;
|
||||
z = chunk.getPos().z;
|
||||
|
||||
top = new short[4];
|
||||
bottom = new short[4];
|
||||
colors = new Color[6];
|
||||
|
||||
// generate the top and bottom points of this LOD
|
||||
for(LodLocation loc : LodLocation.values())
|
||||
{
|
||||
top[loc.value] = generateLodCorner(chunk, SectionGenerationMode.GENERATE_TOP, loc);
|
||||
bottom[loc.value] = generateLodCorner(chunk, SectionGenerationMode.GENERATE_BOTTOM, loc);
|
||||
}
|
||||
|
||||
// determine the average color for each direction
|
||||
for(ColorDirection dir : ColorDirection.values())
|
||||
{
|
||||
colors[dir.value] = generateLodColorForDirection(chunk, world, dir);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
//=====================//
|
||||
// constructor helpers //
|
||||
//=====================//
|
||||
|
||||
|
||||
/**
|
||||
* Generate the height for the given LodLocation, either the top or bottom.
|
||||
* <br><br>
|
||||
* If invalid/null/empty chunks are given
|
||||
* crashes may occur.
|
||||
*/
|
||||
public short generateLodCorner(Chunk chunk, SectionGenerationMode generationMode, LodLocation lodLoc)
|
||||
{
|
||||
// should have a length of 16
|
||||
// (each storage is 16x16x16 and the
|
||||
// world height is 256)
|
||||
ChunkSection[] chunkSections = chunk.getSections();
|
||||
|
||||
|
||||
|
||||
int startX = 0;
|
||||
int endX = 0;
|
||||
|
||||
int startZ = 0;
|
||||
int endZ = 0;
|
||||
|
||||
// determine where we should look in this
|
||||
// chunk
|
||||
switch(lodLoc)
|
||||
{
|
||||
case NE:
|
||||
// -N
|
||||
startZ = 0;
|
||||
endZ = (CHUNK_DATA_WIDTH / 2) - 1;
|
||||
// +E
|
||||
startX = CHUNK_DATA_WIDTH / 2;
|
||||
endX = CHUNK_DATA_WIDTH - 1;
|
||||
break;
|
||||
|
||||
case SE:
|
||||
// +S
|
||||
startZ = CHUNK_DATA_WIDTH / 2;
|
||||
endZ = CHUNK_DATA_WIDTH;
|
||||
// +E
|
||||
startX = CHUNK_DATA_WIDTH / 2;
|
||||
endX = CHUNK_DATA_WIDTH;
|
||||
break;
|
||||
|
||||
case SW:
|
||||
// +S
|
||||
startZ = CHUNK_DATA_WIDTH / 2;
|
||||
endZ = CHUNK_DATA_WIDTH;
|
||||
// -W
|
||||
startX = 0;
|
||||
endX = (CHUNK_DATA_WIDTH / 2) - 1;
|
||||
break;
|
||||
|
||||
case NW:
|
||||
// -N
|
||||
startZ = 0;
|
||||
endZ = CHUNK_DATA_WIDTH / 2;
|
||||
// -W
|
||||
startX = 0;
|
||||
endX = CHUNK_DATA_WIDTH / 2;
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
if(generationMode == SectionGenerationMode.GENERATE_TOP)
|
||||
return determineTopPoint(chunkSections, startX, endX, startZ, endZ);
|
||||
else
|
||||
return determineBottomPoint(chunkSections, startX, endX, startZ, endZ);
|
||||
}
|
||||
/** GENERATE_TOP, GENERATE_BOTTOM */
|
||||
private enum SectionGenerationMode
|
||||
{
|
||||
GENERATE_TOP,
|
||||
GENERATE_BOTTOM;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the lowest valid point from the bottom.
|
||||
*/
|
||||
private short determineBottomPoint(ChunkSection[] chunkSections, int startX, int endX, int startZ, int endZ)
|
||||
{
|
||||
// search from the bottom up
|
||||
for(int i = 0; i < chunkSections.length; i++)
|
||||
{
|
||||
for(int y = 0; y < CHUNK_DATA_HEIGHT; y++)
|
||||
{
|
||||
|
||||
if(isLayerValidLodPoint(chunkSections, startX, endX, startZ, endZ, i, y))
|
||||
{
|
||||
// we found
|
||||
// enough blocks in this
|
||||
// layer to count as an
|
||||
// LOD point
|
||||
return (short) (y + (i * CHUNK_DATA_HEIGHT));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// we never found a valid LOD point
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the highest valid point from the Top
|
||||
*/
|
||||
private short determineTopPoint(ChunkSection[] chunkSections, int startX, int endX, int startZ, int endZ)
|
||||
{
|
||||
// search from the top down
|
||||
for(int i = chunkSections.length - 1; i >= 0; i--)
|
||||
{
|
||||
for(int y = CHUNK_DATA_WIDTH - 1; y >= 0; y--)
|
||||
{
|
||||
if(isLayerValidLodPoint(chunkSections, startX, endX, startZ, endZ, i, y))
|
||||
{
|
||||
// we found
|
||||
// enough blocks in this
|
||||
// layer to count as an
|
||||
// LOD point
|
||||
return (short) (y + (i * CHUNK_DATA_HEIGHT));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// we never found a valid LOD point
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Is the layer between the given X, Z, and dataIndex
|
||||
* values a valid LOD point?
|
||||
*/
|
||||
private boolean isLayerValidLodPoint(
|
||||
ChunkSection[] chunkSections,
|
||||
int startX, int endX,
|
||||
int startZ, int endZ,
|
||||
int sectionIndex, int y)
|
||||
{
|
||||
// search through this layer
|
||||
int layerBlocks = 0;
|
||||
|
||||
for(int x = startX; x < endX; x++)
|
||||
{
|
||||
for(int z = startZ; z < endZ; z++)
|
||||
{
|
||||
if(chunkSections[sectionIndex] == null)
|
||||
{
|
||||
// this section doesn't have any blocks,
|
||||
// it is not a valid section
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
if(chunkSections[sectionIndex].getBlockState(x, y, z) != null && chunkSections[sectionIndex].getBlockState(x, y, z).getBlock() != Blocks.AIR)
|
||||
{
|
||||
// we found a valid block in
|
||||
// in this layer
|
||||
layerBlocks++;
|
||||
|
||||
if(layerBlocks >= LOD_BLOCK_REQ)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // z
|
||||
} // x
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate the color of the given ColorDirection at the given chunk
|
||||
* in the given world.
|
||||
*/
|
||||
private Color generateLodColorForDirection(Chunk chunk, World world, ColorDirection colorDir)
|
||||
{
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
BlockColors bc = mc.getBlockColors();
|
||||
|
||||
switch (colorDir)
|
||||
{
|
||||
case TOP:
|
||||
return generateLodColorVertical(chunk, colorDir, world, bc);
|
||||
case BOTTOM:
|
||||
return generateLodColorVertical(chunk, colorDir, world, bc);
|
||||
|
||||
case N:
|
||||
return generateLodColorHorizontal(chunk, colorDir, world, bc);
|
||||
case S:
|
||||
return generateLodColorHorizontal(chunk, colorDir, world, bc);
|
||||
|
||||
case E:
|
||||
return generateLodColorHorizontal(chunk, colorDir, world, bc);
|
||||
case W:
|
||||
return generateLodColorHorizontal(chunk, colorDir, world, bc);
|
||||
}
|
||||
|
||||
return new Color(0, 0, 0, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates the color of the top or bottom of a given chunk in the given world.
|
||||
*
|
||||
* @throws IllegalArgumentException if given a ColorDirection other than TOP or BOTTOM
|
||||
*/
|
||||
private Color generateLodColorVertical(Chunk chunk, ColorDirection colorDir, World world, BlockColors bc)
|
||||
{
|
||||
if(colorDir != ColorDirection.TOP && colorDir != ColorDirection.BOTTOM)
|
||||
{
|
||||
throw new IllegalArgumentException("generateLodColorVertical only accepts the ColorDirection TOP or BOTTOM");
|
||||
}
|
||||
|
||||
ChunkSection[] chunkSections = chunk.getSections();
|
||||
|
||||
int numbOfBlocks = 0;
|
||||
int red = 0;
|
||||
int green = 0;
|
||||
int blue = 0;
|
||||
|
||||
boolean goTopDown = (colorDir == ColorDirection.TOP);
|
||||
|
||||
|
||||
// either go top down or bottom up
|
||||
int dataStart = goTopDown? chunkSections.length - 1 : 0;
|
||||
int dataMax = chunkSections.length;
|
||||
int dataMin = 0;
|
||||
int dataIncrement = goTopDown? -1 : 1;
|
||||
|
||||
int topStart = goTopDown? CHUNK_DATA_HEIGHT - 1 : 0;
|
||||
int topMax = CHUNK_DATA_HEIGHT;
|
||||
int topMin = 0;
|
||||
int topIncrement = goTopDown? -1 : 1;
|
||||
|
||||
for(int x = 0; x < CHUNK_DATA_WIDTH; x++)
|
||||
{
|
||||
for(int z = 0; z < CHUNK_DATA_WIDTH; z++)
|
||||
{
|
||||
boolean foundBlock = false;
|
||||
|
||||
for(int i = dataStart; !foundBlock && i >= dataMin && i < dataMax; i += dataIncrement)
|
||||
{
|
||||
if(!foundBlock && chunkSections[i] != null)
|
||||
{
|
||||
for(int y = topStart; !foundBlock && y >= topMin && y < topMax; y += topIncrement)
|
||||
{
|
||||
int ci;
|
||||
ci = chunkSections[i].getBlockState(x, y, z).materialColor.colorValue;
|
||||
|
||||
if(ci == 0)
|
||||
{
|
||||
// skip air or invisible blocks
|
||||
continue;
|
||||
}
|
||||
|
||||
Color c = intToColor(ci);
|
||||
|
||||
red += c.getRed();
|
||||
green += c.getGreen();
|
||||
blue += c.getBlue();
|
||||
|
||||
numbOfBlocks++;
|
||||
|
||||
|
||||
// we found a valid block, skip to the
|
||||
// next x and z
|
||||
foundBlock = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
if(numbOfBlocks == 0)
|
||||
numbOfBlocks = 1;
|
||||
|
||||
red /= numbOfBlocks;
|
||||
green /= numbOfBlocks;
|
||||
blue /= numbOfBlocks;
|
||||
|
||||
return new Color(red, green, blue);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates the color of the side of a given chunk in the given world for the given ColorDirection.
|
||||
*
|
||||
* @throws IllegalArgumentException if given a ColorDirection other than N, S, W, E (North, South, East, West)
|
||||
*/
|
||||
private Color generateLodColorHorizontal(Chunk chunk, ColorDirection colorDir, World world, BlockColors bc)
|
||||
{
|
||||
if(colorDir != ColorDirection.N && colorDir != ColorDirection.S && colorDir != ColorDirection.E && colorDir != ColorDirection.W)
|
||||
{
|
||||
throw new IllegalArgumentException("generateLodColorHorizontal only accepts the ColorDirection N (North), S (South), E (East), or W (West)");
|
||||
}
|
||||
|
||||
ChunkSection[] chunkSections = chunk.getSections();
|
||||
|
||||
int numbOfBlocks = 0;
|
||||
int red = 0;
|
||||
int green = 0;
|
||||
int blue = 0;
|
||||
|
||||
|
||||
// these don't change since the over direction doesn't matter
|
||||
int overStart = 0;
|
||||
int overIncrement = 1;
|
||||
|
||||
// determine which direction is "in"
|
||||
int inStart = 0;
|
||||
int inIncrement = 1;
|
||||
switch (colorDir)
|
||||
{
|
||||
case N:
|
||||
inStart = 0;
|
||||
inIncrement = 1;
|
||||
break;
|
||||
case S:
|
||||
inStart = CHUNK_DATA_WIDTH - 1;
|
||||
inIncrement = -1;
|
||||
break;
|
||||
case E:
|
||||
inStart = 0;
|
||||
inIncrement = 1;
|
||||
break;
|
||||
case W:
|
||||
inStart = CHUNK_DATA_WIDTH - 1;
|
||||
inIncrement = -1;
|
||||
break;
|
||||
default:
|
||||
// we were given an invalid position, return invisible.
|
||||
// this shouldn't happen and is mostly here to make the
|
||||
// compiler happy
|
||||
return new Color(0,0,0,0);
|
||||
}
|
||||
|
||||
|
||||
for (int i = 0; i < chunkSections.length; i++)
|
||||
{
|
||||
if (chunkSections[i] != null)
|
||||
{
|
||||
for (int y = 0; y < CHUNK_DATA_HEIGHT; y++)
|
||||
{
|
||||
boolean foundBlock = false;
|
||||
|
||||
// over moves "over" the side of the chunk
|
||||
// in moves "into" the chunk until it finds a block
|
||||
|
||||
for (int over = overStart; !foundBlock && over >= 0 && over < CHUNK_DATA_WIDTH; over += overIncrement)
|
||||
{
|
||||
for (int in = inStart; !foundBlock && in >= 0 && in < CHUNK_DATA_WIDTH; in += inIncrement)
|
||||
{
|
||||
int x = -1;
|
||||
int z = -1;
|
||||
|
||||
// determine which should be X and Z
|
||||
switch(colorDir)
|
||||
{
|
||||
case N:
|
||||
x = over;
|
||||
z = in;
|
||||
break;
|
||||
case S:
|
||||
x = over;
|
||||
z = in;
|
||||
break;
|
||||
case E:
|
||||
x = in;
|
||||
z = over;
|
||||
break;
|
||||
case W:
|
||||
x = in;
|
||||
z = over;
|
||||
break;
|
||||
default:
|
||||
// this will never happen, it would have
|
||||
// been caught by the switch before the loops
|
||||
break;
|
||||
}
|
||||
|
||||
int ci;
|
||||
ci = chunkSections[i].getBlockState(x, y, z).getMaterial().getColor().colorValue;
|
||||
|
||||
if (ci == 0) {
|
||||
// skip air or invisible blocks
|
||||
continue;
|
||||
}
|
||||
|
||||
Color c = intToColor(ci);
|
||||
|
||||
red += c.getRed();
|
||||
green += c.getGreen();
|
||||
blue += c.getBlue();
|
||||
|
||||
numbOfBlocks++;
|
||||
|
||||
// we found a valid block, skip to the
|
||||
// next x and z
|
||||
foundBlock = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
if(numbOfBlocks == 0)
|
||||
numbOfBlocks = 1;
|
||||
|
||||
red /= numbOfBlocks;
|
||||
green /= numbOfBlocks;
|
||||
blue /= numbOfBlocks;
|
||||
|
||||
return new Color(red, green, blue);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a BlockColors int into a Color object.
|
||||
*/
|
||||
private Color intToColor(int num)
|
||||
{
|
||||
int filter = 0b11111111;
|
||||
|
||||
int red = (num >> 16 ) & filter;
|
||||
int green = (num >> 8 ) & filter;
|
||||
int blue = num & filter;
|
||||
|
||||
return new Color(red, green, blue);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a Color into a BlockColors object.
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
private int colorToInt(Color color)
|
||||
{
|
||||
return color.getRGB();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
//================//
|
||||
// misc functions //
|
||||
//================//
|
||||
|
||||
/**
|
||||
* If this LOD is either invisible from every
|
||||
* direction or doesn't have a valid height
|
||||
* it is empty.
|
||||
*/
|
||||
public boolean isLodEmpty()
|
||||
{
|
||||
for(LodCorner corner : LodCorner.values())
|
||||
if(top[corner.value] != -1 || bottom[corner.value] != -1)
|
||||
// at least one corner is valid
|
||||
return false;
|
||||
|
||||
Color invisible = new Color(0,0,0,0);
|
||||
for(ColorDirection dir : ColorDirection.values())
|
||||
if(!colors[dir.value].equals(invisible))
|
||||
// at least one direction has a non-invisible color
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
//========//
|
||||
// output //
|
||||
//========//
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Outputs all data in csv format
|
||||
* with the given delimiter.
|
||||
* <br>
|
||||
* Exports data in the form:
|
||||
* <br>
|
||||
* x, z, top data, bottom data, rgb color data
|
||||
*
|
||||
* <br>
|
||||
* example output:
|
||||
* <br>
|
||||
* 5,8, 4,4,4,4, 0,0,0,0 255,255,255, 255,255,255, 255,255,255, 255,255,255, 255,255,255, 255,255,255,
|
||||
*/
|
||||
public String toData()
|
||||
{
|
||||
String s = "";
|
||||
|
||||
s += Integer.toString(x) + DATA_DELIMITER + Integer.toString(z) + DATA_DELIMITER;
|
||||
|
||||
for(int i = 0; i < top.length; i++)
|
||||
{
|
||||
s += Short.toString(top[i]) + DATA_DELIMITER;
|
||||
}
|
||||
|
||||
for(int i = 0; i < bottom.length; i++)
|
||||
{
|
||||
s += Short.toString(bottom[i]) + DATA_DELIMITER;
|
||||
}
|
||||
|
||||
for(int i = 0; i < colors.length; i++)
|
||||
{
|
||||
s += Integer.toString(colors[i].getRed()) + DATA_DELIMITER + Integer.toString(colors[i].getGreen()) + DATA_DELIMITER + Integer.toString(colors[i].getBlue()) + DATA_DELIMITER;
|
||||
}
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
String s = "";
|
||||
|
||||
s += "x: " + x + " z: " + z + "\t";
|
||||
|
||||
s += "(" + colors[ColorDirection.TOP.value].getRed() + ", " + colors[ColorDirection.TOP.value].getGreen() + ", " + colors[ColorDirection.TOP.value].getBlue() + ")";
|
||||
|
||||
return s;
|
||||
}
|
||||
}
|
||||
@@ -1,351 +0,0 @@
|
||||
package com.backsun.lod.objects;
|
||||
|
||||
import com.backsun.lod.handlers.LodDimensionFileHandler;
|
||||
import com.backsun.lod.util.LodUtils;
|
||||
|
||||
import net.minecraft.world.DimensionType;
|
||||
import net.minecraft.world.server.ServerChunkProvider;
|
||||
|
||||
/**
|
||||
* This object holds all loaded LOD regions
|
||||
* for a given dimension.
|
||||
*
|
||||
* @author James Seibel
|
||||
* @version 02-23-2021
|
||||
*/
|
||||
public class LodDimension
|
||||
{
|
||||
public final DimensionType dimension;
|
||||
|
||||
private volatile int width;
|
||||
private volatile int halfWidth;
|
||||
|
||||
public LodRegion regions[][];
|
||||
public boolean isRegionDirty[][];
|
||||
|
||||
private int centerX;
|
||||
private int centerZ;
|
||||
|
||||
private LodDimensionFileHandler fileHandler;
|
||||
|
||||
|
||||
public LodDimension(DimensionType newDimension, int newMaxWidth)
|
||||
{
|
||||
dimension = newDimension;
|
||||
width = newMaxWidth;
|
||||
|
||||
ServerChunkProvider provider = LodUtils.getServerWorldFromDimension(newDimension).getChunkProvider();
|
||||
fileHandler = new LodDimensionFileHandler(provider.getSavedData().folder, this);
|
||||
|
||||
regions = new LodRegion[width][width];
|
||||
isRegionDirty = new boolean[width][width];
|
||||
|
||||
// populate isRegionDirty
|
||||
for(int i = 0; i < width; i++)
|
||||
for(int j = 0; j < width; j++)
|
||||
isRegionDirty[i][j] = false;
|
||||
|
||||
centerX = 0;
|
||||
centerZ = 0;
|
||||
|
||||
halfWidth = (int)Math.floor(width / 2);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Move the center of this LodDimension and move all owned
|
||||
* regions over by the given x and z offset.
|
||||
*/
|
||||
public void move(int xOffset, int zOffset)
|
||||
{
|
||||
// if the x or z offset is equal to or greater than
|
||||
// the total size, just delete the current data
|
||||
// and update the centerX and/or centerZ
|
||||
if (Math.abs(xOffset) >= width || Math.abs(zOffset) >= width)
|
||||
{
|
||||
for(int x = 0; x < width; x++)
|
||||
{
|
||||
for(int z = 0; z < width; z++)
|
||||
{
|
||||
regions[x][z] = null;
|
||||
}
|
||||
}
|
||||
|
||||
// update the new center
|
||||
centerX += xOffset;
|
||||
centerZ += zOffset;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// X
|
||||
if(xOffset > 0)
|
||||
{
|
||||
// move everything over to the left (as the center moves to the right)
|
||||
for(int x = 0; x < width; x++)
|
||||
{
|
||||
for(int z = 0; z < width; z++)
|
||||
{
|
||||
if(x + xOffset < width)
|
||||
regions[x][z] = regions[x + xOffset][z];
|
||||
else
|
||||
regions[x][z] = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// move everything over to the right (as the center moves to the left)
|
||||
for(int x = width - 1; x >= 0; x--)
|
||||
{
|
||||
for(int z = 0; z < width; z++)
|
||||
{
|
||||
if(x + xOffset >= 0)
|
||||
regions[x][z] = regions[x + xOffset][z];
|
||||
else
|
||||
regions[x][z] = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Z
|
||||
if(zOffset > 0)
|
||||
{
|
||||
// move everything up (as the center moves down)
|
||||
for(int x = 0; x < width; x++)
|
||||
{
|
||||
for(int z = 0; z < width; z++)
|
||||
{
|
||||
if(z + zOffset < width)
|
||||
regions[x][z] = regions[x][z + zOffset];
|
||||
else
|
||||
regions[x][z] = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// move everything down (as the center moves up)
|
||||
for(int x = 0; x < width; x++)
|
||||
{
|
||||
for(int z = width - 1; z >= 0; z--)
|
||||
{
|
||||
if(z + zOffset >= 0)
|
||||
regions[x][z] = regions[x][z + zOffset];
|
||||
else
|
||||
regions[x][z] = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// update the new center
|
||||
centerX += xOffset;
|
||||
centerZ += zOffset;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Gets the region at the given X and Z
|
||||
* <br>
|
||||
* Returns null if the region doesn't exist
|
||||
* or is outside the loaded area.
|
||||
*/
|
||||
public LodRegion getRegion(int regionX, int regionZ)
|
||||
{
|
||||
int xIndex = (regionX - centerX) + halfWidth;
|
||||
int zIndex = (regionZ - centerZ) + halfWidth;
|
||||
|
||||
if (!regionIsInRange(regionX, regionZ))
|
||||
// out of range
|
||||
return null;
|
||||
|
||||
if (regions[xIndex][zIndex] == null)
|
||||
{
|
||||
regions[xIndex][zIndex] = getRegionFromFile(regionX, regionZ);
|
||||
if (regions[xIndex][zIndex] == null)
|
||||
{
|
||||
regions[xIndex][zIndex] = new LodRegion(regionX, regionZ);
|
||||
}
|
||||
}
|
||||
|
||||
return regions[xIndex][zIndex];
|
||||
}
|
||||
|
||||
/**
|
||||
* Overwrite the LodRegion at the location of newRegion with newRegion.
|
||||
* @throws ArrayIndexOutOfBoundsException if newRegion is outside what can be stored in this LodDimension.
|
||||
*/
|
||||
public void setRegion(LodRegion newRegion) throws ArrayIndexOutOfBoundsException
|
||||
{
|
||||
int xIndex = (newRegion.x - centerX) + halfWidth;
|
||||
int zIndex = (centerZ - newRegion.z) + halfWidth;
|
||||
|
||||
if (!regionIsInRange(newRegion.x, newRegion.z))
|
||||
// out of range
|
||||
throw new ArrayIndexOutOfBoundsException();
|
||||
|
||||
regions[xIndex][zIndex] = newRegion;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Add the given LOD to this dimension at the coordinate
|
||||
* stored in the LOD. If an LOD already exists at the given
|
||||
* coordinates it will be overwritten.
|
||||
*/
|
||||
public void addLod(LodChunk lod)
|
||||
{
|
||||
int regionX = lod.x / LodRegion.SIZE;
|
||||
int regionZ = lod.z / LodRegion.SIZE;
|
||||
|
||||
// prevent issues if X/Z is negative and less than 16
|
||||
if (lod.x < 0)
|
||||
{
|
||||
regionX = (Math.abs(regionX) * -1) - 1;
|
||||
}
|
||||
if (lod.z < 0)
|
||||
{
|
||||
regionZ = (Math.abs(regionZ) * -1) - 1;
|
||||
}
|
||||
|
||||
// don't continue if the region can't be saved
|
||||
if (!regionIsInRange(regionX, regionZ))
|
||||
return;
|
||||
|
||||
LodRegion region = getRegion(regionX, regionZ);
|
||||
|
||||
if (region == null)
|
||||
{
|
||||
// if no region exists, create it
|
||||
region = new LodRegion(regionX, regionZ);
|
||||
setRegion(region);
|
||||
}
|
||||
|
||||
region.addLod(lod);
|
||||
|
||||
// mark the region as dirty so it will be saved to disk
|
||||
int xIndex = (regionX - centerX) + halfWidth;
|
||||
int zIndex = (regionZ - centerZ) + halfWidth;
|
||||
isRegionDirty[xIndex][zIndex] = true;
|
||||
fileHandler.saveDirtyRegionsToFileAsync();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the LodChunk at the given X and Z coordinates
|
||||
* in this dimension.
|
||||
* <br>
|
||||
* Returns null if the LodChunk doesn't exist or
|
||||
* is outside the loaded area.
|
||||
*/
|
||||
public LodChunk getLodFromCoordinates(int chunkX, int chunkZ)
|
||||
{
|
||||
int regionX = chunkX / LodRegion.SIZE;
|
||||
int regionZ = chunkZ / LodRegion.SIZE;
|
||||
|
||||
// prevent issues if chunkX/Z is negative and less than width
|
||||
if (chunkX < 0)
|
||||
{
|
||||
regionX = (Math.abs(regionX) * -1) - 1;
|
||||
}
|
||||
if (chunkZ < 0)
|
||||
{
|
||||
regionZ = (Math.abs(regionZ) * -1) - 1;
|
||||
}
|
||||
|
||||
LodRegion region = getRegion(regionX, regionZ);
|
||||
|
||||
if(region == null)
|
||||
return null;
|
||||
|
||||
return region.getLod(chunkX, chunkZ);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get the region at the given X and Z coordinates from the
|
||||
* RegionFileHandler.
|
||||
*/
|
||||
public LodRegion getRegionFromFile(int regionX, int regionZ)
|
||||
{
|
||||
return fileHandler.loadRegionFromFile(regionX, regionZ);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns whether the region at the given X and Z coordinates
|
||||
* is within the loaded range.
|
||||
*/
|
||||
private boolean regionIsInRange(int regionX, int regionZ)
|
||||
{
|
||||
int xIndex = (regionX - centerX) + halfWidth;
|
||||
int zIndex = (regionZ - centerZ) + halfWidth;
|
||||
|
||||
return xIndex >= 0 && xIndex < width && zIndex >= 0 && zIndex < width;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
public int getCenterX()
|
||||
{
|
||||
return centerX;
|
||||
}
|
||||
|
||||
public int getCenterZ()
|
||||
{
|
||||
return centerZ;
|
||||
}
|
||||
|
||||
|
||||
|
||||
public int getWidth()
|
||||
{
|
||||
return width;
|
||||
}
|
||||
|
||||
public void setRegionWidth(int newWidth)
|
||||
{
|
||||
width = newWidth;
|
||||
halfWidth = (int)Math.floor(width / 2);
|
||||
|
||||
regions = new LodRegion[width][width];
|
||||
isRegionDirty = new boolean[width][width];
|
||||
|
||||
// populate isRegionDirty
|
||||
for(int i = 0; i < width; i++)
|
||||
for(int j = 0; j < width; j++)
|
||||
isRegionDirty[i][j] = false;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
String s = "";
|
||||
|
||||
s += "dim: " + dimension.toString() + "\t";
|
||||
s += "(" + centerX + "," + centerZ + ")";
|
||||
|
||||
return s;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,90 +0,0 @@
|
||||
package com.backsun.lod.objects;
|
||||
|
||||
/**
|
||||
* A LodRegion is the a 32x32
|
||||
* 2D array of LodChunk objects.
|
||||
* Each LodRegion corresponds to
|
||||
* one file in the file system.
|
||||
*
|
||||
* @author James Seibel
|
||||
* @version 1-22-2021
|
||||
*/
|
||||
public class LodRegion
|
||||
{
|
||||
/** number of chunks wide */
|
||||
public static final int SIZE = 32;
|
||||
|
||||
/** X coordinate of this region */
|
||||
public final int x;
|
||||
/** Z coordinate of this region */
|
||||
public final int z;
|
||||
|
||||
private LodChunk chunks[][];
|
||||
|
||||
|
||||
public LodRegion(int regionX, int regionZ)
|
||||
{
|
||||
x = regionX;
|
||||
z = regionZ;
|
||||
|
||||
chunks = new LodChunk[SIZE][SIZE];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Add the given LOD to this region at the coordinate
|
||||
* stored in the LOD. If an LOD already exists at the given
|
||||
* coordinates it will be overwritten.
|
||||
*/
|
||||
public void addLod(LodChunk lod)
|
||||
{
|
||||
// we use ABS since LODs can be negative, but if they are
|
||||
// the region will negative first, therefore we don't have to
|
||||
// store the LOD chunks at negative indexes since we search
|
||||
// LOD the region first
|
||||
int xIndex = Math.abs(lod.x % SIZE);
|
||||
int zIndex = Math.abs(lod.z % SIZE);
|
||||
|
||||
chunks[xIndex][zIndex] = lod;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the LodChunk at the given X and Z coordinates
|
||||
* in this region.
|
||||
* <br>
|
||||
* Returns null if the LodChunk doesn't exist or
|
||||
* is outside the loaded area.
|
||||
*/
|
||||
public LodChunk getLod(int chunkX, int chunkZ)
|
||||
{
|
||||
// since we add LOD's with ABS, we get them the same way
|
||||
int arrayX = Math.abs(chunkX % SIZE);
|
||||
int arrayZ = Math.abs(chunkZ % SIZE);
|
||||
|
||||
if(arrayX >= SIZE || arrayZ >= SIZE)
|
||||
return null;
|
||||
|
||||
return chunks[arrayX][arrayZ];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns all LodChunks in this region
|
||||
*/
|
||||
public LodChunk[][] getAllLods()
|
||||
{
|
||||
return chunks;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
String s = "";
|
||||
|
||||
s += "x: " + x + " z: " + z + "\t";
|
||||
|
||||
return s;
|
||||
}
|
||||
}
|
||||
@@ -1,65 +0,0 @@
|
||||
package com.backsun.lod.objects;
|
||||
|
||||
import java.util.Hashtable;
|
||||
import java.util.Map;
|
||||
|
||||
import net.minecraft.world.DimensionType;
|
||||
|
||||
/**
|
||||
* This stores all LODs for a given world.
|
||||
*
|
||||
* @author James Seibel
|
||||
* @version 02-22-2021
|
||||
*/
|
||||
public class LodWorld
|
||||
{
|
||||
public String worldName;
|
||||
|
||||
/**
|
||||
* Key = Dimension id (as an int)
|
||||
*/
|
||||
private Map<DimensionType, LodDimension> lodDimensions;
|
||||
|
||||
|
||||
public LodWorld(String newWorldName)
|
||||
{
|
||||
worldName = newWorldName;
|
||||
lodDimensions = new Hashtable<DimensionType, LodDimension>();
|
||||
}
|
||||
|
||||
|
||||
|
||||
public void addLodDimension(LodDimension newStorage)
|
||||
{
|
||||
lodDimensions.put(newStorage.dimension, newStorage);
|
||||
}
|
||||
|
||||
public LodDimension getLodDimension(DimensionType dimension)
|
||||
{
|
||||
return lodDimensions.get(dimension);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resizes the max width in regions that each LodDimension
|
||||
* should use.
|
||||
*/
|
||||
public void resizeDimensionRegionWidth(int newWidth)
|
||||
{
|
||||
for(DimensionType key : lodDimensions.keySet())
|
||||
lodDimensions.get(key).setRegionWidth(newWidth);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
String s = "";
|
||||
|
||||
s += worldName + "\t - dimensions: ";
|
||||
for(DimensionType key : lodDimensions.keySet())
|
||||
s += lodDimensions.get(key).dimension.toString() + ", ";
|
||||
|
||||
return s;
|
||||
}
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
package com.backsun.lod.objects;
|
||||
|
||||
import net.minecraft.client.renderer.BufferBuilder;
|
||||
|
||||
/**
|
||||
* This object is just a replacement for an array
|
||||
* to make things easier to understand in the LodRenderer
|
||||
* and BuildBufferThread.
|
||||
*
|
||||
* @author James Seibel
|
||||
* @version 02-21-2021
|
||||
*/
|
||||
public class NearFarBuffer
|
||||
{
|
||||
public BufferBuilder nearBuffer;
|
||||
|
||||
public BufferBuilder farBuffer;
|
||||
|
||||
|
||||
public NearFarBuffer(BufferBuilder newNearBuffer, BufferBuilder newFarBuffer)
|
||||
{
|
||||
nearBuffer = newNearBuffer;
|
||||
farBuffer = newFarBuffer;
|
||||
}
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
package com.backsun.lod.objects;
|
||||
|
||||
import com.backsun.lod.util.enums.FogDistance;
|
||||
|
||||
/**
|
||||
* This object is just a replacement for an array
|
||||
* to make things easier to understand in the LodRenderer.
|
||||
*
|
||||
* @author James Seibel
|
||||
* @version 02-27-2021
|
||||
*/
|
||||
public class NearFarFogSetting
|
||||
{
|
||||
public FogDistance nearFogSetting = FogDistance.NEAR;
|
||||
public FogDistance farFogSetting = FogDistance.FAR;
|
||||
|
||||
|
||||
public NearFarFogSetting()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public NearFarFogSetting(FogDistance newNearFogSetting, FogDistance newFarFogSetting)
|
||||
{
|
||||
nearFogSetting = newNearFogSetting;
|
||||
farFogSetting = newFarFogSetting;
|
||||
}
|
||||
}
|
||||
@@ -1,129 +0,0 @@
|
||||
package com.backsun.lod.proxy;
|
||||
|
||||
import org.lwjgl.opengl.GL11;
|
||||
|
||||
import com.backsun.lod.builders.LodBuilder;
|
||||
import com.backsun.lod.handlers.LodDimensionFileHandler;
|
||||
import com.backsun.lod.objects.LodChunk;
|
||||
import com.backsun.lod.objects.LodDimension;
|
||||
import com.backsun.lod.objects.LodRegion;
|
||||
import com.backsun.lod.objects.LodWorld;
|
||||
import com.backsun.lod.renderer.LodRenderer;
|
||||
import com.backsun.lod.renderer.RenderGlobalHook;
|
||||
import com.backsun.lod.util.LodConfig;
|
||||
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.world.chunk.Chunk;
|
||||
import net.minecraftforge.client.event.RenderWorldLastEvent;
|
||||
import net.minecraftforge.event.world.ChunkEvent;
|
||||
import net.minecraftforge.eventbus.api.SubscribeEvent;
|
||||
|
||||
//TODO Find a way to replace getIntegratedServer so this mod could be used on non-local worlds.
|
||||
// Minecraft.getMinecraft().getIntegratedServer()
|
||||
|
||||
/**
|
||||
* This handles all events sent to the client,
|
||||
* and is the starting point for most of this program.
|
||||
*
|
||||
* @author James_Seibel
|
||||
* @version 02-23-2021
|
||||
*/
|
||||
public class ClientProxy
|
||||
{
|
||||
private LodRenderer renderer;
|
||||
private LodWorld lodWorld;
|
||||
private LodBuilder lodBuilder;
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
|
||||
public ClientProxy()
|
||||
{
|
||||
lodBuilder = new LodBuilder();
|
||||
renderer = new LodRenderer();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
//==============//
|
||||
// render event //
|
||||
//==============//
|
||||
|
||||
@SubscribeEvent
|
||||
public void renderWorldLast(RenderWorldLastEvent event)
|
||||
{
|
||||
RenderGlobalHook.endRenderingStencil();
|
||||
GL11.glStencilFunc(GL11.GL_EQUAL, 0, 0xFF);
|
||||
|
||||
if (LodConfig.CLIENT.drawLODs.get())
|
||||
renderLods(event.getPartialTicks());
|
||||
|
||||
GL11.glDisable(GL11.GL_STENCIL_TEST);
|
||||
}
|
||||
|
||||
/**
|
||||
* Do any setup that is required to draw LODs
|
||||
* and then tell the LodRenderer to draw.
|
||||
*/
|
||||
public void renderLods(float partialTicks)
|
||||
{
|
||||
// update the
|
||||
int newWidth = Math.max(4, (mc.gameSettings.renderDistanceChunks * LodChunk.WIDTH * 2) / LodRegion.SIZE);
|
||||
if (lodWorld != null && lodBuilder.regionWidth != newWidth)
|
||||
{
|
||||
lodWorld.resizeDimensionRegionWidth(newWidth);
|
||||
lodBuilder.regionWidth = newWidth;
|
||||
|
||||
// skip this frame, hopefully the lodWorld
|
||||
// should have everything set up by then
|
||||
return;
|
||||
}
|
||||
|
||||
// are we still in the same world?
|
||||
if (!lodWorld.worldName.equals(LodDimensionFileHandler.getCurrentWorldID()))
|
||||
// no, don't render the wrong world
|
||||
return;
|
||||
|
||||
|
||||
if (mc == null || mc.player == null || lodWorld == null)
|
||||
return;
|
||||
|
||||
LodDimension lodDim = lodWorld.getLodDimension(mc.player.world.getDimensionType());
|
||||
if (lodDim == null)
|
||||
return;
|
||||
|
||||
|
||||
|
||||
// offset the regions
|
||||
double playerX = mc.player.getPosX();
|
||||
double playerZ = mc.player.getPosZ();
|
||||
|
||||
int xOffset = ((int)playerX / (LodChunk.WIDTH * LodRegion.SIZE)) - lodDim.getCenterX();
|
||||
int zOffset = ((int)playerZ / (LodChunk.WIDTH * LodRegion.SIZE)) - lodDim.getCenterZ();
|
||||
|
||||
if (xOffset != 0 || zOffset != 0)
|
||||
{
|
||||
lodDim.move(xOffset, zOffset);
|
||||
}
|
||||
|
||||
|
||||
|
||||
renderer.drawLODs(lodDim, partialTicks, mc.getProfiler());
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
//=====================//
|
||||
// lod creation events //
|
||||
//=====================//
|
||||
|
||||
@SubscribeEvent
|
||||
public void chunkLoadEvent(ChunkEvent.Load event)
|
||||
{
|
||||
if (event.getChunk().getClass() == Chunk.class)
|
||||
lodWorld = lodBuilder.generateLodChunkAsync((Chunk) event.getChunk());
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -1,810 +0,0 @@
|
||||
package com.backsun.lod.renderer;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
import java.nio.FloatBuffer;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
import org.lwjgl.opengl.GL11;
|
||||
|
||||
import com.backsun.lod.builders.LodBufferBuilder;
|
||||
import com.backsun.lod.handlers.ReflectionHandler;
|
||||
import com.backsun.lod.objects.LodChunk;
|
||||
import com.backsun.lod.objects.LodDimension;
|
||||
import com.backsun.lod.objects.NearFarBuffer;
|
||||
import com.backsun.lod.objects.NearFarFogSetting;
|
||||
import com.backsun.lod.util.LodConfig;
|
||||
import com.backsun.lod.util.enums.ColorDirection;
|
||||
import com.backsun.lod.util.enums.FogDistance;
|
||||
import com.backsun.lod.util.enums.FogQuality;
|
||||
import com.backsun.lod.util.enums.LodCorner;
|
||||
import com.mojang.blaze3d.matrix.MatrixStack;
|
||||
import com.mojang.blaze3d.platform.GlStateManager;
|
||||
import com.mojang.blaze3d.systems.RenderSystem;
|
||||
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.entity.player.ClientPlayerEntity;
|
||||
import net.minecraft.client.renderer.ActiveRenderInfo;
|
||||
import net.minecraft.client.renderer.BufferBuilder;
|
||||
import net.minecraft.client.renderer.FogRenderer;
|
||||
import net.minecraft.client.renderer.GameRenderer;
|
||||
import net.minecraft.client.renderer.vertex.DefaultVertexFormats;
|
||||
import net.minecraft.client.renderer.vertex.VertexBuffer;
|
||||
import net.minecraft.client.renderer.vertex.VertexFormat;
|
||||
import net.minecraft.potion.Effects;
|
||||
import net.minecraft.profiler.IProfiler;
|
||||
import net.minecraft.util.math.AxisAlignedBB;
|
||||
import net.minecraft.util.math.MathHelper;
|
||||
import net.minecraft.util.math.vector.Matrix4f;
|
||||
import net.minecraft.util.math.vector.Vector3d;
|
||||
import net.minecraft.util.math.vector.Vector3f;
|
||||
|
||||
|
||||
/**
|
||||
* This is where all the magic happens.
|
||||
* This is where LODs are draw to the world.
|
||||
*
|
||||
* @author James Seibel
|
||||
* @version 2-27-2021
|
||||
*/
|
||||
public class LodRenderer
|
||||
{
|
||||
/** this is the light used when rendering the LODs,
|
||||
* it should be something different than what is used by Minecraft */
|
||||
private static final int LOD_GL_LIGHT_NUMBER = GL11.GL_LIGHT2;
|
||||
|
||||
/** If true the LODs colors will be replaced with
|
||||
* a checkerboard, this can be used for debugging. */
|
||||
public boolean debugging = false;
|
||||
|
||||
private Minecraft mc;
|
||||
private GameRenderer gameRender;
|
||||
private IProfiler profiler;
|
||||
private float farPlaneDistance;
|
||||
/** this is the radius of the LODs */
|
||||
private static final int LOD_CHUNK_DISTANCE_RADIUS = 6;
|
||||
|
||||
private ReflectionHandler reflectionHandler;
|
||||
|
||||
public LodDimension lodDimension = null;
|
||||
|
||||
|
||||
/** This is used to generate the buildable buffers */
|
||||
private LodBufferBuilder lodBufferBuilder = null;
|
||||
|
||||
/** The buffers that are used to draw LODs using near fog */
|
||||
private volatile BufferBuilder drawableNearBuffer = null;
|
||||
/** The buffers that are used to draw LODs using far fog */
|
||||
private volatile BufferBuilder drawableFarBuffer = null;
|
||||
|
||||
/** The buffers that are used to create LODs using near fog */
|
||||
private volatile BufferBuilder buildableNearBuffer = null;
|
||||
/** The buffers that are used to create LODs using far fog */
|
||||
private volatile BufferBuilder buildableFarBuffer = null;
|
||||
|
||||
/** This is the VertexBuffer used to draw any LODs that use near fog */
|
||||
private volatile VertexBuffer nearVbo = null;
|
||||
/** This is the VertexBuffer used to draw any LODs that use far fog */
|
||||
private volatile VertexBuffer farVbo = null;
|
||||
public static final VertexFormat LOD_VERTEX_FORMAT = DefaultVertexFormats.POSITION_COLOR;
|
||||
|
||||
/** This holds the thread used to generate new LODs off the main thread. */
|
||||
private ExecutorService genThread = Executors.newSingleThreadExecutor();
|
||||
|
||||
/** This is used to determine if the LODs should be regenerated */
|
||||
private int previousChunkRenderDistance = 0;
|
||||
/** This is used to determine if the LODs should be regenerated */
|
||||
private int prevChunkX = 0;
|
||||
/** This is used to determine if the LODs should be regenerated */
|
||||
private int prevChunkZ = 0;
|
||||
/** This is used to determine if the LODs should be regenerated */
|
||||
private FogDistance prevFogDistance = FogDistance.NEAR_AND_FAR;
|
||||
|
||||
/** if this is true the LOD buffers should be regenerated,
|
||||
* provided they aren't already being regenerated. */
|
||||
private boolean regen = false;
|
||||
/** if this is true the LOD buffers are currently being
|
||||
* regenerated. */
|
||||
private volatile boolean regenerating = false;
|
||||
/** if this is true new LOD buffers have been generated
|
||||
* and are waiting to be swapped with the drawable buffers*/
|
||||
private volatile boolean switchBuffers = false;
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
public LodRenderer()
|
||||
{
|
||||
mc = Minecraft.getInstance();
|
||||
gameRender = mc.gameRenderer;
|
||||
|
||||
reflectionHandler = new ReflectionHandler();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Besides drawing the LODs this method also starts
|
||||
* the async process of generating the Buffers that hold those LODs.
|
||||
*
|
||||
* @param newDimension The dimension to draw, if null doesn't replace the current dimension.
|
||||
* @param partialTicks how far into the current tick this method was called.
|
||||
*/
|
||||
public void drawLODs(LodDimension newDimension, float partialTicks, IProfiler newProfiler)
|
||||
{
|
||||
if (lodDimension == null && newDimension == null)
|
||||
{
|
||||
// if there aren't any loaded LodChunks
|
||||
// don't try drawing anything
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
//===============//
|
||||
// initial setup //
|
||||
//===============//
|
||||
|
||||
|
||||
// used for debugging and viewing how long different processes take
|
||||
profiler = newProfiler;
|
||||
profiler.endSection();
|
||||
profiler.startSection("LOD");
|
||||
profiler.startSection("LOD setup");
|
||||
|
||||
ClientPlayerEntity player = mc.player;
|
||||
|
||||
// should LODs be regenerated?
|
||||
if ((int)player.getPosX() / LodChunk.WIDTH != prevChunkX ||
|
||||
(int)player.getPosZ() / LodChunk.WIDTH != prevChunkZ ||
|
||||
previousChunkRenderDistance != mc.gameSettings.renderDistanceChunks ||
|
||||
prevFogDistance != LodConfig.CLIENT.fogDistance.get() ||
|
||||
lodDimension != newDimension)
|
||||
{
|
||||
// yes
|
||||
regen = true;
|
||||
|
||||
prevChunkX = (int)player.getPosX() / LodChunk.WIDTH;
|
||||
prevChunkZ = (int)player.getPosZ() / LodChunk.WIDTH;
|
||||
prevFogDistance = LodConfig.CLIENT.fogDistance.get();
|
||||
}
|
||||
else
|
||||
{
|
||||
// nope, the player hasn't moved, the
|
||||
// render distance hasn't changed, and
|
||||
// the dimension is the same
|
||||
}
|
||||
|
||||
lodDimension = newDimension;
|
||||
if (lodDimension == null)
|
||||
{
|
||||
// if there aren't any loaded LodChunks
|
||||
// don't try drawing anything
|
||||
return;
|
||||
}
|
||||
|
||||
if (LodConfig.CLIENT.drawCheckerBoard.get())
|
||||
{
|
||||
if (debugging != LodConfig.CLIENT.drawCheckerBoard.get())
|
||||
regen = true;
|
||||
debugging = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (debugging != LodConfig.CLIENT.drawCheckerBoard.get())
|
||||
regen = true;
|
||||
debugging = false;
|
||||
}
|
||||
|
||||
|
||||
|
||||
// determine how far the game's render distance is currently set
|
||||
int renderDistWidth = mc.gameSettings.renderDistanceChunks;
|
||||
farPlaneDistance = renderDistWidth * LodChunk.WIDTH;
|
||||
|
||||
// set how big the LODs will be and how far they will go
|
||||
int totalLength = (int) farPlaneDistance * LOD_CHUNK_DISTANCE_RADIUS * 2;
|
||||
int numbChunksWide = (totalLength / LodChunk.WIDTH);
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
//=================//
|
||||
// create the LODs //
|
||||
//=================//
|
||||
|
||||
// only regenerate the LODs if:
|
||||
// 1. we want to regenerate LODs
|
||||
// 2. we aren't already regenerating the LODs
|
||||
// 3. we aren't waiting for the build and draw buffers to swap
|
||||
// (this is to prevent thread conflicts)
|
||||
if (regen && !regenerating && !switchBuffers)
|
||||
{
|
||||
regenerating = true;
|
||||
|
||||
if (lodBufferBuilder == null)
|
||||
lodBufferBuilder = new LodBufferBuilder();
|
||||
|
||||
// this will mainly happen when the view distance is changed
|
||||
if (drawableNearBuffer == null || drawableFarBuffer == null ||
|
||||
previousChunkRenderDistance != mc.gameSettings.renderDistanceChunks)
|
||||
setupBuffers(numbChunksWide);
|
||||
|
||||
// generate the LODs on a separate thread to prevent stuttering or freezing
|
||||
generateLodBuffersAsync(player.getPosX(), player.getPosZ(), numbChunksWide);
|
||||
|
||||
// the regen process has been started
|
||||
regen = false;
|
||||
}
|
||||
|
||||
// replace the buffers used to draw and build,
|
||||
// this is only done when the createLodBufferGenerationThread
|
||||
// has finished executing on a parallel thread.
|
||||
if (switchBuffers)
|
||||
{
|
||||
swapBuffers();
|
||||
switchBuffers = false;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
//===========================//
|
||||
// GL settings for rendering //
|
||||
//===========================//
|
||||
|
||||
// set the required open GL settings
|
||||
GL11.glPolygonMode(GL11.GL_FRONT_AND_BACK, GL11.GL_FILL);
|
||||
GL11.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA);
|
||||
GL11.glDisable(GL11.GL_TEXTURE_2D);
|
||||
GL11.glEnable(GL11.GL_CULL_FACE);
|
||||
GL11.glEnable(GL11.GL_COLOR_MATERIAL);
|
||||
GL11.glEnable(GL11.GL_DEPTH_TEST);
|
||||
|
||||
Matrix4f modelViewMatrix = generateModelViewMatrix(partialTicks);
|
||||
|
||||
setupProjectionMatrix(partialTicks);
|
||||
setupLighting(partialTicks);
|
||||
|
||||
NearFarFogSetting fogSetting = determineFogSettings();
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
//===========//
|
||||
// rendering //
|
||||
//===========//
|
||||
|
||||
setupFog(fogSetting.nearFogSetting, reflectionHandler.getFogQuality());
|
||||
sendLodsToGpuAndDraw(nearVbo, modelViewMatrix);
|
||||
|
||||
setupFog(fogSetting.farFogSetting, reflectionHandler.getFogQuality());
|
||||
sendLodsToGpuAndDraw(farVbo, modelViewMatrix);
|
||||
|
||||
|
||||
|
||||
//=========//
|
||||
// cleanup //
|
||||
//=========//
|
||||
|
||||
profiler.endStartSection("LOD cleanup");
|
||||
|
||||
// this must be done otherwise other parts of the screen may be drawn with a fog effect
|
||||
// IE the GUI
|
||||
FogRenderer.resetFog();
|
||||
RenderSystem.disableFog();
|
||||
|
||||
GL11.glPolygonMode(GL11.GL_FRONT_AND_BACK, GL11.GL_FILL);
|
||||
GL11.glEnable(GL11.GL_TEXTURE_2D);
|
||||
GL11.glDisable(LOD_GL_LIGHT_NUMBER);
|
||||
GL11.glDisable(GL11.GL_COLOR_MATERIAL);
|
||||
|
||||
// this can't be called until after the buffers are built
|
||||
// because otherwise the buffers may be set to the wrong size
|
||||
previousChunkRenderDistance = mc.gameSettings.renderDistanceChunks;
|
||||
|
||||
|
||||
// end of profiler tracking
|
||||
profiler.endSection();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Create the model view matrix to move the LODs
|
||||
* from object space into world space.
|
||||
*/
|
||||
private Matrix4f generateModelViewMatrix(float partialTicks)
|
||||
{
|
||||
// get all relevant camera info
|
||||
ActiveRenderInfo renderInfo = mc.gameRenderer.getActiveRenderInfo();
|
||||
Vector3d projectedView = renderInfo.getProjectedView();
|
||||
double cameraX = projectedView.x;
|
||||
double cameraY = projectedView.y;
|
||||
double cameraZ = projectedView.z;
|
||||
|
||||
|
||||
// generate the model view matrix
|
||||
MatrixStack matrixStack = new MatrixStack();
|
||||
matrixStack.push();
|
||||
// translate and rotate to the current camera location
|
||||
matrixStack.rotate(Vector3f.XP.rotationDegrees(renderInfo.getPitch()));
|
||||
matrixStack.rotate(Vector3f.YP.rotationDegrees(renderInfo.getYaw() + 180));
|
||||
matrixStack.translate(-cameraX, -cameraY, -cameraZ);
|
||||
|
||||
|
||||
// this isn't a great solution to nausea or portal spinning
|
||||
// but i'm not sure what to do otherwise
|
||||
float f = 0.23f * MathHelper.lerp(partialTicks, this.mc.player.prevTimeInPortal, this.mc.player.timeInPortal) * this.mc.gameSettings.screenEffectScale * this.mc.gameSettings.screenEffectScale;
|
||||
if (f > 0.0F) {
|
||||
int i = this.mc.player.isPotionActive(Effects.NAUSEA) ? 7 : 20;
|
||||
float f1 = 5.0F / (f * f + 5.0F) - f * 0.04F;
|
||||
f1 = f1 * f1;
|
||||
Vector3f vector3f = new Vector3f(0.0F, MathHelper.SQRT_2 / 2.0F, MathHelper.SQRT_2 / 2.0F);
|
||||
matrixStack.rotate(vector3f.rotationDegrees((mc.gameRenderer.rendererUpdateCount + partialTicks) * i));
|
||||
matrixStack.scale(1.01F / f1, 1.0F, 1.0F);
|
||||
float f2 = -(mc.gameRenderer.rendererUpdateCount + partialTicks) * i;
|
||||
matrixStack.rotate(vector3f.rotationDegrees(f2));
|
||||
}
|
||||
|
||||
|
||||
return matrixStack.getLast().getMatrix();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* This is where the actual drawing happens.
|
||||
*
|
||||
* @param buffers the buffers sent to the GPU to draw
|
||||
*/
|
||||
private void sendLodsToGpuAndDraw(VertexBuffer vbo, Matrix4f modelViewMatrix)
|
||||
{
|
||||
if (vbo == null)
|
||||
return;
|
||||
|
||||
profiler.endStartSection("LOD draw setup");
|
||||
vbo.bindBuffer();
|
||||
// 0L is the starting pointer, and the value doesn't appear to matter
|
||||
LOD_VERTEX_FORMAT.setupBufferState(0L);
|
||||
|
||||
profiler.endStartSection("LOD draw");
|
||||
vbo.draw(modelViewMatrix, GL11.GL_QUADS);
|
||||
|
||||
profiler.endStartSection("LOD draw cleanup");
|
||||
VertexBuffer.unbindBuffer();
|
||||
LOD_VERTEX_FORMAT.clearBufferState();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
//=================//
|
||||
// Setup Functions //
|
||||
//=================//
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
private void setupFog(FogDistance fogDistance, FogQuality fogQuality)
|
||||
{
|
||||
if(fogQuality == FogQuality.OFF)
|
||||
{
|
||||
FogRenderer.resetFog();
|
||||
RenderSystem.disableFog();
|
||||
return;
|
||||
}
|
||||
|
||||
if(fogDistance == FogDistance.NEAR_AND_FAR)
|
||||
{
|
||||
throw new IllegalArgumentException("setupFog doesn't accept the NEAR_AND_FAR fog distance.");
|
||||
}
|
||||
|
||||
// the multipliers are percentages
|
||||
// of the regular view distance.
|
||||
|
||||
if(fogDistance == FogDistance.NEAR)
|
||||
{
|
||||
// the reason that I wrote fogEnd then fogStart backwards
|
||||
// is because we are using fog backwards to how
|
||||
// it is normally used, with it hiding near objects
|
||||
// instead of far objects.
|
||||
|
||||
if (fogQuality == FogQuality.FANCY)
|
||||
{
|
||||
RenderSystem.fogEnd(farPlaneDistance * 0.3f * LOD_CHUNK_DISTANCE_RADIUS);
|
||||
RenderSystem.fogStart(farPlaneDistance * 0.35f * LOD_CHUNK_DISTANCE_RADIUS);
|
||||
}
|
||||
else if(fogQuality == FogQuality.FAST)
|
||||
{
|
||||
// for the far fog of the normal chunks
|
||||
// to start right where the LODs' end use:
|
||||
// end = 0.8f, start = 1.5f
|
||||
|
||||
RenderSystem.fogEnd(farPlaneDistance * 1.5f);
|
||||
RenderSystem.fogStart(farPlaneDistance * 2.0f);
|
||||
}
|
||||
}
|
||||
else if(fogDistance == FogDistance.FAR)
|
||||
{
|
||||
if (fogQuality == FogQuality.FANCY)
|
||||
{
|
||||
RenderSystem.fogStart(farPlaneDistance * 0.78f * LOD_CHUNK_DISTANCE_RADIUS);
|
||||
RenderSystem.fogEnd(farPlaneDistance * 1.0f * LOD_CHUNK_DISTANCE_RADIUS);
|
||||
}
|
||||
else if(fogQuality == FogQuality.FAST)
|
||||
{
|
||||
RenderSystem.fogStart(farPlaneDistance * 0.5f * LOD_CHUNK_DISTANCE_RADIUS);
|
||||
RenderSystem.fogEnd(farPlaneDistance * 0.75f * LOD_CHUNK_DISTANCE_RADIUS);
|
||||
}
|
||||
}
|
||||
|
||||
RenderSystem.fogMode(GlStateManager.FogMode.LINEAR);
|
||||
RenderSystem.enableFog();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* create a new projection matrix and send it over to the GPU
|
||||
* @param partialTicks how many ticks into the frame we are
|
||||
*/
|
||||
private void setupProjectionMatrix(float partialTicks)
|
||||
{
|
||||
Matrix4f projectionMatrix =
|
||||
Matrix4f.perspective(
|
||||
getFov(partialTicks, true),
|
||||
(float)this.mc.getMainWindow().getFramebufferWidth() / (float)this.mc.getMainWindow().getFramebufferHeight(),
|
||||
0.5F,
|
||||
this.farPlaneDistance * LOD_CHUNK_DISTANCE_RADIUS * 2);
|
||||
|
||||
gameRender.resetProjectionMatrix(projectionMatrix);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* setup the lighting to be used for the LODs
|
||||
*/
|
||||
private void setupLighting(float partialTicks)
|
||||
{
|
||||
float sunBrightness = lodDimension.dimension.hasSkyLight() ? mc.world.getSunBrightness(partialTicks) : 0.2f;
|
||||
float gammaMultiplyer = (float)mc.gameSettings.gamma - 0.5f;
|
||||
float lightStrength = sunBrightness - 0.7f + (gammaMultiplyer * 0.2f);
|
||||
|
||||
float lightAmbient[] = {lightStrength, lightStrength, lightStrength, 1.0f};
|
||||
|
||||
ByteBuffer temp = ByteBuffer.allocateDirect(16);
|
||||
temp.order(ByteOrder.nativeOrder());
|
||||
GL11.glLightfv(LOD_GL_LIGHT_NUMBER, GL11.GL_AMBIENT, (FloatBuffer) temp.asFloatBuffer().put(lightAmbient).flip());
|
||||
GL11.glEnable(LOD_GL_LIGHT_NUMBER); // Enable the above lighting
|
||||
|
||||
RenderSystem.enableLighting();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create all buffers that will be used.
|
||||
*/
|
||||
private void setupBuffers(int numbChunksWide)
|
||||
{
|
||||
// calculate the max amount of storage needed (in bytes)
|
||||
int bufferMaxCapacity = (numbChunksWide * numbChunksWide * (6 * 4 * (3 + 4)));
|
||||
// (numbChunksWide * numbChunksWide *
|
||||
// (sidesOnACube * pointsInASquare * (positionPoints + colorPoints)))
|
||||
|
||||
// TODO complain or do something when memory is too low
|
||||
// currently the VM will just crash and complain there is no more memory
|
||||
// issue #4
|
||||
drawableNearBuffer = new BufferBuilder(bufferMaxCapacity);
|
||||
drawableFarBuffer = new BufferBuilder(bufferMaxCapacity);
|
||||
|
||||
buildableNearBuffer = new BufferBuilder(bufferMaxCapacity);
|
||||
buildableFarBuffer = new BufferBuilder(bufferMaxCapacity);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
//======================//
|
||||
// Other Misc Functions //
|
||||
//======================//
|
||||
|
||||
|
||||
/**
|
||||
* @Returns -1 if there are no valid points
|
||||
*/
|
||||
private int getValidHeightPoint(short[] heightPoints)
|
||||
{
|
||||
if (heightPoints[LodCorner.NE.value] != -1)
|
||||
return heightPoints[LodCorner.NE.value];
|
||||
if (heightPoints[LodCorner.NW.value] != -1)
|
||||
return heightPoints[LodCorner.NW.value];
|
||||
if (heightPoints[LodCorner.SE.value] != -1)
|
||||
return heightPoints[LodCorner.NE.value];
|
||||
return heightPoints[LodCorner.NE.value];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Create a thread to asynchronously generate LOD buffers
|
||||
* centered around the given camera X and Z.
|
||||
* <br>
|
||||
* This thread will write to the drawableNearBuffers and drawableFarBuffers.
|
||||
* <br>
|
||||
* After the buildable buffers have been generated they must be
|
||||
* swapped with the drawable buffers to be drawn.
|
||||
*/
|
||||
private void generateLodBuffersAsync(double playerX, double playerZ,
|
||||
int numbChunksWide)
|
||||
{
|
||||
// this is where we store the points for each LOD object
|
||||
AxisAlignedBB lodArray[][] = new AxisAlignedBB[numbChunksWide][numbChunksWide];
|
||||
// this is where we store the color for each LOD object
|
||||
Color colorArray[][] = new Color[numbChunksWide][numbChunksWide];
|
||||
|
||||
int alpha = 255; // 0 - 255
|
||||
Color red = new Color(255, 0, 0, alpha);
|
||||
Color black = new Color(0, 0, 0, alpha);
|
||||
Color white = new Color(255, 255, 255, alpha);
|
||||
@SuppressWarnings("unused")
|
||||
Color invisible = new Color(0,0,0,0);
|
||||
@SuppressWarnings("unused")
|
||||
Color error = new Color(255, 0, 225, alpha); // bright pink
|
||||
|
||||
// this seemingly useless math is required,
|
||||
// just using (int) camera doesn't work
|
||||
int playerXChunkOffset = ((int) playerX / LodChunk.WIDTH) * LodChunk.WIDTH;
|
||||
int playerZChunkOffset = ((int) playerZ / LodChunk.WIDTH) * LodChunk.WIDTH;
|
||||
// this is where we will start drawing squares
|
||||
// (exactly half the total width)
|
||||
int startX = (-LodChunk.WIDTH * (numbChunksWide / 2)) + playerXChunkOffset;
|
||||
int startZ = (-LodChunk.WIDTH * (numbChunksWide / 2)) + playerZChunkOffset;
|
||||
|
||||
Thread t = new Thread(()->
|
||||
{
|
||||
// x axis
|
||||
for (int i = 0; i < numbChunksWide; i++)
|
||||
{
|
||||
// z axis
|
||||
for (int j = 0; j < numbChunksWide; j++)
|
||||
{
|
||||
// skip the middle
|
||||
// (As the player moves some chunks will overlap or be missing,
|
||||
// this is just how chunk loading/unloading works. This can hopefully
|
||||
// be hidden with careful use of fog)
|
||||
int middle = (numbChunksWide / 2);
|
||||
if (isCoordInCenterArea(i, j, middle))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
// set where this square will be drawn in the world
|
||||
double xOffset = (LodChunk.WIDTH * i) + // offset by the number of LOD blocks
|
||||
startX; // offset so the center LOD block is centered underneath the player
|
||||
double yOffset = 0;
|
||||
double zOffset = (LodChunk.WIDTH * j) + startZ;
|
||||
|
||||
int chunkX = i + (startX / LodChunk.WIDTH);
|
||||
int chunkZ = j + (startZ / LodChunk.WIDTH);
|
||||
|
||||
LodChunk lod = lodDimension.getLodFromCoordinates(chunkX, chunkZ);
|
||||
if (lod == null)
|
||||
{
|
||||
// note: for some reason if any color or lod objects are set here
|
||||
// it causes the game to use 100% gpu;
|
||||
// undefined in the debug menu
|
||||
// and drop to ~6 fps.
|
||||
colorArray[i][j] = null;
|
||||
lodArray[i][j] = null;
|
||||
|
||||
// This is something I would like to have done someday
|
||||
// but currently world generation is too slow to have it
|
||||
// here, maybe it could be put in a loop
|
||||
// that happens every tick for a specific number of chunks?
|
||||
// LodChunk tmpLod = new LodChunk();
|
||||
// tmpLod.x = chunkX;
|
||||
// tmpLod.z = chunkZ;
|
||||
// lodDimension.addLod(tmpLod);
|
||||
//
|
||||
// world.getChunkProvider().getChunk(chunkX, chunkZ, true);
|
||||
// System.out.println(chunkX + "," + chunkZ);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
Color c = new Color(
|
||||
(lod.colors[ColorDirection.TOP.value].getRed()),
|
||||
(lod.colors[ColorDirection.TOP.value].getGreen()),
|
||||
(lod.colors[ColorDirection.TOP.value].getBlue()),
|
||||
lod.colors[ColorDirection.TOP.value].getAlpha());
|
||||
|
||||
if (!debugging)
|
||||
{
|
||||
// add the color to the array
|
||||
colorArray[i][j] = c;
|
||||
}
|
||||
else
|
||||
{
|
||||
// if debugging draw the squares as a black and white checker board
|
||||
if ((chunkX + chunkZ) % 2 == 0)
|
||||
c = white;
|
||||
else
|
||||
c = black;
|
||||
// draw the first square as red
|
||||
if (i == 0 && j == 0)
|
||||
c = red;
|
||||
|
||||
colorArray[i][j] = c;
|
||||
}
|
||||
|
||||
|
||||
// add the new box to the array
|
||||
int topPoint = getValidHeightPoint(lod.top);
|
||||
int bottomPoint = getValidHeightPoint(lod.bottom);
|
||||
|
||||
// don't draw an LOD if it is empty
|
||||
if (topPoint == -1 && bottomPoint == -1)
|
||||
continue;
|
||||
|
||||
lodArray[i][j] = new AxisAlignedBB(0, bottomPoint, 0, LodChunk.WIDTH, topPoint, LodChunk.WIDTH).offset(xOffset, yOffset, zOffset);
|
||||
}
|
||||
}
|
||||
|
||||
// generate our new buildable buffers
|
||||
NearFarBuffer nearFarBuffers = lodBufferBuilder.createBuffers(
|
||||
buildableNearBuffer, buildableFarBuffer,
|
||||
LodConfig.CLIENT.fogDistance.get(), lodArray, colorArray);
|
||||
|
||||
// update our buffers
|
||||
buildableNearBuffer = nearFarBuffers.nearBuffer;
|
||||
buildableFarBuffer = nearFarBuffers.farBuffer;
|
||||
|
||||
// mark the buildable buffers as ready to swap
|
||||
regenerating = false;
|
||||
switchBuffers = true;
|
||||
});
|
||||
|
||||
genThread.execute(t);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Swap buildable and drawable buffers.
|
||||
*/
|
||||
private void swapBuffers()
|
||||
{
|
||||
// swap the BufferBuilders
|
||||
BufferBuilder tmp = buildableNearBuffer;
|
||||
buildableNearBuffer = drawableNearBuffer;
|
||||
drawableNearBuffer = tmp;
|
||||
|
||||
tmp = buildableFarBuffer;
|
||||
buildableFarBuffer = drawableFarBuffer;
|
||||
drawableFarBuffer = tmp;
|
||||
|
||||
|
||||
// bind the buffers with their respective VBOs
|
||||
if (nearVbo != null)
|
||||
nearVbo.close();
|
||||
|
||||
nearVbo = new VertexBuffer(LOD_VERTEX_FORMAT);
|
||||
nearVbo.upload(drawableNearBuffer);
|
||||
|
||||
|
||||
if (farVbo != null)
|
||||
farVbo.close();
|
||||
|
||||
farVbo = new VertexBuffer(LOD_VERTEX_FORMAT);
|
||||
farVbo.upload(drawableFarBuffer);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns if the given coordinate is in the loaded area of the world.
|
||||
* @param centerCoordinate the center of the loaded world
|
||||
*/
|
||||
private boolean isCoordInCenterArea(int i, int j, int centerCoordinate)
|
||||
{
|
||||
return (i >= centerCoordinate - mc.gameSettings.renderDistanceChunks
|
||||
&& i <= centerCoordinate + mc.gameSettings.renderDistanceChunks)
|
||||
&&
|
||||
(j >= centerCoordinate - mc.gameSettings.renderDistanceChunks
|
||||
&& j <= centerCoordinate + mc.gameSettings.renderDistanceChunks);
|
||||
}
|
||||
|
||||
|
||||
public double getFov(float partialTicks, boolean useFovSetting)
|
||||
{
|
||||
return mc.gameRenderer.getFOVModifier(mc.gameRenderer.getActiveRenderInfo(), partialTicks, useFovSetting);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Based on the fogDistance setting and
|
||||
* optifine's fogQuality setting return what fog
|
||||
* settings should be used when rendering.
|
||||
*/
|
||||
private NearFarFogSetting determineFogSettings()
|
||||
{
|
||||
NearFarFogSetting fogSetting = new NearFarFogSetting();
|
||||
|
||||
LodConfig.CLIENT.fogDistance.get();
|
||||
switch(reflectionHandler.getFogQuality())
|
||||
{
|
||||
case FANCY:
|
||||
|
||||
switch(LodConfig.CLIENT.fogDistance.get())
|
||||
{
|
||||
case NEAR_AND_FAR:
|
||||
fogSetting.nearFogSetting = FogDistance.NEAR;
|
||||
fogSetting.farFogSetting = FogDistance.FAR;
|
||||
break;
|
||||
|
||||
case NEAR:
|
||||
fogSetting.nearFogSetting = FogDistance.NEAR;
|
||||
fogSetting.farFogSetting = FogDistance.NEAR;
|
||||
break;
|
||||
|
||||
case FAR:
|
||||
fogSetting.nearFogSetting = FogDistance.FAR;
|
||||
fogSetting.farFogSetting = FogDistance.FAR;
|
||||
break;
|
||||
}
|
||||
|
||||
break;
|
||||
case FAST:
|
||||
// fast fog setting should only have one type of
|
||||
// fog, since the LODs are separated into a near
|
||||
// and far portion; and fast fog is rendered from the
|
||||
// frustrum's perspective instead of the camera
|
||||
|
||||
switch(LodConfig.CLIENT.fogDistance.get())
|
||||
{
|
||||
case NEAR_AND_FAR:
|
||||
fogSetting.nearFogSetting = FogDistance.NEAR;
|
||||
fogSetting.farFogSetting = FogDistance.NEAR;
|
||||
break;
|
||||
|
||||
case NEAR:
|
||||
fogSetting.nearFogSetting = FogDistance.NEAR;
|
||||
fogSetting.farFogSetting = FogDistance.NEAR;
|
||||
break;
|
||||
|
||||
case FAR:
|
||||
fogSetting.nearFogSetting = FogDistance.FAR;
|
||||
fogSetting.farFogSetting = FogDistance.FAR;
|
||||
break;
|
||||
}
|
||||
|
||||
break;
|
||||
case OFF:
|
||||
break;
|
||||
|
||||
}
|
||||
|
||||
return fogSetting;
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -1,82 +0,0 @@
|
||||
package com.backsun.lod.renderer;
|
||||
|
||||
|
||||
import org.lwjgl.opengl.GL11;
|
||||
|
||||
import net.minecraft.client.renderer.RenderType;
|
||||
|
||||
/**
|
||||
* This code is used for drawing
|
||||
* to the stencil buffer.
|
||||
*
|
||||
* @author James Seibel
|
||||
* @version 02-17-2021
|
||||
*/
|
||||
public class RenderGlobalHook
|
||||
{
|
||||
/**
|
||||
* this variable should be the same as the method name below.
|
||||
* It is used when transforming the RenderGlobal class'
|
||||
* renderBlockLayer method.
|
||||
*/
|
||||
public static final String START_STENCIL_METHOD_NAME = "startRenderingStencil";
|
||||
|
||||
/**
|
||||
* This method tells OpenGL to start drawing everything to the stencil.
|
||||
* This is done to prevent LODs from being rendered on top of the world.
|
||||
* <br><br>
|
||||
* Called in the order (as of minecraft 1.16.4): <br>
|
||||
* RenderType.getSolid() <br>
|
||||
* RenderType.getCutoutMipped() <br>
|
||||
* RenderType.getCutout() <br>
|
||||
* RenderType.getTranslucent() <br>
|
||||
* RenderType.getTripwire() <br>
|
||||
*/
|
||||
public static void startRenderingStencil(RenderType blockLayerIn)
|
||||
{
|
||||
// we only enable drawing to the stencil once since
|
||||
// we want to skip the rendering of the out of world fog
|
||||
// but catch everything else
|
||||
if (blockLayerIn == RenderType.getSolid())
|
||||
{
|
||||
// solid is the first layer rendered
|
||||
// clear the buffer so we can start fresh.
|
||||
// if this isn't cleared first we will have overlap with the fog
|
||||
// outside the world
|
||||
GL11.glClearStencil(0);
|
||||
GL11.glStencilMask(0x11111111);
|
||||
GL11.glClear(GL11.GL_STENCIL_BUFFER_BIT);
|
||||
|
||||
GL11.glEnable(GL11.GL_STENCIL_TEST);
|
||||
GL11.glStencilFunc(GL11.GL_ALWAYS, 1, 0x11111111);
|
||||
GL11.glStencilMask(0b11111111);
|
||||
GL11.glStencilOp(GL11.GL_KEEP, // this doesn't mater since GL_ALWAYS is being used
|
||||
GL11.GL_KEEP, // stencil test passes
|
||||
GL11.GL_REPLACE); // stencil + depth pass
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* this variable should be the same as the method name below.
|
||||
* It is used when transforming the RenderGlobal class'
|
||||
* renderBlockLayer method.
|
||||
*/
|
||||
public static final String END_STENCIL_METHOD_NAME = "endRenderingStencil";
|
||||
|
||||
/**
|
||||
* Currently this method isn't used in any transformations since we end
|
||||
* the stencil drawing in the ClientProxy right before we draw the LODs.
|
||||
*/
|
||||
public static void endRenderingStencil(RenderType blockLayerIn)
|
||||
{
|
||||
GL11.glStencilOp(GL11.GL_KEEP, // this doesn't mater since GL_ALWAYS is being used
|
||||
GL11.GL_KEEP, // stencil test passes
|
||||
GL11.GL_KEEP); // stencil + depth pass
|
||||
}
|
||||
|
||||
public static void endRenderingStencil()
|
||||
{
|
||||
endRenderingStencil(null);
|
||||
}
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
package com.backsun.lod.renderer;
|
||||
|
||||
import net.minecraft.client.Minecraft;
|
||||
|
||||
/**
|
||||
* This holds miscellaneous helper code
|
||||
* to be used in the rendering process.
|
||||
*
|
||||
* @author James Seibel
|
||||
* @version 2-13-2021
|
||||
*/
|
||||
public class RenderUtil
|
||||
{
|
||||
/**
|
||||
* Returns if the given coordinate is in the loaded area of the world.
|
||||
* @param centerCoordinate the center of the loaded world
|
||||
*/
|
||||
public static boolean isCoordinateInLoadedArea(int i, int j, int centerCoordinate)
|
||||
{
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
|
||||
return (i >= centerCoordinate - mc.gameSettings.renderDistanceChunks
|
||||
&& i <= centerCoordinate + mc.gameSettings.renderDistanceChunks)
|
||||
&&
|
||||
(j >= centerCoordinate - mc.gameSettings.renderDistanceChunks
|
||||
&& j <= centerCoordinate + mc.gameSettings.renderDistanceChunks);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Find the coordinates that are in the center half of the given
|
||||
* 2D matrix, starting at (0,0) and going to (2 * lodRadius, 2 * lodRadius).
|
||||
*/
|
||||
public static boolean isCoordinateInNearFogArea(int i, int j, int lodRadius)
|
||||
{
|
||||
int halfRadius = lodRadius / 2;
|
||||
|
||||
return (i >= lodRadius - halfRadius
|
||||
&& i <= lodRadius + halfRadius)
|
||||
&&
|
||||
(j >= lodRadius - halfRadius
|
||||
&& j <= lodRadius + halfRadius);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,105 +0,0 @@
|
||||
package com.backsun.lod.util;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
|
||||
import org.apache.commons.lang3.tuple.Pair;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
|
||||
import com.backsun.lod.ModInfo;
|
||||
import com.backsun.lod.util.enums.FogDistance;
|
||||
import com.electronwill.nightconfig.core.file.CommentedFileConfig;
|
||||
import com.electronwill.nightconfig.core.io.WritingMode;
|
||||
|
||||
import net.minecraftforge.common.ForgeConfigSpec;
|
||||
import net.minecraftforge.eventbus.api.SubscribeEvent;
|
||||
import net.minecraftforge.fml.common.Mod;
|
||||
import net.minecraftforge.fml.config.ModConfig;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author James Seibel
|
||||
* @version 02-27-2021
|
||||
*/
|
||||
@Mod.EventBusSubscriber
|
||||
public class LodConfig
|
||||
{
|
||||
public static class Client
|
||||
{
|
||||
public ForgeConfigSpec.BooleanValue drawLODs;
|
||||
|
||||
public ForgeConfigSpec.EnumValue<FogDistance> fogDistance;
|
||||
|
||||
public ForgeConfigSpec.BooleanValue drawCheckerBoard;
|
||||
|
||||
|
||||
Client(ForgeConfigSpec.Builder builder)
|
||||
{
|
||||
builder.comment(ModInfo.MODNAME + " configuration settings").push("client");
|
||||
|
||||
drawLODs = builder
|
||||
.comment("If false LODs will not be drawn, \n"
|
||||
+ "however they will still be generated \n"
|
||||
+ "and saved to file for later use.")
|
||||
.define("drawLODs", true);
|
||||
|
||||
fogDistance = builder
|
||||
.comment("\n"
|
||||
+ "At what distance should Fog be drawn on the LODs? \n"
|
||||
+ "If fog cuts off ubruptly or you are using Optifine's \"fast\" \n"
|
||||
+ "fog option set this to " + FogDistance.NEAR.toString() + " or " + FogDistance.FAR.toString() + ".")
|
||||
.defineEnum("fogDistance", FogDistance.NEAR_AND_FAR);
|
||||
|
||||
drawCheckerBoard = builder
|
||||
.comment("\n"
|
||||
+ "If false the LODs will draw with their normal world colors. \n"
|
||||
+ "If true they will draw as a black and white checkerboard. \n"
|
||||
+ "This can be used for debugging or imagining you are playing a \n"
|
||||
+ "giant game of chess ;)")
|
||||
.define("drawCheckerBoard", false);
|
||||
|
||||
builder.pop();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* {@link Path} to the configuration file of this mod
|
||||
*/
|
||||
private static final Path CONFIG_PATH =
|
||||
Paths.get("config", ModInfo.MODID + ".toml");
|
||||
|
||||
public static final ForgeConfigSpec clientSpec;
|
||||
public static final Client CLIENT;
|
||||
static {
|
||||
final Pair<Client, ForgeConfigSpec> specPair = new ForgeConfigSpec.Builder().configure(Client::new);
|
||||
clientSpec = specPair.getRight();
|
||||
CLIENT = specPair.getLeft();
|
||||
|
||||
// setup the config file
|
||||
CommentedFileConfig config = CommentedFileConfig.builder(CONFIG_PATH)
|
||||
.writingMode(WritingMode.REPLACE)
|
||||
.build();
|
||||
config.load();
|
||||
config.save();
|
||||
clientSpec.setConfig(config);
|
||||
}
|
||||
|
||||
|
||||
@SubscribeEvent
|
||||
public static void onLoad(final ModConfig.Loading configEvent)
|
||||
{
|
||||
LogManager.getLogger().debug(ModInfo.MODNAME, "Loaded forge config file {}", configEvent.getConfig().getFileName());
|
||||
}
|
||||
|
||||
@SubscribeEvent
|
||||
public static void onFileChange(final ModConfig.Reloading configEvent)
|
||||
{
|
||||
LogManager.getLogger().debug(ModInfo.MODNAME, "Forge config just got changed on the file system!");
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -1,63 +0,0 @@
|
||||
package com.backsun.lod.util;
|
||||
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.server.integrated.IntegratedServer;
|
||||
import net.minecraft.world.DimensionType;
|
||||
import net.minecraft.world.server.ServerWorld;
|
||||
|
||||
/**
|
||||
* This class holds methods that may be used in multiple places.
|
||||
*
|
||||
* @author James Seibel
|
||||
* @version 02-26-2021
|
||||
*/
|
||||
public class LodUtils
|
||||
{
|
||||
private static Minecraft mc = Minecraft.getInstance();
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Gets the first valid ServerWorld.
|
||||
*
|
||||
* @return null if there are no ServerWorlds
|
||||
*/
|
||||
public static ServerWorld getFirstValidServerWorld()
|
||||
{
|
||||
if (mc.getIntegratedServer() == null)
|
||||
return null;
|
||||
|
||||
Iterable<ServerWorld> worlds = mc.getIntegratedServer().getWorlds();
|
||||
|
||||
for (ServerWorld world : worlds)
|
||||
return world;
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the ServerWorld for the relevant dimension.
|
||||
*
|
||||
* @return null if there is no ServerWorld for the given dimension
|
||||
*/
|
||||
public static ServerWorld getServerWorldFromDimension(DimensionType dimension)
|
||||
{
|
||||
IntegratedServer server = mc.getIntegratedServer();
|
||||
if (server == null)
|
||||
return null;
|
||||
|
||||
Iterable<ServerWorld> worlds = server.getWorlds();
|
||||
ServerWorld returnWorld = null;
|
||||
|
||||
for (ServerWorld world : worlds)
|
||||
{
|
||||
if(world.getDimensionType() == dimension)
|
||||
{
|
||||
returnWorld = world;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return returnWorld;
|
||||
}
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
package com.backsun.lod.util;
|
||||
|
||||
/**
|
||||
* This holds meta information about the mod.
|
||||
*
|
||||
* @author James Seibel
|
||||
* @version 04-16-2020
|
||||
*/
|
||||
public class Reference
|
||||
{
|
||||
/** the mod's identifier */
|
||||
public static final String MOD_ID = "lod";
|
||||
/** the mod's name */
|
||||
public static final String NAME = "LOD Mod";
|
||||
/** the mod's version */
|
||||
public static final String VERSION = "1.0";
|
||||
/** the version of minecraft this mod is built for */
|
||||
public static final String ACCEPTED_VERSIONS = "[1.16.4]";
|
||||
|
||||
/** where the client proxy class is */
|
||||
public static final String CLIENT_PROXY_CLASS = "com.backsun.lod.proxy.ClientProxy";
|
||||
/** where the common proxy class is*/
|
||||
public static final String COMMON_PROXY_CLASS = "com.backsun.lod.proxy.CommonProxy";
|
||||
|
||||
|
||||
}
|
||||
@@ -1,96 +0,0 @@
|
||||
package com.backsun.lod.util;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
|
||||
import com.backsun.lod.util.enums.FogQuality;
|
||||
|
||||
import net.minecraft.client.Minecraft;
|
||||
|
||||
/**
|
||||
* This object is used to get variables from methods
|
||||
* where they are private.
|
||||
*
|
||||
* @author James Seibel
|
||||
* @version 02-18-2021
|
||||
*/
|
||||
public class ReflectionHandler
|
||||
{
|
||||
private Minecraft mc = Minecraft.getInstance();
|
||||
|
||||
public Field ofFogField = null;
|
||||
|
||||
|
||||
public ReflectionHandler()
|
||||
{
|
||||
setupFogField();
|
||||
}
|
||||
|
||||
/**
|
||||
* Similar to setupFovMethod.
|
||||
*/
|
||||
private void setupFogField()
|
||||
{
|
||||
// get every variable from the entity renderer
|
||||
Field[] vars = mc.gameSettings.getClass().getDeclaredFields();
|
||||
|
||||
// try and find the ofFogType variable in gameSettings
|
||||
for(Field f : vars)
|
||||
{
|
||||
if(f.getName().equals("ofFogType"))
|
||||
{
|
||||
ofFogField = f;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// we didn't find the field,
|
||||
// either optifine isn't installed, or
|
||||
// optifine changed the name of the variable
|
||||
ofFogField = null;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Get what type of fog optifine is currently set to render.
|
||||
*/
|
||||
public FogQuality getFogQuality()
|
||||
{
|
||||
if (ofFogField == null)
|
||||
{
|
||||
// either optifine isn't installed,
|
||||
// the variable name was changed, or
|
||||
// the setup method wasn't called yet.
|
||||
return FogQuality.OFF;
|
||||
}
|
||||
|
||||
int returnNum = 0;
|
||||
|
||||
try
|
||||
{
|
||||
returnNum = (int)ofFogField.get(mc.gameSettings);
|
||||
}
|
||||
catch (IllegalArgumentException | IllegalAccessException e)
|
||||
{
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
switch (returnNum)
|
||||
{
|
||||
case 0:
|
||||
return FogQuality.FAST;
|
||||
case 1:
|
||||
return FogQuality.FAST;
|
||||
case 2:
|
||||
return FogQuality.FANCY;
|
||||
case 3:
|
||||
return FogQuality.OFF;
|
||||
|
||||
default:
|
||||
return FogQuality.FAST;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
package com.backsun.lod.util.enums;
|
||||
|
||||
/**
|
||||
* TOP, N, S, E, W, BOTTOM
|
||||
*
|
||||
* @author James Seibel
|
||||
* @version 10-17-2020
|
||||
*/
|
||||
public enum ColorDirection
|
||||
{
|
||||
// used for colors
|
||||
/** +Y */
|
||||
TOP(0),
|
||||
|
||||
/** -Z */
|
||||
N(1),
|
||||
/** +Z */
|
||||
S(2),
|
||||
|
||||
/** +X */
|
||||
E(3),
|
||||
/** -X */
|
||||
W(4),
|
||||
|
||||
/** -Y */
|
||||
BOTTOM(5);
|
||||
|
||||
public final int value;
|
||||
|
||||
private ColorDirection(int newValue)
|
||||
{
|
||||
value = newValue;
|
||||
}
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
package com.backsun.lod.util.enums;
|
||||
|
||||
/**
|
||||
* @author James Seibel
|
||||
* @version 1-23-2021
|
||||
*/
|
||||
public enum DrawMode
|
||||
{
|
||||
/** Draw the LOD objects in groups.
|
||||
* <br>
|
||||
* <br>
|
||||
* Fancy fog: render the center and outside LOD
|
||||
* objects in 2 different groups.
|
||||
* <br>
|
||||
* Fast fog: render all LOD objects at one time.
|
||||
*/
|
||||
BATCH(0),
|
||||
|
||||
/** Draw each LOD objects separately.
|
||||
* <br>
|
||||
* <br>
|
||||
* Not suggested normally since draw calls are GPU expensive.
|
||||
*/
|
||||
INDIVIDUAL(5);
|
||||
|
||||
public final int value;
|
||||
|
||||
private DrawMode(int newValue)
|
||||
{
|
||||
value = newValue;
|
||||
}
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
package com.backsun.lod.util.enums;
|
||||
|
||||
/**
|
||||
* Near, far, or NEAR_AND_FAR.
|
||||
*
|
||||
* @author James Seibel
|
||||
* @version 02-14-2021
|
||||
*/
|
||||
public enum FogDistance
|
||||
{
|
||||
/** good for fast or fancy fog qualities. */
|
||||
NEAR,
|
||||
/** good for fast or fancy fog qualities. */
|
||||
FAR,
|
||||
/** only looks good if the fog quality is set to Fancy. */
|
||||
NEAR_AND_FAR;
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
package com.backsun.lod.util.enums;
|
||||
|
||||
/**
|
||||
* fast, fancy, or off
|
||||
*
|
||||
* @author James Seibel
|
||||
* @version 02-14-2021
|
||||
*/
|
||||
public enum FogQuality
|
||||
{
|
||||
FAST,
|
||||
FANCY,
|
||||
OFF;
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
package com.backsun.lod.util.enums;
|
||||
|
||||
/**
|
||||
* NE, SE, SW, NW
|
||||
*
|
||||
* @author James Seibel
|
||||
* @version 1-20-2020
|
||||
*/
|
||||
public enum LodCorner
|
||||
{
|
||||
// used for position
|
||||
|
||||
/** -Z, +X */
|
||||
NE(0),
|
||||
/** +Z, +X */
|
||||
SE(1),
|
||||
/** +Z, -X */
|
||||
SW(2),
|
||||
/** -Z, -X */
|
||||
NW(3);
|
||||
|
||||
public final int value;
|
||||
|
||||
private LodCorner(int newValue)
|
||||
{
|
||||
value = newValue;
|
||||
}
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
package com.backsun.lod.util.enums;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author James Seibel
|
||||
* @version 1-20-2020
|
||||
*
|
||||
* NE, SE, SW, NW
|
||||
*/
|
||||
public enum LodLocation
|
||||
{
|
||||
// used for position
|
||||
|
||||
/** -Z, +X */
|
||||
NE(0),
|
||||
/** +Z, +X */
|
||||
SE(1),
|
||||
/** +Z, -X */
|
||||
SW(2),
|
||||
/** -Z, -X */
|
||||
NW(3);
|
||||
|
||||
public final int value;
|
||||
|
||||
private LodLocation(int newValue)
|
||||
{
|
||||
value = newValue;
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,25 @@
|
||||
package com.backsun.lod;
|
||||
/*
|
||||
* This file is part of the LOD Mod, licensed under the GNU GPL v3 License.
|
||||
*
|
||||
* Copyright (C) 2020 James Seibel
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, version 3.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.seibel.lod;
|
||||
|
||||
import com.backsun.lod.proxy.ClientProxy;
|
||||
import com.backsun.lod.util.LodConfig;
|
||||
import com.seibel.lod.config.LodConfig;
|
||||
import com.seibel.lod.proxy.ClientProxy;
|
||||
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraftforge.common.MinecraftForge;
|
||||
import net.minecraftforge.eventbus.api.SubscribeEvent;
|
||||
import net.minecraftforge.fml.ModLoadingContext;
|
||||
@@ -21,7 +37,7 @@ import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext;
|
||||
* check out the ClientProxy.
|
||||
*
|
||||
* @author James Seibel
|
||||
* @version 02-07-2021
|
||||
* @version 7-3-2021
|
||||
*/
|
||||
@Mod(ModInfo.MODID)
|
||||
public class LodMain
|
||||
@@ -33,8 +49,7 @@ public class LodMain
|
||||
|
||||
private void init(final FMLCommonSetupEvent event)
|
||||
{
|
||||
Minecraft.getInstance().getFramebuffer().enableStencil();
|
||||
ModLoadingContext.get().registerConfig(ModConfig.Type.CLIENT, LodConfig.clientSpec);
|
||||
ModLoadingContext.get().registerConfig(ModConfig.Type.CLIENT, LodConfig.CLIENT_SPEC);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* This file is part of the LOD Mod, licensed under the GNU GPL v3 License.
|
||||
*
|
||||
* Copyright (C) 2020 James Seibel
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, version 3.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.seibel.lod;
|
||||
|
||||
/**
|
||||
* This file is similar to mcmod.info
|
||||
*
|
||||
* @author James Seibel
|
||||
* @version 08-29-2021
|
||||
*/
|
||||
public final class ModInfo
|
||||
{
|
||||
public static final String MODID = "lod";
|
||||
public static final String MODNAME = "LOD";
|
||||
public static final String MODAPI = "LodAPI";
|
||||
public static final String VERSION = "a1.4";
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package com.seibel.lod.builders;
|
||||
|
||||
import com.seibel.lod.enums.DistanceGenerationMode;
|
||||
import com.seibel.lod.objects.LevelPos.LevelPos;
|
||||
import com.seibel.lod.util.LodUtil;
|
||||
import net.minecraft.util.math.ChunkPos;
|
||||
|
||||
/**
|
||||
* @author Leonardo Amato
|
||||
* @version 22-08-2021
|
||||
*/
|
||||
public class GenerationRequest
|
||||
{
|
||||
public final LevelPos levelPos;
|
||||
public final DistanceGenerationMode generationMode;
|
||||
|
||||
public GenerationRequest(LevelPos levelPos, DistanceGenerationMode generationMode)
|
||||
{
|
||||
this.levelPos = levelPos;
|
||||
this.generationMode = generationMode;
|
||||
}
|
||||
|
||||
public ChunkPos getChunkPos()
|
||||
{
|
||||
LevelPos chunkLevelPos = levelPos.getConvertedLevelPos(LodUtil.CHUNK_DETAIL_LEVEL);
|
||||
return new ChunkPos(chunkLevelPos.posX, chunkLevelPos.posZ);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,603 @@
|
||||
/*
|
||||
* This file is part of the LOD Mod, licensed under the GNU GPL v3 License.
|
||||
*
|
||||
* Copyright (C) 2020 James Seibel
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, version 3.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.seibel.lod.builders;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
|
||||
import org.apache.commons.lang3.mutable.MutableBoolean;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
|
||||
import com.seibel.lod.config.LodConfig;
|
||||
import com.seibel.lod.objects.LodDimension;
|
||||
import com.seibel.lod.objects.RegionPos;
|
||||
import com.seibel.lod.objects.LevelPos.LevelPos;
|
||||
import com.seibel.lod.proxy.ClientProxy;
|
||||
import com.seibel.lod.render.LodRenderer;
|
||||
import com.seibel.lod.util.LodThreadFactory;
|
||||
import com.seibel.lod.util.LodUtil;
|
||||
|
||||
import net.minecraft.client.renderer.BufferBuilder;
|
||||
import net.minecraft.client.renderer.vertex.VertexBuffer;
|
||||
import net.minecraft.util.math.BlockPos;
|
||||
import net.minecraft.util.math.ChunkPos;
|
||||
|
||||
|
||||
/**
|
||||
* This object is used to create NearFarBuffer objects.
|
||||
*
|
||||
* @author James Seibel
|
||||
* @version 8-24-2021
|
||||
*/
|
||||
public class LodBufferBuilder
|
||||
{
|
||||
/**
|
||||
* This holds the thread used to generate new LODs off the main thread.
|
||||
*/
|
||||
private ExecutorService mainGenThread = Executors.newSingleThreadExecutor(new LodThreadFactory(this.getClass().getSimpleName() + " - main"));
|
||||
/**
|
||||
* This holds the threads used to generate buffers.
|
||||
*/
|
||||
private ExecutorService bufferBuilderThreads = Executors.newFixedThreadPool(LodConfig.CLIENT.threading.numberOfBufferBuilderThreads.get(), new LodThreadFactory(this.getClass().getSimpleName() + " - builder"));
|
||||
|
||||
/**
|
||||
* The buffers that are used to create LODs using far fog
|
||||
*/
|
||||
public volatile BufferBuilder[][] buildableBuffers;
|
||||
|
||||
/**
|
||||
* Used when building new VBOs
|
||||
*/
|
||||
public volatile VertexBuffer[][] buildableVbos;
|
||||
|
||||
/**
|
||||
* VBOs that are sent over to the LodNodeRenderer
|
||||
*/
|
||||
public volatile VertexBuffer[][] drawableVbos;
|
||||
|
||||
/**
|
||||
* if this is true the LOD buffers are currently being
|
||||
* regenerated.
|
||||
*/
|
||||
public boolean generatingBuffers = false;
|
||||
|
||||
/**
|
||||
* if this is true new LOD buffers have been generated
|
||||
* and are waiting to be swapped with the drawable buffers
|
||||
*/
|
||||
private boolean switchVbos = false;
|
||||
|
||||
|
||||
/**
|
||||
* Size of the buffer builders in bytes last time we created them
|
||||
*/
|
||||
public int previousBufferSize = 0;
|
||||
|
||||
/**
|
||||
* Width of the dimension in regions last time we created the buffers
|
||||
*/
|
||||
public int previousRegionWidth = 0;
|
||||
|
||||
/**
|
||||
* this is used to prevent multiple threads creating, destroying, or using the buffers at the same time
|
||||
*/
|
||||
private ReentrantLock bufferLock = new ReentrantLock();
|
||||
|
||||
private volatile Object[][] setsToRender;
|
||||
private volatile RegionPos center;
|
||||
|
||||
public LodBufferBuilder()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Create a thread to asynchronously generate LOD buffers
|
||||
* centered around the given camera X and Z.
|
||||
* <br>
|
||||
* This method will write to the drawable near and far buffers.
|
||||
* <br>
|
||||
* After the buildable buffers have been generated they must be
|
||||
* swapped with the drawable buffers in the LodRenderer to be drawn.
|
||||
*/
|
||||
public void generateLodBuffersAsync(LodRenderer renderer, LodDimension lodDim,
|
||||
BlockPos playerBlockPos, boolean fullRegen)
|
||||
{
|
||||
// only allow one generation process to happen at a time
|
||||
if (generatingBuffers)
|
||||
return;
|
||||
|
||||
if (buildableBuffers == null)
|
||||
// setupBuffers hasn't been called yet
|
||||
return;
|
||||
|
||||
|
||||
generatingBuffers = true;
|
||||
|
||||
|
||||
// round the player's block position down to the nearest chunk BlockPos
|
||||
ChunkPos playerChunkPos = new ChunkPos(playerBlockPos);
|
||||
BlockPos playerBlockPosRounded = playerChunkPos.getWorldPosition();
|
||||
|
||||
|
||||
Thread thread = new Thread(() ->
|
||||
{
|
||||
bufferLock.lock();
|
||||
|
||||
|
||||
try
|
||||
{
|
||||
long treeStart = System.currentTimeMillis();
|
||||
long treeEnd = System.currentTimeMillis();
|
||||
|
||||
long startTime = System.currentTimeMillis();
|
||||
|
||||
|
||||
ArrayList<Callable<Boolean>> nodeToRenderThreads = new ArrayList<>(lodDim.regions.length * lodDim.regions.length);
|
||||
|
||||
startBuffers(fullRegen, lodDim);
|
||||
|
||||
// =====================//
|
||||
// RENDERING PART //
|
||||
// =====================//
|
||||
|
||||
RegionPos playerRegionPos = new RegionPos(playerChunkPos);
|
||||
if (center == null)
|
||||
center = playerRegionPos;
|
||||
|
||||
if (setsToRender == null)
|
||||
setsToRender = new Object[lodDim.regions.length][lodDim.regions.length];
|
||||
|
||||
if (setsToRender.length != lodDim.regions.length)
|
||||
setsToRender = new Object[lodDim.regions.length][lodDim.regions.length];
|
||||
|
||||
|
||||
RegionPos worldRegionOffset = new RegionPos(playerRegionPos.x - lodDim.getCenterX(), playerRegionPos.z - lodDim.getCenterZ());
|
||||
if (worldRegionOffset.x != 0 || worldRegionOffset.z != 0)
|
||||
{
|
||||
move(worldRegionOffset, Math.floorDiv(lodDim.getWidth(), 2));
|
||||
}
|
||||
|
||||
for (int xRegion = 0; xRegion < lodDim.regions.length; xRegion++)
|
||||
{
|
||||
for (int zRegion = 0; zRegion < lodDim.regions.length; zRegion++)
|
||||
{
|
||||
if (lodDim.regen[xRegion][zRegion] || fullRegen)
|
||||
{
|
||||
RegionPos regionPos = new RegionPos(
|
||||
xRegion + lodDim.getCenterX() - Math.floorDiv(lodDim.getWidth(), 2),
|
||||
zRegion + lodDim.getCenterZ() - Math.floorDiv(lodDim.getWidth(), 2));
|
||||
|
||||
// local position in the vbo and bufferBuilder arrays
|
||||
BufferBuilder currentBuffer = buildableBuffers[xRegion][zRegion];
|
||||
|
||||
// make sure the buffers weren't
|
||||
// changed while we were running this method
|
||||
if (currentBuffer == null || (currentBuffer != null && !currentBuffer.building()))
|
||||
return;
|
||||
|
||||
//previous setToRender chache
|
||||
if (setsToRender[xRegion][zRegion] == null)
|
||||
{
|
||||
setsToRender[xRegion][zRegion] = new ConcurrentHashMap<LevelPos, MutableBoolean>();
|
||||
}
|
||||
ConcurrentMap<LevelPos, MutableBoolean> nodeToRender = (ConcurrentMap<LevelPos, MutableBoolean>) setsToRender[xRegion][zRegion];
|
||||
Callable<Boolean> dataToRenderThread = () ->
|
||||
{
|
||||
lodDim.getDataToRender(
|
||||
nodeToRender,
|
||||
regionPos,
|
||||
playerBlockPosRounded.getX(),
|
||||
playerBlockPosRounded.getZ());
|
||||
|
||||
int posX;
|
||||
int posZ;
|
||||
byte detailLevel;
|
||||
int chunkXdist;
|
||||
int chunkZdist;
|
||||
short gameChunkRenderDistance = (short) (renderer.vanillaRenderedChunks.length/2 - 1);
|
||||
for (LevelPos posToRender : nodeToRender.keySet())
|
||||
{
|
||||
if (!nodeToRender.get(posToRender).booleanValue())
|
||||
{
|
||||
nodeToRender.remove(posToRender);
|
||||
continue;
|
||||
}
|
||||
nodeToRender.get(posToRender).setFalse();
|
||||
// skip any chunks that Minecraft is going to render
|
||||
chunkXdist = posToRender.getChunkPosX() - playerChunkPos.x;
|
||||
chunkZdist = posToRender.getChunkPosZ() - playerChunkPos.z;
|
||||
if(gameChunkRenderDistance >= Math.abs(chunkXdist) && gameChunkRenderDistance >= Math.abs(chunkZdist) && posToRender.detailLevel <= LodUtil.CHUNK_DETAIL_LEVEL)
|
||||
{
|
||||
if (renderer.vanillaRenderedChunks[chunkXdist + gameChunkRenderDistance + 1][chunkZdist + gameChunkRenderDistance + 1])
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
posX = posToRender.posX;
|
||||
posZ = posToRender.posZ;
|
||||
detailLevel = posToRender.detailLevel;
|
||||
// skip any chunks that Minecraft is going to render
|
||||
|
||||
|
||||
try
|
||||
{
|
||||
boolean disableFix = false;
|
||||
if (lodDim.doesDataExist(posToRender.clone()))
|
||||
{
|
||||
short[] lodData = lodDim.getData(posToRender);
|
||||
short[][][] adjData = new short[2][2][];
|
||||
/**TODO The following two for are too complex*/
|
||||
for (int x : new int[]{0, 1})
|
||||
{
|
||||
posToRender.changeParameters(detailLevel, posX + x * 2 - 1, posZ);
|
||||
chunkXdist = posToRender.getChunkPosX() - playerChunkPos.x;
|
||||
chunkZdist = posToRender.getChunkPosZ() - playerChunkPos.z;
|
||||
if(gameChunkRenderDistance >= Math.abs(chunkXdist) && gameChunkRenderDistance >= Math.abs(chunkZdist))
|
||||
{
|
||||
if (!renderer.vanillaRenderedChunks[chunkXdist + gameChunkRenderDistance + 1][chunkZdist + gameChunkRenderDistance + 1]
|
||||
&& (nodeToRender.containsKey(posToRender) || disableFix))
|
||||
{
|
||||
adjData[0][x] = lodDim.getData(posToRender);
|
||||
}
|
||||
}else{
|
||||
if (nodeToRender.containsKey(posToRender) || disableFix)
|
||||
{
|
||||
adjData[0][x] = lodDim.getData(posToRender);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (int z : new int[]{0, 1})
|
||||
{
|
||||
posToRender.changeParameters(detailLevel, posX, posZ + z * 2 - 1);
|
||||
chunkXdist = posToRender.getChunkPosX() - playerChunkPos.x;
|
||||
chunkZdist = posToRender.getChunkPosZ() - playerChunkPos.z;
|
||||
if(gameChunkRenderDistance >= Math.abs(chunkXdist) && gameChunkRenderDistance >= Math.abs(chunkZdist))
|
||||
{
|
||||
if (!renderer.vanillaRenderedChunks[chunkXdist + gameChunkRenderDistance + 1][chunkZdist + gameChunkRenderDistance+ 1]
|
||||
&& (nodeToRender.containsKey(posToRender) || disableFix))
|
||||
{
|
||||
adjData[1][z] = lodDim.getData(posToRender);
|
||||
}
|
||||
}else{
|
||||
if (nodeToRender.containsKey(posToRender) || disableFix)
|
||||
{
|
||||
adjData[1][z] = lodDim.getData(posToRender);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
posToRender.changeParameters(detailLevel, posX, posZ);
|
||||
|
||||
LodConfig.CLIENT.graphics.lodTemplate.get().template.addLodToBuffer(currentBuffer, playerBlockPos, lodData, adjData,
|
||||
posToRender, renderer.previousDebugMode);
|
||||
}
|
||||
} catch (ArrayIndexOutOfBoundsException e)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
}// for pos to in list to render
|
||||
|
||||
// the thread executed successfully
|
||||
return true;
|
||||
};
|
||||
nodeToRenderThreads.add(dataToRenderThread);
|
||||
}
|
||||
}// region z
|
||||
}// region z
|
||||
long renderStart = System.currentTimeMillis();
|
||||
// wait for all threads to finish
|
||||
List<Future<Boolean>> futuresBuffer = bufferBuilderThreads.invokeAll(nodeToRenderThreads);
|
||||
for (Future<Boolean> future : futuresBuffer)
|
||||
{
|
||||
// the future will be false if its thread failed
|
||||
if (!future.get())
|
||||
{
|
||||
ClientProxy.LOGGER.warn("LodBufferBuilder ran into trouble and had to start over.");
|
||||
closeBuffers(fullRegen, lodDim);
|
||||
return;
|
||||
}
|
||||
}
|
||||
long renderEnd = System.currentTimeMillis();
|
||||
|
||||
|
||||
long endTime = System.currentTimeMillis();
|
||||
@SuppressWarnings("unused")
|
||||
long buildTime = endTime - startTime;
|
||||
@SuppressWarnings("unused")
|
||||
long treeTime = treeEnd - treeStart;
|
||||
@SuppressWarnings("unused")
|
||||
long renderingTime = renderEnd - renderStart;
|
||||
|
||||
// ClientProxy.LOGGER.info("Buffer Build time: " + buildTime + " ms" + '\n' +
|
||||
// "Tree cutting time: " + treeTime + " ms" + '\n' +
|
||||
// "Rendering time: " + renderingTime + " ms");
|
||||
|
||||
// mark that the buildable buffers as ready to swap
|
||||
switchVbos = true;
|
||||
} catch (Exception e)
|
||||
{
|
||||
ClientProxy.LOGGER.warn("\"LodNodeBufferBuilder.generateLodBuffersAsync\" ran into trouble: ");
|
||||
e.printStackTrace();
|
||||
} finally
|
||||
{
|
||||
// regardless of if we successfully created the buffers
|
||||
// we are done generating.
|
||||
generatingBuffers = false;
|
||||
|
||||
// clean up any potentially open resources
|
||||
if (buildableBuffers != null)
|
||||
closeBuffers(fullRegen, lodDim);
|
||||
|
||||
// upload the new buffers
|
||||
uploadBuffers(fullRegen, lodDim);
|
||||
bufferLock.unlock();
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
mainGenThread.execute(thread);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Move the center of this LodDimension and move all owned
|
||||
* regions over by the given x and z offset. <br><br>
|
||||
* <p>
|
||||
* Synchronized to prevent multiple moves happening on top of each other.
|
||||
*/
|
||||
public void move(RegionPos regionOffset, int width)
|
||||
{
|
||||
int xOffset = regionOffset.x;
|
||||
int zOffset = regionOffset.z;
|
||||
|
||||
// if the x or z offset is equal to or greater than
|
||||
// the total size, just delete the current data
|
||||
// and update the centerX and/or centerZ
|
||||
if (Math.abs(xOffset) >= width || Math.abs(zOffset) >= width)
|
||||
{
|
||||
for (int x = 0; x < width; x++)
|
||||
{
|
||||
for (int z = 0; z < width; z++)
|
||||
{
|
||||
setsToRender[x][z] = null;
|
||||
}
|
||||
}
|
||||
|
||||
// update the new center
|
||||
center.x += xOffset;
|
||||
center.z += zOffset;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// X
|
||||
if (xOffset > 0)
|
||||
{
|
||||
// move everything over to the left (as the center moves to the right)
|
||||
for (int x = 0; x < width; x++)
|
||||
{
|
||||
for (int z = 0; z < width; z++)
|
||||
{
|
||||
if (x + xOffset < width)
|
||||
setsToRender[x][z] = setsToRender[x + xOffset][z];
|
||||
else
|
||||
setsToRender[x][z] = null;
|
||||
}
|
||||
}
|
||||
} else
|
||||
{
|
||||
// move everything over to the right (as the center moves to the left)
|
||||
for (int x = width - 1; x >= 0; x--)
|
||||
{
|
||||
for (int z = 0; z < width; z++)
|
||||
{
|
||||
if (x + xOffset >= 0)
|
||||
setsToRender[x][z] = setsToRender[x + xOffset][z];
|
||||
else
|
||||
setsToRender[x][z] = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Z
|
||||
if (zOffset > 0)
|
||||
{
|
||||
// move everything up (as the center moves down)
|
||||
for (int x = 0; x < width; x++)
|
||||
{
|
||||
for (int z = 0; z < width; z++)
|
||||
{
|
||||
if (z + zOffset < width)
|
||||
setsToRender[x][z] = setsToRender[x][z + zOffset];
|
||||
else
|
||||
setsToRender[x][z] = null;
|
||||
}
|
||||
}
|
||||
} else
|
||||
{
|
||||
// move everything down (as the center moves up)
|
||||
for (int x = 0; x < width; x++)
|
||||
{
|
||||
for (int z = width - 1; z >= 0; z--)
|
||||
{
|
||||
if (z + zOffset >= 0)
|
||||
setsToRender[x][z] = setsToRender[x][z + zOffset];
|
||||
else
|
||||
setsToRender[x][z] = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// update the new center
|
||||
center.x += xOffset;
|
||||
center.z += zOffset;
|
||||
}
|
||||
|
||||
//===============================//
|
||||
// BufferBuilder related methods //
|
||||
//===============================//
|
||||
|
||||
|
||||
/**
|
||||
* Called from the LodRenderer to create the
|
||||
* BufferBuilders. <br><br>
|
||||
* <p>
|
||||
* May have to wait for the bufferLock to open.
|
||||
*/
|
||||
public void setupBuffers(int numbRegionsWide, int bufferMaxCapacity)
|
||||
{
|
||||
bufferLock.lock();
|
||||
|
||||
previousRegionWidth = numbRegionsWide;
|
||||
previousBufferSize = bufferMaxCapacity;
|
||||
|
||||
|
||||
buildableBuffers = new BufferBuilder[numbRegionsWide][numbRegionsWide];
|
||||
|
||||
buildableVbos = new VertexBuffer[numbRegionsWide][numbRegionsWide];
|
||||
drawableVbos = new VertexBuffer[numbRegionsWide][numbRegionsWide];
|
||||
|
||||
for (int x = 0; x < numbRegionsWide; x++)
|
||||
{
|
||||
for (int z = 0; z < numbRegionsWide; z++)
|
||||
{
|
||||
buildableBuffers[x][z] = new BufferBuilder(bufferMaxCapacity);
|
||||
buildableVbos[x][z] = new VertexBuffer(LodRenderer.LOD_VERTEX_FORMAT);
|
||||
drawableVbos[x][z] = new VertexBuffer(LodRenderer.LOD_VERTEX_FORMAT);
|
||||
}
|
||||
}
|
||||
|
||||
bufferLock.unlock();
|
||||
}
|
||||
|
||||
/**
|
||||
* sets the buffers and Vbos to null, forcing them to be recreated. <br><br>
|
||||
* <p>
|
||||
* May have to wait for the bufferLock to open.
|
||||
*/
|
||||
public void destroyBuffers()
|
||||
{
|
||||
bufferLock.lock();
|
||||
|
||||
buildableBuffers = null;
|
||||
buildableVbos = null;
|
||||
drawableVbos = null;
|
||||
|
||||
bufferLock.unlock();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Calls begin on each of the buildable BufferBuilders.
|
||||
*/
|
||||
private void startBuffers(boolean fullRegen, LodDimension lodDim)
|
||||
{
|
||||
for (int x = 0; x < buildableBuffers.length; x++)
|
||||
for (int z = 0; z < buildableBuffers.length; z++)
|
||||
{
|
||||
if (fullRegen || lodDim.regen[x][z])
|
||||
{
|
||||
buildableBuffers[x][z].begin(GL11.GL_QUADS, LodRenderer.LOD_VERTEX_FORMAT);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls end on each of the buildable BufferBuilders.
|
||||
*/
|
||||
private void closeBuffers(boolean fullRegen, LodDimension lodDim)
|
||||
{
|
||||
for (int x = 0; x < buildableBuffers.length; x++)
|
||||
for (int z = 0; z < buildableBuffers.length; z++)
|
||||
|
||||
if (buildableBuffers[x][z] != null && buildableBuffers[x][z].building() && (fullRegen || lodDim.regen[x][z]))
|
||||
{
|
||||
buildableBuffers[x][z].end();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called from the LodRenderer to create the
|
||||
* BufferBuilders at the right size.
|
||||
*/
|
||||
private void uploadBuffers(boolean fullRegen, LodDimension lodDim)
|
||||
{
|
||||
for (int x = 0; x < buildableVbos.length; x++)
|
||||
{
|
||||
for (int z = 0; z < buildableVbos.length; z++)
|
||||
{
|
||||
if (fullRegen || lodDim.regen[x][z])
|
||||
{
|
||||
buildableVbos[x][z].upload(buildableBuffers[x][z]);
|
||||
lodDim.regen[x][z] = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get the newly created VBOs
|
||||
*/
|
||||
public VertexBuffer[][] getVertexBuffers()
|
||||
{
|
||||
// don't wait for the lock to open
|
||||
// since this is called on the main render thread
|
||||
if (bufferLock.tryLock())
|
||||
{
|
||||
VertexBuffer[][] tmp = drawableVbos;
|
||||
drawableVbos = buildableVbos;
|
||||
buildableVbos = tmp;
|
||||
|
||||
// the vbos have been swapped
|
||||
switchVbos = false;
|
||||
bufferLock.unlock();
|
||||
}
|
||||
|
||||
return drawableVbos;
|
||||
}
|
||||
|
||||
/**
|
||||
* If this is true the buildable near and far
|
||||
* buffers have been generated and are ready to be
|
||||
* sent to the LodRenderer.
|
||||
*/
|
||||
public boolean newBuffersAvaliable()
|
||||
{
|
||||
return switchVbos;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,570 @@
|
||||
/*
|
||||
* This file is part of the LOD Mod, licensed under the GNU GPL v3 License.
|
||||
*
|
||||
* Copyright (C) 2020 James Seibel
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, version 3.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.seibel.lod.builders;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
import com.seibel.lod.config.LodConfig;
|
||||
import com.seibel.lod.enums.DistanceGenerationMode;
|
||||
import com.seibel.lod.enums.LodDetail;
|
||||
import com.seibel.lod.objects.DataPoint;
|
||||
import com.seibel.lod.objects.LodDimension;
|
||||
import com.seibel.lod.objects.LodWorld;
|
||||
import com.seibel.lod.objects.LevelPos.LevelPos;
|
||||
import com.seibel.lod.util.ColorUtil;
|
||||
import com.seibel.lod.util.DetailDistanceUtil;
|
||||
import com.seibel.lod.util.LodThreadFactory;
|
||||
import com.seibel.lod.util.LodUtil;
|
||||
|
||||
import net.minecraft.block.AbstractPlantBlock;
|
||||
import net.minecraft.block.BlockState;
|
||||
import net.minecraft.block.Blocks;
|
||||
import net.minecraft.block.BushBlock;
|
||||
import net.minecraft.block.GrassBlock;
|
||||
import net.minecraft.block.IGrowable;
|
||||
import net.minecraft.block.LeavesBlock;
|
||||
import net.minecraft.block.material.MaterialColor;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.world.DimensionType;
|
||||
import net.minecraft.world.IWorld;
|
||||
import net.minecraft.world.biome.Biome;
|
||||
import net.minecraft.world.chunk.ChunkSection;
|
||||
import net.minecraft.world.chunk.IChunk;
|
||||
import net.minecraft.world.gen.Heightmap;
|
||||
|
||||
/**
|
||||
* This object is in charge of creating Lod related objects. (specifically: Lod
|
||||
* World, Dimension, and Region objects)
|
||||
*
|
||||
* @author Leonardo Amato
|
||||
* @author James Seibel
|
||||
* @version 8-29-2021
|
||||
*/
|
||||
public class LodBuilder
|
||||
{
|
||||
private ExecutorService lodGenThreadPool = Executors.newSingleThreadExecutor(new LodThreadFactory(this.getClass().getSimpleName()));
|
||||
|
||||
public static final int CHUNK_DATA_WIDTH = LodUtil.CHUNK_WIDTH;
|
||||
public static final int CHUNK_SECTION_HEIGHT = CHUNK_DATA_WIDTH;
|
||||
public static final Heightmap.Type DEFAULT_HEIGHTMAP = Heightmap.Type.WORLD_SURFACE_WG;
|
||||
|
||||
/**
|
||||
* If no blocks are found in the area in determineBottomPointForArea return this
|
||||
*/
|
||||
public static final short DEFAULT_DEPTH = -1;
|
||||
/**
|
||||
* If no blocks are found in the area in determineHeightPointForArea return this
|
||||
*/
|
||||
public static final short DEFAULT_HEIGHT = -1;
|
||||
|
||||
/**
|
||||
* How wide LodDimensions should be in regions
|
||||
*/
|
||||
public int defaultDimensionWidthInRegions = 5;
|
||||
|
||||
public LodBuilder()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public void generateLodNodeAsync(IChunk chunk, LodWorld lodWorld, IWorld world)
|
||||
{
|
||||
generateLodNodeAsync(chunk, lodWorld, world, DistanceGenerationMode.SERVER);
|
||||
}
|
||||
|
||||
|
||||
public void generateLodNodeAsync(IChunk chunk, LodWorld lodWorld, IWorld world, DistanceGenerationMode generationMode)
|
||||
{
|
||||
if (lodWorld == null || !lodWorld.getIsWorldLoaded())
|
||||
return;
|
||||
|
||||
// don't try to create an LOD object
|
||||
// if for some reason we aren't
|
||||
// given a valid chunk object
|
||||
if (chunk == null)
|
||||
return;
|
||||
|
||||
Thread thread = new Thread(() ->
|
||||
{
|
||||
try
|
||||
{
|
||||
DimensionType dim = world.dimensionType();
|
||||
|
||||
LodDimension lodDim;
|
||||
|
||||
int playerPosX;
|
||||
int playerPosZ;
|
||||
if (Minecraft.getInstance().player == null)
|
||||
{
|
||||
playerPosX = chunk.getPos().getMinBlockX();
|
||||
playerPosZ = chunk.getPos().getMinBlockZ();
|
||||
} else
|
||||
{
|
||||
playerPosX = (int) world.players().get(0).getX();
|
||||
playerPosZ = (int) world.players().get(0).getZ();
|
||||
}
|
||||
if (lodWorld.getLodDimension(dim) == null)
|
||||
{
|
||||
lodDim = new LodDimension(dim, lodWorld, defaultDimensionWidthInRegions);
|
||||
lodWorld.addLodDimension(lodDim);
|
||||
lodDim.treeGenerator(playerPosX, playerPosZ);
|
||||
} else
|
||||
{
|
||||
lodDim = lodWorld.getLodDimension(dim);
|
||||
}
|
||||
generateLodNodeFromChunk(lodDim, chunk, new LodBuilderConfig(generationMode));
|
||||
} catch (IllegalArgumentException | NullPointerException e)
|
||||
{
|
||||
System.out.println("Chunk pos " + chunk.getPos());
|
||||
e.printStackTrace();
|
||||
// if the world changes while LODs are being generated
|
||||
// they will throw errors as they try to access things that no longer
|
||||
// exist.
|
||||
}
|
||||
});
|
||||
lodGenThreadPool.execute(thread);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a LodChunk for a chunk in the given world.
|
||||
*
|
||||
* @throws IllegalArgumentException thrown if either the chunk or world is null.
|
||||
*/
|
||||
public void generateLodNodeFromChunk(LodDimension lodDim, IChunk chunk) throws IllegalArgumentException
|
||||
{
|
||||
generateLodNodeFromChunk(lodDim, chunk, new LodBuilderConfig());
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a LodChunk for a chunk in the given world.
|
||||
*
|
||||
* @throws IllegalArgumentException thrown if either the chunk or world is null.
|
||||
*/
|
||||
public void generateLodNodeFromChunk(LodDimension lodDim, IChunk chunk, LodBuilderConfig config)
|
||||
throws IllegalArgumentException
|
||||
{
|
||||
|
||||
if (chunk == null)
|
||||
throw new IllegalArgumentException("generateLodFromChunk given a null chunk");
|
||||
|
||||
int startX;
|
||||
int startZ;
|
||||
int endX;
|
||||
int endZ;
|
||||
short[] color;
|
||||
short height;
|
||||
short depth;
|
||||
short[] data;
|
||||
LevelPos levelPos = new LevelPos((byte) 0, 0, 0);
|
||||
levelPos.changeParameters(LodUtil.CHUNK_DETAIL_LEVEL, chunk.getPos().x, chunk.getPos().z);
|
||||
levelPos.convert(LodUtil.REGION_DETAIL_LEVEL);
|
||||
try
|
||||
{
|
||||
byte minDetailLevel = lodDim.getRegion(levelPos).getMinDetailLevel();
|
||||
LodDetail detail = DetailDistanceUtil.getLodGenDetail(minDetailLevel);
|
||||
for (int i = 0; i < detail.dataPointLengthCount * detail.dataPointLengthCount; i++)
|
||||
{
|
||||
startX = detail.startX[i];
|
||||
startZ = detail.startZ[i];
|
||||
endX = detail.endX[i];
|
||||
endZ = detail.endZ[i];
|
||||
|
||||
color = generateLodColorForArea(chunk, config, startX, startZ, endX, endZ);
|
||||
|
||||
if (!config.useHeightmap)
|
||||
{
|
||||
height = determineHeightPointForArea(chunk.getSections(), startX, startZ, endX, endZ);
|
||||
depth = determineBottomPointForArea(chunk.getSections(), startX, startZ, endX, endZ);
|
||||
} else
|
||||
{
|
||||
height = determineHeightPoint(chunk.getOrCreateHeightmapUnprimed(LodUtil.DEFAULT_HEIGHTMAP), startX,
|
||||
startZ, endX, endZ);
|
||||
depth = 0;
|
||||
}
|
||||
levelPos.changeParameters((byte) 0,
|
||||
chunk.getPos().x * 16 + startX,
|
||||
chunk.getPos().z * 16 + startZ);
|
||||
levelPos.convert(detail.detailLevel);
|
||||
boolean isServer = config.distanceGenerationMode == DistanceGenerationMode.SERVER;
|
||||
data = DataPoint.createDataPoint(height, depth, color[0], color[1], color[2]);
|
||||
lodDim.addData(levelPos,
|
||||
data,
|
||||
false,
|
||||
isServer);
|
||||
}
|
||||
levelPos.changeParameters(LodUtil.CHUNK_DETAIL_LEVEL, chunk.getPos().x, chunk.getPos().z);
|
||||
lodDim.updateData(levelPos);
|
||||
} catch (Exception e)
|
||||
{
|
||||
//e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// =====================//
|
||||
// constructor helpers //
|
||||
// =====================//
|
||||
|
||||
/**
|
||||
* Find the lowest valid point from the bottom.
|
||||
*/
|
||||
private short determineBottomPointForArea(ChunkSection[] chunkSections, int startX, int startZ, int endX, int endZ)
|
||||
{
|
||||
int numberOfBlocksRequired = ((endX - startX) * (endZ - startZ) / 2);
|
||||
|
||||
// search from the bottom up
|
||||
for (int section = 0; section < CHUNK_DATA_WIDTH; section++)
|
||||
{
|
||||
for (int y = 0; y < CHUNK_SECTION_HEIGHT; y++)
|
||||
{
|
||||
int numberOfBlocksFound = 0;
|
||||
|
||||
for (int x = startX; x < endX; x++)
|
||||
{
|
||||
for (int z = startZ; z < endZ; z++)
|
||||
{
|
||||
if (isLayerValidLodPoint(chunkSections, section, y, x, z))
|
||||
{
|
||||
numberOfBlocksFound++;
|
||||
|
||||
if (numberOfBlocksFound >= numberOfBlocksRequired)
|
||||
{
|
||||
// we found
|
||||
// enough blocks in this
|
||||
// layer to count as an
|
||||
// LOD point
|
||||
return (short) (y + (section * CHUNK_SECTION_HEIGHT));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// we never found a valid LOD point
|
||||
return DEFAULT_DEPTH;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Find the lowest valid point from the bottom.
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
private short determineBottomPoint(Heightmap heightmap)
|
||||
{
|
||||
// the heightmap only shows how high the blocks go, it
|
||||
// doesn't have any info about how low they go
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the highest valid point from the Top
|
||||
*/
|
||||
private short determineHeightPointForArea(ChunkSection[] chunkSections, int startX, int startZ, int endX, int endZ)
|
||||
{
|
||||
int numberOfBlocksRequired = ((endX - startX) * (endZ - startZ) / 2);
|
||||
// search from the top down
|
||||
for (int section = chunkSections.length - 1; section >= 0; section--)
|
||||
{
|
||||
for (int y = CHUNK_DATA_WIDTH - 1; y >= 0; y--)
|
||||
{
|
||||
int numberOfBlocksFound = 0;
|
||||
|
||||
for (int x = startX; x < endX; x++)
|
||||
{
|
||||
for (int z = startZ; z < endZ; z++)
|
||||
{
|
||||
if (isLayerValidLodPoint(chunkSections, section, y, x, z))
|
||||
{
|
||||
numberOfBlocksFound++;
|
||||
|
||||
if (numberOfBlocksFound >= numberOfBlocksRequired)
|
||||
{
|
||||
// we found
|
||||
// enough blocks in this
|
||||
// layer to count as an
|
||||
// LOD point
|
||||
return (short) (y + 1 + (section * CHUNK_SECTION_HEIGHT));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// we never found a valid LOD point
|
||||
return DEFAULT_HEIGHT;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Find the highest point from the Top
|
||||
*/
|
||||
private short determineHeightPoint(Heightmap heightmap, int startX, int startZ, int endX, int endZ)
|
||||
{
|
||||
short highest = 0;
|
||||
for (int x = startX; x < endX; x++)
|
||||
{
|
||||
for (int z = startZ; z < endZ; z++)
|
||||
{
|
||||
short newHeight = (short) heightmap.getFirstAvailable(x, z);
|
||||
if (newHeight > highest)
|
||||
highest = newHeight;
|
||||
}
|
||||
}
|
||||
|
||||
return highest;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate the color for the given chunk using biome water color, foliage
|
||||
* color, and grass color.
|
||||
*
|
||||
* @param config_useSolidBlocksInColorGen <br>
|
||||
* If true we look down from the top of
|
||||
* the <br>
|
||||
* chunk until we find a non-invisible
|
||||
* block, and then use <br>
|
||||
* its color. If false we generate the
|
||||
* color immediately for <br>
|
||||
* each x and z.
|
||||
* @param config_useBiomeColors <br>
|
||||
* If true use biome foliage, water, and
|
||||
* grass colors, <br>
|
||||
* otherwise only use the block's
|
||||
* material color
|
||||
*/
|
||||
private short[] generateLodColorForArea(IChunk chunk, LodBuilderConfig config, int startX, int startZ, int endX,
|
||||
int endZ)
|
||||
{
|
||||
ChunkSection[] chunkSections = chunk.getSections();
|
||||
|
||||
int numbOfBlocks = 0;
|
||||
int red = 0;
|
||||
int green = 0;
|
||||
int blue = 0;
|
||||
|
||||
for (int x = startX; x < endX; x++)
|
||||
{
|
||||
for (int z = startZ; z < endZ; z++)
|
||||
{
|
||||
boolean foundBlock = false;
|
||||
|
||||
// go top down
|
||||
for (int i = chunkSections.length - 1; !foundBlock && i >= 0; i--)
|
||||
{
|
||||
if (!foundBlock && (chunkSections[i] != null || !config.useSolidBlocksInColorGen))
|
||||
{
|
||||
for (int y = CHUNK_SECTION_HEIGHT - 1; !foundBlock && y >= 0; y--)
|
||||
{
|
||||
int colorInt = 0;
|
||||
BlockState blockState = null;
|
||||
|
||||
if (chunkSections[i] != null)
|
||||
{
|
||||
blockState = chunkSections[i].getBlockState(x, y, z);
|
||||
colorInt = blockState.materialColor.col;
|
||||
}
|
||||
|
||||
if (colorInt == 0 && config.useSolidBlocksInColorGen)
|
||||
{
|
||||
// skip air or invisible blocks
|
||||
continue;
|
||||
}
|
||||
|
||||
if (config.useBiomeColors)
|
||||
{
|
||||
// I have no idea why I need to bit shift to the right, but
|
||||
// if I don't the biomes don't show up correctly.
|
||||
Biome biome = chunk.getBiomes().getNoiseBiome(x >> 2, y + 1 * chunkSections.length >> 2,
|
||||
z >> 2);
|
||||
colorInt = getColorForBiome(x, z, biome);
|
||||
} else
|
||||
{
|
||||
|
||||
// the bit shift is equivalent to dividing by 4
|
||||
Biome biome = chunk.getBiomes().getNoiseBiome(x >> 2, y + i * chunkSections.length >> 2,
|
||||
z >> 2);
|
||||
colorInt = getColorForBlock(x, z, blockState, biome);
|
||||
}
|
||||
|
||||
red += ColorUtil.getRed(colorInt);
|
||||
green += ColorUtil.getGreen(colorInt);
|
||||
blue += ColorUtil.getBlue(colorInt);
|
||||
|
||||
numbOfBlocks++;
|
||||
|
||||
// we found a valid block, skip to the
|
||||
// next x and z
|
||||
foundBlock = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (numbOfBlocks == 0)
|
||||
numbOfBlocks = 1;
|
||||
|
||||
red /= numbOfBlocks;
|
||||
green /= numbOfBlocks;
|
||||
blue /= numbOfBlocks;
|
||||
|
||||
return new short[]{(short) red, (short) green, (short) blue};
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a color int for the given block.
|
||||
*/
|
||||
private int getColorForBlock(int x, int z, BlockState blockState, Biome biome)
|
||||
{
|
||||
int colorInt = 0;
|
||||
|
||||
// block special cases
|
||||
if (blockState == Blocks.AIR.defaultBlockState() || blockState == Blocks.CAVE_AIR.defaultBlockState())
|
||||
{
|
||||
Color tmp = LodUtil.intToColor(biome.getGrassColor(x, z));
|
||||
tmp = tmp.darker();
|
||||
colorInt = LodUtil.colorToInt(tmp);
|
||||
} else if (blockState == Blocks.STONE.defaultBlockState())
|
||||
{
|
||||
colorInt = LodUtil.STONE_COLOR_INT;
|
||||
} else if (blockState == Blocks.MYCELIUM.defaultBlockState())
|
||||
{
|
||||
colorInt = LodUtil.MYCELIUM_COLOR_INT;
|
||||
}
|
||||
|
||||
// plant life
|
||||
else if (blockState.getBlock() instanceof LeavesBlock || blockState.getBlock() == Blocks.VINE)
|
||||
{
|
||||
Color leafColor = LodUtil.intToColor(biome.getFoliageColor()).darker();
|
||||
leafColor = leafColor.darker();
|
||||
colorInt = LodUtil.colorToInt(leafColor);
|
||||
} else if ((blockState.getBlock() instanceof GrassBlock || blockState.getBlock() instanceof AbstractPlantBlock
|
||||
|| blockState.getBlock() instanceof BushBlock || blockState.getBlock() instanceof IGrowable)
|
||||
&& !(blockState.getBlock() == Blocks.BROWN_MUSHROOM || blockState.getBlock() == Blocks.RED_MUSHROOM))
|
||||
{
|
||||
Color plantColor = LodUtil.intToColor(biome.getGrassColor(x, z));
|
||||
plantColor = plantColor.darker();
|
||||
colorInt = LodUtil.colorToInt(plantColor);
|
||||
}
|
||||
|
||||
// water
|
||||
else if (blockState.getBlock() == Blocks.WATER)
|
||||
{
|
||||
colorInt = biome.getWaterColor();
|
||||
}
|
||||
|
||||
// everything else
|
||||
else
|
||||
{
|
||||
colorInt = blockState.materialColor.col;
|
||||
}
|
||||
|
||||
return colorInt;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a color int for the given biome.
|
||||
*/
|
||||
private int getColorForBiome(int x, int z, Biome biome)
|
||||
{
|
||||
int colorInt = 0;
|
||||
|
||||
switch (biome.getBiomeCategory())
|
||||
{
|
||||
|
||||
case NETHER:
|
||||
colorInt = Blocks.BEDROCK.defaultBlockState().materialColor.col;
|
||||
break;
|
||||
|
||||
case THEEND:
|
||||
colorInt = Blocks.END_STONE.defaultBlockState().materialColor.col;
|
||||
break;
|
||||
|
||||
case BEACH:
|
||||
case DESERT:
|
||||
colorInt = Blocks.SAND.defaultBlockState().materialColor.col;
|
||||
break;
|
||||
|
||||
case EXTREME_HILLS:
|
||||
colorInt = Blocks.STONE.defaultMaterialColor().col;
|
||||
break;
|
||||
|
||||
case MUSHROOM:
|
||||
colorInt = MaterialColor.COLOR_LIGHT_GRAY.col;
|
||||
break;
|
||||
|
||||
case ICY:
|
||||
colorInt = Blocks.SNOW.defaultMaterialColor().col;
|
||||
break;
|
||||
|
||||
case MESA:
|
||||
colorInt = Blocks.RED_SAND.defaultMaterialColor().col;
|
||||
break;
|
||||
|
||||
case OCEAN:
|
||||
case RIVER:
|
||||
colorInt = biome.getWaterColor();
|
||||
break;
|
||||
|
||||
case NONE:
|
||||
case FOREST:
|
||||
case TAIGA:
|
||||
case JUNGLE:
|
||||
case PLAINS:
|
||||
case SAVANNA:
|
||||
case SWAMP:
|
||||
default:
|
||||
Color tmp = LodUtil.intToColor(biome.getGrassColor(x, z));
|
||||
tmp = tmp.darker();
|
||||
colorInt = LodUtil.colorToInt(tmp);
|
||||
break;
|
||||
|
||||
}
|
||||
|
||||
return colorInt;
|
||||
}
|
||||
|
||||
/**
|
||||
* Is the layer between the given X, Z, and dataIndex values a valid LOD point?
|
||||
*/
|
||||
private boolean isLayerValidLodPoint(ChunkSection[] chunkSections, int sectionIndex, int y, int x, int z)
|
||||
{
|
||||
if (chunkSections[sectionIndex] == null)
|
||||
{
|
||||
// this section doesn't have any blocks,
|
||||
// it is not a valid section
|
||||
return false;
|
||||
} else
|
||||
{
|
||||
if (chunkSections[sectionIndex].getBlockState(x, y, z) != null
|
||||
&& chunkSections[sectionIndex].getBlockState(x, y, z).getBlock() != Blocks.AIR
|
||||
&& chunkSections[sectionIndex].getBlockState(x, y, z).getBlock() != Blocks.CAVE_AIR)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
/*
|
||||
* This file is part of the LOD Mod, licensed under the GNU GPL v3 License.
|
||||
*
|
||||
* Copyright (C) 2020 James Seibel
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, version 3.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.seibel.lod.builders;
|
||||
|
||||
import com.seibel.lod.enums.DistanceGenerationMode;
|
||||
|
||||
/**
|
||||
* This is used to easily configure how LodChunks are generated.
|
||||
* Generally this will only be used if we want to generate a
|
||||
* LodChunk using a incomplete Chunk, otherwise the defaults
|
||||
* work best for a fully generated chunk (IE has correct surface blocks).
|
||||
*
|
||||
* @author James Seibel
|
||||
* @version 8-14-2021
|
||||
*/
|
||||
public class LodBuilderConfig
|
||||
{
|
||||
/**
|
||||
* default false
|
||||
*/
|
||||
public boolean useHeightmap;
|
||||
/**
|
||||
* default false
|
||||
*/
|
||||
public boolean useBiomeColors;
|
||||
/**
|
||||
* default true
|
||||
*/
|
||||
public boolean useSolidBlocksInColorGen;
|
||||
/**
|
||||
* default server
|
||||
*/
|
||||
public DistanceGenerationMode distanceGenerationMode;
|
||||
|
||||
/**
|
||||
* default settings for a normal chunk <br>
|
||||
* useHeightmap = false <br>
|
||||
* useBiomeColors = false <br>
|
||||
* useSolidBlocksInColorGen = true <br>
|
||||
* generationMode = Server <br>
|
||||
*/
|
||||
public LodBuilderConfig()
|
||||
{
|
||||
useHeightmap = false;
|
||||
useBiomeColors = false;
|
||||
useSolidBlocksInColorGen = true;
|
||||
distanceGenerationMode = DistanceGenerationMode.SERVER;
|
||||
}
|
||||
|
||||
/**
|
||||
* @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;
|
||||
distanceGenerationMode = newDistanceGenerationMode;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param newUseHeightmap default = false
|
||||
* @param newUseBiomeColors default = false
|
||||
* @param newUseSolidBlocksInBiomeColor default = true
|
||||
* @param newDistanceGenerationMode default = Server
|
||||
*/
|
||||
public LodBuilderConfig(boolean newUseHeightmap, boolean newUseBiomeColors, boolean newUseSolidBlocksInBiomeColor)
|
||||
{
|
||||
this();
|
||||
useHeightmap = newUseHeightmap;
|
||||
useBiomeColors = newUseBiomeColors;
|
||||
useSolidBlocksInColorGen = newUseSolidBlocksInBiomeColor;
|
||||
if (newUseHeightmap)
|
||||
{
|
||||
distanceGenerationMode = DistanceGenerationMode.BIOME_ONLY_SIMULATE_HEIGHT;
|
||||
} else
|
||||
{
|
||||
distanceGenerationMode = DistanceGenerationMode.BIOME_ONLY;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param newUseHeightmap default = false
|
||||
* @param newUseBiomeColors default = false
|
||||
* @param newUseSolidBlocksInBiomeColor default = true
|
||||
* @param newDistanceGenerationMode default = Server
|
||||
*/
|
||||
public LodBuilderConfig(DistanceGenerationMode newDistanceGenerationMode)
|
||||
{
|
||||
this();
|
||||
distanceGenerationMode = newDistanceGenerationMode;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
* This file is part of the LOD Mod, licensed under the GNU GPL v3 License.
|
||||
*
|
||||
* Copyright (C) 2020 James Seibel
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, version 3.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.seibel.lod.builders.lodTemplates;
|
||||
|
||||
import com.seibel.lod.enums.DebugMode;
|
||||
import com.seibel.lod.objects.LevelPos.LevelPos;
|
||||
|
||||
import net.minecraft.client.renderer.BufferBuilder;
|
||||
import net.minecraft.util.math.BlockPos;
|
||||
|
||||
/**
|
||||
* This is the abstract class used to create different
|
||||
* BufferBuilders.
|
||||
*
|
||||
* @author James Seibel
|
||||
* @version 8-8-2021
|
||||
*/
|
||||
public abstract class AbstractLodTemplate
|
||||
{
|
||||
public abstract void addLodToBuffer(BufferBuilder buffer, BlockPos playerBlockPos, short[] data, short[][][] adjData,
|
||||
LevelPos levelPos, DebugMode debugging);
|
||||
|
||||
/**
|
||||
* add the given position and color to the buffer
|
||||
*/
|
||||
protected void addPosAndColor(BufferBuilder buffer,
|
||||
double x, double y, double z,
|
||||
int red, int green, int blue, int alpha)
|
||||
{
|
||||
buffer.vertex(x, y, z).color(red, green, blue, alpha).endVertex();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns in bytes how much buffer memory is required
|
||||
* for one LOD object
|
||||
*/
|
||||
public abstract int getBufferMemoryForSingleNode();
|
||||
}
|
||||
@@ -0,0 +1,328 @@
|
||||
/*
|
||||
* This file is part of the LOD Mod, licensed under the GNU GPL v3 License.
|
||||
*
|
||||
* Copyright (C) 2020 James Seibel
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, version 3.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.seibel.lod.builders.lodTemplates;
|
||||
|
||||
import com.seibel.lod.config.LodConfig;
|
||||
import com.seibel.lod.enums.DebugMode;
|
||||
import com.seibel.lod.enums.ShadingMode;
|
||||
import com.seibel.lod.objects.DataPoint;
|
||||
import com.seibel.lod.objects.LevelPos.LevelPos;
|
||||
import com.seibel.lod.util.ColorUtil;
|
||||
import com.seibel.lod.util.LodUtil;
|
||||
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.renderer.BufferBuilder;
|
||||
import net.minecraft.util.Direction;
|
||||
import net.minecraft.util.math.AxisAlignedBB;
|
||||
import net.minecraft.util.math.BlockPos;
|
||||
|
||||
/**
|
||||
* Builds LODs as rectangular prisms.
|
||||
*
|
||||
* @author James Seibel
|
||||
* @version 8-10-2021
|
||||
*/
|
||||
public class CubicLodTemplate extends AbstractLodTemplate
|
||||
{
|
||||
private final int CULL_OFFSET = 16;
|
||||
|
||||
public CubicLodTemplate()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addLodToBuffer(BufferBuilder buffer, BlockPos playerBlockPos, short[] data, short[][][] adjData,
|
||||
LevelPos levelPos, DebugMode debugging)
|
||||
{
|
||||
AxisAlignedBB bbox;
|
||||
|
||||
int width = 1 << levelPos.detailLevel;
|
||||
|
||||
// add each LOD for the detail level
|
||||
bbox = generateBoundingBox(
|
||||
DataPoint.getHeight(data),
|
||||
DataPoint.getDepth(data),
|
||||
width,
|
||||
levelPos.posX * width,
|
||||
0,
|
||||
levelPos.posZ * width);
|
||||
|
||||
int color = DataPoint.getColor(data);
|
||||
if (debugging != DebugMode.OFF)
|
||||
{
|
||||
color = LodUtil.DEBUG_DETAIL_LEVEL_COLORS[levelPos.detailLevel].getRGB();
|
||||
}
|
||||
|
||||
if (bbox != null)
|
||||
{
|
||||
addBoundingBoxToBuffer(buffer, bbox, color, playerBlockPos, adjData);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private AxisAlignedBB generateBoundingBox(int height, int depth, int width, double xOffset, double yOffset, double zOffset)
|
||||
{
|
||||
// don't add an LOD if it is empty
|
||||
if (height == -1 && depth == -1)
|
||||
return null;
|
||||
|
||||
if (depth == height)
|
||||
{
|
||||
// if the top and bottom points are at the same height
|
||||
// render this LOD as 1 block thick
|
||||
height++;
|
||||
}
|
||||
|
||||
return new AxisAlignedBB(0, depth, 0, width, height, width).move(xOffset, yOffset, zOffset);
|
||||
}
|
||||
|
||||
private void addBoundingBoxToBuffer(BufferBuilder buffer, AxisAlignedBB bb, int c, BlockPos playerBlockPos, short[][][] adjData)
|
||||
{
|
||||
int topColor = c;
|
||||
int bottomColor = c;
|
||||
int northColor = c;
|
||||
int southColor = c;
|
||||
int westColor = c;
|
||||
int eastColor = c;
|
||||
|
||||
// darken the bottom and side colors if requested
|
||||
if (LodConfig.CLIENT.graphics.shadingMode.get() == ShadingMode.DARKEN_SIDES)
|
||||
{
|
||||
// the side colors are different because
|
||||
// when using fast lighting in Minecraft the north/south
|
||||
// and east/west sides are different in a similar way
|
||||
/**TODO OPTIMIZE THIS STEP*/
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
topColor = ColorUtil.applyShade(c, mc.level.getShade(Direction.UP, true));
|
||||
bottomColor = ColorUtil.applyShade(c, mc.level.getShade(Direction.DOWN, true));
|
||||
northColor = ColorUtil.applyShade(c, mc.level.getShade(Direction.NORTH, true));
|
||||
southColor = ColorUtil.applyShade(c, mc.level.getShade(Direction.SOUTH, true));
|
||||
westColor = ColorUtil.applyShade(c, mc.level.getShade(Direction.WEST, true));
|
||||
eastColor = ColorUtil.applyShade(c, mc.level.getShade(Direction.EAST, true));
|
||||
}
|
||||
|
||||
// apply the user specified saturation and brightness
|
||||
float saturationMultiplier = LodConfig.CLIENT.graphics.saturationMultiplier.get().floatValue();
|
||||
float brightnessMultiplier = LodConfig.CLIENT.graphics.brightnessMultiplier.get().floatValue();
|
||||
|
||||
if (saturationMultiplier != 1 || brightnessMultiplier != 1)
|
||||
{
|
||||
topColor = ColorUtil.applySaturationAndBrightnessMultipliers(topColor, saturationMultiplier, brightnessMultiplier);
|
||||
bottomColor = ColorUtil.applySaturationAndBrightnessMultipliers(bottomColor, saturationMultiplier, brightnessMultiplier);
|
||||
northColor = ColorUtil.applySaturationAndBrightnessMultipliers(northColor, saturationMultiplier, brightnessMultiplier);
|
||||
southColor = ColorUtil.applySaturationAndBrightnessMultipliers(southColor, saturationMultiplier, brightnessMultiplier);
|
||||
westColor = ColorUtil.applySaturationAndBrightnessMultipliers(westColor, saturationMultiplier, brightnessMultiplier);
|
||||
eastColor = ColorUtil.applySaturationAndBrightnessMultipliers(eastColor, saturationMultiplier, brightnessMultiplier);
|
||||
}
|
||||
int minY;
|
||||
int maxY;
|
||||
short[] data;
|
||||
|
||||
int red;
|
||||
int green;
|
||||
int blue;
|
||||
int alpha;
|
||||
boolean disableCulling = true;
|
||||
/**TODO make all of this more automatic if possible*/
|
||||
if (playerBlockPos.getY() > bb.maxY - CULL_OFFSET || disableCulling)
|
||||
{
|
||||
red = ColorUtil.getRed(topColor);
|
||||
green = ColorUtil.getGreen(topColor);
|
||||
blue = ColorUtil.getBlue(topColor);
|
||||
alpha = ColorUtil.getAlpha(topColor);
|
||||
// top (facing up)
|
||||
addPosAndColor(buffer, bb.minX, bb.maxY, bb.minZ, red, green, blue, alpha);
|
||||
addPosAndColor(buffer, bb.minX, bb.maxY, bb.maxZ, red, green, blue, alpha);
|
||||
addPosAndColor(buffer, bb.maxX, bb.maxY, bb.maxZ, red, green, blue, alpha);
|
||||
addPosAndColor(buffer, bb.maxX, bb.maxY, bb.minZ, red, green, blue, alpha);
|
||||
}
|
||||
if (playerBlockPos.getY() < bb.minY + CULL_OFFSET || disableCulling)
|
||||
{
|
||||
red = ColorUtil.getRed(bottomColor);
|
||||
green = ColorUtil.getGreen(bottomColor);
|
||||
blue = ColorUtil.getBlue(bottomColor);
|
||||
alpha = ColorUtil.getAlpha(bottomColor);
|
||||
// bottom (facing down)
|
||||
addPosAndColor(buffer, bb.maxX, bb.minY, bb.minZ, red, green, blue, alpha);
|
||||
addPosAndColor(buffer, bb.maxX, bb.minY, bb.maxZ, red, green, blue, alpha);
|
||||
addPosAndColor(buffer, bb.minX, bb.minY, bb.maxZ, red, green, blue, alpha);
|
||||
addPosAndColor(buffer, bb.minX, bb.minY, bb.minZ, red, green, blue, alpha);
|
||||
}
|
||||
|
||||
if (playerBlockPos.getZ() > bb.minZ - CULL_OFFSET || disableCulling)
|
||||
{
|
||||
red = ColorUtil.getRed(northColor);
|
||||
green = ColorUtil.getGreen(northColor);
|
||||
blue = ColorUtil.getBlue(northColor);
|
||||
alpha = ColorUtil.getAlpha(northColor);
|
||||
// south (facing -Z)
|
||||
data = adjData[1][1];
|
||||
if (data == null)
|
||||
{
|
||||
addPosAndColor(buffer, bb.maxX, bb.minY, bb.maxZ, red, green, blue, alpha);
|
||||
addPosAndColor(buffer, bb.maxX, bb.maxY, bb.maxZ, red, green, blue, alpha);
|
||||
addPosAndColor(buffer, bb.minX, bb.maxY, bb.maxZ, red, green, blue, alpha);
|
||||
addPosAndColor(buffer, bb.minX, bb.minY, bb.maxZ, red, green, blue, alpha);
|
||||
}
|
||||
else
|
||||
{
|
||||
maxY = DataPoint.getHeight(data);
|
||||
if (maxY < bb.maxY)
|
||||
{
|
||||
minY = (int) Math.max(maxY, bb.minY);
|
||||
addPosAndColor(buffer, bb.maxX, minY, bb.maxZ, red, green, blue, alpha);
|
||||
addPosAndColor(buffer, bb.maxX, bb.maxY, bb.maxZ, red, green, blue, alpha);
|
||||
addPosAndColor(buffer, bb.minX, bb.maxY, bb.maxZ, red, green, blue, alpha);
|
||||
addPosAndColor(buffer, bb.minX, minY, bb.maxZ, red, green, blue, alpha);
|
||||
}
|
||||
minY = DataPoint.getDepth(data);
|
||||
if (minY > bb.minY)
|
||||
{
|
||||
maxY = (int) Math.min(minY, bb.maxY);
|
||||
addPosAndColor(buffer, bb.maxX, bb.minY, bb.maxZ, red, green, blue, alpha);
|
||||
addPosAndColor(buffer, bb.maxX, maxY, bb.maxZ, red, green, blue, alpha);
|
||||
addPosAndColor(buffer, bb.minX, maxY, bb.maxZ, red, green, blue, alpha);
|
||||
addPosAndColor(buffer, bb.minX, bb.minY, bb.maxZ, red, green, blue, alpha);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (playerBlockPos.getZ() < bb.maxZ + CULL_OFFSET || disableCulling)
|
||||
{
|
||||
red = ColorUtil.getRed(southColor);
|
||||
green = ColorUtil.getGreen(southColor);
|
||||
blue = ColorUtil.getBlue(southColor);
|
||||
alpha = ColorUtil.getAlpha(southColor);
|
||||
data = adjData[1][0];
|
||||
// north (facing +Z)
|
||||
if (data == null)
|
||||
{
|
||||
addPosAndColor(buffer, bb.minX, bb.minY, bb.minZ, red, green, blue, alpha);
|
||||
addPosAndColor(buffer, bb.minX, bb.maxY, bb.minZ, red, green, blue, alpha);
|
||||
addPosAndColor(buffer, bb.maxX, bb.maxY, bb.minZ, red, green, blue, alpha);
|
||||
addPosAndColor(buffer, bb.maxX, bb.minY, bb.minZ, red, green, blue, alpha);
|
||||
}
|
||||
else
|
||||
{
|
||||
maxY = DataPoint.getHeight(data);
|
||||
if (maxY < bb.maxY)
|
||||
{
|
||||
minY = (int) Math.max(maxY, bb.minY);
|
||||
addPosAndColor(buffer, bb.minX, minY, bb.minZ, red, green, blue, alpha);
|
||||
addPosAndColor(buffer, bb.minX, bb.maxY, bb.minZ, red, green, blue, alpha);
|
||||
addPosAndColor(buffer, bb.maxX, bb.maxY, bb.minZ, red, green, blue, alpha);
|
||||
addPosAndColor(buffer, bb.maxX, minY, bb.minZ, red, green, blue, alpha);
|
||||
}
|
||||
minY = DataPoint.getDepth(data);
|
||||
if (minY > bb.minY)
|
||||
{
|
||||
maxY = (int) Math.min(minY, bb.maxY);
|
||||
addPosAndColor(buffer, bb.minX, bb.minY, bb.minZ, red, green, blue, alpha);
|
||||
addPosAndColor(buffer, bb.minX, maxY, bb.minZ, red, green, blue, alpha);
|
||||
addPosAndColor(buffer, bb.maxX, maxY, bb.minZ, red, green, blue, alpha);
|
||||
addPosAndColor(buffer, bb.maxX, bb.minY, bb.minZ, red, green, blue, alpha);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (playerBlockPos.getX() < bb.maxX + CULL_OFFSET || disableCulling)
|
||||
{
|
||||
red = ColorUtil.getRed(westColor);
|
||||
green = ColorUtil.getGreen(westColor);
|
||||
blue = ColorUtil.getBlue(westColor);
|
||||
alpha = ColorUtil.getAlpha(westColor);
|
||||
// west (facing -X)
|
||||
data = adjData[0][0];
|
||||
if (data == null)
|
||||
{
|
||||
addPosAndColor(buffer, bb.minX, bb.minY, bb.minZ, red, green, blue, alpha);
|
||||
addPosAndColor(buffer, bb.minX, bb.minY, bb.maxZ, red, green, blue, alpha);
|
||||
addPosAndColor(buffer, bb.minX, bb.maxY, bb.maxZ, red, green, blue, alpha);
|
||||
addPosAndColor(buffer, bb.minX, bb.maxY, bb.minZ, red, green, blue, alpha);
|
||||
}
|
||||
else
|
||||
{
|
||||
maxY = DataPoint.getHeight(data);
|
||||
if (maxY < bb.maxY)
|
||||
{
|
||||
minY = (int) Math.max(maxY, bb.minY);
|
||||
addPosAndColor(buffer, bb.minX, minY, bb.minZ, red, green, blue, alpha);
|
||||
addPosAndColor(buffer, bb.minX, minY, bb.maxZ, red, green, blue, alpha);
|
||||
addPosAndColor(buffer, bb.minX, bb.maxY, bb.maxZ, red, green, blue, alpha);
|
||||
addPosAndColor(buffer, bb.minX, bb.maxY, bb.minZ, red, green, blue, alpha);
|
||||
}
|
||||
minY = DataPoint.getDepth(data);
|
||||
if (minY > bb.minY)
|
||||
{
|
||||
maxY = (int) Math.min(minY, bb.maxY);
|
||||
addPosAndColor(buffer, bb.minX, bb.minY, bb.minZ, red, green, blue, alpha);
|
||||
addPosAndColor(buffer, bb.minX, bb.minY, bb.maxZ, red, green, blue, alpha);
|
||||
addPosAndColor(buffer, bb.minX, maxY, bb.maxZ, red, green, blue, alpha);
|
||||
addPosAndColor(buffer, bb.minX, maxY, bb.minZ, red, green, blue, alpha);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (playerBlockPos.getX() > bb.minX - CULL_OFFSET || disableCulling)
|
||||
{
|
||||
red = ColorUtil.getRed(eastColor);
|
||||
green = ColorUtil.getGreen(eastColor);
|
||||
blue = ColorUtil.getBlue(eastColor);
|
||||
alpha = ColorUtil.getAlpha(eastColor);
|
||||
// east (facing +X)
|
||||
data = adjData[0][1];
|
||||
if (data == null)
|
||||
{
|
||||
addPosAndColor(buffer, bb.maxX, bb.maxY, bb.minZ, red, green, blue, alpha);
|
||||
addPosAndColor(buffer, bb.maxX, bb.maxY, bb.maxZ, red, green, blue, alpha);
|
||||
addPosAndColor(buffer, bb.maxX, bb.minY, bb.maxZ, red, green, blue, alpha);
|
||||
addPosAndColor(buffer, bb.maxX, bb.minY, bb.minZ, red, green, blue, alpha);
|
||||
}
|
||||
else
|
||||
{
|
||||
maxY = DataPoint.getHeight(data);
|
||||
if (maxY < bb.maxY)
|
||||
{
|
||||
minY = (int) Math.max(maxY, bb.minY);
|
||||
addPosAndColor(buffer, bb.maxX, bb.maxY, bb.minZ, red, green, blue, alpha);
|
||||
addPosAndColor(buffer, bb.maxX, bb.maxY, bb.maxZ, red, green, blue, alpha);
|
||||
addPosAndColor(buffer, bb.maxX, minY, bb.maxZ, red, green, blue, alpha);
|
||||
addPosAndColor(buffer, bb.maxX, minY, bb.minZ, red, green, blue, alpha);
|
||||
}
|
||||
minY = DataPoint.getDepth(data);
|
||||
if (minY > bb.minY)
|
||||
{
|
||||
maxY = (int) Math.min(minY, bb.maxY);
|
||||
addPosAndColor(buffer, bb.maxX, maxY, bb.minZ, red, green, blue, alpha);
|
||||
addPosAndColor(buffer, bb.maxX, maxY, bb.maxZ, red, green, blue, alpha);
|
||||
addPosAndColor(buffer, bb.maxX, bb.minY, bb.maxZ, red, green, blue, alpha);
|
||||
addPosAndColor(buffer, bb.maxX, bb.minY, bb.minZ, red, green, blue, alpha);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getBufferMemoryForSingleNode()
|
||||
{
|
||||
// (sidesOnACube * pointsInASquare * (positionPoints + colorPoints)))
|
||||
return (6 * 4 * (3 + 4));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* This file is part of the LOD Mod, licensed under the GNU GPL v3 License.
|
||||
*
|
||||
* Copyright (C) 2020 James Seibel
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, version 3.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.seibel.lod.builders.lodTemplates;
|
||||
|
||||
import com.seibel.lod.enums.DebugMode;
|
||||
import com.seibel.lod.objects.LevelPos.LevelPos;
|
||||
|
||||
import net.minecraft.client.renderer.BufferBuilder;
|
||||
import net.minecraft.util.math.BlockPos;
|
||||
|
||||
/**
|
||||
* TODO DynamicLodTemplate
|
||||
* Chunks smoothly transition between
|
||||
* each other, unless a neighboring chunk
|
||||
* is at a significantly different height.
|
||||
*
|
||||
* @author James Seibel
|
||||
* @version 06-16-2021
|
||||
*/
|
||||
public class DynamicLodTemplate extends AbstractLodTemplate
|
||||
{
|
||||
@Override
|
||||
public void addLodToBuffer(BufferBuilder buffer, BlockPos playerBlockPos, short[] data, short[][][] adjData,
|
||||
LevelPos levelPos, DebugMode debugging)
|
||||
{
|
||||
System.err.println("DynamicLodTemplate not implemented!");
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getBufferMemoryForSingleNode()
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* This file is part of the LOD Mod, licensed under the GNU GPL v3 License.
|
||||
*
|
||||
* Copyright (C) 2020 James Seibel
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, version 3.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.seibel.lod.builders.lodTemplates;
|
||||
|
||||
import com.seibel.lod.enums.DebugMode;
|
||||
import com.seibel.lod.objects.LevelPos.LevelPos;
|
||||
|
||||
import net.minecraft.client.renderer.BufferBuilder;
|
||||
import net.minecraft.util.math.BlockPos;
|
||||
|
||||
/**
|
||||
* TODO #21 TriangularLodTemplate
|
||||
* Builds each LOD chunk as a singular rectangular prism.
|
||||
*
|
||||
* @author James Seibel
|
||||
* @version 06-16-2021
|
||||
*/
|
||||
public class TriangularLodTemplate extends AbstractLodTemplate
|
||||
{
|
||||
@Override
|
||||
public void addLodToBuffer(BufferBuilder buffer, BlockPos playerBlockPos, short[] data, short[][][] adjData,
|
||||
LevelPos levelPos, DebugMode debugging)
|
||||
{
|
||||
System.err.println("DynamicLodTemplate not implemented!");
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getBufferMemoryForSingleNode()
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,669 @@
|
||||
/*
|
||||
* This file is part of the LOD Mod, licensed under the GNU GPL v3 License.
|
||||
*
|
||||
* Copyright (C) 2020 James Seibel
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, version 3.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.seibel.lod.builders.worldGeneration;
|
||||
|
||||
import java.util.ConcurrentModificationException;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import com.seibel.lod.builders.LodBuilder;
|
||||
import com.seibel.lod.builders.LodBuilderConfig;
|
||||
import com.seibel.lod.config.LodConfig;
|
||||
import com.seibel.lod.enums.DistanceGenerationMode;
|
||||
import com.seibel.lod.objects.LodDimension;
|
||||
import com.seibel.lod.proxy.ClientProxy;
|
||||
import com.seibel.lod.render.LodRenderer;
|
||||
import com.seibel.lod.util.LodThreadFactory;
|
||||
import com.seibel.lod.util.LodUtil;
|
||||
|
||||
import net.minecraft.block.Block;
|
||||
import net.minecraft.block.BlockState;
|
||||
import net.minecraft.util.WeightedList.Entry;
|
||||
import net.minecraft.util.math.ChunkPos;
|
||||
import net.minecraft.util.palette.UpgradeData;
|
||||
import net.minecraft.util.registry.Registry;
|
||||
import net.minecraft.world.biome.Biome;
|
||||
import net.minecraft.world.chunk.ChunkPrimer;
|
||||
import net.minecraft.world.chunk.ChunkStatus;
|
||||
import net.minecraft.world.chunk.IChunk;
|
||||
import net.minecraft.world.gen.ChunkGenerator;
|
||||
import net.minecraft.world.gen.Heightmap;
|
||||
import net.minecraft.world.gen.blockstateprovider.WeightedBlockStateProvider;
|
||||
import net.minecraft.world.gen.feature.BlockClusterFeatureConfig;
|
||||
import net.minecraft.world.gen.feature.ConfiguredFeature;
|
||||
import net.minecraft.world.gen.feature.DecoratedFeatureConfig;
|
||||
import net.minecraft.world.gen.feature.FeatureSpread;
|
||||
import net.minecraft.world.gen.feature.FeatureSpreadConfig;
|
||||
import net.minecraft.world.gen.feature.IceAndSnowFeature;
|
||||
import net.minecraft.world.gen.feature.NoFeatureConfig;
|
||||
import net.minecraft.world.gen.placement.ConfiguredPlacement;
|
||||
import net.minecraft.world.gen.placement.DecoratedPlacementConfig;
|
||||
import net.minecraft.world.gen.placement.IPlacementConfig;
|
||||
import net.minecraft.world.gen.placement.NoiseDependant;
|
||||
import net.minecraft.world.server.ServerChunkProvider;
|
||||
import net.minecraft.world.server.ServerWorld;
|
||||
import net.minecraft.world.server.ServerWorldLightManager;
|
||||
import net.minecraftforge.common.WorldWorkerManager.IWorker;
|
||||
|
||||
/**
|
||||
* This is used to generate a LodChunk at a given ChunkPos.
|
||||
*
|
||||
* @author James Seibel
|
||||
* @version 8-26-2021
|
||||
*/
|
||||
public class LodNodeGenWorker implements IWorker
|
||||
{
|
||||
public static ExecutorService genThreads = Executors.newFixedThreadPool(LodConfig.CLIENT.threading.numberOfWorldGenerationThreads.get(), new LodThreadFactory(LodNodeGenWorker.class.getSimpleName()));
|
||||
|
||||
private boolean threadStarted = false;
|
||||
private LodChunkGenThread thread;
|
||||
|
||||
|
||||
/** If a configured feature fails for whatever reason,
|
||||
* add it to this list, this is to hopefully remove any
|
||||
* features that could cause issues down the line. */
|
||||
private static ConcurrentHashMap<Integer, ConfiguredFeature<?, ?>> configuredFeaturesToAvoid = new ConcurrentHashMap<>();
|
||||
|
||||
|
||||
|
||||
public LodNodeGenWorker(ChunkPos newPos, DistanceGenerationMode newGenerationMode, LodRenderer newLodRenderer,
|
||||
LodBuilder newLodBuilder,
|
||||
LodDimension newLodDimension, ServerWorld newServerWorld)
|
||||
{
|
||||
// just a few sanity checks
|
||||
if (newPos == null)
|
||||
throw new IllegalArgumentException("LodChunkGenWorker must have a non-null ChunkPos");
|
||||
|
||||
if (newLodRenderer == null)
|
||||
throw new IllegalArgumentException("LodChunkGenWorker must have a non-null LodRenderer");
|
||||
|
||||
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 (newServerWorld == null)
|
||||
throw new IllegalArgumentException("LodChunkGenThread requires a non-null ServerWorld");
|
||||
|
||||
|
||||
|
||||
thread = new LodChunkGenThread(newPos, newGenerationMode, newLodRenderer,
|
||||
newLodBuilder,
|
||||
newLodDimension, newServerWorld);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean doWork()
|
||||
{
|
||||
if (!threadStarted)
|
||||
{
|
||||
if (LodConfig.CLIENT.worldGenerator.distanceGenerationMode.get() == DistanceGenerationMode.SERVER)
|
||||
{
|
||||
// if we are using SERVER generation that has to be done
|
||||
// synchronously to prevent crashing and harmful
|
||||
// interactions with the normal world generator
|
||||
thread.run();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Every other method can
|
||||
// be done asynchronously
|
||||
Thread newThread = new Thread(thread);
|
||||
newThread.setPriority(3);
|
||||
genThreads.execute(newThread);
|
||||
}
|
||||
|
||||
threadStarted = true;
|
||||
|
||||
// useful for debugging
|
||||
// ClientProxy.LOGGER.info(thread.lodDim.getNumberOfLods());
|
||||
// ClientProxy.LOGGER.info(genThreads.toString());
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasWork()
|
||||
{
|
||||
return !threadStarted;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
private class LodChunkGenThread implements Runnable
|
||||
{
|
||||
public final ServerWorld serverWorld;
|
||||
public final LodDimension lodDim;
|
||||
public final DistanceGenerationMode generationMode;
|
||||
public final LodBuilder lodBuilder;
|
||||
public final LodRenderer lodRenderer;
|
||||
|
||||
private ChunkPos pos;
|
||||
|
||||
public LodChunkGenThread(ChunkPos newPos, DistanceGenerationMode newGenerationMode, LodRenderer newLodRenderer,
|
||||
LodBuilder newLodBuilder,
|
||||
LodDimension newLodDimension, ServerWorld newServerWorld)
|
||||
{
|
||||
pos = newPos;
|
||||
generationMode = newGenerationMode;
|
||||
lodRenderer = newLodRenderer;
|
||||
lodBuilder = newLodBuilder;
|
||||
lodDim = newLodDimension;
|
||||
serverWorld = newServerWorld;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
try
|
||||
{
|
||||
// only generate LodChunks if they can
|
||||
// be added to the current LodDimension
|
||||
|
||||
/**TODO i must disable this if, i will find a way to replace it*/
|
||||
if (lodDim.regionIsInRange(pos.x / LodUtil.REGION_WIDTH_IN_CHUNKS, pos.z / LodUtil.REGION_WIDTH_IN_CHUNKS))
|
||||
{
|
||||
// long startTime = System.currentTimeMillis();
|
||||
|
||||
switch(generationMode)
|
||||
{
|
||||
case NONE:
|
||||
// don't generate
|
||||
break;
|
||||
case BIOME_ONLY:
|
||||
case BIOME_ONLY_SIMULATE_HEIGHT:
|
||||
// fastest
|
||||
generateUsingBiomesOnly();
|
||||
break;
|
||||
case SURFACE:
|
||||
// faster
|
||||
generateUsingSurface();
|
||||
break;
|
||||
case FEATURES:
|
||||
// fast
|
||||
generateUsingFeatures();
|
||||
break;
|
||||
case SERVER:
|
||||
// very slow
|
||||
generateWithServer();
|
||||
break;
|
||||
}
|
||||
|
||||
//lodRenderer.regenerateLODsNextFrame();
|
||||
|
||||
/*
|
||||
boolean dataExistence = lodDim.doesDataExist(new LevelPos((byte) 3, pos.x, pos.z));
|
||||
if (dataExistence)
|
||||
ClientProxy.LOGGER.info(pos.x + " " + pos.z + " Success!");
|
||||
else
|
||||
ClientProxy.LOGGER.info(pos.x + " " + pos.z);
|
||||
*/
|
||||
// shows the pool size, active threads, queued tasks and completed tasks
|
||||
// ClientProxy.LOGGER.info(genThreads.toString());
|
||||
|
||||
// long endTime = System.currentTimeMillis();
|
||||
// System.out.println(endTime - startTime);
|
||||
|
||||
}// if in range
|
||||
else{
|
||||
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
//e.printStackTrace();
|
||||
}
|
||||
finally
|
||||
{
|
||||
// decrement how many threads are running
|
||||
LodWorldGenerator.INSTANCE.numberOfChunksWaitingToGenerate.addAndGet(-1);
|
||||
|
||||
// this position is no longer being generated
|
||||
LodWorldGenerator.INSTANCE.positionWaitingToBeGenerated.remove(pos);
|
||||
}
|
||||
|
||||
}// run
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* takes about 2-5 ms
|
||||
*/
|
||||
private void generateUsingBiomesOnly()
|
||||
{
|
||||
List<IChunk> chunkList = new LinkedList<>();
|
||||
ChunkPrimer chunk = new ChunkPrimer(pos, UpgradeData.EMPTY);
|
||||
chunkList.add(chunk);
|
||||
|
||||
ServerChunkProvider chunkSource = serverWorld.getChunkSource();
|
||||
ChunkGenerator chunkGen = chunkSource.generator;
|
||||
|
||||
// generate the terrain (this is thread safe)
|
||||
ChunkStatus.EMPTY.generate(serverWorld, chunkGen, serverWorld.getStructureManager(), (ServerWorldLightManager) serverWorld.getLightEngine(), null, chunkList);
|
||||
// override the chunk status so we can run the next generator stage
|
||||
chunk.setStatus(ChunkStatus.STRUCTURE_REFERENCES);
|
||||
chunkGen.createBiomes(serverWorld.registryAccess().registryOrThrow(Registry.BIOME_REGISTRY), chunk);
|
||||
chunk.setStatus(ChunkStatus.STRUCTURE_REFERENCES);
|
||||
|
||||
|
||||
|
||||
|
||||
// generate fake height data for this LOD
|
||||
int seaLevel = serverWorld.getSeaLevel();
|
||||
|
||||
boolean simulateHeight = generationMode == DistanceGenerationMode.BIOME_ONLY_SIMULATE_HEIGHT;
|
||||
boolean inTheEnd = false;
|
||||
|
||||
// add fake heightmap data so our LODs aren't at height 0
|
||||
Heightmap heightmap = new Heightmap(chunk, LodUtil.DEFAULT_HEIGHTMAP);
|
||||
for(int x = 0; x < LodUtil.CHUNK_WIDTH && !inTheEnd; x++)
|
||||
{
|
||||
for(int z = 0; z < LodUtil.CHUNK_WIDTH && !inTheEnd; z++)
|
||||
{
|
||||
if (simulateHeight)
|
||||
{
|
||||
// these heights are of course aren't super accurate,
|
||||
// they are just to simulate height data where there isn't any
|
||||
switch(chunk.getBiomes().getNoiseBiome(x >> 2, seaLevel >> 2, z >> 2).getBiomeCategory())
|
||||
{
|
||||
case NETHER:
|
||||
heightmap.setHeight(x, z, serverWorld.getHeight() / 2);
|
||||
break;
|
||||
|
||||
case EXTREME_HILLS:
|
||||
heightmap.setHeight(x, z, seaLevel + 30);
|
||||
break;
|
||||
case MESA:
|
||||
heightmap.setHeight(x, z, seaLevel + 20);
|
||||
break;
|
||||
case JUNGLE:
|
||||
heightmap.setHeight(x, z, seaLevel + 20);
|
||||
break;
|
||||
case BEACH:
|
||||
heightmap.setHeight(x, z, seaLevel + 5);
|
||||
break;
|
||||
case NONE:
|
||||
heightmap.setHeight(x, z, 0);
|
||||
break;
|
||||
|
||||
case OCEAN:
|
||||
case RIVER:
|
||||
heightmap.setHeight(x, z, seaLevel);
|
||||
break;
|
||||
|
||||
case THEEND:
|
||||
inTheEnd = true;
|
||||
break;
|
||||
|
||||
// DESERT
|
||||
// FOREST
|
||||
// ICY
|
||||
// MUSHROOM
|
||||
// SAVANNA
|
||||
// SWAMP
|
||||
// TAIGA
|
||||
// PLAINS
|
||||
default:
|
||||
heightmap.setHeight(x, z, seaLevel + 10);
|
||||
break;
|
||||
}// heightmap switch
|
||||
}
|
||||
else
|
||||
{
|
||||
// we aren't simulating height
|
||||
// always use sea level
|
||||
heightmap.setHeight(x, z, seaLevel);
|
||||
}
|
||||
}// z
|
||||
}// x
|
||||
|
||||
chunk.setHeightmap(LodUtil.DEFAULT_HEIGHTMAP, heightmap.getRawData());
|
||||
|
||||
|
||||
if (!inTheEnd)
|
||||
{
|
||||
lodBuilder.generateLodNodeFromChunk(lodDim, chunk, new LodBuilderConfig(true, true, false));
|
||||
}
|
||||
else
|
||||
{
|
||||
// if we are in the end, don't generate any chunks.
|
||||
// Since we don't know where the islands are, everything
|
||||
// generates the same and it looks really bad.
|
||||
lodBuilder.generateLodNodeFromChunk(lodDim, chunk, new LodBuilderConfig(true, true, false));
|
||||
}
|
||||
|
||||
|
||||
// long startTime = System.currentTimeMillis();
|
||||
// long endTime = System.currentTimeMillis();
|
||||
// System.out.println(endTime - startTime);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* takes about 10 - 20 ms
|
||||
*/
|
||||
private void generateUsingSurface()
|
||||
{
|
||||
List<IChunk> chunkList = new LinkedList<>();
|
||||
ChunkPrimer chunk = new ChunkPrimer(pos, UpgradeData.EMPTY);
|
||||
chunkList.add(chunk);
|
||||
LodServerWorld lodServerWorld = new LodServerWorld(serverWorld, chunk);
|
||||
|
||||
ServerChunkProvider chunkSource = serverWorld.getChunkSource();
|
||||
ChunkGenerator chunkGen = chunkSource.generator;
|
||||
|
||||
|
||||
// generate the terrain (this is thread safe)
|
||||
ChunkStatus.EMPTY.generate(serverWorld, chunkGen, serverWorld.getStructureManager(), (ServerWorldLightManager) serverWorld.getLightEngine(), null, chunkList);
|
||||
// override the chunk status so we can run the next generator stage
|
||||
chunk.setStatus(ChunkStatus.STRUCTURE_REFERENCES);
|
||||
chunkGen.createBiomes(serverWorld.registryAccess().registryOrThrow(Registry.BIOME_REGISTRY), chunk);
|
||||
ChunkStatus.NOISE.generate(serverWorld, chunkGen, serverWorld.getStructureManager(), (ServerWorldLightManager) serverWorld.getLightEngine(), null, chunkList);
|
||||
ChunkStatus.SURFACE.generate(serverWorld, chunkGen, serverWorld.getStructureManager(), (ServerWorldLightManager) serverWorld.getLightEngine(), null, chunkList);
|
||||
|
||||
// this feature has been proven to be thread safe
|
||||
// so we will add it
|
||||
IceAndSnowFeature snowFeature = new IceAndSnowFeature(NoFeatureConfig.CODEC);
|
||||
snowFeature.place(lodServerWorld, chunkGen, serverWorld.random, chunk.getPos().getWorldPosition(), null);
|
||||
|
||||
lodBuilder.generateLodNodeFromChunk(lodDim, chunk, new LodBuilderConfig(DistanceGenerationMode.SURFACE));
|
||||
|
||||
/**TODO if we want to use Biome utils and terrain utils for overworld
|
||||
* lodBuilder.generateLodNodeFromChunk(lodDim, pos ,detailLevel, serverWorld.getSeed());*/
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* takes about 15 - 20 ms
|
||||
*
|
||||
* Causes concurrentModification Exceptions,
|
||||
* which could cause instability or world generation bugs
|
||||
*/
|
||||
private void generateUsingFeatures()
|
||||
{
|
||||
List<IChunk> chunkList = new LinkedList<>();
|
||||
ChunkPrimer chunk = new ChunkPrimer(pos, UpgradeData.EMPTY);
|
||||
chunkList.add(chunk);
|
||||
LodServerWorld lodServerWorld = new LodServerWorld(serverWorld, chunk);
|
||||
|
||||
ServerChunkProvider chunkSource = serverWorld.getChunkSource();
|
||||
ChunkGenerator chunkGen = chunkSource.generator;
|
||||
|
||||
|
||||
// generate the terrain (this is thread safe)
|
||||
ChunkStatus.EMPTY.generate(serverWorld, chunkGen, serverWorld.getStructureManager(), (ServerWorldLightManager) serverWorld.getLightEngine(), null, chunkList);
|
||||
// override the chunk status so we can run the next generator stage
|
||||
chunk.setStatus(ChunkStatus.STRUCTURE_REFERENCES);
|
||||
chunkGen.createBiomes(serverWorld.registryAccess().registryOrThrow(Registry.BIOME_REGISTRY), chunk);
|
||||
ChunkStatus.NOISE.generate(serverWorld, chunkGen, serverWorld.getStructureManager(), (ServerWorldLightManager) serverWorld.getLightEngine(), null, chunkList);
|
||||
ChunkStatus.SURFACE.generate(serverWorld, chunkGen, serverWorld.getStructureManager(), (ServerWorldLightManager) serverWorld.getLightEngine(), null, chunkList);
|
||||
|
||||
|
||||
// get all the biomes in the chunk
|
||||
HashSet<Biome> biomes = new HashSet<>();
|
||||
for (int x = 0; x < LodUtil.CHUNK_WIDTH; x++)
|
||||
{
|
||||
for (int z = 0; z < LodUtil.CHUNK_WIDTH; z++)
|
||||
{
|
||||
Biome biome = chunk.getBiomes().getNoiseBiome(x >> 2, serverWorld.getSeaLevel() >> 2, z >> 2);
|
||||
|
||||
// Issue #35
|
||||
// For some reason Jungle biomes cause incredible lag
|
||||
// the features here must be interacting with each other
|
||||
// in unpredictable ways (specifically tree feature generation).
|
||||
// When generating Features my CPU usage generally hovers around 30 - 40%
|
||||
// when generating Jungles it spikes to 100%.
|
||||
if (biome.getBiomeCategory() != Biome.Category.JUNGLE)
|
||||
{
|
||||
// should probably use the heightmap here instead of seaLevel,
|
||||
// but this seems to get the job done well enough
|
||||
biomes.add(biome);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
boolean allowUnstableFeatures = LodConfig.CLIENT.worldGenerator.allowUnstableFeatureGeneration.get();
|
||||
|
||||
// generate all the features related to this chunk.
|
||||
// this may or may not be thread safe
|
||||
for (Biome biome : biomes)
|
||||
{
|
||||
List<List<Supplier<ConfiguredFeature<?, ?>>>> featuresForState = biome.generationSettings.features();
|
||||
|
||||
for(int featureStateToGenerate = 0; featureStateToGenerate < featuresForState.size(); featureStateToGenerate++)
|
||||
{
|
||||
for(Supplier<ConfiguredFeature<?, ?>> featureSupplier : featuresForState.get(featureStateToGenerate))
|
||||
{
|
||||
ConfiguredFeature<?, ?> configuredFeature = featureSupplier.get();
|
||||
|
||||
if (!allowUnstableFeatures &&
|
||||
configuredFeaturesToAvoid.containsKey(configuredFeature.hashCode()))
|
||||
continue;
|
||||
|
||||
|
||||
try
|
||||
{
|
||||
configuredFeature.place(lodServerWorld, chunkGen, serverWorld.random, chunk.getPos().getWorldPosition());
|
||||
}
|
||||
catch(ConcurrentModificationException e)
|
||||
{
|
||||
// This will happen. I'm not sure what to do about it
|
||||
// except pray that it doesn't effect the normal world generation
|
||||
// in any harmful way.
|
||||
// Update: this can cause crashes and high CPU usage.
|
||||
|
||||
// Issue #35
|
||||
// I tried cloning the config for each feature, but that
|
||||
// path was blocked since I can't clone lambda methods.
|
||||
// I tried using a deep cloning library and discovered
|
||||
// the problem there.
|
||||
// ( https://github.com/kostaskougios/cloning
|
||||
// and
|
||||
// https://github.com/EsotericSoftware/kryo )
|
||||
|
||||
if (!allowUnstableFeatures)
|
||||
configuredFeaturesToAvoid.put(configuredFeature.hashCode(), configuredFeature);
|
||||
// ClientProxy.LOGGER.info(configuredFeaturesToAvoid.mappingCount());
|
||||
}
|
||||
catch(UnsupportedOperationException e)
|
||||
{
|
||||
// This will happen when the LodServerWorld
|
||||
// isn't able to return something that a feature
|
||||
// generator needs
|
||||
|
||||
if (!allowUnstableFeatures)
|
||||
configuredFeaturesToAvoid.put(configuredFeature.hashCode(), configuredFeature);
|
||||
// ClientProxy.LOGGER.info(configuredFeaturesToAvoid.mappingCount());
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
// I'm not sure what happened, print to the log
|
||||
|
||||
System.out.println();
|
||||
System.out.println();
|
||||
e.printStackTrace();
|
||||
System.out.println();
|
||||
System.out.println();
|
||||
|
||||
if (!allowUnstableFeatures)
|
||||
configuredFeaturesToAvoid.put(configuredFeature.hashCode(), configuredFeature);
|
||||
// ClientProxy.LOGGER.info(configuredFeaturesToAvoid.mappingCount());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// generate a Lod like normal
|
||||
|
||||
lodBuilder.generateLodNodeFromChunk(lodDim, chunk, new LodBuilderConfig(DistanceGenerationMode.FEATURES));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* on pre generated chunks 0 - 1 ms
|
||||
* on un generated chunks 0 - 50 ms
|
||||
* with the median seeming to hover around 15 - 30 ms
|
||||
* and outliers in the 100 - 200 ms range
|
||||
*
|
||||
* Note this should not be multithreaded and does cause server/simulation lag
|
||||
* (Higher lag for generating than loading)
|
||||
*/
|
||||
private void generateWithServer()
|
||||
{
|
||||
lodBuilder.generateLodNodeAsync(serverWorld.getChunk(pos.x, pos.z, ChunkStatus.FEATURES), ClientProxy.getLodWorld(), serverWorld);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
//================//
|
||||
// Unused methods //
|
||||
//================//
|
||||
|
||||
// Sadly I wasn't able to get these to work,
|
||||
// they are here for documentation purposes
|
||||
|
||||
@SuppressWarnings({ "rawtypes", "unchecked", "unused" })
|
||||
private DecoratedFeatureConfig cloneDecoratedFeatureConfig(DecoratedFeatureConfig config)
|
||||
{
|
||||
IPlacementConfig placementConfig = null;
|
||||
|
||||
Class oldConfigClass = config.decorator.config().getClass();
|
||||
|
||||
if (oldConfigClass == FeatureSpreadConfig.class)
|
||||
{
|
||||
FeatureSpreadConfig oldPlacementConfig = (FeatureSpreadConfig) config.decorator.config();
|
||||
FeatureSpread oldSpread = oldPlacementConfig.count();
|
||||
|
||||
placementConfig = new FeatureSpreadConfig(oldSpread);
|
||||
}
|
||||
else if(oldConfigClass == DecoratedPlacementConfig.class)
|
||||
{
|
||||
DecoratedPlacementConfig oldPlacementConfig = (DecoratedPlacementConfig) config.decorator.config();
|
||||
placementConfig = new DecoratedPlacementConfig(oldPlacementConfig.inner(), oldPlacementConfig.outer());
|
||||
}
|
||||
else if(oldConfigClass == NoiseDependant.class)
|
||||
{
|
||||
NoiseDependant oldPlacementConfig = (NoiseDependant) config.decorator.config();
|
||||
placementConfig = new NoiseDependant(oldPlacementConfig.noiseLevel, oldPlacementConfig.belowNoise, oldPlacementConfig.aboveNoise);
|
||||
}
|
||||
else
|
||||
{
|
||||
// ClientProxy.LOGGER.debug("unkown decorated placement config: \"" + config.decorator.config().getClass() + "\"");
|
||||
return config;
|
||||
}
|
||||
|
||||
|
||||
ConfiguredPlacement<?> newPlacement = new ConfiguredPlacement(config.decorator.decorator, placementConfig);
|
||||
return new DecoratedFeatureConfig(config.feature, newPlacement);
|
||||
}
|
||||
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private BlockClusterFeatureConfig cloneBlockClusterFeatureConfig(BlockClusterFeatureConfig config)
|
||||
{
|
||||
WeightedBlockStateProvider provider = new WeightedBlockStateProvider();
|
||||
for(Entry<BlockState> state : ((WeightedBlockStateProvider) config.stateProvider).weightedList.entries)
|
||||
provider.weightedList.entries.add(state);
|
||||
|
||||
HashSet<Block> whitelist = new HashSet<>();
|
||||
for(Block block : config.whitelist)
|
||||
whitelist.add(block);
|
||||
|
||||
HashSet<BlockState> blacklist = new HashSet<>();
|
||||
for(BlockState state : config.blacklist)
|
||||
blacklist.add(state);
|
||||
|
||||
|
||||
BlockClusterFeatureConfig.Builder builder = new BlockClusterFeatureConfig.Builder(provider, config.blockPlacer);
|
||||
builder.whitelist(whitelist);
|
||||
builder.blacklist(blacklist);
|
||||
builder.xspread(config.xspread);
|
||||
builder.yspread(config.yspread);
|
||||
builder.zspread(config.zspread);
|
||||
if(config.canReplace) { builder.canReplace(); }
|
||||
if(config.needWater) { builder.needWater(); }
|
||||
if(config.project) { builder.noProjection(); }
|
||||
builder.tries(config.tries);
|
||||
|
||||
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Stops the current genThreads if they are running
|
||||
* and then recreates the Executer service. <br><br>
|
||||
*
|
||||
* This is done to clear any outstanding tasks
|
||||
* that may exist after the player leaves their current world.
|
||||
* If this isn't done unfinished tasks may be left in the queue
|
||||
* preventing new LodChunks form being generated.
|
||||
*/
|
||||
public static void restartExecuterService()
|
||||
{
|
||||
if (genThreads != null && !genThreads.isShutdown())
|
||||
{
|
||||
genThreads.shutdownNow();
|
||||
}
|
||||
genThreads = Executors.newFixedThreadPool(LodConfig.CLIENT.threading.numberOfWorldGenerationThreads.get(), new LodThreadFactory(LodNodeGenWorker.class.getSimpleName()));
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/*
|
||||
* performance/generation tests related to
|
||||
* serverWorld.getChunk(x, z, ChunkStatus. *** )
|
||||
|
||||
true/false is whether they generated blocks or not
|
||||
the time is how long it took to generate
|
||||
|
||||
ChunkStatus.EMPTY 0 - 1 ms false (empty, what did you expect? :P)
|
||||
ChunkStatus.STRUCTURE_REFERENCES 1 - 2 ms false (no height, only generates some chunks)
|
||||
ChunkStatus.BIOMES 1 - 10 ms false (no height)
|
||||
ChunkStatus.NOISE 4 - 15 ms true (all blocks are stone)
|
||||
ChunkStatus.LIQUID_CARVERS 6 - 12 ms true (no snow/trees, just grass)
|
||||
ChunkStatus.SURFACE 5 - 15 ms true (no snow/trees, just grass)
|
||||
ChunkStatus.CARVERS 5 - 30 ms true (no snow/trees, just grass)
|
||||
ChunkStatus.FEATURES 7 - 25 ms true
|
||||
ChunkStatus.HEIGHTMAPS 20 - 40 ms true
|
||||
ChunkStatus.LIGHT 20 - 40 ms true
|
||||
ChunkStatus.FULL 30 - 50 ms true
|
||||
ChunkStatus.SPAWN 50 - 80 ms true
|
||||
|
||||
|
||||
At this point I would suggest using FEATURES, as it generates snow and trees
|
||||
(and any other object that is needed to make biomes distinct)
|
||||
|
||||
Otherwise if snow/trees aren't necessary SURFACE is the next fastest (although not by much)
|
||||
*/
|
||||
}
|
||||
@@ -0,0 +1,330 @@
|
||||
/*
|
||||
* This file is part of the LOD Mod, licensed under the GNU GPL v3 License.
|
||||
*
|
||||
* Copyright (C) 2020 James Seibel
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, version 3.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.seibel.lod.builders.worldGeneration;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import com.seibel.lod.util.LodUtil;
|
||||
|
||||
import net.minecraft.block.Block;
|
||||
import net.minecraft.block.BlockState;
|
||||
import net.minecraft.entity.Entity;
|
||||
import net.minecraft.entity.player.PlayerEntity;
|
||||
import net.minecraft.fluid.Fluid;
|
||||
import net.minecraft.fluid.FluidState;
|
||||
import net.minecraft.particles.IParticleData;
|
||||
import net.minecraft.tileentity.TileEntity;
|
||||
import net.minecraft.util.Direction;
|
||||
import net.minecraft.util.SoundCategory;
|
||||
import net.minecraft.util.SoundEvent;
|
||||
import net.minecraft.util.math.AxisAlignedBB;
|
||||
import net.minecraft.util.math.BlockPos;
|
||||
import net.minecraft.util.math.SectionPos;
|
||||
import net.minecraft.util.registry.DynamicRegistries;
|
||||
import net.minecraft.world.DifficultyInstance;
|
||||
import net.minecraft.world.DimensionType;
|
||||
import net.minecraft.world.EmptyTickList;
|
||||
import net.minecraft.world.ISeedReader;
|
||||
import net.minecraft.world.ITickList;
|
||||
import net.minecraft.world.biome.Biome;
|
||||
import net.minecraft.world.biome.BiomeManager;
|
||||
import net.minecraft.world.border.WorldBorder;
|
||||
import net.minecraft.world.chunk.AbstractChunkProvider;
|
||||
import net.minecraft.world.chunk.ChunkStatus;
|
||||
import net.minecraft.world.chunk.IChunk;
|
||||
import net.minecraft.world.gen.Heightmap;
|
||||
import net.minecraft.world.gen.Heightmap.Type;
|
||||
import net.minecraft.world.gen.feature.structure.Structure;
|
||||
import net.minecraft.world.gen.feature.structure.StructureStart;
|
||||
import net.minecraft.world.lighting.WorldLightManager;
|
||||
import net.minecraft.world.server.ServerWorld;
|
||||
import net.minecraft.world.storage.IWorldInfo;
|
||||
|
||||
|
||||
/**
|
||||
* This is a fake ServerWorld used when generating features.
|
||||
* This allows us to keep each LodChunk generation independent
|
||||
* of the actual ServerWorld, allowing us
|
||||
* to multithread generation.
|
||||
*
|
||||
* @author James Seibel
|
||||
* @version 7-26-2021
|
||||
*/
|
||||
public class LodServerWorld implements ISeedReader
|
||||
{
|
||||
|
||||
public HashMap<Heightmap.Type, Heightmap> heightmaps = new HashMap<>();
|
||||
|
||||
public IChunk chunk;
|
||||
|
||||
public ServerWorld serverWorld;
|
||||
|
||||
public LodServerWorld(ServerWorld newServerWorld, IChunk newChunk)
|
||||
{
|
||||
chunk = newChunk;
|
||||
serverWorld = newServerWorld;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public int getHeight(Type heightmapType, int x, int z)
|
||||
{
|
||||
// make sure the block position is set relative to the chunk
|
||||
x = x % LodUtil.CHUNK_WIDTH;
|
||||
x = (x < 0) ? x + 16 : x;
|
||||
|
||||
z = z % LodUtil.CHUNK_WIDTH;
|
||||
z = (z < 0) ? z + 16 : z;
|
||||
|
||||
return chunk.getOrCreateHeightmapUnprimed(LodUtil.DEFAULT_HEIGHTMAP).getFirstAvailable(x, z);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Biome getBiome(BlockPos pos)
|
||||
{
|
||||
return chunk.getBiomes().getNoiseBiome(pos.getX() >> 2, pos.getY() >> 2, pos.getZ() >> 2);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean setBlock(BlockPos pos, BlockState state, int flags, int recursionLeft)
|
||||
{
|
||||
return chunk.setBlockState(pos, state, false) == state;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BlockState getBlockState(BlockPos pos)
|
||||
{
|
||||
return chunk.getBlockState(pos);
|
||||
}
|
||||
|
||||
@Override
|
||||
public FluidState getFluidState(BlockPos pos)
|
||||
{
|
||||
return chunk.getFluidState(pos);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean isStateAtPosition(BlockPos pos, Predicate<BlockState> state)
|
||||
{
|
||||
return state.test(chunk.getBlockState(pos));
|
||||
}
|
||||
|
||||
@Override
|
||||
public ITickList<Block> getBlockTicks()
|
||||
{
|
||||
return EmptyTickList.empty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public IChunk getChunk(int x, int z, ChunkStatus requiredStatus, boolean nonnull)
|
||||
{
|
||||
return chunk;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream<? extends StructureStart<?>> startsForFeature(SectionPos p_241827_1_, Structure<?> p_241827_2_)
|
||||
{
|
||||
return serverWorld.startsForFeature(p_241827_1_, p_241827_2_);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ITickList<Fluid> getLiquidTicks()
|
||||
{
|
||||
return EmptyTickList.empty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public WorldLightManager getLightEngine()
|
||||
{
|
||||
return new WorldLightManager(null, false, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getSeed()
|
||||
{
|
||||
return serverWorld.getSeed();
|
||||
}
|
||||
|
||||
@Override
|
||||
public DynamicRegistries registryAccess()
|
||||
{
|
||||
return serverWorld.registryAccess();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* All methods below shouldn't be needed
|
||||
* and thus have been left unimplemented.
|
||||
*/
|
||||
|
||||
|
||||
@Override
|
||||
public Random getRandom()
|
||||
{
|
||||
throw new UnsupportedOperationException("Not Implemented");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void playSound(PlayerEntity player, BlockPos pos, SoundEvent soundIn, SoundCategory category, float volume,
|
||||
float pitch)
|
||||
{
|
||||
throw new UnsupportedOperationException("Not Implemented");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addParticle(IParticleData particleData, double x, double y, double z, double xSpeed, double ySpeed,
|
||||
double zSpeed)
|
||||
{
|
||||
throw new UnsupportedOperationException("Not Implemented");
|
||||
}
|
||||
|
||||
@Override
|
||||
public BiomeManager getBiomeManager()
|
||||
{
|
||||
throw new UnsupportedOperationException("Not Implemented");
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getSeaLevel()
|
||||
{
|
||||
throw new UnsupportedOperationException("Not Implemented");
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getShade(Direction p_230487_1_, boolean p_230487_2_)
|
||||
{
|
||||
throw new UnsupportedOperationException("Not Implemented");
|
||||
}
|
||||
|
||||
@Override
|
||||
public WorldBorder getWorldBorder()
|
||||
{
|
||||
throw new UnsupportedOperationException("Not Implemented");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean removeBlock(BlockPos pos, boolean isMoving)
|
||||
{
|
||||
throw new UnsupportedOperationException("Not Implemented");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean destroyBlock(BlockPos pos, boolean dropBlock, Entity entity, int recursionLeft)
|
||||
{
|
||||
throw new UnsupportedOperationException("Not Implemented");
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public ServerWorld getLevel()
|
||||
{
|
||||
throw new UnsupportedOperationException("Not Implemented");
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public AbstractChunkProvider getChunkSource()
|
||||
{
|
||||
throw new UnsupportedOperationException("Not Implemented");
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public DifficultyInstance getCurrentDifficultyAt(BlockPos arg0)
|
||||
{
|
||||
throw new UnsupportedOperationException("Not Implemented");
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public IWorldInfo getLevelData()
|
||||
{
|
||||
throw new UnsupportedOperationException("Not Implemented");
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void levelEvent(PlayerEntity arg0, int arg1, BlockPos arg2, int arg3)
|
||||
{
|
||||
throw new UnsupportedOperationException("Not Implemented");
|
||||
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public List<Entity> getEntities(Entity arg0, AxisAlignedBB arg1, Predicate<? super Entity> arg2)
|
||||
{
|
||||
throw new UnsupportedOperationException("Not Implemented");
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public <T extends Entity> List<T> getEntitiesOfClass(Class<? extends T> arg0, AxisAlignedBB arg1,
|
||||
Predicate<? super T> arg2)
|
||||
{
|
||||
throw new UnsupportedOperationException("Not Implemented");
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public List<? extends PlayerEntity> players()
|
||||
{
|
||||
throw new UnsupportedOperationException("Not Implemented");
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public int getSkyDarken()
|
||||
{
|
||||
throw new UnsupportedOperationException("Not Implemented");
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Biome getUncachedNoiseBiome(int p_225604_1_, int p_225604_2_, int p_225604_3_)
|
||||
{
|
||||
throw new UnsupportedOperationException("Not Implemented");
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean isClientSide()
|
||||
{
|
||||
throw new UnsupportedOperationException("Not Implemented");
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public DimensionType dimensionType()
|
||||
{
|
||||
throw new UnsupportedOperationException("Not Implemented");
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public TileEntity getBlockEntity(BlockPos p_175625_1_)
|
||||
{
|
||||
throw new UnsupportedOperationException("Not Implemented");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,262 @@
|
||||
package com.seibel.lod.builders.worldGeneration;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.SortedSet;
|
||||
import java.util.TreeSet;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import org.apache.commons.lang3.mutable.MutableBoolean;
|
||||
|
||||
import com.seibel.lod.builders.GenerationRequest;
|
||||
import com.seibel.lod.builders.LodBuilder;
|
||||
import com.seibel.lod.config.LodConfig;
|
||||
import com.seibel.lod.enums.DistanceGenerationMode;
|
||||
import com.seibel.lod.objects.LodDimension;
|
||||
import com.seibel.lod.objects.LevelPos.LevelPos;
|
||||
import com.seibel.lod.render.LodRenderer;
|
||||
import com.seibel.lod.util.DetailDistanceUtil;
|
||||
import com.seibel.lod.util.LodThreadFactory;
|
||||
import com.seibel.lod.util.LodUtil;
|
||||
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.util.math.BlockPos;
|
||||
import net.minecraft.util.math.ChunkPos;
|
||||
import net.minecraft.world.server.ServerWorld;
|
||||
import net.minecraftforge.common.WorldWorkerManager;
|
||||
|
||||
/**
|
||||
* A singleton that handles all long distance LOD world generation.
|
||||
*
|
||||
* @author James Seibel
|
||||
* @version 8-24-2021
|
||||
*/
|
||||
public class LodWorldGenerator
|
||||
{
|
||||
public Minecraft mc = Minecraft.getInstance();
|
||||
|
||||
/**
|
||||
* This holds the thread used to generate new LODs off the main thread.
|
||||
*/
|
||||
private ExecutorService mainGenThread = Executors.newSingleThreadExecutor(new LodThreadFactory(this.getClass().getSimpleName() + " world generator"));
|
||||
|
||||
/**
|
||||
* we only want to queue up one generator thread at a time
|
||||
*/
|
||||
private boolean generatorThreadRunning = false;
|
||||
|
||||
/**
|
||||
* how many chunks to generate outside of the player's view distance at one
|
||||
* time. (or more specifically how many requests to make at one time). I
|
||||
* multiply by 8 to make sure there is always a buffer of chunk requests, to
|
||||
* make sure the CPU is always busy and we can generate LODs as quickly as
|
||||
* possible.
|
||||
*/
|
||||
public int maxChunkGenRequests;
|
||||
|
||||
/**
|
||||
* This keeps track of how many chunk generation requests are on going. This is
|
||||
* to limit how many chunks are queued at once. To prevent chunks from being
|
||||
* generated for a long time in an area the player is no longer in.
|
||||
*/
|
||||
public AtomicInteger numberOfChunksWaitingToGenerate = new AtomicInteger(0);
|
||||
|
||||
public Set<ChunkPos> positionWaitingToBeGenerated = new HashSet<>();
|
||||
|
||||
/**
|
||||
* Singleton copy of this object
|
||||
*/
|
||||
public static final LodWorldGenerator INSTANCE = new LodWorldGenerator();
|
||||
|
||||
public volatile ConcurrentMap<LevelPos, MutableBoolean> nodeToGenerate;
|
||||
|
||||
SortedSet<LevelPos> nodeToGenerateListNear;
|
||||
SortedSet<LevelPos> nodeToGenerateListFar;
|
||||
|
||||
private LodWorldGenerator()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Queues up LodNodeGenWorkers for the given lodDimension.
|
||||
*
|
||||
* @param renderer needed so the LodNodeGenWorkers can flag that the
|
||||
* buffers need to be rebuilt.
|
||||
*/
|
||||
public void queueGenerationRequests(LodDimension lodDim, LodRenderer renderer, LodBuilder lodBuilder)
|
||||
{
|
||||
if (LodConfig.CLIENT.worldGenerator.distanceGenerationMode.get() != DistanceGenerationMode.NONE
|
||||
&& !generatorThreadRunning
|
||||
&& mc.hasSingleplayerServer())
|
||||
{
|
||||
// the thread is now running, don't queue up another thread
|
||||
generatorThreadRunning = true;
|
||||
|
||||
// just in case the config changed
|
||||
maxChunkGenRequests = LodConfig.CLIENT.threading.numberOfWorldGenerationThreads.get() * 8;
|
||||
|
||||
Thread generatorThread = new Thread(() ->
|
||||
{
|
||||
try
|
||||
{
|
||||
// round the player's block position down to the nearest chunk BlockPos
|
||||
ChunkPos playerChunkPos = new ChunkPos(mc.player.blockPosition());
|
||||
BlockPos playerBlockPosRounded = playerChunkPos.getWorldPosition();
|
||||
|
||||
// used when determining which chunks are closer when queuing distance
|
||||
// generation
|
||||
int minChunkDist = Integer.MAX_VALUE;
|
||||
|
||||
ArrayList<GenerationRequest> chunksToGen = new ArrayList<>(maxChunkGenRequests);
|
||||
// if we don't have a full number of chunks to generate in chunksToGen
|
||||
// we can top it off from this reserve
|
||||
|
||||
|
||||
//=======================================//
|
||||
// create the generation Request objects //
|
||||
//=======================================//
|
||||
List<GenerationRequest> generationRequestList = new ArrayList<>(maxChunkGenRequests);
|
||||
|
||||
if (nodeToGenerate == null)
|
||||
nodeToGenerate = new ConcurrentHashMap<>();
|
||||
|
||||
|
||||
Comparator<LevelPos> posNearComparator = LevelPos.getPosComparator(
|
||||
playerBlockPosRounded.getX(),
|
||||
playerBlockPosRounded.getZ());
|
||||
Comparator<LevelPos> posFarComparator = LevelPos.getPosAndDetailComparator(
|
||||
playerBlockPosRounded.getX(),
|
||||
playerBlockPosRounded.getZ());
|
||||
nodeToGenerateListNear = new TreeSet(posNearComparator);
|
||||
nodeToGenerateListFar = new TreeSet(posFarComparator);
|
||||
|
||||
lodDim.getDataToGenerate(
|
||||
nodeToGenerate,
|
||||
playerBlockPosRounded.getX(),
|
||||
playerBlockPosRounded.getZ());
|
||||
|
||||
|
||||
//here we prepare two sorted set
|
||||
//the first contains the near pos to render
|
||||
//the second contain the far pos to render
|
||||
byte farDetail = (byte) 7;
|
||||
for (LevelPos pos : nodeToGenerate.keySet())
|
||||
{
|
||||
if (!nodeToGenerate.get(pos).booleanValue())
|
||||
{
|
||||
nodeToGenerate.remove(pos);
|
||||
} else
|
||||
{
|
||||
if (pos.detailLevel > farDetail){
|
||||
nodeToGenerateListFar.add(pos);
|
||||
}
|
||||
nodeToGenerateListNear.add(pos);
|
||||
nodeToGenerate.get(pos).setFalse();
|
||||
}
|
||||
}
|
||||
|
||||
int maxDistance;
|
||||
byte circle;
|
||||
LevelPos levelPos;
|
||||
int requesting = maxChunkGenRequests;
|
||||
int requestingFar = maxChunkGenRequests / 4;
|
||||
while (requesting > 0 && !nodeToGenerateListNear.isEmpty())
|
||||
{
|
||||
levelPos = nodeToGenerateListNear.first();
|
||||
//.out.println(levelPos);
|
||||
nodeToGenerate.remove(levelPos);
|
||||
nodeToGenerateListNear.remove(levelPos);
|
||||
nodeToGenerateListFar.remove(levelPos);
|
||||
|
||||
//maxDistance = levelPos.maxDistance(
|
||||
// playerBlockPosRounded.getX(),
|
||||
// playerBlockPosRounded.getZ());
|
||||
//circle = DetailDistanceUtil.getDistanceGenerationInverse(maxDistance);
|
||||
generationRequestList.add(new GenerationRequest(levelPos, DetailDistanceUtil.getDistanceGenerationMode(levelPos.detailLevel)));
|
||||
requesting--;
|
||||
if (requestingFar > 0 && !nodeToGenerateListFar.isEmpty())
|
||||
{
|
||||
levelPos = nodeToGenerateListFar.first();
|
||||
nodeToGenerate.remove(levelPos);
|
||||
nodeToGenerateListNear.remove(levelPos);
|
||||
nodeToGenerateListFar.remove(levelPos);
|
||||
if (levelPos.detailLevel >= farDetail)
|
||||
{
|
||||
//maxDistance = levelPos.maxDistance( playerBlockPosRounded.getX(), playerBlockPosRounded.getZ());
|
||||
//circle = DetailDistanceUtil.getDistanceGenerationInverse(maxDistance);
|
||||
generationRequestList.add(new GenerationRequest(levelPos, DetailDistanceUtil.getDistanceGenerationMode(levelPos.detailLevel)));
|
||||
requestingFar--;
|
||||
requesting--;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//====================================//
|
||||
// get the closet generation requests //
|
||||
//====================================//
|
||||
|
||||
// determine which points in the posListToGenerate
|
||||
// should actually be queued to generate
|
||||
for (GenerationRequest generationRequest : generationRequestList)
|
||||
{
|
||||
ChunkPos chunkPos = generationRequest.getChunkPos();
|
||||
if (numberOfChunksWaitingToGenerate.get() < maxChunkGenRequests)
|
||||
{
|
||||
// prevent generating the same chunk multiple times
|
||||
if (positionWaitingToBeGenerated.contains(chunkPos))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
chunksToGen.add(generationRequest);
|
||||
|
||||
} // lod null and can generate more chunks
|
||||
} // positions to generate
|
||||
|
||||
|
||||
//=============================//
|
||||
// start the LodNodeGenWorkers //
|
||||
//=============================//
|
||||
|
||||
// issue #19
|
||||
// TODO add a way for a server side mod to generate chunks requested here
|
||||
ServerWorld serverWorld = LodUtil.getServerWorldFromDimension(lodDim.dimension);
|
||||
|
||||
// start chunk generation
|
||||
for (GenerationRequest generationRequest : generationRequestList)
|
||||
{
|
||||
// don't add null chunkPos (which shouldn't happen anyway)
|
||||
// or add more to the generation queue
|
||||
ChunkPos chunkPos = generationRequest.getChunkPos();
|
||||
if (chunkPos == null || numberOfChunksWaitingToGenerate.get() >= maxChunkGenRequests)
|
||||
continue;
|
||||
|
||||
positionWaitingToBeGenerated.add(chunkPos);
|
||||
numberOfChunksWaitingToGenerate.addAndGet(1);
|
||||
LodNodeGenWorker genWorker = new LodNodeGenWorker(chunkPos, generationRequest.generationMode, renderer, lodBuilder, lodDim, serverWorld);
|
||||
WorldWorkerManager.addWorker(genWorker);
|
||||
}
|
||||
|
||||
} catch (Exception e)
|
||||
{
|
||||
// this shouldn't ever happen, but just in case
|
||||
e.printStackTrace();
|
||||
} finally
|
||||
{
|
||||
generatorThreadRunning = false;
|
||||
}
|
||||
});
|
||||
|
||||
mainGenThread.execute(generatorThread);
|
||||
} // if distanceGenerationMode != DistanceGenerationMode.NONE && !generatorThreadRunning
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,440 @@
|
||||
/*
|
||||
* This file is part of the LOD Mod, licensed under the GNU GPL v3 License.
|
||||
*
|
||||
* Copyright (C) 2020 James Seibel
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, version 3.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.seibel.lod.config;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
|
||||
import org.apache.commons.lang3.tuple.Pair;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
|
||||
import com.electronwill.nightconfig.core.file.CommentedFileConfig;
|
||||
import com.electronwill.nightconfig.core.io.WritingMode;
|
||||
import com.seibel.lod.ModInfo;
|
||||
import com.seibel.lod.enums.DebugMode;
|
||||
import com.seibel.lod.enums.DistanceCalculatorType;
|
||||
import com.seibel.lod.enums.DistanceGenerationMode;
|
||||
import com.seibel.lod.enums.FogDistance;
|
||||
import com.seibel.lod.enums.FogDrawOverride;
|
||||
import com.seibel.lod.enums.LodDetail;
|
||||
import com.seibel.lod.enums.LodTemplate;
|
||||
import com.seibel.lod.enums.ShadingMode;
|
||||
|
||||
import net.minecraftforge.common.ForgeConfigSpec;
|
||||
import net.minecraftforge.eventbus.api.SubscribeEvent;
|
||||
import net.minecraftforge.fml.common.Mod;
|
||||
import net.minecraftforge.fml.config.ModConfig;
|
||||
|
||||
/**
|
||||
* This handles any configuration the user has access to.
|
||||
*
|
||||
* @author James Seibel
|
||||
* @version 9-1-2021
|
||||
*/
|
||||
@Mod.EventBusSubscriber
|
||||
public class LodConfig
|
||||
{
|
||||
public static class Client
|
||||
{
|
||||
public final Graphics graphics;
|
||||
public final WorldGenerator worldGenerator;
|
||||
public final Threading threading;
|
||||
public final Debugging debugging;
|
||||
public final Buffers buffers;
|
||||
|
||||
public Client(ForgeConfigSpec.Builder builder)
|
||||
{
|
||||
builder.push("client");
|
||||
{
|
||||
graphics = new Graphics(builder);
|
||||
worldGenerator = new WorldGenerator(builder);
|
||||
threading = new Threading(builder);
|
||||
debugging = new Debugging(builder);
|
||||
buffers = new Buffers(builder);
|
||||
}
|
||||
builder.pop();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
//================//
|
||||
// Client Configs //
|
||||
//================//
|
||||
|
||||
public static class Graphics
|
||||
{
|
||||
public ForgeConfigSpec.BooleanValue drawLODs;
|
||||
|
||||
public ForgeConfigSpec.EnumValue<FogDistance> fogDistance;
|
||||
public ForgeConfigSpec.EnumValue<FogDrawOverride> fogDrawOverride;
|
||||
|
||||
public ForgeConfigSpec.EnumValue<LodTemplate> lodTemplate;
|
||||
|
||||
public ForgeConfigSpec.EnumValue<LodDetail> maxDrawDetail;
|
||||
|
||||
public ForgeConfigSpec.EnumValue<ShadingMode> shadingMode;
|
||||
|
||||
public ForgeConfigSpec.IntValue lodQuality;
|
||||
|
||||
public ForgeConfigSpec.IntValue lodChunkRenderDistance;
|
||||
|
||||
public ForgeConfigSpec.DoubleValue brightnessMultiplier;
|
||||
public ForgeConfigSpec.DoubleValue saturationMultiplier;
|
||||
|
||||
|
||||
Graphics(ForgeConfigSpec.Builder builder)
|
||||
{
|
||||
builder.comment("These settings control how the LODs look.").push(this.getClass().getSimpleName());
|
||||
|
||||
drawLODs = builder
|
||||
.comment("\n\n"
|
||||
+ " If false LODs will not be drawn, \n"
|
||||
+ " however they will still be generated \n"
|
||||
+ " and saved to file for later use. \n")
|
||||
.define("drawLODs", true);
|
||||
|
||||
fogDistance = builder
|
||||
.comment("\n\n"
|
||||
+ " At what distance should Fog be drawn on the LODs? \n"
|
||||
+ " If the fog cuts off ubruptly or you are using Optifine's \"fast\" fog option \n"
|
||||
+ " set this to " + FogDistance.NEAR.toString() + " or " + FogDistance.FAR.toString() + ". \n")
|
||||
.defineEnum("fogDistance", FogDistance.NEAR_AND_FAR);
|
||||
|
||||
fogDrawOverride = builder
|
||||
.comment("\n\n"
|
||||
+ " When should fog be drawn? \n"
|
||||
+ " " + FogDrawOverride.USE_OPTIFINE_FOG_SETTING.toString() + ": Use whatever Fog setting Optifine is using. If Optifine isn't installed this defaults to " + FogDrawOverride.ALWAYS_DRAW_FOG_FANCY.toString() + ". \n"
|
||||
+ " " + FogDrawOverride.NEVER_DRAW_FOG.toString() + ": Never draw fog on the LODs \n"
|
||||
+ " " + FogDrawOverride.ALWAYS_DRAW_FOG_FAST.toString() + ": Always draw fast fog on the LODs \n"
|
||||
+ " " + FogDrawOverride.ALWAYS_DRAW_FOG_FANCY.toString() + ": Always draw fancy fog on the LODs (if your graphics card supports it) \n")
|
||||
.defineEnum("fogDrawOverride", FogDrawOverride.USE_OPTIFINE_FOG_SETTING);
|
||||
|
||||
lodTemplate = builder
|
||||
.comment("\n\n"
|
||||
+ " How should the LODs be drawn? \n"
|
||||
+ " NOTE: Currently only " + LodTemplate.CUBIC.toString() + " is implemented! \n"
|
||||
+ " \n"
|
||||
+ " " + LodTemplate.CUBIC.toString() + ": LOD Chunks are drawn as rectangular prisms (boxes). \n"
|
||||
+ " " + LodTemplate.TRIANGULAR.toString() + ": LOD Chunks smoothly transition between other. \n"
|
||||
+ " " + LodTemplate.DYNAMIC.toString() + ": LOD Chunks smoothly transition between other, \n"
|
||||
+ " " + " unless a neighboring chunk is at a significantly different height. \n")
|
||||
.defineEnum("lodTemplate", LodTemplate.CUBIC);
|
||||
|
||||
maxDrawDetail = builder
|
||||
.comment("\n\n"
|
||||
+ " What is the maximum detail level that LODs should be drawn at? \n"
|
||||
+ " " + LodDetail.SINGLE.toString() + ": render 1 LOD for each Chunk. \n"
|
||||
+ " " + LodDetail.DOUBLE.toString() + ": render 4 LODs for each Chunk. \n"
|
||||
+ " " + LodDetail.QUAD.toString() + ": render 16 LODs for each Chunk. \n"
|
||||
+ " " + LodDetail.HALF.toString() + ": render 64 LODs for each Chunk. \n"
|
||||
+ " " + LodDetail.FULL.toString() + ": render 256 LODs for each Chunk. \n")
|
||||
.defineEnum("lodDrawQuality", LodDetail.FULL);
|
||||
|
||||
lodQuality = builder
|
||||
.comment("\n\n"
|
||||
+ " this value is multiplied by 128 and determine \n"
|
||||
+ " how much the quality decrease over distance \n")
|
||||
.defineInRange("lodQuality", 1, 1, 4);
|
||||
|
||||
lodChunkRenderDistance = builder
|
||||
.comment("\n\n"
|
||||
+ " This is the render distance of the mod \n")
|
||||
.defineInRange("lodChunkRenderDistane", 64, 32, 512);
|
||||
|
||||
shadingMode = builder
|
||||
.comment("\n\n"
|
||||
+ " What kind of shading should the LODs have? \n"
|
||||
+ " \n"
|
||||
+ " " + ShadingMode.NONE.toString() + " \n"
|
||||
+ " " + "LODs will have the same lighting on every side. \n"
|
||||
+ " " + "Can make large similarly colored areas hard to differentiate. \n"
|
||||
+ "\n"
|
||||
+ " " + ShadingMode.DARKEN_SIDES.toString() + " \n"
|
||||
+ " " + "LODs will have darker sides and bottoms to simulate Minecraft's flat lighting.")
|
||||
.defineEnum("lightingMode", ShadingMode.DARKEN_SIDES);
|
||||
|
||||
brightnessMultiplier = builder
|
||||
.comment("\n\n"
|
||||
+ " Change how bright LOD colors are. \n"
|
||||
+ " 0 = black \n"
|
||||
+ " 1 = normal color value \n"
|
||||
+ " 2 = washed out colors \n")
|
||||
.defineInRange("brightnessMultiplier", 1.0, 0, 2);
|
||||
|
||||
saturationMultiplier = builder
|
||||
.comment("\n\n"
|
||||
+ " Change how saturated LOD colors are. \n"
|
||||
+ " 0 = black and white \n"
|
||||
+ " 1 = normal saturation \n"
|
||||
+ " 2 = very saturated \n")
|
||||
.defineInRange("saturationMultiplier", 1.0, 0, 2);
|
||||
|
||||
|
||||
builder.pop();
|
||||
}
|
||||
}
|
||||
|
||||
public static class WorldGenerator
|
||||
{
|
||||
public ForgeConfigSpec.EnumValue<LodDetail> maxGenerationDetail;
|
||||
public ForgeConfigSpec.EnumValue<DistanceGenerationMode> distanceGenerationMode;
|
||||
public ForgeConfigSpec.BooleanValue allowUnstableFeatureGeneration;
|
||||
public ForgeConfigSpec.EnumValue<DistanceCalculatorType> lodDistanceCalculatorType;
|
||||
|
||||
|
||||
WorldGenerator(ForgeConfigSpec.Builder builder)
|
||||
{
|
||||
builder.comment("These settings control how LODs outside your normal view range are generated.").push(this.getClass().getSimpleName());
|
||||
|
||||
maxGenerationDetail = builder
|
||||
.comment("\n\n"
|
||||
+ " What is the maximum detail level that LODs should be generated at? \n"
|
||||
+ " " + LodDetail.SINGLE.toString() + ": render 1 LOD for each Chunk. \n"
|
||||
+ " " + LodDetail.DOUBLE.toString() + ": render 4 LODs for each Chunk. \n"
|
||||
+ " " + LodDetail.QUAD.toString() + ": render 16 LODs for each Chunk. \n"
|
||||
+ " " + LodDetail.HALF.toString() + ": render 64 LODs for each Chunk. \n"
|
||||
+ " " + LodDetail.FULL.toString() + ": render 256 LODs for each Chunk. \n")
|
||||
.defineEnum("lodGenerationQuality", LodDetail.HALF);
|
||||
|
||||
lodDistanceCalculatorType = builder
|
||||
.comment("\n\n"
|
||||
+ " " + DistanceCalculatorType.LINEAR + " \n"
|
||||
+ " with LINEAR calculator the quality of block decrease \n"
|
||||
+ " linearly to the distance of the player \n"
|
||||
|
||||
+ "\n"
|
||||
+ " " + DistanceCalculatorType.QUADRATIC + " \n"
|
||||
+ " with LINEAR calculator the quality of block decrease \n"
|
||||
+ " quadratically to the distance of the player \n"
|
||||
|
||||
+ "\n"
|
||||
+ " " + DistanceCalculatorType.RENDER_DEPENDANT + " \n"
|
||||
+ " with LINEAR calculator the quality of block decrease \n"
|
||||
+ " quadratically to the distance of the player \n")
|
||||
.defineEnum("lodDistanceComputation", DistanceCalculatorType.LINEAR);
|
||||
|
||||
distanceGenerationMode = builder
|
||||
.comment("\n\n"
|
||||
+ " Note: The times listed here are the amount of time it took \n"
|
||||
+ " the developer's PC to generate 1 chunk, \n"
|
||||
+ " and are included so you can compare the \n"
|
||||
+ " different generation options. Your mileage may vary. \n"
|
||||
+ "\n"
|
||||
|
||||
+ " " + DistanceGenerationMode.NONE.toString() + " \n"
|
||||
+ " Don't run the distance generator. \n"
|
||||
|
||||
+ " " + DistanceGenerationMode.BIOME_ONLY.toString() + " \n"
|
||||
+ " Only generate the biomes and use biome \n"
|
||||
+ " grass/foliage color, water color, or snow color \n"
|
||||
+ " to generate the color. \n"
|
||||
+ " Doesn't generate height, everything is shown at sea level. \n"
|
||||
+ " Multithreaded - Fastest (2-5 ms) \n"
|
||||
|
||||
+ "\n"
|
||||
+ " " + DistanceGenerationMode.BIOME_ONLY_SIMULATE_HEIGHT.toString() + " \n"
|
||||
+ " Same as BIOME_ONLY, except instead \n"
|
||||
+ " of always using sea level as the LOD height \n"
|
||||
+ " different biome types (mountain, ocean, forest, etc.) \n"
|
||||
+ " use predetermined heights to simulate having height data. \n"
|
||||
+ " Multithreaded - Fastest (2-5 ms) \n"
|
||||
|
||||
+ "\n"
|
||||
+ " " + DistanceGenerationMode.SURFACE.toString() + " \n"
|
||||
+ " Generate the world surface, \n"
|
||||
+ " this does NOT include caves, trees, \n"
|
||||
+ " or structures. \n"
|
||||
+ " Multithreaded - Faster (10-20 ms) \n"
|
||||
|
||||
+ "\n"
|
||||
+ " " + DistanceGenerationMode.FEATURES.toString() + " \n"
|
||||
+ " Generate everything except structures. \n"
|
||||
+ " WARNING: This may cause world generation bugs or instability! \n"
|
||||
+ " Multithreaded - Fast (15-20 ms) \n"
|
||||
|
||||
+ "\n"
|
||||
+ " " + DistanceGenerationMode.SERVER.toString() + " \n"
|
||||
+ " Ask the server to generate/load each chunk. \n"
|
||||
+ " This is the most compatible, but causes server/simulation lag. \n"
|
||||
+ " This will also show player made structures if you \n"
|
||||
+ " are adding the mod to a pre-existing world. \n"
|
||||
+ " Singlethreaded - Slow (15-50 ms, with spikes up to 200 ms) \n")
|
||||
.defineEnum("distanceGenerationMode", DistanceGenerationMode.SURFACE);
|
||||
|
||||
allowUnstableFeatureGeneration = builder
|
||||
.comment("\n\n"
|
||||
+ " When using the " + DistanceGenerationMode.FEATURES.toString() + " generation mode \n"
|
||||
+ " some features may not be thread safe, which could \n"
|
||||
+ " cause instability and crashes. \n"
|
||||
+ " By default (false) those features are skipped, \n"
|
||||
+ " improving stability, but decreasing how many features are \n"
|
||||
+ " actually generated. \n"
|
||||
+ " (for example: some tree generation is unstable, \n"
|
||||
+ " so some trees may not be generated.) \n"
|
||||
+ " By setting this to true, all features will be generated, \n"
|
||||
+ " but your game will be more unstable and crashes may occur. \n"
|
||||
+ " \n"
|
||||
+ " I would love to remove this option and always generate everything, \n"
|
||||
+ " but I'm not sure how to do that. \n"
|
||||
+ " If you are a Java wizard, check out the git issue here: \n"
|
||||
+ " https://gitlab.com/jeseibel/minecraft-lod-mod/-/issues/35 \n")
|
||||
.define("allowUnstableFeatureGeneration", false);
|
||||
|
||||
|
||||
builder.pop();
|
||||
}
|
||||
}
|
||||
|
||||
public static class Threading
|
||||
{
|
||||
public ForgeConfigSpec.IntValue numberOfWorldGenerationThreads;
|
||||
public ForgeConfigSpec.IntValue numberOfBufferBuilderThreads;
|
||||
|
||||
Threading(ForgeConfigSpec.Builder builder)
|
||||
{
|
||||
builder.comment("These settings control how many CPU threads the mod uses for different tasks.").push(this.getClass().getSimpleName());
|
||||
|
||||
numberOfWorldGenerationThreads = builder
|
||||
.comment("\n\n"
|
||||
+ " This is how many threads are used when generating LODs outside \n"
|
||||
+ " the normal render distance. \n"
|
||||
+ " If you experience stuttering when generating distant LODs, decrease \n"
|
||||
+ " this number. If you want to increase LOD generation speed, \n"
|
||||
+ " increase this number. \n"
|
||||
+ " \n"
|
||||
+ " The maximum value is the number of processors on your CPU. \n"
|
||||
+ " Requires a restart to take effect. \n")
|
||||
.defineInRange("numberOfWorldGenerationThreads", Runtime.getRuntime().availableProcessors() / 2, 1, Runtime.getRuntime().availableProcessors());
|
||||
|
||||
numberOfBufferBuilderThreads = builder
|
||||
.comment("\n\n"
|
||||
+ " This is how many threads are used when building vertex buffers \n"
|
||||
+ " (The things sent to your GPU to draw the LODs). \n"
|
||||
+ " If you experience high CPU useage when NOT generating distant \n"
|
||||
+ " LODs, lower this number. \n"
|
||||
+ " \n"
|
||||
+ " The maximum value is the number of processors on your CPU. \n"
|
||||
+ " Requires a restart to take effect. \n")
|
||||
.defineInRange("numberOfBufferBuilderThreads", Runtime.getRuntime().availableProcessors(), 1, Runtime.getRuntime().availableProcessors());
|
||||
|
||||
builder.pop();
|
||||
}
|
||||
}
|
||||
|
||||
public static class Debugging
|
||||
{
|
||||
public ForgeConfigSpec.EnumValue<DebugMode> debugMode;
|
||||
public ForgeConfigSpec.BooleanValue enableDebugKeybinding;
|
||||
|
||||
Debugging(ForgeConfigSpec.Builder builder)
|
||||
{
|
||||
builder.comment("These settings can be used by to look for bugs, or see how certain parts of the mod are working.").push(this.getClass().getSimpleName());
|
||||
|
||||
debugMode = builder
|
||||
.comment("\n\n"
|
||||
+ " " + DebugMode.OFF.toString() + ": LODs will draw with their normal colors. \n"
|
||||
+ " " + DebugMode.SHOW_DETAIL.toString() + ": LOD colors will be based on their detail. \n"
|
||||
+ " " + DebugMode.SHOW_DETAIL_WIREFRAME.toString() + ": LOD colors will be based on their detail, drawn with wireframe. \n")
|
||||
.defineEnum("debugMode", DebugMode.OFF);
|
||||
|
||||
enableDebugKeybinding = builder
|
||||
.comment("\n\n"
|
||||
+ " If true the F4 key can be used to cycle through the different debug modes. \n")
|
||||
.define("enableDebugKeybinding", false);
|
||||
|
||||
builder.pop();
|
||||
}
|
||||
}
|
||||
|
||||
public static class Buffers
|
||||
{
|
||||
public ForgeConfigSpec.IntValue bufferRebuildPlayerMoveTimeout;
|
||||
public ForgeConfigSpec.IntValue bufferRebuildChunkChangeTimeout;
|
||||
public ForgeConfigSpec.IntValue bufferRebuildLodChangeTimeout;
|
||||
|
||||
Buffers(ForgeConfigSpec.Builder builder)
|
||||
{
|
||||
builder.comment("These settings affect when Vertex Buffers are built.").push(this.getClass().getSimpleName());
|
||||
|
||||
bufferRebuildPlayerMoveTimeout = builder
|
||||
.comment("\n\n"
|
||||
+ " How long in milliseconds should we wait to \n"
|
||||
+ " rebuild the vertex buffers when the player moves \n"
|
||||
+ " a chunk or more? \n")
|
||||
.defineInRange("bufferRebuildPlayerMoveTimeout", 2000, 1, 60000);
|
||||
|
||||
bufferRebuildChunkChangeTimeout = builder
|
||||
.comment("\n\n"
|
||||
+ " How long in milliseconds should we wait to \n"
|
||||
+ " rebuild the vertex buffers when the vanilla rendered \n"
|
||||
+ " chunks change? \n")
|
||||
.defineInRange("bufferRebuildChunkChangeTimeout", 1000, 1, 60000);
|
||||
|
||||
bufferRebuildLodChangeTimeout = builder
|
||||
.comment("\n\n"
|
||||
+ " How long in milliseconds should we wait to \n"
|
||||
+ " rebuild the vertex buffers when the LOD regions change? \n")
|
||||
.defineInRange("bufferRebuildLodChangeTimeout", 5000, 1, 60000);
|
||||
|
||||
|
||||
builder.pop();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* {@link Path} to the configuration file of this mod
|
||||
*/
|
||||
private static final Path CONFIG_PATH = Paths.get("config", ModInfo.MODID + ".toml");
|
||||
|
||||
public static final ForgeConfigSpec CLIENT_SPEC;
|
||||
public static final Client CLIENT;
|
||||
|
||||
static
|
||||
{
|
||||
final Pair<Client, ForgeConfigSpec> specPair = new ForgeConfigSpec.Builder().configure(Client::new);
|
||||
CLIENT_SPEC = specPair.getRight();
|
||||
CLIENT = specPair.getLeft();
|
||||
CommentedFileConfig clientConfig = CommentedFileConfig.builder(CONFIG_PATH)
|
||||
.writingMode(WritingMode.REPLACE)
|
||||
.build();
|
||||
clientConfig.load();
|
||||
clientConfig.save();
|
||||
CLIENT_SPEC.setConfig(clientConfig);
|
||||
}
|
||||
|
||||
@SubscribeEvent
|
||||
public static void onLoad(final ModConfig.Loading configEvent)
|
||||
{
|
||||
LogManager.getLogger().debug(ModInfo.MODNAME, "Loaded forge config file {}", configEvent.getConfig().getFileName());
|
||||
}
|
||||
|
||||
@SubscribeEvent
|
||||
public static void onFileChange(final ModConfig.Reloading configEvent)
|
||||
{
|
||||
LogManager.getLogger().debug(ModInfo.MODNAME, "Forge config just got changed on the file system!");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
* This file is part of the LOD Mod, licensed under the GNU GPL v3 License.
|
||||
*
|
||||
* Copyright (C) 2020 James Seibel
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, version 3.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.seibel.lod.enums;
|
||||
|
||||
/**
|
||||
* TOP, NORTH, SOUTH, EAST, WEST, BOTTOM
|
||||
*
|
||||
* @author James Seibel
|
||||
* @version 10-17-2020
|
||||
*/
|
||||
public enum ColorDirection
|
||||
{
|
||||
// used for colors
|
||||
/** +Y */
|
||||
TOP(0),
|
||||
|
||||
/** -Z */
|
||||
NORTH(1),
|
||||
/** +Z */
|
||||
SOUTH(2),
|
||||
|
||||
/** +X */
|
||||
EAST(3),
|
||||
/** -X */
|
||||
WEST(4),
|
||||
|
||||
/** -Y */
|
||||
BOTTOM(5);
|
||||
|
||||
public final int value;
|
||||
|
||||
private ColorDirection(int newValue)
|
||||
{
|
||||
value = newValue;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* This file is part of the LOD Mod, licensed under the GNU GPL v3 License.
|
||||
*
|
||||
* Copyright (C) 2020 James Seibel
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, version 3.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.seibel.lod.enums;
|
||||
|
||||
/**
|
||||
* off, detail, detail wireframe
|
||||
*
|
||||
* @author James Seibel
|
||||
* @version 8-28-2021
|
||||
*/
|
||||
public enum DebugMode
|
||||
{
|
||||
OFF,
|
||||
|
||||
/** LOD colors are based on their detail */
|
||||
SHOW_DETAIL,
|
||||
|
||||
/** LOD colors are based on their detail, and draws in wireframe. */
|
||||
SHOW_DETAIL_WIREFRAME;
|
||||
|
||||
/** used when cycling through the different modes */
|
||||
private DebugMode next;
|
||||
static
|
||||
{
|
||||
OFF.next = SHOW_DETAIL;
|
||||
SHOW_DETAIL.next = SHOW_DETAIL_WIREFRAME;
|
||||
SHOW_DETAIL_WIREFRAME.next = OFF;
|
||||
}
|
||||
|
||||
/** returns the next debug mode */
|
||||
public DebugMode getNext()
|
||||
{
|
||||
return this.next;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package com.seibel.lod.enums;
|
||||
|
||||
/**
|
||||
* @author Leonardo Amato
|
||||
* @version 22-08-2021
|
||||
*/
|
||||
public enum DistanceCalculatorType
|
||||
{
|
||||
/**
|
||||
* different Lod detail render and generate linearly to the distance
|
||||
*/
|
||||
LINEAR,
|
||||
|
||||
/**
|
||||
* different Lod detail render and generate quadratically to the distance
|
||||
*/
|
||||
QUADRATIC,
|
||||
|
||||
/**
|
||||
* we calculate the distance based on game render distance and mod render distance
|
||||
*/
|
||||
RENDER_DEPENDANT;
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
/*
|
||||
* This file is part of the LOD Mod, licensed under the GNU GPL v3 License.
|
||||
*
|
||||
* Copyright (C) 2020 James Seibel
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, version 3.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.seibel.lod.enums;
|
||||
|
||||
/**
|
||||
* NONE <br>
|
||||
* BIOME_ONLY <br>
|
||||
* BIOME_ONLY_SIMULATE_HEIGHT <br>
|
||||
* SURFACE <br>
|
||||
* FEATURES <br>
|
||||
* SERVER <br><br>
|
||||
* <p>
|
||||
* In order of fastest to slowest.
|
||||
*
|
||||
* @author James Seibel
|
||||
* @author Leonardo Amato
|
||||
* @version 8-7-2021
|
||||
*/
|
||||
public enum DistanceGenerationMode
|
||||
{
|
||||
/**
|
||||
* Don't generate anything
|
||||
*/
|
||||
NONE((byte) 0),
|
||||
|
||||
/**
|
||||
* Only generate the biomes and use biome
|
||||
* grass/foliage color, water color, or ice color
|
||||
* to generate the color.
|
||||
* Doesn't generate height, everything is shown at sea level.
|
||||
* Multithreaded - Fastest (2-5 ms)
|
||||
*/
|
||||
BIOME_ONLY((byte) 1),
|
||||
|
||||
/**
|
||||
* Same as BIOME_ONLY, except instead
|
||||
* of always using sea level as the LOD height
|
||||
* different biome types (mountain, ocean, forest, etc.)
|
||||
* use predetermined heights to simulate having height data.
|
||||
*/
|
||||
BIOME_ONLY_SIMULATE_HEIGHT((byte) 2),
|
||||
|
||||
/**
|
||||
* Generate the world surface,
|
||||
* this does NOT include caves, trees,
|
||||
* or structures.
|
||||
* Multithreaded - Faster (10-20 ms)
|
||||
*/
|
||||
SURFACE((byte) 3),
|
||||
|
||||
/**
|
||||
* Generate everything except structures.
|
||||
* NOTE: This may cause world generation bugs or instability,
|
||||
* since some features cause concurrentModification exceptions.
|
||||
* Multithreaded - Fast (15-20 ms)
|
||||
*/
|
||||
FEATURES((byte) 4),
|
||||
|
||||
/**
|
||||
* Ask the server to generate/load each chunk.
|
||||
* This is the most compatible, but causes server/simulation lag.
|
||||
* This will also show player made structures if you
|
||||
* are adding the mod to a pre-existing world.
|
||||
* Singlethreaded - Slow (15-50 ms, with spikes up to 200 ms)
|
||||
*/
|
||||
SERVER((byte) 5);
|
||||
|
||||
|
||||
/**
|
||||
* The higher the number the more complete the generation is.
|
||||
*/
|
||||
public final byte complexity;
|
||||
|
||||
DistanceGenerationMode(byte complexity)
|
||||
{
|
||||
this.complexity = complexity;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* This file is part of the LOD Mod, licensed under the GNU GPL v3 License.
|
||||
*
|
||||
* Copyright (C) 2020 James Seibel
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, version 3.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.seibel.lod.enums;
|
||||
|
||||
/**
|
||||
* NEAR, FAR, or NEAR_AND_FAR.
|
||||
*
|
||||
* @author James Seibel
|
||||
* @version 02-14-2021
|
||||
*/
|
||||
public enum FogDistance
|
||||
{
|
||||
/** good for fast or fancy fog qualities. */
|
||||
NEAR,
|
||||
|
||||
/** good for fast or fancy fog qualities. */
|
||||
FAR,
|
||||
|
||||
/** only looks good if the fog quality is set to Fancy. */
|
||||
NEAR_AND_FAR;
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* This file is part of the LOD Mod, licensed under the GNU GPL v3 License.
|
||||
*
|
||||
* Copyright (C) 2020 James Seibel
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, version 3.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.seibel.lod.enums;
|
||||
|
||||
/**
|
||||
* USE_OPTIFINE_FOG_SETTING, <br>
|
||||
* NEVER_DRAW_FOG, <br>
|
||||
* ALWAYS_DRAW_FOG_FAST, <br>
|
||||
* ALWAYS_DRAW_FOG_FANCY <br>
|
||||
*
|
||||
* @author James Seibel
|
||||
* @version 7-03-2021
|
||||
*/
|
||||
public enum FogDrawOverride
|
||||
{
|
||||
/** Use whatever Fog setting optifine is using.
|
||||
* If optifine isn't installed this defaults to ALWAYS_DRAW_FOG. */
|
||||
USE_OPTIFINE_FOG_SETTING,
|
||||
|
||||
/** Never draw fog on the LODs */
|
||||
NEVER_DRAW_FOG,
|
||||
|
||||
/** Always draw fog on the LODs */
|
||||
ALWAYS_DRAW_FOG_FAST,
|
||||
|
||||
/** Always draw fog on the LODs */
|
||||
ALWAYS_DRAW_FOG_FANCY;
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
* This file is part of the LOD Mod, licensed under the GNU GPL v3 License.
|
||||
*
|
||||
* Copyright (C) 2020 James Seibel
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, version 3.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.seibel.lod.enums;
|
||||
|
||||
/**
|
||||
* fast, fancy, or off
|
||||
*
|
||||
* @author James Seibel
|
||||
* @version 02-14-2021
|
||||
*/
|
||||
public enum FogQuality
|
||||
{
|
||||
FAST,
|
||||
FANCY,
|
||||
OFF;
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* This file is part of the LOD Mod, licensed under the GNU GPL v3 License.
|
||||
*
|
||||
* Copyright (C) 2020 James Seibel
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, version 3.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.seibel.lod.enums;
|
||||
|
||||
/**
|
||||
* NE, SE, SW, NW
|
||||
*
|
||||
* @author James Seibel
|
||||
* @version 1-20-2020
|
||||
*/
|
||||
public enum LodCorner
|
||||
{
|
||||
/** -Z, +X */
|
||||
NE(0),
|
||||
/** +Z, +X */
|
||||
SE(1),
|
||||
/** +Z, -X */
|
||||
SW(2),
|
||||
/** -Z, -X */
|
||||
NW(3);
|
||||
|
||||
public final int value;
|
||||
|
||||
private LodCorner(int newValue)
|
||||
{
|
||||
value = newValue;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,159 @@
|
||||
/*
|
||||
* This file is part of the LOD Mod, licensed under the GNU GPL v3 License.
|
||||
*
|
||||
* Copyright (C) 2020 James Seibel
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, version 3.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.seibel.lod.enums;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
|
||||
import com.seibel.lod.util.LodUtil;
|
||||
|
||||
/**
|
||||
* single, double, quad, half, full
|
||||
*
|
||||
* @author James Seibel
|
||||
* @version 8-11-2021
|
||||
*/
|
||||
public enum LodDetail
|
||||
{
|
||||
/** render 1 LOD for each chunk */
|
||||
SINGLE(1, 4),
|
||||
|
||||
/** render 4 LODs for each chunk */
|
||||
DOUBLE(2, 3),
|
||||
|
||||
/** render 16 LODs for each chunk */
|
||||
QUAD(4, 2),
|
||||
|
||||
/** render 64 LODs for each chunk */
|
||||
HALF(8, 1),
|
||||
|
||||
/** render 256 LODs for each chunk */
|
||||
FULL(16, 0);
|
||||
|
||||
|
||||
/** How many DataPoints should
|
||||
* be drawn per side per LodChunk */
|
||||
public final int dataPointLengthCount;
|
||||
/** How wide each LOD DataPoint is */
|
||||
public final int dataPointWidth;
|
||||
/** This is the same as detailLevel in LodQuadTreeNode,
|
||||
* lowest is 0 highest is 9 */
|
||||
public final byte detailLevel;
|
||||
|
||||
/* Start/End X/Z give the block positions
|
||||
* for each individual dataPoint in a LodChunk */
|
||||
public final int[] startX;
|
||||
public final int[] startZ;
|
||||
|
||||
public final int[] endX;
|
||||
public final int[] endZ;
|
||||
|
||||
|
||||
/**
|
||||
* 1st dimension: LodDetail.detailLevel <br>
|
||||
* 2nd dimension: An array of all LodDetails that are less than or <br>
|
||||
* equal to that detailLevel
|
||||
*/
|
||||
private static LodDetail[][] lowerDetailArrays;
|
||||
|
||||
|
||||
|
||||
|
||||
private LodDetail(int newLengthCount, int newDetailLevel)
|
||||
{
|
||||
detailLevel = (byte) newDetailLevel;
|
||||
dataPointLengthCount = newLengthCount;
|
||||
dataPointWidth = 16 / dataPointLengthCount;
|
||||
|
||||
startX = new int[dataPointLengthCount * dataPointLengthCount];
|
||||
endX = new int[dataPointLengthCount * dataPointLengthCount];
|
||||
|
||||
startZ = new int[dataPointLengthCount * dataPointLengthCount];
|
||||
endZ = new int[dataPointLengthCount * dataPointLengthCount];
|
||||
|
||||
|
||||
int index = 0;
|
||||
for(int x = 0; x < newLengthCount; x++)
|
||||
{
|
||||
for(int z = 0; z < newLengthCount; z++)
|
||||
{
|
||||
startX[index] = x * dataPointWidth;
|
||||
startZ[index] = z * dataPointWidth;
|
||||
|
||||
endX[index] = (x*dataPointWidth) + dataPointWidth;
|
||||
endZ[index] = (z*dataPointWidth) + dataPointWidth;
|
||||
|
||||
index++;
|
||||
}
|
||||
}
|
||||
|
||||
}// constructor
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Returns an array of all LodDetails that have a detail level
|
||||
* that is less than or equal to the given LodDetail
|
||||
*/
|
||||
public static LodDetail[] getSelfAndLowerDetails(LodDetail detail)
|
||||
{
|
||||
if (lowerDetailArrays == null)
|
||||
{
|
||||
// run first time setup
|
||||
lowerDetailArrays = new LodDetail[LodDetail.values().length][];
|
||||
|
||||
// go through each LodDetail
|
||||
for(LodDetail currentDetail : LodDetail.values())
|
||||
{
|
||||
ArrayList<LodDetail> lowerDetails = new ArrayList<>();
|
||||
|
||||
// find the details lower than currentDetail
|
||||
for(LodDetail compareDetail : LodDetail.values())
|
||||
{
|
||||
if (currentDetail.detailLevel <= compareDetail.detailLevel)
|
||||
{
|
||||
lowerDetails.add(compareDetail);
|
||||
}
|
||||
}
|
||||
|
||||
// have the highest detail item first in the list
|
||||
Collections.sort(lowerDetails);
|
||||
Collections.reverse(lowerDetails);
|
||||
|
||||
lowerDetailArrays[currentDetail.detailLevel] = lowerDetails.toArray(new LodDetail[lowerDetails.size()]);
|
||||
}
|
||||
}
|
||||
|
||||
return lowerDetailArrays[detail.detailLevel];
|
||||
}
|
||||
|
||||
/** Returns what detail level should be used at a given distance and maxDistance. */
|
||||
public static LodDetail getDetailForDistance(LodDetail maxDetailLevel, int distance, int maxDistance)
|
||||
{
|
||||
LodDetail[] lowerDetails = getSelfAndLowerDetails(maxDetailLevel);
|
||||
int distaneBetweenDetails = maxDistance / lowerDetails.length;
|
||||
int index = LodUtil.clamp(0, distance / distaneBetweenDetails, lowerDetails.length - 1);
|
||||
|
||||
return lowerDetails[index];
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* This file is part of the LOD Mod, licensed under the GNU GPL v3 License.
|
||||
*
|
||||
* Copyright (C) 2020 James Seibel
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, version 3.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.seibel.lod.enums;
|
||||
|
||||
/**
|
||||
* NONE <br>
|
||||
* BIOME_ONLY <br>
|
||||
* BIOME_ONLY_SIMULATE_HEIGHT <br>
|
||||
* SURFACE <br>
|
||||
* FEATURES <br>
|
||||
* SERVER <br><br>
|
||||
*
|
||||
* In order of fastest to slowest.
|
||||
*
|
||||
* @author James Seibel
|
||||
* @author Leonardo Amato
|
||||
* @version 8-7-2021
|
||||
*/
|
||||
public enum LodRenderDistance
|
||||
{
|
||||
SHORT(32),
|
||||
|
||||
MEDIUM(64),
|
||||
|
||||
FAR(128);
|
||||
|
||||
|
||||
/** The higher the number the more complete the generation is. */
|
||||
public final int renderDistance;
|
||||
|
||||
LodRenderDistance(int complexity)
|
||||
{
|
||||
this.renderDistance = complexity;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
/*
|
||||
* This file is part of the LOD Mod, licensed under the GNU GPL v3 License.
|
||||
*
|
||||
* Copyright (C) 2020 James Seibel
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, version 3.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.seibel.lod.enums;
|
||||
|
||||
import com.seibel.lod.builders.lodTemplates.AbstractLodTemplate;
|
||||
import com.seibel.lod.builders.lodTemplates.CubicLodTemplate;
|
||||
import com.seibel.lod.builders.lodTemplates.DynamicLodTemplate;
|
||||
import com.seibel.lod.builders.lodTemplates.TriangularLodTemplate;
|
||||
|
||||
/**
|
||||
* Cubic, Triangular, Dynamic
|
||||
*
|
||||
* @author James Seibel
|
||||
* @version 8-4-2021
|
||||
*/
|
||||
public enum LodTemplate
|
||||
{
|
||||
// used for position
|
||||
|
||||
/** Chunks are rendered as
|
||||
* rectangular prisms. */
|
||||
CUBIC(new CubicLodTemplate()),
|
||||
|
||||
/** Chunks smoothly transition between
|
||||
* each other. */
|
||||
TRIANGULAR(new TriangularLodTemplate()),
|
||||
|
||||
/** Chunks smoothly transition between
|
||||
* each other, unless a neighboring chunk
|
||||
* is at a significantly different height. */
|
||||
DYNAMIC(new DynamicLodTemplate());
|
||||
|
||||
|
||||
public final AbstractLodTemplate template;
|
||||
|
||||
private LodTemplate(AbstractLodTemplate newTemplate)
|
||||
{
|
||||
template = newTemplate;
|
||||
}
|
||||
|
||||
|
||||
public int getBufferMemoryForSingleLod()
|
||||
{
|
||||
return template.getBufferMemoryForSingleNode();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
/*
|
||||
* This file is part of the LOD Mod, licensed under the GNU GPL v3 License.
|
||||
*
|
||||
* Copyright (C) 2020 James Seibel
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, version 3.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.seibel.lod.enums;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.EnumSet;
|
||||
|
||||
/**
|
||||
* NE, SE, SW, NW <br>
|
||||
* NORTH, SOUTH, EAST, WEST, <br>
|
||||
* CENTER
|
||||
*
|
||||
* @author James Seibel
|
||||
* @version 05-30-2021
|
||||
*/
|
||||
public enum RelativeChunkPos
|
||||
{
|
||||
/** +X, -Z */
|
||||
NE(0, 1,-1),
|
||||
/** +X, +Z */
|
||||
SE(1, 1,1),
|
||||
/** -X, +Z */
|
||||
SW(2, -1,1),
|
||||
/** -X, -Z */
|
||||
NW(3, -1,-1),
|
||||
|
||||
/** -Z */
|
||||
NORTH(4, 0,-1),
|
||||
/** +Z */
|
||||
SOUTH(5, 0,1),
|
||||
|
||||
/** +X */
|
||||
EAST(6, 1,0),
|
||||
/** -X */
|
||||
WEST(7, -1,0),
|
||||
|
||||
CENTER(8, 0,0);
|
||||
|
||||
/** index used when referencing objects in an array */
|
||||
public final int index;
|
||||
|
||||
/** position relative to X CENTER */
|
||||
public final int x;
|
||||
/** position relative to Z CENTER */
|
||||
public final int z;
|
||||
|
||||
/** NORTH, SOUTH, EAST, WEST */
|
||||
public static EnumSet<RelativeChunkPos> ADJACENT = EnumSet.of(NORTH, SOUTH, EAST, WEST);
|
||||
/** NE, NW, SE, SW */
|
||||
public static EnumSet<RelativeChunkPos> DIAGONAL = EnumSet.of(NE, NW, SE, SW);
|
||||
|
||||
|
||||
public static EnumSet<RelativeChunkPos> NW_CORNER = EnumSet.of(WEST, NW, NORTH);
|
||||
public static EnumSet<RelativeChunkPos> NE_CORNER = EnumSet.of(EAST, NE, NORTH);
|
||||
public static EnumSet<RelativeChunkPos> SW_CORNER = EnumSet.of(WEST, SW, SOUTH);
|
||||
public static EnumSet<RelativeChunkPos> SE_CORNER = EnumSet.of(EAST, SE, SOUTH);
|
||||
|
||||
/** NW_CORNER, NE_CORNER, SW_CORNER, SE_CORNER <br>
|
||||
* Contains the 3 points surrounding a corner. */
|
||||
public static ArrayList<EnumSet<RelativeChunkPos>> CORNERS = new ArrayList<EnumSet<RelativeChunkPos>>( Arrays.asList(NW_CORNER, NE_CORNER, SW_CORNER, SE_CORNER) );
|
||||
|
||||
private RelativeChunkPos(int newIndex, int newX, int newZ)
|
||||
{
|
||||
index = newIndex;
|
||||
x = newX;
|
||||
z = newZ;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
package com.seibel.lod.enums;
|
||||
|
||||
/**
|
||||
* NONE, DARKEN_SIDES
|
||||
*
|
||||
* @author James Seibel
|
||||
* @version 7-25-2020
|
||||
*/
|
||||
public enum ShadingMode
|
||||
{
|
||||
/** LODs will have the same lighting on every side.
|
||||
Fastest, but can make large similarly colored areas hard to differentiate */
|
||||
NONE,
|
||||
|
||||
/** LODs will have darker sides and bottoms to simulate top down lighting.
|
||||
Fastest */
|
||||
DARKEN_SIDES;
|
||||
}
|
||||
@@ -0,0 +1,386 @@
|
||||
/*
|
||||
* This file is part of the LOD Mod, licensed under the GNU GPL v3 License.
|
||||
*
|
||||
* Copyright (C) 2020 James Seibel
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, version 3.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.seibel.lod.handlers;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.File;
|
||||
import java.io.FileReader;
|
||||
import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.StandardCopyOption;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
import com.seibel.lod.enums.DistanceGenerationMode;
|
||||
import com.seibel.lod.objects.LevelContainer;
|
||||
import com.seibel.lod.objects.LodDimension;
|
||||
import com.seibel.lod.objects.LodRegion;
|
||||
import com.seibel.lod.objects.RegionPos;
|
||||
import com.seibel.lod.proxy.ClientProxy;
|
||||
import com.seibel.lod.util.LodThreadFactory;
|
||||
import com.seibel.lod.util.LodUtil;
|
||||
|
||||
/**
|
||||
* This object handles creating LodRegions
|
||||
* from files and saving LodRegion objects
|
||||
* to file.
|
||||
*
|
||||
* @author James Seibel
|
||||
* @version 8-24-2021
|
||||
*/
|
||||
public class LodDimensionFileHandler
|
||||
{
|
||||
/**
|
||||
* This is what separates each piece of data
|
||||
*/
|
||||
public static final char DATA_DELIMITER = ',';
|
||||
|
||||
|
||||
private LodDimension loadedDimension = null;
|
||||
public long regionLastWriteTime[][];
|
||||
|
||||
private File dimensionDataSaveFolder;
|
||||
|
||||
/**
|
||||
* lod
|
||||
*/
|
||||
private static final String FILE_NAME_PREFIX = "lod";
|
||||
/**
|
||||
* .txt
|
||||
*/
|
||||
private static final String FILE_EXTENSION = ".txt";
|
||||
/**
|
||||
* lod/
|
||||
*/
|
||||
private static final String LOD_FOLDER_NAME = "lod";
|
||||
/**
|
||||
* detail-#
|
||||
*/
|
||||
private static final String DETAIL_FOLDER_NAME_PREFIX = "detail-";
|
||||
|
||||
/**
|
||||
* .tmp <br>
|
||||
* Added to the end of the file path when saving to prevent
|
||||
* nulling a currently existing file. <br>
|
||||
* After the file finishes saving it will end with
|
||||
* FILE_EXTENSION.
|
||||
*/
|
||||
private static final String TMP_FILE_EXTENSION = ".tmp";
|
||||
|
||||
/**
|
||||
* 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 = 4;
|
||||
|
||||
/**
|
||||
* This is the string written before the file version
|
||||
*/
|
||||
private static final String LOD_FILE_VERSION_PREFIX = "lod_save_file_version";
|
||||
|
||||
/**
|
||||
* Allow saving asynchronously, but never try to save multiple regions
|
||||
* at a time
|
||||
*/
|
||||
private ExecutorService fileWritingThreadPool = Executors.newSingleThreadExecutor(new LodThreadFactory(this.getClass().getSimpleName()));
|
||||
|
||||
|
||||
public LodDimensionFileHandler(File newSaveFolder, LodDimension newLoadedDimension)
|
||||
{
|
||||
if (newSaveFolder == null)
|
||||
throw new IllegalArgumentException("LodDimensionFileHandler requires a valid File location to read and write to.");
|
||||
|
||||
dimensionDataSaveFolder = newSaveFolder;
|
||||
|
||||
loadedDimension = newLoadedDimension;
|
||||
// these two variable are used in sync with the LodDimension
|
||||
regionLastWriteTime = new long[loadedDimension.getWidth()][loadedDimension.getWidth()];
|
||||
for (int i = 0; i < loadedDimension.getWidth(); i++)
|
||||
for (int j = 0; j < loadedDimension.getWidth(); j++)
|
||||
regionLastWriteTime[i][j] = -1;
|
||||
}
|
||||
|
||||
|
||||
//================//
|
||||
// read from file //
|
||||
//================//
|
||||
|
||||
/**
|
||||
* Return the LodRegion region at the given coordinates.
|
||||
* (null if the file doesn't exist)
|
||||
*/
|
||||
public LodRegion loadRegionFromFile(byte detailLevel, RegionPos regionPos, DistanceGenerationMode generationMode)
|
||||
{
|
||||
int regionX = regionPos.x;
|
||||
int regionZ = regionPos.z;
|
||||
LodRegion region = new LodRegion(LodUtil.REGION_DETAIL_LEVEL,regionPos, generationMode);
|
||||
for (byte tempDetailLevel = LodUtil.REGION_DETAIL_LEVEL; tempDetailLevel >= detailLevel; tempDetailLevel--)
|
||||
{
|
||||
try
|
||||
{
|
||||
String fileName = getFileNameAndPathForRegion(regionX, regionZ, generationMode, tempDetailLevel);
|
||||
|
||||
// if the fileName was null that means the folder is inaccessible
|
||||
// for some reason
|
||||
if (fileName == null)
|
||||
throw new IllegalArgumentException("Game folder is not accessible");
|
||||
|
||||
|
||||
File f = new File(fileName);
|
||||
|
||||
if (!f.exists())
|
||||
{
|
||||
// there wasn't a file, don't
|
||||
// return anything
|
||||
continue;
|
||||
}
|
||||
String data = "";
|
||||
BufferedReader bufferedReader = new BufferedReader(new FileReader(f));
|
||||
data = bufferedReader.readLine();
|
||||
int fileVersion = -1;
|
||||
|
||||
if (data != null && !data.isEmpty())
|
||||
{
|
||||
// try to get the file version
|
||||
try
|
||||
{
|
||||
fileVersion = Integer.parseInt(data.substring(data.indexOf(' ')).trim());
|
||||
} catch (NumberFormatException | StringIndexOutOfBoundsException e)
|
||||
{
|
||||
// this file doesn't have a version
|
||||
// keep the version as -1
|
||||
fileVersion = -1;
|
||||
}
|
||||
|
||||
// check if this file can be read by this file handler
|
||||
if (fileVersion < LOD_SAVE_FILE_VERSION)
|
||||
{
|
||||
// the file we are reading is an older version,
|
||||
// close the reader and delete the file.
|
||||
bufferedReader.close();
|
||||
f.delete();
|
||||
ClientProxy.LOGGER.info("Outdated LOD region file for region: (" + regionX + "," + regionZ + ") version: " + fileVersion +
|
||||
", version requested: " + LOD_SAVE_FILE_VERSION +
|
||||
" File was been deleted.");
|
||||
|
||||
break;
|
||||
} else if (fileVersion > LOD_SAVE_FILE_VERSION)
|
||||
{
|
||||
// the file we are reading is a newer version,
|
||||
// close the reader and ignore the file, we don't
|
||||
// want to accidently delete anything the user may want.
|
||||
bufferedReader.close();
|
||||
ClientProxy.LOGGER.info("Newer LOD region file for region: (" + regionX + "," + regionZ + ") version: " + fileVersion +
|
||||
", version requested: " + LOD_SAVE_FILE_VERSION +
|
||||
" this region will not be written to in order to protect the newer file.");
|
||||
|
||||
break;
|
||||
}
|
||||
} else
|
||||
{
|
||||
// there is no data in this file
|
||||
bufferedReader.close();
|
||||
break;
|
||||
}
|
||||
|
||||
// this file is a readable version, begin reading the file
|
||||
data = bufferedReader.readLine();
|
||||
|
||||
bufferedReader.close();
|
||||
region.addLevel(new LevelContainer(data));
|
||||
} catch (Exception e)
|
||||
{
|
||||
// the buffered reader encountered a
|
||||
// problem reading the file
|
||||
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
if (region.getMinDetailLevel() >= detailLevel)
|
||||
region.expand(detailLevel);
|
||||
return region;
|
||||
}
|
||||
|
||||
|
||||
//==============//
|
||||
// Save to File //
|
||||
//==============//
|
||||
|
||||
/**
|
||||
* Save all dirty regions in this LodDimension to file.
|
||||
*/
|
||||
public void saveDirtyRegionsToFileAsync()
|
||||
{
|
||||
fileWritingThreadPool.execute(saveDirtyRegionsThread);
|
||||
}
|
||||
|
||||
private Thread saveDirtyRegionsThread = new Thread(() ->
|
||||
{
|
||||
try
|
||||
{
|
||||
for (int i = 0; i < loadedDimension.getWidth(); i++)
|
||||
{
|
||||
for (int j = 0; j < loadedDimension.getWidth(); j++)
|
||||
{
|
||||
if (loadedDimension.isRegionDirty[i][j] && loadedDimension.regions[i][j] != null)
|
||||
{
|
||||
saveRegionToFile(loadedDimension.regions[i][j]);
|
||||
loadedDimension.isRegionDirty[i][j] = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Exception e)
|
||||
{
|
||||
e.printStackTrace();
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Save a specific region to disk.<br>
|
||||
* Note: <br>
|
||||
* 1. If a file already exists for a newer version
|
||||
* the file won't be written.<br>
|
||||
* 2. This will save to the LodDimension that this
|
||||
* handler is associated with.
|
||||
*/
|
||||
private void saveRegionToFile(LodRegion region)
|
||||
{
|
||||
// convert to region coordinates
|
||||
int x = region.regionPosX;
|
||||
int z = region.regionPosZ;
|
||||
for (byte detailLevel = region.getMinDetailLevel(); detailLevel <= LodUtil.REGION_DETAIL_LEVEL; detailLevel++)
|
||||
{
|
||||
String fileName = getFileNameAndPathForRegion(x, z, region.getGenerationMode(), detailLevel);
|
||||
File oldFile = new File(fileName);
|
||||
|
||||
// if the fileName was null that means the folder is inaccessible
|
||||
// for some reason
|
||||
if (fileName == null)
|
||||
{
|
||||
ClientProxy.LOGGER.warn("Unable to save region [" + x + ", " + z + "] to file.");
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
try
|
||||
{
|
||||
// make sure the file and folder exists
|
||||
if (!oldFile.exists())
|
||||
{
|
||||
// the file doesn't exist,
|
||||
// create it and the folder if need be
|
||||
if (!oldFile.getParentFile().exists())
|
||||
oldFile.getParentFile().mkdirs();
|
||||
oldFile.createNewFile();
|
||||
} else
|
||||
{
|
||||
// the file exists, make sure it
|
||||
// is the correct version.
|
||||
// (to make sure we don't overwrite a newer
|
||||
// version file if it exists)
|
||||
|
||||
BufferedReader br = new BufferedReader(new FileReader(oldFile));
|
||||
String s = br.readLine();
|
||||
int fileVersion = LOD_SAVE_FILE_VERSION;
|
||||
|
||||
if (s != null && !s.isEmpty())
|
||||
{
|
||||
// try to get the file version
|
||||
try
|
||||
{
|
||||
fileVersion = Integer.parseInt(s.substring(s.indexOf(' ')).trim());
|
||||
} catch (NumberFormatException | StringIndexOutOfBoundsException e)
|
||||
{
|
||||
// this file doesn't have a correctly formated version
|
||||
// just overwrite the file
|
||||
}
|
||||
}
|
||||
br.close();
|
||||
|
||||
// check if this file can be written to by the file handler
|
||||
if (fileVersion <= LOD_SAVE_FILE_VERSION)
|
||||
{
|
||||
// we are good to continue and overwrite the old file
|
||||
} else // if(fileVersion > LOD_SAVE_FILE_VERSION)
|
||||
{
|
||||
// the file we are reading is a newer version,
|
||||
// don't write anything, we don't want to accidently
|
||||
// delete anything the user may want.
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// the old file is good, now create a new save file
|
||||
File newFile = new File(fileName + TMP_FILE_EXTENSION);
|
||||
FileWriter fw = new FileWriter(newFile);
|
||||
|
||||
// add the version of this file
|
||||
fw.write(LOD_FILE_VERSION_PREFIX + " " + LOD_SAVE_FILE_VERSION + "\n");
|
||||
|
||||
// add each LodChunk to the file
|
||||
fw.write(region.getLevel(detailLevel).toString());
|
||||
fw.close();
|
||||
|
||||
// overwrite the old file with the new one
|
||||
Files.move(newFile.toPath(), oldFile.toPath(), StandardCopyOption.ATOMIC_MOVE, StandardCopyOption.REPLACE_EXISTING);
|
||||
} catch (Exception e)
|
||||
{
|
||||
ClientProxy.LOGGER.error("LOD file write error: ");
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//================//
|
||||
// helper methods //
|
||||
//================//
|
||||
|
||||
|
||||
/**
|
||||
* Return the name of the file that should contain the
|
||||
* region at the given x and z. <br>
|
||||
* Returns null if this object isn't ready to read and write. <br><br>
|
||||
* <p>
|
||||
* example: "lod.0.0.txt" <br><br>
|
||||
* <p>
|
||||
* Returns null if there is an IO Exception.
|
||||
*/
|
||||
private String getFileNameAndPathForRegion(int regionX, int regionZ, DistanceGenerationMode generationMode, byte detailLevel)
|
||||
{
|
||||
try
|
||||
{
|
||||
// saveFolder is something like
|
||||
// ".\Super Flat\DIM-1\data"
|
||||
// or
|
||||
// ".\Super Flat\data"
|
||||
return dimensionDataSaveFolder.getCanonicalPath() + File.separatorChar +
|
||||
generationMode.toString() + File.separatorChar +
|
||||
DETAIL_FOLDER_NAME_PREFIX + detailLevel + File.separatorChar +
|
||||
FILE_NAME_PREFIX + "." + regionX + "." + regionZ + FILE_EXTENSION;
|
||||
} catch (IOException | SecurityException e)
|
||||
{
|
||||
ClientProxy.LOGGER.warn("Unable to get the filename for the region [" + regionX + ", " + regionZ + "], error: [" + e.getMessage() + "], stacktrace: ");
|
||||
e.printStackTrace();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,8 +1,25 @@
|
||||
package com.backsun.lod.handlers;
|
||||
/*
|
||||
* This file is part of the LOD Mod, licensed under the GNU GPL v3 License.
|
||||
*
|
||||
* Copyright (C) 2020 James Seibel
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, version 3.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.seibel.lod.handlers;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
|
||||
import com.backsun.lod.util.enums.FogQuality;
|
||||
import com.seibel.lod.enums.FogQuality;
|
||||
|
||||
import net.minecraft.client.Minecraft;
|
||||
|
||||
@@ -10,52 +27,46 @@ import net.minecraft.client.Minecraft;
|
||||
* This object is used to get variables from methods
|
||||
* where they are private. Specifically the fog setting
|
||||
* in Optifine.
|
||||
*
|
||||
*
|
||||
* @author James Seibel
|
||||
* @version 09-21-2020
|
||||
* @version 7-03-2021
|
||||
*/
|
||||
public class ReflectionHandler
|
||||
{
|
||||
private Minecraft mc = Minecraft.getInstance();
|
||||
public Field ofFogField = null;
|
||||
|
||||
|
||||
|
||||
public ReflectionHandler()
|
||||
{
|
||||
setupFogField();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Similar to setupFovMethod.
|
||||
*/
|
||||
private void setupFogField()
|
||||
{
|
||||
// get every variable from the entity renderer
|
||||
Field[] vars = mc.gameSettings.getClass().getDeclaredFields();
|
||||
|
||||
Field[] optionFields = mc.options.getClass().getDeclaredFields();
|
||||
|
||||
// try and find the ofFogType variable in gameSettings
|
||||
for(Field f : vars)
|
||||
for (Field field : optionFields)
|
||||
{
|
||||
if(f.getName().equals("ofFogType"))
|
||||
if (field.getName().equals("ofFogType"))
|
||||
{
|
||||
ofFogField = f;
|
||||
ofFogField = field;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// we didn't find the field,
|
||||
// either optifine isn't installed, or
|
||||
// optifine changed the name of the variable
|
||||
ofFogField = null;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Get what type of fog optifine is currently set to render.
|
||||
*/
|
||||
@@ -68,32 +79,35 @@ public class ReflectionHandler
|
||||
// the setup method wasn't called yet.
|
||||
return FogQuality.FANCY;
|
||||
}
|
||||
|
||||
|
||||
int returnNum = 0;
|
||||
|
||||
|
||||
try
|
||||
{
|
||||
returnNum = (int)ofFogField.get(mc.gameSettings);
|
||||
}
|
||||
catch (IllegalArgumentException | IllegalAccessException e)
|
||||
returnNum = (int) ofFogField.get(mc.options);
|
||||
} catch (IllegalArgumentException | IllegalAccessException e)
|
||||
{
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
|
||||
switch (returnNum)
|
||||
{
|
||||
// optifine's "default" option,
|
||||
// it should never be called in this case
|
||||
case 0:
|
||||
return FogQuality.FAST;
|
||||
|
||||
// normal options
|
||||
case 1:
|
||||
return FogQuality.FAST;
|
||||
case 2:
|
||||
return FogQuality.FANCY;
|
||||
case 3:
|
||||
return FogQuality.OFF;
|
||||
|
||||
|
||||
default:
|
||||
return FogQuality.FAST;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
* This file is part of the LOD Mod, licensed under the GNU GPL v3 License.
|
||||
*
|
||||
* Copyright (C) 2020 James Seibel
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, version 3.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.seibel.lod.mixin;
|
||||
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
|
||||
import com.mojang.blaze3d.matrix.MatrixStack;
|
||||
import com.seibel.lod.LodMain;
|
||||
import com.seibel.lod.config.LodConfig;
|
||||
|
||||
import net.minecraft.client.renderer.RenderType;
|
||||
import net.minecraft.client.renderer.WorldRenderer;
|
||||
|
||||
/**
|
||||
* This class is used to mix in my rendering code
|
||||
* before Minecraft starts rendering blocks.
|
||||
* If this wasn't done the LODs would render on top
|
||||
* of the normal terrain.
|
||||
*
|
||||
* @author James Seibel
|
||||
* @version 05-29-2021
|
||||
*/
|
||||
@Mixin(WorldRenderer.class)
|
||||
public class MixinWorldRenderer
|
||||
{
|
||||
private static float previousPartialTicks = 0;
|
||||
|
||||
@Inject(at = @At("RETURN"), method = "renderSky(Lcom/mojang/blaze3d/matrix/MatrixStack;F)V", cancellable = false)
|
||||
private void renderSky(MatrixStack matrixStackIn, float partialTicks, CallbackInfo callback)
|
||||
{
|
||||
// get the partial ticks since renderBlockLayer doesn't
|
||||
// have access to them
|
||||
previousPartialTicks = partialTicks;
|
||||
}
|
||||
|
||||
@Inject(at = @At("HEAD"), method = "renderChunkLayer(Lnet/minecraft/client/renderer/RenderType;Lcom/mojang/blaze3d/matrix/MatrixStack;DDD)V", cancellable = false)
|
||||
private void renderChunkLayer(RenderType renderType, MatrixStack matrixStackIn, double xIn, double yIn, double zIn, CallbackInfo callback)
|
||||
{
|
||||
// only render if LODs are enabled and
|
||||
// only render before solid blocks
|
||||
if (LodConfig.CLIENT.graphics.drawLODs.get() && renderType.equals(RenderType.solid()))
|
||||
LodMain.client_proxy.renderLods(previousPartialTicks);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
package com.seibel.lod.objects;
|
||||
|
||||
public class DataPoint
|
||||
{
|
||||
|
||||
public static short[] createDataPoint(int height, int depth, int red, int green, int blue)
|
||||
{
|
||||
return new short[]{(short) height, (short) depth, (short) red, (short) green, (short) blue};
|
||||
}
|
||||
|
||||
public static short getHeight(short[] dataPoint)
|
||||
{
|
||||
return dataPoint[0];
|
||||
}
|
||||
|
||||
public static short getDepth(short[] dataPoint)
|
||||
{
|
||||
return dataPoint[1];
|
||||
}
|
||||
|
||||
public static short getRed(short[] dataPoint)
|
||||
{
|
||||
return dataPoint[2];
|
||||
}
|
||||
|
||||
public static short getGreen(short[] dataPoint)
|
||||
{
|
||||
return dataPoint[3];
|
||||
}
|
||||
|
||||
public static short getBlue(short[] dataPoint)
|
||||
{
|
||||
return dataPoint[4];
|
||||
}
|
||||
|
||||
public static short[] getHeightDepth(short[] dataPoint)
|
||||
{
|
||||
return new short[]{dataPoint[0], dataPoint[1]};
|
||||
}
|
||||
|
||||
public static int getColor(short[] dataPoint)
|
||||
{
|
||||
int R = (dataPoint[2] << 16) & 0x00FF0000;
|
||||
int G = (dataPoint[3] << 8) & 0x0000FF00;
|
||||
int B = dataPoint[4] & 0x000000FF;
|
||||
return 0xFF000000 | R | G | B;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
package com.seibel.lod.objects;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
import com.seibel.lod.util.LodUtil;
|
||||
|
||||
public class LevelContainer implements Serializable
|
||||
{
|
||||
|
||||
public static final char DATA_DELIMITER = ',';
|
||||
|
||||
public final byte detailLevel;
|
||||
|
||||
public final byte[][][] colors;
|
||||
|
||||
public final short[][] height;
|
||||
|
||||
public final short[][] depth;
|
||||
|
||||
public final boolean[][] dataExistence;
|
||||
|
||||
public LevelContainer(byte detailLevel, byte[][][] colors, short[][] height, short[][] depth, boolean[][] dataExistence)
|
||||
{
|
||||
this.detailLevel = detailLevel;
|
||||
this.colors = colors;
|
||||
this.height = height;
|
||||
this.depth = depth;
|
||||
this.dataExistence = dataExistence;
|
||||
}
|
||||
|
||||
public LevelContainer(String data)
|
||||
{
|
||||
|
||||
int index = 0;
|
||||
int lastIndex = 0;
|
||||
|
||||
|
||||
index = data.indexOf(DATA_DELIMITER, 0);
|
||||
this.detailLevel = (byte) Integer.parseInt(data.substring(0, index));
|
||||
int size = (int) Math.pow(2, LodUtil.REGION_DETAIL_LEVEL - detailLevel);
|
||||
|
||||
this.colors = new byte[size][size][3];
|
||||
this.height = new short[size][size];
|
||||
this.depth = new short[size][size];
|
||||
this.dataExistence = new boolean[size][size];
|
||||
int intCol;
|
||||
for (int x = 0; x < size; x++)
|
||||
{
|
||||
for (int z = 0; z < size; z++)
|
||||
{
|
||||
lastIndex = index;
|
||||
index = data.indexOf(DATA_DELIMITER, lastIndex + 1);
|
||||
intCol = Integer.parseInt(data.substring(lastIndex + 1, index), 16);
|
||||
colors[x][z][0] = (byte) ((intCol >> 16) - 128);
|
||||
colors[x][z][1] = (byte) ((intCol >> 8) - 128);
|
||||
colors[x][z][2] = (byte) (intCol - 128);
|
||||
|
||||
lastIndex = index;
|
||||
index = data.indexOf(DATA_DELIMITER, lastIndex + 1);
|
||||
height[x][z] = Short.parseShort(data.substring(lastIndex + 1, index), 16);
|
||||
|
||||
lastIndex = index;
|
||||
index = data.indexOf(DATA_DELIMITER, lastIndex + 1);
|
||||
depth[x][z] = Short.parseShort(data.substring(lastIndex + 1, index), 16);
|
||||
|
||||
lastIndex = index;
|
||||
index = data.indexOf(DATA_DELIMITER, lastIndex + 1);
|
||||
dataExistence[x][z] = Boolean.parseBoolean(data.substring(lastIndex + 1, index));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
StringBuilder stringBuilder = new StringBuilder();
|
||||
int combinedCol;
|
||||
int size = (int) Math.pow(2, LodUtil.REGION_DETAIL_LEVEL - detailLevel);
|
||||
stringBuilder.append(detailLevel);
|
||||
stringBuilder.append(DATA_DELIMITER);
|
||||
for (int x = 0; x < size; x++)
|
||||
{
|
||||
for (int z = 0; z < size; z++)
|
||||
{
|
||||
//Converting the colors to intColor and then to HEX
|
||||
combinedCol = ((colors[x][z][0] + 128) << 16) | ((colors[x][z][1] + 128) << 8) | ((colors[x][z][2] + 128));
|
||||
stringBuilder.append(Integer.toHexString(combinedCol));
|
||||
stringBuilder.append(DATA_DELIMITER);
|
||||
stringBuilder.append(Integer.toHexString(height[x][z] & 0xffff));
|
||||
stringBuilder.append(DATA_DELIMITER);
|
||||
stringBuilder.append(Integer.toHexString(depth[x][z] & 0xffff));
|
||||
stringBuilder.append(DATA_DELIMITER);
|
||||
stringBuilder.append(dataExistence[x][z]);
|
||||
stringBuilder.append(DATA_DELIMITER);
|
||||
}
|
||||
}
|
||||
return stringBuilder.toString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package com.seibel.lod.objects.LevelPos;
|
||||
|
||||
public interface ImmutableLevelPos
|
||||
{
|
||||
public LevelPos getConvertedLevelPos(byte newDetailLevel);
|
||||
public LevelPos getRegionModuleLevelPos();
|
||||
}
|
||||
@@ -0,0 +1,415 @@
|
||||
package com.seibel.lod.objects.LevelPos;
|
||||
|
||||
import com.seibel.lod.objects.RegionPos;
|
||||
import com.seibel.lod.util.LodUtil;
|
||||
import net.minecraft.util.math.ChunkPos;
|
||||
|
||||
import java.util.Comparator;
|
||||
|
||||
public class LevelPos implements Cloneable, ImmutableLevelPos, MutableLevelPos, Comparable<LevelPos>
|
||||
{
|
||||
public byte detailLevel;
|
||||
public int posX;
|
||||
public int posZ;
|
||||
public boolean flag;
|
||||
|
||||
public LevelPos()
|
||||
{
|
||||
}
|
||||
|
||||
public LevelPos(byte detailLevel, int posX, int posZ)
|
||||
{
|
||||
this.posX = posX;
|
||||
this.posZ = posZ;
|
||||
this.detailLevel = detailLevel;
|
||||
}
|
||||
|
||||
/**
|
||||
* this operation does not change the state
|
||||
*/
|
||||
public LevelPos getConvertedLevelPos(byte newDetailLevel)
|
||||
{
|
||||
if (newDetailLevel >= detailLevel)
|
||||
{
|
||||
int width = 1 << (newDetailLevel - detailLevel);
|
||||
return new LevelPos(
|
||||
newDetailLevel,
|
||||
Math.floorDiv(posX, width),
|
||||
Math.floorDiv(posZ, width));
|
||||
} else
|
||||
{
|
||||
int width = 1 << (detailLevel - newDetailLevel);
|
||||
return new LevelPos(
|
||||
newDetailLevel,
|
||||
posX * width,
|
||||
posZ * width);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* this operation does not change the state
|
||||
*/
|
||||
public LevelPos getRegionModuleLevelPos()
|
||||
{
|
||||
int width = 1 << (LodUtil.REGION_DETAIL_LEVEL - detailLevel);
|
||||
return new LevelPos(
|
||||
detailLevel,
|
||||
Math.floorMod(posX, width),
|
||||
Math.floorMod(posZ, width));
|
||||
}
|
||||
|
||||
/**
|
||||
* this operation changes the state
|
||||
*/
|
||||
public void convert(byte newDetailLevel)
|
||||
{
|
||||
if (newDetailLevel >= detailLevel)
|
||||
{
|
||||
int width = 1 << (newDetailLevel - detailLevel);
|
||||
detailLevel = newDetailLevel;
|
||||
posX = Math.floorDiv(posX, width);
|
||||
posZ = Math.floorDiv(posZ, width);
|
||||
} else
|
||||
{
|
||||
int width = 1 << (detailLevel - newDetailLevel);
|
||||
detailLevel = newDetailLevel;
|
||||
posX = posX * width;
|
||||
posZ = posZ * width;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* this operation changes the state
|
||||
*/
|
||||
public void performRegionModule()
|
||||
{
|
||||
int width = 1 << (LodUtil.REGION_DETAIL_LEVEL - detailLevel);
|
||||
posX = Math.floorMod(posX, width);
|
||||
posZ = Math.floorMod(posZ, width);
|
||||
}
|
||||
|
||||
/**
|
||||
* this operation changes the state
|
||||
*/
|
||||
public void applyOffset(int xOffset, int zOffset)
|
||||
{
|
||||
posX = posX + xOffset;
|
||||
posX = posZ + zOffset;
|
||||
}
|
||||
|
||||
/**
|
||||
* this operation changes the state
|
||||
*/
|
||||
public void changeParameters(byte newDetailLevel, int newPosX, int newPosZ)
|
||||
{
|
||||
detailLevel = newDetailLevel;
|
||||
posX = newPosX;
|
||||
posZ = newPosZ;
|
||||
}
|
||||
|
||||
public RegionPos getRegionPos()
|
||||
{
|
||||
int width = 1 << (LodUtil.REGION_DETAIL_LEVEL - detailLevel);
|
||||
return new RegionPos(
|
||||
Math.floorDiv(posX, width),
|
||||
Math.floorDiv(posZ, width));
|
||||
}
|
||||
|
||||
public int getRegionPosX()
|
||||
{
|
||||
int width = 1 << (LodUtil.REGION_DETAIL_LEVEL - detailLevel);
|
||||
return Math.floorDiv(posX, width);
|
||||
}
|
||||
|
||||
public int getRegionPosZ()
|
||||
{
|
||||
int width = 1 << (LodUtil.REGION_DETAIL_LEVEL - detailLevel);
|
||||
return Math.floorDiv(posZ, width);
|
||||
}
|
||||
|
||||
public int getChunkPosX()
|
||||
{
|
||||
if (LodUtil.CHUNK_DETAIL_LEVEL >= detailLevel)
|
||||
{
|
||||
int width = 1 << (LodUtil.CHUNK_DETAIL_LEVEL - detailLevel);
|
||||
return Math.floorDiv(posX, width);
|
||||
} else
|
||||
{
|
||||
int width = 1 << (detailLevel - LodUtil.CHUNK_DETAIL_LEVEL);
|
||||
return posX * width;
|
||||
}
|
||||
}
|
||||
|
||||
public int getChunkPosZ()
|
||||
{
|
||||
if (LodUtil.CHUNK_DETAIL_LEVEL >= detailLevel)
|
||||
{
|
||||
int width = 1 << (LodUtil.CHUNK_DETAIL_LEVEL - detailLevel);
|
||||
return Math.floorDiv(posZ, width);
|
||||
} else
|
||||
{
|
||||
int width = 1 << (detailLevel - LodUtil.CHUNK_DETAIL_LEVEL);
|
||||
return posZ * width;
|
||||
}
|
||||
}
|
||||
|
||||
public ChunkPos getChunkPos()
|
||||
{
|
||||
if (LodUtil.CHUNK_DETAIL_LEVEL >= detailLevel)
|
||||
{
|
||||
int width = 1 << (LodUtil.CHUNK_DETAIL_LEVEL - detailLevel);
|
||||
return new ChunkPos(
|
||||
Math.floorDiv(posX, width),
|
||||
Math.floorDiv(posZ, width));
|
||||
} else
|
||||
{
|
||||
int width = 1 << (detailLevel - LodUtil.CHUNK_DETAIL_LEVEL);
|
||||
return new ChunkPos(
|
||||
posX * width,
|
||||
posZ * width);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO fix the region disappearing for a second
|
||||
*/
|
||||
|
||||
public int maxDistance(int playerPosX, int playerPosZ, int regionPosX, int regionPosZ)
|
||||
{
|
||||
int width = 1 << detailLevel;
|
||||
|
||||
int startPosX = regionPosX * 512 + posX * width;
|
||||
int startPosZ = regionPosZ * 512 + posZ * width;
|
||||
int endPosX = startPosX + width;
|
||||
int endPosZ = startPosZ + width;
|
||||
|
||||
int maxDistance = (int) Math.sqrt(Math.pow(playerPosX - startPosX, 2) + Math.pow(playerPosZ - startPosZ, 2));
|
||||
maxDistance = Math.max(maxDistance, (int) Math.sqrt(Math.pow(playerPosX - startPosX, 2) + Math.pow(playerPosZ - endPosZ, 2)));
|
||||
maxDistance = Math.max(maxDistance, (int) Math.sqrt(Math.pow(playerPosX - endPosX, 2) + Math.pow(playerPosZ - startPosZ, 2)));
|
||||
maxDistance = Math.max(maxDistance, (int) Math.sqrt(Math.pow(playerPosX - endPosX, 2) + Math.pow(playerPosZ - endPosZ, 2)));
|
||||
|
||||
return maxDistance;
|
||||
}
|
||||
|
||||
public int maxDistance(int playerPosX, int playerPosZ)
|
||||
{
|
||||
int width = 1 << detailLevel;
|
||||
|
||||
int startPosX = posX * width;
|
||||
int startPosZ = posZ * width;
|
||||
int endPosX = startPosX + width;
|
||||
int endPosZ = startPosZ + width;
|
||||
|
||||
int maxDistance = (int) Math.sqrt(Math.pow(playerPosX - startPosX, 2) + Math.pow(playerPosZ - startPosZ, 2));
|
||||
maxDistance = Math.max(maxDistance, (int) Math.sqrt(Math.pow(playerPosX - startPosX, 2) + Math.pow(playerPosZ - endPosZ, 2)));
|
||||
maxDistance = Math.max(maxDistance, (int) Math.sqrt(Math.pow(playerPosX - endPosX, 2) + Math.pow(playerPosZ - startPosZ, 2)));
|
||||
maxDistance = Math.max(maxDistance, (int) Math.sqrt(Math.pow(playerPosX - endPosX, 2) + Math.pow(playerPosZ - endPosZ, 2)));
|
||||
|
||||
return maxDistance;
|
||||
}
|
||||
|
||||
public int minDistance(int playerPosX, int playerPosZ, int regionPosX, int regionPosZ)
|
||||
{
|
||||
int width = 1 << detailLevel;
|
||||
|
||||
int startPosX = regionPosX * 512 + posX * width;
|
||||
int startPosZ = regionPosZ * 512 + posZ * width;
|
||||
int endPosX = startPosX + width;
|
||||
int endPosZ = startPosZ + width;
|
||||
|
||||
boolean inXArea = playerPosX >= startPosX && playerPosX <= endPosX;
|
||||
boolean inZArea = playerPosZ >= startPosZ && playerPosZ <= endPosZ;
|
||||
if (inXArea && inZArea)
|
||||
{
|
||||
return 0;
|
||||
} else if (inXArea)
|
||||
{
|
||||
return Math.min(
|
||||
Math.abs(playerPosZ - startPosZ),
|
||||
Math.abs(playerPosZ - endPosZ)
|
||||
);
|
||||
} else if (inZArea)
|
||||
{
|
||||
return Math.min(
|
||||
Math.abs(playerPosX - startPosX),
|
||||
Math.abs(playerPosX - endPosX)
|
||||
);
|
||||
} else
|
||||
{
|
||||
int minDistance = (int) Math.sqrt(Math.pow(playerPosX - startPosX, 2) + Math.pow(playerPosZ - startPosZ, 2));
|
||||
minDistance = Math.min(minDistance, (int) Math.sqrt(Math.pow(playerPosX - startPosX, 2) + Math.pow(playerPosZ - endPosZ, 2)));
|
||||
minDistance = Math.min(minDistance, (int) Math.sqrt(Math.pow(playerPosX - endPosX, 2) + Math.pow(playerPosZ - startPosZ, 2)));
|
||||
minDistance = Math.min(minDistance, (int) Math.sqrt(Math.pow(playerPosX - endPosX, 2) + Math.pow(playerPosZ - endPosZ, 2)));
|
||||
return minDistance;
|
||||
}
|
||||
}
|
||||
|
||||
public int minDistance(int playerPosX, int playerPosZ)
|
||||
{
|
||||
int width = 1 << detailLevel;
|
||||
|
||||
int startPosX = posX * width;
|
||||
int startPosZ = posZ * width;
|
||||
int endPosX = startPosX + width;
|
||||
int endPosZ = startPosZ + width;
|
||||
|
||||
boolean inXArea = playerPosX >= startPosX && playerPosX <= endPosX;
|
||||
boolean inZArea = playerPosZ >= startPosZ && playerPosZ <= endPosZ;
|
||||
if (inXArea && inZArea)
|
||||
{
|
||||
return 0;
|
||||
} else if (inXArea)
|
||||
{
|
||||
return Math.min(
|
||||
Math.abs(playerPosZ - startPosZ),
|
||||
Math.abs(playerPosZ - endPosZ)
|
||||
);
|
||||
} else if (inZArea)
|
||||
{
|
||||
return Math.min(
|
||||
Math.abs(playerPosX - startPosX),
|
||||
Math.abs(playerPosX - endPosX)
|
||||
);
|
||||
} else
|
||||
{
|
||||
int minDistance = (int) Math.sqrt(Math.pow(playerPosX - startPosX, 2) + Math.pow(playerPosZ - startPosZ, 2));
|
||||
minDistance = Math.min(minDistance, (int) Math.sqrt(Math.pow(playerPosX - startPosX, 2) + Math.pow(playerPosZ - endPosZ, 2)));
|
||||
minDistance = Math.min(minDistance, (int) Math.sqrt(Math.pow(playerPosX - endPosX, 2) + Math.pow(playerPosZ - startPosZ, 2)));
|
||||
minDistance = Math.min(minDistance, (int) Math.sqrt(Math.pow(playerPosX - endPosX, 2) + Math.pow(playerPosZ - endPosZ, 2)));
|
||||
return minDistance;
|
||||
}
|
||||
}
|
||||
|
||||
public static LevelPosDistanceComparator getPosComparator(int playerPosX, int playerPosZ)
|
||||
{
|
||||
return new LevelPosDistanceComparator(playerPosX, playerPosZ);
|
||||
}
|
||||
|
||||
public static LevelPosDetailComparator getPosAndDetailComparator(int playerPosX, int playerPosZ)
|
||||
{
|
||||
return new LevelPosDetailComparator(playerPosX, playerPosZ);
|
||||
}
|
||||
|
||||
public static class LevelPosDistanceComparator implements Comparator<LevelPos>
|
||||
{
|
||||
int playerPosX;
|
||||
int playerPosZ;
|
||||
|
||||
public LevelPosDistanceComparator(int playerPosX, int playerPosZ)
|
||||
{
|
||||
this.playerPosX = playerPosX;
|
||||
this.playerPosZ = playerPosZ;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compare(LevelPos first, LevelPos second)
|
||||
{
|
||||
return Integer.compare(
|
||||
first.minDistance(playerPosX, playerPosZ),
|
||||
second.minDistance(playerPosX, playerPosZ));
|
||||
}
|
||||
}
|
||||
|
||||
public static class LevelPosDetailComparator implements Comparator<LevelPos>
|
||||
{
|
||||
int playerPosX;
|
||||
int playerPosZ;
|
||||
|
||||
public LevelPosDetailComparator(int playerPosX, int playerPosZ)
|
||||
{
|
||||
this.playerPosX = playerPosX;
|
||||
this.playerPosZ = playerPosZ;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compare(LevelPos first, LevelPos second)
|
||||
{
|
||||
int compareResult = Integer.compare(second.detailLevel, first.detailLevel);
|
||||
if (compareResult == 0)
|
||||
{
|
||||
compareResult = Integer.compare(
|
||||
first.minDistance(playerPosX, playerPosZ),
|
||||
second.minDistance(playerPosX, playerPosZ));
|
||||
}
|
||||
return compareResult;
|
||||
}
|
||||
}
|
||||
|
||||
public static LevelPosComparator getComparator()
|
||||
{
|
||||
return new LevelPosComparator();
|
||||
}
|
||||
|
||||
public static class LevelPosComparator implements Comparator<LevelPos>
|
||||
{
|
||||
@Override
|
||||
public int compare(LevelPos first, LevelPos second)
|
||||
{
|
||||
int compareResult = Integer.compare(first.detailLevel, second.detailLevel);
|
||||
if (compareResult == 0)
|
||||
{
|
||||
compareResult = Integer.compare(
|
||||
first.posX,
|
||||
second.posX);
|
||||
}
|
||||
if (compareResult == 0)
|
||||
{
|
||||
compareResult = Integer.compare(
|
||||
first.posZ,
|
||||
second.posZ);
|
||||
}
|
||||
return compareResult;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(LevelPos other)
|
||||
{
|
||||
int compareResult = Integer.compare(this.detailLevel, other.detailLevel);
|
||||
if (compareResult == 0)
|
||||
{
|
||||
compareResult = Integer.compare(
|
||||
this.posX,
|
||||
other.posX);
|
||||
}
|
||||
if (compareResult == 0)
|
||||
{
|
||||
compareResult = Integer.compare(
|
||||
this.posZ,
|
||||
other.posZ);
|
||||
}
|
||||
return compareResult;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public LevelPos clone()
|
||||
{
|
||||
return new LevelPos(detailLevel, posX, posZ);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode()
|
||||
{
|
||||
int hash = 7;
|
||||
hash = 31 * hash + (int) detailLevel;
|
||||
hash = 31 * hash + posX;
|
||||
hash = 31 * hash + posZ;
|
||||
return hash;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object other)
|
||||
{
|
||||
return (this.detailLevel == ((LevelPos) other).detailLevel &&
|
||||
this.posX == ((LevelPos) other).posX &&
|
||||
this.posZ == ((LevelPos) other).posZ);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
String s = (detailLevel + " " + posX + " " + posZ);
|
||||
return s;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package com.seibel.lod.objects.LevelPos;
|
||||
|
||||
import com.seibel.lod.util.LodUtil;
|
||||
|
||||
public interface MutableLevelPos
|
||||
{
|
||||
public void convert(byte newDetailLevel);
|
||||
|
||||
public void performRegionModule();
|
||||
|
||||
public void applyOffset(int xOffset, int zOffset);
|
||||
|
||||
public void changeParameters(byte newDetailLevel, int newPosX, int newPosZ);
|
||||
}
|
||||
@@ -0,0 +1,226 @@
|
||||
package com.seibel.lod.objects;
|
||||
|
||||
import com.seibel.lod.util.LodUtil;
|
||||
import net.minecraft.util.math.ChunkPos;
|
||||
|
||||
import java.util.Comparator;
|
||||
|
||||
public class LevelPosUtil
|
||||
{
|
||||
public static int[] convert(int[] levelPos, byte newDetailLevel)
|
||||
{
|
||||
return convert(getDetailLevel(levelPos), getPosX(levelPos), getPosZ(levelPos), newDetailLevel);
|
||||
}
|
||||
|
||||
public static int[] convert(byte detailLevel, int posX, int posZ, byte newDetailLevel)
|
||||
{
|
||||
int width;
|
||||
if (newDetailLevel >= detailLevel)
|
||||
{
|
||||
width = 1 << (newDetailLevel - detailLevel);
|
||||
return createLevelPos(
|
||||
newDetailLevel,
|
||||
Math.floorDiv(posX, width),
|
||||
Math.floorDiv(posZ, width));
|
||||
} else
|
||||
{
|
||||
width = 1 << (detailLevel - newDetailLevel);
|
||||
return createLevelPos(
|
||||
newDetailLevel,
|
||||
posX * width,
|
||||
posZ * width);
|
||||
}
|
||||
}
|
||||
|
||||
public static int[] createLevelPos(byte detailLevel, int posX, int posZ)
|
||||
{
|
||||
return new int[]{detailLevel, posX, posZ};
|
||||
}
|
||||
|
||||
public static int[] createLevelPos(byte detailLevel, int posX, int posZ, int distance)
|
||||
{
|
||||
return new int[]{detailLevel, posX, posZ, distance};
|
||||
}
|
||||
|
||||
|
||||
public static byte getDetailLevel(int[] levelPos)
|
||||
{
|
||||
return (byte) levelPos[0];
|
||||
}
|
||||
|
||||
public static int getPosX(int[] levelPos)
|
||||
{
|
||||
return levelPos[1];
|
||||
}
|
||||
|
||||
public static int getPosZ(int[] levelPos)
|
||||
{
|
||||
return levelPos[2];
|
||||
}
|
||||
|
||||
public static int getDistance(int[] levelPos)
|
||||
{
|
||||
return levelPos[3];
|
||||
}
|
||||
|
||||
public static int[] getRegionModule(int[] levelPos)
|
||||
{
|
||||
return getRegionModule(getDetailLevel(levelPos), getPosX(levelPos), getPosZ(levelPos));
|
||||
}
|
||||
|
||||
public static int[] getRegionModule(byte detailLevel, int posX, int posZ)
|
||||
{
|
||||
int width = 1 << (LodUtil.REGION_DETAIL_LEVEL - detailLevel);
|
||||
return createLevelPos(
|
||||
detailLevel,
|
||||
Math.floorMod(posX, width),
|
||||
Math.floorMod(posZ, width));
|
||||
}
|
||||
|
||||
public static int[] applyOffset(int[] levelPos, int xOffset, int zOffset)
|
||||
{
|
||||
return createLevelPos(
|
||||
getDetailLevel(levelPos),
|
||||
getPosX(levelPos) + xOffset,
|
||||
getPosZ(levelPos) + zOffset);
|
||||
}
|
||||
|
||||
public static int[] applyLevelOffset(int[] levelPos, byte detailOffset, int xOffset, int zOffset)
|
||||
{
|
||||
return createLevelPos(
|
||||
getDetailLevel(levelPos),
|
||||
getPosX(levelPos) + xOffset * (1 << detailOffset),
|
||||
getPosZ(levelPos) + zOffset * (1 << detailOffset));
|
||||
}
|
||||
|
||||
public static int getRegionPosX(int[] levelPos)
|
||||
{
|
||||
int width = 1 << (LodUtil.REGION_DETAIL_LEVEL - getDetailLevel(levelPos));
|
||||
return Math.floorDiv(getPosX(levelPos), width);
|
||||
}
|
||||
|
||||
public static int getRegionPosZ(int[] levelPos)
|
||||
{
|
||||
int width = 1 << (LodUtil.REGION_DETAIL_LEVEL - getDetailLevel(levelPos));
|
||||
return Math.floorDiv(getPosZ(levelPos), width);
|
||||
}
|
||||
|
||||
public static int getChunkPosX(int[] levelPos)
|
||||
{
|
||||
levelPos = convert(levelPos, LodUtil.CHUNK_DETAIL_LEVEL);
|
||||
return getPosX(levelPos);
|
||||
}
|
||||
|
||||
public static int getChunkPosZ(int[] levelPos)
|
||||
{
|
||||
levelPos = convert(levelPos, LodUtil.CHUNK_DETAIL_LEVEL);
|
||||
return getPosZ(levelPos);
|
||||
}
|
||||
|
||||
public static int maxDistance(int[] levelPos, int playerPosX, int playerPosZ)
|
||||
{
|
||||
return maxDistance(getDetailLevel(levelPos), getPosX(levelPos), getPosZ(levelPos), playerPosX, playerPosZ);
|
||||
}
|
||||
|
||||
public static int maxDistance(byte detailLevel, int posX, int posZ, int playerPosX, int playerPosZ)
|
||||
{
|
||||
int width = 1 << detailLevel;
|
||||
|
||||
int startPosX = posX * width;
|
||||
int startPosZ = posZ * width;
|
||||
int endPosX = startPosX + width;
|
||||
int endPosZ = startPosZ + width;
|
||||
|
||||
int maxDistance = (int) Math.sqrt(Math.pow(playerPosX - startPosX, 2) + Math.pow(playerPosZ - startPosZ, 2));
|
||||
maxDistance = Math.max(maxDistance, (int) Math.sqrt(Math.pow(playerPosX - startPosX, 2) + Math.pow(playerPosZ - endPosZ, 2)));
|
||||
maxDistance = Math.max(maxDistance, (int) Math.sqrt(Math.pow(playerPosX - endPosX, 2) + Math.pow(playerPosZ - startPosZ, 2)));
|
||||
maxDistance = Math.max(maxDistance, (int) Math.sqrt(Math.pow(playerPosX - endPosX, 2) + Math.pow(playerPosZ - endPosZ, 2)));
|
||||
|
||||
return maxDistance;
|
||||
}
|
||||
|
||||
|
||||
public static int minDistance(int[] levelPos, int playerPosX, int playerPosZ)
|
||||
{
|
||||
return minDistance(getDetailLevel(levelPos), getPosX(levelPos), getPosZ(levelPos), playerPosX, playerPosZ);
|
||||
}
|
||||
|
||||
public static int minDistance(byte detailLevel, int posX, int posZ, int playerPosX, int playerPosZ)
|
||||
{
|
||||
int width = 1 << detailLevel;
|
||||
|
||||
int startPosX = posX * width;
|
||||
int startPosZ = posZ * width;
|
||||
int endPosX = startPosX + width;
|
||||
int endPosZ = startPosZ + width;
|
||||
|
||||
boolean inXArea = playerPosX >= startPosX && playerPosX <= endPosX;
|
||||
boolean inZArea = playerPosZ >= startPosZ && playerPosZ <= endPosZ;
|
||||
if (inXArea && inZArea)
|
||||
{
|
||||
return 0;
|
||||
} else if (inXArea)
|
||||
{
|
||||
return Math.min(
|
||||
Math.abs(playerPosZ - startPosZ),
|
||||
Math.abs(playerPosZ - endPosZ)
|
||||
);
|
||||
} else if (inZArea)
|
||||
{
|
||||
return Math.min(
|
||||
Math.abs(playerPosX - startPosX),
|
||||
Math.abs(playerPosX - endPosX)
|
||||
);
|
||||
} else
|
||||
{
|
||||
int minDistance = (int) Math.sqrt(Math.pow(playerPosX - startPosX, 2) + Math.pow(playerPosZ - startPosZ, 2));
|
||||
minDistance = Math.min(minDistance, (int) Math.sqrt(Math.pow(playerPosX - startPosX, 2) + Math.pow(playerPosZ - endPosZ, 2)));
|
||||
minDistance = Math.min(minDistance, (int) Math.sqrt(Math.pow(playerPosX - endPosX, 2) + Math.pow(playerPosZ - startPosZ, 2)));
|
||||
minDistance = Math.min(minDistance, (int) Math.sqrt(Math.pow(playerPosX - endPosX, 2) + Math.pow(playerPosZ - endPosZ, 2)));
|
||||
return minDistance;
|
||||
}
|
||||
}
|
||||
|
||||
public static int compareDistance(int posX, int posZ, int[] first, int[] second)
|
||||
{
|
||||
return Integer.compare(
|
||||
minDistance(first, posX, posZ),
|
||||
minDistance(second, posX, posZ));
|
||||
}
|
||||
|
||||
public static int compareDistance(int[] first, int[] second)
|
||||
{
|
||||
return Integer.compare(
|
||||
getDistance(first),
|
||||
getDistance(second));
|
||||
}
|
||||
|
||||
public static int compareLevelAndDistance(int[] first, int[] second)
|
||||
{
|
||||
int compareResult = Integer.compare(getDetailLevel(second), getDetailLevel(first));
|
||||
if (compareResult == 0)
|
||||
{
|
||||
compareResult = Integer.compare(
|
||||
getDistance(first),
|
||||
getDistance(second));
|
||||
}
|
||||
return compareResult;
|
||||
}
|
||||
|
||||
public static int compareLevelAndDistance(int posX, int posZ, int[] first, int[] second)
|
||||
{
|
||||
int compareResult = Integer.compare(getDetailLevel(second), getDetailLevel(first));
|
||||
if (compareResult == 0)
|
||||
{
|
||||
compareResult = Integer.compare(
|
||||
minDistance(first, posX, posZ),
|
||||
minDistance(second, posX, posZ));
|
||||
}
|
||||
return compareResult;
|
||||
}
|
||||
|
||||
public static String toString(int[] levelPos)
|
||||
{
|
||||
return (getDetailLevel(levelPos) + " " + getPosX(levelPos) + " " + getPosZ(levelPos));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,727 @@
|
||||
/*
|
||||
* This file is part of the LOD Mod, licensed under the GNU GPL v3 License.
|
||||
*
|
||||
* Copyright (C) 2020 James Seibel
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, version 3.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.seibel.lod.objects;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.security.InvalidParameterException;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
import org.apache.commons.lang3.mutable.MutableBoolean;
|
||||
|
||||
import com.seibel.lod.config.LodConfig;
|
||||
import com.seibel.lod.enums.DistanceGenerationMode;
|
||||
import com.seibel.lod.handlers.LodDimensionFileHandler;
|
||||
import com.seibel.lod.objects.LevelPos.LevelPos;
|
||||
import com.seibel.lod.util.DetailDistanceUtil;
|
||||
import com.seibel.lod.util.LodThreadFactory;
|
||||
import com.seibel.lod.util.LodUtil;
|
||||
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.util.math.ChunkPos;
|
||||
import net.minecraft.world.DimensionType;
|
||||
import net.minecraft.world.server.ServerChunkProvider;
|
||||
import net.minecraft.world.server.ServerWorld;
|
||||
|
||||
|
||||
/**
|
||||
* This object holds all loaded LOD regions
|
||||
* for a given dimension.
|
||||
*
|
||||
* @author Leonardo Amato
|
||||
* @author James Seibel
|
||||
* @version 8-8-2021
|
||||
*/
|
||||
public class LodDimension
|
||||
{
|
||||
|
||||
public final DimensionType dimension;
|
||||
|
||||
/**
|
||||
* measured in regions
|
||||
*/
|
||||
private volatile int width;
|
||||
/**
|
||||
* measured in regions
|
||||
*/
|
||||
private volatile int halfWidth;
|
||||
|
||||
|
||||
public volatile LodRegion regions[][];
|
||||
public volatile boolean isRegionDirty[][];
|
||||
public volatile boolean regen[][];
|
||||
/** if true that means there are regions in this dimension
|
||||
* that need to have their buffers rebuilt. */
|
||||
public volatile boolean regenDimension = false;
|
||||
|
||||
private volatile RegionPos center;
|
||||
private volatile ChunkPos lastGenChunk;
|
||||
private volatile ChunkPos lastCutChunk;
|
||||
private LodDimensionFileHandler fileHandler;
|
||||
private ExecutorService cutAndGenThreads = Executors.newSingleThreadExecutor(new LodThreadFactory(this.getClass().getSimpleName() + " - cutAndGen"));
|
||||
|
||||
/**
|
||||
* Creates the dimension centered at (0,0)
|
||||
*
|
||||
* @param newWidth in regions
|
||||
*/
|
||||
public LodDimension(DimensionType newDimension, LodWorld lodWorld, int newWidth)
|
||||
{
|
||||
lastCutChunk = null;
|
||||
lastGenChunk = null;
|
||||
dimension = newDimension;
|
||||
width = newWidth;
|
||||
halfWidth = (int) Math.floor(width / 2);
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
if (newDimension != null && lodWorld != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
|
||||
File saveDir;
|
||||
if (mc.hasSingleplayerServer())
|
||||
{
|
||||
// local world
|
||||
|
||||
ServerWorld serverWorld = LodUtil.getServerWorldFromDimension(newDimension);
|
||||
|
||||
// provider needs a separate variable to prevent
|
||||
// the compiler from complaining
|
||||
ServerChunkProvider provider = serverWorld.getChunkSource();
|
||||
saveDir = new File(provider.dataStorage.dataFolder.getCanonicalFile().getPath() + File.separatorChar + "lod");
|
||||
} else
|
||||
{
|
||||
// connected to server
|
||||
|
||||
saveDir = new File(mc.gameDirectory.getCanonicalFile().getPath() +
|
||||
File.separatorChar + "lod server data" + File.separatorChar + LodUtil.getDimensionIDFromWorld(mc.level));
|
||||
}
|
||||
|
||||
fileHandler = new LodDimensionFileHandler(saveDir, this);
|
||||
} catch (IOException e)
|
||||
{
|
||||
// the file handler wasn't able to be created
|
||||
// we won't be able to read or write any files
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
regions = new LodRegion[width][width];
|
||||
isRegionDirty = new boolean[width][width];
|
||||
regen = new boolean[width][width];
|
||||
|
||||
//treeGenerator((int) mc.player.getX(),(int) mc.player.getZ());
|
||||
|
||||
// populate isRegionDirty
|
||||
for (int i = 0; i < width; i++)
|
||||
for (int j = 0; j < width; j++)
|
||||
isRegionDirty[i][j] = false;
|
||||
|
||||
center = new RegionPos(0, 0);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Move the center of this LodDimension and move all owned
|
||||
* regions over by the given x and z offset. <br><br>
|
||||
* <p>
|
||||
* Synchronized to prevent multiple moves happening on top of each other.
|
||||
*/
|
||||
public synchronized void move(RegionPos regionOffset)
|
||||
{
|
||||
int xOffset = regionOffset.x;
|
||||
int zOffset = regionOffset.z;
|
||||
|
||||
// if the x or z offset is equal to or greater than
|
||||
// the total size, just delete the current data
|
||||
// and update the centerX and/or centerZ
|
||||
if (Math.abs(xOffset) >= width || Math.abs(zOffset) >= width)
|
||||
{
|
||||
for (int x = 0; x < width; x++)
|
||||
{
|
||||
for (int z = 0; z < width; z++)
|
||||
{
|
||||
regions[x][z] = null;
|
||||
}
|
||||
}
|
||||
|
||||
// update the new center
|
||||
center.x += xOffset;
|
||||
center.z += zOffset;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// X
|
||||
if (xOffset > 0)
|
||||
{
|
||||
// move everything over to the left (as the center moves to the right)
|
||||
for (int x = 0; x < width; x++)
|
||||
{
|
||||
for (int z = 0; z < width; z++)
|
||||
{
|
||||
if (x + xOffset < width)
|
||||
regions[x][z] = regions[x + xOffset][z];
|
||||
else
|
||||
regions[x][z] = null;
|
||||
}
|
||||
}
|
||||
} else
|
||||
{
|
||||
// move everything over to the right (as the center moves to the left)
|
||||
for (int x = width - 1; x >= 0; x--)
|
||||
{
|
||||
for (int z = 0; z < width; z++)
|
||||
{
|
||||
if (x + xOffset >= 0)
|
||||
regions[x][z] = regions[x + xOffset][z];
|
||||
else
|
||||
{
|
||||
regions[x][z] = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Z
|
||||
if (zOffset > 0)
|
||||
{
|
||||
// move everything up (as the center moves down)
|
||||
for (int x = 0; x < width; x++)
|
||||
{
|
||||
for (int z = 0; z < width; z++)
|
||||
{
|
||||
if (z + zOffset < width)
|
||||
regions[x][z] = regions[x][z + zOffset];
|
||||
else
|
||||
regions[x][z] = null;
|
||||
}
|
||||
}
|
||||
} else
|
||||
{
|
||||
// move everything down (as the center moves up)
|
||||
for (int x = 0; x < width; x++)
|
||||
{
|
||||
for (int z = width - 1; z >= 0; z--)
|
||||
{
|
||||
if (z + zOffset >= 0)
|
||||
regions[x][z] = regions[x][z + zOffset];
|
||||
else
|
||||
regions[x][z] = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// update the new center
|
||||
center.x += xOffset;
|
||||
center.z += zOffset;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* return needed memory in byte
|
||||
*/
|
||||
public int getMinMemoryNeeded()
|
||||
{
|
||||
int regionX;
|
||||
int regionZ;
|
||||
int count = 0;
|
||||
LodRegion region;
|
||||
|
||||
for (int x = 0; x < regions.length; x++)
|
||||
{
|
||||
for (int z = 0; z < regions.length; z++)
|
||||
{
|
||||
region = regions[x][z];
|
||||
if (region != null)
|
||||
{
|
||||
count += region.getMinMemoryNeeded();
|
||||
}
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the region at the given X and Z
|
||||
* <br>
|
||||
* Returns null if the region doesn't exist
|
||||
* or is outside the loaded area.
|
||||
*/
|
||||
public LodRegion getRegion(LevelPos levelPos)
|
||||
{
|
||||
|
||||
RegionPos regionPos = levelPos.getRegionPos();
|
||||
int xIndex = (regionPos.x - center.x) + halfWidth;
|
||||
int zIndex = (regionPos.z - center.z) + halfWidth;
|
||||
|
||||
if (!regionIsInRange(regionPos.x, regionPos.z))
|
||||
throw new ArrayIndexOutOfBoundsException("Region for level pos " + levelPos + " out of range");
|
||||
else if (regions[xIndex][zIndex] == null)
|
||||
throw new InvalidParameterException("Region for level pos " + levelPos + " not currently initialized");
|
||||
else if (regions[xIndex][zIndex].getMinDetailLevel() > levelPos.detailLevel)
|
||||
throw new InvalidParameterException("Region for level pos " + levelPos + " currently only reach level " + regions[xIndex][zIndex].getMinDetailLevel());
|
||||
return regions[xIndex][zIndex];
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the region at the given X and Z
|
||||
* <br>
|
||||
* Returns null if the region doesn't exist
|
||||
* or is outside the loaded area.
|
||||
*/
|
||||
public LodRegion getRegion(RegionPos regionPos)
|
||||
{
|
||||
int xIndex = (regionPos.x - center.x) + halfWidth;
|
||||
int zIndex = (regionPos.z - center.z) + halfWidth;
|
||||
|
||||
if (!regionIsInRange(regionPos.x, regionPos.z))
|
||||
throw new ArrayIndexOutOfBoundsException("Region " + regionPos + " out of range");
|
||||
else if (regions[xIndex][zIndex] == null)
|
||||
throw new InvalidParameterException("Region " + regionPos + " not currently initialized");
|
||||
return regions[xIndex][zIndex];
|
||||
}
|
||||
|
||||
/**
|
||||
* Overwrite the LodRegion at the location of newRegion with newRegion.
|
||||
*
|
||||
* @throws ArrayIndexOutOfBoundsException if newRegion is outside what can be stored in this LodDimension.
|
||||
*/
|
||||
public synchronized void addOrOverwriteRegion(LodRegion newRegion) throws ArrayIndexOutOfBoundsException
|
||||
{
|
||||
int xIndex = (newRegion.regionPosX - center.x) + halfWidth;
|
||||
int zIndex = (center.z - newRegion.regionPosZ) + halfWidth;
|
||||
|
||||
if (!regionIsInRange(newRegion.regionPosX, newRegion.regionPosZ))
|
||||
// out of range
|
||||
throw new ArrayIndexOutOfBoundsException("Region " + newRegion.regionPosX + ", " + newRegion.regionPosZ + " out of range");
|
||||
|
||||
regions[xIndex][zIndex] = newRegion;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public void treeCutter(int playerPosX, int playerPosZ)
|
||||
{
|
||||
ChunkPos newPlayerChunk = (new LevelPos((byte) 0, playerPosX, playerPosZ)).getChunkPos();
|
||||
if (lastCutChunk == null)
|
||||
lastCutChunk = new ChunkPos(newPlayerChunk.x + 1, newPlayerChunk.z - 1);
|
||||
if (newPlayerChunk.x != lastCutChunk.x || newPlayerChunk.z != lastCutChunk.z)
|
||||
{
|
||||
lastCutChunk = newPlayerChunk;
|
||||
Thread thread = new Thread(() ->
|
||||
{
|
||||
int regionX;
|
||||
int regionZ;
|
||||
int minDistance;
|
||||
byte detail;
|
||||
byte levelToCut;
|
||||
LevelPos levelPos = new LevelPos();
|
||||
|
||||
for (int x = 0; x < regions.length; x++)
|
||||
{
|
||||
for (int z = 0; z < regions.length; z++)
|
||||
{
|
||||
regionX = (x + center.x) - halfWidth;
|
||||
regionZ = (z + center.z) - halfWidth;
|
||||
//we start checking from the first circle. If the whole region is in the circle
|
||||
//we proceed to cut all the level lower than the level of circle 1 and we break
|
||||
//if this is not the case w
|
||||
if (regions[x][z] != null)
|
||||
{
|
||||
levelPos.changeParameters(LodUtil.REGION_DETAIL_LEVEL, regionX, regionZ);
|
||||
minDistance = levelPos.minDistance(playerPosX, playerPosZ);
|
||||
detail = DetailDistanceUtil.getDistanceTreeCutInverse(minDistance);
|
||||
levelToCut = DetailDistanceUtil.getCutLodDetail(detail);
|
||||
if (regions[x][z].getMinDetailLevel() > levelToCut)
|
||||
{
|
||||
regions[x][z].cutTree(levelToCut);
|
||||
}
|
||||
}
|
||||
|
||||
}// region z
|
||||
}// region z
|
||||
|
||||
});
|
||||
cutAndGenThreads.execute(thread);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public void treeGenerator(int playerPosX, int playerPosZ)
|
||||
{
|
||||
DistanceGenerationMode generationMode = LodConfig.CLIENT.worldGenerator.distanceGenerationMode.get();
|
||||
ChunkPos newPlayerChunk = (new LevelPos((byte) 0, playerPosX, playerPosZ)).getChunkPos();
|
||||
|
||||
if (lastGenChunk == null)
|
||||
lastGenChunk = new ChunkPos(newPlayerChunk.x + 1, newPlayerChunk.z - 1);
|
||||
if (newPlayerChunk.x != lastGenChunk.x || newPlayerChunk.z != lastGenChunk.z)
|
||||
{
|
||||
lastGenChunk = newPlayerChunk;
|
||||
Thread thread = new Thread(() ->
|
||||
{
|
||||
int regionX;
|
||||
int regionZ;
|
||||
LodRegion region;
|
||||
int minDistance;
|
||||
byte detail;
|
||||
byte levelToGen;
|
||||
LevelPos levelPos = new LevelPos();
|
||||
for (int x = 0; x < regions.length; x++)
|
||||
{
|
||||
for (int z = 0; z < regions.length; z++)
|
||||
{
|
||||
regionX = (x + center.x) - halfWidth;
|
||||
regionZ = (z + center.z) - halfWidth;
|
||||
levelPos.changeParameters(LodUtil.REGION_DETAIL_LEVEL, regionX, regionZ);
|
||||
final RegionPos regionPos = new RegionPos(regionX, regionZ);
|
||||
region = regions[x][z];
|
||||
//We require that the region we are checking is loaded with at least this level
|
||||
|
||||
levelPos.changeParameters(LodUtil.REGION_DETAIL_LEVEL, regionX, regionZ);
|
||||
minDistance = levelPos.minDistance(playerPosX, playerPosZ);
|
||||
detail = DetailDistanceUtil.getDistanceTreeGenInverse(minDistance);
|
||||
levelToGen = DetailDistanceUtil.getLodGenDetail(detail).detailLevel;
|
||||
if (region == null || region.getGenerationMode() != generationMode)
|
||||
{
|
||||
//First case, region has to be initialized
|
||||
|
||||
//We check if there is a file at the target level
|
||||
regions[x][z] = getRegionFromFile(regionPos, levelToGen, generationMode);
|
||||
|
||||
//if there is no file we initialize the region
|
||||
if (regions[x][z] == null)
|
||||
{
|
||||
regions[x][z] = new LodRegion(levelToGen, regionPos, generationMode);
|
||||
}
|
||||
regen[x][z] = true;
|
||||
regenDimension = true;
|
||||
|
||||
} else if (region.getMinDetailLevel() > levelToGen)
|
||||
{
|
||||
//Second case, region has been initialized but at a higher level
|
||||
//We expand the region by introducing the missing layer
|
||||
region.expand(levelToGen);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
cutAndGenThreads.execute(thread);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the given LOD to this dimension at the coordinate
|
||||
* stored in the LOD. If an LOD already exists at the given
|
||||
* coordinates it will be overwritten.
|
||||
*/
|
||||
public synchronized Boolean addData(LevelPos levelPos, short[] lodDataPoint, boolean dontSave, boolean serverQuality)
|
||||
{
|
||||
|
||||
// don't continue if the region can't be saved
|
||||
RegionPos regionPos = levelPos.getRegionPos();
|
||||
if (!regionIsInRange(regionPos.x, regionPos.z))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
LodRegion region = getRegion(levelPos);
|
||||
|
||||
boolean nodeAdded = region.addData(levelPos, lodDataPoint, serverQuality);
|
||||
// only save valid LODs to disk
|
||||
if (!dontSave && fileHandler != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
// mark the region as dirty so it will be saved to disk
|
||||
int xIndex = (regionPos.x - center.x) + halfWidth;
|
||||
int zIndex = (regionPos.z - center.z) + halfWidth;
|
||||
isRegionDirty[xIndex][zIndex] = true;
|
||||
regen[xIndex][zIndex] = true;
|
||||
regenDimension = true;
|
||||
} catch (ArrayIndexOutOfBoundsException e)
|
||||
{
|
||||
// This method was probably called when the dimension was changing size.
|
||||
// Hopefully this shouldn't be an issue.
|
||||
}
|
||||
}
|
||||
return nodeAdded;
|
||||
}
|
||||
|
||||
public void setToRegen(int xRegion, int zRegion){
|
||||
int xIndex = (xRegion - center.x) + halfWidth;
|
||||
int zIndex = (zRegion - center.z) + halfWidth;
|
||||
regen[xIndex][zIndex] = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* method to get all the quadtree level that have to be generated based on the position of the player
|
||||
*
|
||||
* @return list of quadTrees
|
||||
*/
|
||||
public void getDataToGenerate(ConcurrentMap<LevelPos, MutableBoolean> dataToGenerate, int playerPosX, int playerPosZ)
|
||||
{
|
||||
|
||||
int n = regions.length;
|
||||
int xIndex;
|
||||
int zIndex;
|
||||
LodRegion region;
|
||||
RegionPos regionPos;
|
||||
for (int xRegion = 0; xRegion < n; xRegion++)
|
||||
{
|
||||
for (int zRegion = 0; zRegion < n; zRegion++)
|
||||
{
|
||||
try
|
||||
{
|
||||
xIndex = (xRegion + center.x) - halfWidth;
|
||||
zIndex = (zRegion + center.z) - halfWidth;
|
||||
regionPos = new RegionPos(xIndex, zIndex);
|
||||
region = getRegion(regionPos);
|
||||
region.getDataToGenerate(dataToGenerate, playerPosX, playerPosZ);
|
||||
|
||||
} catch (Exception e)
|
||||
{
|
||||
//e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* method to get all the nodes that have to be rendered based on the position of the player
|
||||
*
|
||||
* @return list of nodes
|
||||
*/
|
||||
public void getDataToRender(ConcurrentMap<LevelPos, MutableBoolean> dataToRender, RegionPos regionPos, int playerPosX, int playerPosZ)
|
||||
{
|
||||
try
|
||||
{
|
||||
LodRegion region = getRegion(regionPos);
|
||||
region.getDataToRender(dataToRender, playerPosX, playerPosZ);
|
||||
} catch (NullPointerException e)
|
||||
{
|
||||
System.out.println(regionPos);
|
||||
e.printStackTrace();
|
||||
} catch (Exception e)
|
||||
{
|
||||
//e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the LodNodeData at the given X and Z coordinates
|
||||
* in this dimension.
|
||||
* <br>
|
||||
* Returns null if the LodChunk doesn't exist or
|
||||
* is outside the loaded area.
|
||||
*/
|
||||
public short[] getData(ChunkPos chunkPos)
|
||||
{
|
||||
LevelPos levelPos = new LevelPos(LodUtil.CHUNK_DETAIL_LEVEL, chunkPos.x, chunkPos.z);
|
||||
return getData(levelPos);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the data point at the given X and Z coordinates
|
||||
* in this dimension.
|
||||
* <br>
|
||||
* Returns null if the LodChunk doesn't exist or
|
||||
* is outside the loaded area.
|
||||
*/
|
||||
public short[] getData(LevelPos levelPos)
|
||||
{
|
||||
if (levelPos.detailLevel > LodUtil.REGION_DETAIL_LEVEL)
|
||||
throw new IllegalArgumentException("getLodFromCoordinates given a level of \"" + levelPos.detailLevel + "\" when \"" + LodUtil.REGION_DETAIL_LEVEL + "\" is the max.");
|
||||
|
||||
try
|
||||
{
|
||||
LodRegion region = getRegion(levelPos);
|
||||
|
||||
if (region == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return region.getData(levelPos);
|
||||
|
||||
} catch (Exception e)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get the data point at the given X and Z coordinates
|
||||
* in this dimension.
|
||||
* <br>
|
||||
* Returns null if the LodChunk doesn't exist or
|
||||
* is outside the loaded area.
|
||||
*/
|
||||
public void updateData(LevelPos levelPos)
|
||||
{
|
||||
if (levelPos.detailLevel > LodUtil.REGION_DETAIL_LEVEL)
|
||||
throw new IllegalArgumentException("getLodFromCoordinates given a level of \"" + levelPos.detailLevel + "\" when \"" + LodUtil.REGION_DETAIL_LEVEL + "\" is the max.");
|
||||
|
||||
LodRegion region = getRegion(levelPos);
|
||||
|
||||
|
||||
if (region == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
region.updateArea(levelPos);
|
||||
}
|
||||
|
||||
/**
|
||||
* return true if and only if the node at that position exist
|
||||
*/
|
||||
public boolean doesDataExist(LevelPos levelPos)
|
||||
{
|
||||
try
|
||||
{
|
||||
LodRegion region = getRegion(levelPos);
|
||||
|
||||
if (region == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return region.doesDataExist(levelPos.clone());
|
||||
} catch (Exception e)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the region at the given X and Z coordinates from the
|
||||
* RegionFileHandler.
|
||||
*/
|
||||
public LodRegion getRegionFromFile(RegionPos regionPos, byte detailLevel, DistanceGenerationMode generationMode)
|
||||
{
|
||||
if (fileHandler != null)
|
||||
return fileHandler.loadRegionFromFile(detailLevel, regionPos, generationMode);
|
||||
else
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Save all dirty regions in this LodDimension to file.
|
||||
*/
|
||||
public void saveDirtyRegionsToFileAsync()
|
||||
{
|
||||
fileHandler.saveDirtyRegionsToFileAsync();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns whether the region at the given X and Z coordinates
|
||||
* is within the loaded range.
|
||||
*/
|
||||
public boolean regionIsInRange(int regionX, int regionZ)
|
||||
{
|
||||
int xIndex = (regionX - center.x) + halfWidth;
|
||||
int zIndex = (regionZ - center.z) + halfWidth;
|
||||
|
||||
return xIndex >= 0 && xIndex < width && zIndex >= 0 && zIndex < width;
|
||||
}
|
||||
|
||||
|
||||
public int getCenterX()
|
||||
{
|
||||
return center.x;
|
||||
}
|
||||
|
||||
public int getCenterZ()
|
||||
{
|
||||
return center.z;
|
||||
}
|
||||
|
||||
|
||||
public int getWidth()
|
||||
{
|
||||
if (regions != null)
|
||||
{
|
||||
// we want to get the length directly from the
|
||||
// source to make sure it is in sync with region
|
||||
// and isRegionDirty
|
||||
return regions.length;
|
||||
} else
|
||||
{
|
||||
return width;
|
||||
}
|
||||
}
|
||||
|
||||
public void setRegionWidth(int newWidth)
|
||||
{
|
||||
width = newWidth;
|
||||
halfWidth = Math.floorDiv(width, 2);
|
||||
|
||||
regions = new LodRegion[width][width];
|
||||
isRegionDirty = new boolean[width][width];
|
||||
regen = new boolean[width][width];
|
||||
|
||||
// populate isRegionDirty
|
||||
for (int i = 0; i < width; i++)
|
||||
for (int j = 0; j < width; j++)
|
||||
isRegionDirty[i][j] = false;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
int regionX;
|
||||
int regionZ;
|
||||
LevelPos levelPos;
|
||||
LodRegion region;
|
||||
|
||||
StringBuilder stringBuilder = new StringBuilder();
|
||||
stringBuilder.append("Dimension : \n");
|
||||
for (int x = 0; x < regions.length; x++)
|
||||
{
|
||||
for (int z = 0; z < regions.length; z++)
|
||||
{
|
||||
region = regions[x][z];
|
||||
if (region == null)
|
||||
{
|
||||
stringBuilder.append("n");
|
||||
stringBuilder.append("\t");
|
||||
|
||||
} else
|
||||
{
|
||||
stringBuilder.append(region.getMinDetailLevel());
|
||||
stringBuilder.append("\t");
|
||||
}
|
||||
}
|
||||
stringBuilder.append("\n");
|
||||
}
|
||||
System.out.println(stringBuilder);
|
||||
return stringBuilder.toString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,602 @@
|
||||
package com.seibel.lod.objects;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
|
||||
import org.apache.commons.lang3.mutable.MutableBoolean;
|
||||
|
||||
import com.seibel.lod.builders.LodBuilder;
|
||||
import com.seibel.lod.enums.DistanceGenerationMode;
|
||||
import com.seibel.lod.objects.LevelPos.LevelPos;
|
||||
import com.seibel.lod.proxy.ClientProxy;
|
||||
import com.seibel.lod.util.DetailDistanceUtil;
|
||||
import com.seibel.lod.util.LodUtil;
|
||||
|
||||
/**
|
||||
* STANDARD TO FOLLOW
|
||||
* every coordinate called posX or posZ is a relative coordinate and not and absolute coordinate
|
||||
* if an array contain coordinate the order is the following
|
||||
* 0 for x, 1 for z in 2D
|
||||
* 0 for x, 1 for y, 2 for z in 3D
|
||||
*/
|
||||
|
||||
public class LodRegion implements Serializable
|
||||
{
|
||||
//x coord,
|
||||
private byte minDetailLevel;
|
||||
private static final byte POSSIBLE_LOD = 10;
|
||||
private int numberOfPoints;
|
||||
private DistanceGenerationMode generationMode;
|
||||
//For each of the following field the first slot is for the level of detail
|
||||
//Important: byte have a [-128, 127] range. When converting from or to int a 128 should be added or removed
|
||||
//If there is a bug with color then it's probably caused by this.
|
||||
//in the future other fields like transparency and light level could be added
|
||||
private byte[][][][] colors;
|
||||
|
||||
private short[][][] height;
|
||||
|
||||
private short[][][] depth;
|
||||
|
||||
private boolean[][][] dataExistence;
|
||||
|
||||
|
||||
public final int regionPosX;
|
||||
public final int regionPosZ;
|
||||
|
||||
public LodRegion(LevelContainer levelContainer, RegionPos regionPos, DistanceGenerationMode generationMode)
|
||||
{
|
||||
this.generationMode = generationMode;
|
||||
this.regionPosX = regionPos.x;
|
||||
this.regionPosZ = regionPos.z;
|
||||
this.minDetailLevel = levelContainer.detailLevel;
|
||||
|
||||
//Array of matrices of arrays
|
||||
colors = new byte[POSSIBLE_LOD][][][];
|
||||
|
||||
//Arrays of matrices
|
||||
height = new short[POSSIBLE_LOD][][];
|
||||
depth = new short[POSSIBLE_LOD][][];
|
||||
dataExistence = new boolean[POSSIBLE_LOD][][];
|
||||
|
||||
colors[minDetailLevel] = levelContainer.colors;
|
||||
height[minDetailLevel] = levelContainer.height;
|
||||
depth[minDetailLevel] = levelContainer.depth;
|
||||
dataExistence[minDetailLevel] = levelContainer.dataExistence;
|
||||
|
||||
//Initialize all the different matrices
|
||||
for (byte lod = (byte) (minDetailLevel + 1); lod <= LodUtil.REGION_DETAIL_LEVEL; lod++)
|
||||
{
|
||||
int size = (short) Math.pow(2, LodUtil.REGION_DETAIL_LEVEL - lod);
|
||||
colors[lod] = new byte[size][size][3];
|
||||
height[lod] = new short[size][size];
|
||||
depth[lod] = new short[size][size];
|
||||
dataExistence[lod] = new boolean[size][size];
|
||||
}
|
||||
int width;
|
||||
LevelPos levelPos = new LevelPos();
|
||||
for (byte tempLod = (byte) (minDetailLevel + 1); tempLod <= LodUtil.REGION_DETAIL_LEVEL; tempLod++)
|
||||
{
|
||||
width = 1 << (LodUtil.REGION_DETAIL_LEVEL - tempLod);
|
||||
for (int x = 0; x < width; x++)
|
||||
{
|
||||
for (int z = 0; z < width; z++)
|
||||
{
|
||||
levelPos.changeParameters(tempLod, x, z);
|
||||
update(levelPos);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public LodRegion(byte minDetailLevel, RegionPos regionPos, DistanceGenerationMode generationMode)
|
||||
{
|
||||
this.generationMode = generationMode;
|
||||
this.minDetailLevel = minDetailLevel;
|
||||
this.regionPosX = regionPos.x;
|
||||
this.regionPosZ = regionPos.z;
|
||||
|
||||
//Array of matrices of arrays
|
||||
colors = new byte[POSSIBLE_LOD][][][];
|
||||
|
||||
//Arrays of matrices
|
||||
height = new short[POSSIBLE_LOD][][];
|
||||
depth = new short[POSSIBLE_LOD][][];
|
||||
dataExistence = new boolean[POSSIBLE_LOD][][];
|
||||
|
||||
|
||||
//Initialize all the different matrices
|
||||
for (byte lod = minDetailLevel; lod <= LodUtil.REGION_DETAIL_LEVEL; lod++)
|
||||
{
|
||||
int size = (short) Math.pow(2, LodUtil.REGION_DETAIL_LEVEL - lod);
|
||||
colors[lod] = new byte[size][size][3];
|
||||
height[lod] = new short[size][size];
|
||||
depth[lod] = new short[size][size];
|
||||
dataExistence[lod] = new boolean[size][size];
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This method can be used to insert data into the LodRegion
|
||||
*
|
||||
* @param levelPos
|
||||
* @param dataPoint
|
||||
* @return
|
||||
*/
|
||||
public boolean addData(LevelPos levelPos, short[] dataPoint, boolean serverQuality)
|
||||
{
|
||||
levelPos.performRegionModule();
|
||||
if (!doesDataExist(levelPos) || serverQuality)
|
||||
{
|
||||
|
||||
//update the number of node present
|
||||
if (this.dataExistence[levelPos.detailLevel][levelPos.posX][levelPos.posZ]) numberOfPoints++;
|
||||
|
||||
//add the node data
|
||||
this.height[levelPos.detailLevel][levelPos.posX][levelPos.posZ] = DataPoint.getHeight(dataPoint);
|
||||
this.depth[levelPos.detailLevel][levelPos.posX][levelPos.posZ] = DataPoint.getDepth(dataPoint);
|
||||
this.colors[levelPos.detailLevel][levelPos.posX][levelPos.posZ][0] = (byte) (DataPoint.getRed(dataPoint) - 128);
|
||||
this.colors[levelPos.detailLevel][levelPos.posX][levelPos.posZ][1] = (byte) (DataPoint.getGreen(dataPoint) - 128);
|
||||
this.colors[levelPos.detailLevel][levelPos.posX][levelPos.posZ][2] = (byte) (DataPoint.getBlue(dataPoint) - 128);
|
||||
this.dataExistence[levelPos.detailLevel][levelPos.posX][levelPos.posZ] = true;
|
||||
return true;
|
||||
} else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This method will return the data in the position relative to the level of detail
|
||||
*
|
||||
* @param levelPos
|
||||
* @return the data at the relative pos and level
|
||||
*/
|
||||
public short[] getData(LevelPos levelPos)
|
||||
{
|
||||
levelPos = levelPos.getRegionModuleLevelPos();
|
||||
return new short[]{height[levelPos.detailLevel][levelPos.posX][levelPos.posZ],
|
||||
depth[levelPos.detailLevel][levelPos.posX][levelPos.posZ],
|
||||
(short) (colors[levelPos.detailLevel][levelPos.posX][levelPos.posZ][0] + 128),
|
||||
(short) (colors[levelPos.detailLevel][levelPos.posX][levelPos.posZ][1] + 128),
|
||||
(short) (colors[levelPos.detailLevel][levelPos.posX][levelPos.posZ][2] + 128)
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* This method will return all the levelPos that are renderable according to the requisite given in input
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public void getDataToGenerate(ConcurrentMap<LevelPos, MutableBoolean> dataToGenerate, int playerPosX, int playerPosZ)
|
||||
{
|
||||
LevelPos levelPos = new LevelPos(LodUtil.REGION_DETAIL_LEVEL, 0, 0);
|
||||
getDataToGenerate(dataToGenerate, levelPos, playerPosX, playerPosZ);
|
||||
|
||||
}
|
||||
|
||||
private void getDataToGenerate(ConcurrentMap<LevelPos, MutableBoolean> dataToGenerate, LevelPos levelPos, int playerPosX, int playerPosZ)
|
||||
{
|
||||
int size = 1 << (LodUtil.REGION_DETAIL_LEVEL - levelPos.detailLevel);
|
||||
|
||||
//here i calculate the the LevelPos is in range
|
||||
//This is important to avoid any kind of hole in the generation
|
||||
int minDistance = levelPos.minDistance(playerPosX, playerPosZ, regionPosX, regionPosZ);
|
||||
|
||||
|
||||
int posX = levelPos.posX;
|
||||
int posZ = levelPos.posZ;
|
||||
byte detailLevel = levelPos.detailLevel;
|
||||
byte childDetailLevel = (byte) (detailLevel - 1);
|
||||
int childPosX = posX * 2;
|
||||
int childPosZ = posZ * 2;
|
||||
|
||||
int childSize = 1 << (LodUtil.REGION_DETAIL_LEVEL - childDetailLevel);
|
||||
//we have reached the target detail level
|
||||
|
||||
if (DetailDistanceUtil.getDistanceGenerationInverse(minDistance) > detailLevel)
|
||||
{
|
||||
return;
|
||||
} else if (DetailDistanceUtil.getDistanceGenerationInverse(minDistance) == detailLevel)
|
||||
{
|
||||
if (!doesDataExist(levelPos))
|
||||
{
|
||||
levelPos.changeParameters(detailLevel, posX + regionPosX * size, posZ + regionPosZ * size);
|
||||
if (dataToGenerate.containsKey(levelPos))
|
||||
{
|
||||
dataToGenerate.get(levelPos).setTrue();
|
||||
} else
|
||||
{
|
||||
dataToGenerate.put(levelPos.clone(), new MutableBoolean(true));
|
||||
}
|
||||
}
|
||||
} else
|
||||
{
|
||||
//we want max a request per chunk. So for lod smaller than chunk we explore only the top rigth child
|
||||
if (detailLevel > LodUtil.CHUNK_DETAIL_LEVEL)
|
||||
{
|
||||
int num = 0;
|
||||
//We take all the children that are not generated to at least the generation level taken in input
|
||||
for (int x = 0; x <= 1; x++)
|
||||
{
|
||||
for (int z = 0; z <= 1; z++)
|
||||
{
|
||||
levelPos.changeParameters((byte) (detailLevel - 1), childPosX + x, childPosZ + z);
|
||||
|
||||
if (!doesDataExist(levelPos))
|
||||
{
|
||||
num++;
|
||||
levelPos.changeParameters((byte) (detailLevel - 1), childPosX + x + regionPosX * childSize, childPosZ + z + regionPosZ * childSize);
|
||||
if (dataToGenerate.containsKey(levelPos))
|
||||
{
|
||||
dataToGenerate.get(levelPos).setTrue();
|
||||
} else
|
||||
{
|
||||
dataToGenerate.put(levelPos.clone(), new MutableBoolean(true));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//only if all the children are correctly generated we go deeper
|
||||
if (num == 0)
|
||||
{
|
||||
for (int x = 0; x <= 1; x++)
|
||||
{
|
||||
for (int z = 0; z <= 1; z++)
|
||||
{
|
||||
levelPos.changeParameters((byte) (detailLevel - 1), childPosX + x, childPosZ + z);
|
||||
getDataToGenerate(dataToGenerate, levelPos, playerPosX, playerPosZ);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else
|
||||
//now we keep exploring the top right child
|
||||
{
|
||||
if (DetailDistanceUtil.getLodGenDetail(childDetailLevel).detailLevel <= (childDetailLevel))
|
||||
{
|
||||
levelPos.changeParameters(detailLevel, posX, posZ);
|
||||
levelPos.convert(childDetailLevel);
|
||||
if (!doesDataExist(levelPos))
|
||||
{
|
||||
levelPos.changeParameters(levelPos.detailLevel, levelPos.posX + regionPosX * childSize, levelPos.posZ + regionPosZ * childSize);
|
||||
if (dataToGenerate.containsKey(levelPos))
|
||||
{
|
||||
dataToGenerate.get(levelPos).setTrue();
|
||||
} else
|
||||
{
|
||||
dataToGenerate.put(levelPos.clone(), new MutableBoolean(true));
|
||||
}
|
||||
} else
|
||||
{
|
||||
getDataToGenerate(dataToGenerate, levelPos, playerPosX, playerPosZ);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return
|
||||
*/
|
||||
public void getDataToRender(ConcurrentMap<LevelPos, MutableBoolean> dataToRender, int playerPosX, int playerPosZ)
|
||||
{
|
||||
LevelPos levelPos = new LevelPos(LodUtil.REGION_DETAIL_LEVEL, 0, 0);
|
||||
getDataToRender(dataToRender, levelPos, playerPosX, playerPosZ);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return
|
||||
*/
|
||||
private void getDataToRender(ConcurrentMap<LevelPos, MutableBoolean> dataToRender, LevelPos levelPos, int playerPosX, int playerPosZ)
|
||||
{
|
||||
int size = 1 << (LodUtil.REGION_DETAIL_LEVEL - levelPos.detailLevel);
|
||||
|
||||
int posX = levelPos.posX;
|
||||
int posZ = levelPos.posZ;
|
||||
byte detailLevel = levelPos.detailLevel;
|
||||
|
||||
//here i calculate the the LevelPos is in range
|
||||
//This is important to avoid any kind of hole in the rendering
|
||||
int maxDistance = levelPos.maxDistance(playerPosX, playerPosZ, regionPosX, regionPosZ);
|
||||
|
||||
byte supposedLevel = DetailDistanceUtil.getLodDrawDetail(DetailDistanceUtil.getDistanceRenderingInverse(maxDistance));
|
||||
if (supposedLevel > detailLevel)
|
||||
return;
|
||||
else if (supposedLevel == detailLevel)
|
||||
{
|
||||
if (dataToRender.containsKey(levelPos))
|
||||
{
|
||||
levelPos.changeParameters(detailLevel, posX + regionPosX * size, posZ + regionPosZ * size);
|
||||
try
|
||||
{
|
||||
dataToRender.get(levelPos).setTrue();
|
||||
}catch (Exception e){
|
||||
/*TODO Fix this exception*/
|
||||
// This seems to happen more often when using an elytra in an amplified world
|
||||
// maybe it has something to do with the dimensions moving?
|
||||
ClientProxy.LOGGER.error("getDataToRender had a error at " + levelPos.getRegionPos() + ". Exception: " + e.getMessage());
|
||||
dataToRender.put(new LevelPos(detailLevel, posX + regionPosX * size, posZ + regionPosZ * size), new MutableBoolean(true));
|
||||
}
|
||||
} else
|
||||
{
|
||||
dataToRender.put(new LevelPos(detailLevel, posX + regionPosX * size, posZ + regionPosZ * size), new MutableBoolean(true));
|
||||
}
|
||||
} else //case where (detailLevel > supposedLevel)
|
||||
{
|
||||
int childPosX = posX * 2;
|
||||
int childPosZ = posZ * 2;
|
||||
int childrenCount = 0;
|
||||
for (int x = 0; x <= 1; x++)
|
||||
{
|
||||
for (int z = 0; z <= 1; z++)
|
||||
{
|
||||
levelPos.changeParameters((byte) (detailLevel - 1), childPosX + x, childPosZ + z);
|
||||
if (doesDataExist(levelPos)) childrenCount++;
|
||||
}
|
||||
}
|
||||
|
||||
//If all the four children exist we go deeper
|
||||
if (childrenCount == 4)
|
||||
{
|
||||
for (int x = 0; x <= 1; x++)
|
||||
{
|
||||
for (int z = 0; z <= 1; z++)
|
||||
{
|
||||
levelPos.changeParameters((byte) (detailLevel - 1), childPosX + x, childPosZ + z);
|
||||
getDataToRender(dataToRender, levelPos, playerPosX, playerPosZ);
|
||||
}
|
||||
}
|
||||
} else
|
||||
{
|
||||
|
||||
levelPos.changeParameters(detailLevel, posX + regionPosX * size, posZ + regionPosZ * size);
|
||||
if (dataToRender.containsKey(levelPos))
|
||||
{
|
||||
if (dataToRender.get(levelPos) == null)
|
||||
dataToRender.replace(levelPos, new MutableBoolean());
|
||||
dataToRender.get(levelPos).setTrue();
|
||||
} else
|
||||
{
|
||||
dataToRender.put(new LevelPos(detailLevel, posX + regionPosX * size, posZ + regionPosZ * size), new MutableBoolean(true));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param levelPos
|
||||
*/
|
||||
public void updateArea(LevelPos levelPos)
|
||||
{
|
||||
int width;
|
||||
int startX;
|
||||
int startZ;
|
||||
byte detailLevel = levelPos.detailLevel;
|
||||
int posX = levelPos.posX;
|
||||
int posZ = levelPos.posZ;
|
||||
for (byte bottom = (byte) (minDetailLevel + 1); bottom <= detailLevel; bottom++)
|
||||
{
|
||||
levelPos.convert(bottom);
|
||||
startX = levelPos.posX;
|
||||
startZ = levelPos.posZ;
|
||||
width = 1 << (detailLevel - bottom);
|
||||
for (int x = 0; x < width; x++)
|
||||
{
|
||||
for (int z = 0; z < width; z++)
|
||||
{
|
||||
levelPos.changeParameters(bottom, startX + x, startZ + z);
|
||||
update(levelPos);
|
||||
}
|
||||
}
|
||||
levelPos.changeParameters(detailLevel, posX, posZ);
|
||||
}
|
||||
for (byte tempLod = (byte) (detailLevel + 1); tempLod <= LodUtil.REGION_DETAIL_LEVEL; tempLod++)
|
||||
{
|
||||
levelPos.convert(tempLod);
|
||||
update(levelPos);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param levelPos
|
||||
*/
|
||||
private void update(LevelPos levelPos)
|
||||
{
|
||||
levelPos.performRegionModule();
|
||||
int numberOfChildren = 0;
|
||||
int numberOfVoidChildren = 0;
|
||||
|
||||
int tempRed = 0;
|
||||
int tempGreen = 0;
|
||||
int tempBlue = 0;
|
||||
int tempHeight = 0;
|
||||
int tempDepth = 0;
|
||||
int newPosX;
|
||||
int newPosZ;
|
||||
byte newDetailLevel;
|
||||
int detailLevel = levelPos.detailLevel;
|
||||
int posX = levelPos.posX;
|
||||
int posZ = levelPos.posZ;
|
||||
for (int x = 0; x <= 1; x++)
|
||||
{
|
||||
for (int z = 0; z <= 1; z++)
|
||||
{
|
||||
newPosX = 2 * posX + x;
|
||||
newPosZ = 2 * posZ + z;
|
||||
newDetailLevel = (byte) (detailLevel - 1);
|
||||
levelPos.changeParameters(newDetailLevel, newPosX, newPosZ);
|
||||
if (doesDataExist(levelPos))
|
||||
{
|
||||
if (height[newDetailLevel][newPosX][newPosZ] != LodBuilder.DEFAULT_HEIGHT
|
||||
&& depth[newDetailLevel][newPosX][newPosZ] != LodBuilder.DEFAULT_DEPTH)
|
||||
{
|
||||
numberOfChildren++;
|
||||
|
||||
tempRed += colors[newDetailLevel][newPosX][newPosZ][0];
|
||||
tempGreen += colors[newDetailLevel][newPosX][newPosZ][1];
|
||||
tempBlue += colors[newDetailLevel][newPosX][newPosZ][2];
|
||||
tempHeight += height[newDetailLevel][newPosX][newPosZ];
|
||||
tempDepth += depth[newDetailLevel][newPosX][newPosZ];
|
||||
} else
|
||||
{
|
||||
// void children have the default height (most likely -1)
|
||||
// and represent a LOD with no blocks in it
|
||||
numberOfVoidChildren++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (numberOfChildren > 0)
|
||||
{
|
||||
colors[detailLevel][posX][posZ][0] = (byte) (tempRed / numberOfChildren);
|
||||
colors[detailLevel][posX][posZ][1] = (byte) (tempGreen / numberOfChildren);
|
||||
colors[detailLevel][posX][posZ][2] = (byte) (tempBlue / numberOfChildren);
|
||||
height[detailLevel][posX][posZ] = (short) (tempHeight / numberOfChildren);
|
||||
depth[detailLevel][posX][posZ] = (short) (tempDepth / numberOfChildren);
|
||||
dataExistence[detailLevel][posX][posZ] = true;
|
||||
} else if (numberOfVoidChildren > 0)
|
||||
{
|
||||
colors[detailLevel][posX][posZ][0] = (byte) 0;
|
||||
colors[detailLevel][posX][posZ][1] = (byte) 0;
|
||||
colors[detailLevel][posX][posZ][2] = (byte) 0;
|
||||
|
||||
height[detailLevel][posX][posZ] = LodBuilder.DEFAULT_HEIGHT;
|
||||
depth[detailLevel][posX][posZ] = LodBuilder.DEFAULT_DEPTH;
|
||||
|
||||
dataExistence[detailLevel][posX][posZ] = true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param levelPos
|
||||
* @return
|
||||
*/
|
||||
public boolean doesDataExist(LevelPos levelPos)
|
||||
{
|
||||
try
|
||||
{
|
||||
levelPos = levelPos.getRegionModuleLevelPos();
|
||||
return dataExistence[levelPos.detailLevel][levelPos.posX][levelPos.posZ];
|
||||
} catch (NullPointerException e)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return
|
||||
*/
|
||||
public DistanceGenerationMode getGenerationMode()
|
||||
{
|
||||
return generationMode;
|
||||
}
|
||||
|
||||
public byte getMinDetailLevel()
|
||||
{
|
||||
return minDetailLevel;
|
||||
}
|
||||
|
||||
/**
|
||||
* This will be used to save a level
|
||||
*
|
||||
* @param detailLevel
|
||||
* @return
|
||||
*/
|
||||
public LevelContainer getLevel(byte detailLevel)
|
||||
{
|
||||
if (detailLevel < minDetailLevel)
|
||||
{
|
||||
throw new IllegalArgumentException("getLevel asked for a level that does not exist: minimum " + minDetailLevel + " level requested " + detailLevel);
|
||||
}
|
||||
return new LevelContainer(detailLevel, colors[detailLevel], height[detailLevel], depth[detailLevel], dataExistence[detailLevel]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param levelContainer
|
||||
*/
|
||||
public void addLevel(LevelContainer levelContainer)
|
||||
{
|
||||
if (levelContainer.detailLevel < minDetailLevel - 1)
|
||||
{
|
||||
throw new IllegalArgumentException("addLevel requires a level that is at least the minimum level of the region -1 ");
|
||||
}
|
||||
if (levelContainer.detailLevel == minDetailLevel - 1) minDetailLevel = levelContainer.detailLevel;
|
||||
colors[levelContainer.detailLevel] = levelContainer.colors;
|
||||
height[levelContainer.detailLevel] = levelContainer.height;
|
||||
depth[levelContainer.detailLevel] = levelContainer.depth;
|
||||
dataExistence[levelContainer.detailLevel] = levelContainer.dataExistence;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @param detailLevel
|
||||
*/
|
||||
public void cutTree(byte detailLevel)
|
||||
{
|
||||
if (minDetailLevel < detailLevel)
|
||||
{
|
||||
for (byte tempLod = 0; tempLod < detailLevel; tempLod++)
|
||||
{
|
||||
colors[tempLod] = new byte[0][0][0];
|
||||
height[tempLod] = new short[0][0];
|
||||
depth[tempLod] = new short[0][0];
|
||||
dataExistence[tempLod] = new boolean[0][0];
|
||||
}
|
||||
minDetailLevel = detailLevel;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param detailLevel
|
||||
*/
|
||||
public void expand(byte detailLevel)
|
||||
{
|
||||
if (detailLevel < minDetailLevel)
|
||||
{
|
||||
for (byte tempLod = detailLevel; tempLod < minDetailLevel; tempLod++)
|
||||
{
|
||||
int size = (short) Math.pow(2, LodUtil.REGION_DETAIL_LEVEL - tempLod);
|
||||
colors[tempLod] = new byte[size][size][3];
|
||||
height[tempLod] = new short[size][size];
|
||||
depth[tempLod] = new short[size][size];
|
||||
dataExistence[tempLod] = new boolean[size][size];
|
||||
}
|
||||
minDetailLevel = detailLevel;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* return RegionPos of this lod region
|
||||
*/
|
||||
public RegionPos getRegionPos()
|
||||
{
|
||||
return new RegionPos(regionPosX, regionPosZ);
|
||||
}
|
||||
|
||||
/**
|
||||
* return needed memory in byte
|
||||
*/
|
||||
public int getMinMemoryNeeded()
|
||||
{
|
||||
int count = 0;
|
||||
for (byte tempLod = LodUtil.REGION_DETAIL_LEVEL; tempLod > minDetailLevel; tempLod--)
|
||||
{
|
||||
//i'm doing a upper limit of the minimum
|
||||
//Color should be just 3 byte but i'm gonna calculate as 12 byte
|
||||
//Height and depth should be just 4 byte but i'm gonna calculate as 8 byte
|
||||
count += Math.pow(2, LodUtil.REGION_DETAIL_LEVEL - tempLod) * (8 + 3 + 2 + 2 + 1);
|
||||
//count += Math.pow(2, LodUtil.REGION_DETAIL_LEVEL - tempLod) * (24 + 8 + 8 + 8 + 8);
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return getLevel(LodUtil.REGION_DETAIL_LEVEL).toString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,159 @@
|
||||
/*
|
||||
* This file is part of the LOD Mod, licensed under the GNU GPL v3 License.
|
||||
*
|
||||
* Copyright (C) 2020 James Seibel
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, version 3.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.seibel.lod.objects;
|
||||
|
||||
import java.util.Hashtable;
|
||||
import java.util.Map;
|
||||
|
||||
import com.seibel.lod.proxy.ClientProxy;
|
||||
|
||||
import net.minecraft.world.DimensionType;
|
||||
|
||||
/**
|
||||
* This stores all LODs for a given world.
|
||||
*
|
||||
* @author James Seibel
|
||||
* @author Leonardo Amato
|
||||
* @version 8-17-2021
|
||||
*/
|
||||
public class LodWorld
|
||||
{
|
||||
private String worldName;
|
||||
|
||||
private Map<DimensionType, LodDimension> lodDimensions;
|
||||
|
||||
/**
|
||||
* If true then the LOD world is setup and ready to use
|
||||
*/
|
||||
private boolean isWorldLoaded = false;
|
||||
|
||||
public static final String NO_WORLD_LOADED = "No world loaded";
|
||||
|
||||
|
||||
public LodWorld()
|
||||
{
|
||||
worldName = NO_WORLD_LOADED;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set up the LodQuadTreeWorld with the given newWorldName. <br>
|
||||
* This should be done whenever loading a new world.
|
||||
*
|
||||
* @param newWorldName name of the world
|
||||
*/
|
||||
public void selectWorld(String newWorldName)
|
||||
{
|
||||
if (newWorldName.isEmpty())
|
||||
{
|
||||
deselectWorld();
|
||||
return;
|
||||
}
|
||||
|
||||
if (worldName.equals(newWorldName))
|
||||
// don't recreate everything if we
|
||||
// didn't actually change worlds
|
||||
return;
|
||||
|
||||
worldName = newWorldName;
|
||||
lodDimensions = new Hashtable<DimensionType, LodDimension>();
|
||||
isWorldLoaded = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the worldName to "No world loaded"
|
||||
* and clear the lodDimensions Map. <br>
|
||||
* This should be done whenever unloaded a world.
|
||||
*/
|
||||
public void deselectWorld()
|
||||
{
|
||||
worldName = NO_WORLD_LOADED;
|
||||
lodDimensions = null;
|
||||
isWorldLoaded = false;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Adds newStorage to this world, if a LodQuadTreeDimension
|
||||
* already exists for the given dimension it is replaced.
|
||||
*/
|
||||
public void addLodDimension(LodDimension newStorage)
|
||||
{
|
||||
if (lodDimensions == null)
|
||||
return;
|
||||
|
||||
lodDimensions.put(newStorage.dimension, newStorage);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns null if no LodQuadTreeDimension exists for the given dimension
|
||||
*/
|
||||
public LodDimension getLodDimension(DimensionType dimension)
|
||||
{
|
||||
if (lodDimensions == null)
|
||||
return null;
|
||||
|
||||
return lodDimensions.get(dimension);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resizes the max width in regions that each LodDimension
|
||||
* should use.
|
||||
*/
|
||||
public void resizeDimensionRegionWidth(int newWidth)
|
||||
{
|
||||
if (lodDimensions == null)
|
||||
return;
|
||||
|
||||
saveAllDimensions();
|
||||
|
||||
for (DimensionType key : lodDimensions.keySet())
|
||||
lodDimensions.get(key).setRegionWidth(newWidth);
|
||||
}
|
||||
|
||||
/**
|
||||
* Requests all dimensions save any dirty regions they may have.
|
||||
*/
|
||||
public void saveAllDimensions()
|
||||
{
|
||||
if (lodDimensions == null)
|
||||
return;
|
||||
|
||||
ClientProxy.LOGGER.info("Saving LODs");
|
||||
|
||||
for (DimensionType key : lodDimensions.keySet())
|
||||
lodDimensions.get(key).saveDirtyRegionsToFileAsync();
|
||||
}
|
||||
|
||||
|
||||
public boolean getIsWorldLoaded()
|
||||
{
|
||||
return isWorldLoaded;
|
||||
}
|
||||
|
||||
public String getWorldName()
|
||||
{
|
||||
return worldName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return "World name: " + worldName;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
* This file is part of the LOD Mod, licensed under the GNU GPL v3 License.
|
||||
*
|
||||
* Copyright (C) 2020 James Seibel
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, version 3.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.seibel.lod.objects;
|
||||
|
||||
import com.seibel.lod.enums.FogDistance;
|
||||
import com.seibel.lod.enums.FogQuality;
|
||||
|
||||
/**
|
||||
* This object is just a replacement for an array
|
||||
* to make things easier to understand in the LodRenderer.
|
||||
*
|
||||
* @author James Seibel
|
||||
* @version 7-03-2021
|
||||
*/
|
||||
public class NearFarFogSettings
|
||||
{
|
||||
public NearOrFarSetting near = new NearOrFarSetting(FogDistance.NEAR);
|
||||
public NearOrFarSetting far = new NearOrFarSetting(FogDistance.FAR);
|
||||
|
||||
/** If true that means Minecraft is
|
||||
* rendering fog along side us */
|
||||
public boolean vanillaIsRenderingFog = true;
|
||||
|
||||
public NearFarFogSettings()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* This holds all relevant data to rendering fog at either
|
||||
* near or far distances.
|
||||
*/
|
||||
public class NearOrFarSetting
|
||||
{
|
||||
public FogQuality quality = FogQuality.FANCY;
|
||||
public FogDistance distance = FogDistance.FAR;
|
||||
|
||||
/** If true this section should render with fog */
|
||||
public boolean enabled = true;
|
||||
|
||||
public NearOrFarSetting(FogDistance newFogDistance)
|
||||
{
|
||||
distance = newFogDistance;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,140 @@
|
||||
package com.seibel.lod.objects;
|
||||
|
||||
import org.lwjgl.system.CallbackI;
|
||||
|
||||
public class PosToGenerateContainer
|
||||
{
|
||||
private int playerPosX;
|
||||
private int playerPosZ;
|
||||
private byte farMinDetail;
|
||||
private int maxSize;
|
||||
private int maxNearSize;
|
||||
private int maxFarSize;
|
||||
private int nearSize;
|
||||
private int farSize;
|
||||
private int[][] posToGenerate;
|
||||
|
||||
public PosToGenerateContainer(byte farMinDetail, int maxDataToGenerate, int maxFarDataToGenerate, int playerPosX, int playerPosZ)
|
||||
{
|
||||
this.playerPosX = playerPosX;
|
||||
this.playerPosZ = playerPosZ;
|
||||
this.farMinDetail = farMinDetail;
|
||||
maxNearSize = maxDataToGenerate;
|
||||
maxFarSize = maxFarDataToGenerate;
|
||||
maxSize = maxDataToGenerate;
|
||||
nearSize = 0;
|
||||
farSize = 0;
|
||||
posToGenerate = new int[maxDataToGenerate][4];
|
||||
}
|
||||
|
||||
public void addPosToGenerate(int[] levelPos)
|
||||
{
|
||||
addPosToGenerate(LevelPosUtil.getDetailLevel(levelPos), LevelPosUtil.getPosX(levelPos), LevelPosUtil.getPosZ(levelPos));
|
||||
}
|
||||
|
||||
public void addPosToGenerate(byte detailLevel, int posX, int posZ)
|
||||
{
|
||||
int distance = LevelPosUtil.minDistance(detailLevel, posX, posZ, playerPosX, playerPosZ);
|
||||
int index;
|
||||
int[] tempPos = new int[]{
|
||||
detailLevel,
|
||||
posX,
|
||||
posZ,
|
||||
distance};
|
||||
if (detailLevel >= farMinDetail)
|
||||
{//We are introducing a position in the far array
|
||||
if (farSize < maxFarSize)
|
||||
{
|
||||
farSize++;
|
||||
if (nearSize == maxNearSize)
|
||||
{
|
||||
nearSize--;
|
||||
}
|
||||
maxNearSize--;
|
||||
}
|
||||
index = posToGenerate.length - farSize;
|
||||
while (index < posToGenerate.length - 1 && LevelPosUtil.compareLevelAndDistance(tempPos, posToGenerate[index + 1]) <= 0)
|
||||
{
|
||||
posToGenerate[index] = posToGenerate[index + 1];
|
||||
index++;
|
||||
}
|
||||
if (index <= posToGenerate.length - 1)
|
||||
posToGenerate[index] = tempPos;
|
||||
} else
|
||||
{//We are introducing a position in the near array
|
||||
if (nearSize < maxNearSize)
|
||||
nearSize++;
|
||||
index = nearSize - 1;
|
||||
|
||||
while (index > 0 && LevelPosUtil.compareDistance(tempPos, posToGenerate[index - 1]) <= 0)
|
||||
{
|
||||
posToGenerate[index] = posToGenerate[index - 1];
|
||||
index--;
|
||||
}
|
||||
if (index >= 0)
|
||||
posToGenerate[index] = tempPos;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public int getNumberOfPos()
|
||||
{
|
||||
return farSize + nearSize;
|
||||
}
|
||||
|
||||
public int[] getNthPos(int n)
|
||||
{
|
||||
int index;
|
||||
if (n > farSize * 2)
|
||||
index = n - farSize;
|
||||
else if (n % 2 == 0)
|
||||
index = n / 2;
|
||||
else
|
||||
index = posToGenerate.length - n / 2 - 1;
|
||||
return posToGenerate[index];
|
||||
}
|
||||
|
||||
public String toString()
|
||||
{
|
||||
StringBuilder builder = new StringBuilder();
|
||||
builder.append("Number of pos to generate ");
|
||||
builder.append(farSize + nearSize);
|
||||
builder.append('\n');
|
||||
builder.append("Number of near pos to generate ");
|
||||
builder.append(nearSize);
|
||||
builder.append('\n');
|
||||
builder.append("Number of far pos to generate ");
|
||||
builder.append(farSize);
|
||||
builder.append('\n');
|
||||
builder.append('\n');
|
||||
builder.append("near pos to generate");
|
||||
builder.append('\n');
|
||||
for (int i = 0; i < nearSize; i++)
|
||||
{
|
||||
builder.append(posToGenerate[i][0]);
|
||||
builder.append(" ");
|
||||
builder.append(posToGenerate[i][1]);
|
||||
builder.append(" ");
|
||||
builder.append(posToGenerate[i][2]);
|
||||
builder.append(" ");
|
||||
builder.append(posToGenerate[i][3]);
|
||||
builder.append('\n');
|
||||
}
|
||||
builder.append('\n');
|
||||
builder.append("far pos to generate");
|
||||
builder.append('\n');
|
||||
for (int i = maxSize - 1; i >= maxSize - farSize; i--)
|
||||
{
|
||||
builder.append(posToGenerate[i][0]);
|
||||
builder.append(" ");
|
||||
builder.append(posToGenerate[i][1]);
|
||||
builder.append(" ");
|
||||
builder.append(posToGenerate[i][2]);
|
||||
builder.append(" ");
|
||||
builder.append(posToGenerate[i][3]);
|
||||
builder.append('\n');
|
||||
}
|
||||
builder.append('\n');
|
||||
return builder.toString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
package com.seibel.lod.objects;
|
||||
|
||||
import com.seibel.lod.util.LodUtil;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
public class PosToRenderContainer
|
||||
{
|
||||
private byte minDetail;
|
||||
private int numberOfPosToRender;
|
||||
private int[][] posToRender;
|
||||
/*TODO this population matrix could be converted to boolean to improve memory use*/
|
||||
private byte[][] population;
|
||||
|
||||
public PosToRenderContainer(byte minDetail)
|
||||
{
|
||||
this.minDetail = minDetail;
|
||||
this.numberOfPosToRender = 0;
|
||||
posToRender = new int[1][4];
|
||||
int size = 1 << (LodUtil.REGION_DETAIL_LEVEL - minDetail);
|
||||
population = new byte[size][size];
|
||||
}
|
||||
|
||||
public void addPosToRender(int[] levelPos)
|
||||
{
|
||||
if(numberOfPosToRender >= posToRender.length)
|
||||
posToRender = Arrays.copyOf(posToRender, posToRender.length*2);
|
||||
posToRender[numberOfPosToRender] = levelPos;
|
||||
numberOfPosToRender++;
|
||||
int[] newLevelPos = LevelPosUtil.getRegionModule(LevelPosUtil.convert(levelPos, minDetail));
|
||||
population[LevelPosUtil.getPosZ(newLevelPos)][LevelPosUtil.getPosZ(newLevelPos)] = (byte) (LevelPosUtil.getDetailLevel(levelPos) + 1);
|
||||
}
|
||||
|
||||
public boolean contains(int[] levelPos)
|
||||
{
|
||||
int[] newLevelPos = LevelPosUtil.convert(LevelPosUtil.getRegionModule(levelPos), minDetail);
|
||||
return (population[LevelPosUtil.getPosZ(newLevelPos)][LevelPosUtil.getPosZ(newLevelPos)] == (LevelPosUtil.getDetailLevel(levelPos) + 1));
|
||||
}
|
||||
|
||||
public int getNumberOfPos()
|
||||
{
|
||||
return numberOfPosToRender;
|
||||
}
|
||||
|
||||
public int[] getNthPos(int n)
|
||||
{
|
||||
return posToRender[n];
|
||||
}
|
||||
|
||||
public String toString()
|
||||
{
|
||||
|
||||
StringBuilder builder = new StringBuilder();
|
||||
for(int i = 0; i < numberOfPosToRender; i++)
|
||||
{
|
||||
builder.append(posToRender[i][0]);
|
||||
builder.append(" ");
|
||||
builder.append(posToRender[i][1]);
|
||||
builder.append(" ");
|
||||
builder.append(posToRender[i][2]);
|
||||
builder.append('\n');
|
||||
}
|
||||
builder.append('\n');
|
||||
return builder.toString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
/*
|
||||
* This file is part of the LOD Mod, licensed under the GNU GPL v3 License.
|
||||
*
|
||||
* Copyright (C) 2020 James Seibel
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, version 3.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.seibel.lod.objects;
|
||||
|
||||
import com.seibel.lod.util.LodUtil;
|
||||
|
||||
import net.minecraft.util.math.BlockPos;
|
||||
import net.minecraft.util.math.ChunkPos;
|
||||
|
||||
/**
|
||||
* This object is similar to ChunkPos or BlockPos.
|
||||
*
|
||||
* @author James Seibel
|
||||
* @version 8-21-2021
|
||||
*/
|
||||
public class RegionPos
|
||||
{
|
||||
public int x;
|
||||
public int z;
|
||||
|
||||
|
||||
/**
|
||||
* Default Constructor <br>
|
||||
* <p>
|
||||
* Sets x and z to 0
|
||||
*/
|
||||
public RegionPos()
|
||||
{
|
||||
x = 0;
|
||||
z = 0;
|
||||
}
|
||||
|
||||
public RegionPos(int newX, int newZ)
|
||||
{
|
||||
x = newX;
|
||||
z = newZ;
|
||||
}
|
||||
|
||||
public RegionPos(BlockPos pos)
|
||||
{
|
||||
this(new ChunkPos(pos));
|
||||
}
|
||||
|
||||
public RegionPos(ChunkPos pos)
|
||||
{
|
||||
x = Math.floorDiv(pos.x, LodUtil.REGION_WIDTH_IN_CHUNKS);
|
||||
z = Math.floorDiv(pos.z, LodUtil.REGION_WIDTH_IN_CHUNKS);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the ChunkPos at the center of this region
|
||||
*/
|
||||
public ChunkPos chunkPos()
|
||||
{
|
||||
return new ChunkPos((x * LodUtil.REGION_WIDTH_IN_CHUNKS) + LodUtil.REGION_WIDTH_IN_CHUNKS / 2, (z * LodUtil.REGION_WIDTH_IN_CHUNKS) + LodUtil.REGION_WIDTH_IN_CHUNKS / 2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the BlockPos at the center of this region
|
||||
*/
|
||||
public BlockPos blockPos()
|
||||
{
|
||||
return chunkPos().getWorldPosition().offset(LodUtil.CHUNK_WIDTH / 2, 0, LodUtil.CHUNK_WIDTH / 2);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return "(" + x + "," + z + ")";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,340 @@
|
||||
/*
|
||||
* This file is part of the LOD Mod, licensed under the GNU GPL v3 License.
|
||||
*
|
||||
* Copyright (C) 2020 James Seibel
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, version 3.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.seibel.lod.proxy;
|
||||
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.lwjgl.glfw.GLFW;
|
||||
|
||||
import com.seibel.lod.builders.LodBufferBuilder;
|
||||
import com.seibel.lod.builders.LodBuilder;
|
||||
import com.seibel.lod.builders.worldGeneration.LodNodeGenWorker;
|
||||
import com.seibel.lod.builders.worldGeneration.LodWorldGenerator;
|
||||
import com.seibel.lod.config.LodConfig;
|
||||
import com.seibel.lod.enums.DistanceCalculatorType;
|
||||
import com.seibel.lod.enums.DistanceGenerationMode;
|
||||
import com.seibel.lod.enums.FogDistance;
|
||||
import com.seibel.lod.enums.FogDrawOverride;
|
||||
import com.seibel.lod.enums.LodDetail;
|
||||
import com.seibel.lod.enums.ShadingMode;
|
||||
import com.seibel.lod.objects.LodDimension;
|
||||
import com.seibel.lod.objects.LodWorld;
|
||||
import com.seibel.lod.objects.RegionPos;
|
||||
import com.seibel.lod.render.LodRenderer;
|
||||
import com.seibel.lod.util.DetailDistanceUtil;
|
||||
import com.seibel.lod.util.LodUtil;
|
||||
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.profiler.IProfiler;
|
||||
import net.minecraft.util.text.StringTextComponent;
|
||||
import net.minecraftforge.client.event.InputEvent;
|
||||
import net.minecraftforge.event.TickEvent;
|
||||
import net.minecraftforge.event.world.BlockEvent;
|
||||
import net.minecraftforge.event.world.ChunkEvent;
|
||||
import net.minecraftforge.event.world.WorldEvent;
|
||||
import net.minecraftforge.eventbus.api.SubscribeEvent;
|
||||
|
||||
/**
|
||||
* This handles all events sent to the client,
|
||||
* and is the starting point for most of this program.
|
||||
*
|
||||
* @author James_Seibel
|
||||
* @version 8-24-2021
|
||||
*/
|
||||
public class ClientProxy
|
||||
{
|
||||
public static final Logger LOGGER = LogManager.getLogger("LOD");
|
||||
|
||||
private static LodWorld lodWorld = new LodWorld();
|
||||
private static LodBuilder lodBuilder = new LodBuilder();
|
||||
private static LodBufferBuilder lodBufferBuilder = new LodBufferBuilder();
|
||||
private static LodRenderer renderer = new LodRenderer(lodBufferBuilder);
|
||||
private static LodWorldGenerator lodWorldGenerator = LodWorldGenerator.INSTANCE;
|
||||
|
||||
private boolean configOverrideReminderPrinted = false;
|
||||
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
|
||||
|
||||
/**
|
||||
* This is used to determine if the LODs should be regenerated
|
||||
*/
|
||||
public static int previousChunkRenderDistance = 0;
|
||||
/**
|
||||
* This is used to determine if the LODs should be regenerated
|
||||
*/
|
||||
public static int previousLodRenderDistance = 0;
|
||||
|
||||
/**
|
||||
* can be set if we want to recalculate variables related
|
||||
* to the LOD view distance
|
||||
*/
|
||||
private boolean recalculateWidths = false;
|
||||
|
||||
|
||||
public ClientProxy()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
|
||||
//==============//
|
||||
// render event //
|
||||
//==============//
|
||||
|
||||
/**
|
||||
* Do any setup that is required to draw LODs
|
||||
* and then tell the LodRenderer to draw.
|
||||
*/
|
||||
public void renderLods(float partialTicks)
|
||||
{
|
||||
DetailDistanceUtil.updateSettings();
|
||||
if (mc == null || mc.player == null || !lodWorld.getIsWorldLoaded())
|
||||
return;
|
||||
|
||||
viewDistanceChangedEvent();
|
||||
|
||||
LodDimension lodDim = lodWorld.getLodDimension(mc.player.level.dimensionType());
|
||||
if (lodDim == null)
|
||||
return;
|
||||
|
||||
|
||||
playerMoveEvent(lodDim);
|
||||
//System.out.println("memory needed " + lodDim.getMinMemoryNeeded() + " byte");
|
||||
//System.out.println(lodDim);
|
||||
|
||||
lodDim.treeCutter((int) mc.player.getX(), (int) mc.player.getZ());
|
||||
lodDim.treeGenerator((int) mc.player.getX(), (int) mc.player.getZ());
|
||||
|
||||
|
||||
// comment out when creating a release
|
||||
//applyConfigOverrides();
|
||||
|
||||
|
||||
// Note to self:
|
||||
// if "unspecified" shows up in the pie chart, it is
|
||||
// possibly because the amount of time between sections
|
||||
// is too small for the profiler to measure
|
||||
IProfiler profiler = mc.getProfiler();
|
||||
profiler.pop(); // get out of "terrain"
|
||||
profiler.push("LOD");
|
||||
renderer.drawLODs(lodDim, partialTicks, mc.getProfiler());
|
||||
|
||||
profiler.pop(); // end LOD
|
||||
profiler.push("terrain"); // restart "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
|
||||
previousChunkRenderDistance = mc.options.renderDistance;
|
||||
previousLodRenderDistance = LodConfig.CLIENT.graphics.lodChunkRenderDistance.get();
|
||||
}
|
||||
|
||||
|
||||
private void applyConfigOverrides()
|
||||
{
|
||||
// remind the developer(s). that config override is active
|
||||
if (!configOverrideReminderPrinted)
|
||||
{
|
||||
mc.player.sendMessage(new StringTextComponent("Debug settings enabled!"), mc.player.getUUID());
|
||||
configOverrideReminderPrinted = true;
|
||||
}
|
||||
|
||||
//LodConfig.CLIENT.drawLODs.set(true);
|
||||
//LodConfig.CLIENT.debugMode.set(true);
|
||||
|
||||
LodConfig.CLIENT.graphics.maxDrawDetail.set(LodDetail.FULL);
|
||||
LodConfig.CLIENT.worldGenerator.maxGenerationDetail.set(LodDetail.FULL);
|
||||
|
||||
LodConfig.CLIENT.graphics.fogDistance.set(FogDistance.FAR);
|
||||
LodConfig.CLIENT.graphics.fogDrawOverride.set(FogDrawOverride.ALWAYS_DRAW_FOG_FANCY);
|
||||
LodConfig.CLIENT.graphics.shadingMode.set(ShadingMode.DARKEN_SIDES);
|
||||
LodConfig.CLIENT.graphics.brightnessMultiplier.set(1.0);
|
||||
LodConfig.CLIENT.graphics.saturationMultiplier.set(1.0);
|
||||
|
||||
LodConfig.CLIENT.worldGenerator.distanceGenerationMode.set(DistanceGenerationMode.SURFACE);
|
||||
LodConfig.CLIENT.graphics.lodChunkRenderDistance.set(256);
|
||||
LodConfig.CLIENT.worldGenerator.lodDistanceCalculatorType.set(DistanceCalculatorType.LINEAR);
|
||||
LodConfig.CLIENT.graphics.lodQuality.set(3);
|
||||
LodConfig.CLIENT.worldGenerator.allowUnstableFeatureGeneration.set(false);
|
||||
|
||||
LodConfig.CLIENT.buffers.bufferRebuildPlayerMoveTimeout.set(2000); // 2000
|
||||
LodConfig.CLIENT.buffers.bufferRebuildChunkChangeTimeout.set(1000); // 1000
|
||||
LodConfig.CLIENT.buffers.bufferRebuildLodChangeTimeout.set(50); // 5000
|
||||
|
||||
LodConfig.CLIENT.debugging.enableDebugKeybinding.set(true);
|
||||
}
|
||||
|
||||
|
||||
//==============//
|
||||
// forge events //
|
||||
//==============//
|
||||
|
||||
@SubscribeEvent
|
||||
public void serverTickEvent(TickEvent.ServerTickEvent event)
|
||||
{
|
||||
if (mc == null || mc.player == null || !lodWorld.getIsWorldLoaded())
|
||||
return;
|
||||
|
||||
LodDimension lodDim = lodWorld.getLodDimension(mc.player.level.dimensionType());
|
||||
if (lodDim == null)
|
||||
return;
|
||||
|
||||
lodWorldGenerator.queueGenerationRequests(lodDim, renderer, lodBuilder);
|
||||
}
|
||||
|
||||
@SubscribeEvent
|
||||
public void chunkLoadEvent(ChunkEvent.Load event)
|
||||
{
|
||||
lodBuilder.generateLodNodeAsync(event.getChunk(), lodWorld, event.getWorld(), DistanceGenerationMode.SERVER);
|
||||
}
|
||||
|
||||
@SubscribeEvent
|
||||
public void worldSaveEvent(WorldEvent.Save event)
|
||||
{
|
||||
if (lodWorld != null)
|
||||
lodWorld.saveAllDimensions();
|
||||
}
|
||||
|
||||
@SubscribeEvent
|
||||
public void worldLoadEvent(WorldEvent.Load event)
|
||||
{
|
||||
// the player just loaded a new world/dimension
|
||||
lodWorld.selectWorld(LodUtil.getWorldID(event.getWorld()));
|
||||
// make sure the correct LODs are being rendered
|
||||
// (if this isn't done the previous world's LODs may be drawn)
|
||||
renderer.regenerateLODsNextFrame();
|
||||
}
|
||||
|
||||
@SubscribeEvent
|
||||
public void worldUnloadEvent(WorldEvent.Unload event)
|
||||
{
|
||||
// the player just unloaded a world/dimension
|
||||
|
||||
if (mc.getConnection().getLevel() == null)
|
||||
{
|
||||
// if this isn't done unfinished tasks may be left in the queue
|
||||
// preventing new LodChunks form being generated
|
||||
LodNodeGenWorker.restartExecuterService();
|
||||
|
||||
LodWorldGenerator.INSTANCE.numberOfChunksWaitingToGenerate.set(0);
|
||||
// the player has disconnected from a server
|
||||
lodWorld.deselectWorld();
|
||||
|
||||
|
||||
// hopefully this should reduce issues related to the buffer builder
|
||||
// breaking when changing worlds.
|
||||
renderer.destroyBuffers();
|
||||
recalculateWidths = true;
|
||||
}
|
||||
}
|
||||
|
||||
@SubscribeEvent
|
||||
public void blockChangeEvent(BlockEvent event)
|
||||
{
|
||||
if (event.getClass() == BlockEvent.BreakEvent.class ||
|
||||
event.getClass() == BlockEvent.EntityPlaceEvent.class ||
|
||||
event.getClass() == BlockEvent.EntityMultiPlaceEvent.class ||
|
||||
event.getClass() == BlockEvent.FluidPlaceBlockEvent.class ||
|
||||
event.getClass() == BlockEvent.PortalSpawnEvent.class)
|
||||
{
|
||||
// recreate the LOD where the blocks were changed
|
||||
lodBuilder.generateLodNodeAsync(event.getWorld().getChunk(event.getPos()), lodWorld, event.getWorld());
|
||||
}
|
||||
}
|
||||
|
||||
@SubscribeEvent
|
||||
public void onKeyInput(InputEvent.KeyInputEvent event)
|
||||
{
|
||||
if(LodConfig.CLIENT.debugging.enableDebugKeybinding.get()
|
||||
&& event.getKey() == GLFW.GLFW_KEY_F4 && event.getAction() == GLFW.GLFW_PRESS)
|
||||
{
|
||||
LodConfig.CLIENT.debugging.debugMode.set(LodConfig.CLIENT.debugging.debugMode.get().getNext());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
//==================//
|
||||
// frame LOD events //
|
||||
//==================//
|
||||
|
||||
/**
|
||||
* Re-centers the given LodDimension if it needs to be.
|
||||
*/
|
||||
private void playerMoveEvent(LodDimension lodDim)
|
||||
{
|
||||
// make sure the dimension is centered
|
||||
RegionPos playerRegionPos = new RegionPos(mc.player.blockPosition());
|
||||
RegionPos worldRegionOffset = new RegionPos(playerRegionPos.x - lodDim.getCenterX(), playerRegionPos.z - lodDim.getCenterZ());
|
||||
if (worldRegionOffset.x != 0 || worldRegionOffset.z != 0)
|
||||
{
|
||||
lodWorld.saveAllDimensions();
|
||||
lodDim.move(worldRegionOffset);
|
||||
//LOGGER.info("offset: " + worldRegionOffset.x + "," + worldRegionOffset.z + "\t center: " + lodDim.getCenterX() + "," + lodDim.getCenterZ());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Re-sizes all LodDimensions if they needs to be.
|
||||
*/
|
||||
private void viewDistanceChangedEvent()
|
||||
{
|
||||
// calculate how wide the dimension(s) should be in regions
|
||||
int chunksWide = LodConfig.CLIENT.graphics.lodChunkRenderDistance.get() * 2 + 1;
|
||||
int newWidth = (int) Math.ceil(chunksWide / (float) LodUtil.REGION_WIDTH_IN_CHUNKS);
|
||||
newWidth = (newWidth % 2 == 0) ? (newWidth += 1) : (newWidth += 2); // make sure we have a odd number of regions
|
||||
|
||||
// do the dimensions need to change in size?
|
||||
if (lodBuilder.defaultDimensionWidthInRegions != newWidth || recalculateWidths)
|
||||
{
|
||||
lodWorld.saveAllDimensions();
|
||||
|
||||
// update the dimensions to fit the new width
|
||||
lodWorld.resizeDimensionRegionWidth(newWidth);
|
||||
lodBuilder.defaultDimensionWidthInRegions = newWidth;
|
||||
renderer.setupBuffers(newWidth);
|
||||
|
||||
recalculateWidths = false;
|
||||
//LOGGER.info("new dimension width in regions: " + newWidth + "\t potential: " + newWidth );
|
||||
}
|
||||
DetailDistanceUtil.updateSettings();
|
||||
}
|
||||
|
||||
|
||||
|
||||
//================//
|
||||
// public getters //
|
||||
//================//
|
||||
|
||||
public static LodWorld getLodWorld()
|
||||
{
|
||||
return lodWorld;
|
||||
}
|
||||
|
||||
public static LodBuilder getLodBuilder()
|
||||
{
|
||||
return lodBuilder;
|
||||
}
|
||||
|
||||
public static LodRenderer getRenderer()
|
||||
{
|
||||
return renderer;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,894 @@
|
||||
/*
|
||||
* This file is part of the LOD Mod, licensed under the GNU GPL v3 License.
|
||||
*
|
||||
* Copyright (C) 2020 James Seibel
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, version 3.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.seibel.lod.render;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
import java.nio.FloatBuffer;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
|
||||
import org.lwjgl.opengl.GL;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
import org.lwjgl.opengl.NVFogDistance;
|
||||
|
||||
import com.mojang.blaze3d.matrix.MatrixStack;
|
||||
import com.mojang.blaze3d.platform.GlStateManager;
|
||||
import com.mojang.blaze3d.systems.RenderSystem;
|
||||
import com.seibel.lod.builders.LodBufferBuilder;
|
||||
import com.seibel.lod.config.LodConfig;
|
||||
import com.seibel.lod.enums.DebugMode;
|
||||
import com.seibel.lod.enums.FogDistance;
|
||||
import com.seibel.lod.enums.FogDrawOverride;
|
||||
import com.seibel.lod.enums.FogQuality;
|
||||
import com.seibel.lod.handlers.ReflectionHandler;
|
||||
import com.seibel.lod.objects.LodDimension;
|
||||
import com.seibel.lod.objects.NearFarFogSettings;
|
||||
import com.seibel.lod.objects.RegionPos;
|
||||
import com.seibel.lod.objects.LevelPos.LevelPos;
|
||||
import com.seibel.lod.proxy.ClientProxy;
|
||||
import com.seibel.lod.util.DetailDistanceUtil;
|
||||
import com.seibel.lod.util.LodUtil;
|
||||
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.renderer.ActiveRenderInfo;
|
||||
import net.minecraft.client.renderer.FogRenderer;
|
||||
import net.minecraft.client.renderer.GameRenderer;
|
||||
import net.minecraft.client.renderer.vertex.DefaultVertexFormats;
|
||||
import net.minecraft.client.renderer.vertex.VertexBuffer;
|
||||
import net.minecraft.client.renderer.vertex.VertexFormat;
|
||||
import net.minecraft.potion.EffectInstance;
|
||||
import net.minecraft.potion.Effects;
|
||||
import net.minecraft.profiler.IProfiler;
|
||||
import net.minecraft.util.math.ChunkPos;
|
||||
import net.minecraft.util.math.MathHelper;
|
||||
import net.minecraft.util.math.vector.Matrix4f;
|
||||
import net.minecraft.util.math.vector.Vector3d;
|
||||
import net.minecraft.util.math.vector.Vector3f;
|
||||
|
||||
|
||||
/**
|
||||
* This is where all the magic happens. <br>
|
||||
* This is where LODs are draw to the world.
|
||||
*
|
||||
* @author James Seibel
|
||||
* @version 8-31-2021
|
||||
*/
|
||||
public class LodRenderer
|
||||
{
|
||||
/**
|
||||
* this is the light used when rendering the LODs,
|
||||
* it should be something different than what is used by Minecraft
|
||||
*/
|
||||
private static final int LOD_GL_LIGHT_NUMBER = GL11.GL_LIGHT2;
|
||||
|
||||
/**
|
||||
* 64 MB by default is the maximum amount of memory that
|
||||
* can be directly allocated. <br><br>
|
||||
* <p>
|
||||
* I know there are commands to change that amount
|
||||
* (specifically "-XX:MaxDirectMemorySize"), but
|
||||
* I have no idea how to access that amount. <br>
|
||||
* So I guess this will be the hard limit for now. <br><br>
|
||||
* <p>
|
||||
* https://stackoverflow.com/questions/50499238/bytebuffer-allocatedirect-and-xmx
|
||||
*/
|
||||
public static final int MAX_ALOCATEABLE_DIRECT_MEMORY = 64 * 1024 * 1024;
|
||||
|
||||
/**
|
||||
* Does this computer's GPU support fancy fog?
|
||||
*/
|
||||
private static Boolean fancyFogAvailable = null;
|
||||
|
||||
|
||||
/**
|
||||
* If true the LODs colors will be replaced with
|
||||
* a checkerboard, this can be used for debugging.
|
||||
*/
|
||||
public DebugMode previousDebugMode = DebugMode.OFF;
|
||||
|
||||
private Minecraft mc;
|
||||
private GameRenderer gameRender;
|
||||
private IProfiler profiler;
|
||||
private int farPlaneBlockDistance;
|
||||
private ReflectionHandler reflectionHandler;
|
||||
|
||||
|
||||
/**
|
||||
* This is used to generate the buildable buffers
|
||||
*/
|
||||
private LodBufferBuilder lodBufferBuilder;
|
||||
|
||||
/**
|
||||
* Each VertexBuffer represents 1 region
|
||||
*/
|
||||
private VertexBuffer[][] vbos;
|
||||
public static final VertexFormat LOD_VERTEX_FORMAT = DefaultVertexFormats.POSITION_COLOR;
|
||||
|
||||
|
||||
/**
|
||||
* This is used to determine if the LODs should be regenerated
|
||||
*/
|
||||
private LevelPos previousPos = new LevelPos((byte) 0, 0, 0);
|
||||
private int prevRenderDistance = 0;
|
||||
private long prevPlayerPosTime = 0;
|
||||
private long prevVanillaChunkTime = 0;
|
||||
private long prevChunkTime = 0;
|
||||
|
||||
|
||||
/**
|
||||
* This is used to determine if the LODs should be regenerated
|
||||
*/
|
||||
private FogDistance prevFogDistance = FogDistance.NEAR_AND_FAR;
|
||||
|
||||
/**
|
||||
* if this is true the LOD buffers should be regenerated,
|
||||
* provided they aren't already being regenerated.
|
||||
*/
|
||||
private volatile boolean partialRegen = false;
|
||||
private volatile boolean fullRegen = true;
|
||||
|
||||
/**
|
||||
* This HashSet contains every chunk that Vanilla Minecraft
|
||||
* is going to render
|
||||
*/
|
||||
public boolean[][] vanillaRenderedChunks;
|
||||
public boolean vanillaRenderedChunksChanged;
|
||||
|
||||
|
||||
public LodRenderer(LodBufferBuilder newLodNodeBufferBuilder)
|
||||
{
|
||||
mc = Minecraft.getInstance();
|
||||
gameRender = mc.gameRenderer;
|
||||
|
||||
reflectionHandler = new ReflectionHandler();
|
||||
lodBufferBuilder = newLodNodeBufferBuilder;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Besides drawing the LODs this method also starts
|
||||
* the async process of generating the Buffers that hold those LODs.
|
||||
*
|
||||
* @param lodDim The dimension to draw, if null doesn't replace the current dimension.
|
||||
* @param partialTicks how far into the current tick this method was called.
|
||||
*/
|
||||
public void drawLODs(LodDimension lodDim, float partialTicks, IProfiler newProfiler)
|
||||
{
|
||||
if (lodDim == null)
|
||||
{
|
||||
// if there aren't any loaded LodChunks
|
||||
// don't try drawing anything
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
//===============//
|
||||
// initial setup //
|
||||
//===============//
|
||||
|
||||
profiler = newProfiler;
|
||||
profiler.push("LOD setup");
|
||||
|
||||
|
||||
// only check the GPU capability's once
|
||||
if (fancyFogAvailable == null)
|
||||
{
|
||||
// see if this GPU can run fancy fog
|
||||
fancyFogAvailable = GL.getCapabilities().GL_NV_fog_distance;
|
||||
|
||||
if (!fancyFogAvailable)
|
||||
{
|
||||
ClientProxy.LOGGER.info("This GPU does not support GL_NV_fog_distance. This means that fancy fog options will not be available.");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// TODO move the buffer regeneration logic into its own class (probably called in the client proxy instead)
|
||||
// starting here...
|
||||
determineIfLodsShouldRegenerate(lodDim);
|
||||
|
||||
//=================//
|
||||
// create the LODs //
|
||||
//=================//
|
||||
|
||||
// only regenerate the LODs if:
|
||||
// 1. we want to regenerate LODs
|
||||
// 2. we aren't already regenerating the LODs
|
||||
// 3. we aren't waiting for the build and draw buffers to swap
|
||||
// (this is to prevent thread conflicts)
|
||||
if ((partialRegen || fullRegen) && !lodBufferBuilder.generatingBuffers && !lodBufferBuilder.newBuffersAvaliable())
|
||||
{
|
||||
// generate the LODs on a separate thread to prevent stuttering or freezing
|
||||
lodBufferBuilder.generateLodBuffersAsync(this, lodDim, mc.player.blockPosition(), true);
|
||||
|
||||
// the regen process has been started,
|
||||
// it will be done when lodBufferBuilder.newBuffersAvaliable()
|
||||
// is true
|
||||
fullRegen = false;
|
||||
partialRegen = false;
|
||||
}
|
||||
|
||||
// TODO move the buffer regeneration logic into its own class (probably called in the client proxy instead)
|
||||
// ...ending here
|
||||
|
||||
// replace the buffers used to draw and build,
|
||||
// this is only done when the createLodBufferGenerationThread
|
||||
// has finished executing on a parallel thread.
|
||||
if (lodBufferBuilder.newBuffersAvaliable())
|
||||
{
|
||||
swapBuffers();
|
||||
}
|
||||
|
||||
|
||||
|
||||
//===========================//
|
||||
// GL settings for rendering //
|
||||
//===========================//
|
||||
|
||||
// set the required open GL settings
|
||||
|
||||
if (LodConfig.CLIENT.debugging.debugMode.get() == DebugMode.SHOW_DETAIL_WIREFRAME)
|
||||
GL11.glPolygonMode(GL11.GL_FRONT_AND_BACK, GL11.GL_LINE);
|
||||
else
|
||||
GL11.glPolygonMode(GL11.GL_FRONT_AND_BACK, GL11.GL_FILL);
|
||||
|
||||
GL11.glDisable(GL11.GL_TEXTURE_2D);
|
||||
GL11.glEnable(GL11.GL_CULL_FACE);
|
||||
GL11.glEnable(GL11.GL_COLOR_MATERIAL);
|
||||
GL11.glEnable(GL11.GL_DEPTH_TEST);
|
||||
|
||||
// disable the lights Minecraft uses
|
||||
GL11.glDisable(GL11.GL_LIGHT0);
|
||||
GL11.glDisable(GL11.GL_LIGHT1);
|
||||
|
||||
// get the default projection matrix so we can
|
||||
// reset it after drawing the LODs
|
||||
float[] defaultProjMatrix = new float[16];
|
||||
GL11.glGetFloatv(GL11.GL_PROJECTION_MATRIX, defaultProjMatrix);
|
||||
|
||||
Matrix4f modelViewMatrix = generateModelViewMatrix(partialTicks);
|
||||
|
||||
// required for setupFog and setupProjectionMatrix
|
||||
farPlaneBlockDistance = LodConfig.CLIENT.graphics.lodChunkRenderDistance.get() * LodUtil.CHUNK_WIDTH;
|
||||
|
||||
setupProjectionMatrix(partialTicks);
|
||||
setupLighting(lodDim, partialTicks);
|
||||
|
||||
NearFarFogSettings fogSettings = determineFogSettings();
|
||||
|
||||
// determine the current fog settings so they can be
|
||||
// reset after drawing the LODs
|
||||
float defaultFogStartDist = GL11.glGetFloat(GL11.GL_FOG_START);
|
||||
float defaultFogEndDist = GL11.glGetFloat(GL11.GL_FOG_END);
|
||||
int defaultFogMode = GL11.glGetInteger(GL11.GL_FOG_MODE);
|
||||
int defaultFogDistance = GL11.glGetInteger(NVFogDistance.GL_FOG_DISTANCE_MODE_NV);
|
||||
|
||||
|
||||
//===========//
|
||||
// rendering //
|
||||
//===========//
|
||||
profiler.popPush("LOD draw");
|
||||
|
||||
if (vbos != null)
|
||||
{
|
||||
Vector3d cameraDir = mc.cameraEntity.getLookAngle().normalize();
|
||||
cameraDir = mc.options.getCameraType().isMirrored() ? cameraDir.reverse() : cameraDir;
|
||||
|
||||
|
||||
// used to determine what type of fog to render
|
||||
int halfWidth = vbos.length / 2;
|
||||
int quarterWidth = vbos.length / 4;
|
||||
|
||||
for (int i = 0; i < vbos.length; i++)
|
||||
{
|
||||
for (int j = 0; j < vbos.length; j++)
|
||||
{
|
||||
RegionPos vboPos = new RegionPos(i + lodDim.getCenterX() - lodDim.getWidth() / 2, j + lodDim.getCenterZ() - lodDim.getWidth() / 2);
|
||||
if (RenderUtil.isRegionInViewFrustum(mc.cameraEntity.blockPosition(), cameraDir, vboPos.blockPos()))
|
||||
{
|
||||
if ((i > halfWidth - quarterWidth && i < halfWidth + quarterWidth) && (j > halfWidth - quarterWidth && j < halfWidth + quarterWidth))
|
||||
setupFog(fogSettings.near.distance, fogSettings.near.quality);
|
||||
else
|
||||
setupFog(fogSettings.far.distance, fogSettings.far.quality);
|
||||
|
||||
|
||||
sendLodsToGpuAndDraw(vbos[i][j], modelViewMatrix);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//=========//
|
||||
// cleanup //
|
||||
//=========//
|
||||
|
||||
profiler.popPush("LOD cleanup");
|
||||
|
||||
GL11.glPolygonMode(GL11.GL_FRONT_AND_BACK, GL11.GL_FILL);
|
||||
GL11.glEnable(GL11.GL_TEXTURE_2D);
|
||||
GL11.glDisable(LOD_GL_LIGHT_NUMBER);
|
||||
// re-enable the lights Minecraft uses
|
||||
GL11.glEnable(GL11.GL_LIGHT0);
|
||||
GL11.glEnable(GL11.GL_LIGHT1);
|
||||
RenderSystem.disableLighting();
|
||||
|
||||
// reset the fog settings so the normal chunks
|
||||
// will be drawn correctly
|
||||
cleanupFog(fogSettings, defaultFogStartDist, defaultFogEndDist, defaultFogMode, defaultFogDistance);
|
||||
|
||||
// reset the projection matrix so anything drawn after
|
||||
// the LODs will use the correct projection matrix
|
||||
Matrix4f mvm = new Matrix4f(defaultProjMatrix);
|
||||
mvm.transpose();
|
||||
gameRender.resetProjectionMatrix(mvm);
|
||||
|
||||
// clear the depth buffer so anything drawn is drawn
|
||||
// over the LODs
|
||||
GL11.glClear(GL11.GL_DEPTH_BUFFER_BIT);
|
||||
|
||||
|
||||
// end of internal LOD profiling
|
||||
profiler.pop();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* This is where the actual drawing happens.
|
||||
*/
|
||||
private void sendLodsToGpuAndDraw(VertexBuffer vbo, Matrix4f modelViewMatrix)
|
||||
{
|
||||
if (vbo == null)
|
||||
return;
|
||||
|
||||
vbo.bind();
|
||||
// 0L is the starting pointer
|
||||
LOD_VERTEX_FORMAT.setupBufferState(0L);
|
||||
|
||||
vbo.draw(modelViewMatrix, GL11.GL_QUADS);
|
||||
|
||||
VertexBuffer.unbind();
|
||||
LOD_VERTEX_FORMAT.clearBufferState();
|
||||
}
|
||||
|
||||
|
||||
//=================//
|
||||
// Setup Functions //
|
||||
//=================//
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
private void setupFog(FogDistance fogDistance, FogQuality fogQuality)
|
||||
{
|
||||
if (fogQuality == FogQuality.OFF)
|
||||
{
|
||||
FogRenderer.setupNoFog();
|
||||
RenderSystem.disableFog();
|
||||
return;
|
||||
}
|
||||
|
||||
if (fogDistance == FogDistance.NEAR_AND_FAR)
|
||||
{
|
||||
throw new IllegalArgumentException("setupFog doesn't accept the NEAR_AND_FAR fog distance.");
|
||||
}
|
||||
|
||||
// determine the fog distance mode to use
|
||||
int glFogDistanceMode;
|
||||
if (fogQuality == FogQuality.FANCY)
|
||||
{
|
||||
// fancy fog (fragment distance based fog)
|
||||
glFogDistanceMode = NVFogDistance.GL_EYE_RADIAL_NV;
|
||||
}
|
||||
else
|
||||
{
|
||||
// fast fog (frustum distance based fog)
|
||||
glFogDistanceMode = NVFogDistance.GL_EYE_PLANE_ABSOLUTE_NV;
|
||||
}
|
||||
|
||||
// the multipliers are percentages
|
||||
// of the regular view distance.
|
||||
if (fogDistance == FogDistance.FAR)
|
||||
{
|
||||
// the reason that I wrote fogEnd then fogStart backwards
|
||||
// is because we are using fog backwards to how
|
||||
// it is normally used, with it hiding near objects
|
||||
// instead of far objects.
|
||||
|
||||
if (fogQuality == FogQuality.FANCY)
|
||||
{
|
||||
// for more realistic fog when using FAR
|
||||
if(LodConfig.CLIENT.graphics.fogDistance.get() == FogDistance.NEAR_AND_FAR)
|
||||
{
|
||||
RenderSystem.fogStart(farPlaneBlockDistance * 0.9f);
|
||||
RenderSystem.fogEnd(farPlaneBlockDistance * 1.0f);
|
||||
}else{
|
||||
RenderSystem.fogStart(farPlaneBlockDistance * 0.1f);
|
||||
RenderSystem.fogEnd(farPlaneBlockDistance * 1.0f);
|
||||
}
|
||||
}
|
||||
else if (fogQuality == FogQuality.FAST)
|
||||
{
|
||||
// for the far fog of the normal chunks
|
||||
// to start right where the LODs' end use:
|
||||
// end = 0.8f, start = 1.5f
|
||||
RenderSystem.fogStart(farPlaneBlockDistance * 1.5f);
|
||||
RenderSystem.fogEnd(farPlaneBlockDistance * 2.0f);
|
||||
}
|
||||
}
|
||||
else if (fogDistance == FogDistance.NEAR)
|
||||
{
|
||||
if (fogQuality == FogQuality.FANCY)
|
||||
{
|
||||
RenderSystem.fogEnd(mc.options.renderDistance * 16 * 1.41f);
|
||||
RenderSystem.fogStart(mc.options.renderDistance * 16 * 1.6f);
|
||||
}
|
||||
else if (fogQuality == FogQuality.FAST)
|
||||
{
|
||||
RenderSystem.fogEnd(mc.options.renderDistance * 16 * 1.0f);
|
||||
RenderSystem.fogStart(mc.options.renderDistance * 16 * 1.5f);
|
||||
}
|
||||
}
|
||||
|
||||
GL11.glEnable(GL11.GL_FOG);
|
||||
RenderSystem.enableFog();
|
||||
RenderSystem.setupNvFogDistance();
|
||||
RenderSystem.fogMode(GlStateManager.FogMode.LINEAR);
|
||||
GL11.glFogi(NVFogDistance.GL_FOG_DISTANCE_MODE_NV, glFogDistanceMode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Revert any changes that were made to the fog.
|
||||
*/
|
||||
private void cleanupFog(NearFarFogSettings fogSettings,
|
||||
float defaultFogStartDist, float defaultFogEndDist,
|
||||
int defaultFogMode, int defaultFogDistance)
|
||||
{
|
||||
RenderSystem.fogStart(defaultFogStartDist);
|
||||
RenderSystem.fogEnd(defaultFogEndDist);
|
||||
RenderSystem.fogMode(defaultFogMode);
|
||||
GL11.glFogi(NVFogDistance.GL_FOG_DISTANCE_MODE_NV, defaultFogDistance);
|
||||
|
||||
// disable fog if Minecraft wasn't rendering fog
|
||||
// but we were
|
||||
if (!fogSettings.vanillaIsRenderingFog &&
|
||||
(fogSettings.near.quality != FogQuality.OFF ||
|
||||
fogSettings.far.quality != FogQuality.OFF))
|
||||
{
|
||||
GL11.glDisable(GL11.GL_FOG);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Create the model view matrix to move the LODs
|
||||
* from object space into world space.
|
||||
*/
|
||||
private Matrix4f generateModelViewMatrix(float partialTicks)
|
||||
{
|
||||
// get all relevant camera info
|
||||
ActiveRenderInfo renderInfo = mc.gameRenderer.getMainCamera();
|
||||
Vector3d projectedView = renderInfo.getPosition();
|
||||
|
||||
|
||||
// generate the model view matrix
|
||||
MatrixStack matrixStack = new MatrixStack();
|
||||
matrixStack.pushPose();
|
||||
// translate and rotate to the current camera location
|
||||
matrixStack.mulPose(Vector3f.XP.rotationDegrees(renderInfo.getXRot()));
|
||||
matrixStack.mulPose(Vector3f.YP.rotationDegrees(renderInfo.getYRot() + 180));
|
||||
matrixStack.translate(-projectedView.x, -projectedView.y, -projectedView.z);
|
||||
|
||||
return matrixStack.last().pose();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* create a new projection matrix and send it over to the GPU
|
||||
* <br><br>
|
||||
* A lot of this code is copied from renderLevel (line 567)
|
||||
* in the GameRender class. The code copied is anything with
|
||||
* a matrixStack and is responsible for making sure the LOD
|
||||
* objects distort correctly relative to the rest of the world.
|
||||
* Distortions are caused by: standing in a nether portal,
|
||||
* nausea potion effect, walking bobbing.
|
||||
*
|
||||
* @param partialTicks how many ticks into the frame we are
|
||||
*/
|
||||
private void setupProjectionMatrix(float partialTicks)
|
||||
{
|
||||
// Note: if the LOD objects don't distort correctly
|
||||
// compared to regular minecraft terrain, make sure
|
||||
// all the transformations in renderWorld are here too
|
||||
|
||||
MatrixStack matrixStack = new MatrixStack();
|
||||
matrixStack.pushPose();
|
||||
|
||||
gameRender.bobHurt(matrixStack, partialTicks);
|
||||
if (this.mc.options.bobView)
|
||||
{
|
||||
gameRender.bobView(matrixStack, partialTicks);
|
||||
}
|
||||
|
||||
// potion and nausea effects
|
||||
float f = MathHelper.lerp(partialTicks, this.mc.player.oPortalTime, this.mc.player.portalTime) * this.mc.options.screenEffectScale * this.mc.options.screenEffectScale;
|
||||
if (f > 0.0F)
|
||||
{
|
||||
int i = this.mc.player.hasEffect(Effects.CONFUSION) ? 7 : 20;
|
||||
float f1 = 5.0F / (f * f + 5.0F) - f * 0.04F;
|
||||
f1 = f1 * f1;
|
||||
Vector3f vector3f = new Vector3f(0.0F, MathHelper.SQRT_OF_TWO / 2.0F, MathHelper.SQRT_OF_TWO / 2.0F);
|
||||
matrixStack.mulPose(vector3f.rotationDegrees((gameRender.tick + partialTicks) * i));
|
||||
matrixStack.scale(1.0F / f1, 1.0F, 1.0F);
|
||||
float f2 = -(gameRender.tick + partialTicks) * i;
|
||||
matrixStack.mulPose(vector3f.rotationDegrees(f2));
|
||||
}
|
||||
|
||||
|
||||
// this projection matrix allows us to see past the normal
|
||||
// world render distance
|
||||
Matrix4f projectionMatrix =
|
||||
Matrix4f.perspective(
|
||||
getFov(partialTicks, true),
|
||||
(float) this.mc.getWindow().getScreenWidth() / (float) this.mc.getWindow().getScreenHeight(),
|
||||
// it is possible to see the near clip plane, but
|
||||
// you have to be flying quickly in spectator mode through ungenerated
|
||||
// terrain, so I don't think it is much of an issue.
|
||||
mc.options.renderDistance,
|
||||
farPlaneBlockDistance * LodUtil.CHUNK_WIDTH * 2);
|
||||
|
||||
// add the screen space distortions
|
||||
projectionMatrix.multiply(matrixStack.last().pose());
|
||||
gameRender.resetProjectionMatrix(projectionMatrix);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* setup the lighting to be used for the LODs
|
||||
*/
|
||||
private void setupLighting(LodDimension lodDimension, float partialTicks)
|
||||
{
|
||||
// Determine if the player has night vision
|
||||
boolean playerHasNightVision = false;
|
||||
if (this.mc.player != null)
|
||||
{
|
||||
Iterator<EffectInstance> iterator = this.mc.player.getActiveEffects().iterator();
|
||||
while (iterator.hasNext())
|
||||
{
|
||||
EffectInstance instance = iterator.next();
|
||||
if (instance.getEffect() == Effects.NIGHT_VISION)
|
||||
{
|
||||
playerHasNightVision = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
float sunBrightness = lodDimension.dimension.hasSkyLight() ? mc.level.getSkyDarken(partialTicks) : 0.2f;
|
||||
sunBrightness = playerHasNightVision ? 1.0f : sunBrightness;
|
||||
float gammaMultiplyer = (float) mc.options.gamma - 0.5f;
|
||||
float lightStrength = ((sunBrightness / 2f) - 0.2f) + (gammaMultiplyer * 0.3f);
|
||||
|
||||
float lightAmbient[] = {lightStrength, lightStrength, lightStrength, 1.0f};
|
||||
|
||||
// can be used for debugging
|
||||
// if (partialTicks < 0.005)
|
||||
// ClientProxy.LOGGER.debug(lightStrength);
|
||||
|
||||
ByteBuffer temp = ByteBuffer.allocateDirect(16);
|
||||
temp.order(ByteOrder.nativeOrder());
|
||||
GL11.glLightfv(LOD_GL_LIGHT_NUMBER, GL11.GL_AMBIENT, (FloatBuffer) temp.asFloatBuffer().put(lightAmbient).flip());
|
||||
GL11.glEnable(LOD_GL_LIGHT_NUMBER); // Enable the above lighting
|
||||
|
||||
RenderSystem.enableLighting();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create all buffers that will be used.
|
||||
*/
|
||||
public void setupBuffers(int numbRegionsWide)
|
||||
{
|
||||
// calculate the max amount of memory needed (in bytes)
|
||||
int bufferMemory = RenderUtil.getBufferMemoryForRegion();
|
||||
|
||||
// if the required memory is greater than the
|
||||
// MAX_ALOCATEABLE_DIRECT_MEMORY lower the lodChunkRadiusMultiplier
|
||||
// to fit.
|
||||
if (bufferMemory > MAX_ALOCATEABLE_DIRECT_MEMORY)
|
||||
{
|
||||
ClientProxy.LOGGER.warn("setupBuffers tried to allocate too much memory for the BufferBuilders."
|
||||
+ " It tried to allocate \"" + bufferMemory + "\" bytes, when \"" + MAX_ALOCATEABLE_DIRECT_MEMORY + "\" is the max.");
|
||||
}
|
||||
|
||||
lodBufferBuilder.setupBuffers(numbRegionsWide, bufferMemory);
|
||||
}
|
||||
|
||||
|
||||
//======================//
|
||||
// Other Misc Functions //
|
||||
//======================//
|
||||
|
||||
/**
|
||||
* If this is called then the next time "drawLODs" is called
|
||||
* the LODs will be regenerated; the same as if the player moved.
|
||||
*/
|
||||
public void regenerateLODsNextFrame()
|
||||
{
|
||||
fullRegen = true;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Replace the current Vertex Buffers with the newly
|
||||
* created buffers from the lodBufferBuilder.
|
||||
*/
|
||||
private void swapBuffers()
|
||||
{
|
||||
// replace the drawable buffers with
|
||||
// the newly created buffers from the lodBufferBuilder
|
||||
vbos = lodBufferBuilder.getVertexBuffers();
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls the BufferBuilder's destroyBuffers method.
|
||||
*/
|
||||
public void destroyBuffers()
|
||||
{
|
||||
lodBufferBuilder.destroyBuffers();
|
||||
}
|
||||
|
||||
|
||||
private double getFov(float partialTicks, boolean useFovSetting)
|
||||
{
|
||||
return mc.gameRenderer.getFov(mc.gameRenderer.getMainCamera(), partialTicks, useFovSetting);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Return what fog settings should be used when rendering.
|
||||
*/
|
||||
private NearFarFogSettings determineFogSettings()
|
||||
{
|
||||
NearFarFogSettings fogSettings = new NearFarFogSettings();
|
||||
|
||||
|
||||
FogQuality quality = reflectionHandler.getFogQuality();
|
||||
FogDrawOverride override = LodConfig.CLIENT.graphics.fogDrawOverride.get();
|
||||
|
||||
|
||||
if (quality == FogQuality.OFF)
|
||||
fogSettings.vanillaIsRenderingFog = false;
|
||||
else
|
||||
fogSettings.vanillaIsRenderingFog = true;
|
||||
|
||||
|
||||
// use any fog overrides the user may have set
|
||||
switch (override)
|
||||
{
|
||||
case ALWAYS_DRAW_FOG_FANCY:
|
||||
quality = FogQuality.FANCY;
|
||||
break;
|
||||
|
||||
case NEVER_DRAW_FOG:
|
||||
quality = FogQuality.OFF;
|
||||
break;
|
||||
|
||||
case ALWAYS_DRAW_FOG_FAST:
|
||||
quality = FogQuality.FAST;
|
||||
break;
|
||||
|
||||
case USE_OPTIFINE_FOG_SETTING:
|
||||
// don't override anything
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
// only use fancy fog if the user's GPU can deliver
|
||||
if (!fancyFogAvailable && quality == FogQuality.FANCY)
|
||||
{
|
||||
quality = FogQuality.FAST;
|
||||
}
|
||||
|
||||
|
||||
// how different distances are drawn depends on the quality set
|
||||
switch (quality)
|
||||
{
|
||||
case FANCY:
|
||||
fogSettings.near.quality = FogQuality.FANCY;
|
||||
fogSettings.far.quality = FogQuality.FANCY;
|
||||
|
||||
switch (LodConfig.CLIENT.graphics.fogDistance.get())
|
||||
{
|
||||
case NEAR_AND_FAR:
|
||||
fogSettings.near.distance = FogDistance.NEAR;
|
||||
fogSettings.far.distance = FogDistance.FAR;
|
||||
break;
|
||||
|
||||
case NEAR:
|
||||
fogSettings.near.distance = FogDistance.NEAR;
|
||||
fogSettings.far.distance = FogDistance.NEAR;
|
||||
break;
|
||||
|
||||
case FAR:
|
||||
fogSettings.near.distance = FogDistance.FAR;
|
||||
fogSettings.far.distance = FogDistance.FAR;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
case FAST:
|
||||
fogSettings.near.quality = FogQuality.FAST;
|
||||
fogSettings.far.quality = FogQuality.FAST;
|
||||
|
||||
// fast fog setting should only have one type of
|
||||
// fog, since the LODs are separated into a near
|
||||
// and far portion; and fast fog is rendered from the
|
||||
// frustrum's perspective instead of the camera
|
||||
switch (LodConfig.CLIENT.graphics.fogDistance.get())
|
||||
{
|
||||
case NEAR_AND_FAR:
|
||||
fogSettings.near.distance = FogDistance.NEAR;
|
||||
fogSettings.far.distance = FogDistance.NEAR;
|
||||
break;
|
||||
|
||||
case NEAR:
|
||||
fogSettings.near.distance = FogDistance.NEAR;
|
||||
fogSettings.far.distance = FogDistance.NEAR;
|
||||
break;
|
||||
|
||||
case FAR:
|
||||
fogSettings.near.distance = FogDistance.FAR;
|
||||
fogSettings.far.distance = FogDistance.FAR;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
case OFF:
|
||||
|
||||
fogSettings.near.quality = FogQuality.OFF;
|
||||
fogSettings.far.quality = FogQuality.OFF;
|
||||
break;
|
||||
|
||||
}
|
||||
|
||||
|
||||
return fogSettings;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Determines if the LODs should have a fullRegen or partialRegen
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
private void determineIfLodsShouldRegenerate(LodDimension lodDim)
|
||||
{
|
||||
|
||||
short renderDistance = (short) mc.options.renderDistance;
|
||||
//=============//
|
||||
// full regens //
|
||||
//=============//
|
||||
// check if the view distance changed
|
||||
if (ClientProxy.previousLodRenderDistance != LodConfig.CLIENT.graphics.lodChunkRenderDistance.get()
|
||||
|| mc.options.renderDistance != prevRenderDistance
|
||||
|| prevFogDistance != LodConfig.CLIENT.graphics.fogDistance.get())
|
||||
{
|
||||
DetailDistanceUtil.updateSettings();
|
||||
fullRegen = true;
|
||||
previousPos.changeParameters((byte) 4, mc.player.xChunk, mc.player.zChunk);
|
||||
prevFogDistance = LodConfig.CLIENT.graphics.fogDistance.get();
|
||||
prevRenderDistance = mc.options.renderDistance;
|
||||
//should use this when it's ready
|
||||
vanillaRenderedChunks = new boolean[renderDistance*2+2][renderDistance*2+2];
|
||||
}
|
||||
|
||||
// did the user change the debug setting?
|
||||
if (LodConfig.CLIENT.debugging.debugMode.get() != previousDebugMode)
|
||||
{
|
||||
previousDebugMode = LodConfig.CLIENT.debugging.debugMode.get();
|
||||
fullRegen = true;
|
||||
}
|
||||
|
||||
|
||||
long newTime = System.currentTimeMillis();
|
||||
|
||||
// check if the player has moved
|
||||
if (newTime - prevPlayerPosTime > LodConfig.CLIENT.buffers.bufferRebuildPlayerMoveTimeout.get())
|
||||
{
|
||||
if (previousPos.detailLevel == 0
|
||||
|| mc.player.xChunk != previousPos.posX
|
||||
|| mc.player.zChunk != previousPos.posZ)
|
||||
{
|
||||
fullRegen = true;
|
||||
previousPos.changeParameters((byte) 4, mc.player.xChunk, mc.player.zChunk);
|
||||
//should use this when it's ready
|
||||
vanillaRenderedChunks = new boolean[renderDistance*2+2][renderDistance*2+2];
|
||||
}
|
||||
prevPlayerPosTime = newTime;
|
||||
}
|
||||
|
||||
|
||||
|
||||
//================//
|
||||
// partial regens //
|
||||
//================//
|
||||
|
||||
|
||||
// check if the vanilla rendered chunks changed
|
||||
if (newTime - prevVanillaChunkTime > LodConfig.CLIENT.buffers.bufferRebuildChunkChangeTimeout.get())
|
||||
{
|
||||
if (vanillaRenderedChunksChanged)
|
||||
{
|
||||
partialRegen = true;
|
||||
vanillaRenderedChunksChanged = false;
|
||||
|
||||
}
|
||||
prevVanillaChunkTime = newTime;
|
||||
}
|
||||
|
||||
|
||||
// check if there is any newly generated terrain to show
|
||||
if (newTime - prevChunkTime > LodConfig.CLIENT.buffers.bufferRebuildLodChangeTimeout.get())
|
||||
{
|
||||
if (lodDim.regenDimension)
|
||||
{
|
||||
partialRegen = true;
|
||||
lodDim.regenDimension = false;
|
||||
}
|
||||
prevChunkTime = newTime;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
//==============//
|
||||
// LOD skipping //
|
||||
//==============//
|
||||
|
||||
// determine which LODs should not be rendered close to the player
|
||||
HashSet<ChunkPos> chunkPosToSkip = LodUtil.getNearbyLodChunkPosToSkip(lodDim, mc.player.blockPosition());
|
||||
int chunkX;
|
||||
int chunkZ;
|
||||
for (ChunkPos pos : chunkPosToSkip)
|
||||
{
|
||||
chunkX = pos.x - mc.player.xChunk + renderDistance + 1;
|
||||
chunkZ = pos.z - mc.player.zChunk + renderDistance + 1;
|
||||
try
|
||||
{
|
||||
if (!vanillaRenderedChunks[chunkX][chunkZ])
|
||||
{
|
||||
vanillaRenderedChunks[chunkX][chunkZ] = true;
|
||||
vanillaRenderedChunksChanged = true;
|
||||
lodDim.setToRegen(pos.getRegionX(), pos.getRegionZ());
|
||||
}
|
||||
}catch (Exception e){
|
||||
System.out.println(vanillaRenderedChunks.length);
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// if the player is high enough, draw all LODs
|
||||
if(chunkPosToSkip.isEmpty() && mc.player.position().y > 256)
|
||||
{
|
||||
vanillaRenderedChunks = new boolean[renderDistance*2+2][renderDistance*2+2];
|
||||
vanillaRenderedChunksChanged = true;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,155 @@
|
||||
/*
|
||||
* This file is part of the LOD Mod, licensed under the GNU GPL v3 License.
|
||||
*
|
||||
* Copyright (C) 2020 James Seibel
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, version 3.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.seibel.lod.render;
|
||||
|
||||
import com.seibel.lod.config.LodConfig;
|
||||
import com.seibel.lod.enums.LodTemplate;
|
||||
import com.seibel.lod.util.LodUtil;
|
||||
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.util.math.BlockPos;
|
||||
import net.minecraft.util.math.ChunkPos;
|
||||
import net.minecraft.util.math.vector.Vector3d;
|
||||
|
||||
/**
|
||||
* This holds miscellaneous helper code
|
||||
* to be used in the rendering process.
|
||||
*
|
||||
* @author James Seibel
|
||||
* @version 8-21-2021
|
||||
*/
|
||||
public class RenderUtil
|
||||
{
|
||||
private static final Minecraft mc = Minecraft.getInstance();
|
||||
|
||||
|
||||
/**
|
||||
* Returns if the given ChunkPos is in the loaded area of the world.
|
||||
* @param centerCoordinate the center of the loaded world (probably the player's ChunkPos)
|
||||
*/
|
||||
public static boolean isChunkPosInLoadedArea(ChunkPos pos, ChunkPos center)
|
||||
{
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
|
||||
return (pos.x >= center.x - mc.options.renderDistance
|
||||
&& pos.x <= center.x + mc.options.renderDistance)
|
||||
&&
|
||||
(pos.z >= center.z - mc.options.renderDistance
|
||||
&& pos.z <= center.z + mc.options.renderDistance);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns if the given coordinate is in the loaded area of the world.
|
||||
* @param centerCoordinate the center of the loaded world
|
||||
*/
|
||||
public static boolean isCoordinateInLoadedArea(int i, int j, int centerCoordinate)
|
||||
{
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
|
||||
return (i >= centerCoordinate - mc.options.renderDistance
|
||||
&& i <= centerCoordinate + mc.options.renderDistance)
|
||||
&&
|
||||
(j >= centerCoordinate - mc.options.renderDistance
|
||||
&& j <= centerCoordinate + mc.options.renderDistance);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Find the coordinates that are in the center half of the given
|
||||
* 2D matrix, starting at (0,0) and going to (2 * lodRadius, 2 * lodRadius).
|
||||
*/
|
||||
public static boolean isCoordinateInNearFogArea(int i, int j, int lodRadius)
|
||||
{
|
||||
int halfRadius = lodRadius / 2;
|
||||
|
||||
return (i >= lodRadius - halfRadius
|
||||
&& i <= lodRadius + halfRadius)
|
||||
&&
|
||||
(j >= lodRadius - halfRadius
|
||||
&& j <= lodRadius + halfRadius);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get how much buffer memory would be required for the given radius multiplier
|
||||
*
|
||||
* issue #62
|
||||
* TODO check if this is actually returning the correct memory needed
|
||||
* it is possible (albeit unlikely) to have a buffer indexOutOfBounds exception
|
||||
* which is caused by the buffer not being big enough.
|
||||
*/
|
||||
public static int getBufferMemoryForRegion()
|
||||
{
|
||||
// calculate the max amount of buffer memory needed (in bytes)
|
||||
return LodUtil.REGION_WIDTH_IN_CHUNKS * LodUtil.REGION_WIDTH_IN_CHUNKS *
|
||||
LodConfig.CLIENT.graphics.lodTemplate.get().getBufferMemoryForSingleLod();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the maxViewDistanceMultiplier for the given LodTemplate
|
||||
* at the given LodDetail level.
|
||||
*/
|
||||
public static int getMaxRadiusMultiplierWithAvaliableMemory(LodTemplate lodTemplate, int detailLevel)
|
||||
{
|
||||
int maxNumberOfLods = LodRenderer.MAX_ALOCATEABLE_DIRECT_MEMORY / lodTemplate.getBufferMemoryForSingleLod();
|
||||
int numbLodsWide = (int) Math.sqrt(maxNumberOfLods);
|
||||
|
||||
return numbLodsWide / (2 * mc.options.renderDistance);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns true if one of the region's 4 corners is in front
|
||||
* of the camera.
|
||||
*/
|
||||
public static boolean isRegionInViewFrustum(BlockPos playerBlockPos, Vector3d cameraDir, BlockPos vboCenterPos)
|
||||
{
|
||||
// convert the vbo position into a direction vector
|
||||
// starting from the player's position
|
||||
Vector3d vboVec = new Vector3d(vboCenterPos.getX(), 0, vboCenterPos.getZ());
|
||||
Vector3d playerVec = new Vector3d(playerBlockPos.getX(), playerBlockPos.getY(), playerBlockPos.getZ());
|
||||
Vector3d vboCenterVec = vboVec.subtract(playerVec);
|
||||
|
||||
|
||||
int halfRegionWidth = LodUtil.REGION_WIDTH / 2;
|
||||
|
||||
// calculate the 4 corners
|
||||
Vector3d vboSeVec = new Vector3d(vboCenterVec.x + halfRegionWidth, vboCenterVec.y, vboCenterVec.z + halfRegionWidth);//.normalize();
|
||||
Vector3d vboSwVec = new Vector3d(vboCenterVec.x - halfRegionWidth, vboCenterVec.y, vboCenterVec.z + halfRegionWidth);//.normalize();
|
||||
Vector3d vboNwVec = new Vector3d(vboCenterVec.x - halfRegionWidth, vboCenterVec.y, vboCenterVec.z - halfRegionWidth);//.normalize();
|
||||
Vector3d vboNeVec = new Vector3d(vboCenterVec.x + halfRegionWidth, vboCenterVec.y, vboCenterVec.z - halfRegionWidth);//.normalize();
|
||||
|
||||
// if any corner is visible, this region should be rendered
|
||||
return isNormalizedVectorInViewFrustum(vboSeVec, cameraDir) ||
|
||||
isNormalizedVectorInViewFrustum(vboSwVec, cameraDir) ||
|
||||
isNormalizedVectorInViewFrustum(vboNwVec, cameraDir) ||
|
||||
isNormalizedVectorInViewFrustum(vboNeVec, cameraDir);
|
||||
}
|
||||
|
||||
/**
|
||||
* Currently takes the dot product of the two vectors,
|
||||
* but in the future could do more complicated frustum culling tests.
|
||||
*/
|
||||
private static boolean isNormalizedVectorInViewFrustum(Vector3d objectVector, Vector3d cameraDir)
|
||||
{
|
||||
// the -0.1 is to offer a slight buffer so we are
|
||||
// more likely to render LODs and thus, hopefully prevent
|
||||
// flickering or odd disappearences
|
||||
return objectVector.dot(cameraDir) > -0.1;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
package com.seibel.lod.util;
|
||||
|
||||
import java.awt.*;
|
||||
|
||||
public class ColorUtil
|
||||
{
|
||||
public static int rgbToInt(int red, int green, int blue)
|
||||
{
|
||||
return (0xFF << 24) | (red << 16) | (green << 8) | blue;
|
||||
}
|
||||
|
||||
public static int rgbToInt(int alpha, int red, int green, int blue)
|
||||
{
|
||||
return (alpha << 24) | (red << 16) | (green << 8) | blue;
|
||||
}
|
||||
|
||||
public static int getAlpha(int color)
|
||||
{
|
||||
return (color >> 24) & 0xFF;
|
||||
}
|
||||
|
||||
public static int getRed(int color)
|
||||
{
|
||||
return (color >> 16) & 0xFF;
|
||||
}
|
||||
|
||||
public static int getGreen(int color)
|
||||
{
|
||||
return (color >> 8) & 0xFF;
|
||||
}
|
||||
|
||||
public static int getBlue(int color)
|
||||
{
|
||||
return color & 0xFF;
|
||||
}
|
||||
|
||||
public static int applyShade(int color, int shade)
|
||||
{
|
||||
if (shade < 0)
|
||||
{
|
||||
return (getAlpha(color) << 24) | (Math.max(getRed(color) + shade, 0) << 16) | (Math.max(getGreen(color) + shade, 0) << 8) | Math.max(getBlue(color) + shade, 0);
|
||||
} else
|
||||
{
|
||||
return (getAlpha(color) << 24) | (Math.min(getRed(color) + shade, 255) << 16) | (Math.min(getGreen(color) + shade, 255) << 8) | Math.min(getBlue(color) + shade, 255);
|
||||
}
|
||||
}
|
||||
|
||||
public static int applyShade(int color, float shade)
|
||||
{
|
||||
if (shade < 1)
|
||||
{
|
||||
return (getAlpha(color) << 24) | ((int) Math.max(getRed(color) * shade, 0) << 16) | ((int) Math.max(getGreen(color) * shade, 0) << 8) | (int) Math.max(getBlue(color) * shade, 0);
|
||||
} else
|
||||
{
|
||||
return (getAlpha(color) << 24) | ((int) Math.min(getRed(color) * shade, 255) << 16) | ((int) Math.min(getGreen(color) * shade, 255) << 8) | (int) Math.min(getBlue(color) * shade, 255);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Edit the given color as a HSV (Hue Saturation Value) color.
|
||||
*/
|
||||
public static int applySaturationAndBrightnessMultipliers(int color, float saturationMultiplier, float brightnessMultiplier)
|
||||
{
|
||||
float[] hsv = Color.RGBtoHSB(getRed(color), getGreen(color), getBlue(color), null);
|
||||
return Color.getHSBColor(
|
||||
hsv[0], // hue
|
||||
LodUtil.clamp(0.0f, hsv[1] * saturationMultiplier, 1.0f),
|
||||
LodUtil.clamp(0.0f, hsv[2] * brightnessMultiplier, 1.0f)).getRGB();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,189 @@
|
||||
package com.seibel.lod.util;
|
||||
|
||||
import com.seibel.lod.config.LodConfig;
|
||||
import com.seibel.lod.enums.DistanceGenerationMode;
|
||||
import com.seibel.lod.enums.LodDetail;
|
||||
import com.seibel.lod.objects.RegionPos;
|
||||
|
||||
import net.minecraft.client.Minecraft;
|
||||
|
||||
public class DetailDistanceUtil
|
||||
{
|
||||
private static double genMultiplier = 1.0;
|
||||
private static double treeGenMultiplier = 1.0;
|
||||
private static double treeCutMultiplier = 1.0;
|
||||
private static int minGenDetail = LodConfig.CLIENT.worldGenerator.maxGenerationDetail.get().detailLevel;
|
||||
private static int minDrawDetail = Math.max(LodConfig.CLIENT.graphics.maxDrawDetail.get().detailLevel,LodConfig.CLIENT.worldGenerator.maxGenerationDetail.get().detailLevel);
|
||||
private static int maxDetail = LodUtil.REGION_DETAIL_LEVEL + 1;
|
||||
private static int minDistance = 0;
|
||||
private static int maxDistance = LodConfig.CLIENT.graphics.lodChunkRenderDistance.get() * 16 * 2;
|
||||
private static int base = 2;
|
||||
private static double logBase = Math.log(2);
|
||||
|
||||
private static LodDetail[] lodDetails = {
|
||||
LodDetail.FULL,
|
||||
LodDetail.HALF,
|
||||
LodDetail.QUAD,
|
||||
LodDetail.DOUBLE,
|
||||
LodDetail.SINGLE,
|
||||
LodDetail.SINGLE,
|
||||
LodDetail.SINGLE,
|
||||
LodDetail.SINGLE,
|
||||
LodDetail.SINGLE,
|
||||
LodDetail.SINGLE,
|
||||
LodDetail.SINGLE};
|
||||
|
||||
|
||||
public static void updateSettings(){
|
||||
minGenDetail = LodConfig.CLIENT.worldGenerator.maxGenerationDetail.get().detailLevel;
|
||||
minDrawDetail = Math.max(LodConfig.CLIENT.graphics.maxDrawDetail.get().detailLevel,LodConfig.CLIENT.worldGenerator.maxGenerationDetail.get().detailLevel);
|
||||
maxDistance = LodConfig.CLIENT.graphics.lodChunkRenderDistance.get() * 16 * 2;
|
||||
}
|
||||
|
||||
public static int getDistanceRendering(int detail)
|
||||
{
|
||||
int initial;
|
||||
int distance = 0;
|
||||
if (detail <= minGenDetail)
|
||||
return minDistance;
|
||||
if (detail == maxDetail)
|
||||
return maxDistance;
|
||||
if (detail == maxDetail + 1)
|
||||
return maxDistance;
|
||||
switch (LodConfig.CLIENT.worldGenerator.lodDistanceCalculatorType.get())
|
||||
{
|
||||
case LINEAR:
|
||||
initial = LodConfig.CLIENT.graphics.lodQuality.get() * 128;
|
||||
return (detail * initial);
|
||||
case QUADRATIC:
|
||||
initial = LodConfig.CLIENT.graphics.lodQuality.get() * 128;
|
||||
return (int) (Math.pow(base, detail) * initial);
|
||||
case RENDER_DEPENDANT:
|
||||
int realRenderDistance = Minecraft.getInstance().options.renderDistance * 16;
|
||||
int border = 64;
|
||||
byte detailAtBorder = (byte) 4;
|
||||
if (detail > detailAtBorder)
|
||||
{
|
||||
return (detail * (border - realRenderDistance) / detailAtBorder + realRenderDistance);
|
||||
} else
|
||||
{
|
||||
return ((maxDetail - detail) * (maxDistance - border) / (maxDetail - detailAtBorder) + border);
|
||||
}
|
||||
}
|
||||
return distance;
|
||||
}
|
||||
|
||||
public static byte baseInverse(int distance, int minDetail)
|
||||
{
|
||||
int initial;
|
||||
byte detail = 0;
|
||||
if (distance == 0)
|
||||
detail = (byte) minDetail;
|
||||
if (distance > maxDistance)
|
||||
detail = (byte) (maxDetail-1);
|
||||
switch (LodConfig.CLIENT.worldGenerator.lodDistanceCalculatorType.get())
|
||||
{
|
||||
case LINEAR:
|
||||
initial = LodConfig.CLIENT.graphics.lodQuality.get() * 128;
|
||||
detail = (byte) Math.floorDiv(distance, initial);
|
||||
break;
|
||||
case QUADRATIC:
|
||||
initial = LodConfig.CLIENT.graphics.lodQuality.get() * 128;
|
||||
detail = (byte) (Math.log(Math.floorDiv(distance, initial))/logBase);
|
||||
break;
|
||||
case RENDER_DEPENDANT:
|
||||
detail = (byte) 9;
|
||||
break;
|
||||
}
|
||||
return (byte) Math.min(detail, LodUtil.REGION_DETAIL_LEVEL);
|
||||
}
|
||||
|
||||
public static byte getDistanceRenderingInverse(int distance)
|
||||
{
|
||||
return baseInverse(distance, minDrawDetail);
|
||||
}
|
||||
|
||||
public static byte getDistanceGenerationInverse(int distance)
|
||||
{
|
||||
return baseInverse((int) (distance * genMultiplier), minGenDetail);
|
||||
}
|
||||
|
||||
public static byte getDistanceTreeCutInverse(int distance)
|
||||
{
|
||||
return baseInverse((int) (distance * treeCutMultiplier), minGenDetail);
|
||||
}
|
||||
|
||||
|
||||
public static byte getDistanceTreeGenInverse(int distance)
|
||||
{
|
||||
return baseInverse((int) (distance * treeGenMultiplier), minGenDetail);
|
||||
}
|
||||
|
||||
public static int getDistanceGeneration(int detail)
|
||||
{
|
||||
if (detail == maxDetail)
|
||||
return maxDistance;
|
||||
return (int) (getDistanceRendering(detail) * genMultiplier);
|
||||
}
|
||||
|
||||
public static int getDistanceTreeCut(int detail)
|
||||
{
|
||||
if (detail == maxDetail)
|
||||
return maxDistance;
|
||||
return (int) (getDistanceRendering(detail) * treeCutMultiplier);
|
||||
}
|
||||
|
||||
public static int getDistanceTreeGen(int detail)
|
||||
{
|
||||
if (detail == maxDetail)
|
||||
return maxDistance;
|
||||
return (int) (getDistanceRendering(detail) * treeGenMultiplier);
|
||||
}
|
||||
|
||||
public static DistanceGenerationMode getDistanceGenerationMode(int detail)
|
||||
{
|
||||
return LodConfig.CLIENT.worldGenerator.distanceGenerationMode.get();
|
||||
}
|
||||
|
||||
public static byte getLodDrawDetail(int detail)
|
||||
{
|
||||
return (byte) Math.max(detail, minDrawDetail);
|
||||
}
|
||||
|
||||
public static LodDetail getLodGenDetail(int detail)
|
||||
{
|
||||
if (detail < minGenDetail)
|
||||
{
|
||||
return lodDetails[minGenDetail];
|
||||
} else
|
||||
{
|
||||
return lodDetails[detail];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static byte getCutLodDetail(int detail)
|
||||
{
|
||||
if (detail < minGenDetail)
|
||||
{
|
||||
return lodDetails[minGenDetail].detailLevel;
|
||||
} else if (detail == maxDetail)
|
||||
{
|
||||
return LodUtil.REGION_DETAIL_LEVEL;
|
||||
} else
|
||||
{
|
||||
return lodDetails[detail].detailLevel;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static boolean regionInView(int playerPosX, int playerPosY, int playerPosZ, int xRot, int yRot, int fov, RegionPos regionPos)
|
||||
{
|
||||
|
||||
//System.out.println(Math.floorMod((int) mc.player.xRot,360) + " " + Math.floorMod((int) mc.player.yRot,360) + " " + mc.options.fov);
|
||||
//System.out.println(mc.player.xRotO + " " + mc.player.yRotO);
|
||||
//mc.options.fov;
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package com.seibel.lod.util;
|
||||
|
||||
import java.util.concurrent.ThreadFactory;
|
||||
|
||||
/**
|
||||
* Just a simple ThreadFactory to name ExecutorService
|
||||
* threads, which can be helpful when debugging.
|
||||
*
|
||||
* @author James Seibel
|
||||
* @version 8-15-2021
|
||||
*/
|
||||
public class LodThreadFactory implements ThreadFactory
|
||||
{
|
||||
public String threadName;
|
||||
|
||||
|
||||
public LodThreadFactory(String newThreadName)
|
||||
{
|
||||
threadName = newThreadName + " Thread";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Thread newThread(Runnable r)
|
||||
{
|
||||
return new Thread(r, threadName);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,378 @@
|
||||
/*
|
||||
* This file is part of the LOD Mod, licensed under the GNU GPL v3 License.
|
||||
*
|
||||
* Copyright (C) 2020 James Seibel
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, version 3.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.seibel.lod.util;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.io.File;
|
||||
import java.util.HashSet;
|
||||
|
||||
import com.seibel.lod.objects.LodDimension;
|
||||
import com.seibel.lod.objects.RegionPos;
|
||||
import com.seibel.lod.objects.LevelPos.LevelPos;
|
||||
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.multiplayer.ServerData;
|
||||
import net.minecraft.client.renderer.WorldRenderer;
|
||||
import net.minecraft.server.integrated.IntegratedServer;
|
||||
import net.minecraft.util.math.BlockPos;
|
||||
import net.minecraft.util.math.ChunkPos;
|
||||
import net.minecraft.world.DimensionType;
|
||||
import net.minecraft.world.IWorld;
|
||||
import net.minecraft.world.chunk.ChunkSection;
|
||||
import net.minecraft.world.chunk.IChunk;
|
||||
import net.minecraft.world.gen.Heightmap;
|
||||
import net.minecraft.world.server.ServerChunkProvider;
|
||||
import net.minecraft.world.server.ServerWorld;
|
||||
|
||||
/**
|
||||
* This class holds methods and constants that may be used in multiple places.
|
||||
*
|
||||
* @author James Seibel
|
||||
* @version 8-29-2021
|
||||
*/
|
||||
public class LodUtil
|
||||
{
|
||||
private static Minecraft mc = Minecraft.getInstance();
|
||||
|
||||
|
||||
/** alpha used when drawing chunks in debug mode */
|
||||
public static final int DEBUG_ALPHA = 255; // 0 - 255
|
||||
public static final Color COLOR_DEBUG_BLACK = new Color(0, 0, 0, DEBUG_ALPHA);
|
||||
public static final Color COLOR_DEBUG_WHITE = new Color(255, 255, 255, DEBUG_ALPHA);
|
||||
public static final Color COLOR_INVISIBLE = new Color(0,0,0,0);
|
||||
|
||||
/** a gray-purple color */
|
||||
public static final int MYCELIUM_COLOR_INT = LodUtil.colorToInt(Color.decode("#6E6166"));
|
||||
/** TODO, add a better way to override material colors
|
||||
* and/or add a method to generate colors based on texture
|
||||
* issue #64 */
|
||||
public static final int STONE_COLOR_INT = LodUtil.colorToInt(new Color(150, 150, 150));
|
||||
|
||||
/**
|
||||
* In order of nearest to farthest: <br>
|
||||
* Red, Orange, Yellow, Green, Cyan, Blue, Magenta, white, gray, black
|
||||
*/
|
||||
public static final Color DEBUG_DETAIL_LEVEL_COLORS[] = new Color[] { Color.RED, Color.ORANGE, Color.YELLOW, Color.GREEN, Color.CYAN, Color.BLUE, Color.MAGENTA, Color.WHITE, Color.GRAY, Color.BLACK };
|
||||
|
||||
|
||||
/** 512 blocks wide */
|
||||
public static final byte REGION_DETAIL_LEVEL = 9;
|
||||
/** 16 blocks wide */
|
||||
public static final byte CHUNK_DETAIL_LEVEL = 4;
|
||||
/** 1 block wide */
|
||||
public static final byte BLOCK_DETAIL_LEVEL = 0;
|
||||
|
||||
|
||||
public static final byte DETAIL_OPTIONS = 10;
|
||||
|
||||
/** measured in Blocks <br>
|
||||
* detail level 9 */
|
||||
public static final short REGION_WIDTH = 512;
|
||||
/** measured in Blocks <br>
|
||||
* detail level 4 */
|
||||
public static final short CHUNK_WIDTH = 16;
|
||||
/** measured in Blocks <br>
|
||||
* detail level 0 */
|
||||
public static final short BLOCK_WIDTH = 1;
|
||||
|
||||
|
||||
/** number of chunks wide */
|
||||
public static final int REGION_WIDTH_IN_CHUNKS = 32;
|
||||
|
||||
|
||||
/** If we ever need to use a heightmap for any reason, use this one. */
|
||||
public static final Heightmap.Type DEFAULT_HEIGHTMAP = Heightmap.Type.WORLD_SURFACE_WG;
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Gets the first valid ServerWorld.
|
||||
*
|
||||
* @return null if there are no ServerWorlds
|
||||
*/
|
||||
public static ServerWorld getFirstValidServerWorld()
|
||||
{
|
||||
if (mc.hasSingleplayerServer())
|
||||
return null;
|
||||
|
||||
Iterable<ServerWorld> worlds = mc.getSingleplayerServer().getAllLevels();
|
||||
|
||||
for (ServerWorld world : worlds)
|
||||
return world;
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the ServerWorld for the relevant dimension.
|
||||
*
|
||||
* @return null if there is no ServerWorld for the given dimension
|
||||
*/
|
||||
public static ServerWorld getServerWorldFromDimension(DimensionType dimension)
|
||||
{
|
||||
IntegratedServer server = mc.getSingleplayerServer();
|
||||
if (server == null)
|
||||
return null;
|
||||
|
||||
Iterable<ServerWorld> worlds = server.getAllLevels();
|
||||
ServerWorld returnWorld = null;
|
||||
|
||||
for (ServerWorld world : worlds)
|
||||
{
|
||||
if(world.dimensionType() == dimension)
|
||||
{
|
||||
returnWorld = world;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return returnWorld;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a 2D absolute position into a quad tree relative position.
|
||||
*/
|
||||
public static RegionPos convertGenericPosToRegionPos(int x, int z, int detailLevel)
|
||||
{
|
||||
int relativePosX = Math.floorDiv(x, (int) Math.pow(2, LodUtil.REGION_DETAIL_LEVEL - detailLevel));
|
||||
int relativePosZ = Math.floorDiv(z, (int) Math.pow(2, LodUtil.REGION_DETAIL_LEVEL - detailLevel));
|
||||
|
||||
return new RegionPos(relativePosX, relativePosZ);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a 2D absolute position into a quad tree relative position.
|
||||
*/
|
||||
public static int convertLevelPos(int pos, int currectDetailLevel, int targetDetailLevel)
|
||||
{
|
||||
int newPos = Math.floorDiv(pos, (int) Math.pow(2, targetDetailLevel - currectDetailLevel));
|
||||
|
||||
return newPos;
|
||||
}
|
||||
/**
|
||||
* Return whether the given chunk
|
||||
* has any data in it.
|
||||
*/
|
||||
public static boolean chunkHasBlockData(IChunk chunk)
|
||||
{
|
||||
ChunkSection[] blockStorage = chunk.getSections();
|
||||
|
||||
for(ChunkSection section : blockStorage)
|
||||
{
|
||||
if(section != null && !section.isEmpty())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* If on single player this will return the name of the user's
|
||||
* world, if in multiplayer it will return the server name, IP,
|
||||
* and game version.
|
||||
*/
|
||||
public static String getWorldID(IWorld world)
|
||||
{
|
||||
if(mc.hasSingleplayerServer())
|
||||
{
|
||||
// chop off the dimension ID as it is not needed/wanted
|
||||
String dimId = getDimensionIDFromWorld(world);
|
||||
|
||||
// get the world name
|
||||
int saveIndex = dimId.indexOf("saves") + 1 + "saves".length();
|
||||
int slashIndex = dimId.indexOf(File.separatorChar, saveIndex);
|
||||
dimId = dimId.substring(saveIndex, slashIndex);
|
||||
return dimId;
|
||||
}
|
||||
else
|
||||
{
|
||||
ServerData server = mc.getCurrentServer();
|
||||
return server.name + ", IP " +
|
||||
server.ip + ", GameVersion " +
|
||||
server.version.getString();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* If on single player this will return the name of the user's
|
||||
* world and the dimensional save folder, if in multiplayer
|
||||
* it will return the server name, game version, and dimension.<br>
|
||||
* <br>
|
||||
* This can be used to determine where to save files for a given
|
||||
* dimension.
|
||||
*/
|
||||
public static String getDimensionIDFromWorld(IWorld world)
|
||||
{
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
|
||||
if(mc.hasSingleplayerServer())
|
||||
{
|
||||
// this will return the world save location
|
||||
// and the dimension folder
|
||||
|
||||
ServerWorld serverWorld = LodUtil.getServerWorldFromDimension(world.dimensionType());
|
||||
if(serverWorld == null)
|
||||
throw new NullPointerException("getDimensionIDFromWorld wasn't able to get the ServerWorld for the dimension " + world.dimensionType().effectsLocation().getPath());
|
||||
|
||||
ServerChunkProvider provider = serverWorld.getChunkSource();
|
||||
if(provider == null)
|
||||
throw new NullPointerException("getDimensionIDFromWorld wasn't able to get the ServerChunkProvider for the dimension " + world.dimensionType().effectsLocation().getPath());
|
||||
|
||||
return provider.dataStorage.dataFolder.toString();
|
||||
}
|
||||
else
|
||||
{
|
||||
ServerData server = mc.getCurrentServer();
|
||||
return server.name + ", IP " +
|
||||
server.ip + ", GameVersion " +
|
||||
server.version.getString() + File.separatorChar
|
||||
+ "dim_" + world.dimensionType().effectsLocation().getPath() + File.separatorChar;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Convert a BlockColors int into a Color object.
|
||||
*/
|
||||
public static Color intToColor(int num)
|
||||
{
|
||||
int filter = 0b11111111;
|
||||
|
||||
int red = (num >> 16 ) & filter;
|
||||
int green = (num >> 8 ) & filter;
|
||||
int blue = num & filter;
|
||||
|
||||
return new Color(red, green, blue);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a Color into a BlockColors object.
|
||||
*/
|
||||
public static int colorToInt(Color color)
|
||||
{
|
||||
return color.getRGB();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Clamps the given value between the min and max values.
|
||||
* May behave strangely if min > max.
|
||||
*/
|
||||
public static int clamp(int min, int value, int max)
|
||||
{
|
||||
return Math.min(max, Math.max(value, min));
|
||||
}
|
||||
|
||||
/**
|
||||
* Clamps the given value between the min and max values.
|
||||
* May behave strangely if min > max.
|
||||
*/
|
||||
public static float clamp(float min, float value, float max)
|
||||
{
|
||||
return Math.min(max, Math.max(value, min));
|
||||
}
|
||||
|
||||
/**
|
||||
* Clamps the given value between the min and max values.
|
||||
* May behave strangely if min > max.
|
||||
*/
|
||||
public static double clamp(double min, double value, double max)
|
||||
{
|
||||
return Math.min(max, Math.max(value, min));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Get a HashSet of all ChunkPos within the normal render distance
|
||||
* that should not be rendered.
|
||||
*/
|
||||
public static HashSet<ChunkPos> getNearbyLodChunkPosToSkip(LodDimension lodDim, BlockPos playerPos)
|
||||
{
|
||||
int chunkRenderDist = mc.options.renderDistance;
|
||||
ChunkPos centerChunk = new ChunkPos(playerPos);
|
||||
|
||||
// skip chunks that are already going to be rendered by Minecraft
|
||||
HashSet<ChunkPos> posToSkip = getRenderedChunks();
|
||||
|
||||
// go through each chunk within the normal view distance
|
||||
for (int x = centerChunk.x - chunkRenderDist; x < centerChunk.x + chunkRenderDist; x++)
|
||||
{
|
||||
for (int z = centerChunk.z - chunkRenderDist; z < centerChunk.z + chunkRenderDist; z++)
|
||||
{
|
||||
LevelPos levelPos = new LevelPos(LodUtil.CHUNK_DETAIL_LEVEL, x, z);
|
||||
if (!lodDim.doesDataExist(levelPos.clone()))
|
||||
continue;
|
||||
|
||||
short[] data = lodDim.getData(levelPos);
|
||||
|
||||
short lodAverageHeight = data[0];
|
||||
if (playerPos.getY() <= lodAverageHeight)
|
||||
{
|
||||
// don't draw Lod's that are taller than the player
|
||||
// to prevent LODs being drawn on top of the player
|
||||
posToSkip.add(new ChunkPos(x, z));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return posToSkip;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method returns the ChunkPos of all chunks that Minecraft
|
||||
* is going to render this frame. <br><br>
|
||||
* <p>
|
||||
* Note: This isn't perfect. It will return some chunks that are outside
|
||||
* the clipping plane. (For example, if you are high above the ground some chunks
|
||||
* will be incorrectly added, even though they are outside render range).
|
||||
*/
|
||||
public static HashSet<ChunkPos> getRenderedChunks()
|
||||
{
|
||||
HashSet<ChunkPos> loadedPos = new HashSet<>();
|
||||
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
|
||||
// Wow those are some long names!
|
||||
|
||||
// go through every RenderInfo to get the compiled chunks
|
||||
for (WorldRenderer.LocalRenderInformationContainer worldrenderer$localrenderinformationcontainer : mc.levelRenderer.renderChunks)
|
||||
{
|
||||
if (!worldrenderer$localrenderinformationcontainer.chunk.getCompiledChunk().hasNoRenderableLayers())
|
||||
{
|
||||
// add the ChunkPos for every empty compiled chunk
|
||||
BlockPos bpos = worldrenderer$localrenderinformationcontainer.chunk.getOrigin();
|
||||
|
||||
loadedPos.add(new ChunkPos(bpos));
|
||||
}
|
||||
}
|
||||
|
||||
return loadedPos;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* This file is part of the LOD Mod, licensed under the GNU GPL v3 License.
|
||||
*
|
||||
* Copyright (C) 2020 James Seibel
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, version 3.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.seibel.lod.util;
|
||||
|
||||
/**
|
||||
* This holds meta information about the mod.
|
||||
*
|
||||
* @author James Seibel
|
||||
* @version 04-16-2020
|
||||
*/
|
||||
public class Reference
|
||||
{
|
||||
/** the mod's identifier */
|
||||
public static final String MOD_ID = "lod";
|
||||
/** the mod's name */
|
||||
public static final String NAME = "LOD Mod";
|
||||
/** the mod's version */
|
||||
public static final String VERSION = "1.0";
|
||||
/** the version of minecraft this mod is built for */
|
||||
public static final String ACCEPTED_VERSIONS = "[1.16.4]";
|
||||
|
||||
/** where the client proxy class is */
|
||||
public static final String CLIENT_PROXY_CLASS = "com.backsun.lod.proxy.ClientProxy";
|
||||
/** where the common proxy class is*/
|
||||
public static final String COMMON_PROXY_CLASS = "com.backsun.lod.proxy.CommonProxy";
|
||||
|
||||
|
||||
}
|
||||
@@ -1,33 +1,50 @@
|
||||
# Note: to update code in eclipse run the "eclipse" command in graldew
|
||||
|
||||
|
||||
# make public the method getFOVModifier
|
||||
# used when creating the projection matrix
|
||||
public net.minecraft.client.renderer.GameRenderer func_215311_a(Lnet/minecraft/client/renderer/ActiveRenderInfo;FZ)D # getFOVModifier
|
||||
public net.minecraft.client.renderer.GameRenderer field_78529_t # rendererUpdateCount
|
||||
public net.minecraft.client.renderer.GameRenderer func_228380_a_(Lcom/mojang/blaze3d/matrix/MatrixStack;F)V # hurtCameraEffect
|
||||
public net.minecraft.client.renderer.GameRenderer func_228383_b_(Lcom/mojang/blaze3d/matrix/MatrixStack;F)V # applyBobbing
|
||||
|
||||
# make public the byteBuffer in BufferBuilder
|
||||
# used when accessing built byteBuffers
|
||||
public net.minecraft.client.renderer.BufferBuilder field_179001_a # byteBuffer
|
||||
|
||||
# make public the cameraZoom in the GameRenderer
|
||||
public net.minecraft.client.renderer.GameRenderer field_78503_V # cameraZoom
|
||||
|
||||
# make public the cameraYaw in the GameRenderer
|
||||
public net.minecraft.client.renderer.GameRenderer field_228376_w_ # cameraYaw
|
||||
|
||||
# make public the cameraPitch in the GameRenderer
|
||||
public net.minecraft.client.renderer.GameRenderer field_228377_x_ # cameraPitch
|
||||
|
||||
# make public the folder in the DimensionSavedDataManager
|
||||
# used when determining where to save files too
|
||||
public net.minecraft.world.storage.DimensionSavedDataManager field_215759_d # folder
|
||||
|
||||
# make public the ambiantLight field in DimensionType
|
||||
public net.minecraft.world.DimensionType field_236017_x_ # ambientLight
|
||||
|
||||
# make public the renderUpdateCount in GameRenderer
|
||||
public net.minecraft.client.renderer.GameRenderer field_78529_t # rendererUpdateCount
|
||||
|
||||
# make public the materialColor in AbstractBlockState
|
||||
# used when generating LodChunks
|
||||
public net.minecraft.block.AbstractBlock$AbstractBlockState field_235704_h_ # materialColor
|
||||
|
||||
# used when determining which chunks Vanilla Minecraft is going to render
|
||||
public net.minecraft.client.renderer.WorldRenderer$LocalRenderInformationContainer
|
||||
public net.minecraft.client.renderer.WorldRenderer field_72755_R # renderInfos
|
||||
public net.minecraft.client.renderer.WorldRenderer$LocalRenderInformationContainer field_178036_a # renderChunk
|
||||
|
||||
# used in world generation
|
||||
public net.minecraft.world.server.ServerWorld field_241106_P_ # structuremanager
|
||||
public net.minecraft.world.gen.Heightmap func_202267_b(II)I # getDataArrayIndex
|
||||
public net.minecraft.world.gen.Heightmap func_202272_a(III)V # set
|
||||
public net.minecraft.world.chunk.Chunk field_76634_f # heightMap
|
||||
public net.minecraft.world.chunk.Chunk field_76652_q # sections
|
||||
public net.minecraft.world.chunk.ChunkPrimer field_201661_i # sections
|
||||
public net.minecraft.world.server.ChunkManager field_219269_w # templateManager
|
||||
public net.minecraft.world.server.ChunkManager field_219256_j # lightManager
|
||||
public net.minecraft.world.gen.feature.template.TemplateManager field_186240_a # templates
|
||||
public net.minecraft.world.biome.Biome field_242424_k # biomeGenerationSettings
|
||||
public net.minecraft.world.gen.blockstateprovider.WeightedBlockStateProvider field_227406_b_ # weightedStates
|
||||
public net.minecraft.world.gen.placement.ConfiguredPlacement field_215096_a # decorator
|
||||
public net.minecraft.world.gen.placement.ConfiguredPlacement field_215097_b # config
|
||||
public net.minecraft.util.WeightedList field_220658_a # weightedEntries
|
||||
public net.minecraft.world.gen.feature.FeatureSpread field_242250_b # base
|
||||
public net.minecraft.world.gen.feature.FeatureSpread field_242251_c # spread
|
||||
public net.minecraft.world.gen.feature.ConfiguredFeature func_242765_a(Lnet/minecraft/world/ISeedReader;Lnet/minecraft/world/gen/ChunkGenerator;Ljava/util/Random;Lnet/minecraft/util/math/BlockPos;)Z # place
|
||||
public net.minecraft.world.server.ServerChunkProvider field_217244_j # dataStorage
|
||||
public net.minecraft.world.lighting.WorldLightManager field_215576_a # blockEngine
|
||||
public net.minecraft.world.lighting.WorldLightManager field_215577_b # skyEngine
|
||||
public net.minecraft.world.gen.feature.Feature field_236290_a_ # configuredCodec
|
||||
|
||||
|
||||
|
||||
#=====================#
|
||||
# Examples from Forge #
|
||||
|
||||
@@ -7,14 +7,14 @@
|
||||
modLoader="javafml" #mandatory
|
||||
|
||||
#// A version range to match for said mod loader - for regular FML @Mod it will be the forge version
|
||||
loaderVersion="[35,)" #mandatory This is typically bumped every Minecraft version by Forge. See our download page for lists of versions.
|
||||
loaderVersion="[36,)" #mandatory This is typically bumped every Minecraft version by Forge. See our download page for lists of versions.
|
||||
|
||||
#// The license for you mod. This is mandatory metadata and allows for easier comprehension of your redistributive properties.
|
||||
#// Review your options at https://choosealicense.com/. All rights reserved is the default copyright stance, and is thus the default here.
|
||||
license="All rights reserved"
|
||||
license="GNU GPLv3"
|
||||
|
||||
#// A URL to refer people to when problems occur with this mod
|
||||
#//issueTrackerURL="https://change.me.to.your.issue.tracker.example.invalid/" #optional
|
||||
issueTrackerURL="https://gitlab.com/jeseibel/minecraft-lod-mod/-/issues" #optional
|
||||
#// A list of mods - how many allowed here is determined by the individual mod loader
|
||||
[[mods]] #mandatory
|
||||
|
||||
@@ -24,27 +24,25 @@ modId="lod" #mandatory
|
||||
#// The version number of the mod - there's a few well known ${} variables useable here or just hardcode it
|
||||
#//${file.jarVersion} will substitute the value of the Implementation-Version as read from the mod's JAR file metadata
|
||||
#// see the associated build.gradle script for how to populate this completely automatically during a build
|
||||
version="a1" #mandatory
|
||||
version="a1.4" #mandatory
|
||||
|
||||
#// A display name for the mod
|
||||
displayName="Levels of Detail" #mandatory
|
||||
displayName="LOD" #mandatory
|
||||
|
||||
#// A URL to query for updates for this mod. See the JSON update specification https://mcforge.readthedocs.io/en/latest/gettingstarted/autoupdate/
|
||||
#//updateJSONURL="https://change.me.example.invalid/updates.json" #optional
|
||||
|
||||
#// A URL for the "homepage" for this mod, displayed in the mod UI
|
||||
#//displayURL="https://change.me.to.your.mods.homepage.example.invalid/" #optional
|
||||
displayURL="https://www.curseforge.com/minecraft/mc-mods/lod-level-of-detail" #optional
|
||||
|
||||
#// A file name (in the root of the mod JAR) containing a logo for display
|
||||
#//logoFile="examplemod.png" #optional
|
||||
logoFile="logo.png" #optional
|
||||
|
||||
#// A text field displayed in the mod UI
|
||||
credits="TechnoVision, Vike, and Darkhax for their modding tutorials." #optional
|
||||
|
||||
#// A text field displayed in the mod UI
|
||||
authors="James Seibel" #optional
|
||||
authors="James Seibel and Leonardo Amato" #optional
|
||||
|
||||
#// The description text for the mod (multi line!) (#mandatory)
|
||||
description='''
|
||||
This mod generates and renders simplified chunks beyond the normal view distance, at a low performance cost.
|
||||
'''
|
||||
description='''This mod generates and renders simplified terrain beyond the normal view distance, at a low performance cost.'''
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"required": true,
|
||||
"package": "com.backsun.lod.mixin",
|
||||
"package": "com.seibel.lod.mixin",
|
||||
"compatibilityLevel": "JAVA_8",
|
||||
"refmap": "lod.refmap.json",
|
||||
"mixins": [
|
||||
|
||||
|
After Width: | Height: | Size: 172 KiB |
@@ -1,16 +0,0 @@
|
||||
[
|
||||
{
|
||||
"modid": "lod",
|
||||
"name": "Level Of Details",
|
||||
"description": "Generates and renders simplified chunks beyond the normal view distance, at a low performance cost.",
|
||||
"version": "a1",
|
||||
"mcversion": "1.16.4",
|
||||
"url": "",
|
||||
"updateUrl": "",
|
||||
"authorList": ["James Seibel"],
|
||||
"credits": "TechnoVision, Vike, and Darkhax for their modding tutorials.",
|
||||
"logoFile": "",
|
||||
"screenshots": [],
|
||||
"dependencies": []
|
||||
}
|
||||
]
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"pack": {
|
||||
"description": "examplemod resources",
|
||||
"description": "",
|
||||
"pack_format": 6,
|
||||
"_comment": "A pack_format of 6 requires json lang files and some texture changes from 1.16.2. Note: we require v6 pack meta for all mods."
|
||||
}
|
||||
|
||||