Compare commits

..

44 Commits

Author SHA1 Message Date
octarine-noise
78c7b53595 bump version to 2.6.5 2021-06-02 16:41:26 +02:00
octarine-noise
3a04099fc2 fix chinese lang file 2021-05-29 23:47:13 +02:00
octarine-noise
d6038dd072 add PackNameDecorator to avoid NPE
Forge allows pack hiding, but just in case
2021-05-29 23:43:14 +02:00
octarine-noise
4ebb7f2d35 fix blocks with flat lighting 2021-05-29 23:27:20 +02:00
octarine-noise
14a8600552 fix rising soul particle texture 2021-05-29 16:24:05 +02:00
octarine-noise
b3ffb7e4d6 Merge remote-tracking branch 'local/forge-1.15' into forge-1.16
# Conflicts:
#	src/main/kotlin/mods/betterfoliage/BetterFoliageMod.kt
2021-05-22 19:22:10 +02:00
octarine-noise
fc7f2be15c language key fixes 2021-05-22 19:20:34 +02:00
octarine-noise
b7bdd438e4 upgrade to Cloth Config v4 2021-05-22 19:00:54 +02:00
octarine-noise
65c9596a14 Merge remote-tracking branch 'local/forge-1.15' into forge-1.16
# Conflicts:
#	build.gradle.kts
#	src/main/kotlin/mods/betterfoliage/BetterFoliageMod.kt
#	src/main/kotlin/mods/betterfoliage/config/Config.kt
#	src/main/resources/assets/betterfoliage/lang/en_us.lang
2021-05-22 18:38:10 +02:00
octarine-noise
25b8896a25 log config GUI registration 2021-05-22 18:34:18 +02:00
octarine-noise
f1f811219e Merge remote-tracking branch 'local/forge-1.14' into forge-1.15
# Conflicts:
#	build.gradle.kts
#	gradle.properties
#	src/main/kotlin/mods/betterfoliage/BetterFoliageMod.kt
#	src/main/kotlin/mods/betterfoliage/client/Client.kt
#	src/main/kotlin/mods/betterfoliage/client/integration/ForestryIntegration.kt
#	src/main/kotlin/mods/betterfoliage/client/integration/RubberIntegration.kt
#	src/main/kotlin/mods/betterfoliage/client/render/EntityRisingSoulFX.kt
#	src/main/kotlin/mods/betterfoliage/client/render/RenderCactus.kt
#	src/main/kotlin/mods/betterfoliage/client/render/RenderGrass.kt
#	src/main/kotlin/mods/betterfoliage/client/render/RenderLeaves.kt
#	src/main/kotlin/mods/betterfoliage/client/render/RenderLilypad.kt
#	src/main/kotlin/mods/betterfoliage/client/render/RenderLog.kt
#	src/main/kotlin/mods/betterfoliage/client/render/RenderMycelium.kt
#	src/main/kotlin/mods/betterfoliage/client/render/RenderNetherrack.kt
#	src/main/kotlin/mods/betterfoliage/client/render/RenderReeds.kt
#	src/main/kotlin/mods/betterfoliage/client/texture/GrassRegistry.kt
#	src/main/kotlin/mods/betterfoliage/client/texture/LeafParticleRegistry.kt
#	src/main/kotlin/mods/betterfoliage/client/texture/LeafRegistry.kt
#	src/main/kotlin/mods/betterfoliage/client/texture/Utils.kt
#	src/main/kotlin/mods/betterfoliage/config/Config.kt
#	src/main/kotlin/mods/betterfoliage/config/DelegatingConfig.kt
#	src/main/kotlin/mods/betterfoliage/config/MainConfig.kt
#	src/main/kotlin/mods/betterfoliage/resource/discovery/Delegate.kt
#	src/main/kotlin/mods/betterfoliage/resource/generated/CenteringTextureGenerator.kt
#	src/main/kotlin/mods/betterfoliage/resource/generated/GeneratedGrass.kt
#	src/main/kotlin/mods/octarinecore/client/resource/AsyncSpriteProviderManager.kt
#	src/main/kotlin/mods/octarinecore/client/resource/ModelDiscovery.kt
#	src/main/kotlin/mods/octarinecore/client/resource/ResourceGeneration.kt
#	src/main/kotlin/mods/octarinecore/client/resource/ResourceHandler.kt
2021-05-22 18:07:32 +02:00
octarine-noise
57dc83f1af config GUI misc fixes 2021-05-22 17:49:57 +02:00
octarine-noise
180c2bf230 bump version to 2.5.2 2021-05-22 17:29:13 +02:00
octarine-noise
ff89aa7a13 add client-only version tester 2021-05-22 17:27:51 +02:00
octarine-noise
25cea8633c Integrate ClothConfig for Forge 2021-05-22 17:27:28 +02:00
octarine-noise
eeabc1922e translations JSON conversion
+ some cleanup
2021-05-22 17:25:48 +02:00
XiLaiTL
d7e16d603f Add files via upload
(cherry picked from commit ce8afacb019336962848ade302e7fe76c2654ebd)
2021-05-22 16:39:09 +02:00
octarine-noise
b1a08ab500 Merge remote-tracking branch 'local/forge-1.15' into forge-1.16
# Conflicts:
#	gradle.properties
#	src/main/kotlin/mods/betterfoliage/integration/ShadersModIntegration.kt
#	src/main/kotlin/mods/betterfoliage/model/HalfBaked.kt
#	src/main/kotlin/mods/betterfoliage/render/lighting/VanillaAoCalculation.kt
2021-05-16 12:30:07 +02:00
octarine-noise
fae9e9dfa9 adopt AO calculation tweak from Indigo 2021-05-16 12:02:42 +02:00
octarine-noise
512cd786f7 fix diffuse shading being too dark with shaders 2021-05-16 12:01:59 +02:00
octarine-noise
b96a17fdb9 clean up access transformer 2021-05-16 11:49:04 +02:00
octarine-noise
3d78ecce22 fix unbaked block model coords not being zero-centered 2021-05-16 11:48:34 +02:00
octarine-noise
6219e9353d fix crash with Abnormals Core
change random display tick mixin injection type from @Redirect to @Inject
2021-05-14 18:55:10 +02:00
octarine-noise
e3eb222d93 deburr build script 2021-05-14 18:52:22 +02:00
octarine-noise
8f82fefbb7 Port to 1.16.5
Kottle -> KotlinForForge
2021-05-14 14:44:29 +02:00
octarine-noise
8a303a1a29 update Gradle wrapper to 6.8.1
ForgeGradle dependency for FG4 onwards
2021-05-14 14:40:16 +02:00
octarine-noise
a97a575dd5 fix missing Optifine shader wind effects
+minor cleanup
2021-05-14 14:36:48 +02:00
octarine-noise
a5a5d53341 bump version 2021-05-13 21:13:34 +02:00
octarine-noise
4174301ff7 [WIP] finishing touches
+bunch of renames to bring the 2 version closer
2021-05-13 21:11:47 +02:00
octarine-noise
9899816029 [WIP] Cactus, netherrack, round logs work
+ lots more cleanup
+ Optifine x-ray fix
2021-05-13 10:40:18 +02:00
octarine-noise
dbc421c18e [WIP] fix falling leaf color 2021-05-11 16:53:08 +02:00
octarine-noise
a917d5b3db [WIP] Lilypad working
+ shader integration
2021-05-11 16:18:58 +02:00
octarine-noise
835bf45f13 [WIP] Falling leaves working
+ more cleanup
+ fix double-tinted leaves
2021-05-11 15:08:28 +02:00
octarine-noise
7168caded1 [WIP] algae, reeds, mycelium, coral working
+ lots of cleanup, reorganizing
2021-05-07 19:08:00 +02:00
octarine-noise
f44d2a7a50 [WIP] major rewrite, grass and leaves working already 2021-05-06 22:40:32 +02:00
octarine-noise
09ccb83e8b [WIP] start 1.15 port
reorganize packages to match Fabric version
use util classes from Fabric version
2021-05-01 13:52:21 +02:00
octarine-noise
49d4f8aa31 remove some Fabric aliases 2021-04-30 12:28:34 +02:00
octarine-noise
9566ae8341 fix round log x-ray bug 2021-04-29 11:49:31 +02:00
octarine-noise
dac7fa0b42 fix Optifine Shaders integration
use the low-level call on SVertexBuilder to set apparent block ID for the current draw
2021-04-26 17:07:31 +02:00
octarine-noise
c668713051 fix IModelData being swallowed by renderer 2021-04-26 13:39:09 +02:00
octarine-noise
8a82f3772f fix version information in legacy metadata 2021-04-26 13:38:38 +02:00
octarine-noise
3b728cffcd update Forge version 2021-04-26 13:38:12 +02:00
octarine-noise
aa91aed58e fix Mixin annotation processor requirements 2020-01-17 17:34:16 +01:00
octarine-noise
802862f151 [WIP] more fixes and final touch-up 2020-01-17 17:33:32 +01:00
122 changed files with 3490 additions and 3243 deletions

1
.gitignore vendored
View File

@@ -7,3 +7,4 @@ run/
build/ build/
classes/ classes/
temp/ temp/
logs

View File

@@ -1,54 +1,41 @@
import net.fabricmc.loom.task.RemapJarTask
import org.ajoberstar.grgit.Grgit
plugins { plugins {
id("fabric-loom").version("0.6-SNAPSHOT") kotlin("jvm").version("1.4.20")
kotlin("jvm").version("1.4.31") id("net.minecraftforge.gradle").version("4.1.12")
id("org.ajoberstar.grgit").version("3.1.1") id("org.spongepowered.mixin").version("0.7-SNAPSHOT")
} }
apply(plugin = "org.ajoberstar.grgit")
val gitHash = (project.ext.get("grgit") as Grgit).head().abbreviatedId
val semVer = "${project.version}+$gitHash"
val jarName = "BetterFoliage-$semVer-Fabric-${properties["mcVersion"]}"
repositories { repositories {
maven("https://maven.fabricmc.net/") maven("https://files.minecraftforge.net/maven")
maven("https://minecraft.curseforge.com/api/maven") maven("https://repo.spongepowered.org/maven")
maven("https://maven.modmuss50.me/") maven("https://www.cursemaven.com")
maven("https://maven.shedaniel.me/") maven("https://thedarkcolour.github.io/KotlinForForge/")
maven("https://grondag-repo.appspot.com").credentials { username = "guest"; password = "" }
maven("https://jitpack.io")
} }
dependencies { dependencies {
"minecraft"("com.mojang:minecraft:${properties["mcVersion"]}") "minecraft"("net.minecraftforge:forge:${properties["mcVersion"]}-${properties["forgeVersion"]}")
"mappings"("net.fabricmc:yarn:${properties["yarnMappings"]}:v2") "implementation"("thedarkcolour:kotlinforforge:1.7.0")
"api"(fg.deobf("curse.maven:clothconfig-348521:3311352"))
// basic Fabric stuff
"modImplementation"("net.fabricmc:fabric-loader:${properties["loaderVersion"]}")
"modImplementation"("net.fabricmc.fabric-api:fabric-api:${properties["fabricVersion"]}")
"modImplementation"("net.fabricmc:fabric-language-kotlin:${properties["fabricKotlinVersion"]}")
// configuration handling
"modImplementation"("io.github.prospector:modmenu:${properties["modMenuVersion"]}")
listOf("modImplementation", "include").forEach { configuration ->
configuration("me.shedaniel.cloth:cloth-config-fabric:${properties["clothConfigVersion"]}")
configuration("me.zeroeightsix:fiber:${properties["fiberVersion"]}")
}
// Canvas Renderer
// "modImplementation"("grondag:canvas:0.7.+")
// Optifabric
// "modImplementation"("com.github.modmuss50:OptiFabric:1.0.0")
"implementation"("org.zeroturnaround:zt-zip:1.13")
} }
configurations["annotationProcessor"].extendsFrom(configurations["implementation"])
sourceSets { sourceSets {
get("main").ext["refMap"] = "betterfoliage.refmap.json" get("main").ext["refMap"] = "betterfoliage.refmap.json"
} }
minecraft {
mappings(properties["mappingsChannel"] as String, properties["mappingsVersion"] as String)
accessTransformer(file("src/main/resources/META-INF/accesstransformer.cfg"))
runs.create("client") {
workingDirectory(file("run"))
properties["forge.logging.markers"] = "CORE"
properties["forge.logging.console.level"] = "debug"
mods.create("betterfoliage") {
source(sourceSets["main"])
}
}
}
java { java {
sourceCompatibility = JavaVersion.VERSION_1_8 sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8 targetCompatibility = JavaVersion.VERSION_1_8
@@ -61,10 +48,13 @@ kotlin {
} }
} }
tasks.getByName<ProcessResources>("processResources") { tasks.getByName<Jar>("jar") {
filesMatching("fabric.mod.json") { expand(mutableMapOf("version" to semVer)) } archiveName = "BetterFoliage-${project.version}-Forge-${properties["mcVersion"]}.jar"
} manifest {
from(file("src/main/resources/META-INF/MANIFEST.MF"))
tasks.getByName<RemapJarTask>("remapJar") { attributes["Implementation-Version"] = project.version
archiveName = "$jarName.jar" }
exclude("net")
filesMatching("META-INF/mods.toml") { expand(project.properties) }
filesMatching("mcmod.info") { expand(project.properties) }
} }

View File

@@ -2,19 +2,13 @@ org.gradle.jvmargs=-Xmx2G
org.gradle.daemon=false org.gradle.daemon=false
group = com.github.octarine-noise group = com.github.octarine-noise
name = betterfoliage
jarName = BetterFoliage-Forge jarName = BetterFoliage-Forge
version = 2.6.5 version = 2.6.5
mcVersion = 1.16.5 mcVersion = 1.16.5
yarnMappings=1.16.5+build.6 forgeVersion = 36.1.17
loaderVersion=0.11.3 mappingsChannel = official
fabricVersion=0.32.5+1.16 mappingsVersion = 1.16.5
kotlinVersion=1.3.60 #kottleVersion = 1.4.0
fabricKotlinVersion=1.5.0+kotlin.1.4.31
clothConfigVersion=4.11.24
modMenuVersion=1.16.9
fiberVersion=0.8.0-2

Binary file not shown.

View File

@@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-5.6-all.zip distributionUrl=https\://services.gradle.org/distributions/gradle-6.8.1-bin.zip
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists

2
gradlew vendored
View File

@@ -82,6 +82,7 @@ esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM. # Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
@@ -129,6 +130,7 @@ fi
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"` APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=`cygpath --unix "$JAVACMD"` JAVACMD=`cygpath --unix "$JAVACMD"`
# We build the pattern for arguments to be converted via cygpath # We build the pattern for arguments to be converted via cygpath

25
gradlew.bat vendored
View File

@@ -29,6 +29,9 @@ if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0 set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME% 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. @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" set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
@@ -37,7 +40,7 @@ if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1 %JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto init if "%ERRORLEVEL%" == "0" goto execute
echo. echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
@@ -51,7 +54,7 @@ goto fail
set JAVA_HOME=%JAVA_HOME:"=% set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto init if exist "%JAVA_EXE%" goto execute
echo. echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
@@ -61,28 +64,14 @@ echo location of your Java installation.
goto fail goto fail
:init
@rem Get command-line arguments, handling Windows variants
if not "%OS%" == "Windows_NT" goto win9xME_args
:win9xME_args
@rem Slurp the command line arguments.
set CMD_LINE_ARGS=
set _SKIP=2
:win9xME_args_slurp
if "x%~1" == "x" goto execute
set CMD_LINE_ARGS=%*
:execute :execute
@rem Setup the command line @rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle @rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
:end :end
@rem End local scope for the variables with windows NT shell @rem End local scope for the variables with windows NT shell

View File

@@ -1,8 +1,13 @@
pluginManagement { pluginManagement {
repositories { repositories {
jcenter() maven("https://files.minecraftforge.net/maven")
maven("https://maven.fabricmc.net/") maven("https://repo.spongepowered.org/maven")
gradlePluginPortal() gradlePluginPortal()
} }
resolutionStrategy {
eachPlugin {
if (requested.id.let { it.namespace == "net.minecraftforge" && it.name == "gradle"} ) useModule("net.minecraftforge.gradle:ForgeGradle:${requested.version}")
if (requested.id.let { it.namespace == "org.spongepowered" && it.name == "mixin"} ) useModule("org.spongepowered:mixingradle:${requested.version}")
}
}
} }
rootProject.name = "betterfoliage"

View File

@@ -1,49 +0,0 @@
package mods.betterfoliage;
import net.fabricmc.loader.api.FabricLoader;
import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.objectweb.asm.tree.ClassNode;
import org.spongepowered.asm.mixin.extensibility.IMixinConfigPlugin;
import org.spongepowered.asm.mixin.extensibility.IMixinInfo;
import java.util.List;
import java.util.Set;
public class MixinConfigPlugin implements IMixinConfigPlugin {
Logger logger = LogManager.getLogger(this);
Boolean hasOptifine = null;
@Override
public boolean shouldApplyMixin(String targetClassName, String mixinClassName) {
if (hasOptifine == null) {
hasOptifine = FabricLoader.getInstance().isModLoaded("optifabric");
if (hasOptifine) logger.log(Level.INFO, "[BetterFoliage] Optifabric detected, applying Optifine mixins");
else logger.log(Level.INFO, "[BetterFoliage] Optifabric not detected, applying Vanilla mixins");
}
if (mixinClassName.endsWith("Vanilla") && hasOptifine) return false;
if (mixinClassName.endsWith("Optifine") && !hasOptifine) return false;
return true;
}
@Override
public void onLoad(String mixinPackage) { }
@Override
public String getRefMapperConfig() { return null; }
@Override
public void acceptTargets(Set<String> myTargets, Set<String> otherTargets) { }
@Override
public List<String> getMixins() { return null; }
@Override
public void preApply(String targetClassName, ClassNode targetClass, String mixinClassName, IMixinInfo mixinInfo) { }
@Override
public void postApply(String targetClassName, ClassNode targetClass, String mixinClassName, IMixinInfo mixinInfo) { }
}

View File

@@ -0,0 +1,18 @@
package mods.betterfoliage;
import org.spongepowered.asm.mixin.Mixins;
import org.spongepowered.asm.mixin.connect.IMixinConnector;
public class MixinConnector implements IMixinConnector {
@Override
public void connect() {
Mixins.addConfiguration("betterfoliage.common.mixins.json");
try {
Class.forName("optifine.OptiFineTransformationService");
Mixins.addConfiguration("betterfoliage.optifine.mixins.json");
} catch (ClassNotFoundException e) {
Mixins.addConfiguration("betterfoliage.vanilla.mixins.json");
}
}
}

View File

@@ -3,42 +3,31 @@ package mods.betterfoliage.mixin;
import mods.betterfoliage.Hooks; import mods.betterfoliage.Hooks;
import net.minecraft.block.Block; import net.minecraft.block.Block;
import net.minecraft.block.BlockState; import net.minecraft.block.BlockState;
import net.minecraft.util.Direction;
import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Direction; import net.minecraft.util.math.shapes.VoxelShape;
import net.minecraft.util.shape.VoxelShape; import net.minecraft.world.IBlockReader;
import net.minecraft.world.BlockView;
import net.minecraft.world.World;
import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.Redirect; import org.spongepowered.asm.mixin.injection.Redirect;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
import java.util.Random;
/**
* Mixin overriding the {@link VoxelShape} used for the neighbor block in {@link Block}.shouldSideBeRendered().
*
* This way log blocks can be made to not block rendering, without altering any {@link Block} or
* {@link BlockState} properties with potential gameplay ramifications.
*/
@Mixin(Block.class) @Mixin(Block.class)
public class MixinBlock { public class MixinBlock {
private static final String shouldSideBeRendered = "Lnet/minecraft/block/Block;shouldDrawSide(Lnet/minecraft/block/BlockState;Lnet/minecraft/world/BlockView;Lnet/minecraft/util/math/BlockPos;Lnet/minecraft/util/math/Direction;)Z"; private static final String shouldSideBeRendered = "Lnet/minecraft/block/Block;shouldRenderFace(Lnet/minecraft/block/BlockState;Lnet/minecraft/world/IBlockReader;Lnet/minecraft/util/math/BlockPos;Lnet/minecraft/util/Direction;)Z";
private static final String getVoxelShape = "Lnet/minecraft/block/BlockState;getCullingFace(Lnet/minecraft/world/BlockView;Lnet/minecraft/util/math/BlockPos;Lnet/minecraft/util/math/Direction;)Lnet/minecraft/util/shape/VoxelShape;"; private static final String getVoxelShape = "Lnet/minecraft/block/BlockState;func_215702_a(Lnet/minecraft/world/IBlockReader;Lnet/minecraft/util/math/BlockPos;Lnet/minecraft/util/Direction;)Lnet/minecraft/util/math/shapes/VoxelShape;";
private static final String randomDisplayTick = "randomDisplayTick(Lnet/minecraft/block/BlockState;Lnet/minecraft/world/World;Lnet/minecraft/util/math/BlockPos;Ljava/util/Random;)V"; private static final String getFaceOcclusionShape = "Lnet/minecraft/block/BlockState;getFaceOcclusionShape(Lnet/minecraft/world/IBlockReader;Lnet/minecraft/util/math/BlockPos;Lnet/minecraft/util/Direction;)Lnet/minecraft/util/math/shapes/VoxelShape;";
private static final String isOpaqueCube = "Lnet/minecraft/block/Block;isOpaqueCube(Lnet/minecraft/block/BlockState;Lnet/minecraft/world/IBlockReader;Lnet/minecraft/util/math/BlockPos;)Z";
/** @Redirect(method = shouldSideBeRendered, at = @At(value = "INVOKE", target = getFaceOcclusionShape, ordinal = 1))
* Override the {@link VoxelShape} used for the neighbor block in {@link Block}.shouldSideBeRendered(). private static VoxelShape getVoxelShapeOverride(BlockState state, IBlockReader reader, BlockPos pos, Direction dir) {
*
* This way log blocks can be made to not block rendering, without altering any {@link Block} or
* {@link BlockState} properties with potential gameplay ramifications.
*/
@Redirect(method = shouldSideBeRendered, at = @At(value = "INVOKE", target = getVoxelShape, ordinal = 1))
private static VoxelShape getVoxelShapeOverride(BlockState state, BlockView reader, BlockPos pos, Direction dir) {
return Hooks.getVoxelShapeOverride(state, reader, pos, dir); return Hooks.getVoxelShapeOverride(state, reader, pos, dir);
} }
/**
* Inject a callback to call for every random display tick. Used for adding custom particle effects to blocks.
*/
@Inject(method = randomDisplayTick, at = @At("HEAD"))
void onRandomDisplayTick(BlockState state, World world, BlockPos pos, Random rnd, CallbackInfo ci) {
// Hooks.onRandomDisplayTick(state.getBlock(), state, world, pos, rnd);
}
} }

View File

@@ -0,0 +1,41 @@
package mods.betterfoliage.mixin;
import com.mojang.blaze3d.matrix.MatrixStack;
import com.mojang.blaze3d.vertex.IVertexBuilder;
import mods.betterfoliage.model.SpecialRenderModel;
import mods.betterfoliage.render.pipeline.RenderCtxVanilla;
import net.minecraft.block.BlockState;
import net.minecraft.client.renderer.BlockModelRenderer;
import net.minecraft.client.renderer.model.IBakedModel;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.IBlockDisplayReader;
import net.minecraftforge.client.model.data.IModelData;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Redirect;
import java.util.Random;
@Mixin(BlockModelRenderer.class)
public class MixinBlockModelRenderer {
private static final String renderModel = "Lnet/minecraft/client/renderer/BlockModelRenderer;renderModel(Lnet/minecraft/world/IBlockDisplayReader;Lnet/minecraft/client/renderer/model/IBakedModel;Lnet/minecraft/block/BlockState;Lnet/minecraft/util/math/BlockPos;Lcom/mojang/blaze3d/matrix/MatrixStack;Lcom/mojang/blaze3d/vertex/IVertexBuilder;ZLjava/util/Random;JILnet/minecraftforge/client/model/data/IModelData;)Z";
private static final String renderModelFlat = "Lnet/minecraft/client/renderer/BlockModelRenderer;renderModelFlat(Lnet/minecraft/world/IBlockDisplayReader;Lnet/minecraft/client/renderer/model/IBakedModel;Lnet/minecraft/block/BlockState;Lnet/minecraft/util/math/BlockPos;Lcom/mojang/blaze3d/matrix/MatrixStack;Lcom/mojang/blaze3d/vertex/IVertexBuilder;ZLjava/util/Random;JILnet/minecraftforge/client/model/data/IModelData;)Z";
private static final String renderModelSmooth = "Lnet/minecraft/client/renderer/BlockModelRenderer;renderModelSmooth(Lnet/minecraft/world/IBlockDisplayReader;Lnet/minecraft/client/renderer/model/IBakedModel;Lnet/minecraft/block/BlockState;Lnet/minecraft/util/math/BlockPos;Lcom/mojang/blaze3d/matrix/MatrixStack;Lcom/mojang/blaze3d/vertex/IVertexBuilder;ZLjava/util/Random;JILnet/minecraftforge/client/model/data/IModelData;)Z";
@Redirect(method = renderModel, at = @At(value = "INVOKE", target = renderModelSmooth), remap = false)
public boolean onRenderModelSmooth(BlockModelRenderer renderer, IBlockDisplayReader world, IBakedModel model, BlockState state, BlockPos pos, MatrixStack matrixStack, IVertexBuilder buffer, boolean checkSides, Random random, long rand, int combinedOverlay, IModelData modelData) {
if (model instanceof SpecialRenderModel)
return RenderCtxVanilla.render(renderer, world, (SpecialRenderModel) model, state, pos, matrixStack, buffer, checkSides, random, rand, combinedOverlay, modelData, true);
else
return renderer.renderModelSmooth(world, model, state, pos, matrixStack, buffer, checkSides, random, rand, combinedOverlay, modelData);
}
@Redirect(method = renderModel, at = @At(value = "INVOKE", target = renderModelFlat), remap = false)
public boolean onRenderModelFlat(BlockModelRenderer renderer, IBlockDisplayReader world, IBakedModel model, BlockState state, BlockPos pos, MatrixStack matrixStack, IVertexBuilder buffer, boolean checkSides, Random random, long rand, int combinedOverlay, IModelData modelData) {
if (model instanceof SpecialRenderModel)
return RenderCtxVanilla.render(renderer, world, (SpecialRenderModel) model, state, pos, matrixStack, buffer, checkSides, random, rand, combinedOverlay, modelData, false);
else
return renderer.renderModelFlat(world, model, state, pos, matrixStack, buffer, checkSides, random, rand, combinedOverlay, modelData);
}
}

View File

@@ -1,17 +0,0 @@
package mods.betterfoliage.mixin;
import mods.betterfoliage.BlockModelsReloadCallback;
import net.minecraft.client.render.block.BlockModels;
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(BlockModels.class)
public class MixinBlockModels {
@Inject(method = "reload()V", at = @At("RETURN"))
void onReload(CallbackInfo ci) {
BlockModelsReloadCallback.EVENT.invoker().reloadBlockModels((BlockModels) (Object) this);
}
}

View File

@@ -5,9 +5,11 @@ import net.minecraft.block.AbstractBlock;
import net.minecraft.block.Block; import net.minecraft.block.Block;
import net.minecraft.block.BlockState; import net.minecraft.block.BlockState;
import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.BlockPos;
import net.minecraft.world.BlockView; import net.minecraft.world.IBlockReader;
import org.spongepowered.asm.mixin.Debug;
import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Coerce;
import org.spongepowered.asm.mixin.injection.Redirect; import org.spongepowered.asm.mixin.injection.Redirect;
/** /**
@@ -18,12 +20,12 @@ import org.spongepowered.asm.mixin.injection.Redirect;
@Mixin(AbstractBlock.AbstractBlockState.class) @Mixin(AbstractBlock.AbstractBlockState.class)
@SuppressWarnings({"deprecation"}) @SuppressWarnings({"deprecation"})
public class MixinBlockState { public class MixinBlockState {
private static final String callFrom = "Lnet/minecraft/block/AbstractBlock$AbstractBlockState;getAmbientOcclusionLightLevel(Lnet/minecraft/world/BlockView;Lnet/minecraft/util/math/BlockPos;)F"; private static final String callFrom = "Lnet/minecraft/block/AbstractBlock$AbstractBlockState;getShadeBrightness(Lnet/minecraft/world/IBlockReader;Lnet/minecraft/util/math/BlockPos;)F";
// why is the INVOKEVIRTUAL target class Block in the bytecode, not AbstractBlock? // why is the INVOKEVIRTUAL target class Block in the bytecode, not AbstractBlock?
private static final String callTo = "Lnet/minecraft/block/Block;getAmbientOcclusionLightLevel(Lnet/minecraft/block/BlockState;Lnet/minecraft/world/BlockView;Lnet/minecraft/util/math/BlockPos;)F"; private static final String callTo = "Lnet/minecraft/block/Block;getShadeBrightness(Lnet/minecraft/block/BlockState;Lnet/minecraft/world/IBlockReader;Lnet/minecraft/util/math/BlockPos;)F";
@Redirect(method = callFrom, at = @At(value = "INVOKE", target = callTo)) @Redirect(method = callFrom, at = @At(value = "INVOKE", target = callTo))
float getAmbientOcclusionValue(Block block, BlockState state, BlockView reader, BlockPos pos) { float getAmbientOcclusionValue(Block block, BlockState state, IBlockReader reader, BlockPos pos) {
return Hooks.getAmbientOcclusionLightValueOverride(block.getAmbientOcclusionLightLevel(state, reader, pos), state); return Hooks.getAmbientOcclusionLightValueOverride(block.getShadeBrightness(state, reader, pos), state);
} }
} }

View File

@@ -1,24 +0,0 @@
package mods.betterfoliage.mixin;
import mods.betterfoliage.ClientChunkLoadCallback;
import net.minecraft.client.world.ClientChunkManager;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.network.PacketByteBuf;
import net.minecraft.world.biome.source.BiomeArray;
import net.minecraft.world.chunk.WorldChunk;
import org.jetbrains.annotations.Nullable;
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.CallbackInfoReturnable;
@Mixin(ClientChunkManager.class)
public class MixinClientChunkManager {
private static final String onLoadChunkFromPacket = "Lnet/minecraft/client/world/ClientChunkManager;loadChunkFromPacket(IILnet/minecraft/world/biome/source/BiomeArray;Lnet/minecraft/network/PacketByteBuf;Lnet/minecraft/nbt/CompoundTag;IZ)Lnet/minecraft/world/chunk/WorldChunk;";
@Inject(method = onLoadChunkFromPacket, at = @At(value = "RETURN", ordinal = 2))
void onLoadChunkFromPacket(int x, int z, @Nullable BiomeArray biomes, PacketByteBuf buf, CompoundTag tag, int verticalStripBitmask, boolean complete, CallbackInfoReturnable<WorldChunk> ci) {
ClientChunkLoadCallback.EVENT.invoker().loadChunk(ci.getReturnValue());
}
}

View File

@@ -1,19 +0,0 @@
package mods.betterfoliage.mixin;
import mods.betterfoliage.ClientChunkLoadCallback;
import net.minecraft.world.chunk.WorldChunk;
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.CallbackInfoReturnable;
@Mixin(targets = {"net.minecraft.client.world.ClientChunkManager$ClientChunkMap"})
public class MixinClientChunkManagerChunkMap {
private static final String onCompareAndSet = "Lnet/minecraft/client/world/ClientChunkManager$ClientChunkMap;compareAndSet(ILnet/minecraft/world/chunk/WorldChunk;Lnet/minecraft/world/chunk/WorldChunk;)Lnet/minecraft/world/chunk/WorldChunk;";
@Inject(method = onCompareAndSet, at = @At("HEAD"))
void onSetAndCompare(int i, WorldChunk oldChunk, WorldChunk newChunk, CallbackInfoReturnable<WorldChunk> ci) {
ClientChunkLoadCallback.EVENT.invoker().unloadChunk(oldChunk);
}
}

View File

@@ -1,53 +1,43 @@
package mods.betterfoliage.mixin; package mods.betterfoliage.mixin;
import mods.betterfoliage.ClientWorldLoadCallback;
import mods.betterfoliage.Hooks; import mods.betterfoliage.Hooks;
import net.minecraft.block.BlockState; import net.minecraft.block.BlockState;
import net.minecraft.client.network.ClientPlayNetworkHandler; import net.minecraft.client.renderer.WorldRenderer;
import net.minecraft.client.render.WorldRenderer;
import net.minecraft.client.world.ClientWorld; import net.minecraft.client.world.ClientWorld;
import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.BlockPos;
import net.minecraft.util.profiler.Profiler; import net.minecraft.world.IBlockReader;
import net.minecraft.util.registry.RegistryKey;
import net.minecraft.world.World;
import net.minecraft.world.dimension.DimensionType;
import net.minecraft.world.level.LevelInfo;
import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject; 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.CallbackInfo;
import java.util.Random; import java.util.Random;
import java.util.function.Supplier;
@Mixin(ClientWorld.class) @Mixin(ClientWorld.class)
public class MixinClientWorld { public class MixinClientWorld {
private static final String ctor = "Lnet/minecraft/client/world/ClientWorld;<init>(Lnet/minecraft/client/network/ClientPlayNetworkHandler;Lnet/minecraft/client/world/ClientWorld$Properties;Lnet/minecraft/util/registry/RegistryKey;Lnet/minecraft/world/dimension/DimensionType;ILjava/util/function/Supplier;Lnet/minecraft/client/render/WorldRenderer;ZJ)V"; private static final String worldAnimateTick = "Lnet/minecraft/client/world/ClientWorld;doAnimateTick(IIIILjava/util/Random;ZLnet/minecraft/util/math/BlockPos$Mutable;)V";
private static final String scheduleBlockRerenderIfNeeded = "Lnet/minecraft/client/world/ClientWorld;scheduleBlockRerenderIfNeeded(Lnet/minecraft/util/math/BlockPos;Lnet/minecraft/block/BlockState;Lnet/minecraft/block/BlockState;)V"; private static final String blockAnimateTick = "Lnet/minecraft/block/Block;animateTick(Lnet/minecraft/block/BlockState;Lnet/minecraft/world/World;Lnet/minecraft/util/math/BlockPos;Ljava/util/Random;)V";
private static final String rendererNotify = "Lnet/minecraft/client/render/WorldRenderer;method_21596(Lnet/minecraft/util/math/BlockPos;Lnet/minecraft/block/BlockState;Lnet/minecraft/block/BlockState;)V";
private static final String worldDisplayTick = "randomBlockDisplayTick(IIIILjava/util/Random;ZLnet/minecraft/util/math/BlockPos$Mutable;)V"; private static final String worldNotify = "Lnet/minecraft/client/world/ClientWorld;sendBlockUpdated(Lnet/minecraft/util/math/BlockPos;Lnet/minecraft/block/BlockState;Lnet/minecraft/block/BlockState;I)V";
private static final String blockDisplayTick = "Lnet/minecraft/block/Block;randomDisplayTick(Lnet/minecraft/block/BlockState;Lnet/minecraft/world/World;Lnet/minecraft/util/math/BlockPos;Ljava/util/Random;)V"; private static final String rendererNotify = "Lnet/minecraft/client/renderer/WorldRenderer;blockChanged(Lnet/minecraft/world/IBlockReader;Lnet/minecraft/util/math/BlockPos;Lnet/minecraft/block/BlockState;Lnet/minecraft/block/BlockState;I)V";
/**
* Inject a callback to call for every random display tick. Used for adding custom particle effects to blocks.
*/
@Inject(method = worldAnimateTick, at = @At(value = "INVOKE", target = blockAnimateTick))
void onAnimateTick(int x, int y, int z, int range, Random random, boolean doBarrier, BlockPos.Mutable pos, CallbackInfo ci) {
Hooks.onRandomDisplayTick((ClientWorld) (Object) this, pos, random);
}
/** /**
* Inject callback to get notified of client-side blockstate changes. * Inject callback to get notified of client-side blockstate changes.
* Used to invalidate caches in the {@link mods.betterfoliage.chunk.ChunkOverlayManager} * Used to invalidate caches in the {@link mods.betterfoliage.chunk.ChunkOverlayManager}
*/ */
@Inject(method = scheduleBlockRerenderIfNeeded, at = @At(value = "HEAD")) @Redirect(method = worldNotify, at = @At(value = "INVOKE", target = rendererNotify))
void onClientBlockChanged(BlockPos pos, BlockState oldState, BlockState newState, CallbackInfo ci) { void onClientBlockChanged(WorldRenderer renderer, IBlockReader world, BlockPos pos, BlockState oldState, BlockState newState, int flags) {
Hooks.onClientBlockChanged((ClientWorld) (Object) this, pos, oldState, newState); Hooks.onClientBlockChanged((ClientWorld) world, pos, oldState, newState, flags);
} renderer.blockChanged(world, pos, oldState, newState, flags);
@Inject(method = ctor, at = @At("RETURN"))
void onClientWorldCreated(ClientPlayNetworkHandler networkHandler, ClientWorld.Properties properties, RegistryKey<World> registryRef, DimensionType dimensionType, int loadDistance, Supplier<Profiler> profiler, WorldRenderer worldRenderer, boolean debugWorld, long seed, CallbackInfo ci) {
ClientWorldLoadCallback.EVENT.invoker().loadWorld((ClientWorld) (Object) this);
}
/**
* Inject a callback to call for every random display tick. Used for adding custom particle effects to blocks.
*/
@Inject(method = worldDisplayTick, at = @At(value = "INVOKE", target = blockDisplayTick))
void onRandomDisplayTick(int xCenter, int yCenter, int zCenter, int radius, Random random, boolean spawnBarrierParticles, BlockPos.Mutable mutable, CallbackInfo ci) {
Hooks.onRandomDisplayTick((ClientWorld) (Object) this, mutable);
} }
} }

View File

@@ -0,0 +1,44 @@
package mods.betterfoliage.mixin;
import com.mojang.blaze3d.matrix.MatrixStack;
import mods.betterfoliage.model.SpecialRenderModel;
import mods.betterfoliage.render.pipeline.RenderCtxForge;
import net.minecraft.block.BlockState;
import net.minecraft.client.renderer.model.IBakedModel;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.IBlockDisplayReader;
import net.minecraftforge.client.model.data.IModelData;
import net.minecraftforge.client.model.pipeline.ForgeBlockModelRenderer;
import net.minecraftforge.client.model.pipeline.VertexLighterFlat;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Redirect;
import java.util.Random;
@Mixin(ForgeBlockModelRenderer.class)
public class MixinForgeBlockModelRenderer {
private static final String renderModelFlat = "Lnet/minecraftforge/client/model/pipeline/ForgeBlockModelRenderer;renderModelFlat(Lnet/minecraft/world/IBlockDisplayReader;Lnet/minecraft/client/renderer/model/IBakedModel;Lnet/minecraft/block/BlockState;Lnet/minecraft/util/math/BlockPos;Lcom/mojang/blaze3d/matrix/MatrixStack;Lcom/mojang/blaze3d/vertex/IVertexBuilder;ZLjava/util/Random;JILnet/minecraftforge/client/model/data/IModelData;)Z";
private static final String renderModelSmooth = "Lnet/minecraftforge/client/model/pipeline/ForgeBlockModelRenderer;renderModelSmooth(Lnet/minecraft/world/IBlockDisplayReader;Lnet/minecraft/client/renderer/model/IBakedModel;Lnet/minecraft/block/BlockState;Lnet/minecraft/util/math/BlockPos;Lcom/mojang/blaze3d/matrix/MatrixStack;Lcom/mojang/blaze3d/vertex/IVertexBuilder;ZLjava/util/Random;JILnet/minecraftforge/client/model/data/IModelData;)Z";
private static final String render = "Lnet/minecraftforge/client/model/pipeline/ForgeBlockModelRenderer;render(Lnet/minecraftforge/client/model/pipeline/VertexLighterFlat;Lnet/minecraft/world/IBlockDisplayReader;Lnet/minecraft/client/renderer/model/IBakedModel;Lnet/minecraft/block/BlockState;Lnet/minecraft/util/math/BlockPos;Lcom/mojang/blaze3d/matrix/MatrixStack;ZLjava/util/Random;JLnet/minecraftforge/client/model/data/IModelData;)Z";
@Redirect(method = {renderModelFlat, renderModelSmooth}, at = @At(value = "INVOKE", target = render), remap = false)
public boolean render(
VertexLighterFlat lighter,
IBlockDisplayReader world,
IBakedModel model,
BlockState state,
BlockPos pos,
MatrixStack matrixStack,
boolean checkSides,
Random rand,
long seed,
IModelData modelData
) {
if (model instanceof SpecialRenderModel)
return RenderCtxForge.render(lighter, world, (SpecialRenderModel) model, state, pos, matrixStack, checkSides, rand, seed, modelData);
else
return ForgeBlockModelRenderer.render(lighter, world, model, state, pos, matrixStack, checkSides, rand, seed, modelData);
}
}

View File

@@ -0,0 +1,65 @@
package mods.betterfoliage.mixin;
import mods.betterfoliage.render.lighting.ForgeVertexLighter;
import mods.betterfoliage.render.lighting.ForgeVertexLighterAccess;
import net.minecraftforge.client.model.pipeline.VertexLighterFlat;
import org.jetbrains.annotations.NotNull;
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(VertexLighterFlat.class)
abstract public class MixinForgeCustomVertexLighting implements ForgeVertexLighter, ForgeVertexLighterAccess {
private static final String processQuad = "Lnet/minecraftforge/client/model/pipeline/VertexLighterFlat;processQuad()V";
private static final String updateLightmap = "Lnet/minecraftforge/client/model/pipeline/VertexLighterFlat;updateLightmap([F[FFFF)V";
private static final String updateColor = "Lnet/minecraftforge/client/model/pipeline/VertexLighterFlat;updateColor([F[FFFFFI)V";
private static final String resetBlockInfo = "Lnet/minecraftforge/client/model/pipeline/VertexLighterFlat;resetBlockInfo()V";
@NotNull
public ForgeVertexLighter vertexLighter = this;
@NotNull
public ForgeVertexLighter getVertexLighter() {
return vertexLighter;
}
public void setVertexLighter(@NotNull ForgeVertexLighter vertexLighter) {
this.vertexLighter = vertexLighter;
}
@Shadow
protected abstract void updateLightmap(float[] normal, float[] lightmap, float x, float y, float z);
@Shadow
protected abstract void updateColor(float[] normal, float[] color, float x, float y, float z, float tint, int multiplier);
@Override
public void updateVertexLightmap(@NotNull float[] normal, @NotNull float[] lightmap, float x, float y, float z) {
updateLightmap(normal, lightmap, x, y, z);
}
@Override
public void updateVertexColor(@NotNull float[] normal, @NotNull float[] color, float x, float y, float z, float tint, int multiplier) {
updateColor(normal, color, x, y, z, tint, multiplier);
}
@Redirect(method = processQuad, at = @At(value = "INVOKE", target = updateColor), remap = false)
void onUpdateColor(VertexLighterFlat self, float[] normal, float[] color, float x, float y, float z, float tint, int multiplier) {
vertexLighter.updateVertexColor(normal, color, x, y, z, tint, multiplier);
}
@Redirect(method = processQuad, at = @At(value = "INVOKE", target = updateLightmap), remap = false)
void onUpdateLightmap(VertexLighterFlat self, float[] normal, float[] lightmap, float x, float y, float z) {
vertexLighter.updateVertexLightmap(normal, lightmap, x, y, z);
}
@Inject(method = resetBlockInfo, at = @At("RETURN"), remap = false)
void onReset(CallbackInfo ci) {
vertexLighter = this;
}
}

View File

@@ -0,0 +1,43 @@
package mods.betterfoliage.mixin;
import mods.betterfoliage.BetterFoliageMod;
import mods.betterfoliage.resource.discovery.BakeWrapperManager;
import mods.betterfoliage.resource.discovery.ModelDefinitionsLoadedEvent;
import net.minecraft.client.renderer.model.*;
import net.minecraft.client.renderer.texture.TextureAtlasSprite;
import net.minecraft.profiler.IProfiler;
import net.minecraft.util.ResourceLocation;
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.Redirect;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import java.util.function.Function;
@Mixin(ModelBakery.class)
abstract public class MixinModelBakery {
private static final String processLoading = "Lnet/minecraft/client/renderer/model/ModelBakery;processLoading(Lnet/minecraft/profiler/IProfiler;I)V";
private static final String stitch = "Lnet/minecraft/client/renderer/texture/AtlasTexture;stitch(Lnet/minecraft/resources/IResourceManager;Ljava/util/stream/Stream;Lnet/minecraft/profiler/IProfiler;I)Lnet/minecraft/client/renderer/texture/AtlasTexture$SheetData;";
private static final String profilerSection = "Lnet/minecraft/profiler/IProfiler;popPush(Ljava/lang/String;)V";
private static final String getBakedModel = "Lnet/minecraft/client/renderer/model/ModelBakery;getBakedModel(Lnet/minecraft/util/ResourceLocation;Lnet/minecraft/client/renderer/model/IModelTransform;Ljava/util/function/Function;)Lnet/minecraft/client/renderer/model/IBakedModel;";
private static final String bakeModel = "Lnet/minecraft/client/renderer/model/IUnbakedModel;bake(Lnet/minecraft/client/renderer/model/ModelBakery;Ljava/util/function/Function;Lnet/minecraft/client/renderer/model/IModelTransform;Lnet/minecraft/util/ResourceLocation;)Lnet/minecraft/client/renderer/model/IBakedModel;";
@Inject(method = processLoading, at = @At(value = "INVOKE", target = profilerSection, ordinal = 4))
void onBeforeTextures(IProfiler profiler, int maxMipmapLevel, CallbackInfo ci) {
profiler.popPush("betterfoliage");
BetterFoliageMod.INSTANCE.getBus().post(new ModelDefinitionsLoadedEvent(ModelBakery.class.cast(this)));
}
@Redirect(method = getBakedModel, at = @At(value = "INVOKE", target = bakeModel))
IBakedModel onBakeModel(
IUnbakedModel unbaked,
ModelBakery bakery,
Function<RenderMaterial, TextureAtlasSprite> spriteGetter,
IModelTransform transform,
ResourceLocation locationIn
) {
return BakeWrapperManager.INSTANCE.onBake(unbaked, bakery, spriteGetter, transform, locationIn);
}
}

View File

@@ -1,37 +0,0 @@
package mods.betterfoliage.mixin;
import mods.betterfoliage.ModelLoadingCallback;
import mods.betterfoliage.resource.discovery.BakeWrapperManager;
import net.minecraft.client.render.model.BakedModel;
import net.minecraft.client.render.model.ModelBakeSettings;
import net.minecraft.client.render.model.ModelLoader;
import net.minecraft.client.render.model.UnbakedModel;
import net.minecraft.client.texture.Sprite;
import net.minecraft.client.util.ModelIdentifier;
import net.minecraft.client.util.SpriteIdentifier;
import net.minecraft.resource.ResourceManager;
import net.minecraft.util.Identifier;
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 java.util.function.Function;
@Mixin(ModelLoader.class)
public class MixinModelLoader {
@Shadow @Final private ResourceManager resourceManager;
// use the same trick fabric-api does to get around the no-mixins-in-constructors policy
@Inject(at = @At("HEAD"), method = "addModel")
private void addModelHook(ModelIdentifier id, CallbackInfo info) {
if (id.getPath().equals("trident_in_hand")) {
// last step before stitching
ModelLoadingCallback.EVENT.invoker().beginLoadModels((ModelLoader) (Object) this, resourceManager);
}
}
}

View File

@@ -1,34 +0,0 @@
package mods.betterfoliage.mixin;
import mods.betterfoliage.resource.discovery.BakeWrapperManager;
import net.minecraft.client.render.model.BakedModel;
import net.minecraft.client.render.model.ModelBakeSettings;
import net.minecraft.client.render.model.ModelLoader;
import net.minecraft.client.render.model.UnbakedModel;
import net.minecraft.client.texture.Sprite;
import net.minecraft.client.util.SpriteIdentifier;
import net.minecraft.util.Identifier;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Redirect;
import java.util.function.Function;
@Mixin(ModelLoader.class)
public class MixinModelLoaderOptifine {
private static final String loaderBake = "Lnet/minecraft/class_1088;getBakedModel(Lnet/minecraft/class_2960;Lnet/minecraft/class_3665;Ljava/util/function/Function;)Lnet/minecraft/class_1087;";
private static final String modelBake = "Lnet/minecraft/class_1100;method_4753(Lnet/minecraft/class_1088;Ljava/util/function/Function;Lnet/minecraft/class_3665;Lnet/minecraft/class_2960;)Lnet/minecraft/class_1087;";
@SuppressWarnings("UnresolvedMixinReference")
@Redirect(method = loaderBake, at = @At(value = "INVOKE", target = modelBake), remap = false)
BakedModel onBakeModel(
UnbakedModel unbaked,
ModelLoader loader,
Function<SpriteIdentifier, Sprite> textureGetter,
ModelBakeSettings rotationContainer,
Identifier modelId
) {
return BakeWrapperManager.INSTANCE.onBake(unbaked, loader, textureGetter, rotationContainer, modelId);
}
}

View File

@@ -1,34 +0,0 @@
package mods.betterfoliage.mixin;
import mods.betterfoliage.resource.discovery.BakeWrapperManager;
import net.minecraft.client.render.model.BakedModel;
import net.minecraft.client.render.model.ModelBakeSettings;
import net.minecraft.client.render.model.ModelLoader;
import net.minecraft.client.render.model.UnbakedModel;
import net.minecraft.client.texture.Sprite;
import net.minecraft.client.util.SpriteIdentifier;
import net.minecraft.util.Identifier;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Redirect;
import java.util.function.Function;
@Mixin(ModelLoader.class)
public class MixinModelLoaderVanilla {
private static final String loaderBake = "Lnet/minecraft/client/render/model/ModelLoader;bake(Lnet/minecraft/util/Identifier;Lnet/minecraft/client/render/model/ModelBakeSettings;)Lnet/minecraft/client/render/model/BakedModel;";
private static final String modelBake = "Lnet/minecraft/client/render/model/UnbakedModel;bake(Lnet/minecraft/client/render/model/ModelLoader;Ljava/util/function/Function;Lnet/minecraft/client/render/model/ModelBakeSettings;Lnet/minecraft/util/Identifier;)Lnet/minecraft/client/render/model/BakedModel;";
@Redirect(method = loaderBake, at = @At(value = "INVOKE", target = modelBake))
BakedModel onBakeModel(
UnbakedModel unbaked,
ModelLoader loader,
Function<SpriteIdentifier, Sprite> textureGetter,
ModelBakeSettings rotationContainer,
Identifier modelId
) {
return BakeWrapperManager.INSTANCE.onBake(unbaked, loader, textureGetter, rotationContainer, modelId);
}
}

View File

@@ -0,0 +1,38 @@
package mods.betterfoliage.mixin;
import mods.betterfoliage.Hooks;
import net.minecraft.block.BlockState;
import net.minecraft.util.Direction;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.shapes.VoxelShape;
import net.minecraft.world.IBlockReader;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Pseudo;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Coerce;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.Redirect;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
@Pseudo
@Mixin(targets = "net.optifine.util.BlockUtils")
public class MixinOptifineBlockUtils {
private static final String shouldSideBeRendered = "shouldSideBeRendered(Lnet/minecraft/block/BlockState;Lnet/minecraft/world/IBlockReader;Lnet/minecraft/util/math/BlockPos;Lnet/minecraft/util/Direction;Lnet/optifine/render/RenderEnv;)Z";
private static final String shouldSideBeRenderedCached = "shouldSideBeRenderedCached(Lnet/minecraft/block/BlockState;Lnet/minecraft/world/IBlockReader;Lnet/minecraft/util/math/BlockPos;Lnet/minecraft/util/Direction;Lnet/optifine/render/RenderEnv;Lnet/minecraft/block/BlockState;Lnet/minecraft/util/math/BlockPos;)Z";
private static final String getFaceOcclusionShape = "Lnet/minecraft/block/BlockState;getFaceOcclusionShape(Lnet/minecraft/world/IBlockReader;Lnet/minecraft/util/math/BlockPos;Lnet/minecraft/util/Direction;)Lnet/minecraft/util/math/shapes/VoxelShape;";
@SuppressWarnings("UnresolvedMixinReference")
@Redirect(method = shouldSideBeRenderedCached, at = @At(value = "INVOKE", target = getFaceOcclusionShape, ordinal = 1))
private static VoxelShape getVoxelShapeOverride(BlockState state, IBlockReader reader, BlockPos pos, Direction dir) {
return Hooks.getVoxelShapeOverride(state, reader, pos, dir);
}
@SuppressWarnings("UnresolvedMixinReference")
@Inject(method = shouldSideBeRendered, at = @At(value = "HEAD"), cancellable = true)
private static void shouldForceSideRender(BlockState state, IBlockReader reader, BlockPos pos, Direction face, @Coerce Object renderEnv, CallbackInfoReturnable<Boolean> cir) {
if (Hooks.shouldForceSideRenderOF(state, reader, pos, face)) {
cir.setReturnValue(true);
}
}
}

View File

@@ -1,94 +1,101 @@
package mods.betterfoliage package mods.betterfoliage
import me.zeroeightsix.fiber.JanksonSettings
import mods.betterfoliage.chunk.ChunkOverlayManager import mods.betterfoliage.chunk.ChunkOverlayManager
import mods.betterfoliage.config.BlockConfig import mods.betterfoliage.config.BlockConfig
import mods.betterfoliage.config.MainConfig import mods.betterfoliage.integration.OptifineCustomColors
import mods.betterfoliage.render.ShadersModIntegration import mods.betterfoliage.integration.ShadersModIntegration
import mods.betterfoliage.render.block.vanilla.* import mods.betterfoliage.render.block.vanilla.RoundLogOverlayLayer
import mods.betterfoliage.render.particle.LeafParticleRegistry import mods.betterfoliage.render.block.vanilla.StandardCactusDiscovery
import mods.betterfoliage.render.particle.RisingSoulParticle import mods.betterfoliage.render.block.vanilla.StandardCactusModel
import mods.betterfoliage.render.block.vanilla.StandardDirtDiscovery
import mods.betterfoliage.render.block.vanilla.StandardDirtModel
import mods.betterfoliage.render.block.vanilla.StandardGrassDiscovery
import mods.betterfoliage.render.block.vanilla.StandardGrassModel
import mods.betterfoliage.render.block.vanilla.StandardLeafDiscovery
import mods.betterfoliage.render.block.vanilla.StandardLeafModel
import mods.betterfoliage.render.block.vanilla.StandardLilypadDiscovery
import mods.betterfoliage.render.block.vanilla.StandardLilypadModel
import mods.betterfoliage.render.block.vanilla.StandardRoundLogDiscovery
import mods.betterfoliage.render.block.vanilla.StandardMyceliumDiscovery
import mods.betterfoliage.render.block.vanilla.StandardMyceliumModel
import mods.betterfoliage.render.block.vanilla.StandardNetherrackDiscovery
import mods.betterfoliage.render.block.vanilla.StandardNetherrackModel
import mods.betterfoliage.render.block.vanilla.StandardRoundLogModel
import mods.betterfoliage.render.block.vanilla.StandardSandDiscovery
import mods.betterfoliage.render.block.vanilla.StandardSandModel
import mods.betterfoliage.render.lighting.AoSideHelper
import mods.betterfoliage.render.particle.LeafWindTracker
import mods.betterfoliage.resource.discovery.BakeWrapperManager import mods.betterfoliage.resource.discovery.BakeWrapperManager
import mods.betterfoliage.resource.discovery.BlockTypeCache import mods.betterfoliage.resource.discovery.BlockTypeCache
import mods.betterfoliage.resource.generated.GeneratedBlockTexturePack import mods.betterfoliage.resource.discovery.ModelDefinitionsLoadedEvent
import net.fabricmc.api.ClientModInitializer import mods.betterfoliage.resource.generated.GeneratedTexturePack
import net.fabricmc.fabric.api.resource.ResourceManagerHelper import mods.betterfoliage.render.particle.LeafParticleRegistry
import net.fabricmc.fabric.mixin.resource.loader.ResourcePackManagerAccessor import mods.betterfoliage.render.particle.RisingSoulParticle
import net.fabricmc.loader.api.FabricLoader import mods.betterfoliage.util.resourceManager
import net.minecraft.block.BlockState import net.minecraft.block.BlockState
import net.minecraft.client.MinecraftClient import net.minecraft.client.Minecraft
import net.minecraft.resource.ResourceType import net.minecraft.resources.IReloadableResourceManager
import net.minecraft.util.Identifier import net.minecraftforge.eventbus.api.SubscribeEvent
import org.apache.logging.log4j.Level
import org.apache.logging.log4j.LogManager
import org.apache.logging.log4j.simple.SimpleLogger
import org.apache.logging.log4j.util.PropertiesUtil
import java.io.File
import java.io.PrintStream
import java.util.*
/**
object BetterFoliage : ClientModInitializer { * Object responsible for initializing (and holding a reference to) all the infrastructure of the mod
const val MOD_ID = "betterfoliage" * except for the call hooks.
*/
val detailLogStream = PrintStream(File("logs/betterfoliage.log").apply { object BetterFoliage {
parentFile.mkdirs() /** Resource pack holding generated assets */
if (!exists()) createNewFile() val generatedPack = GeneratedTexturePack("bf_gen", "Better Foliage generated assets")
})
fun logger(obj: Any) = LogManager.getLogger(obj)
fun detailLogger(obj: Any) = SimpleLogger(
obj::class.java.simpleName, Level.DEBUG, false, true, true, false, "yyyy-MM-dd HH:mm:ss", null, PropertiesUtil(Properties()), detailLogStream
)
val configFile get() = File(FabricLoader.getInstance().configDirectory, "BetterFoliage.json")
val config = MainConfig().apply {
if (configFile.exists()) JanksonSettings().deserialize(fiberNode, configFile.inputStream())
else JanksonSettings().serialize(fiberNode, configFile.outputStream(), false)
}
val blockConfig = BlockConfig()
val generatedPack = GeneratedBlockTexturePack(Identifier(MOD_ID, "generated"), "betterfoliage-generated", "Better Foliage", "Generated leaf textures")
/** List of recognized [BlockState]s */ /** List of recognized [BlockState]s */
var blockTypes = BlockTypeCache() var blockTypes = BlockTypeCache()
override fun onInitializeClient() { fun init() {
// Register generated resource pack // discoverers
ResourceManagerHelper.get(ResourceType.CLIENT_RESOURCES).registerReloadListener(generatedPack.reloader) BetterFoliageMod.bus.register(BakeWrapperManager)
(MinecraftClient.getInstance().resourcePackManager as ResourcePackManagerAccessor) BetterFoliageMod.bus.register(LeafParticleRegistry)
.providers.add(generatedPack.finder) resourceManager.registerReloadListener(LeafParticleRegistry)
ResourceManagerHelper.get(ResourceType.CLIENT_RESOURCES).registerReloadListener(blockConfig)
// Add standard block support
BakeWrapperManager.discoverers.add(StandardCactusDiscovery)
BakeWrapperManager.discoverers.add(StandardDirtDiscovery)
BakeWrapperManager.discoverers.add(StandardGrassDiscovery)
BakeWrapperManager.discoverers.add(StandardLeafDiscovery)
BakeWrapperManager.discoverers.add(StandardLilypadDiscovery)
BakeWrapperManager.discoverers.add(StandardMyceliumDiscovery)
BakeWrapperManager.discoverers.add(StandardNetherrackDiscovery)
BakeWrapperManager.discoverers.add(StandardRoundLogDiscovery)
BakeWrapperManager.discoverers.add(StandardSandDiscovery)
// Init overlay layers
ChunkOverlayManager.layers.add(RoundLogOverlayLayer) ChunkOverlayManager.layers.add(RoundLogOverlayLayer)
// Init singletons listOf(
LeafParticleRegistry StandardLeafDiscovery,
StandardLeafModel.Companion StandardGrassDiscovery,
StandardGrassModel.Companion StandardDirtDiscovery,
StandardRoundLogModel.Companion StandardMyceliumDiscovery,
StandardCactusModel.Companion StandardSandDiscovery,
StandardLilypadModel.Companion StandardLilypadDiscovery,
DirtModel.Companion StandardCactusDiscovery,
StandardSandModel.Companion StandardNetherrackDiscovery,
StandardMyceliumModel.Companion StandardRoundLogDiscovery
StandardNetherrackModel.Companion ).forEach {
RisingSoulParticle.Companion BakeWrapperManager.discoverers.add(it)
ShadersModIntegration }
}
// init singletons
val singletons = listOf(
AoSideHelper,
BlockConfig,
ChunkOverlayManager,
LeafWindTracker
)
val modelSingletons = listOf(
StandardLeafModel.Companion,
StandardGrassModel.Companion,
StandardDirtModel.Companion,
StandardMyceliumModel.Companion,
StandardSandModel.Companion,
StandardLilypadModel.Companion,
StandardCactusModel.Companion,
StandardNetherrackModel.Companion,
StandardRoundLogModel.Companion,
RisingSoulParticle.Companion
)
// init mod integrations
val integrations = listOf(
ShadersModIntegration,
OptifineCustomColors
)
}
} }

View File

@@ -0,0 +1,78 @@
package mods.betterfoliage
import mods.betterfoliage.config.BlockConfig
import mods.betterfoliage.config.MainConfig
import mods.betterfoliage.util.tryDefault
import mods.betterfoliage.config.clothGuiRoot
import mods.betterfoliage.config.forgeSpecRoot
import net.minecraft.client.Minecraft
import net.minecraft.client.gui.screen.Screen
import net.minecraft.util.ResourceLocation
import net.minecraftforge.fml.ExtensionPoint.CONFIGGUIFACTORY
import net.minecraftforge.fml.ExtensionPoint.DISPLAYTEST
import net.minecraftforge.fml.ModLoadingContext
import net.minecraftforge.fml.common.Mod
import net.minecraftforge.fml.config.ModConfig
import org.apache.commons.lang3.tuple.Pair
import org.apache.logging.log4j.Level
import org.apache.logging.log4j.LogManager
import org.apache.logging.log4j.simple.SimpleLogger
import org.apache.logging.log4j.util.PropertiesUtil
import thedarkcolour.kotlinforforge.forge.MOD_BUS
import java.io.File
import java.io.PrintStream
import java.util.Properties
import java.util.function.BiFunction
import java.util.function.BiPredicate
import java.util.function.Supplier
@Mod(BetterFoliageMod.MOD_ID)
object BetterFoliageMod {
const val MOD_ID = "betterfoliage"
val bus = MOD_BUS
val config = MainConfig()
val detailLogStream = PrintStream(File("logs/betterfoliage.log").apply {
parentFile.mkdirs()
if (!exists()) createNewFile()
})
fun logger(obj: Any) = LogManager.getLogger(obj)
fun detailLogger(obj: Any) = SimpleLogger(
obj::class.java.simpleName, Level.DEBUG, false, true, true, false, "yyyy-MM-dd HH:mm:ss", null, PropertiesUtil(Properties()), detailLogStream
)
init {
val ctx = ModLoadingContext.get()
val configSpec = config.forgeSpecRoot()
ctx.registerConfig(ModConfig.Type.CLIENT, configSpec)
// Add config GUI extension if Cloth Config is available
val clothLoaded = tryDefault(false) { Class.forName("me.shedaniel.clothconfig2.forge.api.ConfigBuilder"); true }
if (clothLoaded) {
logger(this).log(Level.INFO, "Cloth Config found, registering GUI")
ctx.registerExtensionPoint(CONFIGGUIFACTORY) { BiFunction<Minecraft, Screen, Screen> { client, parent ->
config.clothGuiRoot(
parentScreen = parent,
prefix = listOf(MOD_ID),
background = ResourceLocation("minecraft:textures/block/spruce_log.png"),
saveAction = { configSpec.save() }
)
} }
}
// Accept-all version tester (we are client-only)
ctx.registerExtensionPoint(DISPLAYTEST) {
Pair.of(
Supplier { "Honk if you see this!" },
BiPredicate<String, Boolean> { _, _ -> true }
)
}
Minecraft.getInstance().resourcePackRepository.addPackFinder(BetterFoliage.generatedPack.finder)
bus.register(BlockConfig)
BetterFoliage.init()
}
}

View File

@@ -1,11 +1,99 @@
package mods.betterfoliage package mods.octarinecore
import mods.betterfoliage.util.ClassRef
import mods.betterfoliage.util.ClassRef.Companion.float
import mods.betterfoliage.util.ClassRef.Companion.void
import mods.betterfoliage.util.FieldRef
import mods.betterfoliage.util.MethodRef
import net.minecraft.block.Block
import net.minecraft.block.BlockState
import net.minecraft.client.renderer.BlockModelRenderer
import net.minecraft.client.renderer.BlockRendererDispatcher
import net.minecraft.client.renderer.BufferBuilder
import net.minecraft.client.renderer.chunk.ChunkRenderCache
import net.minecraft.client.renderer.model.BakedQuad
import net.minecraft.client.renderer.model.IUnbakedModel
import net.minecraft.client.renderer.model.ModelBakery
import net.minecraft.client.renderer.texture.TextureAtlasSprite
import net.minecraft.util.ResourceLocation
import net.minecraft.util.math.BlockPos
import net.minecraft.world.IBlockDisplayReader
import net.minecraft.world.IBlockReader
import net.minecraftforge.client.model.pipeline.BlockInfo
import net.minecraftforge.client.model.pipeline.VertexLighterFlat
import java.util.Random
typealias Sprite = TextureAtlasSprite
// Java
val String = ClassRef<String>("java.lang.String")
val List = ClassRef<List<*>>("java.util.List")
val Random = ClassRef<Random>("java.util.Random")
fun <K, V> mapRef() = ClassRef<Map<K, V>>("java.util.Map")
fun <K, V> mapRefMutable() = ClassRef<MutableMap<K, V>>("java.util.Map")
// Minecraft
val IBlockReader = ClassRef<IBlockReader>("net.minecraft.world.IBlockReader")
val ILightReader = ClassRef<IBlockDisplayReader>("net.minecraft.world.IBlockDisplayReader")
val BlockState = ClassRef<BlockState>("net.minecraft.block.BlockState")
val BlockPos = ClassRef<BlockPos>("net.minecraft.util.math.BlockPos")
val Block = ClassRef<Block>("net.minecraft.block.Block")
val TextureAtlasSprite = ClassRef<TextureAtlasSprite>("net.minecraft.client.renderer.texture.TextureAtlasSprite")
val BufferBuilder = ClassRef<BufferBuilder>("net.minecraft.client.renderer.BufferBuilder")
val BufferBuilder_setSprite = MethodRef(BufferBuilder, "setSprite", void, TextureAtlasSprite)
val BufferBuilder_sVertexBuilder = FieldRef(BufferBuilder, "sVertexBuilder", SVertexBuilder)
val BlockRendererDispatcher = ClassRef<BlockRendererDispatcher>("net.minecraft.client.renderer.BlockRendererDispatcher")
val ChunkRenderCache = ClassRef<ChunkRenderCache>("net.minecraft.client.renderer.chunk.ChunkRenderCache")
val ResourceLocation = ClassRef<ResourceLocation>("net.minecraft.util.ResourceLocation")
val BakedQuad = ClassRef<BakedQuad>("net.minecraft.client.renderer.model.BakedQuad")
val BlockModelRenderer = ClassRef<BlockModelRenderer>("net.minecraft.client.renderer.BlockModelRenderer")
val VertexLighterFlat = ClassRef<VertexLighterFlat>("net.minecraftforge.client.model.pipeline.VertexLighterFlat")
val BlockInfo = ClassRef<BlockInfo>("net.minecraftforge.client.model.pipeline.BlockInfo")
val VertexLighterFlat_blockInfo = FieldRef(VertexLighterFlat, "blockInfo", BlockInfo)
val BlockInfo_shx = FieldRef(BlockInfo, "shx", float)
val BlockInfo_shy = FieldRef(BlockInfo, "shy", float)
val BlockInfo_shz = FieldRef(BlockInfo, "shz", float)
object ModelBakery : ClassRef<ModelBakery>("net.minecraft.client.renderer.model.ModelBakery") {
val unbakedModels = FieldRef(this, "unbakedModels", mapRefMutable<ResourceLocation, IUnbakedModel>())
val topUnbakedModels = FieldRef(this, "topUnbakedModels", mapRefMutable<ResourceLocation, IUnbakedModel>())
}
// Optifine
val OptifineClassTransformer = ClassRef<Any>("optifine.OptiFineClassTransformer")
val BlockPosM = ClassRef<Any>("net.optifine.BlockPosM")
object ChunkCacheOF : ClassRef<Any>("net.optifine.override.ChunkCacheOF") {
val chunkCache = FieldRef(this, "chunkCache", ChunkRenderCache)
}
object RenderEnv : ClassRef<Any>("net.optifine.render.RenderEnv") {
val reset = MethodRef(this, "reset", void, BlockState, BlockPos)
}
// Optifine custom colors
val IColorizer = ClassRef<Any>("net.optifine.CustomColors\$IColorizer")
object CustomColors : ClassRef<Any>("net.optifine.CustomColors") {
val getColorMultiplier = MethodRef(this, "getColorMultiplier", int, BakedQuad, BlockState, ILightReader, BlockPos, RenderEnv)
}
// Optifine shaders
object Shaders : ClassRef<Any>("net.optifine.shaders.Shaders") {
val shaderPackLoaded = FieldRef(this, "shaderPackLoaded", boolean)
val blockLightLevel05 = FieldRef(this, "blockLightLevel05", float)
val blockLightLevel06 = FieldRef(this, "blockLightLevel06", float)
val blockLightLevel08 = FieldRef(this, "blockLightLevel08", float)
}
object SVertexBuilder : ClassRef<Any>("net.optifine.shaders.SVertexBuilder") {
val pushState = MethodRef(this, "pushEntity", void, long)
val popState = MethodRef(this, "popEntity", void)
}
object BlockAliases : ClassRef<Any>("net.optifine.shaders.BlockAliases") {
val getAliasBlockId = MethodRef(this, "getAliasBlockId", int, BlockState)
}
import it.unimi.dsi.fastutil.ints.IntList
import mods.betterfoliage.util.YarnHelper
import net.minecraft.client.texture.Sprite
import net.minecraft.world.World
val VertexFormat_offsets = YarnHelper.requiredField<IntList>("net.minecraft.class_293", "field_1597", "Lit/unimi/dsi/fastutil/ints/IntList;")
val BakedQuad_sprite = YarnHelper.requiredField<Sprite>("net.minecraft.class_777", "field_4176", "Lnet/minecraft/class_1058;")
val WorldChunk_world = YarnHelper.requiredField<World>("net.minecraft.class_2818", "field_12858", "Lnet/minecraft/class_1937;")
val ChunkRendererRegion_world = YarnHelper.requiredField<World>("net.minecraft.class_853", "field_4490", "Lnet/minecraft/class_1937;")

View File

@@ -1,72 +1,8 @@
package mods.betterfoliage package mods.betterfoliage
import net.fabricmc.fabric.api.event.Event import net.minecraft.client.renderer.model.ModelBakery
import net.fabricmc.fabric.api.event.EventFactory import net.minecraft.client.renderer.texture.AtlasTexture
import net.minecraft.client.render.block.BlockModels import net.minecraft.resources.IResourceManager
import net.minecraft.client.render.model.ModelLoader import net.minecraft.util.ResourceLocation
import net.minecraft.client.world.ClientWorld import net.minecraftforge.eventbus.api.Event
import net.minecraft.resource.ResourceManager
import net.minecraft.world.chunk.WorldChunk
interface ClientChunkLoadCallback {
fun loadChunk(chunk: WorldChunk)
fun unloadChunk(chunk: WorldChunk)
companion object {
@JvmField val EVENT: Event<ClientChunkLoadCallback> = EventFactory.createArrayBacked(ClientChunkLoadCallback::class.java) { listeners ->
object : ClientChunkLoadCallback {
override fun loadChunk(chunk: WorldChunk) { listeners.forEach { it.loadChunk(chunk) } }
override fun unloadChunk(chunk: WorldChunk) { listeners.forEach { it.unloadChunk(chunk) } }
}
}
}
}
interface ClientWorldLoadCallback {
fun loadWorld(world: ClientWorld)
companion object {
@JvmField val EVENT : Event<ClientWorldLoadCallback> = EventFactory.createArrayBacked(ClientWorldLoadCallback::class.java) { listeners ->
object : ClientWorldLoadCallback {
override fun loadWorld(world: ClientWorld) { listeners.forEach { it.loadWorld(world) } }
}
}
}
}
/**
* Event fired after [BlockModels.reload] finishes.
*/
interface BlockModelsReloadCallback {
fun reloadBlockModels(blockModels: BlockModels)
companion object {
@JvmField val EVENT: Event<BlockModelsReloadCallback> = EventFactory.createArrayBacked(BlockModelsReloadCallback::class.java) { listeners ->
object : BlockModelsReloadCallback {
override fun reloadBlockModels(blockModels: BlockModels) {
listeners.forEach { it.reloadBlockModels(blockModels) }
}
}
}
}
}
/**
* Event fired when the [ModelLoader] first starts loading models.
*
* This happens during the constructor, so BEWARE!
* Try to avoid any interaction until the block texture atlas starts stitching.
*/
interface ModelLoadingCallback {
fun beginLoadModels(loader: ModelLoader, manager: ResourceManager)
companion object {
@JvmField val EVENT: Event<ModelLoadingCallback> = EventFactory.createArrayBacked(ModelLoadingCallback::class.java) { listeners ->
object : ModelLoadingCallback {
override fun beginLoadModels(loader: ModelLoader, manager: ResourceManager) {
listeners.forEach { it.beginLoadModels(loader, manager) }
}
}
}
}
}

View File

@@ -2,66 +2,76 @@
package mods.betterfoliage package mods.betterfoliage
import mods.betterfoliage.chunk.ChunkOverlayManager import mods.betterfoliage.chunk.ChunkOverlayManager
import mods.betterfoliage.render.block.vanilla.LeafParticleKey import mods.betterfoliage.config.Config
import mods.betterfoliage.model.SpecialRenderModel
import mods.betterfoliage.model.WeightedModelWrapper
import mods.betterfoliage.render.block.vanilla.RoundLogKey import mods.betterfoliage.render.block.vanilla.RoundLogKey
import mods.betterfoliage.render.particle.FallingLeafParticle import mods.betterfoliage.render.particle.FallingLeafParticle
import mods.betterfoliage.render.particle.LeafBlockModel
import mods.betterfoliage.render.particle.RisingSoulParticle import mods.betterfoliage.render.particle.RisingSoulParticle
import mods.betterfoliage.util.offset import net.minecraft.block.Block
import mods.betterfoliage.util.plus
import mods.betterfoliage.util.random
import mods.betterfoliage.util.randomD
import net.minecraft.block.BlockState import net.minecraft.block.BlockState
import net.minecraft.block.Blocks import net.minecraft.block.Blocks
import net.minecraft.client.MinecraftClient import net.minecraft.client.Minecraft
import net.minecraft.client.world.ClientWorld import net.minecraft.client.world.ClientWorld
import net.minecraft.util.Direction
import net.minecraft.util.Direction.DOWN
import net.minecraft.util.Direction.UP
import net.minecraft.util.math.BlockPos import net.minecraft.util.math.BlockPos
import net.minecraft.util.math.Direction import net.minecraft.util.math.shapes.VoxelShape
import net.minecraft.util.shape.VoxelShape import net.minecraft.util.math.shapes.VoxelShapes
import net.minecraft.util.shape.VoxelShapes import net.minecraft.world.IBlockDisplayReader
import net.minecraft.world.BlockView import net.minecraft.world.IBlockReader
import net.minecraft.world.World
import java.util.Random
fun getAmbientOcclusionLightValueOverride(original: Float, state: BlockState): Float { fun getAmbientOcclusionLightValueOverride(original: Float, state: BlockState): Float {
if (BetterFoliage.config.enabled && if (Config.enabled && Config.roundLogs.enabled && BetterFoliage.blockTypes.hasTyped<RoundLogKey>(state))
BetterFoliage.config.roundLogs.enabled && return Config.roundLogs.dimming.toFloat()
BetterFoliage.blockTypes.hasTyped<RoundLogKey>(state)
) return BetterFoliage.config.roundLogs.dimming.toFloat()
return original return original
} }
fun getUseNeighborBrightnessOverride(original: Boolean, state: BlockState): Boolean { fun onClientBlockChanged(worldClient: ClientWorld, pos: BlockPos, oldState: BlockState, newState: BlockState, flags: Int) {
return original || (BetterFoliage.config.enabled && BetterFoliage.config.roundLogs.enabled && BetterFoliage.blockConfig.logBlocks.matchesClass(state.block));
}
fun onClientBlockChanged(worldClient: ClientWorld, pos: BlockPos, oldState: BlockState, newState: BlockState) {
ChunkOverlayManager.onBlockChange(worldClient, pos) ChunkOverlayManager.onBlockChange(worldClient, pos)
} }
fun onRandomDisplayTick(world: ClientWorld, pos: BlockPos) { fun onRandomDisplayTick(world: ClientWorld, pos: BlockPos, random: Random) {
val state = world.getBlockState(pos) val state = world.getBlockState(pos)
if (Config.enabled &&
if (BetterFoliage.config.enabled && Config.risingSoul.enabled &&
BetterFoliage.config.risingSoul.enabled &&
state.block == Blocks.SOUL_SAND && state.block == Blocks.SOUL_SAND &&
world.isAir(pos + Direction.UP.offset) && world.getBlockState(pos.relative(UP)).isAir &&
Math.random() < BetterFoliage.config.risingSoul.chance) { Math.random() < Config.risingSoul.chance) {
RisingSoulParticle(world, pos).addIfValid() RisingSoulParticle(world, pos).addIfValid()
} }
if (BetterFoliage.config.enabled && if (Config.enabled &&
BetterFoliage.config.fallingLeaves.enabled && Config.fallingLeaves.enabled &&
world.isAir(pos + Direction.DOWN.offset) && random.nextDouble() < Config.fallingLeaves.chance &&
randomD() < BetterFoliage.config.fallingLeaves.chance) { world.getBlockState(pos.relative(DOWN)).isAir
BetterFoliage.blockTypes.getTyped<LeafParticleKey>(state)?.let { key -> ) {
val blockColor = MinecraftClient.getInstance().blockColors.getColor(state, world, pos, 0) (getActualRenderModel(world, pos, state, random) as? LeafBlockModel)?.let { leafModel ->
FallingLeafParticle(world, pos, key, blockColor, random).addIfValid() val blockColor = Minecraft.getInstance().blockColors.getColor(state, world, pos, 0)
} FallingLeafParticle(world, pos, leafModel.key, blockColor, random).addIfValid()
}
} }
} }
fun getVoxelShapeOverride(state: BlockState, reader: BlockView, pos: BlockPos, dir: Direction): VoxelShape { fun getVoxelShapeOverride(state: BlockState, reader: IBlockReader, pos: BlockPos, dir: Direction): VoxelShape {
if (BetterFoliage.blockTypes.hasTyped<RoundLogKey>(state)) { if (Config.enabled && Config.roundLogs.enabled && BetterFoliage.blockTypes.hasTyped<RoundLogKey>(state))
return VoxelShapes.empty() return VoxelShapes.empty()
} return state.getFaceOcclusionShape(reader, pos, dir)
// TODO ? }
return state.getCullingFace(reader, pos, dir)
fun shouldForceSideRenderOF(state: BlockState, world: IBlockReader, pos: BlockPos, face: Direction) =
world.getBlockState(pos.relative(face)).let { neighbor -> BetterFoliage.blockTypes.hasTyped<RoundLogKey>(neighbor) }
fun getActualRenderModel(world: IBlockDisplayReader, pos: BlockPos, state: BlockState, random: Random): SpecialRenderModel? {
val model = Minecraft.getInstance().blockRenderer.blockModelShaper.getBlockModel(state) as? SpecialRenderModel
?: return null
if (model is WeightedModelWrapper) {
random.setSeed(state.getSeed(pos))
return model.getModel(random).model
}
return model
} }

View File

@@ -1,60 +1,60 @@
package mods.betterfoliage.chunk package mods.betterfoliage.chunk
import mods.betterfoliage.ChunkRendererRegion_world
import mods.betterfoliage.util.Int3 import mods.betterfoliage.util.Int3
import mods.betterfoliage.util.allDirections
import mods.betterfoliage.util.offset import mods.betterfoliage.util.offset
import mods.betterfoliage.util.plus import mods.betterfoliage.util.plus
import mods.betterfoliage.util.semiRandom
import net.minecraft.block.Block import net.minecraft.block.Block
import net.minecraft.block.BlockState import net.minecraft.block.BlockState
import net.minecraft.client.MinecraftClient import net.minecraft.client.renderer.chunk.ChunkRenderCache
import net.minecraft.client.render.chunk.ChunkRendererRegion import net.minecraft.util.Direction
import net.minecraft.util.math.BlockPos import net.minecraft.util.math.BlockPos
import net.minecraft.util.math.Direction import net.minecraft.world.IBlockDisplayReader
import net.minecraft.world.BlockRenderView import net.minecraft.world.IWorldReader
import net.minecraft.world.WorldView
import net.minecraft.world.biome.Biome import net.minecraft.world.biome.Biome
import net.minecraft.world.level.ColorResolver
/** /**
* Represents the block being rendered. Has properties and methods to query the neighborhood of the block in * Represents the block being rendered. Has properties and methods to query the neighborhood of the block in
* block-relative coordinates. * block-relative coordinates.
*/ */
interface BlockCtx { interface BlockCtx {
val world: BlockRenderView val world: IBlockDisplayReader
val pos: BlockPos val pos: BlockPos
fun offset(dir: Direction) = offset(dir.offset) fun offset(dir: Direction) = offset(dir.offset)
fun offset(offset: Int3): BlockCtx fun offset(offset: Int3): BlockCtx
val state: BlockState get() = world.getBlockState(pos) val state: BlockState get() = world.getBlockState(pos)
fun state(dir: Direction) = world.getBlockState(pos + dir.offset)
fun state(offset: Int3) = world.getBlockState(pos + offset) fun state(offset: Int3) = world.getBlockState(pos + offset)
fun state(dir: Direction) = state(dir.offset)
fun isAir(offset: Int3) = (pos + offset).let { world.getBlockState(it).isAir(world, it) }
fun isAir(dir: Direction) = isAir(dir.offset)
val biome: Biome? get() = val biome: Biome? get() =
(world as? WorldView)?.getBiome(pos) ?: (world as? IWorldReader)?.getBiome(pos) ?:
(world as? ChunkRendererRegion)?.let { ChunkRendererRegion_world[it]?.getBiome(pos) } (world as? ChunkRenderCache)?.level?.getBiome(pos)
val isNormalCube: Boolean get() = state.isOpaqueFullCube(world, pos) val isFullBlock: Boolean get() = state.isCollisionShapeFullBlock(world, pos)
fun shouldSideBeRendered(side: Direction) = Block.shouldDrawSide(state, world, pos, side) fun isNeighborSturdy(dir: Direction) = offset(dir).let { it.state.isFaceSturdy(it.world, it.pos, dir.opposite) }
fun isNeighborSolid(dir: Direction) = offset(dir).let { it.state.isSideSolidFullSquare(it.world, it.pos, dir.opposite) } fun shouldSideBeRendered(side: Direction) = Block.shouldRenderFace(state, world, pos, side)
fun model(dir: Direction) = state(dir).let { MinecraftClient.getInstance().blockRenderManager.getModel(it)!! } /** Get a semi-random value based on the block coordinate and the given seed. */
fun model(offset: Int3) = state(offset).let { MinecraftClient.getInstance().blockRenderManager.getModel(it)!! } fun semiRandom(seed: Int) = pos.semiRandom(seed)
/** Get an array of semi-random values based on the block coordinate. */
fun semiRandomArray(num: Int): Array<Int> = Array(num) { semiRandom(it) }
fun color(resolver: ColorResolver) = world.getBlockTint(pos, resolver)
} }
open class BasicBlockCtx( class BasicBlockCtx(
override val world: BlockRenderView, override val world: IBlockDisplayReader,
override val pos: BlockPos override val pos: BlockPos
) : BlockCtx { ) : BlockCtx {
override val state = world.getBlockState(pos) override val state: BlockState = world.getBlockState(pos)
override fun offset(offset: Int3) = BasicBlockCtx(world, pos + offset) override fun offset(offset: Int3) = BasicBlockCtx(world, pos + offset)
fun cache() = CachedBlockCtx(world, pos)
}
open class CachedBlockCtx(world: BlockRenderView, pos: BlockPos) : BasicBlockCtx(world, pos) {
var neighbors = Array<BlockState>(6) { world.getBlockState(pos + allDirections[it].offset) }
override var biome: Biome? = super.biome
override fun state(dir: Direction) = neighbors[dir.ordinal]
} }

View File

@@ -1,50 +0,0 @@
package mods.betterfoliage.chunk
import net.minecraft.util.math.BlockPos
import net.minecraft.world.BlockView
import net.minecraft.world.LightType
import net.minecraft.world.WorldView
/**
* Delegating [IBlockAccess] that fakes a _modified_ location to return values from a _target_ location.
* All other locations are handled normally.
*
* @param[original] the [IBlockAccess] that is delegated to
*/
@Suppress("NOTHING_TO_INLINE", "NULLABILITY_MISMATCH_BASED_ON_JAVA_ANNOTATIONS", "HasPlatformType")
open class OffsetBlockView(open val original: BlockView, val modded: BlockPos, val target: BlockPos) : BlockView {
inline fun actualPos(pos: BlockPos) = if (pos != null && pos.x == modded.x && pos.y == modded.y && pos.z == modded.z) target else pos
override fun getBlockState(pos: BlockPos) = original.getBlockState(actualPos(pos))
override fun getBlockEntity(pos: BlockPos) = original.getBlockEntity(actualPos(pos))
override fun getFluidState(pos: BlockPos) = original.getFluidState(actualPos(pos))
}
@Suppress("NOTHING_TO_INLINE", "NULLABILITY_MISMATCH_BASED_ON_JAVA_ANNOTATIONS", "HasPlatformType")
class OffsetExtBlockView(val original: WorldView, val modded: BlockPos, val target: BlockPos) : WorldView by original {
inline fun actualPos(pos: BlockPos) = if (pos != null && pos.x == modded.x && pos.y == modded.y && pos.z == modded.z) target else pos
override fun getBlockState(pos: BlockPos) = original.getBlockState(actualPos(pos))
override fun getBlockEntity(pos: BlockPos) = original.getBlockEntity(actualPos(pos))
override fun getFluidState(pos: BlockPos) = original.getFluidState(actualPos(pos))
override fun getLightLevel(type: LightType, pos: BlockPos) = original.getLightLevel(type, actualPos(pos))
override fun getBaseLightLevel(pos: BlockPos, light: Int) = original.getBaseLightLevel(actualPos(pos), light)
override fun getBiome(pos: BlockPos) = original.getBiome(actualPos(pos))
}
/**
* Temporarily replaces the [IBlockReader] used by this [BlockContext] and the corresponding [ExtendedRenderBlocks]
* to use an [OffsetEnvBlockReader] while executing this lambda.
*
* @param[modded] the _modified_ location
* @param[target] the _target_ location
* @param[func] the lambda to execute
*/
//inline fun <reified T> BlockContext.withOffset(modded: Int3, target: Int3, func: () -> T): T {
// val original = reader!!
// reader = OffsetEnvBlockReader(original, pos + modded, pos + target)
// val result = func()
// reader = original
// return result
//}

View File

@@ -1,30 +1,26 @@
package mods.betterfoliage.chunk package mods.betterfoliage.chunk
import mods.betterfoliage.*
import mods.betterfoliage.util.YarnHelper
import mods.betterfoliage.util.get import mods.betterfoliage.util.get
import net.minecraft.client.render.chunk.ChunkRendererRegion import mods.betterfoliage.util.isInstance
import mods.octarinecore.ChunkCacheOF
import net.minecraft.client.renderer.chunk.ChunkRenderCache
import net.minecraft.client.world.ClientWorld import net.minecraft.client.world.ClientWorld
import net.minecraft.util.math.BlockPos import net.minecraft.util.math.BlockPos
import net.minecraft.util.math.ChunkPos import net.minecraft.util.math.ChunkPos
import net.minecraft.world.BlockRenderView import net.minecraft.world.DimensionType
import net.minecraft.world.World import net.minecraft.world.IBlockDisplayReader
import net.minecraft.world.WorldView import net.minecraft.world.IWorldReader
import net.minecraft.world.chunk.WorldChunk import net.minecraftforge.common.MinecraftForge
import net.minecraft.world.dimension.DimensionType import net.minecraftforge.event.world.ChunkEvent
import net.minecraftforge.event.world.WorldEvent
import net.minecraftforge.eventbus.api.SubscribeEvent
import java.util.* import java.util.*
import kotlin.collections.List
import kotlin.collections.MutableMap
import kotlin.collections.associateWith
import kotlin.collections.forEach
import kotlin.collections.mutableListOf
import kotlin.collections.mutableMapOf
import kotlin.collections.set import kotlin.collections.set
val BlockRenderView.dimType: DimensionType get() = when { val IBlockDisplayReader.dimType: DimensionType get() = when {
this is WorldView -> dimension this is IWorldReader -> dimensionType()
this is ChunkRendererRegion -> this[ChunkRendererRegion_world]!!.dimension this is ChunkRenderCache -> level.dimensionType()
// this.isInstance(ChunkCacheOF) -> this[ChunkCacheOF.chunkCache]!!.dimType this.isInstance(ChunkCacheOF) -> this[ChunkCacheOF.chunkCache].level.dimensionType()
else -> throw IllegalArgumentException("DimensionType of world with class ${this::class.qualifiedName} cannot be determined!") else -> throw IllegalArgumentException("DimensionType of world with class ${this::class.qualifiedName} cannot be determined!")
} }
@@ -33,17 +29,18 @@ val BlockRenderView.dimType: DimensionType get() = when {
*/ */
interface ChunkOverlayLayer<T> { interface ChunkOverlayLayer<T> {
fun calculate(ctx: BlockCtx): T fun calculate(ctx: BlockCtx): T
fun onBlockUpdate(world: WorldView, pos: BlockPos) fun onBlockUpdate(world: IBlockDisplayReader, pos: BlockPos)
} }
/** /**
* Query, lazy calculation and lifecycle management of multiple layers of chunk overlay data. * Query, lazy calculation and lifecycle management of multiple layers of chunk overlay data.
*/ */
object ChunkOverlayManager : ClientChunkLoadCallback, ClientWorldLoadCallback { object ChunkOverlayManager {
var tempCounter = 0
init { init {
ClientWorldLoadCallback.EVENT.register(this) MinecraftForge.EVENT_BUS.register(this)
ClientChunkLoadCallback.EVENT.register(this)
} }
val chunkData = IdentityHashMap<DimensionType, MutableMap<ChunkPos, ChunkOverlayData>>() val chunkData = IdentityHashMap<DimensionType, MutableMap<ChunkPos, ChunkOverlayData>>()
@@ -82,25 +79,27 @@ object ChunkOverlayManager : ClientChunkLoadCallback, ClientWorldLoadCallback {
} }
} }
override fun loadChunk(chunk: WorldChunk) { @SubscribeEvent
chunk[WorldChunk_world]!!.dimType.let { dim -> fun handleLoadWorld(event: WorldEvent.Load) = (event.world as? ClientWorld)?.let { world ->
val data = chunkData[dim] ?: mutableMapOf<ChunkPos, ChunkOverlayData>().apply { chunkData[dim] = this } chunkData[world.dimType] = mutableMapOf()
data.let { chunks -> }
// check for existence first because Optifine fires a TON of these
if (chunk.pos !in chunks.keys) chunks[chunk.pos] = ChunkOverlayData(layers) @SubscribeEvent
} fun handleUnloadWorld(event: WorldEvent.Unload) = (event.world as? ClientWorld)?.let { world ->
chunkData.remove(world.dimType)
}
@SubscribeEvent
fun handleLoadChunk(event: ChunkEvent.Load) = (event.world as? ClientWorld)?.let { world ->
chunkData[world.dimType]?.let { chunks ->
// check for existence first because Optifine fires a TON of these
if (event.chunk.pos !in chunks.keys) chunks[event.chunk.pos] = ChunkOverlayData(layers)
} }
} }
override fun unloadChunk(chunk: WorldChunk) { @SubscribeEvent
chunk[WorldChunk_world]!!.dimType.let { dim -> fun handleUnloadChunk(event: ChunkEvent.Unload) = (event.world as? ClientWorld)?.let { world ->
chunkData[dim]?.remove(chunk.pos) chunkData[world.dimType]?.remove(event.chunk.pos)
}
}
override fun loadWorld(world: ClientWorld) {
val dim = world.dimType
// chunkData.keys.forEach { if (it == dim) chunkData[dim] = mutableMapOf() else chunkData.remove(dim)}
} }
} }

View File

@@ -1,39 +0,0 @@
package mods.betterfoliage.config
import mods.betterfoliage.BetterFoliage
import mods.betterfoliage.resource.VeryEarlyReloadListener
import mods.betterfoliage.resource.discovery.ConfigurableBlockMatcher
import mods.betterfoliage.resource.discovery.ModelTextureListConfiguration
import net.minecraft.resource.ResourceManager
import net.minecraft.util.Identifier
class BlockConfig : VeryEarlyReloadListener {
private val list = mutableListOf<Any>()
val leafBlocks = blocks("leaves_blocks_default.cfg")
val leafModels = models("leaves_models_default.cfg")
val grassBlocks = blocks("grass_blocks_default.cfg")
val grassModels = models("grass_models_default.cfg")
// val mycelium = blocks("mycelium_blocks_default.cfg")
// val dirt = blocks("dirt_default.cfg")
// val crops = blocks("crop_default.cfg")
val logBlocks = blocks("log_blocks_default.cfg")
val logModels = models("log_models_default.cfg")
// val sand = blocks("sand_default.cfg")
// val lilypad = blocks("lilypad_default.cfg")
// val cactus = blocks("cactus_default.cfg")
// val netherrack = blocks("netherrack_blocks_default.cfg")
private fun blocks(cfgName: String) = ConfigurableBlockMatcher(Identifier(BetterFoliage.MOD_ID, cfgName)).apply { list.add(this) }
private fun models(cfgName: String) = ModelTextureListConfiguration(Identifier(BetterFoliage.MOD_ID, cfgName)).apply { list.add(this) }
override fun getFabricId() = Identifier(BetterFoliage.MOD_ID, "block-config")
override fun onReloadStarted(manager: ResourceManager) {
list.forEach { when(it) {
is ConfigurableBlockMatcher -> it.readDefaults(manager)
is ModelTextureListConfiguration -> it.readDefaults(manager)
} }
}
}

View File

@@ -0,0 +1,35 @@
package mods.betterfoliage.config
import mods.betterfoliage.BetterFoliageMod
import mods.betterfoliage.resource.discovery.ConfigurableBlockMatcher
import mods.betterfoliage.resource.discovery.ModelTextureListConfiguration
import net.minecraft.util.ResourceLocation
import net.minecraftforge.eventbus.api.SubscribeEvent
import net.minecraftforge.fml.config.ModConfig
object BlockConfig {
private val list = mutableListOf<Any>()
val leafBlocks = blocks("leaves_blocks_default.cfg")
val leafModels = models("leaves_models_default.cfg")
val grassBlocks = blocks("grass_blocks_default.cfg")
val grassModels = models("grass_models_default.cfg")
val mycelium = blocks("mycelium_blocks_default.cfg")
// val dirt = blocks("dirt_default.cfg")
val crops = blocks("crop_default.cfg")
val logBlocks = blocks("log_blocks_default.cfg")
val logModels = models("log_models_default.cfg")
val lilypad = blocks("lilypad_default.cfg")
init { BetterFoliageMod.bus.register(this) }
private fun blocks(cfgName: String) = ConfigurableBlockMatcher(ResourceLocation(BetterFoliageMod.MOD_ID, cfgName)).apply { list.add(this) }
private fun models(cfgName: String) = ModelTextureListConfiguration(ResourceLocation(BetterFoliageMod.MOD_ID, cfgName)).apply { list.add(this) }
@SubscribeEvent
fun onConfig(event: ModConfig.ModConfigEvent) {
list.forEach { when(it) {
is ConfigurableBlockMatcher -> it.readDefaults()
is ModelTextureListConfiguration -> it.readDefaults()
} }
}
}

View File

@@ -1,141 +1,170 @@
package mods.betterfoliage.config package mods.betterfoliage.config
import me.shedaniel.clothconfig2.api.AbstractConfigListEntry import me.shedaniel.clothconfig2.forge.api.AbstractConfigListEntry
import me.shedaniel.clothconfig2.api.ConfigEntryBuilder import me.shedaniel.clothconfig2.forge.api.ConfigBuilder
import me.shedaniel.clothconfig2.gui.entries.SubCategoryListEntry import me.shedaniel.clothconfig2.forge.api.ConfigEntryBuilder
import me.zeroeightsix.fiber.builder.ConfigValueBuilder import me.shedaniel.clothconfig2.forge.gui.entries.SubCategoryListEntry
import me.zeroeightsix.fiber.tree.ConfigLeaf import mods.betterfoliage.util.asText
import me.zeroeightsix.fiber.tree.ConfigNode import net.minecraft.client.gui.screen.Screen
import me.zeroeightsix.fiber.tree.ConfigValue import net.minecraft.client.resources.I18n
import net.minecraft.client.resource.language.I18n import net.minecraft.util.ResourceLocation
import net.minecraft.text.LiteralText import net.minecraftforge.common.ForgeConfigSpec
import java.util.* import java.util.Optional
import kotlin.properties.ReadOnlyProperty import kotlin.properties.ReadOnlyProperty
import kotlin.reflect.KProperty import kotlin.reflect.KProperty
const val MAX_LINE_LEN = 30 const val MAX_LINE_LEN = 30
fun textify(string: String) = LiteralText(string) fun DelegatingConfigGroup.forgeSpecRoot() =
fun textify(strings: Array<String>) = strings.map(::LiteralText).toTypedArray() ForgeConfigSpec.Builder()
.also { createForgeNode(it) }
.build()
sealed class DelegatingConfigNode<N: ConfigLeaf>(val fiberNode: N) { fun DelegatingConfigGroup.clothGuiRoot(
abstract fun createClothNode(names: List<String>): AbstractConfigListEntry<*> parentScreen: Screen,
prefix: List<String>,
background: ResourceLocation,
saveAction: ()->Unit
) = ConfigBuilder.create()
.setParentScreen(parentScreen)
.setTitle(I18n.get((prefix + "title").joinToString(".")).asText())
.setDefaultBackgroundTexture(background)
.setSavingRunnable(saveAction)
.also { builder ->
createClothNode(prefix).value.forEach { rootCategory ->
builder.getOrCreateCategory("main".asText()).addEntry(rootCategory)
}
}
.build()
sealed class DelegatingConfigNode {
abstract fun createClothNode(path: List<String>): AbstractConfigListEntry<*>
} }
abstract class DelegatingConfigValue<T>(fiberNode: ConfigValue<T>) : DelegatingConfigNode<ConfigValue<T>>(fiberNode), ReadOnlyProperty<DelegatingConfigGroup, T> abstract class DelegatingConfigValue<T> : DelegatingConfigNode(), ReadOnlyProperty<DelegatingConfigGroup, T> {
lateinit var forgeValue: ForgeConfigSpec.ConfigValue<T>
abstract fun createForgeNode(builder: ForgeConfigSpec.Builder, name: String)
}
open class DelegatingConfigGroup(fiberNode: ConfigNode) : DelegatingConfigNode<ConfigNode>(fiberNode) { open class DelegatingConfigGroup : DelegatingConfigNode() {
val children = mutableListOf<DelegatingConfigNode<*>>() val children = mutableMapOf<String, DelegatingConfigNode>()
override fun createClothNode(names: List<String>): SubCategoryListEntry {
fun createForgeNode(builder: ForgeConfigSpec.Builder) {
children.forEach { (name, node) ->
when(node) {
is DelegatingConfigGroup -> {
builder.push(name)
node.createForgeNode(builder)
builder.pop()
}
is DelegatingConfigValue<*> -> node.createForgeNode(builder, name)
}
}
}
override fun createClothNode(path: List<String>): SubCategoryListEntry {
val builder = ConfigEntryBuilder.create() val builder = ConfigEntryBuilder.create()
.startSubCategory(textify(names.joinToString(".").translate())) .startSubCategory(path.joinToString(".").translate())
.setTooltip(*textify(names.joinToString(".").translateTooltip())) .setTooltip(*path.joinToString(".").translateTooltip())
.setExpanded(false) .setExpanded(false)
children.forEach { builder.add(it.createClothNode(names + it.fiberNode.name!!)) } children.forEach { (name, node) -> builder.add(node.createClothNode(path + name)) }
return builder.build() return builder.build()
} }
operator fun get(name: String) = children.find { it.fiberNode.name == name }
} }
interface DelegatingConfigGroupFactory<T> { interface DelegatingConfigGroupFactory<T> {
operator fun provideDelegate(parent: DelegatingConfigGroup, property: KProperty<*>): ReadOnlyProperty<DelegatingConfigGroup, T> operator fun provideDelegate(parent: DelegatingConfigGroup, property: KProperty<*>): ReadOnlyProperty<DelegatingConfigGroup, T>
} }
fun <T: DelegatingConfigGroup> subNode(factory: (ConfigNode)->T) = object : DelegatingConfigGroupFactory<T> { fun <T: DelegatingConfigGroup> subNode(factory: ()->T) = object : DelegatingConfigGroupFactory<T> {
override operator fun provideDelegate(parent: DelegatingConfigGroup, property: KProperty<*>): ReadOnlyProperty<DelegatingConfigGroup, T> { override operator fun provideDelegate(parent: DelegatingConfigGroup, property: KProperty<*>): ReadOnlyProperty<DelegatingConfigGroup, T> {
val childNode = ConfigNode(property.name, null) val child = factory()
val configGroup = factory(childNode) parent.children[property.name] = child
parent.fiberNode.items.add(childNode) return ReadOnlyProperty { _, _ -> child }
parent.children.add(configGroup)
return object : ReadOnlyProperty<DelegatingConfigGroup, T> {
override fun getValue(thisRef: DelegatingConfigGroup, property: KProperty<*>) = configGroup
}
} }
} }
interface DelegatingConfigValueFactory<T> { interface DelegatingConfigValueFactory<T> {
fun createFiberNode(parent: ConfigNode, name: String): ConfigValue<T> fun createForgeNode(builder: ForgeConfigSpec.Builder, name: String): ForgeConfigSpec.ConfigValue<T>
fun createClothNode(node: ConfigValue<T>, names: List<String>): AbstractConfigListEntry<T> fun createClothNode(prop: CachingConfigProperty<T>, path: List<String>): AbstractConfigListEntry<T>
operator fun provideDelegate(parent: DelegatingConfigGroup, property: KProperty<*>): ReadOnlyProperty<DelegatingConfigGroup, T> { operator fun provideDelegate(parent: DelegatingConfigGroup, property: KProperty<*>): ReadOnlyProperty<DelegatingConfigGroup, T> {
return object : DelegatingConfigValue<T>(createFiberNode(parent.fiberNode, property.name)) { return object : CachingConfigProperty<T>(parent, property) {
override fun createClothNode(names: List<String>) = createClothNode(fiberNode, names) override fun createForgeNode(builder: ForgeConfigSpec.Builder, name: String) {
override fun getValue(thisRef: DelegatingConfigGroup, property: KProperty<*>) = fiberNode.value!! forgeValue = this@DelegatingConfigValueFactory.createForgeNode(builder, name)
}.apply { parent.children.add(this) } }
override fun createClothNode(path: List<String>): AbstractConfigListEntry<*> = createClothNode(this, path)
}.apply { parent.children[property.name] = this }
} }
} }
fun String.translate() = I18n.translate(this) abstract class CachingConfigProperty<T>(parent: DelegatingConfigGroup, property: KProperty<*>) : DelegatingConfigValue<T>() {
fun String.translateTooltip(lineLength: Int = MAX_LINE_LEN) = ("$this.tooltip").translate().let { tooltip -> var value: T? = null
tooltip.splitToSequence(" ").fold(mutableListOf("")) { tooltips, word -> override fun getValue(thisRef: DelegatingConfigGroup, property: KProperty<*>) =
value ?: forgeValue.get().apply { value = this }
}
fun String.translate() = I18n.get(this).asText()
fun String.translateTooltip(lineLength: Int = MAX_LINE_LEN) =
I18n.get("$this.tooltip").splitToSequence(" ").fold(mutableListOf("")) { tooltips, word ->
if (tooltips.last().length + word.length < lineLength) { if (tooltips.last().length + word.length < lineLength) {
tooltips[tooltips.lastIndex] += "$word " tooltips[tooltips.lastIndex] += "$word "
} else { } else {
tooltips.add("$word ") tooltips.add("$word ")
} }
tooltips tooltips
}.map { it.trim() }.toTypedArray() }.map { it.trim().asText() }.toTypedArray()
}
fun boolean( fun boolean(
default: Boolean, default: Boolean,
langKey: (List<String>)->String = { it.joinToString(".") }, langKey: (List<String>)->String = { it.joinToString(".") },
valueOverride: (Boolean)->Boolean = { it } valueOverride: (Boolean)->Boolean = { it }
) = object : DelegatingConfigValueFactory<Boolean> { ) = object : DelegatingConfigValueFactory<Boolean> {
override fun createFiberNode(parent: ConfigNode, name: String) = ConfigValueBuilder(Boolean::class.java) override fun createForgeNode(builder: ForgeConfigSpec.Builder, name: String) =
.withName(name) builder.define(name, default)
.withParent(parent)
.withDefaultValue(default)
.build()
override fun createClothNode(node: ConfigValue<Boolean>, names: List<String>) = ConfigEntryBuilder.create() override fun createClothNode(prop: CachingConfigProperty<Boolean>, path: List<String>) = ConfigEntryBuilder.create()
.startBooleanToggle(textify(langKey(names).translate()), node.value!!) .startBooleanToggle(langKey(path).translate(), prop.forgeValue.get())
.setTooltip(langKey(names).let { if (I18n.hasTranslation("$it.tooltip")) Optional.of(textify(it.translateTooltip())) else Optional.empty() }) .setTooltip(langKey(path).let { if (I18n.exists("$it.tooltip")) Optional.of(it.translateTooltip()) else Optional.empty() })
.setSaveConsumer { node.value = valueOverride(it) } .setSaveConsumer { prop.forgeValue.set(valueOverride(it)); prop.value = null }
.build() .build()
} }
fun integer( fun integer(
default: Int, min: Int, max: Int, default: Int = 0, min: Int = 0, max: Int,
langKey: (List<String>)->String = { it.joinToString(".") }, langKey: (List<String>)->String = { it.joinToString(".") },
valueOverride: (Int)->Int = { it } valueOverride: (Int)->Int = { it }
) = object : DelegatingConfigValueFactory<Int> { ) = object : DelegatingConfigValueFactory<Int> {
override fun createFiberNode(parent: ConfigNode, name: String) = ConfigValueBuilder(Int::class.java) override fun createForgeNode(builder: ForgeConfigSpec.Builder, name: String) =
.withName(name) builder.defineInRange(name, default, min, max)
.withParent(parent)
.withDefaultValue(default)
.constraints().minNumerical(min).maxNumerical(max).finish()
.build()
override fun createClothNode(node: ConfigValue<Int>, names: List<String>) = ConfigEntryBuilder.create() override fun createClothNode(prop: CachingConfigProperty<Int>, path: List<String>) = ConfigEntryBuilder.create()
.startIntField(textify(langKey(names).translate()), node.value!!) .startIntField(langKey(path).translate(), prop.forgeValue.get())
.setTooltip(langKey(names).let { if (I18n.hasTranslation("$it.tooltip")) Optional.of(textify(it.translateTooltip())) else Optional.empty() }) .setTooltip(langKey(path).let { if (I18n.exists("$it.tooltip")) Optional.of(it.translateTooltip()) else Optional.empty() })
.setMin(min).setMax(max) .setMin(min).setMax(max)
.setSaveConsumer { node.value = valueOverride(it) } .setSaveConsumer { prop.forgeValue.set(valueOverride(it)); prop.value = null }
.build() .build()
} }
fun double( fun double(
default: Double, min: Double, max: Double, default: Double = 0.0, min: Double = 0.0, max: Double = 1.0,
langKey: (List<String>)->String = { it.joinToString(".") }, langKey: (List<String>)->String = { it.joinToString(".") },
valueOverride: (Double)->Double = { it } valueOverride: (Double)->Double = { it }
) = object : DelegatingConfigValueFactory<Double> { ) = object : DelegatingConfigValueFactory<Double> {
override fun createFiberNode(parent: ConfigNode, name: String) = ConfigValueBuilder(Double::class.java) override fun createForgeNode(builder: ForgeConfigSpec.Builder, name: String) =
.withName(name) builder.defineInRange(name, default, min, max)
.withParent(parent)
.withDefaultValue(default)
.constraints().minNumerical(min).maxNumerical(max).finish()
.build()
override fun createClothNode(node: ConfigValue<Double>, names: List<String>) = ConfigEntryBuilder.create() override fun createClothNode(prop: CachingConfigProperty<Double>, path: List<String>) = ConfigEntryBuilder.create()
.startDoubleField(textify(langKey(names).translate()), node.value!!) .startDoubleField(langKey(path).translate(), prop.forgeValue.get())
.setTooltip(langKey(names).let { if (I18n.hasTranslation("$it.tooltip")) Optional.of(textify(it.translateTooltip())) else Optional.empty() }) .setTooltip(langKey(path).let { if (I18n.exists("$it.tooltip")) Optional.of(it.translateTooltip()) else Optional.empty() })
.setMin(min).setMax(max) .setMin(min).setMax(max)
.setSaveConsumer { node.value = valueOverride(it) } .setSaveConsumer { prop.forgeValue.set(valueOverride(it)); prop.value = null }
.build() .build()
} }
val recurring = { names: List<String> -> "${names.first()}.${names.last()}" } val recurring = { path: List<String> -> "${path.first()}.${path.last()}" }
fun fakeCategory(name: String) = { names: List<String> -> fun fakeCategory(name: String) = { names: List<String> ->
(listOf(names.first(), name) + names.drop(1)).joinToString(".") (listOf(names.first(), name) + names.drop(1)).joinToString(".")
} }

View File

@@ -1,158 +1,158 @@
package mods.betterfoliage.config package mods.betterfoliage.config
import me.zeroeightsix.fiber.tree.ConfigNode import mods.betterfoliage.BetterFoliageMod
import java.util.* import java.util.Random
fun featureEnable(default: Boolean = true) = boolean(default, langKey = recurring)
val Config get() = BetterFoliageMod.config
abstract class PopulationConfigGroup : DelegatingConfigGroup() {
abstract val enabled: Boolean
abstract val population: Int
interface PopulationConfigData {
val enabled: Boolean
val population: Int
fun enabled(random: Random) = random.nextInt(64) < population && enabled fun enabled(random: Random) = random.nextInt(64) < population && enabled
} }
fun population(default: Int) = integer(default, min = 0, max = 64, langKey = recurring) class MainConfig : DelegatingConfigGroup() {
val enabled by boolean(true, langKey = { "betterfoliage.global.enabled" })
val nVidia by boolean(false)
class MainConfig : DelegatingConfigGroup(ConfigNode("root", null)) { val leaves by subNode(::LeavesConfig)
val shortGrass by subNode(::ShortGrassConfig)
val enabled by boolean(true, langKey = fakeCategory("global")) val connectedGrass by subNode(::ConnectedGrassConfig)
val nVidia by boolean(true, langKey = fakeCategory("global")) val roundLogs by subNode(::RoundLogConfig)
val cactus by subNode(::CactusConfig)
val leaves by subNode { LeavesConfig(it) } val lilypad by subNode(::LilypadConfig)
val shortGrass by subNode { ShortGrassConfig(it) } val reed by subNode(::ReedConfig)
val connectedGrass by subNode { ConnectedGrassConfig(it) } val algae by subNode(::AlgaeConfig)
val roundLogs by subNode { RoundLogConfig(it) } val coral by subNode(::CoralConfig)
val cactus by subNode { CactusConfig(it) } val netherrack by subNode(::NetherrackConfig)
val lilypad by subNode { LilypadConfig(it) } val fallingLeaves by subNode(::FallingLeavesConfig)
val reed by subNode { ReedConfig(it) } val risingSoul by subNode(::RisingSoulConfig)
val algae by subNode { AlgaeConfig(it) }
val coral by subNode { CoralConfig(it) }
val netherrack by subNode { NetherrackConfig(it) }
val fallingLeaves by subNode { FallingLeavesConfig(it) }
val risingSoul by subNode { RisingSoulConfig(it) }
} }
class LeavesConfig(node: ConfigNode) : DelegatingConfigGroup(node) { class LeavesConfig : DelegatingConfigGroup() {
val enabled by boolean(true, langKey = recurring) val enabled by featureEnable()
val snowEnabled by boolean(true) val snowEnabled by boolean(true)
val hOffset by double(max=0.4, default=0.2, langKey = recurring)
val vOffset by double(max=0.4, default=0.1, langKey = recurring)
val size by double(min=0.75, max=2.5, default=1.4, langKey = recurring)
val dense by boolean(false) val dense by boolean(false)
val hideInternal by boolean(true) val hideInternal by boolean(true)
val saturationThreshold by double(default=0.1, langKey = recurring)
val hOffset by double(0.2, min = 0.0, max = 0.4, langKey = recurring)
val vOffset by double(0.1, min = 0.0, max = 0.4, langKey = recurring)
val size by double(1.4, min = 0.75, max = 2.5, langKey = recurring)
val shaderWind by boolean(true, langKey = recurring)
val saturationThreshold by double(0.1, min = 0.0, max = 1.0, langKey = recurring)
} }
class ShortGrassConfig(node: ConfigNode) : DelegatingConfigGroup(node), PopulationConfigData { class ShortGrassConfig : PopulationConfigGroup() {
override val enabled by boolean(true, langKey = recurring) override val enabled by featureEnable()
val grassEnabled by boolean(true)
val myceliumEnabled by boolean(true) val myceliumEnabled by boolean(true)
val snowEnabled by boolean(true) val snowEnabled by boolean(true)
val hOffset by double(0.2, min = 0.0, max = 0.4, langKey = recurring) val hOffset by double(max=0.4, default=0.2, langKey = recurring)
val heightMin by double(0.6, min = 0.1, max = 2.5, langKey = recurring) val heightMin by double(min=0.1, max=2.5, default=0.6, langKey = recurring)
val heightMax by double(0.6, min = 0.1, max = 2.5, langKey = recurring) { it.coerceAtLeast(heightMin) } val heightMax by double(min=0.1, max=2.5, default=0.8, langKey = recurring)
val size by double(1.0, min = 0.5, max = 1.5, langKey = recurring) val size by double(min=0.5, max=1.5, default=1.0, langKey = recurring)
override val population by population(64) override val population by integer(max=64, default=64, langKey = recurring)
val useGenerated by boolean(false) val useGenerated by boolean(false)
val shaderWind by boolean(true, langKey = recurring) val shaderWind by boolean(true, langKey = recurring)
val saturationThreshold by double(0.1, min = 0.0, max = 1.0, langKey = recurring) val saturationThreshold by double(default=0.1, langKey = recurring)
} }
class ConnectedGrassConfig(node: ConfigNode) : DelegatingConfigGroup(node) { class ConnectedGrassConfig : DelegatingConfigGroup() {
val enabled by boolean(true, langKey = recurring) val enabled by boolean(true)
val snowEnabled by boolean(true) val snowEnabled by boolean(false)
} }
class RoundLogConfig(node: ConfigNode) : DelegatingConfigGroup(node) { class RoundLogConfig : DelegatingConfigGroup() {
val enabled by boolean(true, langKey = recurring) val enabled by featureEnable()
val plantsOnly by boolean(true)
val defaultY by boolean(false) val radiusSmall by double(max=0.5, default=0.25)
val radiusLarge by double(max=0.5, default=0.44)
val dimming by double(default = 0.7)
val connectSolids by boolean(false) val connectSolids by boolean(false)
val lenientConnect by boolean(true) val lenientConnect by boolean(true)
val connectPerpendicular by boolean(true) val connectPerpendicular by boolean(true)
val connectGrass by boolean(true) val connectGrass by boolean(true)
val defaultY by boolean(false)
val radiusSmall by double(0.25, min = 0.0, max = 0.5) val zProtection by double(min = 0.9, default = 0.99)
val radiusLarge by double(0.44, min = 0.0, max = 0.5) { it.coerceAtLeast(radiusSmall) }
val dimming by double(0.7, min = 0.0, max = 1.0)
val zProtection by double(0.99, min = 0.9, max = 1.0)
} }
class CactusConfig(node: ConfigNode) : DelegatingConfigGroup(node) { class CactusConfig : DelegatingConfigGroup() {
val enabled by boolean(true, langKey = recurring) val enabled by featureEnable()
val size by double(1.3, min = 1.0, max = 2.0, langKey = recurring) val size by double(min=0.5, max=1.5, default=0.8, langKey = recurring)
val sizeVariation by double(0.1, min = 0.0, max = 0.5) val sizeVariation by double(max=0.5, default=0.1)
val hOffset by double(0.1, min = 0.0, max = 0.5, langKey = recurring) val hOffset by double(max=0.5, default=0.1, langKey = recurring)
} }
class LilypadConfig(node: ConfigNode) : DelegatingConfigGroup(node), PopulationConfigData { class LilypadConfig : PopulationConfigGroup() {
override val enabled by boolean(true, langKey = recurring) override val enabled by featureEnable()
val hOffset by double(0.1, min = 0.0, max = 0.25, langKey = recurring) val hOffset by double(max=0.25, default=0.1, langKey = recurring)
override val population by population(16) override val population by integer(max=64, default=16, min=0, langKey = recurring)
val shaderWind by boolean(true, langKey = recurring) val shaderWind by boolean(true, langKey = recurring)
} }
class ReedConfig(node: ConfigNode) : DelegatingConfigGroup(node), PopulationConfigData { class ReedConfig : PopulationConfigGroup() {
override val enabled by boolean(true, langKey = recurring) override val enabled by featureEnable()
val hOffset by double(0.2, min = 0.0, max = 0.4, langKey = recurring) val hOffset by double(max=0.4, default=0.2, langKey = recurring)
val heightMin by double(1.7, min = 1.5, max = 3.0, langKey = recurring) val heightMin by double(min=1.5, max=3.5, default=1.7, langKey = recurring)
val heightMax by double(2.2, min = 1.5, max = 3.0, langKey = recurring) { it.coerceAtLeast(heightMin) } val heightMax by double(min=1.5, max=3.5, default=2.2, langKey = recurring)
override val population by population(32) override val population by integer(max=64, default=32, langKey = recurring)
val minBiomeTemp by double(0.4, min = 0.0, max = 2.0) val minBiomeTemp by double(default=0.4)
val minBiomeRainfall by double(0.4, min = 0.0, max = 1.0) val minBiomeRainfall by double(default=0.4)
val shaderWind by boolean(true, langKey = recurring) val shaderWind by boolean(true, langKey = recurring)
} }
class AlgaeConfig(node: ConfigNode) : DelegatingConfigGroup(node), PopulationConfigData { class AlgaeConfig : PopulationConfigGroup() {
override val enabled by boolean(true, langKey = recurring) override val enabled by featureEnable()
val hOffset by double(0.1, min = 0.0, max = 0.4, langKey = recurring) val hOffset by double(max=0.25, default=0.1, langKey = recurring)
val size by double(1.0, min = 0.5, max = 1.5, langKey = recurring) val size by double(min=0.5, max=1.5, default=1.0, langKey = recurring)
val heightMin by double(0.5, min = 0.1, max = 1.0, langKey = recurring) val heightMin by double(min=0.1, max=1.5, default=0.5, langKey = recurring)
val heightMax by double(0.5, min = 0.1, max = 1.0, langKey = recurring) { it.coerceAtLeast(heightMin) } val heightMax by double(min=0.1, max=1.5, default=1.0, langKey = recurring)
override val population by population(48) override val population by integer(max=64, default=48, langKey = recurring)
val shaderWind by boolean(true, langKey = recurring) val shaderWind by boolean(true, langKey = recurring)
} }
class CoralConfig(node: ConfigNode) : DelegatingConfigGroup(node), PopulationConfigData { class CoralConfig : PopulationConfigGroup() {
override val enabled by boolean(true, langKey = recurring) override val enabled by featureEnable()
val shallowWater by boolean(false) val shallowWater by boolean(false)
val hOffset by double(0.2, min = 0.0, max = 0.4, langKey = recurring) val hOffset by double(max=0.4, default=0.2, langKey = recurring)
val vOffset by double(0.1, min = 0.0, max = 0.4, langKey = recurring) val vOffset by double(max=0.4, default=0.1, langKey = recurring)
val size by double(0.7, min = 0.5, max = 1.5, langKey = recurring) val size by double(min=0.5, max=1.5, default=0.7, langKey = recurring)
val crustSize by double(1.4, min = 0.5, max = 1.5) val crustSize by double(min=0.5, max=1.5, default=1.4)
val chance by integer(32, min = 0, max = 64) val chance by integer(max=64, default=32)
override val population by population(48) override val population by integer(max=64, default=48, langKey = recurring)
} }
class NetherrackConfig(node: ConfigNode) : DelegatingConfigGroup(node) { class NetherrackConfig : DelegatingConfigGroup() {
val enabled by boolean(true, langKey = recurring) val enabled by featureEnable()
val hOffset by double(0.2, min = 0.0, max = 0.4, langKey = recurring) val hOffset by double(max=0.4, default=0.2, langKey = recurring)
val size by double(1.0, min = 0.5, max = 1.5, langKey = recurring) val heightMin by double(min=0.1, max=1.5, default=0.6, langKey = recurring)
val heightMin by double(0.6, min = 0.5, max = 1.5, langKey = recurring) val heightMax by double(min=0.1, max=1.5, default=0.8, langKey = recurring)
val heightMax by double(0.8, min = 0.5, max = 1.5, langKey = recurring) { it.coerceAtLeast(heightMin) } val size by double(min=0.5, max=1.5, default=1.0, langKey = recurring)
} }
class FallingLeavesConfig(node: ConfigNode) : DelegatingConfigGroup(node) { class FallingLeavesConfig : DelegatingConfigGroup() {
val enabled by boolean(true, langKey = recurring) val enabled by featureEnable()
val opacityHack by boolean(false) val speed by double(min=0.01, max=0.15, default=0.05)
val speed by double(0.05, min = 0.01, max = 0.15) val windStrength by double(min=0.1, max=2.0, default=0.5)
val windStrength by double(0.5, min = 0.1, max = 2.0) val stormStrength by double(min=0.1, max=2.0, default=0.8)
val stormStrength by double(0.8, min = 0.1, max = 2.0) { it.coerceAtLeast(windStrength) } val size by double(min=0.25, max=1.5, default=0.75, langKey = recurring)
val size by double(0.75, min = 0.25, max = 1.5) val chance by double(min=0.001, max=1.0, default=0.02)
val chance by double(0.05, min = 0.001, max = 1.0) val perturb by double(min=0.01, max=1.0, default=0.25)
val perturb by double(0.25, min = 0.01, max = 1.0) val lifetime by double(min=1.0, max=15.0, default=5.0)
val lifetime by double(7.5, min = 1.0, max = 15.0) val opacityHack by boolean(true)
} }
class RisingSoulConfig(node: ConfigNode) : DelegatingConfigGroup(node) { class RisingSoulConfig : DelegatingConfigGroup() {
val enabled by boolean(true, langKey = recurring) val enabled by featureEnable()
val chance by double(0.02, min = 0.001, max = 1.0) val chance by double(min=0.001, max=1.0, default=0.02)
val perturb by double(0.05, min = 0.01, max = 0.25) val perturb by double(min=0.01, max=0.25, default=0.05)
val headSize by double(1.0, min = 0.25, max = 1.5) val headSize by double(min=0.25, max=1.5, default=1.0)
val trailSize by double(0.75, min = 0.25, max = 1.5) val trailSize by double(min=0.25, max=1.5, default=0.75)
val opacity by double(0.5, min = 0.05, max = 1.0) val opacity by double(min=0.05, max=1.0, default=0.5)
val sizeDecay by double(0.97, min = 0.5, max = 1.0) val sizeDecay by double(min=0.5, max=1.0, default=0.97)
val opacityDecay by double(0.97, min = 0.5, max = 1.0) val opacityDecay by double(min=0.5, max=1.0, default=0.97)
val lifetime by double(4.0, min = 1.0, max = 15.0) val lifetime by double(min=1.0, max=15.0, default=4.0)
val trailLength by integer(48, min = 2, max = 128) val trailLength by integer(min=2, max=128, default=48)
val trailDensity by integer(3, min = 1, max = 16) val trailDensity by integer(min=1, max=16, default=3)
} }

View File

@@ -1,13 +1,20 @@
package mods.betterfoliage.config package mods.betterfoliage.config
import net.minecraft.block.BlockState
import net.minecraft.block.Blocks import net.minecraft.block.Blocks
import net.minecraft.block.Material import net.minecraft.block.material.Material
import net.minecraft.world.biome.Biome import net.minecraft.world.biome.Biome
val CACTUS_BLOCKS = listOf(Blocks.CACTUS) val CACTUS_BLOCKS = listOf(Blocks.CACTUS)
val DIRT_BLOCKS = listOf(Blocks.DIRT, Blocks.COARSE_DIRT, Blocks.PODZOL) val DIRT_BLOCKS = listOf(Blocks.DIRT, Blocks.COARSE_DIRT, Blocks.PODZOL)
val SAND_BLOCKS = listOf(Blocks.SAND, Blocks.RED_SAND) val SAND_BLOCKS = listOf(Blocks.SAND, Blocks.RED_SAND)
val NETHERRACK_BLOCKS = listOf(Blocks.NETHERRACK) val NETHERRACK_BLOCKS = listOf(Blocks.NETHERRACK)
val LILYPAD_BLOCKS = listOf(Blocks.LILY_PAD)
val MYCELIUM_BLOCKS = listOf(Blocks.MYCELIUM)
val SALTWATER_BIOMES = listOf(Biome.Category.BEACH, Biome.Category.OCEAN) val SALTWATER_BIOMES = listOf(Biome.Category.BEACH, Biome.Category.OCEAN)
val SNOW_MATERIALS = listOf(Material.SNOW_BLOCK)
val SNOW_MATERIALS = listOf(Material.TOP_SNOW, Material.SNOW)
val BlockState.isSnow: Boolean get() = material in SNOW_MATERIALS
val ACCEPTED_ROUND_LOG_MATERIALS = listOf(Material.WOOD, Material.GRASS)

View File

@@ -1,31 +0,0 @@
package mods.betterfoliage.integration
import io.github.prospector.modmenu.api.ModMenuApi
import me.shedaniel.clothconfig2.api.ConfigBuilder
import me.zeroeightsix.fiber.JanksonSettings
import mods.betterfoliage.BetterFoliage
import mods.betterfoliage.resource.discovery.BakeWrapperManager
import net.minecraft.client.MinecraftClient
import net.minecraft.client.gui.screen.Screen
import net.minecraft.client.resource.language.I18n
import net.minecraft.text.LiteralText
import java.util.function.Function
object ModMenu : ModMenuApi {
override fun getModId() = BetterFoliage.MOD_ID
override fun getConfigScreenFactory() = Function { screen: Screen ->
val builder = ConfigBuilder.create()
.setParentScreen(screen)
.setTitle(LiteralText(I18n.translate("betterfoliage.title")))
BetterFoliage.config.createClothNode(listOf("betterfoliage")).value.forEach { rootOption ->
builder.getOrCreateCategory(LiteralText("main")).addEntry(rootOption)
}
builder.savingRunnable = Runnable {
JanksonSettings().serialize(BetterFoliage.config.fiberNode, BetterFoliage.configFile.outputStream(), false)
BakeWrapperManager.invalidate()
MinecraftClient.getInstance().worldRenderer.reload()
}
builder.build()
}
}

View File

@@ -0,0 +1,54 @@
package mods.betterfoliage.integration
import mods.betterfoliage.chunk.BlockCtx
import mods.betterfoliage.util.ThreadLocalDelegate
import mods.betterfoliage.util.allAvailable
import mods.betterfoliage.util.reflectField
import mods.octarinecore.BlockPos
import mods.octarinecore.BlockState
import mods.octarinecore.CustomColors
import mods.octarinecore.RenderEnv
import net.minecraft.block.BlockState
import net.minecraft.client.Minecraft
import net.minecraft.client.renderer.model.BakedQuad
import net.minecraft.util.Direction.UP
import net.minecraft.util.math.BlockPos
import net.minecraft.world.level.ColorResolver
import org.apache.logging.log4j.Level.INFO
import org.apache.logging.log4j.LogManager
/**
* Integration for OptiFine custom block colors.
*/
@Suppress("UNCHECKED_CAST")
object OptifineCustomColors {
val logger = LogManager.getLogger(this)
val isColorAvailable = allAvailable(CustomColors, CustomColors.getColorMultiplier)
init {
logger.log(INFO, "Optifine custom color support is ${if (isColorAvailable) "enabled" else "disabled" }")
}
val renderEnv by ThreadLocalDelegate { OptifineRenderEnv() }
val fakeQuad = BakedQuad(IntArray(0), 1, UP, null, true)
fun getBlockColor(ctx: BlockCtx, resolver: ColorResolver): Int {
val ofColor = if (isColorAvailable && Minecraft.getInstance().options.reflectField<Boolean>("ofCustomColors") == true) {
renderEnv.reset(ctx.state, ctx.pos)
CustomColors.getColorMultiplier.invokeStatic(fakeQuad, ctx.state, ctx.world, ctx.pos, renderEnv.wrapped) as? Int
} else null
return if (ofColor == null || ofColor == -1) ctx.color(resolver) else ofColor
}
}
class OptifineRenderEnv {
val wrapped: Any = RenderEnv.element!!.getDeclaredConstructor(BlockState.element, BlockPos.element).let {
it.isAccessible = true
it.newInstance(null, null)
}
fun reset(state: BlockState, pos: BlockPos) {
RenderEnv.reset.invoke(wrapped, state, pos)
}
}

View File

@@ -1,139 +0,0 @@
package mods.betterfoliage.integration
object IC2RubberIntegration {
// val BlockRubWood = ClassRefOld<Any>("ic2.core.block.BlockRubWood")
init {
// if (ModList.get().isLoaded("ic2") && allAvailable(BlockRubWood)) {
// BetterFoliage.log(Level.INFO, "IC2 rubber support initialized")
// LogRegistry.registries.add(IC2LogDiscovery)
// BetterFoliage.blockSprites.providers.add(IC2LogDiscovery)
// }
}
}
object TechRebornRubberIntegration {
// val BlockRubberLog = ClassRefOld<Any>("techreborn.blocks.BlockRubberLog")
init {
// if (ModList.get().isLoaded("techreborn") && allAvailable(BlockRubberLog)) {
// BetterFoliage.log(Level.INFO, "TechReborn rubber support initialized")
// LogRegistry.registries.add(TechRebornLogDiscovery)
// BetterFoliage.blockSprites.providers.add(TechRebornLogDiscovery)
// }
}
}
/*
class RubberLogInfo(
axis: Axis?,
val spotDir: Direction,
topTexture: Sprite,
bottomTexture: Sprite,
val spotTexture: Sprite,
sideTextures: List<Sprite>
) : SimpleColumnInfo(axis, topTexture, bottomTexture, sideTextures) {
override val side: QuadIconResolver = { ctx: CombinedContext, idx: Int, quad: Quad ->
val worldFace = (if ((idx and 1) == 0) SOUTH else EAST).rotate(ctx.modelRotation)
if (worldFace == spotDir) spotTexture else {
val sideIdx = if (this.sideTextures.size > 1) (ctx.semiRandom(1) + dirToIdx[worldFace.ordinal]) % this.sideTextures.size else 0
this.sideTextures[sideIdx]
}
}
}
object IC2LogDiscovery : ModelDiscovery<ColumnTextureInfo>() {
override val logger = BetterFoliage.logDetail
override fun processModel(ctx: ModelDiscoveryContext, atlas: AtlasFuture): CompletableFuture<ColumnTextureInfo>? {
// check for proper block class, existence of ModelBlock, and "state" blockstate property
if (!IC2RubberIntegration.BlockRubWood.isInstance(ctx.state.block)) return null
val blockLoc = ctx.models.firstOrNull() as Pair<JsonUnbakedModel, Identifier> ?: return null
val type = ctx.state.entries.entries.find { it.key.getName() == "state" }?.value?.toString() ?: return null
// logs with no rubber spot
if (blockLoc.derivesFrom(Identifier("block/cube_column"))) {
val axis = when(type) {
"plain_y" -> Axis.Y
"plain_x" -> Axis.X
"plain_z" -> Axis.Z
else -> null
}
val textureNames = listOf("end", "side").map { blockLoc.first.resolveTexture(it) }
if (textureNames.any { it == "missingno" }) return null
log("IC2LogSupport: block state ${ctx.state.toString()}")
log("IC2LogSupport: axis=$axis, end=${textureNames[0]}, side=${textureNames[1]}")
val endSprite = atlas.sprite(textureNames[0])
val sideSprite = atlas.sprite(textureNames[1])
return atlas.mapAfter {
SimpleColumnInfo(axis, endSprite.get(), endSprite.get(), listOf(sideSprite.get()))
}
}
// logs with rubber spot
val spotDir = when(type) {
"dry_north", "wet_north" -> NORTH
"dry_south", "wet_south" -> SOUTH
"dry_west", "wet_west" -> WEST
"dry_east", "wet_east" -> EAST
else -> null
}
val textureNames = listOf("up", "down", "north", "south").map { blockLoc.first.resolveTexture(it) }
if (textureNames.any { it == "missingno" }) return null
log("IC2LogSupport: block state ${ctx.state.toString()}")
log("IC2LogSupport: spotDir=$spotDir, up=${textureNames[0]}, down=${textureNames[1]}, side=${textureNames[2]}, spot=${textureNames[3]}")
val upSprite = atlas.sprite(textureNames[0])
val downSprite = atlas.sprite(textureNames[1])
val sideSprite = atlas.sprite(textureNames[2])
val spotSprite = atlas.sprite(textureNames[3])
return if (spotDir != null) atlas.mapAfter {
RubberLogInfo(Axis.Y, spotDir, upSprite.get(), downSprite.get(), spotSprite.get(), listOf(sideSprite.get()))
} else atlas.mapAfter {
SimpleColumnInfo(Axis.Y, upSprite.get(), downSprite.get(), listOf(sideSprite.get()))
}
}
}
object TechRebornLogDiscovery : ModelDiscovery<ColumnTextureInfo>() {
override val logger = BetterFoliage.logDetail
override fun processModel(ctx: ModelDiscoveryContext, atlas: AtlasFuture): CompletableFuture<ColumnTextureInfo>? {
// check for proper block class, existence of ModelBlock
if (!TechRebornRubberIntegration.BlockRubberLog.isInstance(ctx.state.block)) return null
val blockLoc = ctx.models.map { it as? Pair<JsonUnbakedModel, Identifier> }.firstOrNull() ?: return null
val hasSap = ctx.state.entries.entries.find { it.key.getName() == "hassap" }?.value as? Boolean ?: return null
val sapSide = ctx.state.entries.entries.find { it.key.getName() == "sapside" }?.value as? Direction ?: return null
log("$logName: block state ${ctx.state}")
if (hasSap) {
val textureNames = listOf("end", "side", "sapside").map { blockLoc.first.resolveTexture(it) }
log("$logName: spotDir=$sapSide, end=${textureNames[0]}, side=${textureNames[2]}, spot=${textureNames[3]}")
if (textureNames.all { it != "missingno" }) {
val endSprite = atlas.sprite(textureNames[0])
val sideSprite = atlas.sprite(textureNames[1])
val sapSprite = atlas.sprite(textureNames[2])
return atlas.mapAfter {
RubberLogInfo(Axis.Y, sapSide, endSprite.get(), endSprite.get(), sapSprite.get(), listOf(sideSprite.get()))
}
}
} else {
val textureNames = listOf("end", "side").map { blockLoc.first.resolveTexture(it) }
log("$logName: end=${textureNames[0]}, side=${textureNames[1]}")
if (textureNames.all { it != "missingno" }) {
val endSprite = atlas.sprite(textureNames[0])
val sideSprite = atlas.sprite(textureNames[1])
return atlas.mapAfter {
SimpleColumnInfo(Axis.Y, endSprite.get(), endSprite.get(), listOf(sideSprite.get()))
}
}
}
return null
}
}
*/

View File

@@ -0,0 +1,95 @@
package mods.betterfoliage.integration
import mods.betterfoliage.render.pipeline.RenderCtxBase
import mods.betterfoliage.render.pipeline.RenderCtxVanilla
import mods.betterfoliage.resource.discovery.BakeWrapperManager
import mods.betterfoliage.util.HasLogger
import mods.betterfoliage.util.allAvailable
import mods.betterfoliage.util.get
import mods.betterfoliage.util.mapArray
import mods.octarinecore.*
import net.minecraft.block.BlockRenderType
import net.minecraft.block.BlockRenderType.MODEL
import net.minecraft.block.BlockState
import net.minecraft.block.Blocks
import net.minecraft.client.renderer.BufferBuilder
import net.minecraft.util.Direction
import net.minecraft.util.Direction.DOWN
import net.minecraft.util.Direction.EAST
import net.minecraft.util.Direction.NORTH
import net.minecraft.util.Direction.SOUTH
import net.minecraft.util.Direction.WEST
import net.minecraft.util.math.BlockPos
import net.minecraft.world.IBlockDisplayReader
import net.minecraftforge.client.model.pipeline.LightUtil
import org.apache.logging.log4j.Level.INFO
/**
* Integration for ShadersMod.
*/
object ShadersModIntegration : HasLogger() {
@JvmStatic val isEffectsAvailable = allAvailable(SVertexBuilder.pushState, SVertexBuilder.popState, BlockAliases.getAliasBlockId)
@JvmStatic val isDiffuseAvailable = allAvailable(Shaders.shaderPackLoaded, Shaders.blockLightLevel05, Shaders.blockLightLevel06, Shaders.blockLightLevel08)
@JvmStatic val defaultLeaves = Blocks.OAK_LEAVES.defaultBlockState()
@JvmStatic val defaultGrass = Blocks.GRASS.defaultBlockState()
@JvmStatic var diffuseShades = Direction.values().mapArray { LightUtil.diffuseLight(it) }
/**
* Called from transformed ShadersMod code.
* @see mods.betterfoliage.loader.BetterFoliageTransformer
*/
@JvmStatic fun getBlockStateOverride(state: BlockState, world: IBlockDisplayReader, pos: BlockPos): BlockState {
// if (LeafRegistry[state, world, pos] != null) return defaultLeaves
// if (BlockConfig.crops.matchesClass(state.block)) return defaultGrass
return state
}
init {
logger.log(INFO, "ShadersMod diffuse shading integration is ${if (isDiffuseAvailable) "enabled" else "disabled" }")
logger.log(INFO, "ShadersMod vertex shader integration is ${if (isEffectsAvailable) "enabled" else "disabled" }")
// Recalculate the diffsuse shading values used when resources are reloaded
if (isDiffuseAvailable) BakeWrapperManager.onInvalidate {
if (Shaders.shaderPackLoaded.getStatic()) {
diffuseShades = Direction.values().mapArray { face ->
when(face) {
DOWN -> Shaders.blockLightLevel05.getStatic()
WEST, EAST -> Shaders.blockLightLevel06.getStatic()
NORTH, SOUTH -> Shaders.blockLightLevel08.getStatic()
else -> LightUtil.diffuseLight(face)
}
}
} else {
diffuseShades = Direction.values().mapArray { LightUtil.diffuseLight(it) }
}
}
}
/** Quads rendered inside this block will use the given block entity data in shader programs. */
inline fun renderAs(buffer: BufferBuilder, state: BlockState, renderType: BlockRenderType, enabled: Boolean = true, func: ()->Unit) {
if (isEffectsAvailable && enabled) {
val aliasBlockId = BlockAliases.getAliasBlockId.invokeStatic(state)
val sVertexBuilder = buffer[BufferBuilder_sVertexBuilder]
SVertexBuilder.pushState.invoke(sVertexBuilder, aliasBlockId)
func()
SVertexBuilder.popState.invoke(sVertexBuilder)
} else {
func()
}
}
/** Quads rendered inside this block will behave as tallgrass blocks in shader programs. */
inline fun grass(ctx: RenderCtxBase, enabled: Boolean = true, func: ()->Unit) =
((ctx as? RenderCtxVanilla)?.buffer as? BufferBuilder)?.let { bufferBuilder ->
renderAs(bufferBuilder, defaultGrass, MODEL, enabled, func)
}
/** Quads rendered inside this block will behave as leaf blocks in shader programs. */
inline fun leaves(ctx: RenderCtxBase, enabled: Boolean = true, func: ()->Unit) =
((ctx as? RenderCtxVanilla)?.buffer as? BufferBuilder)?.let { bufferBuilder ->
renderAs(bufferBuilder, defaultLeaves, MODEL, enabled, func)
}
}

View File

@@ -0,0 +1,118 @@
package mods.betterfoliage.model
import mods.betterfoliage.render.pipeline.RenderCtxBase
import mods.betterfoliage.resource.discovery.ModelBakingContext
import mods.betterfoliage.resource.discovery.ModelBakingKey
import mods.betterfoliage.util.Double3
import mods.betterfoliage.util.HasLogger
import mods.betterfoliage.util.directionsAndNull
import mods.betterfoliage.util.mapArray
import net.minecraft.client.renderer.model.BakedQuad
import net.minecraft.client.renderer.model.IBakedModel
import net.minecraft.client.renderer.model.SimpleBakedModel
import net.minecraft.client.renderer.vertex.DefaultVertexFormats
import net.minecraft.client.renderer.vertex.VertexFormatElement
import net.minecraftforge.client.model.pipeline.BakedQuadBuilder
import java.util.Random
/**
* Hybrid baked quad implementation, carrying both baked and unbaked information.
* Used to do advanced vertex lighting without unbaking vertex data at lighting time.
*/
data class HalfBakedQuad(
val raw: Quad,
val baked: BakedQuad
)
open class HalfBakedSimpleModelWrapper(baseModel: SimpleBakedModel): IBakedModel by baseModel, SpecialRenderModel {
val baseQuads = baseModel.unbakeQuads()
override fun render(ctx: RenderCtxBase, noDecorations: Boolean) {
ctx.renderQuads(baseQuads)
}
}
open class HalfBakedSpecialWrapper(val baseModel: SpecialRenderModel): IBakedModel by baseModel, SpecialRenderModel {
override fun render(ctx: RenderCtxBase, noDecorations: Boolean) {
baseModel.render(ctx, noDecorations)
}
}
abstract class HalfBakedWrapperKey : ModelBakingKey, HasLogger() {
override fun bake(ctx: ModelBakingContext): IBakedModel? {
val baseModel = super.bake(ctx)
val halfBaked = when(baseModel) {
is SimpleBakedModel -> HalfBakedSimpleModelWrapper(baseModel)
else -> null
}
return if (halfBaked == null) baseModel else bake(ctx, halfBaked)
}
abstract fun bake(ctx: ModelBakingContext, wrapped: SpecialRenderModel): SpecialRenderModel
}
fun List<Quad>.bake(applyDiffuseLighting: Boolean) = map { quad ->
if (quad.sprite == null) throw IllegalStateException("Quad must have a texture assigned before baking")
val builder = BakedQuadBuilder(quad.sprite)
builder.setApplyDiffuseLighting(applyDiffuseLighting)
builder.setQuadOrientation(quad.face())
builder.setQuadTint(quad.colorIndex)
quad.verts.forEach { vertex ->
DefaultVertexFormats.BLOCK.elements.forEachIndexed { idx, element ->
when {
element.usage == VertexFormatElement.Usage.POSITION -> builder.put(idx,
(vertex.xyz.x + 0.5).toFloat(),
(vertex.xyz.y + 0.5).toFloat(),
(vertex.xyz.z + 0.5).toFloat(),
1.0f
)
// don't fill lightmap UV coords
element.usage == VertexFormatElement.Usage.UV && element.type == VertexFormatElement.Type.FLOAT -> builder.put(idx,
quad.sprite.u0 + (quad.sprite.u1 - quad.sprite.u0) * (vertex.uv.u + 0.5).toFloat(),
quad.sprite.v0 + (quad.sprite.v1 - quad.sprite.v0) * (vertex.uv.v + 0.5).toFloat(),
0.0f, 1.0f
)
element.usage == VertexFormatElement.Usage.COLOR -> builder.put(idx,
(vertex.color.red and 255).toFloat() / 255.0f,
(vertex.color.green and 255).toFloat() / 255.0f,
(vertex.color.blue and 255).toFloat() / 255.0f,
(vertex.color.alpha and 255).toFloat() / 255.0f
)
element.usage == VertexFormatElement.Usage.NORMAL -> builder.put(idx,
(vertex.normal ?: quad.normal).x.toFloat(),
(vertex.normal ?: quad.normal).y.toFloat(),
(vertex.normal ?: quad.normal).z.toFloat(),
0.0f
)
else -> builder.put(idx)
}
}
}
HalfBakedQuad(quad, builder.build())
}
fun Array<List<Quad>>.bake(applyDiffuseLighting: Boolean) = mapArray { it.bake(applyDiffuseLighting) }
fun BakedQuad.unbake(): HalfBakedQuad {
val size = DefaultVertexFormats.BLOCK.integerSize
val verts = Array(4) { vIdx ->
val x = java.lang.Float.intBitsToFloat(vertices[vIdx * size + 0]) - 0.5f
val y = java.lang.Float.intBitsToFloat(vertices[vIdx * size + 1]) - 0.5f
val z = java.lang.Float.intBitsToFloat(vertices[vIdx * size + 2]) - 0.5f
val color = vertices[vIdx * size + 3]
val u = java.lang.Float.intBitsToFloat(vertices[vIdx * size + 4])
val v = java.lang.Float.intBitsToFloat(vertices[vIdx * size + 5])
Vertex(Double3(x, y, z), UV(u.toDouble(), v.toDouble()), Color(color))
}
val unbaked = Quad(
verts[0], verts[1], verts[2], verts[3],
colorIndex = if (isTinted) tintIndex else -1,
face = direction
)
return HalfBakedQuad(unbaked, this)
}
fun SimpleBakedModel.unbakeQuads() = directionsAndNull.flatMap { face ->
getQuads(null, face, Random()).map { it.unbake() }
}

View File

@@ -1,117 +0,0 @@
package mods.betterfoliage.model
import mods.betterfoliage.BakedQuad_sprite
import mods.betterfoliage.VertexFormat_offsets
import mods.betterfoliage.util.Double3
import mods.betterfoliage.util.allDirections
import mods.betterfoliage.util.findFirst
import mods.betterfoliage.util.get
import net.fabricmc.fabric.api.renderer.v1.material.BlendMode
import net.minecraft.block.BlockState
import net.minecraft.client.render.VertexFormat
import net.minecraft.client.render.VertexFormatElement
import net.minecraft.client.render.VertexFormatElement.Format.FLOAT
import net.minecraft.client.render.VertexFormatElement.Format.UBYTE
import net.minecraft.client.render.VertexFormatElement.Type.*
import net.minecraft.client.render.VertexFormatElement.Type.UV
import net.minecraft.client.render.VertexFormats
import net.minecraft.client.render.model.BakedModel
import net.minecraft.client.render.model.BakedQuad
import net.minecraft.client.render.model.BasicBakedModel
import net.minecraft.util.math.Direction
import java.util.*
interface BakedModelConverter {
/**
* Convert baked model. Returns null if conversion unsuccessful (wrong input type).
* @param model Input model
* @param converter Converter to use for converting nested models.
*/
fun convert(model: BakedModel): BakedModel?
companion object {
fun of(func: (BakedModel)->BakedModel?) = object : BakedModelConverter {
override fun convert(model: BakedModel) = func(model)
}
val identity = of { model -> model }
}
}
/**
* Convert [BakedModel] using the provided list of [BakedModelConverter]s (in order).
* If all converters fail, gives the original model back.
*/
fun List<BakedModelConverter>.convert(model: BakedModel) = object : BakedModelConverter {
val converters = this@convert + BakedModelConverter.identity
override fun convert(model: BakedModel) = converters.findFirst { it.convert(model) }
}.let { converterStack ->
// we are guaranteed a result here because of the identity converter
converterStack.convert(model)!!
}
/**
* Convert [BasicBakedModel] into one using fabric-rendering-api [Mesh] instead of the vanilla pipeline.
* @param blendMode Use the given [BlockRenderLayer] for the [Mesh]
* instead of the one declared by the corresponding [Block]
*/
fun meshifyStandard(model: BasicBakedModel, state: BlockState? = null, blendMode: BlendMode? = null) =
WrappedMeshModel.converter(state, blendModeOverride = blendMode).convert(model)!!
fun meshifySolid(model: BasicBakedModel) = meshifyStandard(model, null, BlendMode.SOLID)
fun meshifyCutoutMipped(model: BasicBakedModel) = meshifyStandard(model, null, BlendMode.CUTOUT_MIPPED)
/**
* Convert a vanilla [BakedModel] into intermediate [Quad]s
* Vertex normals not supported (yet)
* Vertex data elements not aligned to 32 bit boundaries not supported
*/
fun unbakeQuads(model: BakedModel, state: BlockState?, random: Random, unshade: Boolean): List<Quad> {
return (allDirections.toList() + null as Direction?).flatMap { face ->
model.getQuads(state, face, random).mapIndexed { qIdx, bakedQuad ->
var quad = Quad(Vertex(), Vertex(), Vertex(), Vertex(), face = face, colorIndex = bakedQuad.colorIndex, sprite = bakedQuad[BakedQuad_sprite])
val format = quadVertexFormat(bakedQuad)
val stride = format.vertexSizeInteger
format.getIntOffset(POSITION, FLOAT, 3)?.let { posOffset ->
quad = quad.transformVI { vertex, vIdx -> vertex.copy(xyz = Double3(
x = java.lang.Float.intBitsToFloat(bakedQuad.vertexData[vIdx * stride + posOffset + 0]).toDouble(),
y = java.lang.Float.intBitsToFloat(bakedQuad.vertexData[vIdx * stride + posOffset + 1]).toDouble(),
z = java.lang.Float.intBitsToFloat(bakedQuad.vertexData[vIdx * stride + posOffset + 2]).toDouble()
)) }
}
format.getIntOffset(COLOR, UBYTE, 4)?.let { colorOffset ->
quad = quad.transformVI { vertex, vIdx -> vertex.copy(
color = Color(bakedQuad.vertexData[vIdx * stride + colorOffset])
) }
}
format.getIntOffset(UV, FLOAT, 2, 0)?.let { uvOffset ->
quad = quad.transformVI { vertex, vIdx -> vertex.copy(uv = UV(
u = java.lang.Float.intBitsToFloat(bakedQuad.vertexData[vIdx * stride + uvOffset + 0]).toDouble(),
v = java.lang.Float.intBitsToFloat(bakedQuad.vertexData[vIdx * stride + uvOffset + 1]).toDouble()
)) }
}
quad = quad.transformV { it.copy(uv = it.uv.unbake(quad.sprite!!)) }.move(Double3(-0.5, -0.5, -0.5))
if (unshade) quad = quad.transformV { it.copy(color = it.color * (1.0f / Color.bakeShade(quad.face))) }
quad
}
}
}
/** Get the byte offset of the [VertexFormatElement] matching the given criteria */
fun VertexFormat.getByteOffset(type: VertexFormatElement.Type, format: VertexFormatElement.Format, count: Int, index: Int = 0): Int? {
elements.forEachIndexed { idx, element ->
if (element == VertexFormatElement(index, format, type, count))
return VertexFormat_offsets[this]!!.getInt(idx)
}
return null
}
/**
* Get the int (32 bit) offset of the [VertexFormatElement] matching the given criteria
* Returns null if the element is not properly aligned
*/
fun VertexFormat.getIntOffset(type: VertexFormatElement.Type, format: VertexFormatElement.Format, count: Int, index: Int = 0) =
getByteOffset(type, format, count, index)?.let { if (it % 4 == 0) it / 4 else null }
/** Function to determine [VertexFormat] used by [BakedQuad] */
var quadVertexFormat: (BakedQuad)->VertexFormat = { VertexFormats.POSITION_COLOR_TEXTURE_LIGHT_NORMAL }

View File

@@ -1,15 +1,17 @@
package mods.betterfoliage.model package mods.betterfoliage.model
import mods.betterfoliage.util.Atlas import mods.betterfoliage.util.Double3
import mods.betterfoliage.util.* import mods.betterfoliage.util.Rotation
import mods.betterfoliage.util.boxFaces
import mods.betterfoliage.util.get
import mods.betterfoliage.util.minmax import mods.betterfoliage.util.minmax
import net.fabricmc.fabric.api.renderer.v1.RendererAccess import mods.betterfoliage.util.nearestAngle
import net.fabricmc.fabric.api.renderer.v1.material.BlendMode import mods.betterfoliage.util.rotate
import net.fabricmc.fabric.api.renderer.v1.mesh.Mesh import mods.betterfoliage.util.times
import net.minecraft.client.texture.MissingSprite import mods.betterfoliage.util.vec
import net.minecraft.client.texture.Sprite import net.minecraft.client.renderer.texture.NativeImage
import net.minecraft.util.math.Direction import net.minecraft.client.renderer.texture.TextureAtlasSprite
import net.minecraft.util.math.Direction.* import net.minecraft.util.Direction
import java.lang.Math.max import java.lang.Math.max
import java.lang.Math.min import java.lang.Math.min
import java.util.Random import java.util.Random
@@ -19,7 +21,7 @@ import kotlin.math.sin
/** /**
* Vertex UV coordinates * Vertex UV coordinates
* *
* Zero-centered: sprite coordinates fall between (-0.5, 0.5) * Zero-centered: coordinates fall between (-0.5, 0.5) (inclusive)
*/ */
data class UV(val u: Double, val v: Double) { data class UV(val u: Double, val v: Double) {
companion object { companion object {
@@ -31,7 +33,7 @@ data class UV(val u: Double, val v: Double) {
val rotate: UV get() = UV(v, -u) val rotate: UV get() = UV(v, -u)
fun rotate(n: Int) = when(n % 4) { fun rotate(n: Int) = when (n % 4) {
0 -> copy() 0 -> copy()
1 -> UV(v, -u) 1 -> UV(v, -u)
2 -> UV(-u, -v) 2 -> UV(-u, -v)
@@ -42,42 +44,6 @@ data class UV(val u: Double, val v: Double) {
UV(u.minmax(minU, maxU), v.minmax(minV, maxV)) UV(u.minmax(minU, maxU), v.minmax(minV, maxV))
fun mirror(mirrorU: Boolean, mirrorV: Boolean) = UV(if (mirrorU) -u else u, if (mirrorV) -v else v) fun mirror(mirrorU: Boolean, mirrorV: Boolean) = UV(if (mirrorU) -u else u, if (mirrorV) -v else v)
fun unbake(sprite: Sprite) = UV(
(u - sprite.minU.toDouble()) / (sprite.maxU - sprite.minU).toDouble() - 0.5,
(v - sprite.minV.toDouble()) / (sprite.maxV - sprite.minV).toDouble() - 0.5
)
}
data class Color(val alpha: Int, val red: Int, val green: Int, val blue: Int) {
constructor(combined: Int) : this(combined shr 24 and 255, combined shr 16 and 255, combined shr 8 and 255, combined and 255)
val asInt get() = (alpha shl 24) or (red shl 16) or (green shl 8) or blue
operator fun times(f: Float) = Color(
alpha,
(f * red.toFloat()).toInt().coerceIn(0 until 256),
(f * green.toFloat()).toInt().coerceIn(0 until 256),
(f * blue.toFloat()).toInt().coerceIn(0 until 256)
)
companion object {
val white get() = Color(255, 255, 255, 255)
/** Amount of vanilla diffuse lighting applied to face quads */
fun bakeShade(dir: Direction?) = when(dir) {
DOWN -> 0.5f
NORTH, SOUTH -> 0.8f
EAST, WEST -> 0.6f
else -> 1.0f
}
}
}
data class HSB(var hue: Float, var saturation: Float, var brightness: Float) {
companion object {
fun fromColor(color: Int): HSB {
val hsbVals = java.awt.Color.RGBtoHSB((color shr 16) and 255, (color shr 8) and 255, color and 255, null)
return HSB(hsbVals[0], hsbVals[1], hsbVals[2])
}
}
val asColor: Int get() = java.awt.Color.HSBtoRGB(hue, saturation, brightness)
} }
/** /**
@@ -85,46 +51,95 @@ data class HSB(var hue: Float, var saturation: Float, var brightness: Float) {
* *
* @param[xyz] x, y, z coordinates * @param[xyz] x, y, z coordinates
* @param[uv] u, v coordinates * @param[uv] u, v coordinates
* @param[color] vertex color RGB components * @param[aoShader] [ModelLighter] instance to use with AO rendering
* @param[alpha] vertex color alpha component * @param[flatShader] [ModelLighter] instance to use with non-AO rendering
*/ */
data class Vertex(val xyz: Double3 = Double3(0.0, 0.0, 0.0), data class Vertex(
val uv: UV = UV(0.0, 0.0), val xyz: Double3 = Double3(0.0, 0.0, 0.0),
val color: Color = Color.white, val uv: UV = UV(0.0, 0.0),
val alpha: Int = 255, val color: Color = Color.white,
val normal: Double3? = null val normal: Double3? = null
) )
data class Color(val alpha: Int, val red: Int, val green: Int, val blue: Int) {
constructor(combined: Int) : this(
combined shr 24 and 255,
combined shr 16 and 255,
combined shr 8 and 255,
combined and 255
)
val asInt get() = (alpha shl 24) or (red shl 16) or (green shl 8) or blue
operator fun times(f: Float) = Color(
alpha,
(f * red.toFloat()).toInt().coerceIn(0 until 256),
(f * green.toFloat()).toInt().coerceIn(0 until 256),
(f * blue.toFloat()).toInt().coerceIn(0 until 256)
)
companion object {
val white get() = Color(255, 255, 255, 255)
}
}
data class HSB(var hue: Float, var saturation: Float, var brightness: Float) {
companion object {
/** Red is assumed to be LSB, see [NativeImage.PixelFormat.RGBA] */
fun fromColorRGBA(color: Int): HSB {
val hsbVals = java.awt.Color.RGBtoHSB(color and 255, (color shr 8) and 255, (color shr 16) and 255, null)
return HSB(hsbVals[0], hsbVals[1], hsbVals[2])
}
}
val asColor: Int get() = java.awt.Color.HSBtoRGB(hue, saturation, brightness)
}
/** /**
* Intermediate (fabric-renderer-api independent) representation of model quad * Intermediate representation of model quad
* Immutable, double-precision * Immutable, double-precision
* Zero-centered (both XYZ and UV) coordinates for simpler rotation/mirroring * Zero-centered (both XYZ and UV) coordinates for simpler rotation/mirroring
*/ */
data class Quad( data class Quad(
val v1: Vertex, val v2: Vertex, val v3: Vertex, val v4: Vertex, val v1: Vertex, val v2: Vertex, val v3: Vertex, val v4: Vertex,
val sprite: Sprite? = null, val sprite: TextureAtlasSprite? = null,
val colorIndex: Int = -1, val colorIndex: Int = -1,
val face: Direction? = null val face: Direction? = null
) { ) {
val verts = arrayOf(v1, v2, v3, v4) val verts = arrayOf(v1, v2, v3, v4)
inline fun transformV(trans: (Vertex)-> Vertex): Quad = transformVI { vertex, idx -> trans(vertex) } inline fun transformV(trans: (Vertex) -> Vertex): Quad = transformVI { vertex, idx -> trans(vertex) }
inline fun transformVI(trans: (Vertex, Int)-> Vertex): Quad = copy( inline fun transformVI(trans: (Vertex, Int) -> Vertex): Quad = copy(
v1 = trans(v1, 0), v2 = trans(v2, 1), v3 = trans(v3, 2), v4 = trans(v4, 3) v1 = trans(v1, 0), v2 = trans(v2, 1), v3 = trans(v3, 2), v4 = trans(v4, 3)
) )
val normal: Double3 get() = (v2.xyz - v1.xyz).cross(v4.xyz - v1.xyz).normalize val normal: Double3 get() = (v2.xyz - v1.xyz).cross(v4.xyz - v1.xyz).normalize
fun move(trans: Double3) = transformV { it.copy(xyz = it.xyz + trans) } fun move(trans: Double3) = transformV { it.copy(xyz = it.xyz + trans) }
fun move(trans: Pair<Double, Direction>) = move(Double3(trans.second) * trans.first) fun move(trans: Pair<Double, Direction>) = move(Double3(trans.second) * trans.first)
fun scale (scale: Double) = transformV { it.copy(xyz = it.xyz * scale) } fun scale(scale: Double) = transformV { it.copy(xyz = it.xyz * scale) }
fun scale (scale: Double3) = transformV { it.copy(xyz = Double3(it.xyz.x * scale.x, it.xyz.y * scale.y, it.xyz.z * scale.z)) } fun scale(scale: Double3) =
fun rotate(rot: Rotation) = transformV { it.copy(xyz = it.xyz.rotate(rot), normal = it.normal?.rotate(rot)) }.copy(face = face?.rotate(rot)) transformV { it.copy(xyz = Double3(it.xyz.x * scale.x, it.xyz.y * scale.y, it.xyz.z * scale.z)) }
fun rotateZ(angle: Double) = transformV { it.copy(
xyz = Double3(it.xyz.x * cos(angle) + it.xyz.z * sin(angle), it.xyz.y, it.xyz.z * cos(angle) - it.xyz.x * sin(angle)),
normal = it.normal?.let { normal-> Double3(normal.x * cos(angle) + normal.z * sin(angle), normal.y, normal.z * cos(angle) - normal.x * sin(angle)) }
) }
fun scaleUV (scale: Double) = transformV { it.copy(uv = UV(it.uv.u * scale, it.uv.v * scale)) } fun rotate(rot: Rotation) =
transformV { it.copy(xyz = it.xyz.rotate(rot), normal = it.normal?.rotate(rot)) }.copy(face = face?.rotate(rot))
fun rotateZ(angle: Double) = transformV {
it.copy(
xyz = Double3(
it.xyz.x * cos(angle) + it.xyz.z * sin(angle),
it.xyz.y,
it.xyz.z * cos(angle) - it.xyz.x * sin(angle)
),
normal = it.normal?.let { normal ->
Double3(
normal.x * cos(angle) + normal.z * sin(angle),
normal.y,
normal.z * cos(angle) - normal.x * sin(angle)
)
}
)
}
fun scaleUV(scale: Double) = transformV { it.copy(uv = UV(it.uv.u * scale, it.uv.v * scale)) }
fun rotateUV(n: Int) = transformV { it.copy(uv = it.uv.rotate(n)) } fun rotateUV(n: Int) = transformV { it.copy(uv = it.uv.rotate(n)) }
fun clampUV(minU: Double = -0.5, maxU: Double = 0.5, minV: Double = -0.5, maxV: Double = 0.5) = fun clampUV(minU: Double = -0.5, maxU: Double = 0.5, minV: Double = -0.5, maxV: Double = 0.5) =
transformV { it.copy(uv = it.uv.clamp(minU, maxU, minV, maxV)) } transformV { it.copy(uv = it.uv.clamp(minU, maxU, minV, maxV)) }
@@ -133,14 +148,16 @@ data class Quad(
.mirrorUV(canFlipU && random.nextBoolean(), canFlipV && random.nextBoolean()) .mirrorUV(canFlipU && random.nextBoolean(), canFlipV && random.nextBoolean())
.let { if (canRotate) it.rotateUV(random.nextInt(4)) else it } .let { if (canRotate) it.rotateUV(random.nextInt(4)) else it }
fun sprite(sprite: Sprite) = copy(sprite = sprite) fun sprite(sprite: TextureAtlasSprite) = copy(sprite = sprite)
fun color(color: Color) = transformV { it.copy(color = color) } fun color(color: Color) = transformV { it.copy(color = color) }
fun color(color: Int) = transformV { it.copy(color = Color(color)) } fun color(color: Int) = transformV { it.copy(color = Color(color)) }
fun colorIndex(colorIndex: Int) = copy(colorIndex = colorIndex) fun colorIndex(colorIndex: Int) = copy(colorIndex = colorIndex)
fun colorAndIndex(color: Int?) = color(color ?: Color.white.asInt).colorIndex(if (color == null) 0 else -1) fun colorAndIndex(color: Color?) = color(color ?: Color.white).colorIndex(if (color == null) 0 else -1)
fun face() = face ?: nearestAngle(normal, Direction.values().toList()) { it.vec }.first
val flipped: Quad get() = Quad(v4, v3, v2, v1, sprite, colorIndex) val flipped: Quad get() = Quad(v4, v3, v2, v1, sprite, colorIndex)
fun cycleVertices(n: Int) = when(n % 4) { fun cycleVertices(n: Int) = when (n % 4) {
1 -> Quad(v2, v3, v4, v1) 1 -> Quad(v2, v3, v4, v1)
2 -> Quad(v3, v4, v1, v2) 2 -> Quad(v3, v4, v1, v2)
3 -> Quad(v4, v1, v2, v3) 3 -> Quad(v4, v1, v2, v3)
@@ -148,83 +165,43 @@ data class Quad(
} }
companion object { companion object {
fun mix(first: Quad, second: Quad, vertexFactory: (Vertex, Vertex)-> Vertex) = Quad( fun mix(first: Quad, second: Quad, vertexFactory: (Vertex, Vertex) -> Vertex) = Quad(
v1 = vertexFactory(first.v1, second.v1), v1 = vertexFactory(first.v1, second.v1),
v2 = vertexFactory(first.v2, second.v2), v2 = vertexFactory(first.v2, second.v2),
v3 = vertexFactory(first.v3, second.v3), v3 = vertexFactory(first.v3, second.v3),
v4 = vertexFactory(first.v4, second.v4) v4 = vertexFactory(first.v4, second.v4)
) )
}
}
fun List<Quad>.transform(trans: Quad.()-> Quad) = map { it.trans() } fun verticalRectangle(x1: Double, z1: Double, x2: Double, z2: Double, yBottom: Double, yTop: Double) = Quad(
fun Array<List<Quad>>.transform(trans: Quad.(Int)-> Quad) = mapIndexed { idx, qList -> qList.map { it.trans(idx) } }.toTypedArray() Vertex(Double3(x1, yBottom, z1), UV.bottomLeft),
Vertex(Double3(x2, yBottom, z2), UV.bottomRight),
Vertex(Double3(x2, yTop, z2), UV.topRight),
Vertex(Double3(x1, yTop, z1), UV.topLeft)
)
fun List<Quad>.withOpposites() = flatMap { listOf(it, it.flipped) } fun horizontalRectangle(x1: Double, z1: Double, x2: Double, z2: Double, y: Double): Quad {
fun Array<List<Quad>>.withOpposites() = map { it.withOpposites() }.toTypedArray() val xMin = min(x1, x2);
val xMax = max(x1, x2)
val zMin = min(z1, z2);
val zMax = max(z1, z2)
return Quad(
Vertex(Double3(xMin, y, zMin), UV.topLeft),
Vertex(Double3(xMin, y, zMax), UV.bottomLeft),
Vertex(Double3(xMax, y, zMax), UV.bottomRight),
Vertex(Double3(xMax, y, zMin), UV.topRight)
)
}
/** fun faceQuad(face: Direction): Quad {
* Pour quad data into a fabric-renderer-api Mesh val base = face.vec * 0.5
*/ val top = boxFaces[face].top * 0.5
fun List<Quad>.build(blendMode: BlendMode, noDiffuse: Boolean = false, flatLighting: Boolean = false): Mesh { val left = boxFaces[face].left * 0.5
val renderer = RendererAccess.INSTANCE.renderer!! return Quad(
val material = renderer.materialFinder().blendMode(0, blendMode).disableAo(0, flatLighting).disableDiffuse(0, noDiffuse).find() Vertex(base + top + left, UV.topLeft),
val builder = renderer.meshBuilder() Vertex(base - top + left, UV.bottomLeft),
builder.emitter.apply { Vertex(base - top - left, UV.bottomRight),
forEach { quad -> Vertex(base + top - left, UV.topRight)
val sprite = quad.sprite ?: Atlas.BLOCKS[MissingSprite.getMissingSpriteId()]!! )
quad.verts.forEachIndexed { idx, vertex ->
pos(idx, (vertex.xyz + Double3(0.5, 0.5, 0.5)).asVec3f)
sprite(idx, 0,
(sprite.maxU - sprite.minU) * (vertex.uv.u.toFloat() + 0.5f) + sprite.minU,
(sprite.maxV - sprite.minV) * (vertex.uv.v.toFloat() + 0.5f) + sprite.minV
)
spriteColor(idx, 0, vertex.color.asInt)
}
cullFace(quad.face)
colorIndex(quad.colorIndex)
material(material)
emit()
} }
} }
return builder.build()
} }
fun Array<List<Quad>>.build(blendMode: BlendMode, noDiffuse: Boolean = false, flatLighting: Boolean = false) = map { it.build(blendMode, noDiffuse, flatLighting) }.toTypedArray()
/**
* The model should be positioned so that (0,0,0) is the block center.
* The block extends to (-0.5, 0.5) in all directions (inclusive).
*/
fun verticalRectangle(x1: Double, z1: Double, x2: Double, z2: Double, yBottom: Double, yTop: Double) = Quad(
Vertex(Double3(x1, yBottom, z1), UV.bottomLeft),
Vertex(Double3(x2, yBottom, z2), UV.bottomRight),
Vertex(Double3(x2, yTop, z2), UV.topRight),
Vertex(Double3(x1, yTop, z1), UV.topLeft)
)
fun horizontalRectangle(x1: Double, z1: Double, x2: Double, z2: Double, y: Double): Quad {
val xMin = min(x1, x2); val xMax = max(x1, x2)
val zMin = min(z1, z2); val zMax = max(z1, z2)
return Quad(
Vertex(Double3(xMin, y, zMin), UV.topLeft),
Vertex(Double3(xMin, y, zMax), UV.bottomLeft),
Vertex(Double3(xMax, y, zMax), UV.bottomRight),
Vertex(Double3(xMax, y, zMin), UV.topRight)
)
}
fun faceQuad(face: Direction): Quad {
val base = face.vec * 0.5
val top = boxFaces[face].top * 0.5
val left = boxFaces[face].left * 0.5
return Quad(
Vertex(base + top + left, UV.topLeft),
Vertex(base - top + left, UV.bottomLeft),
Vertex(base - top - left, UV.bottomRight),
Vertex(base + top - left, UV.topRight),
face = face
)
}
fun xzDisk(modelIdx: Int) = (PI2 * modelIdx / 64.0).let { Double3(cos(it), 0.0, sin(it)) }

View File

@@ -0,0 +1,26 @@
package mods.betterfoliage.model
import mods.betterfoliage.render.pipeline.RenderCtxBase
import net.minecraft.client.renderer.model.IBakedModel
import net.minecraft.util.WeightedRandom
import java.util.Random
/**
* Model that makes use of advanced rendering features.
*/
interface SpecialRenderModel : IBakedModel {
fun render(ctx: RenderCtxBase, noDecorations: Boolean = false)
}
class WeightedModelWrapper(
val models: List<WeightedModel>, baseModel: SpecialRenderModel
): IBakedModel by baseModel, SpecialRenderModel {
class WeightedModel(val model: SpecialRenderModel, weight: Int) : WeightedRandom.Item(weight)
val totalWeight = models.sumBy { it.weight }
fun getModel(random: Random) = WeightedRandom.getWeightedItem(models, random.nextInt(totalWeight))
override fun render(ctx: RenderCtxBase, noDecorations: Boolean) {
getModel(ctx.random).model.render(ctx, noDecorations)
}
}

View File

@@ -1,74 +1,82 @@
package mods.betterfoliage.model package mods.betterfoliage.model
import mods.betterfoliage.BetterFoliageMod
import mods.betterfoliage.util.Atlas import mods.betterfoliage.util.Atlas
import mods.betterfoliage.util.get import mods.betterfoliage.util.resourceManager
import net.fabricmc.fabric.api.event.client.ClientSpriteRegistryCallback import net.minecraft.client.renderer.texture.MissingTextureSprite
import net.minecraft.client.MinecraftClient import net.minecraft.client.renderer.texture.TextureAtlasSprite
import net.minecraft.client.texture.MissingSprite import net.minecraft.util.ResourceLocation
import net.minecraft.client.texture.Sprite import net.minecraftforge.client.event.TextureStitchEvent
import net.minecraft.client.texture.SpriteAtlasTexture import net.minecraftforge.eventbus.api.SubscribeEvent
import net.minecraft.util.Identifier
import kotlin.properties.ReadOnlyProperty import kotlin.properties.ReadOnlyProperty
import kotlin.reflect.KProperty import kotlin.reflect.KProperty
interface SpriteSet { interface SpriteSet {
val num: Int val num: Int
operator fun get(idx: Int): Sprite operator fun get(idx: Int): TextureAtlasSprite
} }
class FixedSpriteSet(val sprites: List<Sprite>) : SpriteSet { class FixedSpriteSet(val sprites: List<TextureAtlasSprite>) : SpriteSet {
override val num = sprites.size override val num = sprites.size
override fun get(idx: Int) = sprites[idx % num] override fun get(idx: Int) = sprites[idx % num]
constructor(atlas: Atlas, ids: List<Identifier>) : this(
ids.mapNotNull { atlas[it] }.let { sprites ->
if (sprites.isNotEmpty()) sprites else listOf(atlas[MissingSprite.getMissingSpriteId()]!!)
}
)
} }
class SpriteDelegate(val atlas: Atlas, val idFunc: ()->Identifier) : ReadOnlyProperty<Any, Sprite>, ClientSpriteRegistryCallback { class SpriteDelegate(val atlas: Atlas, val idFunc: () -> ResourceLocation) : ReadOnlyProperty<Any, TextureAtlasSprite> {
private var id: Identifier? = null private lateinit var id: ResourceLocation
private var value: Sprite? = null private var value: TextureAtlasSprite? = null
init { ClientSpriteRegistryCallback.event(atlas.resourceId).register(this) } init {
BetterFoliageMod.bus.register(this)
override fun registerSprites(atlasTexture: SpriteAtlasTexture, registry: ClientSpriteRegistryCallback.Registry) {
id = idFunc(); value = null
registry.register(id)
} }
override fun getValue(thisRef: Any, property: KProperty<*>): Sprite { @SubscribeEvent
fun handlePreStitch(event: TextureStitchEvent.Pre) {
id = idFunc(); value = null
event.addSprite(id)
}
override fun getValue(thisRef: Any, property: KProperty<*>): TextureAtlasSprite {
value?.let { return it } value?.let { return it }
synchronized(this) { synchronized(this) {
value?.let { return it } value?.let { return it }
atlas[id!!]!!.let { value = it; return it } atlas[id].let { value = it; return it }
} }
} }
}
}
class SpriteSetDelegate( class SpriteSetDelegate(
val atlas: Atlas, val atlas: Atlas,
val idRegister: (Identifier)->Identifier = { it }, val idRegister: (ResourceLocation) -> ResourceLocation = { it },
val idFunc: (Int)->Identifier val idFunc: (Int) -> ResourceLocation
) : ReadOnlyProperty<Any, SpriteSet>, ClientSpriteRegistryCallback { ) : ReadOnlyProperty<Any, SpriteSet> {
private var idList: List<Identifier> = emptyList() private var idList: List<ResourceLocation> = emptyList()
private var spriteSet: SpriteSet? = null private var spriteSet: SpriteSet? = null
init { ClientSpriteRegistryCallback.event(atlas.resourceId).register(this) }
override fun registerSprites(atlasTexture: SpriteAtlasTexture, registry: ClientSpriteRegistryCallback.Registry) { init {
BetterFoliageMod.bus.register(this)
}
@SubscribeEvent
fun handlePreStitch(event: TextureStitchEvent.Pre) {
if (event.map.location() != atlas.resourceId) return
spriteSet = null spriteSet = null
val manager = MinecraftClient.getInstance().resourceManager idList = (0 until 16)
idList = (0 until 16).map(idFunc).filter { manager.containsResource(atlas.file(it)) }.map(idRegister) .map(idFunc)
idList.forEach { registry.register(it) } .filter { resourceManager.hasResource(atlas.file(it)) }
.map(idRegister)
idList.forEach { event.addSprite(it) }
} }
override fun getValue(thisRef: Any, property: KProperty<*>): SpriteSet { override fun getValue(thisRef: Any, property: KProperty<*>): SpriteSet {
spriteSet?.let { return it } spriteSet?.let { return it }
synchronized(this) { synchronized(this) {
spriteSet?.let { return it } spriteSet?.let { return it }
spriteSet = FixedSpriteSet(atlas, idList) spriteSet = FixedSpriteSet(
idList
.ifEmpty { listOf(MissingTextureSprite.getLocation()) }
.map { atlas[it] }
)
return spriteSet!! return spriteSet!!
} }
} }

View File

@@ -1,11 +1,23 @@
package mods.betterfoliage.model package mods.betterfoliage.model
import mods.betterfoliage.util.* import mods.betterfoliage.util.Atlas
import net.fabricmc.fabric.api.renderer.v1.material.BlendMode import mods.betterfoliage.util.Double3
import net.fabricmc.fabric.api.renderer.v1.mesh.Mesh import mods.betterfoliage.util.PI2
import net.minecraft.client.texture.Sprite import mods.betterfoliage.util.allDirections
import net.minecraft.util.Identifier import mods.betterfoliage.util.random
import net.minecraft.util.math.Direction.UP import mods.betterfoliage.util.randomB
import mods.betterfoliage.util.randomD
import mods.betterfoliage.util.randomI
import mods.betterfoliage.util.rot
import mods.betterfoliage.util.vec
import net.minecraft.client.renderer.texture.TextureAtlasSprite
import net.minecraft.util.Direction.UP
import net.minecraft.util.ResourceLocation
import kotlin.math.cos
import kotlin.math.sin
fun xzDisk(modelIdx: Int) = (PI2 * modelIdx.toDouble() / 64.0).let { Double3(cos(it), 0.0, sin(it)) }
data class TuftShapeKey( data class TuftShapeKey(
val size: Double, val size: Double,
@@ -28,53 +40,69 @@ fun tuftShapeSet(size: Double, heightMin: Double, heightMax: Double, hOffset: Do
} }
fun tuftQuadSingle(size: Double, height: Double, flipU: Boolean) = fun tuftQuadSingle(size: Double, height: Double, flipU: Boolean) =
verticalRectangle(x1 = -0.5 * size, z1 = 0.5 * size, x2 = 0.5 * size, z2 = -0.5 * size, yBottom = 0.5, yTop = 0.5 + height) Quad.verticalRectangle(
x1 = -0.5 * size,
z1 = 0.5 * size,
x2 = 0.5 * size,
z2 = -0.5 * size,
yBottom = 0.5,
yTop = 0.5 + height
)
.mirrorUV(flipU, false) .mirrorUV(flipU, false)
fun tuftModelSet(shapes: Array<TuftShapeKey>, tintIndex: Int, spriteGetter: (Int)->Sprite) = shapes.mapIndexed { idx, shape -> fun tuftModelSet(shapes: Array<TuftShapeKey>, tintIndex: Int, spriteGetter: (Int) -> TextureAtlasSprite) =
listOf( shapes.mapIndexed { idx, shape ->
tuftQuadSingle(shape.size, shape.height, shape.flipU1), listOf(
tuftQuadSingle(shape.size, shape.height, shape.flipU2).rotate(rot(UP)) tuftQuadSingle(shape.size, shape.height, shape.flipU1),
).map { it.move(shape.offset) } tuftQuadSingle(shape.size, shape.height, shape.flipU2).rotate(rot(UP))
.map { it.colorIndex(tintIndex) } ).map { it.move(shape.offset) }
.map { it.sprite(spriteGetter(idx)) } .map { it.colorIndex(tintIndex) }
}.toTypedArray() .map { it.sprite(spriteGetter(idx)) }
}
fun fullCubeTextured(spriteId: Identifier, tintIndex: Int, scrambleUV: Boolean = true): Mesh { fun fullCubeTextured(
val sprite = Atlas.BLOCKS[spriteId]!! spriteLocation: ResourceLocation,
return allDirections.map { faceQuad(it) } tintIndex: Int,
scrambleUV: Boolean = true
): List<HalfBakedQuad> {
val sprite = Atlas.BLOCKS[spriteLocation]
return allDirections.map { Quad.faceQuad(it) }
.map { if (!scrambleUV) it else it.rotateUV(randomI(max = 4)) } .map { if (!scrambleUV) it else it.rotateUV(randomI(max = 4)) }
.map { it.sprite(sprite) } .map { it.sprite(sprite) }
.map { it.colorIndex(tintIndex) } .map { it.colorIndex(tintIndex) }
.build(BlendMode.SOLID, noDiffuse = true) .bake(true)
} }
fun crossModelsRaw(num: Int, size: Double, hOffset: Double, vOffset: Double): Array<List<Quad>> { fun crossModelsRaw(num: Int, size: Double, hOffset: Double, vOffset: Double): List<List<Quad>> {
return Array(num) { idx -> return (0 until num).map { idx ->
listOf( listOf(
verticalRectangle(x1 = -0.5, z1 = 0.5, x2 = 0.5, z2 = -0.5, yBottom = -0.5 * 1.41, yTop = 0.5 * 1.41), Quad.verticalRectangle(x1 = -0.5, z1 = 0.5, x2 = 0.5, z2 = -0.5, yBottom = -0.5 * 1.41, yTop = 0.5 * 1.41),
verticalRectangle(x1 = -0.5, z1 = 0.5, x2 = 0.5, z2 = -0.5, yBottom = -0.5 * 1.41, yTop = 0.5 * 1.41) Quad.verticalRectangle(x1 = -0.5, z1 = 0.5, x2 = 0.5, z2 = -0.5, yBottom = -0.5 * 1.41, yTop = 0.5 * 1.41)
.rotate(rot(UP)) .rotate(rot(UP))
).map { it.scale(size) } ).map { it.scale(size) }
.map { it.move(xzDisk(idx) * hOffset) } .map { it.move(xzDisk(idx) * hOffset) }
.map { it.move(UP.vec * randomD(-1.0, 1.0) * vOffset) } .map { it.move(UP.vec * randomD(-1.0, 1.0) * vOffset) }
} }
} }
fun crossModelSingle(base: List<Quad>, sprite: Sprite, tintIndex: Int,scrambleUV: Boolean) = fun crossModelSingle(base: List<Quad>, sprite: TextureAtlasSprite, tintIndex: Int,scrambleUV: Boolean) =
base.map { if (scrambleUV) it.scrambleUV(random, canFlipU = true, canFlipV = true, canRotate = true) else it } base.map { if (scrambleUV) it.scrambleUV(random, canFlipU = true, canFlipV = true, canRotate = true) else it }
.map { it.colorIndex(tintIndex) } .map { it.colorIndex(tintIndex) }
.mapIndexed { idx, quad -> quad.sprite(sprite) } .mapIndexed { idx, quad -> quad.sprite(sprite) }
.withOpposites() .withOpposites()
.build(BlendMode.CUTOUT_MIPPED) .bake(false)
fun crossModelsTextured( fun crossModelsTextured(
leafBase: Array<List<Quad>>, leafBase: Iterable<List<Quad>>,
tintIndex: Int, tintIndex: Int,
scrambleUV: Boolean, scrambleUV: Boolean,
spriteGetter: (Int) -> Identifier spriteGetter: (Int) -> ResourceLocation
) = leafBase.mapIndexed { idx, leaf -> ) = leafBase.mapIndexed { idx, leaf ->
crossModelSingle(leaf, Atlas.BLOCKS[spriteGetter(idx)], tintIndex, scrambleUV) crossModelSingle(leaf, Atlas.BLOCKS[spriteGetter(idx)], tintIndex, scrambleUV)
}.toTypedArray() }.toTypedArray()
fun Array<List<Quad>>.buildTufts() = withOpposites().build(BlendMode.CUTOUT_MIPPED) fun Iterable<Quad>.withOpposites() = flatMap { listOf(it, it.flipped) }
fun Iterable<List<Quad>>.buildTufts(applyDiffuseLighting: Boolean = false) =
map { it.withOpposites().bake(applyDiffuseLighting) }.toTypedArray()
fun Iterable<List<Quad>>.transform(trans: Quad.(Int)-> Quad) = mapIndexed { idx, qList -> qList.map { it.trans(idx) } }

View File

@@ -1,87 +0,0 @@
package mods.betterfoliage.model
import mods.betterfoliage.resource.discovery.ModelBakingContext
import mods.betterfoliage.resource.discovery.ModelBakingKey
import mods.betterfoliage.util.HasLogger
import net.fabricmc.fabric.api.renderer.v1.material.BlendMode
import net.fabricmc.fabric.api.renderer.v1.mesh.Mesh
import net.fabricmc.fabric.api.renderer.v1.model.FabricBakedModel
import net.fabricmc.fabric.api.renderer.v1.render.RenderContext
import net.minecraft.block.BlockState
import net.minecraft.client.render.RenderLayers
import net.minecraft.client.render.model.BakedModel
import net.minecraft.client.render.model.BasicBakedModel
import net.minecraft.item.ItemStack
import net.minecraft.util.collection.WeightedPicker
import net.minecraft.util.math.BlockPos
import net.minecraft.world.BlockRenderView
import java.util.*
import java.util.function.Supplier
abstract class ModelWrapKey : ModelBakingKey, HasLogger() {
override fun bake(ctx: ModelBakingContext): BakedModel? {
val baseModel = super.bake(ctx)
if (baseModel is BasicBakedModel)
return bake(ctx, baseModel)
else
return baseModel
}
abstract fun bake(ctx: ModelBakingContext, wrapped: BasicBakedModel): BakedModel
}
abstract class WrappedBakedModel(val wrapped: BakedModel) : BakedModel by wrapped, FabricBakedModel {
override fun isVanillaAdapter() = false
override fun emitItemQuads(stack: ItemStack, randomSupplier: Supplier<Random>, context: RenderContext) {
(wrapped as FabricBakedModel).emitItemQuads(stack, randomSupplier, context)
}
override fun emitBlockQuads(blockView: BlockRenderView, state: BlockState, pos: BlockPos, randomSupplier: Supplier<Random>, context: RenderContext) {
(wrapped as FabricBakedModel).emitBlockQuads(blockView, state, pos, randomSupplier, context)
}
}
class WrappedMeshModel(wrapped: BasicBakedModel, val mesh: Mesh) : WrappedBakedModel(wrapped) {
override fun emitBlockQuads(blockView: BlockRenderView, state: BlockState, pos: BlockPos, randomSupplier: Supplier<Random>, context: RenderContext) {
context.meshConsumer().accept(mesh)
}
companion object {
/**
* Converter for [BasicBakedModel] instances.
* @param state [BlockState] to use when querying [BakedModel]
* @param unshade undo vanilla diffuse lighting when unbaking the [BakedModel]
* @param noDiffuse disable diffuse lighting when baking the [Mesh]
* @param blendModeOverride [BlockRenderLayer] to use instead of the one declared by the corresponding [Block]
*/
fun converter(state: BlockState?, unshade: Boolean = false, noDiffuse: Boolean = true, blendModeOverride: BlendMode? = null) = BakedModelConverter.of { model ->
if (model is BasicBakedModel) {
val mesh = unbakeQuads(model, state, Random(42L), unshade).build(
blendMode = blendModeOverride ?: BlendMode.fromRenderLayer(RenderLayers.getBlockLayer(state)),
noDiffuse = noDiffuse,
flatLighting = !model.useAmbientOcclusion()
)
WrappedMeshModel(model, mesh)
} else null
}
}
}
class WeightedModelWrapper(
val models: List<WeightedModel>, baseModel: BakedModel
): WrappedBakedModel(baseModel), FabricBakedModel {
class WeightedModel(val model: BakedModel, val weight: Int) : WeightedPicker.Entry(weight)
fun getModel(random: Random) = WeightedPicker.getRandom(random, models).model
override fun emitBlockQuads(blockView: BlockRenderView, state: BlockState, pos: BlockPos, randomSupplier: Supplier<Random>, context: RenderContext) {
(getModel(randomSupplier.get()) as FabricBakedModel).emitBlockQuads(blockView, state, pos, randomSupplier, context)
}
}
fun getUnderlyingModel(model: BakedModel, random: Random): BakedModel = when(model) {
is WeightedModelWrapper -> getUnderlyingModel(model.getModel(random), random)
is WrappedBakedModel -> model.wrapped
else -> model
}

View File

@@ -1,54 +0,0 @@
package mods.betterfoliage.render
import mods.betterfoliage.BetterFoliage
import mods.betterfoliage.render.lighting.getBufferBuilder
import mods.betterfoliage.util.HasLogger
import mods.betterfoliage.util.getAllMethods
import net.fabricmc.fabric.api.renderer.v1.render.RenderContext
import net.minecraft.block.BlockState
import net.minecraft.block.Blocks
import net.minecraft.client.render.BufferBuilder
import net.minecraft.client.render.RenderLayer
/**
* Integration for ShadersMod.
*/
object ShadersModIntegration : HasLogger() {
val BufferBuilder_SVertexBuilder = BufferBuilder::class.java.fields.find { it.name == "sVertexBuilder" }
val SVertexBuilder_pushState = getAllMethods("net.optifine.shaders.SVertexBuilder", "pushEntity").find { it.parameterCount == 1 }
val SVertexBuilder_popState = getAllMethods("net.optifine.shaders.SVertexBuilder", "popEntity").find { it.parameterCount == 0 }
val BlockAliases_getAliasBlockId = getAllMethods("net.optifine.shaders.BlockAliases", "getAliasBlockId").firstOrNull()
@JvmStatic val isAvailable =
listOf(BufferBuilder_SVertexBuilder).all { it != null } &&
listOf(SVertexBuilder_pushState, SVertexBuilder_popState, BlockAliases_getAliasBlockId).all { it != null }
val defaultLeaves = Blocks.OAK_LEAVES.defaultState
val defaultGrass = Blocks.TALL_GRASS.defaultState
init {
logger.info("[BetterFoliage] ShadersMod integration is ${if (isAvailable) "enabled" else "disabled" }")
}
/** Quads rendered inside this block will use the given block entity data in shader programs. */
inline fun renderAs(ctx: RenderContext, state: BlockState, layer: RenderLayer, enabled: Boolean = true, func: ()->Unit) {
if (isAvailable && enabled) {
val sVertexBuilder = BufferBuilder_SVertexBuilder!!.get(ctx.getBufferBuilder(layer))
val aliasBlockId = BlockAliases_getAliasBlockId!!.invoke(null, state)
SVertexBuilder_pushState!!.invoke(sVertexBuilder, aliasBlockId)
func()
SVertexBuilder_popState!!.invoke(sVertexBuilder)
} else {
func()
}
}
/** Quads rendered inside this block will behave as tallgrass blocks in shader programs. */
inline fun grass(ctx: RenderContext, enabled: Boolean = true, func: ()->Unit) =
renderAs(ctx, defaultGrass, RenderLayer.getCutoutMipped(), enabled, func)
/** Quads rendered inside this block will behave as leaf blocks in shader programs. */
inline fun leaves(ctx: RenderContext, enabled: Boolean = true, func: ()->Unit) =
renderAs(ctx, defaultLeaves, RenderLayer.getCutoutMipped(), enabled, func)
}

View File

@@ -1,23 +1,22 @@
package mods.betterfoliage.render.block.vanilla package mods.betterfoliage.render.block.vanilla
import mods.betterfoliage.BetterFoliageMod
import mods.betterfoliage.BetterFoliage import mods.betterfoliage.BetterFoliage
import mods.betterfoliage.config.CACTUS_BLOCKS import mods.betterfoliage.config.CACTUS_BLOCKS
import mods.betterfoliage.model.Color import mods.betterfoliage.config.Config
import mods.betterfoliage.model.ModelWrapKey import mods.betterfoliage.model.HalfBakedSpecialWrapper
import mods.betterfoliage.model.SpriteDelegate import mods.betterfoliage.model.HalfBakedWrapperKey
import mods.betterfoliage.model.SpecialRenderModel
import mods.betterfoliage.model.SpriteSetDelegate import mods.betterfoliage.model.SpriteSetDelegate
import mods.betterfoliage.model.WrappedBakedModel
import mods.betterfoliage.model.buildTufts import mods.betterfoliage.model.buildTufts
import mods.betterfoliage.model.crossModelsRaw import mods.betterfoliage.model.crossModelsRaw
import mods.betterfoliage.model.crossModelsTextured import mods.betterfoliage.model.crossModelsTextured
import mods.betterfoliage.model.meshifyCutoutMipped
import mods.betterfoliage.model.meshifyStandard
import mods.betterfoliage.model.transform import mods.betterfoliage.model.transform
import mods.betterfoliage.model.tuftModelSet import mods.betterfoliage.model.tuftModelSet
import mods.betterfoliage.model.tuftShapeSet import mods.betterfoliage.model.tuftShapeSet
import mods.betterfoliage.render.lighting.grassTuftLighting import mods.betterfoliage.render.lighting.LightingPreferredFace
import mods.betterfoliage.render.lighting.roundLeafLighting import mods.betterfoliage.render.lighting.RoundLeafLighting
import mods.betterfoliage.render.lighting.withLighting import mods.betterfoliage.render.pipeline.RenderCtxBase
import mods.betterfoliage.resource.discovery.AbstractModelDiscovery import mods.betterfoliage.resource.discovery.AbstractModelDiscovery
import mods.betterfoliage.resource.discovery.BakeWrapperManager import mods.betterfoliage.resource.discovery.BakeWrapperManager
import mods.betterfoliage.resource.discovery.ModelBakingContext import mods.betterfoliage.resource.discovery.ModelBakingContext
@@ -29,25 +28,15 @@ import mods.betterfoliage.util.get
import mods.betterfoliage.util.horizontalDirections import mods.betterfoliage.util.horizontalDirections
import mods.betterfoliage.util.randomD import mods.betterfoliage.util.randomD
import mods.betterfoliage.util.randomI import mods.betterfoliage.util.randomI
import net.fabricmc.fabric.api.renderer.v1.model.FabricBakedModel
import net.fabricmc.fabric.api.renderer.v1.render.RenderContext
import net.minecraft.block.BlockState
import net.minecraft.block.Blocks import net.minecraft.block.Blocks
import net.minecraft.client.render.model.BakedModel import net.minecraft.client.renderer.model.BlockModel
import net.minecraft.client.render.model.BasicBakedModel import net.minecraft.util.Direction.DOWN
import net.minecraft.client.render.model.json.JsonUnbakedModel import net.minecraft.util.ResourceLocation
import net.minecraft.util.Identifier
import net.minecraft.util.math.BlockPos
import net.minecraft.util.math.Direction.DOWN
import net.minecraft.world.BlockRenderView
import java.util.Random
import java.util.function.Supplier
object StandardCactusDiscovery : AbstractModelDiscovery() { object StandardCactusDiscovery : AbstractModelDiscovery() {
override fun processModel(ctx: ModelDiscoveryContext) { override fun processModel(ctx: ModelDiscoveryContext) {
val model = ctx.getUnbaked() val model = ctx.getUnbaked()
if (model is JsonUnbakedModel && ctx.blockState.block in CACTUS_BLOCKS) { if (model is BlockModel && ctx.blockState.block in CACTUS_BLOCKS) {
BetterFoliage.blockTypes.dirt.add(ctx.blockState) BetterFoliage.blockTypes.dirt.add(ctx.blockState)
ctx.addReplacement(StandardCactusKey) ctx.addReplacement(StandardCactusKey)
ctx.sprites.add(StandardCactusModel.cactusCrossSprite) ctx.sprites.add(StandardCactusModel.cactusCrossSprite)
@@ -56,48 +45,46 @@ object StandardCactusDiscovery : AbstractModelDiscovery() {
} }
} }
object StandardCactusKey : ModelWrapKey() { object StandardCactusKey : HalfBakedWrapperKey() {
override fun bake(ctx: ModelBakingContext, wrapped: BasicBakedModel) = StandardCactusModel(meshifyCutoutMipped(wrapped)) override fun bake(ctx: ModelBakingContext, wrapped: SpecialRenderModel) = StandardCactusModel(wrapped)
} }
class StandardCactusModel(wrapped: BakedModel) : WrappedBakedModel(wrapped), FabricBakedModel { class StandardCactusModel(
wrapped: SpecialRenderModel
) : HalfBakedSpecialWrapper(wrapped) {
val armLighting = horizontalDirections.map { grassTuftLighting(it) } val armLighting = horizontalDirections.map { LightingPreferredFace(it) }.toTypedArray()
val crossLighting = roundLeafLighting()
override fun emitBlockQuads(blockView: BlockRenderView, state: BlockState, pos: BlockPos, randomSupplier: Supplier<Random>, context: RenderContext) { override fun render(ctx: RenderCtxBase, noDecorations: Boolean) {
(wrapped as FabricBakedModel).emitBlockQuads(blockView, state, pos, randomSupplier, context) ctx.checkSides = false
if (!BetterFoliage.config.enabled || !BetterFoliage.config.cactus.enabled) return super.render(ctx, noDecorations)
if (!Config.enabled || !Config.cactus.enabled) return
val random = randomSupplier.get() val armSide = ctx.random.nextInt() and 3
val armSide = random.nextInt() and 3 ctx.vertexLighter = armLighting[armSide]
ctx.renderQuads(cactusArmModels[armSide][ctx.random])
context.withLighting(armLighting[armSide]) { ctx.vertexLighter = RoundLeafLighting
it.accept(cactusArmModels[armSide][random]) ctx.renderQuads(cactusCrossModels[ctx.random])
}
context.withLighting(crossLighting) {
it.accept(cactusCrossModels[random])
}
} }
companion object { companion object {
val cactusCrossSprite = Identifier(BetterFoliage.MOD_ID, "blocks/better_cactus") val cactusCrossSprite = ResourceLocation(BetterFoliageMod.MOD_ID, "blocks/better_cactus")
val cactusArmSprites by SpriteSetDelegate(Atlas.BLOCKS) { idx -> val cactusArmSprites by SpriteSetDelegate(Atlas.BLOCKS) { idx ->
Identifier(BetterFoliage.MOD_ID, "blocks/better_cactus_arm_$idx") ResourceLocation(BetterFoliageMod.MOD_ID, "blocks/better_cactus_arm_$idx")
} }
val cactusArmModels by LazyInvalidatable(BakeWrapperManager) { val cactusArmModels by LazyInvalidatable(BakeWrapperManager) {
val shapes = BetterFoliage.config.cactus.let { tuftShapeSet(0.8, 0.8, 0.8, 0.2) } val shapes = Config.cactus.let { tuftShapeSet(0.8, 0.8, 0.8, 0.2) }
val models = tuftModelSet(shapes, Color.white.asInt) { cactusArmSprites[randomI()] } val models = tuftModelSet(shapes, -1) { cactusArmSprites[randomI()] }
horizontalDirections.map { side -> horizontalDirections.map { side ->
models.transform { move(0.0625 to DOWN).rotate(Rotation.fromUp[side.ordinal]) }.buildTufts() models.transform { move(0.0625 to DOWN).rotate(Rotation.fromUp[side.ordinal]) }.buildTufts()
}.toTypedArray() }.toTypedArray()
} }
val cactusCrossModels by LazyInvalidatable(BakeWrapperManager) { val cactusCrossModels by LazyInvalidatable(BakeWrapperManager) {
val models = BetterFoliage.config.cactus.let { config -> val models = Config.cactus.let { config ->
crossModelsRaw(64, config.size, 0.0, 0.0) crossModelsRaw(64, config.size, 0.0, 0.0)
.transform { rotateZ(randomD(-config.sizeVariation, config.sizeVariation)) } .transform { rotateZ(randomD(-config.sizeVariation, config.sizeVariation)) }
} }
crossModelsTextured(models, Color.white.asInt, true) { cactusCrossSprite } crossModelsTextured(models, -1, true) { cactusCrossSprite }
} }
} }
} }

View File

@@ -1,25 +1,21 @@
package mods.betterfoliage.render.block.vanilla package mods.betterfoliage.render.block.vanilla
import mods.betterfoliage.BetterFoliage import mods.betterfoliage.BetterFoliage
import mods.betterfoliage.chunk.BasicBlockCtx import mods.betterfoliage.BetterFoliageMod
import mods.betterfoliage.config.Config
import mods.betterfoliage.config.DIRT_BLOCKS import mods.betterfoliage.config.DIRT_BLOCKS
import mods.betterfoliage.model.Color import mods.betterfoliage.config.SALTWATER_BIOMES
import mods.betterfoliage.model.ModelWrapKey import mods.betterfoliage.config.isSnow
import mods.betterfoliage.integration.ShadersModIntegration
import mods.betterfoliage.model.HalfBakedSpecialWrapper
import mods.betterfoliage.model.HalfBakedWrapperKey
import mods.betterfoliage.model.SpecialRenderModel
import mods.betterfoliage.model.SpriteSetDelegate import mods.betterfoliage.model.SpriteSetDelegate
import mods.betterfoliage.model.WrappedBakedModel import mods.betterfoliage.model.buildTufts
import mods.betterfoliage.model.build
import mods.betterfoliage.model.meshifyStandard
import mods.betterfoliage.model.tuftModelSet import mods.betterfoliage.model.tuftModelSet
import mods.betterfoliage.model.tuftShapeSet import mods.betterfoliage.model.tuftShapeSet
import mods.betterfoliage.model.withOpposites import mods.betterfoliage.render.lighting.LightingPreferredFace
import mods.betterfoliage.config.SALTWATER_BIOMES import mods.betterfoliage.render.pipeline.RenderCtxBase
import mods.betterfoliage.model.getUnderlyingModel
import mods.betterfoliage.model.meshifySolid
import mods.betterfoliage.render.ShadersModIntegration
import mods.betterfoliage.render.lighting.grassTuftLighting
import mods.betterfoliage.render.lighting.reedLighting
import mods.betterfoliage.render.lighting.renderMasquerade
import mods.betterfoliage.render.lighting.withLighting
import mods.betterfoliage.resource.discovery.AbstractModelDiscovery import mods.betterfoliage.resource.discovery.AbstractModelDiscovery
import mods.betterfoliage.resource.discovery.BakeWrapperManager import mods.betterfoliage.resource.discovery.BakeWrapperManager
import mods.betterfoliage.resource.discovery.ModelBakingContext import mods.betterfoliage.resource.discovery.ModelBakingContext
@@ -29,114 +25,96 @@ import mods.betterfoliage.util.Atlas
import mods.betterfoliage.util.Int3 import mods.betterfoliage.util.Int3
import mods.betterfoliage.util.LazyInvalidatable import mods.betterfoliage.util.LazyInvalidatable
import mods.betterfoliage.util.get import mods.betterfoliage.util.get
import mods.betterfoliage.util.offset
import mods.betterfoliage.util.randomI import mods.betterfoliage.util.randomI
import net.fabricmc.fabric.api.renderer.v1.material.BlendMode import net.minecraft.block.material.Material
import net.fabricmc.fabric.api.renderer.v1.render.RenderContext import net.minecraft.client.renderer.RenderType
import net.minecraft.block.BlockState import net.minecraft.client.renderer.RenderTypeLookup
import net.minecraft.block.Blocks import net.minecraft.client.renderer.model.BlockModel
import net.minecraft.block.Material import net.minecraft.util.Direction.UP
import net.minecraft.client.render.RenderLayer import net.minecraft.util.ResourceLocation
import net.minecraft.client.render.model.BakedModel
import net.minecraft.client.render.model.BasicBakedModel
import net.minecraft.client.render.model.json.JsonUnbakedModel
import net.minecraft.util.Identifier
import net.minecraft.util.math.BlockPos
import net.minecraft.util.math.Direction.UP
import net.minecraft.world.BlockRenderView
import java.util.Random
import java.util.function.Supplier
object StandardDirtDiscovery : AbstractModelDiscovery() { object StandardDirtDiscovery : AbstractModelDiscovery() {
fun canRenderInLayer(layer: RenderLayer) = when { fun canRenderInLayer(layer: RenderType) = when {
!BetterFoliage.config.enabled -> layer == RenderLayer.getSolid() !Config.enabled -> layer == RenderType.solid()
(!BetterFoliage.config.connectedGrass.enabled && !Config.connectedGrass.enabled && !Config.algae.enabled && !Config.reed.enabled -> layer == RenderType.solid()
!BetterFoliage.config.algae.enabled && else -> layer == RenderType.cutoutMipped()
!BetterFoliage.config.reed.enabled
) -> layer == RenderLayer.getSolid()
else -> layer == RenderLayer.getCutoutMipped()
} }
override fun processModel(ctx: ModelDiscoveryContext) { override fun processModel(ctx: ModelDiscoveryContext) {
if (ctx.getUnbaked() is JsonUnbakedModel && ctx.blockState.block in DIRT_BLOCKS) { if (ctx.getUnbaked() is BlockModel && ctx.blockState.block in DIRT_BLOCKS) {
BetterFoliage.blockTypes.dirt.add(ctx.blockState) BetterFoliage.blockTypes.dirt.add(ctx.blockState)
ctx.addReplacement(StandardDirtKey) ctx.addReplacement(StandardDirtKey)
// RenderTypeLookup.setRenderLayer(ctx.blockState.block, ::canRenderInLayer) RenderTypeLookup.setRenderLayer(ctx.blockState.block, ::canRenderInLayer)
} }
super.processModel(ctx) super.processModel(ctx)
} }
} }
object StandardDirtKey : ModelWrapKey() { object StandardDirtKey : HalfBakedWrapperKey() {
override fun bake(ctx: ModelBakingContext, wrapped: BasicBakedModel) = DirtModel(meshifySolid(wrapped)) override fun bake(ctx: ModelBakingContext, wrapped: SpecialRenderModel) = StandardDirtModel(wrapped)
} }
class DirtModel(wrapped: BakedModel) : WrappedBakedModel(wrapped) { class StandardDirtModel(
wrapped: SpecialRenderModel
) : HalfBakedSpecialWrapper(wrapped) {
val vanillaTuftLighting = LightingPreferredFace(UP)
val algaeLighting = grassTuftLighting(UP) override fun render(ctx: RenderCtxBase, noDecorations: Boolean) {
val reedLighting = reedLighting() if (!Config.enabled || noDecorations) return super.render(ctx, noDecorations)
override fun emitBlockQuads( val stateUp = ctx.state(UP)
blockView: BlockRenderView, val state2Up = ctx.state(Int3(0, 2, 0))
state: BlockState, val isConnectedGrass = Config.connectedGrass.enabled &&
pos: BlockPos, stateUp in BetterFoliage.blockTypes.grass &&
randomSupplier: Supplier<Random>, (Config.connectedGrass.snowEnabled || !state2Up.isSnow)
context: RenderContext
) {
if (!BetterFoliage.config.enabled) return super.emitBlockQuads(blockView, state, pos, randomSupplier, context)
val ctx = BasicBlockCtx(blockView, pos) if (isConnectedGrass) {
val stateUp = ctx.offset(UP).state (ctx.blockModelShapes.getBlockModel(stateUp) as? SpecialRenderModel)?.let { grassModel ->
val isGrassUp = stateUp in BetterFoliage.blockTypes.grass ctx.renderMasquerade(UP.offset) {
grassModel.render(ctx, true)
val isWater = stateUp.material == Material.WATER }
val isDeepWater = isWater && ctx.offset(Int3(2 to UP)).state.material == Material.WATER return
val isShallowWater = isWater && ctx.offset(Int3(2 to UP)).state.isAir }
val isSaltWater = isWater && ctx.biome?.category in SALTWATER_BIOMES return super.render(ctx, false)
val random = randomSupplier.get()
if (BetterFoliage.config.connectedGrass.enabled && isGrassUp) {
val grassBaseModel = getUnderlyingModel(ctx.model(UP), random)
context.renderMasquerade(grassBaseModel, blockView, stateUp, pos, randomSupplier, context)
} else {
super.emitBlockQuads(blockView, state, pos, randomSupplier, context)
} }
if (BetterFoliage.config.algae.enabled(random) && isDeepWater) { super.render(ctx, false)
ShadersModIntegration.grass(context, BetterFoliage.config.algae.shaderWind) {
context.withLighting(algaeLighting) { val isWater = stateUp.material == Material.WATER
it.accept(algaeModels[random]) val isDeepWater = isWater && state2Up.material == Material.WATER
} val isShallowWater = isWater && state2Up.isAir
val isSaltWater = isWater && ctx.biome?.biomeCategory in SALTWATER_BIOMES
if (Config.algae.enabled(ctx.random) && isDeepWater) {
ctx.vertexLighter = vanillaTuftLighting
ShadersModIntegration.grass(ctx, Config.algae.shaderWind) {
ctx.renderQuads(algaeModels[ctx.random])
} }
} else if (BetterFoliage.config.reed.enabled(random) && isShallowWater && !isSaltWater) { } else if (Config.reed.enabled(ctx.random) && isShallowWater && !isSaltWater) {
ShadersModIntegration.grass(context, BetterFoliage.config.reed.shaderWind) { ctx.vertexLighter = vanillaTuftLighting
context.withLighting(reedLighting) { ShadersModIntegration.grass(ctx, Config.reed.shaderWind) {
it.accept(reedModels[random]) ctx.renderQuads(reedModels[ctx.random])
}
} }
} }
} }
companion object { companion object {
val algaeSprites by SpriteSetDelegate(Atlas.BLOCKS) { idx -> val algaeSprites by SpriteSetDelegate(Atlas.BLOCKS) { idx ->
Identifier(BetterFoliage.MOD_ID, "blocks/better_algae_$idx") ResourceLocation(BetterFoliageMod.MOD_ID, "blocks/better_algae_$idx")
} }
val reedSprites by SpriteSetDelegate(Atlas.BLOCKS, val reedSprites by SpriteSetDelegate(
idFunc = { idx -> Identifier(BetterFoliage.MOD_ID, "blocks/better_reed_$idx") }, Atlas.BLOCKS,
idFunc = { idx -> ResourceLocation(BetterFoliageMod.MOD_ID, "blocks/better_reed_$idx") },
idRegister = { id -> CenteredSprite(id, aspectHeight = 2).register(BetterFoliage.generatedPack) } idRegister = { id -> CenteredSprite(id, aspectHeight = 2).register(BetterFoliage.generatedPack) }
) )
val algaeModels by LazyInvalidatable(BakeWrapperManager) { val algaeModels by LazyInvalidatable(BakeWrapperManager) {
val shapes = val shapes = Config.algae.let { tuftShapeSet(it.size, it.heightMin, it.heightMax, it.hOffset) }
BetterFoliage.config.algae.let { tuftShapeSet(it.size, it.heightMin, it.heightMax, it.hOffset) } tuftModelSet(shapes, -1) { algaeSprites[randomI()] }.buildTufts()
tuftModelSet(shapes, Color.white.asInt) { algaeSprites[randomI()] }
.withOpposites()
.build(BlendMode.CUTOUT_MIPPED, flatLighting = false)
} }
val reedModels by LazyInvalidatable(BakeWrapperManager) { val reedModels by LazyInvalidatable(BakeWrapperManager) {
val shapes = BetterFoliage.config.reed.let { tuftShapeSet(2.0, it.heightMin, it.heightMax, it.hOffset) } val shapes = Config.reed.let { tuftShapeSet(2.0, it.heightMin, it.heightMax, it.hOffset) }
tuftModelSet(shapes, Color.white.asInt) { reedSprites[randomI()] } tuftModelSet(shapes, -1) { reedSprites[randomI()] }.buildTufts()
.withOpposites()
.build(BlendMode.CUTOUT_MIPPED, flatLighting = false)
} }
} }
} }

View File

@@ -1,115 +1,118 @@
package mods.betterfoliage.render.block.vanilla package mods.betterfoliage.render.block.vanilla
import mods.betterfoliage.BetterFoliage import mods.betterfoliage.BetterFoliage
import mods.betterfoliage.chunk.BasicBlockCtx import mods.betterfoliage.BetterFoliageMod
import mods.betterfoliage.config.BlockConfig import mods.betterfoliage.config.BlockConfig
import mods.betterfoliage.config.SNOW_MATERIALS import mods.betterfoliage.config.Config
import mods.betterfoliage.render.ShadersModIntegration import mods.betterfoliage.config.isSnow
import mods.betterfoliage.render.lighting.grassTuftLighting import mods.betterfoliage.integration.ShadersModIntegration
import mods.betterfoliage.render.lighting.withLighting import mods.betterfoliage.model.Color
import mods.betterfoliage.resource.discovery.* import mods.betterfoliage.model.HalfBakedSpecialWrapper
import mods.betterfoliage.model.* import mods.betterfoliage.model.HalfBakedWrapperKey
import mods.betterfoliage.util.* import mods.betterfoliage.model.SpecialRenderModel
import net.fabricmc.fabric.api.renderer.v1.material.BlendMode import mods.betterfoliage.model.SpriteSetDelegate
import net.fabricmc.fabric.api.renderer.v1.model.FabricBakedModel import mods.betterfoliage.model.buildTufts
import net.fabricmc.fabric.api.renderer.v1.render.RenderContext import mods.betterfoliage.model.fullCubeTextured
import net.minecraft.block.BlockState import mods.betterfoliage.model.tuftModelSet
import net.minecraft.client.render.model.BakedModel import mods.betterfoliage.model.tuftShapeSet
import net.minecraft.client.render.model.BasicBakedModel import mods.betterfoliage.render.lighting.LightingPreferredFace
import net.minecraft.util.Identifier import mods.betterfoliage.render.pipeline.RenderCtxBase
import net.minecraft.util.math.BlockPos import mods.betterfoliage.resource.discovery.BakeWrapperManager
import net.minecraft.util.math.Direction.* import mods.betterfoliage.resource.discovery.ConfigurableBlockMatcher
import net.minecraft.world.BlockRenderView import mods.betterfoliage.resource.discovery.ConfigurableModelDiscovery
import java.util.* import mods.betterfoliage.resource.discovery.ModelBakingContext
import java.util.function.Consumer import mods.betterfoliage.resource.discovery.ModelDiscoveryContext
import java.util.function.Supplier import mods.betterfoliage.resource.discovery.ModelTextureList
import mods.betterfoliage.util.Atlas
import mods.betterfoliage.util.LazyInvalidatable
import mods.betterfoliage.util.LazyMapInvalidatable
import mods.betterfoliage.util.averageColor
import mods.betterfoliage.util.colorOverride
import mods.betterfoliage.util.get
import mods.betterfoliage.util.logColorOverride
import mods.betterfoliage.util.randomI
import net.minecraft.util.Direction.DOWN
import net.minecraft.util.Direction.UP
import net.minecraft.util.ResourceLocation
object StandardGrassDiscovery : ConfigurableModelDiscovery() { object StandardGrassDiscovery : ConfigurableModelDiscovery() {
override val matchClasses: ConfigurableBlockMatcher get() = BetterFoliage.blockConfig.grassBlocks override val matchClasses: ConfigurableBlockMatcher get() = BlockConfig.grassBlocks
override val modelTextures: List<ModelTextureList> get() = BetterFoliage.blockConfig.grassModels.modelList override val modelTextures: List<ModelTextureList> get() = BlockConfig.grassModels.modelList
override fun processModel(ctx: ModelDiscoveryContext, textureMatch: List<Identifier>) { override fun processModel(ctx: ModelDiscoveryContext, textureMatch: List<ResourceLocation>) {
ctx.addReplacement(StandardGrassKey(textureMatch[0], null)) ctx.addReplacement(StandardGrassKey(textureMatch[0], null))
BetterFoliage.blockTypes.grass.add(ctx.blockState) BetterFoliage.blockTypes.grass.add(ctx.blockState)
} }
} }
data class StandardGrassKey( data class StandardGrassKey(
val grassLocation: Identifier, val grassLocation: ResourceLocation,
val overrideColor: Color? val overrideColor: Color?
) : ModelWrapKey() { ) : HalfBakedWrapperKey() {
val tintIndex: Int get() = if (overrideColor == null) 0 else -1 val tintIndex: Int get() = if (overrideColor == null) 0 else -1
override fun bake(ctx: ModelBakingContext, wrapped: BasicBakedModel): BakedModel { override fun bake(ctx: ModelBakingContext, wrapped: SpecialRenderModel): SpecialRenderModel {
val grassSpriteColor = Atlas.BLOCKS[grassLocation].averageColor.let { hsb -> val grassSpriteColor = Atlas.BLOCKS[grassLocation].averageColor.let { hsb ->
logColorOverride(detailLogger, BetterFoliage.config.shortGrass.saturationThreshold, hsb) logColorOverride(BetterFoliageMod.detailLogger(this), Config.shortGrass.saturationThreshold, hsb)
hsb.colorOverride(BetterFoliage.config.shortGrass.saturationThreshold) hsb.colorOverride(Config.shortGrass.saturationThreshold)
} }
return StandardGrassModel(meshifyCutoutMipped(wrapped), this.copy(overrideColor = grassSpriteColor)) return StandardGrassModel(wrapped, this.copy(overrideColor = grassSpriteColor))
} }
} }
class StandardGrassModel(wrapped: BakedModel, val key: StandardGrassKey) : WrappedBakedModel(wrapped) { class StandardGrassModel(
wrapped: SpecialRenderModel,
key: StandardGrassKey
) : HalfBakedSpecialWrapper(wrapped) {
val tuftNormal by grassTuftMeshesNormal.delegate(key) val tuftNormal by grassTuftMeshesNormal.delegate(key)
val tuftSnowed by grassTuftMeshesSnowed.delegate(key) val tuftSnowed by grassTuftMeshesSnowed.delegate(key)
val fullBlock by grassFullBlockMeshes.delegate(key) val fullBlock by grassFullBlockMeshes.delegate(key)
val tuftLighting = LightingPreferredFace(UP)
val tuftLighting = grassTuftLighting(UP) override fun render(ctx: RenderCtxBase, noDecorations: Boolean) {
if (!Config.enabled || noDecorations) return super.render(ctx, noDecorations)
override fun emitBlockQuads(blockView: BlockRenderView, state: BlockState, pos: BlockPos, randomSupplier: Supplier<Random>, context: RenderContext) {
if (!BetterFoliage.config.enabled) return super.emitBlockQuads(blockView, state, pos, randomSupplier, context)
val ctx = BasicBlockCtx(blockView, pos)
val stateBelow = ctx.state(DOWN) val stateBelow = ctx.state(DOWN)
val stateAbove = ctx.state(UP) val stateAbove = ctx.state(UP)
val isAir = ctx.isAir(UP)
val isSnowed = stateAbove.isSnow
val connected = Config.connectedGrass.enabled &&
(!isSnowed || Config.connectedGrass.snowEnabled) &&
BetterFoliage.blockTypes.run { stateBelow in grass || stateBelow in dirt }
val isSnowed = stateAbove.material in SNOW_MATERIALS
val connected = BetterFoliage.config.connectedGrass.enabled &&
(!isSnowed || BetterFoliage.config.connectedGrass.snowEnabled) &&
(stateBelow in BetterFoliage.blockTypes.dirt || stateBelow in BetterFoliage.blockTypes.grass)
val random = randomSupplier.get()
if (connected) { if (connected) {
context.meshConsumer().accept(if (isSnowed) snowFullBlockMeshes[random] else fullBlock[random]) ctx.renderQuads(if (isSnowed) snowFullBlockMeshes[ctx.random] else fullBlock[ctx.random])
} else { } else {
super.emitBlockQuads(blockView, state, pos, randomSupplier, context) super.render(ctx, noDecorations)
} }
if (BetterFoliage.config.shortGrass.enabled(random) && !ctx.isNeighborSolid(UP)) { if (Config.shortGrass.enabled(ctx.random) && Config.shortGrass.grassEnabled && (isAir || isSnowed)) {
ShadersModIntegration.grass(context, BetterFoliage.config.shortGrass.shaderWind) { ctx.vertexLighter = tuftLighting
context.withLighting(tuftLighting) { ShadersModIntegration.grass(ctx, Config.shortGrass.shaderWind) {
it.accept(if (isSnowed) tuftSnowed[random] else tuftNormal[random]) ctx.renderQuads(if (isSnowed) tuftSnowed[ctx.random] else tuftNormal[ctx.random])
}
} }
} }
} }
companion object { companion object {
val grassTuftSpritesNormal by SpriteSetDelegate(Atlas.BLOCKS) { idx -> val grassTuftSprites by SpriteSetDelegate(Atlas.BLOCKS) { idx ->
Identifier(BetterFoliage.MOD_ID, "blocks/better_grass_long_$idx") ResourceLocation(BetterFoliageMod.MOD_ID, "blocks/better_grass_long_$idx")
} }
val grassTuftSpritesSnowed by SpriteSetDelegate(Atlas.BLOCKS) { idx -> val grassTuftShapes by LazyInvalidatable(BakeWrapperManager) {
Identifier(BetterFoliage.MOD_ID, "blocks/better_grass_long_$idx") Config.shortGrass.let { tuftShapeSet(it.size, it.heightMin, it.heightMax, it.hOffset) }
} }
val grassTuftShapes = LazyMap(BakeWrapperManager) { key: StandardGrassKey -> val grassTuftMeshesNormal = LazyMapInvalidatable(BakeWrapperManager) { key: StandardGrassKey ->
BetterFoliage.config.shortGrass.let { tuftShapeSet(it.size, it.heightMin, it.heightMax, it.hOffset) } tuftModelSet(grassTuftShapes, key.tintIndex) { idx -> grassTuftSprites[randomI()] }.buildTufts()
} }
val grassTuftMeshesNormal = LazyMap(BakeWrapperManager) { key: StandardGrassKey -> val grassTuftMeshesSnowed = LazyMapInvalidatable(BakeWrapperManager) { key: StandardGrassKey ->
tuftModelSet(grassTuftShapes[key], key.tintIndex) { idx -> grassTuftSpritesNormal[randomI()] } tuftModelSet(grassTuftShapes, -1) { idx -> grassTuftSprites[randomI()] }.buildTufts()
.withOpposites()
.build(BlendMode.CUTOUT_MIPPED, flatLighting = false)
} }
val grassTuftMeshesSnowed = LazyMap(BakeWrapperManager) { key: StandardGrassKey -> val grassFullBlockMeshes = LazyMapInvalidatable(BakeWrapperManager) { key: StandardGrassKey ->
tuftModelSet(grassTuftShapes[key], Color.white.asInt) { idx -> grassTuftSpritesSnowed[randomI()] }
.withOpposites()
.build(BlendMode.CUTOUT_MIPPED, flatLighting = false)
}
val grassFullBlockMeshes = LazyMap(BakeWrapperManager) { key: StandardGrassKey ->
Array(64) { fullCubeTextured(key.grassLocation, key.tintIndex) } Array(64) { fullCubeTextured(key.grassLocation, key.tintIndex) }
} }
val snowFullBlockMeshes by LazyInvalidatable(BakeWrapperManager) { val snowFullBlockMeshes by LazyInvalidatable(BakeWrapperManager) {
Array(64) { fullCubeTextured(Identifier("block/snow"), -1) } Array(64) { fullCubeTextured(ResourceLocation("block/snow"), -1) }
} }
} }
} }

View File

@@ -1,19 +1,23 @@
package mods.betterfoliage.render.block.vanilla package mods.betterfoliage.render.block.vanilla
import mods.betterfoliage.BetterFoliage import mods.betterfoliage.BetterFoliage
import mods.betterfoliage.chunk.BasicBlockCtx import mods.betterfoliage.BetterFoliageMod
import mods.betterfoliage.config.SNOW_MATERIALS import mods.betterfoliage.config.BlockConfig
import mods.betterfoliage.config.Config
import mods.betterfoliage.config.isSnow
import mods.betterfoliage.integration.ShadersModIntegration
import mods.betterfoliage.model.Color import mods.betterfoliage.model.Color
import mods.betterfoliage.model.ModelWrapKey import mods.betterfoliage.model.HalfBakedSpecialWrapper
import mods.betterfoliage.model.HalfBakedWrapperKey
import mods.betterfoliage.model.SpecialRenderModel
import mods.betterfoliage.model.SpriteSetDelegate import mods.betterfoliage.model.SpriteSetDelegate
import mods.betterfoliage.model.WrappedBakedModel
import mods.betterfoliage.model.crossModelsRaw import mods.betterfoliage.model.crossModelsRaw
import mods.betterfoliage.model.crossModelsTextured import mods.betterfoliage.model.crossModelsTextured
import mods.betterfoliage.model.meshifyCutoutMipped import mods.betterfoliage.render.lighting.RoundLeafLightingPreferUp
import mods.betterfoliage.render.ShadersModIntegration import mods.betterfoliage.render.particle.LeafBlockModel
import mods.betterfoliage.render.lighting.roundLeafLighting import mods.betterfoliage.render.particle.LeafParticleKey
import mods.betterfoliage.render.lighting.withLighting
import mods.betterfoliage.render.particle.LeafParticleRegistry import mods.betterfoliage.render.particle.LeafParticleRegistry
import mods.betterfoliage.render.pipeline.RenderCtxBase
import mods.betterfoliage.resource.discovery.BakeWrapperManager import mods.betterfoliage.resource.discovery.BakeWrapperManager
import mods.betterfoliage.resource.discovery.ConfigurableBlockMatcher import mods.betterfoliage.resource.discovery.ConfigurableBlockMatcher
import mods.betterfoliage.resource.discovery.ConfigurableModelDiscovery import mods.betterfoliage.resource.discovery.ConfigurableModelDiscovery
@@ -22,101 +26,76 @@ import mods.betterfoliage.resource.discovery.ModelDiscoveryContext
import mods.betterfoliage.resource.discovery.ModelTextureList import mods.betterfoliage.resource.discovery.ModelTextureList
import mods.betterfoliage.resource.generated.GeneratedLeafSprite import mods.betterfoliage.resource.generated.GeneratedLeafSprite
import mods.betterfoliage.util.Atlas import mods.betterfoliage.util.Atlas
import mods.betterfoliage.util.LazyMap import mods.betterfoliage.util.LazyMapInvalidatable
import mods.betterfoliage.util.averageColor import mods.betterfoliage.util.averageColor
import mods.betterfoliage.util.colorOverride import mods.betterfoliage.util.colorOverride
import mods.betterfoliage.util.get
import mods.betterfoliage.util.logColorOverride import mods.betterfoliage.util.logColorOverride
import net.fabricmc.fabric.api.renderer.v1.render.RenderContext import net.minecraft.util.Direction.UP
import net.minecraft.block.BlockState import net.minecraft.util.ResourceLocation
import net.minecraft.client.render.model.BakedModel import org.apache.logging.log4j.Level.INFO
import net.minecraft.client.render.model.BasicBakedModel
import net.minecraft.util.Identifier
import net.minecraft.util.math.BlockPos
import net.minecraft.util.math.Direction.UP
import net.minecraft.world.BlockRenderView
import org.apache.logging.log4j.Level
import java.util.Random
import java.util.function.Supplier
interface LeafBlockModel {
val key: LeafParticleKey
}
interface LeafParticleKey {
val leafType: String
val overrideColor: Color?
}
object StandardLeafDiscovery : ConfigurableModelDiscovery() { object StandardLeafDiscovery : ConfigurableModelDiscovery() {
override val matchClasses: ConfigurableBlockMatcher get() = BetterFoliage.blockConfig.leafBlocks override val matchClasses: ConfigurableBlockMatcher get() = BlockConfig.leafBlocks
override val modelTextures: List<ModelTextureList> get() = BetterFoliage.blockConfig.leafModels.modelList override val modelTextures: List<ModelTextureList> get() = BlockConfig.leafModels.modelList
override fun processModel(ctx: ModelDiscoveryContext, textureMatch: List<Identifier>) { override fun processModel(ctx: ModelDiscoveryContext, textureMatch: List<ResourceLocation>) {
val leafType = LeafParticleRegistry.typeMappings.getType(textureMatch[0]) ?: "default" val leafType = LeafParticleRegistry.typeMappings.getType(textureMatch[0]) ?: "default"
val generated = GeneratedLeafSprite(textureMatch[0], leafType) val generated = GeneratedLeafSprite(textureMatch[0], leafType)
.register(BetterFoliage.generatedPack) .register(BetterFoliage.generatedPack)
.apply { ctx.sprites.add(this) } .apply { ctx.sprites.add(this) }
detailLogger.log(Level.INFO, " particle $leafType") detailLogger.log(INFO, " particle $leafType")
ctx.addReplacement(StandardLeafKey(generated, leafType, null)) ctx.addReplacement(StandardLeafKey(generated, leafType, null))
} }
} }
data class StandardLeafKey( data class StandardLeafKey(
val roundLeafTexture: Identifier, val roundLeafTexture: ResourceLocation,
override val leafType: String, override val leafType: String,
override val overrideColor: Color? override val overrideColor: Color?
) : ModelWrapKey(), LeafParticleKey { ) : HalfBakedWrapperKey(), LeafParticleKey {
val tintIndex: Int get() = if (overrideColor == null) 0 else -1 val tintIndex: Int get() = if (overrideColor == null) 0 else -1
override fun bake(ctx: ModelBakingContext, wrapped: BasicBakedModel): BakedModel { override fun bake(ctx: ModelBakingContext, wrapped: SpecialRenderModel): SpecialRenderModel {
val leafSpriteColor = Atlas.BLOCKS[roundLeafTexture].averageColor.let { hsb -> val leafSpriteColor = Atlas.BLOCKS[roundLeafTexture].averageColor.let { hsb ->
logColorOverride(detailLogger, BetterFoliage.config.leaves.saturationThreshold, hsb) logColorOverride(BetterFoliageMod.detailLogger(this), Config.leaves.saturationThreshold, hsb)
hsb.colorOverride(BetterFoliage.config.leaves.saturationThreshold) hsb.colorOverride(Config.leaves.saturationThreshold)
} }
return StandardLeafModel(meshifyCutoutMipped(wrapped), this.copy(overrideColor = leafSpriteColor)) return StandardLeafModel(wrapped, this.copy(overrideColor = leafSpriteColor))
} }
} }
class StandardLeafModel( class StandardLeafModel(
wrapped: BakedModel, model: SpecialRenderModel,
override val key: StandardLeafKey override val key: StandardLeafKey
) : WrappedBakedModel(wrapped), LeafBlockModel { ) : HalfBakedSpecialWrapper(model), LeafBlockModel {
val leafNormal by leafModelsNormal.delegate(key) val leafNormal by leafModelsNormal.delegate(key)
val leafSnowed by leafModelsSnowed.delegate(key) val leafSnowed by leafModelsSnowed.delegate(key)
val leafLighting = roundLeafLighting()
override fun emitBlockQuads(blockView: BlockRenderView, state: BlockState, pos: BlockPos, randomSupplier: Supplier<Random>, context: RenderContext) { override fun render(ctx: RenderCtxBase, noDecorations: Boolean) {
ShadersModIntegration.leaves(context, BetterFoliage.config.leaves.shaderWind) { ShadersModIntegration.leaves(ctx, true) {
super.emitBlockQuads(blockView, state, pos, randomSupplier, context) super.render(ctx, noDecorations)
if (!BetterFoliage.config.enabled || !BetterFoliage.config.leaves.enabled) return if (!Config.enabled || !Config.leaves.enabled || noDecorations) return
val ctx = BasicBlockCtx(blockView, pos) ctx.vertexLighter = RoundLeafLightingPreferUp
val stateAbove = ctx.state(UP) val leafIdx = ctx.random.nextInt(64)
val isSnowed = stateAbove.material in SNOW_MATERIALS ctx.renderQuads(leafNormal[leafIdx])
if (ctx.state(UP).isSnow) ctx.renderQuads(leafSnowed[leafIdx])
val random = randomSupplier.get()
context.withLighting(leafLighting) {
it.accept(leafNormal[random])
if (isSnowed) it.accept(leafSnowed[random])
}
} }
} }
companion object { companion object {
val leafSpritesSnowed by SpriteSetDelegate(Atlas.BLOCKS) { idx -> val leafSpritesSnowed by SpriteSetDelegate(Atlas.BLOCKS) { idx ->
Identifier(BetterFoliage.MOD_ID, "blocks/better_leaves_snowed_$idx") ResourceLocation(BetterFoliageMod.MOD_ID, "blocks/better_leaves_snowed_$idx")
} }
val leafModelsBase = LazyMap(BakeWrapperManager) { key: StandardLeafKey -> val leafModelsBase = LazyMapInvalidatable(BakeWrapperManager) { key: StandardLeafKey ->
BetterFoliage.config.leaves.let { crossModelsRaw(64, it.size, it.hOffset, it.vOffset) } Config.leaves.let { crossModelsRaw(64, it.size, it.hOffset, it.vOffset) }
} }
val leafModelsNormal = LazyMap(BakeWrapperManager) { key: StandardLeafKey -> val leafModelsNormal = LazyMapInvalidatable(BakeWrapperManager) { key: StandardLeafKey ->
crossModelsTextured(leafModelsBase[key], key.tintIndex, true) { key.roundLeafTexture } crossModelsTextured(leafModelsBase[key], key.tintIndex, true) { key.roundLeafTexture }
} }
val leafModelsSnowed = LazyMap(BakeWrapperManager) { key: StandardLeafKey -> val leafModelsSnowed = LazyMapInvalidatable(BakeWrapperManager) { key: StandardLeafKey ->
crossModelsTextured(leafModelsBase[key], Color.white.asInt, false) { leafSpritesSnowed[it].id } crossModelsTextured(leafModelsBase[key], -1, false) { leafSpritesSnowed[it].name }
} }
} }
} }

View File

@@ -1,82 +1,76 @@
package mods.betterfoliage.render.block.vanilla package mods.betterfoliage.render.block.vanilla
import mods.betterfoliage.BetterFoliage import mods.betterfoliage.BetterFoliageMod
import mods.betterfoliage.model.Color import mods.betterfoliage.config.Config
import mods.betterfoliage.model.ModelWrapKey import mods.betterfoliage.config.LILYPAD_BLOCKS
import mods.betterfoliage.integration.ShadersModIntegration
import mods.betterfoliage.model.HalfBakedSpecialWrapper
import mods.betterfoliage.model.HalfBakedWrapperKey
import mods.betterfoliage.model.SpecialRenderModel
import mods.betterfoliage.model.SpriteSetDelegate import mods.betterfoliage.model.SpriteSetDelegate
import mods.betterfoliage.model.WrappedBakedModel
import mods.betterfoliage.model.buildTufts import mods.betterfoliage.model.buildTufts
import mods.betterfoliage.model.meshifyCutoutMipped
import mods.betterfoliage.model.meshifyStandard
import mods.betterfoliage.model.transform import mods.betterfoliage.model.transform
import mods.betterfoliage.model.tuftModelSet import mods.betterfoliage.model.tuftModelSet
import mods.betterfoliage.model.tuftShapeSet import mods.betterfoliage.model.tuftShapeSet
import mods.betterfoliage.render.ShadersModIntegration import mods.betterfoliage.render.pipeline.RenderCtxBase
import mods.betterfoliage.resource.discovery.AbstractModelDiscovery import mods.betterfoliage.resource.discovery.AbstractModelDiscovery
import mods.betterfoliage.resource.discovery.BakeWrapperManager import mods.betterfoliage.resource.discovery.BakeWrapperManager
import mods.betterfoliage.resource.discovery.ModelBakingContext import mods.betterfoliage.resource.discovery.ModelBakingContext
import mods.betterfoliage.resource.discovery.ModelBakingKey
import mods.betterfoliage.resource.discovery.ModelDiscoveryContext import mods.betterfoliage.resource.discovery.ModelDiscoveryContext
import mods.betterfoliage.util.Atlas import mods.betterfoliage.util.Atlas
import mods.betterfoliage.util.LazyInvalidatable import mods.betterfoliage.util.LazyInvalidatable
import mods.betterfoliage.util.get import mods.betterfoliage.util.get
import net.fabricmc.fabric.api.renderer.v1.render.RenderContext
import net.minecraft.block.BlockState import net.minecraft.block.BlockState
import net.minecraft.block.Blocks import net.minecraft.block.Blocks
import net.minecraft.client.render.model.BakedModel import net.minecraft.client.renderer.model.BlockModel
import net.minecraft.client.render.model.BasicBakedModel import net.minecraft.client.renderer.model.ModelBakery
import net.minecraft.client.render.model.json.JsonUnbakedModel import net.minecraft.util.Direction.DOWN
import net.minecraft.util.Identifier import net.minecraft.util.ResourceLocation
import net.minecraft.util.math.BlockPos
import net.minecraft.util.math.Direction.DOWN
import net.minecraft.world.BlockRenderView
import java.util.Random
import java.util.function.Supplier
object StandardLilypadDiscovery : AbstractModelDiscovery() { object StandardLilypadDiscovery : AbstractModelDiscovery() {
val LILYPAD_BLOCKS = listOf(Blocks.LILY_PAD)
override fun processModel(ctx: ModelDiscoveryContext) { override fun processModel(ctx: ModelDiscoveryContext) {
if (ctx.getUnbaked() is JsonUnbakedModel && ctx.blockState.block in LILYPAD_BLOCKS) { if (ctx.getUnbaked() is BlockModel && ctx.blockState.block in LILYPAD_BLOCKS) {
ctx.addReplacement(StandardLilypadKey) ctx.addReplacement(StandardLilypadKey)
} }
super.processModel(ctx) super.processModel(ctx)
} }
} }
object StandardLilypadKey : ModelWrapKey() { object StandardLilypadKey : HalfBakedWrapperKey() {
override fun bake(ctx: ModelBakingContext, wrapped: BasicBakedModel) = StandardLilypadModel(meshifyCutoutMipped(wrapped)) override fun bake(ctx: ModelBakingContext, wrapped: SpecialRenderModel) = StandardLilypadModel(wrapped)
} }
class StandardLilypadModel(wrapped: BakedModel) : WrappedBakedModel(wrapped) { class StandardLilypadModel(
override fun emitBlockQuads(blockView: BlockRenderView, state: BlockState, pos: BlockPos, randomSupplier: Supplier<Random>, context: RenderContext) { wrapped: SpecialRenderModel
super.emitBlockQuads(blockView, state, pos, randomSupplier, context) ) : HalfBakedSpecialWrapper(wrapped) {
if (!BetterFoliage.config.enabled || !BetterFoliage.config.lilypad.enabled) return override fun render(ctx: RenderCtxBase, noDecorations: Boolean) {
ctx.checkSides = false
super.render(ctx, noDecorations)
if (!Config.enabled || !Config.lilypad.enabled) return
val random = randomSupplier.get() ShadersModIntegration.grass(ctx, Config.lilypad.shaderWind) {
ShadersModIntegration.grass(context, BetterFoliage.config.lilypad.shaderWind) { ctx.renderQuads(lilypadRootModels[ctx.random])
context.meshConsumer().accept(lilypadRootModels[random])
}
if (random.nextInt(64) < BetterFoliage.config.lilypad.population) {
context.meshConsumer().accept(lilypadFlowerModels[random])
} }
if (Config.lilypad.enabled(ctx.random)) ctx.renderQuads(lilypadFlowerModels[ctx.random])
} }
companion object { companion object {
val lilypadRootSprites by SpriteSetDelegate(Atlas.BLOCKS) { idx -> val lilypadRootSprites by SpriteSetDelegate(Atlas.BLOCKS) { idx ->
Identifier(BetterFoliage.MOD_ID, "blocks/better_lilypad_roots_$idx") ResourceLocation(BetterFoliageMod.MOD_ID, "blocks/better_lilypad_roots_$idx")
} }
val lilypadFlowerSprites by SpriteSetDelegate(Atlas.BLOCKS) { idx -> val lilypadFlowerSprites by SpriteSetDelegate(Atlas.BLOCKS) { idx ->
Identifier(BetterFoliage.MOD_ID, "blocks/better_lilypad_flower_$idx") ResourceLocation(BetterFoliageMod.MOD_ID, "blocks/better_lilypad_flower_$idx")
} }
val lilypadRootModels by LazyInvalidatable(BakeWrapperManager) { val lilypadRootModels by LazyInvalidatable(BakeWrapperManager) {
val shapes = tuftShapeSet(1.0, 1.0, 1.0, BetterFoliage.config.lilypad.hOffset) val shapes = tuftShapeSet(1.0, 1.0, 1.0, Config.lilypad.hOffset)
tuftModelSet(shapes, Color.white.asInt) { lilypadRootSprites[it] } tuftModelSet(shapes, -1) { lilypadRootSprites[it] }
.transform { move(2.0 to DOWN) } .transform { move(2.0 to DOWN) }
.buildTufts() .buildTufts()
} }
val lilypadFlowerModels by LazyInvalidatable(BakeWrapperManager) { val lilypadFlowerModels by LazyInvalidatable(BakeWrapperManager) {
val shapes = tuftShapeSet(0.5, 0.5, 0.5, BetterFoliage.config.lilypad.hOffset) val shapes = tuftShapeSet(0.5, 0.5, 0.5, Config.lilypad.hOffset)
tuftModelSet(shapes, Color.white.asInt) { lilypadFlowerSprites[it] } tuftModelSet(shapes, -1) { lilypadFlowerSprites[it] }
.transform { move(1.0 to DOWN) } .transform { move(1.0 to DOWN) }
.buildTufts() .buildTufts()
} }

View File

@@ -1,18 +1,17 @@
package mods.betterfoliage.render.block.vanilla package mods.betterfoliage.render.block.vanilla
import mods.betterfoliage.BetterFoliage import mods.betterfoliage.BetterFoliageMod
import mods.betterfoliage.model.Color import mods.betterfoliage.config.Config
import mods.betterfoliage.model.ModelWrapKey import mods.betterfoliage.config.MYCELIUM_BLOCKS
import mods.betterfoliage.model.HalfBakedSpecialWrapper
import mods.betterfoliage.model.HalfBakedWrapperKey
import mods.betterfoliage.model.SpecialRenderModel
import mods.betterfoliage.model.SpriteSetDelegate import mods.betterfoliage.model.SpriteSetDelegate
import mods.betterfoliage.model.WrappedBakedModel
import mods.betterfoliage.model.buildTufts import mods.betterfoliage.model.buildTufts
import mods.betterfoliage.model.meshifyCutoutMipped
import mods.betterfoliage.model.meshifyStandard
import mods.betterfoliage.model.tuftModelSet import mods.betterfoliage.model.tuftModelSet
import mods.betterfoliage.model.tuftShapeSet import mods.betterfoliage.model.tuftShapeSet
import mods.betterfoliage.render.ShadersModIntegration import mods.betterfoliage.render.lighting.LightingPreferredFace
import mods.betterfoliage.render.lighting.grassTuftLighting import mods.betterfoliage.render.pipeline.RenderCtxBase
import mods.betterfoliage.render.lighting.withLighting
import mods.betterfoliage.resource.discovery.AbstractModelDiscovery import mods.betterfoliage.resource.discovery.AbstractModelDiscovery
import mods.betterfoliage.resource.discovery.BakeWrapperManager import mods.betterfoliage.resource.discovery.BakeWrapperManager
import mods.betterfoliage.resource.discovery.ModelBakingContext import mods.betterfoliage.resource.discovery.ModelBakingContext
@@ -20,65 +19,53 @@ import mods.betterfoliage.resource.discovery.ModelDiscoveryContext
import mods.betterfoliage.util.Atlas import mods.betterfoliage.util.Atlas
import mods.betterfoliage.util.LazyInvalidatable import mods.betterfoliage.util.LazyInvalidatable
import mods.betterfoliage.util.get import mods.betterfoliage.util.get
import mods.betterfoliage.util.offset
import mods.betterfoliage.util.plus
import mods.betterfoliage.util.randomI import mods.betterfoliage.util.randomI
import net.fabricmc.fabric.api.renderer.v1.render.RenderContext
import net.minecraft.block.BlockState
import net.minecraft.block.Blocks import net.minecraft.block.Blocks
import net.minecraft.client.render.model.BakedModel import net.minecraft.client.renderer.RenderType
import net.minecraft.client.render.model.BasicBakedModel import net.minecraft.client.renderer.RenderTypeLookup
import net.minecraft.client.render.model.json.JsonUnbakedModel import net.minecraft.client.renderer.model.BlockModel
import net.minecraft.util.Identifier import net.minecraft.util.Direction
import net.minecraft.util.math.BlockPos import net.minecraft.util.ResourceLocation
import net.minecraft.util.math.Direction.UP
import net.minecraft.world.BlockRenderView
import java.util.Random
import java.util.function.Supplier
object StandardMyceliumDiscovery : AbstractModelDiscovery() { object StandardMyceliumDiscovery : AbstractModelDiscovery() {
val MYCELIUM_BLOCKS = listOf(Blocks.MYCELIUM)
override fun processModel(ctx: ModelDiscoveryContext) { override fun processModel(ctx: ModelDiscoveryContext) {
if (ctx.getUnbaked() is JsonUnbakedModel && ctx.blockState.block in MYCELIUM_BLOCKS) { if (ctx.getUnbaked() is BlockModel && ctx.blockState.block in MYCELIUM_BLOCKS) {
ctx.addReplacement(StandardMyceliumKey) ctx.addReplacement(StandardMyceliumKey)
// RenderTypeLookup.setRenderLayer(ctx.blockState.block, RenderType.getCutout()) RenderTypeLookup.setRenderLayer(ctx.blockState.block, RenderType.cutout())
} }
super.processModel(ctx) super.processModel(ctx)
} }
} }
object StandardMyceliumKey : ModelWrapKey() { object StandardMyceliumKey : HalfBakedWrapperKey() {
override fun bake(ctx: ModelBakingContext, wrapped: BasicBakedModel) = StandardMyceliumModel(meshifyCutoutMipped(wrapped)) override fun bake(ctx: ModelBakingContext, wrapped: SpecialRenderModel) = StandardMyceliumModel(wrapped)
} }
class StandardMyceliumModel(wrapped: BakedModel) : WrappedBakedModel(wrapped) { class StandardMyceliumModel(
wrapped: SpecialRenderModel
) : HalfBakedSpecialWrapper(wrapped) {
val tuftLighting = grassTuftLighting(UP) val tuftLighting = LightingPreferredFace(Direction.UP)
override fun emitBlockQuads(blockView: BlockRenderView, state: BlockState, pos: BlockPos, randomSupplier: Supplier<Random>, context: RenderContext) { override fun render(ctx: RenderCtxBase, noDecorations: Boolean) {
super.emitBlockQuads(blockView, state, pos, randomSupplier, context) super.render(ctx, noDecorations)
val random = randomSupplier.get() if (Config.shortGrass.enabled(ctx.random) &&
if (BetterFoliage.config.enabled && Config.shortGrass.myceliumEnabled &&
BetterFoliage.config.shortGrass.let { it.myceliumEnabled && random.nextInt(64) < it.population } && ctx.state(Direction.UP).isAir(ctx.world, ctx.pos)
blockView.getBlockState(pos + UP.offset).isAir
) { ) {
ShadersModIntegration.grass(context, BetterFoliage.config.shortGrass.shaderWind) { ctx.vertexLighter = tuftLighting
context.withLighting(tuftLighting) { ctx.renderQuads(myceliumTuftModels[ctx.random])
it.accept(myceliumTuftModels[random])
}
}
} }
} }
companion object { companion object {
val myceliumTuftSprites by SpriteSetDelegate(Atlas.BLOCKS) { idx -> val myceliumTuftSprites by SpriteSetDelegate(Atlas.BLOCKS) { idx ->
Identifier(BetterFoliage.MOD_ID, "blocks/better_mycel_$idx") ResourceLocation(BetterFoliageMod.MOD_ID, "blocks/better_mycel_$idx")
} }
val myceliumTuftModels by LazyInvalidatable(BakeWrapperManager) { val myceliumTuftModels by LazyInvalidatable(BakeWrapperManager) {
val shapes = BetterFoliage.config.shortGrass.let { tuftShapeSet(it.size, it.heightMin, it.heightMax, it.hOffset) } val shapes = Config.shortGrass.let { tuftShapeSet(it.size, it.heightMin, it.heightMax, it.hOffset) }
tuftModelSet(shapes, Color.white.asInt) { idx -> myceliumTuftSprites[randomI()] }.buildTufts() tuftModelSet(shapes, -1) { idx -> myceliumTuftSprites[randomI()] }.buildTufts()
} }
} }
} }

View File

@@ -1,20 +1,19 @@
package mods.betterfoliage.render.block.vanilla package mods.betterfoliage.render.block.vanilla
import mods.betterfoliage.BetterFoliageMod
import mods.betterfoliage.BetterFoliage import mods.betterfoliage.BetterFoliage
import mods.betterfoliage.config.Config
import mods.betterfoliage.config.NETHERRACK_BLOCKS import mods.betterfoliage.config.NETHERRACK_BLOCKS
import mods.betterfoliage.model.Color import mods.betterfoliage.model.HalfBakedSpecialWrapper
import mods.betterfoliage.model.ModelWrapKey import mods.betterfoliage.model.HalfBakedWrapperKey
import mods.betterfoliage.model.SpecialRenderModel
import mods.betterfoliage.model.SpriteSetDelegate import mods.betterfoliage.model.SpriteSetDelegate
import mods.betterfoliage.model.WrappedBakedModel import mods.betterfoliage.model.buildTufts
import mods.betterfoliage.model.build
import mods.betterfoliage.model.meshifyCutoutMipped
import mods.betterfoliage.model.meshifyStandard
import mods.betterfoliage.model.transform import mods.betterfoliage.model.transform
import mods.betterfoliage.model.tuftModelSet import mods.betterfoliage.model.tuftModelSet
import mods.betterfoliage.model.tuftShapeSet import mods.betterfoliage.model.tuftShapeSet
import mods.betterfoliage.model.withOpposites import mods.betterfoliage.render.lighting.LightingPreferredFace
import mods.betterfoliage.render.lighting.grassTuftLighting import mods.betterfoliage.render.pipeline.RenderCtxBase
import mods.betterfoliage.render.lighting.withLighting
import mods.betterfoliage.resource.discovery.AbstractModelDiscovery import mods.betterfoliage.resource.discovery.AbstractModelDiscovery
import mods.betterfoliage.resource.discovery.BakeWrapperManager import mods.betterfoliage.resource.discovery.BakeWrapperManager
import mods.betterfoliage.resource.discovery.ModelBakingContext import mods.betterfoliage.resource.discovery.ModelBakingContext
@@ -23,72 +22,60 @@ import mods.betterfoliage.util.Atlas
import mods.betterfoliage.util.LazyInvalidatable import mods.betterfoliage.util.LazyInvalidatable
import mods.betterfoliage.util.Rotation import mods.betterfoliage.util.Rotation
import mods.betterfoliage.util.get import mods.betterfoliage.util.get
import mods.betterfoliage.util.offset
import mods.betterfoliage.util.plus
import mods.betterfoliage.util.randomI import mods.betterfoliage.util.randomI
import net.fabricmc.fabric.api.renderer.v1.material.BlendMode import net.minecraft.block.Blocks
import net.fabricmc.fabric.api.renderer.v1.render.RenderContext import net.minecraft.client.renderer.RenderType
import net.minecraft.block.BlockState import net.minecraft.client.renderer.RenderTypeLookup
import net.minecraft.client.render.RenderLayer import net.minecraft.client.renderer.model.BlockModel
import net.minecraft.client.render.model.BakedModel import net.minecraft.util.Direction.DOWN
import net.minecraft.client.render.model.BasicBakedModel import net.minecraft.util.ResourceLocation
import net.minecraft.client.render.model.json.JsonUnbakedModel
import net.minecraft.util.Identifier
import net.minecraft.util.math.BlockPos
import net.minecraft.util.math.Direction.DOWN
import net.minecraft.world.BlockRenderView
import java.util.Random
import java.util.function.Supplier
object StandardNetherrackDiscovery : AbstractModelDiscovery() { object StandardNetherrackDiscovery : AbstractModelDiscovery() {
fun canRenderInLayer(layer: RenderType) = when {
fun canRenderInLayer(layer: RenderLayer) = when { !Config.enabled -> layer == RenderType.solid()
!BetterFoliage.config.enabled -> layer == RenderLayer.getSolid() !Config.netherrack.enabled -> layer == RenderType.solid()
!BetterFoliage.config.netherrack.enabled -> layer == RenderLayer.getSolid() else -> layer == RenderType.cutoutMipped()
else -> layer == RenderLayer.getCutoutMipped()
} }
override fun processModel(ctx: ModelDiscoveryContext) { override fun processModel(ctx: ModelDiscoveryContext) {
if (ctx.getUnbaked() is JsonUnbakedModel && ctx.blockState.block in NETHERRACK_BLOCKS) { if (ctx.getUnbaked() is BlockModel && ctx.blockState.block in NETHERRACK_BLOCKS) {
BetterFoliage.blockTypes.dirt.add(ctx.blockState) BetterFoliage.blockTypes.dirt.add(ctx.blockState)
ctx.addReplacement(StandardNetherrackKey) ctx.addReplacement(StandardNetherrackKey)
// RenderTypeLookup.setRenderLayer(ctx.blockState.block, ::canRenderInLayer) RenderTypeLookup.setRenderLayer(ctx.blockState.block, ::canRenderInLayer)
} }
super.processModel(ctx) super.processModel(ctx)
} }
} }
object StandardNetherrackKey : ModelWrapKey() { object StandardNetherrackKey : HalfBakedWrapperKey() {
override fun bake(ctx: ModelBakingContext, wrapped: BasicBakedModel) = StandardNetherrackModel(meshifyCutoutMipped(wrapped)) override fun bake(ctx: ModelBakingContext, wrapped: SpecialRenderModel) = StandardNetherrackModel(wrapped)
} }
class StandardNetherrackModel(wrapped: BakedModel) : WrappedBakedModel(wrapped) { class StandardNetherrackModel(
wrapped: SpecialRenderModel
) : HalfBakedSpecialWrapper(wrapped) {
val tuftLighting = grassTuftLighting(DOWN) val tuftLighting = LightingPreferredFace(DOWN)
override fun emitBlockQuads(blockView: BlockRenderView, state: BlockState, pos: BlockPos, randomSupplier: Supplier<Random>, context: RenderContext) { override fun render(ctx: RenderCtxBase, noDecorations: Boolean) {
super.emitBlockQuads(blockView, state, pos, randomSupplier, context) super.render(ctx, noDecorations)
if (BetterFoliage.config.enabled && if (!Config.enabled || !Config.netherrack.enabled) return
BetterFoliage.config.netherrack.enabled &&
blockView.getBlockState(pos + DOWN.offset).isAir if (ctx.isAir(DOWN)) {
) { ctx.vertexLighter = tuftLighting
val random = randomSupplier.get() ctx.renderQuads(netherrackTuftModels[ctx.random])
context.withLighting(tuftLighting) {
it.accept(netherrackTuftModels[random])
}
} }
} }
companion object { companion object {
val netherrackTuftSprites by SpriteSetDelegate(Atlas.BLOCKS) { idx -> val netherrackTuftSprites by SpriteSetDelegate(Atlas.BLOCKS) { idx ->
Identifier(BetterFoliage.MOD_ID, "blocks/better_netherrack_$idx") ResourceLocation(BetterFoliageMod.MOD_ID, "blocks/better_netherrack_$idx")
} }
val netherrackTuftModels by LazyInvalidatable(BakeWrapperManager) { val netherrackTuftModels by LazyInvalidatable(BakeWrapperManager) {
val shapes = BetterFoliage.config.netherrack.let { tuftShapeSet(it.size, it.heightMin, it.heightMax, it.hOffset) } val shapes = Config.netherrack.let { tuftShapeSet(it.size, it.heightMin, it.heightMax, it.hOffset) }
tuftModelSet(shapes, Color.white.asInt) { netherrackTuftSprites[randomI()] } tuftModelSet(shapes, -1) { netherrackTuftSprites[randomI()] }
.transform { rotate(Rotation.fromUp[DOWN.ordinal]).rotateUV(2) } .transform { rotate(Rotation.fromUp[DOWN.ordinal]).rotateUV(2) }
.withOpposites() .buildTufts()
.build(BlendMode.CUTOUT_MIPPED, flatLighting = false)
} }
} }
} }

View File

@@ -1,8 +1,11 @@
package mods.betterfoliage.render.block.vanilla package mods.betterfoliage.render.block.vanilla
import mods.betterfoliage.BetterFoliage import mods.betterfoliage.BetterFoliage
import mods.betterfoliage.model.ModelWrapKey import mods.betterfoliage.config.ACCEPTED_ROUND_LOG_MATERIALS
import mods.betterfoliage.model.meshifySolid import mods.betterfoliage.config.BlockConfig
import mods.betterfoliage.config.Config
import mods.betterfoliage.model.HalfBakedWrapperKey
import mods.betterfoliage.model.SpecialRenderModel
import mods.betterfoliage.render.column.ColumnBlockKey import mods.betterfoliage.render.column.ColumnBlockKey
import mods.betterfoliage.render.column.ColumnMeshSet import mods.betterfoliage.render.column.ColumnMeshSet
import mods.betterfoliage.render.column.ColumnModelBase import mods.betterfoliage.render.column.ColumnModelBase
@@ -15,41 +18,41 @@ import mods.betterfoliage.resource.discovery.ModelBakingKey
import mods.betterfoliage.resource.discovery.ModelDiscoveryContext import mods.betterfoliage.resource.discovery.ModelDiscoveryContext
import mods.betterfoliage.resource.discovery.ModelTextureList import mods.betterfoliage.resource.discovery.ModelTextureList
import mods.betterfoliage.util.Atlas import mods.betterfoliage.util.Atlas
import mods.betterfoliage.util.LazyMap import mods.betterfoliage.util.LazyMapInvalidatable
import mods.betterfoliage.util.tryDefault import mods.betterfoliage.util.tryDefault
import net.minecraft.block.BlockState import net.minecraft.block.BlockState
import net.minecraft.block.PillarBlock import net.minecraft.block.RotatedPillarBlock
import net.minecraft.client.render.model.BakedModel import net.minecraft.util.Direction.Axis
import net.minecraft.client.render.model.BasicBakedModel import net.minecraft.util.ResourceLocation
import net.minecraft.util.Identifier import org.apache.logging.log4j.Level.INFO
import net.minecraft.util.math.Direction.Axis
import org.apache.logging.log4j.Level
interface RoundLogKey : ColumnBlockKey, ModelBakingKey { interface RoundLogKey : ColumnBlockKey, ModelBakingKey {
val barkSprite: Identifier val barkSprite: ResourceLocation
val endSprite: Identifier val endSprite: ResourceLocation
} }
object RoundLogOverlayLayer : ColumnRenderLayer() { object RoundLogOverlayLayer : ColumnRenderLayer() {
override fun getColumnKey(state: BlockState) = BetterFoliage.blockTypes.getTyped<ColumnBlockKey>(state) override fun getColumnKey(state: BlockState) = BetterFoliage.blockTypes.getTypedOrNull<ColumnBlockKey>(state)
override val connectSolids: Boolean get() = BetterFoliage.config.roundLogs.connectSolids override val connectSolids: Boolean get() = Config.roundLogs.connectSolids
override val lenientConnect: Boolean get() = BetterFoliage.config.roundLogs.lenientConnect override val lenientConnect: Boolean get() = Config.roundLogs.lenientConnect
override val defaultToY: Boolean get() = BetterFoliage.config.roundLogs.defaultY override val defaultToY: Boolean get() = Config.roundLogs.defaultY
} }
object StandardRoundLogDiscovery : ConfigurableModelDiscovery() { object StandardRoundLogDiscovery : ConfigurableModelDiscovery() {
override val matchClasses: ConfigurableBlockMatcher get() = BetterFoliage.blockConfig.logBlocks override val matchClasses: ConfigurableBlockMatcher get() = BlockConfig.logBlocks
override val modelTextures: List<ModelTextureList> get() = BetterFoliage.blockConfig.logModels.modelList override val modelTextures: List<ModelTextureList> get() = BlockConfig.logModels.modelList
override fun processModel(ctx: ModelDiscoveryContext, textureMatch: List<Identifier>) { override fun processModel(ctx: ModelDiscoveryContext, textureMatch: List<ResourceLocation>) {
val axis = getAxis(ctx.blockState) val axis = getAxis(ctx.blockState)
detailLogger.log(Level.INFO, " axis $axis")
ctx.addReplacement(StandardRoundLogKey(axis, textureMatch[0], textureMatch[1])) detailLogger.log(INFO, " axis $axis, material ${ctx.blockState.material}")
if (!Config.roundLogs.plantsOnly || ctx.blockState.material in ACCEPTED_ROUND_LOG_MATERIALS)
ctx.addReplacement(StandardRoundLogKey(axis, textureMatch[0], textureMatch[1]))
} }
fun getAxis(state: BlockState): Axis? { fun getAxis(state: BlockState): Axis? {
val axis = tryDefault(null) { state.get(PillarBlock.AXIS).toString() } ?: val axis = tryDefault(null) { state.getValue(RotatedPillarBlock.AXIS).toString() } ?:
state.entries.entries.find { it.key.getName().toLowerCase() == "axis" }?.value?.toString() state.values.entries.find { it.key.name.toLowerCase() == "axis" }?.value?.toString()
return when (axis) { return when (axis) {
"x" -> Axis.X "x" -> Axis.X
"y" -> Axis.Y "y" -> Axis.Y
@@ -61,25 +64,28 @@ object StandardRoundLogDiscovery : ConfigurableModelDiscovery() {
data class StandardRoundLogKey( data class StandardRoundLogKey(
override val axis: Axis?, override val axis: Axis?,
override val barkSprite: Identifier, override val barkSprite: ResourceLocation,
override val endSprite: Identifier override val endSprite: ResourceLocation
) : RoundLogKey, ModelWrapKey() { ) : RoundLogKey, HalfBakedWrapperKey() {
override fun bake(ctx: ModelBakingContext, wrapped: BasicBakedModel) = StandardRoundLogModel(meshifySolid(wrapped), this) override fun bake(ctx: ModelBakingContext, wrapped: SpecialRenderModel) = StandardRoundLogModel(this, wrapped)
} }
class StandardRoundLogModel(wrapped: BakedModel, val key: StandardRoundLogKey) : ColumnModelBase(wrapped) { class StandardRoundLogModel(
override val enabled: Boolean get() = BetterFoliage.config.enabled && BetterFoliage.config.roundLogs.enabled val key: StandardRoundLogKey,
wrapped: SpecialRenderModel
) : ColumnModelBase(wrapped) {
override val enabled: Boolean get() = Config.enabled && Config.roundLogs.enabled
override val overlayLayer: ColumnRenderLayer get() = RoundLogOverlayLayer override val overlayLayer: ColumnRenderLayer get() = RoundLogOverlayLayer
override val connectPerpendicular: Boolean get() = BetterFoliage.config.roundLogs.connectPerpendicular override val connectPerpendicular: Boolean get() = Config.roundLogs.connectPerpendicular
val modelSet by modelSets.delegate(key) val modelSet by modelSets.delegate(key)
override fun getMeshSet(axis: Axis, quadrant: Int) = modelSet override fun getMeshSet(axis: Axis, quadrant: Int) = modelSet
companion object { companion object {
val modelSets = LazyMap(BakeWrapperManager) { key: StandardRoundLogKey -> val modelSets = LazyMapInvalidatable(BakeWrapperManager) { key: StandardRoundLogKey ->
val barkSprite = Atlas.BLOCKS[key.barkSprite]!! val barkSprite = Atlas.BLOCKS[key.barkSprite]
val endSprite = Atlas.BLOCKS[key.endSprite]!! val endSprite = Atlas.BLOCKS[key.endSprite]
BetterFoliage.config.roundLogs.let { config -> Config.roundLogs.let { config ->
ColumnMeshSet( ColumnMeshSet(
config.radiusSmall, config.radiusLarge, config.zProtection, config.radiusSmall, config.radiusLarge, config.zProtection,
key.axis ?: Axis.Y, key.axis ?: Axis.Y,

View File

@@ -1,23 +1,22 @@
package mods.betterfoliage.render.block.vanilla package mods.betterfoliage.render.block.vanilla
import mods.betterfoliage.BetterFoliage import mods.betterfoliage.BetterFoliage
import mods.betterfoliage.chunk.CachedBlockCtx import mods.betterfoliage.BetterFoliageMod
import mods.betterfoliage.config.Config
import mods.betterfoliage.config.SALTWATER_BIOMES import mods.betterfoliage.config.SALTWATER_BIOMES
import mods.betterfoliage.config.SAND_BLOCKS import mods.betterfoliage.config.SAND_BLOCKS
import mods.betterfoliage.model.Color import mods.betterfoliage.model.HalfBakedSpecialWrapper
import mods.betterfoliage.model.ModelWrapKey import mods.betterfoliage.model.HalfBakedWrapperKey
import mods.betterfoliage.model.Quad
import mods.betterfoliage.model.SpecialRenderModel
import mods.betterfoliage.model.SpriteSetDelegate import mods.betterfoliage.model.SpriteSetDelegate
import mods.betterfoliage.model.WrappedBakedModel import mods.betterfoliage.model.bake
import mods.betterfoliage.model.build import mods.betterfoliage.model.buildTufts
import mods.betterfoliage.model.horizontalRectangle
import mods.betterfoliage.model.meshifySolid
import mods.betterfoliage.model.meshifyStandard
import mods.betterfoliage.model.transform import mods.betterfoliage.model.transform
import mods.betterfoliage.model.tuftModelSet import mods.betterfoliage.model.tuftModelSet
import mods.betterfoliage.model.tuftShapeSet import mods.betterfoliage.model.tuftShapeSet
import mods.betterfoliage.model.withOpposites import mods.betterfoliage.render.lighting.LightingPreferredFace
import mods.betterfoliage.render.lighting.grassTuftLighting import mods.betterfoliage.render.pipeline.RenderCtxBase
import mods.betterfoliage.render.lighting.withLighting
import mods.betterfoliage.resource.discovery.AbstractModelDiscovery import mods.betterfoliage.resource.discovery.AbstractModelDiscovery
import mods.betterfoliage.resource.discovery.BakeWrapperManager import mods.betterfoliage.resource.discovery.BakeWrapperManager
import mods.betterfoliage.resource.discovery.ModelBakingContext import mods.betterfoliage.resource.discovery.ModelBakingContext
@@ -27,94 +26,80 @@ import mods.betterfoliage.util.LazyInvalidatable
import mods.betterfoliage.util.Rotation import mods.betterfoliage.util.Rotation
import mods.betterfoliage.util.allDirections import mods.betterfoliage.util.allDirections
import mods.betterfoliage.util.get import mods.betterfoliage.util.get
import mods.betterfoliage.util.mapArray
import mods.betterfoliage.util.randomB import mods.betterfoliage.util.randomB
import mods.betterfoliage.util.randomD import mods.betterfoliage.util.randomD
import mods.betterfoliage.util.randomI import mods.betterfoliage.util.randomI
import net.fabricmc.fabric.api.renderer.v1.material.BlendMode import net.minecraft.block.material.Material
import net.fabricmc.fabric.api.renderer.v1.render.RenderContext import net.minecraft.client.renderer.RenderType
import net.minecraft.block.BlockState import net.minecraft.client.renderer.RenderTypeLookup
import net.minecraft.block.Blocks import net.minecraft.client.renderer.model.BlockModel
import net.minecraft.block.Material import net.minecraft.util.Direction
import net.minecraft.client.render.model.BakedModel import net.minecraft.util.Direction.UP
import net.minecraft.client.render.model.BasicBakedModel import net.minecraft.util.ResourceLocation
import net.minecraft.client.render.model.json.JsonUnbakedModel
import net.minecraft.util.Identifier
import net.minecraft.util.math.BlockPos
import net.minecraft.util.math.Direction.UP
import net.minecraft.world.BlockRenderView
import java.util.Random
import java.util.function.Supplier
object StandardSandDiscovery : AbstractModelDiscovery() { object StandardSandDiscovery : AbstractModelDiscovery() {
override fun processModel(ctx: ModelDiscoveryContext) { override fun processModel(ctx: ModelDiscoveryContext) {
if (ctx.getUnbaked() is JsonUnbakedModel && ctx.blockState.block in SAND_BLOCKS) { if (ctx.getUnbaked() is BlockModel && ctx.blockState.block in SAND_BLOCKS) {
BetterFoliage.blockTypes.dirt.add(ctx.blockState) BetterFoliage.blockTypes.dirt.add(ctx.blockState)
ctx.addReplacement(StandardSandKey) ctx.addReplacement(StandardSandKey)
// RenderTypeLookup.setRenderLayer(ctx.blockState.block, RenderType.getCutoutMipped()) RenderTypeLookup.setRenderLayer(ctx.blockState.block, RenderType.cutoutMipped())
} }
super.processModel(ctx) super.processModel(ctx)
} }
} }
object StandardSandKey : ModelWrapKey() { object StandardSandKey : HalfBakedWrapperKey() {
override fun bake(ctx: ModelBakingContext, wrapped: BasicBakedModel) = StandardSandModel(meshifySolid(wrapped)) override fun bake(ctx: ModelBakingContext, wrapped: SpecialRenderModel) = StandardSandModel(wrapped)
} }
class StandardSandModel(wrapped: BakedModel) : WrappedBakedModel(wrapped) { class StandardSandModel(
wrapped: SpecialRenderModel
) : HalfBakedSpecialWrapper(wrapped) {
val coralLighting = Direction.values().mapArray { LightingPreferredFace(it) }
val coralLighting = allDirections.map { grassTuftLighting(it) }.toTypedArray() override fun render(ctx: RenderCtxBase, noDecorations: Boolean) {
super.render(ctx, noDecorations)
if (noDecorations || !Config.enabled || !Config.coral.enabled(ctx.random)) return
if (ctx.biome?.biomeCategory !in SALTWATER_BIOMES) return
override fun emitBlockQuads(blockView: BlockRenderView, state: BlockState, pos: BlockPos, randomSupplier: Supplier<Random>, context: RenderContext) { allDirections.filter { ctx.random.nextInt(64) < Config.coral.chance }.forEach { face ->
super.emitBlockQuads(blockView, state, pos, randomSupplier, context)
val ctx = CachedBlockCtx(blockView, pos)
val random = randomSupplier.get()
if (!BetterFoliage.config.enabled || !BetterFoliage.config.coral.enabled(random)) return
if (ctx.biome?.category !in SALTWATER_BIOMES) return
allDirections.filter { random.nextInt(64) < BetterFoliage.config.coral.chance }.forEach { face ->
val isWater = ctx.state(face).material == Material.WATER val isWater = ctx.state(face).material == Material.WATER
val isDeepWater = isWater && ctx.offset(face).state(UP).material == Material.WATER val isDeepWater = isWater && ctx.offset(face).state(UP).material == Material.WATER
if (isDeepWater) context.withLighting(coralLighting[face]) { if (isDeepWater) {
it.accept(coralCrustModels[face][random]) ctx.vertexLighter = coralLighting[face]
it.accept(coralTuftModels[face][random]) ctx.renderQuads(coralCrustModels[face][ctx.random])
ctx.renderQuads(coralTuftModels[face][ctx.random])
} }
} }
} }
companion object { companion object {
// val sandModel by LazyInvalidatable(BetterFoliage.modelReplacer) {
// Array(64) { fullCubeTextured(Identifier("block/sand"), Color.white.asInt) }
// }
val coralTuftSprites by SpriteSetDelegate(Atlas.BLOCKS) { idx -> val coralTuftSprites by SpriteSetDelegate(Atlas.BLOCKS) { idx ->
Identifier(BetterFoliage.MOD_ID, "blocks/better_coral_$idx") ResourceLocation(BetterFoliageMod.MOD_ID, "blocks/better_coral_$idx")
} }
val coralCrustSprites by SpriteSetDelegate(Atlas.BLOCKS) { idx -> val coralCrustSprites by SpriteSetDelegate(Atlas.BLOCKS) { idx ->
Identifier(BetterFoliage.MOD_ID, "blocks/better_crust_$idx") ResourceLocation(BetterFoliageMod.MOD_ID, "blocks/better_crust_$idx")
} }
val coralTuftModels by LazyInvalidatable(BakeWrapperManager) { val coralTuftModels by LazyInvalidatable(BakeWrapperManager) {
val shapes = BetterFoliage.config.coral.let { tuftShapeSet(it.size, 1.0, 1.0, it.hOffset) } val shapes = Config.coral.let { tuftShapeSet(it.size, 1.0, 1.0, it.hOffset) }
allDirections.map { face -> allDirections.mapArray { face ->
tuftModelSet(shapes, Color.white.asInt) { coralTuftSprites[randomI()] } tuftModelSet(shapes, -1) { coralTuftSprites[randomI()] }
.transform { rotate(Rotation.fromUp[face]) } .transform { rotate(Rotation.fromUp[face]) }
.withOpposites() .buildTufts()
.build(BlendMode.CUTOUT_MIPPED) }
}.toTypedArray()
} }
val coralCrustModels by LazyInvalidatable(BakeWrapperManager) { val coralCrustModels by LazyInvalidatable(BakeWrapperManager) {
allDirections.map { face -> allDirections.map { face ->
Array(64) { idx -> Array(64) { idx ->
listOf(horizontalRectangle(x1 = -0.5, x2 = 0.5, z1 = -0.5, z2 = 0.5, y = 0.0) listOf(
.scale(BetterFoliage.config.coral.crustSize) Quad.horizontalRectangle(x1 = -0.5, x2 = 0.5, z1 = -0.5, z2 = 0.5, y = 0.0)
.move(0.5 + randomD(0.01, BetterFoliage.config.coral.vOffset) to UP) .scale(Config.coral.crustSize)
.rotate(Rotation.fromUp[face]) .move(0.5 + randomD(0.01, Config.coral.vOffset) to UP)
.mirrorUV(randomB(), randomB()).rotateUV(randomI(max = 4)) .rotate(Rotation.fromUp[face])
.sprite(coralCrustSprites[idx]).colorAndIndex(null) .mirrorUV(randomB(), randomB()).rotateUV(randomI(max = 4))
).build(BlendMode.CUTOUT_MIPPED) .sprite(coralCrustSprites[idx]).colorAndIndex(null)
).bake(applyDiffuseLighting = false)
} }
}.toTypedArray() }.toTypedArray()
} }

View File

@@ -1,26 +1,22 @@
package mods.betterfoliage.render.column package mods.betterfoliage.render.column
import mods.betterfoliage.BetterFoliage import mods.betterfoliage.config.Config
import mods.betterfoliage.model.Quad
import mods.betterfoliage.model.UV
import mods.betterfoliage.model.Vertex
import mods.betterfoliage.model.bake
import mods.betterfoliage.render.column.ColumnLayerData.SpecialRender.QuadrantType import mods.betterfoliage.render.column.ColumnLayerData.SpecialRender.QuadrantType
import mods.betterfoliage.render.column.ColumnLayerData.SpecialRender.QuadrantType.INVISIBLE import mods.betterfoliage.render.column.ColumnLayerData.SpecialRender.QuadrantType.INVISIBLE
import mods.betterfoliage.render.column.ColumnLayerData.SpecialRender.QuadrantType.LARGE_RADIUS import mods.betterfoliage.render.column.ColumnLayerData.SpecialRender.QuadrantType.LARGE_RADIUS
import mods.betterfoliage.render.column.ColumnLayerData.SpecialRender.QuadrantType.SMALL_RADIUS import mods.betterfoliage.render.column.ColumnLayerData.SpecialRender.QuadrantType.SMALL_RADIUS
import mods.betterfoliage.render.column.ColumnLayerData.SpecialRender.QuadrantType.SQUARE import mods.betterfoliage.render.column.ColumnLayerData.SpecialRender.QuadrantType.SQUARE
import mods.betterfoliage.model.Color
import mods.betterfoliage.model.Quad
import mods.betterfoliage.model.UV
import mods.betterfoliage.model.Vertex
import mods.betterfoliage.model.build
import mods.betterfoliage.model.horizontalRectangle
import mods.betterfoliage.model.verticalRectangle
import mods.betterfoliage.util.Double3 import mods.betterfoliage.util.Double3
import mods.betterfoliage.util.Rotation import mods.betterfoliage.util.Rotation
import net.fabricmc.fabric.api.renderer.v1.material.BlendMode.SOLID import net.minecraft.client.renderer.texture.TextureAtlasSprite
import net.minecraft.client.texture.Sprite import net.minecraft.util.Direction.Axis
import net.minecraft.util.math.Direction.Axis import net.minecraft.util.Direction.EAST
import net.minecraft.util.math.Direction.EAST import net.minecraft.util.Direction.SOUTH
import net.minecraft.util.math.Direction.SOUTH import net.minecraft.util.Direction.UP
import net.minecraft.util.math.Direction.UP
/** /**
* Collection of dynamically generated meshes used to render rounded columns. * Collection of dynamically generated meshes used to render rounded columns.
@@ -30,20 +26,20 @@ class ColumnMeshSet(
radiusLarge: Double, radiusLarge: Double,
zProtection: Double, zProtection: Double,
val axis: Axis, val axis: Axis,
val spriteLeft: Sprite, val spriteLeft: TextureAtlasSprite,
val spriteRight: Sprite, val spriteRight: TextureAtlasSprite,
val spriteTop: Sprite, val spriteTop: TextureAtlasSprite,
val spriteBottom: Sprite val spriteBottom: TextureAtlasSprite
) { ) {
protected fun sideRounded(radius: Double, yBottom: Double, yTop: Double): List<Quad> { protected fun sideRounded(radius: Double, yBottom: Double, yTop: Double): List<Quad> {
val halfRadius = radius * 0.5 val halfRadius = radius * 0.5
return listOf( return listOf(
// left side of the diagonal // left side of the diagonal
verticalRectangle(0.0, 0.5, 0.5 - radius, 0.5, yBottom, yTop).clampUV(minU = 0.0, maxU = 0.5 - radius), Quad.verticalRectangle(0.0, 0.5, 0.5 - radius, 0.5, yBottom, yTop).clampUV(minU = 0.0, maxU = 0.5 - radius),
verticalRectangle(0.5 - radius, 0.5, 0.5 - halfRadius, 0.5 - halfRadius, yBottom, yTop).clampUV(minU = 0.5 - radius), Quad.verticalRectangle(0.5 - radius, 0.5, 0.5 - halfRadius, 0.5 - halfRadius, yBottom, yTop).clampUV(minU = 0.5 - radius),
// right side of the diagonal // right side of the diagonal
verticalRectangle(0.5 - halfRadius, 0.5 - halfRadius, 0.5, 0.5 - radius, yBottom, yTop).clampUV(maxU = radius - 0.5), Quad.verticalRectangle(0.5 - halfRadius, 0.5 - halfRadius, 0.5, 0.5 - radius, yBottom, yTop).clampUV(maxU = radius - 0.5),
verticalRectangle(0.5, 0.5 - radius, 0.5, 0.0, yBottom, yTop).clampUV(minU = radius - 0.5, maxU = 0.0) Quad.verticalRectangle(0.5, 0.5 - radius, 0.5, 0.0, yBottom, yTop).clampUV(minU = radius - 0.5, maxU = 0.0)
) )
} }
@@ -57,8 +53,8 @@ class ColumnMeshSet(
} }
protected fun sideSquare(yBottom: Double, yTop: Double) = listOf( protected fun sideSquare(yBottom: Double, yTop: Double) = listOf(
verticalRectangle(0.0, 0.5, 0.5, 0.5, yBottom, yTop).clampUV(minU = 0.0), Quad.verticalRectangle(0.0, 0.5, 0.5, 0.5, yBottom, yTop).clampUV(minU = 0.0),
verticalRectangle(0.5, 0.5, 0.5, 0.0, yBottom, yTop).clampUV(maxU = 0.0) Quad.verticalRectangle(0.5, 0.5, 0.5, 0.0, yBottom, yTop).clampUV(maxU = 0.0)
) )
protected fun lidRounded(radius: Double, y: Double, isBottom: Boolean) = Array(4) { quadrant -> protected fun lidRounded(radius: Double, y: Double, isBottom: Boolean) = Array(4) { quadrant ->
@@ -70,18 +66,18 @@ class ColumnMeshSet(
val v5 = Vertex(Double3(0.5, y, 0.5 - radius), UV(0.5, 0.5 - radius)) val v5 = Vertex(Double3(0.5, y, 0.5 - radius), UV(0.5, 0.5 - radius))
val v6 = Vertex(Double3(0.5, y, 0.0), UV(0.5, 0.0)) val v6 = Vertex(Double3(0.5, y, 0.0), UV(0.5, 0.0))
listOf(Quad(v1, v2, v3, v4), Quad(v1, v4, v5, v6)) listOf(Quad(v1, v2, v3, v4), Quad(v1, v4, v5, v6))
.map { it.cycleVertices(if (isBottom xor BetterFoliage.config.nVidia) 0 else 1) } .map { it.cycleVertices(if (isBottom xor Config.nVidia) 0 else 1) }
.map { it.rotate(rotation).rotateUV(quadrant) } .map { it.rotate(rotation).rotateUV(quadrant) }
.map { it.sprite(if (isBottom) spriteBottom else spriteTop).colorAndIndex(Color.white.asInt) } .map { it.sprite(if (isBottom) spriteBottom else spriteTop) }
.map { if (isBottom) it.flipped else it } .map { if (isBottom) it.flipped else it }
} }
protected fun lidSquare(y: Double, isBottom: Boolean) = Array(4) { quadrant -> protected fun lidSquare(y: Double, isBottom: Boolean) = Array(4) { quadrant ->
val rotation = baseRotation(axis) + quadrantRotations[quadrant] val rotation = baseRotation(axis) + quadrantRotations[quadrant]
listOf( listOf(
horizontalRectangle(x1 = 0.0, x2 = 0.5, z1 = 0.0, z2 = 0.5, y = y).clampUV(minU = 0.0, minV = 0.0) Quad.horizontalRectangle(x1 = 0.0, x2 = 0.5, z1 = 0.0, z2 = 0.5, y = y).clampUV(minU = 0.0, minV = 0.0)
.rotate(rotation).rotateUV(quadrant) .rotate(rotation).rotateUV(quadrant)
.sprite(if (isBottom) spriteBottom else spriteTop).colorAndIndex(Color.white.asInt) .sprite(if (isBottom) spriteBottom else spriteTop)
.let { if (isBottom) it.flipped else it } .let { if (isBottom) it.flipped else it }
) )
} }
@@ -96,9 +92,9 @@ class ColumnMeshSet(
} }
protected fun List<Quad>.buildSides(quadsPerSprite: Int) = Array(4) { quadrant -> protected fun List<Quad>.buildSides(quadsPerSprite: Int) = Array(4) { quadrant ->
val rotation = baseRotation(axis) + quadrantRotations[quadrant] val rotation = baseRotation(axis) + quadrantRotations[quadrant]
this.map { it.rotate(rotation).colorAndIndex(Color.white.asInt) } this.map { it.rotate(rotation) }
.mapIndexed { idx, q -> if (idx % (2 * quadsPerSprite) >= quadsPerSprite) q.sprite(spriteRight) else q.sprite(spriteLeft) } .mapIndexed { idx, q -> if (idx % (2 * quadsPerSprite) >= quadsPerSprite) q.sprite(spriteRight) else q.sprite(spriteLeft) }
.build(SOLID, flatLighting = false) .bake(false)
} }
companion object { companion object {
@@ -126,13 +122,13 @@ class ColumnMeshSet(
val sideExtendBottomRoundSmall = sideRounded(radiusSmall, -0.5 - radiusLarge, -0.5).extendBottom(radiusLarge).buildSides(quadsPerSprite = 2) val sideExtendBottomRoundSmall = sideRounded(radiusSmall, -0.5 - radiusLarge, -0.5).extendBottom(radiusLarge).buildSides(quadsPerSprite = 2)
val sideExtendBottomRoundLarge = sideRounded(radiusLarge, -0.5 - radiusLarge, -0.5).extendBottom(radiusLarge).buildSides(quadsPerSprite = 2) val sideExtendBottomRoundLarge = sideRounded(radiusLarge, -0.5 - radiusLarge, -0.5).extendBottom(radiusLarge).buildSides(quadsPerSprite = 2)
val lidTopSquare = lidSquare(0.5, false).build(SOLID, flatLighting = false) val lidTopSquare = lidSquare(0.5, false).bake(false)
val lidTopRoundSmall = lidRounded(radiusSmall, 0.5, false).build(SOLID, flatLighting = false) val lidTopRoundSmall = lidRounded(radiusSmall, 0.5, false).bake(false)
val lidTopRoundLarge = lidRounded(radiusLarge, 0.5, false).build(SOLID, flatLighting = false) val lidTopRoundLarge = lidRounded(radiusLarge, 0.5, false).bake(false)
val lidBottomSquare = lidSquare(-0.5, true).build(SOLID, flatLighting = false) val lidBottomSquare = lidSquare(-0.5, true).bake(false)
val lidBottomRoundSmall = lidRounded(radiusSmall, -0.5, true).build(SOLID, flatLighting = false) val lidBottomRoundSmall = lidRounded(radiusSmall, -0.5, true).bake(false)
val lidBottomRoundLarge = lidRounded(radiusLarge, -0.5, true).build(SOLID, flatLighting = false) val lidBottomRoundLarge = lidRounded(radiusLarge, -0.5, true).bake(false)
val transitionTop = sideRoundedTransition(radiusLarge, radiusSmall, -0.5, 0.5).buildSides(quadsPerSprite = 2) val transitionTop = sideRoundedTransition(radiusLarge, radiusSmall, -0.5, 0.5).buildSides(quadsPerSprite = 2)
val transitionBottom = sideRoundedTransition(radiusSmall, radiusLarge, -0.5, 0.5).buildSides(quadsPerSprite = 2) val transitionBottom = sideRoundedTransition(radiusSmall, radiusLarge, -0.5, 0.5).buildSides(quadsPerSprite = 2)

View File

@@ -1,44 +1,48 @@
package mods.betterfoliage.render.column package mods.betterfoliage.render.column
import mods.betterfoliage.chunk.CachedBlockCtx
import mods.betterfoliage.chunk.ChunkOverlayManager import mods.betterfoliage.chunk.ChunkOverlayManager
import mods.betterfoliage.model.HalfBakedSpecialWrapper
import mods.betterfoliage.model.SpecialRenderModel
import mods.betterfoliage.render.column.ColumnLayerData.NormalRender import mods.betterfoliage.render.column.ColumnLayerData.NormalRender
import mods.betterfoliage.render.column.ColumnLayerData.SpecialRender.BlockType.* import mods.betterfoliage.render.column.ColumnLayerData.SpecialRender.BlockType.NONSOLID
import mods.betterfoliage.render.column.ColumnLayerData.SpecialRender.QuadrantType.* import mods.betterfoliage.render.column.ColumnLayerData.SpecialRender.BlockType.PARALLEL
import mods.betterfoliage.model.WrappedBakedModel import mods.betterfoliage.render.column.ColumnLayerData.SpecialRender.BlockType.PERPENDICULAR
import net.fabricmc.fabric.api.renderer.v1.render.RenderContext import mods.betterfoliage.render.column.ColumnLayerData.SpecialRender.QuadrantType.INVISIBLE
import net.minecraft.block.BlockState import mods.betterfoliage.render.column.ColumnLayerData.SpecialRender.QuadrantType.LARGE_RADIUS
import net.minecraft.client.render.model.BakedModel import mods.betterfoliage.render.column.ColumnLayerData.SpecialRender.QuadrantType.SMALL_RADIUS
import net.minecraft.util.math.BlockPos import mods.betterfoliage.render.column.ColumnLayerData.SpecialRender.QuadrantType.SQUARE
import net.minecraft.util.math.Direction.Axis import mods.betterfoliage.render.lighting.ColumnLighting
import net.minecraft.world.BlockRenderView import mods.betterfoliage.render.pipeline.RenderCtxBase
import java.util.* import net.minecraft.util.Direction.Axis
import java.util.function.Supplier
abstract class ColumnModelBase(
wrapped: SpecialRenderModel
) : HalfBakedSpecialWrapper(wrapped) {
abstract class ColumnModelBase(wrapped: BakedModel) : WrappedBakedModel(wrapped) {
abstract val enabled: Boolean abstract val enabled: Boolean
abstract val overlayLayer: ColumnRenderLayer abstract val overlayLayer: ColumnRenderLayer
abstract val connectPerpendicular: Boolean abstract val connectPerpendicular: Boolean
abstract fun getMeshSet(axis: Axis, quadrant: Int): ColumnMeshSet abstract fun getMeshSet(axis: Axis, quadrant: Int): ColumnMeshSet
override fun emitBlockQuads(blockView: BlockRenderView, state: BlockState, pos: BlockPos, randomSupplier: Supplier<Random>, context: RenderContext) { override fun render(ctx: RenderCtxBase, noDecorations: Boolean) {
if (!enabled) return super.emitBlockQuads(blockView, state, pos, randomSupplier, context) if (!enabled) return super.render(ctx, noDecorations)
val ctx = CachedBlockCtx(blockView, pos)
val roundLog = ChunkOverlayManager.get(overlayLayer, ctx)
val roundLog = ChunkOverlayManager.get(overlayLayer, ctx)
when(roundLog) { when(roundLog) {
ColumnLayerData.SkipRender -> return ColumnLayerData.SkipRender -> return
NormalRender -> return super.emitBlockQuads(blockView, state, pos, randomSupplier, context) NormalRender -> return super.render(ctx, noDecorations)
ColumnLayerData.ResolveError, null -> { ColumnLayerData.ResolveError, null -> {
return super.emitBlockQuads(blockView, state, pos, randomSupplier, context) return super.render(ctx, noDecorations)
} }
} }
// if log axis is not defined and "Default to vertical" config option is not set, render normally // if log axis is not defined and "Default to vertical" config option is not set, render normally
if ((roundLog as ColumnLayerData.SpecialRender).column.axis == null && !overlayLayer.defaultToY) { if ((roundLog as ColumnLayerData.SpecialRender).column.axis == null && !overlayLayer.defaultToY) {
return super.emitBlockQuads(blockView, state, pos, randomSupplier, context) return super.render(ctx, noDecorations)
} }
ctx.vertexLighter = ColumnLighting
val axis = roundLog.column.axis ?: Axis.Y val axis = roundLog.column.axis ?: Axis.Y
val baseRotation = ColumnMeshSet.baseRotation(axis) val baseRotation = ColumnMeshSet.baseRotation(axis)
ColumnMeshSet.quadrantRotations.forEachIndexed { idx, quadrantRotation -> ColumnMeshSet.quadrantRotations.forEachIndexed { idx, quadrantRotation ->
@@ -57,8 +61,8 @@ abstract class ColumnModelBase(wrapped: BakedModel) : WrappedBakedModel(wrapped)
val sideMesh = when (roundLog.quadrants[idx]) { val sideMesh = when (roundLog.quadrants[idx]) {
SMALL_RADIUS -> meshSet.sideRoundSmall[idx] SMALL_RADIUS -> meshSet.sideRoundSmall[idx]
LARGE_RADIUS -> if (roundLog.upType == PARALLEL && roundLog.quadrantsTop[idx] == SMALL_RADIUS) meshSet.transitionTop[idx] LARGE_RADIUS -> if (roundLog.upType == PARALLEL && roundLog.quadrantsTop[idx] == SMALL_RADIUS) meshSet.transitionTop[idx]
else if (roundLog.downType == PARALLEL && roundLog.quadrantsBottom[idx] == SMALL_RADIUS) meshSet.transitionBottom[idx] else if (roundLog.downType == PARALLEL && roundLog.quadrantsBottom[idx] == SMALL_RADIUS) meshSet.transitionBottom[idx]
else meshSet.sideRoundLarge[idx] else meshSet.sideRoundLarge[idx]
SQUARE -> meshSet.sideSquare[idx] SQUARE -> meshSet.sideSquare[idx]
else -> null else -> null
} }
@@ -100,10 +104,10 @@ abstract class ColumnModelBase(wrapped: BakedModel) : WrappedBakedModel(wrapped)
} }
// render // render
sideMesh?.let { context.meshConsumer().accept(it) } sideMesh?.let { ctx.renderQuads(it) }
upMesh?.let { context.meshConsumer().accept(it) } upMesh?.let { ctx.renderQuads(it) }
downMesh?.let { context.meshConsumer().accept(it) } downMesh?.let { ctx.renderQuads(it) }
} }
} }
} }

View File

@@ -1,11 +1,10 @@
package mods.betterfoliage.render.column package mods.betterfoliage.render.column
import mods.betterfoliage.BetterFoliage
import mods.betterfoliage.chunk.BlockCtx import mods.betterfoliage.chunk.BlockCtx
import mods.betterfoliage.chunk.ChunkOverlayLayer import mods.betterfoliage.chunk.ChunkOverlayLayer
import mods.betterfoliage.chunk.ChunkOverlayManager import mods.betterfoliage.chunk.ChunkOverlayManager
import mods.betterfoliage.chunk.dimType import mods.betterfoliage.chunk.dimType
import mods.betterfoliage.render.block.vanilla.RoundLogKey import mods.betterfoliage.config.Config
import mods.betterfoliage.render.column.ColumnLayerData.SpecialRender.BlockType.NONSOLID import mods.betterfoliage.render.column.ColumnLayerData.SpecialRender.BlockType.NONSOLID
import mods.betterfoliage.render.column.ColumnLayerData.SpecialRender.BlockType.PARALLEL import mods.betterfoliage.render.column.ColumnLayerData.SpecialRender.BlockType.PARALLEL
import mods.betterfoliage.render.column.ColumnLayerData.SpecialRender.BlockType.PERPENDICULAR import mods.betterfoliage.render.column.ColumnLayerData.SpecialRender.BlockType.PERPENDICULAR
@@ -19,13 +18,12 @@ import mods.betterfoliage.util.Int3
import mods.betterfoliage.util.Rotation import mods.betterfoliage.util.Rotation
import mods.betterfoliage.util.allDirections import mods.betterfoliage.util.allDirections
import mods.betterfoliage.util.face import mods.betterfoliage.util.face
import mods.betterfoliage.util.get
import mods.betterfoliage.util.plus import mods.betterfoliage.util.plus
import net.minecraft.block.BlockState import net.minecraft.block.BlockState
import net.minecraft.util.Direction
import net.minecraft.util.Direction.Axis
import net.minecraft.util.math.BlockPos import net.minecraft.util.math.BlockPos
import net.minecraft.util.math.Direction.Axis import net.minecraft.world.IBlockDisplayReader
import net.minecraft.util.math.Direction.AxisDirection
import net.minecraft.world.WorldView
/** Index of SOUTH-EAST quadrant. */ /** Index of SOUTH-EAST quadrant. */
const val SE = 0 const val SE = 0
@@ -85,22 +83,20 @@ abstract class ColumnRenderLayer : ChunkOverlayLayer<ColumnLayerData> {
val allNeighborOffsets = (-1..1).flatMap { offsetX -> (-1..1).flatMap { offsetY -> (-1..1).map { offsetZ -> Int3(offsetX, offsetY, offsetZ) }}} val allNeighborOffsets = (-1..1).flatMap { offsetX -> (-1..1).flatMap { offsetY -> (-1..1).map { offsetZ -> Int3(offsetX, offsetY, offsetZ) }}}
override fun onBlockUpdate(world: WorldView, pos: BlockPos) { override fun onBlockUpdate(world: IBlockDisplayReader, pos: BlockPos) {
allNeighborOffsets.forEach { offset -> ChunkOverlayManager.clear(world.dimType, this, pos + offset) } allNeighborOffsets.forEach { offset -> ChunkOverlayManager.clear(world.dimType, this, pos + offset) }
} }
override fun calculate(ctx: BlockCtx): ColumnLayerData { override fun calculate(ctx: BlockCtx): ColumnLayerData {
if (allDirections.all { dir -> // TODO detect round logs
ctx.offset(dir).let { it.isNormalCube && !BetterFoliage.blockTypes.hasTyped<RoundLogKey>(it.state) } if (allDirections.all { dir -> ctx.offset(dir).let { it.isFullBlock } }) return ColumnLayerData.SkipRender
}) return ColumnLayerData.SkipRender
val columnTextures = getColumnKey(ctx.state) ?: return ColumnLayerData.ResolveError val columnTextures = getColumnKey(ctx.state) ?: return ColumnLayerData.ResolveError
// if log axis is not defined and "Default to vertical" config option is not set, render normally // if log axis is not defined and "Default to vertical" config option is not set, render normally
val logAxis = columnTextures.axis ?: if (defaultToY) Axis.Y else return ColumnLayerData.NormalRender val logAxis = columnTextures.axis ?: if (defaultToY) Axis.Y else return ColumnLayerData.NormalRender
// check log neighborhood // check log neighborhood
val baseRotation = Rotation.fromUp[(logAxis to AxisDirection.POSITIVE).face.ordinal] val baseRotation = Rotation.fromUp[(logAxis to Direction.AxisDirection.POSITIVE).face.ordinal]
val upType = ctx.blockType(baseRotation, logAxis, Int3(0, 1, 0)) val upType = ctx.blockType(baseRotation, logAxis, Int3(0, 1, 0))
val downType = ctx.blockType(baseRotation, logAxis, Int3(0, -1, 0)) val downType = ctx.blockType(baseRotation, logAxis, Int3(0, -1, 0))
@@ -189,9 +185,9 @@ abstract class ColumnRenderLayer : ChunkOverlayLayer<ColumnLayerData> {
val offsetRot = offset.rotate(rotation) val offsetRot = offset.rotate(rotation)
val key = getColumnKey(state(offsetRot)) val key = getColumnKey(state(offsetRot))
return if (key == null) { return if (key == null) {
if (offset(offsetRot).isNormalCube) SOLID else NONSOLID if (offset(offsetRot).isFullBlock) SOLID else NONSOLID
} else { } else {
(key.axis ?: if (BetterFoliage.config.roundLogs.defaultY) Axis.Y else null)?.let { (key.axis ?: if (Config.roundLogs.defaultY) Axis.Y else null)?.let {
if (it == axis) PARALLEL else PERPENDICULAR if (it == axis) PARALLEL else PERPENDICULAR
} ?: SOLID } ?: SOLID
} }

View File

@@ -0,0 +1,138 @@
package mods.betterfoliage.render.lighting
import mods.betterfoliage.util.get
import mods.betterfoliage.util.mapArray
import mods.betterfoliage.util.perpendiculars
import net.minecraft.util.Direction
import net.minecraft.util.Direction.*
typealias BoxCorner = Triple<Direction, Direction, Direction>
fun BoxCorner.equalsUnordered(other: BoxCorner) = contains(other.first) && contains(other.second) && contains(other.third)
fun BoxCorner.contains(dir: Direction) = first == dir || second == dir || third == dir
fun Array<BoxCorner>.findIdx(corner: BoxCorner): Int? {
forEachIndexed { idx, test -> if (test.contains(corner.first) && test.contains(corner.second) && test.contains(corner.third)) return idx }
return null
}
fun Array<BoxCorner>.findIdx(predicate: (BoxCorner)->Boolean): Int? {
forEachIndexed { idx, test -> if (predicate(test)) return idx }
return null
}
class AoSideHelper private constructor(face: Direction) {
val sides = faceSides[face]
val cornerSideDirections = faceCorners[face]
val aoIndex = faceCornersIdx.mapArray { corner ->
boxCornersDirIdx[face][sides[corner.first]][sides[corner.second]]!!
}
companion object {
/**
* Indexing for undirected box corners (component order does not matter).
* Array contains [Direction] triplets fully defining the corner.
*/
@JvmField
val boxCornersUndir = Array(8) { idx -> Triple(
if (idx and 1 != 0) EAST else WEST,
if (idx and 2 != 0) UP else DOWN,
if (idx and 4 != 0) SOUTH else NORTH
) }
/**
* Reverse lookup for [boxCornersUndir]. Index 3 times with the corner's cardinal directions.
* A null value indicates an invalid corner (multiple indexing along the same axis)
*/
@JvmField
val boxCornersUndirIdx = Array(6) { idx1 -> Array(6) { idx2 -> Array(6) { idx3 ->
boxCornersUndir.findIdx(BoxCorner(
Direction.values()[idx1],
Direction.values()[idx2],
Direction.values()[idx3]
))
} } }
/**
* Indexing for directed face sides
* First index is the face, second is index of side on face
*/
@JvmField
val faceSides = Array(6) { faceIdx -> Array(4) { sideIdx ->
Direction.values()[faceIdx].perpendiculars[sideIdx]
} }
/**
* Pairs of [faceSides] side indexes that form a valid pair describing a corner
*/
@JvmField
val faceCornersIdx = arrayOf(0 to 2, 0 to 3, 1 to 2, 1 to 3)
/**
* Indexing for directed face corners
* First index is the face, second is index of corner on face
*/
@JvmField
val faceCorners = Array(6) { faceIdx -> Array(4) { cornerIdx ->
faceCornersIdx[cornerIdx].let { faceSides[faceIdx][it.first] to faceSides[faceIdx][it.second] }
} }
/**
* Indexing scheme for directed box corners.
* The first direction - the face - matters, the other two are unordered.
* 1:1 correspondence with possible AO values.
* Array contains triplets defining the corner fully.
*/
@JvmField
val boxCornersDir = Array(24) { idx ->
val faceIdx = idx / 4; val face = Direction.values()[faceIdx]
val cornerIdx = idx % 4; val corner = faceCorners[faceIdx][cornerIdx]
BoxCorner(face, corner.first, corner.second)
}
/**
* Reverse lookup for [boxCornersDir]. Index 3 times with the corner's cardinal directions.
* The first direction - the face - matters, the other two are unordered.
* A null value indicates an invalid corner (multiple indexing along the same axis)
*/
@JvmField
val boxCornersDirIdx = Array(6) { face -> Array(6) { side1 -> Array(6) { side2 ->
boxCornersDir.findIdx { boxCorner ->
boxCorner.first.ordinal == face && boxCorner.equalsUnordered(BoxCorner(
Direction.values()[face],
Direction.values()[side1],
Direction.values()[side2]
))
}
} } }
/**
* Reverse lookup for [cornersDir].
* 1st index: primary face
* 2nd index: undirected corner index.
* value: directed corner index
* A null value indicates an invalid corner (primary face not shared by corner).
*/
@JvmField
val boxCornersDirFromUndir = Array(6) { faceIdx -> Array(8) { undirIdx ->
val face = Direction.values()[faceIdx]
val corner = boxCornersUndir[undirIdx]
if (!corner.contains(face)) null
else boxCornersDir.findIdx { it.first == face && it.equalsUnordered(corner) }
} }
@JvmField
val forSide = Direction.values().mapArray { AoSideHelper(it) }
/**
* Get corner index for vertex coordinates
*/
@JvmStatic
fun getCornerUndir(x: Double, y: Double, z: Double): Int {
var result = 0
if (x > 0.0) result += 1
if (y > 0.0) result += 2
if (z > 0.0) result += 4
return result
}
}
}

View File

@@ -1,140 +0,0 @@
package mods.betterfoliage.render.lighting
import mods.betterfoliage.util.*
import net.fabricmc.fabric.api.renderer.v1.mesh.QuadView
import net.minecraft.util.math.Direction
import net.minecraft.util.math.Direction.*
import kotlin.math.abs
val EPSILON = 0.05
interface CustomLighting {
fun applyLighting(lighting: CustomLightingMeshConsumer, quad: QuadView, flat: Boolean, emissive: Boolean)
}
interface CustomLightingMeshConsumer {
/** Clear cached block brightness and AO values */
fun clearLighting()
/** Fill AO/light cache for given face */
fun fillAoData(lightFace: Direction)
/** Set AO/light values for quad vertex */
fun setLighting(vIdx: Int, ao: Float, light: Int)
/** Get neighbor block brightness */
fun brNeighbor(dir: Direction): Int
/** Block brightness value */
val brSelf: Int
/** Cached AO values for all box face corners */
val aoFull: FloatArray
/** Cached light values for all box face corners */
val lightFull: IntArray
}
/** Custom lighting used for protruding tuft quads (short grass, algae, cactus arms, etc.) */
fun grassTuftLighting(lightFace: Direction) = object : CustomLighting {
override fun applyLighting(lighting: CustomLightingMeshConsumer, quad : QuadView, flat: Boolean, emissive: Boolean) {
if (flat) lighting.flatForceNeighbor(quad, lightFace) else lighting.smoothWithFaceOverride(quad, lightFace)
}
}
/** Custom lighting used for round leaves */
fun roundLeafLighting() = object : CustomLighting {
override fun applyLighting(lighting: CustomLightingMeshConsumer, quad: QuadView, flat: Boolean, emissive: Boolean) {
if (flat) lighting.flatMax(quad) else lighting.smooth45PreferUp(quad)
}
}
/** Custom lighting used for reeds */
fun reedLighting() = object : CustomLighting {
override fun applyLighting(lighting: CustomLightingMeshConsumer, quad: QuadView, flat: Boolean, emissive: Boolean) {
lighting.flatForceNeighbor(quad, UP)
}
}
/** Flat lighting, use neighbor brightness in the given direction */
fun CustomLightingMeshConsumer.flatForceNeighbor(quad: QuadView, lightFace: Direction) {
for (vIdx in 0 until 4) {
setLighting(vIdx, 1.0f, brNeighbor(lightFace))
}
}
/** Smooth lighting, use *only* AO/light values on the given face (closest corner) */
fun CustomLightingMeshConsumer.smoothWithFaceOverride(quad: QuadView, lightFace: Direction) {
fillAoData(lightFace)
forEachVertex(quad) { vIdx, x, y, z ->
val cornerUndir = getCornerUndir(x, y, z)
cornerDirFromUndir[lightFace.ordinal][cornerUndir]?.let { aoCorner ->
setLighting(vIdx, aoFull[aoCorner], lightFull[aoCorner])
}
}
}
/**
* Smooth lighting scheme for 45-degree quads bisecting the box along 2 opposing face diagonals.
*
* Determine 2 *primary faces* based on the normal direction.
* Take AO/light values *only* from the 2 primary faces *or* the UP direction,
* based on which box corner is closest. Prefer taking values from the top face.
*/
fun CustomLightingMeshConsumer.smooth45PreferUp(quad: QuadView) {
getAngles45(quad)?.let { normalFaces ->
fillAoData(normalFaces.first)
fillAoData(normalFaces.second)
if (normalFaces.first != UP && normalFaces.second != UP) fillAoData(UP)
forEachVertex(quad) { vIdx, x, y, z ->
val isUp = y > 0.5f
val cornerUndir = getCornerUndir(x, y, z)
val preferredFace = if (isUp) UP else normalFaces.minBy { faceDistance(it, x, y, z) }
val aoCorner = cornerDirFromUndir[preferredFace.ordinal][cornerUndir]!!
setLighting(vIdx, aoFull[aoCorner], lightFull[aoCorner])
}
}
}
/** Flat lighting, use maximum neighbor brightness at the nearest box corner */
fun CustomLightingMeshConsumer.flatMax(quad: QuadView) {
forEachVertex(quad) { vIdx, x, y, z ->
val maxBrightness = cornersUndir[getCornerUndir(x, y, z)].maxValueBy { brNeighbor(it) }
setLighting(vIdx, 1.0f, maxBrightness)
}
}
/**
* If the quad normal approximately bisects 2 axes at a 45 degree angle,
* and is approximately perpendicular to the third, returns the 2 directions
* the quad normal points towards.
* Returns null otherwise.
*/
fun getAngles45(quad: QuadView): Pair<Direction, Direction>? {
val normal = quad.faceNormal()
// one of the components must be close to zero
val zeroAxis = when {
abs(normal.x) < EPSILON -> Axis.X
abs(normal.y) < EPSILON -> Axis.Y
abs(normal.z) < EPSILON -> Axis.Z
else -> return null
}
// the other two must be of similar magnitude
val diff = when(zeroAxis) {
Axis.X -> abs(abs(normal.y) - abs(normal.z))
Axis.Y -> abs(abs(normal.x) - abs(normal.z))
Axis.Z -> abs(abs(normal.x) - abs(normal.y))
}
if (diff > EPSILON) return null
return when(zeroAxis) {
Axis.X -> Pair(if (normal.y > 0.0f) UP else DOWN, if (normal.z > 0.0f) SOUTH else NORTH)
Axis.Y -> Pair(if (normal.x > 0.0f) EAST else WEST, if (normal.z > 0.0f) SOUTH else NORTH)
Axis.Z -> Pair(if (normal.x > 0.0f) EAST else WEST, if (normal.y > 0.0f) UP else DOWN)
}
}
fun faceDistance(face: Direction, x: Float, y: Float, z: Float) = when(face) {
WEST -> x; EAST -> 1.0f - x
DOWN -> y; UP -> 1.0f - y
NORTH -> z; SOUTH -> 1.0f - z
}
inline fun forEachVertex(quad: QuadView, func: (vIdx: Int, x: Float, y: Float, z: Float)->Unit) {
for (vIdx in 0..3) {
func(vIdx, quad.x(vIdx), quad.y(vIdx), quad.z(vIdx))
}
}

View File

@@ -0,0 +1,10 @@
package mods.betterfoliage.render.lighting
interface ForgeVertexLighterAccess {
var vertexLighter: ForgeVertexLighter
}
interface ForgeVertexLighter {
fun updateVertexLightmap(normal: FloatArray, lightmap: FloatArray, x: Float, y: Float, z: Float)
fun updateVertexColor(normal: FloatArray, color: FloatArray, x: Float, y: Float, z: Float, tint: Float, multiplier: Int)
}

View File

@@ -1,78 +0,0 @@
package mods.betterfoliage.render.lighting
import mods.betterfoliage.util.YarnHelper
import mods.betterfoliage.util.get
import mods.betterfoliage.util.reflectField
import net.fabricmc.fabric.api.renderer.v1.mesh.Mesh
import net.fabricmc.fabric.api.renderer.v1.model.FabricBakedModel
import net.fabricmc.fabric.api.renderer.v1.render.RenderContext
import net.fabricmc.fabric.impl.client.indigo.renderer.aocalc.AoCalculator
import net.fabricmc.fabric.impl.client.indigo.renderer.render.*
import net.minecraft.block.BlockState
import net.minecraft.client.render.RenderLayer
import net.minecraft.client.render.VertexConsumer
import net.minecraft.client.render.model.BakedModel
import net.minecraft.util.math.BlockPos
import net.minecraft.world.BlockRenderView
import java.util.*
import java.util.function.Consumer
import java.util.function.Supplier
val AbstractQuadRenderer_blockInfo2 = YarnHelper.requiredField<TerrainBlockRenderInfo>(
"net.fabricmc.fabric.impl.client.indigo.renderer.render.AbstractQuadRenderer",
"blockInfo", "Lnet/fabricmc/fabric/impl/client/indigo/renderer/render/TerrainBlockRenderInfo;"
)
val AbstractQuadRenderer_bufferFunc2 = YarnHelper.requiredField<java.util.function.Function<RenderLayer, VertexConsumer>>(
"net.fabricmc.fabric.impl.client.indigo.renderer.render.AbstractQuadRenderer",
"bufferFunc", "Ljava/util/function/Function;"
)
val AbstractQuadRenderer_aoCalc = YarnHelper.requiredField<AoCalculator>(
"net.fabricmc.fabric.impl.client.indigo.renderer.render.AbstractQuadRenderer",
"aoCalc", "Lnet/fabricmc/fabric/impl/client/indigo/renderer/aocalc/AoCalculator;"
)
val AbstractQuadRenderer_transform = YarnHelper.requiredField<RenderContext.QuadTransform>(
"net.fabricmc.fabric.impl.client.indigo.renderer.render.AbstractQuadRenderer",
"transform", "Lnet/fabricmc/fabric/api/renderer/v1/render/RenderContext\$QuadTransform;"
)
val MODIFIED_CONSUMER_POOL = ThreadLocal<ModifiedTerrainMeshConsumer>()
fun AbstractMeshConsumer.modified() = MODIFIED_CONSUMER_POOL.get() ?: let {
ModifiedTerrainMeshConsumer(this)
}.apply { MODIFIED_CONSUMER_POOL.set(this) }
/**
* Render the given model at the given position.
* Mutates the state of the [RenderContext]!!
*/
fun RenderContext.renderMasquerade(model: BakedModel, blockView: BlockRenderView, state: BlockState, pos: BlockPos, randomSupplier: Supplier<Random>, context: RenderContext) = when(this) {
is TerrainRenderContext -> {
val blockInfo = meshConsumer()[AbstractQuadRenderer_blockInfo2]!!
blockInfo.prepareForBlock(state, pos, model.useAmbientOcclusion())
(model as FabricBakedModel).emitBlockQuads(blockView, state, pos, randomSupplier, context)
}
else -> {
(model as FabricBakedModel).emitBlockQuads(blockView, state, pos, randomSupplier, context)
}
}
/** Execute the provided block with a mesh consumer using the given custom lighting. */
fun RenderContext.withLighting(lighter: CustomLighting, func: (Consumer<Mesh>)->Unit) = when(this) {
is TerrainRenderContext -> {
val consumer = (meshConsumer() as AbstractMeshConsumer).modified()
consumer.clearLighting()
consumer.lighter = lighter
func(consumer)
consumer.lighter = null
}
else -> func(meshConsumer())
}
/** Get the [BufferBuilder] responsible for a given [BlockRenderLayer] */
fun RenderContext.getBufferBuilder(layer: RenderLayer) = when(this) {
is TerrainRenderContext -> {
val bufferFunc = meshConsumer()[AbstractQuadRenderer_bufferFunc2]!!
bufferFunc.apply(layer)
}
else -> null
}

View File

@@ -0,0 +1,152 @@
package mods.betterfoliage.render.lighting
import mods.betterfoliage.chunk.BlockCtx
import net.minecraft.block.BlockState
import net.minecraft.client.renderer.BlockModelRenderer
import net.minecraft.util.Direction
import net.minecraft.util.math.BlockPos
import net.minecraft.world.IBlockDisplayReader
data class LightingData(
@JvmField var packedLight: Int = 0,
@JvmField var colorMultiplier: Float = 1.0f
) {
fun mixFrom(corner: LightingData, side1: LightingData, side2: LightingData, center: LightingData) {
colorMultiplier =
(center.colorMultiplier + side1.colorMultiplier + side2.colorMultiplier + corner.colorMultiplier) * 0.25f
packedLight = (
center.packedLight +
(side1.packedLight.takeUnless { it == 0 } ?: center.packedLight) +
(side2.packedLight.takeUnless { it == 0 } ?: center.packedLight) +
(corner.packedLight.takeUnless { it == 0 } ?: center.packedLight)
).let { sum -> (sum shr 2) and 0xFF00FF }
}
}
// Vanilla has a very suspicious-looking offset here, which Indigo gets rid of and calls it a fix
// Naturally, we're going to believe Indigo, it's a hardcoded option for now
const val OCCLUSION_OFFSET_FIX = true
/**
* Replacement for [BlockModelRenderer.AmbientOcclusionFace]
* This gets called on a LOT, so object instantiation is avoided.
* Not thread-safe, always use a [ThreadLocal] instance
*/
class VanillaAoCalculator {
lateinit var world: IBlockDisplayReader
/** [blockPos] is used to get block-related information (i.e. tint, opacity, etc.)
* [lightPos] is used to get light-related information
* this facilitates masquerade rendering of blocks */
lateinit var blockPos: BlockPos
lateinit var lightPos: BlockPos
private val probe = LightProbe(BlockModelRenderer.CACHE.get())
val isValid = BooleanArray(6)
val aoData = Array(24) { LightingData() }
// scratchpad values used during calculation
private val centerAo = LightingData()
private val sideAo = Array(4) { LightingData() }
private val cornerAo = Array(4) { LightingData() }
private val isOccluded = BooleanArray(4)
fun reset(ctx: BlockCtx) {
world = ctx.world; blockPos = ctx.pos; lightPos = ctx.pos
(0 until 6).forEach { isValid[it] = false }
}
fun fillLightData(lightFace: Direction, isOpaque: Boolean? = null) {
if (!isValid[lightFace.ordinal]) calculate(lightFace, isOpaque)
}
/**
* Replicate [BlockModelRenderer.AmbientOcclusionFace.updateVertexBrightness]
* Does not handle interpolation for non-cubic models, that should be
* done in a [VanillaVertexLighter]
* @param lightFace face of the block to calculate
* @param forceFull force full-block status for lighting calculation, null for auto
*/
private fun calculate(lightFace: Direction, forceFull: Boolean?) {
if (isValid[lightFace.ordinal]) return
val sideHelper = AoSideHelper.forSide[lightFace.ordinal]
// Bit 0 of the bitset in vanilla calculations
// true if the block model is planar with the block boundary
val isFullBlock = forceFull ?: world.getBlockState(blockPos).isCollisionShapeFullBlock(world, blockPos)
val lightOrigin = if (isFullBlock) lightPos.relative(lightFace) else lightPos
// AO calculation for the face center
probe.position { set(lightOrigin) }.writeTo(centerAo)
if (!isFullBlock && !probe.position { move(lightFace) }.state.isSolidRender(world, probe.pos)) {
// if the neighboring block in the lightface direction is
// transparent (non-opaque), use its packed light instead of our own
// (if our block is a full block, we are already using this value)
centerAo.packedLight = probe.packedLight
}
// AO calculation for the 4 sides
sideHelper.sides.forEachIndexed { sideIdx, sideDir ->
// record light data in the block 1 step to the side
probe.position { set(lightOrigin).move(sideDir) }.writeTo(sideAo[sideIdx])
// side is considered occluded if the block 1 step to that side and
// 1 step forward (in the lightface direction) is not fully transparent
if (!OCCLUSION_OFFSET_FIX) probe.position { move(lightFace) }
isOccluded[sideIdx] = probe.isNonTransparent
}
// AO Calculation for the 4 corners
AoSideHelper.faceCornersIdx.forEachIndexed { cornerIdx, sideIndices ->
val bothOccluded = isOccluded[sideIndices.first] && isOccluded[sideIndices.second]
if (bothOccluded) cornerAo[cornerIdx].apply {
// if both sides are occluded, just use the packed light for one of the sides instead
val copyFrom = sideAo[sideIndices.first]
packedLight = copyFrom.packedLight; colorMultiplier = copyFrom.colorMultiplier
}
else {
// lookup actual packed light from the cornering block in the world
probe.position {
set(lightOrigin)
.move(sideHelper.sides[sideIndices.first])
.move(sideHelper.sides[sideIndices.second])
}.writeTo(cornerAo[cornerIdx])
}
}
// Calculate and store final interpolated value for each corner
AoSideHelper.faceCornersIdx.forEachIndexed { cornerIdx, sideIndices ->
val aoIdx = sideHelper.aoIndex[cornerIdx]
aoData[aoIdx].mixFrom(
cornerAo[cornerIdx],
sideAo[sideIndices.first],
sideAo[sideIndices.second],
centerAo
)
}
isValid[lightFace.ordinal] = true
}
inner class LightProbe(
val cache: BlockModelRenderer.Cache
) {
lateinit var state: BlockState
val pos = BlockPos.Mutable()
val packedLight: Int get() = cache.getLightColor(state, world, pos)
val colorMultiplier: Float get() = cache.getShadeBrightness(state, world, pos)
val isNonTransparent: Boolean get() = state.getLightBlock(world, pos) > 0
fun writeTo(data: LightingData) {
data.packedLight = packedLight
data.colorMultiplier = colorMultiplier
}
inline fun position(func: BlockPos.Mutable.() -> Unit): LightProbe {
pos.func()
state = world.getBlockState(pos)
return this
}
}
}

View File

@@ -0,0 +1,210 @@
package mods.betterfoliage.render.lighting
import mods.betterfoliage.integration.ShadersModIntegration
import mods.betterfoliage.model.HalfBakedQuad
import mods.betterfoliage.util.Double3
import mods.betterfoliage.util.EPSILON_ONE
import mods.betterfoliage.util.EPSILON_ZERO
import mods.betterfoliage.util.get
import mods.betterfoliage.util.minBy
import net.minecraft.client.renderer.color.BlockColors
import net.minecraft.util.Direction
import net.minecraft.util.Direction.*
import kotlin.math.abs
class VanillaQuadLighting {
val packedLight = IntArray(4)
val colorMultiplier = FloatArray(4)
val tint = FloatArray(3)
val calc = VanillaAoCalculator()
lateinit var blockColors: BlockColors
fun updateBlockTint(tintIndex: Int) {
if (tintIndex == -1) {
tint[0] = 1.0f; tint[1] = 1.0f; tint[2] = 1.0f
} else {
val state = calc.world.getBlockState(calc.blockPos)
blockColors.getColor(state, calc.world, calc.blockPos, tintIndex).let { blockTint ->
tint[0] = (blockTint shr 16 and 255).toFloat() / 255.0f
tint[1] = (blockTint shr 8 and 255).toFloat() / 255.0f
tint[2] = (blockTint and 255).toFloat() / 255.0f
}
}
}
fun applyDiffuseLighting(face: Direction) {
val factor = ShadersModIntegration.diffuseShades[face]
tint[0] *= factor; tint[1] *= factor; tint[2] *= factor
}
}
abstract class VanillaVertexLighter {
abstract fun updateLightmapAndColor(quad: HalfBakedQuad, lighting: VanillaQuadLighting)
/**
* Update lighting for each vertex with AO values from one of the corners.
* Does not calculate missing AO values!
* @param quad the quad to shade
* @param func selector function from vertex position to directed corner index of desired AO values
*/
inline fun VanillaQuadLighting.updateWithCornerAo(quad: HalfBakedQuad, func: (Double3)->Int?) {
quad.raw.verts.forEachIndexed { idx, vertex ->
func(vertex.xyz)?.let {
packedLight[idx] = calc.aoData[it].packedLight
colorMultiplier[idx] = calc.aoData[it].colorMultiplier
}
}
}
}
/**
* Replicates vanilla shading for full blocks. Interpolation for non-full blocks
* is not implemented.
*/
object VanillaFullBlockLighting : VanillaVertexLighter() {
override fun updateLightmapAndColor(quad: HalfBakedQuad, lighting: VanillaQuadLighting) {
// TODO bounds checking & interpolation
val face = quad.raw.face()
lighting.calc.fillLightData(face, true)
lighting.updateWithCornerAo(quad) { nearestCornerOnFace(it, face) }
lighting.updateBlockTint(quad.baked.tintIndex)
if (quad.baked.isShade) lighting.applyDiffuseLighting(face)
}
}
object RoundLeafLightingPreferUp : VanillaVertexLighter() {
override fun updateLightmapAndColor(quad: HalfBakedQuad, lighting: VanillaQuadLighting) {
val angles = getAngles45(quad)?.let { normalFaces ->
lighting.calc.fillLightData(normalFaces.first)
lighting.calc.fillLightData(normalFaces.second)
if (normalFaces.first != UP && normalFaces.second != UP) lighting.calc.fillLightData(UP)
lighting.updateWithCornerAo(quad) { vertex ->
val isUp = vertex.y > 0.5f
val cornerUndir = AoSideHelper.getCornerUndir(vertex.x, vertex.y, vertex.z)
val preferredFace = if (isUp) UP else normalFaces.minBy { faceDistance(it, vertex) }
AoSideHelper.boxCornersDirFromUndir[preferredFace.ordinal][cornerUndir]
}
lighting.updateBlockTint(quad.baked.tintIndex)
}
}
}
/**
* Lights vertices with the AO values from the nearest corner on either of
* the 2 faces the quad normal points towards.
*/
object RoundLeafLighting : VanillaVertexLighter() {
override fun updateLightmapAndColor(quad: HalfBakedQuad, lighting: VanillaQuadLighting) {
val angles = getAngles45(quad)?.let { normalFaces ->
lighting.calc.fillLightData(normalFaces.first)
lighting.calc.fillLightData(normalFaces.second)
lighting.updateWithCornerAo(quad) { vertex ->
val cornerUndir = AoSideHelper.getCornerUndir(vertex.x, vertex.y, vertex.z)
val preferredFace = normalFaces.minBy { faceDistance(it, vertex) }
AoSideHelper.boxCornersDirFromUndir[preferredFace.ordinal][cornerUndir]
}
lighting.updateBlockTint(quad.baked.tintIndex)
}
}
}
/**
* Lights vertices with the AO values from the nearest corner on the preferred face.
*/
class LightingPreferredFace(val face: Direction) : VanillaVertexLighter() {
override fun updateLightmapAndColor(quad: HalfBakedQuad, lighting: VanillaQuadLighting) {
lighting.calc.fillLightData(face)
lighting.updateWithCornerAo(quad) { nearestCornerOnFace(it, face) }
lighting.updateBlockTint(quad.baked.tintIndex)
}
}
object ColumnLighting : VanillaVertexLighter() {
override fun updateLightmapAndColor(quad: HalfBakedQuad, lighting: VanillaQuadLighting) {
// faces pointing in cardinal directions
getNormalFace(quad)?.let { face ->
lighting.calc.fillLightData(face)
lighting.updateWithCornerAo(quad) { nearestCornerOnFace(it, face) }
lighting.updateBlockTint(quad.baked.tintIndex)
return
}
// faces pointing at 45deg angles
getAngles45(quad)?.let { (face1, face2) ->
lighting.calc.fillLightData(face1)
lighting.calc.fillLightData(face2)
quad.raw.verts.forEachIndexed { idx, vertex ->
val cornerUndir = AoSideHelper.getCornerUndir(vertex.xyz.x, vertex.xyz.y, vertex.xyz.z)
val cornerDir1 = AoSideHelper.boxCornersDirFromUndir[face1.ordinal][cornerUndir]
val cornerDir2 = AoSideHelper.boxCornersDirFromUndir[face2.ordinal][cornerUndir]
if (cornerDir1 == null || cornerDir2 == null) return@let
val ao1 = lighting.calc.aoData[cornerDir1]
val ao2 = lighting.calc.aoData[cornerDir2]
lighting.packedLight[idx] = ((ao1.packedLight + ao2.packedLight) shr 1) and 0xFF00FF
lighting.colorMultiplier[idx] = (ao1.colorMultiplier + ao2.colorMultiplier) * 0.5f
}
lighting.updateBlockTint(quad.baked.tintIndex)
return
}
// something is wrong...
lighting.updateWithCornerAo(quad) { nearestCornerOnFace(it, quad.raw.face()) }
lighting.updateBlockTint(quad.baked.tintIndex)
}
}
/**
* Return the directed box corner index for the corner nearest the given vertex,
* which is on the given face. May return null if the vertex is closest to
* one of the opposite 4 corners
*/
fun nearestCornerOnFace(pos: Double3, face: Direction): Int? {
val cornerUndir = AoSideHelper.getCornerUndir(pos.x, pos.y, pos.z)
return AoSideHelper.boxCornersDirFromUndir[face.ordinal][cornerUndir]
}
/**
* If the quad normal approximately bisects 2 axes at a 45 degree angle,
* and is approximately perpendicular to the third, returns the 2 directions
* the quad normal points towards.
* Returns null otherwise.
*/
fun getAngles45(quad: HalfBakedQuad): Pair<Direction, Direction>? {
val normal = quad.raw.normal
// one of the components must be close to zero
val zeroAxis = when {
abs(normal.x) < EPSILON_ZERO -> Axis.X
abs(normal.y) < EPSILON_ZERO -> Axis.Y
abs(normal.z) < EPSILON_ZERO -> Axis.Z
else -> return null
}
// the other two must be of similar magnitude
val diff = when(zeroAxis) {
Axis.X -> abs(abs(normal.y) - abs(normal.z))
Axis.Y -> abs(abs(normal.x) - abs(normal.z))
Axis.Z -> abs(abs(normal.x) - abs(normal.y))
}
if (diff > EPSILON_ZERO) return null
return when(zeroAxis) {
Axis.X -> Pair(if (normal.y > 0.0f) UP else DOWN, if (normal.z > 0.0f) SOUTH else NORTH)
Axis.Y -> Pair(if (normal.x > 0.0f) EAST else WEST, if (normal.z > 0.0f) SOUTH else NORTH)
Axis.Z -> Pair(if (normal.x > 0.0f) EAST else WEST, if (normal.y > 0.0f) UP else DOWN)
}
}
fun getNormalFace(quad: HalfBakedQuad) = quad.raw.normal.let { normal ->
when {
normal.x > EPSILON_ONE -> EAST
normal.x < -EPSILON_ONE -> WEST
normal.y > EPSILON_ONE -> UP
normal.y < -EPSILON_ONE -> DOWN
normal.z > EPSILON_ONE -> SOUTH
normal.z < -EPSILON_ONE -> NORTH
else -> null
}
}
fun faceDistance(face: Direction, pos: Double3) = when(face) {
WEST -> pos.x; EAST -> 1.0 - pos.x
DOWN -> pos.y; UP -> 1.0 - pos.y
NORTH -> pos.z; SOUTH -> 1.0 - pos.z
}

View File

@@ -1,17 +1,16 @@
package mods.betterfoliage.render.particle package mods.betterfoliage.render.particle
import com.mojang.blaze3d.vertex.IVertexBuilder
import mods.betterfoliage.util.Double3 import mods.betterfoliage.util.Double3
import net.minecraft.client.MinecraftClient import net.minecraft.client.Minecraft
import net.minecraft.client.particle.SpriteBillboardParticle import net.minecraft.client.particle.SpriteTexturedParticle
import net.minecraft.client.render.Camera import net.minecraft.client.renderer.ActiveRenderInfo
import net.minecraft.client.render.VertexConsumer import net.minecraft.client.renderer.texture.TextureAtlasSprite
import net.minecraft.client.texture.Sprite
import net.minecraft.client.util.math.Vector3f
import net.minecraft.client.world.ClientWorld import net.minecraft.client.world.ClientWorld
import net.minecraft.util.math.MathHelper import net.minecraft.util.math.MathHelper
import net.minecraft.world.World import net.minecraft.util.math.vector.Vector3f
abstract class AbstractParticle(world: ClientWorld, x: Double, y: Double, z: Double) : SpriteBillboardParticle(world, x, y, z) { abstract class AbstractParticle(world: ClientWorld, x: Double, y: Double, z: Double) : SpriteTexturedParticle(world, x, y, z) {
companion object { companion object {
// @JvmStatic val sin = Array(64) { idx -> Math.sin(PI2 / 64.0 * idx) } // @JvmStatic val sin = Array(64) { idx -> Math.sin(PI2 / 64.0 * idx) }
@@ -26,11 +25,11 @@ abstract class AbstractParticle(world: ClientWorld, x: Double, y: Double, z: Dou
override fun tick() { override fun tick() {
super.tick() super.tick()
currentPos.setTo(x, y, z) currentPos.setTo(x, y, z)
prevPos.setTo(prevPosX, prevPosY, prevPosZ) prevPos.setTo(xo, yo, zo)
velocity.setTo(velocityX, velocityY, velocityZ) velocity.setTo(xd, yd, zd)
update() update()
x = currentPos.x; y = currentPos.y; z = currentPos.z; x = currentPos.x; y = currentPos.y; z = currentPos.z;
velocityX = velocity.x; velocityY = velocity.y; velocityZ = velocity.z; xd = velocity.x; yd = velocity.y; zd = velocity.z;
} }
/** Update particle on world tick. */ /** Update particle on world tick. */
@@ -40,10 +39,10 @@ abstract class AbstractParticle(world: ClientWorld, x: Double, y: Double, z: Dou
abstract val isValid: Boolean abstract val isValid: Boolean
/** Add the particle to the effect renderer if it is valid. */ /** Add the particle to the effect renderer if it is valid. */
fun addIfValid() { if (isValid) MinecraftClient.getInstance().particleManager.addParticle(this) } fun addIfValid() { if (isValid) Minecraft.getInstance().particleEngine.add(this) }
override fun buildGeometry(vertexConsumer: VertexConsumer, camera: Camera, tickDelta: Float) { override fun render(vertexBuilder: IVertexBuilder, camera: ActiveRenderInfo, tickDelta: Float) {
renderParticleQuad(vertexConsumer, camera, tickDelta) super.render(vertexBuilder, camera, tickDelta)
} }
/** /**
@@ -59,44 +58,44 @@ abstract class AbstractParticle(world: ClientWorld, x: Double, y: Double, z: Dou
* @param[isMirrored] mirror particle texture along V-axis * @param[isMirrored] mirror particle texture along V-axis
* @param[alpha] aplha blending * @param[alpha] aplha blending
*/ */
fun renderParticleQuad(vertexConsumer: VertexConsumer, fun renderParticleQuad(vertexConsumer: IVertexBuilder,
camera: Camera, camera: ActiveRenderInfo,
tickDelta: Float, tickDelta: Float,
currentPos: Double3 = this.currentPos, currentPos: Double3 = this.currentPos,
prevPos: Double3 = this.prevPos, prevPos: Double3 = this.prevPos,
size: Double = scale.toDouble(), size: Double = quadSize.toDouble(),
currentAngle: Float = this.angle, currentAngle: Float = this.roll,
prevAngle: Float = this.prevAngle, prevAngle: Float = this.oRoll,
sprite: Sprite = this.sprite, sprite: TextureAtlasSprite = this.sprite,
alpha: Float = this.colorAlpha) { alpha: Float = this.alpha) {
val center = Double3.lerp(tickDelta.toDouble(), prevPos, currentPos) val center = Double3.lerp(tickDelta.toDouble(), prevPos, currentPos)
val angle = MathHelper.lerp(tickDelta, prevAngle, currentAngle) val angle = MathHelper.lerp(tickDelta, prevAngle, currentAngle)
val rotation = camera.rotation.copy().apply { hamiltonProduct(Vector3f.POSITIVE_Z.getRadialQuaternion(angle)) } val rotation = camera.rotation().copy().apply { mul(Vector3f.ZP.rotation(angle)) }
val lightmapCoord = getColorMultiplier(tickDelta) val lightmapCoord = getLightColor(tickDelta)
val coords = arrayOf( val coords = arrayOf(
Double3(-1.0, -1.0, 0.0), Double3(-1.0, -1.0, 0.0),
Double3(-1.0, 1.0, 0.0), Double3(-1.0, 1.0, 0.0),
Double3(1.0, 1.0, 0.0), Double3(1.0, 1.0, 0.0),
Double3(1.0, -1.0, 0.0) Double3(1.0, -1.0, 0.0)
).map { it.rotate(rotation).mul(size).add(center).sub(camera.pos.x, camera.pos.y, camera.pos.z) } ).map { it.rotate(rotation).mul(size).add(center).sub(camera.position.x, camera.position.y, camera.position.z) }
fun renderVertex(vertex: Double3, u: Float, v: Float) = vertexConsumer fun renderVertex(vertex: Double3, u: Float, v: Float) = vertexConsumer
.vertex(vertex.x, vertex.y, vertex.z).texture(u, v) .vertex(vertex.x, vertex.y, vertex.z).uv(u, v)
.color(colorRed, colorGreen, colorBlue, alpha).light(lightmapCoord) .color(rCol, gCol, bCol, alpha).uv2(lightmapCoord)
.next() .endVertex()
renderVertex(coords[0], sprite.maxU, sprite.maxV) renderVertex(coords[0], sprite.u1, sprite.v1)
renderVertex(coords[1], sprite.maxU, sprite.minV) renderVertex(coords[1], sprite.u1, sprite.v0)
renderVertex(coords[2], sprite.minU, sprite.minV) renderVertex(coords[2], sprite.u0, sprite.v0)
renderVertex(coords[3], sprite.minU, sprite.maxV) renderVertex(coords[3], sprite.u0, sprite.v1)
} }
fun setColor(color: Int) { fun setColor(color: Int) {
colorBlue = (color and 255) / 256.0f bCol = (color and 255) / 256.0f
colorGreen = ((color shr 8) and 255) / 256.0f gCol = ((color shr 8) and 255) / 256.0f
colorRed = ((color shr 16) and 255) / 256.0f rCol = ((color shr 16) and 255) / 256.0f
} }
} }

View File

@@ -1,8 +1,6 @@
package mods.betterfoliage.render.particle package mods.betterfoliage.render.particle
import mods.betterfoliage.BetterFoliage import mods.betterfoliage.config.Config
import mods.betterfoliage.ClientWorldLoadCallback
import mods.betterfoliage.render.block.vanilla.LeafParticleKey
import mods.betterfoliage.util.Double3 import mods.betterfoliage.util.Double3
import mods.betterfoliage.util.PI2 import mods.betterfoliage.util.PI2
import mods.betterfoliage.util.minmax import mods.betterfoliage.util.minmax
@@ -10,12 +8,15 @@ import mods.betterfoliage.util.randomB
import mods.betterfoliage.util.randomD import mods.betterfoliage.util.randomD
import mods.betterfoliage.util.randomF import mods.betterfoliage.util.randomF
import mods.betterfoliage.util.randomI import mods.betterfoliage.util.randomI
import net.fabricmc.fabric.api.event.world.WorldTickCallback import net.minecraft.client.particle.IParticleRenderType
import net.minecraft.client.particle.ParticleTextureSheet
import net.minecraft.client.world.ClientWorld import net.minecraft.client.world.ClientWorld
import net.minecraft.util.math.BlockPos import net.minecraft.util.math.BlockPos
import net.minecraft.util.math.MathHelper import net.minecraft.util.math.MathHelper
import net.minecraft.world.World import net.minecraft.world.World
import net.minecraftforge.common.MinecraftForge
import net.minecraftforge.event.TickEvent
import net.minecraftforge.eventbus.api.SubscribeEvent
import net.minecraftforge.fml.LogicalSide
import java.util.Random import java.util.Random
import kotlin.math.abs import kotlin.math.abs
import kotlin.math.cos import kotlin.math.cos
@@ -36,70 +37,65 @@ class FallingLeafParticle(
var wasCollided = false var wasCollided = false
init { init {
angle = random.randomF(max = PI2) roll = random.randomF(max = PI2)
prevAngle = angle - rotationSpeed oRoll = roll - rotationSpeed
maxAge = MathHelper.floor(randomD(0.6, 1.0) * BetterFoliage.config.fallingLeaves.lifetime * 20.0) lifetime = MathHelper.floor(randomD(0.6, 1.0) * Config.fallingLeaves.lifetime * 20.0)
velocityY = -BetterFoliage.config.fallingLeaves.speed yd = -Config.fallingLeaves.speed
scale = BetterFoliage.config.fallingLeaves.size.toFloat() * 0.1f
val state = world.getBlockState(pos)
quadSize = Config.fallingLeaves.size.toFloat() * 0.1f
setColor(leaf.overrideColor?.asInt ?: blockColor) setColor(leaf.overrideColor?.asInt ?: blockColor)
sprite = LeafParticleRegistry[leaf.leafType][randomI(max = 1024)] sprite = LeafParticleRegistry[leaf.leafType][randomI(max = 1024)]
} }
override val isValid: Boolean get() = (sprite != null) override val isValid: Boolean get() = (sprite != null)
override fun update() { override fun update() {
if (randomF() > 0.95f) rotationSpeed = -rotationSpeed if (random.nextFloat() > 0.95f) rotationSpeed *= -1.0f
if (age > maxAge - 20) colorAlpha = 0.05f * (maxAge - age) if (age > lifetime - 20) alpha = 0.05f * (lifetime - age)
if (onGround || wasCollided) { if (onGround || wasCollided) {
velocity.setTo(0.0, 0.0, 0.0) velocity.setTo(0.0, 0.0, 0.0)
prevAngle = angle
if (!wasCollided) { if (!wasCollided) {
age = age.coerceAtLeast(maxAge - 20) age = age.coerceAtLeast(lifetime - 20)
wasCollided = true wasCollided = true
} }
} else { } else {
val cosRotation = cos(angle).toDouble(); val sinRotation = sin(angle).toDouble() val cosRotation = cos(roll).toDouble(); val sinRotation = sin(roll).toDouble()
velocity.setTo(cosRotation, 0.0, sinRotation).mul(BetterFoliage.config.fallingLeaves.perturb) velocity.setTo(cosRotation, 0.0, sinRotation).mul(Config.fallingLeaves.perturb)
.add(LeafWindTracker.current).add(0.0, -1.0, 0.0).mul(BetterFoliage.config.fallingLeaves.speed) .add(LeafWindTracker.current).add(0.0, -1.0, 0.0).mul(Config.fallingLeaves.speed)
prevAngle = angle oRoll = roll
angle += rotationSpeed roll += rotationSpeed
} }
} }
fun setParticleColor(overrideColor: Int?, blockColor: Int) { override fun getRenderType(): IParticleRenderType = IParticleRenderType.PARTICLE_SHEET_TRANSLUCENT
val color = overrideColor ?: blockColor
setColor(color)
}
override fun getType() =
if (BetterFoliage.config.fallingLeaves.opacityHack) ParticleTextureSheet.PARTICLE_SHEET_OPAQUE
else ParticleTextureSheet.PARTICLE_SHEET_TRANSLUCENT
} }
object LeafWindTracker : WorldTickCallback, ClientWorldLoadCallback { object LeafWindTracker {
val random = Random() var random = Random()
val target = Double3.zero val target = Double3.zero
val current = Double3.zero val current = Double3.zero
var nextChange: Long = 0 var nextChange: Long = 0
fun changeWindTarget(world: World) { init {
nextChange = world.time + 120 + random.nextInt(80) MinecraftForge.EVENT_BUS.register(this)
}
fun changeWind(world: World) {
nextChange = world.gameTime + 120 + random.nextInt(80)
val direction = PI2 * random.nextDouble() val direction = PI2 * random.nextDouble()
val speed = abs(random.nextGaussian()) * BetterFoliage.config.fallingLeaves.windStrength + val speed = abs(random.nextGaussian()) * Config.fallingLeaves.windStrength +
(if (!world.isRaining) 0.0 else abs(random.nextGaussian()) * BetterFoliage.config.fallingLeaves.stormStrength) (if (!world.isRaining) 0.0 else abs(random.nextGaussian()) * Config.fallingLeaves.stormStrength)
target.setTo(cos(direction) * speed, 0.0, sin(direction) * speed) target.setTo(cos(direction) * speed, 0.0, sin(direction) * speed)
} }
override fun tick(world: World) { @SubscribeEvent
if (world.isClient) { fun handleWorldTick(event: TickEvent.WorldTickEvent) {
if (event.phase == TickEvent.Phase.START && event.side == LogicalSide.CLIENT) event.world.let { world ->
// change target wind speed // change target wind speed
if (world.time >= nextChange) changeWindTarget(world) if (world.dayTime >= nextChange) changeWind(world)
// change current wind speed // change current wind speed
val changeRate = if (world.isRaining) 0.015 else 0.005 val changeRate = if (world.isRaining) 0.015 else 0.005
@@ -111,8 +107,6 @@ object LeafWindTracker : WorldTickCallback, ClientWorldLoadCallback {
} }
} }
override fun loadWorld(world: ClientWorld) { // @SubscribeEvent
changeWindTarget(world) // fun handleWorldLoad(event: WorldEvent.Load) { if (event.world.isClientSide) changeWind(event.world) }
}
} }

View File

@@ -1,6 +1,7 @@
package mods.betterfoliage.render.particle package mods.betterfoliage.render.particle
import mods.betterfoliage.BetterFoliage import mods.betterfoliage.BetterFoliageMod
import mods.betterfoliage.model.Color
import mods.betterfoliage.model.FixedSpriteSet import mods.betterfoliage.model.FixedSpriteSet
import mods.betterfoliage.model.SpriteSet import mods.betterfoliage.model.SpriteSet
import mods.betterfoliage.resource.VeryEarlyReloadListener import mods.betterfoliage.resource.VeryEarlyReloadListener
@@ -10,93 +11,92 @@ import mods.betterfoliage.util.get
import mods.betterfoliage.util.getLines import mods.betterfoliage.util.getLines
import mods.betterfoliage.util.resourceManager import mods.betterfoliage.util.resourceManager
import mods.betterfoliage.util.stripStart import mods.betterfoliage.util.stripStart
import net.fabricmc.fabric.api.event.client.ClientSpriteRegistryCallback import net.minecraft.client.renderer.texture.MissingTextureSprite
import net.minecraft.client.texture.MissingSprite import net.minecraft.util.ResourceLocation
import net.minecraft.client.texture.SpriteAtlasTexture import net.minecraftforge.client.event.TextureStitchEvent
import net.minecraft.resource.ResourceManager import net.minecraftforge.eventbus.api.SubscribeEvent
import net.minecraft.util.Identifier import org.apache.logging.log4j.Level.INFO
import org.apache.logging.log4j.Level
import kotlin.collections.MutableList
import kotlin.collections.distinct
import kotlin.collections.filter
import kotlin.collections.firstOrNull
import kotlin.collections.forEach
import kotlin.collections.isNotEmpty
import kotlin.collections.joinToString
import kotlin.collections.listOf
import kotlin.collections.map
import kotlin.collections.mutableListOf
import kotlin.collections.mutableMapOf
import kotlin.collections.plus
import kotlin.collections.set
object LeafParticleRegistry : HasLogger(), ClientSpriteRegistryCallback, VeryEarlyReloadListener { interface LeafBlockModel {
val key: LeafParticleKey
}
interface LeafParticleKey {
val leafType: String
val overrideColor: Color?
}
object LeafParticleRegistry : HasLogger(), VeryEarlyReloadListener {
val typeMappings = TextureMatcher() val typeMappings = TextureMatcher()
val allTypes get() = (typeMappings.mappings.map { it.type } + "default").distinct() val allTypes get() = (typeMappings.mappings.map { it.type } + "default").distinct()
val spriteSets = mutableMapOf<String, SpriteSet>() val particles = hashMapOf<String, SpriteSet>()
override fun getFabricId() = Identifier(BetterFoliage.MOD_ID, "leaf-particles") operator fun get(type: String) = particles[type] ?: particles["default"]!!
override fun onReloadStarted(resourceManager: ResourceManager) { override fun onReloadStarted() {
typeMappings.loadMappings(Identifier(BetterFoliage.MOD_ID, "leaf_texture_mappings.cfg")) typeMappings.loadMappings(ResourceLocation(BetterFoliageMod.MOD_ID, "leaf_texture_mappings.cfg"))
detailLogger.log(Level.INFO, "Loaded leaf particle mappings, types = [${allTypes.joinToString(", ")}]") detailLogger.log(INFO, "Loaded leaf particle mappings, types = [${allTypes.joinToString(", ")}]")
} }
override fun registerSprites(atlasTexture: SpriteAtlasTexture, registry: ClientSpriteRegistryCallback.Registry) { @SubscribeEvent
spriteSets.clear() fun handlePreStitch(event: TextureStitchEvent.Pre) {
allTypes.forEach { leafType -> if (event.map.location() == Atlas.PARTICLES.resourceId) {
val validIds = (0 until 16).map { idx -> Identifier(BetterFoliage.MOD_ID, "particle/falling_leaf_${leafType}_$idx") } allTypes.forEach { leafType ->
.filter { resourceManager.containsResource(Atlas.PARTICLES.file(it)) } val locations = (0 until 16).map { idx ->
validIds.forEach { registry.register(it) } ResourceLocation(BetterFoliageMod.MOD_ID, "particle/falling_leaf_${leafType}_$idx")
}.filter { resourceManager.hasResource(Atlas.PARTICLES.file(it)) }
detailLogger.log(INFO, "Registering sprites for leaf particle type [$leafType], ${locations.size} sprites found")
locations.forEach { event.addSprite(it) }
}
} }
} }
operator fun get(leafType: String): SpriteSet { @SubscribeEvent
spriteSets[leafType]?.let { return it } fun handlePostStitch(event: TextureStitchEvent.Post) {
if (event.map.location() == Atlas.PARTICLES.resourceId) {
val sprites = (0 until 16) (typeMappings.mappings.map { it.type } + "default").distinct().forEach { leafType ->
.map { idx -> Identifier(BetterFoliage.MOD_ID, "particle/falling_leaf_${leafType}_$idx") } val sprites = (0 until 16).map { idx ->
.map { Atlas.PARTICLES[it] } ResourceLocation(BetterFoliageMod.MOD_ID, "particle/falling_leaf_${leafType}_$idx")
.filter { it !is MissingSprite } }
.map { event.map.getSprite(it) }
detailLogger.log(Level.INFO, "Leaf particle type [$leafType], ${sprites.size} sprites in atlas") .filter { it !is MissingTextureSprite }
if (sprites.isNotEmpty()) return FixedSpriteSet(sprites).apply { spriteSets[leafType] = this } detailLogger.log(INFO, "Leaf particle type [$leafType], ${sprites.size} sprites in atlas")
particles[leafType] = FixedSpriteSet(sprites)
return if (leafType == "default") }
FixedSpriteSet(listOf(Atlas.PARTICLES[MissingSprite.getMissingSpriteId()])).apply { spriteSets[leafType] = this } }
else
get("default").apply { spriteSets[leafType] = this }
}
init {
ClientSpriteRegistryCallback.event(SpriteAtlasTexture.PARTICLE_ATLAS_TEXTURE).register(this)
} }
} }
class TextureMatcher { class TextureMatcher {
data class Mapping(val domain: String?, val path: String, val type: String) { data class Mapping(val domain: String?, val path: String, val type: String) {
fun matches(iconLocation: Identifier): Boolean { fun matches(iconLocation: ResourceLocation): Boolean {
return (domain == null || domain == iconLocation.namespace) && return (domain == null || domain == iconLocation.namespace) &&
iconLocation.path.stripStart("blocks/").contains(path, ignoreCase = true) iconLocation.path.stripStart("blocks/").contains(path, ignoreCase = true)
} }
} }
val mappings: MutableList<Mapping> = mutableListOf() val mappings: MutableList<Mapping> = mutableListOf()
fun getType(resource: Identifier) = mappings.filter { it.matches(resource) }.map { it.type }.firstOrNull() fun getType(resource: ResourceLocation) = mappings.filter { it.matches(resource) }.map { it.type }.firstOrNull()
fun getType(iconName: String) = Identifier(iconName).let { getType(it) }
fun loadMappings(mappingLocation: Identifier) { fun loadMappings(mappingLocation: ResourceLocation) {
mappings.clear() mappings.clear()
resourceManager[mappingLocation]?.getLines()?.let { lines -> resourceManager[mappingLocation]?.getLines()?.let { lines ->
lines.filter { !it.startsWith("//") }.filter { it.isNotEmpty() }.forEach { line -> lines.filter { !it.startsWith("//") }.filter { !it.isEmpty() }.forEach { line ->
val line2 = line.trim().split('=') val line2 = line.trim().split('=')
if (line2.size == 2) { if (line2.size == 2) {
val mapping = line2[0].trim().split(':') val mapping = line2[0].trim().split(':')
if (mapping.size == 1) mappings.add(Mapping(null, mapping[0].trim(), line2[1].trim())) if (mapping.size == 1) mappings.add(Mapping(null, mapping[0].trim(), line2[1].trim()))
else if (mapping.size == 2) mappings.add(Mapping(mapping[0].trim(), mapping[1].trim(), line2[1].trim())) else if (mapping.size == 2) mappings.add(
Mapping(
mapping[0].trim(),
mapping[1].trim(),
line2[1].trim()
)
)
} }
} }
} }

View File

@@ -1,6 +1,8 @@
package mods.betterfoliage.render.particle package mods.betterfoliage.render.particle
import mods.betterfoliage.BetterFoliage import com.mojang.blaze3d.vertex.IVertexBuilder
import mods.betterfoliage.BetterFoliageMod
import mods.betterfoliage.config.Config
import mods.betterfoliage.model.SpriteDelegate import mods.betterfoliage.model.SpriteDelegate
import mods.betterfoliage.model.SpriteSetDelegate import mods.betterfoliage.model.SpriteSetDelegate
import mods.betterfoliage.util.Atlas import mods.betterfoliage.util.Atlas
@@ -9,13 +11,13 @@ import mods.betterfoliage.util.PI2
import mods.betterfoliage.util.forEachPairIndexed import mods.betterfoliage.util.forEachPairIndexed
import mods.betterfoliage.util.randomD import mods.betterfoliage.util.randomD
import mods.betterfoliage.util.randomI import mods.betterfoliage.util.randomI
import net.minecraft.client.particle.ParticleTextureSheet import net.minecraft.client.particle.IParticleRenderType
import net.minecraft.client.render.Camera import net.minecraft.client.renderer.ActiveRenderInfo
import net.minecraft.client.render.VertexConsumer
import net.minecraft.client.world.ClientWorld import net.minecraft.client.world.ClientWorld
import net.minecraft.util.Identifier import net.minecraft.util.ResourceLocation
import net.minecraft.util.math.BlockPos import net.minecraft.util.math.BlockPos
import net.minecraft.util.math.MathHelper import net.minecraft.util.math.MathHelper
import net.minecraft.world.World
import java.util.Deque import java.util.Deque
import java.util.LinkedList import java.util.LinkedList
import kotlin.math.cos import kotlin.math.cos
@@ -27,14 +29,14 @@ class RisingSoulParticle(
world, pos.x.toDouble() + 0.5, pos.y.toDouble() + 1.0, pos.z.toDouble() + 0.5 world, pos.x.toDouble() + 0.5, pos.y.toDouble() + 1.0, pos.z.toDouble() + 0.5
) { ) {
val particleTrail: Deque<Double3> = LinkedList() val particleTrail: Deque<Double3> = LinkedList<Double3>()
val initialPhase = randomD(max = PI2) val initialPhase = randomD(max = PI2)
init { init {
velocityY = 0.1 yd = 0.1
gravityStrength = 0.0f gravity = 0.0f
sprite = headIcons[randomI(max = 1024)] sprite = headIcons[randomI(max = 1024)]
maxAge = MathHelper.floor((0.6 + 0.4 * randomD()) * BetterFoliage.config.risingSoul.lifetime * 20.0) lifetime = MathHelper.floor((0.6 + 0.4 * randomD()) * Config.risingSoul.lifetime * 20.0)
} }
override val isValid: Boolean get() = true override val isValid: Boolean get() = true
@@ -43,31 +45,31 @@ class RisingSoulParticle(
val phase = initialPhase + (age.toDouble() * PI2 / 64.0) val phase = initialPhase + (age.toDouble() * PI2 / 64.0)
val cosPhase = cos(phase); val cosPhase = cos(phase);
val sinPhase = sin(phase) val sinPhase = sin(phase)
velocity.setTo(BetterFoliage.config.risingSoul.perturb.let { Double3(cosPhase * it, 0.1, sinPhase * it) }) velocity.setTo(Config.risingSoul.perturb.let { Double3(cosPhase * it, 0.1, sinPhase * it) })
particleTrail.addFirst(currentPos.copy()) particleTrail.addFirst(currentPos.copy())
while (particleTrail.size > BetterFoliage.config.risingSoul.trailLength) particleTrail.removeLast() while (particleTrail.size > Config.risingSoul.trailLength) particleTrail.removeLast()
if (!BetterFoliage.config.enabled) markDead() if (!Config.enabled) remove()
} }
override fun buildGeometry(vertexConsumer: VertexConsumer, camera: Camera, tickDelta: Float) { override fun render(vertexBuilder: IVertexBuilder, camera: ActiveRenderInfo, tickDelta: Float) {
var alpha = BetterFoliage.config.risingSoul.opacity.toFloat() var alpha = Config.risingSoul.opacity.toFloat()
if (age > maxAge - 40) alpha *= (maxAge - age) / 40.0f if (age > lifetime - 40) alpha *= (lifetime - age) / 40.0f
renderParticleQuad( renderParticleQuad(
vertexConsumer, camera, tickDelta, vertexBuilder, camera, tickDelta,
size = BetterFoliage.config.risingSoul.headSize * 0.25, size = Config.risingSoul.headSize * 0.25,
alpha = alpha alpha = alpha
) )
var scale = BetterFoliage.config.risingSoul.trailSize * 0.25 var scale = Config.risingSoul.trailSize * 0.25
particleTrail.forEachPairIndexed { idx, current, previous -> particleTrail.forEachPairIndexed { idx, current, previous ->
scale *= BetterFoliage.config.risingSoul.sizeDecay scale *= Config.risingSoul.sizeDecay
alpha *= BetterFoliage.config.risingSoul.opacityDecay.toFloat() alpha *= Config.risingSoul.opacityDecay.toFloat()
if (idx % BetterFoliage.config.risingSoul.trailDensity == 0) if (idx % Config.risingSoul.trailDensity == 0)
renderParticleQuad( renderParticleQuad(
vertexConsumer, camera, tickDelta, vertexBuilder, camera, tickDelta,
currentPos = current, currentPos = current,
prevPos = previous, prevPos = previous,
size = scale, size = scale,
@@ -77,12 +79,12 @@ class RisingSoulParticle(
} }
} }
override fun getType() = ParticleTextureSheet.PARTICLE_SHEET_TRANSLUCENT override fun getRenderType(): IParticleRenderType = IParticleRenderType.PARTICLE_SHEET_TRANSLUCENT
companion object { companion object {
val headIcons by SpriteSetDelegate(Atlas.PARTICLES) { idx -> val headIcons by SpriteSetDelegate(Atlas.PARTICLES) { idx ->
Identifier(BetterFoliage.MOD_ID, "particle/rising_soul_$idx") ResourceLocation(BetterFoliageMod.MOD_ID, "particle/rising_soul_$idx")
} }
val trackIcon by SpriteDelegate(Atlas.PARTICLES) { Identifier(BetterFoliage.MOD_ID, "particle/soul_track") } val trackIcon by SpriteDelegate(Atlas.PARTICLES) { ResourceLocation(BetterFoliageMod.MOD_ID, "particle/soul_track") }
} }
} }

View File

@@ -0,0 +1,66 @@
package mods.betterfoliage.render.pipeline
import com.mojang.blaze3d.matrix.MatrixStack
import mods.betterfoliage.chunk.BasicBlockCtx
import mods.betterfoliage.chunk.BlockCtx
import mods.betterfoliage.model.HalfBakedQuad
import mods.betterfoliage.model.SpecialRenderModel
import mods.betterfoliage.render.lighting.VanillaFullBlockLighting
import mods.betterfoliage.render.lighting.VanillaQuadLighting
import mods.betterfoliage.render.lighting.VanillaVertexLighter
import mods.betterfoliage.util.Int3
import mods.betterfoliage.util.plus
import net.minecraft.block.Block
import net.minecraft.client.Minecraft
import net.minecraft.util.Direction
import net.minecraft.util.math.BlockPos
import net.minecraft.world.IBlockDisplayReader
import net.minecraftforge.client.model.data.IModelData
import java.util.Random
/**
* Rendering context for drawing [SpecialRenderModel] models.
*
* This class (and others in its constellation) basically form a replacement, highly customizable,
* push-based partial rendering pipeline for [SpecialRenderModel] instances.
*/
abstract class RenderCtxBase(
world: IBlockDisplayReader,
pos: BlockPos,
val matrixStack: MatrixStack,
var checkSides: Boolean,
val random: Random,
val modelData: IModelData
) : BlockCtx by BasicBlockCtx(world, pos) {
abstract fun renderQuad(quad: HalfBakedQuad)
var hasRendered = false
val blockModelShapes = Minecraft.getInstance().blockRenderer.blockModelShaper
var vertexLighter: VanillaVertexLighter = VanillaFullBlockLighting
protected val lightingData = RenderCtxBase.lightingData.get().apply {
calc.reset(this@RenderCtxBase)
blockColors = Minecraft.getInstance().blockColors
}
inline fun Direction?.shouldRender() = this == null || !checkSides || Block.shouldRenderFace(state, world, pos, this)
fun renderQuads(quads: Iterable<HalfBakedQuad>) {
quads.forEach { quad ->
if (quad.raw.face.shouldRender()) {
renderQuad(quad)
hasRendered = true
}
}
}
fun renderMasquerade(offset: Int3, func: () -> Unit) {
lightingData.calc.blockPos += offset
func()
lightingData.calc.blockPos = pos
}
companion object {
val lightingData = ThreadLocal.withInitial { VanillaQuadLighting() }
}
}

View File

@@ -0,0 +1,74 @@
package mods.betterfoliage.render.pipeline
import com.mojang.blaze3d.matrix.MatrixStack
import mods.betterfoliage.model.HalfBakedQuad
import mods.betterfoliage.model.SpecialRenderModel
import mods.betterfoliage.render.lighting.ForgeVertexLighter
import mods.betterfoliage.render.lighting.ForgeVertexLighterAccess
import net.minecraft.block.BlockState
import net.minecraft.client.renderer.LightTexture
import net.minecraft.util.math.BlockPos
import net.minecraft.world.IBlockDisplayReader
import net.minecraftforge.client.model.data.IModelData
import net.minecraftforge.client.model.pipeline.VertexLighterFlat
import java.util.Random
class RenderCtxForge(
world: IBlockDisplayReader,
pos: BlockPos,
val lighter: VertexLighterFlat,
matrixStack: MatrixStack,
checkSides: Boolean,
random: Random,
modelData: IModelData
): RenderCtxBase(world, pos, matrixStack, checkSides, random, modelData), ForgeVertexLighter {
override fun renderQuad(quad: HalfBakedQuad) {
// set Forge lighter AO calculator to us
vertexLighter.updateLightmapAndColor(quad, lightingData)
quad.baked.pipe(lighter)
}
// somewhat ugly hack to pipe lighting values into the Forge pipeline
var vIdx = 0
override fun updateVertexLightmap(normal: FloatArray, lightmap: FloatArray, x: Float, y: Float, z: Float) {
lightingData.packedLight[vIdx].let { packedLight ->
lightmap[0] = LightTexture.block(packedLight) / 0xF.toFloat()
lightmap[1] = LightTexture.sky(packedLight) / 0xF.toFloat()
}
}
override fun updateVertexColor(normal: FloatArray, color: FloatArray, x: Float, y: Float, z: Float, tint: Float, multiplier: Int) {
color[0] = lightingData.tint[0] * lightingData.colorMultiplier[vIdx]
color[1] = lightingData.tint[1] * lightingData.colorMultiplier[vIdx]
color[2] = lightingData.tint[2] * lightingData.colorMultiplier[vIdx]
vIdx++
}
companion object {
@JvmStatic
fun render(
lighter: VertexLighterFlat,
world: IBlockDisplayReader,
model: SpecialRenderModel,
state: BlockState,
pos: BlockPos,
matrixStack: MatrixStack,
checkSides: Boolean,
rand: Random, seed: Long,
modelData: IModelData
): Boolean {
lighter.setWorld(world)
lighter.setState(state)
lighter.setBlockPos(pos)
rand.setSeed(seed)
lighter.updateBlockInfo()
return RenderCtxForge(world, pos, lighter, matrixStack, checkSides, rand, modelData).let {
(lighter as ForgeVertexLighterAccess).vertexLighter = it
model.render(it, false)
lighter.resetBlockInfo()
it.hasRendered
}
}
}
}

View File

@@ -0,0 +1,66 @@
package mods.betterfoliage.render.pipeline
import com.mojang.blaze3d.matrix.MatrixStack
import com.mojang.blaze3d.vertex.IVertexBuilder
import mods.betterfoliage.model.HalfBakedQuad
import mods.betterfoliage.model.SpecialRenderModel
import net.minecraft.block.BlockState
import net.minecraft.client.renderer.BlockModelRenderer
import net.minecraft.util.math.BlockPos
import net.minecraft.world.IBlockDisplayReader
import net.minecraftforge.client.model.data.IModelData
import java.util.Random
class RenderCtxVanilla(
val renderer: BlockModelRenderer,
world: IBlockDisplayReader,
pos: BlockPos,
val buffer: IVertexBuilder,
val combinedOverlay: Int,
matrixStack: MatrixStack,
checkSides: Boolean,
random: Random,
val seed: Long,
modelData: IModelData,
val useAO: Boolean
): RenderCtxBase(world, pos, matrixStack, checkSides, random, modelData) {
override fun renderQuad(quad: HalfBakedQuad) {
vertexLighter.updateLightmapAndColor(quad, lightingData)
buffer.putBulkData(
matrixStack.last(), quad.baked,
lightingData.colorMultiplier,
lightingData.tint[0], lightingData.tint[1], lightingData.tint[2],
lightingData.packedLight, combinedOverlay, true
)
}
companion object {
@JvmStatic
fun render(
renderer: BlockModelRenderer,
world: IBlockDisplayReader,
model: SpecialRenderModel,
state: BlockState,
pos: BlockPos,
matrixStack: MatrixStack,
buffer: IVertexBuilder,
checkSides: Boolean,
random: Random,
rand: Long,
combinedOverlay: Int,
modelData: IModelData,
smooth: Boolean
): Boolean {
random.setSeed(rand)
val ctx = RenderCtxVanilla(renderer, world, pos, buffer, combinedOverlay, matrixStack, checkSides, random, rand, modelData, smooth)
lightingData.apply {
}
model.render(ctx, false)
return ctx.hasRendered
}
}
}

View File

@@ -1,9 +1,8 @@
package mods.betterfoliage.resource package mods.betterfoliage.resource
import net.fabricmc.fabric.api.resource.IdentifiableResourceReloadListener import net.minecraft.profiler.IProfiler
import net.minecraft.resource.ResourceManager import net.minecraft.resources.IFutureReloadListener
import net.minecraft.resource.ResourceReloadListener import net.minecraft.resources.IResourceManager
import net.minecraft.util.profiler.Profiler
import java.util.concurrent.CompletableFuture import java.util.concurrent.CompletableFuture
import java.util.concurrent.Executor import java.util.concurrent.Executor
@@ -11,18 +10,18 @@ import java.util.concurrent.Executor
* Catch resource reload extremely early, before any of the reloaders * Catch resource reload extremely early, before any of the reloaders
* have started working. * have started working.
*/ */
interface VeryEarlyReloadListener : ResourceReloadListener, IdentifiableResourceReloadListener { interface VeryEarlyReloadListener : IFutureReloadListener {
override fun reload( override fun reload(
synchronizer: ResourceReloadListener.Synchronizer, stage: IFutureReloadListener.IStage,
resourceManager: ResourceManager, resourceManager: IResourceManager,
preparationsProfiler: Profiler, preparationsProfiler: IProfiler,
reloadProfiler: Profiler, reloadProfiler: IProfiler,
backgroundExecutor: Executor, backgroundExecutor: Executor,
gameExecutor: Executor gameExecutor: Executor
): CompletableFuture<Void> { ): CompletableFuture<Void> {
onReloadStarted(resourceManager) onReloadStarted()
return synchronizer.whenPrepared(null) return stage.wait(null)
} }
fun onReloadStarted(resourceManager: ResourceManager) {} fun onReloadStarted() {}
} }

View File

@@ -1,37 +1,55 @@
package mods.betterfoliage.resource.discovery package mods.betterfoliage.resource.discovery
import mods.betterfoliage.BetterFoliage import mods.betterfoliage.BetterFoliage
import mods.betterfoliage.BlockModelsReloadCallback import mods.betterfoliage.util.Atlas
import mods.betterfoliage.ModelLoadingCallback
import mods.betterfoliage.util.HasLogger import mods.betterfoliage.util.HasLogger
import mods.betterfoliage.util.Invalidator import mods.betterfoliage.util.Invalidator
import net.fabricmc.fabric.api.event.client.ClientSpriteRegistryCallback
import net.minecraft.block.BlockState import net.minecraft.block.BlockState
import net.minecraft.client.render.block.BlockModels import net.minecraft.client.renderer.model.IBakedModel
import net.minecraft.client.render.model.BakedModel import net.minecraft.client.renderer.model.IModelTransform
import net.minecraft.client.render.model.ModelBakeSettings import net.minecraft.client.renderer.model.IUnbakedModel
import net.minecraft.client.render.model.ModelLoader import net.minecraft.client.renderer.model.ModelBakery
import net.minecraft.client.render.model.UnbakedModel import net.minecraft.client.renderer.model.RenderMaterial
import net.minecraft.client.texture.Sprite import net.minecraft.client.renderer.texture.TextureAtlasSprite
import net.minecraft.client.texture.SpriteAtlasTexture import net.minecraft.util.ResourceLocation
import net.minecraft.client.util.SpriteIdentifier import net.minecraftforge.client.event.ModelBakeEvent
import net.minecraft.resource.ResourceManager import net.minecraftforge.client.event.TextureStitchEvent
import net.minecraft.util.Identifier import net.minecraftforge.eventbus.api.Event
import net.minecraftforge.eventbus.api.EventPriority
import net.minecraftforge.eventbus.api.SubscribeEvent
import net.minecraftforge.fml.loading.progress.StartupMessageManager
import org.apache.logging.log4j.Level.INFO import org.apache.logging.log4j.Level.INFO
import org.apache.logging.log4j.Level.WARN import org.apache.logging.log4j.Level.WARN
import org.apache.logging.log4j.Logger import org.apache.logging.log4j.Logger
import java.lang.ref.WeakReference import java.lang.ref.WeakReference
import java.util.function.Function import java.util.function.Function
data class ModelDefinitionsLoadedEvent(
val bakery: ModelBakery
) : Event()
interface ModelBakingKey {
fun bake(ctx: ModelBakingContext): IBakedModel? =
ctx.getUnbaked().bake(ctx.bakery, ctx.spriteGetter, ctx.transform, ctx.location)
}
interface ModelDiscovery {
fun onModelsLoaded(
bakery: ModelBakery,
sprites: MutableSet<ResourceLocation>,
replacements: MutableMap<ResourceLocation, ModelBakingKey>
)
}
data class ModelDiscoveryContext( data class ModelDiscoveryContext(
val bakery: ModelLoader, val bakery: ModelBakery,
val blockState: BlockState, val blockState: BlockState,
val modelLocation: Identifier, val modelLocation: ResourceLocation,
val sprites: MutableSet<Identifier>, val sprites: MutableSet<ResourceLocation>,
val replacements: MutableMap<Identifier, ModelBakingKey>, val replacements: MutableMap<ResourceLocation, ModelBakingKey>,
val logger: Logger val logger: Logger
) { ) {
fun getUnbaked(location: Identifier = modelLocation) = bakery.getOrLoadModel(location) fun getUnbaked(location: ResourceLocation = modelLocation) = bakery.getModel(location)
fun addReplacement(key: ModelBakingKey, addToStateKeys: Boolean = true) { fun addReplacement(key: ModelBakingKey, addToStateKeys: Boolean = true) {
replacements[modelLocation] = key replacements[modelLocation] = key
if (addToStateKeys) BetterFoliage.blockTypes.stateKeys[blockState] = key if (addToStateKeys) BetterFoliage.blockTypes.stateKeys[blockState] = key
@@ -39,75 +57,62 @@ data class ModelDiscoveryContext(
} }
} }
interface ModelDiscovery {
fun onModelsLoaded(
bakery: ModelLoader,
sprites: MutableSet<Identifier>,
replacements: MutableMap<Identifier, ModelBakingKey>
)
}
data class ModelBakingContext( data class ModelBakingContext(
val bakery: ModelLoader, val bakery: ModelBakery,
val spriteGetter: Function<SpriteIdentifier, Sprite>, val spriteGetter: Function<RenderMaterial, TextureAtlasSprite>,
val location: Identifier, val location: ResourceLocation,
val transform: ModelBakeSettings, val transform: IModelTransform,
val logger: Logger val logger: Logger
) { ) {
fun getUnbaked() = bakery.getOrLoadModel(location) fun getUnbaked() = bakery.getModel(location)
fun getBaked() = bakery.bake(location, transform) fun getBaked() = bakery.getBakedModel(location, transform, spriteGetter)
} }
interface ModelBakingKey { object BakeWrapperManager : Invalidator, HasLogger() {
fun bake(ctx: ModelBakingContext): BakedModel? =
ctx.getUnbaked().bake(ctx.bakery, ctx.spriteGetter, ctx.transform, ctx.location)
}
object BakeWrapperManager : HasLogger(), Invalidator, ModelLoadingCallback, ClientSpriteRegistryCallback, BlockModelsReloadCallback {
init {
ModelLoadingCallback.EVENT.register(this)
ClientSpriteRegistryCallback.event(SpriteAtlasTexture.BLOCK_ATLAS_TEXTURE).register(this)
}
val discoverers = mutableListOf<ModelDiscovery>() val discoverers = mutableListOf<ModelDiscovery>()
override val callbacks = mutableListOf<WeakReference<()->Unit>>() override val callbacks = mutableListOf<WeakReference<()->Unit>>()
private val replacements = mutableMapOf<Identifier, ModelBakingKey>() private val replacements = mutableMapOf<ResourceLocation, ModelBakingKey>()
private val sprites = mutableSetOf<Identifier>() private val sprites = mutableSetOf<ResourceLocation>()
override fun beginLoadModels(loader: ModelLoader, manager: ResourceManager) { @SubscribeEvent(priority = EventPriority.LOWEST)
fun handleModelLoad(event: ModelDefinitionsLoadedEvent) {
val startTime = System.currentTimeMillis() val startTime = System.currentTimeMillis()
replacements.clear()
sprites.clear()
invalidate() invalidate()
BetterFoliage.blockTypes = BlockTypeCache() BetterFoliage.blockTypes = BlockTypeCache()
StartupMessageManager.addModMessage("BetterFoliage: discovering models")
logger.log(INFO, "starting model discovery (${discoverers.size} listeners)") logger.log(INFO, "starting model discovery (${discoverers.size} listeners)")
discoverers.forEach { listener -> discoverers.forEach { listener ->
val replacementsLocal = mutableMapOf<Identifier, ModelBakingKey>() val replacementsLocal = mutableMapOf<ResourceLocation, ModelBakingKey>()
listener.onModelsLoaded(loader, sprites, replacements) listener.onModelsLoaded(event.bakery, sprites, replacements)
} }
val elapsed = System.currentTimeMillis() - startTime val elapsed = System.currentTimeMillis() - startTime
logger.log(INFO, "finished model discovery in $elapsed ms, ${replacements.size} top-level replacements") logger.log(INFO, "finished model discovery in $elapsed ms, ${replacements.size} top-level replacements")
} }
override fun registerSprites(atlas: SpriteAtlasTexture, registry: ClientSpriteRegistryCallback.Registry) { @SubscribeEvent
logger.log(INFO, "Adding ${sprites.size} sprites to block atlas") fun handleStitch(event: TextureStitchEvent.Pre) {
sprites.forEach { registry.register(it) } if (event.map.location() == Atlas.BLOCKS.resourceId) {
sprites.clear() logger.log(INFO, "Adding ${sprites.size} sprites to block atlas")
sprites.forEach { event.addSprite(it) }
sprites.clear()
}
} }
override fun reloadBlockModels(blockModels: BlockModels) { @SubscribeEvent
fun handleModelBake(event: ModelBakeEvent) {
replacements.clear() replacements.clear()
} }
fun onBake( fun onBake(
unbaked: UnbakedModel, unbaked: IUnbakedModel,
bakery: ModelLoader, bakery: ModelBakery,
spriteGetter: Function<SpriteIdentifier, Sprite>, spriteGetter: Function<RenderMaterial, TextureAtlasSprite>,
transform: ModelBakeSettings, transform: IModelTransform,
location: Identifier location: ResourceLocation
): BakedModel? { ): IBakedModel? {
val ctx = ModelBakingContext(bakery, spriteGetter, location, transform, detailLogger) val ctx = ModelBakingContext(bakery, spriteGetter, location, transform, detailLogger)
// bake replacement if available // bake replacement if available
replacements[location]?.let { replacement -> replacements[location]?.let { replacement ->

View File

@@ -9,6 +9,6 @@ class BlockTypeCache {
val stateKeys = mutableMapOf<BlockState, ModelBakingKey>() val stateKeys = mutableMapOf<BlockState, ModelBakingKey>()
inline fun <reified T> getTyped(state: BlockState) = stateKeys[state] as? T inline fun <reified T> getTypedOrNull(state: BlockState) = stateKeys[state] as? T
inline fun <reified T> hasTyped(state: BlockState) = stateKeys[state] is T inline fun <reified T> hasTyped(state: BlockState) = stateKeys[state] is T
} }

View File

@@ -1,16 +1,12 @@
package mods.betterfoliage.resource.discovery package mods.betterfoliage.resource.discovery
import mods.betterfoliage.util.HasLogger import mods.betterfoliage.util.HasLogger
import mods.betterfoliage.util.getLines
import mods.betterfoliage.util.INTERMEDIARY
import mods.betterfoliage.util.getJavaClass import mods.betterfoliage.util.getJavaClass
import net.fabricmc.loader.api.FabricLoader import mods.betterfoliage.util.getLines
import mods.betterfoliage.util.resourceManager
import net.minecraft.block.Block import net.minecraft.block.Block
import net.minecraft.resource.ResourceManager import net.minecraft.util.ResourceLocation
import net.minecraft.util.Identifier
import org.apache.logging.log4j.Level
import org.apache.logging.log4j.Level.INFO import org.apache.logging.log4j.Level.INFO
import org.apache.logging.log4j.Logger
interface IBlockMatcher { interface IBlockMatcher {
fun matchesClass(block: Block): Boolean fun matchesClass(block: Block): Boolean
@@ -27,8 +23,7 @@ class SimpleBlockMatcher(vararg val classes: Class<*>) : IBlockMatcher {
} }
} }
class ConfigurableBlockMatcher(val location: Identifier) : HasLogger(), IBlockMatcher { class ConfigurableBlockMatcher(val location: ResourceLocation) : HasLogger(), IBlockMatcher {
val blackList = mutableListOf<Class<*>>() val blackList = mutableListOf<Class<*>>()
val whiteList = mutableListOf<Class<*>>() val whiteList = mutableListOf<Class<*>>()
@@ -46,42 +41,32 @@ class ConfigurableBlockMatcher(val location: Identifier) : HasLogger(), IBlockMa
return null return null
} }
fun readDefaults(manager: ResourceManager) { fun readDefaults() {
blackList.clear() blackList.clear()
whiteList.clear() whiteList.clear()
manager.getAllResources(location).forEach { resource -> resourceManager.getResources(location).forEach { resource ->
detailLogger.log(INFO, "Reading class list $location from pack ${resource.resourcePackName}") detailLogger.log(INFO, "Reading block class configuration $location from pack ${resource.sourceName}")
resource.getLines().map{ it.trim() }.filter { !it.startsWith("//") && it.isNotEmpty() }.forEach { line -> resource.getLines().map{ it.trim() }.filter { !it.startsWith("//") && it.isNotEmpty() }.forEach { line ->
val name = if (line.startsWith("-")) line.substring(1) else line if (line.startsWith("-")) getJavaClass(line.substring(1))?.let { blackList.add(it) }
val mappedName = FabricLoader.getInstance().mappingResolver.mapClassName(INTERMEDIARY, name) else getJavaClass(line)?.let { whiteList.add(it) }
if (name != mappedName) logger.debug(" found yarn mapping for class: $name -> $mappedName")
val klass = getJavaClass(mappedName)
val list = if (line.startsWith("-")) "blacklist" to blackList else "whitelist" to whiteList
if (klass != null) {
logger.debug(" ${list.first} class $name found")
list.second.add(klass)
} else {
logger.debug(" ${list.first} class $name not found")
}
} }
} }
} }
} }
data class ModelTextureList(val modelLocation: Identifier, val textureNames: List<String>) { data class ModelTextureList(val modelLocation: ResourceLocation, val textureNames: List<String>) {
constructor(vararg args: String) : this(Identifier(args[0]), listOf(*args).drop(1)) constructor(vararg args: String) : this(ResourceLocation(args[0]), listOf(*args).drop(1))
} }
class ModelTextureListConfiguration(val location: Identifier) : HasLogger() { class ModelTextureListConfiguration(val location: ResourceLocation) : HasLogger() {
val modelList = mutableListOf<ModelTextureList>() val modelList = mutableListOf<ModelTextureList>()
fun readDefaults(manager: ResourceManager) { fun readDefaults() {
manager.getAllResources(location).forEach { resource -> resourceManager.getResources(location).forEach { resource ->
detailLogger.log(INFO, "Reading model configuration $location from pack ${resource.resourcePackName}") detailLogger.log(INFO, "Reading model/texture configuration $location from pack ${resource.sourceName}")
resource.getLines().map{ it.trim() }.filter { !it.startsWith("//") && it.isNotEmpty() }.forEach { line -> resource.getLines().map{ it.trim() }.filter { !it.startsWith("//") && it.isNotEmpty() }.forEach { line ->
val elements = line.split(",") val elements = line.split(",")
modelList.add(ModelTextureList(Identifier(elements.first()), elements.drop(1))) modelList.add(ModelTextureList(ResourceLocation(elements.first()), elements.drop(1)))
} }
} }
} }

View File

@@ -2,28 +2,25 @@ package mods.betterfoliage.resource.discovery
import com.google.common.base.Joiner import com.google.common.base.Joiner
import mods.betterfoliage.util.HasLogger import mods.betterfoliage.util.HasLogger
import mods.betterfoliage.util.YarnHelper import net.minecraft.client.renderer.BlockModelShapes
import mods.betterfoliage.util.get import net.minecraft.client.renderer.model.BlockModel
import net.minecraft.block.Block import net.minecraft.client.renderer.model.ModelBakery
import net.minecraft.client.render.block.BlockModels import net.minecraft.client.renderer.model.VariantList
import net.minecraft.client.render.model.ModelLoader import net.minecraft.client.renderer.texture.MissingTextureSprite
import net.minecraft.client.render.model.json.JsonUnbakedModel import net.minecraft.util.ResourceLocation
import net.minecraft.client.render.model.json.WeightedUnbakedModel import net.minecraftforge.registries.ForgeRegistries
import net.minecraft.client.texture.MissingSprite
import net.minecraft.util.Identifier
import net.minecraft.util.registry.Registry
import org.apache.logging.log4j.Level import org.apache.logging.log4j.Level
abstract class AbstractModelDiscovery : HasLogger(), ModelDiscovery { abstract class AbstractModelDiscovery : HasLogger(), ModelDiscovery {
override fun onModelsLoaded( override fun onModelsLoaded(
bakery: ModelLoader, bakery: ModelBakery,
sprites: MutableSet<Identifier>, sprites: MutableSet<ResourceLocation>,
replacements: MutableMap<Identifier, ModelBakingKey> replacements: MutableMap<ResourceLocation, ModelBakingKey>
) { ) {
(Registry.BLOCK as Iterable<Block>) ForgeRegistries.BLOCKS
.flatMap { block -> block.stateManager.states } .flatMap { block -> block.stateDefinition.possibleStates }
.forEach { state -> .forEach { state ->
val location = BlockModels.getModelId(state) val location = BlockModelShapes.stateToModelLocation(state)
val ctx = ModelDiscoveryContext(bakery, state, location, sprites, replacements, detailLogger) val ctx = ModelDiscoveryContext(bakery, state, location, sprites, replacements, detailLogger)
try { try {
processModel(ctx) processModel(ctx)
@@ -37,12 +34,12 @@ abstract class AbstractModelDiscovery : HasLogger(), ModelDiscovery {
val model = ctx.getUnbaked() val model = ctx.getUnbaked()
// built-in support for container models // built-in support for container models
if (model is WeightedUnbakedModel) { if (model is VariantList) {
// per-location replacements need to be scoped to the variant list, as replacement models // per-location replacements need to be scoped to the variant list, as replacement models
// may need information from the BlockState which is not available at baking time // may need information from the BlockState which is not available at baking time
val scopedReplacements = mutableMapOf<Identifier, ModelBakingKey>() val scopedReplacements = mutableMapOf<ResourceLocation, ModelBakingKey>()
model.variants.forEach { variant -> model.variants.forEach { variant ->
processModel(ctx.copy(modelLocation = variant.location, replacements = scopedReplacements)) processModel(ctx.copy(modelLocation = variant.modelLocation, replacements = scopedReplacements))
} }
if (scopedReplacements.isNotEmpty()) { if (scopedReplacements.isNotEmpty()) {
ctx.addReplacement(WeightedUnbakedKey(scopedReplacements), addToStateKeys = false) ctx.addReplacement(WeightedUnbakedKey(scopedReplacements), addToStateKeys = false)
@@ -57,43 +54,51 @@ abstract class ConfigurableModelDiscovery : AbstractModelDiscovery() {
abstract fun processModel( abstract fun processModel(
ctx: ModelDiscoveryContext, ctx: ModelDiscoveryContext,
textureMatch: List<Identifier> textureMatch: List<ResourceLocation>
) )
override fun processModel(ctx: ModelDiscoveryContext) { override fun processModel(ctx: ModelDiscoveryContext) {
val model = ctx.getUnbaked() val model = ctx.getUnbaked()
if (model is JsonUnbakedModel) { if (model is BlockModel) {
val matchClass = matchClasses.matchingClass(ctx.blockState.block) ?: return val matchClass = matchClasses.matchingClass(ctx.blockState.block) ?: return
detailLogger.log(Level.INFO, "block state ${ctx.blockState}") detailLogger.log(Level.INFO, "block state ${ctx.blockState}")
detailLogger.log(Level.INFO, " model ${ctx.modelLocation}") detailLogger.log(Level.INFO, " model ${ctx.modelLocation}")
detailLogger.log(Level.INFO, " class ${ctx.blockState.block.javaClass.name} matches ${matchClass.name}") detailLogger.log(Level.INFO, " class ${ctx.blockState.block.javaClass.name} matches ${matchClass.name}")
modelTextures val ancestry = ctx.bakery.getAncestry(ctx.modelLocation)
.filter { matcher -> ctx.bakery.modelDerivesFrom(model, ctx.modelLocation, matcher.modelLocation) } val matches = modelTextures.filter { matcher ->
.forEach { match -> matcher.modelLocation in ancestry
}
matches.forEach { match ->
detailLogger.log(Level.INFO, " model $model matches ${match.modelLocation}") detailLogger.log(Level.INFO, " model $model matches ${match.modelLocation}")
val materials = match.textureNames.map { it to model.resolveSprite(it) } val materials = match.textureNames.map { it to model.getMaterial(it) }
val texMapString = Joiner.on(", ").join(materials.map { "${it.first}=${it.second.textureId}" }) val texMapString = Joiner.on(", ").join(materials.map { "${it.first}=${it.second.texture()}" })
detailLogger.log(Level.INFO, " sprites [$texMapString]") detailLogger.log(Level.INFO, " sprites [$texMapString]")
if (materials.all { it.second.textureId != MissingSprite.getMissingSpriteId() }) { if (materials.all { it.second.texture() != MissingTextureSprite.getLocation() }) {
// found a valid model (all required textures exist) // found a valid model (all required textures exist)
processModel(ctx, materials.map { it.second.textureId }) processModel(ctx, materials.map { it.second.texture() })
}
} }
}
if (matches.isEmpty()) {
detailLogger.log(Level.INFO, " no matches for model ${ctx.modelLocation}, inheritance chain ${ancestry.joinToString(" -> ")}")
}
} }
return super.processModel(ctx) return super.processModel(ctx)
} }
} }
// net.minecraft.client.render.model.json.JsonUnbakedModel.parentId fun ModelBakery.modelDerivesFrom(model: BlockModel, location: ResourceLocation, target: ResourceLocation): Boolean =
val JsonUnbakedModel_parentId = YarnHelper.requiredField<Identifier>("net.minecraft.class_793", "field_4247", "Lnet/minecraft/class_2960;")
fun ModelLoader.modelDerivesFrom(model: JsonUnbakedModel, location: Identifier, target: Identifier): Boolean =
if (location == target) true if (location == target) true
else model[JsonUnbakedModel_parentId] else model.parentLocation
?.let { getOrLoadModel(it) as? JsonUnbakedModel } ?.let { getModel(it) as? BlockModel }
?.let { parent -> modelDerivesFrom(parent, model[JsonUnbakedModel_parentId]!!, target) } ?.let { parent -> modelDerivesFrom(parent, model.parentLocation!!, target) }
?: false ?: false
fun ModelBakery.getAncestry(location: ResourceLocation): List<ResourceLocation> {
val model = getModel(location) as? BlockModel ?: return listOf(location)
val parentAncestry = model.parentLocation?.let { getAncestry(it) } ?: emptyList()
return listOf(location) + parentAncestry
}

View File

@@ -1,40 +1,37 @@
package mods.betterfoliage.resource.discovery package mods.betterfoliage.resource.discovery
import mods.betterfoliage.model.HalfBakedSimpleModelWrapper
import mods.betterfoliage.model.SpecialRenderModel
import mods.betterfoliage.model.WeightedModelWrapper import mods.betterfoliage.model.WeightedModelWrapper
import mods.betterfoliage.model.WrappedBakedModel
import mods.betterfoliage.model.WrappedMeshModel
import mods.betterfoliage.util.HasLogger import mods.betterfoliage.util.HasLogger
import net.fabricmc.fabric.api.renderer.v1.material.BlendMode import net.minecraft.client.renderer.model.IBakedModel
import net.minecraft.client.render.model.BakedModel import net.minecraft.client.renderer.model.SimpleBakedModel
import net.minecraft.client.render.model.BasicBakedModel import net.minecraft.client.renderer.model.VariantList
import net.minecraft.client.render.model.json.WeightedUnbakedModel import net.minecraft.util.ResourceLocation
import net.minecraft.util.Identifier
import org.apache.logging.log4j.Level.INFO import org.apache.logging.log4j.Level.INFO
import org.apache.logging.log4j.Level.WARN import org.apache.logging.log4j.Level.WARN
class WeightedUnbakedKey( class WeightedUnbakedKey(
val replacements: Map<Identifier, ModelBakingKey> val replacements: Map<ResourceLocation, ModelBakingKey>
) : ModelBakingKey { ) : ModelBakingKey {
override fun bake(ctx: ModelBakingContext): BakedModel? { override fun bake(ctx: ModelBakingContext): IBakedModel? {
val unbaked = ctx.getUnbaked() val unbaked = ctx.getUnbaked()
if (unbaked !is WeightedUnbakedModel) return super.bake(ctx) if (unbaked !is VariantList) return super.bake(ctx)
// bake all variants, replace as needed // bake all variants, replace as needed
val bakedModels = unbaked.variants.mapNotNull { val bakedModels = unbaked.variants.mapNotNull {
val variantCtx = ctx.copy(location = it.location, transform = it) val variantCtx = ctx.copy(location = it.modelLocation, transform = it)
val replacement = replacements[it.location] val replacement = replacements[it.modelLocation]
val baked = replacement?.let { replacement -> val baked = replacement?.let { replacement ->
ctx.logger.log(INFO, "Baking replacement for variant [${variantCtx.getUnbaked()::class.java.simpleName}] ${variantCtx.location} -> $replacement") ctx.logger.log(INFO, "Baking replacement for variant [${variantCtx.getUnbaked()::class.java.simpleName}] ${variantCtx.location} -> $replacement")
replacement.bake(variantCtx) replacement.bake(variantCtx)
} ?: variantCtx.getBaked() } ?: variantCtx.getBaked()
when(baked) { when(baked) {
is WrappedBakedModel -> it to baked is SpecialRenderModel -> it to baked
// just in case we replaced some variants in the list, but not others // just in case we replaced some variants in the list, but not others
// this should not realistically happen, this is just a best-effort fallback // this should not realistically happen, this is just a best-effort fallback
is BasicBakedModel -> it to WrappedMeshModel.converter( is SimpleBakedModel -> it to HalfBakedSimpleModelWrapper(baked)
state = null, unshade = false, noDiffuse = true, blendModeOverride = BlendMode.CUTOUT_MIPPED
).convert(baked)!!
else -> null else -> null
} }
} }
@@ -55,7 +52,7 @@ class WeightedUnbakedKey(
return WeightedModelWrapper(weightedSpecials, weightedSpecials[0].model) return WeightedModelWrapper(weightedSpecials, weightedSpecials[0].model)
} }
override fun toString() = "[WeightedUnbakedKey, ${replacements.size} replacements]" override fun toString() = "[SpecialRenderVariantList, ${replacements.size} replacements]"
companion object : HasLogger() companion object : HasLogger()
} }

View File

@@ -3,16 +3,16 @@ package mods.betterfoliage.resource.generated
import mods.betterfoliage.util.Atlas import mods.betterfoliage.util.Atlas
import mods.betterfoliage.util.bytes import mods.betterfoliage.util.bytes
import mods.betterfoliage.util.loadSprite import mods.betterfoliage.util.loadSprite
import net.minecraft.resource.ResourceManager import net.minecraft.resources.IResourceManager
import net.minecraft.util.Identifier import net.minecraft.util.ResourceLocation
import java.awt.image.BufferedImage import java.awt.image.BufferedImage
import java.lang.Math.max import java.lang.Math.max
data class CenteredSprite(val sprite: Identifier, val atlas: Atlas = Atlas.BLOCKS, val aspectHeight: Int = 1, val aspectWidth: Int = 1) { data class CenteredSprite(val sprite: ResourceLocation, val aspectHeight: Int = 1, val aspectWidth: Int = 1, val atlas: Atlas = Atlas.BLOCKS) {
fun register(pack: GeneratedBlockTexturePack) = pack.register(this, this::draw) fun register(pack: GeneratedTexturePack) = pack.register(atlas, this, this::draw)
fun draw(resourceManager: ResourceManager): ByteArray { fun draw(resourceManager: IResourceManager): ByteArray {
val baseTexture = resourceManager.loadSprite(atlas.file(sprite)) val baseTexture = resourceManager.loadSprite(atlas.file(sprite))
val frameWidth = baseTexture.width val frameWidth = baseTexture.width
@@ -20,8 +20,8 @@ data class CenteredSprite(val sprite: Identifier, val atlas: Atlas = Atlas.BLOCK
val frames = baseTexture.height / frameHeight val frames = baseTexture.height / frameHeight
val size = max(frameWidth, frameHeight) val size = max(frameWidth, frameHeight)
val result = BufferedImage(size, size * frames, BufferedImage.TYPE_4BYTE_ABGR) val resultTexture = BufferedImage(size, size * frames, BufferedImage.TYPE_4BYTE_ABGR)
val graphics = result.createGraphics() val graphics = resultTexture.createGraphics()
// iterate all frames // iterate all frames
for (frame in 0 until frames) { for (frame in 0 until frames) {
@@ -34,6 +34,6 @@ data class CenteredSprite(val sprite: Identifier, val atlas: Atlas = Atlas.BLOCK
graphics.drawImage(resultFrame, 0, size * frame, null) graphics.drawImage(resultFrame, 0, size * frame, null)
} }
return result.bytes return resultTexture.bytes
} }
} }

View File

@@ -1,95 +0,0 @@
package mods.betterfoliage.resource.generated
import mods.betterfoliage.util.Atlas
import mods.betterfoliage.util.HasLogger
import net.fabricmc.fabric.api.resource.IdentifiableResourceReloadListener
import net.minecraft.resource.*
import net.minecraft.resource.ResourceType.CLIENT_RESOURCES
import net.minecraft.resource.metadata.ResourceMetadataReader
import net.minecraft.text.LiteralText
import net.minecraft.util.Identifier
import net.minecraft.util.profiler.Profiler
import org.apache.logging.log4j.Level.INFO
import java.io.IOException
import java.util.*
import java.util.concurrent.CompletableFuture
import java.util.concurrent.ExecutionException
import java.util.concurrent.Executor
import java.util.function.Consumer
import java.util.function.Predicate
/**
* [ResourcePack] containing generated block textures
*
* @param[reloadId] Fabric ID of the pack
* @param[nameSpace] Resource namespace of pack
* @param[packName] Friendly name of pack
* @param[packDesc] Description of pack
* @param[logger] Logger to log to when generating resources
*/
class GeneratedBlockTexturePack(
val reloadId: Identifier, val nameSpace: String, val packName: String, val packDesc: String
) : HasLogger(), ResourcePack {
override fun getName() = reloadId.toString()
override fun getNamespaces(type: ResourceType) = setOf(nameSpace)
override fun <T : Any?> parseMetadata(deserializer: ResourceMetadataReader<T>) = null
override fun openRoot(id: String) = null
override fun findResources(type: ResourceType, path: String, prefix: String, maxDepth: Int, filter: Predicate<String>) = emptyList<Identifier>()
override fun close() {}
protected var manager: ResourceManager? = null
val identifiers: MutableMap<Any, Identifier> = Collections.synchronizedMap(mutableMapOf<Any, Identifier>())
val resources: MutableMap<Identifier, ByteArray> = Collections.synchronizedMap(mutableMapOf<Identifier, ByteArray>())
fun register(key: Any, func: (ResourceManager)->ByteArray): Identifier {
if (manager == null) throw IllegalStateException("Cannot register resources unless resource manager is being reloaded")
identifiers[key]?.let { return it }
val id = Identifier(nameSpace, UUID.randomUUID().toString())
val resource = func(manager!!)
identifiers[key] = id
resources[Atlas.BLOCKS.file(id)] = resource
detailLogger.log(INFO, "generated resource $key -> $id")
return id
}
override fun open(type: ResourceType, id: Identifier) =
if (type != CLIENT_RESOURCES) null else
try { resources[id]!!.inputStream() }
catch (e: ExecutionException) { (e.cause as? IOException)?.let { throw it } } // rethrow wrapped IOException if present
override fun contains(type: ResourceType, id: Identifier) =
type == CLIENT_RESOURCES && resources.containsKey(id)
/**
* Provider for this resource pack. Adds pack as always-on and hidden.
*/
val finder = object : ResourcePackProvider {
val packInfo = ResourcePackProfile(
packName, true, { this@GeneratedBlockTexturePack },
LiteralText(packName),
LiteralText(packDesc),
ResourcePackCompatibility.COMPATIBLE, ResourcePackProfile.InsertionPosition.TOP, true, ResourcePackSource.field_25347
)
override fun register(consumer: Consumer<ResourcePackProfile>, factory: ResourcePackProfile.Factory) {
consumer.accept(packInfo)
}
}
val reloader = object : IdentifiableResourceReloadListener {
override fun getFabricId() = reloadId
override fun reload(synchronizer: ResourceReloadListener.Synchronizer, manager: ResourceManager, prepareProfiler: Profiler, applyProfiler: Profiler, prepareExecutor: Executor, applyExecutor: Executor): CompletableFuture<Void> {
this@GeneratedBlockTexturePack.manager = manager
return synchronizer.whenPrepared(null).thenRun {
this@GeneratedBlockTexturePack.manager = null
identifiers.clear()
resources.clear()
}
}
}
}

View File

@@ -1,8 +1,13 @@
package mods.betterfoliage.resource.generated package mods.betterfoliage.resource.generated
import mods.betterfoliage.util.* import mods.betterfoliage.util.Atlas
import net.minecraft.resource.ResourceManager import mods.betterfoliage.util.blendRGB
import net.minecraft.util.Identifier import mods.betterfoliage.util.bytes
import mods.betterfoliage.util.get
import mods.betterfoliage.util.loadSprite
import mods.betterfoliage.util.set
import net.minecraft.resources.IResourceManager
import net.minecraft.util.ResourceLocation
import java.awt.image.BufferedImage import java.awt.image.BufferedImage
/** /**
@@ -11,13 +16,13 @@ import java.awt.image.BufferedImage
* *
* @param[domain] Resource domain of generator * @param[domain] Resource domain of generator
*/ */
data class GeneratedGrassSprite(val sprite: Identifier, val isSnowed: Boolean, val atlas: Atlas = Atlas.BLOCKS) { data class GeneratedGrass(val baseSprite: ResourceLocation, val isSnowed: Boolean, val atlas: Atlas = Atlas.BLOCKS) {
constructor(sprite: String, isSnowed: Boolean) : this(Identifier(sprite), isSnowed) constructor(sprite: String, isSnowed: Boolean) : this(ResourceLocation(sprite), isSnowed)
fun register(pack: GeneratedBlockTexturePack) = pack.register(this, this::draw) fun register(pack: GeneratedTexturePack) = pack.register(atlas, this, this::draw)
fun draw(resourceManager: ResourceManager): ByteArray { fun draw(resourceManager: IResourceManager): ByteArray {
val baseTexture = resourceManager.loadSprite(atlas.file(sprite)) val baseTexture = resourceManager.loadSprite(atlas.file(baseSprite))
val result = BufferedImage(baseTexture.width, baseTexture.height, BufferedImage.TYPE_4BYTE_ABGR) val result = BufferedImage(baseTexture.width, baseTexture.height, BufferedImage.TYPE_4BYTE_ABGR)
val graphics = result.createGraphics() val graphics = result.createGraphics()
@@ -41,7 +46,7 @@ data class GeneratedGrassSprite(val sprite: Identifier, val isSnowed: Boolean, v
// blend with white if snowed // blend with white if snowed
if (isSnowed) { if (isSnowed) {
for (x in 0..result.width - 1) for (y in 0..result.height - 1) { for (x in 0 until result.width) for (y in 0 until result.height) {
result[x, y] = blendRGB(result[x, y], 16777215, 2, 3) result[x, y] = blendRGB(result[x, y], 16777215, 2, 3)
} }
} }

View File

@@ -1,10 +1,16 @@
package mods.betterfoliage.resource.generated package mods.betterfoliage.resource.generated
import mods.betterfoliage.BetterFoliage import mods.betterfoliage.BetterFoliageMod
import mods.betterfoliage.util.* import mods.betterfoliage.util.Atlas
import net.minecraft.resource.Resource import mods.betterfoliage.util.bytes
import net.minecraft.resource.ResourceManager import mods.betterfoliage.util.get
import net.minecraft.util.Identifier import mods.betterfoliage.util.loadImage
import mods.betterfoliage.util.loadSprite
import mods.betterfoliage.util.resourceManager
import mods.betterfoliage.util.set
import net.minecraft.resources.IResource
import net.minecraft.resources.IResourceManager
import net.minecraft.util.ResourceLocation
import java.awt.image.BufferedImage import java.awt.image.BufferedImage
/** /**
@@ -15,12 +21,12 @@ import java.awt.image.BufferedImage
* *
* @param[domain] Resource domain of generator * @param[domain] Resource domain of generator
*/ */
data class GeneratedLeafSprite(val sprite: Identifier, val leafType: String, val atlas: Atlas = Atlas.BLOCKS) { data class GeneratedLeafSprite(val baseSprite: ResourceLocation, val leafType: String, val atlas: Atlas = Atlas.BLOCKS) {
fun register(pack: GeneratedBlockTexturePack) = pack.register(this, this::draw) fun register(pack: GeneratedTexturePack) = pack.register(atlas, this, this::draw)
fun draw(resourceManager: ResourceManager): ByteArray { fun draw(resourceManager: IResourceManager): ByteArray {
val baseTexture = resourceManager.loadSprite(atlas.file(sprite)) val baseTexture = resourceManager.loadSprite(atlas.file(baseSprite))
val size = baseTexture.width val size = baseTexture.width
val frames = baseTexture.height / size val frames = baseTexture.height / size
@@ -28,8 +34,8 @@ data class GeneratedLeafSprite(val sprite: Identifier, val leafType: String, val
val maskTexture = (getLeafMask(leafType, size * 2) ?: getLeafMask("default", size * 2))?.loadImage() val maskTexture = (getLeafMask(leafType, size * 2) ?: getLeafMask("default", size * 2))?.loadImage()
fun scale(i: Int) = i * maskTexture!!.width / (size * 2) fun scale(i: Int) = i * maskTexture!!.width / (size * 2)
val result = BufferedImage(size * 2, size * 2 * frames, BufferedImage.TYPE_4BYTE_ABGR) val leafTexture = BufferedImage(size * 2, size * 2 * frames, BufferedImage.TYPE_4BYTE_ABGR)
val graphics = result.createGraphics() val graphics = leafTexture.createGraphics()
// iterate all frames // iterate all frames
for (frame in 0 until frames) { for (frame in 0 until frames) {
@@ -57,7 +63,7 @@ data class GeneratedLeafSprite(val sprite: Identifier, val leafType: String, val
graphics.drawImage(leafFrame, 0, size * frame * 2, null) graphics.drawImage(leafFrame, 0, size * frame * 2, null)
} }
return result.bytes return leafTexture.bytes
} }
/** /**
@@ -67,7 +73,7 @@ data class GeneratedLeafSprite(val sprite: Identifier, val leafType: String, val
* @param[maxSize] Preferred mask size. * @param[maxSize] Preferred mask size.
*/ */
fun getLeafMask(type: String, maxSize: Int) = getMultisizeTexture(maxSize) { size -> fun getLeafMask(type: String, maxSize: Int) = getMultisizeTexture(maxSize) { size ->
Atlas.BLOCKS.file(Identifier(BetterFoliage.MOD_ID, "blocks/leafmask_${size}_${type}")) Atlas.BLOCKS.file(ResourceLocation(BetterFoliageMod.MOD_ID, "blocks/leafmask_${size}_${type}"))
} }
/** /**
@@ -77,10 +83,10 @@ data class GeneratedLeafSprite(val sprite: Identifier, val leafType: String, val
* @param[maskPath] Location of the texture of the given size * @param[maskPath] Location of the texture of the given size
* *
*/ */
fun getMultisizeTexture(maxSize: Int, maskPath: (Int)->Identifier): Resource? { fun getMultisizeTexture(maxSize: Int, maskPath: (Int)->ResourceLocation): IResource? {
var size = maxSize var size = maxSize
val sizes = mutableListOf<Int>() val sizes = mutableListOf<Int>()
while(size > 2) { sizes.add(size); size /= 2 } while(size > 2) { sizes.add(size); size /= 2 }
return sizes.findFirst { resourceManager[maskPath(it)] } return sizes.map { resourceManager[maskPath(it)] }.filterNotNull().firstOrNull()
} }
} }

View File

@@ -0,0 +1,69 @@
package mods.betterfoliage.resource.generated
import mods.betterfoliage.util.Atlas
import mods.betterfoliage.util.HasLogger
import net.minecraft.client.Minecraft
import net.minecraft.resources.*
import net.minecraft.resources.ResourcePackType.CLIENT_RESOURCES
import net.minecraft.resources.data.IMetadataSectionSerializer
import net.minecraft.util.ResourceLocation
import net.minecraft.util.text.StringTextComponent
import org.apache.logging.log4j.Level.INFO
import java.util.*
import java.util.function.Consumer
import java.util.function.Predicate
import java.util.function.Supplier
/**
* [IResourcePack] containing generated resources
*
* @param[name] Name of the resource pack
* @param[generators] List of resource generators
*/
class GeneratedTexturePack(
val nameSpace: String, val packName: String
) : HasLogger(), IResourcePack {
override fun getName() = packName
override fun getNamespaces(type: ResourcePackType) = setOf(nameSpace)
override fun <T : Any?> getMetadataSection(deserializer: IMetadataSectionSerializer<T>) = null
override fun getRootResource(id: String) = null
override fun getResources(type: ResourcePackType, namespace:String, path: String, maxDepth: Int, filter: Predicate<String>) = emptyList<ResourceLocation>()
override fun close() {}
protected var manager: IResourceManager = Minecraft.getInstance().resourceManager
val identifiers = Collections.synchronizedMap(mutableMapOf<Any, ResourceLocation>())
val resources = Collections.synchronizedMap(mutableMapOf<ResourceLocation, ByteArray>())
fun register(atlas: Atlas, key: Any, func: (IResourceManager)->ByteArray): ResourceLocation {
identifiers[key]?.let { return it }
val id = ResourceLocation(nameSpace, UUID.randomUUID().toString())
val fileName = atlas.file(id)
val resource = func(manager)
identifiers[key] = id
resources[fileName] = resource
detailLogger.log(INFO, "generated resource $key -> $fileName")
return id
}
override fun getResource(type: ResourcePackType, id: ResourceLocation) =
if (type != CLIENT_RESOURCES) null else resources[id]?.inputStream()
override fun hasResource(type: ResourcePackType, id: ResourceLocation) =
type == CLIENT_RESOURCES && resources.containsKey(id)
val finder = object : IPackFinder {
val packInfo = ResourcePackInfo(
packName, true, Supplier { this@GeneratedTexturePack },
StringTextComponent(packName),
StringTextComponent("Generated block textures resource pack"),
PackCompatibility.COMPATIBLE, ResourcePackInfo.Priority.TOP, true, IPackNameDecorator.DEFAULT, true
)
override fun loadPacks(p0: Consumer<ResourcePackInfo>, p1: ResourcePackInfo.IFactory) {
p0.accept(packInfo)
}
}
}

View File

@@ -17,6 +17,10 @@ interface Invalidator {
} }
} }
class SimpleInvalidator : Invalidator {
override val callbacks = mutableListOf<WeakReference<() -> Unit>>()
}
class LazyInvalidatable<V>(invalidator: Invalidator, val valueFactory: ()->V): ReadOnlyProperty<Any, V> { class LazyInvalidatable<V>(invalidator: Invalidator, val valueFactory: ()->V): ReadOnlyProperty<Any, V> {
init { invalidator.onInvalidate { value = null } } init { invalidator.onInvalidate { value = null } }
@@ -31,7 +35,7 @@ class LazyInvalidatable<V>(invalidator: Invalidator, val valueFactory: ()->V): R
} }
} }
class LazyMap<K, V>(val invalidator: Invalidator, val valueFactory: (K)->V) { open class LazyMapInvalidatable<K, V>(val invalidator: Invalidator, val valueFactory: (K)->V) {
init { invalidator.onInvalidate { values.clear() } } init { invalidator.onInvalidate { values.clear() } }
val values = mutableMapOf<K, V>() val values = mutableMapOf<K, V>()

View File

@@ -1,5 +1,6 @@
package mods.betterfoliage.util package mods.betterfoliage.util
import com.google.common.collect.ImmutableList
import java.util.* import java.util.*
/** /**
@@ -27,6 +28,8 @@ inline fun <T, C: Comparable<C>> Triple<T, T, T>.maxValueBy(func: (T)->C): C {
return result return result
} }
inline fun <reified T, reified R> Array<T>.mapArray(func: (T)->R) = Array<R>(size) { idx -> func(get(idx)) }
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")
inline fun <K, V> Map<K, V?>.filterValuesNotNull() = filterValues { it != null } as Map<K, V> inline fun <K, V> Map<K, V?>.filterValuesNotNull() = filterValues { it != null } as Map<K, V>
@@ -61,3 +64,8 @@ inline fun <T> MutableList<T>.exchange(idx1: Int, idx2: Int) {
/** Return a random element from the array using the provided random generator */ /** Return a random element from the array using the provided random generator */
inline operator fun <T> Array<T>.get(random: Random) = get(random.nextInt(Int.MAX_VALUE) % size) inline operator fun <T> Array<T>.get(random: Random) = get(random.nextInt(Int.MAX_VALUE) % size)
fun <T> Iterable<T>.toImmutableList() = ImmutableList.builder<T>().let { builder ->
forEach { builder.add(it) }
builder.build()
}

View File

@@ -1,6 +1,6 @@
package mods.betterfoliage.util package mods.betterfoliage.util
import net.minecraft.client.MinecraftClient import net.minecraft.client.Minecraft
import java.util.concurrent.CompletableFuture import java.util.concurrent.CompletableFuture
import java.util.concurrent.CompletionStage import java.util.concurrent.CompletionStage
import java.util.function.Consumer import java.util.function.Consumer
@@ -9,7 +9,7 @@ import java.util.function.Function
fun completedVoid() = CompletableFuture.completedFuture<Void>(null)!! fun completedVoid() = CompletableFuture.completedFuture<Void>(null)!!
fun <T, U> CompletionStage<T>.map(func: (T)->U) = thenApply(Function(func)).toCompletableFuture()!! fun <T, U> CompletionStage<T>.map(func: (T)->U) = thenApply(Function(func)).toCompletableFuture()!!
fun <T, U> CompletionStage<T>.mapAsync(func: (T)->U) = thenApplyAsync(Function(func), MinecraftClient.getInstance()).toCompletableFuture()!! fun <T, U> CompletionStage<T>.mapAsync(func: (T)->U) = thenApplyAsync(Function(func), Minecraft.getInstance()).toCompletableFuture()!!
fun <T> CompletionStage<T>.sink(func: (T)->Unit) = thenAccept(Consumer(func)).toCompletableFuture()!! fun <T> CompletionStage<T>.sink(func: (T)->Unit) = thenAccept(Consumer(func)).toCompletableFuture()!!
fun <T> CompletionStage<T>.sinkAsync(func: (T)->Unit) = thenAcceptAsync(Consumer(func), MinecraftClient.getInstance()).toCompletableFuture()!! fun <T> CompletionStage<T>.sinkAsync(func: (T)->Unit) = thenAcceptAsync(Consumer(func), Minecraft.getInstance()).toCompletableFuture()!!

View File

@@ -1,29 +1,44 @@
package mods.betterfoliage.util package mods.betterfoliage.util
import net.minecraft.client.util.math.Vector3f import net.minecraft.util.Direction
import net.minecraft.util.Direction.*
import net.minecraft.util.Direction.Axis.*
import net.minecraft.util.Direction.AxisDirection.NEGATIVE
import net.minecraft.util.Direction.AxisDirection.POSITIVE
import net.minecraft.util.math.BlockPos import net.minecraft.util.math.BlockPos
import net.minecraft.util.math.Direction import net.minecraft.util.math.vector.Quaternion
import net.minecraft.util.math.Direction.Axis.*
import net.minecraft.util.math.Direction.AxisDirection val EPSILON_ZERO = 0.05
import net.minecraft.util.math.Direction.AxisDirection.* val EPSILON_ONE = 0.95
import net.minecraft.util.math.Direction.*
import net.minecraft.util.math.Quaternion
// ================================ // ================================
// Axes and directions // Axes and directions
// ================================ // ================================
val axes = listOf(X, Y, Z) val axes = listOf(X, Y, Z)
val axisDirs = listOf(POSITIVE, NEGATIVE) val axisDirs = listOf(POSITIVE, NEGATIVE)
val Direction.dir: AxisDirection get() = axisDirection
val AxisDirection.sign: String get() = when(this) { POSITIVE -> "+"; NEGATIVE -> "-" }
val allDirections = Direction.values() val allDirections = Direction.values()
val horizontalDirections = listOf(NORTH, SOUTH, EAST, WEST) val horizontalDirections = listOf(NORTH, SOUTH, EAST, WEST)
val allDirOffsets = allDirections.map { Int3(it) } val allDirOffsets = allDirections.map { Int3(it) }
val Pair<Direction.Axis, AxisDirection>.face: Direction get() = when(this) { val Pair<Axis, AxisDirection>.face: Direction get() = when(this) {
X to POSITIVE -> EAST; X to NEGATIVE -> WEST X to POSITIVE -> EAST; X to NEGATIVE -> WEST;
Y to POSITIVE -> UP; Y to NEGATIVE -> DOWN Y to POSITIVE -> UP; Y to NEGATIVE -> DOWN;
Z to POSITIVE -> SOUTH; else -> NORTH Z to POSITIVE -> SOUTH; else -> NORTH;
} }
val Direction.perpendiculars: List<Direction> get() = val directionsAndNull = arrayOf(DOWN, UP, NORTH, SOUTH, WEST, EAST, null)
axes.filter { it != this.axis }.cross(axisDirs).map { it.face }
val Direction.perpendiculars: Array<Direction> get() =
axes.filter { it != this.axis }.flatMap { listOf((it to POSITIVE).face, (it to NEGATIVE).face) }.toTypedArray()
val perpendiculars: Array<Array<Direction>> = Direction.values().map { dir ->
axes.filter { it != dir.axis }
.flatMap { listOf(
(it to POSITIVE).face,
(it to NEGATIVE).face
) }.toTypedArray()
}.toTypedArray()
val Direction.offset: Int3 get() = allDirOffsets[ordinal] val Direction.offset: Int3 get() = allDirOffsets[ordinal]
/** Old ForgeDirection rotation matrix yanked from 1.7.10 */ /** Old ForgeDirection rotation matrix yanked from 1.7.10 */
@@ -40,15 +55,15 @@ val ROTATION_MATRIX: Array<IntArray> get() = arrayOf(
// Vectors // Vectors
// ================================ // ================================
operator fun Direction.times(scale: Double) = operator fun Direction.times(scale: Double) =
Double3(vector.x.toDouble() * scale, vector.y.toDouble() * scale, vector.z.toDouble() * scale) Double3(normal.x.toDouble() * scale, normal.y.toDouble() * scale, normal.z.toDouble() * scale)
val Direction.vec: Double3 get() = Double3(vector.x.toDouble(), vector.y.toDouble(), vector.z.toDouble()) val Direction.vec: Double3 get() = Double3(normal.x.toDouble(), normal.y.toDouble(), normal.z.toDouble())
operator fun BlockPos.plus(other: Int3) = BlockPos(x + other.x, y + other.y, z + other.z) operator fun BlockPos.plus(other: Int3) = BlockPos(x + other.x, y + other.y, z + other.z)
/** 3D vector of [Double]s. Offers both mutable operations, and immutable operations in operator notation. */ /** 3D vector of [Double]s. Offers both mutable operations, and immutable operations in operator notation. */
data class Double3(var x: Double, var y: Double, var z: Double) { data class Double3(var x: Double, var y: Double, var z: Double) {
constructor(x: Float, y: Float, z: Float) : this(x.toDouble(), y.toDouble(), z.toDouble()) constructor(x: Float, y: Float, z: Float) : this(x.toDouble(), y.toDouble(), z.toDouble())
constructor(dir: Direction) : this(dir.vector.x.toDouble(), dir.vector.y.toDouble(), dir.vector.z.toDouble()) constructor(dir: Direction) : this(dir.normal.x.toDouble(), dir.normal.y.toDouble(), dir.normal.z.toDouble())
companion object { companion object {
val zero: Double3 get() = Double3(0.0, 0.0, 0.0) val zero: Double3 get() = Double3(0.0, 0.0, 0.0)
fun weight(v1: Double3, weight1: Double, v2: Double3, weight2: Double) = fun weight(v1: Double3, weight1: Double, v2: Double3, weight2: Double) =
@@ -73,9 +88,9 @@ data class Double3(var x: Double, var y: Double, var z: Double) {
/** Rotate vector by the given [Quaternion] */ /** Rotate vector by the given [Quaternion] */
fun rotate(quat: Quaternion) = fun rotate(quat: Quaternion) =
quat.copy() quat.copy()
.apply { hamiltonProduct(Quaternion(this@Double3.x.toFloat(), this@Double3.y.toFloat(), this@Double3.z.toFloat(), 0.0F)) } .apply { mul(Quaternion(x.toFloat(), y.toFloat(), z.toFloat(), 0.0F)) }
.apply { hamiltonProduct(quat.copy().apply(Quaternion::conjugate)) } .apply { mul(quat.copy().apply(Quaternion::conj)) }
.let { Double3(it.x, it.y, it.z) } .let { Double3(it.i().toDouble(), it.j().toDouble(), it.k().toDouble()) }
// mutable operations // mutable operations
fun setTo(other: Double3): Double3 { x = other.x; y = other.y; z = other.z; return this } fun setTo(other: Double3): Double3 { x = other.x; y = other.y; z = other.z; return this }
@@ -101,16 +116,15 @@ data class Double3(var x: Double, var y: Double, var z: Double) {
val length: Double get() = Math.sqrt(x * x + y * y + z * z) val length: Double get() = Math.sqrt(x * x + y * y + z * z)
val normalize: Double3 get() = (1.0 / length).let { Double3(x * it, y * it, z * it) } val normalize: Double3 get() = (1.0 / length).let { Double3(x * it, y * it, z * it) }
val nearestCardinal: Direction get() = nearestAngle(this, allDirections.asIterable()) { it.vec }.first val nearestCardinal: Direction get() = nearestAngle(this, allDirections.asIterable()) { it.vec }.first
val asVec3f: Vector3f get() = Vector3f(x.toFloat(), y.toFloat(), z.toFloat())
} }
/** 3D vector of [Int]s. Offers both mutable operations, and immutable operations in operator notation. */ /** 3D vector of [Int]s. Offers both mutable operations, and immutable operations in operator notation. */
data class Int3(var x: Int, var y: Int, var z: Int) { data class Int3(var x: Int, var y: Int, var z: Int) {
constructor(dir: Direction) : this(dir.vector.x, dir.vector.y, dir.vector.z) constructor(dir: Direction) : this(dir.normal.x, dir.normal.y, dir.normal.z)
constructor(offset: Pair<Int, Direction>) : this( constructor(offset: Pair<Int, Direction>) : this(
offset.first * offset.second.vector.x, offset.first * offset.second.normal.x,
offset.first * offset.second.vector.y, offset.first * offset.second.normal.y,
offset.first * offset.second.vector.z offset.first * offset.second.normal.z
) )
companion object { companion object {
val zero = Int3(0, 0, 0) val zero = Int3(0, 0, 0)
@@ -119,9 +133,9 @@ data class Int3(var x: Int, var y: Int, var z: Int) {
// immutable operations // immutable operations
operator fun plus(other: Int3) = Int3(x + other.x, y + other.y, z + other.z) operator fun plus(other: Int3) = Int3(x + other.x, y + other.y, z + other.z)
operator fun plus(other: Pair<Int, Direction>) = Int3( operator fun plus(other: Pair<Int, Direction>) = Int3(
x + other.first * other.second.vector.x, x + other.first * other.second.normal.x,
y + other.first * other.second.vector.y, y + other.first * other.second.normal.y,
z + other.first * other.second.vector.z z + other.first * other.second.normal.z
) )
operator fun unaryMinus() = Int3(-x, -y, -z) operator fun unaryMinus() = Int3(-x, -y, -z)
operator fun minus(other: Int3) = Int3(x - other.x, y - other.y, z - other.z) operator fun minus(other: Int3) = Int3(x - other.x, y - other.y, z - other.z)
@@ -170,8 +184,7 @@ class Rotation(val forward: Array<Direction>, val reverse: Array<Direction>) {
Array(6) { idx -> other.reverse[reverse[idx].ordinal] } Array(6) { idx -> other.reverse[reverse[idx].ordinal] }
) )
operator fun unaryMinus() = Rotation(reverse, forward) operator fun unaryMinus() = Rotation(reverse, forward)
operator fun times(num: Int) = when(num % 4) { 1 -> this; 2 -> this + this; 3 -> -this; else -> identity operator fun times(num: Int) = when(num % 4) { 1 -> this; 2 -> this + this; 3 -> -this; else -> identity }
}
inline fun rotatedComponent(dir: Direction, x: Int, y: Int, z: Int) = inline fun rotatedComponent(dir: Direction, x: Int, y: Int, z: Int) =
when(reverse[dir.ordinal]) { EAST -> x; WEST -> -x; UP -> y; DOWN -> -y; SOUTH -> z; NORTH -> -z; else -> 0 } when(reverse[dir.ordinal]) { EAST -> x; WEST -> -x; UP -> y; DOWN -> -y; SOUTH -> z; NORTH -> -z; else -> 0 }
@@ -191,6 +204,7 @@ class Rotation(val forward: Array<Direction>, val reverse: Array<Direction>) {
rot90[NORTH.ordinal] rot90[NORTH.ordinal]
) )
} }
} }
// ================================ // ================================
@@ -200,14 +214,8 @@ class Rotation(val forward: Array<Direction>, val reverse: Array<Direction>) {
inline operator fun <reified T> Array<T>.get(face: Direction): T = get(face.ordinal) inline operator fun <reified T> Array<T>.get(face: Direction): T = get(face.ordinal)
data class BoxFace(val top: Direction, val left: Direction) { data class BoxFace(val top: Direction, val left: Direction) {
val bottom get() = top.opposite
val right get() = left.opposite
val allCorners = listOf(top to left, top to left.opposite, top.opposite to left, top.opposite to left.opposite)
val tl get() = top to left val allCorners = listOf(top to left, top to left.opposite, top.opposite to left, top.opposite to left.opposite)
val tr get() = top to right
val bl get() = bottom to left
val br get() = bottom to right
} }
val boxFaces = allDirections.map { when(it) { val boxFaces = allDirections.map { when(it) {
DOWN -> BoxFace(SOUTH, WEST) DOWN -> BoxFace(SOUTH, WEST)

View File

@@ -0,0 +1,25 @@
@file:JvmName("Utils")
package mods.betterfoliage.util
import net.minecraft.util.text.StringTextComponent
import net.minecraft.util.text.Style
import net.minecraft.util.text.TextFormatting
import net.minecraft.util.text.TextFormatting.AQUA
import net.minecraft.util.text.TextFormatting.GRAY
//fun stripTooltipDefaultText(tooltip: MutableList<String>) {
// var defaultRows = false
// val iter = tooltip.iterator()
// while (iter.hasNext()) {
// if (iter.next().startsWith(AQUA.toString())) defaultRows = true
// if (defaultRows) iter.remove()
// }
//}
//
//fun textComponent(msg: String, color: TextFormatting = GRAY): StringTextComponent {
// val style = Style().apply { this.color = color }
// return StringTextComponent(msg).apply { this.style = style }
//}
val styleGray = Style.EMPTY.applyFormats(GRAY)
fun String.asText() = StringTextComponent(this).setStyle(styleGray)

View File

@@ -1,13 +1,12 @@
@file:Suppress("NOTHING_TO_INLINE") @file:Suppress("NOTHING_TO_INLINE")
package mods.betterfoliage.util package mods.betterfoliage.util
import mods.betterfoliage.BetterFoliage import mods.betterfoliage.BetterFoliageMod
import net.minecraft.text.LiteralText import net.minecraft.util.ResourceLocation
import net.minecraft.text.Style
import net.minecraft.util.Formatting
import net.minecraft.util.Identifier
import net.minecraft.util.math.BlockPos import net.minecraft.util.math.BlockPos
import net.minecraft.world.World import net.minecraft.world.World
import org.apache.logging.log4j.Level
import org.apache.logging.log4j.Logger
import java.lang.Math.* import java.lang.Math.*
import kotlin.reflect.KProperty import kotlin.reflect.KProperty
@@ -18,8 +17,8 @@ inline fun String.stripStart(str: String, ignoreCase: Boolean = true) = if (star
inline fun String.stripEnd(str: String, ignoreCase: Boolean = true) = if (endsWith(str, ignoreCase)) substring(0, length - str.length) else this inline fun String.stripEnd(str: String, ignoreCase: Boolean = true) = if (endsWith(str, ignoreCase)) substring(0, length - str.length) else this
/** Strip the given prefix off the start of the resource path, if present */ /** Strip the given prefix off the start of the resource path, if present */
inline fun Identifier.stripStart(str: String) = Identifier(namespace, path.stripStart(str)) inline fun ResourceLocation.stripStart(str: String) = ResourceLocation(namespace, path.stripStart(str))
inline fun Identifier.stripEnd(str: String) = Identifier(namespace, path.stripEnd(str)) inline fun ResourceLocation.stripEnd(str: String) = ResourceLocation(namespace, path.stripEnd(str))
/** /**
* Property-level delegate backed by a [ThreadLocal]. * Property-level delegate backed by a [ThreadLocal].
@@ -51,6 +50,12 @@ fun nextPowerOf2(x: Int): Int {
return 1 shl (if (x == 0) 0 else 32 - Integer.numberOfLeadingZeros(x - 1)) return 1 shl (if (x == 0) 0 else 32 - Integer.numberOfLeadingZeros(x - 1))
} }
@Suppress("LeakingThis")
abstract class HasLogger {
val logger = BetterFoliageMod.logger(this)
val detailLogger = BetterFoliageMod.detailLogger(this)
}
/** /**
* Check if the Chunk containing the given [BlockPos] is loaded. * Check if the Chunk containing the given [BlockPos] is loaded.
* Works for both [World] and [ChunkCache] (vanilla and OptiFine) instances. * Works for both [World] and [ChunkCache] (vanilla and OptiFine) instances.
@@ -62,13 +67,7 @@ fun nextPowerOf2(x: Int): Int {
// else -> false // else -> false
//} //}
@Suppress("LeakingThis") //fun textComponent(msg: String, color: Formatting = Formatting.GRAY): LiteralText {
abstract class HasLogger { // val style = Style().apply { this.color = color }
val logger = BetterFoliage.logger(this) // return LiteralText(msg).apply { this.style = style }
val detailLogger = BetterFoliage.detailLogger(this) //}
}
fun textComponent(msg: String, color: Formatting = Formatting.GRAY): LiteralText {
val style = Style.EMPTY.withColor(color)
return LiteralText(msg).apply { this.style = style }
}

View File

@@ -20,4 +20,4 @@ fun semiRandom(x: Int, y: Int, z: Int, seed: Int): Int {
return value shr 4 return value shr 4
} }
//fun BlockPos.semiRandom(seed: Int) = semiRandom(x, y, z, seed) fun BlockPos.semiRandom(seed: Int) = semiRandom(x, y, z, seed)

View File

@@ -1,114 +1,179 @@
@file:JvmName("Reflection")
package mods.betterfoliage.util package mods.betterfoliage.util
import java.lang.reflect.Field import java.lang.reflect.Field
import java.lang.reflect.Method import java.lang.reflect.Method
import net.fabricmc.loader.api.FabricLoader import mods.betterfoliage.util.Namespace.*
import net.fabricmc.mappings.EntryTriple
import org.apache.logging.log4j.Level
import org.apache.logging.log4j.LogManager
import java.lang.Exception
/** Get a Java class with the given name. */ /** Get a Java class with the given name. */
fun getJavaClass(name: String) = tryDefault(null) { Class.forName(name) } fun getJavaClass(name: String) = tryDefault(null) { Class.forName(name) }
/** Get the field with the given name and type using reflection. */ /** Get the field with the given name and type using reflection. */
fun <T> Any.reflectField(name: String) = getFieldRecursive(this::class.java, name).let { inline fun <reified T> Any.reflectField(field: String): T? =
it.isAccessible = true tryDefault(null) { this.javaClass.getDeclaredField(field) }?.let {
it.get(this) as T it.isAccessible = true
it.get(this) as T
}
/** Get the static field with the given name and type using reflection. */
inline fun <reified T> Class<*>.reflectStaticField(field: String): T? =
tryDefault(null) { this.getDeclaredField(field) }?.let {
it.isAccessible = true
it.get(null) as T
}
/**
* Get all nested _object_s of this _object_ with reflection.
*
* @return [Pair]s of (name, instance)
*/
val Any.reflectNestedObjects: List<Pair<String, Any>> get() = this.javaClass.declaredClasses.map {
tryDefault(null) { it.name.split("$")[1] to it.getField("INSTANCE").get(null) }
}.filterNotNull()
/**
* Get all fields of this instance that match (or subclass) any of the given classes.
*
* @param[types] classes to look for
* @return [Pair]s of (field name, instance)
*/
fun Any.reflectFieldsOfType(vararg types: Class<*>) = this.javaClass.declaredFields
.filter { field -> types.any { it.isAssignableFrom(field.type) } }
.map { field -> field.name to field.let { it.isAccessible = true; it.get(this) } }
.filterNotNull()
fun <T> Any.reflectFields(type: Class<T>) = this.javaClass.declaredFields
.filter { field -> type.isAssignableFrom(field.type) }
.map { field -> field.name to field.let { it.isAccessible = true; it.get(this) as T } }
fun <T> Any.reflectDelegates(type: Class<T>) = this.javaClass.declaredFields
.filter { field -> type.isAssignableFrom(field.type) && field.name.contains("$") }
.map { field -> field.name.split("$")[0] to field.let { it.isAccessible = true; it.get(this) } as T }
enum class Namespace { MCP, SRG }
abstract class Resolvable<T> {
abstract fun resolve(): T?
val element: T? by lazy { resolve() }
} }
/** Get the field on the class with the given name. /** Return true if all given elements are found. */
* Does not handle overloads, suitable only for unique field names (like Yarn intermediate names) fun allAvailable(vararg codeElement: Resolvable<*>) = codeElement.all { it.element != null }
* */
fun getFieldRecursive(cls: Class<*>, name: String): Field = try { /**
cls.getDeclaredField(name) * Reference to a class.
} catch (e: NoSuchFieldException) { *
cls.superclass?.let { getFieldRecursive(it, name) } ?: throw IllegalArgumentException(e) * @param[name] MCP name of the class
*/
open class ClassRef<T: Any?>(val name: String) : Resolvable<Class<T>>() {
companion object {
val int = ClassRefPrimitive("I", Int::class.java)
val long = ClassRefPrimitive("J", Long::class.java)
val float = ClassRefPrimitive("F", Float::class.java)
val boolean = ClassRefPrimitive("Z", Boolean::class.java)
val void = ClassRefPrimitive("V", Void::class.java)
}
open fun asmDescriptor(namespace: Namespace) = "L${name.replace(".", "/")};"
override fun resolve() = getJavaClass(name) as Class<T>?
fun isInstance(obj: Any) = element?.isInstance(obj) ?: false
} }
/** Get the method on the class with the given name. /**
* Does not handle overloads, suitable only for unique field names (like Yarn intermediate names) * Reference to a primitive type.
* */ *
fun getMethodRecursive(cls: Class<*>, name: String): Method = try { * @param[name] ASM descriptor of this primitive type
cls.declaredMethods.find { it.name == name } ?: throw NoSuchMethodException() * @param[clazz] class of this primitive type
} catch (e: NoSuchMethodException) { */
cls.superclass?.let { getMethodRecursive(it, name) } ?: throw IllegalArgumentException(e) class ClassRefPrimitive<T>(name: String, val clazz: Class<T>?) : ClassRef<T>(name) {
override fun asmDescriptor(namespace: Namespace) = name
override fun resolve() = clazz
} }
fun getAllMethods(className: String, methodName: String): List<Method> = /**
tryDefault(null) { Class.forName(className) }?.declaredMethods?.filter { it.name == methodName } * Reference to a method.
?: emptyList() *
* @param[parentClass] reference to the class containing the method
* @param[mcpName] MCP name of the method
* @param[srgName] SRG name of the method
* @param[returnType] reference to the return type
* @param[returnType] references to the argument types
*/
class MethodRef<T: Any?>(val parentClass: ClassRef<*>,
val mcpName: String,
val srgName: String,
val returnType: ClassRef<T>,
vararg val argTypes: ClassRef<*>
) : Resolvable<Method>() {
constructor(parentClass: ClassRef<*>, mcpName: String, returnType: ClassRef<T>, vararg argTypes: ClassRef<*>) :
this(parentClass, mcpName, mcpName, returnType, *argTypes)
interface FieldRef<T> { fun name(namespace: Namespace) = when(namespace) { SRG -> srgName; MCP -> mcpName }
val field: Field? fun asmDescriptor(namespace: Namespace) = "(${argTypes.map { it.asmDescriptor(namespace) }.fold(""){ s1, s2 -> s1 + s2 } })${returnType.asmDescriptor(namespace)}"
override fun resolve(): Method? =
if (parentClass.element == null || argTypes.any { it.element == null }) null
else {
val args = argTypes.map { it.element!! }.toTypedArray()
listOf(srgName, mcpName).map { tryDefault(null) {
parentClass.element!!.getDeclaredMethod(it, *args)
}}.filterNotNull().firstOrNull()
?.apply { isAccessible = true }
}
/** Invoke this method using reflection. */
operator fun invoke(receiver: Any, vararg args: Any?) = element?.invoke(receiver, *args) as T
/** Invoke this static method using reflection. */
fun invokeStatic(vararg args: Any) = element?.invoke(null, *args)
}
/**
* Reference to a field.
*
* @param[parentClass] reference to the class containing the field
* @param[mcpName] MCP name of the field
* @param[srgName] SRG name of the field
* @param[type] reference to the field type
*/
class FieldRef<T>(val parentClass: ClassRef<*>,
val mcpName: String,
val srgName: String,
val type: ClassRef<T>
) : Resolvable<Field>() {
constructor(parentClass: ClassRef<*>, mcpName: String, type: ClassRef<T>) : this(parentClass, mcpName, mcpName, type)
fun name(namespace: Namespace) = when(namespace) { SRG -> srgName; MCP -> mcpName }
fun asmDescriptor(namespace: Namespace) = type.asmDescriptor(namespace)
override fun resolve(): Field? =
if (parentClass.element == null) null
else {
listOf(srgName, mcpName).mapNotNull { tryDefault(null) {
parentClass.element!!.getDeclaredField(it)
} }.firstOrNull()
?.apply{ isAccessible = true }
}
/** Get this field using reflection. */ /** Get this field using reflection. */
operator fun get(receiver: Any?) = field?.get(receiver) as T? operator fun get(receiver: Any?) = element?.get(receiver) as T
/** Get this static field using reflection. */ /** Get this static field using reflection. */
fun getStatic() = get(null) fun getStatic() = get(null)
/** Get this field using reflection. */ fun set(receiver: Any?, obj: Any?) { element?.set(receiver, obj) }
fun set(receiver: Any?, obj: Any?) { field?.set(receiver, obj) }
} }
interface MethodRef<T> {
val method: Method?
/** Invoke this method using reflection. */
operator fun invoke(receiver: Any, vararg args: Any?) = method?.invoke(receiver, *args) as T
/** Invoke this static method using reflection. */
fun invokeStatic(vararg args: Any) = method?.invoke(null, *args)
}
const val INTERMEDIARY = "intermediary"
object YarnHelper {
val logger = LogManager.getLogger()
val resolver = FabricLoader.getInstance().mappingResolver
fun <T> requiredField(className: String, fieldName: String, descriptor: String) = Field<T>(false, className, fieldName, descriptor)
fun <T> requiredMethod(className: String, methodName: String, descriptor: String) = Method<T>(false, className, methodName, descriptor)
class Field<T>(val optional: Boolean, val className: String, val fieldName: String, descriptor: String) : FieldRef<T> {
override val field = FabricLoader.getInstance().mappingResolver.let { resolver ->
try {
val classMapped = resolver.mapClassName(INTERMEDIARY, className)
val fieldMapped = resolver.mapFieldName(INTERMEDIARY, className, fieldName, descriptor)
Class.forName(classMapped).let { cls -> getFieldRecursive(cls, fieldMapped).apply { isAccessible = true } }
} catch (e: Exception) {
logger.log(
if (optional) Level.DEBUG else Level.ERROR,
"Could not resolve field $className.$fieldName ( $descriptor ): ${e.message}"
)
if (optional) null else throw e
}
}
}
class Method<T>(val optional: Boolean, val className: String, val methodName: String, descriptor: String) : MethodRef<T> {
override val method = FabricLoader.getInstance().mappingResolver.let { resolver ->
try {
val classMapped = resolver.mapClassName(INTERMEDIARY, className)
val methodMapped = resolver.mapMethodName(INTERMEDIARY, className, methodName, descriptor)
Class.forName(classMapped).let { cls -> getMethodRecursive(cls, methodMapped).apply { isAccessible = true } }
} catch (e: Exception) {
logger.log(
if (optional) Level.DEBUG else Level.ERROR,
"Could not resolve field $className.$methodName ( $descriptor ): ${e.message}"
)
if (optional) null else throw e
}
}
}
}
fun Any.isInstance(cls: ClassRef<*>) = cls.isInstance(this)
interface ReflectionCallable<T> { interface ReflectionCallable<T> {
operator fun invoke(vararg args: Any): T operator fun invoke(vararg args: Any): T
} }
inline operator fun <reified T> Any.get(field: FieldRef<T>) = field.get(this) inline operator fun <reified T> Any.get(field: FieldRef<T>) = field.get(this)
inline operator fun <reified T> Any.set(field: FieldRef<T>, value: T) = field.set(this, value) inline operator fun <reified T> Any.set(field: FieldRef<T>, value: T) = field.set(this, value)
inline operator fun <T> Any.get(methodRef: MethodRef<T>) = object : ReflectionCallable<T> { inline operator fun <T> Any.get(methodRef: MethodRef<T>) = object : ReflectionCallable<T> {
override fun invoke(vararg args: Any) = methodRef.invoke(this@get, *args) override fun invoke(vararg args: Any) = methodRef.invoke(this@get, args)
} }

View File

@@ -1,30 +1,30 @@
package mods.betterfoliage.util package mods.betterfoliage.util
import net.minecraft.client.MinecraftClient import net.minecraft.client.Minecraft
import net.minecraft.resource.ReloadableResourceManager import net.minecraft.resources.IReloadableResourceManager
import net.minecraft.resource.Resource import net.minecraft.resources.IResource
import net.minecraft.resource.ResourceManager import net.minecraft.resources.IResourceManager
import net.minecraft.util.Identifier import net.minecraft.util.ResourceLocation
/** Concise getter for the Minecraft resource manager. */ /** Concise getter for the Minecraft resource manager. */
val resourceManager: ReloadableResourceManager get() = val resourceManager: IReloadableResourceManager
MinecraftClient.getInstance().resourceManager as ReloadableResourceManager get() = Minecraft.getInstance().resourceManager as IReloadableResourceManager
/** Append a string to the [ResourceLocation]'s path. */ /** Append a string to the [ResourceLocation]'s path. */
operator fun Identifier.plus(str: String) = Identifier(namespace, path + str) operator fun ResourceLocation.plus(str: String) = ResourceLocation(namespace, path + str)
/** Prepend a string to the [ResourceLocation]'s path. */
fun ResourceLocation.prependLocation(basePath: String) =
ResourceLocation(namespace, basePath.stripEnd("/").let { "$it/$path" })
/** Index operator to get a resource. */ /** Index operator to get a resource. */
operator fun ResourceManager.get(domain: String, path: String): Resource? = get(Identifier(domain, path)) operator fun IResourceManager.get(domain: String, path: String): IResource? = get(ResourceLocation(domain, path))
/** Index operator to get a resource. */ /** Index operator to get a resource. */
operator fun ResourceManager.get(location: Identifier): Resource? = tryDefault(null) { getResource(location) } operator fun IResourceManager.get(location: ResourceLocation): IResource? = tryDefault(null) { getResource(location) }
/** Get the lines of a text resource. */ /** Get the lines of a text resource. */
fun Resource.getLines(): List<String> { fun IResource.getLines(): List<String> {
val result = arrayListOf<String>() val result = arrayListOf<String>()
inputStream.bufferedReader().useLines { it.forEach { result.add(it) } } inputStream.bufferedReader().useLines { it.forEach { result.add(it) } }
return result return result
} }

View File

@@ -2,13 +2,12 @@ package mods.betterfoliage.util
import mods.betterfoliage.model.Color import mods.betterfoliage.model.Color
import mods.betterfoliage.model.HSB import mods.betterfoliage.model.HSB
import net.minecraft.client.MinecraftClient import net.minecraft.client.Minecraft
import net.minecraft.client.texture.NativeImage import net.minecraft.client.renderer.texture.AtlasTexture
import net.minecraft.client.texture.Sprite import net.minecraft.client.renderer.texture.TextureAtlasSprite
import net.minecraft.client.texture.SpriteAtlasTexture import net.minecraft.resources.IResource
import net.minecraft.resource.Resource import net.minecraft.resources.IResourceManager
import net.minecraft.resource.ResourceManager import net.minecraft.util.ResourceLocation
import net.minecraft.util.Identifier
import org.apache.logging.log4j.Level import org.apache.logging.log4j.Level
import org.apache.logging.log4j.Logger import org.apache.logging.log4j.Logger
import java.awt.image.BufferedImage import java.awt.image.BufferedImage
@@ -16,30 +15,36 @@ import java.io.ByteArrayOutputStream
import java.io.IOException import java.io.IOException
import javax.imageio.ImageIO import javax.imageio.ImageIO
import kotlin.math.atan2 import kotlin.math.atan2
import kotlin.math.cos
import kotlin.math.sin
enum class Atlas(val resourceId: Identifier) { enum class Atlas(val resourceId: ResourceLocation) {
BLOCKS(SpriteAtlasTexture.BLOCK_ATLAS_TEXTURE), BLOCKS(AtlasTexture.LOCATION_BLOCKS),
PARTICLES(SpriteAtlasTexture.PARTICLE_ATLAS_TEXTURE); PARTICLES(AtlasTexture.LOCATION_PARTICLES);
/** Get the fully-qualified resource name for sprites belonging to this atlas */ /** Get the fully-qualified resource name for sprites belonging to this atlas */
fun file(resource: Identifier) = Identifier(resource.namespace, "textures/${resource.path}.png") fun file(resource: ResourceLocation) = ResourceLocation(resource.namespace, "textures/${resource.path}.png")
/** Reference to the atlas itself */ /** Reference to the atlas itself */
private val atlas: SpriteAtlasTexture get() = MinecraftClient.getInstance().textureManager.getTexture(resourceId) as SpriteAtlasTexture private val atlas: AtlasTexture get() = Minecraft.getInstance().textureManager.getTexture(resourceId) as AtlasTexture
/** Get a sprite from this atlas */ /** Get a sprite from this atlas */
operator fun get(location: Identifier) = atlas.getSprite(location) operator fun get(location: ResourceLocation) = atlas.getSprite(location)
} }
operator fun SpriteAtlasTexture.get(res: Identifier): Sprite? = getSprite(res) //val Spr.atlas: Atlas get() = Atlas.values().find { it.resourceId == atlasLocation } ?: Atlas.BLOCKS
operator fun SpriteAtlasTexture.get(name: String): Sprite? = getSprite(Identifier(name))
fun ResourceManager.loadSprite(id: Identifier) = this.get(id)?.loadImage() ?: throw IOException("Cannot load resource $id") inline operator fun AtlasTexture.get(res: ResourceLocation): TextureAtlasSprite? = this.getSprite(res)
inline operator fun AtlasTexture.get(name: String): TextureAtlasSprite? = get(ResourceLocation(name))
fun Resource.loadImage(): BufferedImage? = ImageIO.read(this.inputStream) fun IResourceManager.loadSprite(id: ResourceLocation) =
this.get(id)?.loadImage() ?: throw IOException("Cannot load resource $id")
fun IResource.loadImage(): BufferedImage? = ImageIO.read(this.inputStream)
/** Index operator to get the RGB value of a pixel. */ /** Index operator to get the RGB value of a pixel. */
operator fun BufferedImage.get(x: Int, y: Int) = this.getRGB(x, y) operator fun BufferedImage.get(x: Int, y: Int) = this.getRGB(x, y)
/** Index operator to set the RGB value of a pixel. */ /** Index operator to set the RGB value of a pixel. */
operator fun BufferedImage.set(x: Int, y: Int, value: Int) = this.setRGB(x, y, value) operator fun BufferedImage.set(x: Int, y: Int, value: Int) = this.setRGB(x, y, value)
@@ -47,14 +52,12 @@ val BufferedImage.bytes: ByteArray get() =
ByteArrayOutputStream().let { ImageIO.write(this, "PNG", it); it.toByteArray() } ByteArrayOutputStream().let { ImageIO.write(this, "PNG", it); it.toByteArray() }
/** /**
* Calculate the average color of an image. * Calculate the average color of a texture.
* *
* Only non-transparent pixels are considered. Averages are taken in the HSB color space (note: Hue is a circular average), * Only non-transparent pixels are considered. Averages are taken in the HSB color space (note: Hue is a circular average),
* and the result transformed back to the RGB color space. * and the result transformed back to the RGB color space.
*/ */
val Sprite_images = YarnHelper.requiredField<Array<NativeImage>>("net.minecraft.class_1058", "field_5262", "[Lnet/minecraft/class_1011;") val TextureAtlasSprite.averageColor: HSB get() {
val Sprite.averageColor: HSB get() {
var numOpaque = 0 var numOpaque = 0
var sumHueX = 0.0 var sumHueX = 0.0
var sumHueY = 0.0 var sumHueY = 0.0
@@ -62,13 +65,13 @@ val Sprite.averageColor: HSB get() {
var sumBrightness = 0.0f var sumBrightness = 0.0f
for (x in 0 until width) for (x in 0 until width)
for (y in 0 until height) { for (y in 0 until height) {
val pixel = this[Sprite_images]!![0].getPixelColor(x, y) val pixel = getPixelRGBA(0, x, y)
val alpha = (pixel shr 24) and 255 val alpha = (pixel shr 24) and 255
val hsb = HSB.fromColor(pixel) val hsb = HSB.fromColorRGBA(pixel)
if (alpha == 255) { if (alpha == 255) {
numOpaque++ numOpaque++
sumHueX += Math.cos((hsb.hue.toDouble() - 0.5) * PI2) sumHueX += cos((hsb.hue.toDouble() - 0.5) * PI2)
sumHueY += Math.sin((hsb.hue.toDouble() - 0.5) * PI2) sumHueY += sin((hsb.hue.toDouble() - 0.5) * PI2)
sumSaturation += hsb.saturation sumSaturation += hsb.saturation
sumBrightness += hsb.brightness sumBrightness += hsb.brightness
} }

View File

@@ -1,102 +0,0 @@
package mods.betterfoliage.util
import net.minecraft.util.math.Direction
import net.minecraft.util.math.Direction.*
fun Pair<Direction, Direction>.ccwWinding() = arrayOf(first to second, first.opposite to second, first.opposite to second.opposite, first to second.opposite)
typealias BoxCorner = Triple<Direction, Direction, Direction>
fun BoxCorner.equalsUnordered(other: BoxCorner) = contains(other.first) && contains(other.second) && contains(other.third)
fun BoxCorner.contains(dir: Direction) = first == dir || second == dir || third == dir
fun Array<BoxCorner>.findIdx(corner: BoxCorner): Int? {
forEachIndexed { idx, test -> if (test.contains(corner.first) && test.contains(corner.second) && test.contains(corner.third)) return idx }
return null
}
fun Array<BoxCorner>.findIdx(predicate: (BoxCorner)->Boolean): Int? {
forEachIndexed { idx, test -> if (predicate(test)) return idx }
return null
}
/**
* Decribes the winding order for vanilla AO data.
* 1st index: light face ordinal
* 2nd index: index in AO data array
* value: pair of [Direction]s that along with light face fully describe the corner
* the AO data belongs to
*/
val aoWinding = allDirections.map { when(it) {
DOWN -> (SOUTH to WEST).ccwWinding()
UP -> (SOUTH to EAST).ccwWinding()
NORTH -> (WEST to UP).ccwWinding()
SOUTH -> (UP to WEST).ccwWinding()
WEST -> (SOUTH to UP).ccwWinding()
EAST -> (SOUTH to DOWN).ccwWinding()
} }.toTypedArray()
/**
* Indexing for undirected box corners (component order does not matter).
* Array contains [Direction] triplets fully defining the corner.
*/
val cornersUndir = Array(8) { idx -> Triple(
if (idx and 1 != 0) EAST else WEST,
if (idx and 2 != 0) UP else DOWN,
if (idx and 4 != 0) SOUTH else NORTH
) }
/**
* Reverse lookup for [cornersUndir]. Index 3 times with the corner's cardinal directions.
* A null value indicates an invalid corner (multiple indexing along the same axis)
*/
val cornersUndirIdx = Array(6) { idx1 -> Array(6) { idx2 -> Array(6) { idx3 ->
cornersUndir.findIdx(BoxCorner(byId(idx1), byId(idx2), byId(idx3)))
} } }
/**
* Get corner index for vertex coordinates
*/
fun getCornerUndir(x: Float, y: Float, z: Float): Int {
var result = 0
if (x > 0.5f) result += 1
if (y > 0.5f) result += 2
if (z > 0.5f) result += 4
return result
}
/**
* Indexing scheme for directed box corners.
* The first direction - the face - matters, the other two are unordered.
* 1:1 correspondence with possible AO values.
* Array contains triplets defining the corner fully.
*/
val cornersDir = Array(24) { idx ->
val faceIdx = idx / 4
val windingIdx = idx % 4
val winding = aoWinding[faceIdx][windingIdx]
BoxCorner(byId(faceIdx), winding.first, winding.second)
}
/**
* Reverse lookup for [cornersDir].
* 1st index: primary face
* 2nd index: undirected corner index.
* value: directed corner index
* A null value indicates an invalid corner (primary face not shared by corner).
*/
val cornerDirFromUndir = Array(6) { faceIdx -> Array(8) { undirIdx ->
val face = byId(faceIdx); val corner = cornersUndir[undirIdx]
if (!corner.contains(face)) null else cornersDir.findIdx { it.first == face && it.equalsUnordered(corner) }
} }
/**
* Reverse lookup for [cornersDir].
* 1st index: primary face
* 2nd index: AO value index on the face.
* value: directed corner index
*/
val cornerDirFromAo = Array(6) { faceIdx -> IntArray(4) { aoIdx ->
val face = byId(faceIdx); val corner = aoWinding[face.ordinal][aoIdx].let { Triple(face, it.first, it.second) }
cornersDir.findIdx { it.first == face && it.equalsUnordered(corner) }!!
} }

View File

@@ -1,114 +0,0 @@
package net.fabricmc.fabric.impl.client.indigo.renderer.render
import mods.betterfoliage.render.lighting.AbstractQuadRenderer_aoCalc
import mods.betterfoliage.render.lighting.AbstractQuadRenderer_blockInfo2
import mods.betterfoliage.render.lighting.AbstractQuadRenderer_bufferFunc2
import mods.betterfoliage.render.lighting.AbstractQuadRenderer_transform
import mods.betterfoliage.render.lighting.CustomLighting
import mods.betterfoliage.render.lighting.CustomLightingMeshConsumer
import mods.betterfoliage.util.YarnHelper
import mods.betterfoliage.util.cornerDirFromAo
import mods.betterfoliage.util.get
import mods.betterfoliage.util.offset
import mods.betterfoliage.util.plus
import net.fabricmc.fabric.impl.client.indigo.renderer.mesh.MutableQuadViewImpl
import net.minecraft.client.render.RenderLayer
import net.minecraft.client.render.WorldRenderer
import net.minecraft.util.math.Direction
val AoCalculator_computeFace = YarnHelper.requiredMethod<Any>(
"net.fabricmc.fabric.impl.client.indigo.renderer.aocalc.AoCalculator", "computeFace",
"(Lnet/minecraft/util/math/Direction;ZZ)Lnet/fabricmc/fabric/impl/client/indigo/renderer/aocalc/AoFaceData;"
)
val AoFaceData_toArray = YarnHelper.requiredMethod<Unit>(
"net.fabricmc.fabric.impl.client.indigo.renderer.aocalc.AoFaceData", "toArray",
"([F[I[I)V"
)
open class ModifiedTerrainMeshConsumer(
val original: AbstractMeshConsumer
) : AbstractMeshConsumer(
original[AbstractQuadRenderer_blockInfo2],
original[AbstractQuadRenderer_bufferFunc2],
original[AbstractQuadRenderer_aoCalc],
original[AbstractQuadRenderer_transform]
), CustomLightingMeshConsumer {
override fun matrix() = original.matrix()
override fun normalMatrix() = original.normalMatrix()
override fun overlay() = original.overlay()
/** Custom lighting to use */
var lighter: CustomLighting? = null
/** Cache validity for AO/light values */
protected val aoValid = Array(6) { false }
override val aoFull = FloatArray(24)
override val lightFull = IntArray(24)
/** Cached block brightness values for all neighbors */
val brNeighbor = IntArray(6)
/** Cache validity for block brightness values (neighbors + self) */
val brValid = Array(7) { false }
override var brSelf: Int = -1
get() {
if (brValid[6]) return field else {
field = WorldRenderer.getLightmapCoordinates(blockInfo.blockView, blockInfo.blockPos)
brValid[6] = true
return field
}
}
protected set
override fun brNeighbor(dir: Direction): Int {
if (brValid[dir.ordinal]) return brNeighbor[dir.ordinal]
WorldRenderer.getLightmapCoordinates(blockInfo.blockView, blockInfo.blockPos + dir.offset)
.let { brNeighbor[dir.ordinal] = it; brValid[dir.ordinal] = true; return it }
}
override fun clearLighting() {
for (idx in 0 until 6) {
aoValid[idx] = false
brValid[idx] = false
}
brValid[6] = false
}
override fun fillAoData(lightFace: Direction) {
if (!aoValid[lightFace.ordinal]) {
AoFaceData_toArray.invoke(
AoCalculator_computeFace.invoke(aoCalc, lightFace, true, false),
aoFull,
lightFull,
cornerDirFromAo[lightFace.ordinal]
)
aoValid[lightFace.ordinal] = true
}
}
override fun setLighting(vIdx: Int, ao: Float, light: Int) {
aoCalc.ao[vIdx] = ao
aoCalc.light[vIdx] = light
}
override fun tesselateFlat(q: MutableQuadViewImpl, renderLayer: RenderLayer, blockColorIndex: Int) {
lighter?.applyLighting(this, q, flat = true, emissive = false)
super.tesselateSmooth(q, renderLayer, blockColorIndex)
}
override fun tesselateFlatEmissive(q: MutableQuadViewImpl, renderLayer: RenderLayer, blockColorIndex: Int) {
lighter?.applyLighting(this, q, flat = true, emissive = true)
super.tesselateSmoothEmissive(q, renderLayer, blockColorIndex)
}
override fun tesselateSmooth(q: MutableQuadViewImpl, renderLayer: RenderLayer, blockColorIndex: Int) {
lighter?.applyLighting(this, q, flat = false, emissive = false)
super.tesselateSmooth(q, renderLayer, blockColorIndex)
}
override fun tesselateSmoothEmissive(q: MutableQuadViewImpl, renderLayer: RenderLayer, blockColorIndex: Int) {
lighter?.applyLighting(this, q, flat = false, emissive = true)
super.tesselateSmoothEmissive(q, renderLayer, blockColorIndex)
}
}

View File

@@ -0,0 +1,5 @@
public net.minecraft.client.renderer.chunk.ChunkRenderCache field_212408_i #world
public net.minecraft.client.renderer.BlockModelRenderer$Cache
public net.minecraft.client.renderer.BlockModelRenderer field_210267_b
public net.minecraft.client.renderer.BlockModelRenderer field_187499_a

View File

@@ -0,0 +1,14 @@
modLoader="kotlinforforge"
loaderVersion="[1,)"
issueTrackerURL="https://github.com/octarine-noise/BetterFoliage/issues"
license="MIT"
[[mods]]
modId="betterfoliage"
version="${version}"
displayName="Better Foliage"
authors="octarine-noise"
description='''
Leafier leaves and grassier grass.
'''
displayURL="https://www.curseforge.com/minecraft/mc-mods/better-foliage"
side="CLIENT"

View File

@@ -1,3 +0,0 @@
// Vanilla
//net.minecraft.block.CactusBlock
net.minecraft.class_2266

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