Compare commits

..

141 Commits

Author SHA1 Message Date
James Seibel 814ac5df84 Change distanceBiomeOnlyGeneration to distanceGenerationMode in the configHandler 2021-07-11 18:41:38 -05:00
James Seibel 55f9d142e6 Add 1.16.5 a1.3.1 to the compiled Jars 2021-07-09 23:10:36 -05:00
James Seibel 58607ab1fc Update the version to a1.3.1 2021-07-09 23:09:32 -05:00
James Seibel 433ab4f92f Fix #37 (Z fighting far from the world's origin) 2021-07-09 23:08:30 -05:00
James Seibel 8840973a1e Fix #41 (leaving a world causing Lod generation to break) 2021-07-09 22:57:32 -05:00
James Seibel 4460789ab1 Fix a typo in the release 2021-07-05 17:17:08 -05:00
James Seibel a232f449ed change mcmod.info Details to Detail 2021-07-05 17:16:26 -05:00
James Seibel 1c5988fbfb Update the readme to say which version of retail minecraft has been confirmed to work. 2021-07-05 17:12:27 -05:00
James Seibel b386547616 Improve how the compiled jars are stored 2021-07-05 17:06:35 -05:00
James Seibel 03ea7ee460 Rename the mod internally from Levels of Detail to Level of Detail 2021-07-05 16:56:30 -05:00
James Seibel 752a2e4a69 Merge branch '1.16.5' of gitlab.com:jeseibel/minecraft-lod-mod into 1.16.5 2021-07-05 16:51:22 -05:00
James Seibel 3e3e30c4b1 Add some comments about issue #40 2021-07-05 16:51:18 -05:00
James Seibel f5cc5c2846 Improve config wording 2021-07-05 16:40:12 -05:00
James Seibel 609d471346 Update the version number to a1.3 2021-07-05 13:09:01 -05:00
James Seibel 5f921ffcef Make LICENSE.txt uppercase 2021-07-05 18:04:30 +00:00
James Seibel 4aebf36f02 Put the project under the GNU GPL v3 License 2021-07-05 12:55:42 -05:00
James Seibel 0b1fdba6ea Optimize world generation and add a config to set the number of generation threads 2021-07-05 12:41:20 -05:00
James Seibel 475324ed73 fix config typos 2021-07-05 12:34:55 -05:00
James Seibel d36f97d765 Improve javadocs in LodBuilderConfig 2021-07-05 12:00:37 -05:00
James Seibel ee5dc0a6b3 Improve plant color generation 2021-07-05 12:00:25 -05:00
James Seibel 05ed61b0a1 Add a config to enable/disable unstable Feature generation
Related to issue #35
2021-07-05 12:00:03 -05:00
James Seibel 2ef6c59dfc Add an extra comment to Issue #35 and remove a unused log 2021-07-05 09:05:34 -05:00
James Seibel fd90f761a1 Add another library comment for issue #35 2021-07-05 08:59:39 -05:00
James Seibel 7ec7d6bdc5 Add a TODO/Issue and fix capitalization 2021-07-05 00:32:51 -05:00
James Seibel f9ed9a472c Disable Jungle Feature generation, since it contains a world gen bug
Related to #35
2021-07-04 08:31:29 -05:00
James Seibel b542738097 Fix a generation bug created when updating to 1.16.5 2021-07-04 00:09:04 -05:00
James Seibel bceefb5717 Rename LodBuilder to LodChunkBuilder 2021-07-03 21:16:56 -05:00
James Seibel f977687ad6 Add the ability for LOD fog rendering to differ from Minecraft's 2021-07-03 21:10:34 -05:00
James Seibel 31ef1d4959 Update to 1.16.5 forge 36.1.0 2021-07-03 10:25:21 -05:00
James Seibel 86d07eb62b Close #30 and 31 (LODs rendering on top of the player and distance based rendering) 2021-07-01 21:21:34 -05:00
James Seibel 39c7ae8d17 add isChunkPosInLoadedArea to LodUtil 2021-07-01 21:10:07 -05:00
James Seibel 5c369fd515 Add LodDataPoint toString for debugging 2021-07-01 21:09:40 -05:00
James Seibel 2b0926a097 Update the readme 2021-06-28 20:26:40 -05:00
James Seibel 9b6b67b178 Remove some unneeded forge text files 2021-06-28 20:26:28 -05:00
James Seibel bb07e3db9a Close #28 (Optimize Distance Lod Generation) 2021-06-27 22:00:43 -05:00
James Seibel 46e65704d7 Potentially fix #34 (Replace "\" in file paths with File.separatorChar to allow for use on Linux)
I can't currently test if this fixed the problem since I don't have a Linux install to test on.
2021-06-27 13:12:38 -05:00
James Seibel d5ec17551e Add grass and tree generation to biome only generation 2021-06-23 21:32:05 -05:00
James Seibel 8390cfc6fb Fix block positions not wrapping around correctly 2021-06-23 19:31:41 -05:00
James Seibel 2711fbf01a Partially fix 0 area bugs 2021-06-22 22:58:51 -05:00
James Seibel e4f64df660 Add snow to biome only Lod generation 2021-06-22 22:36:47 -05:00
James Seibel a6c9d0df98 Improve the Biome only generation mode 2021-06-21 22:16:46 -05:00
James Seibel 136dd5b47a Add a heightmap generation option 2021-06-21 22:16:23 -05:00
James Seibel 277def2acc Rename LodConfigHandler to LodConfig 2021-06-19 20:47:04 -05:00
James Seibel e941b553e7 rename LodRender to LodRenderer 2021-06-19 20:45:30 -05:00
James Seibel 92e651c779 Rename LodUtils to LodUtil 2021-06-19 20:42:48 -05:00
James Seibel 2b82eb88c8 Partially address #28 (improve LOD chunk generation outside normal view distance) 2021-06-19 20:36:13 -05:00
James Seibel 13fecf9d1d Add constructors and a don't save flag to LodChunk 2021-06-19 20:24:46 -05:00
James Seibel cdd1529a13 Add a int constructor to LodDataPoint 2021-06-19 20:22:31 -05:00
James Seibel 7915444713 Move intoToColor and colorToInto to LotUtils 2021-06-19 20:21:31 -05:00
James Seibel 2135ae769d Close #29 (slightly improve lighting) 2021-06-17 21:33:14 -05:00
James Seibel b98cfb9666 Fix LodChunk not having the detail level set 2021-06-17 20:58:20 -05:00
James Seibel a28da86d44 Rename and move LodConfig to LodConfigHandler 2021-06-17 20:51:13 -05:00
James Seibel 799b0e2481 Fix #4 (Prevent excessive render distances from causing out of memory errors) 2021-06-17 20:27:42 -05:00
James Seibel e380c7c069 Label a comment with its issue number 2021-06-14 21:50:33 -05:00
James Seibel 5a3b8f5baa Add vanilla rendered chunk detection to improve edge rendering
In other words, we now render LODs where ever Minecraft isn't rendering chunks itself.
2021-06-14 21:44:26 -05:00
James Seibel a1c547b72e Minor LodChunkGenWorker optimization 2021-06-13 22:56:04 -05:00
James Seibel bb5613e3fe Slight optimization to LodChunkGenWorker 2021-06-13 20:56:53 -05:00
James Seibel a29060bf4f Fix LodChunk string constructor 2021-06-13 20:18:20 -05:00
James Seibel 79ee23e0b0 Add variably detailed LodChunks 2021-06-13 19:59:25 -05:00
James Seibel d3231a480d Delete a duplicate file, how did this get here? 2021-06-13 19:37:17 -05:00
James Seibel 3694dcba4c Add variable detail LODs 2021-06-12 18:19:45 -05:00
James Seibel bf6db89a4b Add folder for compiled jars
I did this since Gitlab doesn't have an easy way to add release binaries, and I don't want to move everything over to Github right now.
2021-06-01 20:11:41 -05:00
James Seibel 38c644739a Fix the mixin using the old package name 2021-06-01 20:10:27 -05:00
James Seibel a0fe977976 comment out debug lines 2021-05-31 19:31:36 -05:00
James Seibel 739d6d5856 update the version to a1.2 2021-05-31 19:31:12 -05:00
James Seibel 84125735a1 change the packages from com.backsun.lod... to com.seibel.lod... 2021-05-31 19:30:48 -05:00
James Seibel 884f6811a1 Improve how dimension width is determined and add a TODO 2021-05-31 16:43:42 -05:00
James Seibel 402abb0963 add some depreciation supressions and improve the fog slightly 2021-05-31 16:43:21 -05:00
James Seibel 2f7f489e14 Add the ability to count the number of loaded LodChunks 2021-05-31 16:41:27 -05:00
James Seibel 4de5c287fc Close #20 (allow the LOD distance to be changed in the config) 2021-05-31 15:29:26 -05:00
James Seibel ccb58024a1 rename LodRenderer to LodRender 2021-05-31 15:21:58 -05:00
James Seibel 0381d56511 rename method to getFileNameAndPathForRegion 2021-05-31 14:36:11 -05:00
James Seibel 7bbd3fd815 Improve logging 2021-05-31 14:35:34 -05:00
James Seibel 4e243252e9 Simplify the debug render setup 2021-05-31 14:15:24 -05:00
James Seibel b488d21a14 Replace System.out with a Logger 2021-05-31 14:11:58 -05:00
James Seibel 3e8dbf7ac7 Add file versioning logic when writing to a file 2021-05-31 14:11:34 -05:00
James Seibel 695b73f9d3 Fix the CubicLodTemplate taking the average of empty chunks 2021-05-31 11:54:46 -05:00
James Seibel 6e37bce38a Improve how empty LodChunks are determined 2021-05-31 11:54:27 -05:00
James Seibel 06232f65b2 Fix empty chunks not generating
This is most useful in the end, where there are many empty chunks
2021-05-31 11:53:59 -05:00
James Seibel bebe4b7436 Organize the chunk generation modes comment by time 2021-05-31 11:09:38 -05:00
James Seibel 7d9f04d54c Improve wording in the config file 2021-05-31 11:09:03 -05:00
James Seibel 70336acc75 Add a double detail level to the CublicLodTemplate 2021-05-31 10:50:36 -05:00
James Seibel e1ef08a783 Close #24 (only add visible side colors) 2021-05-30 14:06:54 -05:00
James Seibel 648a70097a Add a comment about the LodChunk resolution change 2021-05-30 13:52:38 -05:00
James Seibel 280892f5f4 replace == with .equals 2021-05-29 16:59:10 -05:00
James Seibel 3c57b124d5 Improve profile tracking 2021-05-29 16:59:02 -05:00
James Seibel 04d6bae479 Add several TODOs 2021-05-29 13:47:18 -05:00
James Seibel 98096a5db7 part of 7e4f3a3 2021-05-29 13:23:49 -05:00
James Seibel 7e4f3a3185 Fix chunks not generating out of view range when changing worlds 2021-05-29 13:22:48 -05:00
James Seibel 9fe6be10f6 replace LodGeometryQuality with LodDetail 2021-05-29 13:02:05 -05:00
James Seibel 76b356d38e Add version handling and improve error handling 2021-05-29 13:01:47 -05:00
James Seibel 811e24ee5e Improve and simplify LodChunk 2021-05-29 13:00:43 -05:00
James Seibel 5002db15d6 Remove LodLocation since LodCorner exists 2021-05-29 12:45:46 -05:00
James Seibel a3357c1193 Replace LodGeometryQuality with LodDetail 2021-05-29 12:45:19 -05:00
James Seibel af36224a57 Remove unneeded object creation 2021-05-29 12:42:39 -05:00
James Seibel 630ff361d3 in ColorDirection replace N,S,E,W with NORTH, SOUTH, EAST, WEST 2021-05-19 12:51:23 -05:00
James Seibel 34c6f29a18 Add file versioning to the file handler 2021-05-19 12:34:02 -05:00
James Seibel 909718e491 Remove the readyToReadAndWrite method 2021-05-19 12:10:36 -05:00
James Seibel 8a3d199247 Implement Single and Quad geometry qualities for CubicLod Rendering 2021-05-19 08:47:22 -05:00
James Seibel fa13c981b7 Add a missing empty line in the config file 2021-05-18 21:56:33 -05:00
James Seibel 815b00c3ca Add multiple color modes and set up for geometry quality 2021-05-08 16:41:55 -05:00
James Seibel ae9144a6c4 Add the basis for different LOD drawing modes (aka templates) 2021-05-08 14:58:41 -05:00
James Seibel 359fde3b6b Remove an unneeded line 2021-05-08 14:37:05 -05:00
James Seibel 92fa904cc6 Setup the config and enum for multiple LOD drawing modes 2021-05-05 16:55:01 -05:00
James Seibel 2583ae34d4 Remove DrawMode since it wasn't supported anyway 2021-05-05 16:53:39 -05:00
James Seibel e36b3394f4 Remove an unneeded comment 2021-05-05 16:35:23 -05:00
James Seibel 7af38df92c Remove some outdated TODOs 2021-04-18 21:23:30 -05:00
James Seibel 51add24110 Merge branch '1.16.4' of gitlab.com:jeseibel/minecraft-lod-mod into 1.16.4 2021-04-03 12:11:32 -05:00
James Seibel 9e5aac3bf7 Close #18 (allow client use on servers) 2021-04-03 12:11:20 -05:00
James Seibel 5738a5b7cd Close #8 (allow client use on servers) 2021-04-03 12:05:16 -05:00
James Seibel e1216966a3 Add error checking 2021-04-02 22:56:13 -05:00
James Seibel 222c0de7f1 Only try generating chunks on a local server 2021-04-02 22:50:33 -05:00
James Seibel b4f1fb6d28 Improve a TODO
related to issue #13
2021-04-01 21:05:45 -05:00
James Seibel a32082ad20 Remove the need for a World object in LodBuilder
The world hasn't been needed for a while, I just never got around to removing it.
2021-04-01 13:57:47 -05:00
James Seibel eeb5fb6c3c Improve the generateLodChunkAsync method 2021-04-01 13:56:52 -05:00
James Seibel cb50f24c86 Improve how a unloaded LodWorld is handled 2021-04-01 13:13:14 -05:00
James Seibel 5ca5764c0e Improve how LodWorld, LodBuilder, and LodRenderer objects are handled to prevent multiple references
There was a problem where upon changing worlds the LodWorld wouldn't reflect the commit along with 6c515350 and fccd1db0 should fix that problem.
2021-03-31 14:22:35 -05:00
James Seibel fccd1db045 Add the ability to select and deselect worlds 2021-03-31 14:18:11 -05:00
James Seibel 6c515350bc Clean up references to LodDimension objects 2021-03-31 14:15:49 -05:00
James Seibel 50aee9dfb2 Move getCurrentWorldID into the LodUtils class 2021-03-31 10:56:50 -05:00
James Seibel 0649504770 Fix #16 (stop placeholder chunks from being saved to disk) 2021-03-30 14:55:12 -05:00
James Seibel 29068f9550 Fix the projection matrix not being reset after rendering LODs
Regressive fix for 18c08ccd.
2021-03-30 08:07:15 -05:00
James Seibel 18c08ccd88 Fix #8 and #9 (inaccurate lighting and rendering behind transparent objects)
Instead of using a stencil and rendering in the forgelastdraw event, we now you a mixin to render right before the sold block layer.

