From 42ba3847608d24911a466cc42ceec3c27cb4c325 Mon Sep 17 00:00:00 2001 From: mt1006 Date: Mon, 1 Jan 2024 19:00:27 +0100 Subject: [PATCH] Version 0.1 --- .gitignore | 25 ++ LICENSE | 165 ++++++++ README.md | 31 ++ build.gradle | 96 +++++ gradle.properties | 29 ++ gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 62076 bytes gradle/wrapper/gradle-wrapper.properties | 6 + gradlew | 245 ++++++++++++ gradlew.bat | 92 +++++ settings.gradle | 17 + src/main/java/com/mt1006/ar_mod/ArMod.java | 37 ++ .../java/com/mt1006/ar_mod/ar/ArThread.java | 189 +++++++++ .../java/com/mt1006/ar_mod/ar/ArWindow.java | 96 +++++ .../mt1006/ar_mod/ar/movement/ArMouse.java | 215 ++++++++++ .../mt1006/ar_mod/ar/movement/ArMovement.java | 163 ++++++++ .../ar_mod/ar/movement/ArPlayerBob.java | 34 ++ .../mt1006/ar_mod/ar/rendering/ArFrame.java | 261 ++++++++++++ .../ar_mod/ar/rendering/ArRenderer.java | 151 +++++++ .../mt1006/ar_mod/ar/rendering/ArShaders.java | 258 ++++++++++++ .../mt1006/ar_mod/ar/rendering/ArTexture.java | 122 ++++++ .../mt1006/ar_mod/config/ConfigFields.java | 270 +++++++++++++ .../com/mt1006/ar_mod/config/ModConfig.java | 132 +++++++ .../ar_mod/config/gui/ConfigScreen.java | 67 ++++ .../ar_mod/config/gui/ModOptionList.java | 371 ++++++++++++++++++ .../ar_mod/mixin/GameRendererMixin.java | 140 +++++++ .../com/mt1006/ar_mod/mixin/GuiMixin.java | 19 + .../ar_mod/mixin/InputConstantsMixin.java | 22 ++ .../ar_mod/mixin/KeyboardHandlerMixin.java | 20 + .../mt1006/ar_mod/mixin/LocalPlayerMixin.java | 48 +++ .../com/mt1006/ar_mod/mixin/MainMixin.java | 30 ++ .../mt1006/ar_mod/mixin/MinecraftMixin.java | 118 ++++++ .../ar_mod/mixin/MouseHandlerMixin.java | 37 ++ .../ar_mod/mixin/MovementTutorialMixin.java | 28 ++ .../ar_mod/mixin/OptionInstanceMixin.java | 26 ++ .../mixin/OptionInstanceSliderMixin.java | 30 ++ .../ar_mod/mixin/OptionsScreenMixin.java | 34 ++ .../ar_mod/mixin/RenderSystemMixin.java | 18 + .../ar_mod/mixin/RenderTargetMixin.java | 30 ++ .../ar_mod/mixin/VirtualScreenMixin.java | 33 ++ .../com/mt1006/ar_mod/mixin/WindowMixin.java | 78 ++++ .../mixin/fields/AbstractTextureFields.java | 11 + .../mixin/fields/MouseHandlerFields.java | 14 + .../mixin/fields/OptionInstanceFields.java | 11 + .../ar_mod/mixin/fields/PlayerFields.java | 11 + .../ar_mod/mixin/fields/ScreenFields.java | 18 + src/main/resources/META-INF/mods.toml | 28 ++ src/main/resources/ar_shaders/common.vsh | 11 + src/main/resources/ar_shaders/frame.fsh | 10 + src/main/resources/ar_shaders/full_ar.fsh | 99 +++++ src/main/resources/ar_shaders/timewarp.fsh | 34 ++ src/main/resources/ar_shaders/vignette.fsh | 11 + .../resources/assets/ar_mod/lang/en_us.json | 87 ++++ .../resources/assets/ar_mod/lang/pl_pl.json | 87 ++++ .../assets/ar_mod/textures/gui/button.png | Bin 0 -> 844 bytes src/main/resources/logo.png | Bin 0 -> 109492 bytes src/main/resources/mixins.ar_mod.json | 32 ++ src/main/resources/pack.mcmeta | 7 + 57 files changed, 4254 insertions(+) create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 README.md create mode 100644 build.gradle create mode 100644 gradle.properties create mode 100644 gradle/wrapper/gradle-wrapper.jar create mode 100644 gradle/wrapper/gradle-wrapper.properties create mode 100644 gradlew create mode 100644 gradlew.bat create mode 100644 settings.gradle create mode 100644 src/main/java/com/mt1006/ar_mod/ArMod.java create mode 100644 src/main/java/com/mt1006/ar_mod/ar/ArThread.java create mode 100644 src/main/java/com/mt1006/ar_mod/ar/ArWindow.java create mode 100644 src/main/java/com/mt1006/ar_mod/ar/movement/ArMouse.java create mode 100644 src/main/java/com/mt1006/ar_mod/ar/movement/ArMovement.java create mode 100644 src/main/java/com/mt1006/ar_mod/ar/movement/ArPlayerBob.java create mode 100644 src/main/java/com/mt1006/ar_mod/ar/rendering/ArFrame.java create mode 100644 src/main/java/com/mt1006/ar_mod/ar/rendering/ArRenderer.java create mode 100644 src/main/java/com/mt1006/ar_mod/ar/rendering/ArShaders.java create mode 100644 src/main/java/com/mt1006/ar_mod/ar/rendering/ArTexture.java create mode 100644 src/main/java/com/mt1006/ar_mod/config/ConfigFields.java create mode 100644 src/main/java/com/mt1006/ar_mod/config/ModConfig.java create mode 100644 src/main/java/com/mt1006/ar_mod/config/gui/ConfigScreen.java create mode 100644 src/main/java/com/mt1006/ar_mod/config/gui/ModOptionList.java create mode 100644 src/main/java/com/mt1006/ar_mod/mixin/GameRendererMixin.java create mode 100644 src/main/java/com/mt1006/ar_mod/mixin/GuiMixin.java create mode 100644 src/main/java/com/mt1006/ar_mod/mixin/InputConstantsMixin.java create mode 100644 src/main/java/com/mt1006/ar_mod/mixin/KeyboardHandlerMixin.java create mode 100644 src/main/java/com/mt1006/ar_mod/mixin/LocalPlayerMixin.java create mode 100644 src/main/java/com/mt1006/ar_mod/mixin/MainMixin.java create mode 100644 src/main/java/com/mt1006/ar_mod/mixin/MinecraftMixin.java create mode 100644 src/main/java/com/mt1006/ar_mod/mixin/MouseHandlerMixin.java create mode 100644 src/main/java/com/mt1006/ar_mod/mixin/MovementTutorialMixin.java create mode 100644 src/main/java/com/mt1006/ar_mod/mixin/OptionInstanceMixin.java create mode 100644 src/main/java/com/mt1006/ar_mod/mixin/OptionInstanceSliderMixin.java create mode 100644 src/main/java/com/mt1006/ar_mod/mixin/OptionsScreenMixin.java create mode 100644 src/main/java/com/mt1006/ar_mod/mixin/RenderSystemMixin.java create mode 100644 src/main/java/com/mt1006/ar_mod/mixin/RenderTargetMixin.java create mode 100644 src/main/java/com/mt1006/ar_mod/mixin/VirtualScreenMixin.java create mode 100644 src/main/java/com/mt1006/ar_mod/mixin/WindowMixin.java create mode 100644 src/main/java/com/mt1006/ar_mod/mixin/fields/AbstractTextureFields.java create mode 100644 src/main/java/com/mt1006/ar_mod/mixin/fields/MouseHandlerFields.java create mode 100644 src/main/java/com/mt1006/ar_mod/mixin/fields/OptionInstanceFields.java create mode 100644 src/main/java/com/mt1006/ar_mod/mixin/fields/PlayerFields.java create mode 100644 src/main/java/com/mt1006/ar_mod/mixin/fields/ScreenFields.java create mode 100644 src/main/resources/META-INF/mods.toml create mode 100644 src/main/resources/ar_shaders/common.vsh create mode 100644 src/main/resources/ar_shaders/frame.fsh create mode 100644 src/main/resources/ar_shaders/full_ar.fsh create mode 100644 src/main/resources/ar_shaders/timewarp.fsh create mode 100644 src/main/resources/ar_shaders/vignette.fsh create mode 100644 src/main/resources/assets/ar_mod/lang/en_us.json create mode 100644 src/main/resources/assets/ar_mod/lang/pl_pl.json create mode 100644 src/main/resources/assets/ar_mod/textures/gui/button.png create mode 100644 src/main/resources/logo.png create mode 100644 src/main/resources/mixins.ar_mod.json create mode 100644 src/main/resources/pack.mcmeta diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..12f8644 --- /dev/null +++ b/.gitignore @@ -0,0 +1,25 @@ +# eclipse +bin +*.launch +.settings +.metadata +.classpath +.project + +# idea +out +*.ipr +*.iws +*.iml +.idea + +# gradle +build +.gradle + +# other +eclipse +run + +# Files from Forge MDK +forge*changelog.txt diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..0a04128 --- /dev/null +++ b/LICENSE @@ -0,0 +1,165 @@ + GNU LESSER GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + + This version of the GNU Lesser General Public License incorporates +the terms and conditions of version 3 of the GNU General Public +License, supplemented by the additional permissions listed below. + + 0. Additional Definitions. + + As used herein, "this License" refers to version 3 of the GNU Lesser +General Public License, and the "GNU GPL" refers to version 3 of the GNU +General Public License. + + "The Library" refers to a covered work governed by this License, +other than an Application or a Combined Work as defined below. + + An "Application" is any work that makes use of an interface provided +by the Library, but which is not otherwise based on the Library. +Defining a subclass of a class defined by the Library is deemed a mode +of using an interface provided by the Library. + + A "Combined Work" is a work produced by combining or linking an +Application with the Library. The particular version of the Library +with which the Combined Work was made is also called the "Linked +Version". + + The "Minimal Corresponding Source" for a Combined Work means the +Corresponding Source for the Combined Work, excluding any source code +for portions of the Combined Work that, considered in isolation, are +based on the Application, and not on the Linked Version. + + The "Corresponding Application Code" for a Combined Work means the +object code and/or source code for the Application, including any data +and utility programs needed for reproducing the Combined Work from the +Application, but excluding the System Libraries of the Combined Work. + + 1. Exception to Section 3 of the GNU GPL. + + You may convey a covered work under sections 3 and 4 of this License +without being bound by section 3 of the GNU GPL. + + 2. Conveying Modified Versions. + + If you modify a copy of the Library, and, in your modifications, a +facility refers to a function or data to be supplied by an Application +that uses the facility (other than as an argument passed when the +facility is invoked), then you may convey a copy of the modified +version: + + a) under this License, provided that you make a good faith effort to + ensure that, in the event an Application does not supply the + function or data, the facility still operates, and performs + whatever part of its purpose remains meaningful, or + + b) under the GNU GPL, with none of the additional permissions of + this License applicable to that copy. + + 3. Object Code Incorporating Material from Library Header Files. + + The object code form of an Application may incorporate material from +a header file that is part of the Library. You may convey such object +code under terms of your choice, provided that, if the incorporated +material is not limited to numerical parameters, data structure +layouts and accessors, or small macros, inline functions and templates +(ten or fewer lines in length), you do both of the following: + + a) Give prominent notice with each copy of the object code that the + Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the object code with a copy of the GNU GPL and this license + document. + + 4. Combined Works. + + You may convey a Combined Work under terms of your choice that, +taken together, effectively do not restrict modification of the +portions of the Library contained in the Combined Work and reverse +engineering for debugging such modifications, if you also do each of +the following: + + a) Give prominent notice with each copy of the Combined Work that + the Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the Combined Work with a copy of the GNU GPL and this license + document. + + c) For a Combined Work that displays copyright notices during + execution, include the copyright notice for the Library among + these notices, as well as a reference directing the user to the + copies of the GNU GPL and this license document. + + d) Do one of the following: + + 0) Convey the Minimal Corresponding Source under the terms of this + License, and the Corresponding Application Code in a form + suitable for, and under terms that permit, the user to + recombine or relink the Application with a modified version of + the Linked Version to produce a modified Combined Work, in the + manner specified by section 6 of the GNU GPL for conveying + Corresponding Source. + + 1) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (a) uses at run time + a copy of the Library already present on the user's computer + system, and (b) will operate properly with a modified version + of the Library that is interface-compatible with the Linked + Version. + + e) Provide Installation Information, but only if you would otherwise + be required to provide such information under section 6 of the + GNU GPL, and only to the extent that such information is + necessary to install and execute a modified version of the + Combined Work produced by recombining or relinking the + Application with a modified version of the Linked Version. (If + you use option 4d0, the Installation Information must accompany + the Minimal Corresponding Source and Corresponding Application + Code. If you use option 4d1, you must provide the Installation + Information in the manner specified by section 6 of the GNU GPL + for conveying Corresponding Source.) + + 5. Combined Libraries. + + You may place library facilities that are a work based on the +Library side by side in a single library together with other library +facilities that are not Applications and are not covered by this +License, and convey such a combined library under terms of your +choice, if you do both of the following: + + a) Accompany the combined library with a copy of the same work based + on the Library, uncombined with any other library facilities, + conveyed under the terms of this License. + + b) Give prominent notice with the combined library that part of it + is a work based on the Library, and explaining where to find the + accompanying uncombined form of the same work. + + 6. Revised Versions of the GNU Lesser General Public License. + + The Free Software Foundation may publish revised and/or new versions +of the GNU Lesser General Public License from time to time. Such new +versions will be similar in spirit to the present version, but may +differ in detail to address new problems or concerns. + + Each version is given a distinguishing version number. If the +Library as you received it specifies that a certain numbered version +of the GNU Lesser General Public License "or any later version" +applies to it, you have the option of following the terms and +conditions either of that published version or of any later version +published by the Free Software Foundation. If the Library as you +received it does not specify a version number of the GNU Lesser +General Public License, you may choose any version of the GNU Lesser +General Public License ever published by the Free Software Foundation. + + If the Library as you received it specifies that a proxy can decide +whether future versions of the GNU Lesser General Public License shall +apply, that proxy's public statement of acceptance of any version is +permanent authorization for you to choose that version for the +Library. diff --git a/README.md b/README.md new file mode 100644 index 0000000..7dd0abb --- /dev/null +++ b/README.md @@ -0,0 +1,31 @@ +### This mod (especially in its current state) is more of a proof of concept than actual optimization mod! +It has many issues and greatly reduces performance of main rendering thread (even when disabled), +so for comparing performance use instance without mod installed. + +# About +**Asynchronous Reprojection** is a Minecraft mod that creates second rendering context to asynchronously reproject frames from main rendering thread with new camera rotation and player position. + +It was inspired by Comrade Stinger's demo, which was also base for shader code: +https://www.youtube.com/watch?v=VvFyOFacljg + +Known issues: +- Fabric version works only on Windows and Linux (I haven't tested it on macos). Forge version works only on Windows. +- Screenshots (F2) and world icons don't work - they're black. +- Minor visual glitches. +- Camera bobbing is disabled. +- "Fabulous" graphics doesn't change anything compared to "Fancy". +- Camera sometimes rotates itself when opening or closing GUI. +- Forge version crashes when trying to resize window during startup. +- It sometimes just crashes (for no particular reason). + +CurseForge page: - + +Modrinth page: - + +# Examples + +![](https://raw.githubusercontent.com/mt1006/mc-ar-mod/_common/screenshots/example1.png) + +![](https://raw.githubusercontent.com/mt1006/mc-ar-mod/_common/screenshots/example2.png) + +![](https://raw.githubusercontent.com/mt1006/mc-ar-mod/_common/screenshots/example3.png) diff --git a/build.gradle b/build.gradle new file mode 100644 index 0000000..1ac0c5f --- /dev/null +++ b/build.gradle @@ -0,0 +1,96 @@ +plugins +{ + id 'eclipse' + id 'idea' + id 'maven-publish' + id 'net.minecraftforge.gradle' version '[6.0.16,6.2)' + id 'org.spongepowered.mixin' version '0.7.+' +} + +version = mod_version +group = mod_group_id + +base +{ + archivesName = "${mod_jar_name}-FORGE-${minecraft_version}" +} + +java.toolchain.languageVersion = JavaLanguageVersion.of(17) + +println "Java: ${System.getProperty 'java.version'}, JVM: ${System.getProperty 'java.vm.version'} (${System.getProperty 'java.vendor'}), Arch: ${System.getProperty 'os.arch'}" +minecraft +{ + mappings channel: mapping_channel, version: mapping_version + copyIdeResources = true + + runs + { + configureEach + { + workingDirectory project.file('run') + property 'forge.logging.markers', 'REGISTRIES' + property 'forge.logging.console.level', 'debug' + } + + client { property 'forge.enabledGameTestNamespaces', mod_id } + server { property 'forge.enabledGameTestNamespaces', mod_id } + } +} + +sourceSets.main.resources { srcDir 'src/generated/resources' } + +dependencies +{ + minecraft "net.minecraftforge:forge:${minecraft_version}-${forge_version}" + annotationProcessor 'org.spongepowered:mixin:0.8.5:processor' +} + +mixin +{ + add sourceSets.main, "mixins.${mod_id}.refmap.json" + config "mixins.${mod_id}.json" +} + +tasks.named('processResources', ProcessResources).configure +{ + var replaceProperties = + [ + minecraft_version: minecraft_version, minecraft_version_range: minecraft_version_range, + forge_version: forge_version, forge_version_range: forge_version_range, + loader_version_range: loader_version_range, + mod_id: mod_id, mod_name: mod_name, mod_license: mod_license, mod_version: mod_version, + mod_authors: mod_authors, mod_description: mod_description, + mod_url: mod_url, mod_issues: mod_issues, mod_credits: mod_credits + ] + inputs.properties replaceProperties + + filesMatching(['META-INF/mods.toml', 'pack.mcmeta']) { expand replaceProperties + [project: project] } +} + +tasks.named('jar', Jar).configure +{ + manifest + { + attributes([ + 'Specification-Title' : mod_id, + 'Specification-Vendor' : mod_authors, + 'Specification-Version' : '1', + 'Implementation-Title' : project.name, + 'Implementation-Version' : project.jar.archiveVersion, + 'Implementation-Vendor' : mod_authors + ]) + } + + finalizedBy 'reobfJar' +} + +tasks.withType(JavaCompile).configureEach { options.encoding = 'UTF-8' } + +eclipse { synchronizationTasks 'genEclipseRuns' } + +sourceSets.each +{ + def dir = layout.buildDirectory.dir("sourcesSets/$it.name") + it.output.resourcesDir = dir + it.java.destinationDirectory = dir +} diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 0000000..584a665 --- /dev/null +++ b/gradle.properties @@ -0,0 +1,29 @@ +org.gradle.jvmargs=-Xmx3G +org.gradle.daemon=false + +minecraft_version=1.20.1 +forge_version=47.2.0 + +minecraft_version_range=[1.20,1.20.2) +forge_version_range=[46,) +loader_version_range=[46,) + +mapping_channel=official +mapping_version=1.20.1 + + +## Mod Properties +mod_id=ar_mod +mod_version=0.1 + +mod_group_id=com.mt1006.ar_mod +mod_jar_name=ArMod + +mod_name=Asynchronous Reprojection +mod_license=GNU Lesser General Public License v3.0 + +mod_authors=mt1006 (mt1006x) +mod_credits=Comrade Stinger (https://www.youtube.com/@comradestinger) whose demo was an inspiration for me and a base for shader code. +mod_description=Asynchronous reprojection in Minecraft +mod_url=https://github.com/mt1006/mc-ar-mod +mod_issues=https://github.com/mt1006/mc-ar-mod/issues \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..c1962a79e29d3e0ab67b14947c167a862655af9b GIT binary patch literal 62076 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 literal 0 HcmV?d00001 diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..309b4e1 --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-all.zip +networkTimeout=10000 +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew new file mode 100644 index 0000000..aeb74cb --- /dev/null +++ b/gradlew @@ -0,0 +1,245 @@ +#!/bin/sh + +# +# 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. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +############################################################################## +# +# 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/HEAD/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 +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 + +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit + +# Use the maximum available, or set MAX_FD != -1 to use that value. +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 ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +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 + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + 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 +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# 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. + +# 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 + + +# 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"' + +# 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 \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# 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/gradlew.bat b/gradlew.bat new file mode 100644 index 0000000..93e3f59 --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,92 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%"=="" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if %ERRORLEVEL% equ 0 goto execute + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if %ERRORLEVEL% equ 0 goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/settings.gradle b/settings.gradle new file mode 100644 index 0000000..0ac3745 --- /dev/null +++ b/settings.gradle @@ -0,0 +1,17 @@ +pluginManagement +{ + repositories + { + gradlePluginPortal() + maven + { + name = 'MinecraftForge' + url = 'https://maven.minecraftforge.net/' + } + } +} + +plugins +{ + id 'org.gradle.toolchains.foojay-resolver-convention' version '0.7.0' +} \ No newline at end of file diff --git a/src/main/java/com/mt1006/ar_mod/ArMod.java b/src/main/java/com/mt1006/ar_mod/ArMod.java new file mode 100644 index 0000000..a7ee87f --- /dev/null +++ b/src/main/java/com/mt1006/ar_mod/ArMod.java @@ -0,0 +1,37 @@ +package com.mt1006.ar_mod; + +import com.mt1006.ar_mod.config.ModConfig; +import net.minecraftforge.common.MinecraftForge; +import net.minecraftforge.eventbus.api.SubscribeEvent; +import net.minecraftforge.fml.common.Mod; +import net.minecraftforge.fml.event.lifecycle.FMLCommonSetupEvent; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +@Mod(ArMod.MOD_ID) +@Mod.EventBusSubscriber(modid = ArMod.MOD_ID, bus = Mod.EventBusSubscriber.Bus.MOD) +public class ArMod +{ + public static final String MOD_ID = "ar_mod"; + public static final String VERSION = "0.1"; + public static final String FOR_VERSION = "1.20.1"; + public static final String FOR_LOADER = "Forge"; + public static final Logger LOGGER = LogManager.getLogger(); + + public ArMod() + { + MinecraftForge.EVENT_BUS.register(this); + } + + @SubscribeEvent + public static void setup(final FMLCommonSetupEvent event) + { + ArMod.LOGGER.info(getFullName() + " - Author: mt1006 (mt1006x)"); + ModConfig.load(); + } + + public static String getFullName() + { + return "ArMod v" + VERSION + " for Minecraft " + FOR_VERSION + " [" + FOR_LOADER + "]"; + } +} diff --git a/src/main/java/com/mt1006/ar_mod/ar/ArThread.java b/src/main/java/com/mt1006/ar_mod/ar/ArThread.java new file mode 100644 index 0000000..0034041 --- /dev/null +++ b/src/main/java/com/mt1006/ar_mod/ar/ArThread.java @@ -0,0 +1,189 @@ +package com.mt1006.ar_mod.ar; + +import com.mt1006.ar_mod.ArMod; +import com.mt1006.ar_mod.ar.movement.ArMouse; +import com.mt1006.ar_mod.ar.movement.ArMovement; +import com.mt1006.ar_mod.ar.movement.ArPlayerBob; +import com.mt1006.ar_mod.ar.rendering.ArRenderer; +import com.mt1006.ar_mod.config.ModConfig; +import net.minecraft.util.thread.ReentrantBlockableEventLoop; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.lwjgl.glfw.GLFW; + +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.function.Supplier; + +public class ArThread +{ + public static Thread mainThread; + public static @Nullable Thread gameThread = null; + private static EventLoop initEventLoop; + private static final AtomicBoolean initFinished = new AtomicBoolean(false); + private static volatile Object tempReference = null; + private static double lastDrawTime = 0.0; + private static int oldInterval = -1; + private static int fps = 0; + private static long maxFrameTime = -1, second = -1, oldNanoTime = -1; + + public static void init(Thread newThread) + { + mainThread = Thread.currentThread(); + gameThread = newThread; + initEventLoop = new EventLoop(mainThread, "ArMod main thread"); + } + + public static void run() + { + while (!initFinished.get()) + { + initEventLoop.runAllTasks(); + checkGameThread(); + } + + ArRenderer.init(); + while (true) + { + if (ModConfig.isFPSLimitEnabled()) { limitFPS(ModConfig.maxReprojectedFPS.val); } + updateVSync(ModConfig.reprojectionVSync.val); + + GLFW.glfwPollEvents(); + ArPlayerBob.updateTimer(); + ArRenderer.render(); + initEventLoop.runAllTasks(); + + if (ModConfig.printAsyncFPS.val) { asyncFPS(); } + checkGameThread(); + } + } + + private static void limitFPS(int limit) + { + double expectedTime = lastDrawTime + 1.0 / (double)limit; + + double currentTime = GLFW.glfwGetTime(); + while (currentTime < expectedTime) + { + GLFW.glfwWaitEventsTimeout(expectedTime - currentTime); + currentTime = GLFW.glfwGetTime(); + } + + lastDrawTime = currentTime; + } + + private static void updateVSync(boolean val) + { + int interval = val ? 1 : 0; + if (interval != oldInterval) + { + GLFW.glfwSwapInterval(interval); + oldInterval = interval; + } + } + + private static void asyncFPS() + { + long nanoTime = System.nanoTime(); + long currentSecond = nanoTime / 1000000000L; + + long frameTime = nanoTime - oldNanoTime; + if (frameTime > maxFrameTime) { maxFrameTime = frameTime; } + oldNanoTime = nanoTime; + + if (currentSecond != second) + { + double msFrameTime = (double)maxFrameTime / 1000000.0; + if (second != -1) + { + ArMod.LOGGER.info(String.format("Async FPS: %d (Worst: %.1f ms = %d FPS)", fps, msFrameTime, (int)(1000.0 / msFrameTime))); + } + + fps = 0; + maxFrameTime = -1; + second = currentSecond; + } + fps++; + } + + private static void checkGameThread() + { + if (gameThread != null && !gameThread.isAlive()) { System.exit(-1); } + } + + public static void finishInit() + { + initFinished.set(true); + } + + public static void nextFrame() + { + if (ModConfig.simulateRealDelay.val) { setNextFrame(); } + ArMouse.updateMouseRenderThread(); + ArMovement.updatePlayerPosition(); + } + + public static void finishFrame() + { + ArWindow.getWriteFrame().finishFrame(); + if (!ModConfig.simulateRealDelay.val) { setNextFrame(); } + } + + private static void setNextFrame() + { + ArWindow.currentFrameID++; + ArWindow.renderingLevel = false; + ArWindow.getWriteFrame().refreshSize(); + } + + public static void execute(Runnable runnable) + { + initEventLoop.executeBlocking(runnable); + } + + public static void executeAsync(Runnable runnable) + { + initEventLoop.execute(runnable); + } + + public static Object executeAndGet(Supplier runnable) + { + initEventLoop.executeBlocking(() -> tempReference = runnable.get()); + return tempReference; + } + + public static boolean isMainThread() + { + return Thread.currentThread() == mainThread; + } + + public static class EventLoop extends ReentrantBlockableEventLoop + { + private final Thread thread; + + public EventLoop(Thread thread, String name) + { + super(name); + this.thread = thread; + } + + @Override public void runAllTasks() + { + super.runAllTasks(); + } + + @Override protected @NotNull Runnable wrapRunnable(@NotNull Runnable runnable) + { + return runnable; + } + + @Override protected boolean shouldRun(@NotNull Runnable runnable) + { + return true; + } + + @Override protected @NotNull Thread getRunningThread() + { + return thread; + } + } +} diff --git a/src/main/java/com/mt1006/ar_mod/ar/ArWindow.java b/src/main/java/com/mt1006/ar_mod/ar/ArWindow.java new file mode 100644 index 0000000..dae6d18 --- /dev/null +++ b/src/main/java/com/mt1006/ar_mod/ar/ArWindow.java @@ -0,0 +1,96 @@ +package com.mt1006.ar_mod.ar; + +import com.mt1006.ar_mod.ArMod; +import com.mt1006.ar_mod.ar.rendering.ArFrame; +import com.mt1006.ar_mod.config.ModConfig; +import org.lwjgl.glfw.GLFW; +import org.lwjgl.opengl.GL; + +public class ArWindow +{ + public static boolean initialized = false; + public static long visibleWindow = 0; + public static volatile long hiddenWindow = 0; + + private static final ArFrame[] frames = new ArFrame[3]; + private static volatile int writeFramePos = 0, readFramePos = 0; + public static boolean renderingLevel = false; + public static int currentFrameID = 0, lastFrameID = 0; + public static int w = -1, h = -1; + + public static long init(long oldWindow, int w, int h) + { + if (initialized) { return visibleWindow; } + ArWindow.w = w; + ArWindow.h = h; + + visibleWindow = oldWindow; + + GLFW.glfwWindowHint(GLFW.GLFW_VISIBLE, GLFW.GLFW_FALSE); + hiddenWindow = GLFW.glfwCreateWindow(640, 480, "MC-AR-MOD window", 0L, visibleWindow); + + GLFW.glfwMakeContextCurrent(oldWindow); + GL.createCapabilities(); + GLFW.glfwMakeContextCurrent(hiddenWindow); + for (int i = 0; i < 3; i++) { frames[i] = new ArFrame(w, h); } + GLFW.glfwMakeContextCurrent(0L); + + initialized = true; + return visibleWindow; + } + + public static void resize(int w, int h) + { + for (int i = 0; i < 3; i++) + { + frames[i].resize(w, h); + } + ArWindow.w = w; + ArWindow.h = h; + } + + public static ArFrame getReadFrame() + { + return getNextFrame(false); + } + + public static ArFrame getWriteFrame() + { + if (lastFrameID != currentFrameID) + { + lastFrameID = currentFrameID; + return getNextFrame(true); + } + return frames[writeFramePos]; + } + + private static ArFrame getNextFrame(boolean forWrite) + { + int value = forWrite ? writeFramePos : readFramePos; + int newValue = value < 2 ? value + 1 : 0; + + if (forWrite) + { + if (newValue == readFramePos) + { + if (ModConfig.warnAboutWaiting.val) { ArMod.LOGGER.warn("AR thread slower than render!"); } + while (newValue == readFramePos) { Thread.yield(); } + } + + writeFramePos = newValue; + return frames[newValue]; + } + else + { + if (newValue != writeFramePos) + { + readFramePos = newValue; + return frames[newValue]; + } + else + { + return frames[readFramePos]; + } + } + } +} \ No newline at end of file diff --git a/src/main/java/com/mt1006/ar_mod/ar/movement/ArMouse.java b/src/main/java/com/mt1006/ar_mod/ar/movement/ArMouse.java new file mode 100644 index 0000000..f0ac04c --- /dev/null +++ b/src/main/java/com/mt1006/ar_mod/ar/movement/ArMouse.java @@ -0,0 +1,215 @@ +package com.mt1006.ar_mod.ar.movement; + +import com.google.common.util.concurrent.AtomicDouble; +import com.mojang.blaze3d.vertex.PoseStack; +import com.mojang.math.Axis; +import com.mt1006.ar_mod.ar.ArWindow; +import com.mt1006.ar_mod.ar.rendering.ArFrame; +import com.mt1006.ar_mod.ar.rendering.ArShaders; +import com.mt1006.ar_mod.config.ModConfig; +import com.mt1006.ar_mod.mixin.fields.MouseHandlerFields; +import net.minecraft.client.Camera; +import net.minecraft.client.Minecraft; +import net.minecraft.util.Mth; +import net.minecraft.util.SmoothDouble; +import org.joml.Matrix4f; +import org.joml.Vector2f; +import org.joml.Vector3f; +import org.joml.Vector4f; +import org.lwjgl.glfw.GLFW; + +import java.util.concurrent.atomic.AtomicBoolean; + +public class ArMouse +{ + private static final Vector2f TOP_LEFT = new Vector2f(0.0f, 1.0f); + private static final Vector2f TOP_RIGHT = new Vector2f(1.0f, 1.0f); + private static final Vector2f BOTTOM_LEFT = new Vector2f(0.0f, 0.0f); + private static final Vector2f BOTTOM_RIGHT = new Vector2f(1.0f, 0.0f); + private static final Vector4f NEAR_VECTOR = new Vector4f(); + private static final Vector4f FAR_VECTOR = new Vector4f(); + + private static double lastUpdateTime = 0.0; + private static final AtomicDouble accumulatedDeltaX = new AtomicDouble(0.0); + private static final AtomicDouble accumulatedDeltaY = new AtomicDouble(0.0); + private static final SmoothDouble smoothTurnX = new SmoothDouble(); + private static final SmoothDouble smoothTurnY = new SmoothDouble(); + private static volatile float frozenRotX = 0.0f, frozenRotY = 0.0f; + public static volatile float frozenTick = 0.0f; + private static final AtomicBoolean ignoreFirstMove = new AtomicBoolean(false); + private static final AtomicBoolean oldIgnoreFirstMove = new AtomicBoolean(false); + private static final AtomicBoolean mouseGrabbed = new AtomicBoolean(false); + private static final AtomicBoolean isPlayerScoping = new AtomicBoolean(false); + public static AtomicMatrix projectionMatrix = new AtomicMatrix(); + private static double cursorX, cursorY; + public static volatile float forwardX, forwardY, forwardZ; + + public static void onMove(long window, double x, double y) + { + Minecraft minecraft = Minecraft.getInstance(); + minecraft.execute(() -> ((MouseHandlerFields)minecraft.mouseHandler).callOnMove(window, x, y)); + + if (window != ArWindow.visibleWindow) { return; } + + double updateTime = GLFW.glfwGetTime(); + double updateTimeDiff = updateTime - lastUpdateTime; + lastUpdateTime = updateTime; + + int deltaX, deltaY; + if (ignoreFirstMove.get()) + { + deltaX = 0; + deltaY = 0; + ignoreFirstMove.set(false); + } + else + { + deltaX = (int)(x - cursorX); + deltaY = (int)(y - cursorY); + } + cursorX = x; + cursorY = y; + + if (mouseGrabbed.get() && minecraft.isWindowActive()) + { + updateCamera(minecraft, deltaX, deltaY, updateTimeDiff); + } + } + + private static void updateCamera(Minecraft minecraft, int cursorDeltaX, int cursorDeltaY, double updateTimeDiff) + { + double sensitivitySetting = minecraft.options.sensitivity().get() * 0.6 + 0.2; + double lowSensitivity = sensitivitySetting * sensitivitySetting * sensitivitySetting; + double normalSensitivity = lowSensitivity * 8.0; + + double deltaX, deltaY; + + if (minecraft.options.smoothCamera) + { + deltaX = smoothTurnX.getNewDeltaValue(cursorDeltaX * normalSensitivity, updateTimeDiff * normalSensitivity); + deltaY = smoothTurnY.getNewDeltaValue(cursorDeltaY * normalSensitivity, updateTimeDiff * normalSensitivity); + } + else + { + smoothTurnX.reset(); + smoothTurnY.reset(); + double sensitivity = (minecraft.options.getCameraType().isFirstPerson() && isPlayerScoping.get()) + ? lowSensitivity : normalSensitivity; + deltaX = cursorDeltaX * sensitivity; + deltaY = cursorDeltaY * sensitivity; + } + + accumulatedDeltaY.addAndGet(deltaX); + accumulatedDeltaX.addAndGet(deltaY); + } + + public static void updateMouseRenderThread() + { + Minecraft minecraft = Minecraft.getInstance(); + + boolean ignoreFirstMoveVal = ((MouseHandlerFields)minecraft.mouseHandler).getIgnoreFirstMove(); + if (ignoreFirstMoveVal && !oldIgnoreFirstMove.get()) { ignoreFirstMove.set(true); } + oldIgnoreFirstMove.set(ignoreFirstMoveVal); + + mouseGrabbed.set(((MouseHandlerFields)minecraft.mouseHandler).getMouseGrabbed()); + isPlayerScoping.set(minecraft.player != null && minecraft.player.isScoping()); + + double deltaX = accumulatedDeltaX.getAndSet(0.0); + double deltaY = accumulatedDeltaY.getAndSet(0.0); + + minecraft.getTutorial().onMouse(deltaX, deltaY); + if (minecraft.player != null && minecraft.level != null) + { + int invertMouse = minecraft.options.invertYMouse().get() ? -1 : 1; + minecraft.player.turn(deltaY, deltaX * invertMouse); + + Camera camera = new Camera(); + camera.setup(minecraft.level, + minecraft.getCameraEntity() == null ? minecraft.player : minecraft.getCameraEntity(), + !minecraft.options.getCameraType().isFirstPerson(), + minecraft.options.getCameraType().isMirrored(), frozenTick); + frozenRotX = camera.getXRot(); + frozenRotY = camera.getYRot(); + + Vector3f forwardVector = new Vector3f(camera.getLookVector()).normalize(); + forwardX = forwardVector.x; + forwardY = forwardVector.y; + forwardZ = forwardVector.z; + } + } + + public static void setUniforms(ArFrame frame) + { + Matrix4f viewMatrix; + if (ModConfig.cameraReprojection.val) + { + viewMatrix = recreateViewMatrix(getRotX(), getRotY(), 0.0f); + ArPlayerBob.applyBobIfNeeded(viewMatrix); + } + else + { + viewMatrix = frame.shaderInput.viewMatrix; + } + + Matrix4f matrix = projectionMatrix.get().mul(viewMatrix, new Matrix4f()).invert(); + ArShaders.setVectorTopLeft(viewportPointToRay(TOP_LEFT, matrix)); + ArShaders.setVectorTopRight(viewportPointToRay(TOP_RIGHT, matrix)); + ArShaders.setVectorBottomLeft(viewportPointToRay(BOTTOM_LEFT, matrix)); + ArShaders.setVectorBottomRight(viewportPointToRay(BOTTOM_RIGHT, matrix)); + } + + public static Matrix4f recreateViewMatrix(float xRot, float yRot, float zRot) + { + PoseStack poseStack = new PoseStack(); + poseStack.mulPose(Axis.ZP.rotationDegrees(zRot)); + poseStack.mulPose(Axis.XP.rotationDegrees(xRot)); + poseStack.mulPose(Axis.YP.rotationDegrees(yRot + 180.0f)); + return poseStack.last().pose(); + } + + public static Vector3f viewportPointToRay(Vector2f point, Matrix4f matrix) + { + //Credits: https://discussions.unity.com/t/code-behind-camera-viewportpointtoray/230871/3/ + Vector4f vec = new Vector4f(point.x * 2.0f - 1.0f, point.y * 2.0f - 1.0f, -1.0f, 1.0f); + vec.mul(matrix, NEAR_VECTOR); + NEAR_VECTOR.div(NEAR_VECTOR.w); + + vec.z = 1.0f; + vec.mul(matrix, FAR_VECTOR); + FAR_VECTOR.div(FAR_VECTOR.w); + + Vector4f retVec = FAR_VECTOR.sub(NEAR_VECTOR).normalize(); + return new Vector3f(retVec.x, retVec.y, retVec.z); + } + + private static float getRotX() + { + double direction = Minecraft.getInstance().options.invertYMouse().get() ? -1.0 : 1.0; + return Mth.clamp((float)(accumulatedDeltaX.get() * 0.15 * direction) + frozenRotX, -90.0f, 90.0f); + } + + private static float getRotY() + { + return (float)(accumulatedDeltaY.get() * 0.15) + frozenRotY; + } + + public static class AtomicMatrix + { + private volatile Matrix4f matrix1 = new Matrix4f(); + private volatile Matrix4f matrix2 = new Matrix4f(); + private final AtomicBoolean matrixSwitch = new AtomicBoolean(false); + + public Matrix4f get() + { + return matrixSwitch.get() ? matrix2 : matrix1; + } + + public void set(Matrix4f newMatrix) + { + boolean matrixSwitchVal = matrixSwitch.get(); + if (matrixSwitchVal) { matrix1 = newMatrix; } + else { matrix2 = newMatrix; } + matrixSwitch.set(!matrixSwitchVal); + } + } +} diff --git a/src/main/java/com/mt1006/ar_mod/ar/movement/ArMovement.java b/src/main/java/com/mt1006/ar_mod/ar/movement/ArMovement.java new file mode 100644 index 0000000..da37787 --- /dev/null +++ b/src/main/java/com/mt1006/ar_mod/ar/movement/ArMovement.java @@ -0,0 +1,163 @@ +package com.mt1006.ar_mod.ar.movement; + +import com.mt1006.ar_mod.ar.ArWindow; +import net.minecraft.client.Minecraft; +import net.minecraft.world.phys.Vec3; +import org.joml.Vector3d; +import org.joml.Vector3f; + +import java.util.concurrent.atomic.AtomicLong; + +public class ArMovement +{ + private static final double DOUBLE_PRESS_TIME = 0.4; + private static volatile double cameraX, cameraY, cameraZ; + private static final SynchronizedVector initialPosition = new SynchronizedVector(); + private static final SynchronizedVector velocity = new SynchronizedVector(); + private static double currentTimePos = 0; + private static volatile double newTimePos = 0; + private static int currentFrameID = 0; + private static volatile int newFrameID = 0; + private static boolean updatedWithProperFrame = false; + private static double oldTimeDiff = 0.01; + private static double lastForwardTime = -DOUBLE_PRESS_TIME; + private static double lastJumpTime = -DOUBLE_PRESS_TIME; + public static Vec3 newPos = Vec3.ZERO; + private static Vec3 oldPos = Vec3.ZERO; + public static volatile boolean sprintTriggered = false; + public static volatile boolean flyingTriggered = false; + public static AtomicLong frameCounter = new AtomicLong(0); + private static final double CORRECTION = 0.2; //TODO: add to config + + public static void keyPress(long window, int key, int scancode, int action, int mods) + { + Minecraft minecraft = Minecraft.getInstance(); + minecraft.execute(() -> minecraft.keyboardHandler.keyPress(window, key, scancode, action, mods)); + + if (window == ArWindow.visibleWindow && minecraft.screen == null && action == 1) + { + if (minecraft.options.keyUp.matches(key, scancode)) + { + double newForwardTime = getPreciseTime(); + if (newForwardTime - lastForwardTime < DOUBLE_PRESS_TIME) + { + sprintTriggered = true; + lastForwardTime = -DOUBLE_PRESS_TIME; + } + else + { + lastForwardTime = newForwardTime; + } + } + else if (minecraft.options.keyJump.matches(key, scancode)) + { + double newJumpTime = getPreciseTime(); + if (newJumpTime - lastJumpTime < DOUBLE_PRESS_TIME) + { + flyingTriggered = true; + lastJumpTime = -DOUBLE_PRESS_TIME; + } + else + { + lastJumpTime = newJumpTime; + } + } + } + } + + public static void updatePlayerPosition() + { + Minecraft minecraft = Minecraft.getInstance(); + if (minecraft.player == null || minecraft.level == null) { return; } + + if (updatedWithProperFrame && isProperFrame()) + { + double timeDiff = getPreciseTime() - newTimePos; + double newCameraX = initialPosition.x + (velocity.x * timeDiff); + double newCameraY = initialPosition.y + (velocity.y * timeDiff); + double newCameraZ = initialPosition.z + (velocity.z * timeDiff); + + //double desiredVX = (newPos.x - newCameraX) / oldTimeDiff; + //double desiredVY = (newPos.y - newCameraY) / oldTimeDiff; + //double desiredVZ = (newPos.z - newCameraZ) / oldTimeDiff; + double desiredVX = (((newPos.x - newCameraX) / oldTimeDiff) + velocity.x) / 2.0; + double desiredVY = (((newPos.y - newCameraY) / oldTimeDiff) + velocity.y) / 2.0; + double desiredVZ = (((newPos.z - newCameraZ) / oldTimeDiff) + velocity.z) / 2.0; + + //velocity.x = (newPos.x - newCameraX) / oldTimeDiff; + //velocity.y = (newPos.y - newCameraY) / oldTimeDiff; + //velocity.z = (newPos.z - newCameraZ) / oldTimeDiff; + //velocity.x = (newPos.x - oldPos.x) / oldTimeDiff; + //velocity.y = (newPos.y - oldPos.y) / oldTimeDiff; + //velocity.z = (newPos.z - oldPos.z) / oldTimeDiff; + velocity.x = ((newPos.x - oldPos.x) / oldTimeDiff) * (1.0 - CORRECTION) + desiredVX * CORRECTION; + velocity.y = ((newPos.y - oldPos.y) / oldTimeDiff) * (1.0 - CORRECTION) + desiredVY * CORRECTION; + velocity.z = ((newPos.z - oldPos.z) / oldTimeDiff) * (1.0 - CORRECTION) + desiredVZ * CORRECTION; + + //velocity.x = (((newPos.x - newCameraX) / oldTimeDiff) + velocity.x) / 2.0; + //velocity.y = (((newPos.y - newCameraY) / oldTimeDiff) + velocity.y) / 2.0; + //velocity.z = (((newPos.z - newCameraZ) / oldTimeDiff) + velocity.z) / 2.0; + + initialPosition.x = newCameraX; + initialPosition.y = newCameraY; + initialPosition.z = newCameraZ; + + double newTime = getPreciseTime(); + oldTimeDiff = newTime - newTimePos; + newTimePos = newTime; + } + else + { + cameraX = initialPosition.x = newPos.x; + cameraY = initialPosition.y = newPos.y; + cameraZ = initialPosition.z = newPos.z; + + velocity.x = velocity.y = velocity.z = 0.0; + newTimePos = getPreciseTime(); + updatedWithProperFrame = isProperFrame(); + } + + oldPos = newPos; + newFrameID = ArWindow.currentFrameID; + } + + private static boolean isProperFrame() + { + return frameCounter.get() > 1; + } + + public static Vector3f getNewCameraPosition() + { + if (newFrameID != currentFrameID) + { + currentFrameID = newFrameID; + currentTimePos = newTimePos; + + initialPosition.synchronize(); + velocity.synchronize(); + } + + double timeDiff = getPreciseTime() - currentTimePos; + cameraX = initialPosition.vector.x + (velocity.vector.x * timeDiff); + cameraY = initialPosition.vector.y + (velocity.vector.y * timeDiff); + cameraZ = initialPosition.vector.z + (velocity.vector.z * timeDiff); + + return new Vector3f((float)cameraX, (float)cameraY, (float)cameraZ); + } + + private static double getPreciseTime() + { + return (double)System.nanoTime() / 1.0E9; + } + + private static class SynchronizedVector + { + public volatile double x, y, z; + public final Vector3d vector = new Vector3d(); + + public void synchronize() + { + vector.set(x, y, z); + } + } +} diff --git a/src/main/java/com/mt1006/ar_mod/ar/movement/ArPlayerBob.java b/src/main/java/com/mt1006/ar_mod/ar/movement/ArPlayerBob.java new file mode 100644 index 0000000..a4c0a79 --- /dev/null +++ b/src/main/java/com/mt1006/ar_mod/ar/movement/ArPlayerBob.java @@ -0,0 +1,34 @@ +package com.mt1006.ar_mod.ar.movement; + +import com.mojang.math.Axis; +import com.mt1006.ar_mod.config.ModConfig; +import net.minecraft.Util; +import net.minecraft.client.Minecraft; +import net.minecraft.client.Timer; +import net.minecraft.util.Mth; +import org.joml.Matrix4f; + +public class ArPlayerBob +{ + private static final Timer timer = new Timer(20.0f, 0); + public static volatile float walkDist = 0.0f, oldWalkDist = 0.0f, bob = 0.0f; + + public static void applyBobIfNeeded(Matrix4f viewMatrix) + { + //TODO: fix + if (ModConfig.recreateBobbing.val && Minecraft.getInstance().options.bobView().get()) + { + float diff = walkDist - oldWalkDist; + float val = -(walkDist + diff * timer.partialTick); + float bobVal = bob; + viewMatrix.translate(Mth.sin(val * (float)Math.PI) * bobVal * 0.5f, -Math.abs(Mth.cos(val * (float)Math.PI) * bobVal), 0.0f); + viewMatrix.rotate(Axis.ZP.rotationDegrees(Mth.sin(val * (float)Math.PI) * bobVal * 3.0f)); + viewMatrix.rotate(Axis.XP.rotationDegrees(Math.abs(Mth.cos(val * (float) Math.PI - 0.2f) * bobVal) * 5.0f)); + } + } + + public static void updateTimer() + { + timer.advanceTime(Util.getMillis()); + } +} diff --git a/src/main/java/com/mt1006/ar_mod/ar/rendering/ArFrame.java b/src/main/java/com/mt1006/ar_mod/ar/rendering/ArFrame.java new file mode 100644 index 0000000..a53557f --- /dev/null +++ b/src/main/java/com/mt1006/ar_mod/ar/rendering/ArFrame.java @@ -0,0 +1,261 @@ +package com.mt1006.ar_mod.ar.rendering; + +import com.mt1006.ar_mod.ar.ArWindow; +import com.mt1006.ar_mod.config.ModConfig; +import net.minecraft.client.Minecraft; +import org.joml.Matrix4f; +import org.joml.Vector3f; +import org.lwjgl.BufferUtils; +import org.lwjgl.opengl.GL30; + +import java.nio.ByteBuffer; + +public class ArFrame +{ + private volatile int w, h, newW, newH; + public volatile float rotX, rotY; + public volatile boolean renderHand = false; + public final SubFrame gui, level; + public final ShaderInput shaderInput = new ShaderInput(); + + public ArFrame(int w, int h) + { + this.w = w; + this.h = h; + this.newW = w; + this.newH = h; + gui = new SubFrame(this, false); + level = new SubFrame(this, true); + } + + public void resize(int w, int h) + { + newW = w; + newH = h; + } + + public void refreshSize() + { + if ((w != newW || h != newH) && newW != 0 && newH != 0) + { + w = newW; + h = newH; + gui.resize(); + level.resize(); + } + } + + public void finishFrame() + { + gui.finishFrame(); + level.finishFrame(); + } + + public void setUniforms() + { + ArShaders.setScreenSize(w, h); + shaderInput.setUniforms(); + } + + public SubFrame getStageSubFrame() + { + return ArWindow.renderingLevel ? level : gui; + } + + public static class SubFrame + { + private final ArFrame parent; + private final boolean isLevel; + private final ArTexture colorTexture, depthTexture; + //private final int colorRBO, depthRBO; + private final FrameRenderTarget renderTarget; + private volatile ArTexture.Format colorFormat; + private volatile ByteBuffer colorPixels, depthPixels; + private volatile boolean newPixels = false; + + public SubFrame(ArFrame parent, boolean isLevel) + { + this.parent = parent; + this.isLevel = isLevel; + + colorTexture = new ArTexture(); + depthTexture = new ArTexture(); + + renderTarget = new FrameRenderTarget(parent.w, parent.h); + //colorRBO = GL30.glGenRenderbuffers(); + //depthRBO = GL30.glGenRenderbuffers(); + + colorPixels = BufferUtils.createByteBuffer(parent.w * parent.h * 4); + depthPixels = isLevel ? BufferUtils.createByteBuffer(parent.w * parent.h * 4) : null; + + GL30.glBindFramebuffer(GL30.GL_FRAMEBUFFER, renderTarget.fbo); + + //GL30.glBindRenderbuffer(GL30.GL_RENDERBUFFER, colorRBO); + //GL30.glRenderbufferStorage(GL30.GL_RENDERBUFFER, colorFormat, parent.w, parent.h); //TODO: check resizing + //GL30.glFramebufferRenderbuffer(GL30.GL_FRAMEBUFFER, GL30.GL_COLOR_ATTACHMENT1, GL30.GL_RENDERBUFFER, colorRBO); + + //GL30.glBindRenderbuffer(GL30.GL_RENDERBUFFER, depthRBO); + //GL30.glRenderbufferStorage(GL30.GL_RENDERBUFFER, GL30.GL_DEPTH_COMPONENT, parent.w, parent.h); + //GL30.glFramebufferRenderbuffer(GL30.GL_FRAMEBUFFER, GL30.GL_DEPTH_ATTACHMENT, GL30.GL_RENDERBUFFER, depthRBO); + + //GL30.glBindRenderbuffer(GL30.GL_RENDERBUFFER, 0); + GL30.glBindFramebuffer(GL30.GL_FRAMEBUFFER, 0); + } + + public void bindWrite() + { + GL30.glBindFramebuffer(GL30.GL_FRAMEBUFFER, renderTarget.fbo); + GL30.glViewport(0, 0, parent.w, parent.h); + + if (GL30.glCheckFramebufferStatus(GL30.GL_DRAW_FRAMEBUFFER) != GL30.GL_FRAMEBUFFER_COMPLETE) + { + GL30.glBindFramebuffer(GL30.GL_DRAW_FRAMEBUFFER, 0); + } + } + + public void bindRead() + { + if (newPixels) + { + newPixels = false; + + /*int pbo = renderTarget.asyncBuffer; + + if (pbo != 0) + { + colorTexture.setDataAsync(parent.w, parent.h, colorFormat, colorPixels, pbo); + if (depthUsed) { depthTexture.setDataAsync(parent.w, parent.h, ArTexture.Format.DEPTH, depthPixels, pbo); } + } + else + { + colorTexture.setData(parent.w, parent.h, colorFormat, colorPixels); + if (depthUsed) { depthTexture.setData(parent.w, parent.h, ArTexture.Format.DEPTH, depthPixels); } + }*/ + + colorTexture.setData(parent.w, parent.h, colorFormat, colorPixels); + if (isDepthUsed()) { depthTexture.setData(parent.w, parent.h, ArTexture.Format.DEPTH, depthPixels); } + } + + colorTexture.updateParameters(); + ArShaders.setColorTexture(colorTexture.id); + if (isDepthUsed()) + { + depthTexture.updateParameters(); + ArShaders.setDepthTexture(depthTexture.id); + } + } + + private void resize() + { + renderTarget.resize(parent.w, parent.h); + colorPixels = BufferUtils.createByteBuffer(parent.w * parent.h * 4); + depthPixels = isLevel ? BufferUtils.createByteBuffer(parent.w * parent.h * 4) : null; + } + + private void finishFrame() + { + GL30.glBindFramebuffer(GL30.GL_DRAW_FRAMEBUFFER, 0); + GL30.glBindFramebuffer(GL30.GL_READ_FRAMEBUFFER, renderTarget.fbo); + + colorFormat = (isLevel || Minecraft.getInstance().level == null) ? ArTexture.Format.RGB : ArTexture.Format.RGBA; + colorPixels.clear(); + GL30.glReadPixels(0, 0, parent.w, parent.h, colorFormat.id, GL30.GL_UNSIGNED_BYTE, colorPixels); + + if (isDepthUsed()) + { + depthPixels.clear(); + GL30.glReadPixels(0, 0, parent.w, parent.h, GL30.GL_DEPTH_COMPONENT, GL30.GL_FLOAT, depthPixels); + } + + GL30.glBindFramebuffer(GL30.GL_READ_FRAMEBUFFER, 0); + newPixels = true; + } + + private boolean isDepthUsed() + { + return isLevel && ModConfig.movementReprojection.val; + } + } + + public static class FrameRenderTarget + { + public int fbo; + //public int asyncBuffer; + private final ArTexture colorTexture, depthTexture; + + public FrameRenderTarget(int w, int h) + { + fbo = 0; + //asyncBuffer = 0; + colorTexture = new ArTexture(); + depthTexture = new ArTexture(); + resize(w, h); + } + + public void resize(int w, int h) + { + destroyBuffers(); + createBuffers(w, h); + } + + private void createBuffers(int w, int h) + { + fbo = GL30.glGenFramebuffers(); + + colorTexture.setParameters(w, h, ArTexture.Format.RGBA); + depthTexture.setParameters(w, h, ArTexture.Format.DEPTH); + + GL30.glBindFramebuffer(GL30.GL_FRAMEBUFFER, fbo); + GL30.glFramebufferTexture2D(GL30.GL_FRAMEBUFFER, GL30.GL_COLOR_ATTACHMENT0, GL30.GL_TEXTURE_2D, colorTexture.id, 0); + GL30.glFramebufferTexture2D(GL30.GL_FRAMEBUFFER, GL30.GL_DEPTH_ATTACHMENT, GL30.GL_TEXTURE_2D, depthTexture.id, 0); + + GL30.glClearColor(0.0f, 0.0f, 0.0f, 0.0f); + GL30.glClearDepth(1.0); + GL30.glClear(GL30.GL_COLOR_BUFFER_BIT | GL30.GL_DEPTH_BUFFER_BIT); + + //TODO: refresh on setting change + /*if (ModConfig.asyncTextures.val) + { + asyncBuffer = GL30.glGenBuffers(); + GL30.glBindBuffer(GL30.GL_PIXEL_UNPACK_BUFFER, asyncBuffer); + GL30.glBufferData(GL30.GL_PIXEL_UNPACK_BUFFER, (long)w * (long)h * 4L, GL30.GL_STREAM_DRAW); + GL30.glBindBuffer(GL30.GL_PIXEL_UNPACK_BUFFER, 0); + }*/ + } + + private void destroyBuffers() + { + /*if (asyncBuffer != 0) + { + GL30.glDeleteBuffers(asyncBuffer); + asyncBuffer = 0; + }*/ + + if (fbo != 0) + { + GL30.glDeleteFramebuffers(fbo); + fbo = 0; + } + } + } + + public static class ShaderInput + { + private static final float[] TEMP_BUFFER = new float[16]; + public volatile float projectionMatrix11 = 0.0f; + public volatile Matrix4f viewMatrix = new Matrix4f(); + public volatile Vector3f cameraPosition = new Vector3f(); + public volatile Vector3f playerPosition = new Vector3f(); + public volatile Vector3f cameraForward = new Vector3f(); + public volatile float farClip = 0.0f; + + public void setUniforms() + { + ArShaders.setProjectionMatrix11(projectionMatrix11); + + //TODO: check synchronization + ArShaders.setViewMatrix(viewMatrix.get(TEMP_BUFFER)); + ArShaders.setFarClip(farClip); + } + } +} diff --git a/src/main/java/com/mt1006/ar_mod/ar/rendering/ArRenderer.java b/src/main/java/com/mt1006/ar_mod/ar/rendering/ArRenderer.java new file mode 100644 index 0000000..79f6d12 --- /dev/null +++ b/src/main/java/com/mt1006/ar_mod/ar/rendering/ArRenderer.java @@ -0,0 +1,151 @@ +package com.mt1006.ar_mod.ar.rendering; + +import com.mt1006.ar_mod.ar.ArWindow; +import com.mt1006.ar_mod.ar.movement.ArMouse; +import com.mt1006.ar_mod.ar.movement.ArMovement; +import com.mt1006.ar_mod.config.ModConfig; +import com.mt1006.ar_mod.mixin.fields.AbstractTextureFields; +import net.minecraft.client.Minecraft; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.util.Mth; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.level.border.WorldBorder; +import org.joml.Vector3f; +import org.lwjgl.glfw.GLFW; +import org.lwjgl.opengl.GL; +import org.lwjgl.opengl.GL11; +import org.lwjgl.opengl.GL30; + +public class ArRenderer +{ + private static final ResourceLocation VIGNETTE_LOCATION = new ResourceLocation("textures/misc/vignette.png"); + private static int oldW = -1, oldH = -1; + private static int vao = 0; + + public static void init() + { + GLFW.glfwMakeContextCurrent(ArWindow.visibleWindow); + GL.createCapabilities(); + GL30.glBlendFunc(GL30.GL_SRC_ALPHA, GL30.GL_ONE_MINUS_SRC_ALPHA); + GL30.glEnable(GL30.GL_BLEND); + + ArShaders.init(); + + float[] vaoPos = { + -1.0f,-1.0f,0.0f, + 1.0f,-1.0f,0.0f, + -1.0f, 1.0f,0.0f, + 1.0f, 1.0f,0.0f }; + float[] vaoUV = { + 0.0f,0.0f, + 1.0f,0.0f, + 0.0f,1.0f, + 1.0f,1.0f }; + + vao = GL30.glGenVertexArrays(); + int[] vbo = new int[2]; + GL30.glGenBuffers(vbo); + + GL30.glBindVertexArray(vao); + GL30.glBindBuffer(GL30.GL_ARRAY_BUFFER, vbo[0]); + GL30.glBufferData(GL30.GL_ARRAY_BUFFER, vaoPos, GL30.GL_STATIC_DRAW); + GL30.glEnableVertexAttribArray(0); + GL30.glVertexAttribPointer(0, 3, GL30.GL_FLOAT, false, 0, 0); + + GL30.glBindBuffer(GL30.GL_ARRAY_BUFFER, vbo[1]); + GL30.glBufferData(GL30.GL_ARRAY_BUFFER, vaoUV, GL30.GL_STATIC_DRAW); + GL30.glEnableVertexAttribArray(1); + GL30.glVertexAttribPointer(1, 2, GL30.GL_FLOAT, false, 0, 0); + + GL30.glBindBuffer(GL30.GL_ARRAY_BUFFER, 0); + } + + public static void render() + { + if (ArWindow.w != oldW || ArWindow.h != oldH) + { + GL11.glViewport(0, 0, ArWindow.w, ArWindow.h); + oldW = ArWindow.w; + oldH = ArWindow.h; + } + + GL11.glClearColor(0.0f, 0.0f, 0.0f, 0.0f); + GL11.glClearDepth(1.0f); + GL11.glClear(GL11.GL_COLOR_BUFFER_BIT | GL11.GL_DEPTH_BUFFER_BIT); + + GL30.glBindVertexArray(vao); + + renderLevel(); + renderVignette(); + renderGui(); + + GL11.glFlush(); + GLFW.glfwSwapBuffers(ArWindow.visibleWindow); + } + + private static void renderLevel() + { + if (Minecraft.getInstance().level == null) { return; } + ArFrame frame = ArWindow.getReadFrame(); + + ArShaders.switchShader(ModConfig.getLevelShader()); + if (ArShaders.isReprojectionEnabled()) + { + frame.setUniforms(); + ArMouse.setUniforms(frame); + ModConfig.setUniforms(); + } + + frame.level.bindRead(); + if (ArShaders.isFullArEnabled()) + { + ArShaders.setCameraVector(new Vector3f(ArMouse.forwardX, ArMouse.forwardY, ArMouse.forwardZ)); + ArShaders.setCameraPosition(ArMovement.getNewCameraPosition().sub(frame.shaderInput.playerPosition).add(ModConfig.getOffset())); + } + GL30.glDrawArrays(GL30.GL_TRIANGLE_STRIP, 0, 4); + } + + private static void renderGui() + { + ArShaders.switchShader(ArShaders.frameShader); + ArWindow.getReadFrame().gui.bindRead(); + GL30.glDrawArrays(GL30.GL_TRIANGLE_STRIP, 0, 4); + } + + private static void renderVignette() + { + Minecraft minecraft = Minecraft.getInstance(); + if (minecraft.level == null || !Minecraft.useFancyGraphics()) { return; } + + Entity entity = minecraft.getCameraEntity(); + if (entity == null) { return; } + + WorldBorder worldBorder = minecraft.level.getWorldBorder(); + double dist = worldBorder.getDistanceToBorder(entity); + double d0 = Math.min(worldBorder.getLerpSpeed() * (double)worldBorder.getWarningTime() * 1000.0, + Math.abs(worldBorder.getLerpTarget() - worldBorder.getSize())); + double d1 = Math.max(worldBorder.getWarningBlocks(), d0); + + dist = dist < d1 ? 1.0 - (dist / d1) : 0.0; + + int texture = ((AbstractTextureFields)minecraft.getTextureManager().getTexture(VIGNETTE_LOCATION)).getIdValue(); + if (texture == -1) { return; } + + ArShaders.switchShader(ArShaders.vignetteShader); + if (dist > 0.0F) + { + float color = (float)Mth.clamp(dist, 0.0, 1.0); + ArShaders.setVignetteColor(0.0f, color, color); + } + else + { + float color = Mth.clamp(minecraft.gui.vignetteBrightness, 0.0F, 1.0f); + ArShaders.setVignetteColor(color, color, color); + } + + GL30.glBlendFuncSeparate(GL30.GL_ZERO, GL30.GL_ONE_MINUS_SRC_COLOR, GL30.GL_ONE, GL30.GL_ZERO); + ArShaders.setColorTexture(texture); + GL30.glDrawArrays(GL30.GL_TRIANGLE_STRIP, 0, 4); + GL30.glBlendFuncSeparate(GL30.GL_SRC_ALPHA, GL30.GL_ONE_MINUS_SRC_ALPHA, GL30.GL_ONE, GL30.GL_ZERO); + } +} diff --git a/src/main/java/com/mt1006/ar_mod/ar/rendering/ArShaders.java b/src/main/java/com/mt1006/ar_mod/ar/rendering/ArShaders.java new file mode 100644 index 0000000..3b0fbb9 --- /dev/null +++ b/src/main/java/com/mt1006/ar_mod/ar/rendering/ArShaders.java @@ -0,0 +1,258 @@ +package com.mt1006.ar_mod.ar.rendering; + +import com.mt1006.ar_mod.ArMod; +import org.joml.Vector3f; +import org.lwjgl.opengl.GL30; + +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import java.util.HashMap; +import java.util.Map; + +public class ArShaders +{ + private static final Map programNameMap = new HashMap<>(); + private static final Map shaderObjectMap = new HashMap<>(); + + private static int currentShader = 0; + public static int frameShader = 0; + public static int vignetteShader = 0; + public static int timewarpShader = 0; + public static int fullArShader = 0; + + private static int frameColorTexture = 0; + + private static int vignetteColorTexture = 0; + private static int vignetteColor = 0; + + private static int timewarpColorTexture = 0; + private static int timewarpViewMatrix = 0; + private static int timewarpHeightMul = 0; + private static int timewarpTopLeft = 0; + private static int timewarpTopRight = 0; + private static int timewarpBottomLeft = 0; + private static int timewarpBottomRight = 0; + private static int timewarpScreenSize = 0; + private static int timewarpFarClip = 0; + + private static int fullArColorTexture = 0; + private static int fullArDepthTexture = 0; + private static int fullArViewMatrix = 0; + private static int fullArHeightMul = 0; + private static int fullArCameraVector = 0; + private static int fullArCameraPosition = 0; + private static int fullArTopLeft = 0; + private static int fullArTopRight = 0; + private static int fullArBottomLeft = 0; + private static int fullArBottomRight = 0; + private static int fullArScreenSize = 0; + private static int fullArFarClip = 0; + private static int fullArSequenceN = 0; + private static int fullArSequenceR = 0; + private static int fullArSequenceA0 = 0; + + public static void init() + { + frameShader = compile("frame"); + vignetteShader = compile("vignette"); + timewarpShader = compile("timewarp"); + fullArShader = compile("full_ar"); + + frameColorTexture = findUniform(frameShader, "colorTexture"); + + vignetteColorTexture = findUniform(vignetteShader, "colorTexture"); + vignetteColor = findUniform(vignetteShader, "color"); + + timewarpColorTexture = findUniform(timewarpShader, "colorTexture"); + timewarpViewMatrix = findUniform(timewarpShader, "viewMatrix"); + timewarpHeightMul = findUniform(timewarpShader, "heightMul"); + timewarpTopLeft = findUniform(timewarpShader, "topLeft"); + timewarpTopRight = findUniform(timewarpShader, "topRight"); + timewarpBottomLeft = findUniform(timewarpShader, "bottomLeft"); + timewarpBottomRight = findUniform(timewarpShader, "bottomRight"); + timewarpScreenSize = findUniform(timewarpShader, "screenSize"); + timewarpFarClip = findUniform(timewarpShader, "farClip"); + + fullArColorTexture = findUniform(fullArShader, "colorTexture"); + fullArDepthTexture = findUniform(fullArShader, "depthTexture"); + fullArViewMatrix = findUniform(fullArShader, "viewMatrix"); + fullArHeightMul = findUniform(fullArShader, "heightMul"); + fullArCameraVector = findUniform(fullArShader, "cameraVector"); + fullArCameraPosition = findUniform(fullArShader, "cameraPosition"); + fullArTopLeft = findUniform(fullArShader, "topLeft"); + fullArTopRight = findUniform(fullArShader, "topRight"); + fullArBottomLeft = findUniform(fullArShader, "bottomLeft"); + fullArBottomRight = findUniform(fullArShader, "bottomRight"); + fullArScreenSize = findUniform(fullArShader, "screenSize"); + fullArFarClip = findUniform(fullArShader, "farClip"); + fullArSequenceN = findUniform(fullArShader, "sequenceN"); + fullArSequenceR = findUniform(fullArShader, "sequenceR"); + fullArSequenceA0 = findUniform(fullArShader, "sequenceA0"); + } + + public static void switchShader(int shader) + { + if (shader == currentShader) { return; } + GL30.glUseProgram(shader); + currentShader = shader; + } + + public static void setColorTexture(int texture) + { + int location = 0; + if (currentShader == frameShader) { location = frameColorTexture; } + else if (currentShader == vignetteShader) { location = vignetteColorTexture; } + else if (currentShader == timewarpShader) { location = timewarpColorTexture; } + else if (currentShader == fullArShader) { location = fullArColorTexture; } + + GL30.glUniform1i(location, 0); + GL30.glActiveTexture(GL30.GL_TEXTURE0); + GL30.glBindTexture(GL30.GL_TEXTURE_2D, texture); + } + + public static void setDepthTexture(int texture) + { + GL30.glUniform1i(fullArDepthTexture, 1); + GL30.glActiveTexture(GL30.GL_TEXTURE1); + GL30.glBindTexture(GL30.GL_TEXTURE_2D, texture); + GL30.glActiveTexture(GL30.GL_TEXTURE0); + } + + public static void setVignetteColor(float r, float g, float b) + { + GL30.glUniform3f(vignetteColor, r, g, b); + } + + public static void setViewMatrix(float[] matrix) + { + GL30.glUniformMatrix4fv(currentShader == fullArShader ? fullArViewMatrix : timewarpViewMatrix, false, matrix); + } + + public static void setProjectionMatrix11(float matrix11) + { + float heightMul = 2.0f / matrix11; + GL30.glUniform1f(currentShader == fullArShader ? fullArHeightMul : timewarpHeightMul, heightMul); + } + + public static void setCameraVector(Vector3f vector) + { + GL30.glUniform3f(fullArCameraVector, vector.x, vector.y, vector.z); + } + + public static void setCameraPosition(Vector3f vector) + { + GL30.glUniform3f(fullArCameraPosition, vector.x, vector.y, vector.z); + } + + public static void setVectorTopLeft(Vector3f vector) + { + GL30.glUniform3f(currentShader == fullArShader ? fullArTopLeft : timewarpTopLeft, vector.x, vector.y, vector.z); + } + + public static void setVectorTopRight(Vector3f vector) + { + GL30.glUniform3f(currentShader == fullArShader ? fullArTopRight : timewarpTopRight, vector.x, vector.y, vector.z); + } + + public static void setVectorBottomLeft(Vector3f vector) + { + GL30.glUniform3f(currentShader == fullArShader ? fullArBottomLeft : timewarpBottomLeft, vector.x, vector.y, vector.z); + } + + public static void setVectorBottomRight(Vector3f vector) + { + GL30.glUniform3f(currentShader == fullArShader ? fullArBottomRight : timewarpBottomRight, vector.x, vector.y, vector.z); + } + + public static void setScreenSize(int w, int h) + { + GL30.glUniform2f(currentShader == fullArShader ? fullArScreenSize : timewarpScreenSize, (float)w, (float)h); + } + + public static void setFarClip(float val) + { + GL30.glUniform1f(currentShader == fullArShader ? fullArFarClip : timewarpFarClip, val); + } + + public static void setSequenceParameters(int n, float r, float a0) + { + GL30.glUniform1i(fullArSequenceN, n); + GL30.glUniform1f(fullArSequenceR, r); + GL30.glUniform1f(fullArSequenceA0, a0); + } + + public static boolean isReprojectionEnabled() + { + return currentShader != frameShader; + } + + public static boolean isFullArEnabled() + { + return currentShader == fullArShader; + } + + private static int compile(String fshFile) + { + int program = GL30.glCreateProgram(); + programNameMap.put(program, "common+" + fshFile); + + int vsh = getOrCompileShaderObject(GL30.GL_VERTEX_SHADER, "common.vsh"); + int fsh = getOrCompileShaderObject(GL30.GL_FRAGMENT_SHADER, fshFile + ".fsh"); + GL30.glAttachShader(program, vsh); + GL30.glAttachShader(program, fsh); + + GL30.glLinkProgram(program); + return program; + } + + private static int findUniform(int program, String name) + { + int location = GL30.glGetUniformLocation(program, name); + if (location == -1) { ArMod.LOGGER.error("Failed to find shader uniform: {}/{}", programNameMap.get(program), name); } + return location; + } + + private static int getOrCompileShaderObject(int type, String name) + { + Integer shaderObject = shaderObjectMap.get(name); + if (shaderObject != null) { return shaderObject; } + + int shader = GL30.glCreateShader(type); + GL30.glShaderSource(shader, loadShaderFile(name)); + compileAndCheck(name, shader); + + shaderObjectMap.put(name, shader); + return shader; + } + + private static String loadShaderFile(String name) + { + try (InputStream stream = ArMod.class.getResourceAsStream("/ar_shaders/" + name)) + { + if (stream == null) { throw new RuntimeException("Shader InputStream is null!"); } + return new String(stream.readAllBytes(), StandardCharsets.UTF_8); + } + catch (Exception exception) + { + throw new RuntimeException(exception); + } + } + + private static void compileAndCheck(String name, int shader) + { + GL30.glCompileShader(shader); + + if (GL30.glGetShaderi(shader, GL30.GL_COMPILE_STATUS) == GL30.GL_FALSE) + { + ArMod.LOGGER.error("Shader compilation error - {}!", name); + if (GL30.glGetShaderi(shader, GL30.GL_INFO_LOG_LENGTH) != 0) + { + ArMod.LOGGER.error(GL30.glGetShaderInfoLog(shader)); + } + else + { + ArMod.LOGGER.error("No more information available!"); + } + } + } +} diff --git a/src/main/java/com/mt1006/ar_mod/ar/rendering/ArTexture.java b/src/main/java/com/mt1006/ar_mod/ar/rendering/ArTexture.java new file mode 100644 index 0000000..5573f3b --- /dev/null +++ b/src/main/java/com/mt1006/ar_mod/ar/rendering/ArTexture.java @@ -0,0 +1,122 @@ +package com.mt1006.ar_mod.ar.rendering; + +import com.mt1006.ar_mod.config.ModConfig; +import org.lwjgl.opengl.GL30; + +import java.nio.ByteBuffer; +import java.nio.FloatBuffer; + +public class ArTexture +{ + public final int id; + protected int w = -1, h = -1; + protected Format format = Format.NONE; + private int filteringMode, wrappingMode; + + public ArTexture() + { + id = GL30.glGenTextures(); + } + + public void setParameters(int w, int h, Format format) + { + this.w = w; + this.h = h; + this.format = format; + + filteringMode = ModConfig.getFilteringMode(); + wrappingMode = ModConfig.getWrappingMode(); + + GL30.glBindTexture(GL30.GL_TEXTURE_2D, id); + GL30.glTexParameteri(GL30.GL_TEXTURE_2D, GL30.GL_TEXTURE_MIN_FILTER, filteringMode); + GL30.glTexParameteri(GL30.GL_TEXTURE_2D, GL30.GL_TEXTURE_MAG_FILTER, filteringMode); + GL30.glTexParameteri(GL30.GL_TEXTURE_2D, GL30.GL_TEXTURE_WRAP_S, wrappingMode); + GL30.glTexParameteri(GL30.GL_TEXTURE_2D, GL30.GL_TEXTURE_WRAP_T, wrappingMode); + + if (format.isDepth) { GL30.glTexImage2D(GL30.GL_TEXTURE_2D, 0, format.internal, w, h, 0, format.id, GL30.GL_FLOAT, (FloatBuffer)null); } + else { GL30.glTexImage2D(GL30.GL_TEXTURE_2D, 0, format.internal, w, h, 0, format.id, GL30.GL_UNSIGNED_BYTE, (ByteBuffer)null); } + + GL30.glBindTexture(GL30.GL_TEXTURE_2D, 0); + } + + public void updateParameters() + { + int newFilteringMode = ModConfig.getFilteringMode(); + int newWrappingMode = ModConfig.getWrappingMode(); + + if (newFilteringMode != filteringMode || newWrappingMode != wrappingMode) + { + filteringMode = newFilteringMode; + wrappingMode = newWrappingMode; + + GL30.glBindTexture(GL30.GL_TEXTURE_2D, id); + GL30.glTexParameteri(GL30.GL_TEXTURE_2D, GL30.GL_TEXTURE_MIN_FILTER, filteringMode); + GL30.glTexParameteri(GL30.GL_TEXTURE_2D, GL30.GL_TEXTURE_MAG_FILTER, filteringMode); + GL30.glTexParameteri(GL30.GL_TEXTURE_2D, GL30.GL_TEXTURE_WRAP_S, wrappingMode); + GL30.glTexParameteri(GL30.GL_TEXTURE_2D, GL30.GL_TEXTURE_WRAP_T, wrappingMode); + GL30.glBindTexture(GL30.GL_TEXTURE_2D, 0); + } + } + + public void setData(int w, int h, Format format, ByteBuffer buffer) + { + if (w != this.w || h != this.h || format != this.format) { setParameters(w, h, format); } + buffer.clear(); + + GL30.glBindTexture(GL30.GL_TEXTURE_2D, id); + + if (format.isDepth) { GL30.glTexImage2D(GL30.GL_TEXTURE_2D, 0, format.internal, w, h, 0, format.id, GL30.GL_FLOAT, buffer); } + else { GL30.glTexImage2D(GL30.GL_TEXTURE_2D, 0, format.internal, w, h, 0, format.id, GL30.GL_UNSIGNED_BYTE, buffer); } + //if (format.isDepth) { GL30.glTexSubImage2D(GL30.GL_TEXTURE_2D, 0, 0, 0, w, h, format.id, GL30.GL_FLOAT, buffer); } + //else { GL30.glTexSubImage2D(GL30.GL_TEXTURE_2D, 0, 0, 0, w, h, format.id, GL30.GL_UNSIGNED_BYTE, buffer); } + + GL30.glBindTexture(GL30.GL_TEXTURE_2D, 0); + } + + //TODO: merge with setData (maybe) + //Credits: http://www.songho.ca/opengl/gl_pbo.html + /*public void setDataAsync(int w, int h, Format format, ByteBuffer buffer, int pbo) + { + if (w != this.w || h != this.h || format != this.format) { setParameters(w, h, format); } + buffer.clear(); + + GL30.glBindTexture(GL30.GL_TEXTURE_2D, id); + GL30.glBindBuffer(GL30.GL_PIXEL_UNPACK_BUFFER, pbo); + + if (format.isDepth) { GL30.glTexImage2D(GL30.GL_TEXTURE_2D, 0, format.internal, w, h, 0, format.id, GL30.GL_FLOAT, (ByteBuffer)null); } + else { GL30.glTexImage2D(GL30.GL_TEXTURE_2D, 0, format.internal, w, h, 0, format.id, GL30.GL_UNSIGNED_BYTE, (ByteBuffer)null); } + + GL30.glBufferData(GL30.GL_PIXEL_UNPACK_BUFFER, (long)w * (long)h * 4L, GL30.GL_STREAM_DRAW); + ByteBuffer dest = GL30.glMapBuffer(GL30.GL_PIXEL_UNPACK_BUFFER, GL30.GL_WRITE_ONLY); + if (dest != null) + { + //dest.clear(); + dest.put(buffer); + } + GL30.glUnmapBuffer(GL30.GL_PIXEL_UNPACK_BUFFER); + + GL30.glBindBuffer(GL30.GL_PIXEL_UNPACK_BUFFER, 0); + GL30.glBindTexture(GL30.GL_TEXTURE_2D, id); + }*/ + + public enum Format + { + NONE(0, 0, 0, false), + RGB(GL30.GL_RGB, GL30.GL_RGB8, 3, false), + RGBA(GL30.GL_RGBA, GL30.GL_RGBA8, 3, false), + DEPTH(GL30.GL_DEPTH_COMPONENT, GL30.GL_DEPTH_COMPONENT32F, 4, true); + + public final int id; + public final int internal; + public final long pixelSize; //TODO: remove if not used + public final boolean isDepth; + + Format(int id, int internal, int pixelSize, boolean isDepth) + { + this.id = id; + this.internal = internal; + this.pixelSize = pixelSize; + this.isDepth = isDepth; + } + } +} diff --git a/src/main/java/com/mt1006/ar_mod/config/ConfigFields.java b/src/main/java/com/mt1006/ar_mod/config/ConfigFields.java new file mode 100644 index 0000000..e36c515 --- /dev/null +++ b/src/main/java/com/mt1006/ar_mod/config/ConfigFields.java @@ -0,0 +1,270 @@ +package com.mt1006.ar_mod.config; + +import com.google.gson.Gson; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParseException; +import com.mt1006.ar_mod.ArMod; +import com.mt1006.ar_mod.config.gui.ModOptionList; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.components.AbstractWidget; +import net.minecraft.network.chat.Component; +import net.minecraft.util.GsonHelper; +import org.apache.commons.lang3.StringUtils; +import org.jetbrains.annotations.Nullable; + +import java.io.*; +import java.nio.charset.StandardCharsets; +import java.util.*; +import java.util.regex.Pattern; + +public class ConfigFields +{ + private static @Nullable Map defaultLanguageKeys = null; + private final File file; + private final List> fields = new ArrayList<>(); + private final Map> fieldMap = new HashMap<>(); + private final Set> fieldSet = new HashSet<>(); + + public ConfigFields(String filename) + { + this.file = new File(Minecraft.getInstance().gameDirectory, "config/" + filename); + } + + public ConfigFields.IntegerField add(String name, int val) + { + ConfigFields.IntegerField field = new ConfigFields.IntegerField(name, val); + addField(field, name); + return field; + } + + public ConfigFields.FloatField add(String name, float val) + { + ConfigFields.FloatField field = new ConfigFields.FloatField(name, val); + addField(field, name); + return field; + } + + public ConfigFields.BooleanField add(String name, boolean val) + { + ConfigFields.BooleanField field = new ConfigFields.BooleanField(name, val); + addField(field, name); + return field; + } + + private void addField(Field field, String name) + { + fields.add(field); + if (fieldMap.put(name, field) != null) { throw new RuntimeException("Duplicate field names!"); }; + if (!fieldSet.add(field)) { throw new RuntimeException("Duplicate fields!"); } + } + + public void save() + { + file.getParentFile().mkdirs(); + try (PrintWriter writer = new PrintWriter(new BufferedWriter(new FileWriter(file)))) + { + fields.forEach((field) -> field.save(writer)); + } + catch (IOException ignore) {} + } + + public void load() + { + int loadedCount = 0; + + try (BufferedReader reader = new BufferedReader(new FileReader(file))) + { + String line; + while ((line = reader.readLine()) != null) + { + if (line.isEmpty()) { continue; } + if (line.charAt(0) == '#') { continue; } + if (StringUtils.isBlank(line)) { continue; } + + int equalSignPos = line.indexOf('='); + if (equalSignPos == -1) { throw new IOException(); } + + String name = line.substring(0, equalSignPos).trim(); + String value = line.substring(equalSignPos + 1).trim(); + + Field field = fieldMap.get(name); + if (field == null) { throw new IOException(); } + + field.load(value); + loadedCount++; + } + } + catch (IOException exception) { save(); } + + if (loadedCount != fields.size()) { save(); } + } + + public void reset() + { + fields.forEach(Field::reset); + } + + private static void loadDefaultLanguageKeys() + { + defaultLanguageKeys = new HashMap<>(); + + try (InputStream stream = ArMod.class.getResourceAsStream("/assets/ar_mod/lang/en_us.json")) + { + if (stream == null) { return; } + + JsonObject json = new Gson().fromJson(new InputStreamReader(stream, StandardCharsets.UTF_8), JsonObject.class); + Pattern replacePattern = Pattern.compile("%(\\d+\\$)?[\\d.]*[df]"); + + for(Map.Entry entry : json.entrySet()) + { + String str = replacePattern.matcher(GsonHelper.convertToString(entry.getValue(), entry.getKey())).replaceAll("%$1s"); + defaultLanguageKeys.put(entry.getKey(), str); + } + } + catch (JsonParseException | IOException ioexception) + { + ArMod.LOGGER.error("Failed to load default language keys!"); + } + } + + public abstract static class Field + { + private static final String NAME_KEY_PREFIX = "ar_mod.options.field."; + private static final String DESC_KEY_SUFFIX = ".desc"; + private static final String DESC_ERROR = "[failed to load description]"; + public final String name; + private final T defVal; + public volatile T val; + + protected Field(String name, T val) + { + this.name = name; + this.val = val; + this.defVal = val; + } + + protected void save(PrintWriter writer) + { + String description = String.format("%s\nDefault value: %s", getDefaultDescription(), this); + BufferedReader reader = new BufferedReader(new StringReader(description)); + reader.lines().forEach((line) -> writer.println("# " + line)); + + writer.printf("%s = %s\n\n", name, this); + } + + protected void load(String str) throws IOException + { + try { fromString(str); } + catch (NumberFormatException exception) { throw new IOException(); } + } + + public void reset() + { + val = defVal; + } + + @Override public String toString() + { + return val.toString(); + } + + public Component getWidgetName() + { + return Component.translatable(NAME_KEY_PREFIX + name); + } + + public String getWidgetNameKey() + { + return NAME_KEY_PREFIX + name; + } + + public Component getWidgetTooltip() + { + return Component.translatable("ar_mod.options.common.tooltip", + Component.translatable(getDescriptionKey()), this.toString()); + } + + private String getDefaultDescription() + { + if (defaultLanguageKeys == null) { loadDefaultLanguageKeys(); } + return defaultLanguageKeys.getOrDefault(getDescriptionKey(), DESC_ERROR); + } + + private String getDescriptionKey() + { + return NAME_KEY_PREFIX + name + DESC_KEY_SUFFIX; + } + + abstract void fromString(String str); + } + + public static class IntegerField extends Field + { + public IntegerField(String name, Integer val) + { + super(name, val); + } + + @Override public void fromString(String str) + { + val = Integer.valueOf(str); + } + + public AbstractWidget createSwitch(List options) + { + return new ModOptionList.IntegerSwitch(this, options); + } + + public AbstractWidget createSlider(int min, int max) + { + return new ModOptionList.IntegerSlider(this, min, max, 1, null); + } + + public AbstractWidget createSlider(int min, int max, int multiplier, @Nullable List specialValues) + { + return new ModOptionList.IntegerSlider(this, min, max, multiplier, specialValues); + } + } + + public static class FloatField extends Field + { + public FloatField(String name, Float val) + { + super(name, val); + } + + @Override public void fromString(String str) + { + val = Float.valueOf(str); + } + + public AbstractWidget createSlider(float min, float max, float step, int tailDigits) + { + return new ModOptionList.FloatSlider(this, min, max, step, tailDigits); + } + + public AbstractWidget createPercentageSlider(int min, int max) + { + return new ModOptionList.FloatPercentageSlider(this, min, max); + } + } + + public static class BooleanField extends Field + { + public BooleanField(String name, Boolean val) + { + super(name, val); + } + + @Override public void fromString(String str) + { + val = Boolean.valueOf(str); + } + + public AbstractWidget createSwitch() + { + return new ModOptionList.BooleanSwitch(this); + } + } +} diff --git a/src/main/java/com/mt1006/ar_mod/config/ModConfig.java b/src/main/java/com/mt1006/ar_mod/config/ModConfig.java new file mode 100644 index 0000000..7b7c8db --- /dev/null +++ b/src/main/java/com/mt1006/ar_mod/config/ModConfig.java @@ -0,0 +1,132 @@ +package com.mt1006.ar_mod.config; + +import com.mt1006.ar_mod.ar.rendering.ArShaders; +import com.mt1006.ar_mod.config.gui.ModOptionList; +import org.joml.Vector3f; +import org.lwjgl.opengl.GL30; + +import java.util.List; + +public class ModConfig +{ + private static final ConfigFields configFields = new ConfigFields("ar_mod.txt"); + + public static final ConfigFields.BooleanField cameraReprojection = configFields.add("camera_reprojection", true); + public static final ConfigFields.BooleanField movementReprojection = configFields.add("movement_reprojection", true); + public static final ConfigFields.IntegerField maxReprojectedFPS = configFields.add("max_reprojected_fps", 120); + public static final ConfigFields.BooleanField reprojectionVSync = configFields.add("reprojection_vsync", true); + private static final ConfigFields.IntegerField wrappingMode = configFields.add("wrapping_mode", 1); + private static final ConfigFields.BooleanField linearTextureFiltering = configFields.add("linear_texture_filtering", false); + public static final ConfigFields.FloatField fovScaling = configFields.add("fov_scaling", 1.0f); + public static final ConfigFields.IntegerField sequenceN = configFields.add("sequence_n", 48); + public static final ConfigFields.FloatField sequenceA0 = configFields.add("sequence_a0", 0.45f); + public static final ConfigFields.FloatField sequenceR = configFields.add("sequence_r", 1.12f); + public static final ConfigFields.BooleanField responsiveSprinting = configFields.add("responsive_sprinting", true); + public static final ConfigFields.BooleanField responsiveFlying = configFields.add("responsive_flying", true); + //public static final ConfigFields.BooleanField asyncTextures = configFields.add("asyncTextures", true); + //public static final ConfigFields.BooleanField asyncFrameBuffer = configFields.add("asyncFrameBuffer", true); + private static final ConfigFields.IntegerField debugFPSLimit = configFields.add("debug_fps_limit", 10); + public static final ConfigFields.BooleanField simulateRealDelay = configFields.add("simulate_real_delay", false); + private static final ConfigFields.FloatField offsetX = configFields.add("offset_x", 0.0f); + private static final ConfigFields.FloatField offsetY = configFields.add("offset_y", 0.0f); + private static final ConfigFields.FloatField offsetZ = configFields.add("offset_z", 0.0f); + public static final ConfigFields.BooleanField printAsyncFPS = configFields.add("print_async_fps", false); + public static final ConfigFields.BooleanField warnAboutWaiting = configFields.add("warn_about_waiting", false); + public static final ConfigFields.BooleanField recreateBobbing = configFields.add("recreate_bobbing__broken", false); + + public static void initWidgets(ModOptionList list) + { + list.add(ModConfig.cameraReprojection.createSwitch()); + list.add(ModConfig.movementReprojection.createSwitch()); + list.add(ModConfig.maxReprojectedFPS.createSlider(3, 37, 10, List.of(37))); + list.add(ModConfig.reprojectionVSync.createSwitch()); + + list.addLabel("plane_reprojection_settings"); + list.add(ModConfig.wrappingMode.createSwitch(List.of(0, 1, 2, 3))); + list.add(ModConfig.linearTextureFiltering.createSwitch()); + list.add(ModConfig.fovScaling.createPercentageSlider(100, 200)); + + list.addLabel("movement_reprojection_quality"); + list.add(ModConfig.sequenceN.createSlider(4, 128)); + list.add(ModConfig.sequenceA0.createSlider(0.05f, 2.0f, 0.05f, 2)); + list.add(ModConfig.sequenceR.createSlider(1.05f, 1.7f, 0.01f, 2)); + list.addViewDistanceLabel(); + + list.addLabel("improvements"); + list.add(ModConfig.responsiveSprinting.createSwitch()); + list.add(ModConfig.responsiveFlying.createSwitch()); + + list.addLabel("debugging_options"); + list.add(ModConfig.debugFPSLimit.createSlider(1, 10, 1, List.of(10))); + list.add(ModConfig.simulateRealDelay.createSwitch()); + list.add(ModConfig.offsetX.createSlider(-3.0f, 3.0f, 0.05f, 2)); + list.add(ModConfig.offsetY.createSlider(-3.0f, 3.0f, 0.05f, 2)); + list.add(ModConfig.offsetZ.createSlider(-3.0f, 3.0f, 0.05f, 2)); + list.add(ModConfig.printAsyncFPS.createSwitch()); + list.add(ModConfig.warnAboutWaiting.createSwitch()); + } + + public static void load() + { + configFields.load(); + } + + public static void save() + { + configFields.save(); + } + + public static void reset() + { + configFields.reset(); + } + + public static boolean isFPSLimitEnabled() + { + return maxReprojectedFPS.val >= 1 && maxReprojectedFPS.val <= 360; + } + + public static int getWrappingMode() + { + return switch (wrappingMode.val) + { + case 0 -> GL30.GL_CLAMP_TO_BORDER; + case 2 -> GL30.GL_REPEAT; + case 3 -> GL30.GL_MIRRORED_REPEAT; + default -> GL30.GL_CLAMP_TO_EDGE; + }; + } + + public static int getFilteringMode() + { + return linearTextureFiltering.val ? GL30.GL_LINEAR : GL30.GL_NEAREST; + } + + public static void setUniforms() + { + if (!ArShaders.isFullArEnabled()) { return; } + ArShaders.setSequenceParameters(sequenceN.val, sequenceR.val, sequenceA0.val); + } + + public static Vector3f getOffset() + { + return new Vector3f(offsetX.val, offsetY.val, offsetZ.val); + } + + public static int getLevelShader() + { + if (cameraReprojection.val || movementReprojection.val) + { + return movementReprojection.val ? ArShaders.fullArShader : ArShaders.timewarpShader; + } + else + { + return ArShaders.frameShader; + } + } + + public static int getDebugFPSLimit(int fromVideoSettings) + { + return (debugFPSLimit.val > 0 && debugFPSLimit.val < 10) ? debugFPSLimit.val : fromVideoSettings; + } +} diff --git a/src/main/java/com/mt1006/ar_mod/config/gui/ConfigScreen.java b/src/main/java/com/mt1006/ar_mod/config/gui/ConfigScreen.java new file mode 100644 index 0000000..2cd7fbb --- /dev/null +++ b/src/main/java/com/mt1006/ar_mod/config/gui/ConfigScreen.java @@ -0,0 +1,67 @@ +package com.mt1006.ar_mod.config.gui; + +import com.mt1006.ar_mod.config.ModConfig; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.GuiGraphics; +import net.minecraft.client.gui.components.Button; +import net.minecraft.client.gui.screens.Screen; +import net.minecraft.network.chat.CommonComponents; +import net.minecraft.network.chat.Component; +import org.jetbrains.annotations.NotNull; + +public class ConfigScreen extends Screen +{ + private final Screen lastScreen; + private ModOptionList list; + private Button doneButton, resetButton; + + public ConfigScreen(Screen lastScreen) + { + super(Component.translatable("ar_mod.options")); + this.lastScreen = lastScreen; + } + + @Override public void init() + { + list = new ModOptionList(Minecraft.getInstance(), width, height, 32, height - 32, 25, font); + + doneButton = Button.builder(CommonComponents.GUI_DONE, (b) -> onDonePress(lastScreen)) + .pos(width / 2 - 155, height - 27).size(150, 20).build(); + resetButton = Button.builder(Component.translatable("ar_mod.options.common.reset_settings"), (b) -> onResetPress(list)) + .pos(width / 2 + 5, height - 27).size(150, 20).build(); + + ModConfig.initWidgets(list); + + addWidget(list); + addWidget(doneButton); + addWidget(resetButton); + } + + private static void onDonePress(Screen lastScreen) + { + ModConfig.save(); + Minecraft.getInstance().setScreen(lastScreen); + } + + private static void onResetPress(ModOptionList list) + { + ModConfig.reset(); + list.updateValues(); + } + + @Override public void onClose() + { + ModConfig.save(); + Minecraft.getInstance().setScreen(this.lastScreen); + } + + @Override public void render(@NotNull GuiGraphics guiGraphics, int mouseX, int mouseY, float idk) + { + renderBackground(guiGraphics); + list.render(guiGraphics, mouseX, mouseY, idk); + doneButton.render(guiGraphics, mouseX, mouseY, idk); + resetButton.render(guiGraphics, mouseX, mouseY, idk); + guiGraphics.drawCenteredString(font, title, width / 2, 20, 16777215); + super.render(guiGraphics, mouseX, mouseY, idk); + } +} \ No newline at end of file diff --git a/src/main/java/com/mt1006/ar_mod/config/gui/ModOptionList.java b/src/main/java/com/mt1006/ar_mod/config/gui/ModOptionList.java new file mode 100644 index 0000000..7633234 --- /dev/null +++ b/src/main/java/com/mt1006/ar_mod/config/gui/ModOptionList.java @@ -0,0 +1,371 @@ +package com.mt1006.ar_mod.config.gui; + +import com.mt1006.ar_mod.config.ConfigFields; +import com.mt1006.ar_mod.config.ModConfig; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.Font; +import net.minecraft.client.gui.GuiGraphics; +import net.minecraft.client.gui.components.*; +import net.minecraft.client.gui.components.events.GuiEventListener; +import net.minecraft.client.gui.narration.NarratableEntry; +import net.minecraft.client.gui.narration.NarrationElementOutput; +import net.minecraft.network.chat.CommonComponents; +import net.minecraft.network.chat.Component; +import net.minecraft.util.Mth; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; + +public class ModOptionList extends ContainerObjectSelectionList +{ + protected static final int ELEMENT_WIDTH = 310; + protected static final int ELEMENT_HEIGHT = 20; + private final Font font; + private final List mutableWidgets = new ArrayList<>(); + + public ModOptionList(Minecraft minecraft, int w, int h, int yTop, int yBottom, int itemHeight, Font font) + { + super(minecraft, w, h, yTop, yBottom, itemHeight); + this.font = font; + } + + public void addLabel(String key) + { + addWidget(new Label(width, 9, Component.translatable("ar_mod.options." + key), font)); + } + + public void addViewDistanceLabel() + { + addWidget(new ViewDistanceLabel(width, font)); + } + + public void add(AbstractWidget widget) + { + widget.setX(width / 2 - 155); + addWidget(widget); + } + + private void addWidget(AbstractWidget widget) + { + addEntry(new ListWidget(widget)); + if (widget instanceof MutableWidget) { mutableWidgets.add((MutableWidget)widget); } + } + + public void updateValues() + { + mutableWidgets.forEach(MutableWidget::update); + } + + @Override public int getRowWidth() + { + return 400; + } + + @Override protected int getScrollbarPosition() + { + return super.getScrollbarPosition() + 32; + } + + protected static class ListWidget extends ContainerObjectSelectionList.Entry + { + private final AbstractWidget widget; + + private ListWidget(AbstractWidget widget) + { + this.widget = widget; + } + + @Override public @NotNull List children() + { + return List.of(widget); + } + + @Override public @NotNull List narratables() + { + return List.of(widget); + } + + @Override public void render(@NotNull GuiGraphics guiGraphics, int a, int b, int c, int d, int e, + int mouseX, int mouseY, boolean h, float i) + { + widget.setY(b); + widget.render(guiGraphics, mouseX, mouseY, i); + } + } + + private static class Label extends StringWidget + { + private final int yOffset; + + public Label(int w, int yOffset, Component component, Font font) + { + super(w, 9, component, font); + this.yOffset = yOffset; + } + + @Override public int getY() + { + return super.getY() + yOffset; + } + } + + public static class ViewDistanceLabel extends Label + { + private double oldVal = -1.0f; + private @Nullable Component component = null; + + public ViewDistanceLabel(int w, Font font) + { + super(w, 6, CommonComponents.EMPTY, font); + } + + @Override public @NotNull Component getMessage() + { + double n = ModConfig.sequenceN.val, a = ModConfig.sequenceA0.val, r = ModConfig.sequenceR.val; + double geometricSeriesSum = a * ((1 - Math.pow(r, n)) / (1 - r)); + double val = Math.max((geometricSeriesSum - 32) / 16 / 1.5, 0.0); + if (val != oldVal || component == null) + { + String str = String.format(Locale.US, ": %.2f", val); + component = Component.translatable("ar_mod.options.common.max_view_distance").append(str); + oldVal = val; + } + return component; + } + } + + public static class BooleanSwitch extends AbstractButton implements MutableWidget + { + private final ConfigFields.BooleanField field; + private final Component component; + + public BooleanSwitch(ConfigFields.BooleanField field) + { + super(0, 0, ELEMENT_WIDTH, ELEMENT_HEIGHT, CommonComponents.EMPTY); + this.field = field; + this.component = field.getWidgetName(); + + updateText(); + setTooltip(Tooltip.create(field.getWidgetTooltip())); + } + + public void updateText() + { + Component val = field.val ? CommonComponents.OPTION_ON : CommonComponents.OPTION_OFF; + setMessage(component.copy().append(": ").append(val)); + } + + @Override public void onPress() + { + this.field.val = !this.field.val; + updateText(); + } + + @Override protected void updateWidgetNarration(@NotNull NarrationElementOutput narrationOutput) + { + defaultButtonNarrationText(narrationOutput); + } + + @Override public void update() + { + updateText(); + } + } + + public static class IntegerSwitch extends AbstractButton implements MutableWidget + { + private static final Component UNDEFINED_OPTION = Component.translatable("ar_mod.options.common.undefined"); + private final ConfigFields.IntegerField field; + private final String key; + private final List options; + private final Component component; + + public IntegerSwitch(ConfigFields.IntegerField field, List options) + { + super(0, 0, ELEMENT_WIDTH, ELEMENT_HEIGHT, CommonComponents.EMPTY); + this.field = field; + this.key = field.getWidgetNameKey(); + this.options = options; + this.component = Component.translatable(key); + + updateText(); + setTooltip(Tooltip.create(field.getWidgetTooltip())); + } + + public void updateText() + { + Component optionComponent = options.contains(field.val) + ? Component.translatable(String.format("%s.%d", key, field.val)) + : UNDEFINED_OPTION; + + setMessage(component.copy().append(": ").append(optionComponent)); + } + + @Override public void onPress() + { + int pos = options.indexOf(field.val); + int newIndex = (pos != options.size() - 1) ? (pos + 1) : 0; + field.val = options.get(newIndex); + updateText(); + } + + @Override protected void updateWidgetNarration(@NotNull NarrationElementOutput narrationOutput) + { + defaultButtonNarrationText(narrationOutput); + } + + @Override public void update() + { + updateText(); + } + } + + private static abstract class AbstractSlider> extends AbstractSliderButton implements MutableWidget + { + protected final ConfigFields.Field field; + protected final Component component; + protected final V min, max; + + public AbstractSlider(ConfigFields.Field field, V min, V max) + { + super(0, 0, ELEMENT_WIDTH, ELEMENT_HEIGHT, CommonComponents.EMPTY, 0.0); + this.field = field; + this.component = field.getWidgetName(); + this.min = min; + this.max = max; + + if (min.compareTo(max) > 0) { throw new RuntimeException("Slider - min bigger than max!"); } + setTooltip(Tooltip.create(field.getWidgetTooltip())); + } + + @Override public void update() + { + updateSliderValue(); + updateMessage(); + } + + abstract protected void updateSliderValue(); + } + + public static class FloatSlider extends AbstractSlider + { + private final float step; + private final int steps, tailDigits; + + public FloatSlider(ConfigFields.FloatField field, float min, float max, float step, int tailDigits) + { + super(field, min, max); + this.step = step; + this.tailDigits = tailDigits; + + float stepRatio = (max - min) / step; + float mantissa = stepRatio - (float)(int)stepRatio; + steps = (mantissa > 0.1f) ? ((int)stepRatio + 2) : ((int)stepRatio + 1); + + update(); + } + + @Override protected void updateSliderValue() + { + float diff = max - min; + value = (field.val < max) ? Mth.clamp((field.val - min) / diff, 0.0f, 1.0f) : 1.0; + } + + @Override protected void applyValue() + { + int pos = Math.min((int)(value * (double)steps), steps - 1); + field.val = (pos < steps - 1) ? (min + (step * pos)) : max; + } + + @Override protected void updateMessage() + { + int pow = (int)Math.pow(10, tailDigits); + int finalVal = Math.abs(Math.round(field.val * pow)); + String format = String.format("%%s%%d.%%%dd", tailDigits); + String sign = field.val < 0.0f ? "-" : ""; + String str = String.format(format, sign, finalVal / pow, finalVal % pow).replace(' ', '0'); + + setMessage(component.copy().append(Component.literal(": " + str))); + } + } + + public static class FloatPercentageSlider extends AbstractSlider + { + public FloatPercentageSlider(ConfigFields.FloatField field, int min, int max) + { + super(field, min, max); + update(); + } + + @Override protected void updateSliderValue() + { + float diff = max - min; + value = (field.val < max) ? Mth.clamp((field.val * 100.0f - (float)min) / diff, 0.0f, 1.0f) : 1.0; + } + + @Override protected void applyValue() + { + int steps = max - min + 1; + int pos = Math.min((int)(value * (double)steps), steps - 1); + field.val = (min + pos) / 100.0f; + } + + @Override protected void updateMessage() + { + String str = String.format(": %d%%", Math.round(field.val * 100.0f)); + setMessage(component.copy().append(Component.literal(str))); + } + } + + public static class IntegerSlider extends AbstractSlider + { + private final int multiplier; + private final @Nullable List specialValues; + + public IntegerSlider(ConfigFields.IntegerField field, int min, int max, int multiplier, @Nullable List specialValues) + { + super(field, min, max); + this.multiplier = multiplier; + this.specialValues = specialValues; + update(); + } + + @Override protected void updateSliderValue() + { + float diff = max - min; + int valPos = field.val / multiplier; + value = (valPos < max) ? Mth.clamp(((float)valPos - (float)min) / diff, 0.0f, 1.0f) : 1.0; + } + + @Override protected void applyValue() + { + int steps = max - min + 1; + int pos = Math.min((int)(value * (double)steps), steps - 1); + field.val = (min + pos) * multiplier; + } + + @Override protected void updateMessage() + { + int valPos = field.val / multiplier; + Component subcomponent = (specialValues != null && specialValues.contains(valPos)) + ? Component.translatable(String.format("%s.%d", field.getWidgetNameKey(), valPos)) + : Component.literal(Integer.toString(field.val)); + + setMessage(component.copy().append(": ").append(subcomponent)); + } + + @Override public void update() + { + updateSliderValue(); + updateMessage(); + } + } + + public interface MutableWidget + { + void update(); + } +} diff --git a/src/main/java/com/mt1006/ar_mod/mixin/GameRendererMixin.java b/src/main/java/com/mt1006/ar_mod/mixin/GameRendererMixin.java new file mode 100644 index 0000000..5f69e4f --- /dev/null +++ b/src/main/java/com/mt1006/ar_mod/mixin/GameRendererMixin.java @@ -0,0 +1,140 @@ +package com.mt1006.ar_mod.mixin; + +import com.mojang.blaze3d.systems.RenderSystem; +import com.mojang.blaze3d.vertex.PoseStack; +import com.mt1006.ar_mod.ar.ArWindow; +import com.mt1006.ar_mod.ar.movement.ArMouse; +import com.mt1006.ar_mod.ar.movement.ArMovement; +import com.mt1006.ar_mod.ar.movement.ArPlayerBob; +import com.mt1006.ar_mod.ar.rendering.ArFrame; +import com.mt1006.ar_mod.config.ModConfig; +import net.minecraft.client.Camera; +import net.minecraft.client.Minecraft; +import net.minecraft.client.renderer.GameRenderer; +import net.minecraft.client.renderer.LevelRenderer; +import net.minecraft.client.renderer.LightTexture; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.phys.Vec3; +import org.joml.Matrix4f; +import org.joml.Vector3f; +import org.lwjgl.opengl.GL11; +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.Redirect; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Mixin(GameRenderer.class) +public abstract class GameRendererMixin +{ + @Shadow @Final Minecraft minecraft; + @Shadow private boolean renderHand; + @Shadow public abstract float getDepthFar(); + @Shadow protected abstract void renderItemInHand(PoseStack p_109121_, Camera p_109122_, float p_109123_); + @Shadow public abstract Matrix4f getProjectionMatrix(double p_254507_); + @Shadow protected abstract double getFov(Camera p_109142_, float p_109143_, boolean p_109144_); + + @Inject(method = "render", at = @At(value = "INVOKE", target = "Lcom/mojang/blaze3d/systems/RenderSystem;viewport(IIII)V", remap = false)) + public void bindLevelFBO(float f1, long l1, boolean renderLevel, CallbackInfo ci) + { + if (renderLevel && this.minecraft.level != null) + { + ArWindow.renderingLevel = true; + ArWindow.getWriteFrame().level.bindWrite(); + } + } + + @Redirect(method = "render", at = @At(value = "INVOKE", target = "Lcom/mojang/blaze3d/systems/RenderSystem;clear(IZ)V", remap = false)) + public void cancelRenderClear(int val, boolean b) + { + if (!ArWindow.renderingLevel) { RenderSystem.clear(val, b); } + } + + @Inject(method = "render", at = @At(value = "INVOKE", target = "Lorg/joml/Matrix4f;setOrtho(FFFFFF)Lorg/joml/Matrix4f;", remap = false)) + public void bindGuiFBO(float f1, long l1, boolean renderLevel, CallbackInfo ci) + { + ArWindow.renderingLevel = false; + ArWindow.getWriteFrame().gui.bindWrite(); + + GL11.glClearColor(0.0f, 0.0f, 0.0f, 0.0f); + GL11.glClearDepth(1.0f); + GL11.glClear(GL11.GL_COLOR_BUFFER_BIT | GL11.GL_DEPTH_BUFFER_BIT); + + if (renderLevel && minecraft.level != null && renderHand) + { + this.renderItemInHand(new PoseStack(), new Camera(), f1); + } + + GL11.glClearDepth(1.0f); + GL11.glClear(GL11.GL_DEPTH_BUFFER_BIT); + } + + @Redirect(method = "renderLevel", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/GameRenderer;bobView(Lcom/mojang/blaze3d/vertex/PoseStack;F)V")) + public void cancelBobView(GameRenderer instance, PoseStack poseStack, float ticks) + { + if (ModConfig.recreateBobbing.val && minecraft.getCameraEntity() instanceof Player) + { + Player player = (Player)this.minecraft.getCameraEntity(); + ArPlayerBob.walkDist = player.walkDist; + ArPlayerBob.oldWalkDist = player.walkDistO; + ArPlayerBob.bob = player.bob; + } + } + + @Redirect(method = "renderLevel", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/Camera;getXRot()F")) + public float captureCamera(Camera camera) + { + //TODO: add roll + ArFrame frame = ArWindow.getWriteFrame(); + frame.shaderInput.viewMatrix = ArMouse.recreateViewMatrix(camera.getXRot(), camera.getYRot(), 0.0f); + frame.shaderInput.cameraForward = new Vector3f(camera.getLookVector()).normalize(); + + if (minecraft.player != null) + { + frame.rotX = camera.getXRot(); + frame.rotY = camera.getYRot(); + + //TODO: fix eye height + Vec3 cameraPos = camera.getPosition().subtract(0.0, minecraft.player.getEyeHeight(), 0.0); + ArMovement.newPos = cameraPos; + ArMovement.frameCounter.incrementAndGet(); + frame.shaderInput.playerPosition = cameraPos.toVector3f(); + } + else + { + frame.shaderInput.playerPosition = new Vector3f(); + } + return camera.getXRot(); + } + + @Redirect(method = "renderLevel", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/LevelRenderer;renderLevel(Lcom/mojang/blaze3d/vertex/PoseStack;FJZLnet/minecraft/client/Camera;Lnet/minecraft/client/renderer/GameRenderer;Lnet/minecraft/client/renderer/LightTexture;Lorg/joml/Matrix4f;)V")) + public void captureFrameData(LevelRenderer instance, PoseStack poseStack, float f1, long l1, boolean b1, Camera camera, + GameRenderer gameRenderer, LightTexture lightTexture, Matrix4f matrix) + { + //TODO: move it + ArMouse.frozenTick = f1; + Matrix4f newMatrix = new Matrix4f(matrix); + if (ModConfig.fovScaling.val != 1.0f) + { + newMatrix.mul(getProjectionMatrix(getFov(camera, f1, true)).invert()); + newMatrix.mul(getProjectionMatrix(getFov(camera, f1, true) / ModConfig.fovScaling.val)); + } + ArMouse.projectionMatrix.set(newMatrix); + + ArFrame frame = ArWindow.getWriteFrame(); + frame.renderHand = renderHand; + frame.shaderInput.projectionMatrix11 = matrix.get(1, 1); + frame.shaderInput.cameraPosition = camera.getPosition().toVector3f(); + frame.shaderInput.farClip = getDepthFar(); + + instance.renderLevel(poseStack, f1, l1, b1, camera, gameRenderer, lightTexture, matrix); + } + + @Redirect(method = "renderLevel", at = @At(value = "INVOKE", target = "Lcom/mojang/blaze3d/systems/RenderSystem;clear(IZ)V", remap = false)) + public void cancelRenderLevelClear(int val, boolean b) {} + + @Redirect(method = "renderLevel", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/GameRenderer;renderItemInHand(Lcom/mojang/blaze3d/vertex/PoseStack;Lnet/minecraft/client/Camera;F)V")) + public void cancelRenderItemInHand(GameRenderer gameRenderer, PoseStack poseStack, Camera camera, float f) {} +} diff --git a/src/main/java/com/mt1006/ar_mod/mixin/GuiMixin.java b/src/main/java/com/mt1006/ar_mod/mixin/GuiMixin.java new file mode 100644 index 0000000..773bd15 --- /dev/null +++ b/src/main/java/com/mt1006/ar_mod/mixin/GuiMixin.java @@ -0,0 +1,19 @@ +package com.mt1006.ar_mod.mixin; + +import net.minecraft.client.gui.Gui; +import net.minecraft.client.gui.GuiGraphics; +import net.minecraft.world.entity.Entity; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Mixin(Gui.class) +public abstract class GuiMixin +{ + @Inject(method = "renderVignette", at = @At(value = "HEAD"), cancellable = true) + private void cancelRenderVignette(GuiGraphics guiGraphics, Entity entity, CallbackInfo ci) + { + ci.cancel(); + } +} diff --git a/src/main/java/com/mt1006/ar_mod/mixin/InputConstantsMixin.java b/src/main/java/com/mt1006/ar_mod/mixin/InputConstantsMixin.java new file mode 100644 index 0000000..6ef20cd --- /dev/null +++ b/src/main/java/com/mt1006/ar_mod/mixin/InputConstantsMixin.java @@ -0,0 +1,22 @@ +package com.mt1006.ar_mod.mixin; + +import com.mojang.blaze3d.platform.InputConstants; +import com.mt1006.ar_mod.ar.ArThread; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Mixin(InputConstants.class) +public class InputConstantsMixin +{ + @Inject(method = "grabOrReleaseMouse", at = @At(value = "HEAD"), cancellable = true) + private static void grabOrReleaseMouse(long window, int val, double x, double y, CallbackInfo ci) + { + if (!ArThread.isMainThread()) + { + ArThread.executeAsync(() -> InputConstants.grabOrReleaseMouse(window, val, x, y)); + ci.cancel(); + } + } +} diff --git a/src/main/java/com/mt1006/ar_mod/mixin/KeyboardHandlerMixin.java b/src/main/java/com/mt1006/ar_mod/mixin/KeyboardHandlerMixin.java new file mode 100644 index 0000000..6cd3462 --- /dev/null +++ b/src/main/java/com/mt1006/ar_mod/mixin/KeyboardHandlerMixin.java @@ -0,0 +1,20 @@ +package com.mt1006.ar_mod.mixin; + +import com.mojang.blaze3d.platform.InputConstants; +import com.mt1006.ar_mod.ar.movement.ArMovement; +import net.minecraft.client.KeyboardHandler; +import org.lwjgl.glfw.GLFWCharModsCallbackI; +import org.lwjgl.glfw.GLFWKeyCallbackI; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Redirect; + +@Mixin(KeyboardHandler.class) +public class KeyboardHandlerMixin +{ + @Redirect(method = "setup", at = @At(value = "INVOKE", target = "Lcom/mojang/blaze3d/platform/InputConstants;setupKeyboardCallbacks(JLorg/lwjgl/glfw/GLFWKeyCallbackI;Lorg/lwjgl/glfw/GLFWCharModsCallbackI;)V")) + public void atSetup(long window, GLFWKeyCallbackI cb1, GLFWCharModsCallbackI cb2) + { + InputConstants.setupKeyboardCallbacks(window, ArMovement::keyPress, cb2); + } +} diff --git a/src/main/java/com/mt1006/ar_mod/mixin/LocalPlayerMixin.java b/src/main/java/com/mt1006/ar_mod/mixin/LocalPlayerMixin.java new file mode 100644 index 0000000..20cce02 --- /dev/null +++ b/src/main/java/com/mt1006/ar_mod/mixin/LocalPlayerMixin.java @@ -0,0 +1,48 @@ +package com.mt1006.ar_mod.mixin; + +import com.mt1006.ar_mod.ar.movement.ArMovement; +import com.mt1006.ar_mod.config.ModConfig; +import com.mt1006.ar_mod.mixin.fields.PlayerFields; +import net.minecraft.client.Minecraft; +import net.minecraft.client.player.LocalPlayer; +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Mixin(LocalPlayer.class) +public class LocalPlayerMixin +{ + @Shadow @Final protected Minecraft minecraft; + + @Inject(method = "aiStep", at = @At(value = "RETURN")) + private void atAiStepReturn(CallbackInfo ci) + { + LocalPlayer player = (LocalPlayer)(Object)this; + boolean responsiveFlying = ModConfig.responsiveFlying.val; + + if (ArMovement.sprintTriggered) + { + if (ModConfig.responsiveSprinting.val) + { + if (!player.isSprinting()) { player.setSprinting(true); } + } + ArMovement.sprintTriggered = false; + } + + if (ArMovement.flyingTriggered) + { + if (responsiveFlying && player.getAbilities().mayfly && minecraft.gameMode != null + && !minecraft.gameMode.isAlwaysFlying() && !player.isSwimming()) + { + player.getAbilities().flying = !player.getAbilities().flying; + player.onUpdateAbilities(); + } + ArMovement.flyingTriggered = false; + } + + if (responsiveFlying) { ((PlayerFields)player).setJumpTriggerTime(0); } + } +} diff --git a/src/main/java/com/mt1006/ar_mod/mixin/MainMixin.java b/src/main/java/com/mt1006/ar_mod/mixin/MainMixin.java new file mode 100644 index 0000000..2837d92 --- /dev/null +++ b/src/main/java/com/mt1006/ar_mod/mixin/MainMixin.java @@ -0,0 +1,30 @@ +package com.mt1006.ar_mod.mixin; + +import com.mt1006.ar_mod.ArMod; +import com.mt1006.ar_mod.ar.ArThread; +import net.minecraft.client.main.Main; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Mixin(Main.class) +public class MainMixin +{ + @Inject(method = "main", at = @At(value = "HEAD"), cancellable = true, remap = false) + private static void atMain(String[] args, CallbackInfo ci) + { + if (Thread.currentThread() == ArThread.gameThread) { return; } + ci.cancel(); + + if (ArThread.gameThread != null) + { + ArMod.LOGGER.error("\"main()\" called from other thread after creating game thread!"); + return; + } + + ArThread.init(new Thread(() -> Main.main(args))); + ArThread.gameThread.start(); + ArThread.run(); + } +} diff --git a/src/main/java/com/mt1006/ar_mod/mixin/MinecraftMixin.java b/src/main/java/com/mt1006/ar_mod/mixin/MinecraftMixin.java new file mode 100644 index 0000000..8276537 --- /dev/null +++ b/src/main/java/com/mt1006/ar_mod/mixin/MinecraftMixin.java @@ -0,0 +1,118 @@ +package com.mt1006.ar_mod.mixin; + +import com.mojang.blaze3d.pipeline.RenderTarget; +import com.mojang.blaze3d.platform.Window; +import com.mojang.blaze3d.systems.RenderSystem; +import com.mt1006.ar_mod.ar.ArThread; +import com.mt1006.ar_mod.ar.ArWindow; +import com.mt1006.ar_mod.ar.movement.ArMovement; +import com.mt1006.ar_mod.config.ModConfig; +import net.minecraft.client.KeyboardHandler; +import net.minecraft.client.Minecraft; +import net.minecraft.client.MouseHandler; +import net.minecraft.client.gui.screens.Overlay; +import net.minecraft.client.gui.screens.Screen; +import net.minecraft.client.multiplayer.ClientLevel; +import net.minecraft.server.packs.repository.PackRepository; +import net.minecraft.server.packs.resources.ReloadableResourceManager; +import net.minecraftforge.client.loading.ClientModLoader; +import org.jetbrains.annotations.Nullable; +import org.lwjgl.glfw.GLFW; +import org.lwjgl.glfw.GLFWErrorCallbackI; +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.Redirect; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +@Mixin(Minecraft.class) +public abstract class MinecraftMixin +{ + @Shadow @Final private PackRepository resourcePackRepository; + @Shadow @Final private Window window; + @Shadow @Nullable public ClientLevel level; + @Shadow @Nullable public Screen screen; + @Shadow @Nullable private Overlay overlay; + + @Redirect(method = "", at = @At(value = "INVOKE", target = "Lnet/minecraft/server/packs/repository/PackRepository;reload()V")) + private void makeCurrentContext(PackRepository packRepository) + { + GLFW.glfwMakeContextCurrent(ArWindow.hiddenWindow); + resourcePackRepository.reload(); + } + + @Redirect(method = "", at = @At(value = "INVOKE", target = "Lnet/minecraftforge/client/loading/ClientModLoader;begin(Lnet/minecraft/client/Minecraft;Lnet/minecraft/server/packs/repository/PackRepository;Lnet/minecraft/server/packs/resources/ReloadableResourceManager;)V", remap = false)) + private void atClientModLoaderBegin(Minecraft minecraft, PackRepository defaultResourcePacks, ReloadableResourceManager mcResourceManager) + { + ArThread.execute(() -> ClientModLoader.begin(minecraft, defaultResourcePacks, mcResourceManager)); + } + + @Redirect(method = "", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/MouseHandler;setup(J)V")) + private void atMouseHandlerSetup(MouseHandler mouseHandler, long window) + { + ArThread.execute(() -> mouseHandler.setup(window)); + } + + @Redirect(method = "", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/KeyboardHandler;setup(J)V")) + private void atKeyboardHandlerSetup(KeyboardHandler keyboardHandler, long window) + { + ArThread.execute(() -> keyboardHandler.setup(window)); + } + + @Redirect(method = "", at = @At(value = "INVOKE", target = "Lcom/mojang/blaze3d/systems/RenderSystem;setErrorCallback(Lorg/lwjgl/glfw/GLFWErrorCallbackI;)V", remap = false)) + private void atSetErrorCallback(GLFWErrorCallbackI callback) + { + ArThread.execute(() -> RenderSystem.setErrorCallback(callback)); + } + + @Redirect(method = "", at = @At(value = "INVOKE", target = "Lcom/mojang/blaze3d/platform/Window;updateRawMouseInput(Z)V")) + private void atUpdateRawMouseInput(Window window, boolean val) + { + ArThread.execute(() -> window.updateRawMouseInput(val)); + } + + @Redirect(method = "", at = @At(value = "INVOKE", target = "Lcom/mojang/blaze3d/platform/Window;setDefaultErrorCallback()V")) + private void atSetWindowErrorCallback(Window instance) + { + ArThread.execute(() -> window.setDefaultErrorCallback()); + } + + @Inject(method = "run", at = @At(value = "HEAD")) + private void atRun(CallbackInfo ci) + { + ArThread.finishInit(); + } + + @Inject(method = "runTick", at = @At(value = "HEAD")) + private void initFrame(boolean renderLevel, CallbackInfo ci) + { + ArThread.nextFrame(); + } + + @Redirect(method = "runTick", at = @At(value = "INVOKE", target = "Lcom/mojang/blaze3d/pipeline/RenderTarget;blitToScreen(II)V")) + private void ignoreBlitToScreen(RenderTarget renderTarget, int i1, int i2) {} + + @Inject(method = "getFramerateLimit", at = @At(value = "HEAD"), cancellable = true) + private void debugFPSLimit(CallbackInfoReturnable cir) + { + int actualLimit = (level != null || screen == null && overlay == null) ? this.window.getFramerateLimit() : 60; + cir.setReturnValue(ModConfig.getDebugFPSLimit(actualLimit)); + cir.cancel(); + } + + @Inject(method = "useShaderTransparency", at = @At(value = "HEAD"), cancellable = true) + private static void cancelShaderTransparency(CallbackInfoReturnable cir) + { + cir.setReturnValue(false); + cir.cancel(); + } + + @Inject(method = "setLevel", at = @At(value = "HEAD")) + private void atSetLevel(ClientLevel clientLevel, CallbackInfo ci) + { + ArMovement.frameCounter.set(0L); + } +} diff --git a/src/main/java/com/mt1006/ar_mod/mixin/MouseHandlerMixin.java b/src/main/java/com/mt1006/ar_mod/mixin/MouseHandlerMixin.java new file mode 100644 index 0000000..3b25b25 --- /dev/null +++ b/src/main/java/com/mt1006/ar_mod/mixin/MouseHandlerMixin.java @@ -0,0 +1,37 @@ +package com.mt1006.ar_mod.mixin; + +import com.mojang.blaze3d.platform.InputConstants; +import com.mt1006.ar_mod.ar.movement.ArMouse; +import net.minecraft.client.MouseHandler; +import org.lwjgl.glfw.GLFWCursorPosCallbackI; +import org.lwjgl.glfw.GLFWDropCallbackI; +import org.lwjgl.glfw.GLFWMouseButtonCallbackI; +import org.lwjgl.glfw.GLFWScrollCallbackI; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.Redirect; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + + +@Mixin(MouseHandler.class) +public class MouseHandlerMixin +{ + @Shadow private double xpos; + @Shadow private double ypos; + + @Redirect(method = "setup", at = @At(value = "INVOKE", target = "Lcom/mojang/blaze3d/platform/InputConstants;setupMouseCallbacks(JLorg/lwjgl/glfw/GLFWCursorPosCallbackI;Lorg/lwjgl/glfw/GLFWMouseButtonCallbackI;Lorg/lwjgl/glfw/GLFWScrollCallbackI;Lorg/lwjgl/glfw/GLFWDropCallbackI;)V")) + public void atSetup(long window, GLFWCursorPosCallbackI cb1, GLFWMouseButtonCallbackI cb2, GLFWScrollCallbackI cb3, GLFWDropCallbackI cb4) + { + InputConstants.setupMouseCallbacks(window, ArMouse::onMove, cb2, cb3, cb4); + } + + @Inject(method = "onMove", at = @At(value = "INVOKE", target = "Lnet/minecraft/util/profiling/ProfilerFiller;push(Ljava/lang/String;)V"), cancellable = true) + private void atCameraMovement(long window, double x, double y, CallbackInfo ci) + { + this.xpos = x; + this.ypos = y; + ci.cancel(); + } +} diff --git a/src/main/java/com/mt1006/ar_mod/mixin/MovementTutorialMixin.java b/src/main/java/com/mt1006/ar_mod/mixin/MovementTutorialMixin.java new file mode 100644 index 0000000..9cf40f9 --- /dev/null +++ b/src/main/java/com/mt1006/ar_mod/mixin/MovementTutorialMixin.java @@ -0,0 +1,28 @@ +package com.mt1006.ar_mod.mixin; + +import net.minecraft.client.player.Input; +import net.minecraft.client.tutorial.MovementTutorialStepInstance; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Mixin(MovementTutorialStepInstance.class) +public class MovementTutorialMixin +{ + @Shadow private boolean moved; + @Shadow private boolean turned; + + @Inject(method = "onInput", at = @At(value = "HEAD")) + public void atOnInput(Input input, CallbackInfo ci) + { + moved = true; + } + + @Inject(method = "onMouse", at = @At(value = "HEAD")) + public void atOnMouse(double d1, double d2, CallbackInfo ci) + { + turned = true; + } +} diff --git a/src/main/java/com/mt1006/ar_mod/mixin/OptionInstanceMixin.java b/src/main/java/com/mt1006/ar_mod/mixin/OptionInstanceMixin.java new file mode 100644 index 0000000..6009dd6 --- /dev/null +++ b/src/main/java/com/mt1006/ar_mod/mixin/OptionInstanceMixin.java @@ -0,0 +1,26 @@ +package com.mt1006.ar_mod.mixin; + +import com.mt1006.ar_mod.config.ModConfig; +import net.minecraft.client.Minecraft; +import net.minecraft.client.OptionInstance; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +@Mixin(OptionInstance.class) +public class OptionInstanceMixin +{ + @Shadow Object value; + + @Inject(method = "get", at = @At(value = "HEAD"), cancellable = true) + private void get(CallbackInfoReturnable cir) + { + if (this == (Object)Minecraft.getInstance().options.fov()) + { + cir.setReturnValue((int)((Integer)value * ModConfig.fovScaling.val)); + cir.cancel(); + } + } +} diff --git a/src/main/java/com/mt1006/ar_mod/mixin/OptionInstanceSliderMixin.java b/src/main/java/com/mt1006/ar_mod/mixin/OptionInstanceSliderMixin.java new file mode 100644 index 0000000..66b8d10 --- /dev/null +++ b/src/main/java/com/mt1006/ar_mod/mixin/OptionInstanceSliderMixin.java @@ -0,0 +1,30 @@ +package com.mt1006.ar_mod.mixin; + +import com.mt1006.ar_mod.mixin.fields.OptionInstanceFields; +import net.minecraft.client.Minecraft; +import net.minecraft.client.OptionInstance; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Redirect; + +@Mixin(targets = {"net.minecraft.client.OptionInstance$OptionInstanceSliderButton"}) +public class OptionInstanceSliderMixin +{ + @Redirect(method = "", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/OptionInstance;get()Ljava/lang/Object;")) + private static Object atInit(OptionInstance instance) + { + return (instance == Minecraft.getInstance().options.fov()) ? ((OptionInstanceFields)(Object)instance).getValue() : instance.get(); + } + + @Redirect(method = "updateMessage", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/OptionInstance;get()Ljava/lang/Object;")) + private Object atUpdateMessage(OptionInstance instance) + { + return (instance == Minecraft.getInstance().options.fov()) ? ((OptionInstanceFields)(Object)instance).getValue() : instance.get(); + } + + @Redirect(method = "applyValue", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/OptionInstance;get()Ljava/lang/Object;")) + private Object atApplyValue(OptionInstance instance) + { + return (instance == Minecraft.getInstance().options.fov()) ? ((OptionInstanceFields)(Object)instance).getValue() : instance.get(); + } +} diff --git a/src/main/java/com/mt1006/ar_mod/mixin/OptionsScreenMixin.java b/src/main/java/com/mt1006/ar_mod/mixin/OptionsScreenMixin.java new file mode 100644 index 0000000..4116210 --- /dev/null +++ b/src/main/java/com/mt1006/ar_mod/mixin/OptionsScreenMixin.java @@ -0,0 +1,34 @@ +package com.mt1006.ar_mod.mixin; + +import com.mt1006.ar_mod.ArMod; +import com.mt1006.ar_mod.config.gui.ConfigScreen; +import com.mt1006.ar_mod.mixin.fields.ScreenFields; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.components.Button; +import net.minecraft.client.gui.components.ImageButton; +import net.minecraft.client.gui.screens.OptionsScreen; +import net.minecraft.client.gui.screens.Screen; +import net.minecraft.resources.ResourceLocation; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Unique; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Mixin(OptionsScreen.class) +public abstract class OptionsScreenMixin +{ + @Unique private static final ResourceLocation BUTTON_TEXTURE = new ResourceLocation(ArMod.MOD_ID, "textures/gui/button.png"); + + @Inject(method = "init", at = @At(value = "TAIL")) + private void addButton(CallbackInfo ci) + { + Screen screen = (Screen)(Object)this; + Button button = new ImageButton(screen.width - 24, 4, 20, 20, 0, 0, 20, BUTTON_TEXTURE, 32, 64, + (b) -> Minecraft.getInstance().setScreen(new ConfigScreen(screen))); + + ((ScreenFields)this).getRenderables().add(button); + ((ScreenFields)this).getChildren().add(button); + ((ScreenFields)this).getNarratables().add(button); + } +} diff --git a/src/main/java/com/mt1006/ar_mod/mixin/RenderSystemMixin.java b/src/main/java/com/mt1006/ar_mod/mixin/RenderSystemMixin.java new file mode 100644 index 0000000..8aceed0 --- /dev/null +++ b/src/main/java/com/mt1006/ar_mod/mixin/RenderSystemMixin.java @@ -0,0 +1,18 @@ +package com.mt1006.ar_mod.mixin; + +import com.mojang.blaze3d.systems.RenderSystem; +import com.mt1006.ar_mod.ArMod; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Redirect; + +@Mixin(value = RenderSystem.class, remap = false) +public class RenderSystemMixin +{ + @Redirect(method = "limitDisplayFPS", at = @At(value = "INVOKE", target = "Lorg/lwjgl/glfw/GLFW;glfwWaitEventsTimeout(D)V", remap = false)) + private static void atLimitDisplayFPS(double timeout) + { + try { Thread.sleep((long)Math.floor(timeout * 1000.0)); } + catch (InterruptedException exception) { ArMod.LOGGER.warn("Thread.sleep() interruption! - RenderSystemMixin"); } + } +} diff --git a/src/main/java/com/mt1006/ar_mod/mixin/RenderTargetMixin.java b/src/main/java/com/mt1006/ar_mod/mixin/RenderTargetMixin.java new file mode 100644 index 0000000..d0b1fea --- /dev/null +++ b/src/main/java/com/mt1006/ar_mod/mixin/RenderTargetMixin.java @@ -0,0 +1,30 @@ +package com.mt1006.ar_mod.mixin; + +import com.mojang.blaze3d.pipeline.RenderTarget; +import com.mojang.blaze3d.platform.GlStateManager; +import com.mt1006.ar_mod.ar.ArWindow; +import net.minecraft.client.Minecraft; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Mixin(RenderTarget.class) +public class RenderTargetMixin +{ + @Shadow public int viewWidth; + @Shadow public int viewHeight; + @Shadow public int frameBufferId; + + @Inject(method = "_bindWrite", at = @At(value = "HEAD"), cancellable = true) + private void atBindWrite(boolean setViewport, CallbackInfo ci) + { + if (ArWindow.initialized && frameBufferId == Minecraft.getInstance().getMainRenderTarget().frameBufferId) + { + ArWindow.getWriteFrame().getStageSubFrame().bindWrite(); + if (setViewport) { GlStateManager._viewport(0, 0, viewWidth, viewHeight); } + ci.cancel(); + } + } +} diff --git a/src/main/java/com/mt1006/ar_mod/mixin/VirtualScreenMixin.java b/src/main/java/com/mt1006/ar_mod/mixin/VirtualScreenMixin.java new file mode 100644 index 0000000..0147bf7 --- /dev/null +++ b/src/main/java/com/mt1006/ar_mod/mixin/VirtualScreenMixin.java @@ -0,0 +1,33 @@ +package com.mt1006.ar_mod.mixin; + +import com.mojang.blaze3d.platform.DisplayData; +import com.mojang.blaze3d.platform.ScreenManager; +import com.mojang.blaze3d.platform.Window; +import com.mt1006.ar_mod.ar.ArThread; +import com.mt1006.ar_mod.ar.ArWindow; +import net.minecraft.client.Minecraft; +import net.minecraft.client.renderer.VirtualScreen; +import org.lwjgl.glfw.GLFW; +import org.lwjgl.opengl.GL; +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +@Mixin(VirtualScreen.class) +public class VirtualScreenMixin +{ + @Shadow @Final private Minecraft minecraft; + @Shadow @Final private ScreenManager screenManager; + + @Inject(method = "newWindow", at = @At(value = "HEAD"), cancellable = true) + private void atNewWindow(DisplayData p_110873_, String p_110874_, String p_110875_, CallbackInfoReturnable cir) + { + cir.setReturnValue((Window)ArThread.executeAndGet(() -> new Window(minecraft, screenManager, p_110873_, p_110874_, p_110875_))); + GLFW.glfwMakeContextCurrent(ArWindow.hiddenWindow); + GL.createCapabilities(); + cir.cancel(); + } +} diff --git a/src/main/java/com/mt1006/ar_mod/mixin/WindowMixin.java b/src/main/java/com/mt1006/ar_mod/mixin/WindowMixin.java new file mode 100644 index 0000000..7da44db --- /dev/null +++ b/src/main/java/com/mt1006/ar_mod/mixin/WindowMixin.java @@ -0,0 +1,78 @@ +package com.mt1006.ar_mod.mixin; + +import com.mojang.blaze3d.platform.Window; +import com.mojang.blaze3d.systems.RenderSystem; +import com.mojang.blaze3d.vertex.Tesselator; +import com.mt1006.ar_mod.ar.ArThread; +import com.mt1006.ar_mod.ar.ArWindow; +import net.minecraftforge.fml.loading.ImmediateWindowHandler; +import org.lwjgl.glfw.GLFW; +import org.lwjgl.glfw.GLFWImage; +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Mutable; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.Redirect; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +import java.util.Optional; +import java.util.function.IntConsumer; + +@Mixin(Window.class) +public abstract class WindowMixin +{ + @Shadow private int width; + @Shadow private int height; + @Mutable @Shadow @Final private long window; + + @Redirect(method = "", at = @At(value = "INVOKE", target = "Lnet/minecraftforge/fml/loading/ImmediateWindowHandler;positionWindow(Ljava/util/Optional;Ljava/util/function/IntConsumer;Ljava/util/function/IntConsumer;Ljava/util/function/IntConsumer;Ljava/util/function/IntConsumer;)Z", remap = false)) + private boolean atConstructor(Optional arg1, IntConsumer arg2, IntConsumer arg3, IntConsumer arg4, IntConsumer arg5) + { + // It redirects positionWindow instead of setupMinecraftWindow to prevent conflict with Sodium. + // Injection won't work as it's a constructor. + window = ArWindow.init(window, width, height); + return ImmediateWindowHandler.positionWindow(arg1, arg2, arg3, arg4, arg5); + } + + @Inject(method = "updateDisplay", at = @At(value = "HEAD"), cancellable = true) + public void atUpdateDisplay(CallbackInfo ci) + { + if (ArWindow.initialized) + { + ArThread.finishFrame(); + + //RenderSystem.flipFrame without polling events + RenderSystem.replayQueue(); + Tesselator.getInstance().getBuilder().clear(); + GLFW.glfwSwapBuffers(ArWindow.hiddenWindow); + + ci.cancel(); + } + } + + @Redirect(method = "setIcon", at = @At(value = "INVOKE", target = "Lorg/lwjgl/glfw/GLFW;glfwSetWindowIcon(JLorg/lwjgl/glfw/GLFWImage$Buffer;)V", remap = false)) + private void atSetIcon(long window, GLFWImage.Buffer images) + { + ArThread.execute(() -> GLFW.glfwSetWindowIcon(window, images)); + } + + @Inject(method = "onResize", at = @At(value = "HEAD")) + private void atOnResize(long window, int w, int h, CallbackInfo ci) + { + if (ArWindow.initialized) { ArWindow.resize(w, h); } + } + + @Redirect(method = "setMode", at = @At(value = "INVOKE", target = "Lorg/lwjgl/glfw/GLFW;glfwSetWindowMonitor(JJIIIII)V", remap = false)) + private void atSetMode(long window, long monitor, int xpos, int ypos, int width, int height, int refreshRate) + { + ArThread.executeAsync(() -> GLFW.glfwSetWindowMonitor(window, monitor, xpos, ypos, width, height, refreshRate)); + } + + @Redirect(method = "setTitle", at = @At(value = "INVOKE", target = "Lorg/lwjgl/glfw/GLFW;glfwSetWindowTitle(JLjava/lang/CharSequence;)V", remap = false)) + private void atSetTitle(long window, CharSequence title) + { + ArThread.executeAsync(() -> GLFW.glfwSetWindowTitle(window, title)); + } +} diff --git a/src/main/java/com/mt1006/ar_mod/mixin/fields/AbstractTextureFields.java b/src/main/java/com/mt1006/ar_mod/mixin/fields/AbstractTextureFields.java new file mode 100644 index 0000000..a579d73 --- /dev/null +++ b/src/main/java/com/mt1006/ar_mod/mixin/fields/AbstractTextureFields.java @@ -0,0 +1,11 @@ +package com.mt1006.ar_mod.mixin.fields; + +import net.minecraft.client.renderer.texture.AbstractTexture; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Accessor; + +@Mixin(AbstractTexture.class) +public interface AbstractTextureFields +{ + @Accessor(value = "id") int getIdValue(); +} diff --git a/src/main/java/com/mt1006/ar_mod/mixin/fields/MouseHandlerFields.java b/src/main/java/com/mt1006/ar_mod/mixin/fields/MouseHandlerFields.java new file mode 100644 index 0000000..858ec11 --- /dev/null +++ b/src/main/java/com/mt1006/ar_mod/mixin/fields/MouseHandlerFields.java @@ -0,0 +1,14 @@ +package com.mt1006.ar_mod.mixin.fields; + +import net.minecraft.client.MouseHandler; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Accessor; +import org.spongepowered.asm.mixin.gen.Invoker; + +@Mixin(MouseHandler.class) +public interface MouseHandlerFields +{ + @Accessor boolean getIgnoreFirstMove(); + @Accessor boolean getMouseGrabbed(); + @Invoker void callOnMove(long window, double x, double y); +} diff --git a/src/main/java/com/mt1006/ar_mod/mixin/fields/OptionInstanceFields.java b/src/main/java/com/mt1006/ar_mod/mixin/fields/OptionInstanceFields.java new file mode 100644 index 0000000..dd8bb80 --- /dev/null +++ b/src/main/java/com/mt1006/ar_mod/mixin/fields/OptionInstanceFields.java @@ -0,0 +1,11 @@ +package com.mt1006.ar_mod.mixin.fields; + +import net.minecraft.client.OptionInstance; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Accessor; + +@Mixin(OptionInstance.class) +public interface OptionInstanceFields +{ + @Accessor Object getValue(); +} diff --git a/src/main/java/com/mt1006/ar_mod/mixin/fields/PlayerFields.java b/src/main/java/com/mt1006/ar_mod/mixin/fields/PlayerFields.java new file mode 100644 index 0000000..0e836d3 --- /dev/null +++ b/src/main/java/com/mt1006/ar_mod/mixin/fields/PlayerFields.java @@ -0,0 +1,11 @@ +package com.mt1006.ar_mod.mixin.fields; + +import net.minecraft.world.entity.player.Player; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Accessor; + +@Mixin(Player.class) +public interface PlayerFields +{ + @Accessor void setJumpTriggerTime(int value); +} diff --git a/src/main/java/com/mt1006/ar_mod/mixin/fields/ScreenFields.java b/src/main/java/com/mt1006/ar_mod/mixin/fields/ScreenFields.java new file mode 100644 index 0000000..f71613d --- /dev/null +++ b/src/main/java/com/mt1006/ar_mod/mixin/fields/ScreenFields.java @@ -0,0 +1,18 @@ +package com.mt1006.ar_mod.mixin.fields; + +import net.minecraft.client.gui.components.Renderable; +import net.minecraft.client.gui.components.events.GuiEventListener; +import net.minecraft.client.gui.narration.NarratableEntry; +import net.minecraft.client.gui.screens.Screen; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Accessor; + +import java.util.List; + +@Mixin(Screen.class) +public interface ScreenFields +{ + @Accessor List getRenderables(); + @Accessor List getChildren(); + @Accessor List getNarratables(); +} diff --git a/src/main/resources/META-INF/mods.toml b/src/main/resources/META-INF/mods.toml new file mode 100644 index 0000000..17813f8 --- /dev/null +++ b/src/main/resources/META-INF/mods.toml @@ -0,0 +1,28 @@ +modLoader="javafml" +loaderVersion="${loader_version_range}" +license="${mod_license}" +issueTrackerURL="${mod_issues}" + +[[mods]] +modId="${mod_id}" +version="${mod_version}" +displayName="${mod_name}" +displayURL="${mod_url}" +logoFile="logo.png" +authors="${mod_authors}" +credits="${mod_credits}" +description='''${mod_description}''' + +[[dependencies.${mod_id}]] + modId="forge" + mandatory=true + versionRange="${forge_version_range}" + ordering="NONE" + side="BOTH" + +[[dependencies.${mod_id}]] + modId="minecraft" + mandatory=true + versionRange="${minecraft_version_range}" + ordering="NONE" + side="BOTH" \ No newline at end of file diff --git a/src/main/resources/ar_shaders/common.vsh b/src/main/resources/ar_shaders/common.vsh new file mode 100644 index 0000000..2e24c41 --- /dev/null +++ b/src/main/resources/ar_shaders/common.vsh @@ -0,0 +1,11 @@ +#version 330 core + +layout (location = 0) in vec3 inPos; +layout (location = 1) in vec2 inTexCoord; +out vec2 texCoord; + +void main() +{ + gl_Position = vec4(inPos, 1.0); + texCoord = inTexCoord; +} \ No newline at end of file diff --git a/src/main/resources/ar_shaders/frame.fsh b/src/main/resources/ar_shaders/frame.fsh new file mode 100644 index 0000000..6248e87 --- /dev/null +++ b/src/main/resources/ar_shaders/frame.fsh @@ -0,0 +1,10 @@ +#version 330 core + +in vec2 texCoord; +out vec4 fragColor; +uniform sampler2D colorTexture; + +void main() +{ + fragColor = texture(colorTexture, texCoord); +} \ No newline at end of file diff --git a/src/main/resources/ar_shaders/full_ar.fsh b/src/main/resources/ar_shaders/full_ar.fsh new file mode 100644 index 0000000..601cf37 --- /dev/null +++ b/src/main/resources/ar_shaders/full_ar.fsh @@ -0,0 +1,99 @@ +/* +Sources: +- https://drive.google.com/file/d/1gjVWNzHF3Gddxb3fUNgEXU38ETuLfVM2/view (https://www.youtube.com/@comradestinger) +- https://stackoverflow.com/a/6657284 +*/ +#version 330 core + +in vec2 texCoord; +out vec4 fragColor; +uniform sampler2D colorTexture; +uniform sampler2D depthTexture; +uniform mat4 viewMatrix; +uniform float heightMul; // 2.0 / projectionMatrix[1][1] +uniform vec3 cameraVector; +uniform vec3 cameraPosition; +uniform vec3 topLeft; +uniform vec3 topRight; +uniform vec3 bottomLeft; +uniform vec3 bottomRight; +uniform vec2 screenSize; +uniform float farClip; +uniform int sequenceN; +uniform float sequenceR; +uniform float sequenceA0; + +const float nearClip = 0.05; +float screenRation; +float clipMul, clipDiff; + +float linearDepth(float val) +{ + float sampleVal = ((val + 1.0) / 2.0); + return (clipMul / (farClip - sampleVal * clipDiff)) / 2.0; +} + +vec3 getWorldPos(vec3 vector, vec2 uv) +{ + float dotProduct = dot(cameraVector, vector); + float sceneDistance = linearDepth(texture(depthTexture, uv).r) / dotProduct; + return vector * sceneDistance; +} + +vec2 worldToScreenPos(vec3 vector) +{ + vec3 finalPos = vector * farClip; + vec3 toCam = (viewMatrix * vec4(finalPos, 1.0)).xyz; + float height = toCam.z * heightMul; + float width = screenRation * height; + vec2 uv = vec2((toCam.x + width / 2.0) / width, (toCam.y + height / 2.0) / height); + return 1.0 - uv; +} + +void main() +{ + screenRation = screenSize.x / screenSize.y; + clipMul = nearClip * farClip; + clipDiff = farClip - nearClip; + vec3 pointVector = mix(mix(topLeft, topRight, texCoord.x), mix(bottomLeft, bottomRight, texCoord.x), 1.0 - texCoord.y); + + vec3 pos = cameraPosition; + bool occluded = false; + float sequenceA = sequenceA0; + for (int i = 0; i < sequenceN; i++) + { + pos += pointVector * sequenceA; + + float posLen = length(pos); + vec3 normalizedPos = pos / posLen; + vec2 newScreenPos = worldToScreenPos(normalizedPos); + vec3 tracedPos = getWorldPos(normalizedPos, newScreenPos); + float distanceDiff = posLen - length(tracedPos); + + if (distanceDiff > sequenceA) { occluded = true; } + if (distanceDiff > 0) { break; } + sequenceA *= sequenceR; + } + + vec2 screenPos = worldToScreenPos(normalize(pos)); + vec3 texColor = texture(colorTexture, screenPos).rgb; + if (occluded) + { + vec2 offset1 = screenPos + vec2(1.0, 0.0) * 0.01; + vec2 offset2 = screenPos + vec2(0.0, 1.0) * 0.01; + vec2 offset3 = screenPos + vec2(-1.0, 0.0) * 0.01; + vec2 offset4 = screenPos + vec2(0.0, -1.0) * 0.01; + float depth0 = linearDepth(texture(depthTexture, screenPos).r); + float depth1 = linearDepth(texture(depthTexture, offset1).r); + float depth2 = linearDepth(texture(depthTexture, offset2).r); + float depth3 = linearDepth(texture(depthTexture, offset3).r); + float depth4 = linearDepth(texture(depthTexture, offset4).r); + float furthest = max(max(max(max(depth0, depth1), depth2), depth3), depth4); + if (furthest == depth1) { texColor = texture(colorTexture, offset1).rgb; } + if (furthest == depth2) { texColor = texture(colorTexture, offset2).rgb; } + if (furthest == depth3) { texColor = texture(colorTexture, offset3).rgb; } + if (furthest == depth4) { texColor = texture(colorTexture, offset4).rgb; } + } + + fragColor = vec4(texColor, 1.0); +} \ No newline at end of file diff --git a/src/main/resources/ar_shaders/timewarp.fsh b/src/main/resources/ar_shaders/timewarp.fsh new file mode 100644 index 0000000..7e1b82f --- /dev/null +++ b/src/main/resources/ar_shaders/timewarp.fsh @@ -0,0 +1,34 @@ +/* +Sources: +- https://drive.google.com/file/d/1gjVWNzHF3Gddxb3fUNgEXU38ETuLfVM2/view (https://www.youtube.com/@comradestinger) +*/ +#version 330 core + +in vec2 texCoord; +out vec4 fragColor; +uniform sampler2D colorTexture; +uniform mat4 viewMatrix; +uniform float heightMul; // 2.0 / projectionMatrix[1][1] +uniform vec3 topLeft; +uniform vec3 topRight; +uniform vec3 bottomLeft; +uniform vec3 bottomRight; +uniform vec2 screenSize; +uniform float farClip; + +vec2 worldToScreenPos(vec3 pos) +{ + vec3 finalPos = normalize(pos) * farClip; + vec3 toCam = (viewMatrix * vec4(finalPos, 1.0)).xyz; + float height = toCam.z * heightMul; + float width = screenSize.x / screenSize.y * height; + vec2 uv = vec2((toCam.x + width / 2) / width, (toCam.y + height / 2) / height); + return 1.0 - uv; +} + +void main() +{ + vec3 pointVector = mix(mix(topLeft, topRight, texCoord.x), mix(bottomLeft, bottomRight, texCoord.x), 1.0 - texCoord.y); + vec2 screenPos = worldToScreenPos(pointVector); + fragColor = vec4(texture(colorTexture, screenPos).rgb, 1.0); +} \ No newline at end of file diff --git a/src/main/resources/ar_shaders/vignette.fsh b/src/main/resources/ar_shaders/vignette.fsh new file mode 100644 index 0000000..30584be --- /dev/null +++ b/src/main/resources/ar_shaders/vignette.fsh @@ -0,0 +1,11 @@ +#version 330 core + +in vec2 texCoord; +out vec4 fragColor; +uniform sampler2D colorTexture; +uniform vec3 color; + +void main() +{ + fragColor = texture(colorTexture, texCoord) * vec4(color, 1.0); +} \ No newline at end of file diff --git a/src/main/resources/assets/ar_mod/lang/en_us.json b/src/main/resources/assets/ar_mod/lang/en_us.json new file mode 100644 index 0000000..90edf80 --- /dev/null +++ b/src/main/resources/assets/ar_mod/lang/en_us.json @@ -0,0 +1,87 @@ +{ + "ar_mod.options": "Asynchronous Reprojection Settings", + + + "ar_mod.options.plane_reprojection_settings": "Plane Reprojection", + "ar_mod.options.movement_reprojection_quality": "Movement Reprojection Quality", + "ar_mod.options.improvements": "Improvements", + "ar_mod.options.debugging_options": "Debugging Options", + + + "ar_mod.options.field.camera_reprojection": "Camera Reprojection", + "ar_mod.options.field.camera_reprojection.desc": "Enables camera rotation reprojection.", + + "ar_mod.options.field.movement_reprojection": "Movement Reprojection", + "ar_mod.options.field.movement_reprojection.desc": "Enables player movement reprojection.", + + "ar_mod.options.field.max_reprojected_fps": "Max Reprojected FPS", + "ar_mod.options.field.max_reprojected_fps.37": "[disabled]", + "ar_mod.options.field.max_reprojected_fps.desc": "FPS limit for asynchronous reprojection. For values below 1 or above 360 limit will be disabled.", + + "ar_mod.options.field.reprojection_vsync": "Reprojection VSync", + "ar_mod.options.field.reprojection_vsync.desc": "Enables vertical synchronization for reprojection.", + + + "ar_mod.options.field.wrapping_mode": "Wrapping Mode", + "ar_mod.options.field.wrapping_mode.0": "GL_CLAMP_TO_BORDER", + "ar_mod.options.field.wrapping_mode.1": "GL_CLAMP_TO_EDGE", + "ar_mod.options.field.wrapping_mode.2": "GL_REPEAT", + "ar_mod.options.field.wrapping_mode.3": "GL_MIRRORED_REPEAT", + "ar_mod.options.field.wrapping_mode.desc": "Specifies texture wrapping mode:\n0 - GL_CLAMP_TO_BORDER\n1 - GL_CLAMP_TO_EDGE\n2 - GL_REPEAT\n3 - GL_MIRRORED_REPEAT\nOther values will be treated as 1.", + + "ar_mod.options.field.linear_texture_filtering": "Linear Texture Filtering", + "ar_mod.options.field.linear_texture_filtering.desc": "Enables linear texture filtering (the edges will be blurry).", + + "ar_mod.options.field.fov_scaling": "FOV Scaling", + "ar_mod.options.field.fov_scaling.desc": "Ratio of rendered FOV to value set in settings.\nThe greater the value, the worse the visuals and performance, but texture wrapping is less noticeable.", + + + "ar_mod.options.field.sequence_n": "Ray Marching Iterations", + "ar_mod.options.field.sequence_n.desc": "Number of movement reprojection ray marching iterations.\nN in the formula for partial sum of geometric series, which is equal to the maximum supported view distance.\nThe greater the value, the higher the maximum supported view distance, but worse performance.", + + "ar_mod.options.field.sequence_a0": "Initial Ray Length", + "ar_mod.options.field.sequence_a0.desc": "Movement reprojection ray marching first step.\nA0 (or A) in the formula for partial sum of geometric series, which is equal to the maximum supported view distance.\nThe greater the value, the worse the visuals, but higher the maximum supported view distance.", + + "ar_mod.options.field.sequence_r": "Ray Length Multiplier", + "ar_mod.options.field.sequence_r.desc": "Movement reprojection ray marching steps ratio.\nR in the formula for partial sum of geometric series, which is equal to the maximum supported view distance.\nThe greater the value, the worse the visuals, but higher the maximum supported view distance.", + + + "ar_mod.options.field.responsive_sprinting": "Responsive Sprinting", + "ar_mod.options.field.responsive_sprinting.desc": "Make triggering sprinting more responsive by checking double forward key asynchronously.", + + "ar_mod.options.field.responsive_flying": "Responsive Flying", + "ar_mod.options.field.responsive_flying.desc": "Make enabling and disabling flying more responsive by checking double jump asynchronously.", + + + "ar_mod.options.field.debug_fps_limit": "Debug FPS Limit", + "ar_mod.options.field.debug_fps_limit.10": "[disabled]", + "ar_mod.options.field.debug_fps_limit.desc": "Sets FPS limit to the value between 1 and 9.\nFor other values the limit will be the same as in Minecraft video settings.", + + "ar_mod.options.field.simulate_real_delay": "Simulate Real Delay", + "ar_mod.options.field.simulate_real_delay.desc": "Simulates real delay for limited FPS.", + + "ar_mod.options.field.offset_x": "Position Offset X", + "ar_mod.options.field.offset_x.desc": "Player position X offset for movement reprojection.", + + "ar_mod.options.field.offset_y": "Position Offset Y", + "ar_mod.options.field.offset_y.desc": "Player position Y offset for movement reprojection.", + + "ar_mod.options.field.offset_z": "Position Offset Z", + "ar_mod.options.field.offset_z.desc": "Player position Z offset for movement reprojection.", + + "ar_mod.options.field.print_async_fps": "Print Reprojected FPS", + "ar_mod.options.field.print_async_fps.desc": "Prints to logs FPS values for asynchronous rendering.", + + "ar_mod.options.field.warn_about_waiting": "Warn About Thread Waiting", + "ar_mod.options.field.warn_about_waiting.desc": "Prints message to logs each time render thread waits for asynchronous reprojection thread.", + + + "ar_mod.options.field.recreate_bobbing__broken": "Recreate View Bobbing", + "ar_mod.options.field.recreate_bobbing__broken.desc": "Recreate view bobbing (currently broken).", + + + "ar_mod.options.common.tooltip": "%s\n§8Default value: %s", + "ar_mod.options.common.reset_settings": "Reset Settings", + "ar_mod.options.common.undefined": "[undefined]", + "ar_mod.options.common.max_view_distance": "Maximum Supported Render Distance" +} \ No newline at end of file diff --git a/src/main/resources/assets/ar_mod/lang/pl_pl.json b/src/main/resources/assets/ar_mod/lang/pl_pl.json new file mode 100644 index 0000000..96e37b3 --- /dev/null +++ b/src/main/resources/assets/ar_mod/lang/pl_pl.json @@ -0,0 +1,87 @@ +{ + "ar_mod.options": "Ustawienia asynchronicznej reprojekcji", + + + "ar_mod.options.plane_reprojection_settings": "Reprojekcja płaszczyzny", + "ar_mod.options.movement_reprojection_quality": "Jakość reprojekcji ruchu", + "ar_mod.options.improvements": "Ulepszenia", + "ar_mod.options.debugging_options": "Opcje debugowania", + + + "ar_mod.options.field.camera_reprojection": "Reprojekcja kamery", + "ar_mod.options.field.camera_reprojection.desc": "Włącza reprojekcję kamery.", + + "ar_mod.options.field.movement_reprojection": "Reprojekcja ruchu", + "ar_mod.options.field.movement_reprojection.desc": "Włącza reprojekcję ruchu.", + + "ar_mod.options.field.max_reprojected_fps": "Limit FPS reprojekcji", + "ar_mod.options.field.max_reprojected_fps.37": "[wyłączony]", + "ar_mod.options.field.max_reprojected_fps.desc": "Limit FPS dla asynchronicznej reprojekcji. Dla wartości poniżej 1 lub powyżej 360, limit będzie wyłączony.", + + "ar_mod.options.field.reprojection_vsync": "Synchronizacja pionowa reprojekcji", + "ar_mod.options.field.reprojection_vsync.desc": "Włącza synchronizację pionową dla reprojekcji.", + + + "ar_mod.options.field.wrapping_mode": "Tryb zawijania", + "ar_mod.options.field.wrapping_mode.0": "GL_CLAMP_TO_BORDER", + "ar_mod.options.field.wrapping_mode.1": "GL_CLAMP_TO_EDGE", + "ar_mod.options.field.wrapping_mode.2": "GL_REPEAT", + "ar_mod.options.field.wrapping_mode.3": "GL_MIRRORED_REPEAT", + "ar_mod.options.field.wrapping_mode.desc": "Określa tryb zawijania tekstury:\n0 - GL_CLAMP_TO_BORDER\n1 - GL_CLAMP_TO_EDGE\n2 - GL_REPEAT\n3 - GL_MIRRORED_REPEAT\nInne wartości będą traktowane jako 1.", + + "ar_mod.options.field.linear_texture_filtering": "Liniowe filtrowanie tekstury", + "ar_mod.options.field.linear_texture_filtering.desc": "Włącza liniowe filtrowanie tekstury (krawędzie będą rozmazane).", + + "ar_mod.options.field.fov_scaling": "Skalowanie pola widzenie", + "ar_mod.options.field.fov_scaling.desc": "Stosunek między renderowanym polem widzenia, a tym w ustawieniach.\nIm większa wartość tym gorsza jakość obrazu i wydajność, ale zawijanie tekstury jest mniej widoczne.", + + + "ar_mod.options.field.sequence_n": "Iteracje ray marchingu", + "ar_mod.options.field.sequence_n.desc": "Ilość iteracji ray marchingu przy reprojekcji ruchu.\nN we wzorze na sumę częściową ciągu geometrycznego, która jest równa maksymalnej wspieranej odległości widzenia.\nIm większa wartość, tym większy maksymalny zasięg widzenia, ale gorsza wydajność.", + + "ar_mod.options.field.sequence_a0": "Początkowa długość promienia", + "ar_mod.options.field.sequence_a0.desc": "Pierwszy krok ray marchingu przy reprojekcji ruchu.\nA0 (lub A) we wzorze na sumę częściową ciągu geometrycznego, która jest równa maksymalnej wspieranej odległości widzenia.\nIm większa wartość, tym gorsza jakość obrazu, ale większy maksymalny zasięg widzenia.", + + "ar_mod.options.field.sequence_r": "Mnożnik długości promienia", + "ar_mod.options.field.sequence_r.desc": "Stosunek między kolejnymi długościami promieni ray marchingu, przy reprojekcji ruchu.\nR we wzorze na sumę częściową ciągu geometrycznego, która jest równa maksymalnej wspieranej odległości widzenia.\nIm większa wartość, tym gorsza jakość obrazu, ale większy maksymalny zasięg widzenia.", + + + "ar_mod.options.field.responsive_sprinting": "Responsywny sprint", + "ar_mod.options.field.responsive_sprinting.desc": "Sprawia, że włączanie sprintu jest bardziej responsywne dzięki asynchronicznemu sprawdzaniu podwójnego kliknięcia przycisku chodzenia w przód.", + + "ar_mod.options.field.responsive_flying": "Responsywne latanie", + "ar_mod.options.field.responsive_flying.desc": "Sprawia, że włączanie i wyłączanie latania jest bardziej responsywne dzięki asynchronicznemu sprawdzaniu podwójnego kliknięcia przycisku skoku.", + + + "ar_mod.options.field.debug_fps_limit": "Testowy limit FPS", + "ar_mod.options.field.debug_fps_limit.10": "[wyłączony]", + "ar_mod.options.field.debug_fps_limit.desc": "Ustawia limit FPS na wartość między 1 a 9.\nPrzy innych wartościach limit będzie ten sam co w ustawieniach graficznych Minecrafta.", + + "ar_mod.options.field.simulate_real_delay": "Symuluj rzeczywiste opóźnienie", + "ar_mod.options.field.simulate_real_delay.desc": "Symuluje rzeczywiste opóźnienie przy ograniczonych FPS'ach.", + + "ar_mod.options.field.offset_x": "Offset pozycji X", + "ar_mod.options.field.offset_x.desc": "Offset pozycji X gracza przy reprojekcji ruchu.", + + "ar_mod.options.field.offset_y": "Offset pozycji Y", + "ar_mod.options.field.offset_y.desc": "Offset pozycji Y gracza przy reprojekcji ruchu.", + + "ar_mod.options.field.offset_z": "Offset pozycji Z", + "ar_mod.options.field.offset_z.desc": "Offset pozycji Z gracza przy reprojekcji ruchu.", + + "ar_mod.options.field.print_async_fps": "Wypisuj ilość FPS'ów reprojekcji", + "ar_mod.options.field.print_async_fps.desc": "Wypisuje do logów FPS'y asynchronicznej reprojekcji.", + + "ar_mod.options.field.warn_about_waiting": "Ostrzegaj gdy wątek czeka", + "ar_mod.options.field.warn_about_waiting.desc": "Wypisuje wiadomość do logów za każdym razem kiedy wątek renderowania czeka na wątek asynchronicznej reprojekcji.", + + + "ar_mod.options.field.recreate_bobbing__broken": "Odtwórz animację chodzenia", + "ar_mod.options.field.recreate_bobbing__broken.desc": "Odtwarza animację chodzenia (obecnie nie działa poprawnie).", + + + "ar_mod.options.common.tooltip": "%s\n§8Domyślna wartość: %s", + "ar_mod.options.common.reset_settings": "Zresetuj ustawienia", + "ar_mod.options.common.undefined": "[niezdefiniowane]", + "ar_mod.options.common.max_view_distance": "Maksymalny wspierany zasięg renderowania" +} \ No newline at end of file diff --git a/src/main/resources/assets/ar_mod/textures/gui/button.png b/src/main/resources/assets/ar_mod/textures/gui/button.png new file mode 100644 index 0000000000000000000000000000000000000000..ad04e5933fa944ff0a716ecb7e55ef6020095f0d GIT binary patch literal 844 zcmV-S1GD^zP)dQq^n zz4l8;zk<2T*NB*)rIb><3`Yn-Oj|)od(jgJ-b7kR&hG5)j2Cr=bCL+noi=|>wI8?3CXoH|<_UN$#3Pdwng$2muy z=hSt5VnQOqa5(Hlmy8E$nr7mobB-*_$g(WH11P1)^E{p@%kq_(I*|eAT>NR%G>8aF zDYVu&=Z+;6SaHr#RTah9%qsRQZFi&0Av~7#lIu>oG z8VCWyPeg><5ALyj&~n%PML&BFfSvX^!`8Cv>R1coOm$`;Xe%N(=djiSu=sAqBA)@U z_F|vWr;8}p@@#$)KbEXgYPtlJQpYO%`!BxY%k?{ad$FZB@A>$hB>*nJe+8rUglqs+ zRdr9mdyn@X5y2RPwH6WKms{7lX)TXF_?a`6dUdv0#lc(@CJz%K&9rPRxZ6 z%;Ql?;hbYM8c`HQOggmgRD{!7~m06B0B{gS9s53UFiT8h`%tgyOuPSWb0aW37$5!+YPk72_@#e~xd# zujj5}j3N6li@PDTpoq}x^*B5{?974=t2nIF5TGcEI2}WWgnl>1bl!d^N)RR=Kv5LT z&d$agiAcPyQi@)$HxWFXJG#nGW1rrvCF{D!|2O(6i%|otudh$-2?v7#%gf8%=fGev zNS-ix!sH2)Crq9&dBWrg-<&7h+S+1oZ|^Pjg#I;qGBwRyPDn^dNJvOXNJvO{EAcP9 W=+|fpAU4MU0000yW=bUXb*Y~Y2mm#NK*q}fGC{O?ZVE+^UNtD00?@XXSj35ZTYG3^UA;S0S zeV$Li3p~gcIZ)x);+NR&Fb_O@O`hoAOhG%03hu7{O>$1jNe^5HUI#C zbdmEP^V3n`+yGD(RQnDPB7I-}3?NSm004;k0RRw47rB1vRQ~(^?S1JPG!M4fmuDqJ z!aV{29Dj06`g?zO0Y^`b#MkM(EvBjVJCAt!7peYe<9-0iEH@lk{=xq48~^}T*86G5{bD4}#_xv9JC9yNeH`zn^;P2;biuMX;Ep&ASDF zjblUSXBZ6v{>oO4^jp7wDKfPu=7B#%|+1^6`9zkBw0k^?g zjGQ~s_TRs`2NVD{kB_wP2azw#U-<%QZG-sS60Ui)-h=JC6Bw=b;7~S--&b!KN?)7o zEMT-@KIi%0eAkSPo)2NvjK>qtZM0v4?Y$8D=eMtJ0|0;@$a8pQy$?|@u)g>p^tJhA z+xB&FJ@w~6Tw0e}hi1?9rSdQuTWBog7WRfm!vc%HVu=zY<+QxziVVc2!_^l?Av zcy|wPOX^7!55fZ{e}}v-2&)Sa*4+{Rlxt@3<&p41kfPz zml}JfdpL-(N8e{X##PYobh-6A`=9<}2LJni-GAtF0RUh&PWYo*J33tMfj+FuBml$J z*l64Cf47Gdk%tKOBGB7F93ufXaJQ42vh)2V)5^~Ip@-lLak9Paxd+^ksQ z871cNYX+n!IryvF&i!en1{@!10C6Bkv!p}U)OXmfn=5etp~63Fd#CE zE^-R{+jHoTB2)!H2Mj_ARKO>S1i*}3D27lrE#tCph zJCZ6(2&r*N;o)q)-x!(I4cU_y$JAkaM6{sJ+KCGMkHf|!@`HHF%WNxa{XgemhP3Fv@dgXH#|2^@WD6#xJh zlMF;@oD$5dC-C6=iDW$IlN46!^)gBU3UMt%0sJyA!Sg+Q1%m+WZp~o*=)fwyv898} zjU$lKiRP3O2knkh3WO3)YRaO7be>7_B{>xDw7K_6l|j@BWIjy<1z@z+b2^WDsR^h4L^97c5`Y6eF+OP)lQmdvmhJ~D)xQG`3<#ir zzXFx9-nunzPSc|Oqqj>CfUaL=S(E@2WWXoYe)cyDGVy$ZzI;NOu}4gZZ1ebQ>rv!o z^;AT8f!M*)IGI}B`B(^?A&3&-G-!m^Fxgu`S(I?})Qa^dOwJw%F&%ANAuwLj)%YpH zWHO!$*E0D*O(@JS`qKp69gf+?`7Aldms_p5Say#A_vj+D43a zMhD&HynhDx$t<(R39;ic=acQwlt_ZRR3fGM*l_^>=B@W40zOfQjref=(34025flgv{I_== zz-YavfGla4BryXwabjr1_b zW#lr~ZL+uEzc_MY1s>d)D7=dRr4QHqiC~{+rDSB-J0#u@hq%|iya%*?XRDi!R&~HY zUmQO?Q|9sDB-V)g$90?S%E|)+e{+!UV-EHbAY(FpDS7zJcDu;he&9iomxf4pH}E%k z_`MqZ7XlIR{pPz|aUlR8;7c?J8WKS4N=_5h+kU0S078eGtj;6b&C}1k3|GGW2J)rVgxz}{?2GiW;aw<{N1%JZt<6P<`P=Z}0KwH%quGU;n&-n>B1_srvf zv|rrmw@v`5RhqaT!tVX)fjo3424E0?)JZ%1)e?yw`s0jkuc z`?tomqV)I~^#|YYH{_94b1UivkS6(Kaeeo1jb)`|a{#dMcmw67Pw!M@_lXhgY)xU~ z=n(#M+IH?uCHrXyFkd19t#h-UYpUafdHvY2)hQJAd~VO#F-#u5_}jrs3_vQ+zJT_5 z&xk>u7aV*|@h*%QsM$Sgz%VhX9=7yc!fad)r=NKlF5kES003v6dqpBpe&9he%dKQN zsF+X+y)ed7nidDGC_tHR|*CzG0p_AgaD990pVClp)4p6 z4`6?>15xa^AtdC%J^%q2t;G`fV-kIFWrp~L z9eZ*OCfkd8Vnxfj`AaVSF*e!d$J22FaX)w{c!(~~Fd1)AzL(RL32m znMTMo4FbO+4uw2tcyK#3^$*x#g4nkP?rP-6G5zlHw-@+(xqj;s`nAcK|X(W@WIc&7;rlu`xj6@CI#3j%wAUx8CoqIDh>Uf!_cK5~}FA~K5Ki-+Q5UlEt zWh23V7?#ld?fV3Z{Na$6X@{V^)y)Bnw`XwV)T0I1E1%zOJUt4qcI5F|!jaXtMGf{k z2(^K~3GiE0fyn0$jappJSC-kzvLXRRUVXEx-%!uIc zM;;WZB?cicG{Pf-J~owdA(+}}{!v9S5e~)(jYw?DqBJVVDiM(06H7Idks2#}11?o} z?9QdQrZ~^_{AmOXDWyONkx0nVQ&kqi*6p!1Zb7`ArNLjlZBh-WRa2oC(gOBY&$~HO z5O~J#KljSfgFt-i7WYH#-$p}WQVlFw06X`m6&s}#va%4=BK(e`esIW<&;-_x59%xy z(aUh~{*ilauMl_``PJra>-HExt7uG2M3GOxc0?8s~q~+Zlqc! zxmQ;%d>jKcb%m`l_Jp_tVG#OGQa<}mk(H8a)@h_ukSZejLBJg!j0A`tJ|iH#VF(bn zk$<1xzl$9y(y~^0tGKEl@*$aJFk0^!?OZ4C-id6L!gFc2OP=plF?ngXa|ISx+|iV^ zs{r^u0>1CT_T4F*IDQfU0OrN6mO@Hsvu>LTx#&e!z{ar=`Z9>s=O%nc5Et+#m5Lxv zq%NM6QT8V$?61~epJ1h4Np*GA;3uv1gbo8{L;@fUNZK&uPA4~~T-2Td<)xlY0Jpkv zNFG-GoU7+c;KtYYyDJX|JN1Fc!Ejx-b`1QN-E^=&*tVEvaPyljP9{{(<#F3YB69EB zCRinieb{V~@@Yw114jvYxC~+M!OV%ghJp-j^WeLz>HDF_=dE8mEwWNv4RI#(djVlw z*IFo*Yv(i2B0LLH5%V1?OtSeAlNBhU8J{pB;6M1jSyNpPe4}?wY{)|_RRoD58L7@c0pQNHox=lr zVLSfR8v4+lef5}Z?5ON%JPfdDypVQa7_P;S%?8tq z82kL(*xEjm>)Sm1HsDv)h{4(vb87&9;s*Xe0Mdjt=^@776XJkq4(0iXz-&)EyKa{giG#dhAO(a2d;=hNZ|(^| zXmM{HiuiQrC#23A@r_{o!ziisSo0okH9ZD07}$#dR9(X(){qsj7lT@RH9=%^{Ha%t z!tE+t1;&m~!&rOTO=IZseN%928s7T^aS zEM^(J`P$oX>H2vic$Z2%iES)(8E2|@Piw|znqg&5A&9DqesMoUBk^zCxmbgACIS6? zKQR(m7JtS{yGz%YmqNBe!2i$27|Ti{NTW6BI4$5_6cl`)SdD97#QqwoD|t)s>WMx^ zD+XBC1_*sag+O(NRXb0fI0XO%Ch0a10N8&p?}kB3fd6-ww}Fw<_0>)AdH`@Bna)Lx zsMcq(ltb}nzE2R?>;RC;m&QADM~h{U70B|>g8;y-tJ@GpKAd`CvzwH0h}7Uw=8Vf= zFR=VNh>89GaoT#r5T1Butx}_&Sv$~Fpk*D<`w(UD#WX#9Hq>^DqSJx4C!%~uAhgv7f9r<2~ah(S@Q@FZWGnS z-XMfa*UwAfZ6X59>+jcu#4Xl`z;h>0!}aekBUTbn`^oCo*oJGFmrevv_3))GhsyTW zD<8r~pZ!L{N><}Q1q+|47mf-I!wNJJ@O#(D~VZj#;Jh}*vXA1?MpAatQc$xQ|OTh zmbY?pFyXZ??p0X_08P+{K^f2<`2lMQ%V1AY6~nbT$1>aPE8Aw)W@U6D5jZ$dJPt)- z)aTsmh0yDTk7cDdwhW?fM(`(wS7iwXr3D38qUm3r=MV(JLk9ijUM#VYfQca2`=JMu z@uF4AW^%O4))%U~G0d6X3 zu`&h!TtX{O!*5T{v=b<)Iz_arEo~@Y?u?A$nO~-vN1Dp69$9=EEq|)J+7? zKMZ4E0(|3r2vYVk84$~6!eHleIhMYRwcNQd+nyVi-303zUtA2?OW>K>-_{U zLbJrG0croJlxpo0LAne0msIdK8VnvpNg#>?)P8l(9-yeC85_I~>*QE=LG^J;yLz}8 zeh1oExn)}WM^C?e^r2rY8)vMrkZb3&{hb+{cy2=>X*-RFSU+b!^Z*u*=B2s>nCZsf zz6Ngc`q}4f-Pxr;)lg^xmZiBfxQn1Ig8h6y?F4kE?u*O$4@Oz(KAO+L)5QG^EX$&bbRM-*)C$xCb#p3fJ-%_fdvF&x&NL|bj)OS zx{f)xvwIEvfZz%MRg+!CAR%MLK0q515rw+6^+SrH0Mb+-f1 zv_;_I^jT2B9-vCgiu!>O_)b2zfv7P%onYCO03S97sFo&)(ZE-2v?+fsF63uENvpQ3 zru=)gC|C`|!o2}5N&Wx;XP$l;uHL?g zpRZ0Orgcf*S6_M)uHL+0J;&*%UxmOA;hXE{AWI84`|5iz+1uBif4ZnLsa|>EO}KFN z3nyU5h=38?ry0bKEwqcAwq|iK@OSB~8ffDk8Tk7T7LtXg6kuhuXPhYb+8A-GuTKD? z$ZytPsHxGRUOA8dv!$U9Pg+D`1;;yci24DntoI#?2_etU)nOIW4i`B;!N&2C2L4@L z-czS5BfED~@CJA9T0pIv9C_yL6952k{rvsf$P_jE&k&ICopN2Lb(Jax_6+2vu|33EQs<}D-nxi>&T0@` zyn3FiHdt9=0t7BzIWPVF#n+#}^H09W_g%wE>yqx?zYDl0bv*+%Px7>oDsPIaGqHI1 zvSX5?M4&0%ZioRu0aj_&^Zm75UfvZ0e4bKNQal2f*yRdi?}08}wE_IO!K%}b_8!dm zwTMCn>d{-gcGv3=yG=PSh!BU*RgWdF2UTDmfOl{1!80!(aWKs0$N`o%6xM;AE|-@l zg`9W$C=GYy!{fTK`r_0TJdGsu@KAH(_z0%^x(>gx)`w)8Rlt83L_-}*hq&CbdvD4Q zg%>EQOEm``KWAC!l5JK8wY8}d2T7SUrQwYq%sLZRKOmY%S%H4$Fr;KJh~4ut>XI=9 zSHTJ~C=msHsFp{{qTs(LRH#)!@xn&f+P@!qaOwI5)XJTG^=$wEIQP}3eE(aoy$k0r zekKzdrFF08d61_Glw~Oe-_7bM{lSXdh|d=9hCLS-A}|}L5XL@VE-3O0CjeXqmj0oW z0D@FYU?hM~S}H3B4GEy&M;?rVO-Q{lOvi~Hd5Sd!6xf~7!6&l8o9-_liULg*s61X` zlulX!R|9(%5(qGzBmi}>B*y&2=0ZC78;FLjnvemDAKa1Ut9YFmg#wO`nuzZ4_Kg3G zOZHfliD4;+ik)Oyy~R>e3ACBz5=nlDK&p9ckJ0*le3+$i4+$)%yLurh&m*w1*5{yq z(Cx(Y8*t~^juV;bN@KFgg0KAO| zh+ZX2-?o9h=jd&AAuje|OW@TP-{g+Vg=?R;27VS<37$`!yB3N|cH*5y{Qw55G0et^ zjEA)W{QA;4srSj(m(THt{<18sSmF7NbJW&&t6AS~AMGL$z`kD=6#T%eKiAPS2wS=o z0g$HLrC?OzUZ4d95R(-mI)RvGJ1~ki0iXh{m5n|C(B@hUGA5w_CyqS@x4yrrea7-J zH%F)7`gd2V3RVtl0-I~cU~9W>?h$y1vOtXBFBXE>+nVwZ)>n=IfJ>1V;$+L!O)8WG zRm)jMwgAA`2fCBON*W#~28W7|bhxU^kb4iN%ir>0rYIPI|50o#6?S4MYIRuPAA~-X zEPD8WBme{dhqaw}enY#~0g|_D8asEt-7^||(^i&rsps2vY|P`tzV8B%s{G&f8&etG zHh{JT_?f&-+d2HuGX^e(p${`5!0^pi-{orM<(prj$09;yyf&4EJ!{iF4OZjYGft!_ zGTTp#*z@(Jb9NxcIvN_rY9vVTw(5TDd5geqBV!l|&0`mT=jsn%9IgSnk9j zly)1ai=ZR^dl_%IjS$$p{jTT6z(%{z(EYg^o#d$Ci5FxG7U1Bp#WKC3HpFMG!-*{lcf~Y11 zVk<%-A}|#z|Lb+}sTF6CqS~}Qmz)K?Op@)^in;FKk4=UWSlt3*A|}k+7WhF(2au5& z5THu-LyMhklmGE}N4{gcHJ7M|NZ6J|TMX=&6-v;`o0y6Aaw%?1-!eq6>u;~dP)>~o6W#He9 zkaYuoYE5xL;k5s>G{;JyQu?2%YB> zC`-yohnT|3NLHTahGsxW+g}0zo1+u(U~&h5Qki|*TEf!ilu8_-A;?e9GaRn=%t3Y? zTNGnC{`Vi;sp|4GaCtD3!QWK)k0>1lNi!*v`4*hm+;A{1b<5+LNbTI8NFB&X2WzjA zfG*p;n~j9JeaEdwxE~N>U4esagTNE{q|DLOT1_4dUzR}uW>ls^mjYr&8J`}19^ znZd#lXsw?(``Dxd2{4em)y;ts{0}7i(nj)f>s+Pl29xokOLyvr15kJN0VqiQzms_; z6M#p!O?KvwP3-~O#;RkgUb_`MvQkSY)PPNpLfeifO8BDF683yP^0FEzqh4TLV^zI; zb{)i0E1%i3l9e*7N3##+r2?sRtYO{Vq&T>MzIqQu4Pdsop)mGgI!>Gv9yJbO8KB3C zF6A9ah;4afuo5>!KyyBuZ81yvxDeO)O_dQ@(IK17n(ie4Kp|=g)-=@;(7<0XpyPc96irNfgb~5)3%soEPn`s$d@En zKlC_YsopOJibY;(%VA<14?(0;_IX>kNVVEsI?s(j?*V~Q8Q&O~n%22(_t*-^e)Y`6 zbVv3K+5dgtgXwMpMNz`)kv@zc%$x5ErxYXD)$2cKoC5)3t=z}U2s;pgWQ~G5MgRaH z07*naRGx7XU`q29)v~>u|lt3BXe?9{?2A+3otB%bW-VyZ5Hh8%Er~FQyqpaR9S%#ZXo@`}{%d-k(`5KWQ=CRPaLc zw16P;1r{4?>UEFA%|=Pq1Oge`m^PJ@M>@et7l>s7p6O+6wkdYfIR z>=S%$&<3Qsc*1c{?(;6n>I-3vi=|o@Q+x@+550CtyLJv%EL|y^wdSI;=Ufz}Rt~l| z2qB0($kGDJjJ7HQZse?ri71N}fUiAlT2dfF76Db3FK9ul=yrh!oPPFYxN_s0=F#~T zD0+VFL=|RlT)O|EeR*zwdv3Ifgy4>cDF#3t`zi^OL0*?|%d(o^;x+q06?0}B3kNU* zrXCizwy!||KwvS=U@=XB0>IHzt9+*IZ_AT^{n$VPj!dc9FQo%gUB*|t42nhxuXLt% z^Qm9VD3W$fcIWmU1b`tCe&OcsyacvxkEJ77!QbHd6XOFx-nOuNwH>kdt{0Dy=SB0Lgy2+hwPAl}&aZ8-5*xjK zQ_7x=Xkc~a^R%pF^pH3m!}d&U8;h}OqWh@^`b0@5G$|*SZ(Ojd%*0m#7QY|7J#*czWC-NuDpr+)p!g{K`9EnI-N*T_!0!gi3(8_t0j6bm_VKwrZTUYV3S{~djAxc z@UehBVi8f?9J@x2rc4qlzz==9x}c=Xbpih-)Ik7lQpvIEgal${&qkTQV<7;s95H(Q zRt2M7##$Cf0)GkFm$(ZXw3ftdVE^>XN189!KYad;HA^iLo&}Hndjiw*KYFix_HnofGB>40|Qm>Kqgj%wMQkiNf8zv&|;cF z5P4_|az-XpPtsl8aZA}-X7ht?YD*gdYbB`uGV(WZGHhMMH1_P&~H3&U;=EMv9oEN{j4F16WM8xGotXE55jV4q6R1IqoR*e>@ zMFxYGz(KK!AVuC|5c#;*EbR0G0*mb&-gxsY+`aQX&PBLdR^2DxGL5r!M0r=0Ki}rwFWu`p~nqImKNaq9{k;(kNC4% zOi}>Sq>klT2_7*Ia?#5#h4Jc(22xOyq~}PhCML-&gD?uYQKn#DBW3H&2LU6Kazd0I z=&o@PJb=tl(}2}7SYG1w`0-u9S|vgB#&;jq;bCrQ&fDB()6--V^sNkW3hL zIO1*HyI*_T_lQTDU`gTelmg?c%1={Cu}}jkUwZC!xcuGMHA(QKtd3+{!lm(L-ChaV zO3hNRe#0#DAe~noJCj*1qydUv6?OL~ec^@7#;H~S$7Lx%7bvKu0T6Dyj~zP>moHs} zm6cT^!V>j?@tXHk`;O=@9$7!ezc$TwVVdoMCS+UG57k@ZsefXR1gGvp4vGMEK|Izj zGK70K_h9YF07i95-ybRtpLa5=FKfIr1E2sWU)Ypyq$AKX=1!pspek~DC^xmYHIu6Q z{SX_Q9$_z#h=6K<_mND7wm_n`z(Qtd`zsO5Cu!vY(F*i(8a*mnX+Wm~4}CVQ8}vF^(cy_Ggu+(53_+`fGCYy24p z9*j;JTEbI(4ly##3Vo?gG*co1=Iu=O7IG))U|Y zumJc}0sr;UF;v?m1jk%s1_1@Mu|!*$V&eRCpjCC?6RmCudGF?4^V_Y%cdSj@-J5&x z#M4#UAM+rmbwQ3IFP1zx){9zxIDXyD%~PYP4bI_cqX)MxZ|mgqQKtox1_Xl8M?qiq zSeo=9Kk%IAD7IG10mVQvBeC5mwI7SkN#?zoNCXxfh=GOZL8&?LB2i)>_3!`nn(^NK z4$;?nR&FdFdRXOd#aybImb|qU(!ln;^z57P#f49-3V_Rm zKS&*cR@LOtD@KAawYMwm%+_Q!{0G5UN zmZE*=u?UqTzi3H~DuA^OFzQ(o1=@+lhSt}Cc>7XyKSChO8u>J#`*q_Yqye$8W44!| zFN2t`{?KG=_ECTp>No@sIGN=TtSq;l$N*n%*g`=IDrvnt>ik*T(|z*HrUvv+zPPz0 zvwgT*o)nHo&2y4$nfG7jCCv8I3P=Q@q{vD%HEy^TYx}e+X2NqDuE&t21vgT}tK@Fx zwFi+0xd2vbjER~~ihpp&ECq5`l1-?ltWJR-9}6&KZ-!7AX_rE^(NWzQp!!9MiXJ;-3+4*nS6 zE0{bg}N5560dCh96m(RevSo1AA`6dgtYV z>I-4e42vwwI~=E4R_L!pj^v{*mcpd^)>F6;0emy|sDF%r-gJ3E>{kJjc?o4fjov5o z`lFt&d6KMLv9X%6Wf>z&b2MWkPm9{uXzR}A=6S~hOXHtfp%G(6u(**fJ>+fRA4HCX zNXlxFW5~qEf;Ct={q!qv<>m#H47_&cZ8-P!$I||{UVX13iD}|2`?K@D{n`gc6G8ys zo!38*ouM=VkFo|oRlM0N&sHsrFWmT|Rn-VWpPw5`aAxVU6sSVlsPvn>uF^}X!TM1E zzn;43xV8-|9`x6%F?@IDmQDcJ`dY&<%__iRl2yfMT=KYDZEy7#3-nPX69QGRN}iT* zWOTCfxZ!(8BT8DOdlh>Vx(}X5;Ld}aKmlMlily(*XK8DJA*<9+Iz1{0>;VYxA7Fz> z?adMB{G!4zsJ*XTDk?+-z!yg)b-E#Hln>>~Mbvtorf-~lVFM1bF{4d^9y&P~*MWGc z(DvFE@m&@a%A#8nlvD{S6Qk=*;-0upt*#+Y1!Zfv7I&%(FOp+kGr;%wIw`gUkY%}6 z^WNqqW@!OYuY;FPl%>M5G0tomAdG!UUS(rK1S?TuRY5U`ssQQPIB^2ZvZU5ByJG2Q zHs=_~i|@l0FzO2Y{G9Wwm3*k%R7fc$hG=<_2$;t5H&@SVpRr}U3)esAOS`W=_qJ@? zg@1p2`Aes3W*+`KZ+rlse))&i@4x=?S?-&Dap^MyN%BzR$4FS@2lZUdHQT7ZC0aI1 z-IysQ$}iEm63LdAE5^8;uE993s`!cdt193xgvw5X)wuG`kyI>)enC8$Okd_Mi)gx#yjIDraG};7vmI|;rFx1gTPdZ*#@vdo4 zj1Q`A8->wotmqdMcJ5D`J_^EZ9-dQ8tbYGwccJavd}77<=ue)hB!6`&fJtjvQb0*5 zMA32`?y#tPssIFlu=Y&58)=u#^9Xox072+MmK2a7GAi?eDQ%VjFmF}uK_CVbmIo|i z(pw2(_ui}~ggVGCvthaSHV=`34lOGWxB`4)^n}?vA@-`@S1 z(cY*+lLTfC52&qFvkqdOvAt{<`%M+Pw5S1+NXDNj0C`rmQ|(ziz|uvIXZg`B+U#OK zgI+&`WS%)?80gW~R0HTR@?o}b@7BnG3-d(NxEnSdzW)DkWc5iG0?OSQrFq}r`dihn-R6WKyV}g z^cr&n!4Q7sgO%7)AXDkxzpz(EW;5!(*q1z*xd4&PmT zfX`p77(om8bp`*Ody`6DoMkXvi+S}fYF+gv8rYMi5VuO{5k0_{-aFZ9AP*7(zsxVQ zq-gDSM;;2(Xy5K!+cC~Zx#xCcpqII{sE>7~qY|*NCNHRMz_J*Wj$wo}jc6n zKhTU@>A3#xLR3mhQJ}X4o|Q;|Q4o3%MyBp~g>w)Yi>>es_4h?7yegHhUkfXt-sbh2 zz~2Uzrsaoa6cRPF10z3#R7;I zeWSrE@)BNq=`{cVxN`H7lkyQ0Ss~xsYl5)@2AG4@nD;V4Kzlp;1gu!0qWp$2{I;@koNAKgJ43!B55ps#us)`s1AkMem zN(5Mo+vndr{sX|Ieh~~fFpp%o(z8moPC)+yr0L!JcCRkxp;ydP4X9|KiNj@6i&1Ih zRAM&dP)CEa_S=@s0Ss_3@W&-u&xr`As)flT7SAg73NW4D1mI~>xTz@un3~p~%baSG zbd^s{1qHyqVMTOoKFX|ymtDmGyweT=`&}{>LHQu zLn5SP2-9@j>Hy_XePf$Bt33d!q^%h0 z{#bn7`dD>O=W(KW`zU!M%f3uyl|^WetThQz^%jcjFO|eBL?YQpfC=m^@;{Ty=d<*{ zEjkaA2&}Bj{l=&Ji{%e_h}-?|2wwi@ z=RY++L&0#cx$@1G3$4$%C`woxtn1j2%C;`()i0CMsy4R~kR4S3OBP?l_$Ur^1UVP+ z0XCi(^1pZQO@TsPu9Z|({j^e=7Vy{3f!vqqaW#>vL>d53JiDeCOyYEYRh4aIUeoii zmq7gLcQ*kjz_C-Shv=^#9QQANd|R57s~deS1udOd!JX;6>L0&xeDsj?2OQ}6WRco< z3X>y{)2kwbG|fBsi2y>|ugy7~N>LPCAm8%|m(r7Y)|#5@`NS9mW&&c2T$Tpuo3Bwv ze!JR(7_&C8IcU)q_WLjnw9cBC8c|98y76G1xhOa5|M%Bl;h6`M#4V1^)S6nfRpRq7#ds7-b0ih`^Eri}N8}(t zO68^p6lm*O9;O0xm+zb3^2tW=EPM<2(0jpgz_B^_{wDlDJ{}q%#|?cjw*&PCT~( zhp}zl-tVONh!e}yXCdV!NJ(`Z+AihhX~AoRmj%_N;dGu=L8p?(g5{MvzTMhErU0;D z0b4=KPcHPn8K3R2fhdAnN;g-2p_+a8W*7Ja@4{ zo9SmNFm>6Atc1_LWT|)`!lz&U-a1F4Uf^8(D7U28{`TWv!cX4+8T{s>e-@sd$A5!? zJ@y=k0~m~Ym4h}}j9Z9)DFyMW0MGs|@)8AqwJYKz!Os`;suKj_M4^c5zjJpY)XoQc zIbhQCUFSKphEQ#r3LpYLrQj^*i=UZ)Lbzc*wtf=E$$ja3{b7t>HZff(F@ilcC=bs) zeY)~U$KP2?A71173Cee)$uw46s)x~q+lvT%z(IG zj2Td?2C}Wc8o|5{%oArz>bP{0&T~!zOtlEsjtwNwoYh|!)skeMLlEkf3d0x&bVfo) zO8<$nppYb)mB@(&g!1&@WptSnC<41Gx3j-(<0zYy@Zw7^0sz338+ZVl2>zkL8>Rvf zwxv0WfG+N1+aEsv6<7N5yllR!mb41E~J z5TMG(4H5P&2FPLg@lo8g0JV8(YM@!$b&njI6ft5U8j7MtTId4wU6j7Y$onW5V});2 zH9(9RV8;+aey|#I@K+T8lw1U!1P@d1VdR776UnICI(pJlyk=?~hh;%w zuo4-7P%b;X%)7+Ad0WRp5opT9AY}r;>1SUALI5sbzhrzXYW}=)S4=9$qC~zDxQd+6 z3p|OeqtQAP;G=WDs_wI6y^b#Jor{{lvH!DWnO9D~2EASrM|I}K7r9b>?!qV9d-=n; zUrX;1Tj~tNiAjJMCMwb8W~v+QSSb0aqTpmN;emUit660W3dd%>1Aq#};B-%y$i00H zHik;(JF^9p5&u1}nYkSKIiVz`)5L>Gb zqv|=cs+^|rSrmHbx!xdjQgGU`2nMS$`WqDluq8@n#~Aplj{EJJayF7l=| zyk7)3xv!tcjXPJZUjEsa-s5lJv#<0ef!AI`ul|_-`?nweGZ0Ty zKa?eaj>O##6{IN`1O#AZqc2UU@y;BMoE!nA0Le77pOEN(tJVFuA!ad*R90rvMIiz4 zY(IgaQulzgl=a053W~oOuEp@(r3VrV8?E<{+6s0Ou`~%#$l31J)IiwUI_N#TZM-wn zJalGQL}NuQUWU4!%!(G-O2vAFRBB*zBGQToI< zSv3!Mp7ES_cAa8pF!7bS=A6?KGu#Duo*aahEs7>}YLRoP+C~~0m1Y6!#|ILjVn#w>Ym$+B#Fj7>bqBUREyqpZ9iMqaT1s;-FD5!vFwp%Tp)#z1|xG` z{ICX$WRXdxK~(%qH5kIk=g-4LlEw1P!%aL>;P&?Q`gk5>!o916@UySXsedZ?pZn@F z3mN#-`Pu&TM}J}*J0HINvkIg?{wL}456=EX`uqI(su#fiVh=qB1OHFI_^4eqYOxdQ zynpS5w=_~=0<@wi>pmm>0Cy_@>}ddq&dqY`v!u{HXN*}BfOsgMKjad!>h(8&^E%5! zco0O#TB1Af==Bqan*QQfnCHkEtn_&%g#nN|fIn7Mv5f?rdFuq+{N@4bQtaNJfhoaK z`u-uO0*KwOioDdoUoW1Z^V%+BG8?CEU0N3r(g@jiMKZu5kb1H?l1s6|Q_@5`0+U-Bfw%Zr~QQaiPP{l9cp0e<&c6C@~B zEj)YrUC0)xWIxZm_@V^v@4Wdwdd=^iZHgMjao?!)Es{A9Lg3tmPvGn;?>dja-+b~* z4L|%qE*wSyfV}d-lhjtD_vXb1;QJm7Mse#J8t)926h`m|QVo>N6C(jMsmyvP1+2Ir zCkc_91t3c=dd*=qsA@Wvmm`R!rYvK`E1P|dWT<@U;wRs8kAwigsneTQ@W;0AukS!w zuPv%i`&_n>#=ke4_M^V7(io)Mt-)WWqneky@;s+j3N}FaiHKE}I3ZW?C zKHgTg{*UOvv$40b*)!ilT_7hE8^_f9vNYE!SaT2ynC>yUElD*sJn=TTz`hZ{4(fv2%B7 zBmhRR7Zs?IqDqA;8BqJ)Q^RLr?89IE{XYENUyQio!c~*N!zuwzleZSz~$n%2R>VYUdBSd6>XAWMezq@z^=`FEeUE|8A$t=QC<|w@NdIf+C z><_fnH~o|Me=Y&M6G6vfoGg9A-h1l<>+y7Ny#bmeagTdq`0~k@y1MX3?^O!Mcz++x z3k{4~3BY$3w|P!6A-G0=>gcnuHTh0v_H2zG0ab|rNUpO0Yyt@&wf9T;pbHeHSQub~ zpa5qg0P|@I6etXaacw{v31D@zZ$_v3?{6<`N!N1f^hWaxOg#ydM}O<`w$%Tfef;!G zM-8HLl_5e^P4Rhyo4O}>NrO=qhyRaZ$adyDF_u>(Y6Ch6KSg~n2KajWJ{Fl zrJaf1wGP|5bTVTbQ+9${&UbClWAb)W;eEPF&|)OH3Y=o>f@+K?m}?PG2M^LDubajN zYxT>@N#}WK%syb_u;`Gp&Yr33dz|hjPTy0PYTUf~5|6?G%>V!(07*naRP|16p5$nw z2jlIzRPva4VPWi}W2+E?)ugy88=fi0QPTq3$Uw5loGSliExjT5`2eUuSHEZW0&*I$ z9O*Sc)CzgnCKeSkzPI{${^b2X!9Dy>WdeW&8@k2@wSoN?-?T4DeE8N+`M-blyMIEz z^T9hGYERNE`!@_>e>~<>I87If04Ro6aJLx&s9w74|Mpj7q`|mBOQv$ps3@g~DnJ8l zRKN!!kpe~l7_e5-62Z6$Vk8Mn$BU|vkYSrmlP1@_CSxm%0GLk$-(J{~2Fj_^o4D66 z9AkfbVGlqd09&`l@Dw8e^?%eg(JU#neK)?=!}gzj^_Utjq!ECfyAyfsn&K77ntP>! zBD+41|Q$O}Ku*s5~Ketk=jOS@yoK4oYU6W|!nfHi!w99TH0ccyA z5RExzbWM_K=0E^U-mgwxs&Oz_i5kgX7_U+zm-^X8Mgl;s&!5#lXbZ?n7CFR&5IjPv zj*tK_Sc@SDJZX7AY;lZn7?rR!aVJ!JUQ~MSdN87mOqgm8px0FwWHTL(EhR8gDJyOi z=5_3qSS>Q@2TJVOe{_7HFpkJS?+@hx?7S(UPEkxRzS28dcoT?6nC&mD|0Y1kzF+N5hqK4rnF zd#S+-Zag>|D>@x)gXr(5wYn$vQRoW;Oh~7M};t?o7k>l@UF|V2? z^@7;#;VTQ!ENjG6cKe&Ubnsg*ebDOF|Ll+c6#n_w{}1{-alab3L;>Ir=Nd0xOsA6_ z)N1?vx!=NvXMc>ohqr{ly-&aV2!8VZ&vZr0VV$3#0p+C2k8t%*1^nAr zt$D9}eh1c$4q9&%EP0-ha`B=5M!AivE0`Uo?NwrjIQioWDLqGJa z#c|lFlUkAU2mvo3%5~swe}t$gBH?2H>J0*Ze73X5sxFLT5UW_(P#qxYBG;BH)F3~m zDlnjrdsvC%>U=aXt0g3_?T4ZQ_z3o6}g^gHOKxExh~MPvAcp z*5ero004OZ?GGDiUNMM2dH-kNlJVV9_eIGgX*&U1Ze7qbk&P;^KvP80aPEC{p&;bCBpT?ZAGr+8r??m_A~+hH6)s5mmFDu)dG#q+Xd z0-_k;Zn@tSUE)v+sPvT{1Cu?y(;~|P*mz@NFoHcmRc}D@q$$-@Fyeb?kXDg3fsSp!?;1TG)Xk;T?=!Gg!5#yDQX5=8 z?`SoKS=<74tzo3cm#E82k=TrBkxQn1!W3CrscCA47Jt4qA^7x*kB!g3_L*r`{Q3{S zbOwJ0w152LKW%wusV2>f8E}M~IZDe2&yx8gQwIZpez(lmiMiiITru}YSqEF*^F zgeYxPTT}q`U{NoCBPUk?2*CM^pH=_v@^p}EGXZ?tXO;XT?-aqm)3Sjp_*<46#@pR$ z-mB~R4A`;#Dq#?1clKV`M5l_^qmGZTrFCVMW8WMHm8p?uG*Z@9(?uZxzW8q!&kI|J zRr_3&luD^u6soFrZF1~FAv9_xhPZxTflNE49h(sb`bhq?>u7-Z?RzE7l}MXAn!WjUrSnoe`GNmB2lC>V(8 z)M~bjEme|T%UiFRi`!hjd5e*7Sj|)YrXc$umqdH6lLPRO**JCOkO@Actv*qz=pdsV(Cy*Cx? zlWC?$)lc+E)Eh-o!5P)t5SJG6tOP>jOo1jnlS;tb1@xC``r|DVz|dlVu);CJH62_h zO740E{(7}W4Cqvkk=8^jnYk-1aW9mzS1_PYX1Q~`z`&p&8l#~dfS--czx>->_{V>} zAq5GE;~~$APHA_YnzVH2m>3U2Z8;=TJnO1y6p@}ot-Hm!T9i?Cc| zrBp6A?uSSTRtmrPhkf{mU##-?mrOGluJyG1b2S5?$eYj5#xE1brUikEG$5 zIAhfFp)5+igq1GrT_{VT0hCGv*N!$sXYc+@n+B{Rb`Fed z#{Uv)?ncmVTLK`R{6MjGfC%$G_#J_s@Hjf-mkF^yBLDzOfE+!!!iP;V&zh)S3TQ5C z5cv>BJ`7gls{)lHdgAMHI4-w5*2 zu_ssMWs0;ggFmuu-PV^Q7zo7urVl|jH%}yy2>3y}2Rg0uaRbL<8z!*VpEYJ(*B(vQ3Kxs*>Rl{t}d_=Hk1YkBv>wXa33fkCIfZiyCY*D~snu6zf zkfk|==^t+ddyFj3GC0glyqCPiv-ARwuPsxh5~v7R zSi5D_R z55?e0Y`KHgdT_ZE&nHch9|MbE^G6+b;(MD4Y1yh0@WER@tyEbh=bd@%ys3^zM!d2G zuSo|5pgTEOph1Auue{Pj)T3CtKk>I-*$wF1v;fod0RZUt2I&6#Tg@`CS9&r5Fuiz8 z5@ZZfEi2nf0P26(2tXU)s}F9vw}9uKcnvOGt(rs}dur7zHInv9Nn5w~^}JP&w5}5e zaZCA30ca<6^t8%!F#@o1q}M84Vl4a@e?Nx5`EQ3nOA0SM`!d|!y@47rl~~ve9tuW^ z0rGv)YIs}#oPmAR3pfa~9_Gseey@(DSWGe=SAi8(+rBg5Rq%|4Gf>nLQ`1F?d%^5o zp+SoExG>h^2e()U&m&M26w0F10u*`w2IM|do*6)#iGvCM*m9nU0AYcF?0tuEfT~AS z8Ji{@id|n7)JBZ}&4`QfnIc`l)b(dm#6RnvClV(VaAuh?enPyAbWs5D&Kxz$8s(1Tin0rGD9Zgq!4lSFksd9pB?d#zqGE!%)jd zXZM6DMnQ*iq~aKE1AChX$;KDK6FHv#BaLrGJljWN89W z7F6o>fCb>0)RUin_;Xvy6J$K4+UGV8*W{VEHpH=zgD{GnZHpl08)Aom?k5(pBD+L^QhHH)E`DJ)qcAbIKn#8AwhbCsA)&Uy3W79 zJ8yjlSh-bA_AhZ2Kn(N~V+w+(syYT#kG5n6TOK0ngP_0WV9ATo|al_Q``^uQh-PVu$T$L6va#jd{OSKO4GVC;4CZjB><#c zt4d(^w--h)SCa8!u~|<$*%5=|#9%iS>{;qvR{y`uOKp6aKp*>CG`=br^@brVW*HE# zniK!O7pcv5Ljqt2T}^=KmjCNI;rbVAx>R+j>8j6$%<{sx$*FFrH&?_{1;{Whe^WW);g*rtMgGS9W#ZdDOb|5j@fi0V^O=%kX3r+@cv zHaL+Wgcu9TEW5Gsb@a(q*t<8Y$7aAm-v#JZl4V*^$g_fvLv77G_O!mEusKCvzabs}A~3(%t_ZCSjuOfkB!Y{Z0BXhA^;DPSmwHB1*%B!DIBLM_!hCbW{`v8Zl6fH+#kbwp^u5Ix#~5yZ>~aa}q!-?^rap z38F|a@3ZQkL93v$y4cXlMz3|KEvBhNDnx=14?-yO5^sKFF#AQ}KCeuL$KpW%lifrp zW<+X$^rduIHOR0~(o{vIi5+9>)_&ulP~Fss^T-c93FyV2uvb4DFKV*r7QCr~zY6q> z2r$_`nP*y15R z>ztOQ9wW~R4vN^OuDNzSpF|$!nWmz+E+@1F_c4%-`ypgvkrso25yy_LX$@b(Aoz0; zQxgf2@<1t-6e3kU=wZ?QycKc+gSrCbY%k@@GkSoc?skiDL{(=!y#=nQJQ<(A!Ly|Z zG0^4{i~Se`_6!ZAqn}tR8{1|A>9LVHt9ro#7k#hQ_ed9c>&YteMx}V9sxLq_R#@G9 z9PF`kCiOAmC5rR7DGe@b{H|b{DKP--R|_c!P`(nc^dK7a9DXCrnRsA)9?RU%?yjr^ z2W{y@BH$CpiY&UW>U_*ANU#x zumL|SaAAvt7J(1*sF^p-jCzsD-X{kB%S7T+N@cHy)*f6@%0?6!Ga}%tnFb;V>hLNU2H=vh~Z@AZ;aeq`X)d!gNb*%7U$jxyXvKiQLw;ZZXRw zfDw<^_RK^{86yE$p0l`2Vr=hPlaP($Lpu{sa`#fDL=KLck5f3vCZ{x#+PGz!VGt{W zX_Bk}Hp8_TCPEE*Vp36RW8Ovz=W4Y{q7x#ZRO??as!X;Df{SS;rTH=WbFdmay%U=k z??;}NyDiQ$%u8R)vnC*xG-^RJMya!u+Lqx{meu|J<-gs9|Koo=su_QYkWfaJJw7H_ z1i!i@gB8Hal;@Du%O18ON}{5njw4nl03&W{im|hTB-wp=*$Uv!`AHN-5laaHaL$)MW+n+-V|vmA>c3Fm3m% zRi8tq4Yt%FwS=|ID;sHO^X9X(u%=XE@~l+#&k%o5l%-a&5hIdmlAAjv5;KT1wTD=) zw@t-hfS;!&jF9Ft?DwIeE<$E$D~yo^5$v5%*TJ4(1(eigPHa1JVg$2E(xk^3iy~6A z$c`8+Xj|fGd+t?GZJ?7yp$19?jsc?NP=jJYxouGccBzGs*xGoT=2KZvv^ELx2z>DN zj}*)zTn5L+bky%T0_pN9Ko~}yyyh%TJEbz}UveWcwq*lTK_86JxTpXc0XTMQm5Rof zdvS(iZGE?2c`L+pQ9hU7)UIx#tjIiurp&J(jSDwo3N~YGQ#5qFLFil;-E`f$act6q z4ze+#fDw-@Ed+M0*Fj)jL6MiuzETH%Y`e8}**clUi7y(OHuXdNU7HF+DXk*p@hmhc zD?)oVS-}YaHojTNh**0f>Ics6y!F}@1wbNtg4e#7H+>nzF`LY*Dv2yDtY1gPr%CSY zP1(G741n^iT9z<*bj)b8mQt1KI5 zp+5@cV^T~p42%VwSiOfJ@;T8Imw?0qHW-pqIZ*oD=}TZuEx6-?60w%rRPB-E>y~;$f&M7Bivvj7{P*v2B*$ zF0T7wH!;~Wvn)miOvX>WF02ykbU&$$y000&WfTF)vopzKh(Rs#W__@YY{)&*sd8O` zRWDW7cg1yi5%AgDV1-+TYjJC-VOy{s0KiyjZ!Aj2t4I2%*T@EM+w-;ow|d>G^clqg zWNDR~9mT%p6$ODD?~tZB2cfomwp~{k`H;+Wt)4;!egzDKUg}wg7RKyZnF#fN^S^Du z-~VFW+#Lq0I0##*W3~SWMmW?>Y{+#GbwdQpbvgh5AOJ~3K~$ZijEwe>m>ZXJwxXh7 zE$9V~Oxo4I(*Wy8_EDco$dHscVaob1@F#v;}CehEv`yD{G9@k5h z$8{nAj$rOWSe9A4NA2x+$9O7d0&w#A4T{~6m~))9R7^7x0BDL^u)!rV<2PP@8$MRn z?g47K)N=2(W+4OsX8VaYmKoz$*P$2V>fT02Vrx(!1N_s^yaHFgy|A3CR7dKoQNC2s z57o83FlF$_I2$yz(A-svK#&VZA%fRIyK1z{#jbI0`PP%0Wa{- zcZZ3AqC{<~P0yMEby07sv_GN{Kx*{@F&WmT4l$BoP4mnt1d*q`$8?dSYKR|r&>MtC zG9h~F6(HyVp+P7|?5o~m?yy9J89uhZ*Fr27)6`O%o=QB(^W4ogmffLVuc?|ITZXdr z3(!=edaS2b#kIO0{w_&PsOl8Zzxumf__u$#3Nf<&&(0|?qbLrBV6PieN_n=Fn)7ce zn#Ps@*ge{+)0y7gbZ=~((1%qX*R%wX)&!udx4(=u-*qE|z<9iE{M?lQ1VoVVsK8X~ zOdk#KG3Niq%Wp#*_u==S{wHqy*#Aof%;I6ljmd>;U(~&_n+M1XeIWCSFr0M*` zdtWzYZgVr}D-YZR{&;(!BaTQ>MXk z?TweeZ{m>pNGX-O0h$=ED_ce-ABS4?c}t8zSqBDTX+)(%q^${<+hkW}h}2sA(s%lS zRyEOfZnXriHo#_?+zdxi5Q2_va7B<$4a*j;u@ns%Aeu3o?X!wrCjtuGR*@gb%5A$=WG%Z%BNl2T(69Mxu&v{B$j>pht4Ewn&H z^jRASE~*-c|E}X4)FL{p(qVrT8I}y%J49lsddW1a+Jd)rGi^~C`H~q{UxGQ2v?3?} z<)8jnoxlwHTvGrHV?Y$;8wEiB0jnfvN)P_xja0xCogc+L=(;Vd0*Ffh z>LHvJ0JYGF#_Syk!JDtV17BY~r5Vl5*V$s7nF@cCCC%Y&MgSDos0 zwM}eovD;kt;3!TUMBou6&?pXg)II?{Ffq|`xC0LOF2th9OY}R0D5WC>!_jx_6R+6} z*382vu(GMgQiz3gdV|op`rT|l<$+T&ILSAVTL>6?W~T4-hhZHI8AzlmOA2YdS#0#9 z_Fphb*#PkWQ}<>;mtEI+*thqY@BH8Y?`||^5CBa~BtmgAM~ND2$x2l!en=`+PD+VI zalk|h0vKD0Sdwgs695t*NUJ1SmAL$n=eR1eEK9a1krEk-#K-{yL5$u1c;_?jorgV~ zea3rlcY{vOL-+mfc+Nh1@3q#qzV$7n zcFCa*mkB5#qH3cqvL!`lB{GPDS;h)!g1f&k!9#Zqbp&s|W*TWzx)(r!-w`d6Vw*Bb z;P_TW?Jy2yS)gtfhP4L2iZ(DEu-HbLfN%lnmYaZaj;aGHpRb5U*|ZSYlzRv$q_i{e zF38|-YUH;?0MzjpfBt8+&ku*ghR@aq9YURNyR&*dP=NOSM8y|hd+kRWP44xE`P_<@ zxz_Y1xjR)e4CEcbo__AJ3iNK-6wxwiu5DJv|G#?g+KVmA1J^Rl)ju^~VV;9Ff?o4R zTi$S~EnwzMrxUgbU=qzJ2{XB#U2tGcht{$wmechmNX-P?OvA$EMqE*tXk7v) z5UO@U17B4HG|vf^vb9_RwN{t2$PgCX9P%~y9n(Qyhnls0XB%fITIR-|Gma1c?jb;c zhwkVD0KRbdP&(#hBg8`~5t_!+Tpx-sr>b~d&o25X%BXH>EAD#m1P^?yU%MXFIB;I? zTq%;JCJm|zpsbl3sMa`#NP+T<>wu*WY23v4kqhV#pswwSrymzlaT{u|weh_Wqx^b9 z{nd+q;_=5TUdZ*=U)T6si-i~?&kbOMLhEOXMb0&p$Qp%P1e}ya8Tw8clT@$ahTOY{eTcBay`4=tCt%`CWAq%rf{I^3@|&|Y=W4z&`oV& z%_z3%P~EQ~~!&PC4=sY7O8mTayGIO3SrV*|>^9KCz zaP*hQI8WNx+0u3ZU(J+dBYLV4gsm@9?Z(hht1KrIX6us z?RVXxHiBFO%BYi)gV>bzP=-!%V-_urfLb>y+SC%YmQ^wQPGHIFwDis4jKgw@jj`(Z zEJ>J45UTw_Ia{W4uA<*^W1NqD_6QsVfAH(4@v*NQiAFJUxF5Q6(7LuvN88lSH|K-x z&;q%x4(U?sxZKx5%}1@^Nq{fhHK^@%P~K$Mr60}on?j1BRf4-7Jki|`)jc%#heC5E zV=R}msN(z;gO&ESw1fy4-&ReI>hco+tPT~*lPI-s=Q*lqE^*W0H-nB#f&u_oM8#@= z=b!&!ORRn=53=FQ7v79#JEy7tWuYx}wj^d84{Kpn|<^mu^4c zn(W2&=gLv`_)M?3xfxAPMD$&6QAC$GQFr>S$G@SUzxl>n@S_)A6cZ>}Z}iJ~E!Qwq zW)*}vR^dE077{_UOzInjl#Dc?QWS^ON3dpuyDAf=>NZOwa%?-oWl}LbdF{I0DEr(d z`qMNI)^S2|bf)TD-SyB3zVPv(HvXF&Yjg72z*-l)9b{>SXqgH*;F`@}lTf?sxg&M= zuj(uda$Fl4Qy|eIPi5tw;idJ$q>=RN4IBjNueNa(U%x-L_154mA~eETDynRj?`bY1 zB~F{sREzq`It#XLU#@Dz{SJH}#?r>;!N z>bA6NdEc}Q31&5JZeZ!(RgOYiYS(q1Xxejr>ODWdmGPU+jw_RDwOZ6V zGtWQwLgV*LCX=mge4YX*9=G0lYwh!a-)#w$yseFVt>BRSiYAdD0C@cI#~b(G>u-2N z{cjmI$@c<~-OKuoabKEd=*V1Np%p2wdWC=gZSMyFeC?b68Sj4E`(Yb3o&Wi3|Do|j zt(JVBGQ3#{PU9@kzp209_Lg_b0m~=N3(^6oEF!u9Krr8r&^zVoi%}6wGG`cH*~uGx zMA=$ze^Yq4tsgtkOw?=KRC#?=TTAF{%NbEp+2mQqD*FCruLHA1w5yfw>Ir6Mp1$qq z@A@^Hb6Pn0wyuub7^H0uWt`OgscZkLlmeSJOO~<5vMII6c`e4o@oXJ=2~B$2fL#-$ z3CmFdX;93{7j=)#=b}|oifVabbGKWoSQ^BOY7ijDP?)1z2oITJNp0$e<#bn8lfTXy za@y^pH(9|!OQNje#MV|$)U4x8+GEFIT0m8ruD7noMVod52d|)L99$zHsgc)jg8Pg2 zninmz@!Xvcp5U?n^S|E;_OBzfnGQe!0k{wcpygCd2f%#WEd>XlMV@yHb-ubWX)8Be zg^R|EdE)WM8h@wB0Z5JY?Rf#nW!H@Od=kpMU^51+Hu!afpm*H&La7?N1eQ%(=ZA6v)*1LspnWqJpGH|JH=ldE>kyV=^j@=plAJ_lK4-hM#FJnf zLz_-Tx>d{$wMNQor;aAmREAj&2G%*b>saK#W*zqcQQ2{He!jLhfU{*)jG9v>fv?P^ zh}Tpl)kWwVH|=UT%yer|S}%2#5<$;xta7NGm$H-VM?QUsNADYp{hXWk-*7tW&{oqk z&_-XTgFIUhOG&;z|Kz*>;Z_iMjsWEIc(t0>u7KyC`(d*KFr92Y_M-*_;Feo&t3@E% zj=^AjX%GPI04ys(f<^!yd#ur5U4O%MrT3G11Yo!u@O4JbaT`P>jK}YihTnuj>W)AA zPycsiH_G#ls$jF&^V{6Ss+%yWo@c}Ot0=)N+dFx6h2EZr`9XdYoX+}Y zIdmh%Mf3QX_y40-gWrY#XyYdlfbv&7*X96hkpdtE1b|>NIVm9n^$Lx*-gax_sv8c+ z7k~hSg)<^+5dh@?Jl^=)4L5F)0*E5X8utVW5JhQ)2SBrYq1<}wExGqJ+N&K@+i~$1 zU;X!uCrIt)iJ_oW-r!xY(=LDW&2PbDPe0Z;r8zkKo{Ml6Yj1nDAHntsQb~B~pYjmS z6ZpMSXKDX@Qk@wGl68uU z4I&z=MO-MwTPk<%e0LesL=VU^+dn>_tr}0Mh{|f7?mP0U#BqpVT-F zCk@7YRJ#}7cH3>O2*4(e&*ligqS0y;5P;IlH*A^$ICJ%otBt2TnbtJ{0)Y3u>!Kv+DBl}4X@^LAu^7Q4u4u@ z&Mj&`)Gaji9)Cmagrjo{&BEr4R~YnV17C#T4UK{@E&M&%U&69!A?MB^(g5Y$eA&Qt zhMwpZJ~xa2A-eKlB*l+HgT1ciEGcu(30oWJnq+in*efF{^KSp)l|8#oAAwxQ?Ft6|*--u=@6fIt7sKdtj63K1W*>ZkWQ zt?p{X#K2^k%SIt5fNL+mK?iDj1MgHh0N;7?n~kupdE9ivZE`X{)A*h%ueu7)z4-lV z9?;nPsYNxEuO2TBV)#S5>e(B$(D_8o(H73~(mY}q+CnbXFn;FU;D)@dY2Y`7PZ?($ z2g2b8XB|6}7h^3-yL8U;ItRdy|f)o5J<$qHYuAX!{2ht+W-Jhe7_|f&`?`S09cvWfFr)DyyMEtt_A@7 z@OiNZCjn{zXb*~8&mCo97qTw=boJVE0 z^CpYVc4(_{ZtLw(eof@~^Q-ZD`T8*{AL^n@bq|_^TWo2(2=b}}BAdUsfrIJ6sxenw zCbE@>C7j2wJqsWE%n`nD_Yk%xv;)d>+B9lS#*$EZ{WW7BF5^Zdq0`n{c~;Mz=?;BF z%cL>Vw+!c^(z@I13sOR*+EB(egPta=md~M3kHXwBDnKl!sxzSGZq(u&{OTi*qIG zlf{<1#hsyv0JmIL$${#%XPR%lJeeG}zOSC> zci|2|r|baiobK{+)YjH3y71w|!eEf+y54ltO)VEJ{#RY0{`AlP9RT2Gf9fBVCIi8L z{L3PD;Jt5uzi#vRttbAb_Ke>9wx6n`A&XFm!h@j#?&yK>@?sINnH!2othoQy+nC!0_(1d0f+Z+#taAhn+`tAQMIHMxvm}nP0^!y~15|&ArrdlJv|q>V(CHY~h8|?{x=!%u{kydW zZWDCLgx2fk-Y@^wYXBkIAlL1m>yVGKbNSKF1RLiUsquLw(_~Q#ygd$c-0JxKl|+S>jM57L%OL0 zK%@h3TjRHyDS)}=R2>j190}b5eFeiYVulKd#48DS)TGQFx=7wrGk#e7nZZepp z;y8EiJW?v2v$LYHORWetlgeY^yl@Gly-uxTL(01TrT3`Un)A&>Z`#U9s5oRKpKrNy zR&{R5(bdjnJxun+eOIK;xTfo$<6ENhoScc_T=!dH`*38c7`72N2^-6 zv`wa1IWLMoK@9Y1cuLF#b*&~W-v%k02ETlnZYi?Qbq;b}hiI9gJMe|$r&^^}8~`z{ zZODol^%exR7R?Ax8>zD`vb0@_J3f1e2kxjwaSEl;O}AJP*{YnkajqF-xbyQlT&;Tk{**o9)_t!%9 z+Zg$_rW z#8#Vm$E3Our%6_5(Wy2fuj3Tn9xjqx0=K5{JWF{i%x>9KM4C0{q?ziZXb5$*TpC~n z1M4!kvx>H+L9&_8~|Mj%g>Hh6+_k}qs+Oe)Nlj4QaV`W+K>u@Ro=AK zm)fq-`!DNRWNS#7mGwnXaT0O|gViX_trxv9A^AE7pfm7l(VM34r4~iJO0^LLA^3ye zJX85x)j?|3yw4Z|0l`N*UYT4TtKha#Z zpyi%RyaUtWzT>mSi1TxI$TGjuSKj;RRFL-?LbAJR&dod?l+J{bkXOAWOo6WVyyN{f zfm*lK;8!Y*WD20XCyEpRYWK`%pLuTUi2KAK0siEBe{yZmS-6hUe%qv8xZObv2jJU{ zubrG6U%UgbE878=jlQ|JB~gm3290^wyWWMr{>ER`8qo4aXHK=BdEYNIY^qUVQpC%! za5KL4&97D+hWGwlC0C>uVyy!p7R34e{i-H>1+^&VFI5`i>1UoSq)dv7k<`e~Qrv#a z+iN1o1W*1z+~8Yoyd6m#7b_;@1T^=A7k~648O4XS^WuuVtMS8^ig_?~I-b=*O6osX zP=Ir)8>=|qOdHtR9P9=qi-Z7K12HqG+c*h?+$(8bD;A}>LqRr=5?RJ-%j3#SfAf6m z43+I!MGb{EBh!!yAVi^TL^IV@O}e@QQy`{^fTj$wVt+@Aq)?4vJ63%gTT*hW*BJaw zHZal>>SZaDj#BXrll@`@wRgopjzv;yu9U%7(>bKv1>+KCM zH8i2CZ7odDX{qpPW9>KnX4~+8=iA?D9H^7yOW*)>`W|<^;-+BUYNcoXb07SN`b^Cm z^Vfg-)%MG*nZI7(6n^7_@BVr10Q`sl^e2_~zyBRS2{-Us27OZpAoqs%_xG#Dx-`d= z1_z)CI{5B0PvNa^u0sFc{Px!Y06$Sm3#4g+$DaCDV-(zU+<5Jq#HQlpV=|i;VXSkn zoo@syzg56b>t?mMs#agPW>JPpZwXngpRtsJsZ%6GMcC6i^~FVlT|UHM|66@aqPkP4;#z zE1y(1{dm=mz|}bkO1fBmpX1wwijunHm;Sd`_2UQb%!O+@eYarnIjiQ0RVQ#XIgCmApm6*o3H}ScVsdE ztDv%tNJiN?xiHwj-~bpYfbz$WTO5FQytAHb_QVrU70+q3*WsHmrz_=ra!tJN-S5ZO zzWGmWkg9A>i`s?u>JjgeMiU|1VFdMXmJ2I2RwP5`(bBhiAINKvFVNd2xaoao{H z^8I_DlxThXrmf~f|6-PcI%WU>AOJ~3K~x`)+&ivBnzcF)PQ5d8hmo6q_*chZ1|;27 zx|YV-T9Ki;_0t)6h{Lp&dqE)d@XN;ocMKaNX}7V`fa`nwvCrg=#DjP3zzcHa%kt;j z^aa}5Cv(PI_vtE%nH3yhTdwun-}sIVp!4>IzpHRrJ73zqrzrwAZAoijU_4Qe002SI zYdr7G5r7utjF<>OF$M6hcfTk9t!49Xm7SzI2jH4(u2#)})~jXx)O&8f3O@2;xNgIDVse0vL)6z#Z(PGYi^fT`5oZugr~%=)9HMG;$; zTcIGb>1I*WUycpKPEmd-daG3ud39;z&y(wO>=P5UHaSrF!i<3ycs{@TW>1ZtZYk4{ zp&ZzdV9A}PY*(aN-$$I$(~voGAn4Ej$1x2jurZd6=+Xyuf1}T9o2lm62u|& zqsEa4<6F*e-8^SuGOG8DxA7kzJC3;t6|k|TqN~aV^a^v>FqECHmLNKCtl8V4{jc5W z87jhQ>ExG!zVH?yajK79F3L`yGPMZJUtR*anCk_t`|T}DG8f%jOEa(M{N2WSh+iE< z^pnU?f-VS=^iJ%Z+HuZI^5B?^WyunMZ^BYfsbE%^XInja-umiHNJI44(i*&xx39}w zk+;{XOlwoQ1UCTa20#yE^tsN5b1?^EyP(>S4HFVa0Ax){XRG!%?T14qyy2W_5I!D@W{hv#XNZ4s#CVcuaFF`eEQq_$cFYr2 zNVO{^vrZ6qyP5o+oV^6mC*-mm=AUW(eVdgxD>B3I+SBzkT?Lc*E?lk4jQywLQ|@Yx z7P{&%TZ{dg*?wS7<^Ndhgq6oRj!M<$Sj+9X5JIC&;`ih^4T-zx@L=myv1_h(&ljet zh83EWHhJm}YuiV0~p2i4d1>Vz21#$vek+U{wS z1CA_3iMU%_hC4@W;F1$C?aH!p_fN&lWS+-OgW>Vrs;?a%25%ygh9lnPmmdsNIv2O; zVw*4EN)+9_t}nA{d#d-M7pcA;Q_&#s_gw$!f2e?DQ2kuS-e!I$RMhXAbHYe%by(;J zDq7IsTd3FbB~pimDsDe|NC-&~2X%^6qY5=>y-txV&(9rn9=LR;PX+L4>wJq86mSs@ zV8PRTCE}Ut=d<^%nJn;}bk5A6ns5Fez8Om?O8LgLb9_Jma%iL!Q zVWOf-SyaoBBB{o8`|G>OJhoF`{Lla4nrf#&2pOfF{o?VJi}HPm%W%2B_H{Gx{Y+Q1 z4Sl3x$J0ff#p-VAwj+5Y?NSVJf~L+PatVNRXdb&>gE&zrSBNjM1y2J5A@8t$(Nm#4 zS8~Dfy?emAdWyqTK#5sw;RIKqG1q6lDW*84s`H6631xm%K}_Cm7n&08YhNeKml6S( zl~2CQQjCwgBD7Pp{Bt!zqO5laluu0?{aX$rv^~x1^M5jl4&0fRw{t#`8wH@QVW8E% z>{rFmV5h#5-xyvE}IXL`M7+^@#Z-3IOx1468qER z(-n4?Qyd(odE8R(mxfehSze>g+@jBQoioY!Xrsm?rOJPc7O=HoD%{x|y<9j(fob_m zy;7?xdV3y313%V=baJ^541+>GIVakoqJry8fmC{>_!0xX$5I-HZx98e!lL(PpYL}6 zTC|Dw4$}!-`m931wGiqU9?W~sFk>42&BYe=r4wDmaId8mg-c*W!&-PlM7-{w0Y|~{ zwhN#Y;^U@swFXN;^6-(5wp=BFv>G~J+G7_ac?s-Fw2}Prg4|LDLnQ_Tg%_Y50<}<-a|$_ z!s*QKe$Mth(dXzw{ihf#K_3~=x1?W}0kt_o4;41A@v220+S}HDr;Svj0HEV1El6;% zwF6kS>6{>i2}`FTJ%S_4)U3~$lU?Xu`Mx;zb*Da|AA^tkAZ>B9C7QSgJH#A)q_4)o zSzyf04B0Oty60OwoN|~0nf{dqnLp1nWAV`2u5wc5vY6%{I?umhHUbhke;s*Lny1&< z3Q#{|jj-{f$!=d?`mxGVX*E`4et-z|#?cX2x*pH}`JpcYo_|LgqtXwhcFhM6yeK8x zrr$LDdCm#Fdj2XHKxd8b;E>He0If{>mE%)@2U$KaP9!p#?hmDi(hYkl`*A6un_iczJvC_o(!U)rblh zGW4%?DMTU2j-#7&+ss<5_J4Y8pgQUGVB0WAO~z3?M)V4d@xK`(x^@)(dA}yD*t`PL zAK!K-4~asUW07VNK+NfmN(!cb780EY4NM=i=la|T*Z4sw8ITp{!oJ8%H%kp!l%S-$ zaHj6ez5Kf}i?CG7oY==rOzUBJ4-gdnw<>&CgW3^IV5$fD0 zbien;ePwGm%SV+n3;xFaYd_n6A$#W)_t(`otcAWKx98_lsE`jw0v(+SP(Cy%+BXXC zOq7Al2>dz+k z{?GZMRmaS9o{8tCa0%M1WuCD_F4f`p3wQ#mTnGSVmXO?9)BpRG-xV7y$6YF&%X7%r znu}W zHT$+M)Vty8LQQYs8`!6DF(G=Kg4a$;Hd{*HMQZwbs#6_BzIusMjFzKv1v`YGOwA6$ zhLd2%`VB{u(OWW-JGKSPY~D|1Eel)YtJDas#|K%rrUB~lT@x>zfLBQyiSP5*z|Knw zF?6Sz+gcdxTFKVH9{2o<_Dz}L9yUbk6TxrY>*a(tQuzNK9-^+?1}tzY9Fc_V4c?dC z(vRW#&NvbfNJ1fG=PohJ69dN?OcR+~Zx^4UT#^V&NT)fzjTXtQ##v>ZvxA>mD`2g& zBEuBw7!--yrE1k`_MV#MROP}O7d#Ufkdiv_3%@c~VYl$I%++yeyS*r8$>CakOXaAQ z;H9hIRaaBEXXv(N1Q(W+Mco<+C`67q-VlQLPrKx&H`ycVu7b$M&}-iowg?4rKmIl7 zTKLrm5rGO}qeEu9ddDW%NgOwhh!}B1pKZ;ei3g;qOxisSP20r0KPU^Wg5G1xeXly$ zp$Em3ABG03qo)V0x*dNe+UtXxL`Zw@#;~UHOR|HOx zrs9EWm7SOE!w)9@17r$+S)I>fXY6HaXq6?!!GB)9oX#Zi(!v~Yn8=~!Pf5%=_^%_m zp%edCBA%}f97`4{&F&|hOF;di;lvJz$-g|p?*c43Pm{%5eq|Erp-bplA9Gakf^AC6 zBKDU=ArHoG%AQh8NL~+sxoXNaKPSf4c&@3852;YGNu**0NVB2zCe8SD@NqB360NPR z7<^8E5=Y9FeyjHmA>L9c4|WM!H-AqYoAgoZb#zSk&=0zK`$J4W*B)?Y>7U@(k5pDW z|7%~${$T~@8i0*8QZmYoq`eg-}076VjCKl+Ed7sdT^Dby6K&7&Z z0+hw|uA#&CS@%aZj{J5*%Q9k^wQqA})4k~A;3|ZTz+YoK8ha!Ah_<*?G)GunGKP^b zk5`xH!Or#S;MdVE3+yX{3PVhHF(<86&DWY>|K~4<(U+@3WAC`erUM?pIcILV%rk3! z{CxA6`e;-HYDnQaQH{g>>H>vX&o01K{D=2~*1+#M2=+ktnmI{sg^nf8e5p4q=$3ap*E zaym)0YW!Ys)3NAjU+rYG=sII)NFlHz2PMG%tOhh%s*wagLt&yq{+ln~pHX3Y7v^cy z%!M10u-#t~V!e-56(R^L)m@ns^}DuF#xyaFO;2OZ_Ztxom;MYA6h(BpRrwlcTFM0! ze3wkjD*5#GPoMx;7JXE|X~U9XLU6uThY`MUb)j1!icD~=uFy+7^QFn}LT;h~S912) zM^d$G@!F9KeFq84Td4cO?a-aPE}BM}_`S@;p9H3l>f?|8>%1dc&`R3nmbB+~5ziH; zkU>EY-uCMR9-UcO{$b+ha9-oi96!V?*c< z((8`u*)~*g9{S)O?>-GYr(KNmYtMX9;R)*l^_sI}_bdq$GQ+cLm}a}yY2_2?Gpwzw z#nP`CPS_C*{jfvVd*9|~hc7Tc*CXt^bBgePIj-W&3pv8_omAB^HN;laXCNm~V1@!4 zf5u*-i&Y8FZ8L$m#w0mI#o8#ZlztC33{QBG+C>9AW^|m@jzD-xX*A`uN0RCP&egzG z_+m&MJ@<~-^%-+pD~~P2zV$ouc=1hP?|Xas2c&Y}yj1y!Vd}3zN$0~&Nal4@HWCu0 zHICmdKgR{YOrxTV@vC=-jwbxWsLM?&iqde=7->ZHVk5qf_bin{y!pscn+~t)0;;teJzf0t!(RadD_L#c3bwxk zqmq^(4bcPIl6+dd$9`X-N1ax=sG4aU)G2P83E~^NZfh$)lj8|MbuM-ypp6Jw!Ap`* z1$^5fv6`2U*Cx$n6Dr_Sm5@YD1WDss9mT=ldBROOk8yLQ;rGW6Wo%Qb1>)C5k7;x( zy&@4AA}kp)E0WC94VaUX$b~UkD4<(e|gM7Z?=e1y6we!C{# zEogdZ+3aN~YkhU=#2}+5Xc1ulTv%sIn!i-Ei+F4JW=IoK?Prq8@P!(tm;jcV9yz3C zvh&M#-)}pPQ-2u&^9R_wTxjiDvuy-wW>+XIb&k%7qzS?hr=Y*_6pf+ z&WPTZ7+uf3Swxs9O;!%hrWQ1=XCa9}|9Ud`xaN@Y%s90Ll<$UIgae|qP}H=X8!YWY z48Z(!8b*WzKcZ)R<&I!{57LB)&kou&v{`J@3|sid zZ9^R5fcq;Qz5!2kVBD|U*^pO7%|{C4oNkV6Wd(7jy6j=%3-`M85L5PL$Ozro71R&{ zb678&3bQakZ)ienH}c!`3onP2cH8O?Ai!fw#t}bY4L?KySrZ_&!yO259yCK+Zusrf z{OfoQZ4f|U93=@`Pd8xN61)T$w!>L&R%cI+e+T6UxO6Hs2keYVQ^R~#z4P>F=3dzY z96vOLix;^{{*io31GuYa!IhuZOP7ZuarBst=w$<*4~11>%kk9IUpotGZ~WyUVp$a5 ztgSd#+Dv7|g&AVh{f#t=CBY7^*a~SfFkavDdp<`FJhz;h*-R%SgmDX1E0tn&D))gF z@>DHw3fvTijUDKmlkH{}_+5_WlL;5cl}bA~Hwl4d#3fnffv5X7%{jTcynFN1d80MX z<6fT6?5Pc{I_SO5Y!2cAhmTi1AWp#{XA!JYYSkxWg7Daz0+P3mZtdfWX>Q{@zJ&Q0 z1ZmhJ$Zgbk&RJaW_+$E|?a5@>I0n%|IkmFz-*fy63-S_zG1K$jyQl{v8qCrGULoT? z*1Ck2a=0|du9v8!wYHyJU!2M=&0QMtHF}b%ClG?Zt_E!@G4;9mhw|ut3bnu4!Cfq@ zZx;vYr_whfPvA<<}vz#~uU3FkVy<2^k?c33`2B=-A>rEji40xOY=LVov6Y3+hA7>y1 z=jTeAer>o^&bsK&d~Dtuc(A7~SC%4YCJ)zhHkXOS*XUgTPH8@-4sQ>{wq`qPzLxWg zh<*84c5)0X~RG{9Wy(mUP{NedPZ;op4hTwL0Wa=Z=to zNgPb$ev#L)QLb*ac1&?2-TpT%G*U+RIdx~ORdi95RmudOV{GR~O&lfB#;kQMt0GYvXO zB6!9<=ic$Yy@Px%7POm_45`^Pv8|+ydB1bgCh>$hB9x0R)(BPC@5FMzN76X7AoX>9 zovc{>EI9bv^36M!f`}q4IBiIjix|17FPinzr&EbP)~(xZEB8P5tyizd&EIsh`sq0+ zl;BQrL9L5eu2`iO28lK{QJ@&sp@y0(>x!ADp_Ej%z!NBEN z?DFxsZ?2CN3Bhp#_=^m6_Bb1?ynzN_CstoF`D|zchvJ{#*|KtlN~7vR_#x=Z0bLD0 zqg6b)L<|Zb-lin7p4-%-D(FlwB?ub@aW*&u9&AuKCf2x$FGO9NIN8|6vS+C<;ZK@; z-&Ga27=6K)%`d0_`js)CO2lE=SIqRPL|Z9vXH(mqy7K=CXGm=4sWQ|p4HMTyb{(B+ zjo;+%4~>XnL23tso1dnXH`^(n`5C?%{>SI_RHBRK-?Q_4iq8LPjuXH~H2Ml+?35JL zq7RQ6`cFnR|FPvg^5PPPq+W30`5uxm=<{#C~UOe#B%;EWzK|cckPrU z8Xn>EKrMhQz@~^}n#*ywXdQj=?;YK^=_-B=BjB@Sz|7C$GMq3ak5-HQL`jm;`LK=o z{{+ijaZ7(y889{QTtz>hzkb#>FOv>h^d#F8T%QOHXIOY1HRA26aKZDY?U6LLM4gH8 zvrRIXE;87&Zd|T7Q%GrUy}9?wFc*jrXNz5tcCkD#<;5jLPectj79)P%5q z)lScDp}K`|wDT~mbaci6$J`*rhm>N!sy;R;8C;Ab>J^X}OlG)adH0<}$ZnI=ijPq^ z#jxCYDt?Lm4)&pB(a{rjn<3sPh#I2nKhn|_il9mmgd>_G(W7!~d-IqIstrCqwR>rS zSa)?_u1+7W)~w|pKZx*)fKvTko-+M!ax!=Rumwv!|GU{B*?ghLlB*+5|Psq9Lgp zM!vV9x;s`X=Op@A)nEcGLBf->*P7W7T^wuuXvPR`z0VZafcc}zu) z%A(B01ayXZG+PNALRIqQlDtKve6udnhmFaFy!r-0Hd*|XD6CavI7%UtH;+o6Z2Nol0ogq4!{5C-`)z8YK+yfZrk9@~U?u`&*^E8E+R)#jwoCP? zUUkII^2YmOP7Bnu*?w9WAQ1fdiB%pg1s`Tm9Z3PzgX5G&nfuIma|DW?FM4I6K-rvh z{K3Wd!UEo_8y?C*M({ulNda83?L#cXSPiCaQ5TYK>L@Rl(a*x4DbF-=9oK2Wi-aMwTdX+Wu*3%P@Gow;K z5>vsPU+AcyULHadrcf+jM|R<1`6*QH7H_e1N`pNjhCEgJ?lB$9bCWMki-klGz9l|$ zF0Lf0l)OA=!6@5jr`Uas-8zBuX!u*z4~!XCyRF)2@gKE1t}4Y0Ue!CQ<~#9ifx#+U zh!*{@nz2%z+fXLXvri)oq{OvyPHi{{t)v;6*z|^-4=QO67-_;fC??Bj%KvM~Q$?T( zGmyzo9n&iz8UA&?_^UWyfkWolBDBKvtV`Exr@cL7BSp0I zDvSu3$RBVvq?7lRK@V?kmTLa%HnD9kiIf!nPWB!If0jtHsLrZnQO_be|D5%kzJw;UMTOd3=k5e}_J|GPEvu7pq1 zD7)H`6Ezexh7l;nDd&Ov0HsPxz6fc-vERVe&5R?4u&$KUqyrx^_<=hoJyFU&N+tJID-58wJ4O>4;TzkQrOuAx3oy=4vi z`x_vF^&r?5tW_x-p@Zu8u{;A4j}}6NC(t7N1PTAk|06Eo?|Mma3)eGIjFk?WlXgI4w)ADt526)AX&jqtvB(VqfX-%gqZrrB_WH z6V}{c_={b~uji*oaND4%oB=eAJ~p)(1G<2TOy8{<`kdpL+r)zXa#bp;lKx|WCT_)l zT>P{R+)yX#q?4Ph=z%P2NTifF3{buZE}z>RD&&IL7#3Bj({JXu`B zwZl{C+i^Ai`#�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