diff --git a/.gitattributes b/.gitattributes deleted file mode 100644 index f811f6ae6..000000000 --- a/.gitattributes +++ /dev/null @@ -1,5 +0,0 @@ -# Disable autocrlf on generated files, they always generate with LF -# Add any extra files or paths here to make git stop saying they -# are changed when only line endings change. -src/generated/**/.cache/cache text eol=lf -src/generated/**/*.json text eol=lf diff --git a/.gitignore b/.gitignore index 5c1c9cc5b..09cd281f9 100644 --- a/.gitignore +++ b/.gitignore @@ -1,49 +1,33 @@ +# gradle + +.gradle/ +build/ +out/ +classes/ + # eclipse -bin + *.launch -.settings -.metadata -.classpath -.project # idea -out + +.idea/ +*.iml *.ipr *.iws -*.iml -.idea -# gradle -build -.gradle +# vscode -# other -eclipse -run - -# Files from Forge MDK -logs -forge*changelog.txt - -.architectury-transformer/ -build/ -*.ipr -run/ -*.iws -out/ -*.iml -.gradle/ -output/ +.settings/ +.vscode/ bin/ -libs/ - .classpath .project -.idea/ -classes/ -.metadata -.vscode -.settings -*.launch -**/src/generated/ \ No newline at end of file +# macos + +*.DS_Store + +# fabric + +run/ diff --git a/.gitmodules b/.gitmodules deleted file mode 100644 index 17c60bca6..000000000 --- a/.gitmodules +++ /dev/null @@ -1,3 +0,0 @@ -[submodule "core"] - path = core - url = https://gitlab.com/jeseibel/distant-horizons-core.git diff --git a/build.gradle b/build.gradle index 90c70d6c8..9001ad03c 100644 --- a/build.gradle +++ b/build.gradle @@ -1,65 +1,138 @@ plugins { - id "architectury-plugin" version "3.4-SNAPSHOT" - id "dev.architectury.loom" version "0.10.0.195" apply false + id 'fabric-loom' version '0.10-SNAPSHOT' + id 'maven-publish' + id "com.github.johnrengelman.shadow" version "7.1.0" } -architectury { - minecraft = rootProject.minecraft_version +version = '1.0' +sourceCompatibility = JavaVersion.VERSION_17 +targetCompatibility = JavaVersion.VERSION_17 + +println('Java: ' + System.getProperty('java.version') + ' JVM: ' + System.getProperty('java.vm.version') + '(' + System.getProperty('java.vendor') + ') Arch: ' + System.getProperty('os.arch')) + +// Include resources generated by data generators. +sourceSets.main.resources { srcDir 'src/generated/resources' } + +loom { + accessWidenerPath = file("src/main/resources/lod.accesswidener") } -subprojects { p -> - apply plugin: "dev.architectury.loom" +// this is required so that we can use +// jitpack in the dependencies section below +repositories { + mavenCentral() + // used to download and compile dependencies from git repos + maven { url 'https://jitpack.io' } - loom { - silentMojangMappingsLicense() + // Required for ModMenu + maven { url "https://maven.terraformersmc.com/" } +} + +configurations { + shadowMe + compileOnly.extendsFrom(embed) +} + +dependencies { + minecraft "com.mojang:minecraft:${project.minecraft_version}" + mappings loom.officialMojangMappings() + modImplementation "net.fabricmc:fabric-loader:${project.fabric_loader_version}" + + // Fabric API + modImplementation "net.fabricmc.fabric-api:fabric-api:${project.fabric_api_version}" + + // Mod Menu + modImplementation("com.terraformersmc:modmenu:${project.modmenu_version}") { + exclude(group: "net.fabricmc.fabric-api") } - configurations { - common - shadowMe - implementation.extendsFrom shadowMe + implementation 'org.tukaani:xz:1.9' + shadowMe 'org.tukaani:xz:1.9' + implementation 'org.apache.commons:commons-compress:1.21' + shadowMe 'org.apache.commons:commons-compress:1.21' +} + +shadowJar { + duplicatesStrategy = DuplicatesStrategy.INCLUDE + configurations = [project.configurations.getByName("shadowMe")] + relocate 'org.tukaani', 'shaded.tukaani' + relocate 'org.apache.commons.compress', 'shaded.apache.commons.compress' + archiveClassifier = 'unmapped' + + from("LICENSE") { + rename { "${it}_${project.archivesBaseName}"} } +} - dependencies { - minecraft "com.mojang:minecraft:${rootProject.minecraft_version}" - // The following line declares the mojmap mappings - mappings loom.officialMojangMappings() +jar { + archiveClassifier = 'unmapped' +} - if (p != project(":forge")) { - // We depend on fabric loader here to use the fabric @Environment annotations and get the mixin dependencies - // Do NOT use other classes from fabric loader unless working with fabric - modImplementation "net.fabricmc:fabric-loader:${rootProject.fabric_loader_version}" - } +remapJar { + dependsOn(shadowJar) + mustRunAfter(shadowJar) + input = shadowJar.archiveFile + archiveBaseName = "${project.name}" + archiveVersion = "${project.version}" + archiveClassifier = '' +} - if (p != project(":core")) { - common(project(":core")) { transitive false } - shadowMe(project(":core")) { transitive false } +processResources { + inputs.property "version", project.version + + filesMatching("fabric.mod.json") { + expand "version": project.version + } +} + +tasks.withType(JavaCompile).configureEach { + // ensure that the encoding is set to UTF-8, no matter what the system default is + // this fixes some edge cases with special characters not displaying correctly + // see http://yodaconditions.net/blog/fix-for-java-file-encoding-problems-with-gradle.html + // If Javadoc is generated, this must be specified in that task too. + it.options.encoding = "UTF-8" + + // Minecraft 1.18 (1.18-pre2) upwards uses Java 17. + it.options.release = 17 +} + +java { + // Loom will automatically attach sourcesJar to a RemapSourcesJar task and to the "build" task + // if it is present. + // If you remove this line, sources will not be generated. + withSourcesJar() +} + +// configure the maven publication +publishing { + publications { + mavenJava(MavenPublication) { + // add all the jars that should be included when publishing to maven + artifact(remapJar) { + builtBy remapJar + } + artifact(sourcesJar) { + builtBy remapSourcesJar + } } } -} - -allprojects { - apply plugin: "java" - apply plugin: "architectury-plugin" - apply plugin: "maven-publish" - - archivesBaseName = rootProject.archives_base_name - version = rootProject.mod_version - group = rootProject.maven_group + // See https://docs.gradle.org/current/userguide/publishing_maven.html for information on how to set up publishing. repositories { - mavenCentral() - // used to download and compile dependencies from git repos - maven { url 'https://jitpack.io' } - } - - tasks.withType(JavaCompile) { - options.encoding = "UTF-8" - options.release = 16 - } - - java { - withSourcesJar() + // Add repositories to publish to here. + // Notice: This block does NOT have the same function as the block in the top level. + // The repositories here will be used for publishing your artifact, not for + // retrieving dependencies. } } + +//// add Distant Horizon's Core source folders +//sourceSets { +// main { +// java { +// srcDirs("core/src/main/java"); +// srcDirs("core/src/main/resources") +// } +// } +//} diff --git a/common/build.gradle b/common/build.gradle deleted file mode 100644 index 0002c219a..000000000 --- a/common/build.gradle +++ /dev/null @@ -1,29 +0,0 @@ -loom { - accessWidenerPath.set(file("src/main/resources/lod.accesswidener")) -} - -architectury { - common() -} - -afterEvaluate { - tasks { - remapJar { - remapAccessWidener.set(false) - } - } -} - -publishing { - publications { - mavenCommon(MavenPublication) { - artifactId = rootProject.archives_base_name - from components.java - } - } - - // See https://docs.gradle.org/current/userguide/publishing_maven.html for information on how to set up publishing. - repositories { - // Add repositories to publish to here. - } -} diff --git a/common/src/main/resources/logo.png b/common/src/main/resources/logo.png deleted file mode 100644 index 0caf9c6d1..000000000 Binary files a/common/src/main/resources/logo.png and /dev/null differ diff --git a/core b/core deleted file mode 160000 index f3b6b15bc..000000000 --- a/core +++ /dev/null @@ -1 +0,0 @@ -Subproject commit f3b6b15bcbb50fe64a1eb1e2f3d5058474db1e5f diff --git a/fabric/build.gradle b/fabric/build.gradle deleted file mode 100644 index 452f93701..000000000 --- a/fabric/build.gradle +++ /dev/null @@ -1,124 +0,0 @@ -plugins { - id "com.github.johnrengelman.shadow" version "7.0.0" -} - -loom { - accessWidenerPath.set(project(":common").file("src/main/resources/lod.accesswidener")) -} - -architectury { - platformSetupLoomIde() - fabric() -} - -configurations { - compileClasspath.extendsFrom common - runtimeClasspath.extendsFrom common - developmentFabric.extendsFrom common -} - -repositories { - // Required for ModMenu - maven { url "https://maven.terraformersmc.com/" } -} - - -dependencies { - modImplementation "net.fabricmc:fabric-loader:${rootProject.fabric_loader_version}" - - // TODO: This is only for LodMain, try to find a way to remove it - // Fabric API - modApi "net.fabricmc.fabric-api:fabric-api:${rootProject.fabric_api_version}" - - // Mod Menu - modImplementation("com.terraformersmc:modmenu:${rootProject.modmenu_version}") { - exclude(group: "net.fabricmc.fabric-api") - } - - common(project(path: ":common", configuration: "namedElements")) { transitive false } - shadowMe(project(path: ":common", configuration: "transformProductionFabric")) { transitive false } - - common 'org.tukaani:xz:1.9' - common 'org.apache.commons:commons-compress:1.21' - shadowMe 'org.tukaani:xz:1.9' - shadowMe 'org.apache.commons:commons-compress:1.21' -} - -// This method copies the access wideners from the common project to the fabric project. And it was generated by Github Copilot -task copyAccessWidener(type: Copy) { - from project(":common").file("src/main/resources/lod.accesswidener") - into file("src/generated/resources") -} - -task copyCoreResources(type: Copy) { - from fileTree(project(":core").file("src/main/resources")) - into file("build/resources/main") -} - -task deleteResources(type: Delete) { - delete file("build/resources/main") -} - -task copyCommonResources(type: Copy) { - from fileTree(project(":common").file("src/main/resources")) - into file("build/resources/main") -} - -runClient { - dependsOn(copyCoreResources) - dependsOn(copyCommonResources) - finalizedBy(deleteResources) -} - -processResources { - dependsOn(copyAccessWidener) - inputs.property "version", project.version - - filesMatching("fabric.mod.json") { - expand "version": project.version - } -} - -shadowJar { - configurations = [project.configurations.shadowMe] - relocate 'org.tukaani', 'shaded.tukaani' - relocate 'org.apache.commons.compress', 'shaded.apache.commons.compress' - - classifier "dev-shadow" -} - -remapJar { - input.set shadowJar.archiveFile - dependsOn shadowJar - classifier null -} - -jar { - classifier "dev" -} - -sourcesJar { - def commonSources = project(":common").sourcesJar - dependsOn commonSources - from commonSources.archiveFile.map { zipTree(it) } -} - -components.java { - withVariantsFromConfiguration(project.configurations.shadowRuntimeElements) { - skip() - } -} - -publishing { - publications { - mavenFabric(MavenPublication) { - artifactId = rootProject.archives_base_name + "-" + project.name - from components.java - } - } - - // See https://docs.gradle.org/current/userguide/publishing_maven.html for information on how to set up publishing. - repositories { - // Add repositories to publish to here. - } -} diff --git a/forge/build.gradle b/forge/build.gradle deleted file mode 100644 index eb99f7b85..000000000 --- a/forge/build.gradle +++ /dev/null @@ -1,101 +0,0 @@ -plugins { - id "com.github.johnrengelman.shadow" version "7.0.0" -} - -loom { - accessWidenerPath.set(project(":common").file("src/main/resources/lod.accesswidener")) - - forge { - convertAccessWideners.set(true) - extraAccessWideners.add("lod.accesswidener") - mixinConfigs("lod.mixins.json") - } -} - -architectury { - platformSetupLoomIde() - forge() -} - -configurations { - compileClasspath.extendsFrom common - runtimeClasspath.extendsFrom common - developmentForge.extendsFrom common -} - -dependencies { - forge "net.minecraftforge:forge:${rootProject.minecraft_version}-${rootProject.forge_version}" - - common(project(path: ":common", configuration: "namedElements")) { transitive false } - shadowMe(project(path: ":common", configuration: "transformProductionForge")) { transitive = false } - -// forgeDependencies(project(":core")) { transitive false } - - forgeDependencies('org.tukaani:xz:1.9') - forgeDependencies('org.apache.commons:commons-compress:1.21') - shadowMe 'org.tukaani:xz:1.9' - shadowMe 'org.apache.commons:commons-compress:1.21' -} - -task copyCoreResources(type: Copy) { - from fileTree(project(":core").file("src/main/resources")) - into file("build/resources/main") -} - -task copyCommonResources(type: Copy) { - from fileTree(project(":common").file("src/main/resources")) - into file("build/resources/main") -} - -processResources { - dependsOn(copyCoreResources) - dependsOn(copyCommonResources) -} - -shadowJar { - dependencies { - exclude(dependency("net.fabricmc:fabric-loader:${rootProject.fabric_loader_version}")) - } - exclude "fabric.mod.json" - configurations = [project.configurations.shadowMe] - relocate 'org.tukaani', 'shaded.tukaani' - relocate 'org.apache.commons.compress', 'shaded.apache.commons.compress' - - classifier "dev-shadow" -} - -remapJar { - input.set shadowJar.archiveFile - dependsOn shadowJar - classifier null -} - -jar { - classifier "dev" -} - -sourcesJar { - def commonSources = project(":common").sourcesJar - dependsOn commonSources - from commonSources.archiveFile.map { zipTree(it) } -} - -components.java { - withVariantsFromConfiguration(project.configurations.shadowRuntimeElements) { - skip() - } -} - -publishing { - publications { - mavenForge(MavenPublication) { - artifactId = rootProject.archives_base_name + "-" + project.name - from components.java - } - } - - // See https://docs.gradle.org/current/userguide/publishing_maven.html for information on how to set up publishing. - repositories { - // Add repositories to publish to here. - } -} diff --git a/forge/gradle.properties b/forge/gradle.properties deleted file mode 100644 index 32f842a63..000000000 --- a/forge/gradle.properties +++ /dev/null @@ -1 +0,0 @@ -loom.platform=forge \ No newline at end of file diff --git a/forge/src/main/java/com/seibel/lod/forge/ForgeClientProxy.java b/forge/src/main/java/com/seibel/lod/forge/ForgeClientProxy.java deleted file mode 100644 index ae3daaab6..000000000 --- a/forge/src/main/java/com/seibel/lod/forge/ForgeClientProxy.java +++ /dev/null @@ -1,107 +0,0 @@ -/* - * This file is part of the Distant Horizon mod (formerly the LOD Mod), - * licensed under the GNU GPL v3 License. - * - * Copyright (C) 2020 James Seibel - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, version 3. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package com.seibel.lod.forge; - -import com.seibel.lod.core.api.EventApi; -import com.seibel.lod.core.wrapperInterfaces.chunk.IChunkWrapper; -import com.seibel.lod.common.wrappers.chunk.ChunkWrapper; -import com.seibel.lod.common.wrappers.world.DimensionTypeWrapper; -import com.seibel.lod.common.wrappers.world.WorldWrapper; - -import net.minecraftforge.client.event.InputEvent; -import net.minecraftforge.event.TickEvent; -import net.minecraftforge.event.world.BlockEvent; -import net.minecraftforge.event.world.ChunkEvent; -import net.minecraftforge.event.world.WorldEvent; -import net.minecraftforge.eventbus.api.SubscribeEvent; - -/** - * This handles all events sent to the client, - * and is the starting point for most of the mod. - * - * @author James_Seibel - * @version 11-12-2021 - */ -public class ForgeClientProxy -{ - private final EventApi eventApi = EventApi.INSTANCE; - - - - @SubscribeEvent - public void serverTickEvent(TickEvent.ServerTickEvent event) - { - eventApi.serverTickEvent(); - } - - @SubscribeEvent - public void chunkLoadEvent(ChunkEvent.Load event) - { - eventApi.chunkLoadEvent(new ChunkWrapper(event.getChunk()), DimensionTypeWrapper.getDimensionTypeWrapper(event.getWorld().dimensionType())); - } - - @SubscribeEvent - public void worldSaveEvent(WorldEvent.Save event) - { - eventApi.worldSaveEvent(); - } - - /** This is also called when a new dimension loads */ - @SubscribeEvent - public void worldLoadEvent(WorldEvent.Load event) - { - if (event.getWorld() != null) { - eventApi.worldLoadEvent(WorldWrapper.getWorldWrapper(event.getWorld())); - } - } - - @SubscribeEvent - public void worldUnloadEvent(WorldEvent.Unload event) - { - eventApi.worldUnloadEvent(); - } - - @SubscribeEvent - public void blockChangeEvent(BlockEvent event) - { - // we only care about certain block events - 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) - { - IChunkWrapper chunk = new ChunkWrapper(event.getWorld().getChunk(event.getPos())); - DimensionTypeWrapper dimType = DimensionTypeWrapper.getDimensionTypeWrapper(event.getWorld().dimensionType()); - - // recreate the LOD where the blocks were changed - eventApi.blockChangeEvent(chunk, dimType); - } - } - - @SubscribeEvent - public void onKeyInput(InputEvent.KeyInputEvent event) - { - eventApi.onKeyInput(event.getKey(), event.getAction()); - } - - - -} diff --git a/forge/src/main/java/com/seibel/lod/forge/ForgeMain.java b/forge/src/main/java/com/seibel/lod/forge/ForgeMain.java deleted file mode 100644 index 4839add98..000000000 --- a/forge/src/main/java/com/seibel/lod/forge/ForgeMain.java +++ /dev/null @@ -1,99 +0,0 @@ -/* - * This file is part of the Distant Horizon mod (formerly the LOD Mod), - * licensed under the GNU GPL v3 License. - * - * Copyright (C) 2020 James Seibel - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, version 3. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package com.seibel.lod.forge; - -import com.seibel.lod.common.Config; -import com.seibel.lod.common.LodCommonMain; -import com.seibel.lod.common.forge.LodForgeMethodCaller; -import com.seibel.lod.common.wrappers.config.ConfigGui; -import com.seibel.lod.common.wrappers.minecraft.MinecraftWrapper; -import com.seibel.lod.core.ModInfo; -import com.seibel.lod.forge.wrappers.ForgeDependencySetup; - -import net.minecraft.client.renderer.block.model.BakedQuad; -import net.minecraft.core.Direction; -import net.minecraft.world.level.block.Block; -import net.minecraft.world.level.block.state.BlockState; -import net.minecraftforge.client.model.data.ModelDataMap; -import net.minecraftforge.common.MinecraftForge; -import net.minecraftforge.eventbus.api.SubscribeEvent; -import net.minecraftforge.fml.ModLoadingContext; -import net.minecraftforge.fml.common.Mod; -import net.minecraftforge.fml.config.ModConfig; -import net.minecraftforge.fml.event.lifecycle.FMLClientSetupEvent; -import net.minecraftforge.fml.event.lifecycle.FMLCommonSetupEvent; -import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext; -import net.minecraftforge.fmlserverevents.FMLServerStartedEvent; - -import java.util.List; -import java.util.Random; - -/** - * Initialize and setup the Mod.
- * If you are looking for the real start of the mod - * check out the ClientProxy. - * - * @author James Seibel - * @version 11-21-2021 - */ -@Mod(ModInfo.ID) -public class ForgeMain implements LodForgeMethodCaller -{ - public static ForgeClientProxy forgeClientProxy; - - private void init(final FMLCommonSetupEvent event) - { - // make sure the dependencies are set up before the mod needs them - LodCommonMain.initConfig(); - LodCommonMain.startup(this); - ForgeDependencySetup.createInitialBindings(); - } - - - public ForgeMain() - { - // Register the methods - FMLJavaModLoadingContext.get().getModEventBus().addListener(this::init); - FMLJavaModLoadingContext.get().getModEventBus().addListener(this::onClientStart); - - // Register ourselves for server and other game events we are interested in - MinecraftForge.EVENT_BUS.register(this); - } - - private void onClientStart(final FMLClientSetupEvent event) - { - forgeClientProxy = new ForgeClientProxy(); - MinecraftForge.EVENT_BUS.register(forgeClientProxy); - } - - - - @SubscribeEvent - public void onServerStarting(FMLServerStartedEvent event) - { - // this is called when the server starts - } - - private ModelDataMap dataMap = new ModelDataMap.Builder().build(); - @Override - public List getQuads(MinecraftWrapper mc, Block block, BlockState blockState, Direction direction, Random random) { - return mc.getModelManager().getBlockModelShaper().getBlockModel(block.defaultBlockState()).getQuads(blockState, direction, random, dataMap); - } -} diff --git a/forge/src/main/java/com/seibel/lod/forge/wrappers/ForgeDependencySetup.java b/forge/src/main/java/com/seibel/lod/forge/wrappers/ForgeDependencySetup.java deleted file mode 100644 index 6c18706e7..000000000 --- a/forge/src/main/java/com/seibel/lod/forge/wrappers/ForgeDependencySetup.java +++ /dev/null @@ -1,23 +0,0 @@ -package com.seibel.lod.forge.wrappers; - -import com.seibel.lod.common.wrappers.config.LodConfigWrapperSingleton; -import com.seibel.lod.core.util.SingletonHandler; -import com.seibel.lod.core.wrapperInterfaces.config.ILodConfigWrapperSingleton; - -/** - * Binds all necessary dependencies so we - * can access them in Core.
- * This needs to be called before any Core classes - * are loaded. - * - * @author James Seibel - * @author Ran - * @version 12-1-2021 - */ -public class ForgeDependencySetup -{ - public static void createInitialBindings() - { - SingletonHandler.bind(ILodConfigWrapperSingleton.class, LodConfigWrapperSingleton.INSTANCE); - } -} diff --git a/forge/src/main/resources/META-INF/mods.toml b/forge/src/main/resources/META-INF/mods.toml deleted file mode 100644 index 993cdca17..000000000 --- a/forge/src/main/resources/META-INF/mods.toml +++ /dev/null @@ -1,51 +0,0 @@ -#// This is an example mods.toml file. It contains the data relating to the loading mods. -#// There are several mandatory fields (#mandatory), and many more that are optional (#optional). -#// The overall format is standard TOML format, v0.5.0. -#// Note that there are a couple of TOML lists in this file. -#// Find more information on toml format here: https://github.com/toml-lang/toml -#// The name of the mod loader type to load - for regular FML @Mod mods it should be javafml -modLoader="javafml" #mandatory - -#// A version range to match for said mod loader - for regular FML @Mod it will be the forge version -loaderVersion="[36,)" #mandatory This is typically bumped every Minecraft version by Forge. See our download page for lists of versions. - -#// The license for you mod. This is mandatory metadata and allows for easier comprehension of your redistributive properties. -#// Review your options at https://choosealicense.com/. All rights reserved is the default copyright stance, and is thus the default here. -license="GNU GPLv3" - -#// A URL to refer people to when problems occur with this mod -issueTrackerURL="https://gitlab.com/jeseibel/minecraft-lod-mod/-/issues" #optional -#// A list of mods - how many allowed here is determined by the individual mod loader -[[mods]] #mandatory - -#// The modid of the mod -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="1.5.4a" #mandatory - -#// A display name for the mod -displayName="Distant Horizons" #mandatory - -#// A URL to query for updates for this mod. See the JSON update specification https://mcforge.readthedocs.io/en/latest/gettingstarted/autoupdate/ -#//updateJSONURL="https://change.me.example.invalid/updates.json" #optional - -#// A URL for the "homepage" for this mod, displayed in the mod UI -displayURL="https://www.curseforge.com/minecraft/mc-mods/lod-level-of-detail" #optional - -#// A file name (in the root of the mod JAR) containing a logo for display -logoFile="logo.png" #optional - -#// A file name (in the root of the mod JAR) containing a icon for display by catalogue -catalogueImageIcon="icon.png" - -#// A text field displayed in the mod UI -credits="TechnoVision, Vike, and Darkhax for their modding tutorials." #optional - -#// A text field displayed in the mod UI -authors="James Seibel, Leonardo Amato, and Cola" #optional - -#// The description text for the mod (multi line!) (#mandatory) -description='''This mod generates and renders simplified terrain beyond the normal view distance, at a low performance cost.''' diff --git a/forge/src/main/resources/lod.mixins.json b/forge/src/main/resources/lod.mixins.json deleted file mode 100644 index c02078197..000000000 --- a/forge/src/main/resources/lod.mixins.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "required": true, - "package": "com.seibel.lod.common.mixins", - "compatibilityLevel": "JAVA_16", - "refmap": "lod.refmap.json", - "mixins": [ - "MixinWorldRenderer", - "MixinOptionsScreen" - ], - "minVersion": "0.8" -} \ No newline at end of file diff --git a/forge/src/main/resources/pack.mcmeta b/forge/src/main/resources/pack.mcmeta deleted file mode 100644 index 70b6321cb..000000000 --- a/forge/src/main/resources/pack.mcmeta +++ /dev/null @@ -1,6 +0,0 @@ -{ - "pack": { - "description": "", - "pack_format": 7 - } -} diff --git a/gradle.properties b/gradle.properties index 04574db74..6befa6a57 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,14 +1,12 @@ -org.gradle.jvmargs=-Xmx2048M +# Sets default memory used for gradle commands. Can be overridden by user or command line properties. +# This is required to provide enough memory for the Minecraft decompilation process. +org.gradle.jvmargs=-Xmx3G +org.gradle.daemon=false -# TODO: HEY REMEMBER TO UPDATE THE lod.accesswidener WHEN UPDATING TO 1.18 -minecraft_version=1.17.1 +# Minecraft +minecraft_version=1.18 +fabric_loader_version=0.12.6 -archives_base_name=DistantHorizons -mod_version=1.5.4a -maven_group=com.seibel.lod - -fabric_loader_version=0.11.6 -fabric_api_version=0.37.1+1.17 -modmenu_version=2.0.14 - -forge_version=37.1.0 \ No newline at end of file +# Mod dependencies +fabric_api_version=0.43.1+1.18 +modmenu_version=3.0.0 \ No newline at end of file diff --git a/gradlew b/gradlew index 744e882ed..1b6c78733 100755 --- a/gradlew +++ b/gradlew @@ -1,7 +1,7 @@ -#!/usr/bin/env sh +#!/bin/sh # -# Copyright 2015 the original author or authors. +# Copyright © 2015-2021 the original authors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -17,67 +17,101 @@ # ############################################################################## -## -## Gradle start up script for UN*X -## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# ############################################################################## # Attempt to set APP_HOME + # Resolve links: $0 may be a link -PRG="$0" -# Need this for relative symlinks. -while [ -h "$PRG" ] ; do - ls=`ls -ld "$PRG"` - link=`expr "$ls" : '.*-> \(.*\)$'` - if expr "$link" : '/.*' > /dev/null; then - PRG="$link" - else - PRG=`dirname "$PRG"`"/$link" - fi +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac done -SAVED="`pwd`" -cd "`dirname \"$PRG\"`/" >/dev/null -APP_HOME="`pwd -P`" -cd "$SAVED" >/dev/null + +APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit APP_NAME="Gradle" -APP_BASE_NAME=`basename "$0"` +APP_BASE_NAME=${0##*/} # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' # Use the maximum available, or set MAX_FD != -1 to use that value. -MAX_FD="maximum" +MAX_FD=maximum warn () { echo "$*" -} +} >&2 die () { echo echo "$*" echo exit 1 -} +} >&2 # OS specific support (must be 'true' or 'false'). cygwin=false msys=false darwin=false nonstop=false -case "`uname`" in - CYGWIN* ) - cygwin=true - ;; - Darwin* ) - darwin=true - ;; - MSYS* | MINGW* ) - msys=true - ;; - NONSTOP* ) - nonstop=true - ;; +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; esac CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar @@ -87,9 +121,9 @@ CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar if [ -n "$JAVA_HOME" ] ; then if [ -x "$JAVA_HOME/jre/sh/java" ] ; then # IBM's JDK on AIX uses strange locations for the executables - JAVACMD="$JAVA_HOME/jre/sh/java" + JAVACMD=$JAVA_HOME/jre/sh/java else - JAVACMD="$JAVA_HOME/bin/java" + JAVACMD=$JAVA_HOME/bin/java fi if [ ! -x "$JAVACMD" ] ; then die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME @@ -98,7 +132,7 @@ Please set the JAVA_HOME variable in your environment to match the location of your Java installation." fi else - JAVACMD="java" + JAVACMD=java which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. Please set the JAVA_HOME variable in your environment to match the @@ -106,80 +140,95 @@ location of your Java installation." fi # Increase the maximum file descriptors if we can. -if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then - MAX_FD_LIMIT=`ulimit -H -n` - if [ $? -eq 0 ] ; then - if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then - MAX_FD="$MAX_FD_LIMIT" - fi - ulimit -n $MAX_FD - if [ $? -ne 0 ] ; then - warn "Could not set maximum file descriptor limit: $MAX_FD" - fi - else - warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" - fi -fi - -# For Darwin, add options to specify how the application appears in the dock -if $darwin; then - GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" -fi - -# For Cygwin or MSYS, switch paths to Windows format before running java -if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then - APP_HOME=`cygpath --path --mixed "$APP_HOME"` - CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` - - JAVACMD=`cygpath --unix "$JAVACMD"` - - # We build the pattern for arguments to be converted via cygpath - ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` - SEP="" - for dir in $ROOTDIRSRAW ; do - ROOTDIRS="$ROOTDIRS$SEP$dir" - SEP="|" - done - OURCYGPATTERN="(^($ROOTDIRS))" - # Add a user-defined pattern to the cygpath arguments - if [ "$GRADLE_CYGPATTERN" != "" ] ; then - OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" - fi - # Now convert the arguments - kludge to limit ourselves to /bin/sh - i=0 - for arg in "$@" ; do - CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` - CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option - - if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition - eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` - else - eval `echo args$i`="\"$arg\"" - fi - i=`expr $i + 1` - done - case $i in - 0) set -- ;; - 1) set -- "$args0" ;; - 2) set -- "$args0" "$args1" ;; - 3) set -- "$args0" "$args1" "$args2" ;; - 4) set -- "$args0" "$args1" "$args2" "$args3" ;; - 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; - 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; - 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; - 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; - 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" esac fi -# Escape application args -save () { - for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done - echo " " -} -APP_ARGS=`save "$@"` +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. -# Collect all arguments for the java command, following the shell quoting and substitution rules -eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + +# Collect all arguments for the java command; +# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of +# shell script including quotes and variable substitutions, so put them in +# double quotes to make sure that they get re-expanded; and +# * put everything else in single quotes, so that it's not re-expanded. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' exec "$JAVACMD" "$@" diff --git a/common/src/main/java/com/seibel/lod/common/Config.java b/src/main/java/com/seibel/lod/common/Config.java similarity index 100% rename from common/src/main/java/com/seibel/lod/common/Config.java rename to src/main/java/com/seibel/lod/common/Config.java diff --git a/common/src/main/java/com/seibel/lod/common/LodCommonMain.java b/src/main/java/com/seibel/lod/common/LodCommonMain.java similarity index 100% rename from common/src/main/java/com/seibel/lod/common/LodCommonMain.java rename to src/main/java/com/seibel/lod/common/LodCommonMain.java diff --git a/common/src/main/java/com/seibel/lod/common/forge/LodForgeMethodCaller.java b/src/main/java/com/seibel/lod/common/forge/LodForgeMethodCaller.java similarity index 100% rename from common/src/main/java/com/seibel/lod/common/forge/LodForgeMethodCaller.java rename to src/main/java/com/seibel/lod/common/forge/LodForgeMethodCaller.java diff --git a/common/src/main/java/com/seibel/lod/common/mixins/MixinOptionsScreen.java b/src/main/java/com/seibel/lod/common/mixins/MixinOptionsScreen.java similarity index 100% rename from common/src/main/java/com/seibel/lod/common/mixins/MixinOptionsScreen.java rename to src/main/java/com/seibel/lod/common/mixins/MixinOptionsScreen.java diff --git a/common/src/main/java/com/seibel/lod/common/mixins/MixinWorldRenderer.java b/src/main/java/com/seibel/lod/common/mixins/MixinWorldRenderer.java similarity index 100% rename from common/src/main/java/com/seibel/lod/common/mixins/MixinWorldRenderer.java rename to src/main/java/com/seibel/lod/common/mixins/MixinWorldRenderer.java diff --git a/common/src/main/java/com/seibel/lod/common/wrappers/DependencySetup.java b/src/main/java/com/seibel/lod/common/wrappers/DependencySetup.java similarity index 95% rename from common/src/main/java/com/seibel/lod/common/wrappers/DependencySetup.java rename to src/main/java/com/seibel/lod/common/wrappers/DependencySetup.java index c6c15057c..9204ff3e8 100644 --- a/common/src/main/java/com/seibel/lod/common/wrappers/DependencySetup.java +++ b/src/main/java/com/seibel/lod/common/wrappers/DependencySetup.java @@ -1,7 +1,7 @@ package com.seibel.lod.common.wrappers; import com.seibel.lod.common.wrappers.block.BlockColorSingletonWrapper; -import com.seibel.lod.common.wrappers.minecraft.MinecraftRenderWrapper; +import com.seibel.lod.fabric.wrappers.minecraft.MinecraftRenderWrapper; import com.seibel.lod.common.wrappers.minecraft.MinecraftWrapper; import com.seibel.lod.core.handlers.IReflectionHandler; import com.seibel.lod.core.handlers.ReflectionHandler; diff --git a/common/src/main/java/com/seibel/lod/common/wrappers/McObjectConverter.java b/src/main/java/com/seibel/lod/common/wrappers/McObjectConverter.java similarity index 100% rename from common/src/main/java/com/seibel/lod/common/wrappers/McObjectConverter.java rename to src/main/java/com/seibel/lod/common/wrappers/McObjectConverter.java diff --git a/common/src/main/java/com/seibel/lod/common/wrappers/WrapperFactory.java b/src/main/java/com/seibel/lod/common/wrappers/WrapperFactory.java similarity index 97% rename from common/src/main/java/com/seibel/lod/common/wrappers/WrapperFactory.java rename to src/main/java/com/seibel/lod/common/wrappers/WrapperFactory.java index 4383458a8..78033aacf 100644 --- a/common/src/main/java/com/seibel/lod/common/wrappers/WrapperFactory.java +++ b/src/main/java/com/seibel/lod/common/wrappers/WrapperFactory.java @@ -28,7 +28,7 @@ import com.seibel.lod.core.wrapperInterfaces.world.IWorldWrapper; import com.seibel.lod.core.wrapperInterfaces.worldGeneration.AbstractWorldGeneratorWrapper; import com.seibel.lod.common.wrappers.block.BlockPosWrapper; import com.seibel.lod.common.wrappers.chunk.ChunkPosWrapper; -import com.seibel.lod.common.wrappers.worldGeneration.WorldGeneratorWrapper; +import com.seibel.lod.fabric.wrappers.worldGeneration.WorldGeneratorWrapper; /** * This handles creating abstract wrapper objects. diff --git a/common/src/main/java/com/seibel/lod/common/wrappers/WrapperUtil.java b/src/main/java/com/seibel/lod/common/wrappers/WrapperUtil.java similarity index 100% rename from common/src/main/java/com/seibel/lod/common/wrappers/WrapperUtil.java rename to src/main/java/com/seibel/lod/common/wrappers/WrapperUtil.java diff --git a/common/src/main/java/com/seibel/lod/common/wrappers/block/BlockColorSingletonWrapper.java b/src/main/java/com/seibel/lod/common/wrappers/block/BlockColorSingletonWrapper.java similarity index 100% rename from common/src/main/java/com/seibel/lod/common/wrappers/block/BlockColorSingletonWrapper.java rename to src/main/java/com/seibel/lod/common/wrappers/block/BlockColorSingletonWrapper.java diff --git a/common/src/main/java/com/seibel/lod/common/wrappers/block/BlockColorWrapper.java b/src/main/java/com/seibel/lod/common/wrappers/block/BlockColorWrapper.java similarity index 100% rename from common/src/main/java/com/seibel/lod/common/wrappers/block/BlockColorWrapper.java rename to src/main/java/com/seibel/lod/common/wrappers/block/BlockColorWrapper.java diff --git a/common/src/main/java/com/seibel/lod/common/wrappers/block/BlockPosWrapper.java b/src/main/java/com/seibel/lod/common/wrappers/block/BlockPosWrapper.java similarity index 100% rename from common/src/main/java/com/seibel/lod/common/wrappers/block/BlockPosWrapper.java rename to src/main/java/com/seibel/lod/common/wrappers/block/BlockPosWrapper.java diff --git a/common/src/main/java/com/seibel/lod/common/wrappers/block/BlockShapeWrapper.java b/src/main/java/com/seibel/lod/common/wrappers/block/BlockShapeWrapper.java similarity index 100% rename from common/src/main/java/com/seibel/lod/common/wrappers/block/BlockShapeWrapper.java rename to src/main/java/com/seibel/lod/common/wrappers/block/BlockShapeWrapper.java diff --git a/common/src/main/java/com/seibel/lod/common/wrappers/block/TextureAtlasSpriteWrapper.java b/src/main/java/com/seibel/lod/common/wrappers/block/TextureAtlasSpriteWrapper.java similarity index 100% rename from common/src/main/java/com/seibel/lod/common/wrappers/block/TextureAtlasSpriteWrapper.java rename to src/main/java/com/seibel/lod/common/wrappers/block/TextureAtlasSpriteWrapper.java diff --git a/common/src/main/java/com/seibel/lod/common/wrappers/chunk/ChunkPosWrapper.java b/src/main/java/com/seibel/lod/common/wrappers/chunk/ChunkPosWrapper.java similarity index 100% rename from common/src/main/java/com/seibel/lod/common/wrappers/chunk/ChunkPosWrapper.java rename to src/main/java/com/seibel/lod/common/wrappers/chunk/ChunkPosWrapper.java diff --git a/common/src/main/java/com/seibel/lod/common/wrappers/chunk/ChunkWrapper.java b/src/main/java/com/seibel/lod/common/wrappers/chunk/ChunkWrapper.java similarity index 97% rename from common/src/main/java/com/seibel/lod/common/wrappers/chunk/ChunkWrapper.java rename to src/main/java/com/seibel/lod/common/wrappers/chunk/ChunkWrapper.java index 311c44070..a3d7083fc 100644 --- a/common/src/main/java/com/seibel/lod/common/wrappers/chunk/ChunkWrapper.java +++ b/src/main/java/com/seibel/lod/common/wrappers/chunk/ChunkWrapper.java @@ -58,7 +58,7 @@ public class ChunkWrapper implements IChunkWrapper @Override public IBiomeWrapper getBiome(int xRel, int yAbs, int zRel) { - return BiomeWrapper.getBiomeWrapper(chunk.getBiomes().getNoiseBiome(xRel >> 2, yAbs >> 2, zRel >> 2)); + return BiomeWrapper.getBiomeWrapper(chunk.getNoiseBiome(xRel >> 2, yAbs >> 2, zRel >> 2)); } @Override diff --git a/common/src/main/java/com/seibel/lod/common/wrappers/config/ConfigGui.java b/src/main/java/com/seibel/lod/common/wrappers/config/ConfigGui.java similarity index 100% rename from common/src/main/java/com/seibel/lod/common/wrappers/config/ConfigGui.java rename to src/main/java/com/seibel/lod/common/wrappers/config/ConfigGui.java diff --git a/common/src/main/java/com/seibel/lod/common/wrappers/config/LodConfigWrapperSingleton.java b/src/main/java/com/seibel/lod/common/wrappers/config/LodConfigWrapperSingleton.java similarity index 100% rename from common/src/main/java/com/seibel/lod/common/wrappers/config/LodConfigWrapperSingleton.java rename to src/main/java/com/seibel/lod/common/wrappers/config/LodConfigWrapperSingleton.java diff --git a/common/src/main/java/com/seibel/lod/common/wrappers/config/TexturedButtonWidget.java b/src/main/java/com/seibel/lod/common/wrappers/config/TexturedButtonWidget.java similarity index 97% rename from common/src/main/java/com/seibel/lod/common/wrappers/config/TexturedButtonWidget.java rename to src/main/java/com/seibel/lod/common/wrappers/config/TexturedButtonWidget.java index 9701d9e82..63df62446 100644 --- a/common/src/main/java/com/seibel/lod/common/wrappers/config/TexturedButtonWidget.java +++ b/src/main/java/com/seibel/lod/common/wrappers/config/TexturedButtonWidget.java @@ -31,7 +31,7 @@ public class TexturedButtonWidget extends ImageButton { RenderSystem.setShader(GameRenderer::getPositionTexShader); RenderSystem.setShaderTexture(0, WIDGETS_LOCATION); RenderSystem.setShaderColor(1.0F, 1.0F, 1.0F, this.alpha); - int i = this.getYImage(this.isHovered()); + int i = this.getYImage(this.isHovered); RenderSystem.enableBlend(); RenderSystem.defaultBlendFunc(); RenderSystem.enableDepthTest(); diff --git a/common/src/main/java/com/seibel/lod/common/wrappers/minecraft/MinecraftWrapper.java b/src/main/java/com/seibel/lod/common/wrappers/minecraft/MinecraftWrapper.java similarity index 100% rename from common/src/main/java/com/seibel/lod/common/wrappers/minecraft/MinecraftWrapper.java rename to src/main/java/com/seibel/lod/common/wrappers/minecraft/MinecraftWrapper.java diff --git a/common/src/main/java/com/seibel/lod/common/wrappers/minecraft/ProfilerWrapper.java b/src/main/java/com/seibel/lod/common/wrappers/minecraft/ProfilerWrapper.java similarity index 100% rename from common/src/main/java/com/seibel/lod/common/wrappers/minecraft/ProfilerWrapper.java rename to src/main/java/com/seibel/lod/common/wrappers/minecraft/ProfilerWrapper.java diff --git a/common/src/main/java/com/seibel/lod/common/wrappers/misc/LightMapWrapper.java b/src/main/java/com/seibel/lod/common/wrappers/misc/LightMapWrapper.java similarity index 100% rename from common/src/main/java/com/seibel/lod/common/wrappers/misc/LightMapWrapper.java rename to src/main/java/com/seibel/lod/common/wrappers/misc/LightMapWrapper.java diff --git a/common/src/main/java/com/seibel/lod/common/wrappers/world/BiomeColorWrapperSingleton.java b/src/main/java/com/seibel/lod/common/wrappers/world/BiomeColorWrapperSingleton.java similarity index 100% rename from common/src/main/java/com/seibel/lod/common/wrappers/world/BiomeColorWrapperSingleton.java rename to src/main/java/com/seibel/lod/common/wrappers/world/BiomeColorWrapperSingleton.java diff --git a/common/src/main/java/com/seibel/lod/common/wrappers/world/BiomeWrapper.java b/src/main/java/com/seibel/lod/common/wrappers/world/BiomeWrapper.java similarity index 100% rename from common/src/main/java/com/seibel/lod/common/wrappers/world/BiomeWrapper.java rename to src/main/java/com/seibel/lod/common/wrappers/world/BiomeWrapper.java diff --git a/common/src/main/java/com/seibel/lod/common/wrappers/world/DimensionTypeWrapper.java b/src/main/java/com/seibel/lod/common/wrappers/world/DimensionTypeWrapper.java similarity index 100% rename from common/src/main/java/com/seibel/lod/common/wrappers/world/DimensionTypeWrapper.java rename to src/main/java/com/seibel/lod/common/wrappers/world/DimensionTypeWrapper.java diff --git a/common/src/main/java/com/seibel/lod/common/wrappers/world/WorldWrapper.java b/src/main/java/com/seibel/lod/common/wrappers/world/WorldWrapper.java similarity index 100% rename from common/src/main/java/com/seibel/lod/common/wrappers/world/WorldWrapper.java rename to src/main/java/com/seibel/lod/common/wrappers/world/WorldWrapper.java diff --git a/src/main/java/com/seibel/lod/core/ModInfo.java b/src/main/java/com/seibel/lod/core/ModInfo.java new file mode 100644 index 000000000..5a7e25f8b --- /dev/null +++ b/src/main/java/com/seibel/lod/core/ModInfo.java @@ -0,0 +1,42 @@ +/* + * This file is part of the Distant Horizon mod (formerly the LOD Mod), + * licensed under the GNU GPL v3 License. + * + * Copyright (C) 2020 James Seibel + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.seibel.lod.core; + +/** + * This file is similar to mcmod.info + *
+ * If you are looking at this mod's source code and don't + * know where to start. + * Go to the api/lod package (folder) and take a look at the ClientApi.java file, + * Pretty much all of the mod stems from there. + * + * @author James Seibel + * @version 11-29-2021 + */ +public final class ModInfo +{ + public static final String ID = "lod"; + /** The internal mod name */ + public static final String NAME = "DistantHorizons"; + /** Human readable version of NAME */ + public static final String READABLE_NAME = "Distant Horizons"; + public static final String API = "LodAPI"; + public static final String VERSION = "a1.5.4"; +} \ No newline at end of file diff --git a/src/main/java/com/seibel/lod/core/api/ApiShared.java b/src/main/java/com/seibel/lod/core/api/ApiShared.java new file mode 100644 index 000000000..36f8635b5 --- /dev/null +++ b/src/main/java/com/seibel/lod/core/api/ApiShared.java @@ -0,0 +1,53 @@ +/* + * This file is part of the Distant Horizon mod (formerly the LOD Mod), + * licensed under the GNU GPL v3 License. + * + * Copyright (C) 2020 James Seibel + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.seibel.lod.core.api; + +import com.seibel.lod.core.builders.bufferBuilding.LodBufferBuilderFactory; +import com.seibel.lod.core.builders.lodBuilding.LodBuilder; +import com.seibel.lod.core.objects.lod.LodWorld; + +/** + * This stores objects and variables that + * are shared between the different Core api classes. + * + * @author James Seibel + * @version 11-12-2021 + */ +public class ApiShared +{ + public ApiShared INSTANCE = new ApiShared(); + + public static final LodBufferBuilderFactory lodBufferBuilderFactory = new LodBufferBuilderFactory(); + public static final LodWorld lodWorld = new LodWorld(); + public static final LodBuilder lodBuilder = new LodBuilder(); + + /** Used to determine if the LODs should be regenerated */ + public static int previousChunkRenderDistance = 0; + /** Used to determine if the LODs should be regenerated */ + public static int previousLodRenderDistance = 0; + + + + private ApiShared() + { + + } + +} diff --git a/src/main/java/com/seibel/lod/core/api/ClientApi.java b/src/main/java/com/seibel/lod/core/api/ClientApi.java new file mode 100644 index 000000000..439e74ef0 --- /dev/null +++ b/src/main/java/com/seibel/lod/core/api/ClientApi.java @@ -0,0 +1,190 @@ +/* + * This file is part of the Distant Horizon mod (formerly the LOD Mod), + * licensed under the GNU GPL v3 License. + * + * Copyright (C) 2020 James Seibel + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.seibel.lod.core.api; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import com.seibel.lod.core.ModInfo; +import com.seibel.lod.core.objects.lod.LodDimension; +import com.seibel.lod.core.objects.math.Mat4f; +import com.seibel.lod.core.render.GLProxy; +import com.seibel.lod.core.render.LodRenderer; +import com.seibel.lod.core.util.DetailDistanceUtil; +import com.seibel.lod.core.util.SingletonHandler; +import com.seibel.lod.core.wrapperInterfaces.config.ILodConfigWrapperSingleton; +import com.seibel.lod.core.wrapperInterfaces.minecraft.IMinecraftRenderWrapper; +import com.seibel.lod.core.wrapperInterfaces.minecraft.IMinecraftWrapper; +import com.seibel.lod.core.wrapperInterfaces.minecraft.IProfilerWrapper; + +/** + * This holds the methods that should be called + * by the host mod loader (Fabric, Forge, etc.). + * Specifically for the client. + * + * @author James Seibel + * @version 11-12-2021 + */ +public class ClientApi +{ + public static final ClientApi INSTANCE = new ClientApi(); + public static final Logger LOGGER = LogManager.getLogger(ModInfo.NAME); + + public static LodRenderer renderer = new LodRenderer(ApiShared.lodBufferBuilderFactory); + + private static final IMinecraftWrapper MC = SingletonHandler.get(IMinecraftWrapper.class); + private static final IMinecraftRenderWrapper MC_RENDER = SingletonHandler.get(IMinecraftRenderWrapper.class); + private static final ILodConfigWrapperSingleton CONFIG = SingletonHandler.get(ILodConfigWrapperSingleton.class); + private static final EventApi EVENT_API = EventApi.INSTANCE; + + /** + * there is some setup that should only happen once, + * once this is true that setup has completed + */ + private boolean firstTimeSetupComplete = false; + private boolean configOverrideReminderPrinted = false; + + + + private ClientApi() + { + + } + + + + + public void renderLods(Mat4f mcModelViewMatrix, Mat4f mcProjectionMatrix, float partialTicks) + { + // comment out when creating a release + applyConfigOverrides(); + + // clear any out of date objects + MC.clearFrameObjectCache(); + + try + { + // only run the first time setup once + if (!firstTimeSetupComplete) + firstFrameSetup(); + + + if (!MC.playerExists() || ApiShared.lodWorld.getIsWorldNotLoaded()) + return; + + LodDimension lodDim = ApiShared.lodWorld.getLodDimension(MC.getCurrentDimension()); + if (lodDim == null) + return; + + DetailDistanceUtil.updateSettings(); + EVENT_API.viewDistanceChangedEvent(); + EVENT_API.playerMoveEvent(lodDim); + + lodDim.cutRegionNodesAsync(MC.getPlayerBlockPos().getX(), MC.getPlayerBlockPos().getZ()); + lodDim.expandOrLoadRegionsAsync(MC.getPlayerBlockPos().getX(), MC.getPlayerBlockPos().getZ()); + + + + if (CONFIG.client().advanced().debugging().getDrawLods()) + { + // Note to self: + // if "unspecified" shows up in the pie chart, it is + // possibly because the amount of time between sections + // is too small for the profiler to measure + IProfilerWrapper profiler = MC.getProfiler(); + profiler.pop(); // get out of "terrain" + profiler.push("LOD"); + + + ClientApi.renderer.drawLODs(lodDim, mcModelViewMatrix, mcProjectionMatrix, partialTicks, MC.getProfiler()); + + profiler.pop(); // end LOD + profiler.push("terrain"); // go back into "terrain" + } + + + + // these can't be set until after the buffers are built (in renderer.drawLODs) + // otherwise the buffers may be set to the wrong size, or not changed at all + ApiShared.previousChunkRenderDistance = MC_RENDER.getRenderDistance(); + ApiShared.previousLodRenderDistance = CONFIG.client().graphics().quality().getLodChunkRenderDistance(); + } + catch (Exception e) + { + ClientApi.LOGGER.error("client proxy: " + e.getMessage()); + e.printStackTrace(); + } + } + + /** used in a development environment to change settings on the fly */ + private void applyConfigOverrides() + { + // remind the developer(s) that the config override is active + if (!configOverrideReminderPrinted) + { + MC.sendChatMessage(ModInfo.READABLE_NAME + " experimental build " + ModInfo.VERSION); + MC.sendChatMessage("You are running a unsupported version of the mod!"); + MC.sendChatMessage("Here be dragons!"); + + configOverrideReminderPrinted = true; + } + +// CONFIG.client().worldGenerator().setDistanceGenerationMode(DistanceGenerationMode.FULL); + +// CONFIG.client().worldGenerator().setGenerationPriority(GenerationPriority.AUTO); + +// CONFIG.client().graphics().advancedGraphics().setGpuUploadMethod(GpuUploadMethod.BUFFER_STORAGE); +// CONFIG.client().graphics().quality().setLodChunkRenderDistance(128); + +// CONFIG.client().graphics().fogQuality().setFogDrawMode(FogDrawMode.FOG_ENABLED); +// CONFIG.client().graphics().fogQuality().setFogDistance(FogDistance.FAR); +// CONFIG.client().graphics().fogQuality().setDisableVanillaFog(true); + +// CONFIG.client().advanced().buffers().setRebuildTimes(BufferRebuildTimes.FREQUENT); + + + CONFIG.client().advanced().debugging().setDebugKeybindingsEnabled(true); + } + + + + + //=================// + // Lod maintenance // + //=================// + + /** This event is called once during the first frame Minecraft renders in the world. */ + public void firstFrameSetup() + { + // make sure the GLProxy is created before the LodBufferBuilder needs it + GLProxy.getInstance(); + + firstTimeSetupComplete = true; + } + + + + + + + + + +} diff --git a/src/main/java/com/seibel/lod/core/api/EventApi.java b/src/main/java/com/seibel/lod/core/api/EventApi.java new file mode 100644 index 000000000..580319631 --- /dev/null +++ b/src/main/java/com/seibel/lod/core/api/EventApi.java @@ -0,0 +1,244 @@ +/* + * This file is part of the Distant Horizon mod (formerly the LOD Mod), + * licensed under the GNU GPL v3 License. + * + * Copyright (C) 2020 James Seibel + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.seibel.lod.core.api; + +import org.lwjgl.glfw.GLFW; + +import com.seibel.lod.core.builders.worldGeneration.LodGenWorker; +import com.seibel.lod.core.builders.worldGeneration.LodWorldGenerator; +import com.seibel.lod.core.enums.config.DistanceGenerationMode; +import com.seibel.lod.core.objects.lod.LodDimension; +import com.seibel.lod.core.objects.lod.RegionPos; +import com.seibel.lod.core.render.LodRenderer; +import com.seibel.lod.core.util.DataPointUtil; +import com.seibel.lod.core.util.DetailDistanceUtil; +import com.seibel.lod.core.util.LodUtil; +import com.seibel.lod.core.util.SingletonHandler; +import com.seibel.lod.core.util.ThreadMapUtil; +import com.seibel.lod.core.wrapperInterfaces.chunk.IChunkWrapper; +import com.seibel.lod.core.wrapperInterfaces.config.ILodConfigWrapperSingleton; +import com.seibel.lod.core.wrapperInterfaces.minecraft.IMinecraftWrapper; +import com.seibel.lod.core.wrapperInterfaces.world.IDimensionTypeWrapper; +import com.seibel.lod.core.wrapperInterfaces.world.IWorldWrapper; + +/** + * This holds the methods that should be called + * by the host mod loader (Fabric, Forge, etc.). + * Specifically server and client events. + * + * @author James Seibel + * @version 11-12-2021 + */ +public class EventApi +{ + public static final EventApi INSTANCE = new EventApi(); + + private static final IMinecraftWrapper MC = SingletonHandler.get(IMinecraftWrapper.class); + private static final ILodConfigWrapperSingleton CONFIG = SingletonHandler.get(ILodConfigWrapperSingleton.class); + + /** + * can be set if we want to recalculate variables related + * to the LOD view distance + */ + private boolean recalculateWidths = false; + + + private EventApi() + { + + } + + + + + //=============// + // tick events // + //=============// + + public void serverTickEvent() + { + if (!MC.playerExists() || ApiShared.lodWorld.getIsWorldNotLoaded()) + return; + + LodDimension lodDim = ApiShared.lodWorld.getLodDimension(MC.getCurrentDimension()); + if (lodDim == null) + return; + + LodWorldGenerator.INSTANCE.queueGenerationRequests(lodDim, ClientApi.renderer, ApiShared.lodBuilder); + } + + + + + //==============// + // world events // + //==============// + + public void chunkLoadEvent(IChunkWrapper chunk, IDimensionTypeWrapper dimType) + { + ApiShared.lodBuilder.generateLodNodeAsync(chunk, ApiShared.lodWorld, dimType, DistanceGenerationMode.FULL); + } + + public void worldSaveEvent() + { + ApiShared.lodWorld.saveAllDimensions(); + } + + /** This is also called when a new dimension loads */ + public void worldLoadEvent(IWorldWrapper world) + { + DataPointUtil.WORLD_HEIGHT = world.getHeight(); + //LodNodeGenWorker.restartExecutorService(); + //ThreadMapUtil.clearMaps(); + + // the player just loaded a new world/dimension + ApiShared.lodWorld.selectWorld(LodUtil.getWorldID(world)); + + // make sure the correct LODs are being rendered + // (if this isn't done the previous world's LODs may be drawn) + ClientApi.renderer.regenerateLODsNextFrame(); + } + + /** This is also called when the user disconnects from a server+ */ + public void worldUnloadEvent() + { + // the player just unloaded a world/dimension + ThreadMapUtil.clearMaps(); + + new Thread(() -> checkIfDisconnectedFromServer()).start(); + } + private void checkIfDisconnectedFromServer() + { + try + { + // world unloading events are called before disconnecting from the server, + // so we need to wait a second for MC to disconnect + Thread.sleep(1000); + } + catch (InterruptedException e) + { + // this should never happen, but just in case + e.printStackTrace(); + } + + + if (MC.getWrappedClientWorld() == null || (!MC.connectedToServer() && !MC.hasSinglePlayerServer())) + { + // the player just left the server + + // TODO should "resetMod()" be called here? -James + + // if this isn't done unfinished tasks may be left in the queue + // preventing new LodChunks form being generated + LodGenWorker.restartExecutorService(); + + LodWorldGenerator.INSTANCE.numberOfChunksWaitingToGenerate.set(0); + ApiShared.lodWorld.deselectWorld(); + + + // prevent issues related to the buffer builder + // breaking or retaining previous data when changing worlds. + ClientApi.renderer.destroyBuffers(); + recalculateWidths = true; + ClientApi.renderer = new LodRenderer(ApiShared.lodBufferBuilderFactory); + + + // make sure the nulled objects are freed. + // (this prevents an out of memory error when + // changing worlds) + System.gc(); + } + } + + public void blockChangeEvent(IChunkWrapper chunk, IDimensionTypeWrapper dimType) + { + // recreate the LOD where the blocks were changed + ApiShared.lodBuilder.generateLodNodeAsync(chunk, ApiShared.lodWorld, dimType); + } + + + + + //=============// + // Misc Events // + //=============// + + public void onKeyInput(int key, int keyAction) + { + if (CONFIG.client().advanced().debugging().getDebugKeybindingsEnabled()) + { + if (key == GLFW.GLFW_KEY_F4 && keyAction == GLFW.GLFW_PRESS) + { + CONFIG.client().advanced().debugging().setDebugMode(CONFIG.client().advanced().debugging().getDebugMode().getNext()); + } + + if (key == GLFW.GLFW_KEY_F6 && keyAction == GLFW.GLFW_PRESS) + { + CONFIG.client().advanced().debugging().setDrawLods(!CONFIG.client().advanced().debugging().getDrawLods()); + } + } + } + + /** Re-centers the given LodDimension if it needs to be. */ + public void playerMoveEvent(LodDimension lodDim) + { + // make sure the dimension is centered + RegionPos playerRegionPos = new RegionPos(MC.getPlayerBlockPos()); + RegionPos worldRegionOffset = new RegionPos(playerRegionPos.x - lodDim.getCenterRegionPosX(), playerRegionPos.z - lodDim.getCenterRegionPosZ()); + if (worldRegionOffset.x != 0 || worldRegionOffset.z != 0) + { + ApiShared.lodWorld.saveAllDimensions(); + lodDim.move(worldRegionOffset); + //LOGGER.info("offset: " + worldRegionOffset.x + "," + worldRegionOffset.z + "\t center: " + lodDim.getCenterX() + "," + lodDim.getCenterZ()); + } + } + + /** Re-sizes all LodDimensions if they need to be. */ + public void viewDistanceChangedEvent() + { + // calculate how wide the dimension(s) should be in regions + int chunksWide; + if (MC.getWrappedClientWorld().getDimensionType().hasCeiling()) + chunksWide = Math.min(CONFIG.client().graphics().quality().getLodChunkRenderDistance(), LodUtil.CEILED_DIMENSION_MAX_RENDER_DISTANCE) * 2 + 1; + else + chunksWide = CONFIG.client().graphics().quality().getLodChunkRenderDistance() * 2 + 1; + + int newWidth = (int) Math.ceil(chunksWide / (float) LodUtil.REGION_WIDTH_IN_CHUNKS); + // make sure we have an odd number of regions + newWidth += (newWidth & 1) == 0 ? 1 : 2; + + // do the dimensions need to change in size? + if (ApiShared.lodBuilder.defaultDimensionWidthInRegions != newWidth || recalculateWidths) + { + ApiShared.lodWorld.saveAllDimensions(); + + // update the dimensions to fit the new width + ApiShared.lodWorld.resizeDimensionRegionWidth(newWidth); + ApiShared.lodBuilder.defaultDimensionWidthInRegions = newWidth; + ClientApi.renderer.setupBuffers(ApiShared.lodWorld.getLodDimension(MC.getCurrentDimension())); + + recalculateWidths = false; + //LOGGER.info("new dimension width in regions: " + newWidth + "\t potential: " + newWidth ); + } + DetailDistanceUtil.updateSettings(); + } + + +} diff --git a/src/main/java/com/seibel/lod/core/builders/bufferBuilding/LodBufferBuilderFactory.java b/src/main/java/com/seibel/lod/core/builders/bufferBuilding/LodBufferBuilderFactory.java new file mode 100644 index 000000000..ab792b48c --- /dev/null +++ b/src/main/java/com/seibel/lod/core/builders/bufferBuilding/LodBufferBuilderFactory.java @@ -0,0 +1,1062 @@ +/* + * This file is part of the Distant Horizon mod (formerly the LOD Mod), + * licensed under the GNU GPL v3 License. + * + * Copyright (C) 2020 James Seibel + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.seibel.lod.core.builders.bufferBuilding; + +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.concurrent.locks.ReentrantLock; + +import org.lwjgl.opengl.GL11; +import org.lwjgl.opengl.GL15; +import org.lwjgl.opengl.GL30; +import org.lwjgl.opengl.GL45; + +import com.google.common.util.concurrent.ThreadFactoryBuilder; +import com.seibel.lod.core.api.ClientApi; +import com.seibel.lod.core.enums.LodDirection; +import com.seibel.lod.core.enums.config.GpuUploadMethod; +import com.seibel.lod.core.enums.config.VanillaOverdraw; +import com.seibel.lod.core.enums.rendering.GLProxyContext; +import com.seibel.lod.core.objects.PosToRenderContainer; +import com.seibel.lod.core.objects.VertexOptimizer; +import com.seibel.lod.core.objects.lod.LodDimension; +import com.seibel.lod.core.objects.lod.LodRegion; +import com.seibel.lod.core.objects.lod.RegionPos; +import com.seibel.lod.core.objects.opengl.LodBufferBuilder; +import com.seibel.lod.core.objects.opengl.LodVertexBuffer; +import com.seibel.lod.core.render.GLProxy; +import com.seibel.lod.core.render.LodRenderer; +import com.seibel.lod.core.util.DataPointUtil; +import com.seibel.lod.core.util.DetailDistanceUtil; +import com.seibel.lod.core.util.LevelPosUtil; +import com.seibel.lod.core.util.LodThreadFactory; +import com.seibel.lod.core.util.LodUtil; +import com.seibel.lod.core.util.SingletonHandler; +import com.seibel.lod.core.util.ThreadMapUtil; +import com.seibel.lod.core.wrapperInterfaces.IWrapperFactory; +import com.seibel.lod.core.wrapperInterfaces.block.AbstractBlockPosWrapper; +import com.seibel.lod.core.wrapperInterfaces.chunk.AbstractChunkPosWrapper; +import com.seibel.lod.core.wrapperInterfaces.config.ILodConfigWrapperSingleton; +import com.seibel.lod.core.wrapperInterfaces.minecraft.IMinecraftWrapper; + +/** + * This object creates the buffers that are + * rendered by the LodRenderer. + * + * @author James Seibel + * @version 11-29-2021 + */ +public class LodBufferBuilderFactory +{ + private static final ILodConfigWrapperSingleton CONFIG = SingletonHandler.get(ILodConfigWrapperSingleton.class); + private static final IMinecraftWrapper MC = SingletonHandler.get(IMinecraftWrapper.class); + private static final IWrapperFactory WRAPPER_FACTORY = SingletonHandler.get(IWrapperFactory.class); + + /** The thread used to generate new LODs off the main thread. */ + public static final ExecutorService mainGenThread = Executors.newSingleThreadExecutor(new LodThreadFactory(LodBufferBuilderFactory.class.getSimpleName() + " - main")); + /** The threads used to generate buffers. */ + public static final ExecutorService bufferBuilderThreads = Executors.newFixedThreadPool(CONFIG.client().advanced().threading().getNumberOfBufferBuilderThreads(), new ThreadFactoryBuilder().setNameFormat("Buffer-Builder-%d").build()); + + + + /** + * When uploading to a buffer that is too small, + * recreate it this many times bigger than the upload payload + */ + public static final double BUFFER_EXPANSION_MULTIPLIER = 1.5; + + /** + * When buffers are first created they are allocated to this size (in Bytes). + * This size will be too small, more than likely. The buffers will be expanded + * when need be to fit the larger sizes. + */ + public static final int DEFAULT_MEMORY_ALLOCATION = 1024; + + + + public static int skyLightPlayer = 15; + + /** + * How many buffers there are for the given region.
+ * This is done because some regions may require more memory than + * can be directly allocated, so we split the regions into smaller sections.
+ * This keeps track of those sections. + */ + public volatile int[][] numberOfBuffersPerRegion; + + /** Stores the vertices when building the VBOs */ + public volatile LodBufferBuilder[][][] buildableBuffers; + + /** The OpenGL IDs of the storage buffers used by the buildableVbos */ + public int[][][] buildableStorageBufferIds; + /** The OpenGL IDs of the storage buffers used by the drawableVbos */ + public int[][][] drawableStorageBufferIds; + + /** Used when building new VBOs */ + public volatile LodVertexBuffer[][][] buildableVbos; + /** VBOs that are sent over to the LodNodeRenderer */ + public volatile LodVertexBuffer[][][] drawableVbos; + + /** + * if this is true the LOD buffers are currently being + * regenerated. + */ + public boolean generatingBuffers = false; + + /** + * if this is true new LOD buffers have been generated + * and are waiting to be swapped with the drawable buffers + */ + private boolean switchVbos = false; + + /** Size of the buffer builders in bytes last time we created them */ + public int previousBufferSize = 0; + + /** Width of the dimension in regions last time we created the buffers */ + public int previousRegionWidth = 0; + + /** this is used to prevent multiple threads creating, destroying, or using the buffers at the same time */ + private final ReentrantLock bufferLock = new ReentrantLock(); + + private volatile VertexOptimizer[][] vertexOptimizerCache; + private volatile PosToRenderContainer[][] setsToRender; + private volatile RegionPos center; + + /** + * This is the ChunkPosWrapper the player was at the last time the buffers were built. + * IE the center of the buffers last time they were built + */ + private volatile AbstractChunkPosWrapper drawableCenterChunkPos = WRAPPER_FACTORY.createChunkPos(); + private volatile AbstractChunkPosWrapper buildableCenterChunkPos = WRAPPER_FACTORY.createChunkPos(); + + + + + + + public LodBufferBuilderFactory() + { + + } + + /** + * Create a thread to asynchronously generate LOD buffers + * centered around the given camera X and Z. + *
+ * This method will write to the drawable near and far buffers. + *
+ * 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, + AbstractBlockPosWrapper playerBlockPos, boolean fullRegen) + { + + // only allow one generation process to happen at a time + if (generatingBuffers) + return; + + if (buildableBuffers == null) + // setupBuffers hasn't been called yet + return; + + if (MC.getCurrentLightMap() == null) + // the lighting hasn't loaded yet + return; + + generatingBuffers = true; + + + Thread thread = new Thread(() -> generateLodBuffersThread(renderer, lodDim, playerBlockPos, fullRegen)); + + mainGenThread.execute(thread); + } + + // this was pulled out as a separate method so that it could be + // more easily edited by hot swapping. Because, As far as James is aware + // you can't hot swap lambda expressions. + private void generateLodBuffersThread(LodRenderer renderer, LodDimension lodDim, + AbstractBlockPosWrapper playerBlockPos, boolean fullRegen) + { + bufferLock.lock(); + + try + { + // round the player's block position down to the nearest chunk BlockPos + AbstractChunkPosWrapper playerChunkPos = WRAPPER_FACTORY.createChunkPos(playerBlockPos); + AbstractBlockPosWrapper playerBlockPosRounded = playerChunkPos.getWorldPosition(); + + + //long startTime = System.currentTimeMillis(); + + ArrayList> nodeToRenderThreads = new ArrayList<>(lodDim.getWidth() * lodDim.getWidth()); + + startBuffers(fullRegen, lodDim); + + + RegionPos playerRegionPos = new RegionPos(playerChunkPos); + if (center == null) + center = playerRegionPos; + + if (setsToRender == null) + setsToRender = new PosToRenderContainer[lodDim.getWidth()][lodDim.getWidth()]; + + if (setsToRender.length != lodDim.getWidth()) + setsToRender = new PosToRenderContainer[lodDim.getWidth()][lodDim.getWidth()]; + + if (vertexOptimizerCache == null) + vertexOptimizerCache = new VertexOptimizer[lodDim.getWidth()][lodDim.getWidth()]; + + if (vertexOptimizerCache.length != lodDim.getWidth()) + vertexOptimizerCache = new VertexOptimizer[lodDim.getWidth()][lodDim.getWidth()]; + + // this will be the center of the VBOs once they have been built + buildableCenterChunkPos = playerChunkPos; + + + //================================// + // create the nodeToRenderThreads // + //================================// + + skyLightPlayer = MC.getWrappedClientWorld().getSkyLight(playerBlockPos); + + for (int xRegion = 0; xRegion < lodDim.getWidth(); xRegion++) + { + for (int zRegion = 0; zRegion < lodDim.getWidth(); zRegion++) + { + if (lodDim.doesRegionNeedBufferRegen(xRegion, zRegion) || fullRegen) + { + RegionPos regionPos = new RegionPos( + xRegion + lodDim.getCenterRegionPosX() - lodDim.getWidth() / 2, + zRegion + lodDim.getCenterRegionPosZ() - lodDim.getWidth() / 2); + + // local position in the vbo and bufferBuilder arrays + LodBufferBuilder[] currentBuffers = buildableBuffers[xRegion][zRegion]; + LodRegion region = lodDim.getRegion(regionPos.x, regionPos.z); + + if (region == null) + continue; + + // make sure the buffers weren't + // changed while we were running this method + if (currentBuffers == null || !currentBuffers[0].building()) + { + ClientApi.LOGGER.info("Buffer building quit early"); + return; + } + + byte minDetail = region.getMinDetailLevel(); + + + final int xR = xRegion; + final int zR = zRegion; + + //we create the Callable to use for the buffer builder creation + Callable dataToRenderThread = () -> + { + //Variable initialization + byte detailLevel; + int posX; + int posZ; + int xAdj; + int zAdj; + int bufferIndex; + boolean posNotInPlayerChunk; + boolean adjPosInPlayerChunk; + VertexOptimizer vertexOptimizer = ThreadMapUtil.getBox(); + boolean[] adjShadeDisabled = ThreadMapUtil.getAdjShadeDisabledArray(); + + // determine how many LODs we can stack vertically + int maxVerticalData = DetailDistanceUtil.getMaxVerticalData((byte) 0); + + //we get or create the map that will contain the adj data + Map adjData = ThreadMapUtil.getAdjDataArray(maxVerticalData); + + //previous setToRender cache + if (setsToRender[xR][zR] == null) + setsToRender[xR][zR] = new PosToRenderContainer(minDetail, regionPos.x, regionPos.z); + + + //We ask the lod dimension which block we have to render given the player position + PosToRenderContainer posToRender = setsToRender[xR][zR]; + posToRender.clear(minDetail, regionPos.x, regionPos.z); + + lodDim.getPosToRender( + posToRender, + regionPos, + playerBlockPosRounded.getX(), + playerBlockPosRounded.getZ()); + + + + // keep a local version, so we don't have to worry about indexOutOfBounds Exceptions + // if it changes in the LodRenderer while we are working here + boolean[][] vanillaRenderedChunks = renderer.vanillaRenderedChunks; + short gameChunkRenderDistance = (short) (vanillaRenderedChunks.length / 2 - 1); + + + + for (int index = 0; index < posToRender.getNumberOfPos(); index++) + { + bufferIndex = index % currentBuffers.length; + detailLevel = posToRender.getNthDetailLevel(index); + posX = posToRender.getNthPosX(index); + posZ = posToRender.getNthPosZ(index); + + int chunkXdist = LevelPosUtil.getChunkPos(detailLevel, posX) - playerChunkPos.getX(); + int chunkZdist = LevelPosUtil.getChunkPos(detailLevel, posZ) - playerChunkPos.getZ(); + + //We don't want to render this fake block if + //The block is inside the render distance with, is not bigger than a chunk and is positioned in a chunk set as vanilla rendered + // + //The block is in the player chunk or in a chunk adjacent to the player + if(isThisPositionGoingToBeRendered(detailLevel, posX, posZ, playerChunkPos, vanillaRenderedChunks, gameChunkRenderDistance)) + { + continue; + } + + //we check if the block to render is not in player chunk + posNotInPlayerChunk = !(chunkXdist == 0 && chunkZdist == 0); + + // We extract the adj data in the four cardinal direction + + // we first reset the adjShadeDisabled. This is used to disable the shade on the border when we have transparent block like water or glass + // to avoid having a "darker border" underground + Arrays.fill(adjShadeDisabled, false); + + //We check every adj block in each direction + for (LodDirection lodDirection : VertexOptimizer.ADJ_DIRECTIONS) + { + + xAdj = posX + VertexOptimizer.DIRECTION_NORMAL_MAP.get(lodDirection).x; + zAdj = posZ + VertexOptimizer.DIRECTION_NORMAL_MAP.get(lodDirection).z; + long data; + chunkXdist = LevelPosUtil.getChunkPos(detailLevel, xAdj) - playerChunkPos.getX(); + chunkZdist = LevelPosUtil.getChunkPos(detailLevel, zAdj) - playerChunkPos.getZ(); + adjPosInPlayerChunk = (chunkXdist == 0 && chunkZdist == 0); + + //If the adj block is rendered in the same region and with same detail + // and is positioned in a place that is not going to be rendered by vanilla game + // then we can set this position as adj + // We avoid cases where the adjPosition is in player chunk while the position is not + // to always have a wall underwater + if(posToRender.contains(detailLevel, xAdj, zAdj) + && !isThisPositionGoingToBeRendered(detailLevel, xAdj, zAdj, playerChunkPos, vanillaRenderedChunks, gameChunkRenderDistance) + && !(posNotInPlayerChunk && adjPosInPlayerChunk)) + { + for (int verticalIndex = 0; verticalIndex < lodDim.getMaxVerticalData(detailLevel, xAdj, zAdj); verticalIndex++) + { + data = lodDim.getData(detailLevel, xAdj, zAdj, verticalIndex); + adjShadeDisabled[VertexOptimizer.DIRECTION_INDEX.get(lodDirection)] = false; + adjData.get(lodDirection)[verticalIndex] = data; + } + } + else + { + //Otherwise, we check if this position is + data = lodDim.getSingleData(detailLevel, xAdj, zAdj); + + adjData.get(lodDirection)[0] = DataPointUtil.EMPTY_DATA; + + if ((isThisPositionGoingToBeRendered(detailLevel, xAdj, zAdj, playerChunkPos, vanillaRenderedChunks, gameChunkRenderDistance) || (posNotInPlayerChunk && adjPosInPlayerChunk)) + && !DataPointUtil.isVoid(data)) + { + adjShadeDisabled[VertexOptimizer.DIRECTION_INDEX.get(lodDirection)] = DataPointUtil.getAlpha(data) < 255; + } + } + } + + + // We render every vertical lod present in this position + // We only stop when we find a block that is void or non-existing block + long data; + for (int verticalIndex = 0; verticalIndex < lodDim.getMaxVerticalData(detailLevel, posX, posZ); verticalIndex++) + { + + //we get the above block as adj UP + if (verticalIndex > 0) + adjData.get(LodDirection.UP)[0] = lodDim.getData(detailLevel, posX, posZ, verticalIndex - 1); + else + adjData.get(LodDirection.UP)[0] = DataPointUtil.EMPTY_DATA; + + + //we get the below block as adj DOWN + if (verticalIndex < lodDim.getMaxVerticalData(detailLevel, posX, posZ) - 1) + adjData.get(LodDirection.DOWN)[0] = lodDim.getData(detailLevel, posX, posZ, verticalIndex + 1); + else + adjData.get(LodDirection.DOWN)[0] = DataPointUtil.EMPTY_DATA; + + //We extract the data to render + data = lodDim.getData(detailLevel, posX, posZ, verticalIndex); + + //If the data is not renderable (Void or non-existing) we stop since there is no data left in this position + if (DataPointUtil.isVoid(data) || !DataPointUtil.doesItExist(data)) + break; + + //We send the call to create the vertices + CONFIG.client().graphics().advancedGraphics().getLodTemplate().template.addLodToBuffer(currentBuffers[bufferIndex], playerBlockPosRounded, data, adjData, + detailLevel, posX, posZ, vertexOptimizer, renderer.previousDebugMode, adjShadeDisabled); + } + + } // for pos to in list to render + // the thread executed successfully + return true; + }; + + nodeToRenderThreads.add(dataToRenderThread); + + } + } // region z + } // region z + + + //long executeStart = System.currentTimeMillis(); + // wait for all threads to finish + List> futuresBuffer = bufferBuilderThreads.invokeAll(nodeToRenderThreads); + for (Future future : futuresBuffer) + { + // the future will be false if its thread failed + if (!future.get()) + { + ClientApi.LOGGER.warn("LodBufferBuilder ran into trouble and had to start over."); + break; + } + } + //long executeEnd = System.currentTimeMillis(); + + + //long endTime = System.currentTimeMillis(); + //long buildTime = endTime - startTime; + //long executeTime = executeEnd - executeStart; + +// ClientProxy.LOGGER.info("Thread Build time: " + buildTime + " ms" + '\n' + +// "thread execute time: " + executeTime + " ms"); + + // mark that the buildable buffers as ready to swap + switchVbos = true; + } + catch (Exception e) + { + ClientApi.LOGGER.warn("\"LodNodeBufferBuilder.generateLodBuffersAsync\" ran into trouble: "); + e.printStackTrace(); + } + finally + { + try + { + // clean up any potentially open resources + if (buildableBuffers != null) + closeBuffers(fullRegen, lodDim); + + // upload the new buffers + uploadBuffers(fullRegen, lodDim); + } + catch (Exception e) + { + ClientApi.LOGGER.warn("\"LodNodeBufferBuilder.generateLodBuffersAsync\" was unable to upload the buffers to the GPU: " + e.getMessage()); + e.printStackTrace(); + } + + // regardless of whether we were able to successfully create + // the buffers, we are done generating. + generatingBuffers = false; + bufferLock.unlock(); + } + } + + private boolean isThisPositionGoingToBeRendered(byte detailLevel, int posX, int posZ, AbstractChunkPosWrapper playerChunkPos, boolean[][] vanillaRenderedChunks, int gameChunkRenderDistance){ + + + // skip any chunks that Minecraft is going to render + int chunkXdist = LevelPosUtil.getChunkPos(detailLevel, posX) - playerChunkPos.getX(); + int chunkZdist = LevelPosUtil.getChunkPos(detailLevel, posZ) - playerChunkPos.getZ(); + + // check if the chunk is on the border + boolean isItBorderPos; + if (CONFIG.client().graphics().advancedGraphics().getVanillaOverdraw() == VanillaOverdraw.BORDER) + isItBorderPos = LodUtil.isBorderChunk(vanillaRenderedChunks, chunkXdist + gameChunkRenderDistance + 1, chunkZdist + gameChunkRenderDistance + 1); + else + isItBorderPos = false; + + + //boolean smallRenderDistance = gameChunkRenderDistance <= LodUtil.MINIMUM_RENDER_DISTANCE_FOR_PARTIAL_OVERDRAW; + + // get the positions that will be rendered + + return (gameChunkRenderDistance >= Math.abs(chunkXdist) + && gameChunkRenderDistance >= Math.abs(chunkZdist) + && detailLevel <= LodUtil.CHUNK_DETAIL_LEVEL + && vanillaRenderedChunks[chunkXdist + gameChunkRenderDistance + 1][chunkZdist + gameChunkRenderDistance + 1]) + && (!isItBorderPos); + } + + + + + + + //===============================// + // BufferBuilder related methods // + //===============================// + + /** + * Called from the LodRenderer to create the + * BufferBuilders.

+ *

+ * May have to wait for the bufferLock to open. + */ + public void setupBuffers(LodDimension lodDimension) + { + try + { + bufferLock.lock(); + + int numbRegionsWide = lodDimension.getWidth(); + long regionMemoryRequired; + int numberOfBuffers; + + GLProxy glProxy = GLProxy.getInstance(); + GLProxyContext oldContext = glProxy.getGlContext(); + glProxy.setGlContext(GLProxyContext.LOD_BUILDER); + + + previousRegionWidth = numbRegionsWide; + numberOfBuffersPerRegion = new int[numbRegionsWide][numbRegionsWide]; + buildableBuffers = new LodBufferBuilder[numbRegionsWide][numbRegionsWide][]; + + buildableVbos = new LodVertexBuffer[numbRegionsWide][numbRegionsWide][]; + drawableVbos = new LodVertexBuffer[numbRegionsWide][numbRegionsWide][]; + + if (glProxy.bufferStorageSupported) + { + buildableStorageBufferIds = new int[numbRegionsWide][numbRegionsWide][]; + drawableStorageBufferIds = new int[numbRegionsWide][numbRegionsWide][]; + } + + for (int x = 0; x < numbRegionsWide; x++) + { + for (int z = 0; z < numbRegionsWide; z++) + { + regionMemoryRequired = DEFAULT_MEMORY_ALLOCATION; + + // if the memory required is greater than the max buffer + // capacity, divide the memory across multiple buffers + if (regionMemoryRequired > LodUtil.MAX_ALLOCATABLE_DIRECT_MEMORY) + { + numberOfBuffers = (int) regionMemoryRequired / LodUtil.MAX_ALLOCATABLE_DIRECT_MEMORY + 1; + + // TODO shouldn't this be determined with regionMemoryRequired? + // always allocating the max memory is a bit expensive isn't it? + regionMemoryRequired = LodUtil.MAX_ALLOCATABLE_DIRECT_MEMORY; + numberOfBuffersPerRegion[x][z] = numberOfBuffers; + buildableBuffers[x][z] = new LodBufferBuilder[numberOfBuffers]; + buildableVbos[x][z] = new LodVertexBuffer[numberOfBuffers]; + drawableVbos[x][z] = new LodVertexBuffer[numberOfBuffers]; + + if (glProxy.bufferStorageSupported) + { + buildableStorageBufferIds[x][z] = new int[numberOfBuffers]; + drawableStorageBufferIds[x][z] = new int[numberOfBuffers]; + } + } + else + { + // we only need one buffer for this region + numberOfBuffersPerRegion[x][z] = 1; + buildableBuffers[x][z] = new LodBufferBuilder[1]; + buildableVbos[x][z] = new LodVertexBuffer[1]; + drawableVbos[x][z] = new LodVertexBuffer[1]; + + if (glProxy.bufferStorageSupported) + { + buildableStorageBufferIds[x][z] = new int[1]; + drawableStorageBufferIds[x][z] = new int[1]; + } + } + + + for (int i = 0; i < numberOfBuffersPerRegion[x][z]; i++) + { + buildableBuffers[x][z][i] = new LodBufferBuilder((int) regionMemoryRequired); + + buildableVbos[x][z][i] = new LodVertexBuffer(); + drawableVbos[x][z][i] = new LodVertexBuffer(); + + + // create the initial mapped buffers (system memory) + GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, buildableVbos[x][z][i].id); + GL15.glBufferData(GL15.GL_ARRAY_BUFFER, regionMemoryRequired, GL15.GL_STATIC_DRAW); + GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0); + + GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, drawableVbos[x][z][i].id); + GL15.glBufferData(GL15.GL_ARRAY_BUFFER, regionMemoryRequired, GL15.GL_STATIC_DRAW); + GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0); + + + if (glProxy.bufferStorageSupported) + { + // create the buffer storage (GPU memory) + buildableStorageBufferIds[x][z][i] = GL15.glGenBuffers(); + GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, buildableStorageBufferIds[x][z][i]); + GL45.glBufferStorage(GL15.GL_ARRAY_BUFFER, regionMemoryRequired, 0); // the 0 flag means to create the storage in the GPUs memory + GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0); + + drawableStorageBufferIds[x][z][i] = GL15.glGenBuffers(); + GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, drawableStorageBufferIds[x][z][i]); + GL45.glBufferStorage(GL15.GL_ARRAY_BUFFER, regionMemoryRequired, 0); + GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0); + } + } + } + } + + glProxy.setGlContext(oldContext); + } + catch (Exception e) + { + ClientApi.LOGGER.info("setupBuffers ran into trouble: " + e.getMessage(), e); + } + finally + { + // this shouldn't normally happen, but just in case it sill prevent deadlock + bufferLock.unlock(); + } + } + + + + /** + * Sets the buffers and Vbos to null, forcing them to be recreated
+ * and destroys any bound OpenGL objects.

+ *

+ * May have to wait for the bufferLock to open. + */ + public void destroyBuffers() + { + try + { + bufferLock.lock(); + + + // destroy the buffer storages if they aren't already + if (buildableStorageBufferIds != null) + { + for (int x = 0; x < buildableStorageBufferIds.length; x++) + { + for (int z = 0; z < buildableStorageBufferIds.length; z++) + { + for (int i = 0; i < buildableStorageBufferIds[x][z].length; i++) + { + int buildableId = buildableStorageBufferIds[x][z][i]; + int drawableId = drawableStorageBufferIds[x][z][i]; + + // make sure the buffers are deleted in a openGL context + GLProxy.getInstance().recordOpenGlCall(() -> + { + GL15.glDeleteBuffers(buildableId); + GL15.glDeleteBuffers(drawableId); + }); + } + } + } + } + + buildableStorageBufferIds = null; + drawableStorageBufferIds = null; + + + + + // destroy the VBOs if they aren't already + if (buildableVbos != null) + { + for (int i = 0; i < buildableVbos.length; i++) + { + for (int j = 0; j < buildableVbos.length; j++) + { + for (int k = 0; k < buildableVbos[i][j].length; k++) + { + int buildableId; + int drawableId; + + // variables passed into a lambda expression + // need to be effectively final, so we have + // to use an else statement here + if (buildableVbos[i][j][k] != null) + buildableId = buildableVbos[i][j][k].id; + else + buildableId = 0; + + if (drawableVbos[i][j][k] != null) + drawableId = drawableVbos[i][j][k].id; + else + drawableId = 0; + + + GLProxy.getInstance().recordOpenGlCall(() -> + { + if (buildableId != 0) + GL15.glDeleteBuffers(buildableId); + if (drawableId != 0) + GL15.glDeleteBuffers(drawableId); + }); + } + } + } + } + + buildableVbos = null; + drawableVbos = null; + + + // these don't contain any OpenGL objects, so + // they don't require any special clean-up + buildableBuffers = null; + } + catch (Exception e) + { + ClientApi.LOGGER.info("destroyBuffers ran into trouble: " + e.getMessage(), e); + } + finally + { + // this shouldn't normally happen, but just in case it sill prevent deadlock + bufferLock.unlock(); + } + } + + /** Calls begin on each of the buildable BufferBuilders. */ + private void startBuffers(boolean fullRegen, LodDimension lodDim) + { + for (int x = 0; x < buildableBuffers.length; x++) + { + for (int z = 0; z < buildableBuffers.length; z++) + { + if (fullRegen || lodDim.doesRegionNeedBufferRegen(x, z)) + { + for (int i = 0; i < buildableBuffers[x][z].length; i++) + { + // for some reason BufferBuilder.vertexCounts + // isn't reset unless this is called, which can cause + // a false indexOutOfBoundsException + buildableBuffers[x][z][i].discard(); + + buildableBuffers[x][z][i].begin(GL11.GL_QUADS, LodUtil.LOD_VERTEX_FORMAT); + } + } + } + } + } + + /** Calls end on each of the buildable BufferBuilders. */ + private void closeBuffers(boolean fullRegen, LodDimension lodDim) + { + for (int x = 0; x < buildableBuffers.length; x++) + for (int z = 0; z < buildableBuffers.length; z++) + for (int i = 0; i < buildableBuffers[x][z].length; i++) + if (buildableBuffers[x][z][i] != null && buildableBuffers[x][z][i].building() && (fullRegen || lodDim.doesRegionNeedBufferRegen(x, z))) + buildableBuffers[x][z][i].end(); + } + + + /** Upload all buildableBuffers to the GPU. */ + private void uploadBuffers(boolean fullRegen, LodDimension lodDim) + { + GLProxy glProxy = GLProxy.getInstance(); + long fence = 0; + + try + { + // make sure we are uploading to the builder context, + // this helps prevent interference (IE stuttering) with the Minecraft context. + glProxy.setGlContext(GLProxyContext.LOD_BUILDER); + + // determine the upload method + GpuUploadMethod uploadMethod = CONFIG.client().advanced().buffers().getGpuUploadMethod(); + if (!glProxy.bufferStorageSupported && uploadMethod == GpuUploadMethod.BUFFER_STORAGE) + { + // if buffer storage isn't supported + // default to SUB_DATA + CONFIG.client().advanced().buffers().setGpuUploadMethod(GpuUploadMethod.SUB_DATA); + uploadMethod = GpuUploadMethod.SUB_DATA; + } + + // determine the upload timeout + int uploadTimeoutInMS = CONFIG.client().advanced().buffers().getGpuUploadTimeoutInMilliseconds(); + + // James has no idea if this does anything helpful, + // but in theory it should prevent OpenGL from drawing and + // writing to a buffer at the same time. + GL45.glMemoryBarrier(GL45.GL_CLIENT_MAPPED_BUFFER_BARRIER_BIT); + fence = GL45.glFenceSync(GL45.GL_SYNC_GPU_COMMANDS_COMPLETE, 0); + + + + // actually upload the buffers + for (int x = 0; x < buildableVbos.length; x++) + { + for (int z = 0; z < buildableVbos.length; z++) + { + if (fullRegen || lodDim.doesRegionNeedBufferRegen(x, z)) + { + for (int i = 0; i < buildableBuffers[x][z].length; i++) + { + ByteBuffer uploadBuffer = buildableBuffers[x][z][i].getCleanedByteBuffer(); + vboUpload(x,z,i, uploadBuffer, true, uploadMethod); + lodDim.setRegenRegionBufferByArrayIndex(x, z, false); + + + // upload buffers over an extended period of time + // to hopefully prevent stuttering. + if (uploadTimeoutInMS != 0) + Thread.sleep(uploadTimeoutInMS); + GL15.glFinish(); + } + } + } + } + + // make sure all of the uploads finish before continuing + GL45.glClientWaitSync(fence, GL45.GL_SYNC_FLUSH_COMMANDS_BIT, 5L * 1000000000); // wait up to 5 seconds + } + catch (Exception e) + { + // this doesn't appear to be necessary anymore, but just in case. + ClientApi.LOGGER.error(LodBufferBuilderFactory.class.getSimpleName() + " - UploadBuffers failed: " + e.getMessage()); + e.printStackTrace(); + } + finally + { + GL15.glFinish(); + if (fence != 0) + GL45.glDeleteSync(fence); + + // close the context so it can be re-used later. + // I'm guessing we can't just leave it because the executor service + // does something that invalidates the OpenGL context. + glProxy.setGlContext(GLProxyContext.NONE); + } + } + + /** Uploads the uploadBuffer so the GPU can use it. */ + private void vboUpload(int xIndex, int zIndex, int iIndex, ByteBuffer uploadBuffer, + boolean allowBufferExpansion, GpuUploadMethod uploadMethod) + { + // get the vbos, buffers, ids, etc. + int storageBufferId = 0; + if (buildableStorageBufferIds != null) + storageBufferId = buildableStorageBufferIds[xIndex][zIndex][iIndex]; + + LodVertexBuffer vbo = buildableVbos[xIndex][zIndex][iIndex]; + + + + + // this shouldn't happen, but just to be safe + if (vbo.id != -1 && GLProxy.getInstance().getGlContext() == GLProxyContext.LOD_BUILDER) + { + // this is how many points will be rendered + vbo.vertexCount = (uploadBuffer.capacity() / ((Float.BYTES * 3) + (Byte.BYTES * 4))); // TODO make this change with the LodTemplate + + GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, vbo.id); + try + { + // if possible use the faster buffer storage route + if (uploadMethod == GpuUploadMethod.BUFFER_STORAGE && storageBufferId != 0) + { + // get a pointer to the buffer in system memory + ByteBuffer vboBuffer = GL30.glMapBufferRange(GL15.GL_ARRAY_BUFFER, 0, uploadBuffer.capacity(), GL30.GL_MAP_WRITE_BIT | GL30.GL_MAP_UNSYNCHRONIZED_BIT); + if (vboBuffer == null) + { + int previousCapacity = uploadBuffer.capacity(); + + // only expand the buffers if the uploadBuffer actually + // has something in it and expansion is allowed + if (previousCapacity != 0 && allowBufferExpansion) + { + // the buffer(s) aren't big enough, expand them. + // This does cause lag/stuttering, so it should be avoided! + + // expand the buffer in system memory + GL15.glBufferData(GL15.GL_ARRAY_BUFFER, (int) (uploadBuffer.capacity() * BUFFER_EXPANSION_MULTIPLIER), GL15.GL_DYNAMIC_DRAW); + GL15.glBufferSubData(GL15.GL_ARRAY_BUFFER, 0, uploadBuffer); + + // un-bind the system memory buffer + GL15.glUnmapBuffer(GL15.GL_ARRAY_BUFFER); + GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0); + + // expand the buffer storage + GL15.glDeleteBuffers(storageBufferId); + GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, storageBufferId); + GL45.glBufferStorage(GL15.GL_ARRAY_BUFFER, (int) (uploadBuffer.capacity() * BUFFER_EXPANSION_MULTIPLIER), 0); + GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0); + + + // recursively try to upload into the newly created buffer storage + // but don't recurse again if that fails + // (we don't want an infinitely expanding buffer!) + vboUpload(xIndex,zIndex,iIndex, uploadBuffer, false, uploadMethod); + } + } + else + { + // upload the buffer into system memory... + vboBuffer.put(uploadBuffer); + GL15.glUnmapBuffer(GL15.GL_ARRAY_BUFFER); + + // ...then upload into GPU memory + // (uploading into GPU memory directly can only be done + // through the glCopyBufferSubData/glCopyNamed... methods) + GL45.glCopyNamedBufferSubData(vbo.id, storageBufferId, 0, 0, uploadBuffer.capacity()); + } + } + else if (uploadMethod == GpuUploadMethod.BUFFER_MAPPING) + { + // no stuttering but high GPU usage + // stores everything in system memory instead of GPU memory + // making rendering much slower. + // Unless the user is running integrated graphics, + // in that case this will actually work better than SUB_DATA. + + + ByteBuffer vboBuffer; + + // map buffer range is better since it can be explicitly unsynchronized + if (GLProxy.getInstance().mapBufferRangeSupported) + vboBuffer = GL30.glMapBufferRange(GL30.GL_ARRAY_BUFFER, 0, uploadBuffer.capacity(), GL30.GL_MAP_WRITE_BIT | GL30.GL_MAP_UNSYNCHRONIZED_BIT | GL30.GL_MAP_INVALIDATE_BUFFER_BIT); + else + vboBuffer = GL15.glMapBuffer(GL30.GL_ARRAY_BUFFER, uploadBuffer.capacity()); + + + if (vboBuffer == null) + { + GL15.glBufferData(GL45.GL_ARRAY_BUFFER, (int) (uploadBuffer.capacity() * BUFFER_EXPANSION_MULTIPLIER), GL15.GL_STATIC_DRAW); + GL15.glBufferSubData(GL15.GL_ARRAY_BUFFER, 0, uploadBuffer); + } + else + { + vboBuffer.put(uploadBuffer); + } + } + else if (uploadMethod == GpuUploadMethod.DATA) + { + // hybrid bufferData // + // high stutter, low GPU usage + // But simplest/most compatible + + GL15.glBufferData(GL15.GL_ARRAY_BUFFER, uploadBuffer.capacity(), GL15.GL_STATIC_DRAW); + GL15.glBufferData(GL15.GL_ARRAY_BUFFER, uploadBuffer, GL15.GL_STATIC_DRAW); + } + else + { + // hybrid subData/bufferData // + // less stutter, low GPU usage + + long size = GL15.glGetBufferParameteri(GL15.GL_ARRAY_BUFFER, GL15.GL_BUFFER_SIZE); + if (size < uploadBuffer.capacity() * BUFFER_EXPANSION_MULTIPLIER) + { + GL15.glBufferData(GL15.GL_ARRAY_BUFFER, (int) (uploadBuffer.capacity() * BUFFER_EXPANSION_MULTIPLIER), GL15.GL_STATIC_DRAW); + } + GL15.glBufferSubData(GL15.GL_ARRAY_BUFFER, 0, uploadBuffer); + } + } + catch (Exception e) + { + ClientApi.LOGGER.error("vboUpload failed: " + e.getClass().getSimpleName()); + e.printStackTrace(); + } + finally + { + if (uploadMethod == GpuUploadMethod.BUFFER_MAPPING) + GL15.glUnmapBuffer(GL15.GL_ARRAY_BUFFER); + + GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0); + } + + }//if vbo exists and in correct GL context + }//vboUpload + + /** Get the newly created VBOs */ + public VertexBuffersAndOffset getVertexBuffers() + { + // don't wait for the lock to open, + // since this is called on the main render thread + if (bufferLock.tryLock()) + { + try + { + LodVertexBuffer[][][] tmpVbo = drawableVbos; + drawableVbos = buildableVbos; + buildableVbos = tmpVbo; + + int[][][] tmpStorage = drawableStorageBufferIds; + drawableStorageBufferIds = buildableStorageBufferIds; + buildableStorageBufferIds = tmpStorage; + + drawableCenterChunkPos = buildableCenterChunkPos; + + // the vbos have been swapped + switchVbos = false; + } + catch (Exception e) + { + // this shouldn't normally happen, but just in case it sill prevent deadlock + ClientApi.LOGGER.info("getVertexBuffers ran into trouble: " + e.getMessage(), e); + } + finally + { + bufferLock.unlock(); + } + } + + return new VertexBuffersAndOffset(drawableVbos, drawableStorageBufferIds, drawableCenterChunkPos); + } + + /** A simple container to pass multiple objects back in the getVertexBuffers method. */ + public static class VertexBuffersAndOffset + { + public final LodVertexBuffer[][][] vbos; + public final int[][][] storageBufferIds; + public final AbstractChunkPosWrapper drawableCenterChunkPos; + + public VertexBuffersAndOffset(LodVertexBuffer[][][] newVbos, int[][][] newStorageBufferIds, AbstractChunkPosWrapper newDrawableCenterChunkPos) + { + vbos = newVbos; + storageBufferIds = newStorageBufferIds; + drawableCenterChunkPos = newDrawableCenterChunkPos; + } + } + + /** + * If this is true the buildable near and far + * buffers have been generated and are ready to be + * sent to the LodRenderer. + */ + public boolean newBuffersAvailable() + { + return switchVbos; + } +} diff --git a/src/main/java/com/seibel/lod/core/builders/bufferBuilding/lodTemplates/AbstractLodTemplate.java b/src/main/java/com/seibel/lod/core/builders/bufferBuilding/lodTemplates/AbstractLodTemplate.java new file mode 100644 index 000000000..69bb57b10 --- /dev/null +++ b/src/main/java/com/seibel/lod/core/builders/bufferBuilding/lodTemplates/AbstractLodTemplate.java @@ -0,0 +1,52 @@ +/* + * This file is part of the Distant Horizon mod (formerly the LOD Mod), + * licensed under the GNU GPL v3 License. + * + * Copyright (C) 2020 James Seibel + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.seibel.lod.core.builders.bufferBuilding.lodTemplates; + +import java.util.Map; + +import com.seibel.lod.core.enums.LodDirection; +import com.seibel.lod.core.enums.rendering.DebugMode; +import com.seibel.lod.core.objects.VertexOptimizer; +import com.seibel.lod.core.objects.opengl.LodBufferBuilder; +import com.seibel.lod.core.util.ColorUtil; +import com.seibel.lod.core.wrapperInterfaces.block.AbstractBlockPosWrapper; + +/** + * This is the abstract class used to create different + * BufferBuilders. + * @author James Seibel + * @version 11-13-2021 + */ +public abstract class AbstractLodTemplate +{ + /** Uploads the given LOD to the buffer. */ + public abstract void addLodToBuffer(LodBufferBuilder buffer, AbstractBlockPosWrapper bufferCenterBlockPos, long data, Map adjData, + byte detailLevel, int posX, int posZ, VertexOptimizer vertexOptimizer, DebugMode debugging, boolean[] adjShadeDisabled); + + /** add the given position and color to the buffer */ + protected void addPosAndColor(LodBufferBuilder buffer, + float x, float y, float z, + int color) + { + // TODO re-add transparency by replacing the 255 with "ColorUtil.getAlpha(color)" + buffer.vertex(x, y, z).color(ColorUtil.getRed(color), ColorUtil.getGreen(color), ColorUtil.getBlue(color), 255).endVertex(); + } + +} diff --git a/src/main/java/com/seibel/lod/core/builders/bufferBuilding/lodTemplates/CubicLodTemplate.java b/src/main/java/com/seibel/lod/core/builders/bufferBuilding/lodTemplates/CubicLodTemplate.java new file mode 100644 index 000000000..aaf016b05 --- /dev/null +++ b/src/main/java/com/seibel/lod/core/builders/bufferBuilding/lodTemplates/CubicLodTemplate.java @@ -0,0 +1,142 @@ +/* + * This file is part of the Distant Horizon mod (formerly the LOD Mod), + * licensed under the GNU GPL v3 License. + * + * Copyright (C) 2020 James Seibel + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.seibel.lod.core.builders.bufferBuilding.lodTemplates; + +import java.util.Map; + +import com.seibel.lod.core.enums.LodDirection; +import com.seibel.lod.core.enums.rendering.DebugMode; +import com.seibel.lod.core.objects.VertexOptimizer; +import com.seibel.lod.core.objects.opengl.LodBufferBuilder; +import com.seibel.lod.core.util.ColorUtil; +import com.seibel.lod.core.util.DataPointUtil; +import com.seibel.lod.core.util.LodUtil; +import com.seibel.lod.core.wrapperInterfaces.block.AbstractBlockPosWrapper; + +/** + * Builds LODs as rectangular prisms. + * @author James Seibel + * @version 11-8-2021 + */ +public class CubicLodTemplate extends AbstractLodTemplate +{ + + public CubicLodTemplate() + { + + } + + @Override + public void addLodToBuffer(LodBufferBuilder buffer, AbstractBlockPosWrapper bufferCenterBlockPos, long data, Map adjData, + byte detailLevel, int posX, int posZ, VertexOptimizer vertexOptimizer, DebugMode debugging, boolean[] adjShadeDisabled) + { + if (vertexOptimizer == null) + return; + + // equivalent to 2^detailLevel + int blockWidth = 1 << detailLevel; + + int color; + if (debugging != DebugMode.OFF) + color = LodUtil.DEBUG_DETAIL_LEVEL_COLORS[detailLevel].getRGB(); + else + color = DataPointUtil.getColor(data); + + + generateBoundingBox( + vertexOptimizer, + DataPointUtil.getHeight(data), + DataPointUtil.getDepth(data), + blockWidth, + posX * blockWidth, 0, posZ * blockWidth, // x, y, z offset + bufferCenterBlockPos, + adjData, + color, + DataPointUtil.getLightSkyAlt(data), + DataPointUtil.getLightBlock(data), + adjShadeDisabled); + + addBoundingBoxToBuffer(buffer, vertexOptimizer); + } + + private void generateBoundingBox(VertexOptimizer vertexOptimizer, + int height, int depth, int width, + double xOffset, double yOffset, double zOffset, + AbstractBlockPosWrapper bufferCenterBlockPos, + Map adjData, + int color, + int skyLight, + int blockLight, + boolean[] adjShadeDisabled) + { + // don't add an LOD if it is empty + if (height == -1 && depth == -1) + return; + + if (depth == height) + // if the top and bottom points are at the same height + // render this LOD as 1 block thick + height++; + + // offset the AABB by its x/z position in the world since + // it uses doubles to specify its location, unlike the model view matrix + // which only uses floats + double x = -bufferCenterBlockPos.getX(); + double z = -bufferCenterBlockPos.getZ(); + vertexOptimizer.reset(); + vertexOptimizer.setColor(color, adjShadeDisabled); + vertexOptimizer.setLights(skyLight, blockLight); + vertexOptimizer.setWidth(width, height - depth, width); + vertexOptimizer.setOffset((int) (xOffset + x), (int) (depth + yOffset), (int) (zOffset + z)); + vertexOptimizer.setUpCulling(32, bufferCenterBlockPos); + vertexOptimizer.setAdjData(adjData); + } + + private void addBoundingBoxToBuffer(LodBufferBuilder buffer, VertexOptimizer vertexOptimizer) + { + int color; + int skyLight; + int blockLight; + for (LodDirection lodDirection : VertexOptimizer.DIRECTIONS) + { + if(vertexOptimizer.isCulled(lodDirection)) + continue; + + int verticalFaceIndex = 0; + while (vertexOptimizer.shouldRenderFace(lodDirection, verticalFaceIndex)) + { + for (int vertexIndex = 0; vertexIndex < 6; vertexIndex++) + { + color = vertexOptimizer.getColor(lodDirection); + skyLight = vertexOptimizer.getSkyLight(lodDirection, verticalFaceIndex); + blockLight = vertexOptimizer.getBlockLight(); + color = ColorUtil.applyLightValue(color, skyLight, blockLight); + addPosAndColor(buffer, + vertexOptimizer.getX(lodDirection, vertexIndex), + vertexOptimizer.getY(lodDirection, vertexIndex, verticalFaceIndex) + DataPointUtil.VERTICAL_OFFSET, + vertexOptimizer.getZ(lodDirection, vertexIndex), + color); + } + verticalFaceIndex++; + } + } + } + +} diff --git a/src/main/java/com/seibel/lod/core/builders/bufferBuilding/lodTemplates/DynamicLodTemplate.java b/src/main/java/com/seibel/lod/core/builders/bufferBuilding/lodTemplates/DynamicLodTemplate.java new file mode 100644 index 000000000..a1c302b00 --- /dev/null +++ b/src/main/java/com/seibel/lod/core/builders/bufferBuilding/lodTemplates/DynamicLodTemplate.java @@ -0,0 +1,48 @@ +/* + * This file is part of the Distant Horizon mod (formerly the LOD Mod), + * licensed under the GNU GPL v3 License. + * + * Copyright (C) 2020 James Seibel + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.seibel.lod.core.builders.bufferBuilding.lodTemplates; + +import java.util.Map; + +import com.seibel.lod.core.api.ClientApi; +import com.seibel.lod.core.enums.LodDirection; +import com.seibel.lod.core.enums.rendering.DebugMode; +import com.seibel.lod.core.objects.VertexOptimizer; +import com.seibel.lod.core.objects.opengl.LodBufferBuilder; +import com.seibel.lod.core.wrapperInterfaces.block.AbstractBlockPosWrapper; + +/** + * TODO DynamicLodTemplate + * Chunks smoothly transition between + * each other, unless a neighboring chunk + * is at a significantly different height. + * @author James Seibel + * @version 06-16-2021 + */ +public class DynamicLodTemplate extends AbstractLodTemplate +{ + @Override + public void addLodToBuffer(LodBufferBuilder buffer, AbstractBlockPosWrapper bufferCenterBlockPos, long data, Map adjData, + byte detailLevel, int posX, int posZ, VertexOptimizer vertexOptimizer, DebugMode debugging, boolean[] adjShadeDisabled) + { + ClientApi.LOGGER.error(DynamicLodTemplate.class.getSimpleName() + " is not implemented!"); + } + +} diff --git a/src/main/java/com/seibel/lod/core/builders/bufferBuilding/lodTemplates/TriangularLodTemplate.java b/src/main/java/com/seibel/lod/core/builders/bufferBuilding/lodTemplates/TriangularLodTemplate.java new file mode 100644 index 000000000..489869645 --- /dev/null +++ b/src/main/java/com/seibel/lod/core/builders/bufferBuilding/lodTemplates/TriangularLodTemplate.java @@ -0,0 +1,46 @@ +/* + * This file is part of the Distant Horizon mod (formerly the LOD Mod), + * licensed under the GNU GPL v3 License. + * + * Copyright (C) 2020 James Seibel + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.seibel.lod.core.builders.bufferBuilding.lodTemplates; + +import java.util.Map; + +import com.seibel.lod.core.api.ClientApi; +import com.seibel.lod.core.enums.LodDirection; +import com.seibel.lod.core.enums.rendering.DebugMode; +import com.seibel.lod.core.objects.VertexOptimizer; +import com.seibel.lod.core.objects.opengl.LodBufferBuilder; +import com.seibel.lod.core.wrapperInterfaces.block.AbstractBlockPosWrapper; + +/** + * TODO #21 TriangularLodTemplate + * Builds each LOD chunk as a singular rectangular prism. + * @author James Seibel + * @version 06-16-2021 + */ +public class TriangularLodTemplate extends AbstractLodTemplate +{ + @Override + public void addLodToBuffer(LodBufferBuilder buffer, AbstractBlockPosWrapper bufferCenterBlockPos, long data, Map adjData, + byte detailLevel, int posX, int posZ, VertexOptimizer vertexOptimizer, DebugMode debugging, boolean[] adjShadeDisabled) + { + ClientApi.LOGGER.error(DynamicLodTemplate.class.getSimpleName() + " is not implemented!"); + } + +} diff --git a/src/main/java/com/seibel/lod/core/builders/lodBuilding/LodBuilder.java b/src/main/java/com/seibel/lod/core/builders/lodBuilding/LodBuilder.java new file mode 100644 index 000000000..671abcadc --- /dev/null +++ b/src/main/java/com/seibel/lod/core/builders/lodBuilding/LodBuilder.java @@ -0,0 +1,538 @@ +/* + * This file is part of the Distant Horizon mod (formerly the LOD Mod), + * licensed under the GNU GPL v3 License. + * + * Copyright (C) 2020 James Seibel + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.seibel.lod.core.builders.lodBuilding; + +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +import com.seibel.lod.core.enums.config.DistanceGenerationMode; +import com.seibel.lod.core.enums.config.HorizontalResolution; +import com.seibel.lod.core.objects.lod.LodDimension; +import com.seibel.lod.core.objects.lod.LodRegion; +import com.seibel.lod.core.objects.lod.LodWorld; +import com.seibel.lod.core.util.ColorUtil; +import com.seibel.lod.core.util.DataPointUtil; +import com.seibel.lod.core.util.DetailDistanceUtil; +import com.seibel.lod.core.util.LevelPosUtil; +import com.seibel.lod.core.util.LodThreadFactory; +import com.seibel.lod.core.util.LodUtil; +import com.seibel.lod.core.util.SingletonHandler; +import com.seibel.lod.core.util.ThreadMapUtil; +import com.seibel.lod.core.wrapperInterfaces.IWrapperFactory; +import com.seibel.lod.core.wrapperInterfaces.block.AbstractBlockPosWrapper; +import com.seibel.lod.core.wrapperInterfaces.block.IBlockColorSingletonWrapper; +import com.seibel.lod.core.wrapperInterfaces.block.IBlockColorWrapper; +import com.seibel.lod.core.wrapperInterfaces.block.IBlockShapeWrapper; +import com.seibel.lod.core.wrapperInterfaces.chunk.AbstractChunkPosWrapper; +import com.seibel.lod.core.wrapperInterfaces.chunk.IChunkWrapper; +import com.seibel.lod.core.wrapperInterfaces.config.ILodConfigWrapperSingleton; +import com.seibel.lod.core.wrapperInterfaces.minecraft.IMinecraftWrapper; +import com.seibel.lod.core.wrapperInterfaces.world.IBiomeWrapper; +import com.seibel.lod.core.wrapperInterfaces.world.IDimensionTypeWrapper; +import com.seibel.lod.core.wrapperInterfaces.world.IWorldWrapper; + +/** + * This object is in charge of creating Lod related objects. + * + * @author Cola + * @author Leonardo Amato + * @author James Seibel + * @version 10-22-2021 + */ +@SuppressWarnings("GrazieInspection") public class LodBuilder +{ + private static final IMinecraftWrapper MC = SingletonHandler.get(IMinecraftWrapper.class); + private static final IBlockColorSingletonWrapper BLOCK_COLOR = SingletonHandler.get(IBlockColorSingletonWrapper.class); + private static final IWrapperFactory FACTORY = SingletonHandler.get(IWrapperFactory.class); + + /** If no blocks are found in the area in determineBottomPointForArea return this */ + public static final short DEFAULT_DEPTH = 0; + /** If no blocks are found in the area in determineHeightPointForArea return this */ + public static final short DEFAULT_HEIGHT = 0; + /** Minecraft's max light value */ + public static final short DEFAULT_MAX_LIGHT = 15; + + + private final ExecutorService lodGenThreadPool = Executors.newSingleThreadExecutor(new LodThreadFactory(this.getClass().getSimpleName())); + private final ILodConfigWrapperSingleton config = SingletonHandler.get(ILodConfigWrapperSingleton.class); + + + + /** + * How wide LodDimensions should be in regions
+ * Is automatically set before the first frame in ClientProxy. + */ + public int defaultDimensionWidthInRegions = 0; + + //public static final boolean useExperimentalLighting = true; + + + + + public LodBuilder() + { + + } + + public void generateLodNodeAsync(IChunkWrapper chunk, LodWorld lodWorld, IDimensionTypeWrapper dim) + { + generateLodNodeAsync(chunk, lodWorld, dim, DistanceGenerationMode.FULL); + } + + public void generateLodNodeAsync(IChunkWrapper chunk, LodWorld lodWorld, IDimensionTypeWrapper dim, DistanceGenerationMode generationMode) + { + if (lodWorld == null || lodWorld.getIsWorldNotLoaded()) + 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(() -> + { + //noinspection GrazieInspection + try + { + // we need a loaded client world in order to + // get the textures for blocks + if (MC.getWrappedClientWorld() == null) + return; + + // don't try to generate LODs if the user isn't in the world anymore + // (this happens a lot when the user leaves a world/server) + if (!MC.hasSinglePlayerServer() && !MC.connectedToServer()) + return; + + // make sure the dimension exists + LodDimension lodDim; + if (lodWorld.getLodDimension(dim) == null) + { + lodDim = new LodDimension(dim, lodWorld, defaultDimensionWidthInRegions); + lodWorld.addLodDimension(lodDim); + } + else + { + lodDim = lodWorld.getLodDimension(dim); + } + generateLodNodeFromChunk(lodDim, chunk, new LodBuilderConfig(generationMode)); + } + catch (IllegalArgumentException | NullPointerException e) + { + e.printStackTrace(); + // if the world changes while LODs are being generated + // they will throw errors as they try to access things that no longer + // exist. + } + }); + lodGenThreadPool.execute(thread); + } + + /** + * Creates a LodNode for a chunk in the given world. + * @throws IllegalArgumentException thrown if either the chunk or world is null. + */ + public void generateLodNodeFromChunk(LodDimension lodDim, IChunkWrapper chunk) throws IllegalArgumentException + { + generateLodNodeFromChunk(lodDim, chunk, new LodBuilderConfig()); + } + + /** + * Creates a LodNode for a chunk in the given world. + * @throws IllegalArgumentException thrown if either the chunk or world is null. + */ + public void generateLodNodeFromChunk(LodDimension lodDim, IChunkWrapper chunk, LodBuilderConfig config) + throws IllegalArgumentException + { + if (chunk == null) + throw new IllegalArgumentException("generateLodFromChunk given a null chunk"); + + int startX; + int startZ; + + + LodRegion region = lodDim.getRegion(chunk.getPos().getRegionX(), chunk.getPos().getRegionZ()); + if (region == null) + return; + + // this happens if a LOD is generated after the user leaves the world. + if (MC.getWrappedClientWorld() == null) + return; + + // determine how many LODs to generate horizontally + byte minDetailLevel = region.getMinDetailLevel(); + HorizontalResolution detail = DetailDistanceUtil.getLodGenDetail(minDetailLevel); + + + // determine how many LODs to generate vertically + //VerticalQuality verticalQuality = LodConfig.CLIENT.graphics.qualityOption.verticalQuality.get(); + byte detailLevel = detail.detailLevel; + + + // generate the LODs + int posX; + int posZ; + for (int i = 0; i < detail.dataPointLengthCount * detail.dataPointLengthCount; i++) + { + startX = detail.startX[i]; + startZ = detail.startZ[i]; + + long[] data; + long[] dataToMergeVertical = createVerticalDataToMerge(detail, chunk, config, startX, startZ); + data = DataPointUtil.mergeMultiData(dataToMergeVertical, DataPointUtil.WORLD_HEIGHT / 2 + 1, DetailDistanceUtil.getMaxVerticalData(detailLevel)); + + + //lodDim.clear(detailLevel, posX, posZ); + if (data != null && data.length != 0) + { + posX = LevelPosUtil.convert((byte) 0, chunk.getPos().getX() * 16 + startX, detail.detailLevel); + posZ = LevelPosUtil.convert((byte) 0, chunk.getPos().getZ() * 16 + startZ, detail.detailLevel); + lodDim.addVerticalData(detailLevel, posX, posZ, data, false); + } + } + lodDim.updateData(LodUtil.CHUNK_DETAIL_LEVEL, chunk.getPos().getX(), chunk.getPos().getZ()); + } + + /** creates a vertical DataPoint */ + private long[] createVerticalDataToMerge(HorizontalResolution detail, IChunkWrapper chunk, LodBuilderConfig config, int startX, int startZ) + { + // equivalent to 2^detailLevel + int size = 1 << detail.detailLevel; + + long[] dataToMerge = ThreadMapUtil.getBuilderVerticalArray(detail.detailLevel); + int verticalData = DataPointUtil.WORLD_HEIGHT / 2 + 1; + + AbstractChunkPosWrapper chunkPos = chunk.getPos(); + int height; + int depth; + int color; + int light; + int lightSky; + int lightBlock; + int generation = config.distanceGenerationMode.complexity; + + int xRel; + int zRel; + int xAbs; + int yAbs; + int zAbs; + boolean hasCeiling = MC.getWrappedClientWorld().getDimensionType().hasCeiling(); + boolean hasSkyLight = MC.getWrappedClientWorld().getDimensionType().hasSkyLight(); + boolean isDefault; + AbstractBlockPosWrapper blockPos = FACTORY.createBlockPos(); + int index; + + for (index = 0; index < size * size; index++) + { + xRel = startX + index % size; + zRel = startZ + index / size; + xAbs = chunkPos.getMinBlockX() + xRel; + zAbs = chunkPos.getMinBlockZ() + zRel; + + //Calculate the height of the lod + yAbs = DataPointUtil.WORLD_HEIGHT - DataPointUtil.VERTICAL_OFFSET + 1; + int count = 0; + boolean topBlock = true; + while (yAbs > 0) + { + height = determineHeightPointFrom(chunk, config, xRel, yAbs, zRel, blockPos); + + // If the lod is at the default height, it must be void data + if (height == DEFAULT_HEIGHT) + { + if (topBlock) + dataToMerge[index * verticalData] = DataPointUtil.createVoidDataPoint(generation); + break; + } + + yAbs = height - 1; + // We search light on above air block + depth = determineBottomPointFrom(chunk, config, xRel, yAbs, zRel, blockPos); + if (hasCeiling && topBlock) + { + yAbs = depth; + blockPos.set(xAbs, yAbs, zAbs); + light = getLightValue(chunk, blockPos, true, hasSkyLight, true); + color = generateLodColor(chunk, config, xAbs, yAbs, zAbs, blockPos); + blockPos.set(xAbs, yAbs - 1, zAbs); + } + else + { + blockPos.set(xAbs, yAbs, zAbs); + light = getLightValue(chunk, blockPos, hasCeiling, hasSkyLight, topBlock); + color = generateLodColor(chunk, config, xRel, yAbs, zRel, blockPos); + blockPos.set(xAbs, yAbs + 1, zAbs); + } + lightBlock = light & 0b1111; + lightSky = (light >> 4) & 0b1111; + isDefault = ((light >> 8)) == 1; + + dataToMerge[index * verticalData + count] = DataPointUtil.createDataPoint(height - DataPointUtil.VERTICAL_OFFSET, depth - DataPointUtil.VERTICAL_OFFSET, color, lightSky, lightBlock, generation, isDefault); + topBlock = false; + yAbs = depth - 1; + count++; + } + } + return dataToMerge; + } + + /** + * Find the lowest valid point from the bottom. + * Used when creating a vertical LOD. + */ + private short determineBottomPointFrom(IChunkWrapper chunk, LodBuilderConfig config, int xAbs, int yAbs, int zAbs, AbstractBlockPosWrapper blockPos) + { + short depth = DEFAULT_DEPTH; + + for (int y = yAbs; y >= 0; y--) + { + blockPos.set(xAbs, y, zAbs); + if (!isLayerValidLodPoint(chunk, blockPos)) + { + depth = (short) (y + 1); + break; + } + } + return depth; + } + + /** Find the highest valid point from the Top */ + private short determineHeightPointFrom(IChunkWrapper chunk, LodBuilderConfig config, int xAbs, int yAbs, int zAbs, AbstractBlockPosWrapper blockPos) + { + short height = DEFAULT_HEIGHT; + if (config.useHeightmap) + height = (short) chunk.getHeightMapValue(xAbs, zAbs); + else + { + for (int y = yAbs; y >= 0; y--) + { + blockPos.set(xAbs, y, zAbs); + if (isLayerValidLodPoint(chunk, blockPos)) + { + height = (short) (y + 1); + break; + } + } + } + return height; + } + + + + // =====================// + // constructor helpers // + // =====================// + + /** + * Generate the color for the given chunk using biome water color, foliage + * color, and grass color. + */ + private int generateLodColor(IChunkWrapper chunk, LodBuilderConfig builderConfig, int xRel, int yAbs, int zRel, AbstractBlockPosWrapper blockPos) + { + int colorInt; + if (builderConfig.useBiomeColors) + { + // I have no idea why I need to bit shift to the right, but + // if I don't the biomes don't show up correctly. + colorInt = chunk.getBiome(xRel, yAbs, zRel).getColorForBiome(xRel, zRel); + } + else + { + blockPos.set(chunk.getPos().getMinBlockX() + xRel, yAbs, chunk.getPos().getMinBlockZ() + zRel); + colorInt = getColorForBlock(chunk, blockPos); + + // if we are skipping non-full and non-solid blocks that means we ignore + // snow, flowers, etc. Get the above block so we can still get the color + // of the snow, flower, etc. that may be above this block + int aboveColorInt = 0; + if (config.client().worldGenerator().getBlocksToAvoid().nonFull || config.client().worldGenerator().getBlocksToAvoid().noCollision) + { + blockPos.set(chunk.getPos().getMinBlockX() + xRel, yAbs + 1, chunk.getPos().getMinBlockZ() + zRel); + aboveColorInt = getColorForBlock(chunk, blockPos); + } + + //if (colorInt == 0 && yAbs > 0) + // if this block is invisible, check the block below it + // colorInt = generateLodColor(chunk, config, xRel, yAbs - 1, zRel, blockPos); + + // override this block's color if there was a block above this + // and we were avoiding non-full/non-solid blocks + if (aboveColorInt != 0) + colorInt = aboveColorInt; + } + + return colorInt; + } + + /** Gets the light value for the given block position */ + private int getLightValue(IChunkWrapper chunk, AbstractBlockPosWrapper blockPos, boolean hasCeiling, boolean hasSkyLight, boolean topBlock) + { + int skyLight = 0; + int blockLight; + // 1 means the lighting is a guess + int isDefault = 0; + + IWorldWrapper world = MC.getWrappedServerWorld(); + + int blockBrightness = chunk.getEmittedBrightness(blockPos); + // get the air block above or below this block + if (hasCeiling && topBlock) + blockPos.set(blockPos.getX(), blockPos.getY() - 1, blockPos.getZ()); + else + blockPos.set(blockPos.getX(), blockPos.getY() + 1, blockPos.getZ()); + + + + if (world != null) + { + // server world sky light (always accurate) + blockLight = world.getBlockLight(blockPos); + if (topBlock && !hasCeiling && hasSkyLight) + skyLight = DEFAULT_MAX_LIGHT; + else + { + if (hasSkyLight) + skyLight = world.getSkyLight(blockPos); + //else + // skyLight = 0; + } + if (!topBlock && skyLight == 15) + { + // we are on predicted terrain, and we don't know what the light here is, + // lets just take a guess + if (blockPos.getY() >= MC.getWrappedClientWorld().getSeaLevel() - 5) + { + skyLight = 12; + isDefault = 1; + } + else + skyLight = 0; + } + } + else + { + world = MC.getWrappedClientWorld(); + if (world==null) + { + blockLight = 0; + skyLight = 12; + isDefault = 1; + } + else + { + // client world sky light (almost never accurate) + blockLight = world.getBlockLight(blockPos); + // estimate what the lighting should be + if (hasSkyLight || !hasCeiling) + { + if (topBlock) + skyLight = DEFAULT_MAX_LIGHT; + else + { + if (hasSkyLight) + skyLight = world.getSkyLight(blockPos); + //else + // skyLight = 0; + if (!chunk.isLightCorrect() && (skyLight == 0 || skyLight == 15)) + { + // we don't know what the light here is, + // lets just take a guess + if (blockPos.getY() >= MC.getWrappedClientWorld().getSeaLevel() - 5) + { + skyLight = 12; + isDefault = 1; + } + else + skyLight = 0; + } + } + } + } + } + + blockLight = LodUtil.clamp(0, Math.max(blockLight, blockBrightness), DEFAULT_MAX_LIGHT); + + return blockLight + (skyLight << 4) + (isDefault << 8); + } + + /** Returns a color int for the given block. */ + private int getColorForBlock(IChunkWrapper chunk, AbstractBlockPosWrapper blockPos) + { + int colorOfBlock; + int colorInt; + + int xRel = blockPos.getX() - chunk.getPos().getMinBlockX(); + int zRel = blockPos.getZ() - chunk.getPos().getMinBlockZ(); + //int x = blockPos.getX(); + int y = blockPos.getY(); + //int z = blockPos.getZ(); + + IBlockColorWrapper blockColorWrapper; + IBlockShapeWrapper blockShapeWrapper = chunk.getBlockShapeWrapper(blockPos); + + if (chunk.isWaterLogged(blockPos)) + blockColorWrapper = BLOCK_COLOR.getWaterColor(); + else + blockColorWrapper = chunk.getBlockColorWrapper(blockPos); + + if (blockShapeWrapper.isToAvoid()) + return 0; + + colorOfBlock = blockColorWrapper.getColor(); + + + if (blockColorWrapper.hasTint()) + { + IBiomeWrapper biome = chunk.getBiome(xRel, y, zRel); + int tintValue; + if (blockColorWrapper.hasGrassTint()) + // grass and green plants + tintValue = biome.getGrassTint(0,0); + else if (blockColorWrapper.hasFolliageTint()) + tintValue = biome.getFolliageTint(); + else + //we can reintroduce this with the wrappers + tintValue = biome.getWaterTint(); + + colorInt = ColorUtil.multiplyRGBcolors(tintValue | 0xFF000000, colorOfBlock); + } + else + colorInt = colorOfBlock; + return colorInt; + } + + + /** Is the block at the given blockPos a valid LOD point? */ + private boolean isLayerValidLodPoint(IChunkWrapper chunk, AbstractBlockPosWrapper blockPos) + { + if (chunk.isWaterLogged(blockPos)) + return true; + + boolean nonFullAvoidance = config.client().worldGenerator().getBlocksToAvoid().nonFull; + boolean noCollisionAvoidance = config.client().worldGenerator().getBlocksToAvoid().noCollision; + + IBlockShapeWrapper block = chunk.getBlockShapeWrapper(blockPos); + return !block.isToAvoid() + && !(nonFullAvoidance && block.isNonFull()) + && !(noCollisionAvoidance && block.hasNoCollision()); + + } +} diff --git a/src/main/java/com/seibel/lod/core/builders/lodBuilding/LodBuilderConfig.java b/src/main/java/com/seibel/lod/core/builders/lodBuilding/LodBuilderConfig.java new file mode 100644 index 000000000..e88a5995b --- /dev/null +++ b/src/main/java/com/seibel/lod/core/builders/lodBuilding/LodBuilderConfig.java @@ -0,0 +1,95 @@ +/* + * This file is part of the Distant Horizon mod (formerly the LOD Mod), + * licensed under the GNU GPL v3 License. + * + * Copyright (C) 2020 James Seibel + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.seibel.lod.core.builders.lodBuilding; + +import com.seibel.lod.core.enums.config.DistanceGenerationMode; + +/** + * This is used to easily configure how LodChunks are generated. + * Generally this will only be used if we want to generate a + * LodChunk using an incomplete Chunk, otherwise the defaults + * work best for a fully generated chunk (IE has correct surface blocks). + * @author James Seibel + * @version 8-14-2021 + */ +public class LodBuilderConfig +{ + /** default: false */ + public boolean useHeightmap; + /** default: false */ + public boolean useBiomeColors; + /** default: true */ + public boolean useSolidBlocksInColorGen; + /** default: server */ + public DistanceGenerationMode distanceGenerationMode; + + /** + * default settings for a normal chunk
+ * useHeightmap = false
+ * useBiomeColors = false
+ * useSolidBlocksInColorGen = true
+ * generationMode = Server
+ */ + public LodBuilderConfig() + { + useHeightmap = false; + useBiomeColors = false; + useSolidBlocksInColorGen = true; + distanceGenerationMode = DistanceGenerationMode.FULL; + } + + /** + * @param newUseHeightmap default = false + * @param newUseBiomeColors default = false + * @param newUseSolidBlocksInBiomeColor default = true + * @param newDistanceGenerationMode default = Server + */ + public LodBuilderConfig(boolean newUseHeightmap, boolean newUseBiomeColors, + boolean newUseSolidBlocksInBiomeColor, DistanceGenerationMode newDistanceGenerationMode) + { + useHeightmap = newUseHeightmap; + useBiomeColors = newUseBiomeColors; + useSolidBlocksInColorGen = newUseSolidBlocksInBiomeColor; + distanceGenerationMode = newDistanceGenerationMode; + } + + /** + * @param newUseHeightmap default = false + * @param newUseBiomeColors default = false + * @param newUseSolidBlocksInBiomeColor default = true + */ + public LodBuilderConfig(boolean newUseHeightmap, boolean newUseBiomeColors, boolean newUseSolidBlocksInBiomeColor) + { + this(); + useHeightmap = newUseHeightmap; + useBiomeColors = newUseBiomeColors; + useSolidBlocksInColorGen = newUseSolidBlocksInBiomeColor; + distanceGenerationMode = newUseHeightmap ? DistanceGenerationMode.BIOME_ONLY_SIMULATE_HEIGHT : DistanceGenerationMode.BIOME_ONLY; + } + + /** + * @param newDistanceGenerationMode default = Server + */ + public LodBuilderConfig(DistanceGenerationMode newDistanceGenerationMode) + { + this(); + distanceGenerationMode = newDistanceGenerationMode; + } +} \ No newline at end of file diff --git a/src/main/java/com/seibel/lod/core/builders/worldGeneration/LodGenWorker.java b/src/main/java/com/seibel/lod/core/builders/worldGeneration/LodGenWorker.java new file mode 100644 index 000000000..77eb213b2 --- /dev/null +++ b/src/main/java/com/seibel/lod/core/builders/worldGeneration/LodGenWorker.java @@ -0,0 +1,212 @@ +/* + * This file is part of the Distant Horizon mod (formerly the LOD Mod), + * licensed under the GNU GPL v3 License. + * + * Copyright (C) 2020 James Seibel + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.seibel.lod.core.builders.worldGeneration; + +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +import com.google.common.util.concurrent.ThreadFactoryBuilder; +import com.seibel.lod.core.api.ClientApi; +import com.seibel.lod.core.builders.lodBuilding.LodBuilder; +import com.seibel.lod.core.enums.config.DistanceGenerationMode; +import com.seibel.lod.core.objects.lod.LodDimension; +import com.seibel.lod.core.util.LodUtil; +import com.seibel.lod.core.util.SingletonHandler; +import com.seibel.lod.core.wrapperInterfaces.IWrapperFactory; +import com.seibel.lod.core.wrapperInterfaces.chunk.AbstractChunkPosWrapper; +import com.seibel.lod.core.wrapperInterfaces.config.ILodConfigWrapperSingleton; +import com.seibel.lod.core.wrapperInterfaces.world.IWorldWrapper; +import com.seibel.lod.core.wrapperInterfaces.worldGeneration.AbstractWorldGeneratorWrapper; + +/** + * This is used to generate a LodChunk at a given ChunkPos. + * + * @author James Seibel + * @version 11-20-2021 + */ +public class LodGenWorker +{ + private static final ILodConfigWrapperSingleton CONFIG = SingletonHandler.get(ILodConfigWrapperSingleton.class); + private static final IWrapperFactory FACTORY = SingletonHandler.get(IWrapperFactory.class); + + public static ExecutorService genThreads = Executors.newFixedThreadPool(CONFIG.client().advanced().threading().getNumberOfWorldGenerationThreads(), new ThreadFactoryBuilder().setNameFormat("Gen-Worker-Thread-%d").build()); + + private final LodChunkGenThread thread; + + + + public LodGenWorker(AbstractChunkPosWrapper newPos, DistanceGenerationMode newGenerationMode, + LodBuilder newLodBuilder, + LodDimension newLodDimension, IWorldWrapper serverWorld) + { + // just a few sanity checks + if (newPos == null) + throw new IllegalArgumentException("LodChunkGenWorker must have a non-null ChunkPos"); + + if (newLodBuilder == null) + throw new IllegalArgumentException("LodChunkGenThread requires a non-null LodChunkBuilder"); + + if (newLodDimension == null) + throw new IllegalArgumentException("LodChunkGenThread requires a non-null LodDimension"); + + if (serverWorld == null) + throw new IllegalArgumentException("LodChunkGenThread requires a non-null ServerWorld"); + + + + thread = new LodChunkGenThread(newPos, newGenerationMode, + newLodBuilder, + newLodDimension, serverWorld); + } + + public void queueWork() + { + if (CONFIG.client().worldGenerator().getDistanceGenerationMode() == DistanceGenerationMode.FULL) + { + // if we are using FULL generation there is no reason + // to queue up a bunch of generation requests, + // because MC's internal server (as of 1.16.5) only + // responds with a single thread. And we don't + // want to cause more lag than necessary or queue up + // requests that may end up being unneeded. + thread.run(); + } + else + { + // Every other method can + // be done asynchronously + genThreads.execute(thread); + } + + // useful for debugging +// ClientProxy.LOGGER.info(thread.lodDim.getNumberOfLods()); +// ClientProxy.LOGGER.info(genThreads.toString()); + } + + + + + private static class LodChunkGenThread implements Runnable + { + private final AbstractWorldGeneratorWrapper worldGenWrapper; + + public final LodDimension lodDim; + public final DistanceGenerationMode generationMode; + + private final AbstractChunkPosWrapper pos; + + public LodChunkGenThread(AbstractChunkPosWrapper newPos, DistanceGenerationMode newGenerationMode, + LodBuilder newLodBuilder, + LodDimension newLodDimension, IWorldWrapper worldWrapper) + { + worldGenWrapper = FACTORY.createWorldGenerator(newLodBuilder, newLodDimension, worldWrapper); + + pos = newPos; + generationMode = newGenerationMode; + lodDim = newLodDimension; + } + + @Override + public void run() + { + try + { + // only generate LodChunks if they can + // be added to the current LodDimension + + if (lodDim.regionIsInRange(pos.getX() / LodUtil.REGION_WIDTH_IN_CHUNKS, pos.getZ() / LodUtil.REGION_WIDTH_IN_CHUNKS)) + { + switch (generationMode) + { + case NONE: + // don't generate + break; + case BIOME_ONLY: + case BIOME_ONLY_SIMULATE_HEIGHT: + // fastest + worldGenWrapper.generateBiomesOnly(pos, generationMode); + break; + case SURFACE: + // faster + worldGenWrapper.generateSurface(pos); + break; + case FEATURES: + // fast + worldGenWrapper.generateFeatures(pos); + break; + case FULL: + // very slow + worldGenWrapper.generateFull(pos); + break; + } + + +// boolean dataExistence = lodDim.doesDataExist(new LevelPos((byte) 3, pos.x, pos.z)); +// if (dataExistence) +// ClientProxy.LOGGER.info(pos.x + " " + pos.z + " Success!"); +// else +// ClientProxy.LOGGER.info(pos.x + " " + pos.z); + + // shows the pool size, active threads, queued tasks and completed tasks +// ClientProxy.LOGGER.info(genThreads.toString()); + +// long endTime = System.currentTimeMillis(); +// System.out.println(endTime - startTime); + + }// if in range + } + catch (Exception e) + { + ClientApi.LOGGER.error(LodChunkGenThread.class.getSimpleName() + ": ran into an error: " + e.getMessage()); + e.printStackTrace(); + } + finally + { + // decrement how many threads are running + LodWorldGenerator.INSTANCE.numberOfChunksWaitingToGenerate.addAndGet(-1); + + // this position is no longer being generated + LodWorldGenerator.INSTANCE.positionsWaitingToBeGenerated.remove(pos); + } + }// run + + + } + + + /** + * Stops the current genThreads if they are running + * and then recreates the Executor service.

+ *

+ * This is done to clear any outstanding tasks + * that may exist after the player leaves their current world. + * If this isn't done unfinished tasks may be left in the queue + * preventing new LodChunks form being generated. + */ + public static void restartExecutorService() + { + if (genThreads != null && !genThreads.isShutdown()) + { + genThreads.shutdownNow(); + } + genThreads = Executors.newFixedThreadPool(CONFIG.client().advanced().threading().getNumberOfWorldGenerationThreads(), new ThreadFactoryBuilder().setNameFormat("Gen-Worker-Thread-%d").build()); + } + +} diff --git a/src/main/java/com/seibel/lod/core/builders/worldGeneration/LodWorldGenerator.java b/src/main/java/com/seibel/lod/core/builders/worldGeneration/LodWorldGenerator.java new file mode 100644 index 000000000..e97498c7c --- /dev/null +++ b/src/main/java/com/seibel/lod/core/builders/worldGeneration/LodWorldGenerator.java @@ -0,0 +1,209 @@ +/* + * This file is part of the Distant Horizon mod (formerly the LOD Mod), + * licensed under the GNU GPL v3 License. + * + * Copyright (C) 2020 James Seibel + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.seibel.lod.core.builders.worldGeneration; + +import java.util.HashSet; +import java.util.Set; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.atomic.AtomicInteger; + +import com.seibel.lod.core.builders.lodBuilding.LodBuilder; +import com.seibel.lod.core.enums.config.DistanceGenerationMode; +import com.seibel.lod.core.objects.PosToGenerateContainer; +import com.seibel.lod.core.objects.lod.LodDimension; +import com.seibel.lod.core.render.LodRenderer; +import com.seibel.lod.core.util.DetailDistanceUtil; +import com.seibel.lod.core.util.LevelPosUtil; +import com.seibel.lod.core.util.LodThreadFactory; +import com.seibel.lod.core.util.LodUtil; +import com.seibel.lod.core.util.SingletonHandler; +import com.seibel.lod.core.wrapperInterfaces.IWrapperFactory; +import com.seibel.lod.core.wrapperInterfaces.chunk.AbstractChunkPosWrapper; +import com.seibel.lod.core.wrapperInterfaces.config.ILodConfigWrapperSingleton; +import com.seibel.lod.core.wrapperInterfaces.minecraft.IMinecraftWrapper; +import com.seibel.lod.core.wrapperInterfaces.world.IWorldWrapper; + +/** + * A singleton that handles all long distance LOD world generation. + * @author Leonardo Amato + * @author James Seibel + * @version 9-25-2021 + */ +public class LodWorldGenerator +{ + private static final IMinecraftWrapper MC = SingletonHandler.get(IMinecraftWrapper.class); + private static final ILodConfigWrapperSingleton CONFIG = SingletonHandler.get(ILodConfigWrapperSingleton.class); + private static final IWrapperFactory WRAPPER_FACTORY = SingletonHandler.get(IWrapperFactory.class); + + + /** This holds the thread used to create LOD generation requests off the main thread. */ + private final ExecutorService mainGenThread = Executors.newSingleThreadExecutor(new LodThreadFactory(this.getClass().getSimpleName() + " world generator")); + + /** we only want to queue up one generator thread at a time */ + private boolean generatorThreadRunning = false; + + /** + * How many chunks to generate outside the player's view distance at one + * time. (or more specifically how many requests to make at one time). I + * multiply by 8 to make sure there is always a buffer of chunk requests, to + * make sure the CPU is always busy, and we can generate LODs as quickly as + * possible. + */ + public int maxChunkGenRequests; + + /** + * This keeps track of how many chunk generation requests are on going. This is + * to limit how many chunks are queued at once. To prevent chunks from being + * generated for a long time in an area the player is no longer in. + */ + public final AtomicInteger numberOfChunksWaitingToGenerate = new AtomicInteger(0); + + public final Set positionsWaitingToBeGenerated = new HashSet<>(); + + /** + * Singleton copy of this object + */ + public static final LodWorldGenerator INSTANCE = new LodWorldGenerator(); + + + + private LodWorldGenerator() + { + + } + + /** + * Queues up LodNodeGenWorkers for the given lodDimension. + * @param renderer needed so the LodNodeGenWorkers can flag that the + * buffers need to be rebuilt. + */ + public void queueGenerationRequests(LodDimension lodDim, LodRenderer renderer, LodBuilder lodBuilder) + { + if (CONFIG.client().worldGenerator().getDistanceGenerationMode() != DistanceGenerationMode.NONE + && !generatorThreadRunning + && MC.hasSinglePlayerServer()) + { + // the thread is now running, don't queue up another thread + generatorThreadRunning = true; + + // just in case the config changed + maxChunkGenRequests = CONFIG.client().advanced().threading().getNumberOfWorldGenerationThreads() * 8; + + Thread generatorThread = new Thread(() -> + { + try + { + // round the player's block position down to the nearest chunk BlockPos + int playerPosX = MC.getPlayerBlockPos().getX(); + int playerPosZ = MC.getPlayerBlockPos().getZ(); + + + //=======================================// + // fill in positionsWaitingToBeGenerated // + //=======================================// + + IWorldWrapper serverWorld = LodUtil.getServerWorldFromDimension(lodDim.dimension); + + PosToGenerateContainer posToGenerate = lodDim.getPosToGenerate( + maxChunkGenRequests, + playerPosX, + playerPosZ); + + + byte detailLevel; + int posX; + int posZ; + int nearIndex = 0; + int farIndex = 0; + + for (int i = 0; i < posToGenerate.getNumberOfPos(); i++) + { + // I wish there was a way to compress this code, but I'm not aware of + // an easy way to do so. + + // add the near positions + if (posToGenerate.getNthDetail(nearIndex, true) != 0 && nearIndex < posToGenerate.getNumberOfNearPos()) + { + detailLevel = (byte) (posToGenerate.getNthDetail(nearIndex, true) - 1); + posX = posToGenerate.getNthPosX(nearIndex, true); + posZ = posToGenerate.getNthPosZ(nearIndex, true); + nearIndex++; + + AbstractChunkPosWrapper chunkPos = WRAPPER_FACTORY.createChunkPos(LevelPosUtil.getChunkPos(detailLevel, posX), LevelPosUtil.getChunkPos(detailLevel, posZ)); + + // prevent generating the same chunk multiple times + if (positionsWaitingToBeGenerated.contains(chunkPos)) + continue; + + // don't add more to the generation queue then allowed + if (numberOfChunksWaitingToGenerate.get() >= maxChunkGenRequests) + break; + + positionsWaitingToBeGenerated.add(chunkPos); + numberOfChunksWaitingToGenerate.addAndGet(1); + LodGenWorker genWorker = new LodGenWorker(chunkPos, DetailDistanceUtil.getDistanceGenerationMode(detailLevel), lodBuilder, lodDim, serverWorld); + genWorker.queueWork(); + } + + + // add the far positions + if (posToGenerate.getNthDetail(farIndex, false) != 0 && farIndex < posToGenerate.getNumberOfFarPos()) + { + detailLevel = (byte) (posToGenerate.getNthDetail(farIndex, false) - 1); + posX = posToGenerate.getNthPosX(farIndex, false); + posZ = posToGenerate.getNthPosZ(farIndex, false); + farIndex++; + + AbstractChunkPosWrapper chunkPos = WRAPPER_FACTORY.createChunkPos(LevelPosUtil.getChunkPos(detailLevel, posX), LevelPosUtil.getChunkPos(detailLevel, posZ)); + + // don't add more to the generation queue then allowed + if (numberOfChunksWaitingToGenerate.get() >= maxChunkGenRequests) + continue; + //break; + + // prevent generating the same chunk multiple times + if (positionsWaitingToBeGenerated.contains(chunkPos)) + continue; + + positionsWaitingToBeGenerated.add(chunkPos); + numberOfChunksWaitingToGenerate.addAndGet(1); + LodGenWorker genWorker = new LodGenWorker(chunkPos, DetailDistanceUtil.getDistanceGenerationMode(detailLevel), lodBuilder, lodDim, serverWorld); + genWorker.queueWork(); + } + } + + } + catch (Exception e) + { + // this shouldn't ever happen, but just in case + e.printStackTrace(); + } + finally + { + generatorThreadRunning = false; + } + }); + + mainGenThread.execute(generatorThread); + } // if distanceGenerationMode != DistanceGenerationMode.NONE && !generatorThreadRunning + } // queueGenerationRequests + +} diff --git a/src/main/java/com/seibel/lod/core/dataFormat/BlockDataFormat.java b/src/main/java/com/seibel/lod/core/dataFormat/BlockDataFormat.java new file mode 100644 index 000000000..d8becb6f5 --- /dev/null +++ b/src/main/java/com/seibel/lod/core/dataFormat/BlockDataFormat.java @@ -0,0 +1,5 @@ +package com.seibel.lod.core.dataFormat; + +public class BlockDataFormat +{ +} diff --git a/src/main/java/com/seibel/lod/core/dataFormat/ColorFormat.java b/src/main/java/com/seibel/lod/core/dataFormat/ColorFormat.java new file mode 100644 index 000000000..750fcde64 --- /dev/null +++ b/src/main/java/com/seibel/lod/core/dataFormat/ColorFormat.java @@ -0,0 +1,5 @@ +package com.seibel.lod.core.dataFormat; + +public class ColorFormat +{ +} diff --git a/src/main/java/com/seibel/lod/core/dataFormat/DepthHeightFormat.java b/src/main/java/com/seibel/lod/core/dataFormat/DepthHeightFormat.java new file mode 100644 index 000000000..3573abec2 --- /dev/null +++ b/src/main/java/com/seibel/lod/core/dataFormat/DepthHeightFormat.java @@ -0,0 +1,5 @@ +package com.seibel.lod.core.dataFormat; + +public class DepthHeightFormat +{ +} diff --git a/src/main/java/com/seibel/lod/core/dataFormat/LightFormat.java b/src/main/java/com/seibel/lod/core/dataFormat/LightFormat.java new file mode 100644 index 000000000..188a76cdb --- /dev/null +++ b/src/main/java/com/seibel/lod/core/dataFormat/LightFormat.java @@ -0,0 +1,5 @@ +package com.seibel.lod.core.dataFormat; + +public class LightFormat +{ +} diff --git a/src/main/java/com/seibel/lod/core/dataFormat/PositionDataFormat.java b/src/main/java/com/seibel/lod/core/dataFormat/PositionDataFormat.java new file mode 100644 index 000000000..8f8940ebe --- /dev/null +++ b/src/main/java/com/seibel/lod/core/dataFormat/PositionDataFormat.java @@ -0,0 +1,5 @@ +package com.seibel.lod.core.dataFormat; + +public class PositionDataFormat +{ +} diff --git a/src/main/java/com/seibel/lod/core/enums/LodDirection.java b/src/main/java/com/seibel/lod/core/enums/LodDirection.java new file mode 100644 index 000000000..2467ed14b --- /dev/null +++ b/src/main/java/com/seibel/lod/core/enums/LodDirection.java @@ -0,0 +1,529 @@ +package com.seibel.lod.core.enums; + +import java.util.Arrays; +import java.util.Locale; +import java.util.Map; +import java.util.function.Predicate; +import java.util.stream.Collectors; + +import com.seibel.lod.core.objects.math.Vec3i; + +/** + * A (almost) exact copy of Minecraft's + * Direction enum. + * + * @author James Seibel + * @version 11-13-2021 + */ +public enum LodDirection +{ + DOWN(0, 1, -1, "down", LodDirection.AxisDirection.NEGATIVE, LodDirection.Axis.Y, new Vec3i(0, -1, 0)), + UP(1, 0, -1, "up", LodDirection.AxisDirection.POSITIVE, LodDirection.Axis.Y, new Vec3i(0, 1, 0)), + NORTH(2, 3, 2, "north", LodDirection.AxisDirection.NEGATIVE, LodDirection.Axis.Z, new Vec3i(0, 0, -1)), + SOUTH(3, 2, 0, "south", LodDirection.AxisDirection.POSITIVE, LodDirection.Axis.Z, new Vec3i(0, 0, 1)), + WEST(4, 5, 1, "west", LodDirection.AxisDirection.NEGATIVE, LodDirection.Axis.X, new Vec3i(-1, 0, 0)), + EAST(5, 4, 3, "east", LodDirection.AxisDirection.POSITIVE, LodDirection.Axis.X, new Vec3i(1, 0, 0)); + +// private final int data3d; +// private final int oppositeIndex; +// private final int data2d; + + private final String name; + private final LodDirection.Axis axis; + private final LodDirection.AxisDirection axisDirection; + private final Vec3i normal; + private static final LodDirection[] VALUES = values(); + + private static final Map BY_NAME = Arrays.stream(VALUES).collect(Collectors.toMap(LodDirection::getName, (p_199787_0_) -> + { + return p_199787_0_; + })); + +// private static final LodDirection[] BY_3D_DATA = Arrays.stream(VALUES).sorted(Comparator.comparingInt((p_199790_0_) -> +// { +// return p_199790_0_.data3d; +// })).toArray((p_199788_0_) -> +// { +// return new LodDirection[p_199788_0_]; +// }); +// +// private static final LodDirection[] BY_2D_DATA = Arrays.stream(VALUES).filter((p_199786_0_) -> +// { +// return p_199786_0_.getAxis().isHorizontal(); +// }).sorted(Comparator.comparingInt((p_199789_0_) -> +// { +// return p_199789_0_.data2d; +// })).toArray((p_199791_0_) -> +// { +// return new LodDirection[p_199791_0_]; +// }); + +// private static final Long2ObjectMap BY_NORMAL = Arrays.stream(VALUES).collect(Collectors.toMap((p_218385_0_) -> +// { +// return (new BlockPos(p_218385_0_.getNormal())).asLong(); +// }, (p_218384_0_) -> +// { +// return p_218384_0_; +// }, (p_218386_0_, p_218386_1_) -> +// { +// throw new IllegalArgumentException("Duplicate keys"); +// }, Long2ObjectOpenHashMap::new)); + + + + LodDirection(int p_i46016_3_, int p_i46016_4_, int p_i46016_5_, String p_i46016_6_, LodDirection.AxisDirection p_i46016_7_, LodDirection.Axis p_i46016_8_, Vec3i p_i46016_9_) + { +// this.data3d = p_i46016_3_; +// this.data2d = p_i46016_5_; +// this.oppositeIndex = p_i46016_4_; + this.name = p_i46016_6_; + this.axis = p_i46016_8_; + this.axisDirection = p_i46016_7_; + this.normal = p_i46016_9_; + } + + + + +// public static LodDirection[] orderedByNearest(Entity p_196054_0_) +// { +// float f = p_196054_0_.getViewXRot(1.0F) * ((float) Math.PI / 180F); +// float f1 = -p_196054_0_.getViewYRot(1.0F) * ((float) Math.PI / 180F); +// float f2 = MathHelper.sin(f); +// float f3 = MathHelper.cos(f); +// float f4 = MathHelper.sin(f1); +// float f5 = MathHelper.cos(f1); +// boolean flag = f4 > 0.0F; +// boolean flag1 = f2 < 0.0F; +// boolean flag2 = f5 > 0.0F; +// float f6 = flag ? f4 : -f4; +// float f7 = flag1 ? -f2 : f2; +// float f8 = flag2 ? f5 : -f5; +// float f9 = f6 * f3; +// float f10 = f8 * f3; +// LodDirection lodDirection = flag ? EAST : WEST; +// LodDirection direction1 = flag1 ? UP : DOWN; +// LodDirection direction2 = flag2 ? SOUTH : NORTH; +// if (f6 > f8) +// { +// if (f7 > f9) +// { +// return makeDirectionArray(direction1, lodDirection, direction2); +// } +// else +// { +// return f10 > f7 ? makeDirectionArray(lodDirection, direction2, direction1) : makeDirectionArray(lodDirection, direction1, direction2); +// } +// } +// else if (f7 > f10) +// { +// return makeDirectionArray(direction1, direction2, lodDirection); +// } +// else +// { +// return f9 > f7 ? makeDirectionArray(direction2, lodDirection, direction1) : makeDirectionArray(direction2, direction1, lodDirection); +// } +// } + +// private static LodDirection[] makeDirectionArray(LodDirection p_196053_0_, LodDirection p_196053_1_, LodDirection p_196053_2_) +// { +// return new LodDirection[] { p_196053_0_, p_196053_1_, p_196053_2_, p_196053_2_.getOpposite(), p_196053_1_.getOpposite(), p_196053_0_.getOpposite() }; +// } + +// public static LodDirection rotate(Mat4f p_229385_0_, LodDirection p_229385_1_) +// { +// Vec3i Vec3i = p_229385_1_.getNormal(); +// Vector4f vector4f = new Vector4f(Vec3i.getX(), Vec3i.getY(), Vec3i.getZ(), 0.0F); +// vector4f.transform(p_229385_0_); +// return getNearest(vector4f.x(), vector4f.y(), vector4f.z()); +// } + +// public Quaternion getRotation() +// { +// Quaternion quaternion = Vector3f.XP.rotationDegrees(90.0F); +// switch (this) +// { +// case DOWN: +// return Vector3f.XP.rotationDegrees(180.0F); +// case UP: +// return Quaternion.ONE.copy(); +// case NORTH: +// quaternion.mul(Vector3f.ZP.rotationDegrees(180.0F)); +// return quaternion; +// case SOUTH: +// return quaternion; +// case WEST: +// quaternion.mul(Vector3f.ZP.rotationDegrees(90.0F)); +// return quaternion; +// case EAST: +// default: +// quaternion.mul(Vector3f.ZP.rotationDegrees(-90.0F)); +// return quaternion; +// } +// } + +// public int get3DDataValue() +// { +// return this.data3d; +// } +// +// public int get2DDataValue() +// { +// return this.data2d; +// } + + public LodDirection.AxisDirection getAxisDirection() + { + return this.axisDirection; + } + +// public LodDirection getOpposite() +// { +// return from3DDataValue(this.oppositeIndex); +// } + + public LodDirection getClockWise() + { + switch (this) + { + case NORTH: + return EAST; + case SOUTH: + return WEST; + case WEST: + return NORTH; + case EAST: + return SOUTH; + default: + throw new IllegalStateException("Unable to get Y-rotated facing of " + this); + } + } + + public LodDirection getCounterClockWise() + { + switch (this) + { + case NORTH: + return WEST; + case SOUTH: + return EAST; + case WEST: + return SOUTH; + case EAST: + return NORTH; + default: + throw new IllegalStateException("Unable to get CCW facing of " + this); + } + } + + public String getName() + { + return this.name; + } + + public LodDirection.Axis getAxis() + { + return this.axis; + } + + public static LodDirection byName(String name) + { + return name == null ? null : BY_NAME.get(name.toLowerCase(Locale.ROOT)); + } + +// public static LodDirection from3DDataValue(int p_82600_0_) +// { +// return BY_3D_DATA[MathHelper.abs(p_82600_0_ % BY_3D_DATA.length)]; +// } +// +// public static LodDirection from2DDataValue(int p_176731_0_) +// { +// return BY_2D_DATA[MathHelper.abs(p_176731_0_ % BY_2D_DATA.length)]; +// } + +// @Nullable +// public static LodDirection fromNormal(int p_218383_0_, int p_218383_1_, int p_218383_2_) +// { +// return BY_NORMAL.get(BlockPos.asLong(p_218383_0_, p_218383_1_, p_218383_2_)); +// } + +// public static LodDirection fromYRot(double p_176733_0_) +// { +// return from2DDataValue(MathHelper.floor(p_176733_0_ / 90.0D + 0.5D) & 3); +// } + + public static LodDirection fromAxisAndDirection(LodDirection.Axis p_211699_0_, LodDirection.AxisDirection p_211699_1_) + { + switch (p_211699_0_) + { + case X: + return p_211699_1_ == LodDirection.AxisDirection.POSITIVE ? EAST : WEST; + case Y: + return p_211699_1_ == LodDirection.AxisDirection.POSITIVE ? UP : DOWN; + case Z: + default: + return p_211699_1_ == LodDirection.AxisDirection.POSITIVE ? SOUTH : NORTH; + } + } + +// public float toYRot() +// { +// return (this.data2d & 3) * 90; +// } + +// public static LodDirection getRandom(Random p_239631_0_) +// { +// return Util.getRandom(VALUES, p_239631_0_); +// } + +// public static LodDirection getNearest(double p_210769_0_, double p_210769_2_, double p_210769_4_) +// { +// return getNearest((float) p_210769_0_, (float) p_210769_2_, (float) p_210769_4_); +// } + +// public static LodDirection getNearest(float p_176737_0_, float p_176737_1_, float p_176737_2_) +// { +// LodDirection lodDirection = NORTH; +// float f = Float.MIN_VALUE; +// +// for (LodDirection direction1 : VALUES) +// { +// float f1 = p_176737_0_ * direction1.normal.x + p_176737_1_ * direction1.normal.y + p_176737_2_ * direction1.normal.z; +// if (f1 > f) +// { +// f = f1; +// lodDirection = direction1; +// } +// } +// +// return lodDirection; +// } + + public static LodDirection get(LodDirection.AxisDirection p_181076_0_, LodDirection.Axis p_181076_1_) + { + for (LodDirection lodDirection : VALUES) + { + if (lodDirection.getAxisDirection() == p_181076_0_ && lodDirection.getAxis() == p_181076_1_) + { + return lodDirection; + } + } + + throw new IllegalArgumentException("No such direction: " + p_181076_0_ + " " + p_181076_1_); + } + + public Vec3i getNormal() + { + return this.normal; + } + +// public boolean isFacingAngle(float p_243532_1_) +// { +// float f = p_243532_1_ * ((float) Math.PI / 180F); +// float f1 = -MathHelper.sin(f); +// float f2 = MathHelper.cos(f); +// return this.normal.getX() * f1 + this.normal.getZ() * f2 > 0.0F; +// } + + public enum Axis implements Predicate + { + X("x") + { + @Override + public int choose(int x, int y, int z) + { + return x; + } + + @Override + public double choose(double x, double y, double z) + { + return x; + } + }, + Y("y") + { + @Override + public int choose(int x, int y, int z) + { + return y; + } + + @Override + public double choose(double x, double y, double z) + { + return y; + } + }, + Z("z") + { + @Override + public int choose(int x, int y, int z) + { + return z; + } + + @Override + public double choose(double x, double y, double z) + { + return z; + } + }; + + private static final LodDirection.Axis[] VALUES = values(); + + private static final Map BY_NAME = Arrays.stream(VALUES).collect(Collectors.toMap(LodDirection.Axis::getName, (p_199785_0_) -> + { + return p_199785_0_; + })); + private final String name; + + Axis(String name) + { + this.name = name; + } + + public static LodDirection.Axis byName(String name) + { + return BY_NAME.get(name.toLowerCase(Locale.ROOT)); + } + + public String getName() + { + return this.name; + } + + public boolean isVertical() + { + return this == Y; + } + + public boolean isHorizontal() + { + return this == X || this == Z; + } + + @Override + public String toString() + { + return this.name; + } + +// public static LodDirection.Axis getRandom(Random p_239634_0_) +// { +// return Util.getRandom(VALUES, p_239634_0_); +// } + + @Override + public boolean test(LodDirection p_test_1_) + { + return p_test_1_ != null && p_test_1_.getAxis() == this; + } + +// public LodDirection.Plane getPlane() +// { +// switch (this) +// { +// case X: +// case Z: +// return LodDirection.Plane.HORIZONTAL; +// case Y: +// return LodDirection.Plane.VERTICAL; +// default: +// throw new Error("Someone's been tampering with the universe!"); +// } +// } + + public abstract int choose(int p_196052_1_, int p_196052_2_, int p_196052_3_); + + public abstract double choose(double p_196051_1_, double p_196051_3_, double p_196051_5_); + } + + public enum AxisDirection + { + POSITIVE(1, "Towards positive"), + NEGATIVE(-1, "Towards negative"); + + private final int step; + private final String name; + + AxisDirection(int newStep, String newName) + { + this.step = newStep; + this.name = newName; + } + + public int getStep() + { + return this.step; + } + + @Override + public String toString() + { + return this.name; + } + + public LodDirection.AxisDirection opposite() + { + return this == POSITIVE ? NEGATIVE : POSITIVE; + } + } + +// public static enum Plane implements Iterable, Predicate +// { +// HORIZONTAL(new LodDirection[] { LodDirection.NORTH, LodDirection.EAST, LodDirection.SOUTH, LodDirection.WEST }, new LodDirection.Axis[] { LodDirection.Axis.X, LodDirection.Axis.Z }), +// VERTICAL(new LodDirection[] { LodDirection.UP, LodDirection.DOWN }, new LodDirection.Axis[] { LodDirection.Axis.Y }); +// +// private final LodDirection[] faces; +// private final LodDirection.Axis[] axis; +// +// private Plane(LodDirection[] p_i49393_3_, LodDirection.Axis[] p_i49393_4_) +// { +// this.faces = p_i49393_3_; +// this.axis = p_i49393_4_; +// } +// +// public LodDirection getRandomDirection(Random p_179518_1_) +// { +// return Util.getRandom(this.faces, p_179518_1_); +// } +// +// public LodDirection.Axis getRandomAxis(Random p_244803_1_) +// { +// return Util.getRandom(this.axis, p_244803_1_); +// } +// +// @Override +// public boolean test(@Nullable LodDirection p_test_1_) +// { +// return p_test_1_ != null && p_test_1_.getAxis().getPlane() == this; +// } +// +// @Override +// public Iterator iterator() +// { +// return Iterators.forArray(this.faces); +// } +// +// public Stream stream() +// { +// return Arrays.stream(this.faces); +// } +// } + + + + + public String getSerializedName() + { + return this.name; + } + + @Override + public String toString() + { + return this.name; + } + +} diff --git a/src/main/java/com/seibel/lod/core/enums/WorldType.java b/src/main/java/com/seibel/lod/core/enums/WorldType.java new file mode 100644 index 000000000..a36b06194 --- /dev/null +++ b/src/main/java/com/seibel/lod/core/enums/WorldType.java @@ -0,0 +1,33 @@ +/* + * This file is part of the Distant Horizon mod (formerly the LOD Mod), + * licensed under the GNU GPL v3 License. + * + * Copyright (C) 2020 James Seibel + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.seibel.lod.core.enums; + +/** + * ServerWorld, ClientWorld, Unknown + * + * @author James Seibel + * @version 11-12-2021 + */ +public enum WorldType +{ + ServerWorld, + ClientWorld, + Unknown +} diff --git a/src/main/java/com/seibel/lod/core/enums/config/BlocksToAvoid.java b/src/main/java/com/seibel/lod/core/enums/config/BlocksToAvoid.java new file mode 100644 index 000000000..bdfcaf363 --- /dev/null +++ b/src/main/java/com/seibel/lod/core/enums/config/BlocksToAvoid.java @@ -0,0 +1,47 @@ +/* + * This file is part of the Distant Horizon mod (formerly the LOD Mod), + * licensed under the GNU GPL v3 License. + * + * Copyright (C) 2020 James Seibel + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.seibel.lod.core.enums.config; + +/** + * heightmap
+ * multi_lod
+ * + * @author Leonardo Amato + * @version 11-16-2021 + */ +public enum BlocksToAvoid +{ + NONE(false, false), + + NON_FULL(true, false), + + NO_COLLISION(false, true), + + BOTH(true, true); + + public final boolean nonFull; + public final boolean noCollision; + + BlocksToAvoid(boolean nonFull, boolean noCollision) + { + this.nonFull = nonFull; + this.noCollision = noCollision; + } +} \ No newline at end of file diff --git a/src/main/java/com/seibel/lod/core/enums/config/BufferRebuildTimes.java b/src/main/java/com/seibel/lod/core/enums/config/BufferRebuildTimes.java new file mode 100644 index 000000000..3c5db0390 --- /dev/null +++ b/src/main/java/com/seibel/lod/core/enums/config/BufferRebuildTimes.java @@ -0,0 +1,52 @@ +/* + * This file is part of the Distant Horizon mod (formerly the LOD Mod), + * licensed under the GNU GPL v3 License. + * + * Copyright (C) 2020 James Seibel + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.seibel.lod.core.enums.config; + +/** + * FREQUENT
+ * NORMAL
+ * RARE
+ *
+ * Determines how fast the buffers need to be regenerated + * + * @author Leonardo Amato + * @version 9-25-2021 + */ +public enum BufferRebuildTimes +{ + FREQUENT(1000, 500, 2500, 1), + + NORMAL(2000, 1000, 5000, 4), + + RARE(5000, 2000, 10000, 16); + + public final int playerMoveTimeout; + public final int renderedChunkTimeout; + public final int chunkChangeTimeout; + public final int playerMoveDistance; + + BufferRebuildTimes(int playerMoveTimeout, int renderedChunkTimeout, int chunkChangeTimeout, int playerMoveDistance) + { + this.playerMoveTimeout = playerMoveTimeout; + this.renderedChunkTimeout = renderedChunkTimeout; + this.chunkChangeTimeout = chunkChangeTimeout; + this.playerMoveDistance = playerMoveDistance; + } +} diff --git a/src/main/java/com/seibel/lod/core/enums/config/DistanceGenerationMode.java b/src/main/java/com/seibel/lod/core/enums/config/DistanceGenerationMode.java new file mode 100644 index 000000000..712de3cc3 --- /dev/null +++ b/src/main/java/com/seibel/lod/core/enums/config/DistanceGenerationMode.java @@ -0,0 +1,95 @@ +/* + * This file is part of the Distant Horizon mod (formerly the LOD Mod), + * licensed under the GNU GPL v3 License. + * + * Copyright (C) 2020 James Seibel + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.seibel.lod.core.enums.config; + +/** + * NONE
+ * BIOME_ONLY
+ * BIOME_ONLY_SIMULATE_HEIGHT
+ * SURFACE
+ * FEATURES
+ * SERVER

+ *

+ * In order of fastest to slowest. + * + * @author James Seibel + * @author Leonardo Amato + * @version 8-7-2021 + */ +public enum DistanceGenerationMode +{ + /** + * Don't generate anything + */ + NONE((byte) 0), + + /** + * Only generate the biomes and use biome + * grass/foliage color, water color, or ice color + * to generate the color. + * Doesn't generate height, everything is shown at sea level. + * Multithreaded - Fastest (2-5 ms) + */ + BIOME_ONLY((byte) 1), + + /** + * Same as BIOME_ONLY, except instead + * of always using sea level as the LOD height + * different biome types (mountain, ocean, forest, etc.) + * use predetermined heights to simulate having height data. + */ + BIOME_ONLY_SIMULATE_HEIGHT((byte) 2), + + /** + * Generate the world surface, + * this does NOT include caves, trees, + * or structures. + * Multithreaded - Faster (10-20 ms) + */ + SURFACE((byte) 3), + + /** + * Generate everything except structures. + * NOTE: This may cause world generation bugs or instability, + * since some features cause concurrentModification exceptions. + * Multithreaded - Fast (15-20 ms) + */ + FEATURES((byte) 4), + + /** + * Ask the server to generate/load each chunk. + * This is the most compatible, but causes server/simulation lag. + * This will also show player made structures if you + * are adding the mod on a pre-existing world. + * Singlethreaded - Slow (15-50 ms, with spikes up to 200 ms) + */ + FULL((byte) 5); + + + /** + * The higher the number the more complete the generation is. + */ + public final byte complexity; + + DistanceGenerationMode(byte complexity) + { + this.complexity = complexity; + } +} diff --git a/src/main/java/com/seibel/lod/core/enums/config/GenerationPriority.java b/src/main/java/com/seibel/lod/core/enums/config/GenerationPriority.java new file mode 100644 index 000000000..af2089170 --- /dev/null +++ b/src/main/java/com/seibel/lod/core/enums/config/GenerationPriority.java @@ -0,0 +1,41 @@ +/* + * This file is part of the Distant Horizon mod (formerly the LOD Mod), + * licensed under the GNU GPL v3 License. + * + * Copyright (C) 2020 James Seibel + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.seibel.lod.core.enums.config; + +/** + * AUTO
+ * Near_First
+ * Far_First
+ *
+ * Determines which LODs should have priority when generating + * outside the normal view distance. + * + * @author Leonardo Amato + * @version 12-1-2021 + */ +public enum GenerationPriority +{ + /** NEAR_FIRST when connected to servers and FAR_FIRST when on single player */ + AUTO, + + NEAR_FIRST, + + FAR_FIRST +} diff --git a/src/main/java/com/seibel/lod/core/enums/config/GpuUploadMethod.java b/src/main/java/com/seibel/lod/core/enums/config/GpuUploadMethod.java new file mode 100644 index 000000000..4d36d676a --- /dev/null +++ b/src/main/java/com/seibel/lod/core/enums/config/GpuUploadMethod.java @@ -0,0 +1,59 @@ +/* + * This file is part of the Distant Horizon mod (formerly the LOD Mod), + * licensed under the GNU GPL v3 License. + * + * Copyright (C) 2020 James Seibel + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.seibel.lod.core.enums.config; + +/** + * Auto, Buffer_Storage, Sub_Data, Buffer_Mapping, Data + * + * @author James Seibel + * @version 12-1-2021 + */ +public enum GpuUploadMethod +{ + /** Picks the best option based on the GPU the user has. */ + AUTO, + + /** + * Default for NVIDIA if OpenGL 4.5 is supported.
+ * Fast rendering, no stuttering. + */ + BUFFER_STORAGE, + + /** + * Backup option for NVIDIA.
+ * Fast rendering but may stutter when uploading. + */ + SUB_DATA, + + /** + * Default option for AMD/Intel.
+ * May end up storing buffers in System memory.
+ * Fast rending if in GPU memory, slow if in system memory,
+ * but won't stutter when uploading. + */ + BUFFER_MAPPING, + + /** + * Backup option for AMD/Intel.
+ * Fast rendering but may stutter when uploading. + */ + DATA, + +} \ No newline at end of file diff --git a/src/main/java/com/seibel/lod/core/enums/config/HorizontalQuality.java b/src/main/java/com/seibel/lod/core/enums/config/HorizontalQuality.java new file mode 100644 index 000000000..e91ed0f68 --- /dev/null +++ b/src/main/java/com/seibel/lod/core/enums/config/HorizontalQuality.java @@ -0,0 +1,53 @@ +/* + * This file is part of the Distant Horizon mod (formerly the LOD Mod), + * licensed under the GNU GPL v3 License. + * + * Copyright (C) 2020 James Seibel + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.seibel.lod.core.enums.config; + +/** + * Lowest
+ * Low
+ * Medium
+ * High
+ *
+ * this indicates the base of the quadratic function we use for the quality drop off + * + * @author Leonardo Amato + * @version 9-29-2021 + */ +public enum HorizontalQuality +{ + /** 1.0 AKA Linear */ + LOWEST(1.0f), + + /** exponent 1.5 */ + LOW(1.5f), + + /** exponent 2.0 */ + MEDIUM(2.0f), + + /** exponent 2.2 */ + HIGH(2.2f); + + public final double quadraticBase; + + HorizontalQuality(double distanceUnit) + { + this.quadraticBase = distanceUnit; + } +} \ No newline at end of file diff --git a/src/main/java/com/seibel/lod/core/enums/config/HorizontalResolution.java b/src/main/java/com/seibel/lod/core/enums/config/HorizontalResolution.java new file mode 100644 index 000000000..794b67f71 --- /dev/null +++ b/src/main/java/com/seibel/lod/core/enums/config/HorizontalResolution.java @@ -0,0 +1,175 @@ +/* + * This file is part of the Distant Horizon mod (formerly the LOD Mod), + * licensed under the GNU GPL v3 License. + * + * Copyright (C) 2020 James Seibel + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.seibel.lod.core.enums.config; + +import java.util.ArrayList; +import java.util.Collections; + +import com.seibel.lod.core.util.LodUtil; + +/** + * chunk
+ * half_chunk
+ * four_blocks
+ * two_blocks
+ * block
+ * + * @author James Seibel + * @author Leonardo Amato + * @version 9-25-2021 + */ +public enum HorizontalResolution +{ + /** render 256 LODs for each chunk */ + BLOCK(16, 0), + + /** render 64 LODs for each chunk */ + TWO_BLOCKS(8, 1), + + /** render 16 LODs for each chunk */ + FOUR_BLOCKS(4, 2), + + /** render 4 LODs for each chunk */ + HALF_CHUNK(2, 3), + + /** render 1 LOD for each chunk */ + CHUNK(1, 4); + + + + + + /** + * How many DataPoints should + * be drawn per side, per LodChunk + */ + public final int dataPointLengthCount; + + /** How wide each LOD DataPoint is */ + public final int dataPointWidth; + + /** + * This is the same as detailLevel in LodQuadTreeNode, + * lowest is 0 highest is 9 + */ + public final byte detailLevel; + + /* Start/End X/Z give the block positions + * for each individual dataPoint in a LodChunk */ + public final int[] startX; + public final int[] startZ; + + public final int[] endX; + public final int[] endZ; + + + /** + * 1st dimension: LodDetail.detailLevel
+ * 2nd dimension: An array of all LodDetails that are less than or
+ * equal to that detailLevel + */ + private static HorizontalResolution[][] lowerDetailArrays; + + + + + HorizontalResolution(int newLengthCount, int newDetailLevel) + { + detailLevel = (byte) newDetailLevel; + dataPointLengthCount = newLengthCount; + dataPointWidth = 16 / dataPointLengthCount; + + startX = new int[dataPointLengthCount * dataPointLengthCount]; + endX = new int[dataPointLengthCount * dataPointLengthCount]; + + startZ = new int[dataPointLengthCount * dataPointLengthCount]; + endZ = new int[dataPointLengthCount * dataPointLengthCount]; + + + int index = 0; + for (int x = 0; x < newLengthCount; x++) + { + for (int z = 0; z < newLengthCount; z++) + { + startX[index] = x * dataPointWidth; + startZ[index] = z * dataPointWidth; + + endX[index] = (x * dataPointWidth) + dataPointWidth; + endZ[index] = (z * dataPointWidth) + dataPointWidth; + + index++; + } + } + + }// constructor + + + + + + + /** + * Returns an array of all LodDetails that have a detail level + * that is less than or equal to the given LodDetail + */ + public static HorizontalResolution[] getSelfAndLowerDetails(HorizontalResolution detail) + { + if (lowerDetailArrays == null) + { + // run first time setup + lowerDetailArrays = new HorizontalResolution[HorizontalResolution.values().length][]; + + // go through each LodDetail + for (HorizontalResolution currentDetail : HorizontalResolution.values()) + { + ArrayList lowerDetails = new ArrayList<>(); + + // find the details lower than currentDetail + for (HorizontalResolution compareDetail : HorizontalResolution.values()) + { + if (currentDetail.detailLevel <= compareDetail.detailLevel) + { + lowerDetails.add(compareDetail); + } + } + + // have the highest detail item first in the list + Collections.sort(lowerDetails); + Collections.reverse(lowerDetails); + + lowerDetailArrays[currentDetail.detailLevel] = lowerDetails.toArray(new HorizontalResolution[lowerDetails.size()]); + } + } + + return lowerDetailArrays[detail.detailLevel]; + } + + /** Returns what detail level should be used at a given distance and maxDistance. */ + public static HorizontalResolution getDetailForDistance(HorizontalResolution maxDetailLevel, int distance, int maxDistance) + { + HorizontalResolution[] lowerDetails = getSelfAndLowerDetails(maxDetailLevel); + int distanceBetweenDetails = maxDistance / lowerDetails.length; + int index = LodUtil.clamp(0, distance / distanceBetweenDetails, lowerDetails.length - 1); + + return lowerDetails[index]; + + } + +} diff --git a/src/main/java/com/seibel/lod/core/enums/config/HorizontalScale.java b/src/main/java/com/seibel/lod/core/enums/config/HorizontalScale.java new file mode 100644 index 000000000..e710de006 --- /dev/null +++ b/src/main/java/com/seibel/lod/core/enums/config/HorizontalScale.java @@ -0,0 +1,49 @@ +/* + * This file is part of the Distant Horizon mod (formerly the LOD Mod), + * licensed under the GNU GPL v3 License. + * + * Copyright (C) 2020 James Seibel + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.seibel.lod.core.enums.config; + +/** + * Low
+ * Medium
+ * High
+ *
+ * this is a quality scale for the detail drop-off + * + * @author Leonardo Amato + * @version 9-25-2021 + */ +public enum HorizontalScale +{ + /** Lods are 2D with heightMap */ + LOW(64), + + /** Lods expand in three dimension */ + MEDIUM(128), + + /** Lods expand in three dimension */ + HIGH(256); + + public final int distanceUnit; + + HorizontalScale(int distanceUnit) + { + this.distanceUnit = distanceUnit; + } +} \ No newline at end of file diff --git a/src/main/java/com/seibel/lod/core/enums/config/LodTemplate.java b/src/main/java/com/seibel/lod/core/enums/config/LodTemplate.java new file mode 100644 index 000000000..397699c7d --- /dev/null +++ b/src/main/java/com/seibel/lod/core/enums/config/LodTemplate.java @@ -0,0 +1,62 @@ +/* + * This file is part of the Distant Horizon mod (formerly the LOD Mod), + * licensed under the GNU GPL v3 License. + * + * Copyright (C) 2020 James Seibel + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.seibel.lod.core.enums.config; + +import com.seibel.lod.core.builders.bufferBuilding.lodTemplates.AbstractLodTemplate; +import com.seibel.lod.core.builders.bufferBuilding.lodTemplates.CubicLodTemplate; +import com.seibel.lod.core.builders.bufferBuilding.lodTemplates.DynamicLodTemplate; +import com.seibel.lod.core.builders.bufferBuilding.lodTemplates.TriangularLodTemplate; + +/** + * Cubic, Triangular, Dynamic + * + * @author James Seibel + * @version 10-10-2021 + */ +public enum LodTemplate +{ + /** + * LODs are rendered as + * rectangular prisms. + */ + CUBIC(new CubicLodTemplate()), + + /** + * LODs smoothly transition between + * each other. + */ + TRIANGULAR(new TriangularLodTemplate()), + + /** + * LODs smoothly transition between + * each other, unless a neighboring LOD + * is at a significantly different height. + */ + DYNAMIC(new DynamicLodTemplate()); + + + public final AbstractLodTemplate template; + + LodTemplate(AbstractLodTemplate newTemplate) + { + template = newTemplate; + } + +} diff --git a/src/main/java/com/seibel/lod/core/enums/config/ShadingMode.java b/src/main/java/com/seibel/lod/core/enums/config/ShadingMode.java new file mode 100644 index 000000000..1971d51bb --- /dev/null +++ b/src/main/java/com/seibel/lod/core/enums/config/ShadingMode.java @@ -0,0 +1,22 @@ +package com.seibel.lod.core.enums.config; + +/** + * NONE, GAME_SHADING + * + * @author James Seibel + * @version 7-25-2020 + */ +public enum ShadingMode +{ + /** + * LODs will have darker sides and bottoms to simulate + * Minecraft's fast lighting. + */ + GAME_SHADING, + + /** + * LODs will use ambient occlusion to mimic Minecarft's + * Fancy lighting. + */ + AMBIENT_OCCLUSION +} \ No newline at end of file diff --git a/src/main/java/com/seibel/lod/core/enums/config/VanillaOverdraw.java b/src/main/java/com/seibel/lod/core/enums/config/VanillaOverdraw.java new file mode 100644 index 000000000..e28552893 --- /dev/null +++ b/src/main/java/com/seibel/lod/core/enums/config/VanillaOverdraw.java @@ -0,0 +1,45 @@ +/* + * This file is part of the Distant Horizon mod (formerly the LOD Mod), + * licensed under the GNU GPL v3 License. + * + * Copyright (C) 2020 James Seibel + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.seibel.lod.core.enums.config; + +/** + * None, Dynamic, Always + * + *

+ * This represents how far the LODs should overlap with + * the vanilla Minecraft terrain. + * + * @author James Seibel + * @version 10-11-2021 + */ +public enum VanillaOverdraw +{ + /** Never draw LODs where a minecraft chunk could be. */ + NEVER, + + /** Draw LODs over the farther minecraft chunks. */ + DYNAMIC, + + /** Draw LODs over all minecraft chunks. */ + ALWAYS, + + /** Draw LODs over border chunks. */ + BORDER, +} \ No newline at end of file diff --git a/src/main/java/com/seibel/lod/core/enums/config/VerticalQuality.java b/src/main/java/com/seibel/lod/core/enums/config/VerticalQuality.java new file mode 100644 index 000000000..9d82f9486 --- /dev/null +++ b/src/main/java/com/seibel/lod/core/enums/config/VerticalQuality.java @@ -0,0 +1,80 @@ +/* + * This file is part of the Distant Horizon mod (formerly the LOD Mod), + * licensed under the GNU GPL v3 License. + * + * Copyright (C) 2020 James Seibel + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.seibel.lod.core.enums.config; + +/** + * heightmap
+ * multi_lod
+ * + * @author Leonardo Amato + * @version 10-07-2021 + */ +public enum VerticalQuality +{ + LOW( + new int[] { 2, + 2, + 2, + 2, + 1, + 1, + 1, + 1, + 1, + 1, + 1 } + ), + + MEDIUM( + new int[] { 4, + 4, + 2, + 2, + 2, + 1, + 1, + 1, + 1, + 1, + 1 } + ), + + HIGH( + new int[] { + 8, + 8, + 4, + 4, + 2, + 2, + 2, + 1, + 1, + 1, + 1 } + ); + + public final int[] maxVerticalData; + + VerticalQuality(int[] maxVerticalData) + { + this.maxVerticalData = maxVerticalData; + } +} \ No newline at end of file diff --git a/src/main/java/com/seibel/lod/core/enums/rendering/DebugMode.java b/src/main/java/com/seibel/lod/core/enums/rendering/DebugMode.java new file mode 100644 index 000000000..cdc02fa1f --- /dev/null +++ b/src/main/java/com/seibel/lod/core/enums/rendering/DebugMode.java @@ -0,0 +1,54 @@ +/* + * This file is part of the Distant Horizon mod (formerly the LOD Mod), + * licensed under the GNU GPL v3 License. + * + * Copyright (C) 2020 James Seibel + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.seibel.lod.core.enums.rendering; + +/** + * off, detail, detail wireframe + * + * @author James Seibel + * @version 8-28-2021 + */ +public enum DebugMode +{ + /** LODs are rendered normally */ + OFF, + + /** LOD colors are based on their detail */ + SHOW_DETAIL, + + /** LOD colors are based on their detail, and draws in wireframe. */ + SHOW_DETAIL_WIREFRAME; + + /** used when cycling through the different modes */ + private DebugMode next; + + static + { + OFF.next = SHOW_DETAIL; + SHOW_DETAIL.next = SHOW_DETAIL_WIREFRAME; + SHOW_DETAIL_WIREFRAME.next = OFF; + } + + /** returns the next debug mode */ + public DebugMode getNext() + { + return this.next; + } +} diff --git a/src/main/java/com/seibel/lod/core/enums/rendering/FogColorMode.java b/src/main/java/com/seibel/lod/core/enums/rendering/FogColorMode.java new file mode 100644 index 000000000..14b68004c --- /dev/null +++ b/src/main/java/com/seibel/lod/core/enums/rendering/FogColorMode.java @@ -0,0 +1,42 @@ +/* + * This file is part of the Distant Horizon mod (formerly the LOD Mod), + * licensed under the GNU GPL v3 License. + * + * Copyright (C) 2020 James Seibel + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.seibel.lod.core.enums.rendering; + +/** + * USE_DEFAULT_FOG_COLOR,
+ * USE_SKY_COLOR,
+ * + * @author James Seibel + * @version 11-27-2021 + */ +public enum FogColorMode +{ + /** Fog uses Minecraft's fog color. */ + USE_WORLD_FOG_COLOR, + + /** + * Replicates the effect of the clear sky mod. + * Making the fog blend in with the sky better + * https://www.curseforge.com/minecraft/mc-mods/clear-skies + * https://www.curseforge.com/minecraft/mc-mods/clear-skies-forge-port + * For it to look good you need one of those mods + */ + USE_SKY_COLOR, +} diff --git a/src/main/java/com/seibel/lod/core/enums/rendering/FogDistance.java b/src/main/java/com/seibel/lod/core/enums/rendering/FogDistance.java new file mode 100644 index 000000000..a73443ca4 --- /dev/null +++ b/src/main/java/com/seibel/lod/core/enums/rendering/FogDistance.java @@ -0,0 +1,33 @@ +/* + * This file is part of the Distant Horizon mod (formerly the LOD Mod), + * licensed under the GNU GPL v3 License. + * + * Copyright (C) 2020 James Seibel + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.seibel.lod.core.enums.rendering; + +/** + * NEAR, FAR, or NEAR_AND_FAR. + * + * @author James Seibel + * @version 11-26-2021 + */ +public enum FogDistance +{ + NEAR, + FAR, + NEAR_AND_FAR +} \ No newline at end of file diff --git a/src/main/java/com/seibel/lod/core/enums/rendering/FogDrawMode.java b/src/main/java/com/seibel/lod/core/enums/rendering/FogDrawMode.java new file mode 100644 index 000000000..bf05d7dbe --- /dev/null +++ b/src/main/java/com/seibel/lod/core/enums/rendering/FogDrawMode.java @@ -0,0 +1,40 @@ +/* + * This file is part of the Distant Horizon mod (formerly the LOD Mod), + * licensed under the GNU GPL v3 License. + * + * Copyright (C) 2020 James Seibel + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.seibel.lod.core.enums.rendering; + +/** + * USE_OPTIFINE_FOG_SETTING,
+ * FOG_ENABLED,
+ * FOG_DISABLED
+ * + * @author James Seibel + * @version 11-27-2021 + */ +public enum FogDrawMode +{ + /** + * Use whatever Fog setting optifine is using. + * If optifine isn't installed this defaults to ALWAYS_DRAW_FOG. + */ + USE_OPTIFINE_SETTING, + + FOG_ENABLED, + FOG_DISABLED +} diff --git a/src/main/java/com/seibel/lod/core/enums/rendering/GLProxyContext.java b/src/main/java/com/seibel/lod/core/enums/rendering/GLProxyContext.java new file mode 100644 index 000000000..b83bcc6c3 --- /dev/null +++ b/src/main/java/com/seibel/lod/core/enums/rendering/GLProxyContext.java @@ -0,0 +1,41 @@ +/* + * This file is part of the Distant Horizon mod (formerly the LOD Mod), + * licensed under the GNU GPL v3 License. + * + * Copyright (C) 2020 James Seibel + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.seibel.lod.core.enums.rendering; + +/** + * Minecraft, Lod_Builder, None + * + * @author James Seibel + * @version 10-1-2021 + */ +public enum GLProxyContext +{ + /** Minecraft's render thread */ + MINECRAFT, + + /** The context we send buffers to the GPU on */ + LOD_BUILDER, + + /** A context that can be used for miscellaneous tasks, owned by the GLProxy */ + PROXY_WORKER, + + /** used to un-bind threads */ + NONE, +} \ No newline at end of file diff --git a/src/main/java/com/seibel/lod/core/handlers/ChunkFileLoader.java b/src/main/java/com/seibel/lod/core/handlers/ChunkFileLoader.java new file mode 100644 index 000000000..81cb0068e --- /dev/null +++ b/src/main/java/com/seibel/lod/core/handlers/ChunkFileLoader.java @@ -0,0 +1,65 @@ +/* + * This file is part of the Distant Horizon mod (formerly the LOD Mod), + * licensed under the GNU GPL v3 License. + * + * Copyright (C) 2020 James Seibel + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.seibel.lod.core.handlers; + +/** + * + * @author Cola + * @author Leonardo Amato + * @version 11-12-2021 + */ +public class ChunkFileLoader +{ + // TODO +// public static IChunk getChunkFromFile(ChunkPos pos) +// { +// LevelWrapper clientLevel = MinecraftWrapper.INSTANCE.getWrappedClientLevel(); +// if (clientLevel == null) +// return null; +// WorldWrapper serverWorld = LodUtil.getServerWorldFromDimension(clientLevel.getDimensionType()); +// try +// { +// File file = new File(serverWorld.getSaveFolder().getParent() + File.separatorChar + "region", "r." + (pos.x >> 5) + "." + (pos.z >> 5) + ".mca"); +// if(!file.exists()) +// return null; +// IChunk loadedChunk = ChunkSerializer.read( +// serverWorld, +// serverWorld.getStructureManager(), +// serverWorld.getPoiManager(), +// pos, +// serverWorld.getChunkSource().chunkMap.read(pos) +// ); +// boolean emptyChunk = true; +// for(int i = 0; i < 16; i++){ +// for(int j = 0; j < 16; j++){ +// emptyChunk &= loadedChunk.isYSpaceEmpty(i,j); +// } +// } +// if(emptyChunk) +// return null; +// else +// return loadedChunk; +// } +// catch (Exception e) +// { +// return null; +// } +// } +} diff --git a/src/main/java/com/seibel/lod/core/handlers/IReflectionHandler.java b/src/main/java/com/seibel/lod/core/handlers/IReflectionHandler.java new file mode 100644 index 000000000..6e7aa1c2c --- /dev/null +++ b/src/main/java/com/seibel/lod/core/handlers/IReflectionHandler.java @@ -0,0 +1,58 @@ +/* + * This file is part of the Distant Horizon mod (formerly the LOD Mod), + * licensed under the GNU GPL v3 License. + * + * Copyright (C) 2020 James Seibel + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.seibel.lod.core.handlers; + +import com.seibel.lod.core.enums.rendering.FogDrawMode; +import com.seibel.lod.core.objects.math.Mat4f; + +/** + * A singleton used to get variables from methods + * where they are private or potentially absent. + * Specifically the fog setting used by Optifine or the + * presence/absence of other mods. + *

+ * This interface doesn't necessarily have to exist, but + * it makes using the singleton handler more uniform (always + * passing in interfaces), and it may be needed in the future if + * we find that reflection handlers need to be different for + * different MC versions. + * + * @author James Seibel + * @version 11-26-2021 + */ +public interface IReflectionHandler +{ + /** @returns Whether Optifine is set to render fog or not. */ + FogDrawMode getFogDrawMode(); + + /** @returns if Vivecraft is present. Attempts to find the "VRRenderer" class. */ + boolean vivecraftPresent(); + + /** + * Modifies the projection matrix's clip planes. + * The projection matrix must be in column-major format. + * + * @param projectionMatrix The projection matrix to be modified. + * @param newNearClipPlane the new near clip plane value. + * @param newFarClipPlane the new far clip plane value. + * @return The modified matrix. + */ + Mat4f ModifyProjectionClipPlanes(Mat4f projectionMatrix, float newNearClipPlane, float newFarClipPlane); +} diff --git a/src/main/java/com/seibel/lod/core/handlers/LodDimensionFileHandler.java b/src/main/java/com/seibel/lod/core/handlers/LodDimensionFileHandler.java new file mode 100644 index 000000000..f8e5e1f1e --- /dev/null +++ b/src/main/java/com/seibel/lod/core/handlers/LodDimensionFileHandler.java @@ -0,0 +1,485 @@ +/* + * This file is part of the Distant Horizon mod (formerly the LOD Mod), + * licensed under the GNU GPL v3 License. + * + * Copyright (C) 2020 James Seibel + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.seibel.lod.core.handlers; + +import java.io.*; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.nio.file.StandardCopyOption; +import java.util.Arrays; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +import org.apache.commons.compress.compressors.xz.XZCompressorInputStream; +import org.apache.commons.compress.compressors.xz.XZCompressorOutputStream; + +import com.seibel.lod.core.api.ClientApi; +import com.seibel.lod.core.enums.config.DistanceGenerationMode; +import com.seibel.lod.core.enums.config.VerticalQuality; +import com.seibel.lod.core.objects.lod.LodDimension; +import com.seibel.lod.core.objects.lod.LodRegion; +import com.seibel.lod.core.objects.lod.RegionPos; +import com.seibel.lod.core.objects.lod.VerticalLevelContainer; +import com.seibel.lod.core.util.LodThreadFactory; +import com.seibel.lod.core.util.LodUtil; +import com.seibel.lod.core.util.ThreadMapUtil; + +/** + * This object handles creating LodRegions + * from files and saving LodRegion objects + * to file. + * + * @author James Seibel + * @author Cola + * @version 9-25-2021 + */ +public class LodDimensionFileHandler +{ + /** This is the dimension that owns this file handler */ + private LodDimension lodDimension; + + private final File dimensionDataSaveFolder; + + /** lod */ + private static final String FILE_NAME_PREFIX = "lod"; + /** .txt */ + private static final String FILE_EXTENSION = ".xz"; + /** detail- */ + private static final String DETAIL_FOLDER_NAME_PREFIX = "detail-"; + + /** + * .tmp
+ * Added to the end of the file path when saving to prevent + * nulling a currently existing file.
+ * After the file finishes saving it will end with + * FILE_EXTENSION. + */ + private static final String TMP_FILE_EXTENSION = ".tmp"; + + /** + * This is the file version currently accepted by this + * file handler, older versions (smaller numbers) will be deleted and overwritten, + * newer versions (larger numbers) will be ignored and won't be read. + */ + public static final int LOD_SAVE_FILE_VERSION = 7; + + /** + * Allow saving asynchronously, but never try to save multiple regions + * at a time + */ + private final ExecutorService fileWritingThreadPool = Executors.newSingleThreadExecutor(new LodThreadFactory(this.getClass().getSimpleName())); + + + + + public LodDimensionFileHandler(File newSaveFolder, LodDimension newLodDimension) + { + if (newSaveFolder == null) + throw new IllegalArgumentException("LodDimensionFileHandler requires a valid File location to read and write to."); + + dimensionDataSaveFolder = newSaveFolder; + lodDimension = newLodDimension; + } + + + + //================// + // read from file // + //================// + + /** + * Returns the LodRegion at the given coordinates. + * Returns an empty region if the file doesn't exist. + */ + public LodRegion loadRegionFromFile(byte detailLevel, RegionPos regionPos, DistanceGenerationMode generationMode, VerticalQuality verticalQuality) + { + int regionX = regionPos.x; + int regionZ = regionPos.z; + LodRegion region = new LodRegion(LodUtil.REGION_DETAIL_LEVEL, regionPos, generationMode, verticalQuality); + + for (byte tempDetailLevel = LodUtil.REGION_DETAIL_LEVEL; tempDetailLevel >= detailLevel; tempDetailLevel--) + { + String fileName = getFileNameAndPathForRegion(regionX, regionZ, generationMode, tempDetailLevel, verticalQuality); + + try + { + // if the fileName was null that means the folder is inaccessible + // for some reason + if (fileName == null) + throw new IllegalArgumentException("Unable to read region [" + regionX + ", " + regionZ + "] file, no fileName."); + + File file = new File(fileName); + if (!file.exists()) + { + //there is no file for current gen mode + //search others above current from the most to the least detailed + VerticalQuality tempVerticalQuality = VerticalQuality.HIGH; + do { + DistanceGenerationMode tempGenMode = DistanceGenerationMode.FULL; + do { + fileName = getFileNameAndPathForRegion(regionX, regionZ, tempGenMode, tempDetailLevel, verticalQuality); + if (fileName != null) + { + file = new File(fileName); + if (file.exists()) + break; + } + //decrease gen mode + if (tempGenMode == DistanceGenerationMode.FULL) + tempGenMode = DistanceGenerationMode.FEATURES; + else if (tempGenMode == DistanceGenerationMode.FEATURES) + tempGenMode = DistanceGenerationMode.SURFACE; + else if (tempGenMode == DistanceGenerationMode.SURFACE) + tempGenMode = DistanceGenerationMode.BIOME_ONLY_SIMULATE_HEIGHT; + else if (tempGenMode == DistanceGenerationMode.BIOME_ONLY_SIMULATE_HEIGHT) + tempGenMode = DistanceGenerationMode.BIOME_ONLY; + else if (tempGenMode == DistanceGenerationMode.BIOME_ONLY) + tempGenMode = DistanceGenerationMode.NONE; + } while (tempGenMode != generationMode); + if (fileName != null) + { + file = new File(fileName); + if (file.exists()) + break; + } + if (tempVerticalQuality == VerticalQuality.HIGH) + tempVerticalQuality = VerticalQuality.MEDIUM; + else if (tempVerticalQuality == VerticalQuality.MEDIUM) + tempVerticalQuality = VerticalQuality.LOW; + } while (tempVerticalQuality != verticalQuality); + if (!file.exists()) + //there wasn't a file, don't return anything + continue; + } + + + + // don't try parsing empty files + long dataSize = file.length(); + dataSize -= 1; + if (dataSize > 0) + { + try (XZCompressorInputStream inputStream = new XZCompressorInputStream(new FileInputStream(file))) + { + int fileVersion; + fileVersion = inputStream.read(); + + // check if this file can be read by this file handler + if (fileVersion < 6) + { + // the file we are reading is an older version, + // close the reader and delete the file. + inputStream.close(); + file.delete(); + ClientApi.LOGGER.info("Outdated LOD region file for region: (" + regionX + "," + regionZ + ")" + + " version found: " + fileVersion + + ", version requested: " + LOD_SAVE_FILE_VERSION + + ". File was been deleted."); + + break; + } + else if (fileVersion > LOD_SAVE_FILE_VERSION) + { + // the file we are reading is a newer version, + // close the reader and ignore the file, we don't + // want to accidentally delete anything the user may want. + inputStream.close(); + ClientApi.LOGGER.info("Newer LOD region file for region: (" + regionX + "," + regionZ + ")" + + " version found: " + fileVersion + + ", version requested: " + LOD_SAVE_FILE_VERSION + + " this region will not be written to in order to protect the newer file."); + + break; + } + else if (fileVersion == 6) + { + //this is old, but readable version + byte[] data = ThreadMapUtil.getSaveContainer(tempDetailLevel); + inputStream.read(data); + inputStream.close(); + // add the data to our region + region.addLevelContainer(new VerticalLevelContainer(data, 6)); + } else + { + // this file is a readable version, + // read the file + byte[] data = ThreadMapUtil.getSaveContainer(tempDetailLevel); + inputStream.read(data); + inputStream.close(); + // add the data to our region + region.addLevelContainer(new VerticalLevelContainer(data, LOD_SAVE_FILE_VERSION)); + } + } + catch (IOException ioEx) + { + ClientApi.LOGGER.error("LOD file read error. Unable to read to [" + fileName + "] error [" + ioEx.getMessage() + "]: "); + ioEx.printStackTrace(); + } + } + } + catch (Exception e) + { + // the buffered reader encountered a + // problem reading the file + ClientApi.LOGGER.error("LOD file read error. Unable to read to [" + fileName + "] error [" + e.getMessage() + "]: "); + e.printStackTrace(); + } + }// for each detail level + + if (region.getMinDetailLevel() >= detailLevel) + region.growTree(detailLevel); + + return region; + } + + + //==============// + // Save to File // + //==============// + + /** Save all dirty regions in this LodDimension to file */ + public void saveDirtyRegionsToFileAsync() + { + fileWritingThreadPool.execute(saveDirtyRegionsThread); + } + + private final Thread saveDirtyRegionsThread = new Thread(() -> + { + try + { + for (int i = 0; i < lodDimension.getWidth(); i++) + { + for (int j = 0; j < lodDimension.getWidth(); j++) + { + if (lodDimension.GetIsRegionDirty(i, j) && lodDimension.getRegionByArrayIndex(i, j) != null) + { + saveRegionToFile(lodDimension.getRegionByArrayIndex(i, j)); + lodDimension.SetIsRegionDirty(i, j, false); + } + } + } + } + catch (Exception e) + { + e.printStackTrace(); + } + }); + + /** + * Save a specific region to disk.
+ * Note:
+ * 1. If a file already exists for a newer version + * the file won't be written.
+ * 2. This will save to the LodDimension that this + * handler is associated with. + */ + private void saveRegionToFile(LodRegion region) + { + for (byte detailLevel = region.getMinDetailLevel(); detailLevel <= LodUtil.REGION_DETAIL_LEVEL; detailLevel++) + { + String fileName = getFileNameAndPathForRegion(region.regionPosX, region.regionPosZ, region.getGenerationMode(), detailLevel, region.getVerticalQuality()); + + // if the fileName was null that means the folder is inaccessible + // for some reason + if (fileName == null) + { + ClientApi.LOGGER.warn("Unable to save region [" + region.regionPosX + ", " + region.regionPosZ + "] to file, file is inaccessible."); + return; + } + File oldFile = new File(fileName); + //ClientProxy.LOGGER.info("saving region [" + region.regionPosX + ", " + region.regionPosZ + "] to file."); + byte[] temp = region.getLevel(detailLevel).toDataString(); + + try + { + // make sure the file and folder exists + if (!oldFile.exists()) + { + // the file doesn't exist, + // create it and the folder if need be + if (!oldFile.getParentFile().exists()) + oldFile.getParentFile().mkdirs(); + oldFile.createNewFile(); + } + else + { + // the file exists, make sure it + // is the correct version. + // (to make sure we don't overwrite a newer + // version file if it exists) + int fileVersion = LOD_SAVE_FILE_VERSION; + int isFull = 0; + try (XZCompressorInputStream inputStream = new XZCompressorInputStream(new FileInputStream(oldFile))) + { + fileVersion = inputStream.read(); + inputStream.skip(1); + isFull = inputStream.read() & 0b10000000; + inputStream.close(); + } + catch (IOException ex) + { + ex.printStackTrace(); + } + + // check if this file can be written to by the file handler + if (fileVersion > LOD_SAVE_FILE_VERSION) + { + // the file we are reading is a newer version, + // don't write anything, we don't want to accidentally + // delete anything the user may want. + return; + } + if ((temp[1] & 0b10000000) != 0b10000000 && isFull == 0b10000000) + { + // existing file is complete while new one is only partially generate + // this can happen is for some reason loading failed + // this doesn't fix the bug, but at least protects old data + ClientApi.LOGGER.error("LOD file write error. Attempted to overwrite complete region with incomplete one [" + fileName + "]"); + return; + } + // if we got this far then we are good + // to overwrite the old file + } + // the old file is good, now create a new temporary save file + File newFile = new File(fileName + TMP_FILE_EXTENSION); + try (XZCompressorOutputStream outputStream = new XZCompressorOutputStream(new FileOutputStream(newFile), 3)) + { + // add the version of this file + outputStream.write(LOD_SAVE_FILE_VERSION); + + // add each LodChunk to the file + outputStream.write(temp); + outputStream.close(); + + // overwrite the old file with the new one + Files.move(newFile.toPath(), oldFile.toPath(), StandardCopyOption.ATOMIC_MOVE, StandardCopyOption.REPLACE_EXISTING); + } + catch (IOException ex) + { + ex.printStackTrace(); + } + } + catch (Exception e) + { + ClientApi.LOGGER.error("LOD file write error. Unable to write to [" + fileName + "] error [" + e.getMessage() + "]: "); + e.printStackTrace(); + } + } + } + + + public void saveRegionFile (byte[] regionFile, RegionPos regionPos, DistanceGenerationMode generationMode, byte detailLevel, VerticalQuality verticalQuality) + { + int regionX = regionPos.x; + int regionZ = regionPos.z; + String fileName = getFileNameAndPathForRegion(regionX, regionZ, generationMode, detailLevel, verticalQuality); + + if (fileName != null) + { + File oldFile = new File(fileName); + File newFile = new File(fileName + TMP_FILE_EXTENSION); + try (OutputStream os = new FileOutputStream(newFile)) + { + os.write(regionFile); + Files.move(newFile.toPath(), oldFile.toPath(), StandardCopyOption.ATOMIC_MOVE, StandardCopyOption.REPLACE_EXISTING); + os.close(); + } + catch (IOException ioEx) + { + ClientApi.LOGGER.error("LOD file write error. Unable to write to [" + fileName + "] error [" + ioEx.getMessage() + "]: "); + ioEx.printStackTrace(); + } + } + } + + public byte[] getRegionFile (RegionPos regionPos, DistanceGenerationMode generationMode, byte detailLevel, VerticalQuality verticalQuality) + { + int regionX = regionPos.x; + int regionZ = regionPos.z; + String fileName = getFileNameAndPathForRegion(regionX, regionZ, generationMode, detailLevel, verticalQuality); + if (fileName != null) + { + File file = new File(fileName); + try (InputStream is = new FileInputStream(file)) + { + byte[] data = ThreadMapUtil.getSaveContainer(detailLevel); + is.read(data); + is.close(); + return Arrays.copyOf(data, (int) file.length()); + } + catch (IOException ioEx) + { + ClientApi.LOGGER.error("LOD file read error. Unable to read to [" + fileName + "] error [" + ioEx.getMessage() + "]: "); + ioEx.printStackTrace(); + } + } + return new byte[0]; + } + + + //================// + // helper methods // + //================// + + public int getHashFromFile(RegionPos regionPos, DistanceGenerationMode generationMode, byte detailLevel, VerticalQuality verticalQuality) + { + int regionX = regionPos.x; + int regionZ = regionPos.z; + String fileName = getFileNameAndPathForRegion(regionX, regionZ, generationMode, detailLevel, verticalQuality); + if (fileName == null) + return 0; + + File file = new File(fileName); + return file.hashCode(); + } + + + /** + * Return the name of the file that should contain the + * region at the given x and z.
+ * Returns null if this object isn't available to read and write.

+ *

+ * example: "lod.0.0.txt"
+ *

+ * Returns null if there is an IO or security Exception. + */ + private String getFileNameAndPathForRegion(int regionX, int regionZ, DistanceGenerationMode generationMode, byte detailLevel, VerticalQuality verticalQuality) + { + try + { + // saveFolder is something like + // ".\Super Flat\DIM-1\data\" + // or + // ".\Super Flat\data\" + return dimensionDataSaveFolder.getCanonicalPath() + File.separatorChar + + verticalQuality + File.separatorChar + + generationMode.toString() + File.separatorChar + + DETAIL_FOLDER_NAME_PREFIX + detailLevel + File.separatorChar + + FILE_NAME_PREFIX + "." + regionX + "." + regionZ + FILE_EXTENSION; + } + catch (IOException | SecurityException e) + { + ClientApi.LOGGER.warn("Unable to get the filename for the region [" + regionX + ", " + regionZ + "], error: [" + e.getMessage() + "], stacktrace: "); + e.printStackTrace(); + return null; + } + } + +} diff --git a/src/main/java/com/seibel/lod/core/handlers/ReflectionHandler.java b/src/main/java/com/seibel/lod/core/handlers/ReflectionHandler.java new file mode 100644 index 000000000..a096bb71e --- /dev/null +++ b/src/main/java/com/seibel/lod/core/handlers/ReflectionHandler.java @@ -0,0 +1,195 @@ +/* + * This file is part of the Distant Horizon mod (formerly the LOD Mod), + * licensed under the GNU GPL v3 License. + * + * Copyright (C) 2020 James Seibel + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.seibel.lod.core.handlers; + +import java.lang.reflect.Field; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import com.seibel.lod.core.ModInfo; +import com.seibel.lod.core.enums.rendering.FogDrawMode; +import com.seibel.lod.core.objects.math.Mat4f; + +/** + * A singleton used to get variables from methods + * where they are private or potentially absent. + * Specifically the fog setting in Optifine or the + * presence/absence of other mods. + * + * @author James Seibel + * @version 11-26-2021 + */ +public class ReflectionHandler implements IReflectionHandler +{ + private static final Logger LOGGER = LogManager.getLogger(ModInfo.NAME + "-" + ReflectionHandler.class.getSimpleName()); + + private static ReflectionHandler instance; + + private Field ofFogField = null; + private final Object mcOptionsObject; + + + + private ReflectionHandler(Field[] optionFields, Object newMcOptionsObject) + { + mcOptionsObject = newMcOptionsObject; + + setupFogField(optionFields); + } + + /** + * @param optionFields the fields that should contain "ofFogType" + * @param newMcOptionsObject the object instance that contains "ofFogType" + * @return the ReflectionHandler just created + * @throws IllegalStateException if a ReflectionHandler already exists + */ + public static ReflectionHandler createSingleton(Field[] optionFields, Object newMcOptionsObject) throws IllegalStateException + { + if (instance != null) + { + throw new IllegalStateException(); + } + + instance = new ReflectionHandler(optionFields, newMcOptionsObject); + return instance; + } + + + + + /** finds the Optifine fog type field */ + private void setupFogField(Field[] optionFields) + { + // try and find the ofFogType variable in gameSettings + for (Field field : optionFields) + { + if (field.getName().equals("ofFogType")) + { + ofFogField = field; + return; + } + } + + // we didn't find the field, + // either optifine isn't installed, or + // optifine changed the name of the variable + LOGGER.info(ReflectionHandler.class.getSimpleName() + ": unable to find the Optifine fog field. If Optifine isn't installed this can be ignored."); + } + + + /** + * Get what type of fog optifine is currently set to render. + * @return the fog quality + */ + @Override + public FogDrawMode getFogDrawMode() + { + if (ofFogField == null) + { + // either optifine isn't installed, + // the variable name was changed, or + // the setup method wasn't called yet. + return FogDrawMode.FOG_ENABLED; + } + + int returnNum = 0; + + try + { + returnNum = (int) ofFogField.get(mcOptionsObject); + } + catch (IllegalArgumentException | IllegalAccessException e) + { + e.printStackTrace(); + } + + switch (returnNum) + { + default: + case 0: + // optifine's "default" option, + // it should never be called in this case + + // normal options + case 1: // fast + case 2: // fancy + return FogDrawMode.FOG_ENABLED; + case 3: // off + return FogDrawMode.FOG_DISABLED; + } + } + + + + /** Detect if Vivecraft is present. Attempts to find the "VRRenderer" class. */ + @Override + public boolean vivecraftPresent() + { + try + { + Class.forName("org.vivecraft.provider.VRRenderer"); + return true; + } + catch (ClassNotFoundException ignored) + { + LOGGER.info(ReflectionHandler.class.getSimpleName() + ": Vivecraft not detected."); + } + return false; + } + + /** + * Modifies the projection matrix's clip planes. + * The projection matrix must be in column-major format. + * + * @param projectionMatrix The projection matrix to be modified. + * @param newNearClipPlane the new near clip plane value. + * @param newFarClipPlane the new far clip plane value. + * @return The modified matrix. + */ + @Override + public Mat4f ModifyProjectionClipPlanes(Mat4f projectionMatrix, float newNearClipPlane, float newFarClipPlane) + { + // find the matrix values. + float nearMatrixValue = -((newFarClipPlane + newNearClipPlane) / (newFarClipPlane - newNearClipPlane)); + float farMatrixValue = -((2 * newFarClipPlane * newNearClipPlane) / (newFarClipPlane - newNearClipPlane)); + + try + { + // TODO this was originally created before we had the Mat4f object, + // so this doesn't need to be done with reflection anymore. + // And should be moved to RenderUtil + + // get the fields of the projectionMatrix + Field[] fields = projectionMatrix.getClass().getDeclaredFields(); + // bypass the security protections on the fields that encode near and far plane values. + fields[10].setAccessible(true); + fields[11].setAccessible(true); + // Change the values of the near and far plane. + fields[10].set(projectionMatrix, nearMatrixValue); + fields[11].set(projectionMatrix, farMatrixValue); + } + catch (Exception e) + { + e.printStackTrace(); + } + return projectionMatrix; + } +} diff --git a/src/main/java/com/seibel/lod/core/objects/MinDefaultMax.java b/src/main/java/com/seibel/lod/core/objects/MinDefaultMax.java new file mode 100644 index 000000000..977c3533d --- /dev/null +++ b/src/main/java/com/seibel/lod/core/objects/MinDefaultMax.java @@ -0,0 +1,41 @@ +/* + * This file is part of the Distant Horizon mod (formerly the LOD Mod), + * licensed under the GNU GPL v3 License. + * + * Copyright (C) 2020 James Seibel + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.seibel.lod.core.objects; + +/** + * Used when setting up configuration fields. + * + * @author James Seibel + * @version 11-14-2021 + * @param The data type this object is storing + */ +public class MinDefaultMax +{ + public final T minValue; + public final T defaultValue; + public final T maxValue; + + public MinDefaultMax(T newMinValue, T newDefaultValue, T newMaxValue) + { + minValue = newMinValue; + defaultValue = newDefaultValue; + maxValue = newMaxValue; + } +} diff --git a/src/main/java/com/seibel/lod/core/objects/PosToGenerateContainer.java b/src/main/java/com/seibel/lod/core/objects/PosToGenerateContainer.java new file mode 100644 index 000000000..730c9d6cb --- /dev/null +++ b/src/main/java/com/seibel/lod/core/objects/PosToGenerateContainer.java @@ -0,0 +1,209 @@ +/* + * This file is part of the Distant Horizon mod (formerly the LOD Mod), + * licensed under the GNU GPL v3 License. + * + * Copyright (C) 2020 James Seibel + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.seibel.lod.core.objects; + +import com.seibel.lod.core.util.LevelPosUtil; + +/** + * Holds the levelPos that need to be generated. + * + * @author Leonardo Amato + * @version 9-27-2021 + */ +public class PosToGenerateContainer +{ + private final int playerPosX; + private final int playerPosZ; + private final byte farMinDetail; + private int nearSize; + private int farSize; + + // TODO what is the format of these two arrays? [detailLevel][4-children]? + private final int[][] nearPosToGenerate; + private final int[][] farPosToGenerate; + + + + + public PosToGenerateContainer(byte farMinDetail, int maxDataToGenerate, int playerPosX, int playerPosZ) + { + this.playerPosX = playerPosX; + this.playerPosZ = playerPosZ; + this.farMinDetail = farMinDetail; + nearSize = 0; + farSize = 0; + nearPosToGenerate = new int[maxDataToGenerate][4]; + farPosToGenerate = new int[maxDataToGenerate][4]; + } + + + + // TODO what is going on in this method? + public void addPosToGenerate(byte detailLevel, int posX, int posZ) + { + int distance = LevelPosUtil.minDistance(detailLevel, posX, posZ, playerPosX, playerPosZ); + int index; + + if (detailLevel >= farMinDetail) + { + // We are introducing a position in the far array + + if (farSize < farPosToGenerate.length) + farSize++; + + index = farSize - 1; + while (index > 0 && LevelPosUtil.compareDistance(distance, farPosToGenerate[index - 1][3]) <= 0) + { + farPosToGenerate[index][0] = farPosToGenerate[index - 1][0]; + farPosToGenerate[index][1] = farPosToGenerate[index - 1][1]; + farPosToGenerate[index][2] = farPosToGenerate[index - 1][2]; + farPosToGenerate[index][3] = farPosToGenerate[index - 1][3]; + index--; + } + + + if (index != farSize - 1 || farSize != farPosToGenerate.length) + { + farPosToGenerate[index][0] = detailLevel + 1; + farPosToGenerate[index][1] = posX; + farPosToGenerate[index][2] = posZ; + farPosToGenerate[index][3] = distance; + } + } + else + { + //We are introducing a position in the near array + + if (nearSize < nearPosToGenerate.length) + nearSize++; + + index = nearSize - 1; + while (index > 0 && LevelPosUtil.compareDistance(distance, nearPosToGenerate[index - 1][3]) <= 0) + { + nearPosToGenerate[index][0] = nearPosToGenerate[index - 1][0]; + nearPosToGenerate[index][1] = nearPosToGenerate[index - 1][1]; + nearPosToGenerate[index][2] = nearPosToGenerate[index - 1][2]; + nearPosToGenerate[index][3] = nearPosToGenerate[index - 1][3]; + index--; + } + + + if (index != nearSize - 1 || nearSize != nearPosToGenerate.length) + { + nearPosToGenerate[index][0] = detailLevel + 1; + nearPosToGenerate[index][1] = posX; + nearPosToGenerate[index][2] = posZ; + nearPosToGenerate[index][3] = distance; + } + } + } + + + + public int getNumberOfPos() + { + return nearSize + farSize; + } + + public int getNumberOfNearPos() + { + return nearSize; + } + + public int getNumberOfFarPos() + { + return farSize; + } + + // TODO what does getNth mean? could the name be more descriptive or is it just a index? + public int getNthDetail(int n, boolean near) + { + if (near) + return nearPosToGenerate[n][0]; + else + return farPosToGenerate[n][0]; + } + + public int getNthPosX(int n, boolean near) + { + if (near) + return nearPosToGenerate[n][1]; + else + return farPosToGenerate[n][1]; + } + + public int getNthPosZ(int n, boolean near) + { + if (near) + return nearPosToGenerate[n][2]; + else + return farPosToGenerate[n][2]; + } + + public int getNthGeneration(int n, boolean near) + { + if (near) + return nearPosToGenerate[n][3]; + else + return farPosToGenerate[n][3]; + } + + @Override + public String toString() + { + StringBuilder builder = new StringBuilder(); + builder.append('\n'); + builder.append('\n'); + builder.append('\n'); + builder.append("near pos to generate"); + builder.append('\n'); + for (int[] ints : nearPosToGenerate) + { + if (ints[0] == 0) + break; + builder.append(ints[0] - 1); + builder.append(" "); + builder.append(ints[1]); + builder.append(" "); + builder.append(ints[2]); + builder.append(" "); + builder.append(ints[3]); + builder.append('\n'); + } + builder.append('\n'); + + builder.append("far pos to generate"); + builder.append('\n'); + for (int[] ints : farPosToGenerate) + { + if (ints[0] == 0) + break; + builder.append(ints[0] - 1); + builder.append(" "); + builder.append(ints[1]); + builder.append(" "); + builder.append(ints[2]); + builder.append(" "); + builder.append(ints[3]); + builder.append('\n'); + } + return builder.toString(); + } +} diff --git a/src/main/java/com/seibel/lod/core/objects/PosToRenderContainer.java b/src/main/java/com/seibel/lod/core/objects/PosToRenderContainer.java new file mode 100644 index 000000000..5a42e7212 --- /dev/null +++ b/src/main/java/com/seibel/lod/core/objects/PosToRenderContainer.java @@ -0,0 +1,147 @@ +/* + * This file is part of the Distant Horizon mod (formerly the LOD Mod), + * licensed under the GNU GPL v3 License. + * + * Copyright (C) 2020 James Seibel + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.seibel.lod.core.objects; + +import java.util.Arrays; + +import com.seibel.lod.core.api.ClientApi; +import com.seibel.lod.core.util.LevelPosUtil; +import com.seibel.lod.core.util.LodUtil; + +/** + * Holds a levelPos that needs to be rendered. + * + * @author Leonardo Amato + * @version 9-18-2021 + */ +public class PosToRenderContainer +{ + public byte minDetail; + private int regionPosX; + private int regionPosZ; + private int numberOfPosToRender; + private int[] posToRender; + private byte[][] population; + + public PosToRenderContainer(byte minDetail, int regionPosX, int regionPosZ) + { + this.minDetail = minDetail; + this.numberOfPosToRender = 0; + this.regionPosX = regionPosX; + this.regionPosZ = regionPosZ; + int size = 1 << (LodUtil.REGION_DETAIL_LEVEL - minDetail); + posToRender = new int[size * size * 3]; + population = new byte[size][size]; + } + + public void addPosToRender(byte detailLevel, int posX, int posZ) + { + // When rapidly changing dimensions the bufferBuilder can cause this, + // James isn't sure why, but this will prevent an exception at + // the very least (while stilling logging the problem). + if (numberOfPosToRender >= posToRender.length) + { + // This is might be due to dimensions having a different width + // when first loading in + ClientApi.LOGGER.error("Unable to addPosToRender. numberOfPosToRender [" + numberOfPosToRender + "] detailLevel [" + detailLevel + "] Pos [" + posX + "," + posZ + "]"); + numberOfPosToRender++; // incrementing so we can see how many pos over the limit we would go + return; + } + + //if(numberOfPosToRender >= posToRender.length) + // posToRender = Arrays.copyOf(posToRender, posToRender.length*2); + posToRender[numberOfPosToRender * 3] = detailLevel; + posToRender[numberOfPosToRender * 3 + 1] = posX; + posToRender[numberOfPosToRender * 3 + 2] = posZ; + numberOfPosToRender++; + population[LevelPosUtil.getRegionModule(minDetail, LevelPosUtil.convert(detailLevel, posX, minDetail))] + [LevelPosUtil.getRegionModule(minDetail, LevelPosUtil.convert(detailLevel, posZ, minDetail))] = (byte) (detailLevel + 1); + } + + public boolean contains(byte detailLevel, int posX, int posZ) + { + if (LevelPosUtil.getRegion(detailLevel, posX) == regionPosX && LevelPosUtil.getRegion(detailLevel, posZ) == regionPosZ) + return (population[LevelPosUtil.getRegionModule(minDetail, LevelPosUtil.convert(detailLevel, posX, minDetail))] + [LevelPosUtil.getRegionModule(minDetail, LevelPosUtil.convert(detailLevel, posZ, minDetail))] == (detailLevel + 1)); + else + return false; + } + + public void clear(byte minDetail, int regionPosX, int regionPosZ) + { + this.numberOfPosToRender = 0; + this.regionPosX = regionPosX; + this.regionPosZ = regionPosZ; + if (this.minDetail == minDetail) + { + Arrays.fill(posToRender, 0); + for (byte[] bytes : population) + Arrays.fill(bytes, (byte) 0); + } + else + { + this.minDetail = minDetail; + int size = 1 << (LodUtil.REGION_DETAIL_LEVEL - minDetail); + posToRender = new int[size * size * 3]; + population = new byte[size][size]; + } + } + + public int getNumberOfPos() + { + return numberOfPosToRender; + } + + public byte getNthDetailLevel(int n) + { + return (byte) posToRender[n * 3]; + } + + public int getNthPosX(int n) + { + return posToRender[n * 3 + 1]; + } + + public int getNthPosZ(int n) + { + return posToRender[n * 3 + 2]; + } + + @Override + public String toString() + { + + StringBuilder builder = new StringBuilder(); + builder.append("To render "); + builder.append(numberOfPosToRender); + builder.append('\n'); + for (int i = 0; i < numberOfPosToRender; i++) + { + builder.append(posToRender[i * 3]); + builder.append(" "); + builder.append(posToRender[i * 3 + 1]); + builder.append(" "); + builder.append(posToRender[i * 3 + 2]); + builder.append('\n'); + } + builder.append('\n'); + return builder.toString(); + } +} diff --git a/src/main/java/com/seibel/lod/core/objects/VertexOptimizer.java b/src/main/java/com/seibel/lod/core/objects/VertexOptimizer.java new file mode 100644 index 000000000..4c13f247b --- /dev/null +++ b/src/main/java/com/seibel/lod/core/objects/VertexOptimizer.java @@ -0,0 +1,626 @@ +/* + * This file is part of the Distant Horizon mod (formerly the LOD Mod), + * licensed under the GNU GPL v3 License. + * + * Copyright (C) 2020 James Seibel + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.seibel.lod.core.objects; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; + +import com.seibel.lod.core.enums.LodDirection; +import com.seibel.lod.core.enums.rendering.DebugMode; +import com.seibel.lod.core.objects.math.Vec3i; +import com.seibel.lod.core.util.ColorUtil; +import com.seibel.lod.core.util.DataPointUtil; +import com.seibel.lod.core.util.LodUtil; +import com.seibel.lod.core.util.SingletonHandler; +import com.seibel.lod.core.wrapperInterfaces.block.AbstractBlockPosWrapper; +import com.seibel.lod.core.wrapperInterfaces.config.ILodConfigWrapperSingleton; +import com.seibel.lod.core.wrapperInterfaces.minecraft.IMinecraftWrapper; + +/** + * This class handles all the vertex optimization that's needed for a column of lods. W + * @author Leonardo Amato + * @version 10-2-2021 + */ +public class VertexOptimizer +{ + private static final IMinecraftWrapper MC = SingletonHandler.get(IMinecraftWrapper.class); + private static final ILodConfigWrapperSingleton CONFIG = SingletonHandler.get(ILodConfigWrapperSingleton.class); + + public static final int ADJACENT_HEIGHT_INDEX = 0; + public static final int ADJACENT_DEPTH_INDEX = 1; + + public static final int X = 0; + public static final int Y = 1; + public static final int Z = 2; + + public static final int MIN = 0; + public static final int MAX = 1; + + public static final int VOID_FACE = 0; + + /** The six cardinal directions */ + public static final LodDirection[] DIRECTIONS = new LodDirection[] { + LodDirection.UP, + LodDirection.DOWN, + LodDirection.WEST, + LodDirection.EAST, + LodDirection.NORTH, + LodDirection.SOUTH }; + + /** North, South, East, West */ + public static final LodDirection[] ADJ_DIRECTIONS = new LodDirection[] { + LodDirection.EAST, + LodDirection.WEST, + LodDirection.SOUTH, + LodDirection.NORTH }; + + /** All the faces and vertices of a cube. This is used to extract the vertex from the column */ + @SuppressWarnings("serial") + public static final Map DIRECTION_VERTEX_MAP = new HashMap() + {{ + put(LodDirection.UP, new int[][] { + { 0, 1, 0 }, // 0 + { 0, 1, 1 }, // 1 + { 1, 1, 1 }, // 2 + + { 0, 1, 0 }, // 0 + { 1, 1, 1 }, // 2 + { 1, 1, 0 } // 3 + }); + put(LodDirection.DOWN, new int[][] { + { 1, 0, 0 }, // 0 + { 1, 0, 1 }, // 1 + { 0, 0, 1 }, // 2 + + { 1, 0, 0 }, // 0 + { 0, 0, 1 }, // 2 + { 0, 0, 0 } // 3 + }); + put(LodDirection.EAST, new int[][] { + { 1, 1, 0 }, // 0 + { 1, 1, 1 }, // 1 + { 1, 0, 1 }, // 2 + + { 1, 1, 0 }, // 0 + { 1, 0, 1 }, // 2 + { 1, 0, 0 } }); // 3 + put(LodDirection.WEST, new int[][] { + { 0, 0, 0 }, // 0 + { 0, 0, 1 }, // 1 + { 0, 1, 1 }, // 2 + + { 0, 0, 0 }, // 0 + { 0, 1, 1 }, // 2 + { 0, 1, 0 } // 3 + }); + put(LodDirection.SOUTH, new int[][] { + { 1, 0, 1 }, // 0 + { 1, 1, 1 }, // 1 + { 0, 1, 1 }, // 2 + + { 1, 0, 1 }, // 0 + { 0, 1, 1 }, // 2 + { 0, 0, 1 } // 3 + }); + put(LodDirection.NORTH, new int[][] { + { 0, 0, 0 }, // 0 + { 0, 1, 0 }, // 1 + { 1, 1, 0 }, // 2 + + { 0, 0, 0 }, // 0 + { 1, 1, 0 }, // 2 + { 1, 0, 0 } // 3 + }); + }}; + + + /** + * This indicates which position is invariable in the DIRECTION_VERTEX_MAP. + * Is used to extract the vertex + */ + @SuppressWarnings("serial") + public static final Map FACE_DIRECTION = new HashMap() + {{ + put(LodDirection.UP, new int[] { Y, MAX }); + put(LodDirection.DOWN, new int[] { Y, MIN }); + put(LodDirection.EAST, new int[] { X, MAX }); + put(LodDirection.WEST, new int[] { X, MIN }); + put(LodDirection.SOUTH, new int[] { Z, MAX }); + put(LodDirection.NORTH, new int[] { Z, MIN }); + }}; + + + /** + * This is a map from Direction to the relative normal vector + * we are using this since I'm not sure if the getNormal create new object at every call + */ + @SuppressWarnings("serial") + public static final Map DIRECTION_NORMAL_MAP = new HashMap() + {{ + put(LodDirection.UP, LodDirection.UP.getNormal()); + put(LodDirection.DOWN, LodDirection.DOWN.getNormal()); + put(LodDirection.EAST, LodDirection.EAST.getNormal()); + put(LodDirection.WEST, LodDirection.WEST.getNormal()); + put(LodDirection.SOUTH, LodDirection.SOUTH.getNormal()); + put(LodDirection.NORTH, LodDirection.NORTH.getNormal()); + }}; + + /** We use this index for all array that are going to */ + @SuppressWarnings("serial") + public static final Map DIRECTION_INDEX = new HashMap() + {{ + put(LodDirection.UP, 0); + put(LodDirection.DOWN, 1); + put(LodDirection.EAST, 2); + put(LodDirection.WEST, 3); + put(LodDirection.SOUTH, 4); + put(LodDirection.NORTH, 5); + }}; + + @SuppressWarnings("serial") + public static final Map ADJ_DIRECTION_INDEX = new HashMap() + {{ + put(LodDirection.EAST, 0); + put(LodDirection.WEST, 1); + put(LodDirection.SOUTH, 2); + put(LodDirection.NORTH, 3); + }}; + /** holds the box's x, y, z offset */ + public final int[] boxOffset; + /** holds the box's x, y, z width */ + public final int[] boxWidth; + + /** Holds each direction's color */ + public final int[] colorMap; + /** The original color (before shading) of this box */ + public int color; + /** + * + */ + public final Map adjHeight; + public final Map adjDepth; + public final Map skyLights; + public byte blockLight; + + /** Holds if the given direction should be culled or not */ + public final boolean[] culling; + + + /** creates an empty box */ + @SuppressWarnings("serial") + public VertexOptimizer() + { + boxOffset = new int[3]; + boxWidth = new int[3]; + + colorMap = new int[6]; + skyLights = new HashMap() + {{ + put(LodDirection.UP, new byte[1]); + put(LodDirection.DOWN, new byte[1]); + put(LodDirection.EAST, new byte[LodUtil.MAX_NUMBER_OF_VERTICAL_LODS]); + put(LodDirection.WEST, new byte[LodUtil.MAX_NUMBER_OF_VERTICAL_LODS]); + put(LodDirection.SOUTH, new byte[LodUtil.MAX_NUMBER_OF_VERTICAL_LODS]); + put(LodDirection.NORTH, new byte[LodUtil.MAX_NUMBER_OF_VERTICAL_LODS]); + }}; + adjHeight = new HashMap() + {{ + put(LodDirection.EAST, new int[LodUtil.MAX_NUMBER_OF_VERTICAL_LODS]); + put(LodDirection.WEST, new int[LodUtil.MAX_NUMBER_OF_VERTICAL_LODS]); + put(LodDirection.SOUTH, new int[LodUtil.MAX_NUMBER_OF_VERTICAL_LODS]); + put(LodDirection.NORTH, new int[LodUtil.MAX_NUMBER_OF_VERTICAL_LODS]); + }}; + adjDepth = new HashMap() + {{ + put(LodDirection.EAST, new int[LodUtil.MAX_NUMBER_OF_VERTICAL_LODS]); + put(LodDirection.WEST, new int[LodUtil.MAX_NUMBER_OF_VERTICAL_LODS]); + put(LodDirection.SOUTH, new int[LodUtil.MAX_NUMBER_OF_VERTICAL_LODS]); + put(LodDirection.NORTH, new int[LodUtil.MAX_NUMBER_OF_VERTICAL_LODS]); + }}; + + culling = new boolean[6]; + } + + /** Set the light of the columns */ + public void setLights(int skyLight, int blockLight) + { + this.blockLight = (byte) blockLight; + skyLights.get(LodDirection.UP)[0] = (byte) skyLight; + } + + /** + * Set the color of the columns + * @param color color to add + * @param adjShadeDisabled this array indicates which face does not need shading + */ + public void setColor(int color, boolean[] adjShadeDisabled) + { + this.color = color; + for (LodDirection lodDirection : DIRECTIONS) + { + if (!adjShadeDisabled[DIRECTION_INDEX.get(lodDirection)]) + colorMap[DIRECTION_INDEX.get(lodDirection)] = ColorUtil.applyShade(color, MC.getShade(lodDirection)); + else + colorMap[DIRECTION_INDEX.get(lodDirection)] = color; + } + } + + /** + * @param lodDirection of the face of which we want to get the color + * @return color of the face + */ + public int getColor(LodDirection lodDirection) + { + if (CONFIG.client().advanced().debugging().getDebugMode() != DebugMode.SHOW_DETAIL) + return colorMap[DIRECTION_INDEX.get(lodDirection)]; + else + return ColorUtil.applyShade(color, MC.getShade(lodDirection)); + } + + /** + */ + public byte getSkyLight(LodDirection lodDirection, int verticalIndex) + { + if(lodDirection == LodDirection.UP || lodDirection == LodDirection.DOWN) + return skyLights.get(lodDirection)[0]; + else + return skyLights.get(lodDirection)[verticalIndex]; + } + + /** + */ + public int getBlockLight() + { + return blockLight; + } + /** clears this box, resetting everything to default values */ + public void reset() + { + Arrays.fill(boxWidth, 0); + Arrays.fill(boxOffset, 0); + Arrays.fill(colorMap, 0); + blockLight = 0; + for (LodDirection lodDirection : ADJ_DIRECTIONS) + { + for (int i = 0; i < adjHeight.get(lodDirection).length; i++) + { + adjHeight.get(lodDirection)[i] = VOID_FACE; + adjDepth.get(lodDirection)[i] = VOID_FACE; + skyLights.get(lodDirection)[i] = 0; + } + } + } + + /** determine which faces should be culled */ + public void setUpCulling(int cullingDistance, AbstractBlockPosWrapper playerPos) + { + for (LodDirection lodDirection : DIRECTIONS) + { + if (lodDirection == LodDirection.DOWN || lodDirection == LodDirection.WEST || lodDirection == LodDirection.NORTH) + culling[DIRECTION_INDEX.get(lodDirection)] = playerPos.get(lodDirection.getAxis()) > getFacePos(lodDirection) + cullingDistance; + + else if (lodDirection == LodDirection.UP || lodDirection == LodDirection.EAST || lodDirection == LodDirection.SOUTH) + culling[DIRECTION_INDEX.get(lodDirection)] = playerPos.get(lodDirection.getAxis()) < getFacePos(lodDirection) - cullingDistance; + + culling[DIRECTION_INDEX.get(lodDirection)] = false; + } + } + + /** + * @param lodDirection direction that we want to check if it's culled + * @return true if and only if the face of the direction is culled + */ + public boolean isCulled(LodDirection lodDirection) + { + return culling[DIRECTION_INDEX.get(lodDirection)]; + } + + + /** + * This method create all the shared face culling based on the adjacent data + * @param adjData data adjacent to the column we are going to render + */ + public void setAdjData(Map adjData) + { + int height; + int depth; + int minY = getMinY(); + int maxY = getMaxY(); + long singleAdjDataPoint; + + /* TODO implement attached vertical face culling + //Up direction case + if(DataPointUtil.doesItExist(adjData.get(Direction.UP))) + { + height = DataPointUtil.getHeight(singleAdjDataPoint); + depth = DataPointUtil.getDepth(singleAdjDataPoint); + }*/ + //Down direction case + singleAdjDataPoint = adjData.get(LodDirection.DOWN)[0]; + if(DataPointUtil.doesItExist(singleAdjDataPoint)) + skyLights.get(LodDirection.DOWN)[0] = DataPointUtil.getLightSkyAlt(singleAdjDataPoint); + else + skyLights.get(LodDirection.DOWN)[0] = skyLights.get(LodDirection.UP)[0]; + //other sided + //TODO clean some similar cases + for (LodDirection lodDirection : ADJ_DIRECTIONS) + { + if (isCulled(lodDirection)) + continue; + + long[] dataPoint = adjData.get(lodDirection); + if (dataPoint == null || DataPointUtil.isVoid(dataPoint[0])) + { + adjHeight.get(lodDirection)[0] = maxY; + adjDepth.get(lodDirection)[0] = minY; + adjHeight.get(lodDirection)[1] = VOID_FACE; + adjDepth.get(lodDirection)[1] = VOID_FACE; + skyLights.get(lodDirection)[0] = 15; //in void set full skylight + continue; + } + + int i; + int faceToDraw = 0; + boolean firstFace = true; + boolean toFinish = false; + int toFinishIndex = 0; + boolean allAbove = true; + for (i = 0; i < dataPoint.length; i++) + { + singleAdjDataPoint = dataPoint[i]; + + if (DataPointUtil.isVoid(singleAdjDataPoint) || !DataPointUtil.doesItExist(singleAdjDataPoint)) + break; + + height = DataPointUtil.getHeight(singleAdjDataPoint); + depth = DataPointUtil.getDepth(singleAdjDataPoint); + + if (depth <= maxY) + { + allAbove = false; + if (height < minY) + { + // the adj data is lower than the current data + + if (firstFace) + { + adjHeight.get(lodDirection)[0] = getMaxY(); + adjDepth.get(lodDirection)[0] = getMinY(); + skyLights.get(lodDirection)[0] = DataPointUtil.getLightSkyAlt(singleAdjDataPoint); //skyLights.get(Direction.UP)[0]; + } + else + { + adjDepth.get(lodDirection)[faceToDraw] = getMinY(); + skyLights.get(lodDirection)[faceToDraw] = DataPointUtil.getLightSkyAlt(singleAdjDataPoint); + } + faceToDraw++; + toFinish = false; + + // break since all the other data will be lower + break; + } + else if (depth <= minY) + { + if (height >= maxY) + { + // the adj data is inside the current data + // don't draw the face + adjHeight.get(lodDirection)[0] = VOID_FACE; + adjDepth.get(lodDirection)[0] = VOID_FACE; + } + else // height < maxY + { + // the adj data intersects the lower part of the current data + // if this is the only face, use the maxY and break, + // if there was another face we finish the last one and break + if (firstFace) + { + adjHeight.get(lodDirection)[0] = getMaxY(); + adjDepth.get(lodDirection)[0] = height; + skyLights.get(lodDirection)[0] = DataPointUtil.getLightSkyAlt(singleAdjDataPoint); //skyLights.get(Direction.UP)[0]; + } + else + { + adjDepth.get(lodDirection)[faceToDraw] = height; + skyLights.get(lodDirection)[faceToDraw] = DataPointUtil.getLightSkyAlt(singleAdjDataPoint); + } + toFinish = false; + faceToDraw++; + } + break; + } + else if (height >= maxY)//depth > minY && + { + // the adj data intersects the higher part of the current data + // we start the creation of a new face + adjHeight.get(lodDirection)[faceToDraw] = depth; + //skyLights.get(direction)[faceToDraw] = (byte) DataPointUtil.getLightSkyAlt(singleAdjDataPoint); + firstFace = false; + toFinish = true; + toFinishIndex = i + 1; + } + else + { + // if (depth > minY && height < maxY) + + // the adj data is contained in the current data + if (firstFace) + { + adjHeight.get(lodDirection)[0] = getMaxY(); + } + + adjDepth.get(lodDirection)[faceToDraw] = height; + skyLights.get(lodDirection)[faceToDraw] = DataPointUtil.getLightSkyAlt(singleAdjDataPoint); + faceToDraw++; + adjHeight.get(lodDirection)[faceToDraw] = depth; + firstFace = false; + toFinish = true; + toFinishIndex = i + 1; + } + } + } + + if (allAbove) + { + adjHeight.get(lodDirection)[0] = getMaxY(); + adjDepth.get(lodDirection)[0] = getMinY(); + skyLights.get(lodDirection)[0] = skyLights.get(LodDirection.UP)[0]; + faceToDraw++; + } + else if (toFinish) + { + adjDepth.get(lodDirection)[faceToDraw] = minY; + if(toFinishIndex < dataPoint.length) + { + singleAdjDataPoint = dataPoint[toFinishIndex]; + if (DataPointUtil.doesItExist(singleAdjDataPoint)) + skyLights.get(lodDirection)[faceToDraw] = DataPointUtil.getLightSkyAlt(singleAdjDataPoint); + else + skyLights.get(lodDirection)[faceToDraw] = skyLights.get(LodDirection.UP)[0]; + } + faceToDraw++; + } + + adjHeight.get(lodDirection)[faceToDraw] = VOID_FACE; + adjDepth.get(lodDirection)[faceToDraw] = VOID_FACE; + } + } + + /** We use this method to set the various width of the column */ + public void setWidth(int xWidth, int yWidth, int zWidth) + { + boxWidth[X] = xWidth; + boxWidth[Y] = yWidth; + boxWidth[Z] = zWidth; + } + + /** We use this method to set the various offset of the column */ + public void setOffset(int xOffset, int yOffset, int zOffset) + { + boxOffset[X] = xOffset; + boxOffset[Y] = yOffset; + boxOffset[Z] = zOffset; + } + + /** + * This method return the position of a face in the axis of the face + * This is useful for the face culling + * @param lodDirection that we want to check + * @return position in the axis of the face + */ + public int getFacePos(LodDirection lodDirection) + { + return boxOffset[FACE_DIRECTION.get(lodDirection)[0]] + boxWidth[FACE_DIRECTION.get(lodDirection)[0]] * FACE_DIRECTION.get(lodDirection)[1]; + } + + /** + * returns true if the given direction should be rendered. + */ + public boolean shouldRenderFace(LodDirection lodDirection, int adjIndex) + { + if (lodDirection == LodDirection.UP || lodDirection == LodDirection.DOWN) + return adjIndex == 0; + return !(adjHeight.get(lodDirection)[adjIndex] == VOID_FACE && adjDepth.get(lodDirection)[adjIndex] == VOID_FACE); + } + + + /** + * @param lodDirection direction of the face we want to render + * @param vertexIndex index of the vertex of the face (0-1-2-3) + * @return position x of the relative vertex + */ + public int getX(LodDirection lodDirection, int vertexIndex) + { + return boxOffset[X] + boxWidth[X] * DIRECTION_VERTEX_MAP.get(lodDirection)[vertexIndex][X]; + } + + /** + * @param lodDirection direction of the face we want to render + * @param vertexIndex index of the vertex of the face (0-1-2-3) + * @return position y of the relative vertex + */ + public int getY(LodDirection lodDirection, int vertexIndex) + { + return boxOffset[Y] + boxWidth[Y] * DIRECTION_VERTEX_MAP.get(lodDirection)[vertexIndex][Y]; + } + + /** + * @param lodDirection direction of the face we want to render + * @param vertexIndex index of the vertex of the face (0-1-2-3) + * @param adjIndex, index of the n-th culled face of this direction + * @return position x of the relative vertex + */ + public int getY(LodDirection lodDirection, int vertexIndex, int adjIndex) + { + if (lodDirection == LodDirection.DOWN || lodDirection == LodDirection.UP) + return boxOffset[Y] + boxWidth[Y] * DIRECTION_VERTEX_MAP.get(lodDirection)[vertexIndex][Y]; + else + { + // this could probably be cleaned up more, + // but it still works + if (1 - DIRECTION_VERTEX_MAP.get(lodDirection)[vertexIndex][Y] == ADJACENT_HEIGHT_INDEX) + return adjHeight.get(lodDirection)[adjIndex]; + else + return adjDepth.get(lodDirection)[adjIndex]; + } + } + + /** + * @param lodDirection direction of the face we want to render + * @param vertexIndex index of the vertex of the face (0-1-2-3) + * @return position z of the relative vertex + */ + public int getZ(LodDirection lodDirection, int vertexIndex) + { + return boxOffset[Z] + boxWidth[Z] * DIRECTION_VERTEX_MAP.get(lodDirection)[vertexIndex][Z]; + } + + public int getMinX() + { + return boxOffset[X]; + } + + public int getMaxX() + { + return boxOffset[X] + boxWidth[X]; + } + + public int getMinY() + { + return boxOffset[Y]; + } + + public int getMaxY() + { + return boxOffset[Y] + boxWidth[Y]; + } + + public int getMinZ() + { + return boxOffset[Z]; + } + + public int getMaxZ() + { + return boxOffset[Z] + boxWidth[Z]; + } + +} diff --git a/src/main/java/com/seibel/lod/core/objects/lod/LevelContainer.java b/src/main/java/com/seibel/lod/core/objects/lod/LevelContainer.java new file mode 100644 index 000000000..b314fbcf7 --- /dev/null +++ b/src/main/java/com/seibel/lod/core/objects/lod/LevelContainer.java @@ -0,0 +1,115 @@ +/* + * This file is part of the Distant Horizon mod (formerly the LOD Mod), + * licensed under the GNU GPL v3 License. + * + * Copyright (C) 2020 James Seibel + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.seibel.lod.core.objects.lod; + +/** + * A level container is a quad tree level + */ +public interface LevelContainer +{ + /** + * With this you can add data to the level container + * @param data actual data to add in an array of long format. + * @param posX x position in the detail level + * @param posZ z position in the detail level + * @param index z position in the detail level + * @return true if correctly added, false otherwise + */ + boolean addData(long data, int posX, int posZ, int index); + + /** + * With this you can add data to the level container + * @param data actual data to add in an array of long[] format. + * @param posX x position in the detail level + * @param posZ z position in the detail level + * @return true if correctly added, false otherwise + */ + boolean addVerticalData(long[] data, int posX, int posZ); + + /** + * With this you can add data to the level container + * @param data actual data to add in an array of long format. + * @param posX x position in the detail level + * @param posZ z position in the detail level + * @return true if correctly added, false otherwise + */ + boolean addSingleData(long data, int posX, int posZ); + + /** + * With this you can get data from the level container + * @param posX x position in the detail level + * @param posZ z position in the detail level + * @return the data in long array format + */ + long getData(int posX, int posZ, int index); + + /** + * With this you can get data from the level container + * @param posX x position in the detail level + * @param posZ z position in the detail level + * @return the data in long array format + */ + long getSingleData(int posX, int posZ); + + /** + * @param posX x position in the detail level + * @param posZ z position in the detail level + * @return true only if the data exist + */ + boolean doesItExist(int posX, int posZ); + + /** + * @return return the detailLevel of this level container + */ + byte getDetailLevel(); + + + int getMaxVerticalData(); + + /** Clears the dataPoint at the given array index */ + void clear(int posX, int posZ); + + /** + * This return a level container with detail level lower than the current level. + * The new level container may use information of this level. + * @return the new level container + */ + LevelContainer expand(); + + /** + * @param lowerLevelContainer lower level where we extract the data + * @param posX x position in the detail level to update + * @param posZ z position in the detail level to update + */ + void updateData(LevelContainer lowerLevelContainer, int posX, int posZ); + + /** + * This will give the data to save in the file + * @return data as a String + */ + byte[] toDataString(); + + + /** + * This will give the data to save in the file + * @return data as a String + */ + int getMaxNumberOfLods(); +} diff --git a/src/main/java/com/seibel/lod/core/objects/lod/LodDimension.java b/src/main/java/com/seibel/lod/core/objects/lod/LodDimension.java new file mode 100644 index 000000000..06c8fd921 --- /dev/null +++ b/src/main/java/com/seibel/lod/core/objects/lod/LodDimension.java @@ -0,0 +1,913 @@ +/* + * This file is part of the Distant Horizon mod (formerly the LOD Mod), + * licensed under the GNU GPL v3 License. + * + * Copyright (C) 2020 James Seibel + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.seibel.lod.core.objects.lod; + +import java.io.File; +import java.io.IOException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +import com.seibel.lod.core.enums.config.DistanceGenerationMode; +import com.seibel.lod.core.enums.config.GenerationPriority; +import com.seibel.lod.core.enums.config.VerticalQuality; +import com.seibel.lod.core.handlers.LodDimensionFileHandler; +import com.seibel.lod.core.objects.PosToGenerateContainer; +import com.seibel.lod.core.objects.PosToRenderContainer; +import com.seibel.lod.core.util.DataPointUtil; +import com.seibel.lod.core.util.DetailDistanceUtil; +import com.seibel.lod.core.util.LevelPosUtil; +import com.seibel.lod.core.util.LodThreadFactory; +import com.seibel.lod.core.util.LodUtil; +import com.seibel.lod.core.util.SingletonHandler; +import com.seibel.lod.core.wrapperInterfaces.IWrapperFactory; +import com.seibel.lod.core.wrapperInterfaces.chunk.AbstractChunkPosWrapper; +import com.seibel.lod.core.wrapperInterfaces.config.ILodConfigWrapperSingleton; +import com.seibel.lod.core.wrapperInterfaces.minecraft.IMinecraftWrapper; +import com.seibel.lod.core.wrapperInterfaces.world.IDimensionTypeWrapper; +import com.seibel.lod.core.wrapperInterfaces.world.IWorldWrapper; + + + +/** + * This object holds all loaded LOD regions + * for a given dimension.

+ * + * Coordinate Standard:
+ * Coordinate called posX or posZ are relative LevelPos coordinates
+ * unless stated otherwise.
+ * + * @author Leonardo Amato + * @author James Seibel + * @version 11-12-2021 + */ +public class LodDimension +{ + private static final ILodConfigWrapperSingleton CONFIG = SingletonHandler.get(ILodConfigWrapperSingleton.class); + private static final IMinecraftWrapper MC = SingletonHandler.get(IMinecraftWrapper.class); + private static final IWrapperFactory FACTORY = SingletonHandler.get(IWrapperFactory.class); + + public final IDimensionTypeWrapper dimension; + + /** measured in regions */ + private volatile int width; + /** measured in regions */ + private volatile int halfWidth; + + // these three variables are private to force use of the getWidth() method + // which is a safer way to get the width then directly asking the arrays + /** stores all the regions in this dimension */ + public volatile LodRegion[][] regions; + + /** stores if the region at the given x and z index needs to be saved to disk */ + private volatile boolean[][] isRegionDirty; + /** stores if the region at the given x and z index needs to be regenerated */ + private volatile boolean[][] regenRegionBuffer; + /** stores if the buffer size at the given x and z index needs to be changed */ + private volatile boolean[][] recreateRegionBuffer; + + /** + * if true that means there are regions in this dimension + * that need to have their buffers rebuilt. + */ + public volatile boolean regenDimensionBuffers = false; + + private LodDimensionFileHandler fileHandler; + + private final RegionPos center; + + /** prevents the cutAndExpandThread from expanding at the same location multiple times */ + private volatile AbstractChunkPosWrapper lastExpandedChunk; + /** prevents the cutAndExpandThread from cutting at the same location multiple times */ + private volatile AbstractChunkPosWrapper lastCutChunk; + private final ExecutorService cutAndExpandThread = Executors.newSingleThreadExecutor(new LodThreadFactory(this.getClass().getSimpleName() + " - Cut and Expand")); + + /** + * Creates the dimension centered at (0,0) + * @param newWidth in regions + */ + public LodDimension(IDimensionTypeWrapper newDimension, LodWorld lodWorld, int newWidth) + { + lastCutChunk = null; + lastExpandedChunk = null; + dimension = newDimension; + width = newWidth; + halfWidth = width / 2; + + if (newDimension != null && lodWorld != null) + { + try + { + // determine the save folder + File saveDir; + if (MC.hasSinglePlayerServer()) + { + // local world + + IWorldWrapper serverWorld = LodUtil.getServerWorldFromDimension(newDimension); + saveDir = new File(serverWorld.getSaveFolder().getCanonicalFile().getPath() + File.separatorChar + "lod"); + } + else + { + // connected to server + + saveDir = new File(MC.getGameDirectory().getCanonicalFile().getPath() + + File.separatorChar + "Distant_Horizons_server_data" + File.separatorChar + MC.getCurrentDimensionId()); + } + + 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]; + regenRegionBuffer = new boolean[width][width]; + recreateRegionBuffer = new boolean[width][width]; + + center = new RegionPos(0, 0); + } + + + /** + * Move the center of this LodDimension and move all owned + * regions over by the given x and z offset.

+ *

+ * Synchronized to prevent multiple moves happening on top of each other. + */ + public synchronized void move(RegionPos regionOffset) + { + int xOffset = regionOffset.x; + int zOffset = regionOffset.z; + + // if the x or z offset is equal to or greater than + // the total width, just delete the current data + // and update the centerX and/or centerZ + if (Math.abs(xOffset) >= width || Math.abs(zOffset) >= width) + { + for (int x = 0; x < width; x++) + for (int z = 0; z < width; z++) + regions[x][z] = null; + + // update the new center + center.x += xOffset; + center.z += zOffset; + + return; + } + + + // X + if (xOffset > 0) + { + // move everything over to the left (as the center moves to the right) + for (int x = 0; x < width; x++) + { + for (int z = 0; z < width; z++) + { + if (x + xOffset < width) + regions[x][z] = regions[x + xOffset][z]; + else + regions[x][z] = null; + } + } + } + else + { + // move everything over to the right (as the center moves to the left) + for (int x = width - 1; x >= 0; x--) + { + for (int z = 0; z < width; z++) + { + if (x + xOffset >= 0) + regions[x][z] = regions[x + xOffset][z]; + else + regions[x][z] = null; + } + } + } + + + // Z + if (zOffset > 0) + { + // move everything up (as the center moves down) + for (int x = 0; x < width; x++) + { + for (int z = 0; z < width; z++) + { + if (z + zOffset < width) + regions[x][z] = regions[x][z + zOffset]; + else + regions[x][z] = null; + } + } + } + else + { + // move everything down (as the center moves up) + for (int x = 0; x < width; x++) + { + for (int z = width - 1; z >= 0; z--) + { + if (z + zOffset >= 0) + regions[x][z] = regions[x][z + zOffset]; + else + regions[x][z] = null; + } + } + } + + + // update the new center + center.x += xOffset; + center.z += zOffset; + } + + + /** + * Gets the region at the given LevelPos + *
+ * Returns null if the region doesn't exist + * or is outside the loaded area. + */ + public LodRegion getRegion(byte detailLevel, int levelPosX, int levelPosZ) + { + int xRegion = LevelPosUtil.getRegion(detailLevel, levelPosX); + int zRegion = LevelPosUtil.getRegion(detailLevel, levelPosZ); + int xIndex = (xRegion - center.x) + halfWidth; + int zIndex = (zRegion - center.z) + halfWidth; + + if (!regionIsInRange(xRegion, zRegion)) + return null; + // throw new ArrayIndexOutOfBoundsException("Region for level pos " + LevelPosUtil.toString(detailLevel, posX, posZ) + " out of range"); + else if (regions[xIndex][zIndex] == null) + return null; + else if (regions[xIndex][zIndex].getMinDetailLevel() > detailLevel) + return null; + //throw new InvalidParameterException("Region for level pos " + LevelPosUtil.toString(detailLevel, posX, posZ) + " currently only reach level " + regions[xIndex][zIndex].getMinDetailLevel()); + + return regions[xIndex][zIndex]; + } + + /** + * Gets the region at the given X and Z + *
+ * Returns null if the region doesn't exist + * or is outside the loaded area. + */ + public LodRegion getRegion(int regionPosX, int regionPosZ) + { + int xIndex = (regionPosX - center.x) + halfWidth; + int zIndex = (regionPosZ - center.z) + halfWidth; + + if (!regionIsInRange(regionPosX, regionPosZ)) + return null; + //throw new ArrayIndexOutOfBoundsException("Region " + regionPosX + " " + regionPosZ + " out of range"); + + return regions[xIndex][zIndex]; + } + + /** Useful when iterating over every region. */ + public LodRegion getRegionByArrayIndex(int xIndex, int zIndex) + { + return regions[xIndex][zIndex]; + } + + /** + * Overwrite the LodRegion at the location of newRegion with newRegion. + * @throws ArrayIndexOutOfBoundsException if newRegion is outside what can be stored in this LodDimension. + */ + public synchronized void addOrOverwriteRegion(LodRegion newRegion) throws ArrayIndexOutOfBoundsException + { + int xIndex = (newRegion.regionPosX - center.x) + halfWidth; + int zIndex = (newRegion.regionPosZ - center.z) + halfWidth; + + if (!regionIsInRange(newRegion.regionPosX, newRegion.regionPosZ)) + // out of range + throw new ArrayIndexOutOfBoundsException("Region " + newRegion.regionPosX + ", " + newRegion.regionPosZ + " out of range"); + + regions[xIndex][zIndex] = newRegion; + } + + + /** + * Deletes nodes that are a higher detail then necessary, freeing + * up memory. + */ + public void cutRegionNodesAsync(int playerPosX, int playerPosZ) + { + AbstractChunkPosWrapper newPlayerChunk = FACTORY.createChunkPos(LevelPosUtil.getChunkPos((byte) 0, playerPosX), LevelPosUtil.getChunkPos((byte) 0, playerPosZ)); + + if (lastCutChunk == null) + lastCutChunk = FACTORY.createChunkPos(newPlayerChunk.getX() + 1, newPlayerChunk.getZ() - 1); + + // don't run the tree cutter multiple times + // for the same location + if (newPlayerChunk.getX() != lastCutChunk.getX() || newPlayerChunk.getZ() != lastCutChunk.getZ()) + { + lastCutChunk = newPlayerChunk; + + Thread thread = new Thread(() -> + { + int regionX; + int regionZ; + int minDistance; + byte detail; + byte minAllowedDetailLevel; + + // go over every region in the dimension + for (int x = 0; x < regions.length; x++) + { + for (int z = 0; z < regions.length; z++) + { + regionX = (x + center.x) - halfWidth; + regionZ = (z + center.z) - halfWidth; + + if (regions[x][z] != null) + { + // check what detail level this region should be + // and cut it if it is higher then that + minDistance = LevelPosUtil.minDistance(LodUtil.REGION_DETAIL_LEVEL, regionX, regionZ, playerPosX, playerPosZ); + detail = DetailDistanceUtil.getTreeCutDetailFromDistance(minDistance); + minAllowedDetailLevel = DetailDistanceUtil.getCutLodDetail(detail); + + if (regions[x][z].getMinDetailLevel() > minAllowedDetailLevel) + { + regions[x][z].cutTree(minAllowedDetailLevel); + recreateRegionBuffer[x][z] = true; + } + } + }// region z + }// region z + }); + + cutAndExpandThread.execute(thread); + } + } + + /** Either expands or loads all regions in the rendered LOD area */ + public void expandOrLoadRegionsAsync(int playerPosX, int playerPosZ) + { + DistanceGenerationMode generationMode = CONFIG.client().worldGenerator().getDistanceGenerationMode(); + AbstractChunkPosWrapper newPlayerChunk = FACTORY.createChunkPos(LevelPosUtil.getChunkPos((byte) 0, playerPosX), LevelPosUtil.getChunkPos((byte) 0, playerPosZ)); + VerticalQuality verticalQuality = CONFIG.client().graphics().quality().getVerticalQuality(); + + + if (lastExpandedChunk == null) + lastExpandedChunk = FACTORY.createChunkPos(newPlayerChunk.getX() + 1, newPlayerChunk.getZ() - 1); + + // don't run the expander multiple times + // for the same location + if (newPlayerChunk.getX() != lastExpandedChunk.getX() || newPlayerChunk.getZ() != lastExpandedChunk.getZ()) + { + lastExpandedChunk = newPlayerChunk; + + Thread thread = new Thread(() -> + { + int regionX; + int regionZ; + LodRegion region; + int minDistance; + byte detail; + byte levelToGen; + + for (int x = 0; x < regions.length; x++) + { + for (int z = 0; z < regions.length; z++) + { + regionX = (x + center.x) - halfWidth; + regionZ = (z + center.z) - halfWidth; + final RegionPos regionPos = new RegionPos(regionX, regionZ); + region = regions[x][z]; + + minDistance = LevelPosUtil.minDistance(LodUtil.REGION_DETAIL_LEVEL, regionX, regionZ, playerPosX, playerPosZ); + detail = DetailDistanceUtil.getTreeGenDetailFromDistance(minDistance); + levelToGen = DetailDistanceUtil.getLodGenDetail(detail).detailLevel; + + // check that the region isn't null and at least this detail level + if (region == null || region.getGenerationMode() != generationMode) + { + // First case, region has to be created + + // try to get the region from file + regions[x][z] = getRegionFromFile(regionPos, levelToGen, generationMode, verticalQuality); + + // if there is no region file create an empty region + if (regions[x][z] == null) + regions[x][z] = new LodRegion(levelToGen, regionPos, generationMode, verticalQuality); + + regenRegionBuffer[x][z] = true; + regenDimensionBuffers = true; + recreateRegionBuffer[x][z] = true; + } + else if (region.getMinDetailLevel() > levelToGen) + { + // Second case, the region exists at a higher detail level. + + // Expand the region by introducing the missing layer + region.growTree(levelToGen); + regions[x][z] = getRegionFromFile(regionPos, levelToGen, generationMode, verticalQuality); + recreateRegionBuffer[x][z] = true; + } + } + } + }); + + cutAndExpandThread.execute(thread); + } + } + + /** + * Use addVerticalData when possible. + * Add the given LOD to this dimension at the coordinate + * stored in the LOD. If an LOD already exists at the given + * coordinate it will be overwritten. + */ + public Boolean addData(byte detailLevel, int posX, int posZ, int verticalIndex, long data, boolean dontSave) + { + int regionPosX = LevelPosUtil.getRegion(detailLevel, posX); + int regionPosZ = LevelPosUtil.getRegion(detailLevel, posZ); + + // don't continue if the region can't be saved + LodRegion region = getRegion(regionPosX, regionPosZ); + if (region == null) + return false; + + boolean nodeAdded = region.addData(detailLevel, posX, posZ, verticalIndex, data); + + // only save valid LODs to disk + if (!dontSave && fileHandler != null) + { + try + { + // mark the region as dirty, so it will be saved to disk + int xIndex = (regionPosX - center.x) + halfWidth; + int zIndex = (regionPosZ - center.z) + halfWidth; + + isRegionDirty[xIndex][zIndex] = true; + regenRegionBuffer[xIndex][zIndex] = true; + regenDimensionBuffers = true; + } + catch (ArrayIndexOutOfBoundsException e) + { + e.printStackTrace(); + // If this happens, the method was probably + // called when the dimension was changing size. + // Hopefully this shouldn't be an issue. + } + } + + return nodeAdded; + } + + /** + * Add whole column of LODs to this dimension at the coordinate + * stored in the LOD. If an LOD already exists at the given + * coordinate it will be overwritten. + */ + public Boolean addVerticalData(byte detailLevel, int posX, int posZ, long[] data, boolean dontSave) + { + int regionPosX = LevelPosUtil.getRegion(detailLevel, posX); + int regionPosZ = LevelPosUtil.getRegion(detailLevel, posZ); + + // don't continue if the region can't be saved + LodRegion region = getRegion(regionPosX, regionPosZ); + if (region == null) + return false; + + boolean nodeAdded = region.addVerticalData(detailLevel, posX, posZ, data); + + // only save valid LODs to disk + if (!dontSave && fileHandler != null) + { + try + { + // mark the region as dirty, so it will be saved to disk + int xIndex = (regionPosX - center.x) + halfWidth; + int zIndex = (regionPosZ - center.z) + halfWidth; + + isRegionDirty[xIndex][zIndex] = true; + regenRegionBuffer[xIndex][zIndex] = true; + regenDimensionBuffers = true; + } + catch (ArrayIndexOutOfBoundsException e) + { + e.printStackTrace(); + // If this happens, the method was probably + // called when the dimension was changing size. + // Hopefully this shouldn't be an issue. + } + } + + return nodeAdded; + } + + /** marks the region at the given region position to have its buffer rebuilt */ + public void markRegionBufferToRegen(int xRegion, int zRegion) + { + int xIndex = (xRegion - center.x) + halfWidth; + int zIndex = (zRegion - center.z) + halfWidth; + regenRegionBuffer[xIndex][zIndex] = true; + } + + /** + * Returns every position that need to be generated based on the position of the player + */ + public PosToGenerateContainer getPosToGenerate(int maxDataToGenerate, int playerBlockPosX, int playerBlockPosZ) + { + PosToGenerateContainer posToGenerate; + LodRegion lodRegion; + // all the following values are used for the spiral matrix visit + // x and z are the matrix coord + // dx and dz is the next move on the coordinate in the range -1 0 +1 + int x, z, dx, dz, t; + x = 0; + z = 0; + dx = 0; + dz = -1; + + // We can use two type of generation scheduling + switch (CONFIG.client().worldGenerator().getGenerationPriority()) + { + default: + case NEAR_FIRST: + //in the NEAR_FIRST generation scheduling we prioritize the nearest un-generated position to the player + //the chunk position to generate will be stored in a posToGenerate object + posToGenerate = new PosToGenerateContainer((byte) 10, maxDataToGenerate, playerBlockPosX, playerBlockPosZ); + + int playerChunkX = LevelPosUtil.getChunkPos(LodUtil.BLOCK_DETAIL_LEVEL, playerBlockPosX); + int playerChunkZ = LevelPosUtil.getChunkPos(LodUtil.BLOCK_DETAIL_LEVEL, playerBlockPosZ); + + int complexity; + int xChunkToCheck; + int zChunkToCheck; + byte detailLevel; + int posX; + int posZ; + long data; + int numbChunksWide = (width) * 32; + int circleLimit = Integer.MAX_VALUE; + + //posToGenerate is using an insertion sort algorithm which can become really fast if the + //original data order is almost ordered. For this reason we explore the matrix of the position to generate + //with a spiral matrix visit (a square spiral is almost ordered in the "nearest to farthest" order) + for (int i = 0; i < numbChunksWide * numbChunksWide; i++) + { + //Firstly we check if the posToGenerate has been filled + if (maxDataToGenerate == 0) + { + maxDataToGenerate--; + //if it has been filled then we set a stop distance + //the stop distance will be current distance (generically x) per square root of 2 + //this would guarantee a circular generation since (Math.abs(x) * 1.41f) is the + //radius of a circle that inscribe a square + circleLimit = (int) (Math.abs(x) * 1.41f); + } + //This second if check if we reached the circleLimit decided in the previous if + //if so we stop + else if (maxDataToGenerate < 0) + { + if (circleLimit < Math.abs(x) && circleLimit < Math.abs(z)) + break; + } + + + xChunkToCheck = x + playerChunkX; + zChunkToCheck = z + playerChunkZ; + + //we get the lod region in which the chunk is present + lodRegion = getRegion(LodUtil.CHUNK_DETAIL_LEVEL, xChunkToCheck, zChunkToCheck); + if (lodRegion == null) + continue; + + //Now we check if the current chunk has been generated with the correct complexity + //if(lodRegion.isChunkPreGenerated(xChunkToCheck,zChunkToCheck)) + // complexity = DistanceGenerationMode.SERVER.complexity; + //else + complexity = CONFIG.client().worldGenerator().getDistanceGenerationMode().complexity; + + + //we create the level position info of the chunk + detailLevel = lodRegion.getMinDetailLevel(); + posX = LevelPosUtil.convert(LodUtil.CHUNK_DETAIL_LEVEL, xChunkToCheck, detailLevel); + posZ = LevelPosUtil.convert(LodUtil.CHUNK_DETAIL_LEVEL, zChunkToCheck, detailLevel); + + data = getSingleData(detailLevel, posX, posZ); + + //we will generate the position only if the current generation complexity is lower than the target one. + //an un-generated area will always have 0 generation + if (DataPointUtil.getGenerationMode(data) < complexity) + { + posToGenerate.addPosToGenerate(detailLevel, posX, posZ); + if (maxDataToGenerate >= 0) + maxDataToGenerate--; + } + + //with this code section we find the next chunk to check + if ((x == z) || ((x < 0) && (x == -z)) || ((x > 0) && (x == 1 - z))) + { + t = dx; + dx = -dz; + dz = t; + } + x += dx; + z += dz; + } + break; + + + case FAR_FIRST: + //in the FAR_FIRST generation we dedicate part of the generation process to the far region with really + //low detail quality. + + posToGenerate = new PosToGenerateContainer((byte) 8, maxDataToGenerate, playerBlockPosX, playerBlockPosZ); + + int xRegion; + int zRegion; + + for (int i = 0; i < width * width; i++) + { + xRegion = x + center.x; + zRegion = z + center.z; + + //All of this is handled directly by the region, which scan every pos from top to bottom of the quad tree + lodRegion = getRegion(xRegion, zRegion); + if (lodRegion != null) + lodRegion.getPosToGenerate(posToGenerate, playerBlockPosX, playerBlockPosZ); + + + //with this code section we find the next chunk to check + if ((x == z) || ((x < 0) && (x == -z)) || ((x > 0) && (x == 1 - z))) + { + t = dx; + dx = -dz; + dz = t; + } + x += dx; + z += dz; + } + break; + } + return posToGenerate; + } + + /** + * Fills the posToRender with the position to render for the regionPos given in input + */ + public void getPosToRender(PosToRenderContainer posToRender, RegionPos regionPos, int playerPosX, + int playerPosZ) + { + LodRegion region = getRegion(regionPos.x, regionPos.z); + + // use FAR_FIRST on local worlds and NEAR_FIRST on servers + GenerationPriority generationPriority = CONFIG.client().worldGenerator().getGenerationPriority() == GenerationPriority.AUTO && MC.hasSinglePlayerServer() ? GenerationPriority.FAR_FIRST : GenerationPriority.NEAR_FIRST; + boolean requireCorrectDetailLevel = generationPriority == GenerationPriority.NEAR_FIRST; + + if (region != null) + region.getPosToRender(posToRender, playerPosX, playerPosZ, requireCorrectDetailLevel); + } + + /** + * Determines how many vertical LODs could be used + * for the given region at the given detail level + */ + public int getMaxVerticalData(byte detailLevel, int posX, int posZ) + { + if (detailLevel > LodUtil.REGION_DETAIL_LEVEL) + throw new IllegalArgumentException("getMaxVerticalData given a level of [" + detailLevel + "] when [" + LodUtil.REGION_DETAIL_LEVEL + "] is the max."); + + LodRegion region = getRegion(detailLevel, posX, posZ); + if (region == null) + return 0; + + return region.getMaxVerticalData(detailLevel); + } + + /** + * Get the data point at the given X and Z coordinates + * in this dimension. + *
+ * Returns null if the LodChunk doesn't exist or + * is outside the loaded area. + */ + public long getData(byte detailLevel, int posX, int posZ, int verticalIndex) + { + if (detailLevel > LodUtil.REGION_DETAIL_LEVEL) + throw new IllegalArgumentException("getLodFromCoordinates given a level of \"" + detailLevel + "\" when \"" + LodUtil.REGION_DETAIL_LEVEL + "\" is the max."); + + LodRegion region = getRegion(detailLevel, posX, posZ); + if (region == null) + return DataPointUtil.EMPTY_DATA; + + return region.getData(detailLevel, posX, posZ, verticalIndex); + } + + + /** + * Get the data point at the given X and Z coordinates + * in this dimension. + *
+ * Returns null if the LodChunk doesn't exist or + * is outside the loaded area. + */ + public long getSingleData(byte detailLevel, int posX, int posZ) + { + if (detailLevel > LodUtil.REGION_DETAIL_LEVEL) + throw new IllegalArgumentException("getLodFromCoordinates given a level of \"" + detailLevel + "\" when \"" + LodUtil.REGION_DETAIL_LEVEL + "\" is the max."); + + LodRegion region = getRegion(detailLevel, posX, posZ); + if (region == null) + return DataPointUtil.EMPTY_DATA; + + return region.getSingleData(detailLevel, posX, posZ); + } + + /** Clears the given region */ + public void clear(byte detailLevel, int posX, int posZ) + { + if (detailLevel > LodUtil.REGION_DETAIL_LEVEL) + throw new IllegalArgumentException("getLodFromCoordinates given a level of \"" + detailLevel + "\" when \"" + LodUtil.REGION_DETAIL_LEVEL + "\" is the max."); + + LodRegion region = getRegion(detailLevel, posX, posZ); + if (region == null) + return; + + region.clear(detailLevel, posX, posZ); + } + + /** + * Returns if the buffer at the given array index needs + * to have its buffer regenerated. + */ + public boolean doesRegionNeedBufferRegen(int xIndex, int zIndex) + { + return regenRegionBuffer[xIndex][zIndex] || recreateRegionBuffer[xIndex][zIndex]; + } + + + /** + * Sets if the buffer at the given array index needs + * to have its buffer regenerated. + */ + public void setRegenRegionBufferByArrayIndex(int xIndex, int zIndex, boolean newRegen) + { + regenRegionBuffer[xIndex][zIndex] = newRegen; + } + + /** + * Get the data point at the given LevelPos + * in this dimension. + *
+ * Returns null if the LodChunk doesn't exist or + * is outside the loaded area. + */ + public void updateData(byte detailLevel, int posX, int posZ) + { + if (detailLevel > LodUtil.REGION_DETAIL_LEVEL) + throw new IllegalArgumentException("getLodFromCoordinates given a level of \"" + detailLevel + "\" when \"" + LodUtil.REGION_DETAIL_LEVEL + "\" is the max."); + + LodRegion region = getRegion(detailLevel, posX, posZ); + if (region == null) + return; + + region.updateArea(detailLevel, posX, posZ); + } + + /** Returns true if a region exists at the given LevelPos */ + public boolean doesDataExist(byte detailLevel, int posX, int posZ) + { + LodRegion region = getRegion(detailLevel, posX, posZ); + return region != null && region.doesDataExist(detailLevel, posX, posZ); + } + + /** + * Loads the region at the given RegionPos from file, + * if a file exists for that region. + */ + public LodRegion getRegionFromFile(RegionPos regionPos, byte detailLevel, + DistanceGenerationMode generationMode, VerticalQuality verticalQuality) + { + return fileHandler != null ? fileHandler.loadRegionFromFile(detailLevel, regionPos, generationMode, verticalQuality) : null; + } + + /** Save all dirty regions in this LodDimension to file. */ + public void saveDirtyRegionsToFileAsync() + { + fileHandler.saveDirtyRegionsToFileAsync(); + } + + + /** Return true if the chunk has been pregenerated in game */ + //public boolean isChunkPreGenerated(int xChunkPosWrapper, int zChunkPosWrapper) + //{ + // + // LodRegion region = getRegion(LodUtil.CHUNK_DETAIL_LEVEL, xChunkPosWrapper, zChunkPosWrapper); + // if (region == null) + // return false; + // + // return region.isChunkPreGenerated(xChunkPosWrapper, zChunkPosWrapper); + //} + + /** + * Returns whether the region at the given RegionPos + * is within the loaded range. + */ + public boolean regionIsInRange(int regionX, int regionZ) + { + int xIndex = (regionX - center.x) + halfWidth; + int zIndex = (regionZ - center.z) + halfWidth; + + return xIndex >= 0 && xIndex < width && zIndex >= 0 && zIndex < width; + } + + /** Returns the dimension's center region position X value */ + public int getCenterRegionPosX() + { + return center.x; + } + + /** Returns the dimension's center region position Z value */ + public int getCenterRegionPosZ() + { + return center.z; + } + + /** returns the width of the dimension in regions */ + public int getWidth() + { + // we want to get the length directly from the + // source to make sure it is in sync with region + // and isRegionDirty + return regions != null ? regions.length : width; + } + + /** Update the width of this dimension, in regions */ + public void setRegionWidth(int newWidth) + { + width = newWidth; + halfWidth = width/ 2; + + regions = new LodRegion[width][width]; + isRegionDirty = new boolean[width][width]; + regenRegionBuffer = new boolean[width][width]; + recreateRegionBuffer = new boolean[width][width]; + + // populate isRegionDirty + for (int i = 0; i < width; i++) + for (int j = 0; j < width; j++) + isRegionDirty[i][j] = false; + } + + + @Override + public String toString() + { + StringBuilder stringBuilder = new StringBuilder(); + stringBuilder.append("Dimension : \n"); + for (LodRegion[] lodRegions : regions) + { + for (LodRegion region : lodRegions) + { + if (region == null) + stringBuilder.append("n"); + else + stringBuilder.append(region.getMinDetailLevel()); + stringBuilder.append("\t"); + } + stringBuilder.append("\n"); + } + return stringBuilder.toString(); + } + + public boolean GetIsRegionDirty(int i, int j) + { + return isRegionDirty[i][j]; + } + + public void SetIsRegionDirty(int i, int j, boolean val) + { + isRegionDirty[i][j] = val; + } +} diff --git a/src/main/java/com/seibel/lod/core/objects/lod/LodRegion.java b/src/main/java/com/seibel/lod/core/objects/lod/LodRegion.java new file mode 100644 index 000000000..7acd6b8cf --- /dev/null +++ b/src/main/java/com/seibel/lod/core/objects/lod/LodRegion.java @@ -0,0 +1,611 @@ +/* + * This file is part of the Distant Horizon mod (formerly the LOD Mod), + * licensed under the GNU GPL v3 License. + * + * Copyright (C) 2020 James Seibel + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.seibel.lod.core.objects.lod; + +import com.seibel.lod.core.enums.config.DistanceGenerationMode; +import com.seibel.lod.core.enums.config.VerticalQuality; +import com.seibel.lod.core.objects.PosToGenerateContainer; +import com.seibel.lod.core.objects.PosToRenderContainer; +import com.seibel.lod.core.util.DataPointUtil; +import com.seibel.lod.core.util.DetailDistanceUtil; +import com.seibel.lod.core.util.LevelPosUtil; +import com.seibel.lod.core.util.LodUtil; + +/** + * This object holds all loaded LevelContainers acting as a quad tree + * for a given region.

+ * + * Coordinate Standard:
+ * Coordinate called posX or posZ are relative LevelPos coordinates
+ * unless stated otherwise.
+ * + * @author Leonardo Amato + * @version 10-10-2021 + */ +public class LodRegion +{ + /** Number of detail level supported by a region */ + private static final byte POSSIBLE_LOD = 10; + + + /** Holds the lowest (least detailed) detail level in this region */ + private byte minDetailLevel; + + /** + * This holds all data for this region + */ + private final LevelContainer[] dataContainer; + + /** This chunk Pos has been generated */ + //private final boolean[] preGeneratedChunkPos; + + /** the generation mode for this region */ + private final DistanceGenerationMode generationMode; + /** the vertical quality of this region */ + private final VerticalQuality verticalQuality; + + /** this region's x RegionPos */ + public final int regionPosX; + /** this region's z RegionPos */ + public final int regionPosZ; + + public LodRegion(byte minDetailLevel, RegionPos regionPos, DistanceGenerationMode generationMode, VerticalQuality verticalQuality) + { + this.minDetailLevel = minDetailLevel; + this.regionPosX = regionPos.x; + this.regionPosZ = regionPos.z; + this.verticalQuality = verticalQuality; + this.generationMode = generationMode; + dataContainer = new LevelContainer[POSSIBLE_LOD]; + + + // Initialize all the different matrices + for (byte lod = minDetailLevel; lod <= LodUtil.REGION_DETAIL_LEVEL; lod++) + { + dataContainer[lod] = new VerticalLevelContainer(lod); + } + + boolean fileFound = false; + + /* + preGeneratedChunkPos = new boolean[32 * 32]; + if (MinecraftWrapper.INSTANCE.hasSinglePlayerServer() && LodConfig.CLIENT.worldGenerator.useExperimentalPreGenLoading.get()) + { + File regionFileDirHead; + File regionFileDirParent; + // local world + + ServerWorld serverWorld = LodUtil.getServerWorldFromDimension(MinecraftWrapper.INSTANCE.getCurrentDimension()); + + // provider needs a separate variable to prevent + // the compiler from complaining + StringBuilder string = new StringBuilder(); + try + { + ServerChunkProvider provider = serverWorld.getChunkSource(); + + //System.out.println(provider.dataStorage.dataFolder); + regionFileDirHead = new File(provider.dataStorage.dataFolder.getCanonicalFile().getParentFile().toPath().toAbsolutePath().toString() + File.separatorChar + "region", "r." + regionPosZ + "." + regionPosX + ".mca"); + if (regionFileDirHead.exists()) + { + regionFileDirParent = regionFileDirHead.getParentFile(); + //string.append(regionFileDirParent.toString()); + string.append(regionFileDirHead); + RegionFile regionFile = new RegionFile(regionFileDirHead, regionFileDirParent, true); + for (int x = 0; x < 32; x++) + { + for (int z = 0; z < 32; z++) + { + preGeneratedChunkPos[x * 32 + z] = regionFile.doesChunkExist(new ChunkPos(regionPosX * 32 + x, regionPosZ * 32 + z)); + } + } + + string.append("region " + regionPosX + " " + regionPosZ + "\n"); + for (int x = 0; x < 32; x++) + { + for (int z = 0; z < 32; z++) + { + //regionFile.doesChunkExist() + string.append(preGeneratedChunkPos[x * 32 + z] + "\t"); + } + string.append("\n"); + } + regionFile.close(); + } + } + catch (Exception e) + { + e.printStackTrace(); + } + System.out.println(string); + }*/ + + } + + + /** Return true if the chunk has been pregenerated in game */ + //public boolean isChunkPreGenerated(int xChunkPos, int zChunkPos) + //{ + // xChunkPos = LevelPosUtil.getRegionModule(LodUtil.CHUNK_DETAIL_LEVEL, xChunkPos); + // zChunkPos = LevelPosUtil.getRegionModule(LodUtil.CHUNK_DETAIL_LEVEL, zChunkPos); + // return preGeneratedChunkPos[xChunkPos * 32 + zChunkPos]; + //} + + /** + * Inserts the data point into the region. + *

+ * TODO this will always return true unless it has + * @return true if the data was added successfully + */ + public boolean addData(byte detailLevel, int posX, int posZ, int verticalIndex, long data) + { + posX = LevelPosUtil.getRegionModule(detailLevel, posX); + posZ = LevelPosUtil.getRegionModule(detailLevel, posZ); + + // The dataContainer could have null entries if the + // detailLevel changes. + if (this.dataContainer[detailLevel] == null) + { + this.dataContainer[detailLevel] = new VerticalLevelContainer(detailLevel); + } + + this.dataContainer[detailLevel].addData(data, posX, posZ, verticalIndex); + + return true; + } + + /** + * Inserts the vertical data into the region. + *

+ * TODO this will always return true unless it has + * @return true if the data was added successfully + */ + public boolean addVerticalData(byte detailLevel, int posX, int posZ, long[] data) + { + //position is already relative + //posX = LevelPosUtil.getRegionModule(detailLevel, posX); + //posZ = LevelPosUtil.getRegionModule(detailLevel, posZ); + + // The dataContainer could have null entries if the + // detailLevel changes. + if (this.dataContainer[detailLevel] == null) + this.dataContainer[detailLevel] = new VerticalLevelContainer(detailLevel); + + return this.dataContainer[detailLevel].addVerticalData(data, posX, posZ); + } + + /** + * Get the dataPoint at the given relative position. + * @return the data at the relative pos and detail level, + * 0 if the data doesn't exist. + */ + public long getData(byte detailLevel, int posX, int posZ, int verticalIndex) + { + return dataContainer[detailLevel].getData(posX, posZ, verticalIndex); + } + + /** + * Get the dataPoint at the given relative position. + * @return the data at the relative pos and detail level, + * 0 if the data doesn't exist. + */ + public long getSingleData(byte detailLevel, int posX, int posZ) + { + return dataContainer[detailLevel].getSingleData(posX, posZ); + } + + /** + * Clears the datapoint at the given relative position + */ + public void clear(byte detailLevel, int posX, int posZ) + { + dataContainer[detailLevel].clear(posX, posZ); + } + + /** + * This method will fill the posToGenerate array with all levelPos that + * are render-able. + *

+ * TODO why don't we return the posToGenerate, it would make this easier to understand + */ + public void getPosToGenerate(PosToGenerateContainer posToGenerate, + int playerBlockPosX, int playerBlockPosZ) + { + getPosToGenerate(posToGenerate, LodUtil.REGION_DETAIL_LEVEL, 0, 0, playerBlockPosX, playerBlockPosZ); + + } + + /** + * A recursive method that fills the posToGenerate array with all levelPos that + * need to be generated. + *

+ * TODO why don't we return the posToGenerate, it would make this easier to understand + */ + private void getPosToGenerate(PosToGenerateContainer posToGenerate, byte detailLevel, + int childOffsetPosX, int childOffsetPosZ, int playerPosX, int playerPosZ) + { + // equivalent to 2^(...) + int size = 1 << (LodUtil.REGION_DETAIL_LEVEL - detailLevel); + + // calculate what LevelPos are in range to generate + int maxDistance = LevelPosUtil.maxDistance(detailLevel, childOffsetPosX, childOffsetPosZ, playerPosX, playerPosZ, regionPosX, regionPosZ); + + // determine this child's levelPos + byte childDetailLevel = (byte) (detailLevel - 1); + int childPosX = childOffsetPosX * 2; + int childPosZ = childOffsetPosZ * 2; + + int childSize = 1 << (LodUtil.REGION_DETAIL_LEVEL - childDetailLevel); + + byte targetDetailLevel = DetailDistanceUtil.getLodGenDetail(DetailDistanceUtil.getGenerationDetailFromDistance(maxDistance)).detailLevel; + if (targetDetailLevel <= detailLevel) + { + if (targetDetailLevel == detailLevel) + { + if (!doesDataExist(detailLevel, childOffsetPosX, childOffsetPosZ)) + posToGenerate.addPosToGenerate(detailLevel, childOffsetPosX + regionPosX * size, childOffsetPosZ + regionPosZ * size); + } + else + { + // we want at max one request per chunk (since the world generator creates chunks). + // So for lod smaller than a chunk, only recurse down + // the top right child + + if (detailLevel > LodUtil.CHUNK_DETAIL_LEVEL) + { + int ungeneratedChildren = 0; + + // make sure all children are generated to this detailLevel + for (int x = 0; x <= 1; x++) + { + for (int z = 0; z <= 1; z++) + { + if (!doesDataExist(childDetailLevel, childPosX + x, childPosZ + z)) + { + ungeneratedChildren++; + posToGenerate.addPosToGenerate(childDetailLevel, childPosX + x + regionPosX * childSize, childPosZ + z + regionPosZ * childSize); + } + } + } + + // only if all the children are correctly generated + // should we go deeper + if (ungeneratedChildren == 0) + for (int x = 0; x <= 1; x++) + for (int z = 0; z <= 1; z++) + getPosToGenerate(posToGenerate, childDetailLevel, childPosX + x, childPosZ + z, playerPosX, playerPosZ); + } + else + { + // The detail Level is smaller than a chunk. + // Only recurse down the top right child. + + if (DetailDistanceUtil.getLodGenDetail(childDetailLevel).detailLevel <= (childDetailLevel)) + { + if (!doesDataExist(childDetailLevel, childPosX, childPosZ)) + posToGenerate.addPosToGenerate(childDetailLevel, childPosX + regionPosX * childSize, childPosZ + regionPosZ * childSize); + else + getPosToGenerate(posToGenerate, childDetailLevel, childPosX, childPosZ, playerPosX, playerPosZ); + } + } + } + } + // we have gone beyond the target Detail level + // we can stop generating + + } + + + /** + * This method will fill the posToRender array with all levelPos that + * are render-able. + *

+ * TODO why don't we return the posToRender, it would make this easier to understand + */ + public void getPosToRender(PosToRenderContainer posToRender, + int playerPosX, int playerPosZ, boolean requireCorrectDetailLevel) + { + getPosToRender(posToRender, LodUtil.REGION_DETAIL_LEVEL, 0, 0, playerPosX, playerPosZ, requireCorrectDetailLevel); + } + + /** + * This method will fill the posToRender array with all levelPos that + * are render-able. + *

+ * TODO why don't we return the posToRender, it would make this easier to understand + * TODO this needs some more comments, James was only able to figure out part of it + */ + private void getPosToRender(PosToRenderContainer posToRender, + byte detailLevel, int posX, int posZ, + int playerPosX, int playerPosZ, boolean requireCorrectDetailLevel) + { + // equivalent to 2^(...) + int size = 1 << (LodUtil.REGION_DETAIL_LEVEL - detailLevel); + + byte desiredLevel; + int maxDistance; + int minDistance; + int childLevel; + + + // calculate the LevelPos that are in range + maxDistance = LevelPosUtil.maxDistance(detailLevel, posX, posZ, playerPosX, playerPosZ, regionPosX, regionPosZ); + desiredLevel = DetailDistanceUtil.getLodDrawDetail(DetailDistanceUtil.getDrawDetailFromDistance(maxDistance)); + minDistance = LevelPosUtil.minDistance(detailLevel, posX, posZ, playerPosX, playerPosZ, regionPosX, regionPosZ); + childLevel = DetailDistanceUtil.getLodDrawDetail(DetailDistanceUtil.getDrawDetailFromDistance(minDistance)); + + if (detailLevel == childLevel - 1) + { + posToRender.addPosToRender(detailLevel, + posX + regionPosX * size, + posZ + regionPosZ * size); + } + else + //if (desiredLevel > detailLevel) + //{ + // we have gone beyond the target Detail level + // we can stop generating + //} else + if (desiredLevel == detailLevel) + { + posToRender.addPosToRender(detailLevel, + posX + regionPosX * size, + posZ + regionPosZ * size); + } + else //case where (detailLevel > desiredLevel) + { + int childPosX = posX * 2; + int childPosZ = posZ * 2; + byte childDetailLevel = (byte) (detailLevel - 1); + int childrenCount = 0; + + for (int x = 0; x <= 1; x++) + { + for (int z = 0; z <= 1; z++) + { + if (doesDataExist(childDetailLevel, childPosX + x, childPosZ + z)) + { + if (!requireCorrectDetailLevel) + childrenCount++; + else + getPosToRender(posToRender, childDetailLevel, childPosX + x, childPosZ + z, playerPosX, playerPosZ, requireCorrectDetailLevel); + } + } + } + + + if (!requireCorrectDetailLevel) + { + // If all the four children exist go deeper + if (childrenCount == 4) + { + for (int x = 0; x <= 1; x++) + for (int z = 0; z <= 1; z++) + getPosToRender(posToRender, childDetailLevel, childPosX + x, childPosZ + z, playerPosX, playerPosZ, requireCorrectDetailLevel); + } + else + { + posToRender.addPosToRender(detailLevel, + posX + regionPosX * size, + posZ + regionPosZ * size); + } + } + } + } + + + /** + * Updates all children. + *

+ * TODO could this be renamed mergeArea? + */ + public void updateArea(byte detailLevel, int posX, int posZ) + { + int width; + int startX; + int startZ; + + // TODO what are each of these loops updating? + for (byte down = (byte) (minDetailLevel + 1); down <= detailLevel; down++) + { + startX = LevelPosUtil.convert(detailLevel, posX, down); + startZ = LevelPosUtil.convert(detailLevel, posZ, down); + width = 1 << (detailLevel - down); + + for (int x = 0; x < width; x++) + for (int z = 0; z < width; z++) + update(down, startX + x, startZ + z); + } + + + for (byte up = (byte) (detailLevel + 1); up <= LodUtil.REGION_DETAIL_LEVEL; up++) + { + update(up, + LevelPosUtil.convert(detailLevel, posX, up), + LevelPosUtil.convert(detailLevel, posZ, up)); + } + } + + /** + * Update the child at the given relative Pos + *

+ * TODO could this be renamed mergeChildData? + */ + private void update(byte detailLevel, int posX, int posZ) + { + dataContainer[detailLevel].updateData(dataContainer[detailLevel - 1], posX, posZ); + } + + + /** + * Returns if data exists at the given relative Pos. + */ + public boolean doesDataExist(byte detailLevel, int posX, int posZ) + { + if (detailLevel < minDetailLevel) + return false; + + posX = LevelPosUtil.getRegionModule(detailLevel, posX); + posZ = LevelPosUtil.getRegionModule(detailLevel, posZ); + + if (dataContainer[detailLevel] == null) + return false; + + return dataContainer[detailLevel].doesItExist(posX, posZ); + } + + /** + * Gets the generation mode for the data point at the given relative pos. + */ + public byte getGenerationMode(byte detailLevel, int posX, int posZ) + { + if (dataContainer[detailLevel].doesItExist(posX, posZ)) + // We take the bottom information always + // TODO what does that mean? bottom of what? + return DataPointUtil.getGenerationMode(dataContainer[detailLevel].getSingleData(posX, posZ)); + else + return DistanceGenerationMode.NONE.complexity; + } + + /** + * Returns the lowest (least detailed) detail level in this region + * TODO is that right? + */ + public byte getMinDetailLevel() + { + return minDetailLevel; + } + + /** + * Returns the LevelContainer for the detailLevel + * @throws IllegalArgumentException if the detailLevel is less than minDetailLevel + */ + public LevelContainer getLevel(byte detailLevel) + { + if (detailLevel < minDetailLevel) + throw new IllegalArgumentException("getLevel asked for a detail level that does not exist: minimum: [" + minDetailLevel + "] level requested: [" + detailLevel + "]"); + + return dataContainer[detailLevel]; + } + + /** + * Add the levelContainer to this Region, updating the minDetailLevel + * if necessary. + * @throws IllegalArgumentException if the LevelContainer's detailLevel + * is 2 or more detail levels lower than the + * minDetailLevel of this region. + */ + public void addLevelContainer(LevelContainer levelContainer) + { + if (levelContainer.getDetailLevel() < minDetailLevel - 1) + { + throw new IllegalArgumentException( + "the LevelContainer's detailLevel was " + + "[" + levelContainer.getDetailLevel() + "] but this region " + + "only allows adding LevelContainers with a " + + "detail level of [" + (minDetailLevel - 1) + "]"); + } + + if (levelContainer.getDetailLevel() == minDetailLevel - 1) + minDetailLevel = levelContainer.getDetailLevel(); + + dataContainer[levelContainer.getDetailLevel()] = levelContainer; + } + + // TODO James thinks cutTree and growTree (which he renamed to match cutTree) + // should have more descriptive names, to make sure the "Tree" portion isn't + // confused with Minecraft trees (the plant). + + /** + * Removes any dataContainers that are higher than + * the given detailLevel + */ + public void cutTree(byte detailLevel) + { + if (detailLevel > minDetailLevel) + { + for (byte detailLevelIndex = 0; detailLevelIndex < detailLevel; detailLevelIndex++) + dataContainer[detailLevelIndex] = null; + + minDetailLevel = detailLevel; + } + } + + /** + * Make this region more detailed to the detailLevel given. + * TODO is that correct? + */ + public void growTree(byte detailLevel) + { + if (detailLevel < minDetailLevel) + { + for (byte detailLevelIndex = (byte) (minDetailLevel - 1); detailLevelIndex >= detailLevel; detailLevelIndex--) + { + if (dataContainer[detailLevelIndex + 1] == null) + dataContainer[detailLevelIndex + 1] = new VerticalLevelContainer((byte) (detailLevelIndex + 1)); + + dataContainer[detailLevelIndex] = dataContainer[detailLevelIndex + 1].expand(); + } + minDetailLevel = detailLevel; + } + } + + /** + * return RegionPos of this lod region + */ + public RegionPos getRegionPos() + { + return new RegionPos(regionPosX, regionPosZ); + } + + /** + * Returns how many LODs are in this region + */ + public int getNumberOfLods() + { + int count = 0; + for (LevelContainer container : dataContainer) + count += container.getMaxNumberOfLods(); + + return count; + } + + public VerticalQuality getVerticalQuality() + { + return verticalQuality; + } + + public DistanceGenerationMode getGenerationMode() + { + return generationMode; + } + + public int getMaxVerticalData(byte detailLevel) + { + return dataContainer[detailLevel].getMaxVerticalData(); + } + + + @Override + public String toString() + { + return getLevel(LodUtil.REGION_DETAIL_LEVEL).toString(); + } +} diff --git a/src/main/java/com/seibel/lod/core/objects/lod/LodWorld.java b/src/main/java/com/seibel/lod/core/objects/lod/LodWorld.java new file mode 100644 index 000000000..9897d297d --- /dev/null +++ b/src/main/java/com/seibel/lod/core/objects/lod/LodWorld.java @@ -0,0 +1,171 @@ +/* + * This file is part of the Distant Horizon mod (formerly the LOD Mod), + * licensed under the GNU GPL v3 License. + * + * Copyright (C) 2020 James Seibel + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.seibel.lod.core.objects.lod; + +import java.util.Hashtable; +import java.util.Map; + +import com.seibel.lod.core.api.ClientApi; +import com.seibel.lod.core.wrapperInterfaces.world.IDimensionTypeWrapper; + +/** + * This stores all LODs for a given world. + * + * @author James Seibel + * @author Leonardo Amato + * @version 9-27-2021 + */ +public class LodWorld +{ + /** name of this world */ + private String worldName; + + /** dimensions in this world */ + private Map lodDimensions; + + /** If true then the LOD world is setup and ready to use */ + private boolean isWorldLoaded = false; + + /** the name given to the world if it isn't loaded */ + public static final String NO_WORLD_LOADED = "No world loaded"; + + + + public LodWorld() + { + worldName = NO_WORLD_LOADED; + } + + + + /** + * Set up the LodWorld with the given newWorldName.
+ * This should be done whenever loading a new world.

+ *

+ * Note a System.gc() call may be in order after calling this
+ * since a lot of LOD data is now homeless.
+ * @param newWorldName name of the world + */ + public void selectWorld(String newWorldName) + { + if (newWorldName.isEmpty()) + { + deselectWorld(); + return; + } + + if (worldName.equals(newWorldName)) + // don't recreate everything if we + // didn't actually change worlds + return; + + worldName = newWorldName; + lodDimensions = new Hashtable<>(); + isWorldLoaded = true; + } + + /** + * Set the worldName to "No world loaded" + * and clear the lodDimensions Map.
+ * This should be done whenever unloaded a world.

+ *

+ * Note a System.gc() call may be in order after calling this
+ * since a lot of LOD data is now homeless.
+ */ + public void deselectWorld() + { + worldName = NO_WORLD_LOADED; + lodDimensions = null; + isWorldLoaded = false; + } + + + /** + * Adds newDimension to this world, if a LodDimension + * already exists for the given dimension it is replaced. + */ + public void addLodDimension(LodDimension newDimension) + { + if (lodDimensions == null) + return; + + lodDimensions.put(newDimension.dimension, newDimension); + } + + /** + * Returns null if no LodDimension exists for the given dimension + */ + public LodDimension getLodDimension(IDimensionTypeWrapper dimType) + { + if (lodDimensions == null) + return null; + + return lodDimensions.get(dimType); + } + + /** + * Resizes the max width in regions that each LodDimension + * should use. + */ + public void resizeDimensionRegionWidth(int newRegionWidth) + { + if (lodDimensions == null) + return; + + saveAllDimensions(); + + for (IDimensionTypeWrapper key : lodDimensions.keySet()) + lodDimensions.get(key).setRegionWidth(newRegionWidth); + } + + /** + * Requests all dimensions save any dirty regions they may have. + */ + public void saveAllDimensions() + { + if (lodDimensions == null) + return; + + // TODO we should only print this if lods were actually saved to file + // but that requires a LodDimension.hasDirtyRegions() method or something similar + ClientApi.LOGGER.info("Saving LODs"); + + for (IDimensionTypeWrapper key : lodDimensions.keySet()) + lodDimensions.get(key).saveDirtyRegionsToFileAsync(); + } + + + public boolean getIsWorldNotLoaded() + { + return !isWorldLoaded; + } + + public String getWorldName() + { + return worldName; + } + + @Override + public String toString() + { + return "World name: " + worldName; + } +} + diff --git a/src/main/java/com/seibel/lod/core/objects/lod/RegionPos.java b/src/main/java/com/seibel/lod/core/objects/lod/RegionPos.java new file mode 100644 index 000000000..f50754d02 --- /dev/null +++ b/src/main/java/com/seibel/lod/core/objects/lod/RegionPos.java @@ -0,0 +1,91 @@ +/* + * This file is part of the Distant Horizon mod (formerly the LOD Mod), + * licensed under the GNU GPL v3 License. + * + * Copyright (C) 2020 James Seibel + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.seibel.lod.core.objects.lod; + +import com.seibel.lod.core.util.LodUtil; +import com.seibel.lod.core.util.SingletonHandler; +import com.seibel.lod.core.wrapperInterfaces.IWrapperFactory; +import com.seibel.lod.core.wrapperInterfaces.block.AbstractBlockPosWrapper; +import com.seibel.lod.core.wrapperInterfaces.chunk.AbstractChunkPosWrapper; + +/** + * This object is similar to ChunkPos or BlockPos. + * + * @author James Seibel + * @version 8-21-2021 + */ +public class RegionPos +{ + private static final IWrapperFactory WRAPPER_FACTORY = SingletonHandler.get(IWrapperFactory.class); + + + public int x; + public int z; + + + /** Sets x and z to 0 */ + public RegionPos() + { + x = 0; + z = 0; + } + + /** simple constructor that sets x and z to new x and z. */ + public RegionPos(int newX, int newZ) + { + x = newX; + z = newZ; + } + + /** Converts from a BlockPos to a RegionPos */ + public RegionPos(AbstractBlockPosWrapper pos) + { + this(WRAPPER_FACTORY.createChunkPos(pos)); + } + + /** Converts from a ChunkPos to a RegionPos */ + public RegionPos(AbstractChunkPosWrapper pos) + { + x = Math.floorDiv(pos.getX(), LodUtil.REGION_WIDTH_IN_CHUNKS); + z = Math.floorDiv(pos.getZ(), LodUtil.REGION_WIDTH_IN_CHUNKS); + } + + /** Returns the ChunkPos at the center of this region */ + public AbstractChunkPosWrapper chunkPos() + { + return WRAPPER_FACTORY.createChunkPos( + (x * LodUtil.REGION_WIDTH_IN_CHUNKS) + LodUtil.REGION_WIDTH_IN_CHUNKS / 2, + (z * LodUtil.REGION_WIDTH_IN_CHUNKS) + LodUtil.REGION_WIDTH_IN_CHUNKS / 2); + } + + /** Returns the BlockPos at the center of this region */ + public AbstractBlockPosWrapper blockPos() + { + return chunkPos().getWorldPosition() + .offset(LodUtil.CHUNK_WIDTH / 2, 0, LodUtil.CHUNK_WIDTH / 2); + } + + + @Override + public String toString() + { + return "(" + x + "," + z + ")"; + } +} diff --git a/src/main/java/com/seibel/lod/core/objects/lod/VerticalLevelContainer.java b/src/main/java/com/seibel/lod/core/objects/lod/VerticalLevelContainer.java new file mode 100644 index 000000000..f813a9a86 --- /dev/null +++ b/src/main/java/com/seibel/lod/core/objects/lod/VerticalLevelContainer.java @@ -0,0 +1,289 @@ +/* + * This file is part of the Distant Horizon mod (formerly the LOD Mod), + * licensed under the GNU GPL v3 License. + * + * Copyright (C) 2020 James Seibel + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.seibel.lod.core.objects.lod; + +import com.seibel.lod.core.util.DataPointUtil; +import com.seibel.lod.core.util.DetailDistanceUtil; +import com.seibel.lod.core.util.LevelPosUtil; +import com.seibel.lod.core.util.LodUtil; +import com.seibel.lod.core.util.ThreadMapUtil; + +/** + * + * @author Leonardo Amato + * @version ?? + */ +public class VerticalLevelContainer implements LevelContainer +{ + + public final byte detailLevel; + public final int size; + public final int maxVerticalData; + + public final long[] dataContainer; + + public VerticalLevelContainer(byte detailLevel) + { + this.detailLevel = detailLevel; + size = 1 << (LodUtil.REGION_DETAIL_LEVEL - detailLevel); + maxVerticalData = DetailDistanceUtil.getMaxVerticalData(detailLevel); + dataContainer = new long[size * size * DetailDistanceUtil.getMaxVerticalData(detailLevel)]; + } + + @Override + public byte getDetailLevel() + { + return detailLevel; + } + + @Override + public void clear(int posX, int posZ) + { + posX = LevelPosUtil.getRegionModule(detailLevel, posX); + posZ = LevelPosUtil.getRegionModule(detailLevel, posZ); + for (int verticalIndex = 0; verticalIndex < maxVerticalData; verticalIndex++) + { + dataContainer[posX * size * maxVerticalData + posZ * maxVerticalData + verticalIndex] = DataPointUtil.EMPTY_DATA; + } + } + + @Override + public boolean addData(long data, int posX, int posZ, int verticalIndex) + { + posX = LevelPosUtil.getRegionModule(detailLevel, posX); + posZ = LevelPosUtil.getRegionModule(detailLevel, posZ); + dataContainer[posX * size * maxVerticalData + posZ * maxVerticalData + verticalIndex] = data; + return true; + } + + @Override + public boolean addVerticalData(long[] data, int posX, int posZ) + { + posX = LevelPosUtil.getRegionModule(detailLevel, posX); + posZ = LevelPosUtil.getRegionModule(detailLevel, posZ); + for (int verticalIndex = 0; verticalIndex < maxVerticalData; verticalIndex++) + dataContainer[posX * size * maxVerticalData + posZ * maxVerticalData + verticalIndex] = data[verticalIndex]; + return true; + } + + @Override + public boolean addSingleData(long data, int posX, int posZ) + { + return addData(data, posX, posZ, 0); + } + + @Override + public long getData(int posX, int posZ, int verticalIndex) + { + posX = LevelPosUtil.getRegionModule(detailLevel, posX); + posZ = LevelPosUtil.getRegionModule(detailLevel, posZ); + return dataContainer[posX * size * maxVerticalData + posZ * maxVerticalData + verticalIndex]; + } + + @Override + public long getSingleData(int posX, int posZ) + { + return getData(posX, posZ, 0); + } + + @Override + public int getMaxVerticalData() + { + return maxVerticalData; + } + + public int getSize() + { + return size; + } + + @Override + public boolean doesItExist(int posX, int posZ) + { + posX = LevelPosUtil.getRegionModule(detailLevel, posX); + posZ = LevelPosUtil.getRegionModule(detailLevel, posZ); + return DataPointUtil.doesItExist(getSingleData(posX, posZ)); + } + + public VerticalLevelContainer(byte[] inputData, int version) + { + int tempMaxVerticalData; + int tempIndex; + int index = 0; + long newData; + detailLevel = inputData[index]; + index++; + tempMaxVerticalData = inputData[index] & 0b01111111; + index++; + size = 1 << (LodUtil.REGION_DETAIL_LEVEL - detailLevel); + int x = size * size * tempMaxVerticalData; + long[] tempDataContainer = new long[x]; + + if (version == 6) + { + for (int i = 0; i < x; i++) + { + newData = 0; + for (tempIndex = 0; tempIndex < 8; tempIndex++) + newData += (((long) inputData[index + tempIndex]) & 0xff) << (8 * tempIndex); + index += 8; + + newData = DataPointUtil.createDataPoint( + DataPointUtil.getAlpha(newData), + DataPointUtil.getRed(newData), + DataPointUtil.getGreen(newData), + DataPointUtil.getBlue(newData), + DataPointUtil.getHeight(newData) - DataPointUtil.VERTICAL_OFFSET, + DataPointUtil.getDepth(newData) - DataPointUtil.VERTICAL_OFFSET, + DataPointUtil.getLightSky(newData), + DataPointUtil.getLightBlock(newData), + DataPointUtil.getGenerationMode(newData), + DataPointUtil.getFlag(newData) + ); + tempDataContainer[i] = newData; + } + } + else //if (version == 7) + { + for (int i = 0; i < x; i++) + { + newData = 0; + for (tempIndex = 0; tempIndex < 8; tempIndex++) + newData += (((long) inputData[index + tempIndex]) & 0xff) << (8 * tempIndex); + index += 8; + tempDataContainer[i] = newData; + } + } + + if (tempMaxVerticalData > DetailDistanceUtil.getMaxVerticalData(detailLevel)) + { + int tempMaxVerticalData2 = DetailDistanceUtil.getMaxVerticalData(detailLevel); + long[] dataToMerge = new long[tempMaxVerticalData]; + long[] tempDataContainer2 = new long[size * size * tempMaxVerticalData2]; + for (int i = 0; i < size * size; i++) + { + System.arraycopy(tempDataContainer, i * tempMaxVerticalData, dataToMerge, 0, tempMaxVerticalData); + dataToMerge = DataPointUtil.mergeMultiData(dataToMerge, tempMaxVerticalData, tempMaxVerticalData2); + System.arraycopy(dataToMerge, 0, tempDataContainer2, i * tempMaxVerticalData2, tempMaxVerticalData2); + } + maxVerticalData = tempMaxVerticalData2; + this.dataContainer = tempDataContainer2; + } + else + { + maxVerticalData = tempMaxVerticalData; + this.dataContainer = tempDataContainer; + } + } + + @Override + public LevelContainer expand() + { + return new VerticalLevelContainer((byte) (getDetailLevel() - 1)); + } + + @Override + public void updateData(LevelContainer lowerLevelContainer, int posX, int posZ) + { + //We reset the array + long[] dataToMerge = ThreadMapUtil.getVerticalUpdateArray(detailLevel); + + int lowerMaxVertical = dataToMerge.length / 4; + int childPosX; + int childPosZ; + long[] data; + posX = LevelPosUtil.getRegionModule(detailLevel, posX); + posZ = LevelPosUtil.getRegionModule(detailLevel, posZ); + for (int x = 0; x <= 1; x++) + { + for (int z = 0; z <= 1; z++) + { + childPosX = 2 * posX + x; + childPosZ = 2 * posZ + z; + for (int verticalIndex = 0; verticalIndex < lowerMaxVertical; verticalIndex++) + dataToMerge[(z * 2 + x) * lowerMaxVertical + verticalIndex] = lowerLevelContainer.getData(childPosX, childPosZ, verticalIndex); + } + } + data = DataPointUtil.mergeMultiData(dataToMerge, lowerMaxVertical, getMaxVerticalData()); + + addVerticalData(data, posX, posZ); + } + + @Override + public byte[] toDataString() + { + int index = 0; + int x = size * size; + int tempIndex; + long current; + boolean allGenerated = true; + byte[] tempData = ThreadMapUtil.getSaveContainer(detailLevel); + + tempData[index] = detailLevel; + index++; + tempData[index] = (byte) maxVerticalData; + index++; + int j; + for (int i = 0; i < x; i++) + { + for (j = 0; j < maxVerticalData; j++) + { + current = dataContainer[i * maxVerticalData + j]; + for (tempIndex = 0; tempIndex < 8; tempIndex++) + tempData[index + tempIndex] = (byte) (current >>> (8 * tempIndex)); + index += 8; + } + if(!DataPointUtil.doesItExist(dataContainer[i])) + allGenerated = false; + } + if (allGenerated) + tempData[1] |= 0b10000000; + return tempData; + } + + @Override + @SuppressWarnings("unused") + public String toString() + { + /* + StringBuilder stringBuilder = new StringBuilder(); + int size = 1 << (LodUtil.REGION_DETAIL_LEVEL - detailLevel); + stringBuilder.append(detailLevel); + stringBuilder.append(DATA_DELIMITER); + for (int x = 0; x < size; x++) + { + for (int z = 0; z < size; z++) + { + //Converting the dataToHex + stringBuilder.append(Long.toHexString(dataContainer[x][z][0])); + stringBuilder.append(DATA_DELIMITER); + } + } + return stringBuilder.toString(); + */ + return " "; + } + + @Override + public int getMaxNumberOfLods() + { + return size * size * getMaxVerticalData(); + } +} diff --git a/src/main/java/com/seibel/lod/core/objects/math/Mat4f.java b/src/main/java/com/seibel/lod/core/objects/math/Mat4f.java new file mode 100644 index 000000000..88588131e --- /dev/null +++ b/src/main/java/com/seibel/lod/core/objects/math/Mat4f.java @@ -0,0 +1,543 @@ +/* + * This file is part of the Distant Horizon mod (formerly the LOD Mod), + * licensed under the GNU GPL v3 License. + * + * Copyright (C) 2020 James Seibel + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.seibel.lod.core.objects.math; + +import java.nio.FloatBuffer; + +/** + * A (almost) exact copy of Minecraft's 1.16.5 + * implementation of a 4x4 float matrix. + * + * @author James Seibel + * @version 11-11-2021 + */ +public class Mat4f +{ + private float m00; + private float m01; + private float m02; + private float m03; + private float m10; + private float m11; + private float m12; + private float m13; + private float m20; + private float m21; + private float m22; + private float m23; + private float m30; + private float m31; + private float m32; + private float m33; + + + public Mat4f() + { + + } + + public Mat4f(Mat4f sourceMatrix) + { + this.m00 = sourceMatrix.m00; + this.m01 = sourceMatrix.m01; + this.m02 = sourceMatrix.m02; + this.m03 = sourceMatrix.m03; + this.m10 = sourceMatrix.m10; + this.m11 = sourceMatrix.m11; + this.m12 = sourceMatrix.m12; + this.m13 = sourceMatrix.m13; + this.m20 = sourceMatrix.m20; + this.m21 = sourceMatrix.m21; + this.m22 = sourceMatrix.m22; + this.m23 = sourceMatrix.m23; + this.m30 = sourceMatrix.m30; + this.m31 = sourceMatrix.m31; + this.m32 = sourceMatrix.m32; + this.m33 = sourceMatrix.m33; + } + + /* Quaternions are not currently needed/implemented + public Matrix4float(Quaternion p_i48104_1_) + { + float f = p_i48104_1_.i(); + float f1 = p_i48104_1_.j(); + float f2 = p_i48104_1_.k(); + float f3 = p_i48104_1_.r(); + float f4 = 2.0F * f * f; + float f5 = 2.0F * f1 * f1; + float f6 = 2.0F * f2 * f2; + this.m00 = 1.0F - f5 - f6; + this.m11 = 1.0F - f6 - f4; + this.m22 = 1.0F - f4 - f5; + this.m33 = 1.0F; + float f7 = f * f1; + float f8 = f1 * f2; + float f9 = f2 * f; + float f10 = f * f3; + float f11 = f1 * f3; + float f12 = f2 * f3; + this.m10 = 2.0F * (f7 + f12); + this.m01 = 2.0F * (f7 - f12); + this.m20 = 2.0F * (f9 - f11); + this.m02 = 2.0F * (f9 + f11); + this.m21 = 2.0F * (f8 + f10); + this.m12 = 2.0F * (f8 - f10); + } + */ + + + @Override + public boolean equals(Object obj) + { + if (this == obj) + { + return true; + } + else if (obj != null && this.getClass() == obj.getClass()) + { + Mat4f otherMatrix = (Mat4f) obj; + return Float.compare(otherMatrix.m00, this.m00) == 0 + && Float.compare(otherMatrix.m01, this.m01) == 0 + && Float.compare(otherMatrix.m02, this.m02) == 0 + && Float.compare(otherMatrix.m03, this.m03) == 0 + && Float.compare(otherMatrix.m10, this.m10) == 0 + && Float.compare(otherMatrix.m11, this.m11) == 0 + && Float.compare(otherMatrix.m12, this.m12) == 0 + && Float.compare(otherMatrix.m13, this.m13) == 0 + && Float.compare(otherMatrix.m20, this.m20) == 0 + && Float.compare(otherMatrix.m21, this.m21) == 0 + && Float.compare(otherMatrix.m22, this.m22) == 0 + && Float.compare(otherMatrix.m23, this.m23) == 0 + && Float.compare(otherMatrix.m30, this.m30) == 0 + && Float.compare(otherMatrix.m31, this.m31) == 0 + && Float.compare(otherMatrix.m32, this.m32) == 0 + && Float.compare(otherMatrix.m33, this.m33) == 0; + } + else + { + return false; + } + } + + @Override + public int hashCode() + { + int i = this.m00 != 0.0F ? Float.floatToIntBits(this.m00) : 0; + i = 31 * i + (this.m01 != 0.0F ? Float.floatToIntBits(this.m01) : 0); + i = 31 * i + (this.m02 != 0.0F ? Float.floatToIntBits(this.m02) : 0); + i = 31 * i + (this.m03 != 0.0F ? Float.floatToIntBits(this.m03) : 0); + i = 31 * i + (this.m10 != 0.0F ? Float.floatToIntBits(this.m10) : 0); + i = 31 * i + (this.m11 != 0.0F ? Float.floatToIntBits(this.m11) : 0); + i = 31 * i + (this.m12 != 0.0F ? Float.floatToIntBits(this.m12) : 0); + i = 31 * i + (this.m13 != 0.0F ? Float.floatToIntBits(this.m13) : 0); + i = 31 * i + (this.m20 != 0.0F ? Float.floatToIntBits(this.m20) : 0); + i = 31 * i + (this.m21 != 0.0F ? Float.floatToIntBits(this.m21) : 0); + i = 31 * i + (this.m22 != 0.0F ? Float.floatToIntBits(this.m22) : 0); + i = 31 * i + (this.m23 != 0.0F ? Float.floatToIntBits(this.m23) : 0); + i = 31 * i + (this.m30 != 0.0F ? Float.floatToIntBits(this.m30) : 0); + i = 31 * i + (this.m31 != 0.0F ? Float.floatToIntBits(this.m31) : 0); + i = 31 * i + (this.m32 != 0.0F ? Float.floatToIntBits(this.m32) : 0); + return 31 * i + (this.m33 != 0.0F ? Float.floatToIntBits(this.m33) : 0); + } + + + @Override + public String toString() + { + return "Matrix4f:\n" + + this.m00 + " " + this.m01 + " " + this.m02 + " " + this.m03 + "\n" + + this.m10 + " " + this.m11 + " " + this.m12 + " " + this.m13 + "\n" + + this.m20 + " " + this.m21 + " " + this.m22 + " " + this.m23 + "\n" + + this.m30 + " " + this.m31 + " " + this.m32 + " " + this.m33 + "\n"; + } + + + public void store(FloatBuffer floatBuffer) + { + floatBuffer.put(bufferIndex(0, 0), this.m00); + floatBuffer.put(bufferIndex(0, 1), this.m01); + floatBuffer.put(bufferIndex(0, 2), this.m02); + floatBuffer.put(bufferIndex(0, 3), this.m03); + floatBuffer.put(bufferIndex(1, 0), this.m10); + floatBuffer.put(bufferIndex(1, 1), this.m11); + floatBuffer.put(bufferIndex(1, 2), this.m12); + floatBuffer.put(bufferIndex(1, 3), this.m13); + floatBuffer.put(bufferIndex(2, 0), this.m20); + floatBuffer.put(bufferIndex(2, 1), this.m21); + floatBuffer.put(bufferIndex(2, 2), this.m22); + floatBuffer.put(bufferIndex(2, 3), this.m23); + floatBuffer.put(bufferIndex(3, 0), this.m30); + floatBuffer.put(bufferIndex(3, 1), this.m31); + floatBuffer.put(bufferIndex(3, 2), this.m32); + floatBuffer.put(bufferIndex(3, 3), this.m33); + } + + private static int bufferIndex(int xIndex, int zIndex) + { + return (zIndex * 4) + xIndex; + } + + + public void setIdentity() + { + this.m00 = 1.0F; + this.m01 = 0.0F; + this.m02 = 0.0F; + this.m03 = 0.0F; + this.m10 = 0.0F; + this.m11 = 1.0F; + this.m12 = 0.0F; + this.m13 = 0.0F; + this.m20 = 0.0F; + this.m21 = 0.0F; + this.m22 = 1.0F; + this.m23 = 0.0F; + this.m30 = 0.0F; + this.m31 = 0.0F; + this.m32 = 0.0F; + this.m33 = 1.0F; + } + + /** adjugate and determinate */ + public float adjugateAndDet() + { + float f = this.m00 * this.m11 - this.m01 * this.m10; + float f1 = this.m00 * this.m12 - this.m02 * this.m10; + float f2 = this.m00 * this.m13 - this.m03 * this.m10; + float f3 = this.m01 * this.m12 - this.m02 * this.m11; + float f4 = this.m01 * this.m13 - this.m03 * this.m11; + float f5 = this.m02 * this.m13 - this.m03 * this.m12; + float f6 = this.m20 * this.m31 - this.m21 * this.m30; + float f7 = this.m20 * this.m32 - this.m22 * this.m30; + float f8 = this.m20 * this.m33 - this.m23 * this.m30; + float f9 = this.m21 * this.m32 - this.m22 * this.m31; + float f10 = this.m21 * this.m33 - this.m23 * this.m31; + float f11 = this.m22 * this.m33 - this.m23 * this.m32; + float f12 = this.m11 * f11 - this.m12 * f10 + this.m13 * f9; + float f13 = -this.m10 * f11 + this.m12 * f8 - this.m13 * f7; + float f14 = this.m10 * f10 - this.m11 * f8 + this.m13 * f6; + float f15 = -this.m10 * f9 + this.m11 * f7 - this.m12 * f6; + float f16 = -this.m01 * f11 + this.m02 * f10 - this.m03 * f9; + float f17 = this.m00 * f11 - this.m02 * f8 + this.m03 * f7; + float f18 = -this.m00 * f10 + this.m01 * f8 - this.m03 * f6; + float f19 = this.m00 * f9 - this.m01 * f7 + this.m02 * f6; + float f20 = this.m31 * f5 - this.m32 * f4 + this.m33 * f3; + float f21 = -this.m30 * f5 + this.m32 * f2 - this.m33 * f1; + float f22 = this.m30 * f4 - this.m31 * f2 + this.m33 * f; + float f23 = -this.m30 * f3 + this.m31 * f1 - this.m32 * f; + float f24 = -this.m21 * f5 + this.m22 * f4 - this.m23 * f3; + float f25 = this.m20 * f5 - this.m22 * f2 + this.m23 * f1; + float f26 = -this.m20 * f4 + this.m21 * f2 - this.m23 * f; + float f27 = this.m20 * f3 - this.m21 * f1 + this.m22 * f; + this.m00 = f12; + this.m10 = f13; + this.m20 = f14; + this.m30 = f15; + this.m01 = f16; + this.m11 = f17; + this.m21 = f18; + this.m31 = f19; + this.m02 = f20; + this.m12 = f21; + this.m22 = f22; + this.m32 = f23; + this.m03 = f24; + this.m13 = f25; + this.m23 = f26; + this.m33 = f27; + return f * f11 - f1 * f10 + f2 * f9 + f3 * f8 - f4 * f7 + f5 * f6; + } + + public void transpose() + { + float f = this.m10; + this.m10 = this.m01; + this.m01 = f; + f = this.m20; + this.m20 = this.m02; + this.m02 = f; + f = this.m21; + this.m21 = this.m12; + this.m12 = f; + f = this.m30; + this.m30 = this.m03; + this.m03 = f; + f = this.m31; + this.m31 = this.m13; + this.m13 = f; + f = this.m32; + this.m32 = this.m23; + this.m23 = f; + } + + public boolean invert() + { + float det = this.adjugateAndDet(); + if (Math.abs(det) > 1.0E-6F) + { + this.multiply(det); + return true; + } + else + { + return false; + } + } + + public void multiply(Mat4f multMatrix) + { + float f = this.m00 * multMatrix.m00 + this.m01 * multMatrix.m10 + this.m02 * multMatrix.m20 + this.m03 * multMatrix.m30; + float f1 = this.m00 * multMatrix.m01 + this.m01 * multMatrix.m11 + this.m02 * multMatrix.m21 + this.m03 * multMatrix.m31; + float f2 = this.m00 * multMatrix.m02 + this.m01 * multMatrix.m12 + this.m02 * multMatrix.m22 + this.m03 * multMatrix.m32; + float f3 = this.m00 * multMatrix.m03 + this.m01 * multMatrix.m13 + this.m02 * multMatrix.m23 + this.m03 * multMatrix.m33; + float f4 = this.m10 * multMatrix.m00 + this.m11 * multMatrix.m10 + this.m12 * multMatrix.m20 + this.m13 * multMatrix.m30; + float f5 = this.m10 * multMatrix.m01 + this.m11 * multMatrix.m11 + this.m12 * multMatrix.m21 + this.m13 * multMatrix.m31; + float f6 = this.m10 * multMatrix.m02 + this.m11 * multMatrix.m12 + this.m12 * multMatrix.m22 + this.m13 * multMatrix.m32; + float f7 = this.m10 * multMatrix.m03 + this.m11 * multMatrix.m13 + this.m12 * multMatrix.m23 + this.m13 * multMatrix.m33; + float f8 = this.m20 * multMatrix.m00 + this.m21 * multMatrix.m10 + this.m22 * multMatrix.m20 + this.m23 * multMatrix.m30; + float f9 = this.m20 * multMatrix.m01 + this.m21 * multMatrix.m11 + this.m22 * multMatrix.m21 + this.m23 * multMatrix.m31; + float f10 = this.m20 * multMatrix.m02 + this.m21 * multMatrix.m12 + this.m22 * multMatrix.m22 + this.m23 * multMatrix.m32; + float f11 = this.m20 * multMatrix.m03 + this.m21 * multMatrix.m13 + this.m22 * multMatrix.m23 + this.m23 * multMatrix.m33; + float f12 = this.m30 * multMatrix.m00 + this.m31 * multMatrix.m10 + this.m32 * multMatrix.m20 + this.m33 * multMatrix.m30; + float f13 = this.m30 * multMatrix.m01 + this.m31 * multMatrix.m11 + this.m32 * multMatrix.m21 + this.m33 * multMatrix.m31; + float f14 = this.m30 * multMatrix.m02 + this.m31 * multMatrix.m12 + this.m32 * multMatrix.m22 + this.m33 * multMatrix.m32; + float f15 = this.m30 * multMatrix.m03 + this.m31 * multMatrix.m13 + this.m32 * multMatrix.m23 + this.m33 * multMatrix.m33; + this.m00 = f; + this.m01 = f1; + this.m02 = f2; + this.m03 = f3; + this.m10 = f4; + this.m11 = f5; + this.m12 = f6; + this.m13 = f7; + this.m20 = f8; + this.m21 = f9; + this.m22 = f10; + this.m23 = f11; + this.m30 = f12; + this.m31 = f13; + this.m32 = f14; + this.m33 = f15; + } + + /* Quaternions aren't currently needed/implemented + public void multiply(Quaternion p_226596_1_) + { + this.multiply(new Matrix4f(p_226596_1_)); + } + */ + + public void multiply(float scalar) + { + this.m00 *= scalar; + this.m01 *= scalar; + this.m02 *= scalar; + this.m03 *= scalar; + this.m10 *= scalar; + this.m11 *= scalar; + this.m12 *= scalar; + this.m13 *= scalar; + this.m20 *= scalar; + this.m21 *= scalar; + this.m22 *= scalar; + this.m23 *= scalar; + this.m30 *= scalar; + this.m31 *= scalar; + this.m32 *= scalar; + this.m33 *= scalar; + } + + public static Mat4f perspective(double fov, float widthHeightRatio, float nearClipPlane, float farClipPlane) + { + float f = (float) (1.0D / Math.tan(fov * ((float) Math.PI / 180F) / 2.0D)); + Mat4f matrix = new Mat4f(); + matrix.m00 = f / widthHeightRatio; + matrix.m11 = f; + matrix.m22 = (farClipPlane + nearClipPlane) / (nearClipPlane - farClipPlane); + matrix.m32 = -1.0F; + matrix.m23 = 2.0F * farClipPlane * nearClipPlane / (nearClipPlane - farClipPlane); + return matrix; + } + + + /* not currently needed/implemented + * Also the parameter names should be double checked as they may be incorrect + public static Matrix4Float orthographic(float left, float right, float top, float bottom) + { + Matrix4Float matrix4f = new Matrix4Float(); + matrix4f.m00 = 2.0F / left; + matrix4f.m11 = 2.0F / right; + float f = bottom - top; + matrix4f.m22 = -2.0F / f; + matrix4f.m33 = 1.0F; + matrix4f.m03 = -1.0F; + matrix4f.m13 = -1.0F; + matrix4f.m23 = -(bottom + top) / f; + return matrix4f; + } + */ + + /** + * TODO: what kind of translation is this? + * and how is this different from "multiplyTranslationMatrix"? + */ + public void translate(Vec3f vec) + { + this.m03 += vec.x; + this.m13 += vec.y; + this.m23 += vec.z; + } + + /** originally "translate" from Minecraft's MatrixStack */ + public void multiplyTranslationMatrix(double x, double y, double z) + { + multiply(createTranslateMatrix((float)x, (float)y, (float)z)); + } + + public Mat4f copy() + { + return new Mat4f(this); + } + + public static Mat4f createScaleMatrix(float x, float y, float z) + { + Mat4f matrix = new Mat4f(); + matrix.m00 = x; + matrix.m11 = y; + matrix.m22 = z; + matrix.m33 = 1.0F; + return matrix; + } + + public static Mat4f createTranslateMatrix(float x, float y, float z) + { + Mat4f matrix = new Mat4f(); + matrix.m00 = 1.0F; + matrix.m11 = 1.0F; + matrix.m22 = 1.0F; + matrix.m33 = 1.0F; + matrix.m03 = x; + matrix.m13 = y; + matrix.m23 = z; + return matrix; + } + + + // Forge start + public Mat4f(float[] values) + { + m00 = values[0]; + m01 = values[1]; + m02 = values[2]; + m03 = values[3]; + m10 = values[4]; + m11 = values[5]; + m12 = values[6]; + m13 = values[7]; + m20 = values[8]; + m21 = values[9]; + m22 = values[10]; + m23 = values[11]; + m30 = values[12]; + m31 = values[13]; + m32 = values[14]; + m33 = values[15]; + } + + public Mat4f(FloatBuffer buffer) + { + this(buffer.array()); + } + + public void set(Mat4f mat) + { + this.m00 = mat.m00; + this.m01 = mat.m01; + this.m02 = mat.m02; + this.m03 = mat.m03; + this.m10 = mat.m10; + this.m11 = mat.m11; + this.m12 = mat.m12; + this.m13 = mat.m13; + this.m20 = mat.m20; + this.m21 = mat.m21; + this.m22 = mat.m22; + this.m23 = mat.m23; + this.m30 = mat.m30; + this.m31 = mat.m31; + this.m32 = mat.m32; + this.m33 = mat.m33; + } + + public void add(Mat4f other) + { + m00 += other.m00; + m01 += other.m01; + m02 += other.m02; + m03 += other.m03; + m10 += other.m10; + m11 += other.m11; + m12 += other.m12; + m13 += other.m13; + m20 += other.m20; + m21 += other.m21; + m22 += other.m22; + m23 += other.m23; + m30 += other.m30; + m31 += other.m31; + m32 += other.m32; + m33 += other.m33; + } + + public void multiplyBackward(Mat4f other) + { + Mat4f copy = other.copy(); + copy.multiply(this); + this.set(copy); + } + + public void setTranslation(float x, float y, float z) + { + this.m00 = 1.0F; + this.m11 = 1.0F; + this.m22 = 1.0F; + this.m33 = 1.0F; + this.m03 = x; + this.m13 = y; + this.m23 = z; + } + + /** + * Changes the values that store the clipping planes. + * Formula for calculating matrix values is the same that OpenGL uses when making matrices. + * + * @param nearClip New near clipping plane value. + * @param farClip New far clipping plane value. + */ + public void setClipPlanes(float nearClip,float farClip) + { + //convert to matrix values, formula copied from a textbook / openGL specification. + float matNearClip = -((farClip + nearClip) / (farClip - nearClip)); + float matFarClip = -((2 * farClip * nearClip) / (farClip - nearClip)); + //set new values for the clip planes. + this.m22 = matNearClip; + this.m23 = matFarClip; + } +} diff --git a/src/main/java/com/seibel/lod/core/objects/math/Vec3d.java b/src/main/java/com/seibel/lod/core/objects/math/Vec3d.java new file mode 100644 index 000000000..c2ba8aa24 --- /dev/null +++ b/src/main/java/com/seibel/lod/core/objects/math/Vec3d.java @@ -0,0 +1,257 @@ +/* + * This file is part of the Distant Horizon mod (formerly the LOD Mod), + * licensed under the GNU GPL v3 License. + * + * Copyright (C) 2020 James Seibel + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.seibel.lod.core.objects.math; + +import com.seibel.lod.core.util.LodUtil; + +/** + * This is closer to MC's implementation of a + * 3 element float vector than a 3 element double + * vector. Hopefully that shouldn't cause any issues. + * + * @author James Seibel + * @version 11-18-2021 + */ +public class Vec3d +{ + public static Vec3d XNeg = new Vec3d(-1.0F, 0.0F, 0.0F); + public static Vec3d XPos = new Vec3d(1.0F, 0.0F, 0.0F); + public static Vec3d YNeg = new Vec3d(0.0F, -1.0F, 0.0F); + public static Vec3d YPos = new Vec3d(0.0F, 1.0F, 0.0F); + public static Vec3d ZNeg = new Vec3d(0.0F, 0.0F, -1.0F); + public static Vec3d ZPos = new Vec3d(0.0F, 0.0F, 1.0F); + + public static final Vec3d ZERO_VECTOR = new Vec3d(0.0D, 0.0D, 0.0D); + + public double x; + public double y; + public double z; + + + + public Vec3d() + { + + } + + public Vec3d(double x, double y, double z) + { + this.x = x; + this.y = y; + this.z = z; + } + + @Override + public boolean equals(Object obj) + { + if (this == obj) + { + return true; + } + else if (obj != null && this.getClass() == obj.getClass()) + { + Vec3d Vec3f = (Vec3d) obj; + if (Double.compare(Vec3f.x, this.x) != 0) + { + return false; + } + else if (Double.compare(Vec3f.y, this.y) != 0) + { + return false; + } + else + { + return Double.compare(Vec3f.z, this.z) == 0; + } + } + else + { + return false; + } + } + + @Override + public int hashCode() + { + long longVal = Double.doubleToLongBits(this.x); + + int intVal = (int) (longVal ^ longVal >>> 32); + longVal = Double.doubleToLongBits(this.y); + intVal = 31 * intVal + (int) (longVal ^ longVal >>> 32); + longVal = Double.doubleToLongBits(this.z); + + return 31 * intVal + (int) (longVal ^ longVal >>> 32); + } + + public void mul(double scalar) + { + this.x *= scalar; + this.y *= scalar; + this.z *= scalar; + } + + public void mul(double x, double y, double z) + { + this.x *= x; + this.y *= y; + this.z *= z; + } + + public void clamp(double min, double max) + { + this.x = LodUtil.clamp(min, this.x, max); + this.y = LodUtil.clamp(min, this.y, max); + this.z = LodUtil.clamp(min, this.z, max); + } + + public void set(double x, double y, double z) + { + this.x = x; + this.y = y; + this.z = z; + } + + public void add(double x, double y, double z) + { + this.x += x; + this.y += y; + this.z += z; + } + + public void add(Vec3d vector) + { + this.x += vector.x; + this.y += vector.y; + this.z += vector.z; + } + + public void subtract(Vec3d vector) + { + this.x -= vector.x; + this.y -= vector.y; + this.z -= vector.z; + } + + public double dotProduct(Vec3d vector) + { + return this.x * vector.x + this.y * vector.y + this.z * vector.z; + } + + public Vec3d normalize() + { + double value = Math.sqrt(this.x * this.x + this.y * this.y + this.z * this.z); + return value < 1.0E-4D ? ZERO_VECTOR : new Vec3d(this.x / value, this.y / value, this.z / value); + } + + public void crossProduct(Vec3d vector) + { + double f = this.x; + double f1 = this.y; + double f2 = this.z; + double f3 = vector.x; + double f4 = vector.y; + double f5 = vector.z; + this.x = f1 * f5 - f2 * f4; + this.y = f2 * f3 - f * f5; + this.z = f * f4 - f1 * f3; + } + + /* Matrix3f is not currently needed/implemented + public void transform(Matrix3f p_229188_1_) + { + double f = this.x; + double f1 = this.y; + double f2 = this.z; + this.x = p_229188_1_.m00 * f + p_229188_1_.m01 * f1 + p_229188_1_.m02 * f2; + this.y = p_229188_1_.m10 * f + p_229188_1_.m11 * f1 + p_229188_1_.m12 * f2; + this.z = p_229188_1_.m20 * f + p_229188_1_.m21 * f1 + p_229188_1_.m22 * f2; + } + */ + + /* Quaternions are not currently needed/implemented + public void transform(Quaternion p_214905_1_) + { + Quaternion quaternion = new Quaternion(p_214905_1_); + quaternion.mul(new Quaternion(this.x(), this.y(), this.z(), 0.0F)); + Quaternion quaternion1 = new Quaternion(p_214905_1_); + quaternion1.conj(); + quaternion.mul(quaternion1); + this.set(quaternion.i(), quaternion.j(), quaternion.k()); + } + */ + + /* not currently needed + * percent may actually be partial ticks (which is available when rendering) + public void linearInterp(Vec3f resultingVector, double percent) + { + double f = 1.0F - percent; + this.x = this.x * f + resultingVector.x * percent; + this.y = this.y * f + resultingVector.y * percent; + this.z = this.z * f + resultingVector.z * percent; + } + */ + + /* Quaternions are not currently needed/implemented + public Quaternion rotation(double p_229193_1_) + { + return new Quaternion(this, p_229193_1_, false); + } + + + @OnlyIn(Dist.CLIENT) + public Quaternion rotationDegrees(double p_229187_1_) + { + return new Quaternion(this, p_229187_1_, true); + } + */ + + public Vec3d copy() + { + return new Vec3d(this.x, this.y, this.z); + } + + /* not currently needed/implemented + public void map(double2doubleFunction p_229191_1_) + { + this.x = p_229191_1_.get(this.x); + this.y = p_229191_1_.get(this.y); + this.z = p_229191_1_.get(this.z); + } + */ + + @Override + public String toString() + { + return "[" + this.x + ", " + this.y + ", " + this.z + "]"; + } + + // Forge start + public Vec3d(double[] values) + { + set(values); + } + + public void set(double[] values) + { + this.x = values[0]; + this.y = values[1]; + this.z = values[2]; + } +} diff --git a/src/main/java/com/seibel/lod/core/objects/math/Vec3f.java b/src/main/java/com/seibel/lod/core/objects/math/Vec3f.java new file mode 100644 index 000000000..fe25d8889 --- /dev/null +++ b/src/main/java/com/seibel/lod/core/objects/math/Vec3f.java @@ -0,0 +1,261 @@ +/* + * This file is part of the Distant Horizon mod (formerly the LOD Mod), + * licensed under the GNU GPL v3 License. + * + * Copyright (C) 2020 James Seibel + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.seibel.lod.core.objects.math; + +import com.seibel.lod.core.util.LodUtil; + +/** + * A (almost) exact copy of Minecraft's 1.16.5 + * implementation of a 3 element float vector. + * + * @author James Seibel + * @version 11-11-2021 + */ +public class Vec3f +{ + public static Vec3f XNeg = new Vec3f(-1.0F, 0.0F, 0.0F); + public static Vec3f XPos = new Vec3f(1.0F, 0.0F, 0.0F); + public static Vec3f YNeg = new Vec3f(0.0F, -1.0F, 0.0F); + public static Vec3f YPos = new Vec3f(0.0F, 1.0F, 0.0F); + public static Vec3f ZNeg = new Vec3f(0.0F, 0.0F, -1.0F); + public static Vec3f ZPos = new Vec3f(0.0F, 0.0F, 1.0F); + + + public float x; + public float y; + public float z; + + + + public Vec3f() + { + + } + + public Vec3f(float x, float y, float z) + { + this.x = x; + this.y = y; + this.z = z; + } + + @Override + public boolean equals(Object obj) + { + if (this == obj) + { + return true; + } + else if (obj != null && this.getClass() == obj.getClass()) + { + Vec3f Vec3f = (Vec3f) obj; + if (Float.compare(Vec3f.x, this.x) != 0) + { + return false; + } + else if (Float.compare(Vec3f.y, this.y) != 0) + { + return false; + } + else + { + return Float.compare(Vec3f.z, this.z) == 0; + } + } + else + { + return false; + } + } + + @Override + public int hashCode() + { + int i = Float.floatToIntBits(this.x); + i = 31 * i + Float.floatToIntBits(this.y); + return 31 * i + Float.floatToIntBits(this.z); + } + + public void mul(float scalar) + { + this.x *= scalar; + this.y *= scalar; + this.z *= scalar; + } + + public void mul(float x, float y, float z) + { + this.x *= x; + this.y *= y; + this.z *= z; + } + + public void clamp(float min, float max) + { + this.x = LodUtil.clamp(min, this.x, max); + this.y = LodUtil.clamp(min, this.y, max); + this.z = LodUtil.clamp(min, this.z, max); + } + + public void set(float x, float y, float z) + { + this.x = x; + this.y = y; + this.z = z; + } + + public void add(float x, float y, float z) + { + this.x += x; + this.y += y; + this.z += z; + } + + public void add(Vec3f vector) + { + this.x += vector.x; + this.y += vector.y; + this.z += vector.z; + } + + public void subtract(Vec3f vector) + { + this.x -= vector.x; + this.y -= vector.y; + this.z -= vector.z; + } + + public float dotProduct(Vec3f vector) + { + return this.x * vector.x + this.y * vector.y + this.z * vector.z; + } + + public boolean normalize() + { + float squaredSum = this.x * this.x + this.y * this.y + this.z * this.z; + if (squaredSum < 1.0E-5D) + { + return false; + } + else + { + float f1 = LodUtil.fastInvSqrt(squaredSum); + this.x *= f1; + this.y *= f1; + this.z *= f1; + return true; + } + } + + public void crossProduct(Vec3f vector) + { + float f = this.x; + float f1 = this.y; + float f2 = this.z; + float f3 = vector.x; + float f4 = vector.y; + float f5 = vector.z; + this.x = f1 * f5 - f2 * f4; + this.y = f2 * f3 - f * f5; + this.z = f * f4 - f1 * f3; + } + + /* Matrix3f is not currently needed/implemented + public void transform(Matrix3f p_229188_1_) + { + float f = this.x; + float f1 = this.y; + float f2 = this.z; + this.x = p_229188_1_.m00 * f + p_229188_1_.m01 * f1 + p_229188_1_.m02 * f2; + this.y = p_229188_1_.m10 * f + p_229188_1_.m11 * f1 + p_229188_1_.m12 * f2; + this.z = p_229188_1_.m20 * f + p_229188_1_.m21 * f1 + p_229188_1_.m22 * f2; + } + */ + + /* Quaternions are not currently needed/implemented + public void transform(Quaternion p_214905_1_) + { + Quaternion quaternion = new Quaternion(p_214905_1_); + quaternion.mul(new Quaternion(this.x(), this.y(), this.z(), 0.0F)); + Quaternion quaternion1 = new Quaternion(p_214905_1_); + quaternion1.conj(); + quaternion.mul(quaternion1); + this.set(quaternion.i(), quaternion.j(), quaternion.k()); + } + */ + + /* not currently needed + * percent may actually be partial ticks (which is available when rendering) + public void linearInterp(Vec3f resultingVector, float percent) + { + float f = 1.0F - percent; + this.x = this.x * f + resultingVector.x * percent; + this.y = this.y * f + resultingVector.y * percent; + this.z = this.z * f + resultingVector.z * percent; + } + */ + + /* Quaternions are not currently needed/implemented + public Quaternion rotation(float p_229193_1_) + { + return new Quaternion(this, p_229193_1_, false); + } + + + @OnlyIn(Dist.CLIENT) + public Quaternion rotationDegrees(float p_229187_1_) + { + return new Quaternion(this, p_229187_1_, true); + } + */ + + public Vec3f copy() + { + return new Vec3f(this.x, this.y, this.z); + } + + /* not currently needed/implemented + public void map(Float2FloatFunction p_229191_1_) + { + this.x = p_229191_1_.get(this.x); + this.y = p_229191_1_.get(this.y); + this.z = p_229191_1_.get(this.z); + } + */ + + @Override + public String toString() + { + return "[" + this.x + ", " + this.y + ", " + this.z + "]"; + } + + // Forge start + public Vec3f(float[] values) + { + set(values); + } + + public void set(float[] values) + { + this.x = values[0]; + this.y = values[1]; + this.z = values[2]; + } +} diff --git a/src/main/java/com/seibel/lod/core/objects/math/Vec3i.java b/src/main/java/com/seibel/lod/core/objects/math/Vec3i.java new file mode 100644 index 000000000..a65a404f0 --- /dev/null +++ b/src/main/java/com/seibel/lod/core/objects/math/Vec3i.java @@ -0,0 +1,203 @@ +/* + * This file is part of the Distant Horizon mod (formerly the LOD Mod), + * licensed under the GNU GPL v3 License. + * + * Copyright (C) 2020 James Seibel + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.seibel.lod.core.objects.math; + +import com.seibel.lod.core.util.LodUtil; + +/** + * A (almost) exact copy of Minecraft's 1.16.5 + * implementation of a 3 element integer vector. + * + * @author James Seibel + * @version 11-11-2021 + */ +public class Vec3i +{ + public static Vec3i XNeg = new Vec3i(-1, 0, 0); + public static Vec3i XPos = new Vec3i(1, 0, 0); + public static Vec3i YNeg = new Vec3i(0, -1, 0); + public static Vec3i YPos = new Vec3i(0, 1, 0); + public static Vec3i ZNeg = new Vec3i(0, 0, -1); + public static Vec3i ZPos = new Vec3i(0, 0, 1); + + + public int x; + public int y; + public int z; + + + + public Vec3i() + { + + } + + public Vec3i(int x, int y, int z) + { + this.x = x; + this.y = y; + this.z = z; + } + + @Override + public boolean equals(Object obj) + { + if (this == obj) + { + return true; + } + else if (obj != null && this.getClass() == obj.getClass()) + { + Vec3i Vec3f = (Vec3i) obj; + if (Float.compare(Vec3f.x, this.x) != 0) + { + return false; + } + else if (Float.compare(Vec3f.y, this.y) != 0) + { + return false; + } + else + { + return Float.compare(Vec3f.z, this.z) == 0; + } + } + else + { + return false; + } + } + + @Override + public int hashCode() + { + int i = Float.floatToIntBits(this.x); + i = 31 * i + Float.floatToIntBits(this.y); + return 31 * i + Float.floatToIntBits(this.z); + } + + public void mul(float scalar) + { + this.x *= scalar; + this.y *= scalar; + this.z *= scalar; + } + + public void mul(float x, float y, float z) + { + this.x *= x; + this.y *= y; + this.z *= z; + } + + public void clamp(int min, int max) + { + this.x = LodUtil.clamp(min, this.x, max); + this.y = LodUtil.clamp(min, this.y, max); + this.z = LodUtil.clamp(min, this.z, max); + } + + public void set(int x, int y, int z) + { + this.x = x; + this.y = y; + this.z = z; + } + + public void add(int x, int y, int z) + { + this.x += x; + this.y += y; + this.z += z; + } + + public void add(Vec3i vector) + { + this.x += vector.x; + this.y += vector.y; + this.z += vector.z; + } + + public void subtract(Vec3i vector) + { + this.x -= vector.x; + this.y -= vector.y; + this.z -= vector.z; + } + + public double distSqr(double x, double y, double z, boolean centerOfBlock) + { + double offset = centerOfBlock ? 0.5 : 0.0; + double xAdd = this.x + offset - x; + double yAdd = this.y + offset - y; + double zAdd = this.z + offset - z; + return (xAdd * xAdd) + (yAdd * yAdd) + (zAdd * zAdd); + } + + public int distManhattan(Vec3i otherVec) + { + float xSub = Math.abs(otherVec.x - this.x); + float ySub = Math.abs(otherVec.y - this.y); + float zSub = Math.abs(otherVec.z - this.z); + return (int) (xSub + ySub + zSub); + } + + /** inner product */ + public float dotProduct(Vec3i vector) + { + return (this.x * vector.x) + (this.y * vector.y) + (this.z * vector.z); + } + + /** Cross product */ + public Vec3i cross(Vec3i otherVec) + { + return new Vec3i( + (this.y * otherVec.z) - (this.z * otherVec.y), + (this.z * otherVec.x) - (this.x * otherVec.z), + (this.x * otherVec.y) - (this.y * otherVec.x)); + } + + public Vec3i copy() + { + return new Vec3i(this.x, this.y, this.z); + } + + + + @Override + public String toString() + { + return "[" + this.x + ", " + this.y + ", " + this.z + "]"; + } + + + // Forge start + public Vec3i(int[] values) + { + set(values); + } + + public void set(int[] values) + { + this.x = values[0]; + this.y = values[1]; + this.z = values[2]; + } +} diff --git a/src/main/java/com/seibel/lod/core/objects/opengl/DefaultLodVertexFormats.java b/src/main/java/com/seibel/lod/core/objects/opengl/DefaultLodVertexFormats.java new file mode 100644 index 000000000..116ed0f0c --- /dev/null +++ b/src/main/java/com/seibel/lod/core/objects/opengl/DefaultLodVertexFormats.java @@ -0,0 +1,48 @@ +/* + * This file is part of the Distant Horizon mod (formerly the LOD Mod), + * licensed under the GNU GPL v3 License. + * + * Copyright (C) 2020 James Seibel + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.seibel.lod.core.objects.opengl; + +import com.google.common.collect.ImmutableList; + +/** + * A (almost) exact copy of MC's + * DefaultVertexFormats class. + * + * @author James Seibel + * @version 11-13-2021 + */ +public class DefaultLodVertexFormats +{ + public static final LodVertexFormatElement ELEMENT_POSITION = new LodVertexFormatElement(0, LodVertexFormatElement.DataType.FLOAT, 3); + public static final LodVertexFormatElement ELEMENT_COLOR = new LodVertexFormatElement(0, LodVertexFormatElement.DataType.UBYTE, 4); + public static final LodVertexFormatElement ELEMENT_UV = new LodVertexFormatElement(0, LodVertexFormatElement.DataType.FLOAT, 2); + public static final LodVertexFormatElement ELEMENT_LIGHT_MAP_UV = new LodVertexFormatElement(1, LodVertexFormatElement.DataType.SHORT, 2); + public static final LodVertexFormatElement ELEMENT_NORMAL = new LodVertexFormatElement(0, LodVertexFormatElement.DataType.BYTE, 3); + public static final LodVertexFormatElement ELEMENT_PADDING = new LodVertexFormatElement(0, LodVertexFormatElement.DataType.BYTE, 1); + + + public static final LodVertexFormat POSITION = new LodVertexFormat(ImmutableList.builder().add(ELEMENT_POSITION).build()); + public static final LodVertexFormat POSITION_COLOR = new LodVertexFormat(ImmutableList.builder().add(ELEMENT_POSITION).add(ELEMENT_COLOR).build()); + public static final LodVertexFormat POSITION_COLOR_LIGHTMAP = new LodVertexFormat(ImmutableList.builder().add(ELEMENT_POSITION).add(ELEMENT_COLOR).add(ELEMENT_LIGHT_MAP_UV).build()); + public static final LodVertexFormat POSITION_TEX = new LodVertexFormat(ImmutableList.builder().add(ELEMENT_POSITION).add(ELEMENT_UV).build()); + public static final LodVertexFormat POSITION_COLOR_TEX = new LodVertexFormat(ImmutableList.builder().add(ELEMENT_POSITION).add(ELEMENT_COLOR).add(ELEMENT_UV).build()); + public static final LodVertexFormat POSITION_COLOR_TEX_LIGHTMAP = new LodVertexFormat(ImmutableList.builder().add(ELEMENT_POSITION).add(ELEMENT_COLOR).add(ELEMENT_UV).add(ELEMENT_LIGHT_MAP_UV).build()); + +} diff --git a/src/main/java/com/seibel/lod/core/objects/opengl/LodBufferBuilder.java b/src/main/java/com/seibel/lod/core/objects/opengl/LodBufferBuilder.java new file mode 100644 index 000000000..29d764092 --- /dev/null +++ b/src/main/java/com/seibel/lod/core/objects/opengl/LodBufferBuilder.java @@ -0,0 +1,543 @@ +/* + * This file is part of the Distant Horizon mod (formerly the LOD Mod), + * licensed under the GNU GPL v3 License. + * + * Copyright (C) 2020 James Seibel + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.seibel.lod.core.objects.opengl; + +import java.nio.Buffer; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.FloatBuffer; +import java.util.List; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Lists; + +/** + * A (almost) exact copy of Minecraft's + * BufferBuilder object.
+ * Which allows for creating and filling + * OpenGL buffers. + * + * @author James Seibel + * @version 11-13-2021 + */ +public class LodBufferBuilder +{ + private static final Logger LOGGER = LogManager.getLogger(); + public ByteBuffer buffer; + + private final List vertexCounts = Lists.newArrayList(); + private int lastRenderedCountIndex = 0; + private int totalRenderedBytes = 0; + private int nextElementByte = 0; + private int totalUploadedBytes = 0; + private int vertices; + private LodVertexFormatElement currentElement; + private int elementIndex; + private int mode; + private LodVertexFormat format; + private boolean building; + + + + + public LodBufferBuilder(int bufferSizeInBytes) + { + this.buffer = allocateByteBuffer(bufferSizeInBytes * 4); + } + + + + /** originally from MC's GLAllocation class */ + private ByteBuffer allocateByteBuffer(int bufferSizeInBytes) + { + return ByteBuffer.allocateDirect(bufferSizeInBytes).order(ByteOrder.nativeOrder()); + } + /** originally from MC's GLAllocation class */ + @SuppressWarnings("unused") + private FloatBuffer allocateFloatBuffer(int bufferSizeInBytes) + { + return allocateByteBuffer(bufferSizeInBytes).asFloatBuffer(); + } + + + + /** make sure the buffer doesn't overflow when inserting new elements */ + private void ensureVertexCapacity() + { + this.ensureCapacity(this.format.getVertexSize()); + } + private void ensureCapacity(int vertexSizeInBytes) + { + if (this.nextElementByte + vertexSizeInBytes > this.buffer.capacity()) + { + int i = this.buffer.capacity(); + int j = i + roundUp(vertexSizeInBytes); + //LOGGER.debug("Needed to grow BufferBuilder buffer: Old size {} bytes, new size {} bytes.", i, j); + ByteBuffer bytebuffer = allocateByteBuffer(j); + this.buffer.position(0); + bytebuffer.put(this.buffer); + bytebuffer.rewind(); + this.buffer = bytebuffer; + } + } + private static int roundUp(int vertexSizeInBytes) + { + int i = 2097152; // 2 ^ 21 + if (vertexSizeInBytes == 0) + { + return i; + } + else + { + if (vertexSizeInBytes < 0) + { + i *= -1; + } + + int j = vertexSizeInBytes % i; + return j == 0 ? vertexSizeInBytes : vertexSizeInBytes + i - j; + } + } + + + /* not currently needed sortQuads() + // the x,y,z location is a blockPos + public void sortQuads(float x, float y, float z) + { + ((Buffer) this.buffer).clear(); + FloatBuffer floatbuffer = this.buffer.asFloatBuffer(); + int i = this.vertices / 4; + float[] afloat = new float[i]; + + for (int j = 0; j < i; ++j) + { + afloat[j] = getQuadDistanceFromPlayer(floatbuffer, x, y, z, this.format.getIntegerSize(), this.totalRenderedBytes / 4 + j * this.format.getVertexSize()); + } + + int[] aint = new int[i]; + + for (int k = 0; k < aint.length; aint[k] = k++) + { + } + + IntArrays.mergeSort(aint, (p_227830_1_, p_227830_2_) -> + { + return Floats.compare(afloat[p_227830_2_], afloat[p_227830_1_]); + }); + BitSet bitset = new BitSet(); + FloatBuffer floatbuffer1 = allocateFloatBuffer(this.format.getIntegerSize() * 4); + + for (int l = bitset.nextClearBit(0); l < aint.length; l = bitset.nextClearBit(l + 1)) + { + int i1 = aint[l]; + if (i1 != l) + { + this.limitToVertex(floatbuffer, i1); + ((Buffer) floatbuffer1).clear(); + floatbuffer1.put(floatbuffer); + int j1 = i1; + + for (int k1 = aint[i1]; j1 != l; k1 = aint[k1]) + { + this.limitToVertex(floatbuffer, k1); + FloatBuffer floatbuffer2 = floatbuffer.slice(); + this.limitToVertex(floatbuffer, j1); + floatbuffer.put(floatbuffer2); + bitset.set(j1); + j1 = k1; + } + + this.limitToVertex(floatbuffer, l); + ((Buffer) floatbuffer1).flip(); + floatbuffer.put(floatbuffer1); + } + + bitset.set(l); + } + } + + + private void limitToVertex(FloatBuffer p_227829_1_, int p_227829_2_) + { + int i = this.format.getIntegerSize() * 4; + ((Buffer) p_227829_1_).limit(this.totalRenderedBytes / 4 + (p_227829_2_ + 1) * i); + ((Buffer) p_227829_1_).position(this.totalRenderedBytes / 4 + p_227829_2_ * i); + } + */ + + /* not curerntly needed getState() + public LodBufferBuilder.State getState() + { + ((Buffer) this.buffer).limit(this.nextElementByte); + ((Buffer) this.buffer).position(this.totalRenderedBytes); + ByteBuffer bytebuffer = ByteBuffer.allocate(this.vertices * this.format.getVertexSize()); + bytebuffer.put(this.buffer); + ((Buffer) this.buffer).clear(); + return new LodBufferBuilder.State(bytebuffer, this.format); + } + */ + + /* not currently needed getQuadDistanceFromPlayer() + private static float getQuadDistanceFromPlayer(FloatBuffer floatBuffer, float x, float y, float z, int p_181665_4_, int p_181665_5_) + { + float f = floatBuffer.get(p_181665_5_ + p_181665_4_ * 0 + 0); + float f1 = floatBuffer.get(p_181665_5_ + p_181665_4_ * 0 + 1); + float f2 = floatBuffer.get(p_181665_5_ + p_181665_4_ * 0 + 2); + float f3 = floatBuffer.get(p_181665_5_ + p_181665_4_ * 1 + 0); + float f4 = floatBuffer.get(p_181665_5_ + p_181665_4_ * 1 + 1); + float f5 = floatBuffer.get(p_181665_5_ + p_181665_4_ * 1 + 2); + float f6 = floatBuffer.get(p_181665_5_ + p_181665_4_ * 2 + 0); + float f7 = floatBuffer.get(p_181665_5_ + p_181665_4_ * 2 + 1); + float f8 = floatBuffer.get(p_181665_5_ + p_181665_4_ * 2 + 2); + float f9 = floatBuffer.get(p_181665_5_ + p_181665_4_ * 3 + 0); + float f10 = floatBuffer.get(p_181665_5_ + p_181665_4_ * 3 + 1); + float f11 = floatBuffer.get(p_181665_5_ + p_181665_4_ * 3 + 2); + float f12 = (f + f3 + f6 + f9) * 0.25F - x; + float f13 = (f1 + f4 + f7 + f10) * 0.25F - y; + float f14 = (f2 + f5 + f8 + f11) * 0.25F - z; + return f12 * f12 + f13 * f13 + f14 * f14; + } + */ + + /* not currently needed restoreState() + public void restoreState(LodBufferBuilder.State bufferState) + { + ((Buffer) bufferState.data).clear(); + int i = bufferState.data.capacity(); + this.ensureCapacity(i); + ((Buffer) this.buffer).limit(this.buffer.capacity()); + ((Buffer) this.buffer).position(this.totalRenderedBytes); + this.buffer.put(bufferState.data); + ((Buffer) this.buffer).clear(); + LodVertexFormat LodVertexFormat = bufferState.format; + this.switchFormat(LodVertexFormat); + this.vertices = i / LodVertexFormat.getVertexSize(); + this.nextElementByte = this.totalRenderedBytes + this.vertices * LodVertexFormat.getVertexSize(); + } + */ + + + + + + private void switchFormat(LodVertexFormat newFormat) + { + format = newFormat; + } + + + + + //========================================// + // methods for actually building a buffer // + //========================================// + + /** + * @param openGlLodVertexFormat GL11.GL_QUADS, GL11.GL_TRIANGLES, etc. + * @param LodVertexFormat + */ + public void begin(int openGlLodVertexFormat, LodVertexFormat LodVertexFormat) + { + if (this.building) + { + throw new IllegalStateException("Already building!"); + } + else + { + this.building = true; + this.mode = openGlLodVertexFormat; + this.switchFormat(LodVertexFormat); + this.currentElement = LodVertexFormat.getElements().get(0); + this.elementIndex = 0; + this.buffer.clear(); + } + } + + public void end() + { + if (!this.building) + { + throw new IllegalStateException("Not building!"); + } + else + { + this.building = false; + this.vertexCounts.add(new LodBufferBuilder.DrawState(this.format, this.vertices, this.mode)); + this.totalRenderedBytes += this.vertices * this.format.getVertexSize(); + this.vertices = 0; + this.currentElement = null; + this.elementIndex = 0; + } + } + + public void putByte(int index, byte newByte) + { + this.buffer.put(this.nextElementByte + index, newByte); + } + + public void putShort(int index, short newShort) + { + this.buffer.putShort(this.nextElementByte + index, newShort); + } + + public void putFloat(int index, float newFloat) + { + this.buffer.putFloat(this.nextElementByte + index, newFloat); + } + + public void endVertex() + { + if (this.elementIndex != 0) + { + throw new IllegalStateException("Not filled all elements of the vertex"); + } + else + { + ++this.vertices; + this.ensureVertexCapacity(); + } + } + + public void nextElement() + { + ImmutableList immutablelist = this.format.getElements(); + this.elementIndex = (this.elementIndex + 1) % immutablelist.size(); + this.nextElementByte += this.currentElement.getByteSize(); + this.currentElement = immutablelist.get(this.elementIndex); +// if (LodVertexFormatelement.getUsage() == LodVertexFormatElement.Usage.PADDING) +// { +// this.nextElement(); +// } + +// if (this.defaultColorSet && this.currentElement.getUsage() == LodVertexFormatElement.Usage.COLOR) +// { +// color(this.defaultR, this.defaultG, this.defaultB, this.defaultA); +// } + + } + + public LodBufferBuilder color(int red, int green, int blue, int alpha) + { + LodVertexFormatElement LodVertexFormatelement = this.currentElement(); + if (LodVertexFormatelement.getType() != LodVertexFormatElement.DataType.UBYTE) + { + throw new IllegalStateException("Color must be stored as a UBYTE"); + } + else + { + this.putByte(0, (byte) red); + this.putByte(1, (byte) green); + this.putByte(2, (byte) blue); + this.putByte(3, (byte) alpha); + this.nextElement(); + return this; + } + } + + public LodBufferBuilder vertex(float x, float y, float z) + { + if (this.currentElement().getType() != LodVertexFormatElement.DataType.FLOAT) + { + throw new IllegalStateException("Position verticies must be stored as a FLOAT"); + } + else + { + this.putFloat(0, x); + this.putFloat(4, y); + this.putFloat(8, z); + this.nextElement(); + return this; + } + } + + + + + + + /* not currently needed fullVertex() + * TODO James isn't sure about these names + public void vertex(float blockPosX, float blockPosY, float blockPosZ, + float red, float green, float blue, float alpha, + float textureU, float textureV, + int p_225588_10_, int p_225588_11_, + float p_225588_12_, float p_225588_13_, float p_225588_14_) + { + if (this.defaultColorSet) + { + throw new IllegalStateException(); + } + else if (this.fastFormat) + { + this.putFloat(0, blockPosX); + this.putFloat(4, blockPosY); + this.putFloat(8, blockPosZ); + this.putByte(12, (byte) ((int) (red * 255.0F))); + this.putByte(13, (byte) ((int) (green * 255.0F))); + this.putByte(14, (byte) ((int) (blue * 255.0F))); + this.putByte(15, (byte) ((int) (alpha * 255.0F))); + this.putFloat(16, textureU); + this.putFloat(20, textureV); + int i; + if (this.fullFormat) + { + this.putShort(24, (short) (p_225588_10_ & '\uffff')); + this.putShort(26, (short) (p_225588_10_ >> 16 & '\uffff')); + i = 28; + } + else + { + i = 24; + } + + this.putShort(i + 0, (short) (p_225588_11_ & '\uffff')); + this.putShort(i + 2, (short) (p_225588_11_ >> 16 & '\uffff')); + this.putByte(i + 4, IVertexConsumer.normalIntValue(p_225588_12_)); + this.putByte(i + 5, IVertexConsumer.normalIntValue(p_225588_13_)); + this.putByte(i + 6, IVertexConsumer.normalIntValue(p_225588_14_)); + this.nextElementByte += i + 8; + this.endVertex(); + } + else + { + super.vertex(blockPosX, blockPosY, blockPosZ, red, green, blue, alpha, textureU, textureV, p_225588_10_, p_225588_11_, p_225588_12_, p_225588_13_, p_225588_14_); + } + } + */ + + /** + * James isn't sure what the difference between + * using this method and just directly getting the buffer would be. + * But this was what was being used before, so it will stay for now. + * + * If anyone figures out what is special about this, please replace this comment. + */ + public ByteBuffer getCleanedByteBuffer() + { + LodBufferBuilder.DrawState bufferbuilder$drawstate = this.vertexCounts.get(this.lastRenderedCountIndex++); + this.buffer.position(this.totalUploadedBytes); + this.totalUploadedBytes += bufferbuilder$drawstate.vertexCount() * bufferbuilder$drawstate.format().getVertexSize(); + this.buffer.limit(this.totalUploadedBytes); + if (this.lastRenderedCountIndex == this.vertexCounts.size() && this.vertices == 0) + { + this.clear(); + } + + ByteBuffer bytebuffer = this.buffer.slice(); + bytebuffer.order(this.buffer.order()); // FORGE: Fix incorrect byte order + this.buffer.clear(); + return bytebuffer; // the original method also returned bufferbuilder$drawstate + } + + + public void clear() + { + if (this.totalRenderedBytes != this.totalUploadedBytes) + { + LOGGER.warn("Bytes mismatch " + this.totalRenderedBytes + " " + this.totalUploadedBytes); + } + + this.discard(); + } + + public void discard() + { + this.totalRenderedBytes = 0; + this.totalUploadedBytes = 0; + this.nextElementByte = 0; + this.vertexCounts.clear(); + this.lastRenderedCountIndex = 0; + } + + public LodVertexFormatElement currentElement() + { + if (this.currentElement == null) + { + throw new IllegalStateException("BufferBuilder not started"); + } + else + { + return this.currentElement; + } + } + + public boolean building() + { + return this.building; + } + + + + + + //==================// + // internal classes // + //==================// + + + public static final class DrawState + { + private final LodVertexFormat format; + private final int vertexCount; + private final int mode; + + private DrawState(LodVertexFormat p_i225905_1_, int p_i225905_2_, int p_i225905_3_) + { + this.format = p_i225905_1_; + this.vertexCount = p_i225905_2_; + this.mode = p_i225905_3_; + } + + public LodVertexFormat format() + { + return this.format; + } + + public int vertexCount() + { + return this.vertexCount; + } + + public int mode() + { + return this.mode; + } + } + + + + // Forge added methods + public void putBulkData(ByteBuffer buffer) + { + ensureCapacity(buffer.limit() + this.format.getVertexSize()); + this.buffer.position(this.vertices * this.format.getVertexSize()); + this.buffer.put(buffer); + this.vertices += buffer.limit() / this.format.getVertexSize(); + this.nextElementByte += buffer.limit(); + } + + public LodVertexFormat getLodVertexFormat() + { + return this.format; + } +} \ No newline at end of file diff --git a/src/main/java/com/seibel/lod/core/objects/opengl/LodVertexBuffer.java b/src/main/java/com/seibel/lod/core/objects/opengl/LodVertexBuffer.java new file mode 100644 index 000000000..a933e84ba --- /dev/null +++ b/src/main/java/com/seibel/lod/core/objects/opengl/LodVertexBuffer.java @@ -0,0 +1,57 @@ +/* + * This file is part of the Distant Horizon mod (formerly the LOD Mod), + * licensed under the GNU GPL v3 License. + * + * Copyright (C) 2020 James Seibel + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.seibel.lod.core.objects.opengl; + +import org.lwjgl.opengl.GL15; + +import com.seibel.lod.core.enums.rendering.GLProxyContext; +import com.seibel.lod.core.render.GLProxy; + +/** + * This is a container for a OpenGL + * VBO (Vertex Buffer Object). + * + * @author James Seibel + * @version 11-20-2021 + */ +public class LodVertexBuffer implements AutoCloseable +{ + public int id; + public int vertexCount; + + public LodVertexBuffer() + { + if (GLProxy.getInstance().getGlContext() == GLProxyContext.NONE) + throw new IllegalStateException("Thread [" +Thread.currentThread().getName() + "] tried to create a [" + LodVertexBuffer.class.getSimpleName() + "] outside a OpenGL contex."); + + this.id = GL15.glGenBuffers(); + } + + + @Override + public void close() + { + if (this.id >= 0) + { + GLProxy.getInstance().recordOpenGlCall(() -> GL15.glDeleteBuffers(this.id)); + this.id = -1; + } + } +} \ No newline at end of file diff --git a/src/main/java/com/seibel/lod/core/objects/opengl/LodVertexFormat.java b/src/main/java/com/seibel/lod/core/objects/opengl/LodVertexFormat.java new file mode 100644 index 000000000..a35cee4e9 --- /dev/null +++ b/src/main/java/com/seibel/lod/core/objects/opengl/LodVertexFormat.java @@ -0,0 +1,186 @@ +/* + * This file is part of the Distant Horizon mod (formerly the LOD Mod), + * licensed under the GNU GPL v3 License. + * + * Copyright (C) 2020 James Seibel + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.seibel.lod.core.objects.opengl; + +import java.util.stream.Collectors; + +import com.google.common.collect.ImmutableList; + +import it.unimi.dsi.fastutil.ints.IntArrayList; +import it.unimi.dsi.fastutil.ints.IntList; + +/** + * This is used to represent a single vertex + * stored in GPU memory, + *

+ * A (almost) exact copy of Minecraft's + * VertexFormat class, several methods + * were commented out since we didn't need them. + * + * @author James Seibel + * @version 11-13-2021 + */ +public class LodVertexFormat +{ + private final ImmutableList elements; + private final IntList offsets = new IntArrayList(); + private final int vertexSize; + + public LodVertexFormat(ImmutableList elementList) + { + this.elements = elementList; + int i = 0; + + for (LodVertexFormatElement LodVertexFormatElement : elementList) + { + this.offsets.add(i); + i += LodVertexFormatElement.getByteSize(); + } + + this.vertexSize = i; + } + + public int getIntegerSize() + { + return this.getVertexSize() / 4; + } + + public int getVertexSize() + { + return this.vertexSize; + } + + public ImmutableList getElements() + { + return this.elements; + } + + + // Forge added method + public int getOffset(int index) + { + return offsets.getInt(index); + } + + + + @Override + public String toString() + { + return "format: " + this.elements.size() + " elements: " + this.elements.stream().map(Object::toString).collect(Collectors.joining(" ")); + } + + + @Override + public boolean equals(Object obj) + { + if (this == obj) + { + return true; + } + else if (obj != null && this.getClass() == obj.getClass()) + { + LodVertexFormat vertexformat = (LodVertexFormat) obj; + return this.vertexSize == vertexformat.vertexSize && this.elements.equals(vertexformat.elements); + } + else + { + return false; + } + } + + @Override + public int hashCode() + { + return this.elements.hashCode(); + } + + + + + + + + /* not currently needed setupBufferState() + public void setupBufferState(long p_227892_1_) + { + if (!RenderSystem.isOnRenderThread()) + { + RenderSystem.recordRenderCall(() -> + { + this.setupBufferState(p_227892_1_); + }); + } + else + { + int i = this.getVertexSize(); + List list = this.getElements(); + + for (int j = 0; j < list.size(); ++j) + { + list.get(j).setupBufferState(p_227892_1_ + this.offsets.getInt(j), i); + } + + } + } + */ + + /* not currently needed clearBufferState() + public void clearBufferState() + { + if (!RenderSystem.isOnRenderThread()) + { + RenderSystem.recordRenderCall(this::clearBufferState); + } + else + { + for (LodVertexFormatElement LodVertexFormatElement : this.getElements()) + { + LodVertexFormatElement.clearBufferState(); + } + + } + } + */ + + + /* not currently needed has-Position/Normal/Color/UV + public boolean hasPosition() + { + return elements.stream().anyMatch(e -> e.getUsage() == LodVertexFormatElement.Usage.POSITION); + } + + public boolean hasNormal() + { + return elements.stream().anyMatch(e -> e.getUsage() == LodVertexFormatElement.Usage.NORMAL); + } + + public boolean hasColor() + { + return elements.stream().anyMatch(e -> e.getUsage() == LodVertexFormatElement.Usage.COLOR); + } + + public boolean hasUV(int which) + { + return elements.stream().anyMatch(e -> e.getUsage() == LodVertexFormatElement.Usage.UV && e.getIndex() == which); + } + */ + +} \ No newline at end of file diff --git a/src/main/java/com/seibel/lod/core/objects/opengl/LodVertexFormatElement.java b/src/main/java/com/seibel/lod/core/objects/opengl/LodVertexFormatElement.java new file mode 100644 index 000000000..a85ca82cf --- /dev/null +++ b/src/main/java/com/seibel/lod/core/objects/opengl/LodVertexFormatElement.java @@ -0,0 +1,161 @@ +/* + * This file is part of the Distant Horizon mod (formerly the LOD Mod), + * licensed under the GNU GPL v3 License. + * + * Copyright (C) 2020 James Seibel + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.seibel.lod.core.objects.opengl; + +import org.lwjgl.opengl.GL11; + +/** + * This object is used to build LodVertexFormats. + *

+ * A (almost) exact copy of Minecraft's + * VertexFormatElement class.
+ * A number of things were removed from the original + * object since we didn't need them, specifically "usage". + * + * @author James Seibel + * @version 11-13-2021 + */ +public class LodVertexFormatElement +{ + private final LodVertexFormatElement.DataType dataType; + /** James isn't sure what index is for */ + private final int index; + private final int count; + private final int byteSize; + + public LodVertexFormatElement(int newIndex, LodVertexFormatElement.DataType newType, int newCount) + { + this.dataType = newType; + this.index = newIndex; + this.count = newCount; + this.byteSize = newType.getSize() * this.count; + } + + public final LodVertexFormatElement.DataType getType() + { + return this.dataType; + } + + public final int getIndex() + { + return this.index; + } + + public final int getByteSize() + { + return this.byteSize; + } + + // added by Forge + public int getElementCount() + { + return count; + } + + + + public enum DataType + { + FLOAT(4, "Float", GL11.GL_FLOAT), + UBYTE(1, "Unsigned Byte", GL11.GL_UNSIGNED_BYTE), + BYTE(1, "Byte", GL11.GL_BYTE), + USHORT(2, "Unsigned Short", GL11.GL_UNSIGNED_SHORT), + SHORT(2, "Short", GL11.GL_SHORT), + UINT(4, "Unsigned Int", GL11.GL_UNSIGNED_INT), + INT(4, "Int", GL11.GL_INT); + + private final int size; + private final String name; + private final int glType; + + DataType(int sizeInBytes, String newName, int openGlDataType) + { + this.size = sizeInBytes; + this.name = newName; + this.glType = openGlDataType; + } + + public int getSize() + { + return this.size; + } + + public String getName() + { + return this.name; + } + + public int getGlType() + { + return this.glType; + } + } + + + + + @Override + public int hashCode() + { + int i = this.dataType.hashCode(); + i = 31 * i + this.index; + return 31 * i + this.count; + } + + @Override + public String toString() + { + return this.count + "," + this.dataType.getName(); + } + + @Override + public boolean equals(Object obj) + { + if (this == obj) + { + return true; + } + else if (obj != null && this.getClass() == obj.getClass()) + { + LodVertexFormatElement LodVertexFormatElement = (LodVertexFormatElement) obj; + if (this.count != LodVertexFormatElement.count) + { + return false; + } + else if (this.index != LodVertexFormatElement.index) + { + return false; + } + else if (this.dataType != LodVertexFormatElement.dataType) + { + return false; + } + else + { + return false; + } + } + else + { + return false; + } + } + +} \ No newline at end of file diff --git a/src/main/java/com/seibel/lod/core/objects/rending/LodFogConfig.java b/src/main/java/com/seibel/lod/core/objects/rending/LodFogConfig.java new file mode 100644 index 000000000..0fcdba0db --- /dev/null +++ b/src/main/java/com/seibel/lod/core/objects/rending/LodFogConfig.java @@ -0,0 +1,43 @@ +/* + * This file is part of the Distant Horizon mod (formerly the LOD Mod), + * licensed under the GNU GPL v3 License. + * + * Copyright (C) 2020 James Seibel + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.seibel.lod.core.objects.rending; + +import com.seibel.lod.core.enums.rendering.FogDistance; +import com.seibel.lod.core.enums.rendering.FogDrawMode; + +/** + * This object is just a replacement for an array + * to make things easier to understand in the LodRenderer. + * + * @author James Seibel + * @version 11-26-2021 + */ +public class LodFogConfig +{ + public FogDrawMode fogDrawMode; + public FogDistance fogDistance; + + + public float nearFogStart = 0; + public float nearFogEnd = 0; + + public float farFogStart = 0; + public float farFogEnd = 0; +} diff --git a/src/main/java/com/seibel/lod/core/render/GLProxy.java b/src/main/java/com/seibel/lod/core/render/GLProxy.java new file mode 100644 index 000000000..3c3109e22 --- /dev/null +++ b/src/main/java/com/seibel/lod/core/render/GLProxy.java @@ -0,0 +1,434 @@ +/* + * This file is part of the Distant Horizon mod (formerly the LOD Mod), + * licensed under the GNU GPL v3 License. + * + * Copyright (C) 2020 James Seibel + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.seibel.lod.core.render; + +import java.io.File; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +import org.lwjgl.glfw.GLFW; +import org.lwjgl.opengl.GL; +import org.lwjgl.opengl.GL11; +import org.lwjgl.opengl.GL15; +import org.lwjgl.opengl.GL20; +import org.lwjgl.opengl.GL30; +import org.lwjgl.opengl.GLCapabilities; + +import com.google.common.util.concurrent.ThreadFactoryBuilder; +import com.seibel.lod.core.ModInfo; +import com.seibel.lod.core.api.ClientApi; +import com.seibel.lod.core.enums.config.GpuUploadMethod; +import com.seibel.lod.core.enums.rendering.GLProxyContext; +import com.seibel.lod.core.render.shader.LodShader; +import com.seibel.lod.core.render.shader.LodShaderProgram; +import com.seibel.lod.core.util.SingletonHandler; +import com.seibel.lod.core.wrapperInterfaces.config.ILodConfigWrapperSingleton; +import com.seibel.lod.core.wrapperInterfaces.minecraft.IMinecraftWrapper; + +/** + * A singleton that holds references to different openGL contexts + * and GPU capabilities. + * + *

+ * Helpful OpenGL resources: + *

+ * https://www.seas.upenn.edu/~pcozzi/OpenGLInsights/OpenGLInsights-AsynchronousBufferTransfers.pdf
+ * https://learnopengl.com/Advanced-OpenGL/Advanced-Data
+ * https://www.slideshare.net/CassEveritt/approaching-zero-driver-overhead

+ * + * https://gamedev.stackexchange.com/questions/91995/edit-vbo-data-or-create-a-new-one
+ * https://stackoverflow.com/questions/63509735/massive-performance-loss-with-glmapbuffer

+ * + * @author James Seibel + * @version 12-1-2021 + */ +public class GLProxy +{ + private static final IMinecraftWrapper MC = SingletonHandler.get(IMinecraftWrapper.class); + private static final ILodConfigWrapperSingleton CONFIG = SingletonHandler.get(ILodConfigWrapperSingleton.class); + + private static final ExecutorService workerThread = Executors.newSingleThreadExecutor(new ThreadFactoryBuilder().setNameFormat(GLProxy.class.getSimpleName() + "-Worker-Thread").build()); + + + private static GLProxy instance = null; + + /** Minecraft's GLFW window */ + public final long minecraftGlContext; + /** Minecraft's GL capabilities */ + public final GLCapabilities minecraftGlCapabilities; + + /** the LodBuilder's GLFW window */ + public final long lodBuilderGlContext; + /** the LodBuilder's GL capabilities */ + public final GLCapabilities lodBuilderGlCapabilities; + + /** the proxyWorker's GLFW window */ + public final long proxyWorkerGlContext; + /** the proxyWorker's GL capabilities */ + public final GLCapabilities proxyWorkerGlCapabilities; + + + + /** This program contains all shaders required when rendering LODs */ + public LodShaderProgram lodShaderProgram; + /** This is the VAO that is used when rendering */ + public final int vertexArrayObjectId; + + + /** Requires OpenGL 4.5, and offers the best buffer uploading */ + public final boolean bufferStorageSupported; + + /** Requires OpenGL 3.0 */ + public final boolean mapBufferRangeSupported; + + + + + private GLProxy() + { + ClientApi.LOGGER.error("Creating " + GLProxy.class.getSimpleName() + "... If this is the last message you see in the log there must have been a OpenGL error."); + + // getting Minecraft's context has to be done on the render thread, + // where the GL context is + if (GLFW.glfwGetCurrentContext() == 0L) + throw new IllegalStateException(GLProxy.class.getSimpleName() + " was created outside the render thread!"); + + + + //============================// + // create the builder context // + //============================// + + // get Minecraft's context + minecraftGlContext = GLFW.glfwGetCurrentContext(); + minecraftGlCapabilities = GL.getCapabilities(); + + + // context creation setup + GLFW.glfwDefaultWindowHints(); + // make the context window invisible + GLFW.glfwWindowHint(GLFW.GLFW_VISIBLE, GLFW.GLFW_FALSE); + // by default the context should get the highest available OpenGL version + // but this can be explicitly set for testing +// GLFW.glfwWindowHint(GLFW.GLFW_CONTEXT_VERSION_MAJOR, 4); +// GLFW.glfwWindowHint(GLFW.GLFW_CONTEXT_VERSION_MINOR, 5); + + + // create the LodBuilder context + lodBuilderGlContext = GLFW.glfwCreateWindow(64, 48, "LOD Builder Window", 0L, minecraftGlContext); + GLFW.glfwMakeContextCurrent(lodBuilderGlContext); + lodBuilderGlCapabilities = GL.createCapabilities(); + + + // create the proxyWorker's context + proxyWorkerGlContext = GLFW.glfwCreateWindow(64, 48, "LOD proxy worker Window", 0L, minecraftGlContext); + GLFW.glfwMakeContextCurrent(proxyWorkerGlContext); + proxyWorkerGlCapabilities = GL.createCapabilities(); + + + + + + + //==================================// + // get any GPU related capabilities // + //==================================// + + setGlContext(GLProxyContext.LOD_BUILDER); + + ClientApi.LOGGER.info("Lod Render OpenGL version [" + GL11.glGetString(GL11.GL_VERSION) + "]."); + + // crash the game if the GPU doesn't support OpenGL 2.0 + if (!minecraftGlCapabilities.OpenGL20) + { + // Note: as of MC 1.17 this shouldn't happen since MC + // requires OpenGL 3.3, but just in case. + String errorMessage = ModInfo.READABLE_NAME + " was initializing " + GLProxy.class.getSimpleName() + " and discoverd this GPU doesn't support OpenGL 2.0 or greater."; + MC.crashMinecraft(errorMessage + " Sorry I couldn't tell you sooner :(", new UnsupportedOperationException("This GPU doesn't support OpenGL 2.0 or greater.")); + } + + + + // get specific capabilities + bufferStorageSupported = lodBuilderGlCapabilities.glBufferStorage != 0; + mapBufferRangeSupported = lodBuilderGlCapabilities.glMapBufferRange != 0; + + // display the capabilities + if (!bufferStorageSupported) + { + String fallBackVersion = mapBufferRangeSupported ? "3.0" : "1.5"; + ClientApi.LOGGER.error("This GPU doesn't support Buffer Storage (OpenGL 4.5), falling back to OpenGL " + fallBackVersion + ". This may cause stuttering and reduced performance."); + } + + + // if using AUTO gpuUpload + // determine a good default for the GPU + if (CONFIG.client().advanced().buffers().getGpuUploadMethod() == GpuUploadMethod.AUTO) + { + GpuUploadMethod uploadMethod; + String vendor = GL15.glGetString(GL15.GL_VENDOR).toUpperCase(); // example return: "NVIDIA CORPORATION" + if (vendor.contains("NVIDIA") || vendor.contains("GEFORCE")) + { + // NVIDIA card + + if (bufferStorageSupported) + { + uploadMethod = GpuUploadMethod.BUFFER_STORAGE; + } + else + { + uploadMethod = GpuUploadMethod.SUB_DATA; + } + } + else + { + // AMD or Intel card + + if (mapBufferRangeSupported) + { + uploadMethod = GpuUploadMethod.BUFFER_MAPPING; + } + else + { + uploadMethod = GpuUploadMethod.DATA; + } + } + + CONFIG.client().advanced().buffers().setGpuUploadMethod(uploadMethod); + ClientApi.LOGGER.info("GPU Vendor [" + vendor + "], Upload method set to [" + uploadMethod + "]."); + } + + + + + //==============// + // shader setup // + //==============// + + setGlContext(GLProxyContext.MINECRAFT); + + createShaderProgram(); + + // Note: VAO objects can not be shared between contexts, + // this must be created on minecraft's render context to work correctly + vertexArrayObjectId = GL30.glGenVertexArrays(); + + + + + + //==========// + // clean up // + //==========// + + // Since this is created on the render thread, make sure the Minecraft context is used in the end + setGlContext(GLProxyContext.MINECRAFT); + + + // GLProxy creation success + ClientApi.LOGGER.error(GLProxy.class.getSimpleName() + " creation successful. OpenGL smiles upon you this day."); + } + + /** Creates all required shaders */ + public void createShaderProgram() + { + LodShader vertexShader = null; + LodShader fragmentShader = null; + + try + { + // get the shaders from the resource folder + vertexShader = LodShader.loadShader(GL20.GL_VERTEX_SHADER, "shaders" + File.separator + "standard.vert", false); + fragmentShader = LodShader.loadShader(GL20.GL_FRAGMENT_SHADER, "shaders" + File.separator + "flat_shaded.frag", false); + + // this can be used when testing shaders, + // since we can't hot swap the files in the resource folder +// vertexShader = LodShader.loadShader(GL20.GL_VERTEX_SHADER, "C:/Users/James Seibel/Desktop/shaders/standard.vert", true); +// fragmentShader = LodShader.loadShader(GL20.GL_FRAGMENT_SHADER, "C:/Users/James Seibel/Desktop/shaders/flat_shaded.frag", true); + + + // create the shaders + + lodShaderProgram = new LodShaderProgram(); + + // Attach the compiled shaders to the program + lodShaderProgram.attachShader(vertexShader); + lodShaderProgram.attachShader(fragmentShader); + + // activate the fragment shader output + GL30.glBindFragDataLocation(lodShaderProgram.id, 0, "fragColor"); + + // attach the shader program to the OpenGL context + lodShaderProgram.link(); + + // after the shaders have been attached to the program + // we don't need their OpenGL references anymore + GL20.glDeleteShader(vertexShader.id); + GL20.glDeleteShader(fragmentShader.id); + } + catch (Exception e) + { + ClientApi.LOGGER.error("Unable to compile shaders. Error: " + e.getMessage()); + } + } + + + /** + * A wrapper function to make switching contexts easier.
+ * Does nothing if the calling thread is already using newContext. + */ + public void setGlContext(GLProxyContext newContext) + { + GLProxyContext currentContext = getGlContext(); + + // we don't have to change the context, we are already there. + if (currentContext == newContext) + return; + + + long contextPointer; + GLCapabilities newGlCapabilities = null; + + // get the pointer(s) for this context + switch (newContext) + { + case LOD_BUILDER: + contextPointer = lodBuilderGlContext; + newGlCapabilities = lodBuilderGlCapabilities; + break; + + case MINECRAFT: + contextPointer = minecraftGlContext; + newGlCapabilities = minecraftGlCapabilities; + break; + + case PROXY_WORKER: + contextPointer = proxyWorkerGlContext; + newGlCapabilities = proxyWorkerGlCapabilities; + break; + + default: // default should never happen, it is just here to make the compiler happy + case NONE: + // 0L is equivalent to null + contextPointer = 0L; + break; + } + + GLFW.glfwMakeContextCurrent(contextPointer); + GL.setCapabilities(newGlCapabilities); + } + + /** Returns this thread's OpenGL context. */ + public GLProxyContext getGlContext() + { + long currentContext = GLFW.glfwGetCurrentContext(); + + + if (currentContext == lodBuilderGlContext) + return GLProxyContext.LOD_BUILDER; + else if (currentContext == minecraftGlContext) + return GLProxyContext.MINECRAFT; + else if (currentContext == proxyWorkerGlContext) + return GLProxyContext.PROXY_WORKER; + else if (currentContext == 0L) + return GLProxyContext.NONE; + else + // hopefully this shouldn't happen + throw new IllegalStateException(Thread.currentThread().getName() + + " has a unknown OpenGl context: [" + currentContext + "]. " + + "Minecraft context [" + minecraftGlContext + "], " + + "LodBuilder context [" + lodBuilderGlContext + "], " + + "ProxyWorker context [" + proxyWorkerGlContext + "], " + + "no context [0]."); + } + + + public static GLProxy getInstance() + { + if (instance == null) + instance = new GLProxy(); + + return instance; + } + + + + + + + + + /** + * Asynchronously calls the given runnable on proxy's OpenGL context. + * Useful for creating/destroying OpenGL objects in a thread + * that doesn't normally have access to a OpenGL context.
+ * No rendering can be done through this method. + */ + public void recordOpenGlCall(Runnable renderCall) + { + workerThread.execute(new Thread(() -> { runnableContainer(renderCall); })); + } + private void runnableContainer(Runnable renderCall) + { + try + { + // set up the context... + setGlContext(GLProxyContext.PROXY_WORKER); + // ...run the actual code... + renderCall.run(); + } + catch (Exception e) + { + ClientApi.LOGGER.error(Thread.currentThread().getName() + " ran into a issue: " + e.getMessage()); + e.printStackTrace(); + } + finally + { + // ...and make sure the context is released when the thread finishes + setGlContext(GLProxyContext.NONE); + } + } + + /** + * If called from a legacy OpenGL context this will + * set the fog end to infinity with a density of 0. + * Effectively removing the fog. + *

+ * This only works with Legacy OpenGL because James hasn't + * looking into a way for it to work with Modern OpenGL. + */ + public void disableLegacyFog() + { + // make sure this is a legacy OpenGL context + if (minecraftGlCapabilities.glFogf != 0) + { + // glFogf should only have an address if the current OpenGL + // context can call it, and it should only be able to call it in + // legacy OpenGL contexts; since it is disabled in Modern + // OpenGL. + + GL11.glFogf(GL11.GL_FOG_START, 0.0f); + GL11.glFogf(GL11.GL_FOG_END, Float.MAX_VALUE); + GL11.glFogf(GL11.GL_FOG_DENSITY, 0.0f); + } + } + + +} diff --git a/src/main/java/com/seibel/lod/core/render/LodRenderer.java b/src/main/java/com/seibel/lod/core/render/LodRenderer.java new file mode 100644 index 000000000..2bb298b48 --- /dev/null +++ b/src/main/java/com/seibel/lod/core/render/LodRenderer.java @@ -0,0 +1,777 @@ +/* + * This file is part of the Distant Horizon mod (formerly the LOD Mod), + * licensed under the GNU GPL v3 License. + * + * Copyright (C) 2020 James Seibel + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.seibel.lod.core.render; + +import java.awt.Color; +import java.util.HashSet; + +import org.lwjgl.opengl.GL15; +import org.lwjgl.opengl.GL20; +import org.lwjgl.opengl.GL30; + +import com.seibel.lod.core.api.ApiShared; +import com.seibel.lod.core.builders.bufferBuilding.LodBufferBuilderFactory; +import com.seibel.lod.core.builders.bufferBuilding.LodBufferBuilderFactory.VertexBuffersAndOffset; +import com.seibel.lod.core.enums.config.GpuUploadMethod; +import com.seibel.lod.core.enums.rendering.DebugMode; +import com.seibel.lod.core.enums.rendering.FogColorMode; +import com.seibel.lod.core.enums.rendering.FogDistance; +import com.seibel.lod.core.enums.rendering.FogDrawMode; +import com.seibel.lod.core.handlers.IReflectionHandler; +import com.seibel.lod.core.objects.lod.LodDimension; +import com.seibel.lod.core.objects.lod.RegionPos; +import com.seibel.lod.core.objects.math.Mat4f; +import com.seibel.lod.core.objects.math.Vec3d; +import com.seibel.lod.core.objects.math.Vec3f; +import com.seibel.lod.core.objects.opengl.LodVertexBuffer; +import com.seibel.lod.core.objects.rending.LodFogConfig; +import com.seibel.lod.core.render.shader.LodShaderProgram; +import com.seibel.lod.core.util.DetailDistanceUtil; +import com.seibel.lod.core.util.LevelPosUtil; +import com.seibel.lod.core.util.LodUtil; +import com.seibel.lod.core.util.SingletonHandler; +import com.seibel.lod.core.wrapperInterfaces.IWrapperFactory; +import com.seibel.lod.core.wrapperInterfaces.block.AbstractBlockPosWrapper; +import com.seibel.lod.core.wrapperInterfaces.chunk.AbstractChunkPosWrapper; +import com.seibel.lod.core.wrapperInterfaces.config.ILodConfigWrapperSingleton; +import com.seibel.lod.core.wrapperInterfaces.minecraft.IMinecraftRenderWrapper; +import com.seibel.lod.core.wrapperInterfaces.minecraft.IMinecraftWrapper; +import com.seibel.lod.core.wrapperInterfaces.minecraft.IProfilerWrapper; + +/** + * This is where all the magic happens.
+ * This is where LODs are draw to the world. + * + * @author James Seibel + * @version 11-27-2021 + */ +public class LodRenderer +{ + private static final IMinecraftWrapper MC = SingletonHandler.get(IMinecraftWrapper.class); + private static final IMinecraftRenderWrapper MC_RENDER = SingletonHandler.get(IMinecraftRenderWrapper.class); + private static final ILodConfigWrapperSingleton CONFIG = SingletonHandler.get(ILodConfigWrapperSingleton.class); + private static final IReflectionHandler REFLECTION_HANDLER = SingletonHandler.get(IReflectionHandler.class); + private static final IWrapperFactory FACTORY = SingletonHandler.get(IWrapperFactory.class); + + + /** + * If true the LODs colors will be replaced with + * a checkerboard, this can be used for debugging. + */ + public DebugMode previousDebugMode = DebugMode.OFF; + + private int farPlaneBlockDistance; + + + /** This is used to generate the buildable buffers */ + private final LodBufferBuilderFactory lodBufferBuilderFactory; + + /** Each VertexBuffer represents 1 region */ + private LodVertexBuffer[][][] vbos; + /** + * the OpenGL IDs for the vbos of the same indices. + * These have to be separate because we can't override the + * buffers in the VBOs (and we don't want to) + */ + @SuppressWarnings("unused") + private int[][][] storageBufferIds; + + private AbstractChunkPosWrapper vbosCenter = FACTORY.createChunkPos(); + + + /** This is used to determine if the LODs should be regenerated */ + private int[] previousPos = new int[] { 0, 0, 0 }; + + // these variables are used to determine if the buffers should be rebuilt + private float prevSkyBrightness = 0; + private double prevBrightness = 0; + private int prevRenderDistance = 0; + private long prevPlayerPosTime = 0; + private long prevVanillaChunkTime = 0; + private long prevChunkTime = 0; + + + /** This is used to determine if the LODs should be regenerated */ + private FogDistance prevFogDistance = FogDistance.NEAR_AND_FAR; + + /** + * if this is true the LOD buffers should be regenerated, + * provided they aren't already being regenerated. + */ + private volatile boolean partialRegen = false; + private volatile boolean fullRegen = true; + + /** + * This HashSet contains every chunk that Vanilla Minecraft + * is going to render + */ + public boolean[][] vanillaRenderedChunks; + public boolean vanillaRenderedChunksChanged; + public boolean vanillaRenderedChunksEmptySkip = false; + public int vanillaBlockRenderedDistance; + + + + + public LodRenderer(LodBufferBuilderFactory newLodNodeBufferBuilder) + { + lodBufferBuilderFactory = newLodNodeBufferBuilder; + } + + + + + + + + /** + * Besides drawing the LODs this method also starts + * the async process of generating the Buffers that hold those LODs. + * @param lodDim The dimension to draw, if null doesn't replace the current dimension. + * @param mcModelViewMatrix This matrix stack should come straight from MC's renderChunkLayer (or future equivalent) method + * @param mcProjectionMatrix + * @param partialTicks how far into the current tick this method was called. + */ + public void drawLODs(LodDimension lodDim, Mat4f mcModelViewMatrix, Mat4f mcProjectionMatrix, float partialTicks, IProfilerWrapper profiler) + { + //=================================// + // determine if LODs should render // + //=================================// + + if (lodDim == null) + { + // if there aren't any loaded LodChunks + // don't try drawing anything + return; + } + + if (MC_RENDER.playerHasBlindnessEffect()) + { + // if the player is blind, don't render LODs, + // and don't change minecraft's fog + // which blindness relies on. + return; + } + + if (CONFIG.client().graphics().fogQuality().getDisableVanillaFog()) + GLProxy.getInstance().disableLegacyFog(); + + + + + // TODO move the buffer regeneration logic into its own class (probably called in the client api instead) + // starting here... + determineIfLodsShouldRegenerate(lodDim, partialTicks); + + //=================// + // create the LODs // + //=================// + + // only regenerate the LODs if: + // 1. we want to regenerate LODs + // 2. we aren't already regenerating the LODs + // 3. we aren't waiting for the build and draw buffers to swap + // (this is to prevent thread conflicts) + if ((partialRegen || fullRegen) && !lodBufferBuilderFactory.generatingBuffers && !lodBufferBuilderFactory.newBuffersAvailable()) + { + // generate the LODs on a separate thread to prevent stuttering or freezing + lodBufferBuilderFactory.generateLodBuffersAsync(this, lodDim, MC.getPlayerBlockPos(), true); + + // the regen process has been started, + // it will be done when lodBufferBuilder.newBuffersAvailable() + // is true + fullRegen = false; + partialRegen = false; + } + + // TODO move the buffer regeneration logic into its own class (probably called in the client api instead) + // ...ending here + + if (lodBufferBuilderFactory.newBuffersAvailable()) + { + swapBuffers(); + } + + + + + + //===============// + // initial setup // + //===============// + + profiler.push("LOD setup"); + + GLProxy glProxy = GLProxy.getInstance(); + + + + // set the required open GL settings + + if (CONFIG.client().advanced().debugging().getDebugMode() == DebugMode.SHOW_DETAIL_WIREFRAME) + GL15.glPolygonMode(GL15.GL_FRONT_AND_BACK, GL15.GL_LINE); + else + GL15.glPolygonMode(GL15.GL_FRONT_AND_BACK, GL15.GL_FILL); + + GL15.glEnable(GL15.GL_CULL_FACE); + GL15.glEnable(GL15.GL_DEPTH_TEST); + + // enable transparent rendering + GL15.glBlendFunc(GL15.GL_SRC_ALPHA, GL15.GL_ONE_MINUS_SRC_ALPHA); + GL15.glEnable(GL15.GL_BLEND); + + // get MC's shader program + int currentProgram = GL20.glGetInteger(GL20.GL_CURRENT_PROGRAM); + + + Mat4f modelViewMatrix = translateModelViewMatrix(mcModelViewMatrix, partialTicks); + vanillaBlockRenderedDistance = MC_RENDER.getRenderDistance() * LodUtil.CHUNK_WIDTH; + // required for setupFog and setupProjectionMatrix + if (MC.getWrappedClientWorld().getDimensionType().hasCeiling()) + farPlaneBlockDistance = Math.min(CONFIG.client().graphics().quality().getLodChunkRenderDistance(), LodUtil.CEILED_DIMENSION_MAX_RENDER_DISTANCE) * LodUtil.CHUNK_WIDTH; + else + farPlaneBlockDistance = CONFIG.client().graphics().quality().getLodChunkRenderDistance() * LodUtil.CHUNK_WIDTH; + + + Mat4f projectionMatrix = createProjectionMatrix(mcProjectionMatrix, vanillaBlockRenderedDistance); + + LodFogConfig fogSettings = determineFogConfig(); + + + + + + + if (vbos != null) + { + //==============// + // shader setup // + //==============// + + // can be used when testing shaders + // glProxy.createShaderProgram(); + + + LodShaderProgram shaderProgram = glProxy.lodShaderProgram; + shaderProgram.use(); + + + // determine the VertexArrayObject's element positions + int posAttrib = shaderProgram.getAttributeLocation("vPosition"); + shaderProgram.enableVertexAttribute(posAttrib); + int colAttrib = shaderProgram.getAttributeLocation("color"); + shaderProgram.enableVertexAttribute(colAttrib); + + + // global uniforms + int mvmUniform = shaderProgram.getUniformLocation("modelViewMatrix"); + shaderProgram.setUniform(mvmUniform, modelViewMatrix); + int projUniform = shaderProgram.getUniformLocation("projectionMatrix"); + shaderProgram.setUniform(projUniform, projectionMatrix); + int cameraUniform = shaderProgram.getUniformLocation("cameraPos"); + shaderProgram.setUniform(cameraUniform, getTranslatedCameraPos()); + int fogColorUniform = shaderProgram.getUniformLocation("fogColor"); + shaderProgram.setUniform(fogColorUniform, getFogColor()); + + + // region dependent uniforms + int fogEnabledUniform = shaderProgram.getUniformLocation("fogEnabled"); + int nearFogEnabledUniform = shaderProgram.getUniformLocation("nearFogEnabled"); + int farFogEnabledUniform = shaderProgram.getUniformLocation("farFogEnabled"); + // near + int nearFogStartUniform = shaderProgram.getUniformLocation("nearFogStart"); + int nearFogEndUniform = shaderProgram.getUniformLocation("nearFogEnd"); + // far + int farFogStartUniform = shaderProgram.getUniformLocation("farFogStart"); + int farFogEndUniform = shaderProgram.getUniformLocation("farFogEnd"); + + + + + + //===========// + // rendering // + //===========// + + profiler.popPush("LOD draw"); + + boolean cullingDisabled = CONFIG.client().graphics().advancedGraphics().getDisableDirectionalCulling(); + boolean renderBufferStorage = CONFIG.client().advanced().buffers().getGpuUploadMethod() == GpuUploadMethod.BUFFER_STORAGE && glProxy.bufferStorageSupported; + + // where the center of the buffers is (needed when culling regions) + RegionPos vboCenterRegionPos = new RegionPos(vbosCenter); + RegionPos vboPos = new RegionPos(); + + + // render each of the buffers + for (int x = 0; x < vbos.length; x++) + { + for (int z = 0; z < vbos.length; z++) + { + vboPos.x = x + vboCenterRegionPos.x - (lodDim.getWidth() / 2); + vboPos.z = z + vboCenterRegionPos.z - (lodDim.getWidth() / 2); + + if (cullingDisabled || RenderUtil.isRegionInViewFrustum(MC_RENDER.getCameraBlockPosition(), MC_RENDER.getLookAtVector(), vboPos.blockPos())) + { + // fog may be different from region to region + applyFog(shaderProgram, + fogSettings, fogEnabledUniform, nearFogEnabledUniform, farFogEnabledUniform, + nearFogStartUniform, nearFogEndUniform, farFogStartUniform, farFogEndUniform); + + + // actual rendering + int bufferId = 0; + for (int i = 0; i < vbos[x][z].length; i++) + { + bufferId = (storageBufferIds != null && renderBufferStorage) ? storageBufferIds[x][z][i] : vbos[x][z][i].id; + drawArrays(bufferId, vbos[x][z][i].vertexCount, posAttrib, colAttrib); + } + + } + } + } + + + + //================// + // render cleanup // + //================// + + // if this cleanup isn't done MC may crash + // when trying to render its own terrain + GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0); + GL30.glBindVertexArray(0); + + GL20.glDisableVertexAttribArray(posAttrib); + GL20.glDisableVertexAttribArray(colAttrib); + } + + + + + + //=========// + // cleanup // + //=========// + + profiler.popPush("LOD cleanup"); + + GL15.glPolygonMode(GL15.GL_FRONT_AND_BACK, GL15.GL_FILL); + GL15.glDisable(GL15.GL_BLEND); // TODO: what should this be reset to? + + GL20.glUseProgram(currentProgram); + + // clear the depth buffer so everything is drawn + // over the LODs + GL15.glClear(GL15.GL_DEPTH_BUFFER_BIT); + + + + // end of internal LOD profiling + profiler.pop(); + } + + + + + + + + + /** This is where the actual drawing happens. */ + private void drawArrays(int glBufferId, int vertexCount, int posAttrib, int colAttrib) + { + if (glBufferId == 0) + return; + + // can be used to check for OpenGL errors +// int error = GL15.glGetError(); +// ClientProxy.LOGGER.info(Integer.toHexString(error)); + + + // bind the buffer we are going to draw + GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, glBufferId); + GL30.glBindVertexArray(GLProxy.getInstance().vertexArrayObjectId); + + // let OpenGL know how our buffer is set up + int vertexByteCount = (Float.BYTES * 3) + (Byte.BYTES * 4); + GL20.glEnableVertexAttribArray(posAttrib); + GL20.glVertexAttribPointer(posAttrib, 3, GL15.GL_FLOAT, false, vertexByteCount, 0); + GL20.glEnableVertexAttribArray(colAttrib); + GL20.glVertexAttribPointer(colAttrib, 4, GL15.GL_UNSIGNED_BYTE, true, vertexByteCount, Float.BYTES * 3); + + + // draw the LODs + GL30.glDrawArrays(GL30.GL_TRIANGLES, 0, vertexCount); + } + + + + + + + //=================// + // Setup Functions // + //=================// + + + /** Create all buffers that will be used. */ + public void setupBuffers(LodDimension lodDim) + { + lodBufferBuilderFactory.setupBuffers(lodDim); + } + + + + + /** Return what fog settings should be used when rendering. */ + private LodFogConfig determineFogConfig() + { + LodFogConfig fogConfig = new LodFogConfig(); + + + fogConfig.fogDrawMode = CONFIG.client().graphics().fogQuality().getFogDrawMode(); + if (fogConfig.fogDrawMode == FogDrawMode.USE_OPTIFINE_SETTING) + fogConfig.fogDrawMode = REFLECTION_HANDLER.getFogDrawMode(); + + + // how different distances are drawn depends on the quality set + fogConfig.fogDistance = CONFIG.client().graphics().fogQuality().getFogDistance(); + + + + + + // far fog // + + if (CONFIG.client().graphics().fogQuality().getFogDistance() == FogDistance.NEAR_AND_FAR) + fogConfig.farFogStart = farPlaneBlockDistance * 1.6f * 0.9f; + else + // for more realistic fog when using FAR + fogConfig.farFogStart = Math.min(vanillaBlockRenderedDistance * 1.5f, farPlaneBlockDistance * 0.9f * 1.6f); + + fogConfig.farFogEnd = farPlaneBlockDistance * 1.6f; + + + // near fog // + + // the reason that I wrote fogEnd then fogStart backwards + // is because we are using fog backwards to how + // it is normally used, hiding near objects + // instead of far objects. + fogConfig.nearFogEnd = vanillaBlockRenderedDistance * 1.41f; + fogConfig.nearFogStart = vanillaBlockRenderedDistance * 1.6f; + + + return fogConfig; + } + + private Color getFogColor() + { + Color fogColor; + + if (CONFIG.client().graphics().fogQuality().getFogColorMode() == FogColorMode.USE_SKY_COLOR) + fogColor = MC_RENDER.getSkyColor(); + else + fogColor = MC_RENDER.getFogColor(); + + return fogColor; + } + + /** + * Translate the camera relative to the LodDimension's center, + * this is done since all LOD buffers are created in world space + * instead of object space. + * (since AxisAlignedBoundingBoxes (LODs) use doubles and thus have a higher + * accuracy vs the model view matrix, which only uses floats) + */ + private Mat4f translateModelViewMatrix(Mat4f mcModelViewMatrix, float partialTicks) + { + // get all relevant camera info + Vec3d projectedView = MC_RENDER.getCameraExactPosition(); + + // translate the camera relative to the regions' center + // (AxisAlignedBoundingBoxes (LODs) use doubles and thus have a higher + // accuracy vs the model view matrix, which only uses floats) + AbstractBlockPosWrapper bufferPos = vbosCenter.getWorldPosition(); + double xDiff = projectedView.x - bufferPos.getX(); + double zDiff = projectedView.z - bufferPos.getZ(); + mcModelViewMatrix.multiplyTranslationMatrix(-xDiff, -projectedView.y, -zDiff); + + return mcModelViewMatrix; + } + + /** + * Similar to translateModelViewMatrix (above), + * but for the camera position + */ + private Vec3f getTranslatedCameraPos() + { + AbstractBlockPosWrapper worldCenter = vbosCenter.getWorldPosition(); + Vec3d cameraPos = MC_RENDER.getCameraExactPosition(); + return new Vec3f((float)cameraPos.x - worldCenter.getX(), (float)cameraPos.y, (float)cameraPos.z - worldCenter.getZ()); + } + + /** + * create and return a new projection matrix based on MC's projection matrix + * @param currentProjectionMatrix this is Minecraft's current projection matrix + * @param vanillaBlockRenderedDistance Minecraft's vanilla far plane distance + */ + private Mat4f createProjectionMatrix(Mat4f currentProjectionMatrix, float vanillaBlockRenderedDistance) + { + //Create a copy of the current matrix, so the current matrix isn't modified. + Mat4f lodProj = currentProjectionMatrix.copy(); + + //Set new far and near clip plane values. + lodProj.setClipPlanes( + CONFIG.client().graphics().advancedGraphics().getUseExtendedNearClipPlane() ? vanillaBlockRenderedDistance / 5 : 1, + farPlaneBlockDistance * LodUtil.CHUNK_WIDTH / 2); + + return lodProj; + } + + private void applyFog(LodShaderProgram shaderProgram, + LodFogConfig fogSettings, int fogEnabledUniform, int nearFogEnabledUniform, int farFogEnabledUniform, + int nearFogStartUniform, int nearFogEndUniform, int farFogStartUniform, int farFogEndUniform) + { + if (fogSettings.fogDrawMode != FogDrawMode.FOG_DISABLED) + { + shaderProgram.setUniform(fogEnabledUniform, true); + shaderProgram.setUniform(nearFogEnabledUniform, fogSettings.fogDistance != FogDistance.FAR); + shaderProgram.setUniform(farFogEnabledUniform, fogSettings.fogDistance != FogDistance.NEAR); + + // near + shaderProgram.setUniform(nearFogStartUniform, fogSettings.nearFogStart); + shaderProgram.setUniform(nearFogEndUniform, fogSettings.nearFogEnd); + // far + shaderProgram.setUniform(farFogStartUniform, fogSettings.farFogStart); + shaderProgram.setUniform(farFogEndUniform, fogSettings.farFogEnd); + } + else + { + shaderProgram.setUniform(fogEnabledUniform, false); + } + } + + + + + + //======================// + // Other Misc Functions // + //======================// + + + /** + * If this is called then the next time "drawLODs" is called + * the LODs will be regenerated; the same as if the player moved. + */ + public void regenerateLODsNextFrame() + { + fullRegen = true; + } + + /** + * Replace the current Vertex Buffers with the newly + * created buffers from the lodBufferBuilder.

+ *

+ * For some reason this has to be called after the frame has been rendered, + * otherwise visual stuttering/rubber banding may happen. I'm not sure why... + */ + private void swapBuffers() + { + // replace the drawable buffers with + // the newly created buffers from the lodBufferBuilder + VertexBuffersAndOffset result = lodBufferBuilderFactory.getVertexBuffers(); + vbos = result.vbos; + storageBufferIds = result.storageBufferIds; + vbosCenter = result.drawableCenterChunkPos; + } + + /** Calls the BufferBuilder's destroyBuffers method. */ + public void destroyBuffers() + { + lodBufferBuilderFactory.destroyBuffers(); + } + + + + /** Determines if the LODs should have a fullRegen or partialRegen */ + private void determineIfLodsShouldRegenerate(LodDimension lodDim, float partialTicks) + { + short chunkRenderDistance = (short) MC_RENDER.getRenderDistance(); + int vanillaRenderedChunksWidth = chunkRenderDistance * 2 + 2; + + //=============// + // full regens // + //=============// + + // check if the view distance changed + if (ApiShared.previousLodRenderDistance != CONFIG.client().graphics().quality().getLodChunkRenderDistance() + || chunkRenderDistance != prevRenderDistance + || prevFogDistance != CONFIG.client().graphics().fogQuality().getFogDistance()) + { + + vanillaRenderedChunks = new boolean[vanillaRenderedChunksWidth][vanillaRenderedChunksWidth]; + DetailDistanceUtil.updateSettings(); + fullRegen = true; + previousPos = LevelPosUtil.createLevelPos((byte) 4, MC.getPlayerChunkPos().getZ(), MC.getPlayerChunkPos().getZ()); + prevFogDistance = CONFIG.client().graphics().fogQuality().getFogDistance(); + prevRenderDistance = chunkRenderDistance; + } + + // did the user change the debug setting? + if (CONFIG.client().advanced().debugging().getDebugMode() != previousDebugMode) + { + previousDebugMode = CONFIG.client().advanced().debugging().getDebugMode(); + fullRegen = true; + } + + + long newTime = System.currentTimeMillis(); + + // check if the player has moved + if (newTime - prevPlayerPosTime > CONFIG.client().advanced().buffers().getRebuildTimes().playerMoveTimeout) + { + if (LevelPosUtil.getDetailLevel(previousPos) == 0 + || Math.abs(MC.getPlayerChunkPos().getX() - LevelPosUtil.getPosX(previousPos)) > CONFIG.client().advanced().buffers().getRebuildTimes().playerMoveDistance + || Math.abs(MC.getPlayerChunkPos().getZ() - LevelPosUtil.getPosZ(previousPos)) > CONFIG.client().advanced().buffers().getRebuildTimes().playerMoveDistance) + { + vanillaRenderedChunks = new boolean[vanillaRenderedChunksWidth][vanillaRenderedChunksWidth]; + fullRegen = true; + previousPos = LevelPosUtil.createLevelPos((byte) 4, MC.getPlayerChunkPos().getX(), MC.getPlayerChunkPos().getZ()); + } + prevPlayerPosTime = newTime; + } + + + + // determine how far the lighting has to + // change in order to rebuild the buffers + + // the max brightness is 1 and the minimum is 0.2 + float skyBrightness = lodDim.dimension.hasSkyLight() ? MC.getSkyDarken(partialTicks) : 0.2f; + float minLightingDifference; + switch (CONFIG.client().advanced().buffers().getRebuildTimes()) + { + case FREQUENT: + minLightingDifference = 0.025f; + break; + case NORMAL: + minLightingDifference = 0.05f; + break; + default: + case RARE: + minLightingDifference = 0.1f; + break; + } + + // check if the lighting changed + if (Math.abs(skyBrightness - prevSkyBrightness) > minLightingDifference + // make sure the lighting gets to the max/minimum value + // (just in case the minLightingDifference is too large to notice the change) + || (skyBrightness == 1.0f && prevSkyBrightness != 1.0f) // noon + || (skyBrightness == 0.2f && prevSkyBrightness != 0.2f) // midnight + || MC_RENDER.getGamma() != prevBrightness) + { + fullRegen = true; + prevBrightness = MC_RENDER.getGamma(); + prevSkyBrightness = skyBrightness; + } + + /*if (lightMap != lastLightMap) + { + fullRegen = true; + lastLightMap = lightMap; + }*/ + + //================// + // partial regens // + //================// + + + // check if the vanilla rendered chunks changed + if (newTime - prevVanillaChunkTime > CONFIG.client().advanced().buffers().getRebuildTimes().renderedChunkTimeout) + { + if (vanillaRenderedChunksChanged) + { + partialRegen = true; + vanillaRenderedChunksChanged = false; + } + prevVanillaChunkTime = newTime; + } + + + // check if there is any newly generated terrain to show + if (newTime - prevChunkTime > CONFIG.client().advanced().buffers().getRebuildTimes().chunkChangeTimeout) + { + if (lodDim.regenDimensionBuffers) + { + partialRegen = true; + lodDim.regenDimensionBuffers = false; + } + prevChunkTime = newTime; + } + + + + //==============// + // LOD skipping // + //==============// + + // determine which LODs should not be rendered close to the player + HashSet chunkPosToSkip = LodUtil.getNearbyLodChunkPosToSkip(lodDim, MC.getPlayerBlockPos()); + int xIndex; + int zIndex; + for (AbstractChunkPosWrapper pos : chunkPosToSkip) + { + vanillaRenderedChunksEmptySkip = false; + + xIndex = (pos.getX() - MC.getPlayerChunkPos().getX()) + (chunkRenderDistance + 1); + zIndex = (pos.getZ() - MC.getPlayerChunkPos().getZ()) + (chunkRenderDistance + 1); + + // sometimes we are given chunks that are outside the render distance, + // This prevents index out of bounds exceptions + if (xIndex >= 0 && zIndex >= 0 + && xIndex < vanillaRenderedChunks.length + && zIndex < vanillaRenderedChunks.length) + { + if (!vanillaRenderedChunks[xIndex][zIndex]) + { + vanillaRenderedChunks[xIndex][zIndex] = true; + vanillaRenderedChunksChanged = true; + lodDim.markRegionBufferToRegen(pos.getRegionX(), pos.getRegionZ()); + } + } + } + + + // if the player is high enough, draw all LODs + if (chunkPosToSkip.isEmpty() && MC.getPlayerBlockPos().getY() > 256 && !vanillaRenderedChunksEmptySkip) + { + vanillaRenderedChunks = new boolean[vanillaRenderedChunksWidth][vanillaRenderedChunksWidth]; + vanillaRenderedChunksChanged = true; + vanillaRenderedChunksEmptySkip = true; + } + + vanillaRenderedChunks = new boolean[vanillaRenderedChunksWidth][vanillaRenderedChunksWidth]; + } + +} diff --git a/src/main/java/com/seibel/lod/core/render/RenderUtil.java b/src/main/java/com/seibel/lod/core/render/RenderUtil.java new file mode 100644 index 000000000..15130a691 --- /dev/null +++ b/src/main/java/com/seibel/lod/core/render/RenderUtil.java @@ -0,0 +1,124 @@ +/* + * This file is part of the Distant Horizon mod (formerly the LOD Mod), + * licensed under the GNU GPL v3 License. + * + * Copyright (C) 2020 James Seibel + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.seibel.lod.core.render; + +import com.seibel.lod.core.objects.math.Vec3f; +import com.seibel.lod.core.util.LodUtil; +import com.seibel.lod.core.util.SingletonHandler; +import com.seibel.lod.core.wrapperInterfaces.block.AbstractBlockPosWrapper; +import com.seibel.lod.core.wrapperInterfaces.chunk.AbstractChunkPosWrapper; +import com.seibel.lod.core.wrapperInterfaces.minecraft.IMinecraftRenderWrapper; + +/** + * This holds miscellaneous helper code + * to be used in the rendering process. + * + * @author James Seibel + * @version 10-19-2021 + */ +public class RenderUtil +{ + private static final IMinecraftRenderWrapper MC_RENDER = SingletonHandler.get(IMinecraftRenderWrapper.class); + + + /** + * Returns if the given ChunkPos is in the loaded area of the world. + * @param center the center of the loaded world (probably the player's ChunkPos) + */ + public static boolean isChunkPosInLoadedArea(AbstractChunkPosWrapper pos, AbstractChunkPosWrapper center) + { + return (pos.getX() >= center.getX() - MC_RENDER.getRenderDistance() + && pos.getX() <= center.getX() + MC_RENDER.getRenderDistance()) + && + (pos.getZ() >= center.getZ() - MC_RENDER.getRenderDistance() + && pos.getZ() <= center.getZ() + MC_RENDER.getRenderDistance()); + } + + /** + * 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 x, int z, int centerCoordinate) + { + return (x >= centerCoordinate - MC_RENDER.getRenderDistance() + && x <= centerCoordinate + MC_RENDER.getRenderDistance()) + && + (z >= centerCoordinate - MC_RENDER.getRenderDistance() + && z <= centerCoordinate + MC_RENDER.getRenderDistance()); + } + + + /** + * 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); + } + + + /** + * Returns true if one of the region's 4 corners is in front + * of the camera. + */ + public static boolean isRegionInViewFrustum(AbstractBlockPosWrapper playerBlockPos, Vec3f cameraDir, AbstractBlockPosWrapper vboCenterPos) + { + // convert the vbo position into a direction vector + // starting from the player's position + Vec3f vboVec = new Vec3f(vboCenterPos.getX(), 0, vboCenterPos.getZ()); + Vec3f playerVec = new Vec3f(playerBlockPos.getX(), playerBlockPos.getY(), playerBlockPos.getZ()); + + vboVec.subtract(playerVec); + + + int halfRegionWidth = LodUtil.REGION_WIDTH / 2; + + // calculate the 4 corners + Vec3f vboSeVec = new Vec3f(vboVec.x + halfRegionWidth, vboVec.y, vboVec.z + halfRegionWidth); + Vec3f vboSwVec = new Vec3f(vboVec.x - halfRegionWidth, vboVec.y, vboVec.z + halfRegionWidth); + Vec3f vboNwVec = new Vec3f(vboVec.x - halfRegionWidth, vboVec.y, vboVec.z - halfRegionWidth); + Vec3f vboNeVec = new Vec3f(vboVec.x + halfRegionWidth, vboVec.y, vboVec.z - halfRegionWidth); + + // if any corner is visible, this region should be rendered + return isNormalizedVectorInViewFrustum(vboSeVec, cameraDir) || + isNormalizedVectorInViewFrustum(vboSwVec, cameraDir) || + isNormalizedVectorInViewFrustum(vboNwVec, cameraDir) || + isNormalizedVectorInViewFrustum(vboNeVec, cameraDir); + } + + /** + * Currently takes the dot product of the two vectors, + * but in the future could do more complicated frustum culling tests. + */ + private static boolean isNormalizedVectorInViewFrustum(Vec3f objectVector, Vec3f cameraDir) + { + // the -0.1 is to offer a slight buffer, so we are + // more likely to render LODs and thus, hopefully prevent + // flickering or odd disappearances + return objectVector.dotProduct(cameraDir) > -0.1; + } +} diff --git a/src/main/java/com/seibel/lod/core/render/shader/LodShader.java b/src/main/java/com/seibel/lod/core/render/shader/LodShader.java new file mode 100644 index 000000000..d5689a0a1 --- /dev/null +++ b/src/main/java/com/seibel/lod/core/render/shader/LodShader.java @@ -0,0 +1,116 @@ +/* + * This file is part of the Distant Horizon mod (formerly the LOD Mod), + * licensed under the GNU GPL v3 License. + * + * Copyright (C) 2020 James Seibel + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.seibel.lod.core.render.shader; + +import java.io.BufferedReader; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; + +import org.lwjgl.opengl.GL20; + +import com.seibel.lod.core.api.ClientApi; + +/** + * This object holds a OpenGL reference to a shader + * and allows for reading in and compiling a shader file. + * + * @author James Seibel + * @version 11-8-2021 + */ +public class LodShader +{ + /** OpenGL shader ID */ + public final int id; + + + + /** Creates a shader with specified type. */ + public LodShader(int type) + { + id = GL20.glCreateShader(type); + } + + + + /** + * Loads a shader from file. + * + * @param type Either GL_VERTEX_SHADER or GL_FRAGMENT_SHADER. + * @param path File path of the shader + * @param absoluteFilePath If false the file path is relative to the resource jar folder. + * @throws Exception if the shader fails to compile + */ + public static LodShader loadShader(int type, String path, boolean absoluteFilePath) throws Exception + { + StringBuilder stringBuilder = new StringBuilder(); + + try + { + // open the file + InputStream in = absoluteFilePath ? new FileInputStream(path) : LodShader.class.getClassLoader().getResourceAsStream(path); + BufferedReader reader = new BufferedReader(new InputStreamReader(in)); + + // read in the file + String line; + while ((line = reader.readLine()) != null) + stringBuilder.append(line).append("\n"); + } + catch (IOException e) + { + ClientApi.LOGGER.error("Unable to load shader from file [" + path + "]. Error: " + e.getMessage()); + } + CharSequence shaderFileSource = stringBuilder.toString(); + + return createShader(type, shaderFileSource); + } + + /** + * Creates a shader with the specified type and source. + * + * @param type Either GL_VERTEX_SHADER or GL_FRAGMENT_SHADER. + * @param source Source of the shader + * @throws Exception if the shader fails to compile + */ + public static LodShader createShader(int type, CharSequence source) throws Exception + { + LodShader shader = new LodShader(type); + GL20.glShaderSource(shader.id, source); + shader.compile(); + + return shader; + } + + /** + * Compiles the shader and checks its status afterwards. + * @throws Exception if the shader fails to compile + */ + public void compile() throws Exception + { + GL20.glCompileShader(id); + + // check if the shader compiled + int status = GL20.glGetShaderi(id, GL20.GL_COMPILE_STATUS); + if (status != GL20.GL_TRUE) + throw new Exception(GL20.glGetShaderInfoLog(id)); + } + +} diff --git a/src/main/java/com/seibel/lod/core/render/shader/LodShaderProgram.java b/src/main/java/com/seibel/lod/core/render/shader/LodShaderProgram.java new file mode 100644 index 000000000..f5478f607 --- /dev/null +++ b/src/main/java/com/seibel/lod/core/render/shader/LodShaderProgram.java @@ -0,0 +1,203 @@ +/* + * This file is part of the Distant Horizon mod (formerly the LOD Mod), + * licensed under the GNU GPL v3 License. + * + * Copyright (C) 2020 James Seibel + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.seibel.lod.core.render.shader; + +import java.awt.Color; +import java.nio.FloatBuffer; + +import org.lwjgl.opengl.GL20; +import org.lwjgl.system.MemoryStack; + +import com.seibel.lod.core.objects.math.Mat4f; +import com.seibel.lod.core.objects.math.Vec3d; +import com.seibel.lod.core.objects.math.Vec3f; + + +/** + * This object holds the reference to a OpenGL shader program + * and contains a few methods that can be used with OpenGL shader programs. + * The reason for many of these simple wrapper methods is as reminders of what + * can (and needs to be) done with a shader program. + * + * @author James Seibel + * @version 11-26-2021 + */ +public class LodShaderProgram +{ + /** Stores the handle of the program. */ + public final int id; + + /** Creates a shader program. */ + public LodShaderProgram() + { + id = GL20.glCreateProgram(); + } + + + + /** Calls GL20.glUseProgram(this.id) */ + public void use() + { + GL20.glUseProgram(id); + } + + /** + * Calls GL20.glAttachShader(this.id, shader.id) + * + * @param shader Shader to get attached + */ + public void attachShader(LodShader shader) + { + GL20.glAttachShader(this.id, shader.id); + } + + + /** + * Links the shader program to the current OpenGL context. + * @throws Exception Exception if the program failed to link + */ + public void link() throws Exception + { + GL20.glLinkProgram(this.id); + checkLinkStatus(); + } + + /** + * Checks if the program was linked successfully. + * @throws Exception if the program failed to link + */ + public void checkLinkStatus() throws Exception + { + int status = GL20.glGetProgrami(this.id, GL20.GL_LINK_STATUS); + if (status != GL20.GL_TRUE) + throw new Exception(GL20.glGetProgramInfoLog(this.id)); + } + + + + + /** + * Gets the location of an attribute variable with specified name. + * Calls GL20.glGetAttribLocation(id, name) + * + * @param name Attribute name + * + * @return Location of the attribute + */ + public int getAttributeLocation(CharSequence name) + { + return GL20.glGetAttribLocation(id, name); + } + + /** + * Calls GL20.glEnableVertexAttribArray(location) + * + * @param location Location of the vertex attribute + */ + public void enableVertexAttribute(int location) + { + GL20.glEnableVertexAttribArray(location); + } + + /** + * Calls GL20.glDisableVertexAttribArray(location) + * + * @param location Location of the vertex attribute + */ + public void disableVertexAttribute(int location) + { + GL20.glDisableVertexAttribArray(location); + } + + /** + * Sets the vertex attribute pointer. + * Calls GL20.glVertexAttribPointer(...) + * + * @param location Location of the vertex attribute + * @param size Number of values per vertex + * @param stride Offset between consecutive generic vertex attributes in + * bytes + * @param offset Offset of the first component of the first generic vertex + * attribute in bytes + */ + public void pointVertexAttribute(int location, int size, int stride, int offset) + { + GL20.glVertexAttribPointer(location, size, GL20.GL_FLOAT, false, stride, offset); + } + + /** + * Gets the location of a uniform variable with specified name. + * Calls GL20.glGetUniformLocation(id, name) + * + * @param name Uniform name + * + * @return -1 = error value, 0 = first value, 1 = second value, etc. + */ + public int getUniformLocation(CharSequence name) + { + return GL20.glGetUniformLocation(id, name); + } + + + + public void setUniform(int location, boolean value) + { + GL20.glUniform1i(location, value ? 1 : 0); + } + + public void setUniform(int location, int value) + { + GL20.glUniform1i(location, value); + } + + public void setUniform(int location, float value) + { + GL20.glUniform1f(location, value); + } + + public void setUniform(int location, Vec3f value) + { + GL20.glUniform3f(location, value.x, value.y, value.z); + } + + public void setUniform(int location, Vec3d value) + { + GL20.glUniform3f(location, (float) value.x, (float) value.y, (float) value.z); + } + + public void setUniform(int location, Mat4f value) + { + try (MemoryStack stack = MemoryStack.stackPush()) + { + FloatBuffer buffer = stack.mallocFloat(4 * 4); + value.store(buffer); + GL20.glUniformMatrix4fv(location, false, buffer); + } + } + + /** Converts the color's RGBA values into values between 0 and 1. */ + public void setUniform(int location, Color value) + { + GL20.glUniform4f(location, value.getRed() / 256.0f, value.getGreen() / 256.0f, value.getBlue() / 256.0f, value.getAlpha() / 256.0f); + } + + + +} diff --git a/src/main/java/com/seibel/lod/core/util/ColorUtil.java b/src/main/java/com/seibel/lod/core/util/ColorUtil.java new file mode 100644 index 000000000..1933246d2 --- /dev/null +++ b/src/main/java/com/seibel/lod/core/util/ColorUtil.java @@ -0,0 +1,122 @@ +/* + * This file is part of the Distant Horizon mod (formerly the LOD Mod), + * licensed under the GNU GPL v3 License. + * + * Copyright (C) 2020 James Seibel + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.seibel.lod.core.util; + +import java.awt.Color; + +import com.seibel.lod.core.wrapperInterfaces.minecraft.IMinecraftWrapper; + +/** + * + * @author Cola + * @author Leonardo Amato + * @version 11-13-2021 + */ +public class ColorUtil +{ + private static final IMinecraftWrapper MC = SingletonHandler.get(IMinecraftWrapper.class); + + + public static int rgbToInt(int red, int green, int blue) + { + return (0xFF << 24) | (red << 16) | (green << 8) | blue; + } + + public static int rgbToInt(int alpha, int red, int green, int blue) + { + return (alpha << 24) | (red << 16) | (green << 8) | blue; + } + + /** Returns a value between 0 and 255 */ + public static int getAlpha(int color) + { + return (color >> 24) & 0xFF; + } + + /** Returns a value between 0 and 255 */ + public static int getRed(int color) + { + return (color >> 16) & 0xFF; + } + + /** Returns a value between 0 and 255 */ + public static int getGreen(int color) + { + return (color >> 8) & 0xFF; + } + + /** Returns a value between 0 and 255 */ + public static int getBlue(int color) + { + return color & 0xFF; + } + + public static int applyShade(int color, int shade) + { + if (shade < 0) + return (getAlpha(color) << 24) | (Math.max(getRed(color) + shade, 0) << 16) | (Math.max(getGreen(color) + shade, 0) << 8) | Math.max(getBlue(color) + shade, 0); + else + return (getAlpha(color) << 24) | (Math.min(getRed(color) + shade, 255) << 16) | (Math.min(getGreen(color) + shade, 255) << 8) | Math.min(getBlue(color) + shade, 255); + } + + public static int applyShade(int color, float shade) + { + if (shade < 1) + return (getAlpha(color) << 24) | ((int) Math.max(getRed(color) * shade, 0) << 16) | ((int) Math.max(getGreen(color) * shade, 0) << 8) | (int) Math.max(getBlue(color) * shade, 0); + else + return (getAlpha(color) << 24) | ((int) Math.min(getRed(color) * shade, 255) << 16) | ((int) Math.min(getGreen(color) * shade, 255) << 8) | (int) Math.min(getBlue(color) * shade, 255); + } + + /** This method apply the lightmap to the color to use */ + public static int applyLightValue(int color, int skyLight, int blockLight) + { + int lightColor = MC.getColorIntFromLightMap(blockLight, skyLight); + int red = ColorUtil.getBlue(lightColor); + int green = ColorUtil.getGreen(lightColor); + int blue = ColorUtil.getRed(lightColor); + + return ColorUtil.multiplyRGBcolors(color, ColorUtil.rgbToInt(red, green, blue)); + } + + /** Edit the given color as an HSV (Hue Saturation Value) color */ + public static int applySaturationAndBrightnessMultipliers(int color, float saturationMultiplier, float brightnessMultiplier) + { + float[] hsv = Color.RGBtoHSB(getRed(color), getGreen(color), getBlue(color), null); + return Color.getHSBColor( + hsv[0], // hue + LodUtil.clamp(0.0f, hsv[1] * saturationMultiplier, 1.0f), + LodUtil.clamp(0.0f, hsv[2] * brightnessMultiplier, 1.0f)).getRGB(); + } + + /** Multiply 2 RGB colors */ + public static int multiplyRGBcolors(int color1, int color2) + { + return ((getAlpha(color1) * getAlpha(color2) / 255) << 24) | ((getRed(color1) * getRed(color2) / 255) << 16) | ((getGreen(color1) * getGreen(color2) / 255) << 8) | (getBlue(color1) * getBlue(color2) / 255); + } + + @SuppressWarnings("unused") + public static String toString(int color) + { + return Integer.toHexString(getAlpha(color)) + " " + + Integer.toHexString(getRed(color)) + " " + + Integer.toHexString(getGreen(color)) + " " + + Integer.toHexString(getBlue(color)); + } +} diff --git a/src/main/java/com/seibel/lod/core/util/DataPointUtil.java b/src/main/java/com/seibel/lod/core/util/DataPointUtil.java new file mode 100644 index 000000000..81b11c287 --- /dev/null +++ b/src/main/java/com/seibel/lod/core/util/DataPointUtil.java @@ -0,0 +1,521 @@ +/* + * This file is part of the Distant Horizon mod (formerly the LOD Mod), + * licensed under the GNU GPL v3 License. + * + * Copyright (C) 2020 James Seibel + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.seibel.lod.core.util; + +import static com.seibel.lod.core.builders.bufferBuilding.LodBufferBuilderFactory.skyLightPlayer; + +import com.seibel.lod.core.enums.config.DistanceGenerationMode; + +/** + * + * @author Leonardo Amato + * @version ?? + */ +public class DataPointUtil +{ + /* + |a |a |a |a |r |r |r |r | + + |r |r |r |r |g |g |g |g | + + |g |g |g |g |b |b |b |b | + + |b |b |b |b |h |h |h |h | + + |h |h |h |h |h |h |d |d | + + |d |d |d |d |d |d |d |d | + + |bl |bl |bl |bl |sl |sl |sl |sl | + + |l |l |f |g |g |g |v |e | + + + */ + + // Reminder: bytes have range of [-128, 127]. + // When converting to or from an int a 128 should be added or removed. + // If there is a bug with color then it's probably caused by this. + + //To be used in the future for negative value + //public final static int MIN_DEPTH = -64; + //public final static int MIN_HEIGHT = -64; + public final static int EMPTY_DATA = 0; + public static final short VERTICAL_OFFSET = -64; + public static int WORLD_HEIGHT = 1024; + + public final static int ALPHA_DOWNSIZE_SHIFT = 4; + + //public final static int BLUE_COLOR_SHIFT = 0; + //public final static int GREEN_COLOR_SHIFT = 8; + //public final static int RED_COLOR_SHIFT = 16; + //public final static int ALPHA_COLOR_SHIFT = 24; + + public final static int BLUE_SHIFT = 36; + public final static int GREEN_SHIFT = BLUE_SHIFT + 8; + public final static int RED_SHIFT = BLUE_SHIFT + 16; + public final static int ALPHA_SHIFT = BLUE_SHIFT + 24; + + public final static int COLOR_SHIFT = 36; + + public final static int HEIGHT_SHIFT = 26; + public final static int DEPTH_SHIFT = 16; + public final static int BLOCK_LIGHT_SHIFT = 12; + public final static int SKY_LIGHT_SHIFT = 8; + //public final static int LIGHTS_SHIFT = SKY_LIGHT_SHIFT; + //public final static int VERTICAL_INDEX_SHIFT = 6; + public final static int FLAG_SHIFT = 5; + public final static int GEN_TYPE_SHIFT = 2; + public final static int VOID_SHIFT = 1; + public final static int EXISTENCE_SHIFT = 0; + + public final static long ALPHA_MASK = 0b1111; + public final static long RED_MASK = 0b1111_1111; + public final static long GREEN_MASK = 0b1111_1111; + public final static long BLUE_MASK = 0b1111_1111; + public final static long COLOR_MASK = 0b11111111_11111111_11111111; + public final static long HEIGHT_MASK = 0b11_1111_1111; + public final static long DEPTH_MASK = 0b11_1111_1111; + //public final static long LIGHTS_MASK = 0b1111_1111; + public final static long BLOCK_LIGHT_MASK = 0b1111; + public final static long SKY_LIGHT_MASK = 0b1111; + //public final static long VERTICAL_INDEX_MASK = 0b11; + public final static long FLAG_MASK = 0b1; + public final static long GEN_TYPE_MASK = 0b111; + public final static long VOID_MASK = 1; + public final static long EXISTENCE_MASK = 1; + + + public static long createVoidDataPoint(int generationMode) + { + long dataPoint = 0; + dataPoint += (generationMode & GEN_TYPE_MASK) << GEN_TYPE_SHIFT; + dataPoint += VOID_MASK << VOID_SHIFT; + dataPoint += EXISTENCE_MASK << EXISTENCE_SHIFT; + return dataPoint; + } + + public static long createDataPoint(int height, int depth, int color, int lightSky, int lightBlock, int generationMode, boolean flag) + { + return createDataPoint( + ColorUtil.getAlpha(color), + ColorUtil.getRed(color), + ColorUtil.getGreen(color), + ColorUtil.getBlue(color), + height, depth, lightSky, lightBlock, generationMode, flag); + } + + public static long createDataPoint(int alpha, int red, int green, int blue, int height, int depth, int lightSky, int lightBlock, int generationMode, boolean flag) + { + long dataPoint = 0; + dataPoint += (long) (alpha >>> ALPHA_DOWNSIZE_SHIFT) << ALPHA_SHIFT; + dataPoint += (red & RED_MASK) << RED_SHIFT; + dataPoint += (green & GREEN_MASK) << GREEN_SHIFT; + dataPoint += (blue & BLUE_MASK) << BLUE_SHIFT; + dataPoint += (height & HEIGHT_MASK) << HEIGHT_SHIFT; + dataPoint += (depth & DEPTH_MASK) << DEPTH_SHIFT; + dataPoint += (lightBlock & BLOCK_LIGHT_MASK) << BLOCK_LIGHT_SHIFT; + dataPoint += (lightSky & SKY_LIGHT_MASK) << SKY_LIGHT_SHIFT; + dataPoint += (generationMode & GEN_TYPE_MASK) << GEN_TYPE_SHIFT; + if (flag) dataPoint += FLAG_MASK << FLAG_SHIFT; + dataPoint += EXISTENCE_MASK << EXISTENCE_SHIFT; + + return dataPoint; + } + + public static short getHeight(long dataPoint) + { + return (short) ((dataPoint >>> HEIGHT_SHIFT) & HEIGHT_MASK); + } + + public static short getDepth(long dataPoint) + { + return (short) ((dataPoint >>> DEPTH_SHIFT) & DEPTH_MASK); + } + + public static short getAlpha(long dataPoint) + { + return (short) ((((dataPoint >>> ALPHA_SHIFT) & ALPHA_MASK) << ALPHA_DOWNSIZE_SHIFT) | 0b1111); + } + + public static short getRed(long dataPoint) + { + return (short) ((dataPoint >>> RED_SHIFT) & RED_MASK); + } + + public static short getGreen(long dataPoint) + { + return (short) ((dataPoint >>> GREEN_SHIFT) & GREEN_MASK); + } + + public static short getBlue(long dataPoint) + { + return (short) ((dataPoint >>> BLUE_SHIFT) & BLUE_MASK); + } + + public static byte getLightSky(long dataPoint) + { + return (byte) ((dataPoint >>> SKY_LIGHT_SHIFT) & SKY_LIGHT_MASK); + } + + public static byte getLightSkyAlt(long dataPoint) + { + if (skyLightPlayer == 0 && ((dataPoint >>> FLAG_SHIFT) & FLAG_MASK) == 1) + return 0; + else + return (byte) ((dataPoint >>> SKY_LIGHT_SHIFT) & SKY_LIGHT_MASK); + } + + public static byte getLightBlock(long dataPoint) + { + return (byte) ((dataPoint >>> BLOCK_LIGHT_SHIFT) & BLOCK_LIGHT_MASK); + } + + public static boolean getFlag(long dataPoint) + { + return ((dataPoint >>> FLAG_SHIFT) & FLAG_MASK) == 1; + } + + public static byte getGenerationMode(long dataPoint) + { + return (byte) ((dataPoint >>> GEN_TYPE_SHIFT) & GEN_TYPE_MASK); + } + + + public static boolean isVoid(long dataPoint) + { + return (((dataPoint >>> VOID_SHIFT) & VOID_MASK) == 1); + } + + public static boolean doesItExist(long dataPoint) + { + return (((dataPoint >>> EXISTENCE_SHIFT) & EXISTENCE_MASK) == 1); + } + + public static int getColor(long dataPoint) + { + return (int) (((dataPoint >>> COLOR_SHIFT) & COLOR_MASK) | (/*((dataPoint >>> (ALPHA_SHIFT - ALPHA_DOWNSIZE_SHIFT)) | 0b1111)*/255 << 24)); + } + + /** This is used to convert a dataPoint to string (useful for the print function) */ + @SuppressWarnings("unused") + public static String toString(long dataPoint) + { + return getHeight(dataPoint) + " " + + getDepth(dataPoint) + " " + + getAlpha(dataPoint) + " " + + getRed(dataPoint) + " " + + getBlue(dataPoint) + " " + + getGreen(dataPoint) + " " + + getLightBlock(dataPoint) + " " + + getLightSky(dataPoint) + " " + + getGenerationMode(dataPoint) + " " + + isVoid(dataPoint) + " " + + doesItExist(dataPoint) + '\n'; + } + + public static void shrinkArray(short[] array, int packetSize, int start, int length, int arraySize) + { + start *= packetSize; + length *= packetSize; + arraySize *= packetSize; + for (int i = 0; i < arraySize - start; i++) + { + array[start + i] = array[start + length + i]; + //remove comment to not leave garbage at the end + //array[start + packetSize + i] = 0; + } + } + + public static void extendArray(short[] array, int packetSize, int start, int length, int arraySize) + { + start *= packetSize; + length *= packetSize; + arraySize *= packetSize; + for (int i = arraySize - start - 1; i >= 0; i--) + { + array[start + length + i] = array[start + i]; + array[start + i] = 0; + } + } + + /** + * This method merge column of multiple data together + * @param dataToMerge one or more columns of data + * @param inputVerticalData vertical size of an input data + * @param maxVerticalData max vertical size of the merged data + * @return one column of correctly parsed data + */ + public static long[] mergeMultiData(long[] dataToMerge, int inputVerticalData, int maxVerticalData) + { + int size = dataToMerge.length / inputVerticalData; + + // We initialize the arrays that are going to be used + short[] heightAndDepth = ThreadMapUtil.getHeightAndDepth((WORLD_HEIGHT / 2 + 1) * 2); + long[] dataPoint = ThreadMapUtil.getVerticalDataArray(DetailDistanceUtil.getMaxVerticalData(0)); + + + int genMode = DistanceGenerationMode.FULL.complexity; + boolean allEmpty = true; + boolean allVoid = true; + boolean allDefault; + long singleData; + + + short depth; + short height; + int count = 0; + int i; + int ii; + int dataIndex; + //We collect the indexes of the data, ordered by the depth + for (int index = 0; index < size; index++) + { + for (dataIndex = 0; dataIndex < inputVerticalData; dataIndex++) + { + singleData = dataToMerge[index * inputVerticalData + dataIndex]; + if (doesItExist(singleData)) + { + genMode = Math.min(genMode, getGenerationMode(singleData)); + allEmpty = false; + if (!isVoid(singleData)) + { + allVoid = false; + depth = getDepth(singleData); + height = getHeight(singleData); + + int botPos = -1; + int topPos = -1; + //values fall in between and possibly require extension of array + boolean botExtend = false; + boolean topExtend = false; + for (i = 0; i < count; i++) + { + if (depth <= heightAndDepth[i * 2] && depth >= heightAndDepth[i * 2 + 1]) + { + botPos = i; + break; + } + else if (depth < heightAndDepth[i * 2 + 1] && ((i + 1 < count && depth > heightAndDepth[(i + 1) * 2]) || i + 1 == count)) + { + botPos = i; + botExtend = true; + break; + } + } + for (i = 0; i < count; i++) + { + if (height <= heightAndDepth[i * 2] && height >= heightAndDepth[i * 2 + 1]) + { + topPos = i; + break; + } + else if (height < heightAndDepth[i * 2 + 1] && ((i + 1 < count && height > heightAndDepth[(i + 1) * 2]) || i + 1 == count)) + { + topPos = i; + topExtend = true; + break; + } + } + if (topPos == -1) + { + if (botPos == -1) + { + //whole block falls above + extendArray(heightAndDepth, 2, 0, 1, count); + heightAndDepth[0] = height; + heightAndDepth[1] = depth; + count++; + } + else if (!botExtend) + { + //only top falls above extending it there, while bottom is inside existing + shrinkArray(heightAndDepth, 2, 0, botPos, count); + heightAndDepth[0] = height; + count -= botPos; + } + else + { + //top falls between some blocks, extending those as well + shrinkArray(heightAndDepth, 2, 0, botPos, count); + heightAndDepth[0] = height; + heightAndDepth[1] = depth; + count -= botPos; + } + } + else if (!topExtend) + { + if (!botExtend) + //both top and bottom are within some exiting blocks, possibly merging them + heightAndDepth[topPos * 2 + 1] = heightAndDepth[botPos * 2 + 1]; + else + //top falls between some blocks, extending it there + heightAndDepth[topPos * 2 + 1] = depth; + shrinkArray(heightAndDepth, 2, topPos + 1, botPos - topPos, count); + count -= botPos - topPos; + } + else + { + if (!botExtend) + { + //only top is within some exiting block, extending it + topPos++; //to make it easier + heightAndDepth[topPos * 2] = height; + heightAndDepth[topPos * 2 + 1] = heightAndDepth[botPos * 2 + 1]; + shrinkArray(heightAndDepth, 2, topPos + 1, botPos - topPos, count); + count -= botPos - topPos; + } + else + { + //both top and bottom are outside existing blocks + shrinkArray(heightAndDepth, 2, topPos + 1, botPos - topPos, count); + count -= botPos - topPos; + extendArray(heightAndDepth, 2, topPos + 1, 1, count); + count++; + heightAndDepth[topPos * 2 + 2] = height; + heightAndDepth[topPos * 2 + 3] = depth; + } + } + } + } + else + break; + } + } + + //We check if there is any data that's not empty or void + if (allEmpty) + return dataPoint; + if (allVoid) + { + dataPoint[0] = createVoidDataPoint(genMode); + return dataPoint; + } + + //we limit the vertical portion to maxVerticalData + int j = 0; + while (count > maxVerticalData) + { + ii = WORLD_HEIGHT - VERTICAL_OFFSET; + for (i = 0; i < count - 1; i++) + { + if (heightAndDepth[i * 2 + 1] - heightAndDepth[(i + 1) * 2] <= ii) + { + ii = heightAndDepth[i * 2 + 1] - heightAndDepth[(i + 1) * 2]; + j = i; + } + } + heightAndDepth[j * 2 + 1] = heightAndDepth[(j + 1) * 2 + 1]; + for (i = j + 1; i < count - 1; i++) + { + heightAndDepth[i * 2] = heightAndDepth[(i + 1) * 2]; + heightAndDepth[i * 2 + 1] = heightAndDepth[(i + 1) * 2 + 1]; + } + //System.arraycopy(heightAndDepth, j + 1, heightAndDepth, j, count - j - 1); + count--; + } + //As standard the vertical lods are ordered from top to bottom + for (j = count - 1; j >= 0; j--) + { + height = heightAndDepth[j * 2]; + depth = heightAndDepth[j * 2 + 1]; + + if ((depth == 0 && height == 0) || j >= heightAndDepth.length / 2) + break; + + int numberOfChildren = 0; + int tempAlpha = 0; + int tempRed = 0; + int tempGreen = 0; + int tempBlue = 0; + int tempLightBlock = 0; + int tempLightSky = 0; + byte tempGenMode = DistanceGenerationMode.FULL.complexity; + allEmpty = true; + allVoid = true; + allDefault = true; + long data = 0; + + for (int index = 0; index < size; index++) + { + for (dataIndex = 0; dataIndex < inputVerticalData; dataIndex++) + { + singleData = dataToMerge[index * inputVerticalData + dataIndex]; + if (doesItExist(singleData) && !isVoid(singleData)) + { + + if ((depth <= getDepth(singleData) && getDepth(singleData) <= height) + || (depth <= getHeight(singleData) && getHeight(singleData) <= height)) + { + if (getHeight(singleData) > getHeight(data)) + data = singleData; + } + } + else + break; + } + if (!doesItExist(data)) + { + singleData = dataToMerge[index * inputVerticalData]; + data = createVoidDataPoint(getGenerationMode(singleData)); + } + + if (doesItExist(data)) + { + allEmpty = false; + if (!isVoid(data)) + { + numberOfChildren++; + allVoid = false; + tempAlpha += getAlpha(data); + tempRed += getRed(data); + tempGreen += getGreen(data); + tempBlue += getBlue(data); + tempLightBlock += getLightBlock(data); + tempLightSky += getLightSky(data); + if (!getFlag(data)) allDefault = false; + } + tempGenMode = (byte) Math.min(tempGenMode, getGenerationMode(data)); + } + else + tempGenMode = (byte) Math.min(tempGenMode, DistanceGenerationMode.NONE.complexity); + } + + if (allEmpty) + //no child has been initialized + dataPoint[j] = EMPTY_DATA; + else if (allVoid) + //all the children are void + dataPoint[j] = createVoidDataPoint(tempGenMode); + else + { + //we have at least 1 child + tempAlpha = tempAlpha / numberOfChildren; + tempRed = tempRed / numberOfChildren; + tempGreen = tempGreen / numberOfChildren; + tempBlue = tempBlue / numberOfChildren; + tempLightBlock = tempLightBlock / numberOfChildren; + tempLightSky = tempLightSky / numberOfChildren; + dataPoint[j] = createDataPoint(tempAlpha, tempRed, tempGreen, tempBlue, height, depth, tempLightSky, tempLightBlock, tempGenMode, allDefault); + } + } + return dataPoint; + } +} \ No newline at end of file diff --git a/src/main/java/com/seibel/lod/core/util/DetailDistanceUtil.java b/src/main/java/com/seibel/lod/core/util/DetailDistanceUtil.java new file mode 100644 index 000000000..67793fa3f --- /dev/null +++ b/src/main/java/com/seibel/lod/core/util/DetailDistanceUtil.java @@ -0,0 +1,174 @@ +/* + * This file is part of the Distant Horizon mod (formerly the LOD Mod), + * licensed under the GNU GPL v3 License. + * + * Copyright (C) 2020 James Seibel + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.seibel.lod.core.util; + +import com.seibel.lod.core.enums.config.DistanceGenerationMode; +import com.seibel.lod.core.enums.config.HorizontalQuality; +import com.seibel.lod.core.enums.config.HorizontalResolution; +import com.seibel.lod.core.wrapperInterfaces.config.ILodConfigWrapperSingleton; +import com.seibel.lod.core.wrapperInterfaces.minecraft.IMinecraftRenderWrapper; + +/** + * + * @author Leonardo Amato + * @version ?? + */ +public class DetailDistanceUtil +{ + private static final ILodConfigWrapperSingleton CONFIG = SingletonHandler.get(ILodConfigWrapperSingleton.class); + private static final IMinecraftRenderWrapper MC_RENDER = SingletonHandler.get(IMinecraftRenderWrapper.class); + + private static final double genMultiplier = 1.0; + private static final double treeGenMultiplier = 1.0; + private static final double treeCutMultiplier = 1.0; + private static byte minGenDetail = CONFIG.client().graphics().quality().getDrawResolution().detailLevel; + private static byte minDrawDetail = CONFIG.client().graphics().quality().getDrawResolution().detailLevel; + private static final int maxDetail = LodUtil.REGION_DETAIL_LEVEL + 1; + private static final int minDistance = 0; + private static int minDetailDistance = (int) (MC_RENDER.getRenderDistance()*16 * 1.42f); + private static int maxDistance = CONFIG.client().graphics().quality().getLodChunkRenderDistance() * 16 * 2; + + + private static final HorizontalResolution[] lodGenDetails = { + HorizontalResolution.BLOCK, + HorizontalResolution.TWO_BLOCKS, + HorizontalResolution.FOUR_BLOCKS, + HorizontalResolution.HALF_CHUNK, + HorizontalResolution.CHUNK, + HorizontalResolution.CHUNK, + HorizontalResolution.CHUNK, + HorizontalResolution.CHUNK, + HorizontalResolution.CHUNK, + HorizontalResolution.CHUNK, + HorizontalResolution.CHUNK }; + + + + public static void updateSettings() + { + minDetailDistance = (int) (MC_RENDER.getRenderDistance()*16 * 1.42f); + minGenDetail = CONFIG.client().graphics().quality().getDrawResolution().detailLevel; + minDrawDetail = (byte) Math.max(CONFIG.client().graphics().quality().getDrawResolution().detailLevel, CONFIG.client().graphics().quality().getDrawResolution().detailLevel); + maxDistance = CONFIG.client().graphics().quality().getLodChunkRenderDistance() * 16 * 8; + } + + public static int baseDistanceFunction(int detail) + { + if (detail <= minGenDetail) + return minDistance; + if (detail >= maxDetail) + return maxDistance; + + if (CONFIG.client().graphics().advancedGraphics().getAlwaysDrawAtMaxQuality()) + return detail * 0x10000; //if you want more you are doing wrong + + int distanceUnit = CONFIG.client().graphics().quality().getHorizontalScale() * 16; + if (CONFIG.client().graphics().quality().getHorizontalQuality() == HorizontalQuality.LOWEST) + return (detail * distanceUnit); + else + { + double base = CONFIG.client().graphics().quality().getHorizontalQuality().quadraticBase; + return (int) (Math.pow(base, detail) * distanceUnit); + } + } + + public static int getDrawDistanceFromDetail(int detail) + { + return baseDistanceFunction(detail); + } + + public static byte baseInverseFunction(int distance, byte minDetail, boolean useRenderMinDistance) + { + int detail; + if (distance == 0 + || (distance < minDetailDistance && useRenderMinDistance) + || CONFIG.client().graphics().advancedGraphics().getAlwaysDrawAtMaxQuality()) + return minDetail; + int distanceUnit = CONFIG.client().graphics().quality().getHorizontalScale() * 16; + if (CONFIG.client().graphics().quality().getHorizontalQuality() == HorizontalQuality.LOWEST) + detail = (byte) distance / distanceUnit; + else + { + double base = CONFIG.client().graphics().quality().getHorizontalQuality().quadraticBase; + double logBase = Math.log(base); + //noinspection IntegerDivisionInFloatingPointContext + detail = (byte) (Math.log(distance / distanceUnit) / logBase); + } + return (byte) LodUtil.clamp(minDetail, detail, maxDetail - 1); + } + + public static byte getDrawDetailFromDistance(int distance) + { + return baseInverseFunction(distance, minDrawDetail, false); + } + + public static byte getGenerationDetailFromDistance(int distance) + { + return baseInverseFunction((int) (distance * genMultiplier), minGenDetail, true); + } + + public static byte getTreeCutDetailFromDistance(int distance) + { + return baseInverseFunction((int) (distance * treeCutMultiplier), minGenDetail, true); + } + + public static byte getTreeGenDetailFromDistance(int distance) + { + return baseInverseFunction((int) (distance * treeGenMultiplier), minGenDetail, true); + } + + public static DistanceGenerationMode getDistanceGenerationMode(int detail) + { + return CONFIG.client().worldGenerator().getDistanceGenerationMode(); + } + + public static byte getLodDrawDetail(byte detail) + { + detail += minDrawDetail; + if (detail > 10) + detail = 10; + return detail; + } + + public static HorizontalResolution getLodGenDetail(int detail) + { + if (detail < minGenDetail) + return lodGenDetails[minGenDetail]; + else + return lodGenDetails[detail]; + } + + + public static byte getCutLodDetail(int detail) + { + if (detail < minGenDetail) + return lodGenDetails[minGenDetail].detailLevel; + else if (detail == maxDetail) + return LodUtil.REGION_DETAIL_LEVEL; + else + return lodGenDetails[detail].detailLevel; + } + + public static int getMaxVerticalData(int detail) + { + return CONFIG.client().graphics().quality().getVerticalQuality().maxVerticalData[LodUtil.clamp(minGenDetail, detail, LodUtil.REGION_DETAIL_LEVEL)]; + } + +} diff --git a/src/main/java/com/seibel/lod/core/util/LevelPosUtil.java b/src/main/java/com/seibel/lod/core/util/LevelPosUtil.java new file mode 100644 index 000000000..2dfe29440 --- /dev/null +++ b/src/main/java/com/seibel/lod/core/util/LevelPosUtil.java @@ -0,0 +1,269 @@ +/* + * This file is part of the Distant Horizon mod (formerly the LOD Mod), + * licensed under the GNU GPL v3 License. + * + * Copyright (C) 2020 James Seibel + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.seibel.lod.core.util; + +/** + * + * @author Leonardo Amato + * @version ?? + */ +public class LevelPosUtil +{ + public static int[] convert(int[] levelPos, byte newDetailLevel) + { + return convert(getDetailLevel(levelPos), getPosX(levelPos), getPosZ(levelPos), newDetailLevel); + } + + public static int[] convert(byte detailLevel, int posX, int posZ, byte newDetailLevel) + { + int width; + if (newDetailLevel >= detailLevel) + { + width = 1 << (newDetailLevel - detailLevel); + return createLevelPos( + newDetailLevel, + Math.floorDiv(posX, width), + Math.floorDiv(posZ, width)); + } + else + { + width = 1 << (detailLevel - newDetailLevel); + return createLevelPos( + newDetailLevel, + posX * width, + posZ * width); + } + } + + public static int[] createLevelPos(byte detailLevel, int posX, int posZ) + { + return new int[] { detailLevel, posX, posZ }; + } + + public static int convert(byte detailLevel, int pos, byte newDetailLevel) + { + int width; + if (newDetailLevel >= detailLevel) + { + width = 1 << (newDetailLevel - detailLevel); + return Math.floorDiv(pos, width); + } + else + { + width = 1 << (detailLevel - newDetailLevel); + return pos * width; + } + } + + public static int getRegion(byte detailLevel, int pos) + { + return Math.floorDiv(pos, 1 << (LodUtil.REGION_DETAIL_LEVEL - detailLevel)); + } + + public static int getRegionModule(byte detailLevel, int pos) + { + return Math.floorMod(pos, 1 << (LodUtil.REGION_DETAIL_LEVEL - detailLevel)); + } + + public static byte getDetailLevel(int[] levelPos) + { + return (byte) levelPos[0]; + } + + public static int getPosX(int[] levelPos) + { + return levelPos[1]; + } + + public static int getPosZ(int[] levelPos) + { + return levelPos[2]; + } + + public static int getDistance(int[] levelPos) + { + return levelPos[3]; + } + + public static int[] getRegionModule(int[] levelPos) + { + return getRegionModule(getDetailLevel(levelPos), getPosX(levelPos), getPosZ(levelPos)); + } + + public static int[] getRegionModule(byte detailLevel, int posX, int posZ) + { + int width = 1 << (LodUtil.REGION_DETAIL_LEVEL - detailLevel); + return createLevelPos( + detailLevel, + Math.floorMod(posX, width), + Math.floorMod(posZ, width)); + } + + public static int[] applyOffset(int[] levelPos, int xOffset, int zOffset) + { + return createLevelPos( + getDetailLevel(levelPos), + getPosX(levelPos) + xOffset, + getPosZ(levelPos) + zOffset); + } + + public static int[] applyLevelOffset(int[] levelPos, byte detailOffset, int xOffset, int zOffset) + { + return createLevelPos( + getDetailLevel(levelPos), + getPosX(levelPos) + xOffset * (1 << detailOffset), + getPosZ(levelPos) + zOffset * (1 << detailOffset)); + } + + public static int getRegionPosX(int[] levelPos) + { + int width = 1 << (LodUtil.REGION_DETAIL_LEVEL - getDetailLevel(levelPos)); + return Math.floorDiv(getPosX(levelPos), width); + } + + public static int getRegionPosZ(int[] levelPos) + { + int width = 1 << (LodUtil.REGION_DETAIL_LEVEL - getDetailLevel(levelPos)); + return Math.floorDiv(getPosZ(levelPos), width); + } + + public static int getChunkPos(byte detailLevel, int pos) + { + return convert(detailLevel, pos, LodUtil.CHUNK_DETAIL_LEVEL); + } + + public static int myPow2(int x) + { + return x*x; + } + + public static int maxDistance(byte detailLevel, int posX, int posZ, int playerPosX, int playerPosZ) + { + int width = 1 << detailLevel; + + int startPosX = posX * width; + int startPosZ = posZ * width; + int endPosX = myPow2(playerPosX - startPosX - width); + int endPosZ = myPow2(playerPosZ - startPosZ - width); + startPosX = myPow2(playerPosX - startPosX); + startPosZ = myPow2(playerPosZ - startPosZ); + + int maxDistance = (int) Math.sqrt(startPosX + startPosZ); + maxDistance = Math.max(maxDistance, (int) Math.sqrt(startPosX + endPosZ)); + maxDistance = Math.max(maxDistance, (int) Math.sqrt(endPosX + startPosZ)); + maxDistance = Math.max(maxDistance, (int) Math.sqrt(endPosX + endPosZ)); + + return maxDistance; + } + + public static int maxDistance(byte detailLevel, int posX, int posZ, int playerPosX, int playerPosZ, int xRegion, int zRegion) + { + int width = 1 << (LodUtil.REGION_DETAIL_LEVEL - detailLevel); + int newPosX = xRegion * width + posX; + int newPosZ = zRegion * width + posZ; + return maxDistance(detailLevel, newPosX, newPosZ, playerPosX, playerPosZ); + } + + + public static int minDistance(byte detailLevel, int posX, int posZ, int playerPosX, int playerPosZ) + { + int width = 1 << detailLevel; + + int startPosX = posX * width; + int startPosZ = posZ * width; + int endPosX = startPosX + width; + int endPosZ = startPosZ + width; + + boolean inXArea = playerPosX >= startPosX && playerPosX <= endPosX; + boolean inZArea = playerPosZ >= startPosZ && playerPosZ <= endPosZ; + if (inXArea && inZArea) + return 0; + else if (inXArea) + { + return Math.min( + Math.abs(playerPosZ - startPosZ), + Math.abs(playerPosZ - endPosZ) + ); + } + else if (inZArea) + { + return Math.min( + Math.abs(playerPosX - startPosX), + Math.abs(playerPosX - endPosX) + ); + } + else + { + startPosX = myPow2(playerPosX - startPosX); + startPosZ = myPow2(playerPosZ - startPosZ); + endPosX = myPow2(playerPosX - endPosX); + endPosZ = myPow2(playerPosZ - endPosZ); + + int minDistance = (int) Math.sqrt(startPosX + startPosZ); + minDistance = Math.min(minDistance, (int) Math.sqrt(startPosX + endPosZ)); + minDistance = Math.min(minDistance, (int) Math.sqrt(endPosX + startPosZ)); + minDistance = Math.min(minDistance, (int) Math.sqrt(endPosX + endPosZ)); + return minDistance; + } + } + + public static int minDistance(byte detailLevel, int posX, int posZ, int playerPosX, int playerPosZ, int xRegion, int zRegion) + { + int width = 1 << (LodUtil.REGION_DETAIL_LEVEL - detailLevel); + int newPosX = xRegion * width + posX; + int newPosZ = zRegion * width + posZ; + return minDistance(detailLevel, newPosX, newPosZ, playerPosX, playerPosZ); + } + + public static int compareDistance(int firstDistance, int secondDistance) + { + return Integer.compare( + firstDistance, + secondDistance); + } + + + public static int compareLevelAndDistance(byte firstDetail, int firstDistance, byte secondDetail, int secondDistance) + { + int compareResult = Integer.compare( + secondDetail, + firstDetail); + if (compareResult == 0) + { + compareResult = Integer.compare( + firstDistance, + secondDistance); + } + return compareResult; + } + + @SuppressWarnings("unused") + public static String toString(int[] levelPos) + { + return (getDetailLevel(levelPos) + " " + + getPosX(levelPos) + " " + + getPosZ(levelPos)); + } + + public static String toString(byte detailLevel, int posX, int posZ) + { + return (detailLevel + " " + posX + " " + posZ); + } +} diff --git a/src/main/java/com/seibel/lod/core/util/LodThreadFactory.java b/src/main/java/com/seibel/lod/core/util/LodThreadFactory.java new file mode 100644 index 000000000..c12e8e0ff --- /dev/null +++ b/src/main/java/com/seibel/lod/core/util/LodThreadFactory.java @@ -0,0 +1,46 @@ +/* + * This file is part of the Distant Horizon mod (formerly the LOD Mod), + * licensed under the GNU GPL v3 License. + * + * Copyright (C) 2020 James Seibel + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.seibel.lod.core.util; + +import java.util.concurrent.ThreadFactory; + +/** + * Just a simple ThreadFactory to name ExecutorService + * threads, which can be helpful when debugging. + * @author James Seibel + * @version 8-15-2021 + */ +public class LodThreadFactory implements ThreadFactory +{ + public final String threadName; + + + public LodThreadFactory(String newThreadName) + { + threadName = newThreadName + " Thread"; + } + + @Override + public Thread newThread(Runnable r) + { + return new Thread(r, threadName); + } + +} diff --git a/src/main/java/com/seibel/lod/core/util/LodUtil.java b/src/main/java/com/seibel/lod/core/util/LodUtil.java new file mode 100644 index 000000000..f87c85f0a --- /dev/null +++ b/src/main/java/com/seibel/lod/core/util/LodUtil.java @@ -0,0 +1,427 @@ +/* + * This file is part of the Distant Horizon mod (formerly the LOD Mod), + * licensed under the GNU GPL v3 License. + * + * Copyright (C) 2020 James Seibel + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.seibel.lod.core.util; + +import java.awt.Color; +import java.io.File; +import java.util.HashSet; + +import com.seibel.lod.core.enums.LodDirection; +import com.seibel.lod.core.enums.config.HorizontalResolution; +import com.seibel.lod.core.enums.config.VanillaOverdraw; +import com.seibel.lod.core.objects.VertexOptimizer; +import com.seibel.lod.core.objects.lod.LodDimension; +import com.seibel.lod.core.objects.lod.RegionPos; +import com.seibel.lod.core.objects.opengl.DefaultLodVertexFormats; +import com.seibel.lod.core.objects.opengl.LodVertexFormat; +import com.seibel.lod.core.wrapperInterfaces.IWrapperFactory; +import com.seibel.lod.core.wrapperInterfaces.block.AbstractBlockPosWrapper; +import com.seibel.lod.core.wrapperInterfaces.chunk.AbstractChunkPosWrapper; +import com.seibel.lod.core.wrapperInterfaces.config.ILodConfigWrapperSingleton; +import com.seibel.lod.core.wrapperInterfaces.minecraft.IMinecraftRenderWrapper; +import com.seibel.lod.core.wrapperInterfaces.minecraft.IMinecraftWrapper; +import com.seibel.lod.core.wrapperInterfaces.world.IDimensionTypeWrapper; +import com.seibel.lod.core.wrapperInterfaces.world.IWorldWrapper; + +/** + * This class holds methods and constants that may be used in multiple places. + * + * @author James Seibel + * @version 11-13-2021 + */ +public class LodUtil +{ + private static final IMinecraftWrapper MC = SingletonHandler.get(IMinecraftWrapper.class); + private static final IMinecraftRenderWrapper MC_RENDER = SingletonHandler.get(IMinecraftRenderWrapper.class); + private static final ILodConfigWrapperSingleton CONFIG = SingletonHandler.get(ILodConfigWrapperSingleton.class); + private static final IWrapperFactory FACTORY = SingletonHandler.get(IWrapperFactory.class); + + /** + * Vanilla render distances less than or equal to this will not allow partial + * overdraw. The VanillaOverdraw will either be ALWAYS or NEVER. + */ + public static final int MINIMUM_RENDER_DISTANCE_FOR_PARTIAL_OVERDRAW = 4; + + /** + * Vanilla render distances less than or equal to this will cause the overdraw to + * run at a smaller fraction of the vanilla render distance. + */ + public static final int MINIMUM_RENDER_DISTANCE_FOR_FAR_OVERDRAW = 11; + + + + + /** The maximum number of LODs that can be rendered vertically */ + public static final int MAX_NUMBER_OF_VERTICAL_LODS = 32; + + /** + * alpha used when drawing chunks in debug mode + */ + public static final int DEBUG_ALPHA = 255; // 0 - 255 + public static final Color COLOR_DEBUG_BLACK = new Color(0, 0, 0, DEBUG_ALPHA); + public static final Color COLOR_DEBUG_WHITE = new Color(255, 255, 255, DEBUG_ALPHA); + public static final Color COLOR_INVISIBLE = new Color(0, 0, 0, 0); + + public static final int CEILED_DIMENSION_MAX_RENDER_DISTANCE = 64; // 0 - 255 + + /** + * In order of nearest to farthest:
+ * Red, Orange, Yellow, Green, Cyan, Blue, Magenta, white, gray, black + */ + public static final Color[] DEBUG_DETAIL_LEVEL_COLORS = new Color[] { Color.RED, Color.ORANGE, Color.YELLOW, Color.GREEN, Color.CYAN, Color.BLUE, Color.MAGENTA, Color.WHITE, Color.GRAY, Color.BLACK }; + + + public static final byte DETAIL_OPTIONS = 10; + + /** 512 blocks wide */ + public static final byte REGION_DETAIL_LEVEL = DETAIL_OPTIONS - 1; + /** 16 blocks wide */ + public static final byte CHUNK_DETAIL_LEVEL = 4; + /** 1 block wide */ + public static final byte BLOCK_DETAIL_LEVEL = 0; + + public static final short MAX_VERTICAL_DATA = 4; + + /** + * measured in Blocks
+ * detail level max - 1 + */ + public static final short REGION_WIDTH = 1 << REGION_DETAIL_LEVEL; + /** + * measured in Blocks
+ * detail level 4 + */ + public static final short CHUNK_WIDTH = 16; + /** + * measured in Blocks
+ * detail level 0 + */ + public static final short BLOCK_WIDTH = 1; + + + /** number of chunks wide */ + public static final int REGION_WIDTH_IN_CHUNKS = REGION_WIDTH / CHUNK_WIDTH; + + + /** + * This regex finds any characters that are invalid for use in a windows + * (and by extension mac and linux) file path + */ + public static final String INVALID_FILE_CHARACTERS_REGEX = "[\\\\/:*?\"<>|]"; + + /** + * 64 MB by default is the maximum amount of memory that + * can be directly allocated.

+ *

+ * James knows there are commands to change that amount + * (specifically "-XX:MaxDirectMemorySize"), but + * He has no idea how to access that amount.
+ * So for now this will be the hard limit.

+ *

+ * https://stackoverflow.com/questions/50499238/bytebuffer-allocatedirect-and-xmx + */ + public static final int MAX_ALLOCATABLE_DIRECT_MEMORY = 64 * 1024 * 1024; + + /** the format of data stored in the GPU buffers */ + public static final LodVertexFormat LOD_VERTEX_FORMAT = DefaultLodVertexFormats.POSITION_COLOR; + + + + + + /** + * Gets the ServerWorld for the relevant dimension. + * @return null if there is no ServerWorld for the given dimension + */ + public static IWorldWrapper getServerWorldFromDimension(IDimensionTypeWrapper newDimension) + { + if(!MC.hasSinglePlayerServer()) + return null; + + Iterable worlds = MC.getAllServerWorlds(); + IWorldWrapper returnWorld = null; + + for (IWorldWrapper world : worlds) + { + if (world.getDimensionType() == newDimension) + { + returnWorld = world; + break; + } + } + + return returnWorld; + } + + /** Convert a 2D absolute position into a quad tree relative position. */ + public static RegionPos convertGenericPosToRegionPos(int x, int z, int detailLevel) + { + int relativePosX = Math.floorDiv(x, 1 << (LodUtil.REGION_DETAIL_LEVEL - detailLevel)); + int relativePosZ = Math.floorDiv(z, 1 << (LodUtil.REGION_DETAIL_LEVEL - detailLevel)); + + return new RegionPos(relativePosX, relativePosZ); + } + + /** Convert a 2D absolute position into a quad tree relative position. */ + public static int convertLevelPos(int pos, int currentDetailLevel, int targetDetailLevel) + { + return pos / (1 << (targetDetailLevel - currentDetailLevel)); + } + + + /** + * If on single player this will return the name of the user's + * world, if in multiplayer it will return the server name, IP, + * and game version. + */ + public static String getWorldID(IWorldWrapper 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 + { + return getServerId(); + } + } + + + /** + * 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, ip, game version, and dimension.
+ *
+ * This can be used to determine where to save files for a given + * dimension. + */ + public static String getDimensionIDFromWorld(IWorldWrapper world) + { + if (MC.hasSinglePlayerServer()) + { + // this will return the world save location + // and the dimension folder + + IWorldWrapper serverWorld = LodUtil.getServerWorldFromDimension(world.getDimensionType()); + if (serverWorld == null) + throw new NullPointerException("getDimensionIDFromWorld wasn't able to get the WorldWrapper for the dimension " + world.getDimensionType().getDimensionName()); + + return serverWorld.getSaveFolder().toString(); + } + else + { + return getServerId() + File.separatorChar + "dim_" + world.getDimensionType().getDimensionName() + File.separatorChar; + } + } + + /** returns the server name, IP and game version. */ + public static String getServerId() + { + String serverName = MC.getCurrentServerName().replaceAll(INVALID_FILE_CHARACTERS_REGEX, ""); + String serverIp = MC.getCurrentServerIp().replaceAll(INVALID_FILE_CHARACTERS_REGEX, ""); + String serverMcVersion = MC.getCurrentServerVersion().replaceAll(INVALID_FILE_CHARACTERS_REGEX, ""); + + return serverName + ", IP " + serverIp + ", GameVersion " + serverMcVersion; + } + + + /** Convert a BlockColors int into a Color object */ + public static Color intToColor(int num) + { + int filter = 0b11111111; + + int red = (num >> 16) & filter; + int green = (num >> 8) & filter; + int blue = num & filter; + + return new Color(red, green, blue); + } + + /** Convert a Color into a BlockColors object. */ + public static int colorToInt(Color color) + { + return color.getRGB(); + } + + + /** + * Clamps the given value between the min and max values. + * May behave strangely if min > max. + */ + public static int clamp(int min, int value, int max) + { + return Math.min(max, Math.max(value, min)); + } + + /** + * Clamps the given value between the min and max values. + * May behave strangely if min > max. + */ + public static float clamp(float min, float value, float max) + { + return Math.min(max, Math.max(value, min)); + } + + /** + * Clamps the given value between the min and max values. + * May behave strangely if min > max. + */ + public static double clamp(double min, double value, double max) + { + return Math.min(max, Math.max(value, min)); + } + + /** + * Get a HashSet of all ChunkPos within the normal render distance + * that should not be rendered. + */ + public static HashSet getNearbyLodChunkPosToSkip(LodDimension lodDim, AbstractBlockPosWrapper blockPosWrapper) + { + int chunkRenderDist = MC_RENDER.getRenderDistance(); + AbstractChunkPosWrapper centerChunk = FACTORY.createChunkPos(blockPosWrapper); + + int skipRadius; + VanillaOverdraw overdraw = CONFIG.client().graphics().advancedGraphics().getVanillaOverdraw(); + HorizontalResolution drawRes = CONFIG.client().graphics().quality().getDrawResolution(); + + // apply distance based rules for dynamic overdraw + if (overdraw == VanillaOverdraw.DYNAMIC + && chunkRenderDist <= MINIMUM_RENDER_DISTANCE_FOR_PARTIAL_OVERDRAW) + { + // The vanilla render distance isn't far enough + // for partial skipping to make sense... + if (!lodDim.dimension.hasCeiling() && (drawRes == HorizontalResolution.BLOCK)) + { + // ...and the dimension is open, so we don't have to worry about + // LODs rendering on top of the player, + // and the user is using a high horizontal resolution, + // so the overdraw shouldn't be noticeable + overdraw = VanillaOverdraw.ALWAYS; + } + else + { + // ...but we are underground, so we don't want + // LODs rendering on top of the player, + // Or the user is using a LOW horizontal resolution + // and overdraw would be very noticeable. + overdraw = VanillaOverdraw.NEVER; + } + } + + + // determine the skipping type based + // on the overdraw type + switch (overdraw) + { + case ALWAYS: + // don't skip any positions + return new HashSet<>(); + + case DYNAMIC: + + if (chunkRenderDist > MINIMUM_RENDER_DISTANCE_FOR_PARTIAL_OVERDRAW + && chunkRenderDist <= MINIMUM_RENDER_DISTANCE_FOR_FAR_OVERDRAW) + { + // This is a small render distance (but greater than the minimum partial + // distance), skip positions that are greater than 2/3 the render distance + skipRadius = (int) Math.ceil(chunkRenderDist * (2.0/3.0)); + } + else + { + // This is a large render distance. Skip positions that are greater than + // 4/5ths the render distance + skipRadius = (int) Math.ceil(chunkRenderDist * (4.0 / 5.0)); + } + break; + + default: + case BORDER: + case NEVER: + // skip chunks in render distance that are rendered + // by vanilla minecraft + skipRadius = 0; + break; + } + + + // get the chunks that are going to be rendered by Minecraft + HashSet posToSkip = MC_RENDER.getRenderedChunks(); + + + // remove everything outside the skipRadius, + // if the skipRadius is being used + if (skipRadius != 0) + { + for (int x = centerChunk.getX() - chunkRenderDist; x < centerChunk.getX() + chunkRenderDist; x++) + { + for (int z = centerChunk.getZ() - chunkRenderDist; z < centerChunk.getZ() + chunkRenderDist; z++) + { + if (x <= centerChunk.getX() - skipRadius || x >= centerChunk.getX() + skipRadius + || z <= centerChunk.getZ() - skipRadius || z >= centerChunk.getZ() + skipRadius) + posToSkip.remove(FACTORY.createChunkPos(x, z)); + } + } + } + return posToSkip; + } + + + /** + * This method find if a given chunk is a border chunk of the renderable ones + * @param vanillaRenderedChunks matrix of the vanilla rendered chunks + * @param x relative (to the matrix) x chunk to check + * @param z relative (to the matrix) z chunk to check + * @return true if and only if the chunk is a border of the renderable chunks + */ + public static boolean isBorderChunk(boolean[][] vanillaRenderedChunks, int x, int z) + { + if (x < 0 || z < 0 || x >= vanillaRenderedChunks.length || z >= vanillaRenderedChunks[0].length) + return false; + int tempX; + int tempZ; + for (LodDirection lodDirection : VertexOptimizer.ADJ_DIRECTIONS) + { + tempX = x + VertexOptimizer.DIRECTION_NORMAL_MAP.get(lodDirection).x; + tempZ = z + VertexOptimizer.DIRECTION_NORMAL_MAP.get(lodDirection).z; + if (vanillaRenderedChunks[x][z] || (!(tempX < 0 || tempZ < 0 || tempX >= vanillaRenderedChunks.length || tempZ >= vanillaRenderedChunks[0].length) + && !vanillaRenderedChunks[tempX][tempZ])) + return true; + } + return false; + } + + + /** This is copied from Minecraft's MathHelper class */ + public static float fastInvSqrt(float numb) + { + float half = 0.5F * numb; + int i = Float.floatToIntBits(numb); + i = 1597463007 - (i >> 1); + numb = Float.intBitsToFloat(i); + return numb * (1.5F - half * numb * numb); + } +} diff --git a/src/main/java/com/seibel/lod/core/util/SingletonHandler.java b/src/main/java/com/seibel/lod/core/util/SingletonHandler.java new file mode 100644 index 000000000..1770aef20 --- /dev/null +++ b/src/main/java/com/seibel/lod/core/util/SingletonHandler.java @@ -0,0 +1,98 @@ +/* + * This file is part of the Distant Horizon mod (formerly the LOD Mod), + * licensed under the GNU GPL v3 License. + * + * Copyright (C) 2020 James Seibel + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.seibel.lod.core.util; + +import java.util.HashMap; +import java.util.Map; + +/** + * This class takes care of dependency injection + * for singletons. + * + * @author James Seibel + * @version 11-20-2021 + */ +public class SingletonHandler +{ + private static final Map, Object> singletons = new HashMap, Object>(); + + + + + + /** + * Adds the given singleton so it can be referenced later. + * + * @param interfaceClass + * @param singletonReference + * @throws IllegalStateException + */ + public static void bind(Class interfaceClass, Object singletonReference) throws IllegalStateException + { + // make sure we haven't already bound this singleton + if (singletons.containsKey(interfaceClass)) + { + throw new IllegalStateException("The singleton [" + interfaceClass.getSimpleName() + "] has already been bound."); + } + + + // make sure the given singleton implements the interface + boolean singletonImplementsInterface = false; + for (Class singletonInterface : singletonReference.getClass().getInterfaces()) + { + if (singletonInterface.equals(interfaceClass)) + { + singletonImplementsInterface = true; + break; + } + } + if (!singletonImplementsInterface) + { + throw new IllegalStateException("The singleton [" + interfaceClass.getSimpleName() + "] doesn't implement the interface [" + interfaceClass.getSimpleName() + "]."); + } + + + singletons.put(interfaceClass, singletonReference); + } + + /** + * Returns a singleton of type T + * if one has been bound. + * + * @param class of the singleton + * @param objectClass class of the singleton, but as a parameter! + * @return the singleton of type T + * @throws NullPointerException if no singleton of type T has been bound. + * @throws ClassCastException if the singleton isn't able to be cast to type T. (this shouldn't normally happen, unless the bound object changed somehow) + */ + @SuppressWarnings("unchecked") + public static T get(Class objectClass) throws NullPointerException, ClassCastException + { + // throw an error if the given singleton doesn't exist. + if (!singletons.containsKey(objectClass)) + { + throw new NullPointerException("The singleton [" + objectClass.getSimpleName() + "] was never bound. If you are calling [bind], make sure it is happening before you call [get]."); + } + + + return (T) singletons.get(objectClass); + } + +} diff --git a/src/main/java/com/seibel/lod/core/util/ThreadMapUtil.java b/src/main/java/com/seibel/lod/core/util/ThreadMapUtil.java new file mode 100644 index 000000000..7cf8405b3 --- /dev/null +++ b/src/main/java/com/seibel/lod/core/util/ThreadMapUtil.java @@ -0,0 +1,218 @@ +/* + * This file is part of the Distant Horizon mod (formerly the LOD Mod), + * licensed under the GNU GPL v3 License. + * + * Copyright (C) 2020 James Seibel + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.seibel.lod.core.util; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + +import com.seibel.lod.core.enums.LodDirection; +import com.seibel.lod.core.objects.VertexOptimizer; + +/** + * Holds data used by specific threads so + * the data doesn't have to be recreated every + * time it is needed. + * + * @author Leonardo Amato + * @version 9-25-2021 + */ +public class ThreadMapUtil +{ + public static final ConcurrentMap threadSingleUpdateMap = new ConcurrentHashMap<>(); + public static final ConcurrentMap threadBuilderArrayMap = new ConcurrentHashMap<>(); + public static final ConcurrentMap threadBuilderVerticalArrayMap = new ConcurrentHashMap<>(); + public static final ConcurrentMap threadVerticalAddDataMap = new ConcurrentHashMap<>(); + public static final ConcurrentMap saveContainer = new ConcurrentHashMap<>(); + public static final ConcurrentMap projectionArrayMap = new ConcurrentHashMap<>(); + public static final ConcurrentMap heightAndDepthMap = new ConcurrentHashMap<>(); + public static final ConcurrentMap singleDataToMergeMap = new ConcurrentHashMap<>(); + public static final ConcurrentMap verticalUpdate = new ConcurrentHashMap<>(); + + + //________________________// + // used in BufferBuilder // + //________________________// + + public static final ConcurrentMap adjShadeDisabled = new ConcurrentHashMap<>(); + public static final ConcurrentMap> adjDataMap = new ConcurrentHashMap<>(); + public static final ConcurrentMap boxMap = new ConcurrentHashMap<>(); + + + + /** returns the array NOT cleared every time */ + public static boolean[] getAdjShadeDisabledArray() + { + if (!adjShadeDisabled.containsKey(Thread.currentThread().getName()) + || (adjShadeDisabled.get(Thread.currentThread().getName()) == null)) + { + adjShadeDisabled.put(Thread.currentThread().getName(), new boolean[VertexOptimizer.DIRECTIONS.length]); + } + Arrays.fill(adjShadeDisabled.get(Thread.currentThread().getName()), false); + return adjShadeDisabled.get(Thread.currentThread().getName()); + } + + /** returns the array NOT cleared every time */ + public static Map getAdjDataArray(int verticalData) + { + if (!adjDataMap.containsKey(Thread.currentThread().getName()) + || (adjDataMap.get(Thread.currentThread().getName()) == null) + || (adjDataMap.get(Thread.currentThread().getName()).get(LodDirection.NORTH) == null) + || (adjDataMap.get(Thread.currentThread().getName()).get(LodDirection.NORTH).length != verticalData)) + { + adjDataMap.put(Thread.currentThread().getName(), new HashMap<>()); + adjDataMap.get(Thread.currentThread().getName()).put(LodDirection.UP, new long[1]); + adjDataMap.get(Thread.currentThread().getName()).put(LodDirection.DOWN, new long[1]); + for (LodDirection lodDirection : VertexOptimizer.ADJ_DIRECTIONS) + adjDataMap.get(Thread.currentThread().getName()).put(lodDirection, new long[verticalData]); + } + else + { + + for (LodDirection lodDirection : VertexOptimizer.ADJ_DIRECTIONS) + Arrays.fill(adjDataMap.get(Thread.currentThread().getName()).get(lodDirection), DataPointUtil.EMPTY_DATA); + } + return adjDataMap.get(Thread.currentThread().getName()); + } + + public static VertexOptimizer getBox() + { + if (!boxMap.containsKey(Thread.currentThread().getName()) + || (boxMap.get(Thread.currentThread().getName()) == null)) + { + boxMap.put(Thread.currentThread().getName(), new VertexOptimizer()); + } + boxMap.get(Thread.currentThread().getName()).reset(); + return boxMap.get(Thread.currentThread().getName()); + } + + //________________________// + // used in DataPointUtil // + // mergeVerticalData // + //________________________// + + + //________________________// + // used in DataPointUtil // + // mergeSingleData // + //________________________// + + + + /** returns the array filled with 0's */ + public static long[] getBuilderVerticalArray(int detailLevel) + { + if (!threadBuilderVerticalArrayMap.containsKey(Thread.currentThread().getName()) || (threadBuilderVerticalArrayMap.get(Thread.currentThread().getName()) == null)) + { + long[][] array = new long[5][]; + int size; + for (int i = 0; i < 5; i++) + { + size = 1 << i; + array[i] = new long[size * size * (DataPointUtil.WORLD_HEIGHT / 2 + 1)]; + } + threadBuilderVerticalArrayMap.put(Thread.currentThread().getName(), array); + } + Arrays.fill(threadBuilderVerticalArrayMap.get(Thread.currentThread().getName())[detailLevel], 0); + return threadBuilderVerticalArrayMap.get(Thread.currentThread().getName())[detailLevel]; + } + + /** returns the array NOT cleared every time */ + public static byte[] getSaveContainer(int detailLevel) + { + if (!saveContainer.containsKey(Thread.currentThread().getName()) || (saveContainer.get(Thread.currentThread().getName()) == null)) + { + byte[][] array = new byte[LodUtil.DETAIL_OPTIONS][]; + int size = 1; + for (int i = LodUtil.DETAIL_OPTIONS - 1; i >= 0; i--) + { + array[i] = new byte[2 + 8 * size * size * DetailDistanceUtil.getMaxVerticalData(i)]; + size = size << 1; + } + saveContainer.put(Thread.currentThread().getName(), array); + } + //Arrays.fill(threadBuilderVerticalArrayMap.get(Thread.currentThread().getName())[detailLevel], 0); + return saveContainer.get(Thread.currentThread().getName())[detailLevel]; + } + + + /** returns the array filled with 0's */ + public static long[] getVerticalDataArray(int arrayLength) + { + if (!threadVerticalAddDataMap.containsKey(Thread.currentThread().getName()) || (threadVerticalAddDataMap.get(Thread.currentThread().getName()) == null)) + { + threadVerticalAddDataMap.put(Thread.currentThread().getName(), new long[arrayLength]); + } + else + { + Arrays.fill(threadVerticalAddDataMap.get(Thread.currentThread().getName()), 0); + } + return threadVerticalAddDataMap.get(Thread.currentThread().getName()); + } + + + + /** returns the array NOT cleared every time */ + public static short[] getHeightAndDepth(int arrayLength) + { + if (!heightAndDepthMap.containsKey(Thread.currentThread().getName()) || (heightAndDepthMap.get(Thread.currentThread().getName()) == null)) + { + heightAndDepthMap.put(Thread.currentThread().getName(), new short[arrayLength]); + } + return heightAndDepthMap.get(Thread.currentThread().getName()); + } + + + /** returns the array filled with 0's */ + public static long[] getVerticalUpdateArray(int detailLevel) + { + if (!verticalUpdate.containsKey(Thread.currentThread().getName()) || (verticalUpdate.get(Thread.currentThread().getName()) == null)) + { + long[][] array = new long[LodUtil.DETAIL_OPTIONS][]; + for (int i = 1; i < LodUtil.DETAIL_OPTIONS; i++) + array[i] = new long[DetailDistanceUtil.getMaxVerticalData(i - 1) * 4]; + verticalUpdate.put(Thread.currentThread().getName(), array); + } + else + { + Arrays.fill(verticalUpdate.get(Thread.currentThread().getName())[detailLevel], 0); + } + return verticalUpdate.get(Thread.currentThread().getName())[detailLevel]; + } + + /** clears all arrays so they will have to be rebuilt */ + public static void clearMaps() + { + adjShadeDisabled.clear(); + adjDataMap.clear(); + boxMap.clear(); + threadSingleUpdateMap.clear(); + threadBuilderArrayMap.clear(); + threadBuilderVerticalArrayMap.clear(); + threadVerticalAddDataMap.clear(); + saveContainer.clear(); + projectionArrayMap.clear(); + heightAndDepthMap.clear(); + singleDataToMergeMap.clear(); + verticalUpdate.clear(); + } +} diff --git a/src/main/java/com/seibel/lod/core/wrapperInterfaces/IWrapperFactory.java b/src/main/java/com/seibel/lod/core/wrapperInterfaces/IWrapperFactory.java new file mode 100644 index 000000000..123aa33f7 --- /dev/null +++ b/src/main/java/com/seibel/lod/core/wrapperInterfaces/IWrapperFactory.java @@ -0,0 +1,48 @@ +/* + * This file is part of the Distant Horizon mod (formerly the LOD Mod), + * licensed under the GNU GPL v3 License. + * + * Copyright (C) 2020 James Seibel + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.seibel.lod.core.wrapperInterfaces; + +import com.seibel.lod.core.builders.lodBuilding.LodBuilder; +import com.seibel.lod.core.objects.lod.LodDimension; +import com.seibel.lod.core.wrapperInterfaces.block.AbstractBlockPosWrapper; +import com.seibel.lod.core.wrapperInterfaces.chunk.AbstractChunkPosWrapper; +import com.seibel.lod.core.wrapperInterfaces.world.IWorldWrapper; +import com.seibel.lod.core.wrapperInterfaces.worldGeneration.AbstractWorldGeneratorWrapper; + +/** + * This handles creating abstract wrapper objects. + * + * @author James Seibel + * @version 11-18-2021 + */ +public interface IWrapperFactory +{ + AbstractBlockPosWrapper createBlockPos(); + AbstractBlockPosWrapper createBlockPos(int x, int y, int z); + + + AbstractChunkPosWrapper createChunkPos(); + AbstractChunkPosWrapper createChunkPos(int x, int z); + AbstractChunkPosWrapper createChunkPos(AbstractChunkPosWrapper newChunkPos); + AbstractChunkPosWrapper createChunkPos(AbstractBlockPosWrapper blockPos); + + + AbstractWorldGeneratorWrapper createWorldGenerator(LodBuilder newLodBuilder, LodDimension newLodDimension, IWorldWrapper worldWrapper); +} diff --git a/src/main/java/com/seibel/lod/core/wrapperInterfaces/block/AbstractBlockPosWrapper.java b/src/main/java/com/seibel/lod/core/wrapperInterfaces/block/AbstractBlockPosWrapper.java new file mode 100644 index 000000000..a8cf65f6d --- /dev/null +++ b/src/main/java/com/seibel/lod/core/wrapperInterfaces/block/AbstractBlockPosWrapper.java @@ -0,0 +1,48 @@ +/* + * This file is part of the Distant Horizon mod (formerly the LOD Mod), + * licensed under the GNU GPL v3 License. + * + * Copyright (C) 2020 James Seibel + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.seibel.lod.core.wrapperInterfaces.block; + +import com.seibel.lod.core.enums.LodDirection; + +/** + * BlockPos needs to be abstract instead of an interfaces + * so that we can define its constructors. + * + * @author James Seibel + * @version 11-20-2021 + */ +public abstract class AbstractBlockPosWrapper +{ + public AbstractBlockPosWrapper() { } + public AbstractBlockPosWrapper(int x, int y, int z) { } + + + + public abstract void set(int x, int y, int z); + + public abstract int getX(); + public abstract int getY(); + public abstract int getZ(); + + public abstract int get(LodDirection.Axis axis); + + /** returns itself */ + public abstract AbstractBlockPosWrapper offset(int x, int y, int z); +} diff --git a/src/main/java/com/seibel/lod/core/wrapperInterfaces/block/IBlockColorSingletonWrapper.java b/src/main/java/com/seibel/lod/core/wrapperInterfaces/block/IBlockColorSingletonWrapper.java new file mode 100644 index 000000000..aeef4ba9d --- /dev/null +++ b/src/main/java/com/seibel/lod/core/wrapperInterfaces/block/IBlockColorSingletonWrapper.java @@ -0,0 +1,35 @@ +/* + * This file is part of the Distant Horizon mod (formerly the LOD Mod), + * licensed under the GNU GPL v3 License. + * + * Copyright (C) 2020 James Seibel + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.seibel.lod.core.wrapperInterfaces.block; + +/** + * Contains methods that would have been static in BlockColorWrapper. + * Since interfaces can't create/implement static methods we have + * to split the object up in two. + * + * @author James Seibel + * @version 11-17-2021 + */ +public interface IBlockColorSingletonWrapper +{ + /** @returns the base color of water (grey) */ + IBlockColorWrapper getWaterColor(); +} + diff --git a/src/main/java/com/seibel/lod/core/wrapperInterfaces/block/IBlockColorWrapper.java b/src/main/java/com/seibel/lod/core/wrapperInterfaces/block/IBlockColorWrapper.java new file mode 100644 index 000000000..7bc4f82dc --- /dev/null +++ b/src/main/java/com/seibel/lod/core/wrapperInterfaces/block/IBlockColorWrapper.java @@ -0,0 +1,50 @@ +/* + * This file is part of the Distant Horizon mod (formerly the LOD Mod), + * licensed under the GNU GPL v3 License. + * + * Copyright (C) 2020 James Seibel + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.seibel.lod.core.wrapperInterfaces.block; + +/** + * @author James Seibel + * @version 11-17-2021 + */ +public interface IBlockColorWrapper +{ + //--------------// + //Colors getters// + //--------------// + + boolean hasColor(); + + int getColor(); + + + //------------// + //Tint getters// + //------------// + + boolean hasTint(); + + boolean hasGrassTint(); + + boolean hasFolliageTint(); + + boolean hasWaterTint(); + +} + diff --git a/src/main/java/com/seibel/lod/core/wrapperInterfaces/block/IBlockShapeWrapper.java b/src/main/java/com/seibel/lod/core/wrapperInterfaces/block/IBlockShapeWrapper.java new file mode 100644 index 000000000..4a3b3448b --- /dev/null +++ b/src/main/java/com/seibel/lod/core/wrapperInterfaces/block/IBlockShapeWrapper.java @@ -0,0 +1,39 @@ +/* + * This file is part of the Distant Horizon mod (formerly the LOD Mod), + * licensed under the GNU GPL v3 License. + * + * Copyright (C) 2020 James Seibel + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.seibel.lod.core.wrapperInterfaces.block; + +/** + * @author James Seibel + * @version 11-20-2021 + */ +public interface IBlockShapeWrapper +{ + boolean ofBlockToAvoid(); + + //-----------------// + //Avoidance getters// + //-----------------// + + boolean isNonFull(); + + boolean hasNoCollision(); + + boolean isToAvoid(); +} \ No newline at end of file diff --git a/src/main/java/com/seibel/lod/core/wrapperInterfaces/chunk/AbstractChunkPosWrapper.java b/src/main/java/com/seibel/lod/core/wrapperInterfaces/chunk/AbstractChunkPosWrapper.java new file mode 100644 index 000000000..374d43f7b --- /dev/null +++ b/src/main/java/com/seibel/lod/core/wrapperInterfaces/chunk/AbstractChunkPosWrapper.java @@ -0,0 +1,52 @@ +/* + * This file is part of the Distant Horizon mod (formerly the LOD Mod), + * licensed under the GNU GPL v3 License. + * + * Copyright (C) 2020 James Seibel + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.seibel.lod.core.wrapperInterfaces.chunk; + +import com.seibel.lod.core.wrapperInterfaces.block.AbstractBlockPosWrapper; + + +/** + * This is abstract instead of an interface, so + * we can define its constructors. + * + * @author James Seibel + * @version 11-18-2021 + */ +public abstract class AbstractChunkPosWrapper +{ + public AbstractChunkPosWrapper(AbstractChunkPosWrapper newChunkPos) { } + public AbstractChunkPosWrapper(AbstractBlockPosWrapper blockPos) { } + public AbstractChunkPosWrapper(int chunkX, int chunkZ) { } + public AbstractChunkPosWrapper() { } + + + + public abstract int getX(); + public abstract int getZ(); + + public abstract int getMinBlockX(); + public abstract int getMinBlockZ(); + + public abstract int getRegionX(); + public abstract int getRegionZ(); + + public abstract AbstractBlockPosWrapper getWorldPosition(); + +} diff --git a/src/main/java/com/seibel/lod/core/wrapperInterfaces/chunk/IChunkWrapper.java b/src/main/java/com/seibel/lod/core/wrapperInterfaces/chunk/IChunkWrapper.java new file mode 100644 index 000000000..6aaa2a497 --- /dev/null +++ b/src/main/java/com/seibel/lod/core/wrapperInterfaces/chunk/IChunkWrapper.java @@ -0,0 +1,52 @@ +/* + * This file is part of the Distant Horizon mod (formerly the LOD Mod), + * licensed under the GNU GPL v3 License. + * + * Copyright (C) 2020 James Seibel + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.seibel.lod.core.wrapperInterfaces.chunk; + +import com.seibel.lod.core.wrapperInterfaces.block.AbstractBlockPosWrapper; +import com.seibel.lod.core.wrapperInterfaces.block.IBlockColorWrapper; +import com.seibel.lod.core.wrapperInterfaces.block.IBlockShapeWrapper; +import com.seibel.lod.core.wrapperInterfaces.world.IBiomeWrapper; + +/** + * @author James Seibel + * @version 11-17-2021 + */ +public interface IChunkWrapper +{ + int getHeight(); + + boolean isPositionInWater(AbstractBlockPosWrapper blockPos); + + int getHeightMapValue(int xRel, int zRel); + + IBiomeWrapper getBiome(int xRel, int yAbs, int zRel); + + IBlockColorWrapper getBlockColorWrapper(AbstractBlockPosWrapper blockPos); + + IBlockShapeWrapper getBlockShapeWrapper(AbstractBlockPosWrapper blockPos); + + AbstractChunkPosWrapper getPos(); + + boolean isLightCorrect(); + + boolean isWaterLogged(AbstractBlockPosWrapper blockPos); + + int getEmittedBrightness(AbstractBlockPosWrapper blockPos); +} diff --git a/src/main/java/com/seibel/lod/core/wrapperInterfaces/config/ILodConfigWrapperSingleton.java b/src/main/java/com/seibel/lod/core/wrapperInterfaces/config/ILodConfigWrapperSingleton.java new file mode 100644 index 000000000..2d0fc46e6 --- /dev/null +++ b/src/main/java/com/seibel/lod/core/wrapperInterfaces/config/ILodConfigWrapperSingleton.java @@ -0,0 +1,512 @@ +/* + * This file is part of the Distant Horizon mod (formerly the LOD Mod), + * licensed under the GNU GPL v3 License. + * + * Copyright (C) 2020 James Seibel + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.seibel.lod.core.wrapperInterfaces.config; + +import com.seibel.lod.core.enums.config.BlocksToAvoid; +import com.seibel.lod.core.enums.config.BufferRebuildTimes; +import com.seibel.lod.core.enums.config.DistanceGenerationMode; +import com.seibel.lod.core.enums.config.GenerationPriority; +import com.seibel.lod.core.enums.config.GpuUploadMethod; +import com.seibel.lod.core.enums.config.HorizontalQuality; +import com.seibel.lod.core.enums.config.HorizontalResolution; +import com.seibel.lod.core.enums.config.HorizontalScale; +import com.seibel.lod.core.enums.config.LodTemplate; +import com.seibel.lod.core.enums.config.VanillaOverdraw; +import com.seibel.lod.core.enums.config.VerticalQuality; +import com.seibel.lod.core.enums.rendering.DebugMode; +import com.seibel.lod.core.enums.rendering.FogColorMode; +import com.seibel.lod.core.enums.rendering.FogDistance; +import com.seibel.lod.core.enums.rendering.FogDrawMode; +import com.seibel.lod.core.objects.MinDefaultMax; +import com.seibel.lod.core.util.LodUtil; + +/** + * This holds the config defaults, setters/getters + * that should be hooked into the host mod loader (Fabric, Forge, etc.), and + * the options that should be implemented in a configWrapperSingleton. + * + * @author James Seibel + * @version 12-1-2021 + */ +public interface ILodConfigWrapperSingleton +{ + IClient client(); + + + interface IClient + { + IGraphics graphics(); + IWorldGenerator worldGenerator(); + IAdvanced advanced(); + + + //==================// + // Graphics Configs // + //==================// + interface IGraphics + { + String DESC = "These settings control how the mod will look in game"; + + IQuality quality(); + IFogQuality fogQuality(); + IAdvancedGraphics advancedGraphics(); + + + interface IQuality + { + String DESC = "These settings control how detailed the fake chunks will be."; + + HorizontalResolution DRAW_RESOLUTION_DEFAULT = HorizontalResolution.BLOCK; + String DRAW_RESOLUTION_DESC = "" + + " What is the maximum detail fake chunks should be drawn at? \n" + + " This setting will only affect closer chunks.\n" + + " Higher settings will increase memory and GPU usage. \n" + + "\n" + + " " + HorizontalResolution.CHUNK + ": render 1 LOD for each Chunk. \n" + + " " + HorizontalResolution.HALF_CHUNK + ": render 4 LODs for each Chunk. \n" + + " " + HorizontalResolution.FOUR_BLOCKS + ": render 16 LODs for each Chunk. \n" + + " " + HorizontalResolution.TWO_BLOCKS + ": render 64 LODs for each Chunk. \n" + + " " + HorizontalResolution.BLOCK + ": render 256 LODs for each Chunk (width of one block). \n" + + "\n" + + " Lowest Quality: " + HorizontalResolution.CHUNK + + " Highest Quality: " + HorizontalResolution.BLOCK; + HorizontalResolution getDrawResolution(); + void setDrawResolution(HorizontalResolution newHorizontalResolution); + + MinDefaultMax LOD_CHUNK_RENDER_DISTANCE_MIN_DEFAULT_MAX = new MinDefaultMax(16, 64, 1024); + String LOD_CHUNK_RENDER_DISTANCE_DESC = "" + + " The radius of the mod's render distance. (measured in chunks) \n"; + int getLodChunkRenderDistance(); + void setLodChunkRenderDistance(int newLodChunkRenderDistance); + + VerticalQuality VERTICAL_QUALITY_DEFAULT = VerticalQuality.MEDIUM; + String VERTICAL_QUALITY_DESC = "" + + " This indicates how detailed fake chunks will represent \n" + + " overhangs, caves, floating islands, ect. \n" + + " Higher options will make the world more accurate, but" + + " will increase memory and GPU usage. \n" + + "\n" + + " " + VerticalQuality.LOW + ": uses at max 2 columns per position. \n" + + " " + VerticalQuality.MEDIUM + ": uses at max 4 columns per position. \n" + + " " + VerticalQuality.HIGH + ": uses at max 8 columns per position. \n" + + "\n" + + " Lowest Quality: " + VerticalQuality.LOW + + " Highest Quality: " + VerticalQuality.HIGH; + VerticalQuality getVerticalQuality(); + void setVerticalQuality(VerticalQuality newVerticalQuality); + + MinDefaultMax HORIZONTAL_SCALE_MIN_DEFAULT_MAX = new MinDefaultMax(2, 8, 32); + String HORIZONTAL_SCALE_DESC = "" + + " This indicates how quickly fake chunks decrease in quality the further away they are. \n" + + " Higher settings will render higher quality fake chunks farther away, \n" + + " but will increase memory and GPU usage."; + int getHorizontalScale(); + void setHorizontalScale(int newHorizontalScale); + + HorizontalQuality HORIZONTAL_QUALITY_DEFAULT = HorizontalQuality.MEDIUM; + String HORIZONTAL_QUALITY_DESC = "" + + " This indicates the exponential base of the quadratic drop-off \n" + + "\n" + + " " + HorizontalQuality.LOWEST + ": base " + HorizontalQuality.LOWEST.quadraticBase + ". \n" + + " " + HorizontalQuality.LOW + ": base " + HorizontalQuality.LOW.quadraticBase + ". \n" + + " " + HorizontalQuality.MEDIUM + ": base " + HorizontalQuality.MEDIUM.quadraticBase + ". \n" + + " " + HorizontalQuality.HIGH + ": base " + HorizontalQuality.HIGH.quadraticBase + ". \n" + + "\n" + + " Lowest Quality: " + HorizontalQuality.LOWEST + + " Highest Quality: " + HorizontalQuality.HIGH; + HorizontalQuality getHorizontalQuality(); + void setHorizontalQuality(HorizontalQuality newHorizontalQuality); + } + + interface IFogQuality + { + String DESC = "These settings control the fog quality."; + + FogDistance FOG_DISTANCE_DEFAULT = FogDistance.FAR; + String FOG_DISTANCE_DESC = "" + + " At what distance should Fog be drawn on the fake chunks? \n" + + "\n" + + " This setting shouldn't affect performance."; + FogDistance getFogDistance(); + void setFogDistance(FogDistance newFogDistance); + + FogDrawMode FOG_DRAW_MODE_DEFAULT = FogDrawMode.FOG_ENABLED; + String FOG_DRAW_MODE_DESC = "" + + " When should fog be drawn? \n" + + "\n" + + " " + FogDrawMode.USE_OPTIFINE_SETTING + ": Use whatever Fog setting Optifine is using. If Optifine isn't installed this defaults to " + FogDrawMode.FOG_ENABLED + ". \n" + + " " + FogDrawMode.FOG_ENABLED + ": Never draw fog on the LODs \n" + + " " + FogDrawMode.FOG_DISABLED + ": Always draw fast fog on the LODs \n" + + "\n" + + " Disabling fog will improve GPU performance."; + FogDrawMode getFogDrawMode(); + void setFogDrawMode(FogDrawMode newFogDrawMode); + + FogColorMode FOG_COLOR_MODE_DEFAULT = FogColorMode.USE_WORLD_FOG_COLOR; + String FOG_COLOR_MODE_DESC = "" + + " What color should fog use? \n" + + "\n" + + " " + FogColorMode.USE_WORLD_FOG_COLOR + ": Use the world's fog color. \n" + + " " + FogColorMode.USE_SKY_COLOR + ": Use the sky's color. \n" + + "\n" + + " This setting doesn't affect performance."; + FogColorMode getFogColorMode(); + void setFogColorMode(FogColorMode newFogColorMode); + + boolean DISABLE_VANILLA_FOG_DEFAULT = false; + String DISABLE_VANILLA_FOG_DESC = "" + + " If true disable Minecraft's fog. \n" + + "\n" + + " Experimental! Will cause issues with Sodium and \n" + + " may not play nice with other mods that edit fog. \n"; + boolean getDisableVanillaFog(); + void setDisableVanillaFog(boolean newDisableVanillaFog); + } + + interface IAdvancedGraphics + { + String DESC = "Graphics options that are a bit more technical."; + + LodTemplate LOD_TEMPLATE_DEFAULT = LodTemplate.CUBIC; + String LOD_TEMPLATE_DESC = "" + + " How should the LODs be drawn? \n" + + " NOTE: Currently only " + LodTemplate.CUBIC + " is implemented! \n" + + " \n" + + " " + LodTemplate.CUBIC + ": LOD Chunks are drawn as rectangular prisms (boxes). \n" + + " " + LodTemplate.TRIANGULAR + ": LOD Chunks smoothly transition between other. \n" + + " " + LodTemplate.DYNAMIC + ": LOD Chunks smoothly transition between each other, \n" + + " " + " unless a neighboring chunk is at a significantly different height. \n"; + LodTemplate getLodTemplate(); + void setLodTemplate(LodTemplate newLodTemplate); + + boolean DISABLE_DIRECTIONAL_CULLING_DEFAULT = false; + String DISABLE_DIRECTIONAL_CULLING_DESC = "" + + " If false fake chunks behind the player's camera \n" + + " aren't drawn, increasing GPU performance. \n" + + "\n" + + " If true all LODs are drawn, even those behind \n" + + " the player's camera, decreasing GPU performance. \n" + + "\n" + + " Disable this if you see LODs disappearing at the corners of your vision. \n"; + boolean getDisableDirectionalCulling(); + void setDisableDirectionalCulling(boolean newDisableDirectionalCulling); + + boolean ALWAYS_DRAW_AT_MAD_QUALITY_DEFAULT = false; + String ALWAYS_DRAW_AT_MAD_QUALITY_DESC = "" + + " Disable quality falloff, \n" + + " all fake chunks will be drawn at the highest \n" + + " available detail level. \n" + + "\n" + + " WARNING: \n" + + " This could cause an Out Of Memory crash when using render \n" + + " distances higher than 128 and will drastically increase GPU usage. \n"; + boolean getAlwaysDrawAtMaxQuality(); + void setAlwaysDrawAtMaxQuality(boolean newAlwaysDrawAtMaxQuality); + + VanillaOverdraw VANILLA_OVERDRAW_DEFAULT = VanillaOverdraw.DYNAMIC; + String VANILLA_OVERDRAW_DESC = "" + + " How often should LODs be drawn on top of regular chunks? \n" + + " HALF and ALWAYS will prevent holes in the world, but may look odd for transparent blocks or in caves. \n" + + "\n" + + " " + VanillaOverdraw.NEVER + ": LODs won't render on top of vanilla chunks. \n" + + " " + VanillaOverdraw.BORDER + ": LODs will render only on the border of vanilla chunks preventing only some holes in the world. \n" + + " " + VanillaOverdraw.DYNAMIC + ": LODs will render on top of distant vanilla chunks to hide delayed loading. \n" + + " " + " More effective on higher render distances. \n" + + " " + " For vanilla render distances less than or equal to " + LodUtil.MINIMUM_RENDER_DISTANCE_FOR_PARTIAL_OVERDRAW + " \n" + + " " + " " + VanillaOverdraw.NEVER + " or " + VanillaOverdraw.ALWAYS + " will be used depending on the dimension. \n" + + " " + VanillaOverdraw.ALWAYS + ": LODs will render on all vanilla chunks preventing holes in the world. \n" + + "\n" + + " This setting shouldn't affect performance. \n"; + VanillaOverdraw getVanillaOverdraw(); + void setVanillaOverdraw(VanillaOverdraw newVanillaOverdraw); + + boolean USE_EXTENDED_NEAR_CLIP_PLANE_DEFAULT = false; + String USE_EXTENDED_NEAR_CLIP_PLANE_DESC = "" + + " Will prevent some overdraw issues, but may cause nearby fake chunks to render incorrectly \n" + + " especially when in/near an ocean. \n" + + "\n" + + " This setting shouldn't affect performance. \n"; + boolean getUseExtendedNearClipPlane(); + void setUseExtendedNearClipPlane(boolean newUseExtendedNearClipPlane); + } + } + + + + + //========================// + // WorldGenerator Configs // + //========================// + interface IWorldGenerator + { + String DESC = "These settings control how fake chunks outside your normal view range are generated."; + + GenerationPriority GENERATION_PRIORITY_DEFAULT = GenerationPriority.AUTO; + String GENERATION_PRIORITY_DESC = "" + + " In what order should fake chunks be generated outside the vanilla render distance? \n" + + "\n" + + " " + GenerationPriority.FAR_FIRST + " \n" + + " Fake chunks are generated from lowest to highest detail \n" + + " with a small priority for far away regions. \n" + + " This fills in the world fastest, but you will have large low detail \n" + + " blocks for a while while the generation happens. \n" + + "\n" + + " " + GenerationPriority.NEAR_FIRST + " \n" + + " Fake chunks are generated around the player \n" + + " in a spiral, similar to vanilla minecraft. \n" + + " Best used when on a server since we can't generate \n" + + " fake chunks. \n" + + "\n" + + " " + GenerationPriority.AUTO + " \n" + + " Uses " + GenerationPriority.FAR_FIRST + " when on a single player world \n" + + " and " + GenerationPriority.NEAR_FIRST + " when connected to a server. \n" + + "\n" + + " This shouldn't affect performance."; + GenerationPriority getGenerationPriority(); + void setGenerationPriority(GenerationPriority newGenerationPriority); + + DistanceGenerationMode DISTANCE_GENERATION_MODE_DEFAULT = DistanceGenerationMode.SURFACE; + String DISTANCE_GENERATION_MODE_DESC = "" + + " How detailed should fake chunks be generated outside the vanilla render distance? \n" + + "\n" + + " " + DistanceGenerationMode.NONE + " \n" + + " Don't run the distance generator. \n" + + " No CPU usage - Fastest \n" + + "\n" + + " " + DistanceGenerationMode.BIOME_ONLY + " \n" + + " Only generate the biomes and use the biome's \n" + + " grass color, water color, or snow color. \n" + + " Doesn't generate height, everything is shown at sea level. \n" + + " Multithreaded - Fastest (2-5 ms) \n" + + "\n" + + " " + DistanceGenerationMode.BIOME_ONLY_SIMULATE_HEIGHT + " \n" + + " Same as " + DistanceGenerationMode.BIOME_ONLY + ", except instead \n" + + " of always using sea level as the LOD height \n" + + " different biome types (mountain, ocean, forest, etc.) \n" + + " use predetermined heights to simulate having height data. \n" + + " Multithreaded - Fastest (2-5 ms) \n" + + "\n" + + " " + DistanceGenerationMode.SURFACE + " \n" + + " Generate the world surface, \n" + + " this does NOT include trees, \n" + + " or structures. \n" + + " Multithreaded - Faster (10-20 ms) \n" + + "\n" + + " " + DistanceGenerationMode.FEATURES + " \n" + + " Generate everything except structures. \n" + + " WARNING: This may cause world generation bugs or instability! \n" + + " Multithreaded - Fast (15-20 ms) \n" + + "\n" + + " " + DistanceGenerationMode.FULL + " \n" + + " Ask the local server to generate/load each chunk. \n" + + " This will show player made structures, which can \n" + + " be useful if you are adding the mod to a pre-existing world. \n" + + " This is the most compatible, but causes server/simulation lag. \n" + + " SingleThreaded - Slow (15-50 ms, with spikes up to 200 ms) \n" + + "\n" + + " The multithreaded options may increase CPU load significantly (while generating) \n" + + " depending on how many world generation threads you have allocated. \n"; + DistanceGenerationMode getDistanceGenerationMode(); + void setDistanceGenerationMode(DistanceGenerationMode newDistanceGenerationMode); + + boolean ALLOW_UNSTABLE_FEATURE_GENERATION_DEFAULT = false; + String ALLOW_UNSTABLE_FEATURE_GENERATION_DESC = "" + + " When using the " + DistanceGenerationMode.FEATURES + " 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"; + boolean getAllowUnstableFeatureGeneration(); + void setAllowUnstableFeatureGeneration(boolean newAllowUnstableFeatureGeneration); + + BlocksToAvoid BLOCKS_TO_AVOID_DEFAULT = BlocksToAvoid.BOTH; + String BLOCKS_TO_AVOID_DESC = "" + + " When generating fake chunks, what blocks should be ignored? \n" + + " Ignored blocks don't affect the height of the fake chunk, but might affect the color. \n" + + " So using " + BlocksToAvoid.BOTH + " will prevent snow covered blocks from appearing one block too tall, \n" + + " but will still show the snow's color.\n" + + "\n" + + " " + BlocksToAvoid.NONE + ": Use all blocks when generating fake chunks \n" + + " " + BlocksToAvoid.NON_FULL + ": Only use full blocks when generating fake chunks (ignores slabs, lanterns, torches, tall grass, etc.) \n" + + " " + BlocksToAvoid.NO_COLLISION + ": Only use solid blocks when generating fake chunks (ignores tall grass, torches, etc.) \n" + + " " + BlocksToAvoid.BOTH + ": Only use full solid blocks when generating fake chunks \n" + + "\n" + + " This wont't affect performance."; + BlocksToAvoid getBlocksToAvoid(); + void setBlockToAvoid(BlocksToAvoid newBlockToAvoid); + } + + + + + //============================// + // AdvancedModOptions Configs // + //============================// + interface IAdvanced + { + String DESC = "Advanced mod settings"; + + IThreading threading(); + IDebugging debugging(); + IBuffers buffers(); + + + interface IThreading + { + String DESC = "These settings control how many CPU threads the mod uses for different tasks."; + + MinDefaultMax NUMBER_OF_WORLD_GENERATION_THREADS_DEFAULT + = new MinDefaultMax(1, + Runtime.getRuntime().availableProcessors() / 2, + Runtime.getRuntime().availableProcessors()); + String NUMBER_OF_WORLD_GENERATION_THREADS_DESC = "" + + " How many threads should be used when generating fake chunks outside \n" + + " the normal render distance? \n" + + "\n" + + " If you experience stuttering when generating distant LODs, decrease \n" + + " this number. If you want to increase LOD generation speed, \n" + + " increase this number. \n" + + "\n" + + " This and the number of buffer builder threads are independent, \n" + + " so if they add up to more threads than your CPU has cores, \n" + + " that shouldn't cause an issue. \n" + + "\n" + + " The maximum value is the number of logical processors on your CPU. \n" + + " Requires a restart to take effect. \n"; + int getNumberOfWorldGenerationThreads(); + void setNumberOfWorldGenerationThreads(int newNumberOfWorldGenerationThreads); + + MinDefaultMax NUMBER_OF_BUFFER_BUILDER_THREADS_MIN_DEFAULT_MAX + = new MinDefaultMax(1, + Runtime.getRuntime().availableProcessors() / 2, + Runtime.getRuntime().availableProcessors()); + String NUMBER_OF_BUFFER_BUILDER_THREADS_DESC = "" + + " How many threads are used when building vertex buffers? \n" + + " (The things sent to your GPU to draw the fake chunks). \n" + + "\n" + + " If you experience high CPU usage when NOT generating distant \n" + + " fake chunks, lower this number. A higher number will make fake\n" + + " fake chunks' transition faster when moving around the world. \n" + + "\n" + + " This and the number of world generator threads are independent, \n" + + " so if they add up to more threads than your CPU has cores, \n" + + " that shouldn't cause an issue. \n" + + "\n" + + " The maximum value is the number of logical processors on your CPU. \n" + + " Requires a restart to take effect. \n"; + int getNumberOfBufferBuilderThreads(); + void setNumberOfBufferBuilderThreads(int newNumberOfWorldBuilderThreads); + } + + interface IDebugging + { + String DESC = "These settings can be used to look for bugs, or see how certain aspects of the mod work."; + + boolean DRAW_LODS_DEFAULT = true; + String DRAW_LODS_DESC = "" + + " If true, the mod is enabled and fake chunks will be drawn. \n" + + " If false, the mod will still generate fake chunks, \n" + + " but they won't be rendered. \n" + + "\n" + + " Disabling rendering will reduce GPU usage \n"; + boolean getDrawLods(); + void setDrawLods(boolean newDrawLods); + + DebugMode DEBUG_MODE_DEFAULT = DebugMode.OFF; + String DEBUG_MODE_DESC = "" + + " Should specialized colors/rendering modes be used? \n" + + "\n" + + " " + DebugMode.OFF + ": Fake chunks will be drawn with their normal colors. \n" + + " " + DebugMode.SHOW_DETAIL + ": Fake chunks color will be based on their detail level. \n" + + " " + DebugMode.SHOW_DETAIL_WIREFRAME + ": Fake chunks color will be based on their detail level, drawn as a wireframe. \n"; + DebugMode getDebugMode(); + void setDebugMode(DebugMode newDebugMode); + + boolean DEBUG_KEYBINDINGS_ENABLED_DEFAULT = true; + String DEBUG_KEYBINDINGS_ENABLED_DESC = "" + + " If true the F4 key can be used to cycle through the different debug modes. \n" + + " and the F6 key can be used to enable and disable LOD rendering."; + boolean getDebugKeybindingsEnabled(); + void setDebugKeybindingsEnabled(boolean newEnableDebugKeybindings); + } + + interface IBuffers + { + String DESC = "These settings affect how often geometry is rebuilt."; + + GpuUploadMethod GPU_UPLOAD_METHOD_DEFAULT = GpuUploadMethod.AUTO; + String GPU_UPLOAD_METHOD_DESC = "" + + " What method should be used to upload geometry to the GPU? \n" + + "\n" + + " " + GpuUploadMethod.AUTO + ": Picks the best option based on the GPU you have. \n" + + " " + GpuUploadMethod.BUFFER_STORAGE + ": Default for NVIDIA if OpenGL 4.5 is supported. \n" + + " Fast rendering, no stuttering. \n" + + " " + GpuUploadMethod.SUB_DATA + ": Backup option for NVIDIA. \n" + + " Fast rendering but may stutter when uploading. \n" + + " " + GpuUploadMethod.BUFFER_MAPPING + ": Slow rendering but won't stutter when uploading. Possibly the best option for integrated GPUs. \n" + + " Default option for AMD/Intel. \n" + + " May end up storing buffers in System memory. \n" + + " Fast rending if in GPU memory, slow if in system memory, \n" + + " but won't stutter when uploading. \n" + + " " + GpuUploadMethod.DATA + ": Fast rendering but will stutter when uploading. \n" + + " Backup option for AMD/Intel. \n" + + " Fast rendering but may stutter when uploading. \n" + + "\n" + + " If you don't see any difference when changing these settings, or the world looks corrupted: \n" + + " Restart the game to clear the old buffers. \n"; + GpuUploadMethod getGpuUploadMethod(); + void setGpuUploadMethod(GpuUploadMethod newGpuUploadMethod); + + MinDefaultMax GPU_UPLOAD_TIMEOUT_IN_MILLISECONDS_DEFAULT = new MinDefaultMax(0, 0, 5000); + String GPU_UPLOAD_TIMEOUT_IN_MILLISECONDS_DESC = "" + + " How long should we wait before uploading a buffer to the GPU? \n" + + " Helpful resource for frame times: https://fpstoms.com \n" + + "\n" + + " Longer times may reduce stuttering but will make fake chunks \n" + + " transition and load slower. \n" + + "\n" + + " NOTE:\n" + + " This should be a last resort option." + + " Only change this from [0], after you have tried all of the \n" + + " \"GPU Upload methods\" and determined even the best stutters with yoru hardware."; + int getGpuUploadTimeoutInMilliseconds(); + void setGpuUploadTimeoutInMilliseconds(int newTimeoutInMilliseconds); + + String REBUILD_TIMES_DESC = "" + + " How frequently should vertex buffers (geometry) be rebuilt and sent to the GPU? \n" + + " Higher settings may cause stuttering, but will prevent holes in the world \n"; + BufferRebuildTimes REBUILD_TIMES_DEFAULT = BufferRebuildTimes.NORMAL; + BufferRebuildTimes getRebuildTimes(); + void setRebuildTimes(BufferRebuildTimes newBufferRebuildTimes); + } + } + } + +} diff --git a/src/main/java/com/seibel/lod/core/wrapperInterfaces/minecraft/IMinecraftRenderWrapper.java b/src/main/java/com/seibel/lod/core/wrapperInterfaces/minecraft/IMinecraftRenderWrapper.java new file mode 100644 index 000000000..b39289b1a --- /dev/null +++ b/src/main/java/com/seibel/lod/core/wrapperInterfaces/minecraft/IMinecraftRenderWrapper.java @@ -0,0 +1,69 @@ +/* + * This file is part of the Distant Horizon mod (formerly the LOD Mod), + * licensed under the GNU GPL v3 License. + * + * Copyright (C) 2020 James Seibel + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.seibel.lod.core.wrapperInterfaces.minecraft; + +import java.awt.Color; +import java.util.HashSet; + +import com.seibel.lod.core.objects.math.Mat4f; +import com.seibel.lod.core.objects.math.Vec3d; +import com.seibel.lod.core.objects.math.Vec3f; +import com.seibel.lod.core.wrapperInterfaces.block.AbstractBlockPosWrapper; +import com.seibel.lod.core.wrapperInterfaces.chunk.AbstractChunkPosWrapper; + +/** + * Contains everything related to + * rendering in Minecraft. + * + * @author James Seibel + * @version 11-26-2021 + */ +public interface IMinecraftRenderWrapper +{ + Vec3f getLookAtVector(); + + AbstractBlockPosWrapper getCameraBlockPosition(); + + boolean playerHasBlindnessEffect(); + + Vec3d getCameraExactPosition(); + + Mat4f getDefaultProjectionMatrix(float partialTicks); + + double getGamma(); + + Color getFogColor(); + + Color getSkyColor(); + + double getFov(float partialTicks); + + /** Measured in chunks */ + int getRenderDistance(); + + int getScreenWidth(); + int getScreenHeight(); + + /** + * This method returns the ChunkPos of all chunks that Minecraft + * is going to render this frame. + */ + HashSet getRenderedChunks(); +} diff --git a/src/main/java/com/seibel/lod/core/wrapperInterfaces/minecraft/IMinecraftWrapper.java b/src/main/java/com/seibel/lod/core/wrapperInterfaces/minecraft/IMinecraftWrapper.java new file mode 100644 index 000000000..f6668e0dc --- /dev/null +++ b/src/main/java/com/seibel/lod/core/wrapperInterfaces/minecraft/IMinecraftWrapper.java @@ -0,0 +1,157 @@ +/* + * This file is part of the Distant Horizon mod (formerly the LOD Mod), + * licensed under the GNU GPL v3 License. + * + * Copyright (C) 2020 James Seibel + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.seibel.lod.core.wrapperInterfaces.minecraft; + +import java.awt.Color; +import java.io.File; +import java.util.ArrayList; + +import com.seibel.lod.core.enums.LodDirection; +import com.seibel.lod.core.wrapperInterfaces.block.AbstractBlockPosWrapper; +import com.seibel.lod.core.wrapperInterfaces.chunk.AbstractChunkPosWrapper; +import com.seibel.lod.core.wrapperInterfaces.misc.ILightMapWrapper; +import com.seibel.lod.core.wrapperInterfaces.world.IDimensionTypeWrapper; +import com.seibel.lod.core.wrapperInterfaces.world.IWorldWrapper; + +/** + * Contains everything related to the Minecraft object. + * + * @author James Seibel + * @version 9-16-2021 + */ +public interface IMinecraftWrapper +{ + //================// + // helper methods // + //================// + + /** + * This should be called at the beginning of every frame to + * clear any Minecraft data that becomes out of date after a frame.

+ *

+ * LightMaps and other time sensitive objects fall in this category.

+ *

+ * This doesn't affect OpenGL objects in any way. + */ + void clearFrameObjectCache(); + + + + //=================// + // method wrappers // + //=================// + + float getShade(LodDirection lodDirection); + + boolean hasSinglePlayerServer(); + + String getCurrentServerName(); + String getCurrentServerIp(); + String getCurrentServerVersion(); + + /** Returns the dimension the player is currently in */ + IDimensionTypeWrapper getCurrentDimension(); + + String getCurrentDimensionId(); + + /** This texture changes every frame */ + ILightMapWrapper getCurrentLightMap(); + + /** + * Returns the color int at the given pixel coordinates + * from the current lightmap. + * @param u x location in texture space + * @param v z location in texture space + */ + int getColorIntFromLightMap(int u, int v); + + /** + * Returns the Color at the given pixel coordinates + * from the current lightmap. + * @param u x location in texture space + * @param v z location in texture space + */ + Color getColorFromLightMap(int u, int v); + + + + + //=============// + // Simple gets // + //=============// + + boolean playerExists(); + + AbstractBlockPosWrapper getPlayerBlockPos(); + + AbstractChunkPosWrapper getPlayerChunkPos(); + + /** + * Attempts to get the ServerWorld for the dimension + * the user is currently in. + * @returns null if no ServerWorld is available + */ + IWorldWrapper getWrappedServerWorld(); + + IWorldWrapper getWrappedClientWorld(); + + File getGameDirectory(); + + IProfilerWrapper getProfiler(); + + float getSkyDarken(float partialTicks); + + boolean connectedToServer(); + + /** Returns all worlds available to the server */ + ArrayList getAllServerWorlds(); + + + + void sendChatMessage(String string); + + /** + * Crashes Minecraft, displaying the given errorMessage

+ * In the following format:
+ * + * The game crashed whilst errorMessage
+ * Error: ExceptionClass: exceptionErrorMessage
+ * Exit Code: -1
+ */ + void crashMinecraft(String errorMessage, Throwable exception); + + + + + + + + + + + + + + + + + + +} diff --git a/src/main/java/com/seibel/lod/core/wrapperInterfaces/minecraft/IProfilerWrapper.java b/src/main/java/com/seibel/lod/core/wrapperInterfaces/minecraft/IProfilerWrapper.java new file mode 100644 index 000000000..bf081fb72 --- /dev/null +++ b/src/main/java/com/seibel/lod/core/wrapperInterfaces/minecraft/IProfilerWrapper.java @@ -0,0 +1,33 @@ +/* + * This file is part of the Distant Horizon mod (formerly the LOD Mod), + * licensed under the GNU GPL v3 License. + * + * Copyright (C) 2020 James Seibel + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.seibel.lod.core.wrapperInterfaces.minecraft; + +/** + * @author James Seibel + * @version 11-20-2021 + */ +public interface IProfilerWrapper +{ + void push(String newSection); + + void popPush(String newSection); + + void pop(); +} diff --git a/src/main/java/com/seibel/lod/core/wrapperInterfaces/misc/ILightMapWrapper.java b/src/main/java/com/seibel/lod/core/wrapperInterfaces/misc/ILightMapWrapper.java new file mode 100644 index 000000000..76453b5b8 --- /dev/null +++ b/src/main/java/com/seibel/lod/core/wrapperInterfaces/misc/ILightMapWrapper.java @@ -0,0 +1,29 @@ +/* + * This file is part of the Distant Horizon mod (formerly the LOD Mod), + * licensed under the GNU GPL v3 License. + * + * Copyright (C) 2020 James Seibel + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.seibel.lod.core.wrapperInterfaces.misc; + +/** + * @author James Seibel + * @version 11-20-2021 + */ +public interface ILightMapWrapper +{ + int getLightValue(int skyLight, int blockLight); +} diff --git a/src/main/java/com/seibel/lod/core/wrapperInterfaces/world/IBiomeColorWrapperSingleton.java b/src/main/java/com/seibel/lod/core/wrapperInterfaces/world/IBiomeColorWrapperSingleton.java new file mode 100644 index 000000000..76a1c1768 --- /dev/null +++ b/src/main/java/com/seibel/lod/core/wrapperInterfaces/world/IBiomeColorWrapperSingleton.java @@ -0,0 +1,38 @@ +/* + * This file is part of the Distant Horizon mod (formerly the LOD Mod), + * licensed under the GNU GPL v3 License. + * + * Copyright (C) 2020 James Seibel + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.seibel.lod.core.wrapperInterfaces.world; + +import com.seibel.lod.core.wrapperInterfaces.block.AbstractBlockPosWrapper; + + +/** + * Contains everything related to biome colors. + * + * @author James Seibel + * @version 11-15-2021 + */ +public interface IBiomeColorWrapperSingleton +{ + IBiomeColorWrapperSingleton getInstance(); + + int getGrassColor(IWorldWrapper world, AbstractBlockPosWrapper blockPos); + int getWaterColor(IWorldWrapper world, AbstractBlockPosWrapper blockPos); + int getFoliageColor(IWorldWrapper world, AbstractBlockPosWrapper blockPos); +} diff --git a/src/main/java/com/seibel/lod/core/wrapperInterfaces/world/IBiomeWrapper.java b/src/main/java/com/seibel/lod/core/wrapperInterfaces/world/IBiomeWrapper.java new file mode 100644 index 000000000..eb1b620f6 --- /dev/null +++ b/src/main/java/com/seibel/lod/core/wrapperInterfaces/world/IBiomeWrapper.java @@ -0,0 +1,37 @@ +/* + * This file is part of the Distant Horizon mod (formerly the LOD Mod), + * licensed under the GNU GPL v3 License. + * + * Copyright (C) 2020 James Seibel + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.seibel.lod.core.wrapperInterfaces.world; + +/** + * @author James Seibel + * @version 11-15-2021 + */ +public interface IBiomeWrapper +{ + /** Returns a color int for the given biome. */ + int getColorForBiome(int x, int z); + + int getGrassTint(int x, int z); + + int getFolliageTint(); + + int getWaterTint(); + +} diff --git a/src/main/java/com/seibel/lod/core/wrapperInterfaces/world/IDimensionTypeWrapper.java b/src/main/java/com/seibel/lod/core/wrapperInterfaces/world/IDimensionTypeWrapper.java new file mode 100644 index 000000000..cbafd5cd6 --- /dev/null +++ b/src/main/java/com/seibel/lod/core/wrapperInterfaces/world/IDimensionTypeWrapper.java @@ -0,0 +1,33 @@ +/* + * This file is part of the Distant Horizon mod (formerly the LOD Mod), + * licensed under the GNU GPL v3 License. + * + * Copyright (C) 2020 James Seibel + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.seibel.lod.core.wrapperInterfaces.world; + +/** + * @author James Seibel + * @version 11-15-2021 + */ +public interface IDimensionTypeWrapper +{ + String getDimensionName(); + + boolean hasCeiling(); + + boolean hasSkyLight(); +} diff --git a/src/main/java/com/seibel/lod/core/wrapperInterfaces/world/IWorldWrapper.java b/src/main/java/com/seibel/lod/core/wrapperInterfaces/world/IWorldWrapper.java new file mode 100644 index 000000000..cb35e1fd5 --- /dev/null +++ b/src/main/java/com/seibel/lod/core/wrapperInterfaces/world/IWorldWrapper.java @@ -0,0 +1,61 @@ +/* + * This file is part of the Distant Horizon mod (formerly the LOD Mod), + * licensed under the GNU GPL v3 License. + * + * Copyright (C) 2020 James Seibel + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.seibel.lod.core.wrapperInterfaces.world; + +import java.io.File; + +import com.seibel.lod.core.enums.WorldType; +import com.seibel.lod.core.wrapperInterfaces.block.AbstractBlockPosWrapper; + +/** + * Can be either a Server world or a Client world. + * + * @author James Seibel + * @version 11-20-2021 + */ +public interface IWorldWrapper +{ + IDimensionTypeWrapper getDimensionType(); + + WorldType getWorldType(); + + int getBlockLight(AbstractBlockPosWrapper blockPos); + + int getSkyLight(AbstractBlockPosWrapper blockPos); + + IBiomeWrapper getBiome(AbstractBlockPosWrapper blockPos); + + boolean hasCeiling(); + + boolean hasSkyLight(); + + // Pls don't use this + // If the world is null then this can't be called and gives an error + boolean isEmpty(); + + int getHeight(); + + int getSeaLevel(); + + /** @throws UnsupportedOperationException if the WorldWrapper isn't for a ServerWorld */ + File getSaveFolder() throws UnsupportedOperationException; + + +} diff --git a/src/main/java/com/seibel/lod/core/wrapperInterfaces/worldGeneration/AbstractWorldGeneratorWrapper.java b/src/main/java/com/seibel/lod/core/wrapperInterfaces/worldGeneration/AbstractWorldGeneratorWrapper.java new file mode 100644 index 000000000..696c89e86 --- /dev/null +++ b/src/main/java/com/seibel/lod/core/wrapperInterfaces/worldGeneration/AbstractWorldGeneratorWrapper.java @@ -0,0 +1,50 @@ +/* + * This file is part of the Distant Horizon mod (formerly the LOD Mod), + * licensed under the GNU GPL v3 License. + * + * Copyright (C) 2020 James Seibel + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.seibel.lod.core.wrapperInterfaces.worldGeneration; + +import com.seibel.lod.core.builders.lodBuilding.LodBuilder; +import com.seibel.lod.core.enums.config.DistanceGenerationMode; +import com.seibel.lod.core.objects.lod.LodDimension; +import com.seibel.lod.core.wrapperInterfaces.chunk.AbstractChunkPosWrapper; +import com.seibel.lod.core.wrapperInterfaces.world.IWorldWrapper; + +/** + * This is used for generating chunks + * in a variety of detail and threading levels. + *

+ * Abstract instead of an interface, so + * we can define its constructors. + * + * @author James Seibel + * @version 11-20-2021 + */ +public abstract class AbstractWorldGeneratorWrapper +{ + public AbstractWorldGeneratorWrapper(LodBuilder newLodBuilder, LodDimension newLodDimension, IWorldWrapper worldWrapper) { } + + + public abstract void generateBiomesOnly(AbstractChunkPosWrapper pos, DistanceGenerationMode generationMode); + + public abstract void generateSurface(AbstractChunkPosWrapper pos); + + public abstract void generateFeatures(AbstractChunkPosWrapper pos); + + public abstract void generateFull(AbstractChunkPosWrapper pos); +} diff --git a/fabric/src/main/java/com/seibel/lod/fabric/ClientProxy.java b/src/main/java/com/seibel/lod/fabric/ClientProxy.java similarity index 100% rename from fabric/src/main/java/com/seibel/lod/fabric/ClientProxy.java rename to src/main/java/com/seibel/lod/fabric/ClientProxy.java diff --git a/fabric/src/main/java/com/seibel/lod/fabric/Main.java b/src/main/java/com/seibel/lod/fabric/Main.java similarity index 100% rename from fabric/src/main/java/com/seibel/lod/fabric/Main.java rename to src/main/java/com/seibel/lod/fabric/Main.java diff --git a/fabric/src/main/java/com/seibel/lod/fabric/mixins/MixinMinecraft.java b/src/main/java/com/seibel/lod/fabric/mixins/MixinMinecraft.java similarity index 100% rename from fabric/src/main/java/com/seibel/lod/fabric/mixins/MixinMinecraft.java rename to src/main/java/com/seibel/lod/fabric/mixins/MixinMinecraft.java diff --git a/fabric/src/main/java/com/seibel/lod/fabric/mixins/events/MixinClientLevel.java b/src/main/java/com/seibel/lod/fabric/mixins/events/MixinClientLevel.java similarity index 86% rename from fabric/src/main/java/com/seibel/lod/fabric/mixins/events/MixinClientLevel.java rename to src/main/java/com/seibel/lod/fabric/mixins/events/MixinClientLevel.java index 24856cf04..572491ef2 100644 --- a/fabric/src/main/java/com/seibel/lod/fabric/mixins/events/MixinClientLevel.java +++ b/src/main/java/com/seibel/lod/fabric/mixins/events/MixinClientLevel.java @@ -22,7 +22,7 @@ import java.util.function.Supplier; @Mixin(ClientLevel.class) public class MixinClientLevel { @Inject(method = "", at = @At("TAIL")) - private void loadWorldEvent(ClientPacketListener clientPacketListener, ClientLevel.ClientLevelData clientLevelData, ResourceKey resourceKey, DimensionType dimensionType, int i, Supplier supplier, LevelRenderer levelRenderer, boolean bl, long l, CallbackInfo ci) { + private void loadWorldEvent(ClientPacketListener clientPacketListener, ClientLevel.ClientLevelData clientLevelData, ResourceKey resourceKey, DimensionType dimensionType, int i, int j, Supplier supplier, LevelRenderer levelRenderer, boolean bl, long l, CallbackInfo ci) { Main.client_proxy.worldLoadEvent((ClientLevel) (Object) this); } } diff --git a/fabric/src/main/java/com/seibel/lod/fabric/mixins/events/MixinMinecraft.java b/src/main/java/com/seibel/lod/fabric/mixins/events/MixinMinecraft.java similarity index 100% rename from fabric/src/main/java/com/seibel/lod/fabric/mixins/events/MixinMinecraft.java rename to src/main/java/com/seibel/lod/fabric/mixins/events/MixinMinecraft.java diff --git a/fabric/src/main/java/com/seibel/lod/fabric/mixins/events/MixinServerLevel.java b/src/main/java/com/seibel/lod/fabric/mixins/events/MixinServerLevel.java similarity index 100% rename from fabric/src/main/java/com/seibel/lod/fabric/mixins/events/MixinServerLevel.java rename to src/main/java/com/seibel/lod/fabric/mixins/events/MixinServerLevel.java diff --git a/fabric/src/main/java/com/seibel/lod/fabric/wrappers/DependencySetup.java b/src/main/java/com/seibel/lod/fabric/wrappers/DependencySetup.java similarity index 100% rename from fabric/src/main/java/com/seibel/lod/fabric/wrappers/DependencySetup.java rename to src/main/java/com/seibel/lod/fabric/wrappers/DependencySetup.java diff --git a/fabric/src/main/java/com/seibel/lod/fabric/wrappers/config/ModMenuIntegration.java b/src/main/java/com/seibel/lod/fabric/wrappers/config/ModMenuIntegration.java similarity index 100% rename from fabric/src/main/java/com/seibel/lod/fabric/wrappers/config/ModMenuIntegration.java rename to src/main/java/com/seibel/lod/fabric/wrappers/config/ModMenuIntegration.java diff --git a/common/src/main/java/com/seibel/lod/common/wrappers/minecraft/MinecraftRenderWrapper.java b/src/main/java/com/seibel/lod/fabric/wrappers/minecraft/MinecraftRenderWrapper.java similarity index 98% rename from common/src/main/java/com/seibel/lod/common/wrappers/minecraft/MinecraftRenderWrapper.java rename to src/main/java/com/seibel/lod/fabric/wrappers/minecraft/MinecraftRenderWrapper.java index 3fe97ea0f..fa183b241 100644 --- a/common/src/main/java/com/seibel/lod/common/wrappers/minecraft/MinecraftRenderWrapper.java +++ b/src/main/java/com/seibel/lod/fabric/wrappers/minecraft/MinecraftRenderWrapper.java @@ -1,4 +1,4 @@ -package com.seibel.lod.common.wrappers.minecraft; +package com.seibel.lod.fabric.wrappers.minecraft; import java.awt.*; import java.util.HashSet; @@ -143,6 +143,8 @@ public class MinecraftRenderWrapper implements IMinecraftRenderWrapper // go through every RenderInfo to get the compiled chunks LevelRenderer renderer = mc.levelRenderer; + // TODO[1.18]: Fix this + /* for (RenderChunkInfo worldRenderer$LocalRenderInformationContainer : renderer.renderChunks) { CompiledChunk compiledChunk = worldRenderer$LocalRenderInformationContainer.chunk.getCompiledChunk(); @@ -154,6 +156,7 @@ public class MinecraftRenderWrapper implements IMinecraftRenderWrapper loadedPos.add(new ChunkPosWrapper(bpos)); } } + */ return loadedPos; } diff --git a/common/src/main/java/com/seibel/lod/common/wrappers/worldGeneration/LodServerWorld.java b/src/main/java/com/seibel/lod/fabric/wrappers/worldGeneration/LodServerWorld.java similarity index 93% rename from common/src/main/java/com/seibel/lod/common/wrappers/worldGeneration/LodServerWorld.java rename to src/main/java/com/seibel/lod/fabric/wrappers/worldGeneration/LodServerWorld.java index 4bb09f586..922821627 100644 --- a/common/src/main/java/com/seibel/lod/common/wrappers/worldGeneration/LodServerWorld.java +++ b/src/main/java/com/seibel/lod/fabric/wrappers/worldGeneration/LodServerWorld.java @@ -17,13 +17,12 @@ * along with this program. If not, see . */ -package com.seibel.lod.common.wrappers.worldGeneration; +package com.seibel.lod.fabric.wrappers.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.core.util.LodUtil; import com.seibel.lod.common.wrappers.WrapperUtil; @@ -39,8 +38,6 @@ import net.minecraft.sounds.SoundSource; import net.minecraft.world.DifficultyInstance; import net.minecraft.world.entity.Entity; import net.minecraft.world.entity.player.Player; -import net.minecraft.world.level.EmptyTickList; -import net.minecraft.world.level.TickList; import net.minecraft.world.level.WorldGenLevel; import net.minecraft.world.level.biome.Biome; import net.minecraft.world.level.biome.BiomeManager; @@ -62,6 +59,8 @@ import net.minecraft.world.level.material.Fluid; import net.minecraft.world.level.material.FluidState; import net.minecraft.world.level.storage.LevelData; import net.minecraft.world.phys.AABB; +import net.minecraft.world.ticks.LevelTickAccess; +import net.minecraft.world.ticks.TickAccess; import org.jetbrains.annotations.Nullable; @@ -71,8 +70,9 @@ import org.jetbrains.annotations.Nullable; * of the actual ServerWorld, allowing * multithread generation. * + * @author coolGi2007 * @author James Seibel - * @version 7-26-2021 + * @version 12-06-2021 */ public class LodServerWorld implements WorldGenLevel { @@ -106,7 +106,7 @@ public class LodServerWorld implements WorldGenLevel @Override public Biome getBiome(BlockPos pos) { - return chunk.getBiomes().getNoiseBiome(pos.getX() >> 2, pos.getY() >> 2, pos.getZ() >> 2); + return chunk.getNoiseBiome(pos.getX() >> 2, pos.getY() >> 2, pos.getZ() >> 2); } @Override @@ -140,9 +140,19 @@ public class LodServerWorld implements WorldGenLevel } @Override - public TickList getBlockTicks() + public long nextSubTickCount() { + return 0; + } + + @Override + public LevelTickAccess getBlockTicks() { - return EmptyTickList.empty(); + return null; + } + + @Override + public LevelTickAccess getFluidTicks() { + return null; } @Override @@ -152,17 +162,11 @@ public class LodServerWorld implements WorldGenLevel } @Override - public Stream> startsForFeature(SectionPos p_241827_1_, StructureFeature p_241827_2_) + public List> startsForFeature(SectionPos p_241827_1_, StructureFeature p_241827_2_) { return serverWorld.startsForFeature(p_241827_1_, p_241827_2_); } - @Override - public TickList getLiquidTicks() - { - return EmptyTickList.empty(); - } - @Override public LevelLightEngine getLightEngine() { diff --git a/common/src/main/java/com/seibel/lod/common/wrappers/worldGeneration/WorldGeneratorWrapper.java b/src/main/java/com/seibel/lod/fabric/wrappers/worldGeneration/WorldGeneratorWrapper.java similarity index 98% rename from common/src/main/java/com/seibel/lod/common/wrappers/worldGeneration/WorldGeneratorWrapper.java rename to src/main/java/com/seibel/lod/fabric/wrappers/worldGeneration/WorldGeneratorWrapper.java index f880a10c0..da5d5e5a8 100644 --- a/common/src/main/java/com/seibel/lod/common/wrappers/worldGeneration/WorldGeneratorWrapper.java +++ b/src/main/java/com/seibel/lod/fabric/wrappers/worldGeneration/WorldGeneratorWrapper.java @@ -1,4 +1,4 @@ -package com.seibel.lod.common.wrappers.worldGeneration; +package com.seibel.lod.fabric.wrappers.worldGeneration; import java.util.ConcurrentModificationException; import java.util.HashSet; @@ -24,6 +24,7 @@ import com.seibel.lod.common.wrappers.chunk.ChunkPosWrapper; import com.seibel.lod.common.wrappers.chunk.ChunkWrapper; import com.seibel.lod.common.wrappers.world.WorldWrapper; +import com.seibel.lod.fabric.wrappers.worldGeneration.LodServerWorld; import net.minecraft.core.Registry; import net.minecraft.server.level.ServerChunkCache; import net.minecraft.server.level.ServerLevel; @@ -78,6 +79,7 @@ public class WorldGeneratorWrapper extends AbstractWorldGeneratorWrapper @Override public void generateBiomesOnly(AbstractChunkPosWrapper pos, DistanceGenerationMode generationMode) { + /* List chunkList = new LinkedList<>(); ProtoChunk chunk = new ProtoChunk(((ChunkPosWrapper) pos).getChunkPos(), UpgradeData.EMPTY, serverWorld); chunkList.add(chunk); @@ -182,6 +184,7 @@ public class WorldGeneratorWrapper extends AbstractWorldGeneratorWrapper // long startTime = System.currentTimeMillis(); // long endTime = System.currentTimeMillis(); // System.out.println(endTime - startTime); + */ } @@ -189,6 +192,7 @@ public class WorldGeneratorWrapper extends AbstractWorldGeneratorWrapper @Override public void generateSurface(AbstractChunkPosWrapper pos) { + /* List chunkList = new LinkedList<>(); ProtoChunk chunk = new ProtoChunk(((ChunkPosWrapper) pos).getChunkPos(), UpgradeData.EMPTY, serverWorld); chunkList.add(chunk); @@ -232,6 +236,7 @@ public class WorldGeneratorWrapper extends AbstractWorldGeneratorWrapper @Override public void generateFeatures(AbstractChunkPosWrapper pos) { + /* List chunkList = new LinkedList<>(); ProtoChunk chunk = new ProtoChunk(((ChunkPosWrapper) pos).getChunkPos(), UpgradeData.EMPTY, serverWorld); chunkList.add(chunk); @@ -338,6 +343,7 @@ public class WorldGeneratorWrapper extends AbstractWorldGeneratorWrapper // generate a Lod like normal lodBuilder.generateLodNodeFromChunk(lodDim, new ChunkWrapper(chunk), new LodBuilderConfig(DistanceGenerationMode.FEATURES)); + */ } diff --git a/common/src/main/resources/assets/lod/lang/en_us.json b/src/main/resources/assets/lod/lang/en_us.json similarity index 100% rename from common/src/main/resources/assets/lod/lang/en_us.json rename to src/main/resources/assets/lod/lang/en_us.json diff --git a/common/src/main/resources/assets/lod/textures/gui/button.png b/src/main/resources/assets/lod/textures/gui/button.png similarity index 100% rename from common/src/main/resources/assets/lod/textures/gui/button.png rename to src/main/resources/assets/lod/textures/gui/button.png diff --git a/fabric/src/main/resources/fabric.mod.json b/src/main/resources/fabric.mod.json similarity index 93% rename from fabric/src/main/resources/fabric.mod.json rename to src/main/resources/fabric.mod.json index 56a6256c4..9e6700246 100644 --- a/fabric/src/main/resources/fabric.mod.json +++ b/src/main/resources/fabric.mod.json @@ -37,10 +37,10 @@ "accessWidener" : "lod.accesswidener", "depends": { - "fabricloader": ">=0.11.3", + "fabricloader": ">=0.12.6", "fabric": "*", - "minecraft": "1.17.x", - "java": ">=16" + "minecraft": "1.18", + "java": ">=17" }, "suggests": { "another-mod": "*" diff --git a/common/src/main/resources/icon.png b/src/main/resources/icon.png similarity index 100% rename from common/src/main/resources/icon.png rename to src/main/resources/icon.png diff --git a/common/src/main/resources/lod.accesswidener b/src/main/resources/lod.accesswidener similarity index 100% rename from common/src/main/resources/lod.accesswidener rename to src/main/resources/lod.accesswidener diff --git a/fabric/src/main/resources/lod.common.mixins.json b/src/main/resources/lod.common.mixins.json similarity index 87% rename from fabric/src/main/resources/lod.common.mixins.json rename to src/main/resources/lod.common.mixins.json index 610c2637e..ec976a475 100644 --- a/fabric/src/main/resources/lod.common.mixins.json +++ b/src/main/resources/lod.common.mixins.json @@ -2,7 +2,7 @@ "required": true, "minVersion": "0.8", "package": "com.seibel.lod.common.mixins", - "compatibilityLevel": "JAVA_16", + "compatibilityLevel": "JAVA_17", "mixins": [], "client": [ "MixinOptionsScreen", diff --git a/fabric/src/main/resources/lod.mixins.json b/src/main/resources/lod.mixins.json similarity index 89% rename from fabric/src/main/resources/lod.mixins.json rename to src/main/resources/lod.mixins.json index 478d3c133..f25127bd0 100644 --- a/fabric/src/main/resources/lod.mixins.json +++ b/src/main/resources/lod.mixins.json @@ -2,7 +2,7 @@ "required": true, "minVersion": "0.8", "package": "com.seibel.lod.fabric.mixins", - "compatibilityLevel": "JAVA_16", + "compatibilityLevel": "JAVA_17", "mixins": [], "client": [ "MixinMinecraft", diff --git a/src/main/resources/shaders/flat_shaded.frag b/src/main/resources/shaders/flat_shaded.frag new file mode 100644 index 000000000..7677dfa66 --- /dev/null +++ b/src/main/resources/shaders/flat_shaded.frag @@ -0,0 +1,89 @@ +#version 150 core + +in vec4 vertexColor; +in vec4 vertexWorldPos; +//in vec2 textureCoord; + + +out vec4 fragColor; + + +//uniform sampler2D texImage; +uniform vec3 cameraPos; + +uniform bool fogEnabled; +uniform bool nearFogEnabled; +uniform bool farFogEnabled; + +uniform float nearFogStart; +uniform float nearFogEnd; +uniform float farFogStart; +uniform float farFogEnd; +uniform vec4 fogColor; + + +// method definitions +float getFogAlpha(float start, float end, float dist); + + + +/** + * Fragment Shader + * + * author: James Seibel + * version: 11-26-2021 + */ +void main() +{ + // TODO: add a white texture to support Optifine shaders + //vec4 textureColor = texture(texImage, textureCoord); + //fragColor = vertexColor * textureColor; + + + vec4 returnColor; + if (fogEnabled) + { + // add fog + + float dist = distance(vertexWorldPos, vec4(cameraPos,1)); + // no fog by default + float fogAlpha = 0; + + // less than because nearFogStart is farther away than nearFogEnd + if (nearFogEnabled && dist < nearFogStart) + { + fogAlpha = getFogAlpha(nearFogStart, nearFogEnd, dist); + } + else if (farFogEnabled) + { + fogAlpha = getFogAlpha(farFogStart, farFogEnd, dist); + } + + returnColor = mix(vertexColor, vec4(fogColor.xyz, 1), fogAlpha); + } + else + { + // simple flat color + returnColor = vertexColor; + } + + + + fragColor = returnColor; +} + + + + +/** + * Returns the fog strength for the given fragment. + * This is the same implementation as legacy OpenGL's Linear fog option. + * 1 = completely opaque fog + * 0 = no fog + */ +float getFogAlpha(float start, float end, float dist) +{ + float fogAlpha = 1 - ((end - dist) / (end - start)); + return clamp(fogAlpha, 0, 1); +} + diff --git a/src/main/resources/shaders/standard.vert b/src/main/resources/shaders/standard.vert new file mode 100644 index 000000000..32d457ac6 --- /dev/null +++ b/src/main/resources/shaders/standard.vert @@ -0,0 +1,34 @@ +#version 150 core + +in vec3 vPosition; +in vec4 color; + + +out vec4 vertexColor; +out vec4 vertexWorldPos; +//out vec2 textureCoord; +out float depth; + + +uniform mat4 modelViewMatrix; +uniform mat4 projectionMatrix; + + +/** + * Vertex Shader + * + * author: James Seibel + * version: 11-26-2021 + */ +void main() +{ + // TODO: add a simple white texture to support Optifine shaders + //textureCoord = textureCoord; + + vertexColor = color; + vertexWorldPos = vec4(vPosition, 1); + + // the vPosition needs to be converted to a vec4 so it can be multiplied + // by the 4x4 matrices + gl_Position = projectionMatrix * modelViewMatrix * vec4(vPosition, 1); +}