The main purpose of this was to allow for LODs to be drawn behind transparent objects; however as a happy accident it seems to have also improved the lighting, I'm not sure if it is perfect, but it is much better.
2021-03-28 22:39:58 -05:00
James Seibel b71d6a5e3f Closes issue #10 (Regen LODs on block change) 2021-03-27 21:49:23 -05:00
James Seibel 8f619f3fa1 Closes issue #12 (improve world change detection) 2021-03-27 21:28:03 -05:00
James Seibel eab16ff20a Move all buffer building into the LodBufferBuilder and improve chunk generation logic
Chunks generation requests should no longer stack exponentially (before whenever one chunk was generated the LODs would be regenerated, causing more chunks to generate so if more than one chunk was ever generated at a time they would stack).
2021-03-25 23:04:48 -05:00
James Seibel d913ed9621 Fix issue #11 (generate chunks closer to the player first) 2021-03-25 16:31:11 -05:00
James Seibel a649cf179f comment out a debug statement 2021-03-25 16:04:02 -05:00
James Seibel cf5de39250 Rename SingleLodChunkGenWorker to LodChunkGenWorker 2021-03-25 16:03:44 -05:00
James Seibel bab3cd9656 Move SingleLodChunkGenWorker into the builders package 2021-03-24 21:59:32 -05:00
James Seibel cafd4f0c47 Move all LodChunk generation code to the LodBuilder object 2021-03-24 21:59:06 -05:00
James Seibel e20833225f Slightly improve LOD generation speed and add code related to heightmap
The slight speed increase is done by only generating the chunk to the "FEATURES" status instead of "FULL".

The code related to the heightmap is currently unused, since the LOD color generation requires blocks. Although it may have some use in the future so I will keep it in, albeit commented out.
2021-03-24 19:09:30 -05:00
James Seibel 14a06c220b Move the enums into their own package 2021-03-24 17:50:17 -05:00
James Seibel e5a5ba327e Add a TODO 2021-03-19 20:36:11 -05:00
James Seibel fedc8f7b66 Add LODs generating outside the player's view distance
It isn't fast enough to keep up with flying creating or spectator players; but it does function without causing heavy server or client lag.
LODs are generated in lines starting far away from the player and moving towards them, in the future they should be generated close to the player first.

