Compare commits
21 Commits
1.14.4-Fab
...
1.16.5-For
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6219e9353d | ||
|
|
e3eb222d93 | ||
|
|
8f82fefbb7 | ||
|
|
8a303a1a29 | ||
|
|
a97a575dd5 | ||
|
|
a5a5d53341 | ||
|
|
4174301ff7 | ||
|
|
9899816029 | ||
|
|
dbc421c18e | ||
|
|
a917d5b3db | ||
|
|
835bf45f13 | ||
|
|
7168caded1 | ||
|
|
f44d2a7a50 | ||
|
|
09ccb83e8b | ||
|
|
9566ae8341 | ||
|
|
dac7fa0b42 | ||
|
|
c668713051 | ||
|
|
8a82f3772f | ||
|
|
3b728cffcd | ||
|
|
aa91aed58e | ||
|
|
802862f151 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -7,3 +7,4 @@ run/
|
||||
build/
|
||||
classes/
|
||||
temp/
|
||||
logs
|
||||
|
||||
@@ -1,53 +1,41 @@
|
||||
import net.fabricmc.loom.task.RemapJarTask
|
||||
import org.ajoberstar.grgit.Grgit
|
||||
|
||||
plugins {
|
||||
kotlin("jvm").version("1.3.60")
|
||||
id("fabric-loom").version("0.2.6-SNAPSHOT")
|
||||
id("org.ajoberstar.grgit").version("3.1.1")
|
||||
kotlin("jvm").version("1.4.20")
|
||||
id("net.minecraftforge.gradle").version("4.1.12")
|
||||
id("org.spongepowered.mixin").version("0.7-SNAPSHOT")
|
||||
}
|
||||
apply(plugin = "org.ajoberstar.grgit")
|
||||
|
||||
val gitHash = (project.ext.get("grgit") as Grgit).head().abbreviatedId
|
||||
val semVer = "${project.version}+$gitHash"
|
||||
val jarName = "BetterFoliage-$semVer-Fabric-${properties["mcVersion"]}"
|
||||
|
||||
repositories {
|
||||
maven("http://maven.fabricmc.net/")
|
||||
maven("https://minecraft.curseforge.com/api/maven")
|
||||
maven("http://maven.modmuss50.me/")
|
||||
maven("https://grondag-repo.appspot.com").credentials { username = "guest"; password = "" }
|
||||
maven("https://jitpack.io")
|
||||
maven("https://files.minecraftforge.net/maven")
|
||||
maven("https://repo.spongepowered.org/maven")
|
||||
maven("https://www.cursemaven.com")
|
||||
maven("https://thedarkcolour.github.io/KotlinForForge/")
|
||||
}
|
||||
|
||||
dependencies {
|
||||
"minecraft"("com.mojang:minecraft:${properties["mcVersion"]}")
|
||||
"mappings"("net.fabricmc:yarn:${properties["yarnMappings"]}:v2")
|
||||
|
||||
// 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")
|
||||
"minecraft"("net.minecraftforge:forge:${properties["mcVersion"]}-${properties["forgeVersion"]}")
|
||||
"implementation"("thedarkcolour:kotlinforforge:1.7.0")
|
||||
"implementation"("org.spongepowered:mixin:0.8-SNAPSHOT")
|
||||
}
|
||||
|
||||
configurations["annotationProcessor"].extendsFrom(configurations["implementation"])
|
||||
sourceSets {
|
||||
get("main").ext["refMap"] = "betterfoliage.refmap.json"
|
||||
}
|
||||
|
||||
minecraft {
|
||||
mappings(properties["mappingsChannel"] as String, properties["mappingsVersion"] as String)
|
||||
accessTransformer(file("src/main/resources/META-INF/accesstransformer.cfg"))
|
||||
|
||||
runs.create("client") {
|
||||
workingDirectory(file("run"))
|
||||
properties["forge.logging.markers"] = "CORE"
|
||||
properties["forge.logging.console.level"] = "debug"
|
||||
mods.create("betterfoliage") {
|
||||
source(sourceSets["main"])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
java {
|
||||
sourceCompatibility = JavaVersion.VERSION_1_8
|
||||
targetCompatibility = JavaVersion.VERSION_1_8
|
||||
@@ -57,13 +45,17 @@ kotlin {
|
||||
target.compilations.configureEach {
|
||||
kotlinOptions.jvmTarget = "1.8"
|
||||
kotlinOptions.freeCompilerArgs += listOf("-Xno-param-assertions", "-Xno-call-assertions")
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
tasks.getByName<ProcessResources>("processResources") {
|
||||
filesMatching("fabric.mod.json") { expand(mutableMapOf("version" to semVer)) }
|
||||
}
|
||||
|
||||
tasks.getByName<RemapJarTask>("remapJar") {
|
||||
archiveName = "$jarName.jar"
|
||||
tasks.getByName<Jar>("jar") {
|
||||
archiveName = "BetterFoliage-${project.version}-Forge-${properties["mcVersion"]}.jar"
|
||||
manifest {
|
||||
from(file("src/main/resources/META-INF/MANIFEST.MF"))
|
||||
attributes["Implementation-Version"] = project.version
|
||||
}
|
||||
exclude("net")
|
||||
filesMatching("META-INF/mods.toml") { expand(project.properties) }
|
||||
filesMatching("mcmod.info") { expand(project.properties) }
|
||||
}
|
||||
@@ -2,20 +2,13 @@ org.gradle.jvmargs=-Xmx2G
|
||||
org.gradle.daemon=false
|
||||
|
||||
group = com.github.octarine-noise
|
||||
name = betterfoliage
|
||||
jarName = BetterFoliage-Forge
|
||||
|
||||
version = 2.5.0
|
||||
version = 2.6.2
|
||||
|
||||
mcVersion = 1.14.4
|
||||
yarnMappings=1.14.4+build.15
|
||||
loaderVersion=0.7.3+build.176
|
||||
fabricVersion=0.4.2+build.246-1.14
|
||||
loomVersion=0.2.6-SNAPSHOT
|
||||
mcVersion = 1.16.5
|
||||
forgeVersion = 36.1.17
|
||||
mappingsChannel = official
|
||||
mappingsVersion = 1.16.5
|
||||
|
||||
kotlinVersion=1.3.60
|
||||
fabricKotlinVersion=1.3.60+build.1
|
||||
|
||||
clothConfigVersion=1.8
|
||||
modMenuVersion=1.7.6+build.115
|
||||
fiberVersion=0.8.0-2
|
||||
#kottleVersion = 1.4.0
|
||||
|
||||
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
Binary file not shown.
2
gradle/wrapper/gradle-wrapper.properties
vendored
2
gradle/wrapper/gradle-wrapper.properties
vendored
@@ -1,5 +1,5 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-5.6-all.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-6.8.1-bin.zip
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
|
||||
2
gradlew
vendored
2
gradlew
vendored
@@ -82,6 +82,7 @@ esac
|
||||
|
||||
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||
|
||||
|
||||
# Determine the Java command to use to start the JVM.
|
||||
if [ -n "$JAVA_HOME" ] ; then
|
||||
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
||||
@@ -129,6 +130,7 @@ fi
|
||||
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
|
||||
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
|
||||
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
|
||||
|
||||
JAVACMD=`cygpath --unix "$JAVACMD"`
|
||||
|
||||
# We build the pattern for arguments to be converted via cygpath
|
||||
|
||||
25
gradlew.bat
vendored
25
gradlew.bat
vendored
@@ -29,6 +29,9 @@ if "%DIRNAME%" == "" set DIRNAME=.
|
||||
set APP_BASE_NAME=%~n0
|
||||
set APP_HOME=%DIRNAME%
|
||||
|
||||
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
|
||||
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
|
||||
|
||||
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
|
||||
|
||||
@@ -37,7 +40,7 @@ if defined JAVA_HOME goto findJavaFromJavaHome
|
||||
|
||||
set JAVA_EXE=java.exe
|
||||
%JAVA_EXE% -version >NUL 2>&1
|
||||
if "%ERRORLEVEL%" == "0" goto init
|
||||
if "%ERRORLEVEL%" == "0" goto execute
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
@@ -51,7 +54,7 @@ goto fail
|
||||
set JAVA_HOME=%JAVA_HOME:"=%
|
||||
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||
|
||||
if exist "%JAVA_EXE%" goto init
|
||||
if exist "%JAVA_EXE%" goto execute
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
||||
@@ -61,28 +64,14 @@ echo location of your Java installation.
|
||||
|
||||
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
|
||||
@rem Setup the command line
|
||||
|
||||
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
||||
|
||||
|
||||
@rem Execute Gradle
|
||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
|
||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
|
||||
|
||||
:end
|
||||
@rem End local scope for the variables with windows NT shell
|
||||
|
||||
@@ -1,8 +1,13 @@
|
||||
pluginManagement {
|
||||
repositories {
|
||||
jcenter()
|
||||
maven("https://maven.fabricmc.net/")
|
||||
maven("https://files.minecraftforge.net/maven")
|
||||
maven("https://repo.spongepowered.org/maven")
|
||||
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"
|
||||
18
src/main/java/mods/betterfoliage/MixinConnector.java
Normal file
18
src/main/java/mods/betterfoliage/MixinConnector.java
Normal file
@@ -0,0 +1,18 @@
|
||||
package mods.betterfoliage;
|
||||
|
||||
import org.spongepowered.asm.mixin.Mixins;
|
||||
import org.spongepowered.asm.mixin.connect.IMixinConnector;
|
||||
|
||||
public class MixinConnector implements IMixinConnector {
|
||||
@Override
|
||||
public void connect() {
|
||||
Mixins.addConfiguration("betterfoliage.common.mixins.json");
|
||||
|
||||
try {
|
||||
Class.forName("optifine.OptiFineTransformationService");
|
||||
Mixins.addConfiguration("betterfoliage.optifine.mixins.json");
|
||||
} catch (ClassNotFoundException e) {
|
||||
Mixins.addConfiguration("betterfoliage.vanilla.mixins.json");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,42 +3,31 @@ package mods.betterfoliage.mixin;
|
||||
import mods.betterfoliage.Hooks;
|
||||
import net.minecraft.block.Block;
|
||||
import net.minecraft.block.BlockState;
|
||||
import net.minecraft.util.Direction;
|
||||
import net.minecraft.util.math.BlockPos;
|
||||
import net.minecraft.util.math.Direction;
|
||||
import net.minecraft.util.shape.VoxelShape;
|
||||
import net.minecraft.world.BlockView;
|
||||
import net.minecraft.world.World;
|
||||
import net.minecraft.util.math.shapes.VoxelShape;
|
||||
import net.minecraft.world.IBlockReader;
|
||||
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.Random;
|
||||
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
|
||||
|
||||
/**
|
||||
* 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)
|
||||
public class MixinBlock {
|
||||
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;getCullShape(Lnet/minecraft/world/BlockView;Lnet/minecraft/util/math/BlockPos;Lnet/minecraft/util/math/Direction;)Lnet/minecraft/util/shape/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 shouldSideBeRendered = "Lnet/minecraft/block/Block;shouldRenderFace(Lnet/minecraft/block/BlockState;Lnet/minecraft/world/IBlockReader;Lnet/minecraft/util/math/BlockPos;Lnet/minecraft/util/Direction;)Z";
|
||||
private static final String getVoxelShape = "Lnet/minecraft/block/BlockState;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 getFaceOcclusionShape = "Lnet/minecraft/block/BlockState;getFaceOcclusionShape(Lnet/minecraft/world/IBlockReader;Lnet/minecraft/util/math/BlockPos;Lnet/minecraft/util/Direction;)Lnet/minecraft/util/math/shapes/VoxelShape;";
|
||||
private static final String isOpaqueCube = "Lnet/minecraft/block/Block;isOpaqueCube(Lnet/minecraft/block/BlockState;Lnet/minecraft/world/IBlockReader;Lnet/minecraft/util/math/BlockPos;)Z";
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
@Redirect(method = shouldSideBeRendered, 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);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
package mods.betterfoliage.mixin;
|
||||
|
||||
import com.mojang.blaze3d.matrix.MatrixStack;
|
||||
import com.mojang.blaze3d.vertex.IVertexBuilder;
|
||||
import mods.betterfoliage.model.SpecialRenderModel;
|
||||
import mods.betterfoliage.render.pipeline.RenderCtxVanilla;
|
||||
import net.minecraft.block.BlockState;
|
||||
import net.minecraft.client.renderer.BlockModelRenderer;
|
||||
import net.minecraft.client.renderer.model.IBakedModel;
|
||||
import net.minecraft.util.math.BlockPos;
|
||||
import net.minecraft.world.IBlockDisplayReader;
|
||||
import net.minecraftforge.client.model.data.IModelData;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Redirect;
|
||||
|
||||
import java.util.Random;
|
||||
|
||||
@Mixin(BlockModelRenderer.class)
|
||||
public class MixinBlockModelRenderer {
|
||||
|
||||
private static final String renderModel = "Lnet/minecraft/client/renderer/BlockModelRenderer;renderModel(Lnet/minecraft/world/IBlockDisplayReader;Lnet/minecraft/client/renderer/model/IBakedModel;Lnet/minecraft/block/BlockState;Lnet/minecraft/util/math/BlockPos;Lcom/mojang/blaze3d/matrix/MatrixStack;Lcom/mojang/blaze3d/vertex/IVertexBuilder;ZLjava/util/Random;JILnet/minecraftforge/client/model/data/IModelData;)Z";
|
||||
private static final String renderModelFlat = "Lnet/minecraft/client/renderer/BlockModelRenderer;renderModelFlat(Lnet/minecraft/world/IBlockDisplayReader;Lnet/minecraft/client/renderer/model/IBakedModel;Lnet/minecraft/block/BlockState;Lnet/minecraft/util/math/BlockPos;Lcom/mojang/blaze3d/matrix/MatrixStack;Lcom/mojang/blaze3d/vertex/IVertexBuilder;ZLjava/util/Random;JILnet/minecraftforge/client/model/data/IModelData;)Z";
|
||||
private static final String renderModelSmooth = "Lnet/minecraft/client/renderer/BlockModelRenderer;renderModelSmooth(Lnet/minecraft/world/IBlockDisplayReader;Lnet/minecraft/client/renderer/model/IBakedModel;Lnet/minecraft/block/BlockState;Lnet/minecraft/util/math/BlockPos;Lcom/mojang/blaze3d/matrix/MatrixStack;Lcom/mojang/blaze3d/vertex/IVertexBuilder;ZLjava/util/Random;JILnet/minecraftforge/client/model/data/IModelData;)Z";
|
||||
|
||||
@Redirect(method = renderModel, at = @At(value = "INVOKE", target = renderModelSmooth), remap = false)
|
||||
public boolean onRenderModelSmooth(BlockModelRenderer renderer, IBlockDisplayReader world, IBakedModel model, BlockState state, BlockPos pos, MatrixStack matrixStack, IVertexBuilder buffer, boolean checkSides, Random random, long rand, int combinedOverlay, IModelData modelData) {
|
||||
if (model instanceof SpecialRenderModel)
|
||||
return RenderCtxVanilla.render(renderer, world, (SpecialRenderModel) model, state, pos, matrixStack, buffer, checkSides, random, rand, combinedOverlay, modelData, true);
|
||||
else
|
||||
return renderer.renderModelSmooth(world, model, state, pos, matrixStack, buffer, checkSides, random, rand, combinedOverlay, modelData);
|
||||
}
|
||||
|
||||
@Redirect(method = renderModel, at = @At(value = "INVOKE", target = renderModelFlat), remap = false)
|
||||
public boolean onRenderModelFlat(BlockModelRenderer renderer, IBlockDisplayReader world, IBakedModel model, BlockState state, BlockPos pos, MatrixStack matrixStack, IVertexBuilder buffer, boolean checkSides, Random random, long rand, int combinedOverlay, IModelData modelData) {
|
||||
if (model instanceof SpecialRenderModel)
|
||||
return RenderCtxVanilla.render(renderer, world, (SpecialRenderModel) model, state, pos, matrixStack, buffer, checkSides, random, rand, combinedOverlay, modelData, false);
|
||||
else
|
||||
return renderer.renderModelSmooth(world, model, state, pos, matrixStack, buffer, checkSides, random, rand, combinedOverlay, modelData);
|
||||
}
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
package mods.betterfoliage.mixin;
|
||||
|
||||
import mods.betterfoliage.BlockModelsReloadCallback;
|
||||
import net.minecraft.client.render.block.BlockModels;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
|
||||
@Mixin(BlockModels.class)
|
||||
public class MixinBlockModels {
|
||||
|
||||
@Inject(method = "reload()V", at = @At("RETURN"))
|
||||
void onReload(CallbackInfo ci) {
|
||||
BlockModelsReloadCallback.EVENT.invoker().reloadBlockModels((BlockModels) (Object) this);
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,15 @@
|
||||
package mods.betterfoliage.mixin;
|
||||
|
||||
import mods.betterfoliage.Hooks;
|
||||
import net.minecraft.block.AbstractBlock;
|
||||
import net.minecraft.block.Block;
|
||||
import net.minecraft.block.BlockState;
|
||||
import net.minecraft.util.math.BlockPos;
|
||||
import net.minecraft.world.BlockView;
|
||||
import net.minecraft.world.IBlockReader;
|
||||
import org.spongepowered.asm.mixin.Debug;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Coerce;
|
||||
import org.spongepowered.asm.mixin.injection.Redirect;
|
||||
|
||||
/**
|
||||
@@ -14,14 +17,15 @@ 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.
|
||||
*/
|
||||
@Mixin(BlockState.class)
|
||||
@SuppressWarnings({"UnnecessaryQualifiedMemberReference", "deprecation"})
|
||||
@Mixin(AbstractBlock.AbstractBlockState.class)
|
||||
@SuppressWarnings({"deprecation"})
|
||||
public class MixinBlockState {
|
||||
private static final String callFrom = "Lnet/minecraft/block/BlockState;getAmbientOcclusionLightLevel(Lnet/minecraft/world/BlockView;Lnet/minecraft/util/math/BlockPos;)F";
|
||||
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 callFrom = "Lnet/minecraft/block/AbstractBlock$AbstractBlockState;getShadeBrightness(Lnet/minecraft/world/IBlockReader;Lnet/minecraft/util/math/BlockPos;)F";
|
||||
// why is the INVOKEVIRTUAL target class Block in the bytecode, not AbstractBlock?
|
||||
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))
|
||||
float getAmbientOcclusionValue(Block block, BlockState state, BlockView reader, BlockPos pos) {
|
||||
return Hooks.getAmbientOcclusionLightValueOverride(block.getAmbientOcclusionLightLevel(state, reader, pos), state);
|
||||
float getAmbientOcclusionValue(Block block, BlockState state, IBlockReader reader, BlockPos pos) {
|
||||
return Hooks.getAmbientOcclusionLightValueOverride(block.getShadeBrightness(state, reader, pos), state);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
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());
|
||||
}
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
package mods.betterfoliage.mixin;
|
||||
|
||||
import mods.betterfoliage.ClientChunkLoadCallback;
|
||||
import net.minecraft.world.chunk.WorldChunk;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
|
||||
|
||||
@Mixin(targets = {"net.minecraft.client.world.ClientChunkManager$ClientChunkMap"})
|
||||
public class MixinClientChunkManagerChunkMap {
|
||||
|
||||
private static final String 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);
|
||||
}
|
||||
}
|
||||
@@ -1,18 +1,15 @@
|
||||
package mods.betterfoliage.mixin;
|
||||
|
||||
import mods.betterfoliage.ClientWorldLoadCallback;
|
||||
import mods.betterfoliage.Hooks;
|
||||
import net.minecraft.block.BlockState;
|
||||
import net.minecraft.client.network.ClientPlayNetworkHandler;
|
||||
import net.minecraft.client.render.WorldRenderer;
|
||||
import net.minecraft.client.renderer.WorldRenderer;
|
||||
import net.minecraft.client.world.ClientWorld;
|
||||
import net.minecraft.util.math.BlockPos;
|
||||
import net.minecraft.util.profiler.Profiler;
|
||||
import net.minecraft.world.dimension.DimensionType;
|
||||
import net.minecraft.world.level.LevelInfo;
|
||||
import net.minecraft.world.IBlockReader;
|
||||
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.Random;
|
||||
@@ -20,31 +17,27 @@ import java.util.Random;
|
||||
@Mixin(ClientWorld.class)
|
||||
public class MixinClientWorld {
|
||||
|
||||
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 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 worldDisplayTick = "randomBlockDisplayTick(IIIILjava/util/Random;ZLnet/minecraft/util/math/BlockPos$Mutable;)V";
|
||||
private static final String blockDisplayTick = "Lnet/minecraft/block/Block;randomDisplayTick(Lnet/minecraft/block/BlockState;Lnet/minecraft/world/World;Lnet/minecraft/util/math/BlockPos;Ljava/util/Random;)V";
|
||||
private static final String worldAnimateTick = "Lnet/minecraft/client/world/ClientWorld;doAnimateTick(IIIILjava/util/Random;ZLnet/minecraft/util/math/BlockPos$Mutable;)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 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 rendererNotify = "Lnet/minecraft/client/renderer/WorldRenderer;blockChanged(Lnet/minecraft/world/IBlockReader;Lnet/minecraft/util/math/BlockPos;Lnet/minecraft/block/BlockState;Lnet/minecraft/block/BlockState;I)V";
|
||||
|
||||
/**
|
||||
* Inject a callback to call for every random display tick. Used for adding custom particle effects to blocks.
|
||||
*/
|
||||
@Inject(method = worldAnimateTick, at = @At(value = "INVOKE", target = blockAnimateTick))
|
||||
void onAnimateTick(int x, int y, int z, int range, Random random, boolean doBarrier, BlockPos.Mutable pos, CallbackInfo ci) {
|
||||
Hooks.onRandomDisplayTick((ClientWorld) (Object) this, pos, random);
|
||||
}
|
||||
|
||||
/**
|
||||
* Inject callback to get notified of client-side blockstate changes.
|
||||
* Used to invalidate caches in the {@link mods.betterfoliage.chunk.ChunkOverlayManager}
|
||||
*/
|
||||
@Inject(method = scheduleBlockRender, at = @At(value = "HEAD"))
|
||||
void onClientBlockChanged(BlockPos pos, BlockState oldState, BlockState newState, CallbackInfo ci) {
|
||||
Hooks.onClientBlockChanged((ClientWorld) (Object) this, pos, oldState, newState);
|
||||
}
|
||||
|
||||
@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);
|
||||
@Redirect(method = worldNotify, at = @At(value = "INVOKE", target = rendererNotify))
|
||||
void onClientBlockChanged(WorldRenderer renderer, IBlockReader world, BlockPos pos, BlockState oldState, BlockState newState, int flags) {
|
||||
Hooks.onClientBlockChanged((ClientWorld) world, pos, oldState, newState, flags);
|
||||
renderer.blockChanged(world, pos, oldState, newState, flags);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
package mods.betterfoliage.mixin;
|
||||
|
||||
import com.mojang.blaze3d.matrix.MatrixStack;
|
||||
import mods.betterfoliage.model.SpecialRenderModel;
|
||||
import mods.betterfoliage.render.pipeline.RenderCtxForge;
|
||||
import net.minecraft.block.BlockState;
|
||||
import net.minecraft.client.renderer.model.IBakedModel;
|
||||
import net.minecraft.util.math.BlockPos;
|
||||
import net.minecraft.world.IBlockDisplayReader;
|
||||
import net.minecraftforge.client.model.data.IModelData;
|
||||
import net.minecraftforge.client.model.pipeline.ForgeBlockModelRenderer;
|
||||
import net.minecraftforge.client.model.pipeline.VertexLighterFlat;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Redirect;
|
||||
|
||||
import java.util.Random;
|
||||
|
||||
@Mixin(ForgeBlockModelRenderer.class)
|
||||
public class MixinForgeBlockModelRenderer {
|
||||
|
||||
private static final String renderModelFlat = "Lnet/minecraftforge/client/model/pipeline/ForgeBlockModelRenderer;renderModelFlat(Lnet/minecraft/world/IBlockDisplayReader;Lnet/minecraft/client/renderer/model/IBakedModel;Lnet/minecraft/block/BlockState;Lnet/minecraft/util/math/BlockPos;Lcom/mojang/blaze3d/matrix/MatrixStack;Lcom/mojang/blaze3d/vertex/IVertexBuilder;ZLjava/util/Random;JILnet/minecraftforge/client/model/data/IModelData;)Z";
|
||||
private static final String renderModelSmooth = "Lnet/minecraftforge/client/model/pipeline/ForgeBlockModelRenderer;renderModelSmooth(Lnet/minecraft/world/IBlockDisplayReader;Lnet/minecraft/client/renderer/model/IBakedModel;Lnet/minecraft/block/BlockState;Lnet/minecraft/util/math/BlockPos;Lcom/mojang/blaze3d/matrix/MatrixStack;Lcom/mojang/blaze3d/vertex/IVertexBuilder;ZLjava/util/Random;JILnet/minecraftforge/client/model/data/IModelData;)Z";
|
||||
private static final String render = "Lnet/minecraftforge/client/model/pipeline/ForgeBlockModelRenderer;render(Lnet/minecraftforge/client/model/pipeline/VertexLighterFlat;Lnet/minecraft/world/IBlockDisplayReader;Lnet/minecraft/client/renderer/model/IBakedModel;Lnet/minecraft/block/BlockState;Lnet/minecraft/util/math/BlockPos;Lcom/mojang/blaze3d/matrix/MatrixStack;ZLjava/util/Random;JLnet/minecraftforge/client/model/data/IModelData;)Z";
|
||||
|
||||
@Redirect(method = {renderModelFlat, renderModelSmooth}, at = @At(value = "INVOKE", target = render), remap = false)
|
||||
public boolean render(
|
||||
VertexLighterFlat lighter,
|
||||
IBlockDisplayReader world,
|
||||
IBakedModel model,
|
||||
BlockState state,
|
||||
BlockPos pos,
|
||||
MatrixStack matrixStack,
|
||||
boolean checkSides,
|
||||
Random rand,
|
||||
long seed,
|
||||
IModelData modelData
|
||||
) {
|
||||
if (model instanceof SpecialRenderModel)
|
||||
return RenderCtxForge.render(lighter, world, (SpecialRenderModel) model, state, pos, matrixStack, checkSides, rand, seed, modelData);
|
||||
else
|
||||
return ForgeBlockModelRenderer.render(lighter, world, model, state, pos, matrixStack, checkSides, rand, seed, modelData);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
package mods.betterfoliage.mixin;
|
||||
|
||||
import mods.betterfoliage.render.lighting.ForgeVertexLighter;
|
||||
import mods.betterfoliage.render.lighting.ForgeVertexLighterAccess;
|
||||
import net.minecraftforge.client.model.pipeline.VertexLighterFlat;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.Redirect;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
|
||||
@Mixin(VertexLighterFlat.class)
|
||||
abstract public class MixinForgeCustomVertexLighting implements ForgeVertexLighter, ForgeVertexLighterAccess {
|
||||
|
||||
private static final String processQuad = "Lnet/minecraftforge/client/model/pipeline/VertexLighterFlat;processQuad()V";
|
||||
private static final String updateLightmap = "Lnet/minecraftforge/client/model/pipeline/VertexLighterFlat;updateLightmap([F[FFFF)V";
|
||||
private static final String updateColor = "Lnet/minecraftforge/client/model/pipeline/VertexLighterFlat;updateColor([F[FFFFFI)V";
|
||||
private static final String resetBlockInfo = "Lnet/minecraftforge/client/model/pipeline/VertexLighterFlat;resetBlockInfo()V";
|
||||
|
||||
@NotNull
|
||||
public ForgeVertexLighter vertexLighter = this;
|
||||
|
||||
@NotNull
|
||||
public ForgeVertexLighter getVertexLighter() {
|
||||
return vertexLighter;
|
||||
}
|
||||
|
||||
public void setVertexLighter(@NotNull ForgeVertexLighter vertexLighter) {
|
||||
this.vertexLighter = vertexLighter;
|
||||
}
|
||||
|
||||
|
||||
@Shadow
|
||||
protected abstract void updateLightmap(float[] normal, float[] lightmap, float x, float y, float z);
|
||||
@Shadow
|
||||
protected abstract void updateColor(float[] normal, float[] color, float x, float y, float z, float tint, int multiplier);
|
||||
|
||||
@Override
|
||||
public void updateVertexLightmap(@NotNull float[] normal, @NotNull float[] lightmap, float x, float y, float z) {
|
||||
updateLightmap(normal, lightmap, x, y, z);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateVertexColor(@NotNull float[] normal, @NotNull float[] color, float x, float y, float z, float tint, int multiplier) {
|
||||
updateColor(normal, color, x, y, z, tint, multiplier);
|
||||
}
|
||||
|
||||
|
||||
@Redirect(method = processQuad, at = @At(value = "INVOKE", target = updateColor), remap = false)
|
||||
void onUpdateColor(VertexLighterFlat self, float[] normal, float[] color, float x, float y, float z, float tint, int multiplier) {
|
||||
vertexLighter.updateVertexColor(normal, color, x, y, z, tint, multiplier);
|
||||
}
|
||||
|
||||
@Redirect(method = processQuad, at = @At(value = "INVOKE", target = updateLightmap), remap = false)
|
||||
void onUpdateLightmap(VertexLighterFlat self, float[] normal, float[] lightmap, float x, float y, float z) {
|
||||
vertexLighter.updateVertexLightmap(normal, lightmap, x, y, z);
|
||||
}
|
||||
|
||||
@Inject(method = resetBlockInfo, at = @At("RETURN"), remap = false)
|
||||
void onReset(CallbackInfo ci) {
|
||||
vertexLighter = this;
|
||||
}
|
||||
}
|
||||
43
src/main/java/mods/betterfoliage/mixin/MixinModelBakery.java
Normal file
43
src/main/java/mods/betterfoliage/mixin/MixinModelBakery.java
Normal file
@@ -0,0 +1,43 @@
|
||||
package mods.betterfoliage.mixin;
|
||||
|
||||
import mods.betterfoliage.BetterFoliageMod;
|
||||
import mods.betterfoliage.resource.discovery.BakeWrapperManager;
|
||||
import mods.betterfoliage.resource.discovery.ModelDefinitionsLoadedEvent;
|
||||
import net.minecraft.client.renderer.model.*;
|
||||
import net.minecraft.client.renderer.texture.TextureAtlasSprite;
|
||||
import net.minecraft.profiler.IProfiler;
|
||||
import net.minecraft.util.ResourceLocation;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.Redirect;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
|
||||
import java.util.function.Function;
|
||||
|
||||
@Mixin(ModelBakery.class)
|
||||
abstract public class MixinModelBakery {
|
||||
|
||||
private static final String processLoading = "Lnet/minecraft/client/renderer/model/ModelBakery;processLoading(Lnet/minecraft/profiler/IProfiler;I)V";
|
||||
private static final String stitch = "Lnet/minecraft/client/renderer/texture/AtlasTexture;stitch(Lnet/minecraft/resources/IResourceManager;Ljava/util/stream/Stream;Lnet/minecraft/profiler/IProfiler;I)Lnet/minecraft/client/renderer/texture/AtlasTexture$SheetData;";
|
||||
private static final String profilerSection = "Lnet/minecraft/profiler/IProfiler;popPush(Ljava/lang/String;)V";
|
||||
private static final String getBakedModel = "Lnet/minecraft/client/renderer/model/ModelBakery;getBakedModel(Lnet/minecraft/util/ResourceLocation;Lnet/minecraft/client/renderer/model/IModelTransform;Ljava/util/function/Function;)Lnet/minecraft/client/renderer/model/IBakedModel;";
|
||||
private static final String bakeModel = "Lnet/minecraft/client/renderer/model/IUnbakedModel;bake(Lnet/minecraft/client/renderer/model/ModelBakery;Ljava/util/function/Function;Lnet/minecraft/client/renderer/model/IModelTransform;Lnet/minecraft/util/ResourceLocation;)Lnet/minecraft/client/renderer/model/IBakedModel;";
|
||||
|
||||
@Inject(method = processLoading, at = @At(value = "INVOKE", target = profilerSection, ordinal = 4))
|
||||
void onBeforeTextures(IProfiler profiler, int maxMipmapLevel, CallbackInfo ci) {
|
||||
profiler.popPush("betterfoliage");
|
||||
BetterFoliageMod.INSTANCE.getBus().post(new ModelDefinitionsLoadedEvent(ModelBakery.class.cast(this)));
|
||||
}
|
||||
|
||||
@Redirect(method = getBakedModel, at = @At(value = "INVOKE", target = bakeModel))
|
||||
IBakedModel onBakeModel(
|
||||
IUnbakedModel unbaked,
|
||||
ModelBakery bakery,
|
||||
Function<RenderMaterial, TextureAtlasSprite> spriteGetter,
|
||||
IModelTransform transform,
|
||||
ResourceLocation locationIn
|
||||
) {
|
||||
return BakeWrapperManager.INSTANCE.onBake(unbaked, bakery, spriteGetter, transform, locationIn);
|
||||
}
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package mods.betterfoliage.mixin;
|
||||
|
||||
import mods.betterfoliage.Hooks;
|
||||
import net.minecraft.block.BlockState;
|
||||
import net.minecraft.util.Direction;
|
||||
import net.minecraft.util.math.BlockPos;
|
||||
import net.minecraft.util.math.shapes.VoxelShape;
|
||||
import net.minecraft.world.IBlockReader;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Pseudo;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Coerce;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.Redirect;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
|
||||
|
||||
@Pseudo
|
||||
@Mixin(targets = "net.optifine.util.BlockUtils")
|
||||
public class MixinOptifineBlockUtils {
|
||||
private static final String shouldSideBeRendered = "shouldSideBeRendered(Lnet/minecraft/block/BlockState;Lnet/minecraft/world/IBlockReader;Lnet/minecraft/util/math/BlockPos;Lnet/minecraft/util/Direction;Lnet/optifine/render/RenderEnv;)Z";
|
||||
|
||||
private static final String shouldSideBeRenderedCached = "shouldSideBeRenderedCached(Lnet/minecraft/block/BlockState;Lnet/minecraft/world/IBlockReader;Lnet/minecraft/util/math/BlockPos;Lnet/minecraft/util/Direction;Lnet/optifine/render/RenderEnv;Lnet/minecraft/block/BlockState;Lnet/minecraft/util/math/BlockPos;)Z";
|
||||
private static final String getFaceOcclusionShape = "Lnet/minecraft/block/BlockState;getFaceOcclusionShape(Lnet/minecraft/world/IBlockReader;Lnet/minecraft/util/math/BlockPos;Lnet/minecraft/util/Direction;)Lnet/minecraft/util/math/shapes/VoxelShape;";
|
||||
|
||||
@SuppressWarnings("UnresolvedMixinReference")
|
||||
@Redirect(method = shouldSideBeRenderedCached, at = @At(value = "INVOKE", target = getFaceOcclusionShape, ordinal = 1))
|
||||
private static VoxelShape getVoxelShapeOverride(BlockState state, IBlockReader reader, BlockPos pos, Direction dir) {
|
||||
return Hooks.getVoxelShapeOverride(state, reader, pos, dir);
|
||||
}
|
||||
|
||||
@SuppressWarnings("UnresolvedMixinReference")
|
||||
@Inject(method = shouldSideBeRendered, at = @At(value = "HEAD"), cancellable = true)
|
||||
private static void shouldForceSideRender(BlockState state, IBlockReader reader, BlockPos pos, Direction face, @Coerce Object renderEnv, CallbackInfoReturnable<Boolean> cir) {
|
||||
if (Hooks.shouldForceSideRenderOF(state, reader, pos, face)) {
|
||||
cir.setReturnValue(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,88 +1,101 @@
|
||||
package mods.betterfoliage
|
||||
|
||||
import me.zeroeightsix.fiber.JanksonSettings
|
||||
import mods.betterfoliage.chunk.ChunkOverlayManager
|
||||
import mods.betterfoliage.config.BlockConfig
|
||||
import mods.betterfoliage.config.MainConfig
|
||||
import mods.betterfoliage.render.block.vanilla.*
|
||||
import mods.betterfoliage.integration.OptifineCustomColors
|
||||
import mods.betterfoliage.integration.ShadersModIntegration
|
||||
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.StandardRoundLogDiscovery
|
||||
import mods.betterfoliage.render.block.vanilla.StandardMyceliumDiscovery
|
||||
import mods.betterfoliage.render.block.vanilla.StandardMyceliumModel
|
||||
import mods.betterfoliage.render.block.vanilla.StandardNetherrackDiscovery
|
||||
import mods.betterfoliage.render.block.vanilla.StandardNetherrackModel
|
||||
import mods.betterfoliage.render.block.vanilla.StandardRoundLogModel
|
||||
import mods.betterfoliage.render.block.vanilla.StandardSandDiscovery
|
||||
import mods.betterfoliage.render.block.vanilla.StandardSandModel
|
||||
import mods.betterfoliage.render.lighting.AoSideHelper
|
||||
import mods.betterfoliage.render.particle.LeafWindTracker
|
||||
import mods.betterfoliage.resource.discovery.BakeWrapperManager
|
||||
import mods.betterfoliage.resource.discovery.BlockTypeCache
|
||||
import mods.betterfoliage.resource.discovery.ModelDefinitionsLoadedEvent
|
||||
import mods.betterfoliage.resource.generated.GeneratedTexturePack
|
||||
import mods.betterfoliage.render.particle.LeafParticleRegistry
|
||||
import mods.betterfoliage.render.particle.RisingSoulParticle
|
||||
import mods.betterfoliage.resource.discovery.BakedModelReplacer
|
||||
import mods.betterfoliage.resource.generated.GeneratedBlockTexturePack
|
||||
import net.fabricmc.api.ClientModInitializer
|
||||
import net.fabricmc.fabric.api.resource.ResourceManagerHelper
|
||||
import net.fabricmc.loader.api.FabricLoader
|
||||
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.*
|
||||
import mods.betterfoliage.util.resourceManager
|
||||
import net.minecraft.block.BlockState
|
||||
import net.minecraft.client.Minecraft
|
||||
import net.minecraft.resources.IReloadableResourceManager
|
||||
import net.minecraftforge.eventbus.api.SubscribeEvent
|
||||
|
||||
/**
|
||||
* 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")
|
||||
|
||||
object BetterFoliage : ClientModInitializer {
|
||||
const val MOD_ID = "betterfoliage"
|
||||
/** List of recognized [BlockState]s */
|
||||
var blockTypes = BlockTypeCache()
|
||||
|
||||
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()
|
||||
})
|
||||
)
|
||||
fun init() {
|
||||
// discoverers
|
||||
BetterFoliageMod.bus.register(BakeWrapperManager)
|
||||
BetterFoliageMod.bus.register(LeafParticleRegistry)
|
||||
resourceManager.registerReloadListener(LeafParticleRegistry)
|
||||
|
||||
val configFile get() = File(FabricLoader.getInstance().configDirectory, "BetterFoliage.json")
|
||||
|
||||
val config = MainConfig().apply {
|
||||
if (configFile.exists()) JanksonSettings().deserialize(fiberNode, configFile.inputStream())
|
||||
else JanksonSettings().serialize(fiberNode, configFile.outputStream(), false)
|
||||
}
|
||||
|
||||
val blockConfig = BlockConfig()
|
||||
val generatedPack = GeneratedBlockTexturePack(Identifier(MOD_ID, "generated"), "betterfoliage-generated", "Better Foliage", "Generated leaf textures", logDetail)
|
||||
val modelReplacer = BakedModelReplacer()
|
||||
|
||||
override fun onInitializeClient() {
|
||||
// Register generated resource pack
|
||||
ResourceManagerHelper.get(ResourceType.CLIENT_RESOURCES).registerReloadListener(generatedPack)
|
||||
MinecraftClient.getInstance().resourcePackContainerManager.addCreator(generatedPack.finder)
|
||||
|
||||
// Add standard block support
|
||||
modelReplacer.discoverers.add(StandardLeafDiscovery)
|
||||
modelReplacer.discoverers.add(StandardGrassDiscovery)
|
||||
modelReplacer.discoverers.add(StandardLogDiscovery)
|
||||
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)
|
||||
|
||||
// Init singletons
|
||||
LeafParticleRegistry
|
||||
NormalLeavesModel.Companion
|
||||
GrassBlockModel.Companion
|
||||
RoundLogModel.Companion
|
||||
CactusModel.Companion
|
||||
LilypadModel.Companion
|
||||
DirtModel.Companion
|
||||
SandModel.Companion
|
||||
MyceliumModel.Companion
|
||||
NetherrackModel.Companion
|
||||
RisingSoulParticle.Companion
|
||||
}
|
||||
listOf(
|
||||
StandardLeafDiscovery,
|
||||
StandardGrassDiscovery,
|
||||
StandardDirtDiscovery,
|
||||
StandardMyceliumDiscovery,
|
||||
StandardSandDiscovery,
|
||||
StandardLilypadDiscovery,
|
||||
StandardCactusDiscovery,
|
||||
StandardNetherrackDiscovery,
|
||||
StandardRoundLogDiscovery
|
||||
).forEach {
|
||||
BakeWrapperManager.discoverers.add(it)
|
||||
}
|
||||
|
||||
// init singletons
|
||||
val singletons = listOf(
|
||||
AoSideHelper,
|
||||
BlockConfig,
|
||||
ChunkOverlayManager,
|
||||
LeafWindTracker
|
||||
)
|
||||
|
||||
val modelSingletons = listOf(
|
||||
StandardLeafModel.Companion,
|
||||
StandardGrassModel.Companion,
|
||||
StandardDirtModel.Companion,
|
||||
StandardMyceliumModel.Companion,
|
||||
StandardSandModel.Companion,
|
||||
StandardLilypadModel.Companion,
|
||||
StandardCactusModel.Companion,
|
||||
StandardNetherrackModel.Companion,
|
||||
StandardRoundLogModel.Companion,
|
||||
RisingSoulParticle.Companion
|
||||
)
|
||||
|
||||
// init mod integrations
|
||||
val integrations = listOf(
|
||||
ShadersModIntegration,
|
||||
OptifineCustomColors
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
40
src/main/kotlin/mods/betterfoliage/BetterFoliageMod.kt
Normal file
40
src/main/kotlin/mods/betterfoliage/BetterFoliageMod.kt
Normal file
@@ -0,0 +1,40 @@
|
||||
package mods.betterfoliage
|
||||
|
||||
import mods.betterfoliage.config.BlockConfig
|
||||
import mods.betterfoliage.config.Config
|
||||
import net.minecraft.client.Minecraft
|
||||
import net.minecraftforge.fml.ModLoadingContext
|
||||
import net.minecraftforge.fml.common.Mod
|
||||
import net.minecraftforge.fml.config.ModConfig
|
||||
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
|
||||
|
||||
@Mod(BetterFoliageMod.MOD_ID)
|
||||
object BetterFoliageMod {
|
||||
const val MOD_ID = "betterfoliage"
|
||||
|
||||
val bus = MOD_BUS
|
||||
|
||||
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 {
|
||||
ModLoadingContext.get().registerConfig(ModConfig.Type.CLIENT, Config.build())
|
||||
Minecraft.getInstance().resourcePackRepository.addPackFinder(BetterFoliage.generatedPack.finder)
|
||||
bus.register(BlockConfig)
|
||||
BetterFoliage.init()
|
||||
}
|
||||
}
|
||||
@@ -1,29 +1,90 @@
|
||||
package mods.betterfoliage
|
||||
package mods.octarinecore
|
||||
|
||||
import mods.betterfoliage.util.ClassRef
|
||||
import mods.betterfoliage.util.ClassRef.Companion.float
|
||||
import mods.betterfoliage.util.ClassRef.Companion.void
|
||||
import mods.betterfoliage.util.FieldRef
|
||||
import mods.betterfoliage.util.MethodRef
|
||||
import net.minecraft.block.Block
|
||||
import net.minecraft.block.BlockState
|
||||
import net.minecraft.client.renderer.BlockModelRenderer
|
||||
import net.minecraft.client.renderer.BlockRendererDispatcher
|
||||
import net.minecraft.client.renderer.BufferBuilder
|
||||
import net.minecraft.client.renderer.chunk.ChunkRenderCache
|
||||
import net.minecraft.client.renderer.model.BakedQuad
|
||||
import net.minecraft.client.renderer.model.IUnbakedModel
|
||||
import net.minecraft.client.renderer.model.ModelBakery
|
||||
import net.minecraft.client.renderer.texture.TextureAtlasSprite
|
||||
import net.minecraft.util.ResourceLocation
|
||||
import net.minecraft.util.math.BlockPos
|
||||
import net.minecraft.world.IBlockDisplayReader
|
||||
import net.minecraft.world.IBlockReader
|
||||
import net.minecraftforge.client.model.pipeline.BlockInfo
|
||||
import net.minecraftforge.client.model.pipeline.VertexLighterFlat
|
||||
import java.util.Random
|
||||
|
||||
// Java
|
||||
val String = ClassRef<String>("java.lang.String")
|
||||
val List = ClassRef<List<*>>("java.util.List")
|
||||
val Random = ClassRef<Random>("java.util.Random")
|
||||
fun <K, V> mapRef() = ClassRef<Map<K, V>>("java.util.Map")
|
||||
fun <K, V> mapRefMutable() = ClassRef<MutableMap<K, V>>("java.util.Map")
|
||||
|
||||
// Minecraft
|
||||
val IBlockReader = ClassRef<IBlockReader>("net.minecraft.world.IBlockReader")
|
||||
val ILightReader = ClassRef<IBlockDisplayReader>("net.minecraft.world.IBlockDisplayReader")
|
||||
val BlockState = ClassRef<BlockState>("net.minecraft.block.BlockState")
|
||||
val BlockPos = ClassRef<BlockPos>("net.minecraft.util.math.BlockPos")
|
||||
val Block = ClassRef<Block>("net.minecraft.block.Block")
|
||||
|
||||
val TextureAtlasSprite = ClassRef<TextureAtlasSprite>("net.minecraft.client.renderer.texture.TextureAtlasSprite")
|
||||
val BufferBuilder = ClassRef<BufferBuilder>("net.minecraft.client.renderer.BufferBuilder")
|
||||
val BufferBuilder_setSprite = MethodRef(BufferBuilder, "setSprite", void, TextureAtlasSprite)
|
||||
val BufferBuilder_sVertexBuilder = FieldRef(BufferBuilder, "sVertexBuilder", SVertexBuilder)
|
||||
val BlockRendererDispatcher = ClassRef<BlockRendererDispatcher>("net.minecraft.client.renderer.BlockRendererDispatcher")
|
||||
val ChunkRenderCache = ClassRef<ChunkRenderCache>("net.minecraft.client.renderer.chunk.ChunkRenderCache")
|
||||
val ResourceLocation = ClassRef<ResourceLocation>("net.minecraft.util.ResourceLocation")
|
||||
val BakedQuad = ClassRef<BakedQuad>("net.minecraft.client.renderer.model.BakedQuad")
|
||||
val BlockModelRenderer = ClassRef<BlockModelRenderer>("net.minecraft.client.renderer.BlockModelRenderer")
|
||||
|
||||
val VertexLighterFlat = ClassRef<VertexLighterFlat>("net.minecraftforge.client.model.pipeline.VertexLighterFlat")
|
||||
val BlockInfo = ClassRef<BlockInfo>("net.minecraftforge.client.model.pipeline.BlockInfo")
|
||||
val VertexLighterFlat_blockInfo = FieldRef(VertexLighterFlat, "blockInfo", BlockInfo)
|
||||
val BlockInfo_shx = FieldRef(BlockInfo, "shx", float)
|
||||
val BlockInfo_shy = FieldRef(BlockInfo, "shy", float)
|
||||
val BlockInfo_shz = FieldRef(BlockInfo, "shz", float)
|
||||
|
||||
object ModelBakery : ClassRef<ModelBakery>("net.minecraft.client.renderer.model.ModelBakery") {
|
||||
val unbakedModels = FieldRef(this, "unbakedModels", mapRefMutable<ResourceLocation, IUnbakedModel>())
|
||||
val topUnbakedModels = FieldRef(this, "topUnbakedModels", mapRefMutable<ResourceLocation, IUnbakedModel>())
|
||||
}
|
||||
|
||||
// Optifine
|
||||
//val OptifineClassTransformer = ClassRefOld<Any>("optifine.OptiFineClassTransformer")
|
||||
//val BlockPosM = ClassRefOld<Any>("net.optifine.BlockPosM")
|
||||
//object ChunkCacheOF : ClassRefOld<Any>("net.optifine.override.ChunkCacheOF") {
|
||||
// val chunkCache = FieldRefOld(this, "chunkCache", ChunkRendererRegion)
|
||||
//}
|
||||
val OptifineClassTransformer = ClassRef<Any>("optifine.OptiFineClassTransformer")
|
||||
val BlockPosM = ClassRef<Any>("net.optifine.BlockPosM")
|
||||
object ChunkCacheOF : ClassRef<Any>("net.optifine.override.ChunkCacheOF") {
|
||||
val chunkCache = FieldRef(this, "chunkCache", ChunkRenderCache)
|
||||
}
|
||||
|
||||
//object RenderEnv : ClassRefOld<Any>("net.optifine.render.RenderEnv") {
|
||||
// val reset = MethodRefOld(this, "reset", void, BlockState, BlockPos)
|
||||
//}
|
||||
object RenderEnv : ClassRef<Any>("net.optifine.render.RenderEnv") {
|
||||
val reset = MethodRef(this, "reset", void, BlockState, BlockPos)
|
||||
}
|
||||
|
||||
// Optifine custom colors
|
||||
//val IColorizer = ClassRefOld<Any>("net.optifine.CustomColors\$IColorizer")
|
||||
//object CustomColors : ClassRefOld<Any>("net.optifine.CustomColors") {
|
||||
// val getColorMultiplier = MethodRefOld(this, "getColorMultiplier", int, BakedQuad, BlockState, ExtendedBlockView, BlockPos, RenderEnv)
|
||||
//}
|
||||
val IColorizer = ClassRef<Any>("net.optifine.CustomColors\$IColorizer")
|
||||
object CustomColors : ClassRef<Any>("net.optifine.CustomColors") {
|
||||
val getColorMultiplier = MethodRef(this, "getColorMultiplier", int, BakedQuad, BlockState, ILightReader, BlockPos, RenderEnv)
|
||||
}
|
||||
|
||||
// Optifine shaders
|
||||
//object SVertexBuilder : ClassRefOld<Any>("net.optifine.shaders.SVertexBuilder") {
|
||||
// val pushState = MethodRefOld(this, "pushEntity", void, BlockState, BlockPos, ExtendedBlockView, BufferBuilder)
|
||||
// val pushNum = MethodRefOld(this, "pushEntity", void, long)
|
||||
// val pop = MethodRefOld(this, "popEntity", void)
|
||||
//}
|
||||
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)
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,72 +1,8 @@
|
||||
package mods.betterfoliage
|
||||
|
||||
import net.fabricmc.fabric.api.event.Event
|
||||
import net.fabricmc.fabric.api.event.EventFactory
|
||||
import net.minecraft.client.render.block.BlockModels
|
||||
import net.minecraft.client.render.model.ModelLoader
|
||||
import net.minecraft.client.world.ClientWorld
|
||||
import net.minecraft.resource.ResourceManager
|
||||
import net.minecraft.world.chunk.WorldChunk
|
||||
import net.minecraft.client.renderer.model.ModelBakery
|
||||
import net.minecraft.client.renderer.texture.AtlasTexture
|
||||
import net.minecraft.resources.IResourceManager
|
||||
import net.minecraft.util.ResourceLocation
|
||||
import net.minecraftforge.eventbus.api.Event
|
||||
|
||||
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) }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,66 +2,76 @@
|
||||
package mods.betterfoliage
|
||||
|
||||
import mods.betterfoliage.chunk.ChunkOverlayManager
|
||||
import mods.betterfoliage.render.particle.FallingLeafParticle
|
||||
import mods.betterfoliage.render.particle.RisingSoulParticle
|
||||
import mods.betterfoliage.render.block.vanilla.LeafKey
|
||||
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.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 mods.betterfoliage.render.particle.FallingLeafParticle
|
||||
import mods.betterfoliage.render.particle.LeafBlockModel
|
||||
import mods.betterfoliage.render.particle.RisingSoulParticle
|
||||
import net.minecraft.block.Block
|
||||
import net.minecraft.block.BlockState
|
||||
import net.minecraft.block.Blocks
|
||||
import net.minecraft.client.MinecraftClient
|
||||
import net.minecraft.client.Minecraft
|
||||
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.Direction
|
||||
import net.minecraft.util.shape.VoxelShape
|
||||
import net.minecraft.util.shape.VoxelShapes
|
||||
import net.minecraft.world.BlockView
|
||||
import net.minecraft.util.math.shapes.VoxelShape
|
||||
import net.minecraft.util.math.shapes.VoxelShapes
|
||||
import net.minecraft.world.IBlockDisplayReader
|
||||
import net.minecraft.world.IBlockReader
|
||||
import net.minecraft.world.World
|
||||
import java.util.Random
|
||||
|
||||
fun getAmbientOcclusionLightValueOverride(original: Float, state: BlockState): Float {
|
||||
if (BetterFoliage.config.enabled &&
|
||||
BetterFoliage.config.roundLogs.enabled &&
|
||||
BetterFoliage.modelReplacer.getTyped<RoundLogKey>(state) != null
|
||||
) return BetterFoliage.config.roundLogs.dimming.toFloat()
|
||||
if (Config.enabled && Config.roundLogs.enabled && BetterFoliage.blockTypes.hasTyped<RoundLogKey>(state))
|
||||
return Config.roundLogs.dimming.toFloat()
|
||||
return original
|
||||
}
|
||||
|
||||
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) {
|
||||
fun onClientBlockChanged(worldClient: ClientWorld, pos: BlockPos, oldState: BlockState, newState: BlockState, flags: Int) {
|
||||
ChunkOverlayManager.onBlockChange(worldClient, pos)
|
||||
}
|
||||
|
||||
fun onRandomDisplayTick(world: ClientWorld, pos: BlockPos) {
|
||||
fun onRandomDisplayTick(world: ClientWorld, pos: BlockPos, random: Random) {
|
||||
val state = world.getBlockState(pos)
|
||||
|
||||
if (BetterFoliage.config.enabled &&
|
||||
BetterFoliage.config.risingSoul.enabled &&
|
||||
if (Config.enabled &&
|
||||
Config.risingSoul.enabled &&
|
||||
state.block == Blocks.SOUL_SAND &&
|
||||
world.isAir(pos + Direction.UP.offset) &&
|
||||
Math.random() < BetterFoliage.config.risingSoul.chance) {
|
||||
world.getBlockState(pos.relative(UP)).isAir &&
|
||||
Math.random() < Config.risingSoul.chance) {
|
||||
RisingSoulParticle(world, pos).addIfValid()
|
||||
}
|
||||
|
||||
if (BetterFoliage.config.enabled &&
|
||||
BetterFoliage.config.fallingLeaves.enabled &&
|
||||
world.isAir(pos + Direction.DOWN.offset) &&
|
||||
randomD() < BetterFoliage.config.fallingLeaves.chance) {
|
||||
BetterFoliage.modelReplacer.getTyped<LeafKey>(state)?.let { key ->
|
||||
FallingLeafParticle(world, pos, key).addIfValid()
|
||||
}
|
||||
if (Config.enabled &&
|
||||
Config.fallingLeaves.enabled &&
|
||||
random.nextDouble() < Config.fallingLeaves.chance &&
|
||||
world.getBlockState(pos.relative(DOWN)).isAir
|
||||
) {
|
||||
(getActualRenderModel(world, pos, state, random) as? LeafBlockModel)?.let { leafModel ->
|
||||
val blockColor = Minecraft.getInstance().blockColors.getColor(state, world, pos, 0)
|
||||
FallingLeafParticle(world, pos, leafModel.key, blockColor, random).addIfValid()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun getVoxelShapeOverride(state: BlockState, reader: BlockView, pos: BlockPos, dir: Direction): VoxelShape {
|
||||
if (BetterFoliage.modelReplacer[state] is RoundLogKey) {
|
||||
fun getVoxelShapeOverride(state: BlockState, reader: IBlockReader, pos: BlockPos, dir: Direction): VoxelShape {
|
||||
if (Config.enabled && Config.roundLogs.enabled && BetterFoliage.blockTypes.hasTyped<RoundLogKey>(state))
|
||||
return VoxelShapes.empty()
|
||||
}
|
||||
return state.getCullShape(reader, pos, dir)
|
||||
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
|
||||
}
|
||||
@@ -1,55 +1,60 @@
|
||||
package mods.betterfoliage.chunk
|
||||
|
||||
import mods.betterfoliage.util.Int3
|
||||
import mods.betterfoliage.util.allDirections
|
||||
import mods.betterfoliage.util.offset
|
||||
import mods.betterfoliage.util.plus
|
||||
import mods.betterfoliage.util.semiRandom
|
||||
import net.minecraft.block.Block
|
||||
import net.minecraft.block.BlockState
|
||||
import net.minecraft.client.MinecraftClient
|
||||
import net.minecraft.client.renderer.chunk.ChunkRenderCache
|
||||
import net.minecraft.util.Direction
|
||||
import net.minecraft.util.math.BlockPos
|
||||
import net.minecraft.util.math.Direction
|
||||
import net.minecraft.world.ExtendedBlockView
|
||||
import net.minecraft.world.IBlockDisplayReader
|
||||
import net.minecraft.world.IWorldReader
|
||||
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
|
||||
* block-relative coordinates.
|
||||
*/
|
||||
interface BlockCtx {
|
||||
val world: ExtendedBlockView
|
||||
val world: IBlockDisplayReader
|
||||
val pos: BlockPos
|
||||
|
||||
fun offset(dir: Direction) = offset(dir.offset)
|
||||
fun offset(offset: Int3): BlockCtx
|
||||
|
||||
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(dir: Direction) = state(dir.offset)
|
||||
|
||||
val biome: Biome get() = world.getBiome(pos)
|
||||
fun isAir(offset: Int3) = (pos + offset).let { world.getBlockState(it).isAir(world, it) }
|
||||
fun isAir(dir: Direction) = isAir(dir.offset)
|
||||
|
||||
val isNormalCube: Boolean get() = state.isSimpleFullBlock(world, pos)
|
||||
val biome: Biome? get() =
|
||||
(world as? IWorldReader)?.getBiome(pos) ?:
|
||||
(world as? ChunkRenderCache)?.level?.getBiome(pos)
|
||||
|
||||
fun shouldSideBeRendered(side: Direction) = Block.shouldDrawSide(state, world, pos, side)
|
||||
val isFullBlock: Boolean get() = state.isCollisionShapeFullBlock(world, pos)
|
||||
|
||||
fun isNeighborSolid(dir: Direction) = offset(dir).let { it.state.isSideSolidFullSquare(it.world, it.pos, dir.opposite) }
|
||||
fun isNeighborSturdy(dir: Direction) = offset(dir).let { it.state.isFaceSturdy(it.world, it.pos, dir.opposite) }
|
||||
|
||||
fun model(dir: Direction) = state(dir).let { MinecraftClient.getInstance().blockRenderManager.getModel(it)!! }
|
||||
fun model(offset: Int3) = state(offset).let { MinecraftClient.getInstance().blockRenderManager.getModel(it)!! }
|
||||
fun shouldSideBeRendered(side: Direction) = Block.shouldRenderFace(state, world, pos, side)
|
||||
|
||||
/** 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)
|
||||
}
|
||||
|
||||
open class BasicBlockCtx(
|
||||
override val world: ExtendedBlockView,
|
||||
class BasicBlockCtx(
|
||||
override val world: IBlockDisplayReader,
|
||||
override val pos: BlockPos
|
||||
) : BlockCtx {
|
||||
override val state = world.getBlockState(pos)
|
||||
override val state: BlockState = world.getBlockState(pos)
|
||||
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]
|
||||
}
|
||||
|
||||
@@ -1,50 +0,0 @@
|
||||
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
|
||||
//}
|
||||
@@ -1,35 +1,26 @@
|
||||
package mods.betterfoliage.chunk
|
||||
|
||||
import mods.betterfoliage.*
|
||||
import mods.betterfoliage.util.YarnHelper
|
||||
import mods.betterfoliage.util.get
|
||||
import net.minecraft.client.render.chunk.ChunkRendererRegion
|
||||
import mods.betterfoliage.util.isInstance
|
||||
import mods.octarinecore.ChunkCacheOF
|
||||
import net.minecraft.client.renderer.chunk.ChunkRenderCache
|
||||
import net.minecraft.client.world.ClientWorld
|
||||
import net.minecraft.util.math.BlockPos
|
||||
import net.minecraft.util.math.ChunkPos
|
||||
import net.minecraft.world.ExtendedBlockView
|
||||
import net.minecraft.world.ViewableWorld
|
||||
import net.minecraft.world.World
|
||||
import net.minecraft.world.chunk.WorldChunk
|
||||
import net.minecraft.world.dimension.DimensionType
|
||||
import net.minecraft.world.DimensionType
|
||||
import net.minecraft.world.IBlockDisplayReader
|
||||
import net.minecraft.world.IWorldReader
|
||||
import net.minecraftforge.common.MinecraftForge
|
||||
import net.minecraftforge.event.world.ChunkEvent
|
||||
import net.minecraftforge.event.world.WorldEvent
|
||||
import net.minecraftforge.eventbus.api.SubscribeEvent
|
||||
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
|
||||
|
||||
// net.minecraft.world.chunk.WorldChunk.world
|
||||
val WorldChunk_world = YarnHelper.requiredField<World>("net.minecraft.class_2818", "field_12858", "Lnet/minecraft/class_1937;")
|
||||
// net.minecraft.client.render.chunk.ChunkRendererRegion.world
|
||||
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
|
||||
val IBlockDisplayReader.dimType: DimensionType get() = when {
|
||||
this is IWorldReader -> dimensionType()
|
||||
this is ChunkRenderCache -> level.dimensionType()
|
||||
this.isInstance(ChunkCacheOF) -> this[ChunkCacheOF.chunkCache].level.dimensionType()
|
||||
else -> throw IllegalArgumentException("DimensionType of world with class ${this::class.qualifiedName} cannot be determined!")
|
||||
}
|
||||
|
||||
@@ -38,17 +29,18 @@ val ExtendedBlockView.dimType: DimensionType get() = when {
|
||||
*/
|
||||
interface ChunkOverlayLayer<T> {
|
||||
fun calculate(ctx: BlockCtx): T
|
||||
fun onBlockUpdate(world: ExtendedBlockView, pos: BlockPos)
|
||||
fun onBlockUpdate(world: IBlockDisplayReader, pos: BlockPos)
|
||||
}
|
||||
|
||||
/**
|
||||
* Query, lazy calculation and lifecycle management of multiple layers of chunk overlay data.
|
||||
*/
|
||||
object ChunkOverlayManager : ClientChunkLoadCallback, ClientWorldLoadCallback {
|
||||
object ChunkOverlayManager {
|
||||
|
||||
var tempCounter = 0
|
||||
|
||||
init {
|
||||
ClientWorldLoadCallback.EVENT.register(this)
|
||||
ClientChunkLoadCallback.EVENT.register(this)
|
||||
MinecraftForge.EVENT_BUS.register(this)
|
||||
}
|
||||
|
||||
val chunkData = IdentityHashMap<DimensionType, MutableMap<ChunkPos, ChunkOverlayData>>()
|
||||
@@ -87,25 +79,27 @@ object ChunkOverlayManager : ClientChunkLoadCallback, ClientWorldLoadCallback {
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
@SubscribeEvent
|
||||
fun handleLoadWorld(event: WorldEvent.Load) = (event.world as? ClientWorld)?.let { world ->
|
||||
chunkData[world.dimType] = mutableMapOf()
|
||||
}
|
||||
|
||||
@SubscribeEvent
|
||||
fun handleUnloadWorld(event: WorldEvent.Unload) = (event.world as? ClientWorld)?.let { world ->
|
||||
chunkData.remove(world.dimType)
|
||||
}
|
||||
|
||||
@SubscribeEvent
|
||||
fun handleLoadChunk(event: ChunkEvent.Load) = (event.world as? ClientWorld)?.let { world ->
|
||||
chunkData[world.dimType]?.let { chunks ->
|
||||
// check for existence first because Optifine fires a TON of these
|
||||
if (event.chunk.pos !in chunks.keys) chunks[event.chunk.pos] = ChunkOverlayData(layers)
|
||||
}
|
||||
}
|
||||
|
||||
override fun unloadChunk(chunk: WorldChunk) {
|
||||
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)}
|
||||
@SubscribeEvent
|
||||
fun handleUnloadChunk(event: ChunkEvent.Unload) = (event.world as? ClientWorld)?.let { world ->
|
||||
chunkData[world.dimType]?.remove(event.chunk.pos)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,36 +0,0 @@
|
||||
package mods.betterfoliage.config
|
||||
|
||||
import mods.betterfoliage.BetterFoliage
|
||||
import mods.betterfoliage.util.Invalidator
|
||||
import mods.betterfoliage.resource.discovery.ConfigurableBlockMatcher
|
||||
import mods.betterfoliage.resource.discovery.ModelTextureListConfiguration
|
||||
import net.minecraft.resource.ResourceManager
|
||||
import net.minecraft.util.Identifier
|
||||
|
||||
class BlockConfig {
|
||||
private val list = mutableListOf<Any>()
|
||||
|
||||
val leafBlocks = blocks("leaves_blocks_default.cfg")
|
||||
val leafModels = models("leaves_models_default.cfg")
|
||||
val grassBlocks = blocks("grass_blocks_default.cfg")
|
||||
val grassModels = models("grass_models_default.cfg")
|
||||
val mycelium = blocks("mycelium_blocks_default.cfg")
|
||||
// val dirt = blocks("dirt_default.cfg")
|
||||
val crops = blocks("crop_default.cfg")
|
||||
val logBlocks = blocks("log_blocks_default.cfg")
|
||||
val logModels = models("log_models_default.cfg")
|
||||
val sand = blocks("sand_default.cfg")
|
||||
val lilypad = blocks("lilypad_default.cfg")
|
||||
val cactus = blocks("cactus_default.cfg")
|
||||
val netherrack = blocks("netherrack_blocks_default.cfg")
|
||||
|
||||
private fun blocks(cfgName: String) = ConfigurableBlockMatcher(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)
|
||||
} }
|
||||
}
|
||||
}
|
||||
180
src/main/kotlin/mods/betterfoliage/config/Config.kt
Normal file
180
src/main/kotlin/mods/betterfoliage/config/Config.kt
Normal file
@@ -0,0 +1,180 @@
|
||||
package mods.betterfoliage.config
|
||||
|
||||
import mods.betterfoliage.BetterFoliageMod
|
||||
import mods.betterfoliage.resource.discovery.ConfigurableBlockMatcher
|
||||
import mods.betterfoliage.resource.discovery.ModelTextureListConfiguration
|
||||
import net.minecraft.util.ResourceLocation
|
||||
import net.minecraftforge.eventbus.api.SubscribeEvent
|
||||
import net.minecraftforge.fml.config.ModConfig
|
||||
import java.util.Random
|
||||
|
||||
private fun featureEnable() = boolean(true).lang("enabled")
|
||||
|
||||
abstract class PopulationConfigCategory() : ConfigCategory() {
|
||||
abstract val enabled: Boolean
|
||||
abstract val population: Int
|
||||
|
||||
fun enabled(random: Random) = random.nextInt(64) < population && enabled
|
||||
}
|
||||
|
||||
// Config singleton
|
||||
object Config : DelegatingConfig(BetterFoliageMod.MOD_ID, BetterFoliageMod.MOD_ID) {
|
||||
|
||||
val enabled by boolean(true)
|
||||
val nVidia by boolean(false)
|
||||
|
||||
object leaves : ConfigCategory() {
|
||||
val enabled by featureEnable()
|
||||
val snowEnabled by boolean(true)
|
||||
val hOffset by double(max=0.4, default=0.2).lang("hOffset")
|
||||
val vOffset by double(max=0.4, default=0.1).lang("vOffset")
|
||||
val size by double(min=0.75, max=2.5, default=1.4).lang("size")
|
||||
val dense by boolean(false)
|
||||
val hideInternal by boolean(true)
|
||||
val saturationThreshold by double(default=0.1)
|
||||
}
|
||||
|
||||
object shortGrass : PopulationConfigCategory(){
|
||||
override val enabled by featureEnable()
|
||||
val myceliumEnabled by boolean(true)
|
||||
val snowEnabled by boolean(true)
|
||||
val hOffset by double(max=0.4, default=0.2).lang("hOffset")
|
||||
val heightMin by double(min=0.1, max=2.5, default=0.6).lang("heightMin")
|
||||
val heightMax by double(min=0.1, max=2.5, default=0.8).lang("heightMax")
|
||||
val size by double(min=0.5, max=1.5, default=1.0).lang("size")
|
||||
override val population by int(max=64, default=64).lang("population")
|
||||
val useGenerated by boolean(false)
|
||||
val shaderWind by boolean(true).lang("shaderWind")
|
||||
val saturationThreshold by double(default=0.1)
|
||||
}
|
||||
|
||||
object connectedGrass : ConfigCategory(){
|
||||
val enabled by featureEnable()
|
||||
val snowEnabled by boolean(false)
|
||||
}
|
||||
|
||||
object roundLogs : ConfigCategory(){
|
||||
val enabled by featureEnable()
|
||||
val plantsOnly by boolean(true)
|
||||
val radiusSmall by double(max=0.5, default=0.25)
|
||||
val radiusLarge by double(max=0.5, default=0.44)
|
||||
val dimming by double(default = 0.7)
|
||||
val connectSolids by boolean(false)
|
||||
val lenientConnect by boolean(true)
|
||||
val connectPerpendicular by boolean(true)
|
||||
val connectGrass by boolean(true)
|
||||
val defaultY by boolean(false)
|
||||
val zProtection by double(min = 0.9, default = 0.99)
|
||||
}
|
||||
|
||||
object cactus : ConfigCategory(){
|
||||
val enabled by featureEnable()
|
||||
val size by double(min=1.0, max=2.0, default=1.3).lang("size")
|
||||
val sizeVariation by double(max=0.5, default=0.1)
|
||||
val hOffset by double(max=0.5, default=0.1).lang("hOffset")
|
||||
}
|
||||
|
||||
object lilypad : PopulationConfigCategory(){
|
||||
override val enabled by featureEnable()
|
||||
val hOffset by double(max=0.25, default=0.1).lang("hOffset")
|
||||
override val population by int(max=64, default=16, min=0)
|
||||
val shaderWind by boolean(true).lang("shaderWind")
|
||||
}
|
||||
|
||||
object reed : PopulationConfigCategory(){
|
||||
override val enabled by featureEnable()
|
||||
val hOffset by double(max=0.4, default=0.2).lang("hOffset")
|
||||
val heightMin by double(min=1.5, max=3.5, default=1.7).lang("heightMin")
|
||||
val heightMax by double(min=1.5, max=3.5, default=2.2).lang("heightMax")
|
||||
override val population by int(max=64, default=32).lang("population")
|
||||
val minBiomeTemp by double(default=0.4)
|
||||
val minBiomeRainfall by double(default=0.4)
|
||||
// val biomes by biomeList { it.filterTemp(0.4f, null) && it.filterRain(0.4f, null) }
|
||||
val shaderWind by boolean(true).lang("shaderWind")
|
||||
}
|
||||
|
||||
object algae : PopulationConfigCategory(){
|
||||
override val enabled by featureEnable()
|
||||
val hOffset by double(max=0.25, default=0.1).lang("hOffset")
|
||||
val size by double(min=0.5, max=1.5, default=1.0).lang("size")
|
||||
val heightMin by double(min=0.1, max=1.5, default=0.5).lang("heightMin")
|
||||
val heightMax by double(min=0.1, max=1.5, default=1.0).lang("heightMax")
|
||||
override val population by int(max=64, default=48).lang("population")
|
||||
// val biomes by biomeList { it.filterClass("river", "ocean") }
|
||||
val shaderWind by boolean(true).lang("shaderWind")
|
||||
}
|
||||
|
||||
object coral : PopulationConfigCategory(){
|
||||
override val enabled by featureEnable()
|
||||
val shallowWater by boolean(false)
|
||||
val hOffset by double(max=0.4, default=0.2).lang("hOffset")
|
||||
val vOffset by double(max=0.4, default=0.1).lang("vOffset")
|
||||
val size by double(min=0.5, max=1.5, default=0.7).lang("size")
|
||||
val crustSize by double(min=0.5, max=1.5, default=1.4)
|
||||
val chance by int(max=64, default=32)
|
||||
override val population by int(max=64, default=48).lang("population")
|
||||
// val biomes by biomeList { it.filterClass("river", "ocean", "beach") }
|
||||
}
|
||||
|
||||
object netherrack : ConfigCategory(){
|
||||
val enabled by featureEnable()
|
||||
val hOffset by double(max=0.4, default=0.2).lang("hOffset")
|
||||
val heightMin by double(min=0.1, max=1.5, default=0.2).lang("heightMin")
|
||||
val heightMax by double(min=0.1, max=1.5, default=0.5).lang("heightMax")
|
||||
val size by double(min=0.5, max=1.5, default=1.0).lang("size")
|
||||
}
|
||||
|
||||
object fallingLeaves : ConfigCategory(){
|
||||
val enabled by featureEnable()
|
||||
val speed by double(min=0.01, max=0.15, default=0.05)
|
||||
val windStrength by double(min=0.1, max=2.0, default=0.5)
|
||||
val stormStrength by double(min=0.1, max=2.0, default=0.8)
|
||||
val size by double(min=0.25, max=1.5, default=0.75).lang("size")
|
||||
val chance by double(min=0.001, max=1.0, default=0.02)
|
||||
val perturb by double(min=0.01, max=1.0, default=0.25)
|
||||
val lifetime by double(min=1.0, max=15.0, default=5.0)
|
||||
val opacityHack by boolean(true)
|
||||
}
|
||||
|
||||
object risingSoul : ConfigCategory(){
|
||||
val enabled by featureEnable()
|
||||
val chance by double(min=0.001, max=1.0, default=0.02)
|
||||
val perturb by double(min=0.01, max=0.25, default=0.05)
|
||||
val headSize by double(min=0.25, max=1.5, default=1.0)
|
||||
val trailSize by double(min=0.25, max=1.5, default=0.75)
|
||||
val opacity by double(min=0.05, max=1.0, default=0.5)
|
||||
val sizeDecay by double(min=0.5, max=1.0, default=0.97)
|
||||
val opacityDecay by double(min=0.5, max=1.0, default=0.97)
|
||||
val lifetime by double(min=1.0, max=15.0, default=4.0)
|
||||
val trailLength by int(min=2, max=128, default=48)
|
||||
val trailDensity by int(min=1, max=16, default=3)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
object BlockConfig {
|
||||
private val list = mutableListOf<Any>()
|
||||
|
||||
val leafBlocks = blocks("leaves_blocks_default.cfg")
|
||||
val leafModels = models("leaves_models_default.cfg")
|
||||
val grassBlocks = blocks("grass_blocks_default.cfg")
|
||||
val grassModels = models("grass_models_default.cfg")
|
||||
val mycelium = blocks("mycelium_blocks_default.cfg")
|
||||
// val dirt = blocks("dirt_default.cfg")
|
||||
val crops = blocks("crop_default.cfg")
|
||||
val logBlocks = blocks("log_blocks_default.cfg")
|
||||
val logModels = models("log_models_default.cfg")
|
||||
val lilypad = blocks("lilypad_default.cfg")
|
||||
|
||||
init { BetterFoliageMod.bus.register(this) }
|
||||
private fun blocks(cfgName: String) = ConfigurableBlockMatcher(ResourceLocation(BetterFoliageMod.MOD_ID, cfgName)).apply { list.add(this) }
|
||||
private fun models(cfgName: String) = ModelTextureListConfiguration(ResourceLocation(BetterFoliageMod.MOD_ID, cfgName)).apply { list.add(this) }
|
||||
|
||||
@SubscribeEvent
|
||||
fun onConfig(event: ModConfig.ModConfigEvent) {
|
||||
list.forEach { when(it) {
|
||||
is ConfigurableBlockMatcher -> it.readDefaults()
|
||||
is ModelTextureListConfiguration -> it.readDefaults()
|
||||
} }
|
||||
}
|
||||
}
|
||||
@@ -1,137 +0,0 @@
|
||||
package mods.betterfoliage.config
|
||||
|
||||
import me.shedaniel.clothconfig2.api.AbstractConfigListEntry
|
||||
import me.shedaniel.clothconfig2.api.ConfigEntryBuilder
|
||||
import me.shedaniel.clothconfig2.gui.entries.SubCategoryListEntry
|
||||
import me.zeroeightsix.fiber.builder.ConfigValueBuilder
|
||||
import me.zeroeightsix.fiber.tree.ConfigLeaf
|
||||
import me.zeroeightsix.fiber.tree.ConfigNode
|
||||
import me.zeroeightsix.fiber.tree.ConfigValue
|
||||
import net.minecraft.client.resource.language.I18n
|
||||
import java.util.*
|
||||
import kotlin.properties.ReadOnlyProperty
|
||||
import kotlin.reflect.KProperty
|
||||
|
||||
const val MAX_LINE_LEN = 30
|
||||
|
||||
sealed class DelegatingConfigNode<N: ConfigLeaf>(val fiberNode: N) {
|
||||
abstract fun createClothNode(names: List<String>): AbstractConfigListEntry<*>
|
||||
}
|
||||
|
||||
abstract class DelegatingConfigValue<T>(fiberNode: ConfigValue<T>) : DelegatingConfigNode<ConfigValue<T>>(fiberNode), ReadOnlyProperty<DelegatingConfigGroup, T>
|
||||
|
||||
open class DelegatingConfigGroup(fiberNode: ConfigNode) : DelegatingConfigNode<ConfigNode>(fiberNode) {
|
||||
val children = mutableListOf<DelegatingConfigNode<*>>()
|
||||
override fun createClothNode(names: List<String>): SubCategoryListEntry {
|
||||
val builder = ConfigEntryBuilder.create()
|
||||
.startSubCategory(names.joinToString(".").translate())
|
||||
.setTooltip(*names.joinToString(".").translateTooltip())
|
||||
.setExpended(false)
|
||||
children.forEach { builder.add(it.createClothNode(names + it.fiberNode.name!!)) }
|
||||
return builder.build()
|
||||
}
|
||||
operator fun get(name: String) = children.find { it.fiberNode.name == name }
|
||||
}
|
||||
|
||||
interface DelegatingConfigGroupFactory<T> {
|
||||
operator fun provideDelegate(parent: DelegatingConfigGroup, property: KProperty<*>): ReadOnlyProperty<DelegatingConfigGroup, T>
|
||||
}
|
||||
|
||||
fun <T: DelegatingConfigGroup> subNode(factory: (ConfigNode)->T) = object : DelegatingConfigGroupFactory<T> {
|
||||
override operator fun provideDelegate(parent: DelegatingConfigGroup, property: KProperty<*>): ReadOnlyProperty<DelegatingConfigGroup, T> {
|
||||
val childNode = ConfigNode(property.name, null)
|
||||
val configGroup = factory(childNode)
|
||||
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> {
|
||||
fun createFiberNode(parent: ConfigNode, name: String): ConfigValue<T>
|
||||
fun createClothNode(node: ConfigValue<T>, names: List<String>): AbstractConfigListEntry<T>
|
||||
|
||||
operator fun provideDelegate(parent: DelegatingConfigGroup, property: KProperty<*>): ReadOnlyProperty<DelegatingConfigGroup, T> {
|
||||
return object : DelegatingConfigValue<T>(createFiberNode(parent.fiberNode, property.name)) {
|
||||
override fun createClothNode(names: List<String>) = createClothNode(fiberNode, names)
|
||||
override fun getValue(thisRef: DelegatingConfigGroup, property: KProperty<*>) = fiberNode.value!!
|
||||
}.apply { parent.children.add(this) }
|
||||
}
|
||||
}
|
||||
|
||||
fun String.translate() = I18n.translate(this)
|
||||
fun String.translateTooltip(lineLength: Int = MAX_LINE_LEN) = ("$this.tooltip").translate().let { tooltip ->
|
||||
tooltip.splitToSequence(" ").fold(mutableListOf("")) { tooltips, word ->
|
||||
if (tooltips.last().length + word.length < lineLength) {
|
||||
tooltips[tooltips.lastIndex] += "$word "
|
||||
} else {
|
||||
tooltips.add("$word ")
|
||||
}
|
||||
tooltips
|
||||
}.map { it.trim() }.toTypedArray()
|
||||
}
|
||||
|
||||
fun boolean(
|
||||
default: Boolean,
|
||||
langKey: (List<String>)->String = { it.joinToString(".") },
|
||||
valueOverride: (Boolean)->Boolean = { it }
|
||||
) = object : DelegatingConfigValueFactory<Boolean> {
|
||||
override fun createFiberNode(parent: ConfigNode, name: String) = ConfigValueBuilder(Boolean::class.java)
|
||||
.withName(name)
|
||||
.withParent(parent)
|
||||
.withDefaultValue(default)
|
||||
.build()
|
||||
|
||||
override fun createClothNode(node: ConfigValue<Boolean>, names: List<String>) = ConfigEntryBuilder.create()
|
||||
.startBooleanToggle(langKey(names).translate(), node.value!!)
|
||||
.setTooltip(langKey(names).let { if (I18n.hasTranslation("$it.tooltip")) Optional.of(it.translateTooltip()) else Optional.empty() })
|
||||
.setSaveConsumer { node.value = valueOverride(it) }
|
||||
.build()
|
||||
}
|
||||
|
||||
fun integer(
|
||||
default: Int, min: Int, max: Int,
|
||||
langKey: (List<String>)->String = { it.joinToString(".") },
|
||||
valueOverride: (Int)->Int = { it }
|
||||
) = object : DelegatingConfigValueFactory<Int> {
|
||||
override fun createFiberNode(parent: ConfigNode, name: String) = ConfigValueBuilder(Int::class.java)
|
||||
.withName(name)
|
||||
.withParent(parent)
|
||||
.withDefaultValue(default)
|
||||
.constraints().minNumerical(min).maxNumerical(max).finish()
|
||||
.build()
|
||||
|
||||
override fun createClothNode(node: ConfigValue<Int>, names: List<String>) = ConfigEntryBuilder.create()
|
||||
.startIntField(langKey(names).translate(), node.value!!)
|
||||
.setTooltip(langKey(names).let { if (I18n.hasTranslation("$it.tooltip")) Optional.of(it.translateTooltip()) else Optional.empty() })
|
||||
.setMin(min).setMax(max)
|
||||
.setSaveConsumer { node.value = valueOverride(it) }
|
||||
.build()
|
||||
}
|
||||
|
||||
fun double(
|
||||
default: Double, min: Double, max: Double,
|
||||
langKey: (List<String>)->String = { it.joinToString(".") },
|
||||
valueOverride: (Double)->Double = { it }
|
||||
) = object : DelegatingConfigValueFactory<Double> {
|
||||
override fun createFiberNode(parent: ConfigNode, name: String) = ConfigValueBuilder(Double::class.java)
|
||||
.withName(name)
|
||||
.withParent(parent)
|
||||
.withDefaultValue(default)
|
||||
.constraints().minNumerical(min).maxNumerical(max).finish()
|
||||
.build()
|
||||
|
||||
override fun createClothNode(node: ConfigValue<Double>, names: List<String>) = ConfigEntryBuilder.create()
|
||||
.startDoubleField(langKey(names).translate(), node.value!!)
|
||||
.setTooltip(langKey(names).let { if (I18n.hasTranslation("$it.tooltip")) Optional.of(it.translateTooltip()) else Optional.empty() })
|
||||
.setMin(min).setMax(max)
|
||||
.setSaveConsumer { node.value = valueOverride(it) }
|
||||
.build()
|
||||
}
|
||||
|
||||
val recurring = { names: List<String> -> "${names.first()}.${names.last()}" }
|
||||
fun fakeCategory(name: String) = { names: List<String> ->
|
||||
(listOf(names.first(), name) + names.drop(1)).joinToString(".")
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
@file:JvmName("DelegatingConfigKt")
|
||||
|
||||
package mods.betterfoliage.config
|
||||
|
||||
import mods.betterfoliage.util.reflectDelegates
|
||||
import mods.betterfoliage.util.reflectNestedObjects
|
||||
import net.minecraftforge.common.ForgeConfigSpec
|
||||
import kotlin.properties.ReadOnlyProperty
|
||||
import kotlin.reflect.KProperty
|
||||
|
||||
open class DelegatingConfig(val modId: String, val langPrefix: String) {
|
||||
fun build() = ForgeConfigSpec.Builder().apply { ConfigBuildContext(langPrefix, emptyList(), this).addCategory(this@DelegatingConfig) }.build()
|
||||
}
|
||||
|
||||
class ConfigBuildContext(val langPrefix: String, val path: List<String>, val builder: ForgeConfigSpec.Builder) {
|
||||
|
||||
fun addCategory(configObj: Any) {
|
||||
configObj.reflectNestedObjects.forEach { (name, category) ->
|
||||
builder.push(name)
|
||||
descend(name).addCategory(category)
|
||||
builder.pop()
|
||||
}
|
||||
configObj.reflectDelegates(ConfigDelegate::class.java).forEach { (name, delegate) ->
|
||||
descend(name).apply { delegate.addToBuilder(this) }
|
||||
}
|
||||
}
|
||||
|
||||
fun descend(pathName: String) = ConfigBuildContext(langPrefix, path + pathName, builder)
|
||||
}
|
||||
|
||||
open class ConfigCategory(val comment: String? = null) {
|
||||
}
|
||||
|
||||
abstract class ConfigDelegate<T> : ReadOnlyProperty<Any, T> {
|
||||
lateinit var configValue: ForgeConfigSpec.ConfigValue<T>
|
||||
var cachedValue: T? = null
|
||||
|
||||
override fun getValue(thisRef: Any, property: KProperty<*>): T {
|
||||
if (cachedValue == null) cachedValue = configValue.get()
|
||||
return cachedValue!!
|
||||
}
|
||||
|
||||
abstract fun getConfigValue(name: String, builder: ForgeConfigSpec.Builder): ForgeConfigSpec.ConfigValue<T>
|
||||
fun addToBuilder(ctx: ConfigBuildContext) {
|
||||
val langKey = ctx.langPrefix + "." + (langPrefixOverride ?: ctx.path.joinToString("."))
|
||||
ctx.builder.translation(langKey)
|
||||
configValue = getConfigValue(ctx.path.last(), ctx.builder)
|
||||
}
|
||||
|
||||
var langPrefixOverride: String? = null
|
||||
fun lang(prefix: String) = apply { langPrefixOverride = prefix }
|
||||
|
||||
}
|
||||
|
||||
class DelegatingBooleanValue(val defaultValue: Boolean) : ConfigDelegate<Boolean>() {
|
||||
override fun getConfigValue(name: String, builder: ForgeConfigSpec.Builder) = builder.define(name, defaultValue)
|
||||
}
|
||||
|
||||
class DelegatingIntValue(
|
||||
val minValue: Int = 0,
|
||||
val maxValue: Int = 1,
|
||||
val defaultValue: Int = 0
|
||||
) : ConfigDelegate<Int>() {
|
||||
override fun getConfigValue(name: String, builder: ForgeConfigSpec.Builder) = builder.defineInRange(name, defaultValue, minValue, maxValue)
|
||||
}
|
||||
|
||||
class DelegatingLongValue(
|
||||
val minValue: Long = 0,
|
||||
val maxValue: Long = 1,
|
||||
val defaultValue: Long = 0
|
||||
) : ConfigDelegate<Long>() {
|
||||
override fun getConfigValue(name: String, builder: ForgeConfigSpec.Builder) = builder.defineInRange(name, defaultValue, minValue, maxValue)
|
||||
}
|
||||
|
||||
class DelegatingDoubleValue(
|
||||
val minValue: Double = 0.0,
|
||||
val maxValue: Double = 1.0,
|
||||
val defaultValue: Double = 0.0
|
||||
) : ConfigDelegate<Double>() {
|
||||
override fun getConfigValue(name: String, builder: ForgeConfigSpec.Builder) = builder.defineInRange(name, defaultValue, minValue, maxValue)
|
||||
}
|
||||
|
||||
// ============================
|
||||
// Delegate factory methods
|
||||
// ============================
|
||||
fun double(min: Double = 0.0, max: Double = 1.0, default: Double) = DelegatingDoubleValue(min, max, default)
|
||||
fun int(min: Int = 0, max: Int, default: Int) = DelegatingIntValue(min, max, default)
|
||||
fun long(min: Long = 0, max: Long, default: Long) = DelegatingLongValue(min, max, default)
|
||||
fun boolean(default: Boolean) = DelegatingBooleanValue(default)
|
||||
@@ -1,154 +0,0 @@
|
||||
package mods.betterfoliage.config
|
||||
|
||||
import me.zeroeightsix.fiber.tree.ConfigNode
|
||||
import java.util.*
|
||||
|
||||
interface PopulationConfigData {
|
||||
val enabled: Boolean
|
||||
val population: Int
|
||||
fun enabled(random: Random) = random.nextInt(64) < population && enabled
|
||||
}
|
||||
|
||||
fun population(default: Int) = integer(default, min = 0, max = 64, langKey = recurring)
|
||||
|
||||
class MainConfig : DelegatingConfigGroup(ConfigNode("root", null)) {
|
||||
|
||||
val enabled by boolean(true, langKey = fakeCategory("global"))
|
||||
val nVidia by boolean(true, langKey = fakeCategory("global"))
|
||||
|
||||
val leaves by subNode { LeavesConfig(it) }
|
||||
val shortGrass by subNode { ShortGrassConfig(it) }
|
||||
val connectedGrass by subNode { ConnectedGrassConfig(it) }
|
||||
val roundLogs by subNode { RoundLogConfig(it) }
|
||||
val cactus by subNode { CactusConfig(it) }
|
||||
val lilypad by subNode { LilypadConfig(it) }
|
||||
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(node: ConfigNode) : DelegatingConfigGroup(node) {
|
||||
val enabled by boolean(true, langKey = recurring)
|
||||
val snowEnabled by boolean(true)
|
||||
val dense by boolean(false)
|
||||
val hideInternal by boolean(true)
|
||||
|
||||
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(node: ConfigNode) : DelegatingConfigGroup(node), PopulationConfigData {
|
||||
override val enabled by boolean(true, langKey = recurring)
|
||||
val myceliumEnabled by boolean(true)
|
||||
val snowEnabled by boolean(true)
|
||||
val hOffset by double(0.2, min = 0.0, max = 0.4, langKey = recurring)
|
||||
val heightMin by double(0.6, min = 0.1, max = 2.5, langKey = recurring)
|
||||
val heightMax by double(0.6, min = 0.1, max = 2.5, langKey = recurring) { it.coerceAtLeast(heightMin) }
|
||||
val size by double(1.0, min = 0.5, max = 1.5, langKey = recurring)
|
||||
override val population by population(64)
|
||||
val useGenerated by boolean(false)
|
||||
val shaderWind by boolean(true, langKey = recurring)
|
||||
val saturationThreshold by double(0.1, min = 0.0, max = 1.0)
|
||||
}
|
||||
|
||||
class ConnectedGrassConfig(node: ConfigNode) : DelegatingConfigGroup(node) {
|
||||
val enabled by boolean(true, langKey = recurring)
|
||||
val snowEnabled by boolean(true)
|
||||
}
|
||||
|
||||
class RoundLogConfig(node: ConfigNode) : DelegatingConfigGroup(node) {
|
||||
val enabled by boolean(true, langKey = recurring)
|
||||
|
||||
val defaultY by boolean(false)
|
||||
val connectSolids by boolean(false)
|
||||
val lenientConnect by boolean(true)
|
||||
val connectPerpendicular by boolean(true)
|
||||
val connectGrass by boolean(true)
|
||||
|
||||
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(node: ConfigNode) : DelegatingConfigGroup(node) {
|
||||
val enabled by boolean(true, langKey = recurring)
|
||||
val size by double(1.3, min = 0.5, max = 1.5, langKey = recurring)
|
||||
val sizeVariation by double(0.1, min = 0.0, max = 0.5)
|
||||
val hOffset by double(0.1, min = 0.0, max = 0.5, langKey = recurring)
|
||||
}
|
||||
|
||||
class LilypadConfig(node: ConfigNode) : DelegatingConfigGroup(node), PopulationConfigData {
|
||||
override val enabled by boolean(true, langKey = recurring)
|
||||
val hOffset by double(0.1, min = 0.0, max = 0.25, 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)
|
||||
}
|
||||
|
||||
class AlgaeConfig(node: ConfigNode) : DelegatingConfigGroup(node), PopulationConfigData {
|
||||
override val enabled by boolean(true, langKey = recurring)
|
||||
val hOffset by double(0.1, min = 0.0, max = 0.4, langKey = recurring)
|
||||
val size by double(1.0, min = 0.5, max = 1.5, langKey = recurring)
|
||||
val heightMin by double(0.5, min = 0.1, max = 1.0, langKey = recurring)
|
||||
val heightMax by double(0.5, min = 0.1, max = 1.0, langKey = recurring) { it.coerceAtLeast(heightMin) }
|
||||
override val population by population(48)
|
||||
val shaderWind by boolean(true, langKey = recurring)
|
||||
}
|
||||
|
||||
class CoralConfig(node: ConfigNode) : DelegatingConfigGroup(node), PopulationConfigData {
|
||||
override val enabled by boolean(true, langKey = recurring)
|
||||
val shallowWater by boolean(false)
|
||||
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(0.7, min = 0.5, max = 1.5, langKey = recurring)
|
||||
val crustSize by double(1.4, min = 0.5, max = 1.5)
|
||||
val chance by integer(32, min = 0, max = 64)
|
||||
override val population by population(48)
|
||||
}
|
||||
|
||||
class NetherrackConfig(node: ConfigNode) : DelegatingConfigGroup(node) {
|
||||
val enabled by boolean(true, langKey = recurring)
|
||||
val hOffset by double(0.2, min = 0.0, max = 0.4, langKey = recurring)
|
||||
val size by double(1.0, min = 0.5, max = 1.5, langKey = recurring)
|
||||
val heightMin by double(0.6, min = 0.5, max = 1.5, langKey = recurring)
|
||||
val heightMax by double(0.8, min = 0.5, max = 1.5, langKey = recurring) { it.coerceAtLeast(heightMin) }
|
||||
}
|
||||
|
||||
class FallingLeavesConfig(node: ConfigNode) : DelegatingConfigGroup(node) {
|
||||
val enabled by boolean(true, langKey = recurring)
|
||||
val speed by double(0.05, min = 0.01, max = 0.15)
|
||||
val windStrength by double(0.5, min = 0.1, max = 2.0)
|
||||
val stormStrength by double(0.8, min = 0.1, max = 2.0) { it.coerceAtLeast(windStrength) }
|
||||
val size by double(0.75, min = 0.25, max = 1.5)
|
||||
val chance by double(0.05, min = 0.001, max = 1.0)
|
||||
val perturb by double(0.25, min = 0.01, max = 1.0)
|
||||
val lifetime by double(7.5, min = 1.0, max = 15.0)
|
||||
}
|
||||
|
||||
class RisingSoulConfig(node: ConfigNode) : DelegatingConfigGroup(node) {
|
||||
val enabled by boolean(true, langKey = recurring)
|
||||
val chance by double(0.02, min = 0.001, max = 1.0)
|
||||
val perturb by double(0.05, min = 0.01, max = 0.25)
|
||||
val headSize by double(1.0, min = 0.25, max = 1.5)
|
||||
val trailSize by double(0.75, min = 0.25, max = 1.5)
|
||||
val opacity by double(0.5, min = 0.05, max = 1.0)
|
||||
val sizeDecay by double(0.97, min = 0.5, max = 1.0)
|
||||
val opacityDecay by double(0.97, min = 0.5, max = 1.0)
|
||||
val lifetime by double(4.0, min = 1.0, max = 15.0)
|
||||
val trailLength by integer(48, min = 2, max = 128)
|
||||
val trailDensity by integer(3, min = 1, max = 16)
|
||||
}
|
||||
20
src/main/kotlin/mods/betterfoliage/config/MiscDefaults.kt
Normal file
20
src/main/kotlin/mods/betterfoliage/config/MiscDefaults.kt
Normal file
@@ -0,0 +1,20 @@
|
||||
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 CACTUS_BLOCKS = listOf(Blocks.CACTUS)
|
||||
val DIRT_BLOCKS = listOf(Blocks.DIRT, Blocks.COARSE_DIRT, Blocks.PODZOL)
|
||||
val SAND_BLOCKS = listOf(Blocks.SAND, Blocks.RED_SAND)
|
||||
val NETHERRACK_BLOCKS = listOf(Blocks.NETHERRACK)
|
||||
val LILYPAD_BLOCKS = listOf(Blocks.LILY_PAD)
|
||||
val MYCELIUM_BLOCKS = listOf(Blocks.MYCELIUM)
|
||||
|
||||
val SALTWATER_BIOMES = listOf(Biome.Category.BEACH, Biome.Category.OCEAN)
|
||||
|
||||
val 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)
|
||||
@@ -1,110 +0,0 @@
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
*/
|
||||
@@ -1,29 +0,0 @@
|
||||
package mods.betterfoliage.integration
|
||||
|
||||
import io.github.prospector.modmenu.api.ModMenuApi
|
||||
import me.shedaniel.clothconfig2.api.ConfigBuilder
|
||||
import me.zeroeightsix.fiber.JanksonSettings
|
||||
import mods.betterfoliage.BetterFoliage
|
||||
import 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()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
package mods.betterfoliage.integration
|
||||
|
||||
import mods.betterfoliage.chunk.BlockCtx
|
||||
import mods.betterfoliage.util.ThreadLocalDelegate
|
||||
import mods.betterfoliage.util.allAvailable
|
||||
import mods.betterfoliage.util.reflectField
|
||||
import mods.octarinecore.BlockPos
|
||||
import mods.octarinecore.BlockState
|
||||
import mods.octarinecore.CustomColors
|
||||
import mods.octarinecore.RenderEnv
|
||||
import net.minecraft.block.BlockState
|
||||
import net.minecraft.client.Minecraft
|
||||
import net.minecraft.client.renderer.model.BakedQuad
|
||||
import net.minecraft.util.Direction.UP
|
||||
import net.minecraft.util.math.BlockPos
|
||||
import net.minecraft.world.level.ColorResolver
|
||||
import org.apache.logging.log4j.Level.INFO
|
||||
import org.apache.logging.log4j.LogManager
|
||||
|
||||
/**
|
||||
* Integration for OptiFine custom block colors.
|
||||
*/
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
object OptifineCustomColors {
|
||||
val logger = LogManager.getLogger(this)
|
||||
|
||||
val isColorAvailable = allAvailable(CustomColors, CustomColors.getColorMultiplier)
|
||||
|
||||
init {
|
||||
logger.log(INFO, "Optifine custom color support is ${if (isColorAvailable) "enabled" else "disabled" }")
|
||||
}
|
||||
|
||||
val renderEnv by ThreadLocalDelegate { OptifineRenderEnv() }
|
||||
val fakeQuad = BakedQuad(IntArray(0), 1, UP, null, true)
|
||||
|
||||
fun getBlockColor(ctx: BlockCtx, resolver: ColorResolver): Int {
|
||||
val ofColor = if (isColorAvailable && Minecraft.getInstance().options.reflectField<Boolean>("ofCustomColors") == true) {
|
||||
renderEnv.reset(ctx.state, ctx.pos)
|
||||
CustomColors.getColorMultiplier.invokeStatic(fakeQuad, ctx.state, ctx.world, ctx.pos, renderEnv.wrapped) as? Int
|
||||
} else null
|
||||
return if (ofColor == null || ofColor == -1) ctx.color(resolver) else ofColor
|
||||
}
|
||||
}
|
||||
|
||||
class OptifineRenderEnv {
|
||||
val wrapped: Any = RenderEnv.element!!.getDeclaredConstructor(BlockState.element, BlockPos.element).let {
|
||||
it.isAccessible = true
|
||||
it.newInstance(null, null)
|
||||
}
|
||||
|
||||
fun reset(state: BlockState, pos: BlockPos) {
|
||||
RenderEnv.reset.invoke(wrapped, state, pos)
|
||||
}
|
||||
}
|
||||
@@ -1,139 +0,0 @@
|
||||
package mods.betterfoliage.integration
|
||||
|
||||
|
||||
object IC2RubberIntegration {
|
||||
|
||||
// val BlockRubWood = ClassRefOld<Any>("ic2.core.block.BlockRubWood")
|
||||
|
||||
init {
|
||||
// if (ModList.get().isLoaded("ic2") && allAvailable(BlockRubWood)) {
|
||||
// BetterFoliage.log(Level.INFO, "IC2 rubber support initialized")
|
||||
// LogRegistry.registries.add(IC2LogDiscovery)
|
||||
// BetterFoliage.blockSprites.providers.add(IC2LogDiscovery)
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
object TechRebornRubberIntegration {
|
||||
|
||||
// val BlockRubberLog = ClassRefOld<Any>("techreborn.blocks.BlockRubberLog")
|
||||
|
||||
init {
|
||||
// if (ModList.get().isLoaded("techreborn") && allAvailable(BlockRubberLog)) {
|
||||
// BetterFoliage.log(Level.INFO, "TechReborn rubber support initialized")
|
||||
// LogRegistry.registries.add(TechRebornLogDiscovery)
|
||||
// BetterFoliage.blockSprites.providers.add(TechRebornLogDiscovery)
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
class RubberLogInfo(
|
||||
axis: Axis?,
|
||||
val spotDir: Direction,
|
||||
topTexture: Sprite,
|
||||
bottomTexture: Sprite,
|
||||
val spotTexture: Sprite,
|
||||
sideTextures: List<Sprite>
|
||||
) : SimpleColumnInfo(axis, topTexture, bottomTexture, sideTextures) {
|
||||
|
||||
override val side: QuadIconResolver = { ctx: CombinedContext, idx: Int, quad: Quad ->
|
||||
val worldFace = (if ((idx and 1) == 0) SOUTH else EAST).rotate(ctx.modelRotation)
|
||||
if (worldFace == spotDir) spotTexture else {
|
||||
val sideIdx = if (this.sideTextures.size > 1) (ctx.semiRandom(1) + dirToIdx[worldFace.ordinal]) % this.sideTextures.size else 0
|
||||
this.sideTextures[sideIdx]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
object IC2LogDiscovery : ModelDiscovery<ColumnTextureInfo>() {
|
||||
override val logger = BetterFoliage.logDetail
|
||||
|
||||
override fun processModel(ctx: ModelDiscoveryContext, atlas: AtlasFuture): CompletableFuture<ColumnTextureInfo>? {
|
||||
// check for proper block class, existence of ModelBlock, and "state" blockstate property
|
||||
if (!IC2RubberIntegration.BlockRubWood.isInstance(ctx.state.block)) return null
|
||||
val blockLoc = ctx.models.firstOrNull() as Pair<JsonUnbakedModel, Identifier> ?: return null
|
||||
val type = ctx.state.entries.entries.find { it.key.getName() == "state" }?.value?.toString() ?: return null
|
||||
|
||||
// logs with no rubber spot
|
||||
if (blockLoc.derivesFrom(Identifier("block/cube_column"))) {
|
||||
val axis = when(type) {
|
||||
"plain_y" -> Axis.Y
|
||||
"plain_x" -> Axis.X
|
||||
"plain_z" -> Axis.Z
|
||||
else -> null
|
||||
}
|
||||
val textureNames = listOf("end", "side").map { blockLoc.first.resolveTexture(it) }
|
||||
if (textureNames.any { it == "missingno" }) return null
|
||||
log("IC2LogSupport: block state ${ctx.state.toString()}")
|
||||
log("IC2LogSupport: axis=$axis, end=${textureNames[0]}, side=${textureNames[1]}")
|
||||
val endSprite = atlas.sprite(textureNames[0])
|
||||
val sideSprite = atlas.sprite(textureNames[1])
|
||||
return atlas.mapAfter {
|
||||
SimpleColumnInfo(axis, endSprite.get(), endSprite.get(), listOf(sideSprite.get()))
|
||||
}
|
||||
}
|
||||
|
||||
// logs with rubber spot
|
||||
val spotDir = when(type) {
|
||||
"dry_north", "wet_north" -> NORTH
|
||||
"dry_south", "wet_south" -> SOUTH
|
||||
"dry_west", "wet_west" -> WEST
|
||||
"dry_east", "wet_east" -> EAST
|
||||
else -> null
|
||||
}
|
||||
val textureNames = listOf("up", "down", "north", "south").map { blockLoc.first.resolveTexture(it) }
|
||||
if (textureNames.any { it == "missingno" }) return null
|
||||
log("IC2LogSupport: block state ${ctx.state.toString()}")
|
||||
log("IC2LogSupport: spotDir=$spotDir, up=${textureNames[0]}, down=${textureNames[1]}, side=${textureNames[2]}, spot=${textureNames[3]}")
|
||||
val upSprite = atlas.sprite(textureNames[0])
|
||||
val downSprite = atlas.sprite(textureNames[1])
|
||||
val sideSprite = atlas.sprite(textureNames[2])
|
||||
val spotSprite = atlas.sprite(textureNames[3])
|
||||
return if (spotDir != null) atlas.mapAfter {
|
||||
RubberLogInfo(Axis.Y, spotDir, upSprite.get(), downSprite.get(), spotSprite.get(), listOf(sideSprite.get()))
|
||||
} else atlas.mapAfter {
|
||||
SimpleColumnInfo(Axis.Y, upSprite.get(), downSprite.get(), listOf(sideSprite.get()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
object TechRebornLogDiscovery : ModelDiscovery<ColumnTextureInfo>() {
|
||||
override val logger = BetterFoliage.logDetail
|
||||
|
||||
override fun processModel(ctx: ModelDiscoveryContext, atlas: AtlasFuture): CompletableFuture<ColumnTextureInfo>? {
|
||||
// check for proper block class, existence of ModelBlock
|
||||
if (!TechRebornRubberIntegration.BlockRubberLog.isInstance(ctx.state.block)) return null
|
||||
val blockLoc = ctx.models.map { it as? Pair<JsonUnbakedModel, Identifier> }.firstOrNull() ?: return null
|
||||
|
||||
val hasSap = ctx.state.entries.entries.find { it.key.getName() == "hassap" }?.value as? Boolean ?: return null
|
||||
val sapSide = ctx.state.entries.entries.find { it.key.getName() == "sapside" }?.value as? Direction ?: return null
|
||||
|
||||
log("$logName: block state ${ctx.state}")
|
||||
if (hasSap) {
|
||||
val textureNames = listOf("end", "side", "sapside").map { blockLoc.first.resolveTexture(it) }
|
||||
log("$logName: spotDir=$sapSide, end=${textureNames[0]}, side=${textureNames[2]}, spot=${textureNames[3]}")
|
||||
if (textureNames.all { it != "missingno" }) {
|
||||
val endSprite = atlas.sprite(textureNames[0])
|
||||
val sideSprite = atlas.sprite(textureNames[1])
|
||||
val sapSprite = atlas.sprite(textureNames[2])
|
||||
return atlas.mapAfter {
|
||||
RubberLogInfo(Axis.Y, sapSide, endSprite.get(), endSprite.get(), sapSprite.get(), listOf(sideSprite.get()))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
val textureNames = listOf("end", "side").map { blockLoc.first.resolveTexture(it) }
|
||||
log("$logName: end=${textureNames[0]}, side=${textureNames[1]}")
|
||||
if (textureNames.all { it != "missingno" }) {
|
||||
val endSprite = atlas.sprite(textureNames[0])
|
||||
val sideSprite = atlas.sprite(textureNames[1])
|
||||
return atlas.mapAfter {
|
||||
SimpleColumnInfo(Axis.Y, endSprite.get(), endSprite.get(), listOf(sideSprite.get()))
|
||||
}
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
*/
|
||||
@@ -0,0 +1,66 @@
|
||||
package mods.betterfoliage.integration
|
||||
|
||||
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.octarinecore.*
|
||||
import net.minecraft.block.BlockRenderType
|
||||
import net.minecraft.block.BlockRenderType.MODEL
|
||||
import net.minecraft.block.BlockState
|
||||
import net.minecraft.block.Blocks
|
||||
import net.minecraft.client.renderer.BufferBuilder
|
||||
import net.minecraft.util.math.BlockPos
|
||||
import net.minecraft.world.IBlockDisplayReader
|
||||
import org.apache.logging.log4j.Level.INFO
|
||||
|
||||
/**
|
||||
* Integration for ShadersMod.
|
||||
*/
|
||||
object ShadersModIntegration : HasLogger() {
|
||||
@JvmStatic val isAvailable = allAvailable(SVertexBuilder, SVertexBuilder.pushState, SVertexBuilder.popState, BlockAliases.getAliasBlockId)
|
||||
|
||||
val defaultLeaves = Blocks.OAK_LEAVES.defaultBlockState()
|
||||
val defaultGrass = Blocks.GRASS.defaultBlockState()
|
||||
|
||||
/**
|
||||
* Called from transformed ShadersMod code.
|
||||
* @see mods.betterfoliage.loader.BetterFoliageTransformer
|
||||
*/
|
||||
@JvmStatic fun getBlockStateOverride(state: BlockState, world: IBlockDisplayReader, pos: BlockPos): BlockState {
|
||||
// if (LeafRegistry[state, world, pos] != null) return defaultLeaves
|
||||
// if (BlockConfig.crops.matchesClass(state.block)) return defaultGrass
|
||||
return state
|
||||
}
|
||||
|
||||
init {
|
||||
logger.log(INFO, "ShadersMod integration is ${if (isAvailable) "enabled" else "disabled" }")
|
||||
}
|
||||
|
||||
/** Quads rendered inside this block will use the given block entity data in shader programs. */
|
||||
inline fun renderAs(buffer: BufferBuilder, state: BlockState, renderType: BlockRenderType, enabled: Boolean = true, func: ()->Unit) {
|
||||
if (isAvailable && 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)
|
||||
}
|
||||
}
|
||||
118
src/main/kotlin/mods/betterfoliage/model/HalfBaked.kt
Normal file
118
src/main/kotlin/mods/betterfoliage/model/HalfBaked.kt
Normal file
@@ -0,0 +1,118 @@
|
||||
package mods.betterfoliage.model
|
||||
|
||||
import mods.betterfoliage.render.pipeline.RenderCtxBase
|
||||
import mods.betterfoliage.resource.discovery.ModelBakingContext
|
||||
import mods.betterfoliage.resource.discovery.ModelBakingKey
|
||||
import mods.betterfoliage.util.Double3
|
||||
import mods.betterfoliage.util.HasLogger
|
||||
import mods.betterfoliage.util.directionsAndNull
|
||||
import mods.betterfoliage.util.mapArray
|
||||
import net.minecraft.client.renderer.model.BakedQuad
|
||||
import net.minecraft.client.renderer.model.IBakedModel
|
||||
import net.minecraft.client.renderer.model.SimpleBakedModel
|
||||
import net.minecraft.client.renderer.vertex.DefaultVertexFormats
|
||||
import net.minecraft.client.renderer.vertex.VertexFormatElement
|
||||
import net.minecraftforge.client.model.pipeline.BakedQuadBuilder
|
||||
import java.util.Random
|
||||
|
||||
/**
|
||||
* Hybrid baked quad implementation, carrying both baked and unbaked information.
|
||||
* Used to do advanced vertex lighting without unbaking vertex data at lighting time.
|
||||
*/
|
||||
data class HalfBakedQuad(
|
||||
val raw: Quad,
|
||||
val baked: BakedQuad
|
||||
)
|
||||
|
||||
open class HalfBakedSimpleModelWrapper(baseModel: SimpleBakedModel): IBakedModel by baseModel, SpecialRenderModel {
|
||||
val baseQuads = baseModel.unbakeQuads()
|
||||
|
||||
override fun render(ctx: RenderCtxBase, noDecorations: Boolean) {
|
||||
ctx.renderQuads(baseQuads)
|
||||
}
|
||||
}
|
||||
|
||||
open class HalfBakedSpecialWrapper(val baseModel: SpecialRenderModel): IBakedModel by baseModel, SpecialRenderModel {
|
||||
override fun render(ctx: RenderCtxBase, noDecorations: Boolean) {
|
||||
baseModel.render(ctx, noDecorations)
|
||||
}
|
||||
}
|
||||
|
||||
abstract class HalfBakedWrapperKey : ModelBakingKey, HasLogger() {
|
||||
override fun bake(ctx: ModelBakingContext): IBakedModel? {
|
||||
val baseModel = super.bake(ctx)
|
||||
val halfBaked = when(baseModel) {
|
||||
is SimpleBakedModel -> HalfBakedSimpleModelWrapper(baseModel)
|
||||
else -> null
|
||||
}
|
||||
return if (halfBaked == null) baseModel else bake(ctx, halfBaked)
|
||||
}
|
||||
|
||||
abstract fun bake(ctx: ModelBakingContext, wrapped: SpecialRenderModel): SpecialRenderModel
|
||||
}
|
||||
|
||||
fun List<Quad>.bake(applyDiffuseLighting: Boolean) = map { quad ->
|
||||
if (quad.sprite == null) throw IllegalStateException("Quad must have a texture assigned before baking")
|
||||
val builder = BakedQuadBuilder(quad.sprite)
|
||||
builder.setApplyDiffuseLighting(applyDiffuseLighting)
|
||||
builder.setQuadOrientation(quad.face())
|
||||
builder.setQuadTint(quad.colorIndex)
|
||||
quad.verts.forEach { vertex ->
|
||||
DefaultVertexFormats.BLOCK.elements.forEachIndexed { idx, element ->
|
||||
when {
|
||||
element.usage == VertexFormatElement.Usage.POSITION -> builder.put(idx,
|
||||
(vertex.xyz.x + 0.5).toFloat(),
|
||||
(vertex.xyz.y + 0.5).toFloat(),
|
||||
(vertex.xyz.z + 0.5).toFloat(),
|
||||
1.0f
|
||||
)
|
||||
// don't fill lightmap UV coords
|
||||
element.usage == VertexFormatElement.Usage.UV && element.type == VertexFormatElement.Type.FLOAT -> builder.put(idx,
|
||||
quad.sprite.u0 + (quad.sprite.u1 - quad.sprite.u0) * (vertex.uv.u + 0.5).toFloat(),
|
||||
quad.sprite.v0 + (quad.sprite.v1 - quad.sprite.v0) * (vertex.uv.v + 0.5).toFloat(),
|
||||
0.0f, 1.0f
|
||||
)
|
||||
element.usage == VertexFormatElement.Usage.COLOR -> builder.put(idx,
|
||||
(vertex.color.red and 255).toFloat() / 255.0f,
|
||||
(vertex.color.green and 255).toFloat() / 255.0f,
|
||||
(vertex.color.blue and 255).toFloat() / 255.0f,
|
||||
(vertex.color.alpha and 255).toFloat() / 255.0f
|
||||
)
|
||||
element.usage == VertexFormatElement.Usage.NORMAL -> builder.put(idx,
|
||||
(vertex.normal ?: quad.normal).x.toFloat(),
|
||||
(vertex.normal ?: quad.normal).y.toFloat(),
|
||||
(vertex.normal ?: quad.normal).z.toFloat(),
|
||||
0.0f
|
||||
)
|
||||
else -> builder.put(idx)
|
||||
}
|
||||
}
|
||||
}
|
||||
HalfBakedQuad(quad, builder.build())
|
||||
}
|
||||
|
||||
fun Array<List<Quad>>.bake(applyDiffuseLighting: Boolean) = mapArray { it.bake(applyDiffuseLighting) }
|
||||
|
||||
fun BakedQuad.unbake(): HalfBakedQuad {
|
||||
val size = DefaultVertexFormats.BLOCK.integerSize
|
||||
val verts = Array(4) { vIdx ->
|
||||
val x = java.lang.Float.intBitsToFloat(vertices[vIdx * size + 0])
|
||||
val y = java.lang.Float.intBitsToFloat(vertices[vIdx * size + 1])
|
||||
val z = java.lang.Float.intBitsToFloat(vertices[vIdx * size + 2])
|
||||
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() }
|
||||
}
|
||||
|
||||
207
src/main/kotlin/mods/betterfoliage/model/Quads.kt
Normal file
207
src/main/kotlin/mods/betterfoliage/model/Quads.kt
Normal file
@@ -0,0 +1,207 @@
|
||||
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
|
||||
operator fun times(f: Float) = Color(
|
||||
alpha,
|
||||
(f * red.toFloat()).toInt().coerceIn(0 until 256),
|
||||
(f * green.toFloat()).toInt().coerceIn(0 until 256),
|
||||
(f * blue.toFloat()).toInt().coerceIn(0 until 256)
|
||||
)
|
||||
|
||||
companion object {
|
||||
val white get() = Color(255, 255, 255, 255)
|
||||
}
|
||||
}
|
||||
|
||||
data class HSB(var hue: Float, var saturation: Float, var brightness: Float) {
|
||||
companion object {
|
||||
/** Red is assumed to be LSB, see [NativeImage.PixelFormat.RGBA] */
|
||||
fun fromColorRGBA(color: Int): HSB {
|
||||
val hsbVals = java.awt.Color.RGBtoHSB(color and 255, (color shr 8) and 255, (color shr 16) and 255, null)
|
||||
return HSB(hsbVals[0], hsbVals[1], hsbVals[2])
|
||||
}
|
||||
}
|
||||
val asColor: Int get() = java.awt.Color.HSBtoRGB(hue, saturation, brightness)
|
||||
}
|
||||
|
||||
/**
|
||||
* Intermediate 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)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package mods.betterfoliage.model
|
||||
|
||||
import mods.betterfoliage.render.pipeline.RenderCtxBase
|
||||
import net.minecraft.client.renderer.model.IBakedModel
|
||||
import net.minecraft.util.WeightedRandom
|
||||
import java.util.Random
|
||||
|
||||
/**
|
||||
* Model that makes use of advanced rendering features.
|
||||
*/
|
||||
interface SpecialRenderModel : IBakedModel {
|
||||
fun render(ctx: RenderCtxBase, noDecorations: Boolean = false)
|
||||
}
|
||||
|
||||
class WeightedModelWrapper(
|
||||
val models: List<WeightedModel>, baseModel: SpecialRenderModel
|
||||
): IBakedModel by baseModel, SpecialRenderModel {
|
||||
class WeightedModel(val model: SpecialRenderModel, weight: Int) : WeightedRandom.Item(weight)
|
||||
val totalWeight = models.sumBy { it.weight }
|
||||
|
||||
fun getModel(random: Random) = WeightedRandom.getWeightedItem(models, random.nextInt(totalWeight))
|
||||
|
||||
override fun render(ctx: RenderCtxBase, noDecorations: Boolean) {
|
||||
getModel(ctx.random).model.render(ctx, noDecorations)
|
||||
}
|
||||
}
|
||||
83
src/main/kotlin/mods/betterfoliage/model/SpriteSets.kt
Normal file
83
src/main/kotlin/mods/betterfoliage/model/SpriteSets.kt
Normal file
@@ -0,0 +1,83 @@
|
||||
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.BLOCKS.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!!
|
||||
}
|
||||
}
|
||||
}
|
||||
108
src/main/kotlin/mods/betterfoliage/model/TuftMeshes.kt
Normal file
108
src/main/kotlin/mods/betterfoliage/model/TuftMeshes.kt
Normal file
@@ -0,0 +1,108 @@
|
||||
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>, tintIndex: 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.colorIndex(tintIndex) }
|
||||
.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, tintIndex: Int,scrambleUV: Boolean) =
|
||||
base.map { if (scrambleUV) it.scrambleUV(random, canFlipU = true, canFlipV = true, canRotate = true) else it }
|
||||
.map { it.colorIndex(tintIndex) }
|
||||
.mapIndexed { idx, quad -> quad.sprite(sprite) }
|
||||
.withOpposites()
|
||||
.bake(false)
|
||||
|
||||
fun crossModelsTextured(
|
||||
leafBase: Iterable<List<Quad>>,
|
||||
tintIndex: Int,
|
||||
scrambleUV: Boolean,
|
||||
spriteGetter: (Int) -> ResourceLocation
|
||||
) = leafBase.mapIndexed { idx, leaf ->
|
||||
crossModelSingle(leaf, Atlas.BLOCKS[spriteGetter(idx)], tintIndex, 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) } }
|
||||
@@ -1,130 +0,0 @@
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
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)
|
||||
@@ -1,48 +0,0 @@
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
*/
|
||||
@@ -1,64 +0,0 @@
|
||||
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)
|
||||
}
|
||||
|
||||
|
||||
*/
|
||||
@@ -1,94 +1,90 @@
|
||||
package mods.betterfoliage.render.block.vanilla
|
||||
|
||||
import mods.betterfoliage.BetterFoliageMod
|
||||
import mods.betterfoliage.BetterFoliage
|
||||
import mods.betterfoliage.render.lighting.withLighting
|
||||
import mods.betterfoliage.render.lighting.grassTuftLighting
|
||||
import mods.betterfoliage.render.lighting.roundLeafLighting
|
||||
import mods.betterfoliage.config.CACTUS_BLOCKS
|
||||
import mods.betterfoliage.config.Config
|
||||
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.AbstractModelDiscovery
|
||||
import mods.betterfoliage.resource.discovery.BakeWrapperManager
|
||||
import mods.betterfoliage.resource.discovery.ModelBakingContext
|
||||
import mods.betterfoliage.resource.discovery.ModelDiscoveryContext
|
||||
import mods.betterfoliage.util.Atlas
|
||||
import mods.betterfoliage.resource.discovery.*
|
||||
import mods.betterfoliage.resource.model.*
|
||||
import mods.betterfoliage.util.*
|
||||
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.CactusBlock
|
||||
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
|
||||
import mods.betterfoliage.util.LazyInvalidatable
|
||||
import mods.betterfoliage.util.Rotation
|
||||
import mods.betterfoliage.util.get
|
||||
import mods.betterfoliage.util.horizontalDirections
|
||||
import mods.betterfoliage.util.randomD
|
||||
import mods.betterfoliage.util.randomI
|
||||
import net.minecraft.block.Blocks
|
||||
import net.minecraft.client.renderer.model.BlockModel
|
||||
import net.minecraft.util.Direction.DOWN
|
||||
import net.minecraft.util.ResourceLocation
|
||||
|
||||
|
||||
interface CactusKey : BlockRenderKey {
|
||||
val cactusTop: Identifier
|
||||
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 StandardCactusDiscovery : AbstractModelDiscovery() {
|
||||
override fun processModel(ctx: ModelDiscoveryContext) {
|
||||
val model = ctx.getUnbaked()
|
||||
if (model is BlockModel && ctx.blockState.block in CACTUS_BLOCKS) {
|
||||
BetterFoliage.blockTypes.dirt.add(ctx.blockState)
|
||||
ctx.addReplacement(StandardCactusKey)
|
||||
ctx.sprites.add(StandardCactusModel.cactusCrossSprite)
|
||||
}
|
||||
super.processModel(ctx)
|
||||
}
|
||||
}
|
||||
|
||||
class CactusModel(val key: Key, wrapped: BakedModel) : WrappedBakedModel(wrapped), FabricBakedModel {
|
||||
object StandardCactusKey : HalfBakedWrapperKey() {
|
||||
override fun bake(ctx: ModelBakingContext, wrapped: SpecialRenderModel) = StandardCactusModel(wrapped)
|
||||
}
|
||||
|
||||
val crossModels by cactusCrossModels.delegate(key)
|
||||
val armModels by cactusArmModels.delegate(key)
|
||||
val armLighting = horizontalDirections.map { grassTuftLighting(it) }
|
||||
val crossLighting = roundLeafLighting()
|
||||
class StandardCactusModel(
|
||||
wrapped: SpecialRenderModel
|
||||
) : HalfBakedSpecialWrapper(wrapped) {
|
||||
|
||||
override fun emitBlockQuads(blockView: ExtendedBlockView, state: BlockState, pos: BlockPos, randomSupplier: Supplier<Random>, context: RenderContext) {
|
||||
(wrapped as FabricBakedModel).emitBlockQuads(blockView, state, pos, randomSupplier, context)
|
||||
if (!BetterFoliage.config.enabled || !BetterFoliage.config.cactus.enabled) return
|
||||
val armLighting = horizontalDirections.map { LightingPreferredFace(it) }.toTypedArray()
|
||||
|
||||
val random = randomSupplier.get()
|
||||
val armSide = random.nextInt() and 3
|
||||
override fun render(ctx: RenderCtxBase, noDecorations: Boolean) {
|
||||
ctx.checkSides = false
|
||||
super.render(ctx, noDecorations)
|
||||
if (!Config.enabled || !Config.cactus.enabled) return
|
||||
|
||||
context.withLighting(armLighting[armSide]) {
|
||||
it.accept(armModels[armSide][random])
|
||||
}
|
||||
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))
|
||||
val armSide = ctx.random.nextInt() and 3
|
||||
ctx.vertexLighter = armLighting[armSide]
|
||||
ctx.renderQuads(cactusArmModels[armSide][ctx.random])
|
||||
ctx.vertexLighter = RoundLeafLighting
|
||||
ctx.renderQuads(cactusCrossModels[ctx.random])
|
||||
}
|
||||
|
||||
companion object {
|
||||
val cactusCrossSprite by SpriteDelegate(Atlas.BLOCKS) {
|
||||
Identifier(BetterFoliage.MOD_ID, "blocks/better_cactus")
|
||||
}
|
||||
val cactusCrossSprite = ResourceLocation(BetterFoliageMod.MOD_ID, "blocks/better_cactus")
|
||||
val cactusArmSprites by SpriteSetDelegate(Atlas.BLOCKS) { idx ->
|
||||
Identifier(BetterFoliage.MOD_ID, "blocks/better_cactus_arm_$idx")
|
||||
ResourceLocation(BetterFoliageMod.MOD_ID, "blocks/better_cactus_arm_$idx")
|
||||
}
|
||||
val cactusArmModels = 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()] }
|
||||
val cactusArmModels by LazyInvalidatable(BakeWrapperManager) {
|
||||
val shapes = Config.cactus.let { tuftShapeSet(0.8, 0.8, 0.8, 0.2) }
|
||||
val models = tuftModelSet(shapes, -1) { cactusArmSprites[randomI()] }
|
||||
horizontalDirections.map { side ->
|
||||
models.transform { move(0.0625 to DOWN).rotate(Rotation.fromUp[side.ordinal]) }.buildTufts()
|
||||
}.toTypedArray()
|
||||
}
|
||||
val cactusCrossModels = LazyMap(BetterFoliage.modelReplacer) { key: CactusKey ->
|
||||
val models = BetterFoliage.config.cactus.let { config ->
|
||||
val cactusCrossModels by LazyInvalidatable(BakeWrapperManager) {
|
||||
val models = Config.cactus.let { config ->
|
||||
crossModelsRaw(64, config.size, 0.0, 0.0)
|
||||
.transform { rotateZ(randomD(-config.sizeVariation, config.sizeVariation)) }
|
||||
}
|
||||
crossModelsTextured(models, Color.white.asInt, true) { cactusCrossSprite }
|
||||
crossModelsTextured(models, -1, true) { cactusCrossSprite }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,101 +1,120 @@
|
||||
package mods.betterfoliage.render.block.vanilla
|
||||
|
||||
import mods.betterfoliage.BetterFoliage
|
||||
import mods.betterfoliage.chunk.BasicBlockCtx
|
||||
import mods.betterfoliage.render.DIRT_BLOCKS
|
||||
import mods.betterfoliage.render.SALTWATER_BIOMES
|
||||
import mods.betterfoliage.render.lighting.withLighting
|
||||
import mods.betterfoliage.render.lighting.grassTuftLighting
|
||||
import mods.betterfoliage.render.lighting.reedLighting
|
||||
import mods.betterfoliage.render.lighting.renderMasquerade
|
||||
import mods.betterfoliage.util.Atlas
|
||||
import mods.betterfoliage.resource.discovery.BlockRenderKey
|
||||
import mods.betterfoliage.resource.discovery.ModelDiscoveryBase
|
||||
import mods.betterfoliage.BetterFoliageMod
|
||||
import mods.betterfoliage.config.Config
|
||||
import mods.betterfoliage.config.DIRT_BLOCKS
|
||||
import mods.betterfoliage.config.SALTWATER_BIOMES
|
||||
import mods.betterfoliage.config.isSnow
|
||||
import mods.betterfoliage.integration.ShadersModIntegration
|
||||
import mods.betterfoliage.model.HalfBakedSpecialWrapper
|
||||
import mods.betterfoliage.model.HalfBakedWrapperKey
|
||||
import mods.betterfoliage.model.SpecialRenderModel
|
||||
import mods.betterfoliage.model.SpriteSetDelegate
|
||||
import mods.betterfoliage.model.buildTufts
|
||||
import mods.betterfoliage.model.tuftModelSet
|
||||
import mods.betterfoliage.model.tuftShapeSet
|
||||
import mods.betterfoliage.render.lighting.LightingPreferredFace
|
||||
import mods.betterfoliage.render.pipeline.RenderCtxBase
|
||||
import mods.betterfoliage.resource.discovery.AbstractModelDiscovery
|
||||
import mods.betterfoliage.resource.discovery.BakeWrapperManager
|
||||
import mods.betterfoliage.resource.discovery.ModelBakingContext
|
||||
import mods.betterfoliage.resource.discovery.ModelDiscoveryContext
|
||||
import mods.betterfoliage.resource.generated.CenteredSprite
|
||||
import mods.betterfoliage.resource.model.*
|
||||
import mods.betterfoliage.util.*
|
||||
import net.fabricmc.fabric.api.renderer.v1.render.RenderContext
|
||||
import net.minecraft.block.BlockRenderLayer
|
||||
import net.minecraft.block.BlockState
|
||||
import net.minecraft.block.Material
|
||||
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
|
||||
import mods.betterfoliage.util.Atlas
|
||||
import mods.betterfoliage.util.Int3
|
||||
import mods.betterfoliage.util.LazyInvalidatable
|
||||
import mods.betterfoliage.util.get
|
||||
import mods.betterfoliage.util.offset
|
||||
import mods.betterfoliage.util.randomI
|
||||
import net.minecraft.block.material.Material
|
||||
import net.minecraft.client.renderer.RenderType
|
||||
import net.minecraft.client.renderer.RenderTypeLookup
|
||||
import net.minecraft.client.renderer.model.BlockModel
|
||||
import net.minecraft.util.Direction.UP
|
||||
import net.minecraft.util.ResourceLocation
|
||||
|
||||
object DirtKey : BlockRenderKey {
|
||||
override fun replace(model: BakedModel, state: BlockState) = DirtModel(meshifyStandard(model, state))
|
||||
object StandardDirtDiscovery : AbstractModelDiscovery() {
|
||||
fun canRenderInLayer(layer: RenderType) = when {
|
||||
!Config.enabled -> layer == RenderType.solid()
|
||||
!Config.connectedGrass.enabled && !Config.algae.enabled && !Config.reed.enabled -> layer == RenderType.solid()
|
||||
else -> layer == RenderType.cutoutMipped()
|
||||
}
|
||||
|
||||
override fun processModel(ctx: ModelDiscoveryContext) {
|
||||
if (ctx.getUnbaked() is BlockModel && ctx.blockState.block in DIRT_BLOCKS) {
|
||||
BetterFoliage.blockTypes.dirt.add(ctx.blockState)
|
||||
ctx.addReplacement(StandardDirtKey)
|
||||
RenderTypeLookup.setRenderLayer(ctx.blockState.block, ::canRenderInLayer)
|
||||
}
|
||||
super.processModel(ctx)
|
||||
}
|
||||
}
|
||||
|
||||
object DirtDiscovery : ModelDiscoveryBase() {
|
||||
override val logger = BetterFoliage.logDetail
|
||||
|
||||
override fun processModel(ctx: ModelDiscoveryContext, atlas: Consumer<Identifier>) =
|
||||
if (ctx.state.block in DIRT_BLOCKS) DirtKey else null
|
||||
object StandardDirtKey : HalfBakedWrapperKey() {
|
||||
override fun bake(ctx: ModelBakingContext, wrapped: SpecialRenderModel) = StandardDirtModel(wrapped)
|
||||
}
|
||||
|
||||
class DirtModel(wrapped: BakedModel) : WrappedBakedModel(wrapped) {
|
||||
class StandardDirtModel(
|
||||
wrapped: SpecialRenderModel
|
||||
) : HalfBakedSpecialWrapper(wrapped) {
|
||||
val vanillaTuftLighting = LightingPreferredFace(UP)
|
||||
|
||||
val algaeLighting = grassTuftLighting(UP)
|
||||
val reedLighting = reedLighting()
|
||||
override fun render(ctx: RenderCtxBase, noDecorations: Boolean) {
|
||||
if (!Config.enabled || noDecorations) return super.render(ctx, noDecorations)
|
||||
|
||||
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 stateUp = ctx.state(UP)
|
||||
val state2Up = ctx.state(Int3(0, 2, 0))
|
||||
val isConnectedGrass = Config.connectedGrass.enabled &&
|
||||
stateUp in BetterFoliage.blockTypes.grass &&
|
||||
(Config.connectedGrass.snowEnabled || !state2Up.isSnow)
|
||||
|
||||
val ctx = BasicBlockCtx(blockView, pos)
|
||||
val stateUp = ctx.offset(UP).state
|
||||
val keyUp = BetterFoliage.modelReplacer[stateUp]
|
||||
|
||||
val isWater = stateUp.material == Material.WATER
|
||||
val isDeepWater = isWater && ctx.offset(Int3(2 to UP)).state.material == Material.WATER
|
||||
val isShallowWater = isWater && ctx.offset(Int3(2 to UP)).state.isAir
|
||||
val isSaltWater = isWater && ctx.biome.category in SALTWATER_BIOMES
|
||||
|
||||
if (BetterFoliage.config.connectedGrass.enabled && keyUp is GrassKey) {
|
||||
val grassBaseModel = (ctx.model(UP) as WrappedBakedModel).wrapped
|
||||
context.renderMasquerade(grassBaseModel, blockView, stateUp, pos, randomSupplier, context)
|
||||
} else {
|
||||
super.emitBlockQuads(blockView, state, pos, randomSupplier, context)
|
||||
if (isConnectedGrass) {
|
||||
(ctx.blockModelShapes.getBlockModel(stateUp) as? SpecialRenderModel)?.let { grassModel ->
|
||||
ctx.renderMasquerade(UP.offset) {
|
||||
grassModel.render(ctx, true)
|
||||
}
|
||||
return
|
||||
}
|
||||
return super.render(ctx, false)
|
||||
}
|
||||
|
||||
val random = randomSupplier.get()
|
||||
if (BetterFoliage.config.algae.enabled(random) && isDeepWater) {
|
||||
context.withLighting(algaeLighting) {
|
||||
it.accept(algaeModels[random])
|
||||
super.render(ctx, false)
|
||||
|
||||
val isWater = stateUp.material == Material.WATER
|
||||
val isDeepWater = isWater && state2Up.material == Material.WATER
|
||||
val isShallowWater = isWater && state2Up.isAir
|
||||
val isSaltWater = isWater && ctx.biome?.biomeCategory in SALTWATER_BIOMES
|
||||
|
||||
if (Config.algae.enabled(ctx.random) && isDeepWater) {
|
||||
ctx.vertexLighter = vanillaTuftLighting
|
||||
ShadersModIntegration.grass(ctx, Config.algae.shaderWind) {
|
||||
ctx.renderQuads(algaeModels[ctx.random])
|
||||
}
|
||||
} else if (BetterFoliage.config.reed.enabled(random) && isShallowWater && !isSaltWater) {
|
||||
context.withLighting(reedLighting) {
|
||||
it.accept(reedModels[random])
|
||||
} else if (Config.reed.enabled(ctx.random) && isShallowWater && !isSaltWater) {
|
||||
ctx.vertexLighter = vanillaTuftLighting
|
||||
ShadersModIntegration.grass(ctx, Config.reed.shaderWind) {
|
||||
ctx.renderQuads(reedModels[ctx.random])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
val algaeSprites by SpriteSetDelegate(Atlas.BLOCKS) { idx ->
|
||||
Identifier(BetterFoliage.MOD_ID, "blocks/better_algae_$idx")
|
||||
ResourceLocation(BetterFoliageMod.MOD_ID, "blocks/better_algae_$idx")
|
||||
}
|
||||
val reedSprites by SpriteSetDelegate(Atlas.BLOCKS,
|
||||
idFunc = { idx -> Identifier(BetterFoliage.MOD_ID, "blocks/better_reed_$idx") },
|
||||
val reedSprites by SpriteSetDelegate(
|
||||
Atlas.BLOCKS,
|
||||
idFunc = { idx -> ResourceLocation(BetterFoliageMod.MOD_ID, "blocks/better_reed_$idx") },
|
||||
idRegister = { id -> CenteredSprite(id, aspectHeight = 2).register(BetterFoliage.generatedPack) }
|
||||
)
|
||||
val algaeModels by LazyInvalidatable(BetterFoliage.modelReplacer) {
|
||||
val shapes = BetterFoliage.config.algae.let { tuftShapeSet(it.size, it.heightMin, it.heightMax, it.hOffset) }
|
||||
tuftModelSet(shapes, Color.white.asInt) { algaeSprites[randomI()] }
|
||||
.withOpposites()
|
||||
.build(BlockRenderLayer.CUTOUT_MIPPED, flatLighting = false)
|
||||
|
||||
val algaeModels by LazyInvalidatable(BakeWrapperManager) {
|
||||
val shapes = Config.algae.let { tuftShapeSet(it.size, it.heightMin, it.heightMax, it.hOffset) }
|
||||
tuftModelSet(shapes, -1) { algaeSprites[randomI()] }.buildTufts()
|
||||
}
|
||||
val reedModels by LazyInvalidatable(BetterFoliage.modelReplacer) {
|
||||
val shapes = BetterFoliage.config.reed.let { tuftShapeSet(2.0, it.heightMin, it.heightMax, it.hOffset) }
|
||||
tuftModelSet(shapes, Color.white.asInt) { reedSprites[randomI()] }
|
||||
.withOpposites()
|
||||
.build(BlockRenderLayer.CUTOUT_MIPPED, flatLighting = false)
|
||||
val reedModels by LazyInvalidatable(BakeWrapperManager) {
|
||||
val shapes = Config.reed.let { tuftShapeSet(2.0, it.heightMin, it.heightMax, it.hOffset) }
|
||||
tuftModelSet(shapes, -1) { reedSprites[randomI()] }.buildTufts()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,121 +1,118 @@
|
||||
package mods.betterfoliage.render.block.vanilla
|
||||
|
||||
import mods.betterfoliage.BetterFoliage
|
||||
import mods.betterfoliage.chunk.BasicBlockCtx
|
||||
import mods.betterfoliage.render.SNOW_MATERIALS
|
||||
import mods.betterfoliage.render.lighting.withLighting
|
||||
import mods.betterfoliage.render.lighting.grassTuftLighting
|
||||
import mods.betterfoliage.BetterFoliageMod
|
||||
import mods.betterfoliage.config.BlockConfig
|
||||
import mods.betterfoliage.config.Config
|
||||
import mods.betterfoliage.config.isSnow
|
||||
import mods.betterfoliage.integration.ShadersModIntegration
|
||||
import mods.betterfoliage.model.Color
|
||||
import mods.betterfoliage.model.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.fullCubeTextured
|
||||
import mods.betterfoliage.model.tuftModelSet
|
||||
import mods.betterfoliage.model.tuftShapeSet
|
||||
import mods.betterfoliage.render.lighting.LightingPreferredFace
|
||||
import mods.betterfoliage.render.pipeline.RenderCtxBase
|
||||
import mods.betterfoliage.resource.discovery.BakeWrapperManager
|
||||
import mods.betterfoliage.resource.discovery.ConfigurableBlockMatcher
|
||||
import mods.betterfoliage.resource.discovery.ConfigurableModelDiscovery
|
||||
import mods.betterfoliage.resource.discovery.ModelBakingContext
|
||||
import mods.betterfoliage.resource.discovery.ModelDiscoveryContext
|
||||
import mods.betterfoliage.resource.discovery.ModelTextureList
|
||||
import mods.betterfoliage.util.Atlas
|
||||
import mods.betterfoliage.resource.discovery.*
|
||||
import mods.betterfoliage.resource.model.*
|
||||
import mods.betterfoliage.util.*
|
||||
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.tag.BlockTags
|
||||
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
|
||||
|
||||
interface GrassKey : BlockRenderKey {
|
||||
val grassTopTexture: Identifier
|
||||
|
||||
/**
|
||||
* Color to use for Short Grass rendering instead of the biome color.
|
||||
*
|
||||
* 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 overrideColor: Int?
|
||||
}
|
||||
import mods.betterfoliage.util.LazyInvalidatable
|
||||
import mods.betterfoliage.util.LazyMapInvalidatable
|
||||
import mods.betterfoliage.util.averageColor
|
||||
import mods.betterfoliage.util.colorOverride
|
||||
import mods.betterfoliage.util.get
|
||||
import mods.betterfoliage.util.logColorOverride
|
||||
import mods.betterfoliage.util.randomI
|
||||
import net.minecraft.util.Direction.DOWN
|
||||
import net.minecraft.util.Direction.UP
|
||||
import net.minecraft.util.ResourceLocation
|
||||
|
||||
object StandardGrassDiscovery : ConfigurableModelDiscovery() {
|
||||
override val logger = BetterFoliage.logDetail
|
||||
override val matchClasses: ConfigurableBlockMatcher get() = BetterFoliage.blockConfig.grassBlocks
|
||||
override val modelTextures: List<ModelTextureList> get() = BetterFoliage.blockConfig.grassModels.modelList
|
||||
override val matchClasses: ConfigurableBlockMatcher get() = BlockConfig.grassBlocks
|
||||
override val modelTextures: List<ModelTextureList> get() = 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))
|
||||
override fun processModel(ctx: ModelDiscoveryContext, textureMatch: List<ResourceLocation>) {
|
||||
ctx.addReplacement(StandardGrassKey(textureMatch[0], null))
|
||||
BetterFoliage.blockTypes.grass.add(ctx.blockState)
|
||||
}
|
||||
}
|
||||
|
||||
class GrassBlockModel(val key: Key, wrapped: BakedModel) : WrappedBakedModel(wrapped), FabricBakedModel {
|
||||
data class StandardGrassKey(
|
||||
val grassLocation: ResourceLocation,
|
||||
val overrideColor: Color?
|
||||
) : HalfBakedWrapperKey() {
|
||||
val tintIndex: Int get() = if (overrideColor == null) 0 else -1
|
||||
|
||||
override fun bake(ctx: ModelBakingContext, wrapped: SpecialRenderModel): SpecialRenderModel {
|
||||
val grassSpriteColor = Atlas.BLOCKS[grassLocation].averageColor.let { hsb ->
|
||||
logColorOverride(BetterFoliageMod.detailLogger(this), Config.shortGrass.saturationThreshold, hsb)
|
||||
hsb.colorOverride(Config.shortGrass.saturationThreshold)
|
||||
}
|
||||
return StandardGrassModel(wrapped, this.copy(overrideColor = grassSpriteColor))
|
||||
}
|
||||
}
|
||||
|
||||
class StandardGrassModel(
|
||||
wrapped: SpecialRenderModel,
|
||||
key: StandardGrassKey
|
||||
) : HalfBakedSpecialWrapper(wrapped) {
|
||||
|
||||
val tuftNormal by grassTuftMeshesNormal.delegate(key)
|
||||
val tuftSnowed by grassTuftMeshesSnowed.delegate(key)
|
||||
val fullBlock by grassFullBlockMeshes.delegate(key)
|
||||
val tuftLighting = LightingPreferredFace(UP)
|
||||
|
||||
val tuftLighting = grassTuftLighting(UP)
|
||||
override fun render(ctx: RenderCtxBase, noDecorations: Boolean) {
|
||||
if (!Config.enabled || noDecorations) return super.render(ctx, noDecorations)
|
||||
|
||||
override fun emitBlockQuads(blockView: 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 stateAbove = ctx.state(UP)
|
||||
val isAir = ctx.isAir(UP)
|
||||
val isSnowed = stateAbove.isSnow
|
||||
val connected = Config.connectedGrass.enabled &&
|
||||
(!isSnowed || Config.connectedGrass.snowEnabled) &&
|
||||
BetterFoliage.blockTypes.run { stateBelow in grass || stateBelow in dirt }
|
||||
|
||||
val isSnowed = stateAbove.material in SNOW_MATERIALS
|
||||
val connected = BetterFoliage.config.connectedGrass.enabled &&
|
||||
(!isSnowed || BetterFoliage.config.connectedGrass.snowEnabled) && (
|
||||
BlockTags.DIRT_LIKE.contains(stateBelow.block) ||
|
||||
BetterFoliage.modelReplacer.getTyped<GrassKey>(stateBelow) != null
|
||||
)
|
||||
|
||||
val random = randomSupplier.get()
|
||||
if (connected) {
|
||||
context.meshConsumer().accept(if (isSnowed) snowFullBlockMeshes[random] else fullBlock[random])
|
||||
ctx.renderQuads(if (isSnowed) snowFullBlockMeshes[ctx.random] else fullBlock[ctx.random])
|
||||
} else {
|
||||
super.emitBlockQuads(blockView, state, pos, randomSupplier, context)
|
||||
super.render(ctx, noDecorations)
|
||||
}
|
||||
|
||||
if (BetterFoliage.config.shortGrass.enabled(random) && !ctx.isNeighborSolid(UP)) {
|
||||
context.withLighting(tuftLighting) {
|
||||
it.accept(if (isSnowed) tuftSnowed[random] else tuftNormal[random])
|
||||
if (Config.shortGrass.enabled(ctx.random) && (isAir || isSnowed)) {
|
||||
ctx.vertexLighter = tuftLighting
|
||||
ShadersModIntegration.grass(ctx, Config.shortGrass.shaderWind) {
|
||||
ctx.renderQuads(if (isSnowed) tuftSnowed[ctx.random] else tuftNormal[ctx.random])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
data class Key(
|
||||
override val grassTopTexture: Identifier,
|
||||
override val overrideColor: Int?
|
||||
) : GrassKey {
|
||||
override fun replace(model: BakedModel, state: BlockState) = GrassBlockModel(this, meshifyStandard(model, state))
|
||||
}
|
||||
|
||||
companion object {
|
||||
val grassTuftSpritesNormal by SpriteSetDelegate(Atlas.BLOCKS) { idx ->
|
||||
Identifier(BetterFoliage.MOD_ID, "blocks/better_grass_long_$idx")
|
||||
val grassTuftSprites by SpriteSetDelegate(Atlas.BLOCKS) { idx ->
|
||||
ResourceLocation(BetterFoliageMod.MOD_ID, "blocks/better_grass_long_$idx")
|
||||
}
|
||||
val grassTuftSpritesSnowed by SpriteSetDelegate(Atlas.BLOCKS) { idx ->
|
||||
Identifier(BetterFoliage.MOD_ID, "blocks/better_grass_long_$idx")
|
||||
val grassTuftShapes by LazyInvalidatable(BakeWrapperManager) {
|
||||
Config.shortGrass.let { tuftShapeSet(it.size, it.heightMin, it.heightMax, it.hOffset) }
|
||||
}
|
||||
val grassTuftShapes = LazyMap(BetterFoliage.modelReplacer) { key: GrassKey ->
|
||||
BetterFoliage.config.shortGrass.let { tuftShapeSet(it.size, it.heightMin, it.heightMax, it.hOffset) }
|
||||
val grassTuftMeshesNormal = LazyMapInvalidatable(BakeWrapperManager) { key: StandardGrassKey ->
|
||||
tuftModelSet(grassTuftShapes, key.tintIndex) { idx -> grassTuftSprites[randomI()] }.buildTufts()
|
||||
}
|
||||
val grassTuftMeshesNormal = LazyMap(BetterFoliage.modelReplacer) { key: GrassKey ->
|
||||
tuftModelSet(grassTuftShapes[key], key.overrideColor) { idx -> grassTuftSpritesNormal[randomI()] }
|
||||
.withOpposites()
|
||||
.build(BlockRenderLayer.CUTOUT_MIPPED, flatLighting = false)
|
||||
val grassTuftMeshesSnowed = LazyMapInvalidatable(BakeWrapperManager) { key: StandardGrassKey ->
|
||||
tuftModelSet(grassTuftShapes, -1) { idx -> grassTuftSprites[randomI()] }.buildTufts()
|
||||
}
|
||||
val grassTuftMeshesSnowed = LazyMap(BetterFoliage.modelReplacer) { key: GrassKey ->
|
||||
tuftModelSet(grassTuftShapes[key], Color.white.asInt) { idx -> grassTuftSpritesSnowed[randomI()] }
|
||||
.withOpposites()
|
||||
.build(BlockRenderLayer.CUTOUT_MIPPED, flatLighting = false)
|
||||
val grassFullBlockMeshes = LazyMapInvalidatable(BakeWrapperManager) { key: StandardGrassKey ->
|
||||
Array(64) { fullCubeTextured(key.grassLocation, key.tintIndex) }
|
||||
}
|
||||
val grassFullBlockMeshes = LazyMap(BetterFoliage.modelReplacer) { key: GrassKey ->
|
||||
Array(64) { fullCubeTextured(key.grassTopTexture, key.overrideColor) }
|
||||
}
|
||||
val snowFullBlockMeshes by LazyInvalidatable(BetterFoliage.modelReplacer) {
|
||||
Array(64) { fullCubeTextured(Identifier("block/snow"), Color.white.asInt) }
|
||||
val snowFullBlockMeshes by LazyInvalidatable(BakeWrapperManager) {
|
||||
Array(64) { fullCubeTextured(ResourceLocation("block/snow"), -1) }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,116 +1,101 @@
|
||||
package mods.betterfoliage.render.block.vanilla
|
||||
|
||||
import mods.betterfoliage.BetterFoliage
|
||||
import mods.betterfoliage.chunk.BasicBlockCtx
|
||||
import mods.betterfoliage.render.SNOW_MATERIALS
|
||||
import mods.betterfoliage.render.lighting.withLighting
|
||||
import mods.betterfoliage.render.lighting.roundLeafLighting
|
||||
import mods.betterfoliage.BetterFoliageMod
|
||||
import mods.betterfoliage.config.BlockConfig
|
||||
import mods.betterfoliage.config.Config
|
||||
import mods.betterfoliage.config.isSnow
|
||||
import mods.betterfoliage.integration.ShadersModIntegration
|
||||
import mods.betterfoliage.model.Color
|
||||
import mods.betterfoliage.model.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.util.Atlas
|
||||
import mods.betterfoliage.resource.discovery.*
|
||||
import mods.betterfoliage.render.pipeline.RenderCtxBase
|
||||
import mods.betterfoliage.resource.discovery.BakeWrapperManager
|
||||
import mods.betterfoliage.resource.discovery.ConfigurableBlockMatcher
|
||||
import mods.betterfoliage.resource.discovery.ConfigurableModelDiscovery
|
||||
import mods.betterfoliage.resource.discovery.ModelBakingContext
|
||||
import mods.betterfoliage.resource.discovery.ModelDiscoveryContext
|
||||
import mods.betterfoliage.resource.discovery.ModelTextureList
|
||||
import mods.betterfoliage.resource.generated.GeneratedLeafSprite
|
||||
import mods.betterfoliage.resource.model.*
|
||||
import mods.betterfoliage.util.*
|
||||
import net.fabricmc.fabric.api.renderer.v1.model.FabricBakedModel
|
||||
import net.fabricmc.fabric.api.renderer.v1.render.RenderContext
|
||||
import net.minecraft.block.BlockRenderLayer.CUTOUT_MIPPED
|
||||
import net.minecraft.block.BlockState
|
||||
import net.minecraft.client.render.model.BakedModel
|
||||
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
|
||||
|
||||
interface LeafKey : BlockRenderKey {
|
||||
val roundLeafTexture: Identifier
|
||||
|
||||
/** Type of the leaf block (configurable by user). */
|
||||
val leafType: String
|
||||
|
||||
/** Average color of the round leaf texture. */
|
||||
val overrideColor: Int?
|
||||
}
|
||||
import mods.betterfoliage.util.Atlas
|
||||
import mods.betterfoliage.util.LazyMapInvalidatable
|
||||
import mods.betterfoliage.util.averageColor
|
||||
import mods.betterfoliage.util.colorOverride
|
||||
import mods.betterfoliage.util.logColorOverride
|
||||
import net.minecraft.util.Direction.UP
|
||||
import net.minecraft.util.ResourceLocation
|
||||
import org.apache.logging.log4j.Level.INFO
|
||||
|
||||
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 val matchClasses: ConfigurableBlockMatcher get() = BlockConfig.leafBlocks
|
||||
override val modelTextures: List<ModelTextureList> get() = BlockConfig.leafModels.modelList
|
||||
|
||||
override fun processModel(state: BlockState, textures: List<String>, atlas: Consumer<Identifier>) =
|
||||
defaultRegisterLeaf(Identifier(textures[0]), atlas)
|
||||
override fun processModel(ctx: ModelDiscoveryContext, textureMatch: List<ResourceLocation>) {
|
||||
val leafType = LeafParticleRegistry.typeMappings.getType(textureMatch[0]) ?: "default"
|
||||
val generated = GeneratedLeafSprite(textureMatch[0], leafType)
|
||||
.register(BetterFoliage.generatedPack)
|
||||
.apply { ctx.sprites.add(this) }
|
||||
|
||||
}
|
||||
|
||||
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
|
||||
detailLogger.log(INFO, " particle $leafType")
|
||||
ctx.addReplacement(StandardLeafKey(generated, leafType, null))
|
||||
}
|
||||
}
|
||||
|
||||
class NormalLeavesModel(val key: Key, wrapped: BakedModel) : WrappedBakedModel(wrapped), FabricBakedModel {
|
||||
data class StandardLeafKey(
|
||||
val roundLeafTexture: ResourceLocation,
|
||||
override val leafType: String,
|
||||
override val overrideColor: Color?
|
||||
) : HalfBakedWrapperKey(), LeafParticleKey {
|
||||
val tintIndex: Int get() = if (overrideColor == null) 0 else -1
|
||||
|
||||
override fun bake(ctx: ModelBakingContext, wrapped: SpecialRenderModel): SpecialRenderModel {
|
||||
val leafSpriteColor = Atlas.BLOCKS[roundLeafTexture].averageColor.let { hsb ->
|
||||
logColorOverride(BetterFoliageMod.detailLogger(this), Config.leaves.saturationThreshold, hsb)
|
||||
hsb.colorOverride(Config.leaves.saturationThreshold)
|
||||
}
|
||||
return StandardLeafModel(wrapped, this.copy(overrideColor = leafSpriteColor))
|
||||
}
|
||||
}
|
||||
|
||||
class StandardLeafModel(
|
||||
model: SpecialRenderModel,
|
||||
override val key: StandardLeafKey
|
||||
) : HalfBakedSpecialWrapper(model), LeafBlockModel {
|
||||
val leafNormal by leafModelsNormal.delegate(key)
|
||||
val leafSnowed by leafModelsSnowed.delegate(key)
|
||||
val leafLighting = roundLeafLighting()
|
||||
|
||||
override fun emitBlockQuads(blockView: ExtendedBlockView, state: BlockState, pos: BlockPos, randomSupplier: Supplier<Random>, context: RenderContext) {
|
||||
super.emitBlockQuads(blockView, state, pos, randomSupplier, context)
|
||||
if (!BetterFoliage.config.enabled || !BetterFoliage.config.leaves.enabled) return
|
||||
override fun render(ctx: RenderCtxBase, noDecorations: Boolean) {
|
||||
ShadersModIntegration.leaves(ctx, true) {
|
||||
super.render(ctx, noDecorations)
|
||||
if (!Config.enabled || !Config.leaves.enabled || noDecorations) return
|
||||
|
||||
val ctx = BasicBlockCtx(blockView, pos)
|
||||
val stateAbove = ctx.state(UP)
|
||||
val isSnowed = stateAbove.material in SNOW_MATERIALS
|
||||
|
||||
val random = randomSupplier.get()
|
||||
context.withLighting(leafLighting) {
|
||||
it.accept(leafNormal[random])
|
||||
if (isSnowed) it.accept(leafSnowed[random])
|
||||
ctx.vertexLighter = RoundLeafLightingPreferUp
|
||||
val leafIdx = ctx.random.nextInt(64)
|
||||
ctx.renderQuads(leafNormal[leafIdx])
|
||||
if (ctx.state(UP).isSnow) ctx.renderQuads(leafSnowed[leafIdx])
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
val leafSpritesSnowed by SpriteSetDelegate(Atlas.BLOCKS) { idx ->
|
||||
Identifier(BetterFoliage.MOD_ID, "blocks/better_leaves_snowed_$idx")
|
||||
ResourceLocation(BetterFoliageMod.MOD_ID, "blocks/better_leaves_snowed_$idx")
|
||||
}
|
||||
val leafModelsBase = LazyMap(BetterFoliage.modelReplacer) { key: LeafKey ->
|
||||
BetterFoliage.config.leaves.let { crossModelsRaw(64, it.size, it.hOffset, it.vOffset) }
|
||||
val leafModelsBase = LazyMapInvalidatable(BakeWrapperManager) { key: StandardLeafKey ->
|
||||
Config.leaves.let { crossModelsRaw(64, it.size, it.hOffset, it.vOffset) }
|
||||
}
|
||||
val leafModelsNormal = LazyMap(BetterFoliage.modelReplacer) { key: LeafKey ->
|
||||
crossModelsTextured(leafModelsBase[key], key.overrideColor, true) { Atlas.BLOCKS.atlas[key.roundLeafTexture]!! }
|
||||
val leafModelsNormal = LazyMapInvalidatable(BakeWrapperManager) { key: StandardLeafKey ->
|
||||
crossModelsTextured(leafModelsBase[key], key.tintIndex, true) { key.roundLeafTexture }
|
||||
}
|
||||
val leafModelsSnowed = LazyMap(BetterFoliage.modelReplacer) { key: LeafKey ->
|
||||
crossModelsTextured(leafModelsBase[key], Color.white.asInt, false) { leafSpritesSnowed[it] }
|
||||
val leafModelsSnowed = LazyMapInvalidatable(BakeWrapperManager) { key: StandardLeafKey ->
|
||||
crossModelsTextured(leafModelsBase[key], -1, false) { leafSpritesSnowed[it].name }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,66 +1,76 @@
|
||||
package mods.betterfoliage.render.block.vanilla
|
||||
|
||||
import mods.betterfoliage.BetterFoliage
|
||||
import mods.betterfoliage.util.Atlas
|
||||
import mods.betterfoliage.resource.discovery.BlockRenderKey
|
||||
import mods.betterfoliage.resource.discovery.ModelDiscoveryBase
|
||||
import mods.betterfoliage.BetterFoliageMod
|
||||
import mods.betterfoliage.config.Config
|
||||
import mods.betterfoliage.config.LILYPAD_BLOCKS
|
||||
import mods.betterfoliage.integration.ShadersModIntegration
|
||||
import mods.betterfoliage.model.HalfBakedSpecialWrapper
|
||||
import mods.betterfoliage.model.HalfBakedWrapperKey
|
||||
import mods.betterfoliage.model.SpecialRenderModel
|
||||
import mods.betterfoliage.model.SpriteSetDelegate
|
||||
import mods.betterfoliage.model.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.AbstractModelDiscovery
|
||||
import mods.betterfoliage.resource.discovery.BakeWrapperManager
|
||||
import mods.betterfoliage.resource.discovery.ModelBakingContext
|
||||
import mods.betterfoliage.resource.discovery.ModelBakingKey
|
||||
import mods.betterfoliage.resource.discovery.ModelDiscoveryContext
|
||||
import mods.betterfoliage.resource.discovery.RenderKeyFactory
|
||||
import mods.betterfoliage.resource.model.*
|
||||
import mods.betterfoliage.util.Atlas
|
||||
import mods.betterfoliage.util.LazyInvalidatable
|
||||
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
|
||||
import net.minecraft.client.renderer.model.BlockModel
|
||||
import net.minecraft.client.renderer.model.ModelBakery
|
||||
import net.minecraft.util.Direction.DOWN
|
||||
import net.minecraft.util.ResourceLocation
|
||||
|
||||
object LilypadKey : BlockRenderKey {
|
||||
override fun replace(model: BakedModel, state: BlockState) = LilypadModel(meshifyStandard(model, state))
|
||||
}
|
||||
|
||||
object LilyPadDiscovery : ModelDiscoveryBase() {
|
||||
override val logger = BetterFoliage.logDetail
|
||||
override fun processModel(ctx: ModelDiscoveryContext, atlas: Consumer<Identifier>) =
|
||||
if (ctx.state.block == Blocks.LILY_PAD) LilypadKey else null
|
||||
}
|
||||
|
||||
class LilypadModel(wrapped: BakedModel) : WrappedBakedModel(wrapped) {
|
||||
override fun emitBlockQuads(blockView: ExtendedBlockView, state: BlockState, pos: BlockPos, randomSupplier: Supplier<Random>, context: RenderContext) {
|
||||
super.emitBlockQuads(blockView, state, pos, randomSupplier, context)
|
||||
if (!BetterFoliage.config.enabled || !BetterFoliage.config.lilypad.enabled) return
|
||||
|
||||
val random = randomSupplier.get()
|
||||
context.meshConsumer().accept(lilypadRootModels[random])
|
||||
if (random.nextInt(64) < BetterFoliage.config.lilypad.population) {
|
||||
context.meshConsumer().accept(lilypadFlowerModels[random])
|
||||
object StandardLilypadDiscovery : AbstractModelDiscovery() {
|
||||
override fun processModel(ctx: ModelDiscoveryContext) {
|
||||
if (ctx.getUnbaked() is BlockModel && ctx.blockState.block in LILYPAD_BLOCKS) {
|
||||
ctx.addReplacement(StandardLilypadKey)
|
||||
}
|
||||
super.processModel(ctx)
|
||||
}
|
||||
}
|
||||
|
||||
object StandardLilypadKey : HalfBakedWrapperKey() {
|
||||
override fun bake(ctx: ModelBakingContext, wrapped: SpecialRenderModel) = StandardLilypadModel(wrapped)
|
||||
}
|
||||
|
||||
class StandardLilypadModel(
|
||||
wrapped: SpecialRenderModel
|
||||
) : HalfBakedSpecialWrapper(wrapped) {
|
||||
override fun render(ctx: RenderCtxBase, noDecorations: Boolean) {
|
||||
ctx.checkSides = false
|
||||
super.render(ctx, noDecorations)
|
||||
if (!Config.enabled || !Config.lilypad.enabled) return
|
||||
|
||||
ShadersModIntegration.grass(ctx, Config.lilypad.shaderWind) {
|
||||
ctx.renderQuads(lilypadRootModels[ctx.random])
|
||||
}
|
||||
if (Config.lilypad.enabled(ctx.random)) ctx.renderQuads(lilypadFlowerModels[ctx.random])
|
||||
}
|
||||
|
||||
companion object {
|
||||
val lilypadRootSprites by SpriteSetDelegate(Atlas.BLOCKS) { idx ->
|
||||
Identifier(BetterFoliage.MOD_ID, "blocks/better_lilypad_roots_$idx")
|
||||
ResourceLocation(BetterFoliageMod.MOD_ID, "blocks/better_lilypad_roots_$idx")
|
||||
}
|
||||
val lilypadFlowerSprites by SpriteSetDelegate(Atlas.BLOCKS) { idx ->
|
||||
Identifier(BetterFoliage.MOD_ID, "blocks/better_lilypad_flower_$idx")
|
||||
ResourceLocation(BetterFoliageMod.MOD_ID, "blocks/better_lilypad_flower_$idx")
|
||||
}
|
||||
val lilypadRootModels by LazyInvalidatable(BetterFoliage.modelReplacer) {
|
||||
val shapes = tuftShapeSet(1.0, 1.0, 1.0, BetterFoliage.config.lilypad.hOffset)
|
||||
tuftModelSet(shapes, Color.white.asInt) { lilypadRootSprites[it] }
|
||||
val lilypadRootModels by LazyInvalidatable(BakeWrapperManager) {
|
||||
val shapes = tuftShapeSet(1.0, 1.0, 1.0, Config.lilypad.hOffset)
|
||||
tuftModelSet(shapes, -1) { lilypadRootSprites[it] }
|
||||
.transform { move(2.0 to DOWN) }
|
||||
.buildTufts()
|
||||
}
|
||||
val lilypadFlowerModels by LazyInvalidatable(BetterFoliage.modelReplacer) {
|
||||
val shapes = tuftShapeSet(0.5, 0.5, 0.5, BetterFoliage.config.lilypad.hOffset)
|
||||
tuftModelSet(shapes, Color.white.asInt) { lilypadFlowerSprites[it] }
|
||||
val lilypadFlowerModels by LazyInvalidatable(BakeWrapperManager) {
|
||||
val shapes = tuftShapeSet(0.5, 0.5, 0.5, Config.lilypad.hOffset)
|
||||
tuftModelSet(shapes, -1) { lilypadFlowerSprites[it] }
|
||||
.transform { move(1.0 to DOWN) }
|
||||
.buildTufts()
|
||||
}
|
||||
|
||||
@@ -1,63 +1,72 @@
|
||||
package mods.betterfoliage.render.block.vanilla
|
||||
|
||||
import mods.betterfoliage.BetterFoliage
|
||||
import mods.betterfoliage.render.lighting.withLighting
|
||||
import mods.betterfoliage.render.lighting.grassTuftLighting
|
||||
import mods.betterfoliage.util.Atlas
|
||||
import mods.betterfoliage.resource.discovery.BlockRenderKey
|
||||
import mods.betterfoliage.resource.discovery.ModelDiscoveryBase
|
||||
import mods.betterfoliage.BetterFoliageMod
|
||||
import mods.betterfoliage.config.Config
|
||||
import mods.betterfoliage.config.MYCELIUM_BLOCKS
|
||||
import mods.betterfoliage.model.HalfBakedSpecialWrapper
|
||||
import mods.betterfoliage.model.HalfBakedWrapperKey
|
||||
import mods.betterfoliage.model.SpecialRenderModel
|
||||
import mods.betterfoliage.model.SpriteSetDelegate
|
||||
import mods.betterfoliage.model.buildTufts
|
||||
import mods.betterfoliage.model.tuftModelSet
|
||||
import mods.betterfoliage.model.tuftShapeSet
|
||||
import mods.betterfoliage.render.lighting.LightingPreferredFace
|
||||
import mods.betterfoliage.render.pipeline.RenderCtxBase
|
||||
import mods.betterfoliage.resource.discovery.AbstractModelDiscovery
|
||||
import mods.betterfoliage.resource.discovery.BakeWrapperManager
|
||||
import mods.betterfoliage.resource.discovery.ModelBakingContext
|
||||
import mods.betterfoliage.resource.discovery.ModelDiscoveryContext
|
||||
import mods.betterfoliage.resource.discovery.RenderKeyFactory
|
||||
import mods.betterfoliage.resource.model.*
|
||||
import mods.betterfoliage.util.*
|
||||
import net.fabricmc.fabric.api.renderer.v1.render.RenderContext
|
||||
import net.minecraft.block.BlockState
|
||||
import mods.betterfoliage.util.Atlas
|
||||
import mods.betterfoliage.util.LazyInvalidatable
|
||||
import mods.betterfoliage.util.get
|
||||
import mods.betterfoliage.util.randomI
|
||||
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.UP
|
||||
import net.minecraft.world.ExtendedBlockView
|
||||
import java.util.*
|
||||
import java.util.function.Consumer
|
||||
import java.util.function.Supplier
|
||||
import net.minecraft.client.renderer.RenderType
|
||||
import net.minecraft.client.renderer.RenderTypeLookup
|
||||
import net.minecraft.client.renderer.model.BlockModel
|
||||
import net.minecraft.util.Direction
|
||||
import net.minecraft.util.ResourceLocation
|
||||
|
||||
object MyceliumKey : BlockRenderKey {
|
||||
override fun replace(model: BakedModel, state: BlockState) = MyceliumModel(meshifyStandard(model, state))
|
||||
object StandardMyceliumDiscovery : AbstractModelDiscovery() {
|
||||
override fun processModel(ctx: ModelDiscoveryContext) {
|
||||
if (ctx.getUnbaked() is BlockModel && ctx.blockState.block in MYCELIUM_BLOCKS) {
|
||||
ctx.addReplacement(StandardMyceliumKey)
|
||||
RenderTypeLookup.setRenderLayer(ctx.blockState.block, RenderType.cutout())
|
||||
}
|
||||
super.processModel(ctx)
|
||||
}
|
||||
}
|
||||
|
||||
object MyceliumDiscovery : ModelDiscoveryBase() {
|
||||
override val logger = BetterFoliage.logDetail
|
||||
val myceliumBlocks = listOf(Blocks.MYCELIUM)
|
||||
override fun processModel(ctx: ModelDiscoveryContext, atlas: Consumer<Identifier>) =
|
||||
if (ctx.state.block in myceliumBlocks) MyceliumKey else null
|
||||
object StandardMyceliumKey : HalfBakedWrapperKey() {
|
||||
override fun bake(ctx: ModelBakingContext, wrapped: SpecialRenderModel) = StandardMyceliumModel(wrapped)
|
||||
}
|
||||
|
||||
class MyceliumModel(wrapped: BakedModel) : WrappedBakedModel(wrapped) {
|
||||
class StandardMyceliumModel(
|
||||
wrapped: SpecialRenderModel
|
||||
) : HalfBakedSpecialWrapper(wrapped) {
|
||||
|
||||
val tuftLighting = grassTuftLighting(UP)
|
||||
val tuftLighting = LightingPreferredFace(Direction.UP)
|
||||
|
||||
override fun emitBlockQuads(blockView: ExtendedBlockView, state: BlockState, pos: BlockPos, randomSupplier: Supplier<Random>, context: RenderContext) {
|
||||
super.emitBlockQuads(blockView, state, pos, randomSupplier, context)
|
||||
override fun render(ctx: RenderCtxBase, noDecorations: Boolean) {
|
||||
super.render(ctx, noDecorations)
|
||||
|
||||
val random = randomSupplier.get()
|
||||
if (BetterFoliage.config.enabled &&
|
||||
BetterFoliage.config.shortGrass.let { it.myceliumEnabled && random.nextInt(64) < it.population } &&
|
||||
blockView.getBlockState(pos + UP.offset).isAir
|
||||
if (Config.shortGrass.enabled &&
|
||||
Config.shortGrass.myceliumEnabled &&
|
||||
Config.shortGrass.enabled(ctx.random) &&
|
||||
ctx.state(Direction.UP).isAir(ctx.world, ctx.pos)
|
||||
) {
|
||||
context.withLighting(tuftLighting) {
|
||||
it.accept(myceliumTuftModels[random])
|
||||
}
|
||||
ctx.vertexLighter = tuftLighting
|
||||
ctx.renderQuads(myceliumTuftModels[ctx.random])
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
val myceliumTuftSprites by SpriteSetDelegate(Atlas.BLOCKS) { idx ->
|
||||
Identifier(BetterFoliage.MOD_ID, "blocks/better_mycel_$idx")
|
||||
ResourceLocation(BetterFoliageMod.MOD_ID, "blocks/better_mycel_$idx")
|
||||
}
|
||||
val myceliumTuftModels by LazyInvalidatable(BetterFoliage.modelReplacer) {
|
||||
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 by LazyInvalidatable(BakeWrapperManager) {
|
||||
val shapes = Config.shortGrass.let { tuftShapeSet(it.size, it.heightMin, it.heightMax, it.hOffset) }
|
||||
tuftModelSet(shapes, -1) { idx -> myceliumTuftSprites[randomI()] }.buildTufts()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,66 +1,81 @@
|
||||
package mods.betterfoliage.render.block.vanilla
|
||||
|
||||
import mods.betterfoliage.BetterFoliageMod
|
||||
import mods.betterfoliage.BetterFoliage
|
||||
import mods.betterfoliage.render.lighting.withLighting
|
||||
import mods.betterfoliage.render.lighting.grassTuftLighting
|
||||
import mods.betterfoliage.util.Atlas
|
||||
import mods.betterfoliage.resource.discovery.BlockRenderKey
|
||||
import mods.betterfoliage.resource.discovery.ModelDiscoveryBase
|
||||
import mods.betterfoliage.config.Config
|
||||
import mods.betterfoliage.config.NETHERRACK_BLOCKS
|
||||
import mods.betterfoliage.model.HalfBakedSpecialWrapper
|
||||
import mods.betterfoliage.model.HalfBakedWrapperKey
|
||||
import mods.betterfoliage.model.SpecialRenderModel
|
||||
import mods.betterfoliage.model.SpriteSetDelegate
|
||||
import mods.betterfoliage.model.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.RenderCtxBase
|
||||
import mods.betterfoliage.resource.discovery.AbstractModelDiscovery
|
||||
import mods.betterfoliage.resource.discovery.BakeWrapperManager
|
||||
import mods.betterfoliage.resource.discovery.ModelBakingContext
|
||||
import mods.betterfoliage.resource.discovery.ModelDiscoveryContext
|
||||
import mods.betterfoliage.resource.discovery.RenderKeyFactory
|
||||
import mods.betterfoliage.resource.model.*
|
||||
import mods.betterfoliage.util.*
|
||||
import net.fabricmc.fabric.api.renderer.v1.render.RenderContext
|
||||
import net.minecraft.block.BlockRenderLayer
|
||||
import net.minecraft.block.BlockState
|
||||
import mods.betterfoliage.util.Atlas
|
||||
import mods.betterfoliage.util.LazyInvalidatable
|
||||
import mods.betterfoliage.util.Rotation
|
||||
import mods.betterfoliage.util.get
|
||||
import mods.betterfoliage.util.randomI
|
||||
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
|
||||
import net.minecraft.client.renderer.RenderType
|
||||
import net.minecraft.client.renderer.RenderTypeLookup
|
||||
import net.minecraft.client.renderer.model.BlockModel
|
||||
import net.minecraft.util.Direction.DOWN
|
||||
import net.minecraft.util.ResourceLocation
|
||||
|
||||
object NetherrackKey : BlockRenderKey {
|
||||
override fun replace(model: BakedModel, state: BlockState) = NetherrackModel(meshifyStandard(model, state))
|
||||
object StandardNetherrackDiscovery : AbstractModelDiscovery() {
|
||||
fun canRenderInLayer(layer: RenderType) = when {
|
||||
!Config.enabled -> layer == RenderType.solid()
|
||||
!Config.netherrack.enabled -> layer == RenderType.solid()
|
||||
else -> layer == RenderType.cutoutMipped()
|
||||
}
|
||||
|
||||
override fun processModel(ctx: ModelDiscoveryContext) {
|
||||
if (ctx.getUnbaked() is BlockModel && ctx.blockState.block in NETHERRACK_BLOCKS) {
|
||||
BetterFoliage.blockTypes.dirt.add(ctx.blockState)
|
||||
ctx.addReplacement(StandardNetherrackKey)
|
||||
RenderTypeLookup.setRenderLayer(ctx.blockState.block, ::canRenderInLayer)
|
||||
}
|
||||
super.processModel(ctx)
|
||||
}
|
||||
}
|
||||
|
||||
object NetherrackDiscovery : ModelDiscoveryBase() {
|
||||
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
|
||||
object StandardNetherrackKey : HalfBakedWrapperKey() {
|
||||
override fun bake(ctx: ModelBakingContext, wrapped: SpecialRenderModel) = StandardNetherrackModel(wrapped)
|
||||
}
|
||||
|
||||
class NetherrackModel(wrapped: BakedModel) : WrappedBakedModel(wrapped) {
|
||||
class StandardNetherrackModel(
|
||||
wrapped: SpecialRenderModel
|
||||
) : HalfBakedSpecialWrapper(wrapped) {
|
||||
|
||||
val tuftLighting = grassTuftLighting(DOWN)
|
||||
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)
|
||||
if (BetterFoliage.config.enabled &&
|
||||
BetterFoliage.config.netherrack.enabled &&
|
||||
blockView.getBlockState(pos + DOWN.offset).isAir
|
||||
) {
|
||||
val random = randomSupplier.get()
|
||||
context.withLighting(tuftLighting) {
|
||||
it.accept(netherrackTuftModels[random])
|
||||
}
|
||||
override fun render(ctx: RenderCtxBase, noDecorations: Boolean) {
|
||||
super.render(ctx, noDecorations)
|
||||
if (!Config.enabled || !Config.netherrack.enabled) return
|
||||
|
||||
if (ctx.isAir(DOWN)) {
|
||||
ctx.vertexLighter = tuftLighting
|
||||
ctx.renderQuads(netherrackTuftModels[ctx.random])
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
val netherrackTuftSprites by SpriteSetDelegate(Atlas.BLOCKS) { idx ->
|
||||
Identifier(BetterFoliage.MOD_ID, "blocks/better_netherrack_$idx")
|
||||
ResourceLocation(BetterFoliageMod.MOD_ID, "blocks/better_netherrack_$idx")
|
||||
}
|
||||
val netherrackTuftModels by LazyInvalidatable(BetterFoliage.modelReplacer) {
|
||||
val shapes = BetterFoliage.config.netherrack.let { tuftShapeSet(it.size, it.heightMin, it.heightMax, it.hOffset) }
|
||||
tuftModelSet(shapes, Color.white.asInt) { netherrackTuftSprites[randomI()] }
|
||||
val netherrackTuftModels by LazyInvalidatable(BakeWrapperManager) {
|
||||
val shapes = Config.netherrack.let { tuftShapeSet(it.size, it.heightMin, it.heightMax, it.hOffset) }
|
||||
tuftModelSet(shapes, -1) { netherrackTuftSprites[randomI()] }
|
||||
.transform { rotate(Rotation.fromUp[DOWN.ordinal]).rotateUV(2) }
|
||||
.withOpposites()
|
||||
.build(BlockRenderLayer.CUTOUT_MIPPED, flatLighting = false)
|
||||
.buildTufts()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,42 +1,58 @@
|
||||
package mods.betterfoliage.render.block.vanilla
|
||||
|
||||
import mods.betterfoliage.BetterFoliage
|
||||
import mods.betterfoliage.render.column.*
|
||||
import mods.betterfoliage.config.ACCEPTED_ROUND_LOG_MATERIALS
|
||||
import mods.betterfoliage.config.BlockConfig
|
||||
import mods.betterfoliage.config.Config
|
||||
import mods.betterfoliage.model.HalfBakedWrapperKey
|
||||
import mods.betterfoliage.model.SpecialRenderModel
|
||||
import mods.betterfoliage.render.column.ColumnBlockKey
|
||||
import mods.betterfoliage.render.column.ColumnMeshSet
|
||||
import mods.betterfoliage.render.column.ColumnModelBase
|
||||
import mods.betterfoliage.render.column.ColumnRenderLayer
|
||||
import mods.betterfoliage.resource.discovery.BakeWrapperManager
|
||||
import mods.betterfoliage.resource.discovery.ConfigurableBlockMatcher
|
||||
import mods.betterfoliage.resource.discovery.ConfigurableModelDiscovery
|
||||
import mods.betterfoliage.resource.discovery.ModelBakingContext
|
||||
import mods.betterfoliage.resource.discovery.ModelBakingKey
|
||||
import mods.betterfoliage.resource.discovery.ModelDiscoveryContext
|
||||
import mods.betterfoliage.resource.discovery.ModelTextureList
|
||||
import mods.betterfoliage.util.Atlas
|
||||
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.LazyMapInvalidatable
|
||||
import mods.betterfoliage.util.tryDefault
|
||||
import net.minecraft.block.BlockState
|
||||
import net.minecraft.block.LogBlock
|
||||
import net.minecraft.client.render.model.BakedModel
|
||||
import net.minecraft.client.texture.SpriteAtlasTexture
|
||||
import net.minecraft.util.Identifier
|
||||
import net.minecraft.util.math.Direction.Axis
|
||||
import java.util.function.Consumer
|
||||
import net.minecraft.block.RotatedPillarBlock
|
||||
import net.minecraft.util.Direction.Axis
|
||||
import net.minecraft.util.ResourceLocation
|
||||
import org.apache.logging.log4j.Level.INFO
|
||||
|
||||
object RoundLogOverlayLayer : ColumnRenderLayer() {
|
||||
override fun getColumnKey(state: BlockState) = BetterFoliage.modelReplacer.getTyped<ColumnBlockKey>(state)
|
||||
override val connectSolids: Boolean get() = BetterFoliage.config.roundLogs.connectSolids
|
||||
override val lenientConnect: Boolean get() = BetterFoliage.config.roundLogs.lenientConnect
|
||||
override val defaultToY: Boolean get() = BetterFoliage.config.roundLogs.defaultY
|
||||
interface RoundLogKey : ColumnBlockKey, ModelBakingKey {
|
||||
val barkSprite: ResourceLocation
|
||||
val endSprite: ResourceLocation
|
||||
}
|
||||
|
||||
object StandardLogDiscovery : ConfigurableModelDiscovery() {
|
||||
override val logger = BetterFoliage.logDetail
|
||||
override val matchClasses: ConfigurableBlockMatcher get() = BetterFoliage.blockConfig.logBlocks
|
||||
override val modelTextures: List<ModelTextureList> get() = BetterFoliage.blockConfig.logModels.modelList
|
||||
object RoundLogOverlayLayer : ColumnRenderLayer() {
|
||||
override fun getColumnKey(state: BlockState) = BetterFoliage.blockTypes.getTypedOrNull<ColumnBlockKey>(state)
|
||||
override val connectSolids: Boolean get() = Config.roundLogs.connectSolids
|
||||
override val lenientConnect: Boolean get() = Config.roundLogs.lenientConnect
|
||||
override val defaultToY: Boolean get() = Config.roundLogs.defaultY
|
||||
}
|
||||
|
||||
override fun processModel(state: BlockState, textures: List<String>, atlas: Consumer<Identifier>): BlockRenderKey? {
|
||||
val axis = getAxis(state)
|
||||
log(" axis $axis")
|
||||
return RoundLogModel.Key(axis, Identifier(textures[0]), Identifier(textures[1]))
|
||||
object StandardRoundLogDiscovery : ConfigurableModelDiscovery() {
|
||||
override val matchClasses: ConfigurableBlockMatcher get() = BlockConfig.logBlocks
|
||||
override val modelTextures: List<ModelTextureList> get() = BlockConfig.logModels.modelList
|
||||
|
||||
override fun processModel(ctx: ModelDiscoveryContext, textureMatch: List<ResourceLocation>) {
|
||||
val axis = getAxis(ctx.blockState)
|
||||
|
||||
detailLogger.log(INFO, " axis $axis, material ${ctx.blockState.material}")
|
||||
if (!Config.roundLogs.plantsOnly || ctx.blockState.material in ACCEPTED_ROUND_LOG_MATERIALS)
|
||||
ctx.addReplacement(StandardRoundLogKey(axis, textureMatch[0], textureMatch[1]))
|
||||
}
|
||||
|
||||
fun getAxis(state: BlockState): Axis? {
|
||||
val axis = tryDefault(null) { state.get(LogBlock.AXIS).toString() } ?:
|
||||
state.entries.entries.find { it.key.getName().toLowerCase() == "axis" }?.value?.toString()
|
||||
val axis = tryDefault(null) { state.getValue(RotatedPillarBlock.AXIS).toString() } ?:
|
||||
state.values.entries.find { it.key.name.toLowerCase() == "axis" }?.value?.toString()
|
||||
return when (axis) {
|
||||
"x" -> Axis.X
|
||||
"y" -> Axis.Y
|
||||
@@ -46,32 +62,30 @@ object StandardLogDiscovery : ConfigurableModelDiscovery() {
|
||||
}
|
||||
}
|
||||
|
||||
interface RoundLogKey : ColumnBlockKey, BlockRenderKey {
|
||||
val barkSprite: Identifier
|
||||
val endSprite: Identifier
|
||||
data class StandardRoundLogKey(
|
||||
override val axis: Axis?,
|
||||
override val barkSprite: ResourceLocation,
|
||||
override val endSprite: ResourceLocation
|
||||
) : RoundLogKey, HalfBakedWrapperKey() {
|
||||
override fun bake(ctx: ModelBakingContext, wrapped: SpecialRenderModel) = StandardRoundLogModel(this, wrapped)
|
||||
}
|
||||
|
||||
class RoundLogModel(val key: Key, wrapped: BakedModel) : ColumnModelBase(wrapped) {
|
||||
override val enabled: Boolean get() = BetterFoliage.config.enabled && BetterFoliage.config.roundLogs.enabled
|
||||
class StandardRoundLogModel(
|
||||
val key: StandardRoundLogKey,
|
||||
wrapped: SpecialRenderModel
|
||||
) : ColumnModelBase(wrapped) {
|
||||
override val enabled: Boolean get() = Config.enabled && Config.roundLogs.enabled
|
||||
override val overlayLayer: ColumnRenderLayer get() = RoundLogOverlayLayer
|
||||
override val connectPerpendicular: Boolean get() = BetterFoliage.config.roundLogs.connectPerpendicular
|
||||
override val connectPerpendicular: Boolean get() = Config.roundLogs.connectPerpendicular
|
||||
|
||||
val modelSet by modelSets.delegate(key)
|
||||
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 {
|
||||
val modelSets = LazyMap(BetterFoliage.modelReplacer) { key: Key ->
|
||||
val barkSprite = Atlas.BLOCKS.atlas[key.barkSprite]!!
|
||||
val endSprite = Atlas.BLOCKS.atlas[key.endSprite]!!
|
||||
BetterFoliage.config.roundLogs.let { config ->
|
||||
val modelSets = LazyMapInvalidatable(BakeWrapperManager) { key: StandardRoundLogKey ->
|
||||
val barkSprite = Atlas.BLOCKS[key.barkSprite]
|
||||
val endSprite = Atlas.BLOCKS[key.endSprite]
|
||||
Config.roundLogs.let { config ->
|
||||
ColumnMeshSet(
|
||||
config.radiusSmall, config.radiusLarge, config.zProtection,
|
||||
key.axis ?: Axis.Y,
|
||||
@@ -81,4 +95,4 @@ class RoundLogModel(val key: Key, wrapped: BakedModel) : ColumnModelBase(wrapped
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,94 +1,105 @@
|
||||
package mods.betterfoliage.render.block.vanilla
|
||||
|
||||
import mods.betterfoliage.BetterFoliage
|
||||
import mods.betterfoliage.chunk.CachedBlockCtx
|
||||
import mods.betterfoliage.render.SALTWATER_BIOMES
|
||||
import mods.betterfoliage.render.SAND_BLOCKS
|
||||
import mods.betterfoliage.render.lighting.withLighting
|
||||
import mods.betterfoliage.render.lighting.grassTuftLighting
|
||||
import mods.betterfoliage.util.Atlas
|
||||
import mods.betterfoliage.resource.discovery.BlockRenderKey
|
||||
import mods.betterfoliage.resource.discovery.ModelDiscoveryBase
|
||||
import mods.betterfoliage.BetterFoliageMod
|
||||
import mods.betterfoliage.config.Config
|
||||
import mods.betterfoliage.config.SALTWATER_BIOMES
|
||||
import mods.betterfoliage.config.SAND_BLOCKS
|
||||
import mods.betterfoliage.model.HalfBakedSpecialWrapper
|
||||
import mods.betterfoliage.model.HalfBakedWrapperKey
|
||||
import mods.betterfoliage.model.Quad
|
||||
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.RenderCtxBase
|
||||
import mods.betterfoliage.resource.discovery.AbstractModelDiscovery
|
||||
import mods.betterfoliage.resource.discovery.BakeWrapperManager
|
||||
import mods.betterfoliage.resource.discovery.ModelBakingContext
|
||||
import mods.betterfoliage.resource.discovery.ModelDiscoveryContext
|
||||
import mods.betterfoliage.resource.model.*
|
||||
import mods.betterfoliage.util.*
|
||||
import net.fabricmc.fabric.api.renderer.v1.render.RenderContext
|
||||
import net.minecraft.block.BlockRenderLayer.CUTOUT_MIPPED
|
||||
import net.minecraft.block.BlockState
|
||||
import net.minecraft.block.Material
|
||||
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
|
||||
import mods.betterfoliage.util.Atlas
|
||||
import mods.betterfoliage.util.LazyInvalidatable
|
||||
import mods.betterfoliage.util.Rotation
|
||||
import mods.betterfoliage.util.allDirections
|
||||
import mods.betterfoliage.util.get
|
||||
import mods.betterfoliage.util.mapArray
|
||||
import mods.betterfoliage.util.randomB
|
||||
import mods.betterfoliage.util.randomD
|
||||
import mods.betterfoliage.util.randomI
|
||||
import net.minecraft.block.material.Material
|
||||
import net.minecraft.client.renderer.RenderType
|
||||
import net.minecraft.client.renderer.RenderTypeLookup
|
||||
import net.minecraft.client.renderer.model.BlockModel
|
||||
import net.minecraft.util.Direction
|
||||
import net.minecraft.util.Direction.UP
|
||||
import net.minecraft.util.ResourceLocation
|
||||
|
||||
object SandKey : BlockRenderKey {
|
||||
override fun replace(model: BakedModel, state: BlockState) = SandModel(meshifyStandard(model, state))
|
||||
object StandardSandDiscovery : AbstractModelDiscovery() {
|
||||
override fun processModel(ctx: ModelDiscoveryContext) {
|
||||
if (ctx.getUnbaked() is BlockModel && ctx.blockState.block in SAND_BLOCKS) {
|
||||
BetterFoliage.blockTypes.dirt.add(ctx.blockState)
|
||||
ctx.addReplacement(StandardSandKey)
|
||||
RenderTypeLookup.setRenderLayer(ctx.blockState.block, RenderType.cutoutMipped())
|
||||
}
|
||||
super.processModel(ctx)
|
||||
}
|
||||
}
|
||||
|
||||
object SandDiscovery : ModelDiscoveryBase() {
|
||||
override val logger = BetterFoliage.logDetail
|
||||
|
||||
override fun processModel(ctx: ModelDiscoveryContext, atlas: Consumer<Identifier>) =
|
||||
if (ctx.state.block in SAND_BLOCKS) SandKey else null
|
||||
object StandardSandKey : HalfBakedWrapperKey() {
|
||||
override fun bake(ctx: ModelBakingContext, wrapped: SpecialRenderModel) = StandardSandModel(wrapped)
|
||||
}
|
||||
|
||||
class SandModel(wrapped: BakedModel) : WrappedBakedModel(wrapped) {
|
||||
class StandardSandModel(
|
||||
wrapped: SpecialRenderModel
|
||||
) : HalfBakedSpecialWrapper(wrapped) {
|
||||
val coralLighting = Direction.values().mapArray { LightingPreferredFace(it) }
|
||||
|
||||
val coralLighting = allDirections.map { grassTuftLighting(it) }.toTypedArray()
|
||||
override fun render(ctx: RenderCtxBase, noDecorations: Boolean) {
|
||||
super.render(ctx, noDecorations)
|
||||
if (noDecorations || !Config.enabled || !Config.coral.enabled(ctx.random)) return
|
||||
if (ctx.biome?.biomeCategory !in SALTWATER_BIOMES) return
|
||||
|
||||
override fun emitBlockQuads(blockView: ExtendedBlockView, state: BlockState, pos: BlockPos, randomSupplier: Supplier<Random>, context: RenderContext) {
|
||||
super.emitBlockQuads(blockView, state, pos, randomSupplier, context)
|
||||
|
||||
val ctx = CachedBlockCtx(blockView, pos)
|
||||
|
||||
val random = randomSupplier.get()
|
||||
if (!BetterFoliage.config.enabled || !BetterFoliage.config.coral.enabled(random)) return
|
||||
if (ctx.biome.category !in SALTWATER_BIOMES) return
|
||||
|
||||
allDirections.filter { random.nextInt(64) < BetterFoliage.config.coral.chance }.forEach { face ->
|
||||
allDirections.filter { ctx.random.nextInt(64) < Config.coral.chance }.forEach { face ->
|
||||
val isWater = ctx.state(face).material == Material.WATER
|
||||
val isDeepWater = isWater && ctx.offset(face).state(UP).material == Material.WATER
|
||||
if (isDeepWater) context.withLighting(coralLighting[face]) {
|
||||
it.accept(coralCrustModels[face][random])
|
||||
it.accept(coralTuftModels[face][random])
|
||||
if (isDeepWater) {
|
||||
ctx.vertexLighter = coralLighting[face]
|
||||
ctx.renderQuads(coralCrustModels[face][ctx.random])
|
||||
ctx.renderQuads(coralTuftModels[face][ctx.random])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
// val sandModel by LazyInvalidatable(BetterFoliage.modelReplacer) {
|
||||
// Array(64) { fullCubeTextured(Identifier("block/sand"), Color.white.asInt) }
|
||||
// }
|
||||
|
||||
val coralTuftSprites by SpriteSetDelegate(Atlas.BLOCKS) { idx ->
|
||||
Identifier(BetterFoliage.MOD_ID, "blocks/better_coral_$idx")
|
||||
ResourceLocation(BetterFoliageMod.MOD_ID, "blocks/better_coral_$idx")
|
||||
}
|
||||
val coralCrustSprites by SpriteSetDelegate(Atlas.BLOCKS) { idx ->
|
||||
Identifier(BetterFoliage.MOD_ID, "blocks/better_crust_$idx")
|
||||
ResourceLocation(BetterFoliageMod.MOD_ID, "blocks/better_crust_$idx")
|
||||
}
|
||||
val coralTuftModels by LazyInvalidatable(BetterFoliage.modelReplacer) {
|
||||
val shapes = BetterFoliage.config.coral.let { tuftShapeSet(it.size, 1.0, 1.0, it.hOffset) }
|
||||
allDirections.map { face ->
|
||||
tuftModelSet(shapes, Color.white.asInt) { coralTuftSprites[randomI()] }
|
||||
val coralTuftModels by LazyInvalidatable(BakeWrapperManager) {
|
||||
val shapes = Config.coral.let { tuftShapeSet(it.size, 1.0, 1.0, it.hOffset) }
|
||||
allDirections.mapArray { face ->
|
||||
tuftModelSet(shapes, -1) { coralTuftSprites[randomI()] }
|
||||
.transform { rotate(Rotation.fromUp[face]) }
|
||||
.withOpposites()
|
||||
.build(CUTOUT_MIPPED)
|
||||
}.toTypedArray()
|
||||
.buildTufts()
|
||||
}
|
||||
}
|
||||
val coralCrustModels by LazyInvalidatable(BetterFoliage.modelReplacer) {
|
||||
val coralCrustModels by LazyInvalidatable(BakeWrapperManager) {
|
||||
allDirections.map { face ->
|
||||
Array(64) { idx ->
|
||||
listOf(horizontalRectangle(x1 = -0.5, x2 = 0.5, z1 = -0.5, z2 = 0.5, y = 0.0)
|
||||
.scale(BetterFoliage.config.coral.crustSize)
|
||||
.move(0.5 + randomD(0.01, BetterFoliage.config.coral.vOffset) to UP)
|
||||
.rotate(Rotation.fromUp[face])
|
||||
.mirrorUV(randomB(), randomB()).rotateUV(randomI(max = 4))
|
||||
.sprite(coralCrustSprites[idx]).colorAndIndex(null)
|
||||
).build(CUTOUT_MIPPED)
|
||||
listOf(
|
||||
Quad.horizontalRectangle(x1 = -0.5, x2 = 0.5, z1 = -0.5, z2 = 0.5, y = 0.0)
|
||||
.scale(Config.coral.crustSize)
|
||||
.move(0.5 + randomD(0.01, Config.coral.vOffset) to UP)
|
||||
.rotate(Rotation.fromUp[face])
|
||||
.mirrorUV(randomB(), randomB()).rotateUV(randomI(max = 4))
|
||||
.sprite(coralCrustSprites[idx]).colorAndIndex(null)
|
||||
).bake(applyDiffuseLighting = false)
|
||||
}
|
||||
}.toTypedArray()
|
||||
}
|
||||
|
||||
@@ -1,14 +1,22 @@
|
||||
package mods.betterfoliage.render.column
|
||||
|
||||
import mods.betterfoliage.BetterFoliage
|
||||
import mods.betterfoliage.config.Config
|
||||
import mods.betterfoliage.model.Quad
|
||||
import mods.betterfoliage.model.UV
|
||||
import mods.betterfoliage.model.Vertex
|
||||
import mods.betterfoliage.model.bake
|
||||
import mods.betterfoliage.render.column.ColumnLayerData.SpecialRender.QuadrantType
|
||||
import mods.betterfoliage.render.column.ColumnLayerData.SpecialRender.QuadrantType.*
|
||||
import mods.betterfoliage.resource.model.*
|
||||
import mods.betterfoliage.render.column.ColumnLayerData.SpecialRender.QuadrantType.INVISIBLE
|
||||
import mods.betterfoliage.render.column.ColumnLayerData.SpecialRender.QuadrantType.LARGE_RADIUS
|
||||
import mods.betterfoliage.render.column.ColumnLayerData.SpecialRender.QuadrantType.SMALL_RADIUS
|
||||
import mods.betterfoliage.render.column.ColumnLayerData.SpecialRender.QuadrantType.SQUARE
|
||||
import mods.betterfoliage.util.Double3
|
||||
import mods.betterfoliage.util.Rotation
|
||||
import net.minecraft.block.BlockRenderLayer.SOLID
|
||||
import net.minecraft.client.texture.Sprite
|
||||
import net.minecraft.util.math.Direction.*
|
||||
import net.minecraft.client.renderer.texture.TextureAtlasSprite
|
||||
import net.minecraft.util.Direction.Axis
|
||||
import net.minecraft.util.Direction.EAST
|
||||
import net.minecraft.util.Direction.SOUTH
|
||||
import net.minecraft.util.Direction.UP
|
||||
|
||||
/**
|
||||
* Collection of dynamically generated meshes used to render rounded columns.
|
||||
@@ -18,20 +26,20 @@ class ColumnMeshSet(
|
||||
radiusLarge: Double,
|
||||
zProtection: Double,
|
||||
val axis: Axis,
|
||||
val spriteLeft: Sprite,
|
||||
val spriteRight: Sprite,
|
||||
val spriteTop: Sprite,
|
||||
val spriteBottom: Sprite
|
||||
val spriteLeft: TextureAtlasSprite,
|
||||
val spriteRight: TextureAtlasSprite,
|
||||
val spriteTop: TextureAtlasSprite,
|
||||
val spriteBottom: TextureAtlasSprite
|
||||
) {
|
||||
protected fun sideRounded(radius: Double, yBottom: Double, yTop: Double): List<Quad> {
|
||||
val halfRadius = radius * 0.5
|
||||
return listOf(
|
||||
// left side of the diagonal
|
||||
verticalRectangle(0.0, 0.5, 0.5 - radius, 0.5, yBottom, yTop).clampUV(minU = 0.0, maxU = 0.5 - radius),
|
||||
verticalRectangle(0.5 - radius, 0.5, 0.5 - halfRadius, 0.5 - halfRadius, yBottom, yTop).clampUV(minU = 0.5 - radius),
|
||||
Quad.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),
|
||||
// right side of the diagonal
|
||||
verticalRectangle(0.5 - halfRadius, 0.5 - halfRadius, 0.5, 0.5 - radius, yBottom, yTop).clampUV(maxU = radius - 0.5),
|
||||
verticalRectangle(0.5, 0.5 - radius, 0.5, 0.0, yBottom, yTop).clampUV(minU = radius - 0.5, maxU = 0.0)
|
||||
Quad.verticalRectangle(0.5 - 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)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -45,8 +53,8 @@ class ColumnMeshSet(
|
||||
}
|
||||
|
||||
protected fun sideSquare(yBottom: Double, yTop: Double) = listOf(
|
||||
verticalRectangle(0.0, 0.5, 0.5, 0.5, yBottom, yTop).clampUV(minU = 0.0),
|
||||
verticalRectangle(0.5, 0.5, 0.5, 0.0, yBottom, yTop).clampUV(maxU = 0.0)
|
||||
Quad.verticalRectangle(0.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)
|
||||
)
|
||||
|
||||
protected fun lidRounded(radius: Double, y: Double, isBottom: Boolean) = Array(4) { quadrant ->
|
||||
@@ -58,18 +66,18 @@ class ColumnMeshSet(
|
||||
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))
|
||||
listOf(Quad(v1, v2, v3, v4), Quad(v1, v4, v5, v6))
|
||||
.map { it.cycleVertices(if (isBottom xor BetterFoliage.config.nVidia) 0 else 1) }
|
||||
.map { it.cycleVertices(if (isBottom xor Config.nVidia) 0 else 1) }
|
||||
.map { it.rotate(rotation).rotateUV(quadrant) }
|
||||
.map { it.sprite(if (isBottom) spriteBottom else spriteTop).colorAndIndex(Color.white.asInt) }
|
||||
.map { it.sprite(if (isBottom) spriteBottom else spriteTop) }
|
||||
.map { if (isBottom) it.flipped else it }
|
||||
}
|
||||
|
||||
protected fun lidSquare(y: Double, isBottom: Boolean) = Array(4) { quadrant ->
|
||||
val rotation = baseRotation(axis) + quadrantRotations[quadrant]
|
||||
listOf(
|
||||
horizontalRectangle(x1 = 0.0, x2 = 0.5, z1 = 0.0, z2 = 0.5, y = y).clampUV(minU = 0.0, minV = 0.0)
|
||||
Quad.horizontalRectangle(x1 = 0.0, x2 = 0.5, z1 = 0.0, z2 = 0.5, y = y).clampUV(minU = 0.0, minV = 0.0)
|
||||
.rotate(rotation).rotateUV(quadrant)
|
||||
.sprite(if (isBottom) spriteBottom else spriteTop).colorAndIndex(Color.white.asInt)
|
||||
.sprite(if (isBottom) spriteBottom else spriteTop)
|
||||
.let { if (isBottom) it.flipped else it }
|
||||
)
|
||||
}
|
||||
@@ -84,9 +92,9 @@ class ColumnMeshSet(
|
||||
}
|
||||
protected fun List<Quad>.buildSides(quadsPerSprite: Int) = Array(4) { quadrant ->
|
||||
val rotation = baseRotation(axis) + quadrantRotations[quadrant]
|
||||
this.map { it.rotate(rotation).colorAndIndex(Color.white.asInt) }
|
||||
this.map { it.rotate(rotation) }
|
||||
.mapIndexed { idx, q -> if (idx % (2 * quadsPerSprite) >= quadsPerSprite) q.sprite(spriteRight) else q.sprite(spriteLeft) }
|
||||
.build(SOLID, flatLighting = false)
|
||||
.bake(false)
|
||||
}
|
||||
|
||||
companion object {
|
||||
@@ -114,13 +122,13 @@ class ColumnMeshSet(
|
||||
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 lidTopSquare = lidSquare(0.5, false).build(SOLID, flatLighting = false)
|
||||
val lidTopRoundSmall = lidRounded(radiusSmall, 0.5, false).build(SOLID, flatLighting = false)
|
||||
val lidTopRoundLarge = lidRounded(radiusLarge, 0.5, false).build(SOLID, flatLighting = false)
|
||||
val lidTopSquare = lidSquare(0.5, false).bake(false)
|
||||
val lidTopRoundSmall = lidRounded(radiusSmall, 0.5, false).bake(false)
|
||||
val lidTopRoundLarge = lidRounded(radiusLarge, 0.5, false).bake(false)
|
||||
|
||||
val lidBottomSquare = lidSquare(-0.5, true).build(SOLID, flatLighting = false)
|
||||
val lidBottomRoundSmall = lidRounded(radiusSmall, -0.5, true).build(SOLID, flatLighting = false)
|
||||
val lidBottomRoundLarge = lidRounded(radiusLarge, -0.5, true).build(SOLID, flatLighting = false)
|
||||
val lidBottomSquare = lidSquare(-0.5, true).bake(false)
|
||||
val lidBottomRoundSmall = lidRounded(radiusSmall, -0.5, true).bake(false)
|
||||
val lidBottomRoundLarge = lidRounded(radiusLarge, -0.5, true).bake(false)
|
||||
|
||||
val transitionTop = sideRoundedTransition(radiusLarge, radiusSmall, -0.5, 0.5).buildSides(quadsPerSprite = 2)
|
||||
val transitionBottom = sideRoundedTransition(radiusSmall, radiusLarge, -0.5, 0.5).buildSides(quadsPerSprite = 2)
|
||||
|
||||
@@ -1,43 +1,48 @@
|
||||
package mods.betterfoliage.render.column
|
||||
|
||||
import mods.betterfoliage.chunk.CachedBlockCtx
|
||||
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.SpecialRender.BlockType.*
|
||||
import mods.betterfoliage.render.column.ColumnLayerData.SpecialRender.QuadrantType.*
|
||||
import mods.betterfoliage.resource.model.WrappedBakedModel
|
||||
import net.fabricmc.fabric.api.renderer.v1.render.RenderContext
|
||||
import net.minecraft.block.BlockState
|
||||
import net.minecraft.client.render.model.BakedModel
|
||||
import net.minecraft.util.math.BlockPos
|
||||
import net.minecraft.util.math.Direction.Axis
|
||||
import net.minecraft.world.ExtendedBlockView
|
||||
import java.util.*
|
||||
import java.util.function.Supplier
|
||||
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.QuadrantType.INVISIBLE
|
||||
import mods.betterfoliage.render.column.ColumnLayerData.SpecialRender.QuadrantType.LARGE_RADIUS
|
||||
import mods.betterfoliage.render.column.ColumnLayerData.SpecialRender.QuadrantType.SMALL_RADIUS
|
||||
import mods.betterfoliage.render.column.ColumnLayerData.SpecialRender.QuadrantType.SQUARE
|
||||
import mods.betterfoliage.render.lighting.ColumnLighting
|
||||
import mods.betterfoliage.render.pipeline.RenderCtxBase
|
||||
import net.minecraft.util.Direction.Axis
|
||||
|
||||
abstract class ColumnModelBase(
|
||||
wrapped: SpecialRenderModel
|
||||
) : HalfBakedSpecialWrapper(wrapped) {
|
||||
|
||||
abstract class ColumnModelBase(wrapped: BakedModel) : WrappedBakedModel(wrapped) {
|
||||
abstract val enabled: Boolean
|
||||
abstract val overlayLayer: ColumnRenderLayer
|
||||
abstract val connectPerpendicular: Boolean
|
||||
abstract fun getMeshSet(axis: Axis, quadrant: Int): ColumnMeshSet
|
||||
|
||||
override fun emitBlockQuads(blockView: ExtendedBlockView, state: BlockState, pos: BlockPos, randomSupplier: Supplier<Random>, context: RenderContext) {
|
||||
val ctx = CachedBlockCtx(blockView, pos)
|
||||
override fun render(ctx: RenderCtxBase, noDecorations: Boolean) {
|
||||
if (!enabled) return super.render(ctx, noDecorations)
|
||||
|
||||
val roundLog = ChunkOverlayManager.get(overlayLayer, ctx)
|
||||
|
||||
when(roundLog) {
|
||||
ColumnLayerData.SkipRender -> return
|
||||
NormalRender -> return super.emitBlockQuads(blockView, state, pos, randomSupplier, context)
|
||||
NormalRender -> return super.render(ctx, noDecorations)
|
||||
ColumnLayerData.ResolveError, null -> {
|
||||
return super.emitBlockQuads(blockView, state, pos, randomSupplier, context)
|
||||
return super.render(ctx, noDecorations)
|
||||
}
|
||||
}
|
||||
|
||||
// if log axis is not defined and "Default to vertical" config option is not set, render normally
|
||||
if ((roundLog as ColumnLayerData.SpecialRender).column.axis == null && !overlayLayer.defaultToY) {
|
||||
return super.emitBlockQuads(blockView, state, pos, randomSupplier, context)
|
||||
return super.render(ctx, noDecorations)
|
||||
}
|
||||
|
||||
ctx.vertexLighter = ColumnLighting
|
||||
|
||||
val axis = roundLog.column.axis ?: Axis.Y
|
||||
val baseRotation = ColumnMeshSet.baseRotation(axis)
|
||||
ColumnMeshSet.quadrantRotations.forEachIndexed { idx, quadrantRotation ->
|
||||
@@ -56,8 +61,8 @@ abstract class ColumnModelBase(wrapped: BakedModel) : WrappedBakedModel(wrapped)
|
||||
val sideMesh = when (roundLog.quadrants[idx]) {
|
||||
SMALL_RADIUS -> meshSet.sideRoundSmall[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 meshSet.sideRoundLarge[idx]
|
||||
else if (roundLog.downType == PARALLEL && roundLog.quadrantsBottom[idx] == SMALL_RADIUS) meshSet.transitionBottom[idx]
|
||||
else meshSet.sideRoundLarge[idx]
|
||||
SQUARE -> meshSet.sideSquare[idx]
|
||||
else -> null
|
||||
}
|
||||
@@ -99,10 +104,10 @@ abstract class ColumnModelBase(wrapped: BakedModel) : WrappedBakedModel(wrapped)
|
||||
}
|
||||
|
||||
// render
|
||||
sideMesh?.let { context.meshConsumer().accept(it) }
|
||||
upMesh?.let { context.meshConsumer().accept(it) }
|
||||
downMesh?.let { context.meshConsumer().accept(it) }
|
||||
|
||||
sideMesh?.let { ctx.renderQuads(it) }
|
||||
upMesh?.let { ctx.renderQuads(it) }
|
||||
downMesh?.let { ctx.renderQuads(it) }
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,19 +1,29 @@
|
||||
package mods.betterfoliage.render.column
|
||||
|
||||
import mods.betterfoliage.BetterFoliage
|
||||
import mods.betterfoliage.chunk.BlockCtx
|
||||
import mods.betterfoliage.chunk.ChunkOverlayLayer
|
||||
import mods.betterfoliage.chunk.ChunkOverlayManager
|
||||
import mods.betterfoliage.chunk.dimType
|
||||
import mods.betterfoliage.render.column.ColumnLayerData.SpecialRender.BlockType.*
|
||||
import mods.betterfoliage.config.Config
|
||||
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.chunk.BlockCtx
|
||||
import mods.betterfoliage.util.*
|
||||
import mods.betterfoliage.render.column.ColumnLayerData.SpecialRender.QuadrantType.INVISIBLE
|
||||
import mods.betterfoliage.render.column.ColumnLayerData.SpecialRender.QuadrantType.LARGE_RADIUS
|
||||
import mods.betterfoliage.render.column.ColumnLayerData.SpecialRender.QuadrantType.SMALL_RADIUS
|
||||
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.util.Direction
|
||||
import net.minecraft.util.Direction.Axis
|
||||
import net.minecraft.util.math.BlockPos
|
||||
import net.minecraft.util.math.Direction.Axis
|
||||
import net.minecraft.util.math.Direction.AxisDirection
|
||||
import net.minecraft.world.ExtendedBlockView
|
||||
import net.minecraft.world.IBlockDisplayReader
|
||||
|
||||
/** Index of SOUTH-EAST quadrant. */
|
||||
const val SE = 0
|
||||
@@ -73,21 +83,20 @@ abstract class ColumnRenderLayer : ChunkOverlayLayer<ColumnLayerData> {
|
||||
|
||||
val allNeighborOffsets = (-1..1).flatMap { offsetX -> (-1..1).flatMap { offsetY -> (-1..1).map { offsetZ -> Int3(offsetX, offsetY, offsetZ) }}}
|
||||
|
||||
override fun onBlockUpdate(world: ExtendedBlockView, pos: BlockPos) {
|
||||
override fun onBlockUpdate(world: IBlockDisplayReader, pos: BlockPos) {
|
||||
allNeighborOffsets.forEach { offset -> ChunkOverlayManager.clear(world.dimType, this, pos + offset) }
|
||||
}
|
||||
|
||||
override fun calculate(ctx: BlockCtx): ColumnLayerData {
|
||||
if (allDirections.all { ctx.offset(it).isNormalCube }) return ColumnLayerData.SkipRender
|
||||
// val columnTextures = registry[ctx] ?: return ColumnLayerData.ResolveError
|
||||
// TODO detect round logs
|
||||
if (allDirections.all { dir -> ctx.offset(dir).let { it.isFullBlock } }) return ColumnLayerData.SkipRender
|
||||
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
|
||||
val logAxis = columnTextures.axis ?: if (defaultToY) Axis.Y else return ColumnLayerData.NormalRender
|
||||
|
||||
// check log neighborhood
|
||||
val baseRotation = Rotation.fromUp[(logAxis to AxisDirection.POSITIVE).face.ordinal]
|
||||
|
||||
val baseRotation = Rotation.fromUp[(logAxis to Direction.AxisDirection.POSITIVE).face.ordinal]
|
||||
val upType = ctx.blockType(baseRotation, logAxis, Int3(0, 1, 0))
|
||||
val downType = ctx.blockType(baseRotation, logAxis, Int3(0, -1, 0))
|
||||
|
||||
@@ -176,9 +185,9 @@ abstract class ColumnRenderLayer : ChunkOverlayLayer<ColumnLayerData> {
|
||||
val offsetRot = offset.rotate(rotation)
|
||||
val key = getColumnKey(state(offsetRot))
|
||||
return if (key == null) {
|
||||
if (offset(offsetRot).isNormalCube) SOLID else NONSOLID
|
||||
if (offset(offsetRot).isFullBlock) SOLID else NONSOLID
|
||||
} else {
|
||||
(key.axis ?: if (BetterFoliage.config.roundLogs.defaultY) Axis.Y else null)?.let {
|
||||
(key.axis ?: if (Config.roundLogs.defaultY) Axis.Y else null)?.let {
|
||||
if (it == axis) PARALLEL else PERPENDICULAR
|
||||
} ?: SOLID
|
||||
}
|
||||
|
||||
@@ -0,0 +1,138 @@
|
||||
package mods.betterfoliage.render.lighting
|
||||
|
||||
import mods.betterfoliage.util.get
|
||||
import mods.betterfoliage.util.mapArray
|
||||
import mods.betterfoliage.util.perpendiculars
|
||||
import net.minecraft.util.Direction
|
||||
import net.minecraft.util.Direction.*
|
||||
|
||||
typealias BoxCorner = Triple<Direction, Direction, Direction>
|
||||
fun BoxCorner.equalsUnordered(other: BoxCorner) = contains(other.first) && contains(other.second) && contains(other.third)
|
||||
|
||||
fun BoxCorner.contains(dir: Direction) = first == dir || second == dir || third == dir
|
||||
|
||||
fun Array<BoxCorner>.findIdx(corner: BoxCorner): Int? {
|
||||
forEachIndexed { idx, test -> if (test.contains(corner.first) && test.contains(corner.second) && test.contains(corner.third)) return idx }
|
||||
return null
|
||||
}
|
||||
fun Array<BoxCorner>.findIdx(predicate: (BoxCorner)->Boolean): Int? {
|
||||
forEachIndexed { idx, test -> if (predicate(test)) return idx }
|
||||
return null
|
||||
}
|
||||
|
||||
class AoSideHelper private constructor(face: Direction) {
|
||||
val sides = faceSides[face]
|
||||
val cornerSideDirections = faceCorners[face]
|
||||
val aoIndex = faceCornersIdx.mapArray { corner ->
|
||||
boxCornersDirIdx[face][sides[corner.first]][sides[corner.second]]!!
|
||||
}
|
||||
|
||||
companion object {
|
||||
/**
|
||||
* Indexing for undirected box corners (component order does not matter).
|
||||
* Array contains [Direction] triplets fully defining the corner.
|
||||
*/
|
||||
@JvmField
|
||||
val boxCornersUndir = Array(8) { idx -> Triple(
|
||||
if (idx and 1 != 0) EAST else WEST,
|
||||
if (idx and 2 != 0) UP else DOWN,
|
||||
if (idx and 4 != 0) SOUTH else NORTH
|
||||
) }
|
||||
|
||||
/**
|
||||
* Reverse lookup for [boxCornersUndir]. Index 3 times with the corner's cardinal directions.
|
||||
* A null value indicates an invalid corner (multiple indexing along the same axis)
|
||||
*/
|
||||
@JvmField
|
||||
val boxCornersUndirIdx = Array(6) { idx1 -> Array(6) { idx2 -> Array(6) { idx3 ->
|
||||
boxCornersUndir.findIdx(BoxCorner(
|
||||
Direction.values()[idx1],
|
||||
Direction.values()[idx2],
|
||||
Direction.values()[idx3]
|
||||
))
|
||||
} } }
|
||||
|
||||
/**
|
||||
* Indexing for directed face sides
|
||||
* First index is the face, second is index of side on face
|
||||
*/
|
||||
@JvmField
|
||||
val faceSides = Array(6) { faceIdx -> Array(4) { sideIdx ->
|
||||
Direction.values()[faceIdx].perpendiculars[sideIdx]
|
||||
} }
|
||||
|
||||
/**
|
||||
* Pairs of [faceSides] side indexes that form a valid pair describing a corner
|
||||
*/
|
||||
@JvmField
|
||||
val faceCornersIdx = arrayOf(0 to 2, 0 to 3, 1 to 2, 1 to 3)
|
||||
|
||||
/**
|
||||
* Indexing for directed face corners
|
||||
* First index is the face, second is index of corner on face
|
||||
*/
|
||||
@JvmField
|
||||
val faceCorners = Array(6) { faceIdx -> Array(4) { cornerIdx ->
|
||||
faceCornersIdx[cornerIdx].let { faceSides[faceIdx][it.first] to faceSides[faceIdx][it.second] }
|
||||
} }
|
||||
|
||||
/**
|
||||
* Indexing scheme for directed box corners.
|
||||
* The first direction - the face - matters, the other two are unordered.
|
||||
* 1:1 correspondence with possible AO values.
|
||||
* Array contains triplets defining the corner fully.
|
||||
*/
|
||||
@JvmField
|
||||
val boxCornersDir = Array(24) { idx ->
|
||||
val faceIdx = idx / 4; val face = Direction.values()[faceIdx]
|
||||
val cornerIdx = idx % 4; val corner = faceCorners[faceIdx][cornerIdx]
|
||||
BoxCorner(face, corner.first, corner.second)
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse lookup for [boxCornersDir]. Index 3 times with the corner's cardinal directions.
|
||||
* The first direction - the face - matters, the other two are unordered.
|
||||
* A null value indicates an invalid corner (multiple indexing along the same axis)
|
||||
*/
|
||||
@JvmField
|
||||
val boxCornersDirIdx = Array(6) { face -> Array(6) { side1 -> Array(6) { side2 ->
|
||||
boxCornersDir.findIdx { boxCorner ->
|
||||
boxCorner.first.ordinal == face && boxCorner.equalsUnordered(BoxCorner(
|
||||
Direction.values()[face],
|
||||
Direction.values()[side1],
|
||||
Direction.values()[side2]
|
||||
))
|
||||
}
|
||||
} } }
|
||||
|
||||
/**
|
||||
* Reverse lookup for [cornersDir].
|
||||
* 1st index: primary face
|
||||
* 2nd index: undirected corner index.
|
||||
* value: directed corner index
|
||||
* A null value indicates an invalid corner (primary face not shared by corner).
|
||||
*/
|
||||
@JvmField
|
||||
val boxCornersDirFromUndir = Array(6) { faceIdx -> Array(8) { undirIdx ->
|
||||
val face = Direction.values()[faceIdx]
|
||||
val corner = boxCornersUndir[undirIdx]
|
||||
if (!corner.contains(face)) null
|
||||
else boxCornersDir.findIdx { it.first == face && it.equalsUnordered(corner) }
|
||||
} }
|
||||
|
||||
@JvmField
|
||||
val forSide = Direction.values().mapArray { AoSideHelper(it) }
|
||||
|
||||
/**
|
||||
* Get corner index for vertex coordinates
|
||||
*/
|
||||
@JvmStatic
|
||||
fun getCornerUndir(x: Double, y: Double, z: Double): Int {
|
||||
var result = 0
|
||||
if (x > 0.0) result += 1
|
||||
if (y > 0.0) result += 2
|
||||
if (z > 0.0) result += 4
|
||||
return result
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,140 +0,0 @@
|
||||
package mods.betterfoliage.render.lighting
|
||||
|
||||
import mods.betterfoliage.util.*
|
||||
import net.fabricmc.fabric.api.renderer.v1.mesh.QuadView
|
||||
import net.minecraft.util.math.Direction
|
||||
import net.minecraft.util.math.Direction.*
|
||||
import kotlin.math.abs
|
||||
|
||||
val EPSILON = 0.05
|
||||
|
||||
interface CustomLighting {
|
||||
fun applyLighting(lighting: CustomLightingMeshConsumer, quad: QuadView, flat: Boolean, emissive: Boolean)
|
||||
}
|
||||
|
||||
interface CustomLightingMeshConsumer {
|
||||
/** Clear cached block brightness and AO values */
|
||||
fun clearLighting()
|
||||
/** Fill AO/light cache for given face */
|
||||
fun fillAoData(lightFace: Direction)
|
||||
/** Set AO/light values for quad vertex */
|
||||
fun setLighting(vIdx: Int, ao: Float, light: Int)
|
||||
/** Get neighbor block brightness */
|
||||
fun brNeighbor(dir: Direction): Int
|
||||
/** Block brightness value */
|
||||
val brSelf: Int
|
||||
/** Cached AO values for all box face corners */
|
||||
val aoFull: FloatArray
|
||||
/** Cached light values for all box face corners */
|
||||
val lightFull: IntArray
|
||||
}
|
||||
|
||||
/** Custom lighting used for protruding tuft quads (short grass, algae, cactus arms, etc.) */
|
||||
fun grassTuftLighting(lightFace: Direction) = object : CustomLighting {
|
||||
override fun applyLighting(lighting: CustomLightingMeshConsumer, quad : QuadView, flat: Boolean, emissive: Boolean) {
|
||||
if (flat) lighting.flatForceNeighbor(quad, lightFace) else lighting.smoothWithFaceOverride(quad, lightFace)
|
||||
}
|
||||
}
|
||||
|
||||
/** Custom lighting used for round leaves */
|
||||
fun roundLeafLighting() = object : CustomLighting {
|
||||
override fun applyLighting(lighting: CustomLightingMeshConsumer, quad: QuadView, flat: Boolean, emissive: Boolean) {
|
||||
if (flat) lighting.flatMax(quad) else lighting.smooth45PreferUp(quad)
|
||||
}
|
||||
}
|
||||
|
||||
/** Custom lighting used for reeds */
|
||||
fun reedLighting() = object : CustomLighting {
|
||||
override fun applyLighting(lighting: CustomLightingMeshConsumer, quad: QuadView, flat: Boolean, emissive: Boolean) {
|
||||
lighting.flatForceNeighbor(quad, UP)
|
||||
}
|
||||
}
|
||||
|
||||
/** Flat lighting, use neighbor brightness in the given direction */
|
||||
fun CustomLightingMeshConsumer.flatForceNeighbor(quad: QuadView, lightFace: Direction) {
|
||||
for (vIdx in 0 until 4) {
|
||||
setLighting(vIdx, 1.0f, brNeighbor(lightFace))
|
||||
}
|
||||
}
|
||||
|
||||
/** Smooth lighting, use *only* AO/light values on the given face (closest corner) */
|
||||
fun CustomLightingMeshConsumer.smoothWithFaceOverride(quad: QuadView, lightFace: Direction) {
|
||||
fillAoData(lightFace)
|
||||
forEachVertex(quad) { vIdx, x, y, z ->
|
||||
val cornerUndir = getCornerUndir(x, y, z)
|
||||
cornerDirFromUndir[lightFace.ordinal][cornerUndir]?.let { aoCorner ->
|
||||
setLighting(vIdx, aoFull[aoCorner], lightFull[aoCorner])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Smooth lighting scheme for 45-degree quads bisecting the box along 2 opposing face diagonals.
|
||||
*
|
||||
* Determine 2 *primary faces* based on the normal direction.
|
||||
* Take AO/light values *only* from the 2 primary faces *or* the UP direction,
|
||||
* based on which box corner is closest. Prefer taking values from the top face.
|
||||
*/
|
||||
fun CustomLightingMeshConsumer.smooth45PreferUp(quad: QuadView) {
|
||||
getAngles45(quad)?.let { normalFaces ->
|
||||
fillAoData(normalFaces.first)
|
||||
fillAoData(normalFaces.second)
|
||||
if (normalFaces.first != UP && normalFaces.second != UP) fillAoData(UP)
|
||||
forEachVertex(quad) { vIdx, x, y, z ->
|
||||
val isUp = y > 0.5f
|
||||
val cornerUndir = getCornerUndir(x, y, z)
|
||||
val preferredFace = if (isUp) UP else normalFaces.minBy { faceDistance(it, x, y, z) }
|
||||
val aoCorner = cornerDirFromUndir[preferredFace.ordinal][cornerUndir]!!
|
||||
setLighting(vIdx, aoFull[aoCorner], lightFull[aoCorner])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Flat lighting, use maximum neighbor brightness at the nearest box corner */
|
||||
fun CustomLightingMeshConsumer.flatMax(quad: QuadView) {
|
||||
forEachVertex(quad) { vIdx, x, y, z ->
|
||||
val maxBrightness = cornersUndir[getCornerUndir(x, y, z)].maxValueBy { brNeighbor(it) }
|
||||
setLighting(vIdx, 1.0f, maxBrightness)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* If the quad normal approximately bisects 2 axes at a 45 degree angle,
|
||||
* and is approximately perpendicular to the third, returns the 2 directions
|
||||
* the quad normal points towards.
|
||||
* Returns null otherwise.
|
||||
*/
|
||||
fun getAngles45(quad: QuadView): Pair<Direction, Direction>? {
|
||||
val normal = quad.faceNormal()
|
||||
// one of the components must be close to zero
|
||||
val zeroAxis = when {
|
||||
abs(normal.x) < EPSILON -> Axis.X
|
||||
abs(normal.y) < EPSILON -> Axis.Y
|
||||
abs(normal.z) < EPSILON -> Axis.Z
|
||||
else -> return null
|
||||
}
|
||||
// the other two must be of similar magnitude
|
||||
val diff = when(zeroAxis) {
|
||||
Axis.X -> abs(abs(normal.y) - abs(normal.z))
|
||||
Axis.Y -> abs(abs(normal.x) - abs(normal.z))
|
||||
Axis.Z -> abs(abs(normal.x) - abs(normal.y))
|
||||
}
|
||||
if (diff > EPSILON) return null
|
||||
return when(zeroAxis) {
|
||||
Axis.X -> Pair(if (normal.y > 0.0f) UP else DOWN, if (normal.z > 0.0f) SOUTH else NORTH)
|
||||
Axis.Y -> Pair(if (normal.x > 0.0f) EAST else WEST, if (normal.z > 0.0f) SOUTH else NORTH)
|
||||
Axis.Z -> Pair(if (normal.x > 0.0f) EAST else WEST, if (normal.y > 0.0f) UP else DOWN)
|
||||
}
|
||||
}
|
||||
|
||||
fun faceDistance(face: Direction, x: Float, y: Float, z: Float) = when(face) {
|
||||
WEST -> x; EAST -> 1.0f - x
|
||||
DOWN -> y; UP -> 1.0f - y
|
||||
NORTH -> z; SOUTH -> 1.0f - z
|
||||
}
|
||||
|
||||
inline fun forEachVertex(quad: QuadView, func: (vIdx: Int, x: Float, y: Float, z: Float)->Unit) {
|
||||
for (vIdx in 0..3) {
|
||||
func(vIdx, quad.x(vIdx), quad.y(vIdx), quad.z(vIdx))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package mods.betterfoliage.render.lighting
|
||||
|
||||
interface ForgeVertexLighterAccess {
|
||||
var vertexLighter: ForgeVertexLighter
|
||||
}
|
||||
|
||||
interface ForgeVertexLighter {
|
||||
fun updateVertexLightmap(normal: FloatArray, lightmap: FloatArray, x: Float, y: Float, z: Float)
|
||||
fun updateVertexColor(normal: FloatArray, color: FloatArray, x: Float, y: Float, z: Float, tint: Float, multiplier: Int)
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
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())
|
||||
}
|
||||
@@ -0,0 +1,147 @@
|
||||
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 }
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
isOccluded[sideIdx] = probe.position { move(lightFace) }.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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,210 @@
|
||||
package mods.betterfoliage.render.lighting
|
||||
|
||||
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.minBy
|
||||
import net.minecraft.client.renderer.color.BlockColors
|
||||
import net.minecraft.util.Direction
|
||||
import net.minecraft.util.Direction.*
|
||||
import net.minecraft.util.Direction.Axis
|
||||
import net.minecraftforge.client.model.pipeline.LightUtil
|
||||
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 = LightUtil.diffuseLight(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
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
package mods.betterfoliage.render.particle
|
||||
|
||||
import com.mojang.blaze3d.vertex.IVertexBuilder
|
||||
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: Int) {
|
||||
bCol = (color and 255) / 256.0f
|
||||
gCol = ((color shr 8) and 255) / 256.0f
|
||||
rCol = ((color shr 16) and 255) / 256.0f
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,24 +1,29 @@
|
||||
package mods.betterfoliage.render.particle
|
||||
|
||||
import mods.betterfoliage.BetterFoliage
|
||||
import mods.betterfoliage.ClientWorldLoadCallback
|
||||
import mods.betterfoliage.render.AbstractParticle
|
||||
import mods.betterfoliage.render.block.vanilla.LeafKey
|
||||
import mods.betterfoliage.util.*
|
||||
import net.fabricmc.fabric.api.event.world.WorldTickCallback
|
||||
import net.minecraft.client.MinecraftClient
|
||||
import net.minecraft.client.render.BufferBuilder
|
||||
import mods.betterfoliage.config.Config
|
||||
import mods.betterfoliage.util.Double3
|
||||
import mods.betterfoliage.util.PI2
|
||||
import mods.betterfoliage.util.minmax
|
||||
import mods.betterfoliage.util.randomB
|
||||
import mods.betterfoliage.util.randomD
|
||||
import mods.betterfoliage.util.randomF
|
||||
import mods.betterfoliage.util.randomI
|
||||
import net.minecraft.client.particle.IParticleRenderType
|
||||
import net.minecraft.client.world.ClientWorld
|
||||
import net.minecraft.util.math.BlockPos
|
||||
import net.minecraft.util.math.MathHelper
|
||||
import net.minecraft.world.World
|
||||
import java.util.*
|
||||
import net.minecraftforge.common.MinecraftForge
|
||||
import net.minecraftforge.event.TickEvent
|
||||
import net.minecraftforge.eventbus.api.SubscribeEvent
|
||||
import net.minecraftforge.fml.LogicalSide
|
||||
import java.util.Random
|
||||
import kotlin.math.abs
|
||||
import kotlin.math.cos
|
||||
import kotlin.math.sin
|
||||
|
||||
class FallingLeafParticle(
|
||||
world: World, pos: BlockPos, leafKey: LeafKey
|
||||
world: ClientWorld, pos: BlockPos, leaf: LeafParticleKey, blockColor: Int, random: Random
|
||||
) : AbstractParticle(
|
||||
world, pos.x.toDouble() + 0.5, pos.y.toDouble(), pos.z.toDouble() + 0.5
|
||||
) {
|
||||
@@ -27,73 +32,70 @@ class FallingLeafParticle(
|
||||
@JvmStatic val biomeBrightnessMultiplier = 0.5f
|
||||
}
|
||||
|
||||
var rotationSpeed = randomF(min = PI2 / 80.0, max = PI2 / 50.0)
|
||||
var rotPositive = true
|
||||
var rotationSpeed = random.randomF(min = PI2 / 80.0, max = PI2 / 50.0)
|
||||
val isMirrored = randomB()
|
||||
var wasCollided = false
|
||||
|
||||
init {
|
||||
angle = randomF(max = PI2)
|
||||
maxAge = MathHelper.floor(randomD(0.6, 1.0) * BetterFoliage.config.fallingLeaves.lifetime * 20.0)
|
||||
velocityY = -BetterFoliage.config.fallingLeaves.speed
|
||||
roll = random.randomF(max = PI2)
|
||||
oRoll = roll - rotationSpeed
|
||||
|
||||
scale = BetterFoliage.config.fallingLeaves.size.toFloat() * 0.1f
|
||||
lifetime = MathHelper.floor(randomD(0.6, 1.0) * Config.fallingLeaves.lifetime * 20.0)
|
||||
yd = -Config.fallingLeaves.speed
|
||||
|
||||
val state = world.getBlockState(pos)
|
||||
val blockColor = MinecraftClient.getInstance().blockColorMap.getColorMultiplier(state, world, pos, 0)
|
||||
sprite = LeafParticleRegistry[leafKey.leafType][randomI(max = 1024)]
|
||||
setParticleColor(leafKey.overrideColor, blockColor)
|
||||
quadSize = Config.fallingLeaves.size.toFloat() * 0.1f
|
||||
setColor(leaf.overrideColor?.asInt ?: blockColor)
|
||||
sprite = LeafParticleRegistry[leaf.leafType][randomI(max = 1024)]
|
||||
}
|
||||
|
||||
override val isValid: Boolean get() = (sprite != null)
|
||||
|
||||
|
||||
override fun update() {
|
||||
if (randomF() > 0.95f) rotPositive = !rotPositive
|
||||
// if (age > maxAge - 20) colorAlpha = 0.05f * (maxAge - age)
|
||||
if (random.nextFloat() > 0.95f) rotationSpeed *= -1.0f
|
||||
if (age > lifetime - 20) alpha = 0.05f * (lifetime - age)
|
||||
|
||||
if (onGround || wasCollided) {
|
||||
velocity.setTo(0.0, 0.0, 0.0)
|
||||
if (!wasCollided) {
|
||||
age = age.coerceAtLeast(maxAge - 20)
|
||||
age = age.coerceAtLeast(lifetime - 20)
|
||||
wasCollided = true
|
||||
}
|
||||
} else {
|
||||
val cosRotation = cos(angle).toDouble(); val sinRotation = sin(angle).toDouble()
|
||||
velocity.setTo(cosRotation, 0.0, sinRotation).mul(BetterFoliage.config.fallingLeaves.perturb)
|
||||
.add(LeafWindTracker.current).add(0.0, -1.0, 0.0).mul(BetterFoliage.config.fallingLeaves.speed)
|
||||
angle += if (rotPositive) rotationSpeed else -rotationSpeed
|
||||
val cosRotation = cos(roll).toDouble(); val sinRotation = sin(roll).toDouble()
|
||||
velocity.setTo(cosRotation, 0.0, sinRotation).mul(Config.fallingLeaves.perturb)
|
||||
.add(LeafWindTracker.current).add(0.0, -1.0, 0.0).mul(Config.fallingLeaves.speed)
|
||||
oRoll = roll
|
||||
roll += rotationSpeed
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
override fun getRenderType(): IParticleRenderType = IParticleRenderType.PARTICLE_SHEET_TRANSLUCENT
|
||||
}
|
||||
|
||||
object LeafWindTracker : WorldTickCallback, ClientWorldLoadCallback {
|
||||
val random = Random()
|
||||
object LeafWindTracker {
|
||||
var random = Random()
|
||||
val target = Double3.zero
|
||||
val current = Double3.zero
|
||||
var nextChange: Long = 0
|
||||
|
||||
fun changeWindTarget(world: World) {
|
||||
nextChange = world.time + 120 + random.nextInt(80)
|
||||
init {
|
||||
MinecraftForge.EVENT_BUS.register(this)
|
||||
}
|
||||
|
||||
fun changeWind(world: World) {
|
||||
nextChange = world.gameTime + 120 + random.nextInt(80)
|
||||
val direction = PI2 * random.nextDouble()
|
||||
val speed = abs(random.nextGaussian()) * BetterFoliage.config.fallingLeaves.windStrength +
|
||||
(if (!world.isRaining) 0.0 else abs(random.nextGaussian()) * BetterFoliage.config.fallingLeaves.stormStrength)
|
||||
val speed = abs(random.nextGaussian()) * Config.fallingLeaves.windStrength +
|
||||
(if (!world.isRaining) 0.0 else abs(random.nextGaussian()) * Config.fallingLeaves.stormStrength)
|
||||
target.setTo(cos(direction) * speed, 0.0, sin(direction) * speed)
|
||||
}
|
||||
|
||||
override fun tick(world: World) {
|
||||
if (world.isClient) {
|
||||
@SubscribeEvent
|
||||
fun handleWorldTick(event: TickEvent.WorldTickEvent) {
|
||||
if (event.phase == TickEvent.Phase.START && event.side == LogicalSide.CLIENT) event.world.let { world ->
|
||||
// change target wind speed
|
||||
if (world.time >= nextChange) changeWindTarget(world)
|
||||
if (world.dayTime >= nextChange) changeWind(world)
|
||||
|
||||
// change current wind speed
|
||||
val changeRate = if (world.isRaining) 0.015 else 0.005
|
||||
@@ -105,8 +107,6 @@ object LeafWindTracker : WorldTickCallback, ClientWorldLoadCallback {
|
||||
}
|
||||
}
|
||||
|
||||
override fun loadWorld(world: ClientWorld) {
|
||||
changeWindTarget(world)
|
||||
}
|
||||
}
|
||||
|
||||
// @SubscribeEvent
|
||||
// fun handleWorldLoad(event: WorldEvent.Load) { if (event.world.isClientSide) changeWind(event.world) }
|
||||
}
|
||||
@@ -1,68 +1,102 @@
|
||||
package mods.betterfoliage.render.particle
|
||||
|
||||
import mods.betterfoliage.BetterFoliage
|
||||
import mods.betterfoliage.resource.model.FixedSpriteSet
|
||||
import mods.betterfoliage.resource.model.SpriteSet
|
||||
import mods.betterfoliage.util.*
|
||||
import net.fabricmc.fabric.api.event.client.ClientSpriteRegistryCallback
|
||||
import net.minecraft.client.texture.SpriteAtlasTexture
|
||||
import net.minecraft.util.Identifier
|
||||
import mods.betterfoliage.BetterFoliageMod
|
||||
import mods.betterfoliage.model.Color
|
||||
import mods.betterfoliage.model.FixedSpriteSet
|
||||
import mods.betterfoliage.model.SpriteSet
|
||||
import mods.betterfoliage.resource.VeryEarlyReloadListener
|
||||
import mods.betterfoliage.util.Atlas
|
||||
import mods.betterfoliage.util.HasLogger
|
||||
import mods.betterfoliage.util.get
|
||||
import mods.betterfoliage.util.getLines
|
||||
import mods.betterfoliage.util.resourceManager
|
||||
import mods.betterfoliage.util.stripStart
|
||||
import net.minecraft.client.renderer.texture.MissingTextureSprite
|
||||
import net.minecraft.util.ResourceLocation
|
||||
import net.minecraftforge.client.event.TextureStitchEvent
|
||||
import net.minecraftforge.eventbus.api.SubscribeEvent
|
||||
import org.apache.logging.log4j.Level.INFO
|
||||
|
||||
object LeafParticleRegistry : ClientSpriteRegistryCallback {
|
||||
interface LeafBlockModel {
|
||||
val key: LeafParticleKey
|
||||
}
|
||||
|
||||
interface LeafParticleKey {
|
||||
val leafType: String
|
||||
val overrideColor: Color?
|
||||
}
|
||||
|
||||
object LeafParticleRegistry : HasLogger(), VeryEarlyReloadListener {
|
||||
val typeMappings = TextureMatcher()
|
||||
val allTypes get() = (typeMappings.mappings.map { it.type } + "default").distinct()
|
||||
|
||||
val ids = mutableMapOf<String, List<Identifier>>()
|
||||
val spriteSets = mutableMapOf<String, SpriteSet>()
|
||||
val particles = hashMapOf<String, SpriteSet>()
|
||||
|
||||
override fun registerSprites(atlasTexture: SpriteAtlasTexture, registry: ClientSpriteRegistryCallback.Registry) {
|
||||
ids.clear()
|
||||
spriteSets.clear()
|
||||
typeMappings.loadMappings(Identifier(BetterFoliage.MOD_ID, "leaf_texture_mappings.cfg"))
|
||||
(typeMappings.mappings.map { it.type } + "default").distinct().forEach { leafType ->
|
||||
val validIds = (0 until 16).map { idx -> Identifier(BetterFoliage.MOD_ID, "falling_leaf_${leafType}_$idx") }
|
||||
.filter { resourceManager.containsResource(Atlas.PARTICLES.wrap(it)) }
|
||||
ids[leafType] = validIds
|
||||
validIds.forEach { registry.register(it) }
|
||||
operator fun get(type: String) = particles[type] ?: particles["default"]!!
|
||||
|
||||
override fun onReloadStarted() {
|
||||
typeMappings.loadMappings(ResourceLocation(BetterFoliageMod.MOD_ID, "leaf_texture_mappings.cfg"))
|
||||
detailLogger.log(INFO, "Loaded leaf particle mappings, types = [${allTypes.joinToString(", ")}]")
|
||||
}
|
||||
|
||||
@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) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
operator fun get(type: String): SpriteSet {
|
||||
spriteSets[type]?.let { return it }
|
||||
ids[type]?.let {
|
||||
return FixedSpriteSet(Atlas.PARTICLES, it).apply { spriteSets[type] = this }
|
||||
@SubscribeEvent
|
||||
fun handlePostStitch(event: TextureStitchEvent.Post) {
|
||||
if (event.map.location() == Atlas.PARTICLES.resourceId) {
|
||||
(typeMappings.mappings.map { it.type } + "default").distinct().forEach { leafType ->
|
||||
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 {
|
||||
|
||||
data class Mapping(val domain: String?, val path: String, val type: String) {
|
||||
fun matches(iconLocation: Identifier): Boolean {
|
||||
fun matches(iconLocation: ResourceLocation): Boolean {
|
||||
return (domain == null || domain == iconLocation.namespace) &&
|
||||
iconLocation.path.stripStart("blocks/").contains(path, ignoreCase = true)
|
||||
iconLocation.path.stripStart("blocks/").contains(path, ignoreCase = true)
|
||||
}
|
||||
}
|
||||
|
||||
val mappings: MutableList<Mapping> = mutableListOf()
|
||||
|
||||
fun getType(resource: Identifier) = mappings.filter { it.matches(resource) }.map { it.type }.firstOrNull()
|
||||
fun getType(iconName: String) = Identifier(iconName).let { getType(it) }
|
||||
fun getType(resource: ResourceLocation) = mappings.filter { it.matches(resource) }.map { it.type }.firstOrNull()
|
||||
|
||||
fun loadMappings(mappingLocation: Identifier) {
|
||||
fun loadMappings(mappingLocation: ResourceLocation) {
|
||||
mappings.clear()
|
||||
resourceManager[mappingLocation]?.getLines()?.let { lines ->
|
||||
lines.filter { !it.startsWith("//") }.filter { it.isNotEmpty() }.forEach { line ->
|
||||
lines.filter { !it.startsWith("//") }.filter { !it.isEmpty() }.forEach { line ->
|
||||
val line2 = line.trim().split('=')
|
||||
if (line2.size == 2) {
|
||||
val mapping = line2[0].trim().split(':')
|
||||
if (mapping.size == 1) mappings.add(Mapping(null, mapping[0].trim(), line2[1].trim()))
|
||||
else if (mapping.size == 2) mappings.add(Mapping(mapping[0].trim(), mapping[1].trim(), line2[1].trim()))
|
||||
else if (mapping.size == 2) mappings.add(
|
||||
Mapping(
|
||||
mapping[0].trim(),
|
||||
mapping[1].trim(),
|
||||
line2[1].trim()
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,22 +1,30 @@
|
||||
package mods.betterfoliage.render.particle
|
||||
|
||||
import mods.betterfoliage.BetterFoliage
|
||||
import mods.betterfoliage.render.AbstractParticle
|
||||
import mods.betterfoliage.resource.model.SpriteDelegate
|
||||
import mods.betterfoliage.resource.model.SpriteSetDelegate
|
||||
import mods.betterfoliage.util.*
|
||||
import net.minecraft.client.particle.ParticleTextureSheet
|
||||
import net.minecraft.client.render.BufferBuilder
|
||||
import net.minecraft.util.Identifier
|
||||
import com.mojang.blaze3d.vertex.IVertexBuilder
|
||||
import mods.betterfoliage.BetterFoliageMod
|
||||
import mods.betterfoliage.config.Config
|
||||
import mods.betterfoliage.model.SpriteDelegate
|
||||
import mods.betterfoliage.model.SpriteSetDelegate
|
||||
import mods.betterfoliage.util.Atlas
|
||||
import mods.betterfoliage.util.Double3
|
||||
import mods.betterfoliage.util.PI2
|
||||
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.MathHelper
|
||||
import net.minecraft.world.World
|
||||
import java.util.*
|
||||
import java.util.Deque
|
||||
import java.util.LinkedList
|
||||
import kotlin.math.cos
|
||||
import kotlin.math.sin
|
||||
|
||||
class RisingSoulParticle(
|
||||
world: World, pos: BlockPos
|
||||
world: ClientWorld, pos: BlockPos
|
||||
) : AbstractParticle(
|
||||
world, pos.x.toDouble() + 0.5, pos.y.toDouble() + 1.0, pos.z.toDouble() + 0.5
|
||||
) {
|
||||
@@ -25,52 +33,58 @@ class RisingSoulParticle(
|
||||
val initialPhase = randomD(max = PI2)
|
||||
|
||||
init {
|
||||
velocityY = 0.1
|
||||
gravityStrength = 0.0f
|
||||
yd = 0.1
|
||||
gravity = 0.0f
|
||||
sprite = headIcons[randomI(max = 1024)]
|
||||
maxAge = MathHelper.floor((0.6 + 0.4 * randomD()) * BetterFoliage.config.risingSoul.lifetime * 20.0)
|
||||
lifetime = MathHelper.floor((0.6 + 0.4 * randomD()) * Config.risingSoul.lifetime * 20.0)
|
||||
}
|
||||
|
||||
override val isValid: Boolean get() = true
|
||||
|
||||
override fun update() {
|
||||
val phase = initialPhase + (age.toDouble() * PI2 / 64.0 )
|
||||
val cosPhase = cos(phase); val sinPhase = sin(phase)
|
||||
velocity.setTo(BetterFoliage.config.risingSoul.perturb.let { Double3(cosPhase * it, 0.1, sinPhase * it) })
|
||||
val phase = initialPhase + (age.toDouble() * PI2 / 64.0)
|
||||
val cosPhase = cos(phase);
|
||||
val sinPhase = sin(phase)
|
||||
velocity.setTo(Config.risingSoul.perturb.let { Double3(cosPhase * it, 0.1, sinPhase * it) })
|
||||
|
||||
particleTrail.addFirst(currentPos.copy())
|
||||
while (particleTrail.size > BetterFoliage.config.risingSoul.trailLength) particleTrail.removeLast()
|
||||
while (particleTrail.size > Config.risingSoul.trailLength) particleTrail.removeLast()
|
||||
|
||||
if (!BetterFoliage.config.enabled) markDead()
|
||||
if (!Config.enabled) remove()
|
||||
}
|
||||
|
||||
override fun render(worldRenderer: BufferBuilder, partialTickTime: Float) {
|
||||
var alpha = BetterFoliage.config.risingSoul.opacity.toFloat()
|
||||
if (age > maxAge - 40) alpha *= (maxAge - age) / 40.0f
|
||||
override fun render(vertexBuilder: IVertexBuilder, camera: ActiveRenderInfo, tickDelta: Float) {
|
||||
var alpha = Config.risingSoul.opacity.toFloat()
|
||||
if (age > lifetime - 40) alpha *= (lifetime - age) / 40.0f
|
||||
|
||||
renderParticleQuad(worldRenderer, partialTickTime,
|
||||
size = BetterFoliage.config.risingSoul.headSize * 0.25,
|
||||
renderParticleQuad(
|
||||
vertexBuilder, camera, tickDelta,
|
||||
size = Config.risingSoul.headSize * 0.25,
|
||||
alpha = alpha
|
||||
)
|
||||
|
||||
var scale = BetterFoliage.config.risingSoul.trailSize * 0.25
|
||||
var scale = Config.risingSoul.trailSize * 0.25
|
||||
particleTrail.forEachPairIndexed { idx, current, previous ->
|
||||
scale *= BetterFoliage.config.risingSoul.sizeDecay
|
||||
alpha *= BetterFoliage.config.risingSoul.opacityDecay.toFloat()
|
||||
if (idx % BetterFoliage.config.risingSoul.trailDensity == 0) renderParticleQuad(worldRenderer, partialTickTime,
|
||||
currentPos = current,
|
||||
prevPos = previous,
|
||||
size = scale,
|
||||
alpha = alpha,
|
||||
sprite = trackIcon
|
||||
)
|
||||
scale *= Config.risingSoul.sizeDecay
|
||||
alpha *= Config.risingSoul.opacityDecay.toFloat()
|
||||
if (idx % Config.risingSoul.trailDensity == 0)
|
||||
renderParticleQuad(
|
||||
vertexBuilder, camera, tickDelta,
|
||||
currentPos = current,
|
||||
prevPos = previous,
|
||||
size = scale,
|
||||
alpha = alpha,
|
||||
sprite = trackIcon
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun getType() = ParticleTextureSheet.PARTICLE_SHEET_TRANSLUCENT
|
||||
override fun getRenderType(): IParticleRenderType = IParticleRenderType.PARTICLE_SHEET_TRANSLUCENT
|
||||
|
||||
companion object {
|
||||
val headIcons by SpriteSetDelegate(Atlas.PARTICLES) { idx -> Identifier(BetterFoliage.MOD_ID, "rising_soul_$idx") }
|
||||
val trackIcon by SpriteDelegate(Atlas.PARTICLES) { Identifier(BetterFoliage.MOD_ID, "soul_track") }
|
||||
val headIcons by SpriteSetDelegate(Atlas.PARTICLES) { idx ->
|
||||
ResourceLocation(BetterFoliageMod.MOD_ID, "particle/rising_soul_$idx")
|
||||
}
|
||||
val trackIcon by SpriteDelegate(Atlas.PARTICLES) { ResourceLocation(BetterFoliageMod.MOD_ID, "particle/soul_track") }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
package mods.betterfoliage.render.pipeline
|
||||
|
||||
import com.mojang.blaze3d.matrix.MatrixStack
|
||||
import mods.betterfoliage.chunk.BasicBlockCtx
|
||||
import mods.betterfoliage.chunk.BlockCtx
|
||||
import mods.betterfoliage.model.HalfBakedQuad
|
||||
import mods.betterfoliage.model.SpecialRenderModel
|
||||
import mods.betterfoliage.render.lighting.VanillaFullBlockLighting
|
||||
import mods.betterfoliage.render.lighting.VanillaQuadLighting
|
||||
import mods.betterfoliage.render.lighting.VanillaVertexLighter
|
||||
import mods.betterfoliage.util.Int3
|
||||
import mods.betterfoliage.util.plus
|
||||
import net.minecraft.block.Block
|
||||
import net.minecraft.client.Minecraft
|
||||
import net.minecraft.util.Direction
|
||||
import net.minecraft.util.math.BlockPos
|
||||
import net.minecraft.world.IBlockDisplayReader
|
||||
import net.minecraftforge.client.model.data.IModelData
|
||||
import java.util.Random
|
||||
|
||||
/**
|
||||
* Rendering context for drawing [SpecialRenderModel] models.
|
||||
*
|
||||
* This class (and others in its constellation) basically form a replacement, highly customizable,
|
||||
* push-based partial rendering pipeline for [SpecialRenderModel] instances.
|
||||
*/
|
||||
abstract class RenderCtxBase(
|
||||
world: IBlockDisplayReader,
|
||||
pos: BlockPos,
|
||||
val matrixStack: MatrixStack,
|
||||
var checkSides: Boolean,
|
||||
val random: Random,
|
||||
val modelData: IModelData
|
||||
) : BlockCtx by BasicBlockCtx(world, pos) {
|
||||
|
||||
abstract fun renderQuad(quad: HalfBakedQuad)
|
||||
|
||||
var hasRendered = false
|
||||
val blockModelShapes = Minecraft.getInstance().blockRenderer.blockModelShaper
|
||||
var vertexLighter: VanillaVertexLighter = VanillaFullBlockLighting
|
||||
protected val lightingData = RenderCtxBase.lightingData.get().apply {
|
||||
calc.reset(this@RenderCtxBase)
|
||||
blockColors = Minecraft.getInstance().blockColors
|
||||
}
|
||||
|
||||
inline fun Direction?.shouldRender() = this == null || !checkSides || Block.shouldRenderFace(state, world, pos, this)
|
||||
|
||||
fun renderQuads(quads: Iterable<HalfBakedQuad>) {
|
||||
quads.forEach { quad ->
|
||||
if (quad.raw.face.shouldRender()) {
|
||||
renderQuad(quad)
|
||||
hasRendered = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun renderMasquerade(offset: Int3, func: () -> Unit) {
|
||||
lightingData.calc.blockPos += offset
|
||||
func()
|
||||
lightingData.calc.blockPos = pos
|
||||
}
|
||||
|
||||
companion object {
|
||||
val lightingData = ThreadLocal.withInitial { VanillaQuadLighting() }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
package mods.betterfoliage.render.pipeline
|
||||
|
||||
import com.mojang.blaze3d.matrix.MatrixStack
|
||||
import mods.betterfoliage.model.HalfBakedQuad
|
||||
import mods.betterfoliage.model.SpecialRenderModel
|
||||
import mods.betterfoliage.render.lighting.ForgeVertexLighter
|
||||
import mods.betterfoliage.render.lighting.ForgeVertexLighterAccess
|
||||
import net.minecraft.block.BlockState
|
||||
import net.minecraft.client.renderer.LightTexture
|
||||
import net.minecraft.util.math.BlockPos
|
||||
import net.minecraft.world.IBlockDisplayReader
|
||||
import net.minecraftforge.client.model.data.IModelData
|
||||
import net.minecraftforge.client.model.pipeline.VertexLighterFlat
|
||||
import java.util.Random
|
||||
|
||||
class RenderCtxForge(
|
||||
world: IBlockDisplayReader,
|
||||
pos: BlockPos,
|
||||
val lighter: VertexLighterFlat,
|
||||
matrixStack: MatrixStack,
|
||||
checkSides: Boolean,
|
||||
random: Random,
|
||||
modelData: IModelData
|
||||
): RenderCtxBase(world, pos, matrixStack, checkSides, random, modelData), ForgeVertexLighter {
|
||||
|
||||
override fun renderQuad(quad: HalfBakedQuad) {
|
||||
// set Forge lighter AO calculator to us
|
||||
vertexLighter.updateLightmapAndColor(quad, lightingData)
|
||||
quad.baked.pipe(lighter)
|
||||
}
|
||||
|
||||
// somewhat ugly hack to pipe lighting values into the Forge pipeline
|
||||
var vIdx = 0
|
||||
override fun updateVertexLightmap(normal: FloatArray, lightmap: FloatArray, x: Float, y: Float, z: Float) {
|
||||
lightingData.packedLight[vIdx].let { packedLight ->
|
||||
lightmap[0] = LightTexture.block(packedLight) / 0xF.toFloat()
|
||||
lightmap[1] = LightTexture.sky(packedLight) / 0xF.toFloat()
|
||||
}
|
||||
}
|
||||
|
||||
override fun updateVertexColor(normal: FloatArray, color: FloatArray, x: Float, y: Float, z: Float, tint: Float, multiplier: Int) {
|
||||
color[0] = lightingData.tint[0] * lightingData.colorMultiplier[vIdx]
|
||||
color[1] = lightingData.tint[1] * lightingData.colorMultiplier[vIdx]
|
||||
color[2] = lightingData.tint[2] * lightingData.colorMultiplier[vIdx]
|
||||
vIdx++
|
||||
}
|
||||
|
||||
companion object {
|
||||
@JvmStatic
|
||||
fun render(
|
||||
lighter: VertexLighterFlat,
|
||||
world: IBlockDisplayReader,
|
||||
model: SpecialRenderModel,
|
||||
state: BlockState,
|
||||
pos: BlockPos,
|
||||
matrixStack: MatrixStack,
|
||||
checkSides: Boolean,
|
||||
rand: Random, seed: Long,
|
||||
modelData: IModelData
|
||||
): Boolean {
|
||||
lighter.setWorld(world)
|
||||
lighter.setState(state)
|
||||
lighter.setBlockPos(pos)
|
||||
rand.setSeed(seed)
|
||||
lighter.updateBlockInfo()
|
||||
return RenderCtxForge(world, pos, lighter, matrixStack, checkSides, rand, modelData).let {
|
||||
(lighter as ForgeVertexLighterAccess).vertexLighter = it
|
||||
model.render(it, false)
|
||||
lighter.resetBlockInfo()
|
||||
it.hasRendered
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
package mods.betterfoliage.render.pipeline
|
||||
|
||||
import com.mojang.blaze3d.matrix.MatrixStack
|
||||
import com.mojang.blaze3d.vertex.IVertexBuilder
|
||||
import mods.betterfoliage.model.HalfBakedQuad
|
||||
import mods.betterfoliage.model.SpecialRenderModel
|
||||
import net.minecraft.block.BlockState
|
||||
import net.minecraft.client.renderer.BlockModelRenderer
|
||||
import net.minecraft.util.math.BlockPos
|
||||
import net.minecraft.world.IBlockDisplayReader
|
||||
import net.minecraftforge.client.model.data.IModelData
|
||||
import java.util.Random
|
||||
|
||||
class RenderCtxVanilla(
|
||||
val renderer: BlockModelRenderer,
|
||||
world: IBlockDisplayReader,
|
||||
pos: BlockPos,
|
||||
val buffer: IVertexBuilder,
|
||||
val combinedOverlay: Int,
|
||||
matrixStack: MatrixStack,
|
||||
checkSides: Boolean,
|
||||
random: Random,
|
||||
val seed: Long,
|
||||
modelData: IModelData,
|
||||
val useAO: Boolean
|
||||
): RenderCtxBase(world, pos, matrixStack, checkSides, random, modelData) {
|
||||
|
||||
override fun renderQuad(quad: HalfBakedQuad) {
|
||||
vertexLighter.updateLightmapAndColor(quad, lightingData)
|
||||
buffer.putBulkData(
|
||||
matrixStack.last(), quad.baked,
|
||||
lightingData.colorMultiplier,
|
||||
lightingData.tint[0], lightingData.tint[1], lightingData.tint[2],
|
||||
lightingData.packedLight, combinedOverlay, true
|
||||
)
|
||||
}
|
||||
|
||||
companion object {
|
||||
@JvmStatic
|
||||
fun render(
|
||||
renderer: BlockModelRenderer,
|
||||
world: IBlockDisplayReader,
|
||||
model: SpecialRenderModel,
|
||||
state: BlockState,
|
||||
pos: BlockPos,
|
||||
matrixStack: MatrixStack,
|
||||
buffer: IVertexBuilder,
|
||||
checkSides: Boolean,
|
||||
random: Random,
|
||||
rand: Long,
|
||||
combinedOverlay: Int,
|
||||
modelData: IModelData,
|
||||
smooth: Boolean
|
||||
): Boolean {
|
||||
random.setSeed(rand)
|
||||
val ctx = RenderCtxVanilla(renderer, world, pos, buffer, combinedOverlay, matrixStack, checkSides, random, rand, modelData, smooth)
|
||||
lightingData.apply {
|
||||
|
||||
}
|
||||
model.render(ctx, false)
|
||||
return ctx.hasRendered
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
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()
|
||||
return stage.wait(null)
|
||||
}
|
||||
|
||||
fun onReloadStarted() {}
|
||||
}
|
||||
@@ -1,99 +0,0 @@
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,129 @@
|
||||
package mods.betterfoliage.resource.discovery
|
||||
|
||||
import mods.betterfoliage.BetterFoliage
|
||||
import mods.betterfoliage.util.Atlas
|
||||
import mods.betterfoliage.util.HasLogger
|
||||
import mods.betterfoliage.util.Invalidator
|
||||
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")
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
object 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.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)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
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
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
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
|
||||
}
|
||||
}
|
||||
@@ -1,13 +1,12 @@
|
||||
package mods.betterfoliage.resource.discovery
|
||||
|
||||
import mods.betterfoliage.util.getLines
|
||||
import mods.betterfoliage.util.INTERMEDIARY
|
||||
import mods.betterfoliage.util.HasLogger
|
||||
import mods.betterfoliage.util.getJavaClass
|
||||
import net.fabricmc.loader.api.FabricLoader
|
||||
import mods.betterfoliage.util.getLines
|
||||
import mods.betterfoliage.util.resourceManager
|
||||
import net.minecraft.block.Block
|
||||
import net.minecraft.resource.ResourceManager
|
||||
import net.minecraft.util.Identifier
|
||||
import org.apache.logging.log4j.Logger
|
||||
import net.minecraft.util.ResourceLocation
|
||||
import org.apache.logging.log4j.Level.INFO
|
||||
|
||||
interface IBlockMatcher {
|
||||
fun matchesClass(block: Block): Boolean
|
||||
@@ -24,8 +23,7 @@ class SimpleBlockMatcher(vararg val classes: Class<*>) : IBlockMatcher {
|
||||
}
|
||||
}
|
||||
|
||||
class ConfigurableBlockMatcher(val logger: Logger, val location: Identifier) : IBlockMatcher {
|
||||
|
||||
class ConfigurableBlockMatcher(val location: ResourceLocation) : HasLogger(), IBlockMatcher {
|
||||
val blackList = mutableListOf<Class<*>>()
|
||||
val whiteList = mutableListOf<Class<*>>()
|
||||
|
||||
@@ -43,34 +41,32 @@ class ConfigurableBlockMatcher(val logger: Logger, val location: Identifier) : I
|
||||
return null
|
||||
}
|
||||
|
||||
fun readDefaults(manager: ResourceManager) {
|
||||
fun readDefaults() {
|
||||
blackList.clear()
|
||||
whiteList.clear()
|
||||
manager.getAllResources(location).forEach { resource ->
|
||||
logger.debug("Reading resource $location from pack ${resource.resourcePackName}")
|
||||
resourceManager.getResources(location).forEach { resource ->
|
||||
detailLogger.log(INFO, "Reading block class configuration $location from pack ${resource.sourceName}")
|
||||
resource.getLines().map{ it.trim() }.filter { !it.startsWith("//") && it.isNotEmpty() }.forEach { line ->
|
||||
if (line.startsWith("-")) getBlockClass(line.substring(1))?.let { blackList.add(it) }
|
||||
else getBlockClass(line)?.let { whiteList.add(it) }
|
||||
if (line.startsWith("-")) getJavaClass(line.substring(1))?.let { blackList.add(it) }
|
||||
else getJavaClass(line)?.let { whiteList.add(it) }
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun getBlockClass(name: String) = getJavaClass(FabricLoader.getInstance().mappingResolver.mapClassName(INTERMEDIARY, name))
|
||||
}
|
||||
|
||||
data class ModelTextureList(val modelLocation: Identifier, val textureNames: List<String>) {
|
||||
constructor(vararg args: String) : this(Identifier(args[0]), listOf(*args).drop(1))
|
||||
data class ModelTextureList(val modelLocation: ResourceLocation, val textureNames: List<String>) {
|
||||
constructor(vararg args: String) : this(ResourceLocation(args[0]), listOf(*args).drop(1))
|
||||
}
|
||||
|
||||
class ModelTextureListConfiguration(val logger: Logger, val location: Identifier) {
|
||||
class ModelTextureListConfiguration(val location: ResourceLocation) : HasLogger() {
|
||||
val modelList = mutableListOf<ModelTextureList>()
|
||||
fun readDefaults(manager: ResourceManager) {
|
||||
manager.getAllResources(location).forEach { resource ->
|
||||
logger.debug("Reading resource $location from pack ${resource.resourcePackName}")
|
||||
fun readDefaults() {
|
||||
resourceManager.getResources(location).forEach { resource ->
|
||||
detailLogger.log(INFO, "Reading model/texture configuration $location from pack ${resource.sourceName}")
|
||||
resource.getLines().map{ it.trim() }.filter { !it.startsWith("//") && it.isNotEmpty() }.forEach { line ->
|
||||
val elements = line.split(",")
|
||||
modelList.add(ModelTextureList(Identifier(elements.first()), elements.drop(1)))
|
||||
modelList.add(ModelTextureList(ResourceLocation(elements.first()), elements.drop(1)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,104 @@
|
||||
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) {
|
||||
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
|
||||
}
|
||||
@@ -1,76 +0,0 @@
|
||||
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?
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
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()
|
||||
}
|
||||
@@ -3,25 +3,25 @@ package mods.betterfoliage.resource.generated
|
||||
import mods.betterfoliage.util.Atlas
|
||||
import mods.betterfoliage.util.bytes
|
||||
import mods.betterfoliage.util.loadSprite
|
||||
import net.minecraft.resource.ResourceManager
|
||||
import net.minecraft.util.Identifier
|
||||
import net.minecraft.resources.IResourceManager
|
||||
import net.minecraft.util.ResourceLocation
|
||||
import java.awt.image.BufferedImage
|
||||
import java.lang.Math.max
|
||||
|
||||
data class CenteredSprite(val sprite: Identifier, val atlas: Atlas = Atlas.BLOCKS, val aspectHeight: Int = 1, val aspectWidth: Int = 1) {
|
||||
data class CenteredSprite(val sprite: ResourceLocation, val aspectHeight: Int = 1, val aspectWidth: Int = 1, val atlas: Atlas = Atlas.BLOCKS) {
|
||||
|
||||
fun register(pack: GeneratedBlockTexturePack) = pack.register(this, this::draw)
|
||||
fun register(pack: GeneratedTexturePack) = pack.register(atlas, this, this::draw)
|
||||
|
||||
fun draw(resourceManager: ResourceManager): ByteArray {
|
||||
val baseTexture = resourceManager.loadSprite(atlas.wrap(sprite))
|
||||
fun draw(resourceManager: IResourceManager): ByteArray {
|
||||
val baseTexture = resourceManager.loadSprite(atlas.file(sprite))
|
||||
|
||||
val frameWidth = baseTexture.width
|
||||
val frameHeight = baseTexture.width * aspectHeight / aspectWidth
|
||||
val frames = baseTexture.height / frameHeight
|
||||
val size = max(frameWidth, frameHeight)
|
||||
|
||||
val result = BufferedImage(size, size * frames, BufferedImage.TYPE_4BYTE_ABGR)
|
||||
val graphics = result.createGraphics()
|
||||
val resultTexture = BufferedImage(size, size * frames, BufferedImage.TYPE_4BYTE_ABGR)
|
||||
val graphics = resultTexture.createGraphics()
|
||||
|
||||
// iterate all frames
|
||||
for (frame in 0 until frames) {
|
||||
@@ -34,6 +34,6 @@ data class CenteredSprite(val sprite: Identifier, val atlas: Atlas = Atlas.BLOCK
|
||||
graphics.drawImage(resultFrame, 0, size * frame, null)
|
||||
}
|
||||
|
||||
return result.bytes
|
||||
return resultTexture.bytes
|
||||
}
|
||||
}
|
||||
@@ -1,92 +0,0 @@
|
||||
package mods.betterfoliage.resource.generated
|
||||
|
||||
import mods.betterfoliage.util.Atlas
|
||||
import mods.betterfoliage.util.HasLogger
|
||||
import net.fabricmc.fabric.api.resource.IdentifiableResourceReloadListener
|
||||
import net.minecraft.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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,13 @@
|
||||
package mods.betterfoliage.resource.generated
|
||||
|
||||
import mods.betterfoliage.util.*
|
||||
import net.minecraft.resource.ResourceManager
|
||||
import net.minecraft.util.Identifier
|
||||
import mods.betterfoliage.util.Atlas
|
||||
import mods.betterfoliage.util.blendRGB
|
||||
import mods.betterfoliage.util.bytes
|
||||
import mods.betterfoliage.util.get
|
||||
import mods.betterfoliage.util.loadSprite
|
||||
import mods.betterfoliage.util.set
|
||||
import net.minecraft.resources.IResourceManager
|
||||
import net.minecraft.util.ResourceLocation
|
||||
import java.awt.image.BufferedImage
|
||||
|
||||
/**
|
||||
@@ -11,13 +16,13 @@ import java.awt.image.BufferedImage
|
||||
*
|
||||
* @param[domain] Resource domain of generator
|
||||
*/
|
||||
data class GeneratedGrassSprite(val sprite: Identifier, val isSnowed: Boolean, val atlas: Atlas = Atlas.BLOCKS) {
|
||||
constructor(sprite: String, isSnowed: Boolean) : this(Identifier(sprite), isSnowed)
|
||||
data class GeneratedGrass(val baseSprite: ResourceLocation, val isSnowed: Boolean, val atlas: Atlas = Atlas.BLOCKS) {
|
||||
constructor(sprite: String, isSnowed: Boolean) : this(ResourceLocation(sprite), isSnowed)
|
||||
|
||||
fun register(pack: GeneratedBlockTexturePack) = pack.register(this, this::draw)
|
||||
fun register(pack: GeneratedTexturePack) = pack.register(atlas, this, this::draw)
|
||||
|
||||
fun draw(resourceManager: ResourceManager): ByteArray {
|
||||
val baseTexture = resourceManager.loadSprite(atlas.wrap(sprite))
|
||||
fun draw(resourceManager: IResourceManager): ByteArray {
|
||||
val baseTexture = resourceManager.loadSprite(atlas.file(baseSprite))
|
||||
|
||||
val result = BufferedImage(baseTexture.width, baseTexture.height, BufferedImage.TYPE_4BYTE_ABGR)
|
||||
val graphics = result.createGraphics()
|
||||
@@ -41,7 +46,7 @@ data class GeneratedGrassSprite(val sprite: Identifier, val isSnowed: Boolean, v
|
||||
|
||||
// blend with white if snowed
|
||||
if (isSnowed) {
|
||||
for (x in 0..result.width - 1) for (y in 0..result.height - 1) {
|
||||
for (x in 0 until result.width) for (y in 0 until result.height) {
|
||||
result[x, y] = blendRGB(result[x, y], 16777215, 2, 3)
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,16 @@
|
||||
package mods.betterfoliage.resource.generated
|
||||
|
||||
import mods.betterfoliage.BetterFoliage
|
||||
import mods.betterfoliage.util.*
|
||||
import net.minecraft.resource.Resource
|
||||
import net.minecraft.resource.ResourceManager
|
||||
import net.minecraft.util.Identifier
|
||||
import mods.betterfoliage.BetterFoliageMod
|
||||
import mods.betterfoliage.util.Atlas
|
||||
import mods.betterfoliage.util.bytes
|
||||
import mods.betterfoliage.util.get
|
||||
import mods.betterfoliage.util.loadImage
|
||||
import mods.betterfoliage.util.loadSprite
|
||||
import mods.betterfoliage.util.resourceManager
|
||||
import mods.betterfoliage.util.set
|
||||
import net.minecraft.resources.IResource
|
||||
import net.minecraft.resources.IResourceManager
|
||||
import net.minecraft.util.ResourceLocation
|
||||
import java.awt.image.BufferedImage
|
||||
|
||||
/**
|
||||
@@ -15,12 +21,12 @@ import java.awt.image.BufferedImage
|
||||
*
|
||||
* @param[domain] Resource domain of generator
|
||||
*/
|
||||
data class GeneratedLeafSprite(val sprite: Identifier, val leafType: String, val atlas: Atlas = Atlas.BLOCKS) {
|
||||
data class GeneratedLeafSprite(val baseSprite: ResourceLocation, val leafType: String, val atlas: Atlas = Atlas.BLOCKS) {
|
||||
|
||||
fun register(pack: GeneratedBlockTexturePack) = pack.register(this, this::draw)
|
||||
fun register(pack: GeneratedTexturePack) = pack.register(atlas, this, this::draw)
|
||||
|
||||
fun draw(resourceManager: ResourceManager): ByteArray {
|
||||
val baseTexture = resourceManager.loadSprite(atlas.wrap(sprite))
|
||||
fun draw(resourceManager: IResourceManager): ByteArray {
|
||||
val baseTexture = resourceManager.loadSprite(atlas.file(baseSprite))
|
||||
|
||||
val size = baseTexture.width
|
||||
val frames = baseTexture.height / size
|
||||
@@ -28,8 +34,8 @@ data class GeneratedLeafSprite(val sprite: Identifier, val leafType: String, val
|
||||
val maskTexture = (getLeafMask(leafType, size * 2) ?: getLeafMask("default", size * 2))?.loadImage()
|
||||
fun scale(i: Int) = i * maskTexture!!.width / (size * 2)
|
||||
|
||||
val result = BufferedImage(size * 2, size * 2 * frames, BufferedImage.TYPE_4BYTE_ABGR)
|
||||
val graphics = result.createGraphics()
|
||||
val leafTexture = BufferedImage(size * 2, size * 2 * frames, BufferedImage.TYPE_4BYTE_ABGR)
|
||||
val graphics = leafTexture.createGraphics()
|
||||
|
||||
// iterate all frames
|
||||
for (frame in 0 until frames) {
|
||||
@@ -57,7 +63,7 @@ data class GeneratedLeafSprite(val sprite: Identifier, val leafType: String, val
|
||||
graphics.drawImage(leafFrame, 0, size * frame * 2, null)
|
||||
}
|
||||
|
||||
return result.bytes
|
||||
return leafTexture.bytes
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -67,7 +73,7 @@ data class GeneratedLeafSprite(val sprite: Identifier, val leafType: String, val
|
||||
* @param[maxSize] Preferred mask size.
|
||||
*/
|
||||
fun getLeafMask(type: String, maxSize: Int) = getMultisizeTexture(maxSize) { size ->
|
||||
Atlas.BLOCKS.wrap(Identifier(BetterFoliage.MOD_ID, "blocks/leafmask_${size}_${type}"))
|
||||
Atlas.BLOCKS.file(ResourceLocation(BetterFoliageMod.MOD_ID, "blocks/leafmask_${size}_${type}"))
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -77,10 +83,10 @@ data class GeneratedLeafSprite(val sprite: Identifier, val leafType: String, val
|
||||
* @param[maskPath] Location of the texture of the given size
|
||||
*
|
||||
*/
|
||||
fun getMultisizeTexture(maxSize: Int, maskPath: (Int)->Identifier): Resource? {
|
||||
fun getMultisizeTexture(maxSize: Int, maskPath: (Int)->ResourceLocation): IResource? {
|
||||
var size = maxSize
|
||||
val sizes = mutableListOf<Int>()
|
||||
while(size > 2) { sizes.add(size); size /= 2 }
|
||||
return sizes.findFirst { resourceManager[maskPath(it)] }
|
||||
return sizes.map { resourceManager[maskPath(it)] }.filterNotNull().firstOrNull()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
package mods.betterfoliage.resource.generated
|
||||
|
||||
import mods.betterfoliage.util.Atlas
|
||||
import mods.betterfoliage.util.HasLogger
|
||||
import net.minecraft.client.Minecraft
|
||||
import net.minecraft.resources.*
|
||||
import net.minecraft.resources.ResourcePackType.CLIENT_RESOURCES
|
||||
import net.minecraft.resources.data.IMetadataSectionSerializer
|
||||
import net.minecraft.util.ResourceLocation
|
||||
import net.minecraft.util.text.StringTextComponent
|
||||
import org.apache.logging.log4j.Level.INFO
|
||||
import java.util.*
|
||||
import java.util.function.Consumer
|
||||
import java.util.function.Predicate
|
||||
import java.util.function.Supplier
|
||||
|
||||
/**
|
||||
* [IResourcePack] containing generated resources
|
||||
*
|
||||
* @param[name] Name of the resource pack
|
||||
* @param[generators] List of resource generators
|
||||
*/
|
||||
class GeneratedTexturePack(
|
||||
val nameSpace: String, val packName: String
|
||||
) : HasLogger(), IResourcePack {
|
||||
override fun getName() = packName
|
||||
override fun getNamespaces(type: ResourcePackType) = setOf(nameSpace)
|
||||
override fun <T : Any?> getMetadataSection(deserializer: IMetadataSectionSerializer<T>) = null
|
||||
override fun getRootResource(id: String) = null
|
||||
override fun getResources(type: ResourcePackType, namespace:String, path: String, maxDepth: Int, filter: Predicate<String>) = emptyList<ResourceLocation>()
|
||||
|
||||
override fun close() {}
|
||||
|
||||
protected var manager: IResourceManager = Minecraft.getInstance().resourceManager
|
||||
val identifiers = Collections.synchronizedMap(mutableMapOf<Any, ResourceLocation>())
|
||||
val resources = Collections.synchronizedMap(mutableMapOf<ResourceLocation, ByteArray>())
|
||||
|
||||
fun register(atlas: Atlas, key: Any, func: (IResourceManager)->ByteArray): ResourceLocation {
|
||||
identifiers[key]?.let { return it }
|
||||
|
||||
val id = ResourceLocation(nameSpace, UUID.randomUUID().toString())
|
||||
val fileName = atlas.file(id)
|
||||
val resource = func(manager)
|
||||
|
||||
identifiers[key] = id
|
||||
resources[fileName] = resource
|
||||
detailLogger.log(INFO, "generated resource $key -> $fileName")
|
||||
return id
|
||||
}
|
||||
|
||||
override fun getResource(type: ResourcePackType, id: ResourceLocation) =
|
||||
if (type != CLIENT_RESOURCES) null else resources[id]?.inputStream()
|
||||
|
||||
override fun hasResource(type: ResourcePackType, id: ResourceLocation) =
|
||||
type == CLIENT_RESOURCES && resources.containsKey(id)
|
||||
|
||||
val finder = object : IPackFinder {
|
||||
val packInfo = ResourcePackInfo(
|
||||
packName, true, Supplier { this@GeneratedTexturePack },
|
||||
StringTextComponent(packName),
|
||||
StringTextComponent("Generated block textures resource pack"),
|
||||
PackCompatibility.COMPATIBLE, ResourcePackInfo.Priority.TOP, true, null, true
|
||||
)
|
||||
|
||||
override fun loadPacks(p0: Consumer<ResourcePackInfo>, p1: ResourcePackInfo.IFactory) {
|
||||
p0.accept(packInfo)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,114 +0,0 @@
|
||||
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 }
|
||||
@@ -1,230 +0,0 @@
|
||||
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)) }
|
||||
@@ -1,70 +0,0 @@
|
||||
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!!
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,74 +0,0 @@
|
||||
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)
|
||||
@@ -1,86 +0,0 @@
|
||||
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) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,6 +17,10 @@ interface Invalidator {
|
||||
}
|
||||
}
|
||||
|
||||
class SimpleInvalidator : Invalidator {
|
||||
override val callbacks = mutableListOf<WeakReference<() -> Unit>>()
|
||||
}
|
||||
|
||||
class LazyInvalidatable<V>(invalidator: Invalidator, val valueFactory: ()->V): ReadOnlyProperty<Any, V> {
|
||||
init { invalidator.onInvalidate { value = null } }
|
||||
|
||||
@@ -31,7 +35,7 @@ class LazyInvalidatable<V>(invalidator: Invalidator, val valueFactory: ()->V): R
|
||||
}
|
||||
}
|
||||
|
||||
class LazyMap<K, V>(val invalidator: Invalidator, val valueFactory: (K)->V) {
|
||||
open class LazyMapInvalidatable<K, V>(val invalidator: Invalidator, val valueFactory: (K)->V) {
|
||||
init { invalidator.onInvalidate { values.clear() } }
|
||||
|
||||
val values = mutableMapOf<K, V>()
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package mods.betterfoliage.util
|
||||
|
||||
import com.google.common.collect.ImmutableList
|
||||
import java.util.*
|
||||
|
||||
/**
|
||||
@@ -27,6 +28,8 @@ inline fun <T, C: Comparable<C>> Triple<T, T, T>.maxValueBy(func: (T)->C): C {
|
||||
return result
|
||||
}
|
||||
|
||||
inline fun <reified T, reified R> Array<T>.mapArray(func: (T)->R) = Array<R>(size) { idx -> func(get(idx)) }
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
inline fun <K, V> Map<K, V?>.filterValuesNotNull() = filterValues { it != null } as Map<K, V>
|
||||
|
||||
@@ -61,3 +64,8 @@ inline fun <T> MutableList<T>.exchange(idx1: Int, idx2: Int) {
|
||||
|
||||
/** Return a random element from the array using the provided random generator */
|
||||
inline operator fun <T> Array<T>.get(random: Random) = get(random.nextInt(Int.MAX_VALUE) % size)
|
||||
|
||||
fun <T> Iterable<T>.toImmutableList() = ImmutableList.builder<T>().let { builder ->
|
||||
forEach { builder.add(it) }
|
||||
builder.build()
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
package mods.betterfoliage.util
|
||||
|
||||
import net.minecraft.client.MinecraftClient
|
||||
import net.minecraft.client.Minecraft
|
||||
import java.util.concurrent.CompletableFuture
|
||||
import java.util.concurrent.CompletionStage
|
||||
import java.util.function.Consumer
|
||||
@@ -9,7 +9,7 @@ import java.util.function.Function
|
||||
fun completedVoid() = CompletableFuture.completedFuture<Void>(null)!!
|
||||
|
||||
fun <T, U> CompletionStage<T>.map(func: (T)->U) = thenApply(Function(func)).toCompletableFuture()!!
|
||||
fun <T, U> CompletionStage<T>.mapAsync(func: (T)->U) = thenApplyAsync(Function(func), MinecraftClient.getInstance()).toCompletableFuture()!!
|
||||
fun <T, U> CompletionStage<T>.mapAsync(func: (T)->U) = thenApplyAsync(Function(func), Minecraft.getInstance()).toCompletableFuture()!!
|
||||
|
||||
fun <T> CompletionStage<T>.sink(func: (T)->Unit) = thenAccept(Consumer(func)).toCompletableFuture()!!
|
||||
fun <T> CompletionStage<T>.sinkAsync(func: (T)->Unit) = thenAcceptAsync(Consumer(func), MinecraftClient.getInstance()).toCompletableFuture()!!
|
||||
fun <T> CompletionStage<T>.sinkAsync(func: (T)->Unit) = thenAcceptAsync(Consumer(func), Minecraft.getInstance()).toCompletableFuture()!!
|
||||
@@ -1,28 +1,44 @@
|
||||
package mods.betterfoliage.util
|
||||
|
||||
import net.minecraft.client.util.math.Vector3f
|
||||
import net.minecraft.util.Direction
|
||||
import net.minecraft.util.Direction.*
|
||||
import net.minecraft.util.Direction.Axis.*
|
||||
import net.minecraft.util.Direction.AxisDirection.NEGATIVE
|
||||
import net.minecraft.util.Direction.AxisDirection.POSITIVE
|
||||
import net.minecraft.util.math.BlockPos
|
||||
import net.minecraft.util.math.Direction
|
||||
import net.minecraft.util.math.Direction.Axis.*
|
||||
import net.minecraft.util.math.Direction.AxisDirection
|
||||
import net.minecraft.util.math.Direction.AxisDirection.*
|
||||
import net.minecraft.util.math.Direction.*
|
||||
import net.minecraft.util.math.vector.Quaternion
|
||||
|
||||
val EPSILON_ZERO = 0.05
|
||||
val EPSILON_ONE = 0.95
|
||||
|
||||
// ================================
|
||||
// Axes and directions
|
||||
// ================================
|
||||
val axes = listOf(X, Y, Z)
|
||||
val axisDirs = listOf(POSITIVE, NEGATIVE)
|
||||
val Direction.dir: AxisDirection get() = axisDirection
|
||||
val AxisDirection.sign: String get() = when(this) { POSITIVE -> "+"; NEGATIVE -> "-" }
|
||||
val allDirections = Direction.values()
|
||||
val horizontalDirections = listOf(NORTH, SOUTH, EAST, WEST)
|
||||
val allDirOffsets = allDirections.map { Int3(it) }
|
||||
val Pair<Direction.Axis, AxisDirection>.face: Direction get() = when(this) {
|
||||
X to POSITIVE -> EAST; X to NEGATIVE -> WEST
|
||||
Y to POSITIVE -> UP; Y to NEGATIVE -> DOWN
|
||||
Z to POSITIVE -> SOUTH; else -> NORTH
|
||||
val Pair<Axis, AxisDirection>.face: Direction get() = when(this) {
|
||||
X to POSITIVE -> EAST; X to NEGATIVE -> WEST;
|
||||
Y to POSITIVE -> UP; Y to NEGATIVE -> DOWN;
|
||||
Z to POSITIVE -> SOUTH; else -> NORTH;
|
||||
}
|
||||
val Direction.perpendiculars: List<Direction> get() =
|
||||
axes.filter { it != this.axis }.cross(axisDirs).map { it.face }
|
||||
val directionsAndNull = arrayOf(DOWN, UP, NORTH, SOUTH, WEST, EAST, null)
|
||||
|
||||
val Direction.perpendiculars: Array<Direction> get() =
|
||||
axes.filter { it != this.axis }.flatMap { listOf((it to POSITIVE).face, (it to NEGATIVE).face) }.toTypedArray()
|
||||
|
||||
val perpendiculars: Array<Array<Direction>> = Direction.values().map { dir ->
|
||||
axes.filter { it != dir.axis }
|
||||
.flatMap { listOf(
|
||||
(it to POSITIVE).face,
|
||||
(it to NEGATIVE).face
|
||||
) }.toTypedArray()
|
||||
}.toTypedArray()
|
||||
|
||||
val Direction.offset: Int3 get() = allDirOffsets[ordinal]
|
||||
|
||||
/** Old ForgeDirection rotation matrix yanked from 1.7.10 */
|
||||
@@ -39,19 +55,20 @@ val ROTATION_MATRIX: Array<IntArray> get() = arrayOf(
|
||||
// Vectors
|
||||
// ================================
|
||||
operator fun Direction.times(scale: Double) =
|
||||
Double3(vector.x.toDouble() * scale, vector.y.toDouble() * scale, vector.z.toDouble() * scale)
|
||||
val Direction.vec: Double3 get() = Double3(vector.x.toDouble(), vector.y.toDouble(), vector.z.toDouble())
|
||||
Double3(normal.x.toDouble() * scale, normal.y.toDouble() * scale, normal.z.toDouble() * scale)
|
||||
val Direction.vec: Double3 get() = Double3(normal.x.toDouble(), normal.y.toDouble(), normal.z.toDouble())
|
||||
|
||||
operator fun BlockPos.plus(other: Int3) = BlockPos(x + other.x, y + other.y, z + other.z)
|
||||
|
||||
/** 3D vector of [Double]s. Offers both mutable operations, and immutable operations in operator notation. */
|
||||
data class Double3(var x: Double, var y: Double, var z: Double) {
|
||||
constructor(x: Float, y: Float, z: Float) : this(x.toDouble(), y.toDouble(), z.toDouble())
|
||||
constructor(dir: Direction) : this(dir.vector.x.toDouble(), dir.vector.y.toDouble(), dir.vector.z.toDouble())
|
||||
constructor(dir: Direction) : this(dir.normal.x.toDouble(), dir.normal.y.toDouble(), dir.normal.z.toDouble())
|
||||
companion object {
|
||||
val zero: Double3 get() = Double3(0.0, 0.0, 0.0)
|
||||
fun weight(v1: Double3, weight1: Double, v2: Double3, weight2: Double) =
|
||||
Double3(v1.x * weight1 + v2.x * weight2, v1.y * weight1 + v2.y * weight2, v1.z * weight1 + v2.z * weight2)
|
||||
fun lerp(delta: Double, first: Double3, second: Double3) = first + (second - first) * delta
|
||||
}
|
||||
|
||||
// immutable operations
|
||||
@@ -68,6 +85,13 @@ data class Double3(var x: Double, var y: Double, var z: Double) {
|
||||
rot.rotatedComponent(SOUTH, x, y, z)
|
||||
)
|
||||
|
||||
/** Rotate vector by the given [Quaternion] */
|
||||
fun rotate(quat: Quaternion) =
|
||||
quat.copy()
|
||||
.apply { mul(Quaternion(x.toFloat(), y.toFloat(), z.toFloat(), 0.0F)) }
|
||||
.apply { mul(quat.copy().apply(Quaternion::conj)) }
|
||||
.let { Double3(it.i().toDouble(), it.j().toDouble(), it.k().toDouble()) }
|
||||
|
||||
// mutable operations
|
||||
fun setTo(other: Double3): Double3 { x = other.x; y = other.y; z = other.z; return this }
|
||||
fun setTo(x: Double, y: Double, z: Double): Double3 { this.x = x; this.y = y; this.z = z; return this }
|
||||
@@ -92,16 +116,15 @@ data class Double3(var x: Double, var y: Double, var z: Double) {
|
||||
val length: Double get() = Math.sqrt(x * x + y * y + z * z)
|
||||
val normalize: Double3 get() = (1.0 / length).let { Double3(x * it, y * it, z * it) }
|
||||
val nearestCardinal: Direction get() = nearestAngle(this, allDirections.asIterable()) { it.vec }.first
|
||||
val asVec3f: Vector3f get() = Vector3f(x.toFloat(), y.toFloat(), z.toFloat())
|
||||
}
|
||||
|
||||
/** 3D vector of [Int]s. Offers both mutable operations, and immutable operations in operator notation. */
|
||||
data class Int3(var x: Int, var y: Int, var z: Int) {
|
||||
constructor(dir: Direction) : this(dir.vector.x, dir.vector.y, dir.vector.z)
|
||||
constructor(dir: Direction) : this(dir.normal.x, dir.normal.y, dir.normal.z)
|
||||
constructor(offset: Pair<Int, Direction>) : this(
|
||||
offset.first * offset.second.vector.x,
|
||||
offset.first * offset.second.vector.y,
|
||||
offset.first * offset.second.vector.z
|
||||
offset.first * offset.second.normal.x,
|
||||
offset.first * offset.second.normal.y,
|
||||
offset.first * offset.second.normal.z
|
||||
)
|
||||
companion object {
|
||||
val zero = Int3(0, 0, 0)
|
||||
@@ -110,9 +133,9 @@ data class Int3(var x: Int, var y: Int, var z: Int) {
|
||||
// immutable operations
|
||||
operator fun plus(other: Int3) = Int3(x + other.x, y + other.y, z + other.z)
|
||||
operator fun plus(other: Pair<Int, Direction>) = Int3(
|
||||
x + other.first * other.second.vector.x,
|
||||
y + other.first * other.second.vector.y,
|
||||
z + other.first * other.second.vector.z
|
||||
x + other.first * other.second.normal.x,
|
||||
y + other.first * other.second.normal.y,
|
||||
z + other.first * other.second.normal.z
|
||||
)
|
||||
operator fun unaryMinus() = Int3(-x, -y, -z)
|
||||
operator fun minus(other: Int3) = Int3(x - other.x, y - other.y, z - other.z)
|
||||
@@ -161,8 +184,7 @@ class Rotation(val forward: Array<Direction>, val reverse: Array<Direction>) {
|
||||
Array(6) { idx -> other.reverse[reverse[idx].ordinal] }
|
||||
)
|
||||
operator fun unaryMinus() = Rotation(reverse, forward)
|
||||
operator fun times(num: Int) = when(num % 4) { 1 -> this; 2 -> this + this; 3 -> -this; else -> identity
|
||||
}
|
||||
operator fun times(num: Int) = when(num % 4) { 1 -> this; 2 -> this + this; 3 -> -this; else -> identity }
|
||||
|
||||
inline fun rotatedComponent(dir: Direction, x: Int, y: Int, z: Int) =
|
||||
when(reverse[dir.ordinal]) { EAST -> x; WEST -> -x; UP -> y; DOWN -> -y; SOUTH -> z; NORTH -> -z; else -> 0 }
|
||||
@@ -182,6 +204,7 @@ class Rotation(val forward: Array<Direction>, val reverse: Array<Direction>) {
|
||||
rot90[NORTH.ordinal]
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// ================================
|
||||
@@ -191,14 +214,8 @@ class Rotation(val forward: Array<Direction>, val reverse: Array<Direction>) {
|
||||
inline operator fun <reified T> Array<T>.get(face: Direction): T = get(face.ordinal)
|
||||
|
||||
data class BoxFace(val top: Direction, val left: Direction) {
|
||||
val bottom get() = top.opposite
|
||||
val right get() = left.opposite
|
||||
val allCorners = listOf(top to left, top to left.opposite, top.opposite to left, top.opposite to left.opposite)
|
||||
|
||||
val tl get() = top to left
|
||||
val tr get() = top to right
|
||||
val bl get() = bottom to left
|
||||
val br get() = bottom to right
|
||||
val allCorners = listOf(top to left, top to left.opposite, top.opposite to left, top.opposite to left.opposite)
|
||||
}
|
||||
val boxFaces = allDirections.map { when(it) {
|
||||
DOWN -> BoxFace(SOUTH, WEST)
|
||||
|
||||
22
src/main/kotlin/mods/betterfoliage/util/Gui.kt
Normal file
22
src/main/kotlin/mods/betterfoliage/util/Gui.kt
Normal file
@@ -0,0 +1,22 @@
|
||||
@file:JvmName("Utils")
|
||||
package mods.betterfoliage.util
|
||||
|
||||
import net.minecraft.util.text.StringTextComponent
|
||||
import net.minecraft.util.text.Style
|
||||
import net.minecraft.util.text.TextFormatting
|
||||
import net.minecraft.util.text.TextFormatting.AQUA
|
||||
import net.minecraft.util.text.TextFormatting.GRAY
|
||||
|
||||
//fun stripTooltipDefaultText(tooltip: MutableList<String>) {
|
||||
// var defaultRows = false
|
||||
// val iter = tooltip.iterator()
|
||||
// while (iter.hasNext()) {
|
||||
// if (iter.next().startsWith(AQUA.toString())) defaultRows = true
|
||||
// if (defaultRows) iter.remove()
|
||||
// }
|
||||
//}
|
||||
//
|
||||
//fun textComponent(msg: String, color: TextFormatting = GRAY): StringTextComponent {
|
||||
// val style = Style().apply { this.color = color }
|
||||
// return StringTextComponent(msg).apply { this.style = style }
|
||||
//}
|
||||
@@ -1,10 +1,8 @@
|
||||
@file:Suppress("NOTHING_TO_INLINE")
|
||||
package mods.betterfoliage.util
|
||||
|
||||
import net.minecraft.text.LiteralText
|
||||
import net.minecraft.text.Style
|
||||
import net.minecraft.util.Formatting
|
||||
import net.minecraft.util.Identifier
|
||||
import mods.betterfoliage.BetterFoliageMod
|
||||
import net.minecraft.util.ResourceLocation
|
||||
import net.minecraft.util.math.BlockPos
|
||||
import net.minecraft.world.World
|
||||
import org.apache.logging.log4j.Level
|
||||
@@ -19,8 +17,8 @@ inline fun String.stripStart(str: String, ignoreCase: Boolean = true) = if (star
|
||||
inline fun String.stripEnd(str: String, ignoreCase: Boolean = true) = if (endsWith(str, ignoreCase)) substring(0, length - str.length) else this
|
||||
|
||||
/** Strip the given prefix off the start of the resource path, if present */
|
||||
inline fun Identifier.stripStart(str: String) = Identifier(namespace, path.stripStart(str))
|
||||
inline fun Identifier.stripEnd(str: String) = Identifier(namespace, path.stripEnd(str))
|
||||
inline fun ResourceLocation.stripStart(str: String) = ResourceLocation(namespace, path.stripStart(str))
|
||||
inline fun ResourceLocation.stripEnd(str: String) = ResourceLocation(namespace, path.stripEnd(str))
|
||||
|
||||
/**
|
||||
* Property-level delegate backed by a [ThreadLocal].
|
||||
@@ -52,6 +50,12 @@ fun nextPowerOf2(x: Int): Int {
|
||||
return 1 shl (if (x == 0) 0 else 32 - Integer.numberOfLeadingZeros(x - 1))
|
||||
}
|
||||
|
||||
@Suppress("LeakingThis")
|
||||
abstract class HasLogger {
|
||||
val logger = BetterFoliageMod.logger(this)
|
||||
val detailLogger = BetterFoliageMod.detailLogger(this)
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the Chunk containing the given [BlockPos] is loaded.
|
||||
* Works for both [World] and [ChunkCache] (vanilla and OptiFine) instances.
|
||||
@@ -63,16 +67,7 @@ fun nextPowerOf2(x: Int): Int {
|
||||
// else -> false
|
||||
//}
|
||||
|
||||
interface HasLogger {
|
||||
val logger: Logger
|
||||
val logName: String get() = this::class.simpleName!!
|
||||
fun log(msg: String) = log(Level.INFO, msg)
|
||||
fun log(level: Level, msg: String) = logger.log(level, "[$logName] $msg")
|
||||
fun log(msg: String, e: Throwable) = log(Level.WARN, msg, e)
|
||||
fun log(level: Level, msg: String, e: Throwable) = logger.log(level, "[$logName] $msg", e)
|
||||
}
|
||||
|
||||
fun textComponent(msg: String, color: Formatting = Formatting.GRAY): LiteralText {
|
||||
val style = Style().apply { this.color = color }
|
||||
return LiteralText(msg).apply { this.style = style }
|
||||
}
|
||||
//fun textComponent(msg: String, color: Formatting = Formatting.GRAY): LiteralText {
|
||||
// val style = Style().apply { this.color = color }
|
||||
// return LiteralText(msg).apply { this.style = style }
|
||||
//}
|
||||
@@ -1,15 +1,18 @@
|
||||
package mods.betterfoliage.util
|
||||
|
||||
import net.minecraft.util.math.BlockPos
|
||||
import kotlin.random.Random
|
||||
import java.util.Random
|
||||
|
||||
val random = Random(System.nanoTime())
|
||||
|
||||
fun randomB() = random.nextBoolean()
|
||||
fun randomI(min: Int = 0, max: Int = Int.MAX_VALUE) = random.nextInt(min, max)
|
||||
fun randomL(min: Long = 0, max: Long = Long.MAX_VALUE) = random.nextLong(min, max)
|
||||
fun randomF(min: Double = 0.0, max: Double = 1.0) = random.nextDouble(min, max).toFloat()
|
||||
fun randomD(min: Double = 0.0, max: Double = 1.0) = if (min == max) min else random.nextDouble(min, max)
|
||||
fun randomI(min: Int = 0, max: Int = Int.MAX_VALUE) = min + random.nextInt(max - min)
|
||||
fun randomF(min: Float = 0.0f, max: Float = 1.0f) = random.randomF(min, max)
|
||||
fun randomD(min: Double = 0.0, max: Double = 1.0) = random.randomD(min, max)
|
||||
|
||||
fun Random.randomF(min: Float = 0.0f, max: Float = 1.0f) = nextFloat() * (max - min) + min
|
||||
fun Random.randomF(min: Double = 0.0, max: Double = 1.0) = randomF(min.toFloat(), max.toFloat())
|
||||
fun Random.randomD(min: Double = 0.0, max: Double = 1.0) = nextDouble() * (max - min) + min
|
||||
|
||||
fun semiRandom(x: Int, y: Int, z: Int, seed: Int): Int {
|
||||
var value = (x * x + y * y + z * z + x * y + y * z + z * x + (seed * seed))
|
||||
@@ -17,4 +20,4 @@ fun semiRandom(x: Int, y: Int, z: Int, seed: Int): Int {
|
||||
return value shr 4
|
||||
}
|
||||
|
||||
//fun BlockPos.semiRandom(seed: Int) = semiRandom(x, y, z, seed)
|
||||
fun BlockPos.semiRandom(seed: Int) = semiRandom(x, y, z, seed)
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user