Compare commits

..

4 Commits

Author SHA1 Message Date
octarine-noise
d741338d46 minor cleanup 2020-02-17 15:55:37 +01:00
octarine-noise
01f697d2d3 un-bundle Mod Menu 2020-02-17 15:55:14 +01:00
octarine-noise
4c439dccd2 re-render chunks when configuration is changed 2020-02-17 12:40:21 +01:00
octarine-noise
df50f61b0d [WIP] initial Fabric port
major package refactoring
2020-02-05 13:43:54 +01:00
150 changed files with 3941 additions and 5620 deletions

2
.gitignore vendored
View File

@@ -7,5 +7,3 @@ run/
build/ build/
classes/ classes/
temp/ temp/
logs
src/main/javacc/mods

View File

@@ -1,52 +1,51 @@
import net.fabricmc.loom.task.RemapJarTask
import org.ajoberstar.grgit.Grgit
plugins { plugins {
kotlin("jvm").version("1.4.20") kotlin("jvm").version("1.3.60")
id("net.minecraftforge.gradle").version("4.1.12") id("fabric-loom").version("0.2.6-SNAPSHOT")
id("org.spongepowered.mixin").version("0.7-SNAPSHOT") id("org.ajoberstar.grgit").version("3.1.1")
id("com.intershop.gradle.javacc").version("4.0.0")
} }
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://files.minecraftforge.net/maven") maven("http://maven.fabricmc.net/")
maven("https://repo.spongepowered.org/maven") maven("https://minecraft.curseforge.com/api/maven")
maven("https://www.cursemaven.com") maven("http://maven.modmuss50.me/")
maven("https://thedarkcolour.github.io/KotlinForForge/") maven("https://grondag-repo.appspot.com").credentials { username = "guest"; password = "" }
maven("https://jitpack.io")
} }
dependencies { dependencies {
"minecraft"("net.minecraftforge:forge:${properties["mcVersion"]}-${properties["forgeVersion"]}") "minecraft"("com.mojang:minecraft:${properties["mcVersion"]}")
"implementation"("thedarkcolour:kotlinforforge:1.7.0") "mappings"("net.fabricmc:yarn:${properties["yarnMappings"]}:v2")
"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:config-2:${properties["clothConfigVersion"]}")
configuration("me.zeroeightsix:fiber:${properties["fiberVersion"]}")
}
// Canvas Renderer
// "modImplementation"("grondag:canvas:0.7.+")
// Optifabric
"modImplementation"("com.github.modmuss50:OptiFabric:df03dc2c22")
"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"
get("main").java.srcDir("src/main/javacc/")
}
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"])
}
}
}
javacc {
configs {
create("blockconfig") {
staticParam = "false"
inputFile = file("src/main/javacc/BlockConfig.jj")
outputDir = file("src/main/javacc/")
packageName = "mods.betterfoliage.config.match.parser"
}
}
} }
java { java {
@@ -61,13 +60,10 @@ kotlin {
} }
} }
tasks.getByName<Jar>("jar") { tasks.getByName<ProcessResources>("processResources") {
archiveName = "BetterFoliage-${project.version}-Forge-${properties["mcVersion"]}.jar" filesMatching("fabric.mod.json") { expand(mutableMapOf("version" to semVer)) }
manifest { }
from(file("src/main/resources/META-INF/MANIFEST.MF"))
attributes["Implementation-Version"] = project.version tasks.getByName<RemapJarTask>("remapJar") {
} archiveName = "$jarName.jar"
exclude("net")
filesMatching("META-INF/mods.toml") { expand(project.properties) }
filesMatching("mcmod.info") { expand(project.properties) }
} }

View File

@@ -2,13 +2,20 @@ 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.7.0 version = 2.5.0
mcVersion = 1.16.5 mcVersion = 1.14.4
forgeVersion = 36.1.17 yarnMappings=1.14.4+build.15
mappingsChannel = official loaderVersion=0.7.3+build.176
mappingsVersion = 1.16.5 fabricVersion=0.4.2+build.246-1.14
loomVersion=0.2.6-SNAPSHOT
#kottleVersion = 1.4.0 kotlinVersion=1.3.60
fabricKotlinVersion=1.3.60+build.1
clothConfigVersion=1.8
modMenuVersion=1.7.6+build.115
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-6.8.1-bin.zip distributionUrl=https\://services.gradle.org/distributions/gradle-5.6-all.zip
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists

2
gradlew vendored
View File

@@ -82,7 +82,6 @@ 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
@@ -130,7 +129,6 @@ 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,9 +29,6 @@ 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"
@@ -40,7 +37,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 execute if "%ERRORLEVEL%" == "0" goto init
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.
@@ -54,7 +51,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 execute if exist "%JAVA_EXE%" goto init
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%
@@ -64,14 +61,28 @@ 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 %* "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
: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,13 +1,8 @@
pluginManagement { pluginManagement {
repositories { repositories {
maven("https://files.minecraftforge.net/maven") jcenter()
maven("https://repo.spongepowered.org/maven") maven("https://maven.fabricmc.net/")
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,18 +0,0 @@
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,31 +3,42 @@ 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.shapes.VoxelShape; import net.minecraft.util.math.Direction;
import net.minecraft.world.IBlockReader; import net.minecraft.util.shape.VoxelShape;
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.CallbackInfoReturnable; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
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;shouldRenderFace(Lnet/minecraft/block/BlockState;Lnet/minecraft/world/IBlockReader;Lnet/minecraft/util/math/BlockPos;Lnet/minecraft/util/Direction;)Z"; private static final String shouldSideBeRendered = "shouldDrawSide(Lnet/minecraft/block/BlockState;Lnet/minecraft/world/BlockView;Lnet/minecraft/util/math/BlockPos;Lnet/minecraft/util/math/Direction;)Z";
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 getVoxelShape = "Lnet/minecraft/block/BlockState;getCullShape(Lnet/minecraft/world/BlockView;Lnet/minecraft/util/math/BlockPos;Lnet/minecraft/util/math/Direction;)Lnet/minecraft/util/shape/VoxelShape;";
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 randomDisplayTick = "randomDisplayTick(Lnet/minecraft/block/BlockState;Lnet/minecraft/world/World;Lnet/minecraft/util/math/BlockPos;Ljava/util/Random;)V";
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)) /**
private static VoxelShape getVoxelShapeOverride(BlockState state, IBlockReader reader, BlockPos pos, Direction dir) { * Override 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.
*/
@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

@@ -1,41 +0,0 @@
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

@@ -0,0 +1,17 @@
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

@@ -1,15 +1,12 @@
package mods.betterfoliage.mixin; package mods.betterfoliage.mixin;
import mods.betterfoliage.Hooks; import mods.betterfoliage.Hooks;
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.IBlockReader; import net.minecraft.world.BlockView;
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;
/** /**
@@ -17,15 +14,14 @@ import org.spongepowered.asm.mixin.injection.Redirect;
* *
* Needed to avoid excessive darkening of Round Logs at the corners, now that they are not full blocks. * Needed to avoid excessive darkening of Round Logs at the corners, now that they are not full blocks.
*/ */
@Mixin(AbstractBlock.AbstractBlockState.class) @Mixin(BlockState.class)
@SuppressWarnings({"deprecation"}) @SuppressWarnings({"UnnecessaryQualifiedMemberReference", "deprecation"})
public class MixinBlockState { public class MixinBlockState {
private static final String callFrom = "Lnet/minecraft/block/AbstractBlock$AbstractBlockState;getShadeBrightness(Lnet/minecraft/world/IBlockReader;Lnet/minecraft/util/math/BlockPos;)F"; private static final String callFrom = "Lnet/minecraft/block/BlockState;getAmbientOcclusionLightLevel(Lnet/minecraft/world/BlockView;Lnet/minecraft/util/math/BlockPos;)F";
// 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, IBlockReader reader, BlockPos pos) { float getAmbientOcclusionValue(Block block, BlockState state, BlockView reader, BlockPos pos) {
return Hooks.getAmbientOcclusionLightValueOverride(block.getShadeBrightness(state, reader, pos), state); return Hooks.getAmbientOcclusionLightValueOverride(block.getAmbientOcclusionLightLevel(state, reader, pos), state);
} }
} }

View File

@@ -0,0 +1,23 @@
package mods.betterfoliage.mixin;
import mods.betterfoliage.ClientChunkLoadCallback;
import net.minecraft.client.world.ClientChunkManager;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.util.PacketByteBuf;
import net.minecraft.world.World;
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(ClientChunkManager.class)
public class MixinClientChunkManager {
private static final String onLoadChunkFromPacket = "loadChunkFromPacket(Lnet/minecraft/world/World;IILnet/minecraft/util/PacketByteBuf;Lnet/minecraft/nbt/CompoundTag;IZ)Lnet/minecraft/world/chunk/WorldChunk;";
@Inject(method = onLoadChunkFromPacket, at = @At(value = "RETURN", ordinal = 2))
void onLoadChunkFromPacket(World world, int chunkX, int chunkZ, PacketByteBuf data, CompoundTag nbt, int updatedSectionsBits, boolean clearOld, CallbackInfoReturnable<WorldChunk> ci) {
ClientChunkLoadCallback.EVENT.invoker().loadChunk(ci.getReturnValue());
}
}

View File

@@ -0,0 +1,19 @@
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 onSetAndCompare = "method_20183(ILnet/minecraft/world/chunk/WorldChunk;Lnet/minecraft/world/chunk/WorldChunk;)Lnet/minecraft/world/chunk/WorldChunk;";
@Inject(method = onSetAndCompare, at = @At("HEAD"))
void onSetAndCompare(int i, WorldChunk oldChunk, WorldChunk newChunk, CallbackInfoReturnable<WorldChunk> ci) {
ClientChunkLoadCallback.EVENT.invoker().unloadChunk(oldChunk);
}
}

View File

@@ -1,15 +1,18 @@
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.renderer.WorldRenderer; import net.minecraft.client.network.ClientPlayNetworkHandler;
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.world.IBlockReader; import net.minecraft.util.profiler.Profiler;
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;
@@ -17,27 +20,31 @@ import java.util.Random;
@Mixin(ClientWorld.class) @Mixin(ClientWorld.class)
public class MixinClientWorld { public class MixinClientWorld {
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 ctor = "<init>(Lnet/minecraft/client/network/ClientPlayNetworkHandler;Lnet/minecraft/world/level/LevelInfo;Lnet/minecraft/world/dimension/DimensionType;ILnet/minecraft/util/profiler/Profiler;Lnet/minecraft/client/render/WorldRenderer;)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 scheduleBlockRender = "scheduleBlockRender(Lnet/minecraft/util/math/BlockPos;Lnet/minecraft/block/BlockState;Lnet/minecraft/block/BlockState;)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 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 worldDisplayTick = "randomBlockDisplayTick(IIIILjava/util/Random;ZLnet/minecraft/util/math/BlockPos$Mutable;)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"; 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";
/**
* 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}
*/ */
@Redirect(method = worldNotify, at = @At(value = "INVOKE", target = rendererNotify)) @Inject(method = scheduleBlockRender, at = @At(value = "HEAD"))
void onClientBlockChanged(WorldRenderer renderer, IBlockReader world, BlockPos pos, BlockState oldState, BlockState newState, int flags) { void onClientBlockChanged(BlockPos pos, BlockState oldState, BlockState newState, CallbackInfo ci) {
Hooks.onClientBlockChanged((ClientWorld) world, pos, oldState, newState, flags); Hooks.onClientBlockChanged((ClientWorld) (Object) this, pos, oldState, newState);
renderer.blockChanged(world, pos, oldState, newState, flags); }
@Inject(method = ctor, at = @At("RETURN"))
void onClientWorldCreated(ClientPlayNetworkHandler netHandler, LevelInfo levelInfo, DimensionType dimensionType, int i, Profiler profiler, WorldRenderer worldRenderer, 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

@@ -1,44 +0,0 @@
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

@@ -1,65 +0,0 @@
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

@@ -1,44 +0,0 @@
package mods.betterfoliage.mixin;
import mods.betterfoliage.BetterFoliage;
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 BetterFoliage.INSTANCE.getModelManager().onBake(unbaked, bakery, spriteGetter, transform, locationIn);
}
}

View File

@@ -0,0 +1,28 @@
package mods.betterfoliage.mixin;
import mods.betterfoliage.ModelLoadingCallback;
import net.minecraft.client.render.model.ModelLoader;
import net.minecraft.client.util.ModelIdentifier;
import net.minecraft.resource.ResourceManager;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import static net.minecraft.client.render.model.ModelLoader.MISSING;
@Mixin(ModelLoader.class)
public class MixinModelLoader {
@Shadow @Final private ResourceManager resourceManager;
@Inject(at = @At("HEAD"), method = "addModel")
private void addModelHook(ModelIdentifier id, CallbackInfo info) {
// use the same trick fabric-api does to get around the no-mixins-in-constructors policy
if (id == MISSING) {
ModelLoadingCallback.EVENT.invoker().beginLoadModels((ModelLoader) (Object) this, resourceManager);
}
}
}

View File

@@ -1,38 +0,0 @@
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,57 +0,0 @@
package mods.betterfoliage.mixin;
import com.mojang.blaze3d.matrix.MatrixStack;
import mods.betterfoliage.render.pipeline.RenderCtxBase;
import net.minecraft.block.BlockState;
import net.minecraft.client.renderer.BlockRendererDispatcher;
import net.minecraft.client.renderer.RegionRenderCacheBuilder;
import net.minecraft.client.renderer.RenderType;
import net.minecraft.client.renderer.chunk.ChunkRenderDispatcher;
import net.minecraft.client.renderer.chunk.VisGraph;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.IBlockDisplayReader;
import org.spongepowered.asm.mixin.Mixin;
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.callback.CallbackInfoReturnable;
import org.spongepowered.asm.mixin.injection.callback.LocalCapture;
import java.util.Iterator;
import java.util.Random;
import java.util.Set;
@Mixin(targets = "net.minecraft.client.renderer.chunk.ChunkRenderDispatcher$ChunkRender$RebuildTask")
public class MixinOptifineChunkRendererDispatcher {
private static final String compile = "Lnet/minecraft/client/renderer/chunk/ChunkRenderDispatcher$ChunkRender$RebuildTask;compile(FFFLnet/minecraft/client/renderer/chunk/ChunkRenderDispatcher$CompiledChunk;Lnet/minecraft/client/renderer/RegionRenderCacheBuilder;)Ljava/util/Set;";
private static final String getBlockStateMcp = "Lnet/optifine/override/ChunkCacheOF;getBlockState(Lnet/minecraft/util/math/BlockPos;)Lnet/minecraft/block/BlockState;";
private static final String getBlockStateSrg = "Lnet/optifine/override/ChunkCacheOF;func_180495_p(Lnet/minecraft/util/math/BlockPos;)Lnet/minecraft/block/BlockState;";
@Inject(method = compile, locals = LocalCapture.CAPTURE_FAILHARD, require = 1, at = {
@At(value = "INVOKE", target = getBlockStateMcp),
@At(value = "INVOKE", target = getBlockStateSrg),
})
void onStartBlockRender(
float xIn, float yIn, float zIn,
ChunkRenderDispatcher.CompiledChunk compiledChunkIn, RegionRenderCacheBuilder builderIn,
CallbackInfoReturnable<Set> cir,
int i, BlockPos blockpos, BlockPos blockpos1, VisGraph visgraph, Set set, MatrixStack matrixstack,
@Coerce IBlockDisplayReader chunkrendercache, RenderType[] singleLayer,
boolean shaders, boolean shadersMidBlock, Random random,
BlockRendererDispatcher blockrendererdispatcher, Iterator var18,
@Coerce BlockPos blockpos2
) {
RenderCtxBase.reset(chunkrendercache, blockrendererdispatcher, blockpos2, random);
}
// @Inject(method = compile, locals = LocalCapture.PRINT, require = 1, at = {
// @At(value = "INVOKE", target = getBlockStateMcp),
// @At(value = "INVOKE", target = getBlockStateSrg),
// })
// void printLocals(
// float p_228940_1_, float p_228940_2_, float p_228940_3_,
// ChunkRenderDispatcher.CompiledChunk p_228940_4_, RegionRenderCacheBuilder p_228940_5_,
// CallbackInfoReturnable<BlockState> ci) {
// }
}

View File

@@ -1,46 +0,0 @@
package mods.betterfoliage.mixin;
import com.mojang.blaze3d.matrix.MatrixStack;
import mods.betterfoliage.render.pipeline.RenderCtxBase;
import net.minecraft.client.renderer.BlockRendererDispatcher;
import net.minecraft.client.renderer.RegionRenderCacheBuilder;
import net.minecraft.client.renderer.chunk.ChunkRenderCache;
import net.minecraft.client.renderer.chunk.ChunkRenderDispatcher;
import net.minecraft.client.renderer.chunk.VisGraph;
import net.minecraft.util.math.BlockPos;
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;
import org.spongepowered.asm.mixin.injection.callback.LocalCapture;
import java.util.Iterator;
import java.util.Random;
import java.util.Set;
@Mixin(targets = "net.minecraft.client.renderer.chunk.ChunkRenderDispatcher$ChunkRender$RebuildTask")
public class MixinVanillaChunkRendererDispatcher {
private static final String compile = "Lnet/minecraft/client/renderer/chunk/ChunkRenderDispatcher$ChunkRender$RebuildTask;compile(FFFLnet/minecraft/client/renderer/chunk/ChunkRenderDispatcher$CompiledChunk;Lnet/minecraft/client/renderer/RegionRenderCacheBuilder;)Ljava/util/Set;";
private static final String getBlockState = "Lnet/minecraft/client/renderer/chunk/ChunkRenderCache;getBlockState(Lnet/minecraft/util/math/BlockPos;)Lnet/minecraft/block/BlockState;";
@Inject(method = compile, at = @At(value = "INVOKE", target = getBlockState), locals = LocalCapture.CAPTURE_FAILHARD)
void onStartBlockRender(
float p_228940_1_, float p_228940_2_, float p_228940_3_,
ChunkRenderDispatcher.CompiledChunk p_228940_4_, RegionRenderCacheBuilder p_228940_5_,
CallbackInfoReturnable ci,
int i, BlockPos blockpos, BlockPos blockpos1, VisGraph visgraph, Set set,
ChunkRenderCache chunkrendercache, MatrixStack matrixstack,
Random random,
BlockRendererDispatcher blockrendererdispatcher, Iterator var15,
BlockPos blockpos2) {
RenderCtxBase.reset(chunkrendercache, blockrendererdispatcher, blockpos2, random);
}
// @Inject(method = compile, at = @At(value = "INVOKE", target = getBlockState), locals = LocalCapture.PRINT)
// void printLocals(
// float p_228940_1_, float p_228940_2_, float p_228940_3_,
// ChunkRenderDispatcher.CompiledChunk p_228940_4_, RegionRenderCacheBuilder p_228940_5_,
// CallbackInfoReturnable ci) {
// }
}

View File

@@ -1,101 +0,0 @@
PARSER_BEGIN(BlockConfigParser)
package mods.betterfoliage.config.match.parser;
import java.util.List;
import java.util.LinkedList;
import mods.betterfoliage.config.match.*;
public class BlockConfigParser {
public String configFile;
ConfigSource getSource(Token t) {
return new ConfigSource(configFile, t.beginLine, t.beginColumn);
}
}
PARSER_END(BlockConfigParser)
// Whitespace definition
SKIP : { " " | "\n" | "\t" | "\r" }
// Single-line comment
SPECIAL_TOKEN : { <lineComment: "//" (~["\n","\r"])* ("\n"|"\r"|"\r\n")> }
// Lexical state for string literal in quotes
SPECIAL_TOKEN : { < quoteStart : "\"" > : withinQuotes }
<withinQuotes> SPECIAL_TOKEN : { < quoteEnd : "\"" > : DEFAULT }
<withinQuotes> TOKEN : { < stringLiteral : (["a"-"z"] | ["0"-"9"] | "/" | "." | "_" | "-" | ":" )* > }
// Symbol tokens
TOKEN : {
< parenStart : "(" > |
< parenEnd : ")" > |
< dot : "." > |
< comma : "," > |
< exclamation : "!" >
}
List<Node.MatchAll> matchFile() : {
Token t; Node n; List<Node.MatchAll> rules = new LinkedList<Node.MatchAll>();
} {
(
t = "match"
{ List<Node> nodes = new LinkedList<Node>(); }
(n = match() { nodes.add(n); })*
"end"
{ rules.add(new Node.MatchAll(getSource(t), nodes)); }
)*
{ return rules; }
}
Node match() : {
Token t; Token t2; MatchMethod mm; List<Node.Value> values; Node.Value v; Node n;
} {
<exclamation> n = match() { return new Node.Negate(n); }
|
t = "block.class." mm = matchMethod() <parenStart> values = matchValueList() <parenEnd>
{ return new Node.MatchValueList(Node.MatchSource.BLOCK_CLASS, mm, getSource(t), values); }
|
t = "block.name." mm = matchMethod() <parenStart> values = matchValueList() <parenEnd>
{ return new Node.MatchValueList(Node.MatchSource.BLOCK_NAME, mm, getSource(t), values); }
|
t = "model." mm = matchMethod() <parenStart> values = matchValueList() <parenEnd>
{ return new Node.MatchValueList(Node.MatchSource.MODEL_LOCATION, mm, getSource(t), values); }
|
t = "isParam" <parenStart> t2 = <stringLiteral> <comma> values = matchValueList() <parenEnd>
{ return new Node.MatchParam(t2.image, values, getSource(t)); }
|
t = "setParam" <parenStart> t2 = <stringLiteral> <comma> v = matchValue() <parenEnd>
{ return new Node.SetParam(t2.image, v, getSource(t)); }
}
MatchMethod matchMethod() : {} {
"matches" { return MatchMethod.EXACT_MATCH; } |
"extends" { return MatchMethod.EXTENDS; } |
"contains" { return MatchMethod.CONTAINS; }
}
List<Node.Value> matchValueList() : {
List<Node.Value> values = new LinkedList<Node.Value>();
Node.Value v;
} {
v = matchValue() { values.add(v); }
(<comma> v = matchValue() { values.add(v); } )*
{ return values; }
}
Node.Value matchValue() : {
Token t;
} {
t = <stringLiteral>
{ return new Node.Value.Literal(getSource(t), t.image); }
|
"classOf" <parenStart> t = <stringLiteral> <parenEnd>
{ return new Node.Value.ClassOf(getSource(t), t.image); }
|
"model.texture" <parenStart> t = <stringLiteral> <parenEnd>
{ return new Node.Value.Texture(getSource(t), t.image); }
|
"model.tint" <parenStart> t = <stringLiteral> <parenEnd>
{ return new Node.Value.Tint(getSource(t), t.image); }
}

View File

@@ -1,107 +1,88 @@
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.integration.OptifineCustomColors import mods.betterfoliage.config.MainConfig
import mods.betterfoliage.integration.ShadersModIntegration import mods.betterfoliage.render.block.vanilla.*
import mods.betterfoliage.render.block.vanilla.RoundLogOverlayLayer
import mods.betterfoliage.render.block.vanilla.StandardCactusDiscovery
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.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.StandardRoundLogDiscovery
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.LeafParticleRegistry import mods.betterfoliage.render.particle.LeafParticleRegistry
import mods.betterfoliage.render.particle.LeafWindTracker
import mods.betterfoliage.render.particle.RisingSoulParticle import mods.betterfoliage.render.particle.RisingSoulParticle
import mods.betterfoliage.resource.discovery.BakeWrapperManager import mods.betterfoliage.resource.discovery.BakedModelReplacer
import mods.betterfoliage.resource.discovery.BlockTypeCache import mods.betterfoliage.resource.generated.GeneratedBlockTexturePack
import mods.betterfoliage.resource.discovery.RuleBasedDiscovery import net.fabricmc.api.ClientModInitializer
import mods.betterfoliage.resource.generated.GeneratedTexturePack import net.fabricmc.fabric.api.resource.ResourceManagerHelper
import mods.betterfoliage.util.resourceManager import net.fabricmc.loader.api.FabricLoader
import net.minecraft.block.BlockState import net.minecraft.client.MinecraftClient
import net.minecraft.resource.ResourceType
import net.minecraft.util.Identifier
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 responsible for initializing (and holding a reference to) all the infrastructure of the mod
* except for the call hooks.
*/
object BetterFoliage {
/** Resource pack holding generated assets */
val generatedPack = GeneratedTexturePack("bf_gen", "Better Foliage generated assets")
/** List of recognized [BlockState]s */ object BetterFoliage : ClientModInitializer {
var blockTypes = BlockTypeCache() const val MOD_ID = "betterfoliage"
var logger = LogManager.getLogger()
var logDetail = SimpleLogger(
"BetterFoliage",
Level.DEBUG,
false, false, true, false,
"yyyy-MM-dd HH:mm:ss",
null,
PropertiesUtil(Properties()),
PrintStream(File(FabricLoader.getInstance().gameDirectory, "logs/betterfoliage.log").apply {
parentFile.mkdirs()
if (!exists()) createNewFile()
})
)
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 blockConfig = BlockConfig()
val generatedPack = GeneratedBlockTexturePack(Identifier(MOD_ID, "generated"), "betterfoliage-generated", "Better Foliage", "Generated leaf textures", logDetail)
val modelReplacer = BakedModelReplacer()
val standardModelSupport = RuleBasedDiscovery().apply { override fun onInitializeClient() {
discoverers["cactus"] = StandardCactusDiscovery // Register generated resource pack
discoverers["dirt"] = StandardDirtDiscovery ResourceManagerHelper.get(ResourceType.CLIENT_RESOURCES).registerReloadListener(generatedPack)
discoverers["grass"] = StandardGrassDiscovery MinecraftClient.getInstance().resourcePackContainerManager.addCreator(generatedPack.finder)
discoverers["leaf"] = StandardLeafDiscovery
discoverers["lilypad"] = StandardLilypadDiscovery
discoverers["mycelium"] = StandardMyceliumDiscovery
discoverers["netherrack"] = StandardNetherrackDiscovery
discoverers["round-log"] = StandardRoundLogDiscovery
discoverers["sand"] = StandardSandDiscovery
}
val modelManager = BakeWrapperManager().apply {
discoverers.add(standardModelSupport)
}
fun init() { // Add standard block support
// discoverers modelReplacer.discoverers.add(StandardLeafDiscovery)
BetterFoliageMod.bus.register(modelManager) modelReplacer.discoverers.add(StandardGrassDiscovery)
BetterFoliageMod.bus.register(LeafParticleRegistry) modelReplacer.discoverers.add(StandardLogDiscovery)
resourceManager.registerReloadListener(LeafParticleRegistry) modelReplacer.discoverers.add(StandardCactusDiscovery)
modelReplacer.discoverers.add(LilyPadDiscovery)
modelReplacer.discoverers.add(DirtDiscovery)
modelReplacer.discoverers.add(SandDiscovery)
modelReplacer.discoverers.add(MyceliumDiscovery)
modelReplacer.discoverers.add(NetherrackDiscovery)
// Init overlay layers
ChunkOverlayManager.layers.add(RoundLogOverlayLayer) ChunkOverlayManager.layers.add(RoundLogOverlayLayer)
listOf( // Init singletons
StandardLeafDiscovery, LeafParticleRegistry
StandardSandDiscovery, NormalLeavesModel.Companion
StandardRoundLogDiscovery, GrassBlockModel.Companion
).forEach { RoundLogModel.Companion
} CactusModel.Companion
LilypadModel.Companion
// init singletons DirtModel.Companion
val singletons = listOf( SandModel.Companion
AoSideHelper, MyceliumModel.Companion
ChunkOverlayManager, NetherrackModel.Companion
LeafWindTracker RisingSoulParticle.Companion
)
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

@@ -1,76 +0,0 @@
package mods.betterfoliage
import mods.betterfoliage.config.MainConfig
import mods.betterfoliage.config.clothGuiRoot
import mods.betterfoliage.config.forgeSpecRoot
import mods.betterfoliage.util.tryDefault
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)
BetterFoliage.init()
}
}

View File

@@ -1,106 +1,29 @@
package mods.betterfoliage package mods.betterfoliage
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.RenderType
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 net.minecraftforge.registries.IRegistryDelegate
import java.util.Random
import java.util.function.Predicate
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>())
}
object RenderTypeLookup : ClassRef<RenderTypeLookup>("net.minecraft.client.renderer.RenderTypeLookup") {
val blockRenderChecks = FieldRef(this, "blockRenderChecks", mapRefMutable<IRegistryDelegate<Block>, Predicate<RenderType>>())
}
// Optifine // Optifine
val OptifineClassTransformer = ClassRef<Any>("optifine.OptiFineClassTransformer") //val OptifineClassTransformer = ClassRefOld<Any>("optifine.OptiFineClassTransformer")
val BlockPosM = ClassRef<Any>("net.optifine.BlockPosM") //val BlockPosM = ClassRefOld<Any>("net.optifine.BlockPosM")
object ChunkCacheOF : ClassRef<Any>("net.optifine.override.ChunkCacheOF") { //object ChunkCacheOF : ClassRefOld<Any>("net.optifine.override.ChunkCacheOF") {
val chunkCache = FieldRef(this, "chunkCache", ChunkRenderCache) // val chunkCache = FieldRefOld(this, "chunkCache", ChunkRendererRegion)
} //}
object RenderEnv : ClassRef<Any>("net.optifine.render.RenderEnv") { //object RenderEnv : ClassRefOld<Any>("net.optifine.render.RenderEnv") {
val reset = MethodRef(this, "reset", void, BlockState, BlockPos) // val reset = MethodRefOld(this, "reset", void, BlockState, BlockPos)
} //}
// Optifine custom colors // Optifine custom colors
val IColorizer = ClassRef<Any>("net.optifine.CustomColors\$IColorizer") //val IColorizer = ClassRefOld<Any>("net.optifine.CustomColors\$IColorizer")
object CustomColors : ClassRef<Any>("net.optifine.CustomColors") { //object CustomColors : ClassRefOld<Any>("net.optifine.CustomColors") {
val getColorMultiplier = MethodRef(this, "getColorMultiplier", int, BakedQuad, BlockState, ILightReader, BlockPos, RenderEnv) // val getColorMultiplier = MethodRefOld(this, "getColorMultiplier", int, BakedQuad, BlockState, ExtendedBlockView, BlockPos, RenderEnv)
} //}
// Optifine shaders // Optifine shaders
object Shaders : ClassRef<Any>("net.optifine.shaders.Shaders") { //object SVertexBuilder : ClassRefOld<Any>("net.optifine.shaders.SVertexBuilder") {
val shaderPackLoaded = FieldRef(this, "shaderPackLoaded", boolean) // val pushState = MethodRefOld(this, "pushEntity", void, BlockState, BlockPos, ExtendedBlockView, BufferBuilder)
val blockLightLevel05 = FieldRef(this, "blockLightLevel05", float) // val pushNum = MethodRefOld(this, "pushEntity", void, long)
val blockLightLevel06 = FieldRef(this, "blockLightLevel06", float) // val pop = MethodRefOld(this, "popEntity", void)
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)
}

View File