Also add a RegionPos object and a way to convert from ChunkPos objects in LodUtils
2021-03-19 20:25:23 -05:00
James Seibel 7a3497d44c Update and improve Access Transformer
Remove old unneeded transformations and add useful comments.
2021-03-04 21:55:26 -06:00
James Seibel 873034f7e4 Merge branch '1.16.4' of gitlab.com:jeseibel/minecraft-lod-mod into 1.16.4 2021-03-04 21:30:33 -06:00
James Seibel a151054b48 Fix issue #7 (screen space distortions not be applied) 2021-03-04 21:30:19 -06:00
James Seibel 14c69971f6 Fix a bug with holding grass blocks
For some reason hold grass blocks (and presumably other biome colored blocks)  would look gray if GL_COLOR_MATERIAL is disabled
2021-03-04 21:16:41 -06:00
James Seibel edc3858699 Add the youtube demo video 2021-03-03 16:16:47 +00:00
James Seibel bdaf33b80b Add a simplified description to the readme 2021-03-03 03:48:13 +00:00
76 changed files with 6818 additions and 5333 deletions
+1
View File
@@ -22,4 +22,5 @@ eclipse
run
# Files from Forge MDK
logs
forge*changelog.txt
-65
View File
@@ -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.
+555 -454
View File
File diff suppressed because it is too large Load Diff
+11 -6
View File
@@ -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
Binary file not shown.
Binary file not shown.
Binary file not shown.
+45 -11
View File
@@ -3,7 +3,9 @@ buildscript {
maven { url = 'https://files.minecraftforge.net/maven' }
jcenter()
mavenCentral()
maven { url='https://dist.creeper.host/Sponge/maven' }
maven { url = 'https://repo.spongepowered.org/maven/' }
// potential replacement in case of problems:
// https://dist.creeper.host/Sponge/maven
}
dependencies {
classpath group: 'net.minecraftforge.gradle', name: 'ForgeGradle', version: '3.+', changing: true
@@ -16,9 +18,9 @@ apply plugin: 'org.spongepowered.mixin'
apply plugin: 'eclipse'
apply plugin: 'maven-publish'
version = 'a1'
version = 'a1.3.1'
group = 'com.backsun.lod'
archivesBaseName = 'lod_1.16.4'
archivesBaseName = 'lod_1.16.5'
sourceCompatibility = targetCompatibility = compileJava.sourceCompatibility = compileJava.targetCompatibility = '1.8' // Need this here so eclipse task generates correctly.
@@ -29,7 +31,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 +45,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 +68,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 +90,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.
@@ -100,8 +120,22 @@ 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 were added to hopefully allow for cloning
// configuredFeatures to allow for safe
// multi threaded feature generation. Sadly I couldn't find
// a way to duplicate lambda functions (which features use)
// so for now I'm not sure what to do.
//implementation 'io.github.kostaskougios:cloning:1.10.3'
//
//implementation ('com.esotericsoftware:kryo:5.1.1') {
// exclude group: "org.objenesis"
//}
//implementation 'org.objenesis:objenesis:3.2'
// You may put jars on which you depend on in ./libs or you may define them like so..
// compile "some.group:artifact:version:classifier"
// compile "some.group:artifact:version"
@@ -126,10 +160,10 @@ dependencies {
jar {
manifest {
attributes([
"Specification-Title": "Levels of Detail",
"Specification-Title": "Level of Detail",
"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",
])
-1664
View File
File diff suppressed because it is too large Load Diff
Binary file not shown.
View File
View File
@@ -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,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,11 +1,31 @@
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 org.lwjgl.opengl.GL;
import com.seibel.lod.handlers.LodConfig;
import com.seibel.lod.proxy.ClientProxy;
import com.seibel.lod.render.LodRenderer;
import net.minecraft.client.Minecraft;
import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.eventbus.api.SubscribeEvent;
import net.minecraftforge.fml.DeferredWorkQueue;
import net.minecraftforge.fml.ModLoadingContext;
import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.config.ModConfig;
@@ -21,7 +41,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
@@ -31,10 +51,26 @@ public class LodMain
public static ClientProxy client_proxy;
@SuppressWarnings("deprecation")
private void init(final FMLCommonSetupEvent event)
{
Minecraft.getInstance().getFramebuffer().enableStencil();
ModLoadingContext.get().registerConfig(ModConfig.Type.CLIENT, LodConfig.clientSpec);
Thread setFancyFog = new Thread(() ->
{
LodRenderer.fancyFogAvailable = GL.getCapabilities().GL_NV_fog_distance;
if (!LodRenderer.fancyFogAvailable)
{
ClientProxy.LOGGER.info("This GPU does not support GL_NV_fog_distance. This means that fancy fog options will not be available.");
}
});
// This will be run on the main thread when it is able.
// If it wasn't run on the main thread GL.getCapabilities()
// would fail.
DeferredWorkQueue.runLater(setFancyFog);
}
+32
View File
@@ -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 05-31-2021
*/
public final class ModInfo
{
public static final String MODID = "lod";
public static final String MODNAME = "Level of Detail";
public static final String MODAPI = "LodAPI";
public static final String VERSION = "a1.3.1";
}
@@ -0,0 +1,437 @@
/*
* 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.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicInteger;
import org.lwjgl.opengl.GL11;
import com.seibel.lod.builders.worldGeneration.LodChunkGenWorker;
import com.seibel.lod.handlers.LodConfig;
import com.seibel.lod.objects.LodChunk;
import com.seibel.lod.objects.LodDimension;
import com.seibel.lod.objects.NearFarBuffer;
import com.seibel.lod.render.LodRenderer;
import com.seibel.lod.util.LodUtil;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.BufferBuilder;
import net.minecraft.util.math.ChunkPos;
import net.minecraft.world.biome.BiomeContainer;
import net.minecraft.world.chunk.ChunkStatus;
import net.minecraft.world.server.ServerWorld;
import net.minecraftforge.common.WorldWorkerManager;
/**
* This object is used to create NearFarBuffer objects.
*
* @author James Seibel
* @version 06-19-2021
*/
public class LodBufferBuilder
{
private Minecraft mc;
/** This holds the thread used to generate new LODs off the main thread. */
private ExecutorService genThread = Executors.newSingleThreadExecutor();
private LodChunkBuilder lodChunkBuilder;
/** The buffers that are used to create LODs using near fog */
public volatile BufferBuilder buildableNearBuffer;
/** The buffers that are used to create LODs using far fog */
public volatile BufferBuilder buildableFarBuffer;
/** if this is true the LOD buffers are currently being
* regenerated. */
public volatile boolean generatingBuffers = 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;
/** This keeps track of how many chunk generation requests are on going.
* This is to prevent chunks from being generated for a long time in an area
* the player is no longer in. */
public volatile AtomicInteger numberOfChunksWaitingToGenerate = new AtomicInteger(0);
/** 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 = LodConfig.CLIENT.numberOfWorldGenerationThreads.get() * 8;
public LodBufferBuilder(LodChunkBuilder newLodBuilder)
{
mc = Minecraft.getInstance();
lodChunkBuilder = newLodBuilder;
}
private BiomeContainer biomeContainer = null;
private LodDimension previousDimension = null;
/**
* Create a thread to asynchronously generate LOD buffers
* centered around the given camera X and Z.
* <br>
* This method will write to the drawableNearBuffers and drawableFarBuffers.
* <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,
double playerX, double playerZ, int numbChunksWide)
{
// only allow one generation process to happen at a time
if (generatingBuffers)
return;
if (buildableNearBuffer == null || buildableFarBuffer == null)
throw new IllegalStateException("generateLodBuffersAsync was called before the buildableNearBuffer and buildableFarBuffer were created.");
if (previousDimension != lodDim)
{
biomeContainer = LodUtil.getServerWorldFromDimension(lodDim.dimension).getChunk(0, 0, ChunkStatus.EMPTY).getBiomes();
previousDimension = lodDim;
}
generatingBuffers = true;
// this seemingly useless math is required,
// just using (int) playerX/Z 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 thread = new Thread(()->
{
// index of the chunk currently being added to the
// generation list
int chunkGenIndex = 0;
ChunkPos[] chunksToGen = new ChunkPos[maxChunkGenRequests];
// if we don't have a full number of chunks to generate in chunksToGen
// we can top it off from the reserve
ChunkPos[] chunksToGenReserve = new ChunkPos[maxChunkGenRequests];
int minChunkDist = Integer.MAX_VALUE;
ChunkPos playerChunkPos = new ChunkPos((int)playerX / LodChunk.WIDTH, (int)playerZ / LodChunk.WIDTH);
// generate our new buildable buffers
buildableNearBuffer.begin(GL11.GL_QUADS, LodRenderer.LOD_VERTEX_FORMAT);
buildableFarBuffer.begin(GL11.GL_QUADS, LodRenderer.LOD_VERTEX_FORMAT);
// x axis
for (int i = 0; i < numbChunksWide; i++)
{
// z axis
for (int j = 0; j < numbChunksWide; j++)
{
int chunkX = i + (startX / LodChunk.WIDTH);
int chunkZ = j + (startZ / LodChunk.WIDTH);
// skip any chunks that Minecraft is going to render
if(isCoordInCenterArea(i, j, (numbChunksWide / 2))
&& renderer.vanillaRenderedChunks.contains(new ChunkPos(chunkX, chunkZ)))
{
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;
LodChunk lod = lodDim.getLodFromCoordinates(chunkX, chunkZ);
if (lod == null || lod.isLodEmpty())
{
// generate a new chunk if no chunk currently exists
// and we aren't waiting on any other chunks to generate
if (lod == null && numberOfChunksWaitingToGenerate.get() < maxChunkGenRequests)
{
ChunkPos pos = new ChunkPos(chunkX, chunkZ);
// can be used for debugging
// if (chunksToGen == null)
// {
// chunkGenIndex = 0;
// chunksToGen = new ChunkPos[maxChunkGenRequests];
// }
//
// if (chunkGenIndex < maxChunkGenRequests)
// {
// chunksToGen[chunkGenIndex] = pos;
// chunkGenIndex++;
// }
// determine if this position is closer to the player
// than the previous
int newDistance = playerChunkPos.getChessboardDistance(pos);
// issue #40
// TODO optimize this code,
// using the purely optimized code above we can achieve close to
// 100% CPU utilization, this code generally achieves 40 - 50 %
// I'm sure there is a better data structure for this.
if (newDistance < minChunkDist)
{
// this chunk is closer, clear any previous
// positions and update the new minimum distance
minChunkDist = newDistance;
// move all the old chunks into the reserve
ChunkPos[] newReserve = new ChunkPos[maxChunkGenRequests];
int oldToGenIndex = 0;
int oldReserveIndex = 0;
for(int tmpIndex = 0; tmpIndex < newReserve.length; tmpIndex++)
{
// we don't check if the boundaries are good since
// the tmp array will always be the same length
// as chunksToGen and chunksToGenReserve
if (chunksToGen[oldToGenIndex] != null)
{
// add all the closest chunks...
newReserve[tmpIndex] = chunksToGen[oldToGenIndex];
oldToGenIndex++;
}
else if (chunksToGenReserve[oldReserveIndex] != null)
{
// ...then add all the previous reserve chunks
// (which are farther away)
newReserve[tmpIndex] = chunksToGenReserve[oldToGenIndex];
oldReserveIndex++;
}
else
{
// we have moved all the items from
// the old chunksToGen and reserve
break;
}
}
chunksToGenReserve = newReserve;
chunkGenIndex = 0;
chunksToGen = new ChunkPos[maxChunkGenRequests];
chunksToGen[chunkGenIndex] = pos;
chunkGenIndex++;
}
else if (newDistance <= minChunkDist)
{
// this chunk position is as close or closers than the
// minimum distance
if(chunkGenIndex < maxChunkGenRequests)
{
// we are still under the number of chunks to generate
// add this position to the list
chunksToGen[chunkGenIndex] = pos;
chunkGenIndex++;
}
}
}
// don't render this null chunk
continue;
}
BufferBuilder currentBuffer = null;
if (isCoordinateInNearFogArea(i, j, numbChunksWide / 2))
currentBuffer = buildableNearBuffer;
else
currentBuffer = buildableFarBuffer;
// get the desired LodTemplate and
// add this LOD to the buffer
LodConfig.CLIENT.lodTemplate.get().
template.addLodToBuffer(currentBuffer, lodDim, lod,
xOffset, yOffset, zOffset, renderer.debugging);
}
}
// TODO add a way for a server side mod to generate chunks requested here
if(mc.hasSingleplayerServer())
{
ServerWorld serverWorld = LodUtil.getServerWorldFromDimension(lodDim.dimension);
// make sure we have as many chunks to generate as we are allowed
if (chunkGenIndex < maxChunkGenRequests)
{
for(int i = chunkGenIndex, j = 0; i < maxChunkGenRequests; i++, j++)
{
chunksToGen[i] = chunksToGenReserve[j];
}
}
// start chunk generation
for(ChunkPos chunkPos : chunksToGen)
{
if(chunkPos == null)
break;
// make sure we don't generate this chunk again
lodDim.addLod(new LodChunk(chunkPos));
numberOfChunksWaitingToGenerate.addAndGet(1);
LodChunkGenWorker genWorker = new LodChunkGenWorker(chunkPos, renderer, lodChunkBuilder, this, lodDim, serverWorld, biomeContainer);
WorldWorkerManager.addWorker(genWorker);
}
}
// finish the buffer building
buildableNearBuffer.end();
buildableFarBuffer.end();
// mark that the buildable buffers as ready to swap
generatingBuffers = false;
switchBuffers = true;
});
genThread.execute(thread);
return;
}
//====================//
// generation helpers //
//====================//
/**
* 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.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).
*/
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);
}
//===============================//
// BufferBuilder related methods //
//===============================//
/**
* Called from the LodRenderer to create the
* BufferBuilders at the right size.
*
* @param bufferMaxCapacity
*/
public void setupBuffers(int bufferMaxCapacity)
{
buildableNearBuffer = new BufferBuilder(bufferMaxCapacity);
buildableFarBuffer = new BufferBuilder(bufferMaxCapacity);
}
/**
* Swap the drawable and buildable buffers and return
* the old drawable buffers.
* @param drawableNearBuffer
* @param drawableFarBuffer
*/
public NearFarBuffer swapBuffers(BufferBuilder drawableNearBuffer, BufferBuilder drawableFarBuffer)
{
// swap the BufferBuilders
BufferBuilder tmp = buildableNearBuffer;
buildableNearBuffer = drawableNearBuffer;
drawableNearBuffer = tmp;
tmp = buildableFarBuffer;
buildableFarBuffer = drawableFarBuffer;
drawableFarBuffer = tmp;
// the buffers have been swapped
switchBuffers = false;
return new NearFarBuffer(drawableNearBuffer, drawableFarBuffer);
}
/**
* 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 switchBuffers;
}
}
@@ -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.builders;
/**
* 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 6-27-2021
*/
public class LodBuilderConfig
{
/** default false */
public boolean useHeightmap;
/** default false */
public boolean useBiomeColors;
/** default true */
public boolean useSolidBlocksInColorGen;
/** default settings for a normal chunk
* useHeightmap = false
* useBiomeColors = false
* useSolidBlocksInColorGen = true
*/
public LodBuilderConfig()
{
useHeightmap = false;
useBiomeColors = false;
useSolidBlocksInColorGen = true;
}
/**
* @param newUseHeightmap default = false
* @param newUseBiomeColors default = false
* @param newUseSolidBlocksInBiomeColor default = true
*/
public LodBuilderConfig(boolean newUseHeightmap, boolean newUseBiomeColors, boolean newUseSolidBlocksInBiomeColor)
{
useHeightmap = newUseHeightmap;
useBiomeColors = newUseBiomeColors;
useSolidBlocksInColorGen = newUseSolidBlocksInBiomeColor;
}
}
@@ -0,0 +1,527 @@
/*
* 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.enums.LodDetail;
import com.seibel.lod.handlers.LodConfig;
import com.seibel.lod.objects.LodChunk;
import com.seibel.lod.objects.LodDataPoint;
import com.seibel.lod.objects.LodDimension;
import com.seibel.lod.objects.LodWorld;
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.FlowingFluidBlock;
import net.minecraft.block.GrassBlock;
import net.minecraft.block.IGrowable;
import net.minecraft.block.LeavesBlock;
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, Region, and Chunk objects)
*
* @author James Seibel
* @version 6-27-2021
*/
public class LodChunkBuilder
{
private ExecutorService lodGenThreadPool = Executors.newSingleThreadExecutor();
/** Default size of any LOD regions we use */
public int regionWidth = 5;
public static final int CHUNK_DATA_WIDTH = LodChunk.WIDTH;
public static final int CHUNK_SECTION_HEIGHT = LodChunk.WIDTH;
public LodChunkBuilder()
{
}
public void generateLodChunkAsync(IChunk chunk, LodWorld lodWorld, IWorld world)
{
generateLodChunkAsync(chunk, new LodBuilderConfig(), lodWorld, world);
}
public void generateLodChunkAsync(IChunk chunk, LodBuilderConfig config, LodWorld lodWorld, IWorld world)
{
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();
LodChunk lod = generateLodFromChunk(chunk, config);
LodDimension lodDim;
if (lodWorld.getLodDimension(dim) == null)
{
lodDim = new LodDimension(dim, lodWorld, 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;
}
/**
* Creates a LodChunk for a chunk in the given world.
*
* @throws IllegalArgumentException
* thrown if either the chunk or world is null.
*/
public LodChunk generateLodFromChunk(IChunk chunk) throws IllegalArgumentException
{
return generateLodFromChunk(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 LodChunk generateLodFromChunk(IChunk chunk, LodBuilderConfig config) throws IllegalArgumentException
{
if(chunk == null)
throw new IllegalArgumentException("generateLodFromChunk given a null chunk");
LodDetail detail = LodConfig.CLIENT.lodDetail.get();
LodDataPoint[][] dataPoints = new LodDataPoint[detail.dataPointLengthCount][detail.dataPointLengthCount];
for(int i = 0; i < detail.dataPointLengthCount * detail.dataPointLengthCount; i++)
{
int startX = detail.startX[i];
int startZ = detail.startZ[i];
int endX = detail.endX[i];
int endZ = detail.endZ[i];
Color color;
color = generateLodColorForArea(chunk, config, startX, startZ, endX, endZ);
short height;
short depth;
if (!config.useHeightmap)
{
height = determineHeightPointForArea(chunk.getSections(), startX, startZ, endX, endZ);
depth = determineBottomPointForArea(chunk.getSections(), startX, startZ, endX, endZ);
}
else
{
height = determineHeightPoint(chunk.getOrCreateHeightmapUnprimed(LodChunk.DEFAULT_HEIGHTMAP), startX, startZ, endX, endZ);
depth = 0;
}
int x = i / detail.dataPointLengthCount;
int z = i % detail.dataPointLengthCount;
dataPoints[x][z] = new LodDataPoint(height, depth, color);
}
return new LodChunk(chunk.getPos(), dataPoints, detail);
}
//=====================//
// constructor helpers //
//=====================//
/**
* Find the lowest valid point from the bottom.
* @param chunkSections
* @param startX
* @param startZ
* @param endX
* @param endZ
*/
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 -1;
}
/**
* 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
* @param chunkSections
* @param startX
* @param startZ
* @param endX
* @param endZ
*/
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 + (section * CHUNK_SECTION_HEIGHT));
}
}
}
}
}
}
// we never found a valid LOD point
return -1;
}
/**
* 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 Color 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)
{
Biome biome = chunk.getBiomes().getNoiseBiome(x, y + i * chunkSections.length, z);
if (biome.getBiomeCategory() == Biome.Category.OCEAN ||
biome.getBiomeCategory() == Biome.Category.RIVER)
{
if (config.useSolidBlocksInColorGen &&
blockState != Blocks.WATER.defaultBlockState())
{
// non-water block
colorInt = getColorForBlock(x, z, blockState, biome);
}
else
{
// water block
colorInt = biome.getWaterColor();
}
}
else if (biome.getBiomeCategory() == Biome.Category.EXTREME_HILLS)
{
colorInt = Blocks.STONE.defaultMaterialColor().col;
}
else if (biome.getBiomeCategory() == Biome.Category.ICY)
{
colorInt = LodUtil.colorToInt(Color.WHITE);
}
else if (biome.getBiomeCategory() == Biome.Category.THEEND)
{
colorInt = Blocks.END_STONE.defaultBlockState().materialColor.col;
}
else if (config.useSolidBlocksInColorGen)
{
colorInt = getColorForBlock(x, z, blockState, biome);
}
else
{
colorInt = biome.getGrassColor(x, z);
}
}
else
{
Biome biome = chunk.getBiomes().getNoiseBiome(x, y + i * chunkSections.length, z);
colorInt = getColorForBlock(x,z, blockState, biome);
}
Color c = LodUtil.intToColor(colorInt);
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);
}
/**
* Returns a color int for a given block.
*/
private int getColorForBlock(int x, int z, BlockState blockState, Biome biome)
{
int colorInt = 0;
if (blockState == Blocks.AIR.defaultBlockState())
{
colorInt = biome.getGrassColor(x, z);
}
else if (blockState.getBlock() instanceof LeavesBlock)
{
Color leafColor = LodUtil.intToColor(biome.getFoliageColor()).darker();
colorInt = LodUtil.colorToInt(leafColor);
}
else if (blockState.getBlock() instanceof GrassBlock ||
blockState.getBlock() instanceof AbstractPlantBlock ||
blockState.getBlock() instanceof BushBlock ||
blockState.getBlock() instanceof IGrowable)
{
colorInt = biome.getGrassColor(x, z);
}
else if (blockState.getBlock() instanceof FlowingFluidBlock)
{
colorInt = biome.getWaterColor();
}
else
{
colorInt = blockState.materialColor.col;
}
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)
{
return true;
}
}
return false;
}
}
@@ -0,0 +1,54 @@
/*
* 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.LodDetail;
import com.seibel.lod.objects.LodChunk;
import com.seibel.lod.objects.LodDimension;
import net.minecraft.client.renderer.BufferBuilder;
/**
* This is the abstract class used to create different
* BufferBuilders.
*
* @author James Seibel
* @version 06-16-2021
*/
public abstract class AbstractLodTemplate
{
public abstract void addLodToBuffer(BufferBuilder buffer,
LodDimension lodDim, LodChunk lod,
double xOffset, double yOffset, double zOffset,
boolean 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 getBufferMemoryForSingleLod(LodDetail detail);
}
@@ -0,0 +1,191 @@
/*
* 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 java.awt.Color;
import com.seibel.lod.enums.ColorDirection;
import com.seibel.lod.enums.LodDetail;
import com.seibel.lod.handlers.LodConfig;
import com.seibel.lod.objects.LodChunk;
import com.seibel.lod.objects.LodDimension;
import net.minecraft.client.renderer.BufferBuilder;
import net.minecraft.util.math.AxisAlignedBB;
/**
* Builds LODs as rectangular prisms.
*
* @author James Seibel
* @version 06-16-2021
*/
public class CubicLodTemplate extends AbstractLodTemplate
{
public CubicLodTemplate()
{
}
@Override
public void addLodToBuffer(BufferBuilder buffer,
LodDimension lodDim, LodChunk centerLod,
double xOffset, double yOffset, double zOffset,
boolean debugging)
{
AxisAlignedBB bbox;
// Add this LOD to the BufferBuilder
// using the quality setting set by the config
LodDetail detail = LodConfig.CLIENT.lodDetail.get();
int halfWidth = detail.dataPointWidth / 2;
for(int i = 0; i < detail.dataPointLengthCount * detail.dataPointLengthCount; i++)
{
int startX = detail.startX[i];
int startZ = detail.startZ[i];
int endX = detail.endX[i];
int endZ = detail.endZ[i];
// returns null if the lod is empty at the given location
bbox = generateBoundingBox(
centerLod.getAverageHeightOverArea(startX, startZ, endX, endZ),
centerLod.getAverageDepthOverArea(startX, startZ, endX, endZ),
detail.dataPointWidth,
xOffset - (halfWidth / 2) + detail.startX[i],
yOffset,
zOffset - (halfWidth / 2) + detail.startZ[i]);
if (bbox != null)
{
addBoundingBoxToBuffer(buffer, bbox, centerLod.getAverageColorOverArea(startX, startZ, endX, endZ, debugging));
}
}
}
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, Color c)
{
// top (facing up)
addPosAndColor(buffer, bb.minX, bb.maxY, bb.minZ, c.getRed(), c.getGreen(), c.getBlue(), c.getAlpha());
addPosAndColor(buffer, bb.minX, bb.maxY, bb.maxZ, c.getRed(), c.getGreen(), c.getBlue(), c.getAlpha());
addPosAndColor(buffer, bb.maxX, bb.maxY, bb.maxZ, c.getRed(), c.getGreen(), c.getBlue(), c.getAlpha());
addPosAndColor(buffer, bb.maxX, bb.maxY, bb.minZ, c.getRed(), c.getGreen(), c.getBlue(), c.getAlpha());
// bottom (facing down)
addPosAndColor(buffer, bb.maxX, bb.minY, bb.minZ, c.getRed(), c.getGreen(), c.getBlue(), c.getAlpha());
addPosAndColor(buffer, bb.maxX, bb.minY, bb.maxZ, c.getRed(), c.getGreen(), c.getBlue(), c.getAlpha());
addPosAndColor(buffer, bb.minX, bb.minY, bb.maxZ, c.getRed(), c.getGreen(), c.getBlue(), c.getAlpha());
addPosAndColor(buffer, bb.minX, bb.minY, bb.minZ, c.getRed(), c.getGreen(), c.getBlue(), c.getAlpha());
// south (facing -Z)
addPosAndColor(buffer, bb.maxX, bb.minY, bb.maxZ, c.getRed(), c.getGreen(), c.getBlue(), c.getAlpha());
addPosAndColor(buffer, bb.maxX, bb.maxY, bb.maxZ, c.getRed(), c.getGreen(), c.getBlue(), c.getAlpha());
addPosAndColor(buffer, bb.minX, bb.maxY, bb.maxZ, c.getRed(), c.getGreen(), c.getBlue(), c.getAlpha());
addPosAndColor(buffer, bb.minX, bb.minY, bb.maxZ, c.getRed(), c.getGreen(), c.getBlue(), c.getAlpha());
// north (facing +Z)
addPosAndColor(buffer, bb.minX, bb.minY, bb.minZ, c.getRed(), c.getGreen(), c.getBlue(), c.getAlpha());
addPosAndColor(buffer, bb.minX, bb.maxY, bb.minZ, c.getRed(), c.getGreen(), c.getBlue(), c.getAlpha());
addPosAndColor(buffer, bb.maxX, bb.maxY, bb.minZ, c.getRed(), c.getGreen(), c.getBlue(), c.getAlpha());
addPosAndColor(buffer, bb.maxX, bb.minY, bb.minZ, c.getRed(), c.getGreen(), c.getBlue(), c.getAlpha());
// west (facing -X)
addPosAndColor(buffer, bb.minX, bb.minY, bb.minZ, c.getRed(), c.getGreen(), c.getBlue(), c.getAlpha());
addPosAndColor(buffer, bb.minX, bb.minY, bb.maxZ, c.getRed(), c.getGreen(), c.getBlue(), c.getAlpha());
addPosAndColor(buffer, bb.minX, bb.maxY, bb.maxZ, c.getRed(), c.getGreen(), c.getBlue(), c.getAlpha());
addPosAndColor(buffer, bb.minX, bb.maxY, bb.minZ, c.getRed(), c.getGreen(), c.getBlue(), c.getAlpha());
// east (facing +X)
addPosAndColor(buffer, bb.maxX, bb.maxY, bb.minZ, c.getRed(), c.getGreen(), c.getBlue(), c.getAlpha());
addPosAndColor(buffer, bb.maxX, bb.maxY, bb.maxZ, c.getRed(), c.getGreen(), c.getBlue(), c.getAlpha());
addPosAndColor(buffer, bb.maxX, bb.minY, bb.maxZ, c.getRed(), c.getGreen(), c.getBlue(), c.getAlpha());
addPosAndColor(buffer, bb.maxX, bb.minY, bb.minZ, c.getRed(), c.getGreen(), c.getBlue(), c.getAlpha());
}
@SuppressWarnings("unused")
private void addBoundingBoxToBuffer(BufferBuilder buffer, AxisAlignedBB bb, Color[] c)
{
// top (facing up)
addPosAndColor(buffer, bb.minX, bb.maxY, bb.minZ, c[ColorDirection.TOP.value].getRed(), c[ColorDirection.TOP.value].getGreen(), c[ColorDirection.TOP.value].getBlue(), c[ColorDirection.TOP.value].getAlpha());
addPosAndColor(buffer, bb.minX, bb.maxY, bb.maxZ, c[ColorDirection.TOP.value].getRed(), c[ColorDirection.TOP.value].getGreen(), c[ColorDirection.TOP.value].getBlue(), c[ColorDirection.TOP.value].getAlpha());
addPosAndColor(buffer, bb.maxX, bb.maxY, bb.maxZ, c[ColorDirection.TOP.value].getRed(), c[ColorDirection.TOP.value].getGreen(), c[ColorDirection.TOP.value].getBlue(), c[ColorDirection.TOP.value].getAlpha());
addPosAndColor(buffer, bb.maxX, bb.maxY, bb.minZ, c[ColorDirection.TOP.value].getRed(), c[ColorDirection.TOP.value].getGreen(), c[ColorDirection.TOP.value].getBlue(), c[ColorDirection.TOP.value].getAlpha());
// bottom (facing down)
addPosAndColor(buffer, bb.maxX, bb.minY, bb.minZ, c[ColorDirection.BOTTOM.value].getRed(), c[ColorDirection.BOTTOM.value].getGreen(), c[ColorDirection.BOTTOM.value].getBlue(), c[ColorDirection.BOTTOM.value].getAlpha());
addPosAndColor(buffer, bb.maxX, bb.minY, bb.maxZ, c[ColorDirection.BOTTOM.value].getRed(), c[ColorDirection.BOTTOM.value].getGreen(), c[ColorDirection.BOTTOM.value].getBlue(), c[ColorDirection.BOTTOM.value].getAlpha());
addPosAndColor(buffer, bb.minX, bb.minY, bb.maxZ, c[ColorDirection.BOTTOM.value].getRed(), c[ColorDirection.BOTTOM.value].getGreen(), c[ColorDirection.BOTTOM.value].getBlue(), c[ColorDirection.BOTTOM.value].getAlpha());
addPosAndColor(buffer, bb.minX, bb.minY, bb.minZ, c[ColorDirection.BOTTOM.value].getRed(), c[ColorDirection.BOTTOM.value].getGreen(), c[ColorDirection.BOTTOM.value].getBlue(), c[ColorDirection.BOTTOM.value].getAlpha());
// south (facing -Z)
addPosAndColor(buffer, bb.maxX, bb.minY, bb.maxZ, c[ColorDirection.SOUTH.value].getRed(), c[ColorDirection.SOUTH.value].getGreen(), c[ColorDirection.SOUTH.value].getBlue(), c[ColorDirection.SOUTH.value].getAlpha());
addPosAndColor(buffer, bb.maxX, bb.maxY, bb.maxZ, c[ColorDirection.SOUTH.value].getRed(), c[ColorDirection.SOUTH.value].getGreen(), c[ColorDirection.SOUTH.value].getBlue(), c[ColorDirection.SOUTH.value].getAlpha());
addPosAndColor(buffer, bb.minX, bb.maxY, bb.maxZ, c[ColorDirection.SOUTH.value].getRed(), c[ColorDirection.SOUTH.value].getGreen(), c[ColorDirection.SOUTH.value].getBlue(), c[ColorDirection.SOUTH.value].getAlpha());
addPosAndColor(buffer, bb.minX, bb.minY, bb.maxZ, c[ColorDirection.SOUTH.value].getRed(), c[ColorDirection.SOUTH.value].getGreen(), c[ColorDirection.SOUTH.value].getBlue(), c[ColorDirection.SOUTH.value].getAlpha());
// north (facing +Z)
addPosAndColor(buffer, bb.minX, bb.minY, bb.minZ, c[ColorDirection.NORTH.value].getRed(), c[ColorDirection.NORTH.value].getGreen(), c[ColorDirection.NORTH.value].getBlue(), c[ColorDirection.NORTH.value].getAlpha());
addPosAndColor(buffer, bb.minX, bb.maxY, bb.minZ, c[ColorDirection.NORTH.value].getRed(), c[ColorDirection.NORTH.value].getGreen(), c[ColorDirection.NORTH.value].getBlue(), c[ColorDirection.NORTH.value].getAlpha());
addPosAndColor(buffer, bb.maxX, bb.maxY, bb.minZ, c[ColorDirection.NORTH.value].getRed(), c[ColorDirection.NORTH.value].getGreen(), c[ColorDirection.NORTH.value].getBlue(), c[ColorDirection.NORTH.value].getAlpha());
addPosAndColor(buffer, bb.maxX, bb.minY, bb.minZ, c[ColorDirection.NORTH.value].getRed(), c[ColorDirection.NORTH.value].getGreen(), c[ColorDirection.NORTH.value].getBlue(), c[ColorDirection.NORTH.value].getAlpha());
// west (facing -X)
addPosAndColor(buffer, bb.minX, bb.minY, bb.minZ, c[ColorDirection.WEST.value].getRed(), c[ColorDirection.WEST.value].getGreen(), c[ColorDirection.WEST.value].getBlue(), c[ColorDirection.WEST.value].getAlpha());
addPosAndColor(buffer, bb.minX, bb.minY, bb.maxZ, c[ColorDirection.WEST.value].getRed(), c[ColorDirection.WEST.value].getGreen(), c[ColorDirection.WEST.value].getBlue(), c[ColorDirection.WEST.value].getAlpha());
addPosAndColor(buffer, bb.minX, bb.maxY, bb.maxZ, c[ColorDirection.WEST.value].getRed(), c[ColorDirection.WEST.value].getGreen(), c[ColorDirection.WEST.value].getBlue(), c[ColorDirection.WEST.value].getAlpha());
addPosAndColor(buffer, bb.minX, bb.maxY, bb.minZ, c[ColorDirection.WEST.value].getRed(), c[ColorDirection.WEST.value].getGreen(), c[ColorDirection.WEST.value].getBlue(), c[ColorDirection.WEST.value].getAlpha());
// east (facing +X)
addPosAndColor(buffer, bb.maxX, bb.maxY, bb.minZ, c[ColorDirection.EAST.value].getRed(), c[ColorDirection.EAST.value].getGreen(), c[ColorDirection.EAST.value].getBlue(), c[ColorDirection.EAST.value].getAlpha());
addPosAndColor(buffer, bb.maxX, bb.maxY, bb.maxZ, c[ColorDirection.EAST.value].getRed(), c[ColorDirection.EAST.value].getGreen(), c[ColorDirection.EAST.value].getBlue(), c[ColorDirection.EAST.value].getAlpha());
addPosAndColor(buffer, bb.maxX, bb.minY, bb.maxZ, c[ColorDirection.EAST.value].getRed(), c[ColorDirection.EAST.value].getGreen(), c[ColorDirection.EAST.value].getBlue(), c[ColorDirection.EAST.value].getAlpha());
addPosAndColor(buffer, bb.maxX, bb.minY, bb.minZ, c[ColorDirection.EAST.value].getRed(), c[ColorDirection.EAST.value].getGreen(), c[ColorDirection.EAST.value].getBlue(), c[ColorDirection.EAST.value].getAlpha());
}
@Override
public int getBufferMemoryForSingleLod(LodDetail detail)
{
// (sidesOnACube * pointsInASquare * (positionPoints + colorPoints))) * howManyPointsPerLodChunk
return (6 * 4 * (3 + 4)) * detail.dataPointLengthCount * detail.dataPointLengthCount;
}
}
@@ -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.builders.lodTemplates;
import com.seibel.lod.enums.LodDetail;
import com.seibel.lod.objects.LodChunk;
import com.seibel.lod.objects.LodDimension;
import net.minecraft.client.renderer.BufferBuilder;
/**
* 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,
LodDimension lodDim, LodChunk lod,
double xOffset, double yOffset, double zOffset,
boolean debugging)
{
System.err.println("DynamicLodTemplate not implemented!");
}
@Override
public int getBufferMemoryForSingleLod(LodDetail detail) {
// TODO Auto-generated method stub
return 0;
}
}
@@ -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.LodDetail;
import com.seibel.lod.objects.LodChunk;
import com.seibel.lod.objects.LodDimension;
import net.minecraft.client.renderer.BufferBuilder;
/**
* 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,
LodDimension lodDim, LodChunk lod,
double xOffset, double yOffset, double zOffset,
boolean debugging)
{
System.err.println("DynamicLodTemplate not implemented!");
}
@Override
public int getBufferMemoryForSingleLod(LodDetail detail) {
// TODO Auto-generated method stub
return 0;
}
}
@@ -0,0 +1,625 @@
/*
* 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.LodBufferBuilder;
import com.seibel.lod.builders.LodBuilderConfig;
import com.seibel.lod.builders.LodChunkBuilder;
import com.seibel.lod.enums.DistanceGenerationMode;
import com.seibel.lod.handlers.LodConfig;
import com.seibel.lod.objects.LodChunk;
import com.seibel.lod.objects.LodDimension;
import com.seibel.lod.objects.LodRegion;
import com.seibel.lod.proxy.ClientProxy;
import com.seibel.lod.render.LodRenderer;
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.world.biome.Biome;
import net.minecraft.world.biome.BiomeContainer;
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 7-9-2021
*/
public class LodChunkGenWorker implements IWorker
{
public static ExecutorService genThreads = Executors.newFixedThreadPool(LodConfig.CLIENT.numberOfWorldGenerationThreads.get());
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 LodChunkGenWorker(ChunkPos newPos, LodRenderer newLodRenderer,
LodChunkBuilder newLodBuilder, LodBufferBuilder newLodBufferBuilder,
LodDimension newLodDimension, ServerWorld newServerWorld,
BiomeContainer newBiomeContainer)
{
if (newServerWorld == null)
throw new IllegalArgumentException("LodChunkGenWorker must have a non-null ServerWorld");
thread = new LodChunkGenThread(newPos, newLodRenderer,
newLodBuilder, newLodBufferBuilder,
newLodDimension, newServerWorld);
}
@Override
public boolean doWork()
{
if (!threadStarted)
{
thread.lodBufferBuilder.numberOfChunksWaitingToGenerate.addAndGet(-1);
if (LodConfig.CLIENT.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
genThreads.execute(thread);
}
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 LodChunkBuilder lodChunkBuilder;
public final LodRenderer lodRenderer;
private LodBufferBuilder lodBufferBuilder;
private ChunkPos pos;
public LodChunkGenThread(ChunkPos newPos, LodRenderer newLodRenderer,
LodChunkBuilder newLodBuilder, LodBufferBuilder newLodBufferBuilder,
LodDimension newLodDimension, ServerWorld newServerWorld)
{
pos = newPos;
lodRenderer = newLodRenderer;
lodChunkBuilder = newLodBuilder;
lodBufferBuilder = newLodBufferBuilder;
lodDim = newLodDimension;
serverWorld = newServerWorld;
}
@Override
public void run()
{
// only generate LodChunks if they can
// be added to the current LodDimension
if (lodDim.regionIsInRange(pos.x / LodRegion.SIZE, pos.z / LodRegion.SIZE))
{
// long startTime = System.currentTimeMillis();
switch(LodConfig.CLIENT.distanceGenerationMode.get())
{
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();
// if (lodDim.getLodFromCoordinates(pos.x, pos.z) != null)
// ClientProxy.LOGGER.info(pos.x + " " + pos.z + " Success!");
// else
// ClientProxy.LOGGER.info(pos.x + " " + pos.z);
// long endTime = System.currentTimeMillis();
// System.out.println(endTime - startTime);
}// if in range
}// 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);
ChunkStatus.BIOMES.generate(serverWorld, chunkGen, serverWorld.getStructureManager(), (ServerWorldLightManager) serverWorld.getLightEngine(), null, chunkList);
chunk.setStatus(ChunkStatus.STRUCTURE_REFERENCES);
// generate fake height data for this LOD
int seaLevel = serverWorld.getSeaLevel();
boolean simulateHeight = LodConfig.CLIENT.distanceGenerationMode.get() == 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, LodChunk.DEFAULT_HEIGHTMAP);
for(int x = 0; x < LodChunk.WIDTH && !inTheEnd; x++)
{
for(int z = 0; z < LodChunk.WIDTH && !inTheEnd; z++)
{
if (simulateHeight)
{
// TODO use the biomes around each block to smooth out the transition
// 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, seaLevel, z).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(LodChunk.DEFAULT_HEIGHTMAP, heightmap.getRawData());
LodChunk lod;
if (!inTheEnd)
{
lod = lodChunkBuilder.generateLodFromChunk(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.
lod = new LodChunk(chunk.getPos().x, chunk.getPos().z);
}
lodDim.addLod(lod);
}
/**
* 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);
ChunkStatus.BIOMES.generate(serverWorld, chunkGen, serverWorld.getStructureManager(), (ServerWorldLightManager) serverWorld.getLightEngine(), null, chunkList);
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 proved 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);
LodChunk lod = lodChunkBuilder.generateLodFromChunk(chunk);
lodDim.addLod(lod);
}
/**
* 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);
ChunkStatus.BIOMES.generate(serverWorld, chunkGen, serverWorld.getStructureManager(), (ServerWorldLightManager) serverWorld.getLightEngine(), null, chunkList);
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 < LodChunk.WIDTH; x++)
{
for (int z = 0; z < LodChunk.WIDTH; z++)
{
Biome biome = chunk.getBiomes().getNoiseBiome(x, serverWorld.getSeaLevel(), z);
// 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.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
LodChunk lod = lodChunkBuilder.generateLodFromChunk(chunk);
lodDim.addLod(lod);
}
/**
* 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()
{
lodChunkBuilder.generateLodChunkAsync(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.numberOfWorldGenerationThreads.get());
}
/*
* 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,321 @@
/*
* 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.objects.LodChunk;
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-4-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 % LodChunk.WIDTH;
x = (x < 0) ? x + 16 : x;
z = z % LodChunk.WIDTH;
z = (z < 0) ? z + 16 : z;
return chunk.getOrCreateHeightmapUnprimed(LodChunk.DEFAULT_HEIGHTMAP).getFirstAvailable(x, z);
}
@Override
public Biome getBiome(BlockPos pos)
{
return chunk.getBiomes().getNoiseBiome(pos.getX(), pos.getY(), pos.getZ());
}
@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);
}
/**
*
* 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 DynamicRegistries registryAccess() {
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 long getSeed() {
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,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,68 @@
/*
* 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;
/**
* BIOME_ONLY <br>
* BIOME_ONLY_SIMULATE_HEIGHT <br>
* SURFACE <br>
* FEATURES <br>
* SERVER <br><br>
*
* In order of fastest to slowest.
*
* @author James Seibel
* @version 6-27-2021
*/
public enum DistanceGenerationMode
{
/** 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,
/**
* 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,
/** Generate the world surface,
* this does NOT include caves, trees,
* or structures.
* Multithreaded - Faster (10-20 ms) */
SURFACE,
/** 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,
/** 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;
}
@@ -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,114 @@
/*
* 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.objects.LodChunk;
import com.seibel.lod.objects.LodDataPoint;
/**
* single, double, quad, half, full
*
* @author James Seibel
* @version 06-13-2021
*/
public enum LodDetail
{
/** render 1 LOD for each chunk */
SINGLE(1),
/** render 4 LODs for each chunk */
DOUBLE(2),
/** render 16 LODs for each chunk */
QUAD(4),
/** render 64 LODs for each chunk */
HALF(8),
/** render 256 LODs for each chunk */
FULL(16);
/** How many DataPoints should
* be drawn per side per LodChunk */
public final int dataPointLengthCount;
/** How wide each LOD DataPoint is */
public final int dataPointWidth;
/* 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;
/** This is how many pieces of data should be expected
* when creating a LodChunk with this detail level */
public final int lodChunkStringDelimiterCount;
private LodDetail(int newLengthCount)
{
dataPointLengthCount = newLengthCount;
dataPointWidth = 16 / dataPointLengthCount;
if(newLengthCount == LodChunk.WIDTH)
{
// this is to prevent overflow
newLengthCount = LodChunk.WIDTH - 1;
}
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;
// special case for FULL
if(dataPointWidth != 1)
{
endX[index] = (x*dataPointWidth) + dataPointWidth - 1;
endZ[index] = (z*dataPointWidth) + dataPointWidth - 1;
}
else
{
endX[index] = (x*dataPointWidth) + dataPointWidth;
endZ[index] = (z*dataPointWidth) + dataPointWidth;
}
index++;
}
}
lodChunkStringDelimiterCount = 2 + (dataPointLengthCount * dataPointLengthCount * LodDataPoint.NUMBER_OF_DELIMITERS);
}// constructor
}
@@ -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 06-16-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(LodDetail detail)
{
return template.getBufferMemoryForSingleLod(detail);
}
}
@@ -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,258 @@
/*
* 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.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.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 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 7-5-2021
*/
@Mod.EventBusSubscriber
public class LodConfig
{
public static class Client
{
public ForgeConfigSpec.BooleanValue drawLODs;
public ForgeConfigSpec.EnumValue<FogDistance> fogDistance;
public ForgeConfigSpec.EnumValue<FogDrawOverride> fogDrawOverride;
public ForgeConfigSpec.BooleanValue debugMode;
public ForgeConfigSpec.EnumValue<LodTemplate> lodTemplate;
public ForgeConfigSpec.EnumValue<LodDetail> lodDetail;
public ForgeConfigSpec.EnumValue<DistanceGenerationMode> distanceGenerationMode;
public ForgeConfigSpec.BooleanValue allowUnstableFeatureGeneration;
public ForgeConfigSpec.IntValue numberOfWorldGenerationThreads;
/** this is multiplied by the default view distance
* to determine how far out to generate/render LODs */
public ForgeConfigSpec.IntValue lodChunkRadiusMultiplier;
Client(ForgeConfigSpec.Builder builder)
{
builder.comment(ModInfo.MODNAME + " configuration settings").push("client");
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\" \n"
+ " fog option 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);
debugMode = builder
.comment("\n\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 ;) \n")
.define("drawCheckerBoard", false);
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);
lodDetail = builder
.comment("\n\n"
+ " How detailed should the LODs be? \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")
.defineEnum("lodGeometryQuality", LodDetail.DOUBLE);
lodChunkRadiusMultiplier = builder
.comment("\n\n"
+ " This is multiplied by the default view distance \n"
+ " to determine how far out to generate/render LODs. \n"
+ " A value of 2 means that there is 1 render distance worth \n"
+ " of LODs in each cardinal direction. \n")
.defineInRange("lodChunkRadiusMultiplier", 8, 2, 16);
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.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);
numberOfWorldGenerationThreads = builder
.comment("\n\n"
+ " This is how many threads are used when generating terrain. \n"
+ " If you experience stuttering when generating terrain, decrease \n"
+ " this number. If you want to increase LOD generation speed, \n"
+ " increase the number. \n"
+ " The max is the number of processors on your CPU. \n"
+ "\n"
+ " Requires a restart to take effect. \n"
)
.defineInRange("numberOfWorldGenerationThreads", Runtime.getRuntime().availableProcessors(), 1, Runtime.getRuntime().availableProcessors());
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!");
}
}
@@ -0,0 +1,361 @@
/*
* 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.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import com.seibel.lod.enums.LodDetail;
import com.seibel.lod.objects.LodChunk;
import com.seibel.lod.objects.LodDimension;
import com.seibel.lod.objects.LodRegion;
import com.seibel.lod.proxy.ClientProxy;
/**
* This object handles creating LodRegions
* from files and saving LodRegion objects
* to file.
*
* @author James Seibel
* @version 6-27-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 final String FILE_NAME_PREFIX = "lod";
/** .txt */
private final String FILE_EXTENSION = ".txt";
/** 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 = 2;
/** 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();
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 at the given coordinates.
* (null if the file doesn't exist)
*/
public LodRegion loadRegionFromFile(int regionX, int regionZ)
{
String fileName = getFileNameAndPathForRegion(regionX, regionZ, LodConfig.CLIENT.lodDetail.get());
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();
int fileVersion = -1;
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 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.
br.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.");
return null;
}
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.
br.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.");
return null;
}
}
else
{
// there is no data in this file
br.close();
return null;
}
// this file is a readable version, begin reading the file
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, LodConfig.CLIENT.lodDetail.get());
region.addLod(lod);
}
catch(IllegalArgumentException e)
{
// we were unable to create this chunk
// for whatever reason.
// skip to the next chunk
ClientProxy.LOGGER.warn(e.getMessage());
}
s = br.readLine();
}
br.close();
}
catch (IOException e)
{
// 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 void saveDirtyRegionsToFileAsync()
{
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] && loadedDimension.regions[i][j] != null)
{
saveRegionToDisk(loadedDimension.regions[i][j]);
loadedDimension.isRegionDirty[i][j] = false;
}
}
}
});
/**
* 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 saveRegionToDisk(LodRegion region)
{
// convert chunk coordinates to region
// coordinates
int x = region.x;
int z = region.z;
File f = new File(getFileNameAndPathForRegion(x, z, LodConfig.CLIENT.lodDetail.get()));
try
{
// make sure the file and folder exists
if (!f.exists())
{
// the file doesn't exist,
// create it and the folder if need be
if(!f.getParentFile().exists())
f.getParentFile().mkdirs();
f.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(f));
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;
}
}
FileWriter fw = new FileWriter(f);
// add the version of this file
fw.write(LOD_FILE_VERSION_PREFIX + " " + LOD_SAVE_FILE_VERSION + "\n");
// add each LodChunk to the file
for(LodChunk[] chunkArray : region.getAllLods())
for(LodChunk chunk : chunkArray)
if(chunk != null && !chunk.isPlaceholder())
fw.write(chunk.toData() + "\n");
fw.close();
}
catch(Exception e)
{
ClientProxy.LOGGER.error("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. <br><br>
*
* example: "lod.FULL.0.0.txt"
*/
private String getFileNameAndPathForRegion(int regionX, int regionZ, LodDetail detail)
{
try
{
// saveFolder is something like
// ".\Super Flat\DIM-1\data"
// or
// ".\Super Flat\data"
return dimensionDataSaveFolder.getCanonicalPath() + File.separatorChar +
FILE_NAME_PREFIX + "." + detail.toString() + "." + regionX + "." + regionZ + FILE_EXTENSION;
}
catch(IOException e)
{
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;
@@ -12,14 +29,13 @@ import net.minecraft.client.Minecraft;
* 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();
@@ -34,14 +50,14 @@ public class ReflectionHandler
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;
}
}
@@ -73,7 +89,7 @@ public class ReflectionHandler
try
{
returnNum = (int)ofFogField.get(mc.gameSettings);
returnNum = (int)ofFogField.get(mc.options);
}
catch (IllegalArgumentException | IllegalAccessException e)
{
@@ -82,8 +98,12 @@ public class ReflectionHandler
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:
@@ -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.handlers.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.drawLODs.get() && renderType.equals(RenderType.solid()))
LodMain.client_proxy.renderLods(previousPartialTicks);
}
}
@@ -0,0 +1,503 @@
/*
* 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.awt.Color;
import com.seibel.lod.enums.LodDetail;
import com.seibel.lod.handlers.LodDimensionFileHandler;
import net.minecraft.util.math.ChunkPos;
import net.minecraft.world.gen.Heightmap;
/**
* This object contains position
* and color data for an LOD object.
*
* @author James Seibel
* @version 6-27-2021
*/
public class LodChunk
{
/** This is what separates each piece of data in the toData method */
private static final char DATA_DELIMITER = LodDimensionFileHandler.DATA_DELIMITER;
/** Width of a Minecraft Chunk */
public static final int WIDTH = 16;
/** alpha used when drawing chunks in debug mode */
private static final int DEBUG_ALPHA = 255; // 0 - 255
private static final Color DEBUG_BLACK = new Color(0, 0, 0, DEBUG_ALPHA);
private static final Color DEBUG_WHITE = new Color(255, 255, 255, DEBUG_ALPHA);
private static final Color INVISIBLE = new Color(0,0,0,0);
/** If we ever have to use a heightmap for any reason, use this one. */
public static final Heightmap.Type DEFAULT_HEIGHTMAP = Heightmap.Type.WORLD_SURFACE_WG;
public LodDetail detail = LodDetail.SINGLE;
/** If this is set to true then toData will return
* the empty string */
public boolean dontSave = false;
// TODO store the DistanceGenerationMethod used for this chunk (so we can upgrade old chunks if we want to)
/** The x coordinate of the chunk. */
public int x = 0;
/** The z coordinate of the chunk. */
public int z = 0;
/** This stores the height and color for each data point in the LodChunk */
public LodDataPoint dataPoints[][];
/** If true then this LodChunk contains no data */
private boolean empty = false;
/**
* Create an empty, invisible, non-saving LodChunk at (0,0)
*/
public LodChunk()
{
dontSave = true;
empty = true;
x = 0;
z = 0;
detail = LodDetail.SINGLE;
dataPoints = new LodDataPoint[detail.dataPointLengthCount][detail.dataPointLengthCount];
// fill with dummy data, to prevent null pointers
for(int i = 0; i < detail.dataPointLengthCount; i++)
for(int j = 0; j < detail.dataPointLengthCount; j++)
dataPoints[i][j] = new LodDataPoint();
}
/**
* Create an empty, invisible, non-saving LodChunk at the given ChunkPos
*/
public LodChunk(ChunkPos pos)
{
this();
x = pos.x;
z = pos.z;
}
/**
* Create an empty, invisible, non-saving LodChunk at the given ChunkPos
*/
public LodChunk(int newX, int newZ)
{
this();
x = newX;
z = newZ;
}
/**
* 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 any piece of data can't be converted at any point
*/
public LodChunk(String data, LodDetail newDetail) throws IllegalArgumentException, NumberFormatException
{
/*
* data format:
* x, z, dataPoint[0][0], dataPoint[0][1], ...
* x, z, height, depth, rgb color data, height, depth, rgb....
*
* example:
* 5,8, 4, 0, 255,255,255, 4, 0, 255, 255, 255, ...
*/
dontSave = false;
// make sure there are the correct number of entries
// in the data string
int count = 0;
detail = newDetail;
for(int i = 0; i < data.length(); i++)
if(data.charAt(i) == DATA_DELIMITER)
count++;
if(count != detail.lodChunkStringDelimiterCount)
throw new IllegalArgumentException("LodChunk constructor givin an invalid string. The data given had " + count + " delimiters when it should have had " + detail.lodChunkStringDelimiterCount + ".");
dataPoints = new LodDataPoint[detail.dataPointLengthCount][detail.dataPointLengthCount];
// 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);
this.x = Integer.parseInt(data.substring(0,index));
lastIndex = index;
index = data.indexOf(DATA_DELIMITER, lastIndex+1);
this.z = Integer.parseInt(data.substring(lastIndex+1,index));
// LodDataPoints
for(int blockX = 0; blockX < detail.dataPointLengthCount; blockX++)
{
for(int blockZ = 0; blockZ < detail.dataPointLengthCount; blockZ++)
{
// height
lastIndex = index;
index = data.indexOf(DATA_DELIMITER, lastIndex+1);
int height = Short.parseShort(data.substring(lastIndex+1,index));
// depth
lastIndex = index;
index = data.indexOf(DATA_DELIMITER, lastIndex+1);
int depth = Short.parseShort(data.substring(lastIndex+1,index));
// color
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;
}
}
dataPoints[blockX][blockZ] = new LodDataPoint((short)height, (short)depth, new Color(red, green, blue));
}
}
empty = determineIfEmtpy();
}
/**
* Create a LodChunk from the given values.
*/
public LodChunk(ChunkPos pos, LodDataPoint[][] newDataPoints, LodDetail newDetail)
{
x = pos.x;
z = pos.z;
dataPoints = newDataPoints;
dontSave = false;
detail = newDetail;
empty = determineIfEmtpy();
}
//================//
// misc functions //
//================//
/**
* Returns true if this LodChunk is an emptyPlaceholder
*/
public boolean isPlaceholder()
{
return empty;
}
public boolean isLodEmpty()
{
return empty;
}
/**
* Returns true if this LOD is either invisible
* from every direction or doesn't have a valid height.
*/
private boolean determineIfEmtpy()
{
for(LodDataPoint[] dataPointArray : dataPoints)
{
for(LodDataPoint dataPoint : dataPointArray)
{
if (dataPoint == null)
continue;
// we don't check the depth since the
// height should always be greater than or equal
// to the depth
if(dataPoint.height >= 0)
{
// the height is valid,
if (dataPoint.color == INVISIBLE)
continue;
// the color and height are valid
// this LodChunk isn't empty
return false;
}
}
}
// we checked everywhere, this LodChunk is empty
return true;
}
//========//
// output //
//========//
/**
* Returns the data point for the given relative block position.
*/
public LodDataPoint getDataPointForBlockPos(int blockX, int blockZ)
{
return dataPoints[blockX / detail.dataPointWidth][blockZ / detail.dataPointWidth];
}
public Color getColorForBlockPos(int blockX, int blockZ)
{
return getDataPointForBlockPos(blockX, blockZ).color;
}
public short getHeightForBlockPos(int blockX, int blockZ)
{
return getDataPointForBlockPos(blockX, blockZ).height;
}
public short getDepthForBlockPos(int blockX, int blockZ)
{
return getDataPointForBlockPos(blockX, blockZ).depth;
}
public Color getColor(int xIndex, int zIndex)
{
return dataPoints[xIndex][zIndex].color;
}
public short getHeight(int xIndex, int zIndex)
{
return dataPoints[xIndex][zIndex].height;
}
public short getDepth(int xIndex, int zIndex)
{
return dataPoints[xIndex][zIndex].depth;
}
public short calculateHighestPoint()
{
short highest = 0;
for(int x = 0; x < detail.dataPointLengthCount; x++)
{
for(int z = 0; z < detail.dataPointLengthCount; z++)
{
if (getHeight(x,z) > highest)
highest = getHeight(x,z);
}
}
return highest;
}
/**
* @param startX
* @param startZ
* @param endX
* @param endZ
* @return
*/
public short getAverageHeightOverArea(int startX, int startZ, int endX, int endZ)
{
if (startX == endX || startZ == endZ)
// we were given an area with 0 blocks in it
return getHeightForBlockPos(startX,startZ);
int average = 0;
for(int x = startX; x < endX; x++)
for(int z = startZ; z < endZ; z++)
average += getHeightForBlockPos(x,z);
return (short) (average / ((endX - startX) * (endZ - startZ)));
}
/**
* @param startX
* @param startZ
* @param endX
* @param endZ
* @return
*/
public short getAverageDepthOverArea(int startX, int startZ, int endX, int endZ)
{
if (startX == endX || startZ == endZ)
// we were given an area with 0 blocks in it
return getDepthForBlockPos(startX,startZ);
int average = 0;
for(int x = startX; x < endX; x++)
for(int z = startZ; z < endZ; z++)
average += getDepthForBlockPos(x,z);
return (short) (average / ((endX - startX) * (endZ - startZ)));
}
/**
* Determine the color of this LOD.
*/
public Color getAverageColorOverArea(int startX, int startZ, int endX, int endZ, boolean debugging)
{
if (startX == endX || startZ == endZ)
// we were given an area with 0 blocks in it
return getColorForBlockPos(startX,startZ);
int[] colorComponents = new int[3];
if (debugging)
{
// draw the squares as a black and white,
// like a checker board
// only return 1 color to prevent
// getting back gray
if ((startX + startZ) % 2 == 0)
return DEBUG_WHITE;
else
return DEBUG_BLACK;
}
for(int x = startX; x < endX; x++)
{
for(int z = startZ; z < endZ; z++)
{
colorComponents = addColorToColorAverages(colorComponents, getColorForBlockPos(x,z));
}
}
int numbPoints = ((endX - startX) * (endZ - startZ));
return new Color(colorComponents[0]/numbPoints, colorComponents[1]/numbPoints, colorComponents[2]/numbPoints);
}
private int[] addColorToColorAverages(int[] colorAverages, Color colorToAdd)
{
// convert the colorToAdd to an int array
float[] colorCompoments = new float[4];
colorCompoments = colorToAdd.getColorComponents(colorCompoments);
// add each color component to the array
for(int rgbIndex = 0; rgbIndex < 3; rgbIndex++)
{
// * 255 + 0.5 taken from the Color java class
colorAverages[rgbIndex] += (int) (colorCompoments[rgbIndex] * 255 + 0.5);
}
return colorAverages;
}
/**
* Outputs all data in a csv format
* with the given delimiter, if
* dontSave is false. <br><br>
*
* data format: <br>
* x, z, dataPoint[0][0], dataPoint[0][1], ... <br>
* x, z, height, depth, rgb color data, height, depth, rgb.... <br><br>
*
* example: <br>
* 5,8, 4, 0, 255,255,255, 4, 0, 255, 255, 255, ...
*/
public String toData()
{
if (dontSave)
return "";
String s = "";
s += Integer.toString(x) + DATA_DELIMITER + Integer.toString(z) + DATA_DELIMITER;
for (int i = 0; i < dataPoints.length; i++)
for (int j = 0; j < dataPoints[i].length; j++)
s += dataPoints[i][j].toData();
return s;
}
@Override
public String toString()
{
String s = "";
s += "x: " + x + " z: " + z + "\t";
return s;
}
}
@@ -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.objects;
import java.awt.Color;
import com.seibel.lod.handlers.LodDimensionFileHandler;
/**
* This stores the height and color
* for a specific area in a LodChunk.
*
* @author James Seibel
* @version 6-19-2021
*/
public class LodDataPoint
{
/** This is what separates each piece of data in the toData method */
private static final char DATA_DELIMITER = LodDimensionFileHandler.DATA_DELIMITER;
/** this is how many pieces of data are exported when toData is called */
public static final int NUMBER_OF_DELIMITERS = 5;
private static final Color INVISIBLE = new Color(0,0,0,0);
/** highest point */
public short height;
/** lowest point */
public short depth;
/** The average color for the 6 cardinal directions */
public Color color;
/**
* Creates and empty LodDataPoint
*/
public LodDataPoint()
{
height = -1;
depth = -1;
color = INVISIBLE;
}
public LodDataPoint(short newHeight, short newDepth, Color newColor)
{
height = newHeight;
depth = newDepth;
color = newColor;
}
public LodDataPoint(int newHeight, int newDepth, Color newColor)
{
height = (short) newHeight;
depth = (short) newDepth;
color = newColor;
}
/**
* Outputs all data in a csv format
* with the given delimiter.
* <br>
* Exports data in the form:
* <br>
* height, depth, rgb color data
*
* <br>
* example output:
* <br>
* 4, 0, 255,255,255,
*/
public String toData()
{
String s = Short.toString(height) + DATA_DELIMITER;
s += Short.toString(depth) + DATA_DELIMITER;
s += Integer.toString(color.getRed()) + DATA_DELIMITER + Integer.toString(color.getGreen()) + DATA_DELIMITER + Integer.toString(color.getBlue()) + DATA_DELIMITER;
return s;
}
@Override
public String toString()
{
String s = Short.toString(height) + DATA_DELIMITER;
s += Short.toString(depth) + DATA_DELIMITER;
s += Integer.toString(color.getRed()) + DATA_DELIMITER + Integer.toString(color.getGreen()) + DATA_DELIMITER + Integer.toString(color.getBlue()) + DATA_DELIMITER;
return s;
}
}
@@ -1,17 +1,40 @@
package com.backsun.lod.objects;
/*
* 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.backsun.lod.handlers.LodDimensionFileHandler;
import com.backsun.lod.util.LodUtils;
import java.io.File;
import java.io.IOException;
import com.seibel.lod.handlers.LodDimensionFileHandler;
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 James Seibel
* @version 02-23-2021
* @version 06-27-2021
*/
public class LodDimension
{
@@ -20,8 +43,8 @@ public class LodDimension
private volatile int width;
private volatile int halfWidth;
public LodRegion regions[][];
public boolean isRegionDirty[][];
public volatile LodRegion regions[][];
public volatile boolean isRegionDirty[][];
private int centerX;
private int centerZ;
@@ -29,13 +52,42 @@ public class LodDimension
private LodDimensionFileHandler fileHandler;
public LodDimension(DimensionType newDimension, int newMaxWidth)
public LodDimension(DimensionType newDimension, LodWorld lodWorld, int newMaxWidth)
{
dimension = newDimension;
width = newMaxWidth;
ServerChunkProvider provider = LodUtils.getServerWorldFromDimension(newDimension).getChunkProvider();
fileHandler = new LodDimensionFileHandler(provider.getSavedData().folder, this);
try
{
Minecraft mc = Minecraft.getInstance();
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];
@@ -207,39 +259,34 @@ public class LodDimension
*/
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;
}
RegionPos pos = LodUtil.convertChunkPosToRegionPos(new ChunkPos(lod.x, lod.z));
// don't continue if the region can't be saved
if (!regionIsInRange(regionX, regionZ))
if (!regionIsInRange(pos.x, pos.z))
{
return;
LodRegion region = getRegion(regionX, regionZ);
}
LodRegion region = getRegion(pos.x, pos.z);
if (region == null)
{
// if no region exists, create it
region = new LodRegion(regionX, regionZ);
region = new LodRegion(pos.x, pos.z);
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();
// don't save empty place holders to disk
if (!lod.isPlaceholder() && fileHandler != null)
{
// mark the region as dirty so it will be saved to disk
int xIndex = (pos.x - centerX) + halfWidth;
int zIndex = (pos.z - centerZ) + halfWidth;
isRegionDirty[xIndex][zIndex] = true;
fileHandler.saveDirtyRegionsToFileAsync();
}
}
/**
@@ -251,20 +298,9 @@ public class LodDimension
*/
public LodChunk getLodFromCoordinates(int chunkX, int chunkZ)
{
int regionX = chunkX / LodRegion.SIZE;
int regionZ = chunkZ / LodRegion.SIZE;
RegionPos pos = LodUtil.convertChunkPosToRegionPos(new ChunkPos(chunkX, chunkZ));
// 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);
LodRegion region = getRegion(pos.x, pos.z);
if(region == null)
return null;
@@ -279,7 +315,10 @@ public class LodDimension
*/
public LodRegion getRegionFromFile(int regionX, int regionZ)
{
return fileHandler.loadRegionFromFile(regionX, regionZ);
if (fileHandler != null)
return fileHandler.loadRegionFromFile(regionX, regionZ);
else
return null;
}
@@ -287,7 +326,7 @@ public class LodDimension
* Returns whether the region at the given X and Z coordinates
* is within the loaded range.
*/
private boolean regionIsInRange(int regionX, int regionZ)
public boolean regionIsInRange(int regionX, int regionZ)
{
int xIndex = (regionX - centerX) + halfWidth;
int zIndex = (regionZ - centerZ) + halfWidth;
@@ -312,6 +351,40 @@ public class LodDimension
}
/**
* Returns how many non-null LodChunks
* are stored in this LodDimension.
*/
public int getNumberOfLods()
{
int numbLods = 0;
for (LodRegion[] regions : regions)
{
if(regions == null)
continue;
for (LodRegion region : regions)
{
if(region == null)
continue;
for(LodChunk[] lods : region.getAllLods())
{
if(lods == null)
continue;
for(LodChunk lod : lods)
{
if (lod != null)
numbLods++;
}
}
}
}
return numbLods;
}
public int getWidth()
{
@@ -1,4 +1,21 @@
package com.backsun.lod.objects;
/*
* 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;
/**
* A LodRegion is the a 32x32
@@ -7,7 +24,7 @@ package com.backsun.lod.objects;
* one file in the file system.
*
* @author James Seibel
* @version 1-22-2021
* @version 6-12-2021
*/
public class LodRegion
{
@@ -61,9 +78,6 @@ public class LodRegion
int arrayX = Math.abs(chunkX % SIZE);
int arrayZ = Math.abs(chunkZ % SIZE);
if(arrayX >= SIZE || arrayZ >= SIZE)
return null;
return chunks[arrayX][arrayZ];
}
@@ -0,0 +1,127 @@
/*
* 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 net.minecraft.world.DimensionType;
/**
* This stores all LODs for a given world.
*
* @author James Seibel
* @version 04-01-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 LodWorld with the given newWorldName. <br>
* This should be done whenever loading a new world.
* @param newWorldName
*/
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;
}
public void addLodDimension(LodDimension newStorage)
{
if (lodDimensions == null)
throw new IllegalStateException("LodWorld hasn't been given a world yet.");
lodDimensions.put(newStorage.dimension, newStorage);
}
public LodDimension getLodDimension(DimensionType dimension)
{
if (lodDimensions == null)
throw new IllegalStateException("LodWorld hasn't been given a world yet.");
return lodDimensions.get(dimension);
}
/**
* Resizes the max width in regions that each LodDimension
* should use.
*/
public void resizeDimensionRegionWidth(int newWidth)
{
if (lodDimensions == null)
throw new IllegalStateException("LodWorld hasn't been given a world yet.");
for(DimensionType key : lodDimensions.keySet())
lodDimensions.get(key).setRegionWidth(newWidth);
}
public boolean getIsWorldLoaded()
{
return isWorldLoaded;
}
public String getWorldName()
{
return worldName;
}
@Override
public String toString()
{
return "World name: " + worldName;
}
}
@@ -0,0 +1,45 @@
/*
* 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 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 03-25-2021
*/
public class NearFarBuffer
{
public BufferBuilder nearBuffer;
public BufferBuilder farBuffer;
/**
* @param newNearBuffer
* @param newFarBuffer
*/
public NearFarBuffer(BufferBuilder newNearBuffer, BufferBuilder newFarBuffer)
{
nearBuffer = newNearBuffer;
farBuffer = newFarBuffer;
}
}
@@ -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,48 @@
/*
* 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;
/**
* This object is similar to ChunkPos or BlockPos.
*
* @author James Seibel
* @version 03-19-2021
*/
public class RegionPos
{
public int x;
public int z;
/**
* Default Constructor <br>
*
* Sets x and z to 0
*/
public RegionPos()
{
x = 0;
z = 0;
}
public RegionPos(int newX, int newZ)
{
x = newX;
z = newZ;
}
}
@@ -0,0 +1,219 @@
/*
* 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 com.seibel.lod.builders.LodBufferBuilder;
import com.seibel.lod.builders.LodChunkBuilder;
import com.seibel.lod.builders.worldGeneration.LodChunkGenWorker;
import com.seibel.lod.handlers.LodConfig;
import com.seibel.lod.objects.LodChunk;
import com.seibel.lod.objects.LodDimension;
import com.seibel.lod.objects.LodRegion;
import com.seibel.lod.objects.LodWorld;
import com.seibel.lod.render.LodRenderer;
import com.seibel.lod.util.LodUtil;
import net.minecraft.client.Minecraft;
import net.minecraft.profiler.IProfiler;
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 7-9-2021
*/
public class ClientProxy
{
public static final Logger LOGGER = LogManager.getLogger("LOD");
private static LodWorld lodWorld = new LodWorld();
private static LodChunkBuilder lodChunkBuilder = new LodChunkBuilder();
private static LodBufferBuilder lodBufferBuilder = new LodBufferBuilder(lodChunkBuilder);
private static LodRenderer renderer = new LodRenderer(lodBufferBuilder);
Minecraft mc = Minecraft.getInstance();
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)
{
if (mc == null || mc.player == null || !lodWorld.getIsWorldLoaded())
return;
// update each regions' width to match the new render distance
int newWidth = Math.max(4,
// TODO is this logic good?
(mc.options.renderDistance * LodChunk.WIDTH * 2 * LodConfig.CLIENT.lodChunkRadiusMultiplier.get()) / LodRegion.SIZE
);
if (lodChunkBuilder.regionWidth != newWidth)
{
lodWorld.resizeDimensionRegionWidth(newWidth);
lodChunkBuilder.regionWidth = newWidth;
// skip this frame, hopefully the lodWorld
// should have everything set up by then
return;
}
LodDimension lodDim = lodWorld.getLodDimension(mc.player.level.dimensionType());
if (lodDim == null)
return;
// offset the regions
double playerX = mc.player.getX();
double playerZ = mc.player.getZ();
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);
}
// for testing
// LodConfig.CLIENT.drawLODs.set(true);
// LodConfig.CLIENT.debugMode.set(false);
// LodConfig.CLIENT.lodDetail.set(LodDetail.DOUBLE);
// LodConfig.CLIENT.lodChunkRadiusMultiplier.set(12);
// LodConfig.CLIENT.fogDistance.set(FogDistance.FAR);
// LodConfig.CLIENT.fogDrawOverride.set(FogDrawOverride.ALWAYS_DRAW_FOG_FANCY);
// LodConfig.CLIENT.distanceGenerationMode.set(DistanceGenerationMode.FEATURES);
// LodConfig.CLIENT.allowUnstableFeatureGeneration.set(false);
// LOGGER.info(lodBufferBuilder.numberOfChunksWaitingToGenerate.get());
// 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 profile 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
}
//==============//
// forge events //
//==============//
@SubscribeEvent
public void chunkLoadEvent(ChunkEvent.Load event)
{
lodChunkBuilder.generateLodChunkAsync(event.getChunk(), lodWorld, event.getWorld());
}
@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
LodChunkGenWorker.restartExecuterService();
lodBufferBuilder.numberOfChunksWaitingToGenerate.set(0);
// the player has disconnected from a server
lodWorld.deselectWorld();
}
}
@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
lodChunkBuilder.generateLodChunkAsync(event.getWorld().getChunk(event.getPos()), lodWorld, event.getWorld());
}
}
//================//
// public getters //
//================//
public static LodWorld getLodWorld()
{
return lodWorld;
}
public static LodChunkBuilder getLodBuilder()
{
return lodChunkBuilder;
}
public static LodRenderer getRenderer()
{
return renderer;
}
}
@@ -0,0 +1,866 @@
/*
* 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 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.enums.FogDistance;
import com.seibel.lod.enums.FogDrawOverride;
import com.seibel.lod.enums.FogQuality;
import com.seibel.lod.handlers.LodConfig;
import com.seibel.lod.handlers.ReflectionHandler;
import com.seibel.lod.objects.LodChunk;
import com.seibel.lod.objects.LodDimension;
import com.seibel.lod.objects.NearFarBuffer;
import com.seibel.lod.objects.NearFarFogSettings;
import com.seibel.lod.proxy.ClientProxy;
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.WorldRenderer;
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.BlockPos;
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 07-4-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>
*
* 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>
*
* 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? */
public static boolean fancyFogAvailable = false;
/** 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;
private ReflectionHandler reflectionHandler;
/** This is used to generate the buildable buffers */
private LodBufferBuilder lodBufferBuilder;
/** The buffers that are used to draw LODs using near fog */
private volatile BufferBuilder drawableNearBuffer;
/** The buffers that are used to draw LODs using far fog */
private volatile BufferBuilder drawableFarBuffer;
/** This is the VertexBuffer used to draw any LODs that use near fog */
private volatile VertexBuffer nearVbo;
/** This is the VertexBuffer used to draw any LODs that use far fog */
private volatile VertexBuffer farVbo;
public static final VertexFormat LOD_VERTEX_FORMAT = DefaultVertexFormats.POSITION_COLOR;
/** 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 volatile boolean regen = false;
/** This HashSet contains every chunk that Vanilla Minecraft
* is going to render */
public HashSet<ChunkPos> vanillaRenderedChunks = new HashSet<>();
public LodRenderer(LodBufferBuilder newLodBufferBuilder)
{
mc = Minecraft.getInstance();
gameRender = mc.gameRenderer;
reflectionHandler = new ReflectionHandler();
lodBufferBuilder = newLodBufferBuilder;
}
/**
* 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 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");
ClientPlayerEntity player = mc.player;
// should LODs be regenerated?
if ((int)player.getX() / LodChunk.WIDTH != prevChunkX ||
(int)player.getZ() / LodChunk.WIDTH != prevChunkZ ||
previousChunkRenderDistance != mc.options.renderDistance ||
prevFogDistance != LodConfig.CLIENT.fogDistance.get())
{
// yes
regen = true;
prevChunkX = (int)player.getX() / LodChunk.WIDTH;
prevChunkZ = (int)player.getZ() / 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
}
// did the user change the debug setting?
if (LodConfig.CLIENT.debugMode.get() != debugging)
{
debugging = LodConfig.CLIENT.debugMode.get();
regen = true;
}
// determine how far the game's render distance is currently set
int renderDistWidth = mc.options.renderDistance;
farPlaneDistance = renderDistWidth * LodChunk.WIDTH;
// set how big the LODs will be and how far they will go
int totalLength = (int) farPlaneDistance * LodConfig.CLIENT.lodChunkRadiusMultiplier.get() * 2;
int numbChunksWide = (totalLength / LodChunk.WIDTH);
// determine which LODs should not be rendered close to the player
HashSet<ChunkPos> chunkPosToSkip = getNearbyLodChunkPosToSkip(lodDim, player.blockPosition());
// see if the chunks Minecraft is going to render are the
// same as last time
if (!vanillaRenderedChunks.containsAll(chunkPosToSkip))
{
regen = true;
vanillaRenderedChunks = chunkPosToSkip;
}
//=================//
// 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 && !lodBufferBuilder.generatingBuffers && !lodBufferBuilder.newBuffersAvaliable())
{
// this will mainly happen when the view distance is changed
if (drawableNearBuffer == null || drawableFarBuffer == null ||
previousChunkRenderDistance != mc.options.renderDistance)
setupBuffers(numbChunksWide);
// generate the LODs on a separate thread to prevent stuttering or freezing
lodBufferBuilder.generateLodBuffersAsync(this, lodDim, player.getX(), player.getZ(), numbChunksWide);
// the regen process has been started,
// it will be done when lodBufferBuilder.newBuffersAvaliable
// is true
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 (lodBufferBuilder.newBuffersAvaliable())
{
swapBuffers();
}
//===========================//
// 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);
// 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);
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");
setupFog(fogSettings.near.distance, fogSettings.near.quality);
sendLodsToGpuAndDraw(nearVbo, modelViewMatrix);
setupFog(fogSettings.far.distance, fogSettings.far.quality);
sendLodsToGpuAndDraw(farVbo, 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();
// this can't be called until after the buffers are built
// because otherwise the buffers may be set to the wrong size
previousChunkRenderDistance = mc.options.renderDistance;
// 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.
*
* @param buffers the buffers sent to the GPU to draw
*/
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 = NVFogDistance.GL_EYE_RADIAL_NV;
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.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 * 1.75f);
RenderSystem.fogStart(farPlaneDistance * 1.95f);
}
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.85f * LodConfig.CLIENT.lodChunkRadiusMultiplier.get());
RenderSystem.fogEnd(farPlaneDistance * 1.0f * LodConfig.CLIENT.lodChunkRadiusMultiplier.get());
}
else if(fogQuality == FogQuality.FAST)
{
RenderSystem.fogStart(farPlaneDistance * 0.5f * LodConfig.CLIENT.lodChunkRadiusMultiplier.get());
RenderSystem.fogEnd(farPlaneDistance * 0.75f * LodConfig.CLIENT.lodChunkRadiusMultiplier.get());
}
}
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.
LodConfig.CLIENT.lodChunkRadiusMultiplier.get(),
this.farPlaneDistance * LodConfig.CLIENT.lodChunkRadiusMultiplier.get() * 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)
{
float sunBrightness = lodDimension.dimension.hasSkyLight() ? mc.level.getSkyDarken(partialTicks) : 0.2f;
float gammaMultiplyer = (float)mc.options.gamma - 0.5f;
float lightStrength = ((sunBrightness / 2f) - 0.2f) + (gammaMultiplyer * 0.2f);
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.
*/
private void setupBuffers(int numbChunksWide)
{
// calculate the max amount of memory needed (in bytes)
int bufferMemory = RenderUtil.getBufferMemoryForRadiusMultiplier(LodConfig.CLIENT.lodChunkRadiusMultiplier.get());
// if the required memory is greater than the
// MAX_ALOCATEABLE_DIRECT_MEMORY lower the lodChunkRadiusMultiplier
// to fit.
if (bufferMemory > MAX_ALOCATEABLE_DIRECT_MEMORY)
{
int maxRadiusMultiplier = RenderUtil.getMaxRadiusMultiplierWithAvaliableMemory(LodConfig.CLIENT.lodTemplate.get(), LodConfig.CLIENT.lodDetail.get());
ClientProxy.LOGGER.warn("The lodChunkRadiusMultiplier was set too high "
+ "and had to be lowered to fit memory constraints "
+ "from " + LodConfig.CLIENT.lodChunkRadiusMultiplier.get() + " "
+ "to " + maxRadiusMultiplier);
LodConfig.CLIENT.lodChunkRadiusMultiplier.set(
maxRadiusMultiplier);
bufferMemory = RenderUtil.getBufferMemoryForRadiusMultiplier(maxRadiusMultiplier);
}
drawableNearBuffer = new BufferBuilder(bufferMemory);
drawableFarBuffer = new BufferBuilder(bufferMemory);
lodBufferBuilder.setupBuffers(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()
{
regen = true;
}
/**
* Replace the current drawable buffers with the newly
* created buffers from the lodBufferBuilder.
*/
private void swapBuffers()
{
// replace the drawable buffers with
// the newly created buffers from the lodBufferBuilder
NearFarBuffer newBuffers = lodBufferBuilder.swapBuffers(drawableNearBuffer, drawableFarBuffer);
drawableNearBuffer = newBuffers.nearBuffer;
drawableFarBuffer = newBuffers.farBuffer;
// 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);
}
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.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.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.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;
}
/**
* Get a HashSet of all ChunkPos within the normal render distance
* that should not be rendered.
*/
private HashSet<ChunkPos> getNearbyLodChunkPosToSkip(LodDimension lodDim, BlockPos playerPos)
{
int chunkRenderDist = mc.options.renderDistance;
int blockRenderDist = chunkRenderDist * 16;
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++)
{
LodChunk lod = lodDim.getLodFromCoordinates(x, z);
if (lod != null)
{
short lodHighestPoint = lod.calculateHighestPoint();
if (playerPos.getY() < lodHighestPoint)
{
// 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));
}
else if (blockRenderDist < Math.abs(playerPos.getY() - lodHighestPoint))
{
// draw Lod's that are lower than the player's view range
posToSkip.remove(new ChunkPos(x, z));
}
}
}
}
return posToSkip;
}
/**
* This method returns the ChunkPos of all chunks that Minecraft
* is going to render this frame. <br><br>
*
* 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.getX() / 16, bpos.getZ() / 16));
}
}
return loadedPos;
}
}
@@ -0,0 +1,111 @@
/*
* 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.enums.LodDetail;
import com.seibel.lod.enums.LodTemplate;
import com.seibel.lod.handlers.LodConfig;
import net.minecraft.client.Minecraft;
import net.minecraft.util.math.ChunkPos;
/**
* This holds miscellaneous helper code
* to be used in the rendering process.
*
* @author James Seibel
* @version 6-17-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
*/
public static int getBufferMemoryForRadiusMultiplier(int radiusMultiplier)
{
int numbChunksWide = mc.options.renderDistance *
radiusMultiplier * 2;
// calculate the max amount of buffer memory needed (in bytes)
return numbChunksWide * numbChunksWide *
LodConfig.CLIENT.lodTemplate.get().
getBufferMemoryForSingleLod(LodConfig.CLIENT.lodDetail.get());
}
/**
* Returns the maxViewDistanceMultiplier for the given LodTemplate
* at the given LodDetail level.
*/
public static int getMaxRadiusMultiplierWithAvaliableMemory(LodTemplate lodTemplate, LodDetail lodDetail)
{
int maxNumberOfLods = LodRenderer.MAX_ALOCATEABLE_DIRECT_MEMORY / lodTemplate.getBufferMemoryForSingleLod(lodDetail);
int numbLodsWide = (int) Math.sqrt(maxNumberOfLods);
return numbLodsWide / (2 * mc.options.renderDistance);
}
}
@@ -0,0 +1,257 @@
/*
* 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 com.seibel.lod.objects.LodRegion;
import com.seibel.lod.objects.RegionPos;
import net.minecraft.client.Minecraft;
import net.minecraft.client.multiplayer.ServerData;
import net.minecraft.server.integrated.IntegratedServer;
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.server.ServerChunkProvider;
import net.minecraft.world.server.ServerWorld;
/**
* This class holds methods that may be used in multiple places.
*
* @author James Seibel
* @version 06-27-2021
*/
public class LodUtil
{
private static Minecraft mc = Minecraft.getInstance();
/**
* 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 the given ChunkPos into a RegionPos.
*/
public static RegionPos convertChunkPosToRegionPos(ChunkPos pos)
{
RegionPos rPos = new RegionPos();
rPos.x = pos.x / LodRegion.SIZE;
rPos.z = pos.z / LodRegion.SIZE;
// prevent issues if X/Z is negative and less than 16
if (pos.x < 0)
{
rPos.x = (Math.abs(rPos.x) * -1) - 1;
}
if (pos.z < 0)
{
rPos.z = (Math.abs(rPos.z) * -1) - 1;
}
return rPos;
}
/**
* 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;
}
public static String getCurrentDimensionID()
{
Minecraft mc = Minecraft.getInstance();
if(mc.hasSingleplayerServer())
{
// this will return the world save location
// and the dimension folder
if(mc.level == null)
return "";
ServerWorld serverWorld = LodUtil.getServerWorldFromDimension(mc.level.dimensionType());
if(serverWorld == null)
return "";
ServerChunkProvider provider = serverWorld.getChunkSource();
if(provider == null)
return "";
return provider.dataStorage.dataFolder.toString();
}
else
{
ServerData server = mc.getCurrentServer();
return server.name + ", IP " +
server.ip + ", GameVersion " +
server.version.getString() + File.separatorChar
+ "dim_" + mc.level.dimensionType().effectsLocation().getPath() + File.separatorChar;
}
}
/**
* 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;
}
}
/**
* 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 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();
}
}
/**
* 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();
}
}
@@ -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 #
+3 -3
View File
@@ -7,7 +7,7 @@
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.
@@ -24,10 +24,10 @@ 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.3.1" #mandatory
#// A display name for the mod
displayName="Levels of Detail" #mandatory
displayName="Level of Detail" #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
+1 -1
View File
@@ -1,6 +1,6 @@
{
"required": true,
"package": "com.backsun.lod.mixin",
"package": "com.seibel.lod.mixin",
"compatibilityLevel": "JAVA_8",
"refmap": "lod.refmap.json",
"mixins": [
+2 -2
View File
@@ -1,9 +1,9 @@
[
{
"modid": "lod",
"name": "Level Of Details",
"name": "Level Of Detail",
"description": "Generates and renders simplified chunks beyond the normal view distance, at a low performance cost.",
"version": "a1",
"version": "a1.3.1",
"mcversion": "1.16.4",
"url": "",
"updateUrl": "",