@@ -1,8 +1,72 @@
package mods.betterfoliage package mods.betterfoliage
import net.minecraft.client.renderer.model.ModelBakery import net.fabricmc.fabric.api.event.Event
import net.minecraft.client.renderer.texture.AtlasTexture import net.fabricmc.fabric.api.event.EventFactory
import net.minecraft.resources.IResourceManager import net.minecraft.client.render.block.BlockModels
import net.minecraft.util.ResourceLocation import net.minecraft.client.render.model.ModelLoader
import net.minecraftforge.eventbus.api.Event import net.minecraft.client.world.ClientWorld
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,76 +2,66 @@
package mods.betterfoliage package mods.betterfoliage
import mods.betterfoliage.chunk.ChunkOverlayManager import mods.betterfoliage.chunk.ChunkOverlayManager
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.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 net.minecraft.block.Block import mods.betterfoliage.render.block.vanilla.LeafKey
import mods.betterfoliage.render.block.vanilla.RoundLogKey
import mods.betterfoliage.util.offset
import mods.betterfoliage.util.plus
import mods.betterfoliage.util.randomD
import net.minecraft.block.BlockRenderLayer
import net.minecraft.block.BlockRenderLayer.CUTOUT
import net.minecraft.block.BlockRenderLayer.CUTOUT_MIPPED
import net.minecraft.block.BlockState import net.minecraft.block.BlockState
import net.minecraft.block.Blocks import net.minecraft.block.Blocks
import net.minecraft.client.Minecraft import net.minecraft.client.MinecraftClient
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.shapes.VoxelShape import net.minecraft.util.math.Direction
import net.minecraft.util.math.shapes.VoxelShapes import net.minecraft.util.shape.VoxelShape
import net.minecraft.world.IBlockDisplayReader import net.minecraft.util.shape.VoxelShapes
import net.minecraft.world.IBlockReader import net.minecraft.world.BlockView
import net.minecraft.world.World
import java.util.Random
fun getAmbientOcclusionLightValueOverride(original: Float, state: BlockState): Float { fun getAmbientOcclusionLightValueOverride(original: Float, state: BlockState): Float {
if (Config.enabled && Config.roundLogs.enabled && BetterFoliage.blockTypes.hasTyped<RoundLogKey>(state)) if (BetterFoliage.config.enabled &&
return Config.roundLogs.dimming.toFloat() BetterFoliage.config.roundLogs.enabled &&
BetterFoliage.modelReplacer.getTyped<RoundLogKey>(state) != null
) return BetterFoliage.config.roundLogs.dimming.toFloat()
return original return original
} }
fun onClientBlockChanged(worldClient: ClientWorld, pos: BlockPos, oldState: BlockState, newState: BlockState, flags: Int) { fun getUseNeighborBrightnessOverride(original: Boolean, state: BlockState): Boolean {
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, random: Random) { fun onRandomDisplayTick(world: ClientWorld, pos: BlockPos) {
val state = world.getBlockState(pos) val state = world.getBlockState(pos)
if (Config.enabled &&
Config.risingSoul.enabled && if (BetterFoliage.config.enabled &&
BetterFoliage.config.risingSoul.enabled &&
state.block == Blocks.SOUL_SAND && state.block == Blocks.SOUL_SAND &&
world.getBlockState(pos.relative(UP)).isAir && world.isAir(pos + Direction.UP.offset) &&
Math.random() < Config.risingSoul.chance) { Math.random() < BetterFoliage.config.risingSoul.chance) {
RisingSoulParticle(world, pos).addIfValid() RisingSoulParticle(world, pos).addIfValid()
} }
if (Config.enabled && if (BetterFoliage.config.enabled &&
Config.fallingLeaves.enabled && BetterFoliage.config.fallingLeaves.enabled &&
random.nextDouble() < Config.fallingLeaves.chance && world.isAir(pos + Direction.DOWN.offset) &&
world.getBlockState(pos.relative(DOWN)).isAir randomD() < BetterFoliage.config.fallingLeaves.chance) {
) { BetterFoliage.modelReplacer.getTyped<LeafKey>(state)?.let { key ->
(getActualRenderModel(world, pos, state, random) as? LeafBlockModel)?.let { leafModel -> FallingLeafParticle(world, pos, key).addIfValid()
val blockColor = Minecraft.getInstance().blockColors.getColor(state, world, pos, 0) }
FallingLeafParticle(world, pos, leafModel.key, blockColor, random).addIfValid()
}
} }
} }
fun getVoxelShapeOverride(state: BlockState, reader: IBlockReader, pos: BlockPos, dir: Direction): VoxelShape { fun getVoxelShapeOverride(state: BlockState, reader: BlockView, pos: BlockPos, dir: Direction): VoxelShape {
if (Config.enabled && Config.roundLogs.enabled && BetterFoliage.blockTypes.hasTyped<RoundLogKey>(state)) if (BetterFoliage.modelReplacer[state] is RoundLogKey) {
return VoxelShapes.empty() return VoxelShapes.empty()
return state.getFaceOcclusionShape(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 return state.getCullShape(reader, pos, dir)
} }

View File

@@ -1,62 +1,55 @@
package mods.betterfoliage.chunk package mods.betterfoliage.chunk
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.renderer.chunk.ChunkRenderCache import net.minecraft.client.MinecraftClient
import net.minecraft.util.Direction
import net.minecraft.util.math.BlockPos import net.minecraft.util.math.BlockPos
import net.minecraft.world.IBlockDisplayReader import net.minecraft.util.math.Direction
import net.minecraft.world.IWorldReader import net.minecraft.world.ExtendedBlockView
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: IBlockDisplayReader val world: ExtendedBlockView
val pos: BlockPos val pos: BlockPos
val seed: Long get() = state.getSeed(pos)
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) } val biome: Biome get() = world.getBiome(pos)
fun isAir(dir: Direction) = isAir(dir.offset)
val biome: Biome? get() = val isNormalCube: Boolean get() = state.isSimpleFullBlock(world, pos)
(world as? IWorldReader)?.getBiome(pos) ?:
(world as? ChunkRenderCache)?.level?.getBiome(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)!! }
fun model(offset: Int3) = state(offset).let { MinecraftClient.getInstance().blockRenderManager.getModel(it)!! }
/** Get a semi-random value based on the block coordinate and the given seed. */
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)
} }
class BasicBlockCtx( open class BasicBlockCtx(
override val world: IBlockDisplayReader, override val world: ExtendedBlockView,
override val pos: BlockPos override val pos: BlockPos
) : BlockCtx { ) : BlockCtx {
override val state: BlockState = world.getBlockState(pos) override val state = 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: ExtendedBlockView, pos: BlockPos) : BasicBlockCtx(world, pos) {
var neighbors = Array<BlockState>(6) { world.getBlockState(pos + allDirections[it].offset) }
override var biome: Biome = world.getBiome(pos)
override fun state(dir: Direction) = neighbors[dir.ordinal]
} }

View File

@@ -0,0 +1,50 @@
package mods.betterfoliage.chunk
import net.minecraft.util.math.BlockPos
import net.minecraft.world.BlockView
import net.minecraft.world.ExtendedBlockView
import net.minecraft.world.LightType
/**
* 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: ExtendedBlockView, val modded: BlockPos, val target: BlockPos) : ExtendedBlockView 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 getLightmapIndex(pos: BlockPos, light: Int) = original.getLightmapIndex(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,95 +1,124 @@
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 mods.betterfoliage.util.isInstance import net.minecraft.client.render.chunk.ChunkRendererRegion
import mods.betterfoliage.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.DimensionType import net.minecraft.world.ExtendedBlockView
import net.minecraft.world.IBlockDisplayReader import net.minecraft.world.ViewableWorld
import net.minecraft.world.IWorldReader import net.minecraft.world.World
import net.minecraftforge.common.MinecraftForge import net.minecraft.world.chunk.WorldChunk
import net.minecraftforge.event.world.ChunkEvent import net.minecraft.world.dimension.DimensionType
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 IBlockDisplayReader.dimType: DimensionType get() = when { // net.minecraft.world.chunk.WorldChunk.world
this is IWorldReader -> dimensionType() val WorldChunk_world = YarnHelper.requiredField<World>("net.minecraft.class_2818", "field_12858", "Lnet/minecraft/class_1937;")
this is ChunkRenderCache -> level.dimensionType() // net.minecraft.client.render.chunk.ChunkRendererRegion.world
this.isInstance(ChunkCacheOF) -> this[ChunkCacheOF.chunkCache].level.dimensionType() val ChunkRendererRegion_world = YarnHelper.requiredField<World>("net.minecraft.class_853", "field_4490", "Lnet/minecraft/class_1937;")
val ExtendedBlockView.dimType: DimensionType get() = when {
this is ViewableWorld -> dimension.type
this is ChunkRendererRegion -> this[ChunkRendererRegion_world]!!.dimension.type
// this.isInstance(ChunkCacheOF) -> this[ChunkCacheOF.chunkCache]!!.dimType
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!")
} }
/** /**
* Represents some form of arbitrary non-persistent data that can be calculated and cached for each block position * Represents some form of arbitrary non-persistent data that can be calculated and cached for each block position
*/ */
abstract class ChunkOverlayLayer<T> { interface ChunkOverlayLayer<T> {
val dimData = IdentityHashMap<DimensionType, SparseChunkedMap<T>>() fun calculate(ctx: BlockCtx): T
abstract fun calculate(ctx: BlockCtx): T fun onBlockUpdate(world: ExtendedBlockView, pos: BlockPos)
abstract fun onBlockUpdate(world: IBlockDisplayReader, pos: BlockPos)
operator fun get(ctx: BlockCtx): T {
return dimData
.getOrPut(ctx.world.dimType) { SparseChunkedMap() }
.getOrPut(ctx.pos) { calculate(ctx) }
}
fun remove(world: IBlockDisplayReader, pos: BlockPos) {
dimData[world.dimType]?.remove(pos)
}
} }
/** /**
* Event forwarder for multiple layers of chunk overlay data. * Query, lazy calculation and lifecycle management of multiple layers of chunk overlay data.
*/ */
object ChunkOverlayManager { object ChunkOverlayManager : ClientChunkLoadCallback, ClientWorldLoadCallback {
init { MinecraftForge.EVENT_BUS.register(this) }
init {
ClientWorldLoadCallback.EVENT.register(this)
ClientChunkLoadCallback.EVENT.register(this)
}
val chunkData = IdentityHashMap<DimensionType, MutableMap<ChunkPos, ChunkOverlayData>>()
val layers = mutableListOf<ChunkOverlayLayer<*>>() val layers = mutableListOf<ChunkOverlayLayer<*>>()
fun onBlockChange(world: ClientWorld, pos: BlockPos) { /**
layers.forEach { layer -> layer.onBlockUpdate(world, pos) } * Get the overlay data for a given layer and position
} *
* @param layer Overlay layer to query
@SubscribeEvent * @param reader World to use if calculation of overlay value is necessary
fun handleUnloadWorld(event: WorldEvent.Unload) = (event.world as? ClientWorld)?.let { world -> * @param pos Block position
layers.forEach { layer -> layer.dimData.remove(world.dimType) } */
} fun <T> get(layer: ChunkOverlayLayer<T>, ctx: BlockCtx): T? {
val data = chunkData[ctx.world.dimType]?.get(ChunkPos(ctx.pos)) ?: return null
@SubscribeEvent data.get(layer, ctx.pos).let { value ->
fun handleUnloadChunk(event: ChunkEvent.Unload) = (event.world as? ClientWorld)?.let { world -> if (value !== ChunkOverlayData.UNCALCULATED) return value
layers.forEach { layer -> layer.dimData[world.dimType]?.removeChunk(event.chunk.pos) } val newValue = layer.calculate(ctx)
} data.set(layer, ctx.pos, newValue)
} return newValue
interface DoubleMap<K1, K2, V> {
val map1: MutableMap<K1, MutableMap<K2, V>>
fun createMap2(): MutableMap<K2, V>
fun remove(key1: K1) {
map1.remove(key1)
}
fun remove(key1: K1, key2: K2) {
map1[key1]?.remove(key2)
}
fun contains(key1: K1) = map1.contains(key1)
fun getOrSet(key1: K1, key2: K2, factory: () -> V) =
(map1[key1] ?: createMap2().apply { map1[key1] = this }).let { subMap ->
subMap[key2] ?: factory().apply { subMap[key2] = this }
} }
}
class SparseChunkedMap<V> {
val map = object : DoubleMap<ChunkPos, BlockPos, V> {
override val map1 = mutableMapOf<ChunkPos, MutableMap<BlockPos, V>>()
override fun createMap2() = mutableMapOf<BlockPos, V>()
} }
fun getOrPut(pos: BlockPos, factory: () -> V) = map.getOrSet(ChunkPos(pos), pos, factory) /**
fun remove(pos: BlockPos) = map.remove(ChunkPos(pos), pos) * Clear the overlay data for a given layer and position
fun removeChunk(pos: ChunkPos) = map.map1.remove(pos) *
} * @param layer Overlay layer to clear
* @param pos Block position
*/
fun <T> clear(dimension: DimensionType, layer: ChunkOverlayLayer<T>, pos: BlockPos) {
chunkData[dimension]?.get(ChunkPos(pos))?.clear(layer, pos)
}
fun onBlockChange(world: ClientWorld, pos: BlockPos) {
if (chunkData[world.dimType]?.containsKey(ChunkPos(pos)) == true) {
layers.forEach { layer -> layer.onBlockUpdate(world, pos) }
}
}
override fun loadChunk(chunk: WorldChunk) {
chunk[WorldChunk_world]!!.dimType.let { dim ->
val data = chunkData[dim] ?: mutableMapOf<ChunkPos, ChunkOverlayData>().apply { chunkData[dim] = this }
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)
}
}
}
override fun unloadChunk(chunk: WorldChunk) {
chunk[WorldChunk_world]!!.dimType.let { dim ->
chunkData[dim]?.remove(chunk.pos)
}
}
override fun loadWorld(world: ClientWorld) {
val dim = world.dimType
// chunkData.keys.forEach { if (it == dim) chunkData[dim] = mutableMapOf() else chunkData.remove(dim)}
}
}
class ChunkOverlayData(layers: List<ChunkOverlayLayer<*>>) {
val BlockPos.isValid: Boolean get() = y in validYRange
val rawData = layers.associateWith { emptyOverlay() }
fun <T> get(layer: ChunkOverlayLayer<T>, pos: BlockPos): T? = if (pos.isValid) rawData[layer]?.get(pos.x and 15)?.get(pos.z and 15)?.get(pos.y) as T? else null
fun <T> set(layer: ChunkOverlayLayer<T>, pos: BlockPos, data: T) = if (pos.isValid) rawData[layer]?.get(pos.x and 15)?.get(pos.z and 15)?.set(pos.y, data) else null
fun <T> clear(layer: ChunkOverlayLayer<T>, pos: BlockPos) = if (pos.isValid) rawData[layer]?.get(pos.x and 15)?.get(pos.z and 15)?.set(pos.y, UNCALCULATED) else null
companion object {
val UNCALCULATED = object {}
fun emptyOverlay() = Array(16) { Array(16) { Array<Any?>(256) { UNCALCULATED }}}
val validYRange = 0 until 256
}
}

View File

@@ -1,40 +1,36 @@
package mods.betterfoliage.config package mods.betterfoliage.config
import mods.betterfoliage.config.match.Node import mods.betterfoliage.BetterFoliage
import mods.betterfoliage.config.match.parser.BlockConfigParser import mods.betterfoliage.util.Invalidator
import mods.betterfoliage.config.match.parser.ParseException import mods.betterfoliage.resource.discovery.ConfigurableBlockMatcher
import mods.betterfoliage.config.match.parser.TokenMgrError import mods.betterfoliage.resource.discovery.ModelTextureListConfiguration
import mods.betterfoliage.util.HasLogger import net.minecraft.resource.ResourceManager
import mods.betterfoliage.util.stripStart import net.minecraft.util.Identifier
import net.minecraft.resources.IResourceManager
import net.minecraft.util.ResourceLocation
import org.apache.logging.log4j.Level
import org.apache.logging.log4j.Level.ERROR
class BlockConfig : HasLogger() { class BlockConfig {
lateinit var rules: List<Node.MatchAll> private val list = mutableListOf<Any>()
fun readConfig(manager: IResourceManager) { val leafBlocks = blocks("leaves_blocks_default.cfg")
val configs = manager.listResources("config/betterfoliage") { it.endsWith(".rules") } val leafModels = models("leaves_models_default.cfg")
rules = configs.flatMap { configLocation -> val grassBlocks = blocks("grass_blocks_default.cfg")
val resource = manager.getResource(configLocation) val grassModels = models("grass_models_default.cfg")
val parser = BlockConfigParser(resource.inputStream) val mycelium = blocks("mycelium_blocks_default.cfg")
.apply { configFile = configLocation.stripStart("config/betterfoliage/").toString() } // val dirt = blocks("dirt_default.cfg")
try { val crops = blocks("crop_default.cfg")
parser.matchFile() val logBlocks = blocks("log_blocks_default.cfg")
} catch (e: ParseException) { val logModels = models("log_models_default.cfg")
parseError(e, configLocation) val sand = blocks("sand_default.cfg")
} catch (e: TokenMgrError) { val lilypad = blocks("lilypad_default.cfg")
parseError(e, configLocation) val cactus = blocks("cactus_default.cfg")
} val netherrack = blocks("netherrack_blocks_default.cfg")
}
private fun blocks(cfgName: String) = ConfigurableBlockMatcher(BetterFoliage.logDetail, Identifier(BetterFoliage.MOD_ID, cfgName)).apply { list.add(this) }
private fun models(cfgName: String) = ModelTextureListConfiguration(BetterFoliage.logDetail, Identifier(BetterFoliage.MOD_ID, cfgName)).apply { list.add(this) }
fun reloadConfig(manager: ResourceManager) {
list.forEach { when(it) {
is ConfigurableBlockMatcher -> it.readDefaults(manager)
is ModelTextureListConfiguration -> it.readDefaults(manager)
} }
} }
}
fun parseError(e: Throwable, location: ResourceLocation): List<Node.MatchAll> {
"Error parsing block config $location: ${e.message}".let {
logger.log(ERROR, it)
detailLogger.log(ERROR, it)
}
return emptyList()
}
}

View File

@@ -1,170 +1,137 @@
package mods.betterfoliage.config package mods.betterfoliage.config
import me.shedaniel.clothconfig2.forge.api.AbstractConfigListEntry import me.shedaniel.clothconfig2.api.AbstractConfigListEntry
import me.shedaniel.clothconfig2.forge.api.ConfigBuilder import me.shedaniel.clothconfig2.api.ConfigEntryBuilder
import me.shedaniel.clothconfig2.forge.api.ConfigEntryBuilder import me.shedaniel.clothconfig2.gui.entries.SubCategoryListEntry
import me.shedaniel.clothconfig2.forge.gui.entries.SubCategoryListEntry import me.zeroeightsix.fiber.builder.ConfigValueBuilder
import mods.betterfoliage.util.asText import me.zeroeightsix.fiber.tree.ConfigLeaf
import net.minecraft.client.gui.screen.Screen import me.zeroeightsix.fiber.tree.ConfigNode
import net.minecraft.client.resources.I18n import me.zeroeightsix.fiber.tree.ConfigValue
import net.minecraft.util.ResourceLocation import net.minecraft.client.resource.language.I18n
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 DelegatingConfigGroup.forgeSpecRoot() = sealed class DelegatingConfigNode<N: ConfigLeaf>(val fiberNode: N) {
ForgeConfigSpec.Builder() abstract fun createClothNode(names: List<String>): AbstractConfigListEntry<*>
.also { createForgeNode(it) }
.build()
fun DelegatingConfigGroup.clothGuiRoot(
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> : DelegatingConfigNode(), ReadOnlyProperty<DelegatingConfigGroup, T> { abstract class DelegatingConfigValue<T>(fiberNode: ConfigValue<T>) : DelegatingConfigNode<ConfigValue<T>>(fiberNode), ReadOnlyProperty<DelegatingConfigGroup, T>
lateinit var forgeValue: ForgeConfigSpec.ConfigValue<T>
abstract fun createForgeNode(builder: ForgeConfigSpec.Builder, name: String)
}
open class DelegatingConfigGroup : DelegatingConfigNode() { open class DelegatingConfigGroup(fiberNode: ConfigNode) : DelegatingConfigNode<ConfigNode>(fiberNode) {
val children = mutableMapOf<String, DelegatingConfigNode>() val children = mutableListOf<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(path.joinToString(".").translate()) .startSubCategory(names.joinToString(".").translate())
.setTooltip(*path.joinToString(".").translateTooltip()) .setTooltip(*names.joinToString(".").translateTooltip())
.setExpanded(false) .setExpended(false)
children.forEach { (name, node) -> builder.add(node.createClothNode(path + name)) } children.forEach { builder.add(it.createClothNode(names + it.fiberNode.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: ()->T) = object : DelegatingConfigGroupFactory<T> { fun <T: DelegatingConfigGroup> subNode(factory: (ConfigNode)->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 child = factory() val childNode = ConfigNode(property.name, null)
parent.children[property.name] = child val configGroup = factory(childNode)
return ReadOnlyProperty { _, _ -> child } parent.fiberNode.items.add(childNode)
parent.children.add(configGroup)
return object : ReadOnlyProperty<DelegatingConfigGroup, T> {
override fun getValue(thisRef: DelegatingConfigGroup, property: KProperty<*>) = configGroup
}
} }
} }
interface DelegatingConfigValueFactory<T> { interface DelegatingConfigValueFactory<T> {
fun createForgeNode(builder: ForgeConfigSpec.Builder, name: String): ForgeConfigSpec.ConfigValue<T> fun createFiberNode(parent: ConfigNode, name: String): ConfigValue<T>
fun createClothNode(prop: CachingConfigProperty<T>, path: List<String>): AbstractConfigListEntry<T> fun createClothNode(node: ConfigValue<T>, names: 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 : CachingConfigProperty<T>(parent, property) { return object : DelegatingConfigValue<T>(createFiberNode(parent.fiberNode, property.name)) {
override fun createForgeNode(builder: ForgeConfigSpec.Builder, name: String) { override fun createClothNode(names: List<String>) = createClothNode(fiberNode, names)
forgeValue = this@DelegatingConfigValueFactory.createForgeNode(builder, name) override fun getValue(thisRef: DelegatingConfigGroup, property: KProperty<*>) = fiberNode.value!!
} }.apply { parent.children.add(this) }
override fun createClothNode(path: List<String>): AbstractConfigListEntry<*> = createClothNode(this, path)
}.apply { parent.children[property.name] = this }
} }
} }
abstract class CachingConfigProperty<T>(parent: DelegatingConfigGroup, property: KProperty<*>) : DelegatingConfigValue<T>() { fun String.translate() = I18n.translate(this)
var value: T? = null fun String.translateTooltip(lineLength: Int = MAX_LINE_LEN) = ("$this.tooltip").translate().let { tooltip ->
override fun getValue(thisRef: DelegatingConfigGroup, property: KProperty<*>) = tooltip.splitToSequence(" ").fold(mutableListOf("")) { tooltips, word ->
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().asText() }.toTypedArray() }.map { it.trim() }.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 createForgeNode(builder: ForgeConfigSpec.Builder, name: String) = override fun createFiberNode(parent: ConfigNode, name: String) = ConfigValueBuilder(Boolean::class.java)
builder.define(name, default) .withName(name)
.withParent(parent)
.withDefaultValue(default)
.build()
override fun createClothNode(prop: CachingConfigProperty<Boolean>, path: List<String>) = ConfigEntryBuilder.create() override fun createClothNode(node: ConfigValue<Boolean>, names: List<String>) = ConfigEntryBuilder.create()
.startBooleanToggle(langKey(path).translate(), prop.forgeValue.get()) .startBooleanToggle(langKey(names).translate(), node.value!!)
.setTooltip(langKey(path).let { if (I18n.exists("$it.tooltip")) Optional.of(it.translateTooltip()) else Optional.empty() }) .setTooltip(langKey(names).let { if (I18n.hasTranslation("$it.tooltip")) Optional.of(it.translateTooltip()) else Optional.empty() })
.setSaveConsumer { prop.forgeValue.set(valueOverride(it)); prop.value = null } .setSaveConsumer { node.value = valueOverride(it) }
.build() .build()
} }
fun integer( fun integer(
default: Int = 0, min: Int = 0, max: Int, default: Int, min: Int, 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 createForgeNode(builder: ForgeConfigSpec.Builder, name: String) = override fun createFiberNode(parent: ConfigNode, name: String) = ConfigValueBuilder(Int::class.java)
builder.defineInRange(name, default, min, max) .withName(name)
.withParent(parent)
.withDefaultValue(default)
.constraints().minNumerical(min).maxNumerical(max).finish()
.build()
override fun createClothNode(prop: CachingConfigProperty<Int>, path: List<String>) = ConfigEntryBuilder.create() override fun createClothNode(node: ConfigValue<Int>, names: List<String>) = ConfigEntryBuilder.create()
.startIntField(langKey(path).translate(), prop.forgeValue.get()) .startIntField(langKey(names).translate(), node.value!!)
.setTooltip(langKey(path).let { if (I18n.exists("$it.tooltip")) Optional.of(it.translateTooltip()) else Optional.empty() }) .setTooltip(langKey(names).let { if (I18n.hasTranslation("$it.tooltip")) Optional.of(it.translateTooltip()) else Optional.empty() })
.setMin(min).setMax(max) .setMin(min).setMax(max)
.setSaveConsumer { prop.forgeValue.set(valueOverride(it)); prop.value = null } .setSaveConsumer { node.value = valueOverride(it) }
.build() .build()
} }
fun double( fun double(
default: Double = 0.0, min: Double = 0.0, max: Double = 1.0, default: Double, min: Double, max: Double,
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 createForgeNode(builder: ForgeConfigSpec.Builder, name: String) = override fun createFiberNode(parent: ConfigNode, name: String) = ConfigValueBuilder(Double::class.java)
builder.defineInRange(name, default, min, max) .withName(name)
.withParent(parent)
.withDefaultValue(default)
.constraints().minNumerical(min).maxNumerical(max).finish()
.build()
override fun createClothNode(prop: CachingConfigProperty<Double>, path: List<String>) = ConfigEntryBuilder.create() override fun createClothNode(node: ConfigValue<Double>, names: List<String>) = ConfigEntryBuilder.create()
.startDoubleField(langKey(path).translate(), prop.forgeValue.get()) .startDoubleField(langKey(names).translate(), node.value!!)
.setTooltip(langKey(path).let { if (I18n.exists("$it.tooltip")) Optional.of(it.translateTooltip()) else Optional.empty() }) .setTooltip(langKey(names).let { if (I18n.hasTranslation("$it.tooltip")) Optional.of(it.translateTooltip()) else Optional.empty() })
.setMin(min).setMax(max) .setMin(min).setMax(max)
.setSaveConsumer { prop.forgeValue.set(valueOverride(it)); prop.value = null } .setSaveConsumer { node.value = valueOverride(it) }
.build() .build()
} }
val recurring = { path: List<String> -> "${path.first()}.${path.last()}" } val recurring = { names: List<String> -> "${names.first()}.${names.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,154 @@
package mods.betterfoliage.config package mods.betterfoliage.config
import mods.betterfoliage.BetterFoliageMod import me.zeroeightsix.fiber.tree.ConfigNode
import java.util.Random import java.util.*
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
} }
class MainConfig : DelegatingConfigGroup() { fun population(default: Int) = integer(default, min = 0, max = 64, langKey = recurring)
val enabled by boolean(true, langKey = { "betterfoliage.global.enabled" })
val nVidia by boolean(false)
val leaves by subNode(::LeavesConfig) class MainConfig : DelegatingConfigGroup(ConfigNode("root", null)) {
val shortGrass by subNode(::ShortGrassConfig)
val connectedGrass by subNode(::ConnectedGrassConfig) val enabled by boolean(true, langKey = fakeCategory("global"))
val roundLogs by subNode(::RoundLogConfig) val nVidia by boolean(true, langKey = fakeCategory("global"))
val cactus by subNode(::CactusConfig)
val lilypad by subNode(::LilypadConfig) val leaves by subNode { LeavesConfig(it) }
val reed by subNode(::ReedConfig) val shortGrass by subNode { ShortGrassConfig(it) }
val algae by subNode(::AlgaeConfig) val connectedGrass by subNode { ConnectedGrassConfig(it) }
val coral by subNode(::CoralConfig) val roundLogs by subNode { RoundLogConfig(it) }
val netherrack by subNode(::NetherrackConfig) val cactus by subNode { CactusConfig(it) }
val fallingLeaves by subNode(::FallingLeavesConfig) val lilypad by subNode { LilypadConfig(it) }
val risingSoul by subNode(::RisingSoulConfig) val reed by subNode { ReedConfig(it) }
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 : DelegatingConfigGroup() { class LeavesConfig(node: ConfigNode) : DelegatingConfigGroup(node) {
val enabled by featureEnable() val enabled by boolean(true, langKey = recurring)
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)
} }
class ShortGrassConfig : PopulationConfigGroup() { class ShortGrassConfig(node: ConfigNode) : DelegatingConfigGroup(node), PopulationConfigData {
override val enabled by featureEnable() override val enabled by boolean(true, langKey = recurring)
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(max=0.4, default=0.2, langKey = recurring) val hOffset by double(0.2, min = 0.0, max = 0.4, langKey = recurring)
val heightMin by double(min=0.1, max=2.5, default=0.6, langKey = recurring) val heightMin by double(0.6, min = 0.1, max = 2.5, langKey = recurring)
val heightMax by double(min=0.1, max=2.5, default=0.8, langKey = recurring) val heightMax by double(0.6, min = 0.1, max = 2.5, langKey = recurring) { it.coerceAtLeast(heightMin) }
val size by double(min=0.5, max=1.5, default=1.0, langKey = recurring) val size by double(1.0, min = 0.5, max = 1.5, langKey = recurring)
override val population by integer(max=64, default=64, langKey = recurring) override val population by population(64)
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(default=0.1, langKey = recurring) val saturationThreshold by double(0.1, min = 0.0, max = 1.0)
} }
class ConnectedGrassConfig : DelegatingConfigGroup() { class ConnectedGrassConfig(node: ConfigNode) : DelegatingConfigGroup(node) {
val enabled by boolean(true) val enabled by boolean(true, langKey = recurring)
val snowEnabled by boolean(false) val snowEnabled by boolean(true)
} }
class RoundLogConfig : DelegatingConfigGroup() { class RoundLogConfig(node: ConfigNode) : DelegatingConfigGroup(node) {
val enabled by featureEnable() val enabled by boolean(true, langKey = recurring)
val plantsOnly by boolean(true)
val radiusSmall by double(max=0.5, default=0.25) val defaultY by boolean(false)
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 zProtection by double(min = 0.9, default = 0.99) val radiusSmall by double(0.25, min = 0.0, max = 0.5)
val radiusLarge by double(0.25, 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 : DelegatingConfigGroup() { class CactusConfig(node: ConfigNode) : DelegatingConfigGroup(node) {
val enabled by featureEnable() val enabled by boolean(true, langKey = recurring)
val size by double(min=0.5, max=1.5, default=0.8, langKey = recurring) val size by double(1.3, min = 0.5, max = 1.5, langKey = recurring)
val sizeVariation by double(max=0.5, default=0.1) val sizeVariation by double(0.1, min = 0.0, max = 0.5)
val hOffset by double(max=0.5, default=0.1, langKey = recurring) val hOffset by double(0.1, min = 0.0, max = 0.5, langKey = recurring)
} }
class LilypadConfig : PopulationConfigGroup() { class LilypadConfig(node: ConfigNode) : DelegatingConfigGroup(node), PopulationConfigData {
override val enabled by featureEnable() override val enabled by boolean(true, langKey = recurring)
val hOffset by double(max=0.25, default=0.1, langKey = recurring) val hOffset by double(0.1, min = 0.0, max = 0.25, langKey = recurring)
override val population by integer(max=64, default=16, min=0, langKey = recurring) override val population by population(16)
}
class ReedConfig(node: ConfigNode) : DelegatingConfigGroup(node), PopulationConfigData {
override val enabled by boolean(true, langKey = recurring)
val hOffset by double(0.2, min = 0.0, max = 0.4, langKey = recurring)
val heightMin by double(1.7, min = 1.5, max = 3.0, langKey = recurring)
val heightMax by double(2.2, min = 1.5, max = 3.0, langKey = recurring) { it.coerceAtLeast(heightMin) }
override val population by population(32)
val minBiomeTemp by double(0.4, min = 0.0, max = 2.0)
val minBiomeRainfall by double(0.4, min = 0.0, max = 1.0)
val shaderWind by boolean(true, langKey = recurring) val shaderWind by boolean(true, langKey = recurring)
} }
class ReedConfig : PopulationConfigGroup() { class AlgaeConfig(node: ConfigNode) : DelegatingConfigGroup(node), PopulationConfigData {
override val enabled by featureEnable() override val enabled by boolean(true, langKey = recurring)
val hOffset by double(max=0.4, default=0.2, langKey = recurring) val hOffset by double(0.1, min = 0.0, max = 0.4, langKey = recurring)
val heightMin by double(min=1.5, max=3.5, default=1.7, langKey = recurring) val size by double(1.0, min = 0.5, max = 1.5, langKey = recurring)
val heightMax by double(min=1.5, max=3.5, default=2.2, langKey = recurring) val heightMin by double(0.5, min = 0.1, max = 1.0, langKey = recurring)
override val population by integer(max=64, default=32, langKey = recurring) val heightMax by double(0.5, min = 0.1, max = 1.0, langKey = recurring) { it.coerceAtLeast(heightMin) }
val minBiomeTemp by double(default=0.4) override val population by population(48)
val minBiomeRainfall by double(default=0.4)
val shaderWind by boolean(true, langKey = recurring) val shaderWind by boolean(true, langKey = recurring)
} }
class AlgaeConfig : PopulationConfigGroup() { class CoralConfig(node: ConfigNode) : DelegatingConfigGroup(node), PopulationConfigData {
override val enabled by featureEnable() override val enabled by boolean(true, langKey = recurring)
val hOffset by double(max=0.25, default=0.1, langKey = recurring)
val size by double(min=0.5, max=1.5, default=1.0, langKey = recurring)
val heightMin by double(min=0.1, max=1.5, default=0.5, langKey = recurring)
val heightMax by double(min=0.1, max=1.5, default=1.0, langKey = recurring)
override val population by integer(max=64, default=48, langKey = recurring)
val shaderWind by boolean(true, langKey = recurring)
}
class CoralConfig : PopulationConfigGroup() {
override val enabled by featureEnable()
val shallowWater by boolean(false) val shallowWater by boolean(false)
val hOffset by double(max=0.4, default=0.2, langKey = recurring) val hOffset by double(0.2, min = 0.0, max = 0.4, langKey = recurring)
val vOffset by double(max=0.4, default=0.1, langKey = recurring) val vOffset by double(0.1, min = 0.0, max = 0.4, langKey = recurring)
val size by double(min=0.5, max=1.5, default=0.7, langKey = recurring) val size by double(0.7, min = 0.5, max = 1.5, langKey = recurring)
val crustSize by double(min=0.5, max=1.5, default=1.4) val crustSize by double(1.4, min = 0.5, max = 1.5)
val chance by integer(max=64, default=32) val chance by integer(32, min = 0, max = 64)
override val population by integer(max=64, default=48, langKey = recurring) override val population by population(48)
} }
class NetherrackConfig : DelegatingConfigGroup() { class NetherrackConfig(node: ConfigNode) : DelegatingConfigGroup(node) {
val enabled by featureEnable() val enabled by boolean(true, langKey = recurring)
val hOffset by double(max=0.4, default=0.2, langKey = recurring) val hOffset by double(0.2, min = 0.0, max = 0.4, langKey = recurring)
val heightMin by double(min=0.1, max=1.5, default=0.6, langKey = recurring) val size by double(1.0, min = 0.5, max = 1.5, langKey = recurring)
val heightMax by double(min=0.1, max=1.5, default=0.8, langKey = recurring) val heightMin by double(0.6, min = 0.5, max = 1.5, langKey = recurring)
val size by double(min=0.5, max=1.5, default=1.0, langKey = recurring) val heightMax by double(0.8, min = 0.5, max = 1.5, langKey = recurring) { it.coerceAtLeast(heightMin) }
} }
class FallingLeavesConfig : DelegatingConfigGroup() { class FallingLeavesConfig(node: ConfigNode) : DelegatingConfigGroup(node) {
val enabled by featureEnable() val enabled by boolean(true, langKey = recurring)
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 : DelegatingConfigGroup() { class RisingSoulConfig(node: ConfigNode) : DelegatingConfigGroup(node) {
val enabled by featureEnable() val enabled by boolean(true, langKey = recurring)
val chance by double(min=0.001, max=1.0, default=0.02) val chance by double(0.02, min = 0.001, max = 1.0)
val perturb by double(min=0.01, max=0.25, default=0.05) val perturb by double(0.05, min = 0.01, max = 0.25)
val headSize by double(min=0.25, max=1.5, default=1.0) val headSize by double(1.0, min = 0.25, max = 1.5)
val trailSize by double(min=0.25, max=1.5, default=0.75) val trailSize by double(0.75, min = 0.25, max = 1.5)
val opacity by double(min=0.05, max=1.0, default=0.5) val opacity by double(0.5, min = 0.05, max = 1.0)
val sizeDecay by double(min=0.5, max=1.0, default=0.97) val sizeDecay by double(0.97, min = 0.5, max = 1.0)
val opacityDecay by double(min=0.5, max=1.0, default=0.97) val opacityDecay by double(0.97, min = 0.5, max = 1.0)
val lifetime by double(min=1.0, max=15.0, default=4.0) val lifetime by double(4.0, min = 1.0, max = 15.0)
val trailLength by integer(min=2, max=128, default=48) val trailLength by integer(48, min = 2, max = 128)
val trailDensity by integer(min=1, max=16, default=3) val trailDensity by integer(3, min = 1, max = 16)
} }

View File

@@ -1,13 +0,0 @@
package mods.betterfoliage.config
import net.minecraft.block.BlockState
import net.minecraft.block.Blocks
import net.minecraft.block.material.Material
import net.minecraft.world.biome.Biome
val SALTWATER_BIOMES = listOf(Biome.Category.BEACH, Biome.Category.OCEAN)
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,162 +0,0 @@
package mods.betterfoliage.config.match
enum class MatchMethod {
EXACT_MATCH, EXTENDS, CONTAINS;
fun description(isSuccess: Boolean) = when (this) {
EXACT_MATCH -> if (isSuccess) "matches" else "does not match"
EXTENDS -> if (isSuccess) "extends" else "does not extend"
CONTAINS -> if (isSuccess) "contains" else "does not contain"
}
}
/**
* Basic Either monad implementation
*/
sealed class Either<out L, out R> {
class Left<L>(val left: L) : Either<L, Nothing>()
class Right<R>(val right: R) : Either<Nothing, R>()
fun leftOrNull() = if (this is Left) left else null
fun rightOrNull() = if (this is Right) right else null
fun <R2> map(func: (R) -> R2): Either<L, R2> = when (this) {
is Left<L> -> this
is Right<R> -> Right(func(right))
}
fun <L2> mapLeft(func: (L) -> L2): Either<L2, R> = when (this) {
is Left<L> -> Left(func(left))
is Right<R> -> this
}
fun ifRight(action: (R) -> Unit) {
if (this is Right) action(right)
}
companion object {
fun <L> ofLeft(left: L) = Left(left)
fun <R> ofRight(right: R) = Right(right)
}
}
// this cannot be inside the class for variance reasons
fun <L, R, R2> Either<L, R>.flatMap(func: (R) -> Either<L, R2>) = when (this) {
is Either.Left<L> -> this
is Either.Right<R> -> func(right)
}
fun <L, R, L2> Either<L, R>.flatMapLeft(func: (L) -> Either<L2, R>) = when (this) {
is Either.Left<L> -> func(left)
is Either.Right<R> -> this
}
fun <T> Either<T, T>.flatten() = when (this) {
is Either.Left -> left
is Either.Right -> right
}
interface MAnything<out T> {
val value: T
val immutable: Boolean
}
class MListAll(val list: List<MAnything<Boolean>>) : MAnything<Boolean> {
override val value get() = list.all { it.value }
override val immutable get() = list.all { it.immutable }
}
class MListAny(val list: List<MValue<Boolean>>) : MAnything<Boolean> {
override val value get() = list.any { it.value }
override val immutable get() = list.all { it.immutable }
}
class MNegated(val inner: MAnything<Boolean>) : MAnything<Boolean> {
override val value get() = !inner.value
override val immutable get() = inner.immutable
}
/**
* Value with metadata related to rule matching applied.
*
* @param value the wrapped value
* @param description human-readable description of what the value represents
* @param configSource identifies where the value is described in the config
* @param immutable true if the value never changes
* (another [MValue] constructed in the same way will have the same value)
*
*/
class MValue<out T>(
override val value: T,
val description: String,
val configSource: ConfigSource,
override val immutable: Boolean,
) : MAnything<T> {
companion object {
fun <T> right(value: T, description: String, configSource: ConfigSource, immutable: Boolean = true) =
Either.ofRight(MValue(value, description, configSource, immutable))
fun left(description: String, configSource: ConfigSource, immutable: Boolean = true) =
Either.ofLeft(MValue(false, description, configSource, immutable))
}
}
typealias MEither<T> = Either<MValue<Boolean>, MValue<T>>
val Node.Value.asEither get() = MValue.right(value, value, configSource, true)
fun Node.Value.left(description: String) = MValue.left(description, configSource)
fun Node.invalidTypeFor(type: String) = MValue.left("invalid type for $type: [${this::class.java.name}]", configSource)
fun Node.error(description: String) = MValue.left(description, configSource)
fun <T, R> MEither<T>.mapValue(func: (T) -> R) = map {
MValue(func(it.value), it.description, it.configSource, it.immutable)
}
fun <T> MEither<T>.mapDescription(func: (MValue<T>) -> String) = map {
MValue(it.value, func(it), it.configSource, it.immutable)
}
fun <T, R> MEither<T>.map(
func: (T) -> R,
description: (MValue<T>, R) -> String
) = map { t -> func(t.value).let { r -> MValue(r, description(t, r), t.configSource, t.immutable) } }
fun <T, R> MEither<T>.mapNotNull(
func: (T) -> R?,
dLeft: (MValue<T>) -> String = { it.description },
dRight: (MValue<T>, R) -> String = { m, _ -> m.description }
) = flatMap { t ->
func(t.value)?.let { r ->
MValue.right(r, dRight(t, r), t.configSource, t.immutable)
} ?: MValue.left(dLeft(t), t.configSource, t.immutable)
}
fun <T> MEither<T>.toRight(value: T) =
flatMapLeft { MValue.right(value, it.description, it.configSource, it.immutable) }
data class MComparison<T1, T2>(
private val opTrue: String,
private val opFalse: String,
val testFunc: (T1, T2) -> Boolean
) {
fun compare(value1: MEither<T1>, value2: MEither<T2>) = when {
value1 is Either.Left -> value1
value2 is Either.Left -> value2
else -> {
val isSuccess = testFunc((value1 as Either.Right).right.value, (value2 as Either.Right).right.value)
MValue.right(
isSuccess,
"${value1.right.description} ${if (isSuccess) opTrue else opFalse} ${value2.right.description}",
value2.right.configSource,
value1.right.immutable && value2.right.immutable
)
}
}.flatten()
companion object {
fun <T1, T2> of(matchMethod: MatchMethod, testFunc: (T1, T2) -> Boolean) =
MComparison(matchMethod.description(true), matchMethod.description(false), testFunc)
val equals = of(MatchMethod.EXACT_MATCH) { t1: Any, t2: Any -> t1 == t2 }
}
}

View File

@@ -1,42 +0,0 @@
package mods.betterfoliage.config.match
data class ConfigSource(
val configFile: String,
val line: Int,
val column: Int
) {
override fun toString() = "$configFile @ R$line,C$column"
}
sealed class Node {
enum class MatchSource { BLOCK_CLASS, BLOCK_NAME, MODEL_LOCATION }
abstract val configSource: ConfigSource
class MatchValueList(
val matchSource: MatchSource,
val matchMethod: MatchMethod,
override val configSource: ConfigSource,
val values: List<Value>
) : Node()
class MatchParam(
val name: String,
val values: List<Value>,
override val configSource: ConfigSource,
) : Node()
class Negate(val node: Node) : Node() {
override val configSource get() = node.configSource
}
class SetParam(val name: String, val value: Value, override val configSource: ConfigSource) : Node()
class MatchAll(override val configSource: ConfigSource, val list: List<Node>) : Node()
abstract class Value(override val configSource: ConfigSource, val value: String) : Node() {
class Literal(configSource: ConfigSource, value: String) : Value(configSource, value)
class ClassOf(configSource: ConfigSource, value: String) : Value(configSource, value)
class Texture(configSource: ConfigSource, value: String) : Value(configSource, value)
class Tint(configSource: ConfigSource, value: String) : Value(configSource, value)
}
}

View File

@@ -1,172 +0,0 @@
package mods.betterfoliage.config.match
import mods.betterfoliage.config.match.MatchMethod.CONTAINS
import mods.betterfoliage.config.match.MatchMethod.EXACT_MATCH
import mods.betterfoliage.config.match.MatchMethod.EXTENDS
import mods.betterfoliage.resource.discovery.RuleProcessingContext
import mods.betterfoliage.util.findFirst
import mods.betterfoliage.util.quoted
import mods.betterfoliage.util.tryDefault
import net.minecraft.client.renderer.model.BlockModel
import net.minecraft.util.ResourceLocation
import net.minecraftforge.registries.ForgeRegistries
typealias PartialLocation = Pair<String?, String>
object MatchRules {
fun visitRoot(ctx: RuleProcessingContext, node: Node.MatchAll): MListAll {
val results = mutableListOf<MAnything<Boolean>>()
for (rule in node.list) {
val result = mNode(ctx, rule)
results.add(result)
if (!result.value) break
}
return MListAll(results)
}
fun mNode(ctx: RuleProcessingContext, node: Node): MAnything<Boolean> = when(node) {
is Node.MatchValueList -> mMatchList(ctx, node)
is Node.MatchParam -> mParam(ctx, node)
is Node.SetParam -> mParamSet(ctx, node)
is Node.Negate -> mNegate(ctx, node)
else -> node.error("match type not implemented: ${node::class.java.name.quoted}").left
}
fun mNegate(ctx: RuleProcessingContext, node: Node.Negate) = MNegated(mNode(ctx, node.node))
fun mMatchList(ctx: RuleProcessingContext, node: Node.MatchValueList) = node.values.map { value ->
when (node.matchSource) {
Node.MatchSource.BLOCK_CLASS -> mBlockClass(ctx, node, value)
Node.MatchSource.BLOCK_NAME -> mBlockName(ctx, node, value)
Node.MatchSource.MODEL_LOCATION -> mModel(ctx, node, value)
}
}.let { MListAny(it) }
fun mBlockClass(ctx: RuleProcessingContext, node: Node.MatchValueList, value: Node.Value): MValue<Boolean> {
val blockClass = ctx.discovery.blockState.block::class.java.let {
MValue.right(it, "block class ${it.name.quoted}", node.configSource)
}
val target = when(value) {
is Node.Value.Literal -> value.asEither.mapNotNull(
func = { tryDefault(null) { Class.forName(it) }},
dLeft = { "missing class ${it.value}" },
dRight = { m, _ -> " class ${m.value}" }
)
is Node.Value.ClassOf -> value.asEither.mapValue(::ResourceLocation).mapNotNull(
func = { loc -> ForgeRegistries.BLOCKS.getValue(loc)?.let { it::class.java } },
dLeft = { "missing block ${it.value.toString().quoted}" },
dRight = { m, r -> "class ${r.name.quoted} of block ${m.value}" }
)
else -> value.invalidTypeFor("block class")
}
return when(node.matchMethod) {
EXACT_MATCH -> MComparison.equals.compare(blockClass, target)
EXTENDS -> classExtends.compare(blockClass, target)
CONTAINS -> node.error("invalid match type for block class: contains").left
}
}
fun mBlockName(ctx: RuleProcessingContext, node: Node.MatchValueList, value: Node.Value): MValue<Boolean> {
val blockName = MValue.right(ctx.discovery.blockState.block, "", node.configSource).mapNotNull(
func = { it.registryName }, dLeft = { "missing block name" }, dRight = { _, r -> "block name ${r.toString().quoted}" }
)
val target = when(value) {
is Node.Value.Literal -> value.asEither.map(::splitLocation, ::quoteString)
else -> value.invalidTypeFor("block name")
}
return when(node.matchMethod) {
EXACT_MATCH -> blockNameExact.compare(blockName, target)
CONTAINS -> blockNameContains.compare(blockName, target)
EXTENDS -> node.error("invalid match type for block name: extends").left
}
}
fun mModel(ctx: RuleProcessingContext, node: Node.MatchValueList, value: Node.Value): MValue<Boolean> {
val model = (ctx.discovery.getUnbaked() as? BlockModel)?.let {
MValue.right(it, "model ${it.name.quoted}", node.configSource)
} ?: node.error("unsupported model type: ${ctx.discovery.getUnbaked()::class.java.name.quoted}")
val target = when(value) {
is Node.Value.Literal -> value.asEither.map(::splitLocation, ::quoteString)
else -> value.invalidTypeFor("model")
}
val models = when(node.matchMethod) {
EXTENDS -> model.mapValue { ctx.discovery.loadHierarchy(it).ancestors() }
else -> model.mapValue { listOf(it) }
}
return when(node.matchMethod) {
EXACT_MATCH, EXTENDS -> anyModel(node.matchMethod, ::locationMatches)
CONTAINS -> anyModel(CONTAINS, ::locationContains)
}.compare(models, target)
}
fun mParam(ctx: RuleProcessingContext, node: Node.MatchParam) = node.values.map { value ->
val paramValue = ctx.params[node.name] ?.let {
MValue.right(it, "parameter ${node.name.quoted}", node.configSource, immutable = false)
} ?: node.error("missing parameter ${node.name.quoted}")
val target = when(value) {
is Node.Value.Literal -> value.asEither.mapDescription { it.description.quoted }
else -> value.invalidTypeFor("parameter")
}
MComparison.equals.compare(paramValue, target)
}.let { MListAny(it) }
fun mParamSet(ctx: RuleProcessingContext, node: Node.SetParam): MValue<Boolean> {
val target = when(node.value) {
is Node.Value.Literal -> node.value.asEither
is Node.Value.Texture -> when(val model = ctx.discovery.getUnbaked()) {
is BlockModel -> node.value.asEither.map(
func = { model.getMaterial(it).texture().toString() },
description = { m, r -> "texture \"${m.value}\" = \"$r\" of model ${model.name}"}
)
else -> node.error("unsupported model type: ${model::class.java.name.quoted}")
}
is Node.Value.Tint -> when(val model = ctx.discovery.getUnbaked()) {
is BlockModel -> node.value.asEither.mapNotNull(
func = { model.tintOf(it)?.toString() },
dRight = { m, r -> "tint index $r for sprite ${m.value}" },
dLeft = { m -> "tint index -1 for unused sprite ${m.value}"}
).toRight("-1")
else -> node.error("unsupported model type: ${model::class.java.name.quoted}")
}
else -> node.value.invalidTypeFor("prameter")
}
target.ifRight { ctx.params[node.name] = it.value }
return target.mapDescription { m -> "parameter ${node.name} set to ${m.value}" }.mapValue { true }.flatten()
}
private val classExtends = MComparison.of<Class<*>, Class<*>>(EXTENDS) { c1, c2 -> c2.isAssignableFrom(c1) }
private val blockNameExact = MComparison.of<ResourceLocation, PartialLocation>(EXACT_MATCH) { block, partial ->
locationMatches(block, partial)
}
private val blockNameContains = MComparison.of<ResourceLocation, Pair<String?, String>>(CONTAINS) { block, partial ->
locationContains(block, partial)
}
private fun anyModel(matchMethod: MatchMethod, func: (ResourceLocation, PartialLocation)->Boolean) =
MComparison.of<List<BlockModel>, PartialLocation>(matchMethod) { models, partial ->
models.any { func(ResourceLocation(it.name), partial) }
}
fun locationMatches(loc: ResourceLocation, partial: PartialLocation) =
(partial.first == null || loc.namespace == partial.first) && loc.path == partial.second
fun locationContains(loc: ResourceLocation, partial: PartialLocation) =
(partial.first == null || loc.namespace.contains(partial.first!!)) && loc.path.contains(partial.second)
fun splitLocation(str: String): PartialLocation =
if (str.contains(":")) ResourceLocation(str).let { it.namespace to it.path } else null to str
fun <T, R> quoteString(mValue: MValue<T>, newValue: R) = mValue.description.quoted
fun BlockModel.ancestors(): List<BlockModel> = if (parent == null) listOf(this) else parent!!.ancestors() + this
fun BlockModel.tintOf(spriteName: String) =
elements.findFirst { element ->
element.faces.entries.findFirst { (_, face) ->
if (face.texture == "#$spriteName") face.tintIndex else null
}
}
}

View File

@@ -0,0 +1,110 @@
package mods.betterfoliage.integration
/*
val TextureLeaves = ClassRefOld<Any>("forestry.arboriculture.models.TextureLeaves")
val TextureLeaves_leafTextures = FieldRefOld(TextureLeaves, "leafTextures", Map)
val TextureLeaves_plain = FieldRefOld(TextureLeaves, "plain", Identifier)
val TextureLeaves_fancy = FieldRefOld(TextureLeaves, "fancy", Identifier)
val TextureLeaves_pollinatedPlain = FieldRefOld(TextureLeaves, "pollinatedPlain", Identifier)
val TextureLeaves_pollinatedFancy = FieldRefOld(TextureLeaves, "pollinatedFancy", Identifier)
val TileLeaves = ClassRefOld<Any>("forestry.arboriculture.tiles.TileLeaves")
val TileLeaves_getLeaveSprite = MethodRefOld(TileLeaves, "getLeaveSprite", Identifier, boolean)
val PropertyWoodType = ClassRefOld<Any>("forestry.arboriculture.blocks.PropertyWoodType")
val IWoodType = ClassRefOld<Any>("forestry.api.arboriculture.IWoodType")
val IWoodType_barkTex = MethodRefOld(IWoodType, "getBarkTexture", String)
val IWoodType_heartTex = MethodRefOld(IWoodType, "getHeartTexture", String)
val PropertyTreeType = ClassRefOld<Any>("forestry.arboriculture.blocks.PropertyTreeType")
val IAlleleTreeSpecies = ClassRefOld<Any>("forestry.api.arboriculture.IAlleleTreeSpecies")
val ILeafSpriteProvider = ClassRefOld<Any>("forestry.api.arboriculture.ILeafSpriteProvider")
val TreeDefinition = ClassRefOld<Any>("forestry.arboriculture.genetics.TreeDefinition")
val IAlleleTreeSpecies_getLeafSpriteProvider = MethodRefOld(IAlleleTreeSpecies, "getLeafSpriteProvider", ILeafSpriteProvider)
val TreeDefinition_species = FieldRefOld(TreeDefinition, "species", IAlleleTreeSpecies)
val ILeafSpriteProvider_getSprite = MethodRefOld(ILeafSpriteProvider, "getSprite", Identifier, boolean, boolean)
object ForestryIntegration {
init {
}
}
*/
/*
object ForestryLeafDiscovery : HasLogger, AsyncSpriteProvider<ModelLoader>, ModelRenderRegistry<LeafInfo> {
override val logger = BetterFoliage.logDetail
var idToValue = emptyMap<Identifier, LeafInfo>()
override fun get(state: BlockState, world: BlockView, pos: BlockPos): LeafInfo? {
// check variant property (used in decorative leaves)
state.entries.entries.find {
PropertyTreeType.isInstance(it.key) && TreeDefinition.isInstance(it.value)
} ?.let {
val species = it.value[TreeDefinition_species]!!
val spriteProvider = species[IAlleleTreeSpecies_getLeafSpriteProvider]()
val textureLoc = spriteProvider[ILeafSpriteProvider_getSprite](false, MinecraftClient.isFancyGraphicsEnabled())
return idToValue[textureLoc]
}
// extract leaf texture information from TileEntity
val tile = world.getBlockEntity(pos) ?: return null
if (!TileLeaves.isInstance(tile)) return null
val textureLoc = tile[TileLeaves_getLeaveSprite](MinecraftClient.isFancyGraphicsEnabled())
return idToValue[textureLoc]
}
override fun setup(manager: ResourceManager, bakeryF: CompletableFuture<ModelLoader>, atlasFuture: AtlasFuture): StitchPhases {
val futures = mutableMapOf<Identifier, CompletableFuture<LeafInfo>>()
return StitchPhases(
discovery = bakeryF.thenRunAsync {
val allLeaves = TextureLeaves_leafTextures.getStatic()
allLeaves!!.entries.forEach { (type, leaves) ->
log("base leaf type $type")
leaves!!
listOf(
leaves[TextureLeaves_plain], leaves[TextureLeaves_pollinatedPlain],
leaves[TextureLeaves_fancy], leaves[TextureLeaves_pollinatedFancy]
).forEach { textureLocation ->
futures[textureLocation!!] = defaultRegisterLeaf(textureLocation, atlasFuture)
}
}
},
cleanup = atlasFuture.runAfter {
idToValue = futures.mapValues { it.value.get() }
}
)
}
}
object ForestryLogDiscovery : ModelDiscovery<ColumnTextureInfo>() {
override val logger = BetterFoliage.logDetail
override fun processModel(ctx: ModelDiscoveryContext, atlas: AtlasFuture): CompletableFuture<ColumnTextureInfo>? {
// respect class list to avoid triggering on fences, stairs, etc.
if (!BetterFoliageMod.blockConfig.logBlocks.matchesClass(ctx.state.block)) return null
// find wood type property
val woodType = ctx.state.entries.entries.find {
PropertyWoodType.isInstance(it.key) && IWoodType.isInstance(it.value)
}
if (woodType != null) {
logger.log(Level.DEBUG, "ForestryLogRegistry: block state ${ctx.state}")
logger.log(Level.DEBUG, "ForestryLogRegistry: variant ${woodType.value}")
// get texture names for wood type
val bark = woodType.value[IWoodType_barkTex]()
val heart = woodType.value[IWoodType_heartTex]()
logger.log(Level.DEBUG, "ForestryLogSupport: textures [heart=$heart, bark=$bark]")
val heartSprite = atlas.sprite(heart)
val barkSprite = atlas.sprite(bark)
return atlas.mapAfter {
SimpleColumnInfo(AsyncLogDiscovery.getAxis(ctx.state), heartSprite.get(), heartSprite.get(), listOf(barkSprite.get()))
}
}
return null
}
}
*/

View File

@@ -0,0 +1,29 @@
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 net.minecraft.client.MinecraftClient
import net.minecraft.client.gui.screen.Screen
import net.minecraft.client.resource.language.I18n
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(I18n.translate("betterfoliage.title"))
BetterFoliage.config.createClothNode(listOf("betterfoliage")).value.forEach { rootOption ->
builder.getOrCreateCategory("main").addEntry(rootOption)
}
builder.savingRunnable = Runnable {
JanksonSettings().serialize(BetterFoliage.config.fiberNode, BetterFoliage.configFile.outputStream(), false)
BetterFoliage.modelReplacer.invalidate()
MinecraftClient.getInstance().worldRenderer.reload()
}
builder.build()
}
}

View File

@@ -1,54 +0,0 @@
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.betterfoliage.BlockPos
import mods.betterfoliage.BlockState
import mods.betterfoliage.CustomColors
import mods.betterfoliage.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

@@ -0,0 +1,139 @@
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

@@ -1,98 +0,0 @@
package mods.betterfoliage.integration
import mods.betterfoliage.BetterFoliage
import mods.betterfoliage.BlockAliases
import mods.betterfoliage.BufferBuilder_sVertexBuilder
import mods.betterfoliage.SVertexBuilder
import mods.betterfoliage.Shaders
import mods.betterfoliage.render.pipeline.RenderCtxBase
import mods.betterfoliage.render.pipeline.RenderCtxVanilla
import mods.betterfoliage.util.HasLogger
import mods.betterfoliage.util.allAvailable
import mods.betterfoliage.util.get
import mods.betterfoliage.util.mapArray
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 (state in BetterFoliage.blockTypes.leaf) 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 diffuse shading values used when resources are reloaded
if (isDiffuseAvailable) BetterFoliage.modelManager.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

@@ -1,133 +0,0 @@
package mods.betterfoliage.model
import mods.betterfoliage.chunk.BlockCtx
import mods.betterfoliage.render.pipeline.RenderCtxBase
import mods.betterfoliage.render.pipeline.WrappedLayerPredicate
import mods.betterfoliage.render.pipeline.layerPredicate
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.block.Block
import net.minecraft.block.BlockState
import net.minecraft.client.renderer.RenderType
import net.minecraft.client.renderer.RenderTypeLookup
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 prepare(ctx: BlockCtx, random: Random) = Unit
override fun renderLayer(ctx: RenderCtxBase, data: Any, layer: RenderType) {
// if the passed data is a BlockState, render on the same layer(s) as that block
val testState = (data as? BlockState) ?: ctx.state
// this could get called for more layers than the underlying model is on
// ignore extra decoration layers
val shouldRender = when(val predicate = testState.block.layerPredicate) {
is WrappedLayerPredicate -> predicate.original.test(layer)
else -> RenderTypeLookup.canRenderInLayer(testState, layer)
}
if (shouldRender) ctx.renderQuads(baseQuads)
}
}
open class HalfBakedSpecialWrapper(val baseModel: SpecialRenderModel): SpecialRenderModel by baseModel {
}
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,218 +0,0 @@
package mods.betterfoliage.model
import mods.betterfoliage.util.Double3
import mods.betterfoliage.util.Rotation
import mods.betterfoliage.util.boxFaces
import mods.betterfoliage.util.get
import mods.betterfoliage.util.minmax
import mods.betterfoliage.util.nearestAngle
import mods.betterfoliage.util.rotate
import mods.betterfoliage.util.times
import mods.betterfoliage.util.vec
import net.minecraft.client.renderer.texture.NativeImage
import net.minecraft.client.renderer.texture.TextureAtlasSprite
import net.minecraft.util.Direction
import java.lang.Math.max
import java.lang.Math.min
import java.util.Random
import kotlin.math.cos
import kotlin.math.sin
/**
* Vertex UV coordinates
*
* Zero-centered: coordinates fall between (-0.5, 0.5) (inclusive)
*/
data class UV(val u: Double, val v: Double) {
companion object {
val topLeft = UV(-0.5, -0.5)
val topRight = UV(0.5, -0.5)
val bottomLeft = UV(-0.5, 0.5)
val bottomRight = UV(0.5, 0.5)
}
val rotate: UV get() = UV(v, -u)
fun rotate(n: Int) = when (n % 4) {
0 -> copy()
1 -> UV(v, -u)
2 -> UV(-u, -v)
else -> UV(-v, u)
}
fun clamp(minU: Double = -0.5, maxU: Double = 0.5, minV: Double = -0.5, maxV: Double = 0.5) =
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)
}
/**
* Model vertex
*
* @param[xyz] x, y, z coordinates
* @param[uv] u, v coordinates
* @param[aoShader] [ModelLighter] instance to use with AO rendering
* @param[flatShader] [ModelLighter] instance to use with non-AO rendering
*/
data class Vertex(
val xyz: Double3 = Double3(0.0, 0.0, 0.0),
val uv: UV = UV(0.0, 0.0),
val color: Color = Color.white,
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
val asHSB get() = HSB.fromColor(this)
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])
}
fun fromColorBGRA(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])
}
fun fromColor(color: Color): HSB {
val hsbVals = java.awt.Color.RGBtoHSB(color.red, color.green, color.blue, null)
return HSB(hsbVals[0], hsbVals[1], hsbVals[2])
}
}
val asInt: Int get() = java.awt.Color.HSBtoRGB(hue, saturation, brightness)
val asColor: Color get() = Color(asInt)
}
/**
* Intermediate representation of model quad
* Immutable, double-precision
* Zero-centered (both XYZ and UV) coordinates for simpler rotation/mirroring
*/
data class Quad(
val v1: Vertex, val v2: Vertex, val v3: Vertex, val v4: Vertex,
val sprite: TextureAtlasSprite? = null,
val colorIndex: Int = -1,
val face: Direction? = null
) {
val verts = arrayOf(v1, v2, v3, v4)
inline fun transformV(trans: (Vertex) -> Vertex): Quad = transformVI { vertex, idx -> trans(vertex) }
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)
)
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: Pair<Double, Direction>) = move(Double3(trans.second) * trans.first)
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 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 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)) }
fun mirrorUV(mirrorU: Boolean, mirrorV: Boolean) = transformV { it.copy(uv = it.uv.mirror(mirrorU, mirrorV)) }
fun scrambleUV(random: Random, canFlipU: Boolean, canFlipV: Boolean, canRotate: Boolean) = this
.mirrorUV(canFlipU && random.nextBoolean(), canFlipV && random.nextBoolean())
.let { if (canRotate) it.rotateUV(random.nextInt(4)) else it }
fun sprite(sprite: TextureAtlasSprite) = copy(sprite = sprite)
fun color(color: Color) = transformV { it.copy(color = color) }
fun color(color: Int) = transformV { it.copy(color = Color(color)) }
fun colorIndex(colorIndex: Int) = copy(colorIndex = colorIndex)
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)
fun cycleVertices(n: Int) = when (n % 4) {
1 -> Quad(v2, v3, v4, v1)
2 -> Quad(v3, v4, v1, v2)
3 -> Quad(v4, v1, v2, v3)
else -> this.copy()
}
companion object {
fun mix(first: Quad, second: Quad, vertexFactory: (Vertex, Vertex) -> Vertex) = Quad(
v1 = vertexFactory(first.v1, second.v1),
v2 = vertexFactory(first.v2, second.v2),
v3 = vertexFactory(first.v3, second.v3),
v4 = vertexFactory(first.v4, second.v4)
)
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)
)
}
}
}

View File

@@ -1,57 +0,0 @@
package mods.betterfoliage.model
import mods.betterfoliage.chunk.BlockCtx
import mods.betterfoliage.render.pipeline.RenderCtxBase
import net.minecraft.client.renderer.RenderType
import net.minecraft.client.renderer.model.IBakedModel
import net.minecraft.client.renderer.model.WeightedBakedModel
import net.minecraft.util.WeightedRandom
import java.util.Random
/**
* Model that makes use of advanced rendering features.
*/
interface SpecialRenderModel : IBakedModel {
/**
* Create custom renderdata object. Called once per block. Result is passed to renderLayer().
*/
fun prepare(ctx: BlockCtx, random: Random): Any
fun renderLayer(ctx: RenderCtxBase, data: Any, layer: RenderType)
/**
* Get the actual model that will be rendered. Useful for container models (like [WeightedBakedModel]).
*/
fun resolve(random: Random) = this
}
interface SpecialRenderData {
fun canRenderInLayer(layer: RenderType) = 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 resolve(random: Random) = getModel(random).model.resolve(random)
override fun prepare(ctx: BlockCtx, random: Random) = getModel(random).model.let { actual ->
WeightedRenderData(actual, actual.prepare(ctx, random))
}
override fun renderLayer(ctx: RenderCtxBase, data: Any, layer: RenderType) = when (data) {
is WeightedRenderData -> data.model.renderLayer(ctx, data.modelRenderData, layer)
else -> getModel(ctx.random).model.renderLayer(ctx, data, layer)
}
}
data class WeightedRenderData(
val model: SpecialRenderModel,
val modelRenderData: Any
) : SpecialRenderData {
override fun canRenderInLayer(layer: RenderType) = (modelRenderData as? SpecialRenderData)?.canRenderInLayer(layer) ?: false
}

View File

@@ -1,83 +0,0 @@
package mods.betterfoliage.model
import mods.betterfoliage.BetterFoliageMod
import mods.betterfoliage.util.Atlas
import mods.betterfoliage.util.resourceManager
import net.minecraft.client.renderer.texture.MissingTextureSprite
import net.minecraft.client.renderer.texture.TextureAtlasSprite
import net.minecraft.util.ResourceLocation
import net.minecraftforge.client.event.TextureStitchEvent
import net.minecraftforge.eventbus.api.SubscribeEvent
import kotlin.properties.ReadOnlyProperty
import kotlin.reflect.KProperty
interface SpriteSet {
val num: Int
operator fun get(idx: Int): TextureAtlasSprite
}
class FixedSpriteSet(val sprites: List<TextureAtlasSprite>) : SpriteSet {
override val num = sprites.size
override fun get(idx: Int) = sprites[idx % num]
}
class SpriteDelegate(val atlas: Atlas, val idFunc: () -> ResourceLocation) : ReadOnlyProperty<Any, TextureAtlasSprite> {
private lateinit var id: ResourceLocation
private var value: TextureAtlasSprite? = null
init {
BetterFoliageMod.bus.register(this)
}
@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 }
synchronized(this) {
value?.let { return it }
atlas[id].let { value = it; return it }
}
}
}
class SpriteSetDelegate(
val atlas: Atlas,
val idRegister: (ResourceLocation) -> ResourceLocation = { it },
val idFunc: (Int) -> ResourceLocation
) : ReadOnlyProperty<Any, SpriteSet> {
private var idList: List<ResourceLocation> = emptyList()
private var spriteSet: SpriteSet? = null
init {
BetterFoliageMod.bus.register(this)
}
@SubscribeEvent
fun handlePreStitch(event: TextureStitchEvent.Pre) {
if (event.map.location() != atlas.resourceId) return
spriteSet = null
idList = (0 until 16)
.map(idFunc)
.filter { resourceManager.hasResource(atlas.file(it)) }
.map(idRegister)
idList.forEach { event.addSprite(it) }
}
override fun getValue(thisRef: Any, property: KProperty<*>): SpriteSet {
spriteSet?.let { return it }
synchronized(this) {
spriteSet?.let { return it }
spriteSet = FixedSpriteSet(
idList
.ifEmpty { listOf(MissingTextureSprite.getLocation()) }
.map { atlas[it] }
)
return spriteSet!!
}
}
}

View File

@@ -1,107 +0,0 @@
package mods.betterfoliage.model
import mods.betterfoliage.util.Atlas
import mods.betterfoliage.util.Double3
import mods.betterfoliage.util.PI2
import mods.betterfoliage.util.allDirections
import mods.betterfoliage.util.random
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(
val size: Double,
val height: Double,
val offset: Double3,
val flipU1: Boolean,
val flipU2: Boolean
)
fun tuftShapeSet(size: Double, heightMin: Double, heightMax: Double, hOffset: Double): Array<TuftShapeKey> {
return Array(64) { idx ->
TuftShapeKey(
size,
randomD(heightMin, heightMax),
xzDisk(idx) * randomD(hOffset / 2.0, hOffset),
randomB(),
randomB()
)
}
}
fun tuftQuadSingle(size: Double, height: Double, flipU: Boolean) =
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)
fun tuftModelSet(shapes: Array<TuftShapeKey>, color: Color, tint: Int, spriteGetter: (Int) -> TextureAtlasSprite) =
shapes.mapIndexed { idx, shape ->
listOf(
tuftQuadSingle(shape.size, shape.height, shape.flipU1),
tuftQuadSingle(shape.size, shape.height, shape.flipU2).rotate(rot(UP))
).map { it.move(shape.offset) }
.map { it.color(color).colorIndex(tint) }
.map { it.sprite(spriteGetter(idx)) }
}
fun fullCubeTextured(
spriteLocation: ResourceLocation,
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 { it.sprite(sprite) }
.map { it.colorIndex(tintIndex) }
.bake(true)
}
fun crossModelsRaw(num: Int, size: Double, hOffset: Double, vOffset: Double): List<List<Quad>> {
return (0 until num).map { idx ->
listOf(
Quad.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))
).map { it.scale(size) }
.map { it.move(xzDisk(idx) * hOffset) }
.map { it.move(UP.vec * randomD(-1.0, 1.0) * vOffset) }
}
}
fun crossModelSingle(base: List<Quad>, sprite: TextureAtlasSprite, color: Color, tint: Int, scrambleUV: Boolean) =
base.map { if (scrambleUV) it.scrambleUV(random, canFlipU = true, canFlipV = true, canRotate = true) else it }
.map { it.color(color).colorIndex(tint) }
.mapIndexed { idx, quad -> quad.sprite(sprite) }
.withOpposites()
.bake(false)
fun crossModelsTextured(
leafBase: Iterable<List<Quad>>,
color: Color, tint: Int,
scrambleUV: Boolean,
spriteGetter: (Int) -> ResourceLocation
) = leafBase.mapIndexed { idx, leaf ->
crossModelSingle(leaf, Atlas.BLOCKS[spriteGetter(idx)], color, tint, scrambleUV)
}.toTypedArray()
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

@@ -0,0 +1,130 @@
package mods.betterfoliage.render
import mods.betterfoliage.util.Double3
import net.minecraft.client.MinecraftClient
import net.minecraft.client.particle.ParticleTextureSheet
import net.minecraft.client.particle.SpriteBillboardParticle
import net.minecraft.client.render.BufferBuilder
import net.minecraft.client.render.Camera
import net.minecraft.client.texture.Sprite
import net.minecraft.world.World
import kotlin.math.cos
import kotlin.math.sin
abstract class AbstractParticle(world: World, x: Double, y: Double, z: Double) : SpriteBillboardParticle(world, x, y, z) {
companion object {
// @JvmStatic val sin = Array(64) { idx -> Math.sin(PI2 / 64.0 * idx) }
// @JvmStatic val cos = Array(64) { idx -> Math.cos(PI2 / 64.0 * idx) }
}
val billboardRot = Pair(Double3.zero, Double3.zero)
val currentPos = Double3.zero
val prevPos = Double3.zero
val velocity = Double3.zero
override fun tick() {
super.tick()
currentPos.setTo(x, y, z)
prevPos.setTo(prevPosX, prevPosY, prevPosZ)
velocity.setTo(velocityX, velocityY, velocityZ)
update()
x = currentPos.x; y = currentPos.y; z = currentPos.z;
velocityX = velocity.x; velocityY = velocity.y; velocityZ = velocity.z;
}
/** Render the particle. */
abstract fun render(worldRenderer: BufferBuilder, partialTickTime: Float)
/** Update particle on world tick. */
abstract fun update()
/** True if the particle is renderable. */
abstract val isValid: Boolean
/** Add the particle to the effect renderer if it is valid. */
fun addIfValid() { if (isValid) MinecraftClient.getInstance().particleManager.addParticle(this) }
override fun buildGeometry(buffer: BufferBuilder, camera: Camera, tickDelta: Float, rotX: Float, rotZ: Float, rotYZ: Float, rotXY: Float, rotXZ: Float) {
billboardRot.first.setTo(rotX + rotXY, rotZ, rotYZ + rotXZ)
billboardRot.second.setTo(rotX - rotXY, -rotZ, rotYZ - rotXZ)
render(buffer, tickDelta)
}
/**
* Render a particle quad.
*
* @param[tessellator] the [Tessellator] instance to use
* @param[partialTickTime] partial tick time
* @param[currentPos] render position
* @param[prevPos] previous tick position for interpolation
* @param[size] particle size
* @param[rotation] viewpoint-dependent particle rotation (64 steps)
* @param[sprite] particle texture
* @param[isMirrored] mirror particle texture along V-axis
* @param[alpha] aplha blending
*/
fun renderParticleQuad(worldRenderer: BufferBuilder,
partialTickTime: Float,
currentPos: Double3 = this.currentPos,
prevPos: Double3 = this.prevPos,
size: Double = scale.toDouble(),
rotation: Double = 0.0,
sprite: Sprite = this.sprite,
isMirrored: Boolean = false,
alpha: Float = this.colorAlpha) {
val minU = (if (isMirrored) sprite.minU else sprite.maxU).toDouble()
val maxU = (if (isMirrored) sprite.maxU else sprite.minU).toDouble()
val minV = sprite.minV.toDouble()
val maxV = sprite.maxV.toDouble()
val center = currentPos.copy().sub(prevPos).mul(partialTickTime.toDouble()).add(prevPos).sub(cameraX, cameraY, cameraZ)
val cosRotation = cos(rotation); val sinRotation = sin(rotation)
val v1 = Double3.weight(billboardRot.first, cosRotation * size, billboardRot.second, sinRotation * size)
val v2 = Double3.weight(billboardRot.first, -sinRotation * size, billboardRot.second, cosRotation * size)
val renderBrightness = this.getColorMultiplier(partialTickTime)
val brHigh = renderBrightness shr 16 and 65535
val brLow = renderBrightness and 65535
worldRenderer
.vertex(center.x - v1.x, center.y - v1.y, center.z - v1.z)
.texture(maxU, maxV)
.color(colorRed, colorGreen, colorBlue, alpha)
.texture(brHigh, brLow)
.next()
worldRenderer
.vertex(center.x - v2.x, center.y - v2.y, center.z - v2.z)
.texture(maxU, minV)
.color(colorRed, colorGreen, colorBlue, alpha)
.texture(brHigh, brLow)
.next()
worldRenderer
.vertex(center.x + v1.x, center.y + v1.y, center.z + v1.z)
.texture(minU, minV)
.color(colorRed, colorGreen, colorBlue, alpha)
.texture(brHigh, brLow)
.next()
worldRenderer
.vertex(center.x + v2.x, center.y + v2.y, center.z + v2.z)
.texture(minU, maxV)
.color(colorRed, colorGreen, colorBlue, alpha)
.texture(brHigh, brLow)
.next()
}
override fun getType() = ParticleTextureSheet.PARTICLE_SHEET_OPAQUE
fun setColor(color: Int) {
colorBlue = (color and 255) / 256.0f
colorGreen = ((color shr 8) and 255) / 256.0f
colorRed = ((color shr 16) and 255) / 256.0f
}
}

View File

@@ -0,0 +1,12 @@
package mods.betterfoliage.render
import net.minecraft.block.Blocks
import net.minecraft.block.Material
import net.minecraft.world.biome.Biome
val DIRT_BLOCKS = listOf(Blocks.DIRT, Blocks.COARSE_DIRT)
val SAND_BLOCKS = listOf(Blocks.SAND, Blocks.RED_SAND)
val SALTWATER_BIOMES = listOf(Biome.Category.BEACH, Biome.Category.OCEAN)
val SNOW_MATERIALS = listOf(Material.SNOW, Material.SNOW_BLOCK)

View File

@@ -0,0 +1,48 @@
package mods.betterfoliage.render
import mods.betterfoliage.*
import mods.betterfoliage.util.ThreadLocalDelegate
import net.minecraft.block.BlockState
import net.minecraft.client.MinecraftClient
import net.minecraft.client.render.model.BakedQuad
import net.minecraft.util.math.BlockPos
import net.minecraft.util.math.Direction.UP
import org.apache.logging.log4j.Level
/**
* Integration for OptiFine custom block colors.
*/
/*
@Suppress("UNCHECKED_CAST")
object OptifineCustomColors {
val isColorAvailable = allAvailable(CustomColors, CustomColors.getColorMultiplier)
init {
BetterFoliage.log(Level.INFO, "Optifine custom color support is ${if (isColorAvailable) "enabled" else "disabled" }")
}
val renderEnv by ThreadLocalDelegate { OptifineRenderEnv() }
val fakeQuad = BakedQuad(IntArray(0), 1, UP, null)
fun getBlockColor(ctx: CombinedContext): Int {
val ofColor = if (isColorAvailable && MinecraftClient.getInstance().options.reflectDeclaredField<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.lightingCtx.color 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

@@ -0,0 +1,64 @@
package mods.betterfoliage.render
import mods.betterfoliage.BetterFoliage
import mods.betterfoliage.util.get
import net.minecraft.block.BlockRenderType
import net.minecraft.block.BlockRenderType.MODEL
import net.minecraft.block.BlockState
import net.minecraft.block.Blocks
import net.minecraft.util.math.BlockPos
import net.minecraft.world.ExtendedBlockView
import org.apache.logging.log4j.Level.INFO
/**
* Integration for ShadersMod.
*/
/*
object ShadersModIntegration {
@JvmStatic val isAvailable = allAvailable(SVertexBuilder, SVertexBuilder.pushState, SVertexBuilder.pushNum, SVertexBuilder.pop)
val defaultLeaves = Blocks.OAK_LEAVES.defaultState
val defaultGrass = Blocks.TALL_GRASS.defaultState
/**
* Called from transformed ShadersMod code.
* @see mods.betterfoliage.loader.BetterFoliageTransformer
*/
@JvmStatic fun getBlockStateOverride(state: BlockState, world: ExtendedBlockView, pos: BlockPos): BlockState {
// if (LeafRegistry[state, world, pos] != null) return defaultLeaves
if (BetterFoliage.blockConfig.crops.matchesClass(state.block)) return defaultGrass
return state
}
init {
BetterFoliage.log(INFO, "ShadersMod integration is ${if (isAvailable) "enabled" else "disabled" }")
}
inline fun renderAs(ctx: CombinedContext, renderType: BlockRenderType, enabled: Boolean = true, func: ()->Unit) =
renderAs(ctx, ctx.state, renderType, enabled, func)
/** Quads rendered inside this block will use the given block entity data in shader programs. */
inline fun renderAs(ctx: CombinedContext, state: BlockState, renderType: BlockRenderType, enabled: Boolean = true, func: ()->Unit) {
if (isAvailable && enabled) {
val buffer = ctx.renderCtx.renderBuffer
val sVertexBuilder = buffer[BufferBuilder_sVertexBuilder]
SVertexBuilder.pushState.invoke(sVertexBuilder!!, ctx.state, ctx.pos, ctx.world, buffer)
func()
SVertexBuilder.pop.invoke(sVertexBuilder)
} else {
func()
}
}
/** Quads rendered inside this block will behave as tallgrass blocks in shader programs. */
inline fun grass(ctx: CombinedContext, enabled: Boolean = true, func: ()->Unit) =
renderAs(ctx, defaultGrass, MODEL, enabled, func)
/** Quads rendered inside this block will behave as leaf blocks in shader programs. */
inline fun leaves(ctx: CombinedContext, enabled: Boolean = true, func: ()->Unit) =
renderAs(ctx, defaultLeaves, MODEL, enabled, func)
}
*/

View File

@@ -1,93 +1,94 @@
package mods.betterfoliage.render.block.vanilla package mods.betterfoliage.render.block.vanilla
import mods.betterfoliage.BetterFoliage import mods.betterfoliage.BetterFoliage
import mods.betterfoliage.BetterFoliageMod import mods.betterfoliage.render.lighting.withLighting
import mods.betterfoliage.chunk.BlockCtx import mods.betterfoliage.render.lighting.grassTuftLighting
import mods.betterfoliage.config.Config import mods.betterfoliage.render.lighting.roundLeafLighting
import mods.betterfoliage.model.Color
import mods.betterfoliage.model.HalfBakedSpecialWrapper
import mods.betterfoliage.model.HalfBakedWrapperKey
import mods.betterfoliage.model.SpecialRenderModel
import mods.betterfoliage.model.SpriteSetDelegate
import mods.betterfoliage.model.buildTufts
import mods.betterfoliage.model.crossModelsRaw
import mods.betterfoliage.model.crossModelsTextured
import mods.betterfoliage.model.transform
import mods.betterfoliage.model.tuftModelSet
import mods.betterfoliage.model.tuftShapeSet
import mods.betterfoliage.render.lighting.LightingPreferredFace
import mods.betterfoliage.render.lighting.RoundLeafLighting
import mods.betterfoliage.render.pipeline.RenderCtxBase
import mods.betterfoliage.resource.discovery.ModelBakingContext
import mods.betterfoliage.resource.discovery.ModelDiscoveryContext
import mods.betterfoliage.resource.discovery.ParametrizedModelDiscovery
import mods.betterfoliage.util.Atlas import mods.betterfoliage.util.Atlas
import mods.betterfoliage.util.Rotation import mods.betterfoliage.resource.discovery.*
import mods.betterfoliage.util.horizontalDirections import mods.betterfoliage.resource.model.*
import mods.betterfoliage.util.idx import mods.betterfoliage.util.*
import mods.betterfoliage.util.lazy import net.fabricmc.fabric.api.renderer.v1.model.FabricBakedModel
import mods.betterfoliage.util.randomD import net.fabricmc.fabric.api.renderer.v1.render.RenderContext
import mods.betterfoliage.util.randomI import net.minecraft.block.BlockState
import net.minecraft.client.renderer.RenderType import net.minecraft.block.CactusBlock
import net.minecraft.util.Direction.DOWN import net.minecraft.client.render.model.BakedModel
import net.minecraft.util.ResourceLocation import net.minecraft.util.Identifier
import java.util.Random import net.minecraft.util.math.BlockPos
import net.minecraft.util.math.Direction.DOWN
import net.minecraft.world.ExtendedBlockView
import java.util.*
import java.util.function.Consumer
import java.util.function.Supplier
object StandardCactusDiscovery : ParametrizedModelDiscovery() {
override fun processModel(ctx: ModelDiscoveryContext, params: Map<String, String>) { interface CactusKey : BlockRenderKey {
ctx.addReplacement(StandardCactusKey) val cactusTop: Identifier
ctx.sprites.add(StandardCactusModel.cactusCrossSprite) val cactusBottom: Identifier
val cactusSide: Identifier
}
object StandardCactusDiscovery : ConfigurableModelDiscovery() {
override val logger = BetterFoliage.logDetail
override val matchClasses = SimpleBlockMatcher(CactusBlock::class.java)
override val modelTextures = listOf(ModelTextureList("block/cactus", "top", "bottom", "side"))
override fun processModel(state: BlockState, textures: List<String>, atlas: Consumer<Identifier>): BlockRenderKey? {
val sprites = textures.map { Identifier(it) }
return CactusModel.Key(sprites[0], sprites[1], sprites[2])
} }
} }
object StandardCactusKey : HalfBakedWrapperKey() { class CactusModel(val key: Key, wrapped: BakedModel) : WrappedBakedModel(wrapped), FabricBakedModel {
override fun bake(ctx: ModelBakingContext, wrapped: SpecialRenderModel) = StandardCactusModel(wrapped)
}
class CactusRenderData(val armSide: Int, val armIdx: Int, val crossIdx: Int) val crossModels by cactusCrossModels.delegate(key)
val armModels by cactusArmModels.delegate(key)
val armLighting = horizontalDirections.map { grassTuftLighting(it) }
val crossLighting = roundLeafLighting()
class StandardCactusModel( override fun emitBlockQuads(blockView: ExtendedBlockView, state: BlockState, pos: BlockPos, randomSupplier: Supplier<Random>, context: RenderContext) {
wrapped: SpecialRenderModel (wrapped as FabricBakedModel).emitBlockQuads(blockView, state, pos, randomSupplier, context)
) : HalfBakedSpecialWrapper(wrapped) { if (!BetterFoliage.config.enabled || !BetterFoliage.config.cactus.enabled) return
override fun prepare(ctx: BlockCtx, random: Random): Any = when {
!Config.enabled || !Config.cactus.enabled -> Unit
else -> CactusRenderData(
armSide = random.nextInt() and 3,
armIdx = random.idx(cactusArmModels),
crossIdx = random.idx(cactusCrossModels)
)
}
val armLighting = horizontalDirections.map { LightingPreferredFace(it) }.toTypedArray() val random = randomSupplier.get()
val armSide = random.nextInt() and 3
override fun renderLayer(ctx: RenderCtxBase, data: Any, layer: RenderType) { context.withLighting(armLighting[armSide]) {
super.renderLayer(ctx, data, layer) it.accept(armModels[armSide][random])
if (data is CactusRenderData) {
ctx.vertexLighter = armLighting[data.armSide]
ctx.renderQuads(cactusArmModels[data.armSide][data.armIdx])
ctx.vertexLighter = RoundLeafLighting
ctx.renderQuads(cactusCrossModels[data.crossIdx])
} }
context.withLighting(crossLighting) {
it.accept(crossModels[random])
}
}
data class Key(
override val cactusTop: Identifier,
override val cactusBottom: Identifier,
override val cactusSide: Identifier
) : CactusKey {
override fun replace(model: BakedModel, state: BlockState) = CactusModel(this, meshifyStandard(model, state))
} }
companion object { companion object {
val cactusCrossSprite = ResourceLocation(BetterFoliageMod.MOD_ID, "blocks/better_cactus") val cactusCrossSprite by SpriteDelegate(Atlas.BLOCKS) {
val cactusArmSprites by SpriteSetDelegate(Atlas.BLOCKS) { idx -> Identifier(BetterFoliage.MOD_ID, "blocks/better_cactus")
ResourceLocation(BetterFoliageMod.MOD_ID, "blocks/better_cactus_arm_$idx")
} }
val cactusArmModels by BetterFoliage.modelManager.lazy { val cactusArmSprites by SpriteSetDelegate(Atlas.BLOCKS) { idx ->
val shapes = Config.cactus.let { tuftShapeSet(0.8, 0.8, 0.8, 0.2) } Identifier(BetterFoliage.MOD_ID, "blocks/better_cactus_arm_$idx")
val models = tuftModelSet(shapes, Color.white, -1) { cactusArmSprites[randomI()] } }
val cactusArmModels = LazyMap(BetterFoliage.modelReplacer) { key: CactusKey ->
val shapes = BetterFoliage.config.cactus.let { tuftShapeSet(0.8, 0.8, 0.8, 0.2) }
val models = tuftModelSet(shapes, Color.white.asInt) { 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 BetterFoliage.modelManager.lazy { val cactusCrossModels = LazyMap(BetterFoliage.modelReplacer) { key: CactusKey ->
val models = Config.cactus.let { config -> val models = BetterFoliage.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, -1, true) { cactusCrossSprite } crossModelsTextured(models, Color.white.asInt, true) { cactusCrossSprite }
} }
} }
} }

View File

@@ -1,143 +1,101 @@
package mods.betterfoliage.render.block.vanilla package mods.betterfoliage.render.block.vanilla
import mods.betterfoliage.BetterFoliage import mods.betterfoliage.BetterFoliage
import mods.betterfoliage.BetterFoliageMod import mods.betterfoliage.chunk.BasicBlockCtx
import mods.betterfoliage.chunk.BlockCtx import mods.betterfoliage.render.DIRT_BLOCKS
import mods.betterfoliage.config.Config import mods.betterfoliage.render.SALTWATER_BIOMES
import mods.betterfoliage.config.SALTWATER_BIOMES import mods.betterfoliage.render.lighting.withLighting
import mods.betterfoliage.config.isSnow import mods.betterfoliage.render.lighting.grassTuftLighting
import mods.betterfoliage.integration.ShadersModIntegration import mods.betterfoliage.render.lighting.reedLighting
import mods.betterfoliage.model.Color import mods.betterfoliage.render.lighting.renderMasquerade
import mods.betterfoliage.model.HalfBakedSpecialWrapper
import mods.betterfoliage.model.HalfBakedWrapperKey
import mods.betterfoliage.model.SpecialRenderData
import mods.betterfoliage.model.SpecialRenderModel
import mods.betterfoliage.model.SpriteSetDelegate
import mods.betterfoliage.model.buildTufts
import mods.betterfoliage.model.tuftModelSet
import mods.betterfoliage.model.tuftShapeSet
import mods.betterfoliage.render.lighting.LightingPreferredFace
import mods.betterfoliage.render.pipeline.Layers
import mods.betterfoliage.render.pipeline.RenderCtxBase
import mods.betterfoliage.render.pipeline.extendLayers
import mods.betterfoliage.resource.discovery.ModelBakingContext
import mods.betterfoliage.resource.discovery.ModelDiscoveryContext
import mods.betterfoliage.resource.discovery.ParametrizedModelDiscovery
import mods.betterfoliage.resource.generated.CenteredSprite
import mods.betterfoliage.util.Atlas import mods.betterfoliage.util.Atlas
import mods.betterfoliage.util.Int3 import mods.betterfoliage.resource.discovery.BlockRenderKey
import mods.betterfoliage.util.getBlockModel import mods.betterfoliage.resource.discovery.ModelDiscoveryBase
import mods.betterfoliage.util.idxOrNull import mods.betterfoliage.resource.discovery.ModelDiscoveryContext
import mods.betterfoliage.util.lazy import mods.betterfoliage.resource.generated.CenteredSprite
import mods.betterfoliage.util.offset import mods.betterfoliage.resource.model.*
import mods.betterfoliage.util.randomI import mods.betterfoliage.util.*
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.BlockRenderLayer
import net.minecraft.util.Direction.UP import net.minecraft.block.BlockState
import net.minecraft.util.ResourceLocation import net.minecraft.block.Material
import java.util.Random import net.minecraft.client.MinecraftClient
import net.minecraft.client.render.model.BakedModel
import net.minecraft.util.Identifier
import net.minecraft.util.math.BlockPos
import net.minecraft.util.math.Direction.UP
import net.minecraft.world.ExtendedBlockView
import java.util.*
import java.util.function.Consumer
import java.util.function.Supplier
object StandardDirtDiscovery : ParametrizedModelDiscovery() { object DirtKey : BlockRenderKey {
override fun processModel(ctx: ModelDiscoveryContext, params: Map<String, String>) { override fun replace(model: BakedModel, state: BlockState) = DirtModel(meshifyStandard(model, state))
BetterFoliage.blockTypes.dirt.add(ctx.blockState)
ctx.addReplacement(StandardDirtKey)
ctx.blockState.block.extendLayers()
}
} }
object StandardDirtKey : HalfBakedWrapperKey() { object DirtDiscovery : ModelDiscoveryBase() {
override fun bake(ctx: ModelBakingContext, wrapped: SpecialRenderModel) = StandardDirtModel(wrapped) override val logger = BetterFoliage.logDetail
override fun processModel(ctx: ModelDiscoveryContext, atlas: Consumer<Identifier>) =
if (ctx.state.block in DIRT_BLOCKS) DirtKey else null
} }
class DirtRenderData( class DirtModel(wrapped: BakedModel) : WrappedBakedModel(wrapped) {
val connectedGrassModel: SpecialRenderModel?,
val algaeIdx: Int?,
val reedIdx: Int?
) : SpecialRenderData {
override fun canRenderInLayer(layer: RenderType) = when {
connectedGrassModel != null && layer == Layers.connectedDirt -> true
(algaeIdx != null || reedIdx != null) && layer == Layers.tufts -> true
else -> false
}
}
class StandardDirtModel( val algaeLighting = grassTuftLighting(UP)
wrapped: SpecialRenderModel val reedLighting = reedLighting()
) : HalfBakedSpecialWrapper(wrapped) {
val vanillaTuftLighting = LightingPreferredFace(UP)
override fun prepare(ctx: BlockCtx, random: Random): Any { override fun emitBlockQuads(blockView: ExtendedBlockView, state: BlockState, pos: BlockPos, randomSupplier: Supplier<Random>, context: RenderContext) {
if (!Config.enabled) return Unit if (!BetterFoliage.config.enabled) return super.emitBlockQuads(blockView, state, pos, randomSupplier, context)
val stateUp = ctx.state(UP)
val state2Up = ctx.state(Int3(0, 2, 0)) val ctx = BasicBlockCtx(blockView, pos)
val isConnectedGrass = Config.connectedGrass.enabled && val stateUp = ctx.offset(UP).state
stateUp in BetterFoliage.blockTypes.grass && val keyUp = BetterFoliage.modelReplacer[stateUp]
(Config.connectedGrass.snowEnabled || !state2Up.isSnow)
val isWater = stateUp.material == Material.WATER val isWater = stateUp.material == Material.WATER
val isDeepWater = isWater && state2Up.material == Material.WATER val isDeepWater = isWater && ctx.offset(Int3(2 to UP)).state.material == Material.WATER
val isShallowWater = isWater && state2Up.isAir val isShallowWater = isWater && ctx.offset(Int3(2 to UP)).state.isAir
val isSaltWater = isWater && ctx.biome?.biomeCategory in SALTWATER_BIOMES val isSaltWater = isWater && ctx.biome.category in SALTWATER_BIOMES
// get the actual grass model to use for connected grass rendering if (BetterFoliage.config.connectedGrass.enabled && keyUp is GrassKey) {
// return null if the grass specifically does not want to connect val grassBaseModel = (ctx.model(UP) as WrappedBakedModel).wrapped
val connectedGrassModel = if (!isConnectedGrass) null else getBlockModel(stateUp).let { model -> context.renderMasquerade(grassBaseModel, blockView, stateUp, pos, randomSupplier, context)
(model as? SpecialRenderModel)?.resolve(random)?.let { grassModel -> } else {
if ((grassModel as? StandardGrassModel)?.key?.noConnect == true) null else grassModel super.emitBlockQuads(blockView, state, pos, randomSupplier, context)
}
} }
return DirtRenderData( val random = randomSupplier.get()
connectedGrassModel = connectedGrassModel, if (BetterFoliage.config.algae.enabled(random) && isDeepWater) {
algaeIdx = random.idxOrNull(algaeModels) { Config.algae.enabled(random) && isDeepWater && isSaltWater }, context.withLighting(algaeLighting) {
reedIdx = random.idxOrNull(reedModels) { Config.reed.enabled(random) && isShallowWater && !isSaltWater } it.accept(algaeModels[random])
)
}
override fun renderLayer(ctx: RenderCtxBase, data: Any, layer: RenderType) {
if (data is DirtRenderData) {
if (data.connectedGrassModel != null) {
ctx.renderMasquerade(UP.offset) {
data.connectedGrassModel.renderLayer(ctx, ctx.state(UP), layer)
}
} else {
// render non-connected grass
super.renderLayer(ctx, data, layer)
} }
} else if (BetterFoliage.config.reed.enabled(random) && isShallowWater && !isSaltWater) {
if (layer == Layers.tufts) { context.withLighting(reedLighting) {
data.algaeIdx?.let { it.accept(reedModels[random])
ctx.vertexLighter = vanillaTuftLighting
ShadersModIntegration.grass(ctx, Config.algae.shaderWind) {
ctx.renderQuads(algaeModels[it])
}
}
data.reedIdx?.let {
ctx.vertexLighter = vanillaTuftLighting
ShadersModIntegration.grass(ctx, Config.reed.shaderWind) {
ctx.renderQuads(reedModels[it])
}
}
} }
} else super.renderLayer(ctx, data, layer) }
} }
companion object { companion object {
val algaeSprites by SpriteSetDelegate(Atlas.BLOCKS) { idx -> val algaeSprites by SpriteSetDelegate(Atlas.BLOCKS) { idx ->
ResourceLocation(BetterFoliageMod.MOD_ID, "blocks/better_algae_$idx") Identifier(BetterFoliage.MOD_ID, "blocks/better_algae_$idx")
} }
val reedSprites by SpriteSetDelegate( val reedSprites by SpriteSetDelegate(Atlas.BLOCKS,
Atlas.BLOCKS, idFunc = { idx -> Identifier(BetterFoliage.MOD_ID, "blocks/better_reed_$idx") },
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 BetterFoliage.modelManager.lazy { val algaeModels by LazyInvalidatable(BetterFoliage.modelReplacer) {
val shapes = Config.algae.let { tuftShapeSet(it.size, it.heightMin, it.heightMax, it.hOffset) } val shapes = BetterFoliage.config.algae.let { tuftShapeSet(it.size, it.heightMin, it.heightMax, it.hOffset) }
tuftModelSet(shapes, Color.white, -1) { algaeSprites[randomI()] }.buildTufts() tuftModelSet(shapes, Color.white.asInt) { algaeSprites[randomI()] }
.withOpposites()
.build(BlockRenderLayer.CUTOUT_MIPPED, flatLighting = false)
} }
val reedModels by BetterFoliage.modelManager.lazy { val reedModels by LazyInvalidatable(BetterFoliage.modelReplacer) {
val shapes = Config.reed.let { tuftShapeSet(2.0, it.heightMin, it.heightMax, it.hOffset) } val shapes = BetterFoliage.config.reed.let { tuftShapeSet(2.0, it.heightMin, it.heightMax, it.hOffset) }
tuftModelSet(shapes, Color.white, -1) { reedSprites[randomI()] }.buildTufts() tuftModelSet(shapes, Color.white.asInt) { reedSprites[randomI()] }
.withOpposites()
.build(BlockRenderLayer.CUTOUT_MIPPED, flatLighting = false)
} }
} }
} }

View File

@@ -1,148 +1,121 @@
package mods.betterfoliage.render.block.vanilla package mods.betterfoliage.render.block.vanilla
import mods.betterfoliage.BetterFoliage import mods.betterfoliage.BetterFoliage
import mods.betterfoliage.BetterFoliageMod import mods.betterfoliage.chunk.BasicBlockCtx
import mods.betterfoliage.chunk.BlockCtx import mods.betterfoliage.render.SNOW_MATERIALS
import mods.betterfoliage.config.Config import mods.betterfoliage.render.lighting.withLighting
import mods.betterfoliage.config.isSnow import mods.betterfoliage.render.lighting.grassTuftLighting
import mods.betterfoliage.integration.ShadersModIntegration
import mods.betterfoliage.model.Color
import mods.betterfoliage.model.HalfBakedSpecialWrapper
import mods.betterfoliage.model.HalfBakedWrapperKey
import mods.betterfoliage.model.SpecialRenderData
import mods.betterfoliage.model.SpecialRenderModel
import mods.betterfoliage.model.SpriteSetDelegate
import mods.betterfoliage.model.buildTufts
import mods.betterfoliage.model.fullCubeTextured
import mods.betterfoliage.model.tuftModelSet
import mods.betterfoliage.model.tuftShapeSet
import mods.betterfoliage.render.lighting.LightingPreferredFace
import mods.betterfoliage.render.pipeline.Layers
import mods.betterfoliage.render.pipeline.RenderCtxBase
import mods.betterfoliage.render.pipeline.extendLayers
import mods.betterfoliage.resource.discovery.ModelBakingContext
import mods.betterfoliage.resource.discovery.ModelDiscoveryContext
import mods.betterfoliage.resource.discovery.ParametrizedModelDiscovery
import mods.betterfoliage.util.Atlas import mods.betterfoliage.util.Atlas
import mods.betterfoliage.util.averageHSB import mods.betterfoliage.resource.discovery.*
import mods.betterfoliage.util.idxOrNull import mods.betterfoliage.resource.model.*
import mods.betterfoliage.util.lazy import mods.betterfoliage.util.*
import mods.betterfoliage.util.lazyMap import net.fabricmc.fabric.api.renderer.v1.model.FabricBakedModel
import mods.betterfoliage.util.brighten import net.fabricmc.fabric.api.renderer.v1.render.RenderContext
import mods.betterfoliage.util.logTextureColor import net.minecraft.block.BlockRenderLayer
import mods.betterfoliage.util.randomI import net.minecraft.block.BlockState
import net.minecraft.client.renderer.RenderType import net.minecraft.client.render.model.BakedModel
import net.minecraft.util.Direction.DOWN import net.minecraft.tag.BlockTags
import net.minecraft.util.Direction.UP import net.minecraft.util.Identifier
import net.minecraft.util.ResourceLocation import net.minecraft.util.math.BlockPos
import org.apache.logging.log4j.Level.INFO import net.minecraft.util.math.Direction.*
import java.util.Random import net.minecraft.world.ExtendedBlockView
import java.util.*
import java.util.function.Consumer
import java.util.function.Supplier
object StandardGrassDiscovery : ParametrizedModelDiscovery() { interface GrassKey : BlockRenderKey {
override fun processModel(ctx: ModelDiscoveryContext, params: Map<String, String>) { val grassTopTexture: Identifier
val texture = params.location("texture") ?: return
val tint = params.int("tint") ?: -1 /**
val color = Atlas.BLOCKS.file(texture).averageHSB.let { * Color to use for Short Grass rendering instead of the biome color.
detailLogger.logTextureColor(INFO, "grass texture \"$texture\"", it) *
it.brighten().asColor * Value is null if the texture is mostly grey (the saturation of its average color is under a configurable limit),
} * the average color of the texture otherwise.
val noConnect = params["no-connect"] == "true" */
ctx.addReplacement(StandardGrassKey(texture, tint, color, noConnect)) val overrideColor: Int?
BetterFoliage.blockTypes.grass.add(ctx.blockState) }
ctx.blockState.block.extendLayers()
object StandardGrassDiscovery : ConfigurableModelDiscovery() {
override val logger = BetterFoliage.logDetail
override val matchClasses: ConfigurableBlockMatcher get() = BetterFoliage.blockConfig.grassBlocks
override val modelTextures: List<ModelTextureList> get() = BetterFoliage.blockConfig.grassModels.modelList
override fun processModel(state: BlockState, textures: List<String>, atlas: Consumer<Identifier>): BlockRenderKey? {
val grassId = Identifier(textures[0])
log(" block state $state")
log(" texture $grassId")
return GrassBlockModel.Key(grassId, getAndLogColorOverride(grassId, Atlas.BLOCKS, BetterFoliage.config.shortGrass.saturationThreshold))
} }
} }
data class StandardGrassKey( class GrassBlockModel(val key: Key, wrapped: BakedModel) : WrappedBakedModel(wrapped), FabricBakedModel {
val sprite: ResourceLocation,
val tintIndex: Int,
val avgColor: Color,
val noConnect: Boolean
) : HalfBakedWrapperKey() {
override fun bake(ctx: ModelBakingContext, wrapped: SpecialRenderModel): SpecialRenderModel {
return StandardGrassModel(wrapped, this)
}
}
class GrassRenderData(
val isSnowed: Boolean,
val connectedGrassIdx: Int?,
val tuftIdx: Int?
): SpecialRenderData {
override fun canRenderInLayer(layer: RenderType) = when {
connectedGrassIdx != null && layer == Layers.connectedGrass -> true
tuftIdx != null && layer == Layers.tufts -> true
else -> false
}
}
class StandardGrassModel(
wrapped: SpecialRenderModel,
val 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)
override fun prepare(ctx: BlockCtx, random: Random): Any { val tuftLighting = grassTuftLighting(UP)
if (!Config.enabled) return Unit
override fun emitBlockQuads(blockView: ExtendedBlockView, 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 = !key.noConnect && Config.connectedGrass.enabled &&
(!isSnowed || Config.connectedGrass.snowEnabled) &&
BetterFoliage.blockTypes.run { stateBelow in grass || stateBelow in dirt }
return GrassRenderData( val isSnowed = stateAbove.material in SNOW_MATERIALS
isSnowed = isSnowed, val connected = BetterFoliage.config.connectedGrass.enabled &&
connectedGrassIdx = random.idxOrNull(if (isSnowed) snowFullBlockMeshes else fullBlock) { connected }, (!isSnowed || BetterFoliage.config.connectedGrass.snowEnabled) && (
tuftIdx = random.idxOrNull(if (isSnowed) tuftSnowed else tuftNormal) { BlockTags.DIRT_LIKE.contains(stateBelow.block) ||
Config.shortGrass.enabled(random) && BetterFoliage.modelReplacer.getTyped<GrassKey>(stateBelow) != null
Config.shortGrass.grassEnabled && )
(isAir || isSnowed)
val random = randomSupplier.get()
if (connected) {
context.meshConsumer().accept(if (isSnowed) snowFullBlockMeshes[random] else fullBlock[random])
} else {
super.emitBlockQuads(blockView, state, pos, randomSupplier, context)
}
if (BetterFoliage.config.shortGrass.enabled(random) && !ctx.isNeighborSolid(UP)) {
context.withLighting(tuftLighting) {
it.accept(if (isSnowed) tuftSnowed[random] else tuftNormal[random])
} }
) }
} }
override fun renderLayer(ctx: RenderCtxBase, data: Any, layer: RenderType) { data class Key(
if (data is GrassRenderData) { override val grassTopTexture: Identifier,
if (data.connectedGrassIdx != null) { override val overrideColor: Int?
if (layer == Layers.connectedGrass) ) : GrassKey {
ctx.renderQuads((if (data.isSnowed) snowFullBlockMeshes else fullBlock)[data.connectedGrassIdx]) override fun replace(model: BakedModel, state: BlockState) = GrassBlockModel(this, meshifyStandard(model, state))
} else {
super.renderLayer(ctx, data, layer)
}
if (data.tuftIdx != null && layer == Layers.tufts) {
ctx.vertexLighter = tuftLighting
ShadersModIntegration.grass(ctx, Config.shortGrass.shaderWind) {
ctx.renderQuads((if (data.isSnowed) tuftSnowed else tuftNormal)[data.tuftIdx])
}
}
} else super.renderLayer(ctx, data, layer)
} }
companion object { companion object {
val grassTuftSprites by SpriteSetDelegate(Atlas.BLOCKS) { idx -> val grassTuftSpritesNormal by SpriteSetDelegate(Atlas.BLOCKS) { idx ->
ResourceLocation(BetterFoliageMod.MOD_ID, "blocks/better_grass_long_$idx") Identifier(BetterFoliage.MOD_ID, "blocks/better_grass_long_$idx")
} }
val grassTuftShapes by BetterFoliage.modelManager.lazy { val grassTuftSpritesSnowed by SpriteSetDelegate(Atlas.BLOCKS) { idx ->
Config.shortGrass.let { tuftShapeSet(it.size, it.heightMin, it.heightMax, it.hOffset) } Identifier(BetterFoliage.MOD_ID, "blocks/better_grass_long_$idx")
} }
val grassTuftMeshesNormal = BetterFoliage.modelManager.lazyMap { key: StandardGrassKey -> val grassTuftShapes = LazyMap(BetterFoliage.modelReplacer) { key: GrassKey ->
tuftModelSet(grassTuftShapes, key.avgColor, key.tintIndex) { idx -> grassTuftSprites[randomI()] }.buildTufts() BetterFoliage.config.shortGrass.let { tuftShapeSet(it.size, it.heightMin, it.heightMax, it.hOffset) }
} }
val grassTuftMeshesSnowed = BetterFoliage.modelManager.lazyMap { key: StandardGrassKey -> val grassTuftMeshesNormal = LazyMap(BetterFoliage.modelReplacer) { key: GrassKey ->
tuftModelSet(grassTuftShapes, Color.white, -1) { idx -> grassTuftSprites[randomI()] }.buildTufts() tuftModelSet(grassTuftShapes[key], key.overrideColor) { idx -> grassTuftSpritesNormal[randomI()] }
.withOpposites()
.build(BlockRenderLayer.CUTOUT_MIPPED, flatLighting = false)
} }
val grassFullBlockMeshes = BetterFoliage.modelManager.lazyMap { key: StandardGrassKey -> val grassTuftMeshesSnowed = LazyMap(BetterFoliage.modelReplacer) { key: GrassKey ->
Array(64) { fullCubeTextured(key.sprite, key.tintIndex) } tuftModelSet(grassTuftShapes[key], Color.white.asInt) { idx -> grassTuftSpritesSnowed[randomI()] }
.withOpposites()
.build(BlockRenderLayer.CUTOUT_MIPPED, flatLighting = false)
} }
val snowFullBlockMeshes by BetterFoliage.modelManager.lazy { val grassFullBlockMeshes = LazyMap(BetterFoliage.modelReplacer) { key: GrassKey ->
Array(64) { fullCubeTextured(ResourceLocation("block/snow"), -1) } Array(64) { fullCubeTextured(key.grassTopTexture, key.overrideColor) }
}
val snowFullBlockMeshes by LazyInvalidatable(BetterFoliage.modelReplacer) {
Array(64) { fullCubeTextured(Identifier("block/snow"), Color.white.asInt) }
} }
} }
} }

View File

@@ -1,101 +1,116 @@
package mods.betterfoliage.render.block.vanilla package mods.betterfoliage.render.block.vanilla
import mods.betterfoliage.BetterFoliage import mods.betterfoliage.BetterFoliage
import mods.betterfoliage.BetterFoliageMod import mods.betterfoliage.chunk.BasicBlockCtx
import mods.betterfoliage.config.Config import mods.betterfoliage.render.SNOW_MATERIALS
import mods.betterfoliage.config.isSnow import mods.betterfoliage.render.lighting.withLighting
import mods.betterfoliage.integration.ShadersModIntegration import mods.betterfoliage.render.lighting.roundLeafLighting
import mods.betterfoliage.model.Color
import mods.betterfoliage.model.HalfBakedSpecialWrapper
import mods.betterfoliage.model.HalfBakedWrapperKey
import mods.betterfoliage.model.SpecialRenderModel
import mods.betterfoliage.model.SpriteSetDelegate
import mods.betterfoliage.model.crossModelsRaw
import mods.betterfoliage.model.crossModelsTextured
import mods.betterfoliage.render.lighting.RoundLeafLightingPreferUp
import mods.betterfoliage.render.particle.LeafBlockModel
import mods.betterfoliage.render.particle.LeafParticleKey
import mods.betterfoliage.render.particle.LeafParticleRegistry import mods.betterfoliage.render.particle.LeafParticleRegistry
import mods.betterfoliage.render.pipeline.RenderCtxBase
import mods.betterfoliage.resource.discovery.ModelBakingContext
import mods.betterfoliage.resource.discovery.ModelDiscoveryContext
import mods.betterfoliage.resource.discovery.ParametrizedModelDiscovery
import mods.betterfoliage.resource.generated.GeneratedLeafSprite
import mods.betterfoliage.util.Atlas import mods.betterfoliage.util.Atlas
import mods.betterfoliage.util.averageHSB import mods.betterfoliage.resource.discovery.*
import mods.betterfoliage.util.lazy import mods.betterfoliage.resource.generated.GeneratedLeafSprite
import mods.betterfoliage.util.lazyMap import mods.betterfoliage.resource.model.*
import mods.betterfoliage.util.brighten import mods.betterfoliage.util.*
import mods.betterfoliage.util.logTextureColor import net.fabricmc.fabric.api.renderer.v1.model.FabricBakedModel
import mods.betterfoliage.util.saturate import net.fabricmc.fabric.api.renderer.v1.render.RenderContext
import net.minecraft.client.renderer.RenderType import net.minecraft.block.BlockRenderLayer.CUTOUT_MIPPED
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.util.Identifier
import net.minecraft.util.math.BlockPos
import net.minecraft.util.math.Direction.*
import net.minecraft.world.ExtendedBlockView
import java.util.*
import java.util.function.Consumer
import java.util.function.Supplier
object StandardLeafDiscovery : ParametrizedModelDiscovery() { interface LeafKey : BlockRenderKey {
override fun processModel(ctx: ModelDiscoveryContext, params: Map<String, String>) { val roundLeafTexture: Identifier
val texture = params.location("texture") ?: return
val tint = params.int("tint") ?: -1
val color = Atlas.BLOCKS.file(texture).averageHSB.let {
detailLogger.logTextureColor(INFO, "leaf texture \"$texture\"", it)
it.brighten().asColor
}
val leafType = params["particle"] ?: "default"
val generated = GeneratedLeafSprite(texture, leafType)
.register(BetterFoliage.generatedPack)
.apply { ctx.sprites.add(this) }
detailLogger.log(INFO, " particle $leafType") /** Type of the leaf block (configurable by user). */
ctx.addReplacement(StandardLeafKey(generated, leafType, tint, color)) val leafType: String
BetterFoliage.blockTypes.leaf.add(ctx.blockState)
/** Average color of the round leaf texture. */
val overrideColor: Int?
}
object StandardLeafDiscovery : ConfigurableModelDiscovery() {
override val logger = BetterFoliage.logDetail
override val matchClasses: ConfigurableBlockMatcher get() = BetterFoliage.blockConfig.leafBlocks
override val modelTextures: List<ModelTextureList> get() = BetterFoliage.blockConfig.leafModels.modelList
override fun processModel(state: BlockState, textures: List<String>, atlas: Consumer<Identifier>) =
defaultRegisterLeaf(Identifier(textures[0]), atlas)
}
fun HasLogger.defaultRegisterLeaf(sprite: Identifier, atlas: Consumer<Identifier>): BlockRenderKey? {
val leafType = LeafParticleRegistry.typeMappings.getType(sprite) ?: "default"
val leafId = GeneratedLeafSprite(sprite, leafType).register(BetterFoliage.generatedPack)
atlas.accept(leafId)
log(" leaf texture $sprite")
log(" particle $leafType")
return NormalLeavesModel.Key(
leafId, leafType,
getAndLogColorOverride(leafId, Atlas.BLOCKS, BetterFoliage.config.shortGrass.saturationThreshold)
)
}
fun HasLogger.getAndLogColorOverride(sprite: Identifier, atlas: Atlas, threshold: Double): Int? {
val hsb = resourceManager.averageImageColorHSB(sprite, atlas)
return if (hsb.saturation >= threshold) {
log(" brightness ${hsb.brightness}")
log(" saturation ${hsb.saturation} >= ${threshold}, using texture color")
hsb.copy(brightness = 0.9f.coerceAtMost(hsb.brightness * 2.0f)).asColor
} else {
log(" saturation ${hsb.saturation} < ${threshold}, using block color")
null
} }
} }
data class StandardLeafKey( class NormalLeavesModel(val key: Key, wrapped: BakedModel) : WrappedBakedModel(wrapped), FabricBakedModel {
val roundLeafTexture: ResourceLocation,
override val leafType: String,
override val tintIndex: Int,
override val avgColor: Color
) : HalfBakedWrapperKey(), LeafParticleKey {
override fun bake(ctx: ModelBakingContext, wrapped: SpecialRenderModel): SpecialRenderModel {
return StandardLeafModel(wrapped, this)
}
}
class StandardLeafModel(
model: SpecialRenderModel,
override val key: StandardLeafKey
) : 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 renderLayer(ctx: RenderCtxBase, data: Any, layer: RenderType) { override fun emitBlockQuads(blockView: ExtendedBlockView, state: BlockState, pos: BlockPos, randomSupplier: Supplier<Random>, context: RenderContext) {
ShadersModIntegration.leaves(ctx, true) { super.emitBlockQuads(blockView, state, pos, randomSupplier, context)
super.renderLayer(ctx, data, layer) if (!BetterFoliage.config.enabled || !BetterFoliage.config.leaves.enabled) return
if (!Config.enabled || !Config.leaves.enabled) return
ctx.vertexLighter = RoundLeafLightingPreferUp val ctx = BasicBlockCtx(blockView, pos)
val leafIdx = ctx.random.nextInt(64) val stateAbove = ctx.state(UP)
ctx.renderQuads(leafNormal[leafIdx]) val isSnowed = stateAbove.material in SNOW_MATERIALS
if (Config.leaves.snowEnabled && 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])
} }
} }
data class Key(
override val roundLeafTexture: Identifier,
override val leafType: String,
override val overrideColor: Int?
) : LeafKey {
override fun replace(model: BakedModel, state: BlockState) = NormalLeavesModel(this, meshifyStandard(model, state, renderLayerOverride = CUTOUT_MIPPED))
}
companion object { companion object {
val leafSpritesSnowed by SpriteSetDelegate(Atlas.BLOCKS) { idx -> val leafSpritesSnowed by SpriteSetDelegate(Atlas.BLOCKS) { idx ->
ResourceLocation(BetterFoliageMod.MOD_ID, "blocks/better_leaves_snowed_$idx") Identifier(BetterFoliage.MOD_ID, "blocks/better_leaves_snowed_$idx")
} }
val leafModelsBase by BetterFoliage.modelManager.lazy { val leafModelsBase = LazyMap(BetterFoliage.modelReplacer) { key: LeafKey ->
Config.leaves.let { crossModelsRaw(64, it.size, it.hOffset, it.vOffset) } BetterFoliage.config.leaves.let { crossModelsRaw(64, it.size, it.hOffset, it.vOffset) }
} }
val leafModelsNormal = BetterFoliage.modelManager.lazyMap { key: StandardLeafKey -> val leafModelsNormal = LazyMap(BetterFoliage.modelReplacer) { key: LeafKey ->
// generated leaf textures naturally carry the color of their source textures crossModelsTextured(leafModelsBase[key], key.overrideColor, true) { Atlas.BLOCKS.atlas[key.roundLeafTexture]!! }
// no need to color the quad a second time
crossModelsTextured(leafModelsBase, Color.white, key.tintIndex, true) { key.roundLeafTexture }
} }
val leafModelsSnowed = BetterFoliage.modelManager.lazyMap { key: StandardLeafKey -> val leafModelsSnowed = LazyMap(BetterFoliage.modelReplacer) { key: LeafKey ->
crossModelsTextured(leafModelsBase, Color.white, -1, false) { leafSpritesSnowed[it].name } crossModelsTextured(leafModelsBase[key], Color.white.asInt, false) { leafSpritesSnowed[it] }
} }
} }
} }

View File

@@ -1,86 +1,66 @@
package mods.betterfoliage.render.block.vanilla package mods.betterfoliage.render.block.vanilla
import mods.betterfoliage.BetterFoliage import mods.betterfoliage.BetterFoliage
import mods.betterfoliage.BetterFoliageMod
import mods.betterfoliage.chunk.BlockCtx
import mods.betterfoliage.config.Config
import mods.betterfoliage.integration.ShadersModIntegration
import mods.betterfoliage.model.Color
import mods.betterfoliage.model.HalfBakedSpecialWrapper
import mods.betterfoliage.model.HalfBakedWrapperKey
import mods.betterfoliage.model.SpecialRenderModel
import mods.betterfoliage.model.SpriteSetDelegate
import mods.betterfoliage.model.buildTufts
import mods.betterfoliage.model.transform
import mods.betterfoliage.model.tuftModelSet
import mods.betterfoliage.model.tuftShapeSet
import mods.betterfoliage.render.pipeline.RenderCtxBase
import mods.betterfoliage.resource.discovery.ModelBakingContext
import mods.betterfoliage.resource.discovery.ModelDiscoveryContext
import mods.betterfoliage.resource.discovery.ParametrizedModelDiscovery
import mods.betterfoliage.util.Atlas import mods.betterfoliage.util.Atlas
import mods.betterfoliage.util.idx import mods.betterfoliage.resource.discovery.BlockRenderKey
import mods.betterfoliage.util.idxOrNull import mods.betterfoliage.resource.discovery.ModelDiscoveryBase
import mods.betterfoliage.util.lazy import mods.betterfoliage.resource.discovery.ModelDiscoveryContext
import net.minecraft.client.renderer.RenderType import mods.betterfoliage.resource.discovery.RenderKeyFactory
import net.minecraft.util.Direction.DOWN import mods.betterfoliage.resource.model.*
import net.minecraft.util.ResourceLocation import mods.betterfoliage.util.LazyInvalidatable
import java.util.Random import mods.betterfoliage.util.get
import mods.betterfoliage.util.semiRandom
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.client.render.model.BakedModel
import net.minecraft.util.Identifier
import net.minecraft.util.math.BlockPos
import net.minecraft.util.math.Direction.DOWN
import net.minecraft.world.ExtendedBlockView
import java.util.*
import java.util.function.Consumer
import java.util.function.Supplier
object StandardLilypadDiscovery : ParametrizedModelDiscovery() { object LilypadKey : BlockRenderKey {
override fun processModel(ctx: ModelDiscoveryContext, params: Map<String, String>) { override fun replace(model: BakedModel, state: BlockState) = LilypadModel(meshifyStandard(model, state))
ctx.addReplacement(StandardLilypadKey)
}
} }
object StandardLilypadKey : HalfBakedWrapperKey() { object LilyPadDiscovery : ModelDiscoveryBase() {
override fun bake(ctx: ModelBakingContext, wrapped: SpecialRenderModel) = StandardLilypadModel(wrapped) override val logger = BetterFoliage.logDetail
override fun processModel(ctx: ModelDiscoveryContext, atlas: Consumer<Identifier>) =
if (ctx.state.block == Blocks.LILY_PAD) LilypadKey else null
} }
class LilypadRenderData( class LilypadModel(wrapped: BakedModel) : WrappedBakedModel(wrapped) {
val rootIdx: Int, override fun emitBlockQuads(blockView: ExtendedBlockView, state: BlockState, pos: BlockPos, randomSupplier: Supplier<Random>, context: RenderContext) {
val flowerIdx: Int? super.emitBlockQuads(blockView, state, pos, randomSupplier, context)
) if (!BetterFoliage.config.enabled || !BetterFoliage.config.lilypad.enabled) return
class StandardLilypadModel( val random = randomSupplier.get()
wrapped: SpecialRenderModel context.meshConsumer().accept(lilypadRootModels[random])
) : HalfBakedSpecialWrapper(wrapped) { if (random.nextInt(64) < BetterFoliage.config.lilypad.population) {
context.meshConsumer().accept(lilypadFlowerModels[random])
override fun prepare(ctx: BlockCtx, random: Random): Any {
if (!Config.enabled) return Unit
return LilypadRenderData(
rootIdx = random.idx(lilypadRootModels),
flowerIdx = random.idxOrNull(lilypadFlowerModels) { Config.lilypad.enabled(random) }
)
}
override fun renderLayer(ctx: RenderCtxBase, data: Any, layer: RenderType) {
ctx.checkSides = false
super.renderLayer(ctx, data, layer)
if (data is LilypadRenderData) {
data.flowerIdx?.let { ctx.renderQuads(lilypadFlowerModels[it]) }
ShadersModIntegration.grass(ctx, Config.lilypad.shaderWind) {
ctx.renderQuads(lilypadRootModels[data.rootIdx])
}
} }
} }
companion object { companion object {
val lilypadRootSprites by SpriteSetDelegate(Atlas.BLOCKS) { idx -> val lilypadRootSprites by SpriteSetDelegate(Atlas.BLOCKS) { idx ->
ResourceLocation(BetterFoliageMod.MOD_ID, "blocks/better_lilypad_roots_$idx") Identifier(BetterFoliage.MOD_ID, "blocks/better_lilypad_roots_$idx")
} }
val lilypadFlowerSprites by SpriteSetDelegate(Atlas.BLOCKS) { idx -> val lilypadFlowerSprites by SpriteSetDelegate(Atlas.BLOCKS) { idx ->
ResourceLocation(BetterFoliageMod.MOD_ID, "blocks/better_lilypad_flower_$idx") Identifier(BetterFoliage.MOD_ID, "blocks/better_lilypad_flower_$idx")
} }
val lilypadRootModels by BetterFoliage.modelManager.lazy { val lilypadRootModels by LazyInvalidatable(BetterFoliage.modelReplacer) {
val shapes = tuftShapeSet(1.0, 1.0, 1.0, Config.lilypad.hOffset) val shapes = tuftShapeSet(1.0, 1.0, 1.0, BetterFoliage.config.lilypad.hOffset)
tuftModelSet(shapes, Color.white, -1) { lilypadRootSprites[it] } tuftModelSet(shapes, Color.white.asInt) { lilypadRootSprites[it] }
.transform { move(2.0 to DOWN) } .transform { move(2.0 to DOWN) }
.buildTufts() .buildTufts()
} }
val lilypadFlowerModels by BetterFoliage.modelManager.lazy { val lilypadFlowerModels by LazyInvalidatable(BetterFoliage.modelReplacer) {
val shapes = tuftShapeSet(0.5, 0.5, 0.5, Config.lilypad.hOffset) val shapes = tuftShapeSet(0.5, 0.5, 0.5, BetterFoliage.config.lilypad.hOffset)
tuftModelSet(shapes, Color.white, -1) { lilypadFlowerSprites[it] } tuftModelSet(shapes, Color.white.asInt) { lilypadFlowerSprites[it] }
.transform { move(1.0 to DOWN) } .transform { move(1.0 to DOWN) }
.buildTufts() .buildTufts()
} }

View File

@@ -1,99 +1,63 @@
package mods.betterfoliage.render.block.vanilla package mods.betterfoliage.render.block.vanilla
import mods.betterfoliage.BetterFoliage import mods.betterfoliage.BetterFoliage
import mods.betterfoliage.BetterFoliageMod import mods.betterfoliage.render.lighting.withLighting
import mods.betterfoliage.chunk.BlockCtx import mods.betterfoliage.render.lighting.grassTuftLighting
import mods.betterfoliage.config.Config
import mods.betterfoliage.model.Color
import mods.betterfoliage.model.HalfBakedSpecialWrapper
import mods.betterfoliage.model.HalfBakedWrapperKey
import mods.betterfoliage.model.SpecialRenderData
import mods.betterfoliage.model.SpecialRenderModel
import mods.betterfoliage.model.SpriteSetDelegate
import mods.betterfoliage.model.buildTufts
import mods.betterfoliage.model.tuftModelSet
import mods.betterfoliage.model.tuftShapeSet
import mods.betterfoliage.render.lighting.LightingPreferredFace
import mods.betterfoliage.render.pipeline.Layers
import mods.betterfoliage.render.pipeline.RenderCtxBase
import mods.betterfoliage.render.pipeline.extendLayers
import mods.betterfoliage.resource.discovery.ModelBakingContext
import mods.betterfoliage.resource.discovery.ModelDiscoveryContext
import mods.betterfoliage.resource.discovery.ParametrizedModelDiscovery
import mods.betterfoliage.util.Atlas import mods.betterfoliage.util.Atlas
import mods.betterfoliage.util.averageHSB import mods.betterfoliage.resource.discovery.BlockRenderKey
import mods.betterfoliage.util.idxOrNull import mods.betterfoliage.resource.discovery.ModelDiscoveryBase
import mods.betterfoliage.util.lazy import mods.betterfoliage.resource.discovery.ModelDiscoveryContext
import mods.betterfoliage.util.lazyMap import mods.betterfoliage.resource.discovery.RenderKeyFactory
import mods.betterfoliage.util.brighten import mods.betterfoliage.resource.model.*
import mods.betterfoliage.util.randomI import mods.betterfoliage.util.*
import net.minecraft.client.renderer.RenderType import net.fabricmc.fabric.api.renderer.v1.render.RenderContext
import net.minecraft.util.Direction import net.minecraft.block.BlockState
import net.minecraft.util.ResourceLocation import net.minecraft.block.Blocks
import java.util.Random import net.minecraft.client.render.model.BakedModel
import net.minecraft.util.Identifier
import net.minecraft.util.math.BlockPos
import net.minecraft.util.math.Direction.UP
import net.minecraft.world.ExtendedBlockView
import java.util.*
import java.util.function.Consumer
import java.util.function.Supplier
object StandardMyceliumDiscovery : ParametrizedModelDiscovery() { object MyceliumKey : BlockRenderKey {
override fun processModel(ctx: ModelDiscoveryContext, params: Map<String, String>) { override fun replace(model: BakedModel, state: BlockState) = MyceliumModel(meshifyStandard(model, state))
val texture = params.location("texture") ?: return
val tint = params.int("tint") ?: -1
val color = Atlas.BLOCKS.file(texture).averageHSB.brighten(multiplier = 1.5f).asColor
ctx.addReplacement(StandardMyceliumKey(texture, tint, color))
ctx.blockState.block.extendLayers()
}
} }
data class StandardMyceliumKey( object MyceliumDiscovery : ModelDiscoveryBase() {
val sprite: ResourceLocation, override val logger = BetterFoliage.logDetail
val tintIndex: Int, val myceliumBlocks = listOf(Blocks.MYCELIUM)
val avgColor: Color, override fun processModel(ctx: ModelDiscoveryContext, atlas: Consumer<Identifier>) =
) : HalfBakedWrapperKey() { if (ctx.state.block in myceliumBlocks) MyceliumKey else null
override fun bake(ctx: ModelBakingContext, wrapped: SpecialRenderModel): SpecialRenderModel {
return StandardMyceliumModel(wrapped, this)
}
} }
class MyceliumRenderData( class MyceliumModel(wrapped: BakedModel) : WrappedBakedModel(wrapped) {
val tuftIndex: Int?
) : SpecialRenderData {
override fun canRenderInLayer(layer: RenderType) = tuftIndex != null && layer == Layers.tufts
}
class StandardMyceliumModel( val tuftLighting = grassTuftLighting(UP)
wrapped: SpecialRenderModel,
key: StandardMyceliumKey
) : HalfBakedSpecialWrapper(wrapped) {
val tuftModels by myceliumTuftModels.delegate(key) override fun emitBlockQuads(blockView: ExtendedBlockView, state: BlockState, pos: BlockPos, randomSupplier: Supplier<Random>, context: RenderContext) {
val tuftLighting = LightingPreferredFace(Direction.UP) super.emitBlockQuads(blockView, state, pos, randomSupplier, context)
override fun prepare(ctx: BlockCtx, random: Random): Any { val random = randomSupplier.get()
if (!Config.enabled) return Unit if (BetterFoliage.config.enabled &&
return MyceliumRenderData( BetterFoliage.config.shortGrass.let { it.myceliumEnabled && random.nextInt(64) < it.population } &&
random.idxOrNull(tuftModels) { blockView.getBlockState(pos + UP.offset).isAir
Config.shortGrass.enabled(random) && ) {
Config.shortGrass.myceliumEnabled && context.withLighting(tuftLighting) {
ctx.state(Direction.UP).isAir(ctx.world, ctx.pos) it.accept(myceliumTuftModels[random])
} }
)
}
override fun renderLayer(ctx: RenderCtxBase, data: Any, layer: RenderType) {
super.renderLayer(ctx, data, layer)
if (data is MyceliumRenderData && data.tuftIndex != null && layer == Layers.tufts) {
ctx.vertexLighter = tuftLighting
ctx.renderQuads(tuftModels[data.tuftIndex])
} }
} }
companion object { companion object {
val myceliumTuftSprites by SpriteSetDelegate(Atlas.BLOCKS) { idx -> val myceliumTuftSprites by SpriteSetDelegate(Atlas.BLOCKS) { idx ->
ResourceLocation(BetterFoliageMod.MOD_ID, "blocks/better_mycel_$idx") Identifier(BetterFoliage.MOD_ID, "blocks/better_mycel_$idx")
} }
val myceliumTuftShapes by BetterFoliage.modelManager.lazy { val myceliumTuftModels by LazyInvalidatable(BetterFoliage.modelReplacer) {
Config.shortGrass.let { tuftShapeSet(it.size, it.heightMin, it.heightMax, it.hOffset) } val shapes = BetterFoliage.config.shortGrass.let { tuftShapeSet(it.size, it.heightMin, it.heightMax, it.hOffset) }
} tuftModelSet(shapes, Color.white.asInt) { idx -> myceliumTuftSprites[randomI()] }.buildTufts()
val myceliumTuftModels = BetterFoliage.modelManager.lazyMap { key: StandardMyceliumKey ->
tuftModelSet(myceliumTuftShapes, key.avgColor, key.tintIndex) { idx -> myceliumTuftSprites[randomI()] }.buildTufts()
} }
} }
} }

View File

@@ -1,86 +1,66 @@
package mods.betterfoliage.render.block.vanilla package mods.betterfoliage.render.block.vanilla
import mods.betterfoliage.BetterFoliage import mods.betterfoliage.BetterFoliage
import mods.betterfoliage.BetterFoliageMod import mods.betterfoliage.render.lighting.withLighting
import mods.betterfoliage.chunk.BlockCtx import mods.betterfoliage.render.lighting.grassTuftLighting
import mods.betterfoliage.config.Config
import mods.betterfoliage.model.Color
import mods.betterfoliage.model.HalfBakedSpecialWrapper
import mods.betterfoliage.model.HalfBakedWrapperKey
import mods.betterfoliage.model.SpecialRenderData
import mods.betterfoliage.model.SpecialRenderModel
import mods.betterfoliage.model.SpriteSetDelegate
import mods.betterfoliage.model.buildTufts
import mods.betterfoliage.model.transform
import mods.betterfoliage.model.tuftModelSet
import mods.betterfoliage.model.tuftShapeSet
import mods.betterfoliage.render.lighting.LightingPreferredFace
import mods.betterfoliage.render.pipeline.Layers
import mods.betterfoliage.render.pipeline.RenderCtxBase
import mods.betterfoliage.render.pipeline.extendLayers
import mods.betterfoliage.resource.discovery.ModelBakingContext
import mods.betterfoliage.resource.discovery.ModelDiscoveryContext
import mods.betterfoliage.resource.discovery.ParametrizedModelDiscovery
import mods.betterfoliage.util.Atlas import mods.betterfoliage.util.Atlas
import mods.betterfoliage.util.Rotation import mods.betterfoliage.resource.discovery.BlockRenderKey
import mods.betterfoliage.util.idxOrNull import mods.betterfoliage.resource.discovery.ModelDiscoveryBase
import mods.betterfoliage.util.lazy import mods.betterfoliage.resource.discovery.ModelDiscoveryContext
import mods.betterfoliage.util.randomI import mods.betterfoliage.resource.discovery.RenderKeyFactory
import net.minecraft.client.renderer.RenderType import mods.betterfoliage.resource.model.*
import net.minecraft.util.Direction.DOWN import mods.betterfoliage.util.*
import net.minecraft.util.ResourceLocation import net.fabricmc.fabric.api.renderer.v1.render.RenderContext
import java.util.Random import net.minecraft.block.BlockRenderLayer
import net.minecraft.block.BlockState
import net.minecraft.block.Blocks
import net.minecraft.client.render.model.BakedModel
import net.minecraft.util.Identifier
import net.minecraft.util.math.BlockPos
import net.minecraft.util.math.Direction.DOWN
import net.minecraft.world.ExtendedBlockView
import java.util.*
import java.util.function.Consumer
import java.util.function.Supplier
object StandardNetherrackDiscovery : ParametrizedModelDiscovery() { object NetherrackKey : BlockRenderKey {
override fun processModel(ctx: ModelDiscoveryContext, params: Map<String, String>) { override fun replace(model: BakedModel, state: BlockState) = NetherrackModel(meshifyStandard(model, state))
ctx.addReplacement(StandardNetherrackKey)
ctx.blockState.block.extendLayers()
}
} }
object StandardNetherrackKey : HalfBakedWrapperKey() { object NetherrackDiscovery : ModelDiscoveryBase() {
override fun bake(ctx: ModelBakingContext, wrapped: SpecialRenderModel) = StandardNetherrackModel(wrapped) override val logger = BetterFoliage.logDetail
val netherrackBlocks = listOf(Blocks.NETHERRACK)
override fun processModel(ctx: ModelDiscoveryContext, atlas: Consumer<Identifier>) =
if (ctx.state.block in netherrackBlocks) NetherrackKey else null
} }
class NetherrackRenderData( class NetherrackModel(wrapped: BakedModel) : WrappedBakedModel(wrapped) {
val tuftIndex: Int?
): SpecialRenderData {
override fun canRenderInLayer(layer: RenderType) = tuftIndex != null && layer == Layers.tufts
}
class StandardNetherrackModel( val tuftLighting = grassTuftLighting(DOWN)
wrapped: SpecialRenderModel
) : HalfBakedSpecialWrapper(wrapped) {
val tuftLighting = LightingPreferredFace(DOWN) override fun emitBlockQuads(blockView: ExtendedBlockView, state: BlockState, pos: BlockPos, randomSupplier: Supplier<Random>, context: RenderContext) {
super.emitBlockQuads(blockView, state, pos, randomSupplier, context)
override fun prepare(ctx: BlockCtx, random: Random): Any { if (BetterFoliage.config.enabled &&
if (!Config.enabled) return Unit BetterFoliage.config.netherrack.enabled &&
return NetherrackRenderData( blockView.getBlockState(pos + DOWN.offset).isAir
random.idxOrNull(netherrackTuftModels) { ) {
Config.netherrack.enabled && val random = randomSupplier.get()
ctx.isAir(DOWN) context.withLighting(tuftLighting) {
it.accept(netherrackTuftModels[random])
} }
)
}
override fun renderLayer(ctx: RenderCtxBase, data: Any, layer: RenderType) {
super.renderLayer(ctx, data, layer)
if (data is NetherrackRenderData && data.tuftIndex != null && layer == Layers.tufts) {
ctx.vertexLighter = tuftLighting
ctx.renderQuads(netherrackTuftModels[data.tuftIndex])
} }
} }
companion object { companion object {
val netherrackTuftSprites by SpriteSetDelegate(Atlas.BLOCKS) { idx -> val netherrackTuftSprites by SpriteSetDelegate(Atlas.BLOCKS) { idx ->
ResourceLocation(BetterFoliageMod.MOD_ID, "blocks/better_netherrack_$idx") Identifier(BetterFoliage.MOD_ID, "blocks/better_netherrack_$idx")
} }
val netherrackTuftModels by BetterFoliage.modelManager.lazy { val netherrackTuftModels by LazyInvalidatable(BetterFoliage.modelReplacer) {
val shapes = Config.netherrack.let { tuftShapeSet(it.size, it.heightMin, it.heightMax, it.hOffset) } val shapes = BetterFoliage.config.netherrack.let { tuftShapeSet(it.size, it.heightMin, it.heightMax, it.hOffset) }
tuftModelSet(shapes, Color.white, -1) { netherrackTuftSprites[randomI()] } tuftModelSet(shapes, Color.white.asInt) { netherrackTuftSprites[randomI()] }
.transform { rotate(Rotation.fromUp[DOWN.ordinal]).rotateUV(2) } .transform { rotate(Rotation.fromUp[DOWN.ordinal]).rotateUV(2) }
.buildTufts() .withOpposites()
.build(BlockRenderLayer.CUTOUT_MIPPED, flatLighting = false)
} }
} }
} }

View File

@@ -1,53 +1,42 @@
package mods.betterfoliage.render.block.vanilla package mods.betterfoliage.render.block.vanilla
import mods.betterfoliage.BetterFoliage import mods.betterfoliage.BetterFoliage
import mods.betterfoliage.config.ACCEPTED_ROUND_LOG_MATERIALS import mods.betterfoliage.render.column.*
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.ColumnMeshSet
import mods.betterfoliage.render.column.ColumnModelBase
import mods.betterfoliage.render.column.ColumnRenderLayer
import mods.betterfoliage.resource.discovery.ModelBakingContext
import mods.betterfoliage.resource.discovery.ModelBakingKey
import mods.betterfoliage.resource.discovery.ModelDiscoveryContext
import mods.betterfoliage.resource.discovery.ParametrizedModelDiscovery
import mods.betterfoliage.util.Atlas import mods.betterfoliage.util.Atlas
import mods.betterfoliage.util.lazyMap import mods.betterfoliage.resource.discovery.*
import mods.betterfoliage.resource.model.meshifyStandard
import mods.betterfoliage.util.LazyMap
import mods.betterfoliage.util.get
import mods.betterfoliage.util.tryDefault import mods.betterfoliage.util.tryDefault
import net.minecraft.block.BlockState import net.minecraft.block.BlockState
import net.minecraft.block.RotatedPillarBlock import net.minecraft.block.LogBlock
import net.minecraft.util.Direction.Axis import net.minecraft.client.render.model.BakedModel
import net.minecraft.util.ResourceLocation import net.minecraft.client.texture.SpriteAtlasTexture
import org.apache.logging.log4j.Level.INFO import net.minecraft.util.Identifier
import net.minecraft.util.math.Direction.Axis
interface RoundLogKey : ColumnBlockKey, ModelBakingKey { import java.util.function.Consumer
val barkSprite: ResourceLocation
val endSprite: ResourceLocation
}
object RoundLogOverlayLayer : ColumnRenderLayer() { object RoundLogOverlayLayer : ColumnRenderLayer() {
override fun getColumnKey(state: BlockState) = BetterFoliage.blockTypes.getTypedOrNull<ColumnBlockKey>(state) override fun getColumnKey(state: BlockState) = BetterFoliage.modelReplacer.getTyped<ColumnBlockKey>(state)
override val connectSolids: Boolean get() = Config.roundLogs.connectSolids override val connectSolids: Boolean get() = BetterFoliage.config.roundLogs.connectSolids
override val lenientConnect: Boolean get() = Config.roundLogs.lenientConnect override val lenientConnect: Boolean get() = BetterFoliage.config.roundLogs.lenientConnect
override val defaultToY: Boolean get() = Config.roundLogs.defaultY override val defaultToY: Boolean get() = BetterFoliage.config.roundLogs.defaultY
} }
object StandardRoundLogDiscovery : ParametrizedModelDiscovery() { object StandardLogDiscovery : ConfigurableModelDiscovery() {
override fun processModel(ctx: ModelDiscoveryContext, params: Map<String, String>) { override val logger = BetterFoliage.logDetail
val barkSprite = params.location("texture-side") ?: return override val matchClasses: ConfigurableBlockMatcher get() = BetterFoliage.blockConfig.logBlocks
val endSprite = params.location("texture-end") ?: return override val modelTextures: List<ModelTextureList> get() = BetterFoliage.blockConfig.logModels.modelList
val axis = getAxis(ctx.blockState)
detailLogger.log(INFO, " axis $axis, material ${ctx.blockState.material}") override fun processModel(state: BlockState, textures: List<String>, atlas: Consumer<Identifier>): BlockRenderKey? {
if (!Config.roundLogs.plantsOnly || ctx.blockState.material in ACCEPTED_ROUND_LOG_MATERIALS) val axis = getAxis(state)
ctx.addReplacement(StandardRoundLogKey(axis, barkSprite, endSprite)) log(" axis $axis")
return RoundLogModel.Key(axis, Identifier(textures[0]), Identifier(textures[1]))
} }
fun getAxis(state: BlockState): Axis? { fun getAxis(state: BlockState): Axis? {
val axis = tryDefault(null) { state.getValue(RotatedPillarBlock.AXIS).toString() } ?: val axis = tryDefault(null) { state.get(LogBlock.AXIS).toString() } ?:
state.values.entries.find { it.key.name.toLowerCase() == "axis" }?.value?.toString() state.entries.entries.find { it.key.getName().toLowerCase() == "axis" }?.value?.toString()
return when (axis) { return when (axis) {
"x" -> Axis.X "x" -> Axis.X
"y" -> Axis.Y "y" -> Axis.Y
@@ -57,30 +46,32 @@ object StandardRoundLogDiscovery : ParametrizedModelDiscovery() {
} }
} }
data class StandardRoundLogKey( interface RoundLogKey : ColumnBlockKey, BlockRenderKey {
override val axis: Axis?, val barkSprite: Identifier
override val barkSprite: ResourceLocation, val endSprite: Identifier
override val endSprite: ResourceLocation
) : RoundLogKey, HalfBakedWrapperKey() {
override fun bake(ctx: ModelBakingContext, wrapped: SpecialRenderModel) = StandardRoundLogModel(this, wrapped)
} }
class StandardRoundLogModel( class RoundLogModel(val key: Key, wrapped: BakedModel) : ColumnModelBase(wrapped) {
val key: StandardRoundLogKey, override val enabled: Boolean get() = BetterFoliage.config.enabled && BetterFoliage.config.roundLogs.enabled
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() = Config.roundLogs.connectPerpendicular override val connectPerpendicular: Boolean get() = BetterFoliage.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
data class Key(
override val axis: Axis?,
override val barkSprite: Identifier,
override val endSprite: Identifier
) : RoundLogKey {
override fun replace(model: BakedModel, state: BlockState) = RoundLogModel(this, meshifyStandard(model, state))
}
companion object { companion object {
val modelSets = BetterFoliage.modelManager.lazyMap { key: StandardRoundLogKey -> val modelSets = LazyMap(BetterFoliage.modelReplacer) { key: Key ->
val barkSprite = Atlas.BLOCKS[key.barkSprite] val barkSprite = Atlas.BLOCKS.atlas[key.barkSprite]!!
val endSprite = Atlas.BLOCKS[key.endSprite] val endSprite = Atlas.BLOCKS.atlas[key.endSprite]!!
Config.roundLogs.let { config -> BetterFoliage.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,
@@ -90,4 +81,4 @@ class StandardRoundLogModel(
} }
} }
} }
} }

View File

@@ -1,127 +1,94 @@
package mods.betterfoliage.render.block.vanilla package mods.betterfoliage.render.block.vanilla
import mods.betterfoliage.BetterFoliage import mods.betterfoliage.BetterFoliage
import mods.betterfoliage.BetterFoliageMod import mods.betterfoliage.chunk.CachedBlockCtx
import mods.betterfoliage.chunk.BlockCtx import mods.betterfoliage.render.SALTWATER_BIOMES
import mods.betterfoliage.config.Config import mods.betterfoliage.render.SAND_BLOCKS
import mods.betterfoliage.config.SALTWATER_BIOMES import mods.betterfoliage.render.lighting.withLighting
import mods.betterfoliage.model.Color import mods.betterfoliage.render.lighting.grassTuftLighting
import mods.betterfoliage.model.HalfBakedSpecialWrapper
import mods.betterfoliage.model.HalfBakedWrapperKey
import mods.betterfoliage.model.Quad
import mods.betterfoliage.model.SpecialRenderData
import mods.betterfoliage.model.SpecialRenderModel
import mods.betterfoliage.model.SpriteSetDelegate
import mods.betterfoliage.model.bake
import mods.betterfoliage.model.buildTufts
import mods.betterfoliage.model.transform
import mods.betterfoliage.model.tuftModelSet
import mods.betterfoliage.model.tuftShapeSet
import mods.betterfoliage.render.lighting.LightingPreferredFace
import mods.betterfoliage.render.pipeline.Layers
import mods.betterfoliage.render.pipeline.RenderCtxBase
import mods.betterfoliage.render.pipeline.extendLayers
import mods.betterfoliage.resource.discovery.ModelBakingContext
import mods.betterfoliage.resource.discovery.ModelDiscoveryContext
import mods.betterfoliage.resource.discovery.ParametrizedModelDiscovery
import mods.betterfoliage.util.Atlas import mods.betterfoliage.util.Atlas
import mods.betterfoliage.util.Rotation import mods.betterfoliage.resource.discovery.BlockRenderKey
import mods.betterfoliage.util.allDirections import mods.betterfoliage.resource.discovery.ModelDiscoveryBase
import mods.betterfoliage.util.get import mods.betterfoliage.resource.discovery.ModelDiscoveryContext
import mods.betterfoliage.util.idx import mods.betterfoliage.resource.model.*
import mods.betterfoliage.util.lazy import mods.betterfoliage.util.*
import mods.betterfoliage.util.mapArray import net.fabricmc.fabric.api.renderer.v1.render.RenderContext
import mods.betterfoliage.util.randomB import net.minecraft.block.BlockRenderLayer.CUTOUT_MIPPED
import mods.betterfoliage.util.randomD import net.minecraft.block.BlockState
import mods.betterfoliage.util.randomI import net.minecraft.block.Material
import net.minecraft.block.material.Material import net.minecraft.client.render.model.BakedModel
import net.minecraft.client.renderer.RenderType import net.minecraft.util.Identifier
import net.minecraft.util.Direction import net.minecraft.util.math.BlockPos
import net.minecraft.util.Direction.UP import net.minecraft.util.math.Direction.UP
import net.minecraft.util.ResourceLocation import net.minecraft.world.ExtendedBlockView
import java.util.Random import java.util.*
import java.util.function.Consumer
import java.util.function.Supplier
object StandardSandDiscovery : ParametrizedModelDiscovery() { object SandKey : BlockRenderKey {
override fun processModel(ctx: ModelDiscoveryContext, params: Map<String, String>) { override fun replace(model: BakedModel, state: BlockState) = SandModel(meshifyStandard(model, state))
ctx.addReplacement(StandardSandKey)
ctx.blockState.block.extendLayers()
}
} }
object StandardSandKey : HalfBakedWrapperKey() { object SandDiscovery : ModelDiscoveryBase() {
override fun bake(ctx: ModelBakingContext, wrapped: SpecialRenderModel) = StandardSandModel(wrapped) override val logger = BetterFoliage.logDetail
override fun processModel(ctx: ModelDiscoveryContext, atlas: Consumer<Identifier>) =
if (ctx.state.block in SAND_BLOCKS) SandKey else null
} }
class SandRenderData( class SandModel(wrapped: BakedModel) : WrappedBakedModel(wrapped) {
val crustIdx: Array<Int?>,
val tuftIdx: Array<Int?>
): SpecialRenderData {
override fun canRenderInLayer(layer: RenderType) = when {
(crustIdx.any { it != null } || tuftIdx.any { it != null }) && layer == Layers.coral -> true
else -> false
}
}
class StandardSandModel( val coralLighting = allDirections.map { grassTuftLighting(it) }.toTypedArray()
wrapped: SpecialRenderModel
) : HalfBakedSpecialWrapper(wrapped) {
val coralLighting = Direction.values().mapArray { LightingPreferredFace(it) }
override fun prepare(ctx: BlockCtx, random: Random): Any { override fun emitBlockQuads(blockView: ExtendedBlockView, state: BlockState, pos: BlockPos, randomSupplier: Supplier<Random>, context: RenderContext) {
if (!Config.enabled) return Unit super.emitBlockQuads(blockView, state, pos, randomSupplier, context)
if (!Config.coral.enabled(random)) return Unit
if (ctx.biome?.biomeCategory !in SALTWATER_BIOMES) return Unit
val crustIdx = Array<Int?>(6) { null } val ctx = CachedBlockCtx(blockView, pos)
val tuftIdx = Array<Int?>(6) { null }
allDirections.filter { random.nextInt(64) < Config.coral.chance }.forEach { face -> 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) { if (isDeepWater) context.withLighting(coralLighting[face]) {
crustIdx[face.ordinal] = random.idx(coralCrustModels) it.accept(coralCrustModels[face][random])
tuftIdx[face.ordinal] = random.idx(coralTuftModels) it.accept(coralTuftModels[face][random])
}
}
return SandRenderData(crustIdx, tuftIdx)
}
override fun renderLayer(ctx: RenderCtxBase, data: Any, layer: RenderType) {
super.renderLayer(ctx, data, layer)
if (data is SandRenderData && layer == Layers.coral) {
for (face in 0 until 6) {
ctx.vertexLighter = coralLighting[face]
data.crustIdx[face]?.let { ctx.renderQuads(coralCrustModels[face][it]) }
data.tuftIdx[face]?.let { ctx.renderQuads(coralTuftModels[face][it]) }
} }
} }
} }
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 ->
ResourceLocation(BetterFoliageMod.MOD_ID, "blocks/better_coral_$idx") Identifier(BetterFoliage.MOD_ID, "blocks/better_coral_$idx")
} }
val coralCrustSprites by SpriteSetDelegate(Atlas.BLOCKS) { idx -> val coralCrustSprites by SpriteSetDelegate(Atlas.BLOCKS) { idx ->
ResourceLocation(BetterFoliageMod.MOD_ID, "blocks/better_crust_$idx") Identifier(BetterFoliage.MOD_ID, "blocks/better_crust_$idx")
} }
val coralTuftModels by BetterFoliage.modelManager.lazy { val coralTuftModels by LazyInvalidatable(BetterFoliage.modelReplacer) {
val shapes = Config.coral.let { tuftShapeSet(it.size, 1.0, 1.0, it.hOffset) } val shapes = BetterFoliage.config.coral.let { tuftShapeSet(it.size, 1.0, 1.0, it.hOffset) }
allDirections.mapArray { face -> allDirections.map { face ->
tuftModelSet(shapes, Color.white, -1) { coralTuftSprites[randomI()] } tuftModelSet(shapes, Color.white.asInt) { coralTuftSprites[randomI()] }
.transform { rotate(Rotation.fromUp[face]) } .transform { rotate(Rotation.fromUp[face]) }
.buildTufts() .withOpposites()
} .build(CUTOUT_MIPPED)
}.toTypedArray()
} }
val coralCrustModels by BetterFoliage.modelManager.lazy { val coralCrustModels by LazyInvalidatable(BetterFoliage.modelReplacer) {
allDirections.map { face -> allDirections.map { face ->
Array(64) { idx -> Array(64) { idx ->
listOf( listOf(horizontalRectangle(x1 = -0.5, x2 = 0.5, z1 = -0.5, z2 = 0.5, y = 0.0)
Quad.horizontalRectangle(x1 = -0.5, x2 = 0.5, z1 = -0.5, z2 = 0.5, y = 0.0) .scale(BetterFoliage.config.coral.crustSize)
.scale(Config.coral.crustSize) .move(0.5 + randomD(0.01, BetterFoliage.config.coral.vOffset) to UP)
.move(0.5 + randomD(0.01, Config.coral.vOffset) to UP) .rotate(Rotation.fromUp[face])
.rotate(Rotation.fromUp[face]) .mirrorUV(randomB(), randomB()).rotateUV(randomI(max = 4))
.mirrorUV(randomB(), randomB()).rotateUV(randomI(max = 4)) .sprite(coralCrustSprites[idx]).colorAndIndex(null)
.sprite(coralCrustSprites[idx]).colorAndIndex(null) ).build(CUTOUT_MIPPED)
).bake(applyDiffuseLighting = false)
} }
}.toTypedArray() }.toTypedArray()
} }

View File

@@ -1,22 +1,14 @@
package mods.betterfoliage.render.column package mods.betterfoliage.render.column
import mods.betterfoliage.config.Config import mods.betterfoliage.BetterFoliage
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.*
import mods.betterfoliage.render.column.ColumnLayerData.SpecialRender.QuadrantType.LARGE_RADIUS import mods.betterfoliage.resource.model.*
import mods.betterfoliage.render.column.ColumnLayerData.SpecialRender.QuadrantType.SMALL_RADIUS
import mods.betterfoliage.render.column.ColumnLayerData.SpecialRender.QuadrantType.SQUARE
import mods.betterfoliage.util.Double3 import mods.betterfoliage.util.Double3
import mods.betterfoliage.util.Rotation import mods.betterfoliage.util.Rotation
import net.minecraft.client.renderer.texture.TextureAtlasSprite import net.minecraft.block.BlockRenderLayer.SOLID
import net.minecraft.util.Direction.Axis import net.minecraft.client.texture.Sprite
import net.minecraft.util.Direction.EAST import net.minecraft.util.math.Direction.*
import net.minecraft.util.Direction.SOUTH
import net.minecraft.util.Direction.UP
/** /**
* Collection of dynamically generated meshes used to render rounded columns. * Collection of dynamically generated meshes used to render rounded columns.
@@ -26,20 +18,20 @@ class ColumnMeshSet(
radiusLarge: Double, radiusLarge: Double,
zProtection: Double, zProtection: Double,
val axis: Axis, val axis: Axis,
val spriteLeft: TextureAtlasSprite, val spriteLeft: Sprite,
val spriteRight: TextureAtlasSprite, val spriteRight: Sprite,
val spriteTop: TextureAtlasSprite, val spriteTop: Sprite,
val spriteBottom: TextureAtlasSprite val spriteBottom: Sprite
) { ) {
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
Quad.verticalRectangle(0.0, 0.5, 0.5 - radius, 0.5, yBottom, yTop).clampUV(minU = 0.0, maxU = 0.5 - radius), verticalRectangle(0.0, 0.5, 0.5 - radius, 0.5, yBottom, yTop).clampUV(minU = 0.0, maxU = 0.5 - radius),
Quad.verticalRectangle(0.5 - radius, 0.5, 0.5 - halfRadius, 0.5 - halfRadius, yBottom, yTop).clampUV(minU = 0.5 - radius), 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
Quad.verticalRectangle(0.5 - halfRadius, 0.5 - halfRadius, 0.5, 0.5 - radius, yBottom, yTop).clampUV(maxU = radius - 0.5), verticalRectangle(0.5 - halfRadius, 0.5 - halfRadius, 0.5, 0.5 - radius, yBottom, yTop).clampUV(maxU = radius - 0.5),
Quad.verticalRectangle(0.5, 0.5 - radius, 0.5, 0.0, yBottom, yTop).clampUV(minU = radius - 0.5, maxU = 0.0) verticalRectangle(0.5, 0.5 - radius, 0.5, 0.0, yBottom, yTop).clampUV(minU = radius - 0.5, maxU = 0.0)
) )
} }
@@ -53,8 +45,8 @@ class ColumnMeshSet(
} }
protected fun sideSquare(yBottom: Double, yTop: Double) = listOf( protected fun sideSquare(yBottom: Double, yTop: Double) = listOf(
Quad.verticalRectangle(0.0, 0.5, 0.5, 0.5, yBottom, yTop).clampUV(minU = 0.0), verticalRectangle(0.0, 0.5, 0.5, 0.5, yBottom, yTop).clampUV(minU = 0.0),
Quad.verticalRectangle(0.5, 0.5, 0.5, 0.0, yBottom, yTop).clampUV(maxU = 0.0) 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 ->
@@ -66,18 +58,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 Config.nVidia) 0 else 1) } .map { it.cycleVertices(if (isBottom xor BetterFoliage.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) } .map { it.sprite(if (isBottom) spriteBottom else spriteTop).colorAndIndex(Color.white.asInt) }
.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(
Quad.horizontalRectangle(x1 = 0.0, x2 = 0.5, z1 = 0.0, z2 = 0.5, y = y).clampUV(minU = 0.0, minV = 0.0) 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) .sprite(if (isBottom) spriteBottom else spriteTop).colorAndIndex(Color.white.asInt)
.let { if (isBottom) it.flipped else it } .let { if (isBottom) it.flipped else it }
) )
} }
@@ -92,9 +84,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) } this.map { it.rotate(rotation).colorAndIndex(Color.white.asInt) }
.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) }
.bake(false) .build(SOLID, flatLighting = false)
} }
companion object { companion object {
@@ -122,13 +114,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).bake(false) val lidTopSquare = lidSquare(0.5, false).build(SOLID, flatLighting = false)
val lidTopRoundSmall = lidRounded(radiusSmall, 0.5, false).bake(false) val lidTopRoundSmall = lidRounded(radiusSmall, 0.5, false).build(SOLID, flatLighting = false)
val lidTopRoundLarge = lidRounded(radiusLarge, 0.5, false).bake(false) val lidTopRoundLarge = lidRounded(radiusLarge, 0.5, false).build(SOLID, flatLighting = false)
val lidBottomSquare = lidSquare(-0.5, true).bake(false) val lidBottomSquare = lidSquare(-0.5, true).build(SOLID, flatLighting = false)
val lidBottomRoundSmall = lidRounded(radiusSmall, -0.5, true).bake(false) val lidBottomRoundSmall = lidRounded(radiusSmall, -0.5, true).build(SOLID, flatLighting = false)
val lidBottomRoundLarge = lidRounded(radiusLarge, -0.5, true).bake(false) val lidBottomRoundLarge = lidRounded(radiusLarge, -0.5, true).build(SOLID, flatLighting = 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,49 +1,43 @@
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.NONSOLID import mods.betterfoliage.render.column.ColumnLayerData.SpecialRender.BlockType.*
import mods.betterfoliage.render.column.ColumnLayerData.SpecialRender.BlockType.PARALLEL import mods.betterfoliage.render.column.ColumnLayerData.SpecialRender.QuadrantType.*
import mods.betterfoliage.render.column.ColumnLayerData.SpecialRender.BlockType.PERPENDICULAR import mods.betterfoliage.resource.model.WrappedBakedModel
import mods.betterfoliage.render.column.ColumnLayerData.SpecialRender.QuadrantType.INVISIBLE import net.fabricmc.fabric.api.renderer.v1.render.RenderContext
import mods.betterfoliage.render.column.ColumnLayerData.SpecialRender.QuadrantType.LARGE_RADIUS import net.minecraft.block.BlockState
import mods.betterfoliage.render.column.ColumnLayerData.SpecialRender.QuadrantType.SMALL_RADIUS import net.minecraft.client.render.model.BakedModel
import mods.betterfoliage.render.column.ColumnLayerData.SpecialRender.QuadrantType.SQUARE import net.minecraft.util.math.BlockPos
import mods.betterfoliage.render.lighting.ColumnLighting import net.minecraft.util.math.Direction.Axis
import mods.betterfoliage.render.pipeline.RenderCtxBase import net.minecraft.world.ExtendedBlockView
import net.minecraft.client.renderer.RenderType 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 renderLayer(ctx: RenderCtxBase, data: Any, layer: RenderType) { override fun emitBlockQuads(blockView: ExtendedBlockView, state: BlockState, pos: BlockPos, randomSupplier: Supplier<Random>, context: RenderContext) {
if (!enabled) return super.renderLayer(ctx, data, layer) val ctx = CachedBlockCtx(blockView, pos)
val roundLog = ChunkOverlayManager.get(overlayLayer, ctx)
val roundLog = overlayLayer[ctx]
when(roundLog) { when(roundLog) {
ColumnLayerData.SkipRender -> return ColumnLayerData.SkipRender -> return
NormalRender -> return super.renderLayer(ctx, data, layer) NormalRender -> return super.emitBlockQuads(blockView, state, pos, randomSupplier, context)
ColumnLayerData.ResolveError, null -> { ColumnLayerData.ResolveError, null -> {
return super.renderLayer(ctx, data, layer) return super.emitBlockQuads(blockView, state, pos, randomSupplier, context)
} }
} }
// 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.renderLayer(ctx, data, layer) return super.emitBlockQuads(blockView, state, pos, randomSupplier, context)
} }
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 ->
@@ -62,8 +56,8 @@ abstract class ColumnModelBase(
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
} }
@@ -105,10 +99,10 @@ abstract class ColumnModelBase(
} }
// render // render
sideMesh?.let { ctx.renderQuads(it) } sideMesh?.let { context.meshConsumer().accept(it) }
upMesh?.let { ctx.renderQuads(it) } upMesh?.let { context.meshConsumer().accept(it) }
downMesh?.let { ctx.renderQuads(it) } downMesh?.let { context.meshConsumer().accept(it) }
}
}
} }
} }

View File

@@ -1,29 +1,19 @@
package mods.betterfoliage.render.column package mods.betterfoliage.render.column
import mods.betterfoliage.chunk.BlockCtx import mods.betterfoliage.BetterFoliage
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.config.Config import mods.betterfoliage.render.column.ColumnLayerData.SpecialRender.BlockType.*
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.PERPENDICULAR
import mods.betterfoliage.render.column.ColumnLayerData.SpecialRender.BlockType.SOLID
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.*
import mods.betterfoliage.render.column.ColumnLayerData.SpecialRender.QuadrantType.LARGE_RADIUS import mods.betterfoliage.chunk.BlockCtx
import mods.betterfoliage.render.column.ColumnLayerData.SpecialRender.QuadrantType.SMALL_RADIUS import mods.betterfoliage.util.*
import mods.betterfoliage.render.column.ColumnLayerData.SpecialRender.QuadrantType.SQUARE
import mods.betterfoliage.util.Int3
import mods.betterfoliage.util.Rotation
import mods.betterfoliage.util.allDirections
import mods.betterfoliage.util.face
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.world.IBlockDisplayReader import net.minecraft.util.math.Direction.Axis
import net.minecraft.util.math.Direction.AxisDirection
import net.minecraft.world.ExtendedBlockView
/** Index of SOUTH-EAST quadrant. */ /** Index of SOUTH-EAST quadrant. */
const val SE = 0 const val SE = 0
@@ -73,7 +63,7 @@ sealed class ColumnLayerData {
object ResolveError : ColumnLayerData() object ResolveError : ColumnLayerData()
} }
abstract class ColumnRenderLayer : ChunkOverlayLayer<ColumnLayerData>() { abstract class ColumnRenderLayer : ChunkOverlayLayer<ColumnLayerData> {
abstract val connectSolids: Boolean abstract val connectSolids: Boolean
abstract val lenientConnect: Boolean abstract val lenientConnect: Boolean
@@ -83,20 +73,21 @@ 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: IBlockDisplayReader, pos: BlockPos) { override fun onBlockUpdate(world: ExtendedBlockView, pos: BlockPos) {
allNeighborOffsets.forEach { offset -> remove(world, pos + offset) } allNeighborOffsets.forEach { offset -> ChunkOverlayManager.clear(world.dimType, this, pos + offset) }
} }
override fun calculate(ctx: BlockCtx): ColumnLayerData { override fun calculate(ctx: BlockCtx): ColumnLayerData {
// TODO detect round logs if (allDirections.all { ctx.offset(it).isNormalCube }) return ColumnLayerData.SkipRender
if (allDirections.all { dir -> ctx.offset(dir).let { it.isFullBlock } }) return ColumnLayerData.SkipRender // val columnTextures = registry[ctx] ?: return ColumnLayerData.ResolveError
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 Direction.AxisDirection.POSITIVE).face.ordinal] val baseRotation = Rotation.fromUp[(logAxis to 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))
@@ -185,9 +176,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).isFullBlock) SOLID else NONSOLID if (offset(offsetRot).isNormalCube) SOLID else NONSOLID
} else { } else {
(key.axis ?: if (Config.roundLogs.defaultY) Axis.Y else null)?.let { (key.axis ?: if (BetterFoliage.config.roundLogs.defaultY) Axis.Y else null)?.let {
if (it == axis) PARALLEL else PERPENDICULAR if (it == axis) PARALLEL else PERPENDICULAR
} ?: SOLID } ?: SOLID
} }

View File

@@ -1,138 +0,0 @@
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

@@ -0,0 +1,140 @@
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

@@ -1,10 +0,0 @@
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

@@ -0,0 +1,53 @@
package mods.betterfoliage.render.lighting
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.MinecraftClient
import net.minecraft.client.render.model.BakedModel
import net.minecraft.util.math.BlockPos
import net.minecraft.world.ExtendedBlockView
import java.util.*
import java.util.function.Consumer
import java.util.function.Supplier
val MODIFIED_CONSUMER_POOL = ThreadLocal<ModifiedTerrainMeshConsumer>()
fun TerrainMeshConsumer.modified() = MODIFIED_CONSUMER_POOL.get() ?: let {
val blockInfo = reflectField<TerrainBlockRenderInfo>("blockInfo")
val chunkInfo = reflectField<ChunkRenderInfo>("chunkInfo")
val aoCalc = reflectField<AoCalculator>("aoCalc")
val transform = reflectField<RenderContext.QuadTransform>("transform")
ModifiedTerrainMeshConsumer(blockInfo, chunkInfo, aoCalc, transform)
}.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: ExtendedBlockView, state: BlockState, pos: BlockPos, randomSupplier: Supplier<Random>, context: RenderContext) = when(this) {
is TerrainRenderContext -> {
val blockInfo = reflectField<TerrainBlockRenderInfo>("blockInfo")
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 TerrainMeshConsumer).modified()
consumer.clearLighting()
consumer.lighter = lighter
func(consumer)
consumer.lighter = null
}
else -> func(meshConsumer())
}

View File

@@ -1,152 +0,0 @@
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

@@ -1,210 +0,0 @@
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,109 +0,0 @@
package mods.betterfoliage.render.particle
import com.mojang.blaze3d.vertex.IVertexBuilder
import mods.betterfoliage.model.Color
import mods.betterfoliage.model.HSB
import mods.betterfoliage.util.Double3
import net.minecraft.client.Minecraft
import net.minecraft.client.particle.SpriteTexturedParticle
import net.minecraft.client.renderer.ActiveRenderInfo
import net.minecraft.client.renderer.texture.TextureAtlasSprite
import net.minecraft.client.world.ClientWorld
import net.minecraft.util.math.MathHelper
import net.minecraft.util.math.vector.Vector3f
abstract class AbstractParticle(world: ClientWorld, x: Double, y: Double, z: Double) : SpriteTexturedParticle(world, x, y, z) {
companion object {
// @JvmStatic val sin = Array(64) { idx -> Math.sin(PI2 / 64.0 * idx) }
// @JvmStatic val cos = Array(64) { idx -> Math.cos(PI2 / 64.0 * idx) }
}
val billboardRot = Pair(Double3.zero, Double3.zero)
val currentPos = Double3.zero
val prevPos = Double3.zero
val velocity = Double3.zero
override fun tick() {
super.tick()
currentPos.setTo(x, y, z)
prevPos.setTo(xo, yo, zo)
velocity.setTo(xd, yd, zd)
update()
x = currentPos.x; y = currentPos.y; z = currentPos.z;
xd = velocity.x; yd = velocity.y; zd = velocity.z;
}
/** Update particle on world tick. */
abstract fun update()
/** True if the particle is renderable. */
abstract val isValid: Boolean
/** Add the particle to the effect renderer if it is valid. */
fun addIfValid() { if (isValid) Minecraft.getInstance().particleEngine.add(this) }
override fun render(vertexBuilder: IVertexBuilder, camera: ActiveRenderInfo, tickDelta: Float) {
super.render(vertexBuilder, camera, tickDelta)
}
/**
* Render a particle quad.
*
* @param[tessellator] the [Tessellator] instance to use
* @param[tickDelta] partial tick time
* @param[currentPos] render position
* @param[prevPos] previous tick position for interpolation
* @param[size] particle size
* @param[currentAngle] viewpoint-dependent particle rotation (64 steps)
* @param[sprite] particle texture
* @param[isMirrored] mirror particle texture along V-axis
* @param[alpha] aplha blending
*/
fun renderParticleQuad(vertexConsumer: IVertexBuilder,
camera: ActiveRenderInfo,
tickDelta: Float,
currentPos: Double3 = this.currentPos,
prevPos: Double3 = this.prevPos,
size: Double = quadSize.toDouble(),
currentAngle: Float = this.roll,
prevAngle: Float = this.oRoll,
sprite: TextureAtlasSprite = this.sprite,
alpha: Float = this.alpha) {
val center = Double3.lerp(tickDelta.toDouble(), prevPos, currentPos)
val angle = MathHelper.lerp(tickDelta, prevAngle, currentAngle)
val rotation = camera.rotation().copy().apply { mul(Vector3f.ZP.rotation(angle)) }
val lightmapCoord = getLightColor(tickDelta)
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)
).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
.vertex(vertex.x, vertex.y, vertex.z).uv(u, v)
.color(rCol, gCol, bCol, alpha).uv2(lightmapCoord)
.endVertex()
renderVertex(coords[0], sprite.u1, sprite.v1)
renderVertex(coords[1], sprite.u1, sprite.v0)
renderVertex(coords[2], sprite.u0, sprite.v0)
renderVertex(coords[3], sprite.u0, sprite.v1)
}
fun setColor(color: Color) {
rCol = color.red / 256.0f
gCol = color.green / 256.0f
bCol = color.blue / 256.0f
}
/**
* Set particle color to the "stronger" of the given colors, determined by higher color saturation
*/
fun setColor(color1: Color, color2: Color) =
setColor(if (color1.asHSB.saturation > color2.asHSB.saturation) color1 else color2)
}

View File

@@ -1,30 +1,24 @@
package mods.betterfoliage.render.particle package mods.betterfoliage.render.particle
import mods.betterfoliage.config.Config import mods.betterfoliage.BetterFoliage
import mods.betterfoliage.model.Color import mods.betterfoliage.ClientWorldLoadCallback
import mods.betterfoliage.util.Double3 import mods.betterfoliage.render.AbstractParticle
import mods.betterfoliage.util.PI2 import mods.betterfoliage.render.block.vanilla.LeafKey
import mods.betterfoliage.util.minmax import mods.betterfoliage.util.*
import mods.betterfoliage.util.randomB import net.fabricmc.fabric.api.event.world.WorldTickCallback
import mods.betterfoliage.util.randomD import net.minecraft.client.MinecraftClient
import mods.betterfoliage.util.randomF import net.minecraft.client.render.BufferBuilder
import mods.betterfoliage.util.randomI
import net.minecraft.client.particle.IParticleRenderType
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 java.util.*
import net.minecraftforge.event.TickEvent
import net.minecraftforge.eventbus.api.SubscribeEvent
import net.minecraftforge.fml.LogicalSide
import java.util.Random
import kotlin.math.abs import kotlin.math.abs
import kotlin.math.cos import kotlin.math.cos
import kotlin.math.sin import kotlin.math.sin
class FallingLeafParticle( class FallingLeafParticle(
world: ClientWorld, pos: BlockPos, leaf: LeafParticleKey, blockColor: Int, random: Random world: World, pos: BlockPos, leafKey: LeafKey
) : AbstractParticle( ) : AbstractParticle(
world, pos.x.toDouble() + 0.5, pos.y.toDouble(), pos.z.toDouble() + 0.5 world, pos.x.toDouble() + 0.5, pos.y.toDouble(), pos.z.toDouble() + 0.5
) { ) {
@@ -33,70 +27,73 @@ class FallingLeafParticle(
@JvmStatic val biomeBrightnessMultiplier = 0.5f @JvmStatic val biomeBrightnessMultiplier = 0.5f
} }
var rotationSpeed = random.randomF(min = PI2 / 80.0, max = PI2 / 50.0) var rotationSpeed = randomF(min = PI2 / 80.0, max = PI2 / 50.0)
var rotPositive = true
val isMirrored = randomB() val isMirrored = randomB()
var wasCollided = false var wasCollided = false
init { init {
roll = random.randomF(max = PI2) angle = randomF(max = PI2)
oRoll = roll - rotationSpeed maxAge = MathHelper.floor(randomD(0.6, 1.0) * BetterFoliage.config.fallingLeaves.lifetime * 20.0)
velocityY = -BetterFoliage.config.fallingLeaves.speed
lifetime = MathHelper.floor(randomD(0.6, 1.0) * Config.fallingLeaves.lifetime * 20.0) scale = BetterFoliage.config.fallingLeaves.size.toFloat() * 0.1f
yd = -Config.fallingLeaves.speed
quadSize = Config.fallingLeaves.size.toFloat() * 0.1f val state = world.getBlockState(pos)
if (leaf.tintIndex == -1) setColor(leaf.avgColor) else setColor(leaf.avgColor, Color(blockColor)) val blockColor = MinecraftClient.getInstance().blockColorMap.getColorMultiplier(state, world, pos, 0)
sprite = LeafParticleRegistry[leaf.leafType][randomI(max = 1024)] sprite = LeafParticleRegistry[leafKey.leafType][randomI(max = 1024)]
setParticleColor(leafKey.overrideColor, blockColor)
} }
override val isValid: Boolean get() = (sprite != null) override val isValid: Boolean get() = (sprite != null)
override fun update() { override fun update() {
if (random.nextFloat() > 0.95f) rotationSpeed *= -1.0f if (randomF() > 0.95f) rotPositive = !rotPositive
if (age > lifetime - 20) alpha = 0.05f * (lifetime - age) // if (age > maxAge - 20) colorAlpha = 0.05f * (maxAge - age)
if (onGround || wasCollided) { if (onGround || wasCollided) {
velocity.setTo(0.0, 0.0, 0.0) velocity.setTo(0.0, 0.0, 0.0)
if (!wasCollided) { if (!wasCollided) {
age = age.coerceAtLeast(lifetime - 20) age = age.coerceAtLeast(maxAge - 20)
wasCollided = true wasCollided = true
} }
} else { } else {
val cosRotation = cos(roll).toDouble(); val sinRotation = sin(roll).toDouble() val cosRotation = cos(angle).toDouble(); val sinRotation = sin(angle).toDouble()
velocity.setTo(cosRotation, 0.0, sinRotation).mul(Config.fallingLeaves.perturb) velocity.setTo(cosRotation, 0.0, sinRotation).mul(BetterFoliage.config.fallingLeaves.perturb)
.add(LeafWindTracker.current).add(0.0, -1.0, 0.0).mul(Config.fallingLeaves.speed) .add(LeafWindTracker.current).add(0.0, -1.0, 0.0).mul(BetterFoliage.config.fallingLeaves.speed)
oRoll = roll angle += if (rotPositive) rotationSpeed else -rotationSpeed
roll += rotationSpeed
} }
} }
override fun getRenderType(): IParticleRenderType = IParticleRenderType.PARTICLE_SHEET_TRANSLUCENT override fun render(worldRenderer: BufferBuilder, partialTickTime: Float) {
val tickAngle = angle + partialTickTime * (if (rotPositive) rotationSpeed else -rotationSpeed)
renderParticleQuad(worldRenderer, partialTickTime, rotation = tickAngle.toDouble(), isMirrored = isMirrored)
}
fun setParticleColor(overrideColor: Int?, blockColor: Int) {
val color = overrideColor ?: blockColor
setColor(color)
}
} }
object LeafWindTracker { object LeafWindTracker : WorldTickCallback, ClientWorldLoadCallback {
var random = Random() val 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
init { fun changeWindTarget(world: World) {
MinecraftForge.EVENT_BUS.register(this) nextChange = world.time + 120 + random.nextInt(80)
}
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()) * Config.fallingLeaves.windStrength + val speed = abs(random.nextGaussian()) * BetterFoliage.config.fallingLeaves.windStrength +
(if (!world.isRaining) 0.0 else abs(random.nextGaussian()) * Config.fallingLeaves.stormStrength) (if (!world.isRaining) 0.0 else abs(random.nextGaussian()) * BetterFoliage.config.fallingLeaves.stormStrength)
target.setTo(cos(direction) * speed, 0.0, sin(direction) * speed) target.setTo(cos(direction) * speed, 0.0, sin(direction) * speed)
} }
@SubscribeEvent override fun tick(world: World) {
fun handleWorldTick(event: TickEvent.WorldTickEvent) { if (world.isClient) {
if (event.phase == TickEvent.Phase.START && event.side == LogicalSide.CLIENT) event.world.let { world ->
// change target wind speed // change target wind speed
if (world.dayTime >= nextChange) changeWind(world) if (world.time >= nextChange) changeWindTarget(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
@@ -108,6 +105,8 @@ object LeafWindTracker {
} }
} }
// @SubscribeEvent override fun loadWorld(world: ClientWorld) {
// fun handleWorldLoad(event: WorldEvent.Load) { if (event.world.isClientSide) changeWind(event.world) } changeWindTarget(world)
} }
}

View File

@@ -1,107 +1,68 @@
package mods.betterfoliage.render.particle package mods.betterfoliage.render.particle
import mods.betterfoliage.BetterFoliageMod import mods.betterfoliage.BetterFoliage
import mods.betterfoliage.model.Color import mods.betterfoliage.resource.model.FixedSpriteSet
import mods.betterfoliage.model.FixedSpriteSet import mods.betterfoliage.resource.model.SpriteSet
import mods.betterfoliage.model.SpriteSet import mods.betterfoliage.util.*
import mods.betterfoliage.resource.VeryEarlyReloadListener import net.fabricmc.fabric.api.event.client.ClientSpriteRegistryCallback
import mods.betterfoliage.util.Atlas import net.minecraft.client.texture.SpriteAtlasTexture
import mods.betterfoliage.util.HasLogger import net.minecraft.util.Identifier
import mods.betterfoliage.util.get
import mods.betterfoliage.util.getLines
import mods.betterfoliage.util.resourceManager
import mods.betterfoliage.util.stripEnd
import mods.betterfoliage.util.stripStart
import net.minecraft.client.renderer.texture.MissingTextureSprite
import net.minecraft.resources.IResourceManager
import net.minecraft.util.ResourceLocation
import net.minecraftforge.client.event.TextureStitchEvent
import net.minecraftforge.eventbus.api.SubscribeEvent
import org.apache.logging.log4j.Level.INFO
interface LeafBlockModel { object LeafParticleRegistry : ClientSpriteRegistryCallback {
val key: LeafParticleKey val typeMappings = TextureMatcher()
}
interface LeafParticleKey { val ids = mutableMapOf<String, List<Identifier>>()
val leafType: String val spriteSets = mutableMapOf<String, SpriteSet>()
val tintIndex: Int
val avgColor: Color
}
object LeafParticleRegistry : HasLogger(), VeryEarlyReloadListener { override fun registerSprites(atlasTexture: SpriteAtlasTexture, registry: ClientSpriteRegistryCallback.Registry) {
val allTypes = mutableSetOf<String>() ids.clear()
val particles = hashMapOf<String, SpriteSet>() spriteSets.clear()
typeMappings.loadMappings(Identifier(BetterFoliage.MOD_ID, "leaf_texture_mappings.cfg"))
operator fun get(type: String) = particles[type] ?: particles["default"]!! (typeMappings.mappings.map { it.type } + "default").distinct().forEach { leafType ->
val validIds = (0 until 16).map { idx -> Identifier(BetterFoliage.MOD_ID, "falling_leaf_${leafType}_$idx") }
override fun onReloadStarted(resourceManager: IResourceManager) { .filter { resourceManager.containsResource(Atlas.PARTICLES.wrap(it)) }
allTypes.clear() ids[leafType] = validIds
resourceManager.listResources("textures/particle") { it.startsWith("falling_leaf_") } validIds.forEach { registry.register(it) }
.filter { it.namespace == BetterFoliageMod.MOD_ID }
.map { it.stripStart("textures/particle/falling_leaf_").stripEnd(".png") }
.map { it.path.substringBefore("_", "") }
.forEach { leafType -> if (!leafType.isEmpty()) allTypes.add(leafType) }
}
@SubscribeEvent
fun handlePreStitch(event: TextureStitchEvent.Pre) {
if (event.map.location() == Atlas.PARTICLES.resourceId) {
allTypes.forEach { leafType ->
val locations = (0 until 16).map { idx ->
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) }
}
} }
} }
@SubscribeEvent operator fun get(type: String): SpriteSet {
fun handlePostStitch(event: TextureStitchEvent.Post) { spriteSets[type]?.let { return it }
if (event.map.location() == Atlas.PARTICLES.resourceId) { ids[type]?.let {
allTypes.forEach { leafType -> return FixedSpriteSet(Atlas.PARTICLES, it).apply { spriteSets[type] = this }
val sprites = (0 until 16).map { idx ->
ResourceLocation(BetterFoliageMod.MOD_ID, "particle/falling_leaf_${leafType}_$idx")
}
.map { event.map.getSprite(it) }
.filter { it !is MissingTextureSprite }
detailLogger.log(INFO, "Leaf particle type [$leafType], ${sprites.size} sprites in atlas")
particles[leafType] = FixedSpriteSet(sprites)
}
} }
return if (type == "default") FixedSpriteSet(Atlas.PARTICLES, emptyList()).apply { spriteSets[type] = this }
else get("default")
}
init {
ClientSpriteRegistryCallback.event(SpriteAtlasTexture.PARTICLE_ATLAS_TEX).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: ResourceLocation): Boolean { fun matches(iconLocation: Identifier): 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: ResourceLocation) = mappings.filter { it.matches(resource) }.map { it.type }.firstOrNull() fun getType(resource: Identifier) = mappings.filter { it.matches(resource) }.map { it.type }.firstOrNull()
fun getType(iconName: String) = Identifier(iconName).let { getType(it) }
fun loadMappings(mappingLocation: ResourceLocation) { fun loadMappings(mappingLocation: Identifier) {
mappings.clear() mappings.clear()
resourceManager[mappingLocation]?.getLines()?.let { lines -> resourceManager[mappingLocation]?.getLines()?.let { lines ->
lines.filter { !it.startsWith("//") }.filter { !it.isEmpty() }.forEach { line -> lines.filter { !it.startsWith("//") }.filter { it.isNotEmpty() }.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( else if (mapping.size == 2) mappings.add(Mapping(mapping[0].trim(), mapping[1].trim(), line2[1].trim()))
Mapping(
mapping[0].trim(),
mapping[1].trim(),
line2[1].trim()
)
)
} }
} }
} }

View File

@@ -1,30 +1,22 @@
package mods.betterfoliage.render.particle package mods.betterfoliage.render.particle
import com.mojang.blaze3d.vertex.IVertexBuilder import mods.betterfoliage.BetterFoliage
import mods.betterfoliage.BetterFoliageMod import mods.betterfoliage.render.AbstractParticle
import mods.betterfoliage.config.Config import mods.betterfoliage.resource.model.SpriteDelegate
import mods.betterfoliage.model.SpriteDelegate import mods.betterfoliage.resource.model.SpriteSetDelegate
import mods.betterfoliage.model.SpriteSetDelegate import mods.betterfoliage.util.*
import mods.betterfoliage.util.Atlas import net.minecraft.client.particle.ParticleTextureSheet
import mods.betterfoliage.util.Double3 import net.minecraft.client.render.BufferBuilder
import mods.betterfoliage.util.PI2 import net.minecraft.util.Identifier
import mods.betterfoliage.util.forEachPairIndexed
import mods.betterfoliage.util.randomD
import mods.betterfoliage.util.randomI
import net.minecraft.client.particle.IParticleRenderType
import net.minecraft.client.renderer.ActiveRenderInfo
import net.minecraft.client.world.ClientWorld
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 net.minecraft.world.World
import java.util.Deque import java.util.*
import java.util.LinkedList
import kotlin.math.cos import kotlin.math.cos
import kotlin.math.sin import kotlin.math.sin
class RisingSoulParticle( class RisingSoulParticle(
world: ClientWorld, pos: BlockPos world: World, pos: BlockPos
) : AbstractParticle( ) : AbstractParticle(
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
) { ) {
@@ -33,58 +25,52 @@ class RisingSoulParticle(
val initialPhase = randomD(max = PI2) val initialPhase = randomD(max = PI2)
init { init {
yd = 0.1 velocityY = 0.1
gravity = 0.0f gravityStrength = 0.0f
sprite = headIcons[randomI(max = 1024)] sprite = headIcons[randomI(max = 1024)]
lifetime = MathHelper.floor((0.6 + 0.4 * randomD()) * Config.risingSoul.lifetime * 20.0) maxAge = MathHelper.floor((0.6 + 0.4 * randomD()) * BetterFoliage.config.risingSoul.lifetime * 20.0)
} }
override val isValid: Boolean get() = true override val isValid: Boolean get() = true
override fun update() { override fun update() {
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 > Config.risingSoul.trailLength) particleTrail.removeLast() while (particleTrail.size > BetterFoliage.config.risingSoul.trailLength) particleTrail.removeLast()
if (!Config.enabled) remove() if (!BetterFoliage.config.enabled) markDead()
} }
override fun render(vertexBuilder: IVertexBuilder, camera: ActiveRenderInfo, tickDelta: Float) { override fun render(worldRenderer: BufferBuilder, partialTickTime: Float) {
var alpha = Config.risingSoul.opacity.toFloat() var alpha = BetterFoliage.config.risingSoul.opacity.toFloat()
if (age > lifetime - 40) alpha *= (lifetime - age) / 40.0f if (age > maxAge - 40) alpha *= (maxAge - age) / 40.0f
renderParticleQuad( renderParticleQuad(worldRenderer, partialTickTime,
vertexBuilder, camera, tickDelta, size = BetterFoliage.config.risingSoul.headSize * 0.25,
size = Config.risingSoul.headSize * 0.25,
alpha = alpha alpha = alpha
) )
var scale = Config.risingSoul.trailSize * 0.25 var scale = BetterFoliage.config.risingSoul.trailSize * 0.25
particleTrail.forEachPairIndexed { idx, current, previous -> particleTrail.forEachPairIndexed { idx, current, previous ->
scale *= Config.risingSoul.sizeDecay scale *= BetterFoliage.config.risingSoul.sizeDecay
alpha *= Config.risingSoul.opacityDecay.toFloat() alpha *= BetterFoliage.config.risingSoul.opacityDecay.toFloat()
if (idx % Config.risingSoul.trailDensity == 0) if (idx % BetterFoliage.config.risingSoul.trailDensity == 0) renderParticleQuad(worldRenderer, partialTickTime,
renderParticleQuad( currentPos = current,
vertexBuilder, camera, tickDelta, prevPos = previous,
currentPos = current, size = scale,
prevPos = previous, alpha = alpha,
size = scale, sprite = trackIcon
alpha = alpha, )
sprite = trackIcon
)
} }
} }
override fun getRenderType(): IParticleRenderType = IParticleRenderType.PARTICLE_SHEET_TRANSLUCENT override fun getType() = ParticleTextureSheet.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, "rising_soul_$idx") }
ResourceLocation(BetterFoliageMod.MOD_ID, "particle/rising_soul_$idx") val trackIcon by SpriteDelegate(Atlas.PARTICLES) { Identifier(BetterFoliage.MOD_ID, "soul_track") }
}
val trackIcon by SpriteDelegate(Atlas.PARTICLES) { ResourceLocation(BetterFoliageMod.MOD_ID, "particle/soul_track") }
} }
} }

View File

@@ -1,42 +0,0 @@
package mods.betterfoliage.render.pipeline
import mods.betterfoliage.model.SpecialRenderData
import mods.betterfoliage.RenderTypeLookup
import net.minecraft.block.Block
import net.minecraft.client.renderer.RenderType
import java.util.function.Predicate
object Layers {
val tufts = RenderType.cutout()
val connectedGrass = RenderType.solid()
val connectedDirt = RenderType.cutoutMipped()
val coral = RenderType.cutoutMipped()
}
val defaultLayerBehaviour = Predicate<RenderType> { layer -> layer == RenderType.solid() }
class WrappedLayerPredicate(val original: Predicate<RenderType>, val func: (RenderType, Predicate<RenderType>) -> Boolean) : Predicate<RenderType> {
override fun test(layer: RenderType) = func(layer, original)
}
/**
* Extension method to access the canRenderInLayer() predicate in [RenderTypeLookup]
*/
var Block.layerPredicate : Predicate<RenderType>?
get() = RenderTypeLookup.blockRenderChecks.getStatic()[delegate]
set(value) {
RenderTypeLookup.blockRenderChecks.getStatic()[delegate] = value!!
}
/**
* Add a wrapper to the block's canRenderInLayer() predicate to enable dynamic multi-layer rendering.
* If the render data for the block implements [SpecialRenderData], the layers it enables will be
* rendered _in addition to_ the block's normal layers.
*/
fun Block.extendLayers() {
val original = layerPredicate ?: defaultLayerBehaviour
if (original !is WrappedLayerPredicate) layerPredicate = WrappedLayerPredicate(original) { layer, original ->
original.test(layer) ||
(RenderCtxBase.specialRenderData.get() as? SpecialRenderData)?.canRenderInLayer(layer) ?: false
}
}

View File

@@ -1,83 +0,0 @@
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.client.renderer.BlockRendererDispatcher
import net.minecraft.client.renderer.chunk.ChunkRenderCache
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(
blockCtx: BlockCtx,
val matrixStack: MatrixStack,
var checkSides: Boolean,
val random: Random,
val modelData: IModelData,
) : BlockCtx by blockCtx {
var hasRendered = false
var modelRenderData: Any? = null
inline fun <reified T> withRenderData(renderFunc: (T)->Boolean) = (modelRenderData as? T?).let {
if (it == null) false else renderFunc(it)
}
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
}
abstract fun renderQuad(quad: HalfBakedQuad)
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 {
@JvmStatic
fun reset(chunkRenderCache: IBlockDisplayReader, blockRendererDispatcher: BlockRendererDispatcher, pos: BlockPos, random: Random) {
// prepare render data
val blockCtx = BasicBlockCtx(chunkRenderCache, pos)
val model = blockRendererDispatcher.getBlockModel(blockCtx.state)
random.setSeed(blockCtx.seed)
val data = if (model is SpecialRenderModel) model.prepare(blockCtx, random) else Unit
specialRenderData.set(data)
}
val lightingData = ThreadLocal.withInitial { VanillaQuadLighting() }
val specialRenderData = ThreadLocal<Any?>()
}
}

View File

@@ -1,90 +0,0 @@
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.ForgeVertexLighter
import mods.betterfoliage.render.lighting.ForgeVertexLighterAccess
import mods.betterfoliage.util.getWithDefault
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.ForgeHooksClient
import net.minecraftforge.client.MinecraftForgeClient
import net.minecraftforge.client.model.data.IModelData
import net.minecraftforge.client.model.pipeline.VertexLighterFlat
import java.util.Random
class RenderCtxForge(
blockCtx: BlockCtx,
val lighter: VertexLighterFlat,
matrixStack: MatrixStack,
checkSides: Boolean,
random: Random,
modelData: IModelData,
) : RenderCtxBase(blockCtx, 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,
random: Random, seed: Long,
modelData: IModelData
): Boolean {
val blockCtx = BasicBlockCtx(world, pos)
val ctx = RenderCtxForge(blockCtx, lighter, matrixStack, checkSides, random, modelData).apply {
lighter.setWorld(world)
lighter.setState(state)
lighter.setBlockPos(pos)
lighter.updateBlockInfo()
}
// render layer
return ctx.let {
(lighter as ForgeVertexLighterAccess).vertexLighter = it
model.renderLayer(it, specialRenderData.get()!!, MinecraftForgeClient.getRenderLayer())
lighter.resetBlockInfo()
it.hasRendered
}
}
}
}

View File

@@ -1,65 +0,0 @@
package mods.betterfoliage.render.pipeline
import com.mojang.blaze3d.matrix.MatrixStack
import com.mojang.blaze3d.vertex.IVertexBuilder
import mods.betterfoliage.chunk.BasicBlockCtx
import mods.betterfoliage.chunk.BlockCtx
import mods.betterfoliage.model.HalfBakedQuad
import mods.betterfoliage.model.SpecialRenderModel
import mods.betterfoliage.util.getWithDefault
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.MinecraftForgeClient
import net.minecraftforge.client.model.data.IModelData
import java.util.Random
class RenderCtxVanilla(
val renderer: BlockModelRenderer,
blockCtx: BlockCtx,
val buffer: IVertexBuilder,
val combinedOverlay: Int,
matrixStack: MatrixStack,
checkSides: Boolean,
random: Random,
val randomSeed: Long,
modelData: IModelData,
val useAO: Boolean
): RenderCtxBase(blockCtx, 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,
seed: Long,
combinedOverlay: Int,
modelData: IModelData,
smooth: Boolean
): Boolean {
val blockCtx = BasicBlockCtx(world, pos)
// init context if missing (this is the first render layer)
val ctx = RenderCtxVanilla(renderer, blockCtx, buffer, combinedOverlay, matrixStack, checkSides, random, seed, modelData, smooth)
model.renderLayer(ctx, specialRenderData.get()!!, MinecraftForgeClient.getRenderLayer())
return ctx.hasRendered
}
}
}

View File

@@ -1,27 +0,0 @@
package mods.betterfoliage.resource
import net.minecraft.profiler.IProfiler
import net.minecraft.resources.IFutureReloadListener
import net.minecraft.resources.IResourceManager
import java.util.concurrent.CompletableFuture
import java.util.concurrent.Executor
/**
* Catch resource reload extremely early, before any of the reloaders
* have started working.
*/
interface VeryEarlyReloadListener : IFutureReloadListener {
override fun reload(
stage: IFutureReloadListener.IStage,
resourceManager: IResourceManager,
preparationsProfiler: IProfiler,
reloadProfiler: IProfiler,
backgroundExecutor: Executor,
gameExecutor: Executor
): CompletableFuture<Void> {
onReloadStarted(resourceManager)
return stage.wait(null)
}
fun onReloadStarted(resourceManager: IResourceManager) {}
}

View File

@@ -0,0 +1,99 @@
package mods.betterfoliage.resource.discovery
import mods.betterfoliage.BetterFoliage
import mods.betterfoliage.BlockModelsReloadCallback
import mods.betterfoliage.ModelLoadingCallback
import mods.betterfoliage.util.HasLogger
import mods.betterfoliage.util.Invalidator
import mods.betterfoliage.util.YarnHelper
import mods.betterfoliage.util.get
import net.fabricmc.fabric.api.event.client.ClientSpriteRegistryCallback
import net.minecraft.block.BlockState
import net.minecraft.client.MinecraftClient
import net.minecraft.client.render.block.BlockModels
import net.minecraft.client.render.model.BakedModel
import net.minecraft.client.render.model.ModelLoader
import net.minecraft.client.texture.SpriteAtlasTexture
import net.minecraft.resource.ResourceManager
import net.minecraft.util.Identifier
import java.lang.ref.WeakReference
import java.util.*
import java.util.concurrent.CompletableFuture
import java.util.function.Consumer
import java.util.function.Supplier
// net.minecraft.client.render.block.BlockModels.models
val BlockModels_models = YarnHelper.requiredField<Map<BlockState, BakedModel>>("net.minecraft.class_773", "field_4162", "Ljava/util/Map;")
class BakedModelReplacer : ModelLoadingCallback, ClientSpriteRegistryCallback, BlockModelsReloadCallback, Invalidator, HasLogger {
override val logger get() = BetterFoliage.logDetail
val discoverers = mutableListOf<ModelDiscovery>()
override val callbacks = mutableListOf<WeakReference<()->Unit>>()
protected var keys = emptyMap<BlockState, BlockRenderKey>()
operator fun get(state: BlockState) = keys[state]
inline fun <reified T> getTyped(state: BlockState) = get(state) as? T
var currentLoader: ModelLoader? = null
override fun beginLoadModels(loader: ModelLoader, manager: ResourceManager) {
// Step 1: get a hold of the ModelLoader instance when model reloading starts
currentLoader = loader
log("reloading block discovery configuration")
BetterFoliage.blockConfig.reloadConfig(manager)
invalidate()
}
override fun registerSprites(atlasTexture: SpriteAtlasTexture, registry: ClientSpriteRegistryCallback.Registry) {
// Step 2: ModelLoader is finished with the unbaked models by now, we can inspect them
log("discovering blocks")
val idSet = Collections.synchronizedSet(mutableSetOf<Identifier>())
val allKeys = discoverers.map {
// run model discoverers in parallel
CompletableFuture.supplyAsync(Supplier {
it.discover(currentLoader!!, Consumer { idSet.add(it) })
}, MinecraftClient.getInstance())
}.map { it.join() }
idSet.forEach { registry.register(it) }
val result = mutableMapOf<BlockState, BlockRenderKey>()
allKeys.forEach { keys ->
keys.entries.forEach { (state, key) ->
val oldKey = result[state]
if (oldKey != null) log("Replacing $oldKey with $key for state $state")
else log("Adding replacement $key for state $state")
result[state] = key
}
}
keys = result
}
override fun reloadBlockModels(blockModels: BlockModels) {
// Step 3: replace the baked models with our own
log("block model baking finished")
val modelMap = blockModels[BlockModels_models] as MutableMap<BlockState, BakedModel>
keys.forEach { (state, key) ->
val oldModel = modelMap[state]
if (oldModel == null) log("Cannot find model for state $state, ignoring")
else {
try {
val newModel = key.replace(oldModel, state)
modelMap[state] = newModel
log("Replaced model for state $state with $key")
} catch (e: Exception) {
log("Error creating model for state $state with $key", e)
}
}
}
}
init {
ModelLoadingCallback.EVENT.register(this)
ClientSpriteRegistryCallback.event(SpriteAtlasTexture.BLOCK_ATLAS_TEX).register(this)
BlockModelsReloadCallback.EVENT.register(this)
}
}

View File

@@ -1,135 +0,0 @@
package mods.betterfoliage.resource.discovery
import mods.betterfoliage.BetterFoliage
import mods.betterfoliage.BetterFoliageMod
import mods.betterfoliage.util.Atlas
import mods.betterfoliage.util.HasLogger
import mods.betterfoliage.util.Invalidator
import mods.betterfoliage.util.resourceManager
import net.minecraft.block.BlockState
import net.minecraft.client.renderer.model.IBakedModel
import net.minecraft.client.renderer.model.IModelTransform
import net.minecraft.client.renderer.model.IUnbakedModel
import net.minecraft.client.renderer.model.ModelBakery
import net.minecraft.client.renderer.model.RenderMaterial
import net.minecraft.client.renderer.texture.TextureAtlasSprite
import net.minecraft.util.ResourceLocation
import net.minecraftforge.client.event.ModelBakeEvent
import net.minecraftforge.client.event.TextureStitchEvent
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.WARN
import org.apache.logging.log4j.Logger
import java.lang.ref.WeakReference
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(
val bakery: ModelBakery,
val blockState: BlockState,
val modelLocation: ResourceLocation,
val sprites: MutableSet<ResourceLocation>,
val replacements: MutableMap<ResourceLocation, ModelBakingKey>,
val logger: Logger
) {
fun getUnbaked(location: ResourceLocation = modelLocation) = bakery.getModel(location)
fun addReplacement(key: ModelBakingKey, addToStateKeys: Boolean = true) {
replacements[modelLocation] = key
if (addToStateKeys) BetterFoliage.blockTypes.stateKeys[blockState] = key
logger.log(INFO, "Adding model replacement $modelLocation -> $key")
}
fun <T: IUnbakedModel> loadHierarchy(model: T) = model.apply {
getMaterials(this@ModelDiscoveryContext::getUnbaked, mutableSetOf())
}
}
data class ModelBakingContext(
val bakery: ModelBakery,
val spriteGetter: Function<RenderMaterial, TextureAtlasSprite>,
val location: ResourceLocation,
val transform: IModelTransform,
val logger: Logger
) {
fun getUnbaked() = bakery.getModel(location)
fun getBaked() = bakery.getBakedModel(location, transform, spriteGetter)
}
class BakeWrapperManager : Invalidator, HasLogger() {
val discoverers = mutableListOf<ModelDiscovery>()
override val callbacks = mutableListOf<WeakReference<()->Unit>>()
private val replacements = mutableMapOf<ResourceLocation, ModelBakingKey>()
private val sprites = mutableSetOf<ResourceLocation>()
@SubscribeEvent(priority = EventPriority.LOWEST)
fun handleModelLoad(event: ModelDefinitionsLoadedEvent) {
val startTime = System.currentTimeMillis()
invalidate()
BetterFoliage.blockConfig.readConfig(resourceManager)
BetterFoliage.blockTypes = BlockTypeCache()
StartupMessageManager.addModMessage("BetterFoliage: discovering models")
logger.log(INFO, "starting model discovery (${discoverers.size} listeners)")
discoverers.forEach { listener ->
val replacementsLocal = mutableMapOf<ResourceLocation, ModelBakingKey>()
listener.onModelsLoaded(event.bakery, sprites, replacements)
}
val elapsed = System.currentTimeMillis() - startTime
logger.log(INFO, "finished model discovery in $elapsed ms, ${replacements.size} top-level replacements")
}
@SubscribeEvent
fun handleStitch(event: TextureStitchEvent.Pre) {
if (event.map.location() == Atlas.BLOCKS.resourceId) {
logger.log(INFO, "Adding ${sprites.size} sprites to block atlas")
sprites.forEach { event.addSprite(it) }
sprites.clear()
}
}
@SubscribeEvent
fun handleModelBake(event: ModelBakeEvent) {
replacements.clear()
}
fun onBake(
unbaked: IUnbakedModel,
bakery: ModelBakery,
spriteGetter: Function<RenderMaterial, TextureAtlasSprite>,
transform: IModelTransform,
location: ResourceLocation
): IBakedModel? {
val ctx = ModelBakingContext(bakery, spriteGetter, location, transform, detailLogger)
// bake replacement if available
replacements[location]?.let { replacement ->
detailLogger.log(INFO, "Baking replacement for [${unbaked::class.java.simpleName}] $location -> $replacement")
try {
return replacement.bake(ctx)
} catch (e: Exception) {
detailLogger.log(WARN, "Error while baking $replacement", e)
logger.log(WARN, "Error while baking $replacement", e)
}
}
return unbaked.bake(bakery, spriteGetter, transform, location)
}
}

View File

@@ -1,14 +0,0 @@
package mods.betterfoliage.resource.discovery
import net.minecraft.block.BlockState
class BlockTypeCache {
val leaf = mutableSetOf<BlockState>()
val grass = mutableSetOf<BlockState>()
val dirt = mutableSetOf<BlockState>()
val stateKeys = mutableMapOf<BlockState, ModelBakingKey>()
inline fun <reified T> getTypedOrNull(state: BlockState) = stateKeys[state] as? T
inline fun <reified T> hasTyped(state: BlockState) = stateKeys[state] is T
}

View File

@@ -0,0 +1,53 @@
package mods.betterfoliage.resource.discovery
import com.google.common.base.Joiner
import mods.betterfoliage.util.YarnHelper
import mods.betterfoliage.util.get
import mods.betterfoliage.util.stripStart
import net.minecraft.block.BlockState
import net.minecraft.client.render.model.json.JsonUnbakedModel
import net.minecraft.util.Identifier
import java.util.function.Consumer
// net.minecraft.client.render.model.json.JsonUnbakedModel.parent
val JsonUnbakedModel_parent = YarnHelper.requiredField<JsonUnbakedModel>("net.minecraft.class_793", "field_4253", "Lnet/minecraft/class_793;")
// net.minecraft.client.render.model.json.JsonUnbakedModel.parentId
val JsonUnbakedModel_parentId = YarnHelper.requiredField<Identifier>("net.minecraft.class_793", "field_4247", "Lnet/minecraft/class_2960;")
fun Pair<JsonUnbakedModel, Identifier>.derivesFrom(targetLocation: Identifier): Boolean {
if (second.stripStart("models/") == targetLocation) return true
if (first[JsonUnbakedModel_parent] != null && first[JsonUnbakedModel_parentId] != null)
return Pair(first[JsonUnbakedModel_parent]!!, first[JsonUnbakedModel_parentId]!!).derivesFrom(targetLocation)
return false
}
abstract class ConfigurableModelDiscovery : ModelDiscoveryBase() {
abstract val matchClasses: IBlockMatcher
abstract val modelTextures: List<ModelTextureList>
abstract fun processModel(state: BlockState, textures: List<String>, atlas: Consumer<Identifier>): BlockRenderKey?
override fun processModel(ctx: ModelDiscoveryContext, atlas: Consumer<Identifier>): BlockRenderKey? {
val matchClass = matchClasses.matchingClass(ctx.state.block) ?: return null
log("block state ${ctx.state.toString()}")
log(" class ${ctx.state.block.javaClass.name} matches ${matchClass.name}")
(ctx.models.filter { it.first is JsonUnbakedModel } as List<Pair<JsonUnbakedModel, Identifier>>).forEach { (model, location) ->
val modelMatch = modelTextures.firstOrNull { (model to location).derivesFrom(it.modelLocation) }
if (modelMatch != null) {
log(" model ${model} matches ${modelMatch.modelLocation}")
val textures = modelMatch.textureNames.map { it to model.resolveTexture(it) }
val texMapString = Joiner.on(", ").join(textures.map { "${it.first}=${it.second}" })
log(" sprites [$texMapString]")
if (textures.all { it.second != "missingno" }) {
// found a valid model (all required textures exist)
return processModel(ctx.state, textures.map { it.second }, atlas)
}
}
}
return null
}
}

View File

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

View File

@@ -1,108 +0,0 @@
package mods.betterfoliage.resource.discovery
import com.google.common.base.Joiner
import mods.betterfoliage.util.HasLogger
import net.minecraft.client.renderer.BlockModelShapes
import net.minecraft.client.renderer.model.BlockModel
import net.minecraft.client.renderer.model.ModelBakery
import net.minecraft.client.renderer.model.VariantList
import net.minecraft.client.renderer.texture.MissingTextureSprite
import net.minecraft.util.ResourceLocation
import net.minecraftforge.registries.ForgeRegistries
import org.apache.logging.log4j.Level
abstract class AbstractModelDiscovery : HasLogger(), ModelDiscovery {
override fun onModelsLoaded(
bakery: ModelBakery,
sprites: MutableSet<ResourceLocation>,
replacements: MutableMap<ResourceLocation, ModelBakingKey>
) {
ForgeRegistries.BLOCKS
.flatMap { block -> block.stateDefinition.possibleStates }
.forEach { state ->
val location = BlockModelShapes.stateToModelLocation(state)
val ctx = ModelDiscoveryContext(bakery, state, location, sprites, replacements, detailLogger)
try {
processModel(ctx)
} catch (e: Exception) {
logger.log(Level.WARN, "Discovery error in $location", e)
}
}
}
open fun processModel(ctx: ModelDiscoveryContext) {
processContainerModel(ctx)
}
fun processContainerModel(ctx: ModelDiscoveryContext) {
val model = ctx.getUnbaked()
// built-in support for container models
if (model is VariantList) {
// 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
val scopedReplacements = mutableMapOf<ResourceLocation, ModelBakingKey>()
model.variants.forEach { variant ->
processModel(ctx.copy(modelLocation = variant.modelLocation, replacements = scopedReplacements))
}
if (scopedReplacements.isNotEmpty()) {
ctx.addReplacement(WeightedUnbakedKey(scopedReplacements), addToStateKeys = false)
}
}
}
}
abstract class ConfigurableModelDiscovery : AbstractModelDiscovery() {
abstract val matchClasses: IBlockMatcher
abstract val modelTextures: List<ModelTextureList>
abstract fun processModel(
ctx: ModelDiscoveryContext,
textureMatch: List<ResourceLocation>
)
override fun processModel(ctx: ModelDiscoveryContext) {
val model = ctx.getUnbaked()
if (model is BlockModel) {
val matchClass = matchClasses.matchingClass(ctx.blockState.block) ?: return
detailLogger.log(Level.INFO, "block state ${ctx.blockState}")
detailLogger.log(Level.INFO, " model ${ctx.modelLocation}")
detailLogger.log(Level.INFO, " class ${ctx.blockState.block.javaClass.name} matches ${matchClass.name}")
val ancestry = ctx.bakery.getAncestry(ctx.modelLocation)
val matches = modelTextures.filter { matcher ->
matcher.modelLocation in ancestry
}
matches.forEach { match ->
detailLogger.log(Level.INFO, " model $model matches ${match.modelLocation}")
val materials = match.textureNames.map { it to model.getMaterial(it) }
val texMapString = Joiner.on(", ").join(materials.map { "${it.first}=${it.second.texture()}" })
detailLogger.log(Level.INFO, " sprites [$texMapString]")
if (materials.all { it.second.texture() != MissingTextureSprite.getLocation() }) {
// found a valid model (all required textures exist)
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)
}
}
fun ModelBakery.modelDerivesFrom(model: BlockModel, location: ResourceLocation, target: ResourceLocation): Boolean =
if (location == target) true
else model.parentLocation
?.let { getModel(it) as? BlockModel }
?.let { parent -> modelDerivesFrom(parent, model.parentLocation!!, target) }
?: 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

@@ -0,0 +1,76 @@
package mods.betterfoliage.resource.discovery
import mods.betterfoliage.util.HasLogger
import net.minecraft.block.BlockState
import net.minecraft.client.render.block.BlockModels
import net.minecraft.client.render.model.BakedModel
import net.minecraft.client.render.model.ModelLoader
import net.minecraft.client.render.model.UnbakedModel
import net.minecraft.client.render.model.json.JsonUnbakedModel
import net.minecraft.client.render.model.json.ModelVariant
import net.minecraft.client.render.model.json.WeightedUnbakedModel
import net.minecraft.client.texture.SpriteAtlasTexture
import net.minecraft.client.util.ModelIdentifier
import net.minecraft.util.Identifier
import net.minecraft.util.registry.Registry
import java.util.function.Consumer
typealias RenderKeyFactory = (SpriteAtlasTexture)->BlockRenderKey
interface BlockRenderKey {
fun replace(model: BakedModel, state: BlockState): BakedModel = model
}
fun ModelLoader.iterateModels(func: (ModelDiscoveryContext)->Unit) {
Registry.BLOCK.flatMap { block ->
block.stateFactory.states.map { state -> state to BlockModels.getModelId(state) }
}.forEach { (state, stateModelResource) ->
func(ModelDiscoveryContext(this, state, stateModelResource))
}
}
/**
* Information about a single [BlockState] and all the [UnbakedModel]s it could render as.
*/
class ModelDiscoveryContext(
loader: ModelLoader,
val state: BlockState,
val modelId: ModelIdentifier
) {
val models = loader.unwrapVariants(loader.getOrLoadModel(modelId) to modelId)
.filter { it.second != loader.getOrLoadModel(ModelLoader.MISSING) }
fun ModelLoader.unwrapVariants(modelAndLoc: Pair<UnbakedModel, Identifier>): List<Pair<UnbakedModel, Identifier>> = when(val model = modelAndLoc.first) {
is WeightedUnbakedModel -> (model.variants as List<ModelVariant>).flatMap {
variant -> unwrapVariants(getOrLoadModel(variant.location) to variant.location)
}
is JsonUnbakedModel -> listOf(modelAndLoc)
else -> emptyList()
}
}
interface ModelDiscovery {
fun discover(loader: ModelLoader, atlas: Consumer<Identifier>): Map<BlockState, BlockRenderKey>
}
abstract class ModelDiscoveryBase : ModelDiscovery, HasLogger {
override fun discover(loader: ModelLoader, atlas: Consumer<Identifier>): Map<BlockState, BlockRenderKey> {
val keys = mutableMapOf<BlockState, BlockRenderKey>()
var errors = 0
loader.iterateModels { ctx ->
try {
val result = processModel(ctx, atlas)
result?.let { keys[ctx.state] = it }
} catch (e: Exception) {
errors++
}
}
log("${keys.size} BlockStates discovered, $errors errors")
return keys
}
abstract fun processModel(ctx: ModelDiscoveryContext, atlas: Consumer<Identifier>): BlockRenderKey?
}

View File

@@ -1,97 +0,0 @@
package mods.betterfoliage.resource.discovery
import mods.betterfoliage.BetterFoliage
import mods.betterfoliage.config.match.MAnything
import mods.betterfoliage.config.match.MListAll
import mods.betterfoliage.config.match.MListAny
import mods.betterfoliage.config.match.MNegated
import mods.betterfoliage.config.match.MValue
import mods.betterfoliage.config.match.MatchRules
import mods.betterfoliage.util.HasLogger
import net.minecraft.client.renderer.model.BlockModel
import net.minecraft.client.renderer.model.VariantList
import net.minecraft.util.ResourceLocation
import org.apache.logging.log4j.Level
abstract class ParametrizedModelDiscovery : HasLogger() {
abstract fun processModel(ctx: ModelDiscoveryContext, params: Map<String, String>)
fun Map<String, String>.location(key: String): ResourceLocation? {
val result = get(key)?.let { ResourceLocation(it) }
if (result == null) detailLogger.log(Level.WARN, "Cannot find texture parameter \"$key\"")
return result
}
fun Map<String, String>.int(key: String): Int? {
val result = get(key)?.toInt()
if (result == null) detailLogger.log(Level.WARN, "Cannot find integer parameter \"$key\"")
return result
}
}
class RuleProcessingContext(
val discovery: ModelDiscoveryContext
) {
val params = mutableMapOf("type" to "none")
}
class RuleBasedDiscovery : AbstractModelDiscovery() {
val discoverers = mutableMapOf<String, ParametrizedModelDiscovery>()
override fun processModel(ctx: ModelDiscoveryContext) = when(ctx.getUnbaked()) {
is VariantList -> processContainerModel(ctx)
is BlockModel -> processBlockModel(ctx)
else -> Unit
}
fun processBlockModel(ctx: ModelDiscoveryContext) {
val ruleCtx = RuleProcessingContext(ctx)
val rulesToCheck = BetterFoliage.blockConfig.rules.toMutableList()
val ruleResults = mutableListOf<MListAll>()
var previousSize = 0
// stop processing if nothing changes anymore
while (rulesToCheck.size != previousSize) {
previousSize = rulesToCheck.size
val iterator = rulesToCheck.listIterator()
while (iterator.hasNext()) iterator.next().let { rule ->
// process single rule
MatchRules.visitRoot(ruleCtx, rule).let { result ->
ruleResults.add(result)
// remove rule from active list if:
// - rule succeeded (all directives returned success)
// - rule is immutable (result will always be the same)
if (result.value || result.immutable) iterator.remove()
}
}
}
// log result of rule processing
if (ruleResults.any { it.value }) {
detailLogger.log(Level.INFO, "================================")
detailLogger.log(Level.INFO, "block state: ${ctx.blockState}")
detailLogger.log(Level.INFO, "block class: ${ctx.blockState.block::class.java.name}")
detailLogger.log(Level.INFO, "model : ${ctx.modelLocation}")
detailLogger.log(Level.INFO, "--------------------------------")
ruleResults.forEach { logResult(it) }
}
discoverers[ruleCtx.params["type"]]?.processModel(ctx, ruleCtx.params)
}
fun logResult(match: MAnything<Boolean>) {
when(match) {
is MListAll -> if (match.list.any { it.value }) {
var seenInvariantSuccess = false
match.list.forEach { item ->
if (item.immutable && item.value) seenInvariantSuccess = true
if (seenInvariantSuccess) logResult(item)
}
}
is MListAny -> if (match.value) match.list.first { it.value }.let { logResult(it) }
else match.list.forEach { logResult(it) }
is MNegated -> logResult(match.inner)
is MValue<Boolean> -> detailLogger.log(Level.INFO, "[${match.configSource}] ${match.description}")
}
}
}

View File

@@ -1,58 +0,0 @@
package mods.betterfoliage.resource.discovery
import mods.betterfoliage.model.HalfBakedSimpleModelWrapper
import mods.betterfoliage.model.SpecialRenderModel
import mods.betterfoliage.model.WeightedModelWrapper
import mods.betterfoliage.util.HasLogger
import net.minecraft.client.renderer.model.IBakedModel
import net.minecraft.client.renderer.model.SimpleBakedModel
import net.minecraft.client.renderer.model.VariantList
import net.minecraft.util.ResourceLocation
import org.apache.logging.log4j.Level.INFO
import org.apache.logging.log4j.Level.WARN
class WeightedUnbakedKey(
val replacements: Map<ResourceLocation, ModelBakingKey>
) : ModelBakingKey {
override fun bake(ctx: ModelBakingContext): IBakedModel? {
val unbaked = ctx.getUnbaked()
if (unbaked !is VariantList) return super.bake(ctx)
// bake all variants, replace as needed
val bakedModels = unbaked.variants.mapNotNull {
val variantCtx = ctx.copy(location = it.modelLocation, transform = it)
val replacement = replacements[it.modelLocation]
val baked = replacement?.let { replacement ->
ctx.logger.log(INFO, "Baking replacement for variant [${variantCtx.getUnbaked()::class.java.simpleName}] ${variantCtx.location} -> $replacement")
replacement.bake(variantCtx)
} ?: variantCtx.getBaked()
when(baked) {
is SpecialRenderModel -> it to baked
// 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
is SimpleBakedModel -> it to HalfBakedSimpleModelWrapper(baked)
else -> null
}
}
// something fishy going on, possibly unknown model type
// let it through unchanged
if (bakedModels.isEmpty()) return super.bake(ctx)
if (bakedModels.size < unbaked.variants.size) {
detailLogger.log(
WARN,
"Dropped ${unbaked.variants.size - bakedModels.size} variants from model ${ctx.location}"
)
}
val weightedSpecials = bakedModels.map { (variant, model) ->
WeightedModelWrapper.WeightedModel(model, variant.weight)
}
return WeightedModelWrapper(weightedSpecials, weightedSpecials[0].model)
}
override fun toString() = "[SpecialRenderVariantList, ${replacements.size} replacements]"
companion object : HasLogger()
}

View File

@@ -3,25 +3,25 @@ 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.resources.IResourceManager import net.minecraft.resource.ResourceManager
import net.minecraft.util.ResourceLocation import net.minecraft.util.Identifier
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: ResourceLocation, val aspectHeight: Int = 1, val aspectWidth: Int = 1, val atlas: Atlas = Atlas.BLOCKS) { data class CenteredSprite(val sprite: Identifier, val atlas: Atlas = Atlas.BLOCKS, val aspectHeight: Int = 1, val aspectWidth: Int = 1) {
fun register(pack: GeneratedTexturePack) = pack.register(atlas, this, this::draw) fun register(pack: GeneratedBlockTexturePack) = pack.register(this, this::draw)
fun draw(resourceManager: IResourceManager): ByteArray { fun draw(resourceManager: ResourceManager): ByteArray {
val baseTexture = resourceManager.loadSprite(atlas.file(sprite)) val baseTexture = resourceManager.loadSprite(atlas.wrap(sprite))
val frameWidth = baseTexture.width val frameWidth = baseTexture.width
val frameHeight = baseTexture.width * aspectHeight / aspectWidth val frameHeight = baseTexture.width * aspectHeight / aspectWidth
val frames = baseTexture.height / frameHeight val frames = baseTexture.height / frameHeight
val size = max(frameWidth, frameHeight) val size = max(frameWidth, frameHeight)
val resultTexture = BufferedImage(size, size * frames, BufferedImage.TYPE_4BYTE_ABGR) val result = BufferedImage(size, size * frames, BufferedImage.TYPE_4BYTE_ABGR)
val graphics = resultTexture.createGraphics() val graphics = result.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: ResourceLocation, val aspectHeight: Int =
graphics.drawImage(resultFrame, 0, size * frame, null) graphics.drawImage(resultFrame, 0, size * frame, null)
} }
return resultTexture.bytes return result.bytes
} }
} }

View File

@@ -0,0 +1,92 @@
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.client.resource.ClientResourcePackContainer
import net.minecraft.resource.*
import net.minecraft.resource.ResourcePackContainer.InsertionPosition
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.Logger
import java.io.IOException
import java.lang.IllegalStateException
import java.util.*
import java.util.concurrent.CompletableFuture
import java.util.concurrent.ExecutionException
import java.util.concurrent.Executor
import java.util.function.Predicate
import java.util.function.Supplier
/**
* [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, override val logger: Logger) : HasLogger, ResourcePack, IdentifiableResourceReloadListener {
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, 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.wrap(id)] = resource
log("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)
override fun reload(synchronizer: ResourceReloadListener.Synchronizer, manager: ResourceManager, prepareProfiler: Profiler, applyProfiler: Profiler, prepareExecutor: Executor, applyExecutor: Executor): CompletableFuture<Void> {
this.manager = manager
return synchronizer.whenPrepared(null).thenRun {
this.manager = null
identifiers.clear()
resources.clear()
}
}
override fun getFabricId() = reloadId
/**
* Supplier for this resource pack. Adds pack as always-on and hidden.
*/
val finder = object : ResourcePackCreator {
val packInfo = ClientResourcePackContainer(
packName, true, Supplier { this@GeneratedBlockTexturePack },
LiteralText(packName),
LiteralText(packDesc),
ResourcePackCompatibility.COMPATIBLE, InsertionPosition.TOP, true, null
)
override fun <T : ResourcePackContainer> registerContainer(nameToPackMap: MutableMap<String, T>, packInfoFactory: ResourcePackContainer.Factory<T>) {
(nameToPackMap as MutableMap<String, ResourcePackContainer>)[reloadId.toString()] = packInfo
}
}
}

View File

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

View File

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

View File

@@ -1,69 +0,0 @@
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

@@ -0,0 +1,114 @@
package mods.betterfoliage.resource.model
import mods.betterfoliage.util.Double3
import mods.betterfoliage.util.allDirections
import mods.betterfoliage.util.findFirst
import net.minecraft.block.BlockRenderLayer
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.util.math.Direction
import java.lang.Float
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, converter: BakedModelConverter): BakedModel?
companion object {
fun of(func: (BakedModel, BakedModelConverter)->BakedModel?) = object : BakedModelConverter {
override fun convert(model: BakedModel, converter: BakedModelConverter) = func(model, converter)
}
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, converter: BakedModelConverter) = converters.findFirst { it.convert(model, converter) }
}.let { converterStack ->
// we are guaranteed a result here because of the identity converter
converterStack.convert(model, converterStack)!!
}
/** List of converters without meaningful configuration that should always be used */
val COMMON_MESH_CONVERTERS = listOf(WrappedWeightedModel.converter)
/**
* Convert [BakedModel] into one using fabric-rendering-api [Mesh] instead of the vanilla pipeline.
* @param renderLayerOverride Use the given [BlockRenderLayer] for the [Mesh]
* instead of the one declared by the corresponding [Block]
*/
fun meshifyStandard(model: BakedModel, state: BlockState, renderLayerOverride: BlockRenderLayer? = null) =
(COMMON_MESH_CONVERTERS + WrappedMeshModel.converter(state, renderLayerOverride = renderLayerOverride)).convert(model)
/**
* 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.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 = Float.intBitsToFloat(bakedQuad.vertexData[vIdx * stride + posOffset + 0]).toDouble(),
y = Float.intBitsToFloat(bakedQuad.vertexData[vIdx * stride + posOffset + 1]).toDouble(),
z = 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 = Float.intBitsToFloat(bakedQuad.vertexData[vIdx * stride + uvOffset + 0]).toDouble(),
v = 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.type == type && element.format == format && element.count == count && element.index == index)
return getElementOffset(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_UV_LMAP }

View File

@@ -0,0 +1,230 @@
package mods.betterfoliage.resource.model
import mods.betterfoliage.util.Atlas
import mods.betterfoliage.util.*
import mods.betterfoliage.util.minmax
import net.fabricmc.fabric.api.renderer.v1.RendererAccess
import net.fabricmc.fabric.api.renderer.v1.mesh.Mesh
import net.minecraft.block.BlockRenderLayer
import net.minecraft.client.texture.MissingSprite
import net.minecraft.client.texture.Sprite
import net.minecraft.util.math.Direction
import net.minecraft.util.math.Direction.*
import java.lang.Math.max
import java.lang.Math.min
import kotlin.math.cos
import kotlin.math.sin
import kotlin.random.Random
/**
* Vertex UV coordinates
*
* Zero-centered: sprite coordinates fall between (-0.5, 0.5)
*/
data class UV(val u: Double, val v: Double) {
companion object {
val topLeft = UV(-0.5, -0.5)
val topRight = UV(0.5, -0.5)
val bottomLeft = UV(-0.5, 0.5)
val bottomRight = UV(0.5, 0.5)
}
val rotate: UV get() = UV(v, -u)
fun rotate(n: Int) = when(n % 4) {
0 -> copy()
1 -> UV(v, -u)
2 -> UV(-u, -v)
else -> UV(-v, u)
}
fun clamp(minU: Double = -0.5, maxU: Double = 0.5, minV: Double = -0.5, maxV: Double = 0.5) =
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 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)
}
/**
* Model vertex
*
* @param[xyz] x, y, z coordinates
* @param[uv] u, v coordinates
* @param[color] vertex color RGB components
* @param[alpha] vertex color alpha component
*/
data class Vertex(val xyz: Double3 = Double3(0.0, 0.0, 0.0),
val uv: UV = UV(0.0, 0.0),
val color: Color = Color.white,
val alpha: Int = 255,
val normal: Double3? = null
)
/**
* Intermediate (fabric-renderer-api independent) representation of model quad
* Immutable, double-precision
* Zero-centered (both XYZ and UV) coordinates for simpler rotation/mirroring
*/
data class Quad(
val v1: Vertex, val v2: Vertex, val v3: Vertex, val v4: Vertex,
val sprite: Sprite? = null,
val colorIndex: Int = -1,
val face: Direction? = null
) {
val verts = arrayOf(v1, v2, v3, v4)
inline fun transformV(trans: (Vertex)-> Vertex): Quad = transformVI { vertex, idx -> trans(vertex) }
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)
)
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: Pair<Double, Direction>) = move(Double3(trans.second) * trans.first)
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 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 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)) }
fun mirrorUV(mirrorU: Boolean, mirrorV: Boolean) = transformV { it.copy(uv = it.uv.mirror(mirrorU, mirrorV)) }
fun scrambleUV(random: Random, canFlipU: Boolean, canFlipV: Boolean, canRotate: Boolean) = this
.mirrorUV(canFlipU && random.nextBoolean(), canFlipV && random.nextBoolean())
.let { if (canRotate) it.rotateUV(random.nextInt(4)) else it }
fun sprite(sprite: Sprite) = copy(sprite = sprite)
fun color(color: Color) = transformV { it.copy(color = color) }
fun color(color: Int) = transformV { it.copy(color = Color(color)) }
fun colorIndex(colorIndex: Int) = copy(colorIndex = colorIndex)
fun colorAndIndex(color: Int?) = color(color ?: Color.white.asInt).colorIndex(if (color == null) 0 else -1)
val flipped: Quad get() = Quad(v4, v3, v2, v1, sprite, colorIndex)
fun cycleVertices(n: Int) = when(n % 4) {
1 -> Quad(v2, v3, v4, v1)
2 -> Quad(v3, v4, v1, v2)
3 -> Quad(v4, v1, v2, v3)
else -> this.copy()
}
companion object {
fun mix(first: Quad, second: Quad, vertexFactory: (Vertex, Vertex)-> Vertex) = Quad(
v1 = vertexFactory(first.v1, second.v1),
v2 = vertexFactory(first.v2, second.v2),
v3 = vertexFactory(first.v3, second.v3),
v4 = vertexFactory(first.v4, second.v4)
)
}
}
fun List<Quad>.transform(trans: Quad.()-> Quad) = map { it.trans() }
fun Array<List<Quad>>.transform(trans: Quad.(Int)-> Quad) = mapIndexed { idx, qList -> qList.map { it.trans(idx) } }.toTypedArray()
fun List<Quad>.withOpposites() = flatMap { listOf(it, it.flipped) }
fun Array<List<Quad>>.withOpposites() = map { it.withOpposites() }.toTypedArray()
/**
* Pour quad data into a fabric-renderer-api Mesh
*/
fun List<Quad>.build(layer: BlockRenderLayer, noDiffuse: Boolean = false, flatLighting: Boolean = false): Mesh {
val renderer = RendererAccess.INSTANCE.renderer
val material = renderer.materialFinder().blendMode(0, layer).disableAo(0, flatLighting).disableDiffuse(0, noDiffuse).find()
val builder = renderer.meshBuilder()
builder.emitter.apply {
forEach { quad ->
val sprite = quad.sprite ?: Atlas.BLOCKS.atlas[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(layer: BlockRenderLayer, noDiffuse: Boolean = false, flatLighting: Boolean = false) = map { it.build(layer, 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,70 @@
package mods.betterfoliage.resource.model
import mods.betterfoliage.util.Atlas
import mods.betterfoliage.util.get
import net.fabricmc.fabric.api.event.client.ClientSpriteRegistryCallback
import net.minecraft.client.MinecraftClient
import net.minecraft.client.texture.MissingSprite
import net.minecraft.client.texture.Sprite
import net.minecraft.client.texture.SpriteAtlasTexture
import net.minecraft.util.Identifier
import kotlin.properties.ReadOnlyProperty
import kotlin.reflect.KProperty
interface SpriteSet {
val num: Int
operator fun get(idx: Int): Sprite
}
class FixedSpriteSet(val sprites: List<Sprite>) : SpriteSet {
override val num = sprites.size
override fun get(idx: Int) = sprites[idx % num]
constructor(atlas: Atlas, ids: List<Identifier>) : this(
ids.mapNotNull { atlas.atlas[it] }.let { sprites ->
if (sprites.isNotEmpty()) sprites else listOf(atlas.atlas[MissingSprite.getMissingSpriteId()]!!)
}
)
}
class SpriteDelegate(val atlas: Atlas, val idFunc: ()->Identifier) : ReadOnlyProperty<Any, Sprite>, ClientSpriteRegistryCallback {
private var id: Identifier? = null
private var value: Sprite? = null
init { ClientSpriteRegistryCallback.event(atlas.resourceId).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 {
value?.let { return it }
synchronized(this) {
value?.let { return it }
atlas.atlas[id!!]!!.let { value = it; return it }
}
}
}
class SpriteSetDelegate(val atlas: Atlas, val idRegister: (Identifier)->Identifier = { it }, val idFunc: (Int)->Identifier) : ReadOnlyProperty<Any, SpriteSet>, ClientSpriteRegistryCallback {
private var idList: List<Identifier> = emptyList()
private var spriteSet: SpriteSet? = null
init { ClientSpriteRegistryCallback.event(atlas.resourceId).register(this) }
override fun registerSprites(atlasTexture: SpriteAtlasTexture, registry: ClientSpriteRegistryCallback.Registry) {
spriteSet = null
val manager = MinecraftClient.getInstance().resourceManager
idList = (0 until 16).map(idFunc).filter { manager.containsResource(atlas.wrap(it)) }.map(idRegister)
idList.forEach { registry.register(it) }
}
override fun getValue(thisRef: Any, property: KProperty<*>): SpriteSet {
spriteSet?.let { return it }
synchronized(this) {
spriteSet?.let { return it }
spriteSet = FixedSpriteSet(atlas, idList)
return spriteSet!!
}
}
}

View File

@@ -0,0 +1,74 @@
package mods.betterfoliage.resource.model
import mods.betterfoliage.util.Atlas
import mods.betterfoliage.util.*
import net.fabricmc.fabric.api.renderer.v1.RendererAccess
import net.fabricmc.fabric.api.renderer.v1.mesh.Mesh
import net.minecraft.block.BlockRenderLayer
import net.minecraft.block.BlockRenderLayer.CUTOUT_MIPPED
import net.minecraft.client.texture.Sprite
import net.minecraft.util.Identifier
import net.minecraft.util.math.Direction.UP
data class TuftShapeKey(
val size: Double,
val height: Double,
val offset: Double3,
val flipU1: Boolean,
val flipU2: Boolean
)
fun tuftShapeSet(size: Double, heightMin: Double, heightMax: Double, hOffset: Double): Array<TuftShapeKey> {
return Array(64) { idx ->
TuftShapeKey(
size,
randomD(heightMin, heightMax),
xzDisk(idx) * randomD(hOffset / 2.0, hOffset),
randomB(),
randomB()
)
}
}
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)
.mirrorUV(flipU, false)
fun tuftModelSet(shapes: Array<TuftShapeKey>, overrideColor: Int?, spriteGetter: (Int)->Sprite) = shapes.mapIndexed { idx, shape ->
listOf(
tuftQuadSingle(shape.size, shape.height, shape.flipU1),
tuftQuadSingle(shape.size, shape.height, shape.flipU2).rotate(rot(UP))
).map { it.move(shape.offset) }
.map { it.colorAndIndex(overrideColor) }
.map { it.sprite(spriteGetter(idx)) }
}.toTypedArray()
fun fullCubeTextured(spriteId: Identifier, overrideColor: Int?, scrambleUV: Boolean = true): Mesh {
val sprite = Atlas.BLOCKS.atlas[spriteId]!!
return allDirections.map { faceQuad(it) }
.map { if (!scrambleUV) it else it.rotateUV(randomI(max = 4)) }
.map { it.sprite(sprite) }
.map { it.colorAndIndex(overrideColor) }
.build(BlockRenderLayer.SOLID)
}
fun crossModelsRaw(num: Int, size: Double, hOffset: Double, vOffset: Double): Array<List<Quad>> {
return Array(num) { idx ->
listOf(
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)
.rotate(rot(UP))
).map { it.scale(size) }
.map { it.move(xzDisk(idx) * hOffset) }
.map { it.move(UP.vec * randomD(-1.0, 1.0) * vOffset) }
}
}
fun crossModelsTextured(leafBase: Array<List<Quad>>, overrideColor: Int?, scrambleUV: Boolean, spriteGetter: (Int)->Sprite) = leafBase.map { leaf ->
leaf.map { if (scrambleUV) it.scrambleUV(random, canFlipU = true, canFlipV = true, canRotate = true) else it }
.map { it.colorAndIndex(overrideColor) }
.mapIndexed { idx, quad -> quad.sprite(spriteGetter(idx)) }
.withOpposites().build(CUTOUT_MIPPED)
}.toTypedArray()
fun Array<List<Quad>>.buildTufts() = withOpposites().build(CUTOUT_MIPPED)

View File

@@ -0,0 +1,86 @@
package mods.betterfoliage.resource.model
import mods.betterfoliage.util.YarnHelper
import mods.betterfoliage.util.get
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.BlockRenderLayer
import net.minecraft.block.BlockState
import net.minecraft.client.render.model.BakedModel
import net.minecraft.client.render.model.BasicBakedModel
import net.minecraft.client.render.model.WeightedBakedModel
import net.minecraft.item.ItemStack
import net.minecraft.util.WeightedPicker
import net.minecraft.util.math.BlockPos
import net.minecraft.world.ExtendedBlockView
import java.util.*
import java.util.function.Supplier
// net.minecraft.client.render.model.WeightedBakedModel.totalWeight
val WeightedBakedModel_totalWeight = YarnHelper.requiredField<Int>("net.minecraft.class_1097", "field_5433", "I")
// net.minecraft.client.render.model.WeightedBakedModel.models
val WeightedBakedModel_models = YarnHelper.requiredField<List<WeightedPicker.Entry>>("net.minecraft.class_1097", "field_5434", "Ljava/util/List;")
// net.minecraft.client.render.model.WeightedBakedModel.ModelEntry.model
val WeightedBakedModelEntry_model = YarnHelper.requiredField<BakedModel>("net.minecraft.class_1097\$class_1099", "field_5437", "Lnet/minecraft/class_1087;")
// net.minecraft.util.WeightedPicker.Entry.weight
val WeightedPickerEntry_weight = YarnHelper.requiredField<Int>("net.minecraft.class_3549\$class_3550", "field_15774", "I")
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: ExtendedBlockView, 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: ExtendedBlockView, 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 renderLayerOverride [BlockRenderLayer] to use instead of the one declared by the corresponding [Block]
*/
fun converter(state: BlockState, unshade: Boolean = false, noDiffuse: Boolean = true, renderLayerOverride: BlockRenderLayer? = null) = BakedModelConverter.of { model, _ ->
if (model is BasicBakedModel) {
val mesh = unbakeQuads(model, state, Random(42L), unshade).build(
layer = renderLayerOverride ?: state.block.renderLayer,
noDiffuse = noDiffuse,
flatLighting = !model.useAmbientOcclusion()
)
WrappedMeshModel(model, mesh)
} else null
}
}
}
class WrappedWeightedModel(wrapped: WeightedBakedModel, transformer: BakedModelConverter) : WrappedBakedModel(wrapped) {
val totalWeight = wrapped[WeightedBakedModel_totalWeight] as Int
val models = wrapped[WeightedBakedModel_models]!!.map { entry ->
Entry(transformer.convert(entry[WeightedBakedModelEntry_model]!!, transformer)!!, entry[WeightedPickerEntry_weight]!!)
}
override fun emitBlockQuads(blockView: ExtendedBlockView, state: BlockState, pos: BlockPos, randomSupplier: Supplier<Random>, context: RenderContext) {
(WeightedPicker.getRandom(randomSupplier.get(), models, totalWeight).model as FabricBakedModel).emitBlockQuads(blockView, state, pos, randomSupplier, context)
}
class Entry(val model: BakedModel, weight: Int) : WeightedPicker.Entry(weight)
companion object {
val converter = object : BakedModelConverter {
override fun convert(model: BakedModel, converter: BakedModelConverter) =
(model as? WeightedBakedModel)?.let { WrappedWeightedModel(it, converter) }
}
}
}

View File

@@ -17,10 +17,6 @@ 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 } }
@@ -35,7 +31,7 @@ class LazyInvalidatable<V>(invalidator: Invalidator, val valueFactory: ()->V): R
} }
} }
open class LazyMapInvalidatable<K, V>(val invalidator: Invalidator, val valueFactory: (K)->V) { class LazyMap<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>()
@@ -62,6 +58,3 @@ open class LazyMapInvalidatable<K, V>(val invalidator: Invalidator, val valueFac
} }
} }
} }
fun <V> Invalidator.lazy(valueFactory: ()->V) = LazyInvalidatable(this, valueFactory)
fun <K, V> Invalidator.lazyMap(valueFactory: (K)->V) = LazyMapInvalidatable(this, valueFactory)

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