Compare commits

...

16 Commits

Author SHA1 Message Date
octarine-noise
7065c9dd8c bump version to 2.6.5 2021-06-02 16:39:45 +02:00
octarine-noise
368b50a578 cherry-pick chinese translation
+lang file cleanup
2021-05-29 23:52:25 +02:00
octarine-noise
cd2d46f422 fix crash in resource pack selection GUI 2021-05-29 23:43:55 +02:00
octarine-noise
03367443b4 [WIP] fix log block detection 2021-05-16 20:08:44 +02:00
octarine-noise
81d3a2eba4 [WIP] beginning port to 1.16.5
+most of the stuff is working
2021-05-14 22:40:04 +02:00
octarine-noise
79ef6cfaa9 bump version 2021-05-13 21:13:45 +02:00
octarine-noise
40fd46b278 [WIP] adopt model replacement from Forge vesion
+ bunch of renames to bring the 2 version closer
+ at-least-not-crashing levels of Optifine support
2021-05-13 21:13:10 +02:00
octarine-noise
9dacdde761 [WIP] fix crash with falling blocks 2021-05-12 12:33:31 +02:00
octarine-noise
6a31adcdd8 [WIP] move AbstractParticles 2021-05-12 12:33:05 +02:00
octarine-noise
b46fdeaeef [WIP] start port to 1.15.2 2021-05-07 19:11:11 +02:00
octarine-noise
f1fa629c5c bring back the fade-out feature for falling leaf particles 2021-04-28 16:56:24 +02:00
octarine-noise
ad914ac03a fix round log x-ray bug 2021-04-28 15:53:02 +02:00
octarine-noise
d8ce8ecb06 bump version 2021-04-28 08:58:59 +02:00
octarine-noise
8d9214c190 add support for shader wind effects 2021-04-27 18:13:57 +02:00
octarine-noise
594db19bfb update block configs 2021-04-27 15:15:26 +02:00
octarine-noise
a89edd53a4 better block discovery logging 2021-04-27 15:12:04 +02:00
85 changed files with 1943 additions and 1605 deletions

View File

@@ -2,8 +2,8 @@ import net.fabricmc.loom.task.RemapJarTask
import org.ajoberstar.grgit.Grgit import org.ajoberstar.grgit.Grgit
plugins { plugins {
kotlin("jvm").version("1.3.60") id("fabric-loom").version("0.6-SNAPSHOT")
id("fabric-loom").version("0.2.6-SNAPSHOT") kotlin("jvm").version("1.4.31")
id("org.ajoberstar.grgit").version("3.1.1") id("org.ajoberstar.grgit").version("3.1.1")
} }
apply(plugin = "org.ajoberstar.grgit") apply(plugin = "org.ajoberstar.grgit")
@@ -13,9 +13,10 @@ val semVer = "${project.version}+$gitHash"
val jarName = "BetterFoliage-$semVer-Fabric-${properties["mcVersion"]}" val jarName = "BetterFoliage-$semVer-Fabric-${properties["mcVersion"]}"
repositories { repositories {
maven("http://maven.fabricmc.net/") maven("https://maven.fabricmc.net/")
maven("https://minecraft.curseforge.com/api/maven") maven("https://minecraft.curseforge.com/api/maven")
maven("http://maven.modmuss50.me/") maven("https://maven.modmuss50.me/")
maven("https://maven.shedaniel.me/")
maven("https://grondag-repo.appspot.com").credentials { username = "guest"; password = "" } maven("https://grondag-repo.appspot.com").credentials { username = "guest"; password = "" }
maven("https://jitpack.io") maven("https://jitpack.io")
} }
@@ -32,7 +33,7 @@ dependencies {
// configuration handling // configuration handling
"modImplementation"("io.github.prospector:modmenu:${properties["modMenuVersion"]}") "modImplementation"("io.github.prospector:modmenu:${properties["modMenuVersion"]}")
listOf("modImplementation", "include").forEach { configuration -> listOf("modImplementation", "include").forEach { configuration ->
configuration("me.shedaniel.cloth:config-2:${properties["clothConfigVersion"]}") configuration("me.shedaniel.cloth:cloth-config-fabric:${properties["clothConfigVersion"]}")
configuration("me.zeroeightsix:fiber:${properties["fiberVersion"]}") configuration("me.zeroeightsix:fiber:${properties["fiberVersion"]}")
} }
@@ -40,7 +41,7 @@ dependencies {
// "modImplementation"("grondag:canvas:0.7.+") // "modImplementation"("grondag:canvas:0.7.+")
// Optifabric // Optifabric
"modImplementation"("com.github.modmuss50:OptiFabric:df03dc2c22") // "modImplementation"("com.github.modmuss50:OptiFabric:1.0.0")
"implementation"("org.zeroturnaround:zt-zip:1.13") "implementation"("org.zeroturnaround:zt-zip:1.13")
} }

View File

@@ -5,17 +5,16 @@ group = com.github.octarine-noise
name = betterfoliage name = betterfoliage
jarName = BetterFoliage-Forge jarName = BetterFoliage-Forge
version = 2.5.0 version = 2.6.5
mcVersion = 1.14.4 mcVersion = 1.16.5
yarnMappings=1.14.4+build.15 yarnMappings=1.16.5+build.6
loaderVersion=0.7.3+build.176 loaderVersion=0.11.3
fabricVersion=0.4.2+build.246-1.14 fabricVersion=0.32.5+1.16
loomVersion=0.2.6-SNAPSHOT
kotlinVersion=1.3.60 kotlinVersion=1.3.60
fabricKotlinVersion=1.3.60+build.1 fabricKotlinVersion=1.5.0+kotlin.1.4.31
clothConfigVersion=1.8 clothConfigVersion=4.11.24
modMenuVersion=1.7.6+build.115 modMenuVersion=1.16.9
fiberVersion=0.8.0-2 fiberVersion=0.8.0-2

View File

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

View File

@@ -19,8 +19,8 @@ import java.util.Random;
@Mixin(Block.class) @Mixin(Block.class)
public class MixinBlock { 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 shouldSideBeRendered = "Lnet/minecraft/block/Block;shouldDrawSide(Lnet/minecraft/block/BlockState;Lnet/minecraft/world/BlockView;Lnet/minecraft/util/math/BlockPos;Lnet/minecraft/util/math/Direction;)Z";
private static final String 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 getVoxelShape = "Lnet/minecraft/block/BlockState;getCullingFace(Lnet/minecraft/world/BlockView;Lnet/minecraft/util/math/BlockPos;Lnet/minecraft/util/math/Direction;)Lnet/minecraft/util/shape/VoxelShape;";
private static final String randomDisplayTick = "randomDisplayTick(Lnet/minecraft/block/BlockState;Lnet/minecraft/world/World;Lnet/minecraft/util/math/BlockPos;Ljava/util/Random;)V"; private static final String randomDisplayTick = "randomDisplayTick(Lnet/minecraft/block/BlockState;Lnet/minecraft/world/World;Lnet/minecraft/util/math/BlockPos;Ljava/util/Random;)V";
/** /**

View File

@@ -1,6 +1,7 @@
package mods.betterfoliage.mixin; package mods.betterfoliage.mixin;
import mods.betterfoliage.Hooks; import mods.betterfoliage.Hooks;
import net.minecraft.block.AbstractBlock;
import net.minecraft.block.Block; import net.minecraft.block.Block;
import net.minecraft.block.BlockState; import net.minecraft.block.BlockState;
import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.BlockPos;
@@ -14,10 +15,11 @@ import org.spongepowered.asm.mixin.injection.Redirect;
* *
* Needed to avoid excessive darkening of Round Logs at the corners, now that they are not full blocks. * Needed to avoid excessive darkening of Round Logs at the corners, now that they are not full blocks.
*/ */
@Mixin(BlockState.class) @Mixin(AbstractBlock.AbstractBlockState.class)
@SuppressWarnings({"UnnecessaryQualifiedMemberReference", "deprecation"}) @SuppressWarnings({"deprecation"})
public class MixinBlockState { 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 callFrom = "Lnet/minecraft/block/AbstractBlock$AbstractBlockState;getAmbientOcclusionLightLevel(Lnet/minecraft/world/BlockView;Lnet/minecraft/util/math/BlockPos;)F";
// why is the INVOKEVIRTUAL target class Block in the bytecode, not AbstractBlock?
private static final String callTo = "Lnet/minecraft/block/Block;getAmbientOcclusionLightLevel(Lnet/minecraft/block/BlockState;Lnet/minecraft/world/BlockView;Lnet/minecraft/util/math/BlockPos;)F"; private static final String callTo = "Lnet/minecraft/block/Block;getAmbientOcclusionLightLevel(Lnet/minecraft/block/BlockState;Lnet/minecraft/world/BlockView;Lnet/minecraft/util/math/BlockPos;)F";
@Redirect(method = callFrom, at = @At(value = "INVOKE", target = callTo)) @Redirect(method = callFrom, at = @At(value = "INVOKE", target = callTo))

View File

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

View File

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

View File

@@ -8,6 +8,8 @@ import net.minecraft.client.render.WorldRenderer;
import net.minecraft.client.world.ClientWorld; import net.minecraft.client.world.ClientWorld;
import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.BlockPos;
import net.minecraft.util.profiler.Profiler; import net.minecraft.util.profiler.Profiler;
import net.minecraft.util.registry.RegistryKey;
import net.minecraft.world.World;
import net.minecraft.world.dimension.DimensionType; import net.minecraft.world.dimension.DimensionType;
import net.minecraft.world.level.LevelInfo; import net.minecraft.world.level.LevelInfo;
import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Mixin;
@@ -16,12 +18,13 @@ import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import java.util.Random; import java.util.Random;
import java.util.function.Supplier;
@Mixin(ClientWorld.class) @Mixin(ClientWorld.class)
public class MixinClientWorld { public class MixinClientWorld {
private static final String ctor = "<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 ctor = "Lnet/minecraft/client/world/ClientWorld;<init>(Lnet/minecraft/client/network/ClientPlayNetworkHandler;Lnet/minecraft/client/world/ClientWorld$Properties;Lnet/minecraft/util/registry/RegistryKey;Lnet/minecraft/world/dimension/DimensionType;ILjava/util/function/Supplier;Lnet/minecraft/client/render/WorldRenderer;ZJ)V";
private static final String scheduleBlockRender = "scheduleBlockRender(Lnet/minecraft/util/math/BlockPos;Lnet/minecraft/block/BlockState;Lnet/minecraft/block/BlockState;)V"; private static final String scheduleBlockRerenderIfNeeded = "Lnet/minecraft/client/world/ClientWorld;scheduleBlockRerenderIfNeeded(Lnet/minecraft/util/math/BlockPos;Lnet/minecraft/block/BlockState;Lnet/minecraft/block/BlockState;)V";
private static final String 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 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 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 blockDisplayTick = "Lnet/minecraft/block/Block;randomDisplayTick(Lnet/minecraft/block/BlockState;Lnet/minecraft/world/World;Lnet/minecraft/util/math/BlockPos;Ljava/util/Random;)V";
@@ -30,13 +33,13 @@ public class MixinClientWorld {
* Inject callback to get notified of client-side blockstate changes. * Inject callback to get notified of client-side blockstate changes.
* Used to invalidate caches in the {@link mods.betterfoliage.chunk.ChunkOverlayManager} * Used to invalidate caches in the {@link mods.betterfoliage.chunk.ChunkOverlayManager}
*/ */
@Inject(method = scheduleBlockRender, at = @At(value = "HEAD")) @Inject(method = scheduleBlockRerenderIfNeeded, at = @At(value = "HEAD"))
void onClientBlockChanged(BlockPos pos, BlockState oldState, BlockState newState, CallbackInfo ci) { void onClientBlockChanged(BlockPos pos, BlockState oldState, BlockState newState, CallbackInfo ci) {
Hooks.onClientBlockChanged((ClientWorld) (Object) this, pos, oldState, newState); Hooks.onClientBlockChanged((ClientWorld) (Object) this, pos, oldState, newState);
} }
@Inject(method = ctor, at = @At("RETURN")) @Inject(method = ctor, at = @At("RETURN"))
void onClientWorldCreated(ClientPlayNetworkHandler netHandler, LevelInfo levelInfo, DimensionType dimensionType, int i, Profiler profiler, WorldRenderer worldRenderer, CallbackInfo ci) { void onClientWorldCreated(ClientPlayNetworkHandler networkHandler, ClientWorld.Properties properties, RegistryKey<World> registryRef, DimensionType dimensionType, int loadDistance, Supplier<Profiler> profiler, WorldRenderer worldRenderer, boolean debugWorld, long seed, CallbackInfo ci) {
ClientWorldLoadCallback.EVENT.invoker().loadWorld((ClientWorld) (Object) this); ClientWorldLoadCallback.EVENT.invoker().loadWorld((ClientWorld) (Object) this);
} }

View File

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

View File

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

View File

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

View File

@@ -4,14 +4,18 @@ import me.zeroeightsix.fiber.JanksonSettings
import mods.betterfoliage.chunk.ChunkOverlayManager import mods.betterfoliage.chunk.ChunkOverlayManager
import mods.betterfoliage.config.BlockConfig import mods.betterfoliage.config.BlockConfig
import mods.betterfoliage.config.MainConfig import mods.betterfoliage.config.MainConfig
import mods.betterfoliage.render.ShadersModIntegration
import mods.betterfoliage.render.block.vanilla.* import mods.betterfoliage.render.block.vanilla.*
import mods.betterfoliage.render.particle.LeafParticleRegistry import mods.betterfoliage.render.particle.LeafParticleRegistry
import mods.betterfoliage.render.particle.RisingSoulParticle import mods.betterfoliage.render.particle.RisingSoulParticle
import mods.betterfoliage.resource.discovery.BakedModelReplacer import mods.betterfoliage.resource.discovery.BakeWrapperManager
import mods.betterfoliage.resource.discovery.BlockTypeCache
import mods.betterfoliage.resource.generated.GeneratedBlockTexturePack import mods.betterfoliage.resource.generated.GeneratedBlockTexturePack
import net.fabricmc.api.ClientModInitializer import net.fabricmc.api.ClientModInitializer
import net.fabricmc.fabric.api.resource.ResourceManagerHelper import net.fabricmc.fabric.api.resource.ResourceManagerHelper
import net.fabricmc.fabric.mixin.resource.loader.ResourcePackManagerAccessor
import net.fabricmc.loader.api.FabricLoader import net.fabricmc.loader.api.FabricLoader
import net.minecraft.block.BlockState
import net.minecraft.client.MinecraftClient import net.minecraft.client.MinecraftClient
import net.minecraft.resource.ResourceType import net.minecraft.resource.ResourceType
import net.minecraft.util.Identifier import net.minecraft.util.Identifier
@@ -27,18 +31,14 @@ import java.util.*
object BetterFoliage : ClientModInitializer { object BetterFoliage : ClientModInitializer {
const val MOD_ID = "betterfoliage" const val MOD_ID = "betterfoliage"
var logger = LogManager.getLogger() val detailLogStream = PrintStream(File("logs/betterfoliage.log").apply {
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() parentFile.mkdirs()
if (!exists()) createNewFile() 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
) )
val configFile get() = File(FabricLoader.getInstance().configDirectory, "BetterFoliage.json") val configFile get() = File(FabricLoader.getInstance().configDirectory, "BetterFoliage.json")
@@ -49,40 +49,46 @@ object BetterFoliage : ClientModInitializer {
} }
val blockConfig = BlockConfig() val blockConfig = BlockConfig()
val generatedPack = GeneratedBlockTexturePack(Identifier(MOD_ID, "generated"), "betterfoliage-generated", "Better Foliage", "Generated leaf textures", logDetail) val generatedPack = GeneratedBlockTexturePack(Identifier(MOD_ID, "generated"), "betterfoliage-generated", "Better Foliage", "Generated leaf textures")
val modelReplacer = BakedModelReplacer()
/** List of recognized [BlockState]s */
var blockTypes = BlockTypeCache()
override fun onInitializeClient() { override fun onInitializeClient() {
// Register generated resource pack // Register generated resource pack
ResourceManagerHelper.get(ResourceType.CLIENT_RESOURCES).registerReloadListener(generatedPack) ResourceManagerHelper.get(ResourceType.CLIENT_RESOURCES).registerReloadListener(generatedPack.reloader)
MinecraftClient.getInstance().resourcePackContainerManager.addCreator(generatedPack.finder) (MinecraftClient.getInstance().resourcePackManager as ResourcePackManagerAccessor)
.providers.add(generatedPack.finder)
ResourceManagerHelper.get(ResourceType.CLIENT_RESOURCES).registerReloadListener(blockConfig)
// Add standard block support // Add standard block support
modelReplacer.discoverers.add(StandardLeafDiscovery) BakeWrapperManager.discoverers.add(StandardCactusDiscovery)
modelReplacer.discoverers.add(StandardGrassDiscovery) BakeWrapperManager.discoverers.add(StandardDirtDiscovery)
modelReplacer.discoverers.add(StandardLogDiscovery) BakeWrapperManager.discoverers.add(StandardGrassDiscovery)
modelReplacer.discoverers.add(StandardCactusDiscovery) BakeWrapperManager.discoverers.add(StandardLeafDiscovery)
modelReplacer.discoverers.add(LilyPadDiscovery) BakeWrapperManager.discoverers.add(StandardLilypadDiscovery)
modelReplacer.discoverers.add(DirtDiscovery) BakeWrapperManager.discoverers.add(StandardMyceliumDiscovery)
modelReplacer.discoverers.add(SandDiscovery) BakeWrapperManager.discoverers.add(StandardNetherrackDiscovery)
modelReplacer.discoverers.add(MyceliumDiscovery) BakeWrapperManager.discoverers.add(StandardRoundLogDiscovery)
modelReplacer.discoverers.add(NetherrackDiscovery) BakeWrapperManager.discoverers.add(StandardSandDiscovery)
// Init overlay layers // Init overlay layers
ChunkOverlayManager.layers.add(RoundLogOverlayLayer) ChunkOverlayManager.layers.add(RoundLogOverlayLayer)
// Init singletons // Init singletons
LeafParticleRegistry LeafParticleRegistry
NormalLeavesModel.Companion StandardLeafModel.Companion
GrassBlockModel.Companion StandardGrassModel.Companion
RoundLogModel.Companion StandardRoundLogModel.Companion
CactusModel.Companion StandardCactusModel.Companion
LilypadModel.Companion StandardLilypadModel.Companion
DirtModel.Companion DirtModel.Companion
SandModel.Companion StandardSandModel.Companion
MyceliumModel.Companion StandardMyceliumModel.Companion
NetherrackModel.Companion StandardNetherrackModel.Companion
RisingSoulParticle.Companion RisingSoulParticle.Companion
ShadersModIntegration
} }
} }

View File

@@ -1,29 +1,11 @@
package mods.betterfoliage package mods.betterfoliage
import it.unimi.dsi.fastutil.ints.IntList
import mods.betterfoliage.util.YarnHelper
import net.minecraft.client.texture.Sprite
import net.minecraft.world.World
// Optifine val VertexFormat_offsets = YarnHelper.requiredField<IntList>("net.minecraft.class_293", "field_1597", "Lit/unimi/dsi/fastutil/ints/IntList;")
//val OptifineClassTransformer = ClassRefOld<Any>("optifine.OptiFineClassTransformer") val BakedQuad_sprite = YarnHelper.requiredField<Sprite>("net.minecraft.class_777", "field_4176", "Lnet/minecraft/class_1058;")
//val BlockPosM = ClassRefOld<Any>("net.optifine.BlockPosM") val WorldChunk_world = YarnHelper.requiredField<World>("net.minecraft.class_2818", "field_12858", "Lnet/minecraft/class_1937;")
//object ChunkCacheOF : ClassRefOld<Any>("net.optifine.override.ChunkCacheOF") { val ChunkRendererRegion_world = YarnHelper.requiredField<World>("net.minecraft.class_853", "field_4490", "Lnet/minecraft/class_1937;")
// val chunkCache = FieldRefOld(this, "chunkCache", ChunkRendererRegion)
//}
//object RenderEnv : ClassRefOld<Any>("net.optifine.render.RenderEnv") {
// val reset = MethodRefOld(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)
//}
// 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)
//}

View File

@@ -2,16 +2,14 @@
package mods.betterfoliage package mods.betterfoliage
import mods.betterfoliage.chunk.ChunkOverlayManager import mods.betterfoliage.chunk.ChunkOverlayManager
import mods.betterfoliage.render.block.vanilla.LeafParticleKey
import mods.betterfoliage.render.block.vanilla.RoundLogKey
import mods.betterfoliage.render.particle.FallingLeafParticle import mods.betterfoliage.render.particle.FallingLeafParticle
import mods.betterfoliage.render.particle.RisingSoulParticle import mods.betterfoliage.render.particle.RisingSoulParticle
import mods.betterfoliage.render.block.vanilla.LeafKey
import mods.betterfoliage.render.block.vanilla.RoundLogKey
import mods.betterfoliage.util.offset import mods.betterfoliage.util.offset
import mods.betterfoliage.util.plus import mods.betterfoliage.util.plus
import mods.betterfoliage.util.random
import mods.betterfoliage.util.randomD import mods.betterfoliage.util.randomD
import net.minecraft.block.BlockRenderLayer
import net.minecraft.block.BlockRenderLayer.CUTOUT
import net.minecraft.block.BlockRenderLayer.CUTOUT_MIPPED
import net.minecraft.block.BlockState import net.minecraft.block.BlockState
import net.minecraft.block.Blocks import net.minecraft.block.Blocks
import net.minecraft.client.MinecraftClient import net.minecraft.client.MinecraftClient
@@ -25,7 +23,7 @@ import net.minecraft.world.BlockView
fun getAmbientOcclusionLightValueOverride(original: Float, state: BlockState): Float { fun getAmbientOcclusionLightValueOverride(original: Float, state: BlockState): Float {
if (BetterFoliage.config.enabled && if (BetterFoliage.config.enabled &&
BetterFoliage.config.roundLogs.enabled && BetterFoliage.config.roundLogs.enabled &&
BetterFoliage.modelReplacer.getTyped<RoundLogKey>(state) != null BetterFoliage.blockTypes.hasTyped<RoundLogKey>(state)
) return BetterFoliage.config.roundLogs.dimming.toFloat() ) return BetterFoliage.config.roundLogs.dimming.toFloat()
return original return original
} }
@@ -53,15 +51,17 @@ fun onRandomDisplayTick(world: ClientWorld, pos: BlockPos) {
BetterFoliage.config.fallingLeaves.enabled && BetterFoliage.config.fallingLeaves.enabled &&
world.isAir(pos + Direction.DOWN.offset) && world.isAir(pos + Direction.DOWN.offset) &&
randomD() < BetterFoliage.config.fallingLeaves.chance) { randomD() < BetterFoliage.config.fallingLeaves.chance) {
BetterFoliage.modelReplacer.getTyped<LeafKey>(state)?.let { key -> BetterFoliage.blockTypes.getTyped<LeafParticleKey>(state)?.let { key ->
FallingLeafParticle(world, pos, key).addIfValid() val blockColor = MinecraftClient.getInstance().blockColors.getColor(state, world, pos, 0)
FallingLeafParticle(world, pos, key, blockColor, random).addIfValid()
} }
} }
} }
fun getVoxelShapeOverride(state: BlockState, reader: BlockView, pos: BlockPos, dir: Direction): VoxelShape { fun getVoxelShapeOverride(state: BlockState, reader: BlockView, pos: BlockPos, dir: Direction): VoxelShape {
if (BetterFoliage.modelReplacer[state] is RoundLogKey) { if (BetterFoliage.blockTypes.hasTyped<RoundLogKey>(state)) {
return VoxelShapes.empty() return VoxelShapes.empty()
} }
return state.getCullShape(reader, pos, dir) // TODO ?
return state.getCullingFace(reader, pos, dir)
} }

View File

@@ -1,5 +1,6 @@
package mods.betterfoliage.chunk package mods.betterfoliage.chunk
import mods.betterfoliage.ChunkRendererRegion_world
import mods.betterfoliage.util.Int3 import mods.betterfoliage.util.Int3
import mods.betterfoliage.util.allDirections import mods.betterfoliage.util.allDirections
import mods.betterfoliage.util.offset import mods.betterfoliage.util.offset
@@ -7,9 +8,11 @@ import mods.betterfoliage.util.plus
import net.minecraft.block.Block import net.minecraft.block.Block
import net.minecraft.block.BlockState import net.minecraft.block.BlockState
import net.minecraft.client.MinecraftClient import net.minecraft.client.MinecraftClient
import net.minecraft.client.render.chunk.ChunkRendererRegion
import net.minecraft.util.math.BlockPos import net.minecraft.util.math.BlockPos
import net.minecraft.util.math.Direction import net.minecraft.util.math.Direction
import net.minecraft.world.ExtendedBlockView import net.minecraft.world.BlockRenderView
import net.minecraft.world.WorldView
import net.minecraft.world.biome.Biome import net.minecraft.world.biome.Biome
/** /**
@@ -17,7 +20,7 @@ import net.minecraft.world.biome.Biome
* block-relative coordinates. * block-relative coordinates.
*/ */
interface BlockCtx { interface BlockCtx {
val world: ExtendedBlockView val world: BlockRenderView
val pos: BlockPos val pos: BlockPos
fun offset(dir: Direction) = offset(dir.offset) fun offset(dir: Direction) = offset(dir.offset)
@@ -27,9 +30,11 @@ interface BlockCtx {
fun state(dir: Direction) = world.getBlockState(pos + dir.offset) fun state(dir: Direction) = world.getBlockState(pos + dir.offset)
fun state(offset: Int3) = world.getBlockState(pos + offset) fun state(offset: Int3) = world.getBlockState(pos + offset)
val biome: Biome get() = world.getBiome(pos) val biome: Biome? get() =
(world as? WorldView)?.getBiome(pos) ?:
(world as? ChunkRendererRegion)?.let { ChunkRendererRegion_world[it]?.getBiome(pos) }
val isNormalCube: Boolean get() = state.isSimpleFullBlock(world, pos) val isNormalCube: Boolean get() = state.isOpaqueFullCube(world, pos)
fun shouldSideBeRendered(side: Direction) = Block.shouldDrawSide(state, world, pos, side) fun shouldSideBeRendered(side: Direction) = Block.shouldDrawSide(state, world, pos, side)
@@ -40,7 +45,7 @@ interface BlockCtx {
} }
open class BasicBlockCtx( open class BasicBlockCtx(
override val world: ExtendedBlockView, override val world: BlockRenderView,
override val pos: BlockPos override val pos: BlockPos
) : BlockCtx { ) : BlockCtx {
override val state = world.getBlockState(pos) override val state = world.getBlockState(pos)
@@ -48,8 +53,8 @@ open class BasicBlockCtx(
fun cache() = CachedBlockCtx(world, pos) fun cache() = CachedBlockCtx(world, pos)
} }
open class CachedBlockCtx(world: ExtendedBlockView, pos: BlockPos) : BasicBlockCtx(world, pos) { open class CachedBlockCtx(world: BlockRenderView, pos: BlockPos) : BasicBlockCtx(world, pos) {
var neighbors = Array<BlockState>(6) { world.getBlockState(pos + allDirections[it].offset) } var neighbors = Array<BlockState>(6) { world.getBlockState(pos + allDirections[it].offset) }
override var biome: Biome = world.getBiome(pos) override var biome: Biome? = super.biome
override fun state(dir: Direction) = neighbors[dir.ordinal] override fun state(dir: Direction) = neighbors[dir.ordinal]
} }

View File

@@ -2,8 +2,8 @@ package mods.betterfoliage.chunk
import net.minecraft.util.math.BlockPos import net.minecraft.util.math.BlockPos
import net.minecraft.world.BlockView import net.minecraft.world.BlockView
import net.minecraft.world.ExtendedBlockView
import net.minecraft.world.LightType import net.minecraft.world.LightType
import net.minecraft.world.WorldView
/** /**
* Delegating [IBlockAccess] that fakes a _modified_ location to return values from a _target_ location. * Delegating [IBlockAccess] that fakes a _modified_ location to return values from a _target_ location.
@@ -21,7 +21,7 @@ open class OffsetBlockView(open val original: BlockView, val modded: BlockPos, v
} }
@Suppress("NOTHING_TO_INLINE", "NULLABILITY_MISMATCH_BASED_ON_JAVA_ANNOTATIONS", "HasPlatformType") @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 { class OffsetExtBlockView(val original: WorldView, val modded: BlockPos, val target: BlockPos) : WorldView by original {
inline fun actualPos(pos: BlockPos) = if (pos != null && pos.x == modded.x && pos.y == modded.y && pos.z == modded.z) target else pos 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 getBlockState(pos: BlockPos) = original.getBlockState(actualPos(pos))
@@ -29,7 +29,7 @@ class OffsetExtBlockView(val original: ExtendedBlockView, val modded: BlockPos,
override fun getFluidState(pos: BlockPos) = original.getFluidState(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 getLightLevel(type: LightType, pos: BlockPos) = original.getLightLevel(type, actualPos(pos))
override fun getLightmapIndex(pos: BlockPos, light: Int) = original.getLightmapIndex(actualPos(pos), light) override fun getBaseLightLevel(pos: BlockPos, light: Int) = original.getBaseLightLevel(actualPos(pos), light)
override fun getBiome(pos: BlockPos) = original.getBiome(actualPos(pos)) override fun getBiome(pos: BlockPos) = original.getBiome(actualPos(pos))
} }

View File

@@ -7,9 +7,9 @@ import net.minecraft.client.render.chunk.ChunkRendererRegion
import net.minecraft.client.world.ClientWorld import net.minecraft.client.world.ClientWorld
import net.minecraft.util.math.BlockPos import net.minecraft.util.math.BlockPos
import net.minecraft.util.math.ChunkPos import net.minecraft.util.math.ChunkPos
import net.minecraft.world.ExtendedBlockView import net.minecraft.world.BlockRenderView
import net.minecraft.world.ViewableWorld
import net.minecraft.world.World import net.minecraft.world.World
import net.minecraft.world.WorldView
import net.minecraft.world.chunk.WorldChunk import net.minecraft.world.chunk.WorldChunk
import net.minecraft.world.dimension.DimensionType import net.minecraft.world.dimension.DimensionType
import java.util.* import java.util.*
@@ -21,14 +21,9 @@ import kotlin.collections.mutableListOf
import kotlin.collections.mutableMapOf import kotlin.collections.mutableMapOf
import kotlin.collections.set import kotlin.collections.set
// net.minecraft.world.chunk.WorldChunk.world val BlockRenderView.dimType: DimensionType get() = when {
val WorldChunk_world = YarnHelper.requiredField<World>("net.minecraft.class_2818", "field_12858", "Lnet/minecraft/class_1937;") this is WorldView -> dimension
// net.minecraft.client.render.chunk.ChunkRendererRegion.world this is ChunkRendererRegion -> this[ChunkRendererRegion_world]!!.dimension
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 // this.isInstance(ChunkCacheOF) -> this[ChunkCacheOF.chunkCache]!!.dimType
else -> throw IllegalArgumentException("DimensionType of world with class ${this::class.qualifiedName} cannot be determined!") else -> throw IllegalArgumentException("DimensionType of world with class ${this::class.qualifiedName} cannot be determined!")
} }
@@ -38,7 +33,7 @@ val ExtendedBlockView.dimType: DimensionType get() = when {
*/ */
interface ChunkOverlayLayer<T> { interface ChunkOverlayLayer<T> {
fun calculate(ctx: BlockCtx): T fun calculate(ctx: BlockCtx): T
fun onBlockUpdate(world: ExtendedBlockView, pos: BlockPos) fun onBlockUpdate(world: WorldView, pos: BlockPos)
} }
/** /**

View File

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

View File

@@ -8,12 +8,16 @@ import me.zeroeightsix.fiber.tree.ConfigLeaf
import me.zeroeightsix.fiber.tree.ConfigNode import me.zeroeightsix.fiber.tree.ConfigNode
import me.zeroeightsix.fiber.tree.ConfigValue import me.zeroeightsix.fiber.tree.ConfigValue
import net.minecraft.client.resource.language.I18n import net.minecraft.client.resource.language.I18n
import net.minecraft.text.LiteralText
import java.util.* import java.util.*
import kotlin.properties.ReadOnlyProperty import kotlin.properties.ReadOnlyProperty
import kotlin.reflect.KProperty import kotlin.reflect.KProperty
const val MAX_LINE_LEN = 30 const val MAX_LINE_LEN = 30
fun textify(string: String) = LiteralText(string)
fun textify(strings: Array<String>) = strings.map(::LiteralText).toTypedArray()
sealed class DelegatingConfigNode<N: ConfigLeaf>(val fiberNode: N) { sealed class DelegatingConfigNode<N: ConfigLeaf>(val fiberNode: N) {
abstract fun createClothNode(names: List<String>): AbstractConfigListEntry<*> abstract fun createClothNode(names: List<String>): AbstractConfigListEntry<*>
} }
@@ -24,9 +28,9 @@ open class DelegatingConfigGroup(fiberNode: ConfigNode) : DelegatingConfigNode<C
val children = mutableListOf<DelegatingConfigNode<*>>() val children = mutableListOf<DelegatingConfigNode<*>>()
override fun createClothNode(names: List<String>): SubCategoryListEntry { override fun createClothNode(names: List<String>): SubCategoryListEntry {
val builder = ConfigEntryBuilder.create() val builder = ConfigEntryBuilder.create()
.startSubCategory(names.joinToString(".").translate()) .startSubCategory(textify(names.joinToString(".").translate()))
.setTooltip(*names.joinToString(".").translateTooltip()) .setTooltip(*textify(names.joinToString(".").translateTooltip()))
.setExpended(false) .setExpanded(false)
children.forEach { builder.add(it.createClothNode(names + it.fiberNode.name!!)) } children.forEach { builder.add(it.createClothNode(names + it.fiberNode.name!!)) }
return builder.build() return builder.build()
} }
@@ -85,8 +89,8 @@ fun boolean(
.build() .build()
override fun createClothNode(node: ConfigValue<Boolean>, names: List<String>) = ConfigEntryBuilder.create() override fun createClothNode(node: ConfigValue<Boolean>, names: List<String>) = ConfigEntryBuilder.create()
.startBooleanToggle(langKey(names).translate(), node.value!!) .startBooleanToggle(textify(langKey(names).translate()), node.value!!)
.setTooltip(langKey(names).let { if (I18n.hasTranslation("$it.tooltip")) Optional.of(it.translateTooltip()) else Optional.empty() }) .setTooltip(langKey(names).let { if (I18n.hasTranslation("$it.tooltip")) Optional.of(textify(it.translateTooltip())) else Optional.empty() })
.setSaveConsumer { node.value = valueOverride(it) } .setSaveConsumer { node.value = valueOverride(it) }
.build() .build()
} }
@@ -104,8 +108,8 @@ fun integer(
.build() .build()
override fun createClothNode(node: ConfigValue<Int>, names: List<String>) = ConfigEntryBuilder.create() override fun createClothNode(node: ConfigValue<Int>, names: List<String>) = ConfigEntryBuilder.create()
.startIntField(langKey(names).translate(), node.value!!) .startIntField(textify(langKey(names).translate()), node.value!!)
.setTooltip(langKey(names).let { if (I18n.hasTranslation("$it.tooltip")) Optional.of(it.translateTooltip()) else Optional.empty() }) .setTooltip(langKey(names).let { if (I18n.hasTranslation("$it.tooltip")) Optional.of(textify(it.translateTooltip())) else Optional.empty() })
.setMin(min).setMax(max) .setMin(min).setMax(max)
.setSaveConsumer { node.value = valueOverride(it) } .setSaveConsumer { node.value = valueOverride(it) }
.build() .build()
@@ -124,8 +128,8 @@ fun double(
.build() .build()
override fun createClothNode(node: ConfigValue<Double>, names: List<String>) = ConfigEntryBuilder.create() override fun createClothNode(node: ConfigValue<Double>, names: List<String>) = ConfigEntryBuilder.create()
.startDoubleField(langKey(names).translate(), node.value!!) .startDoubleField(textify(langKey(names).translate()), node.value!!)
.setTooltip(langKey(names).let { if (I18n.hasTranslation("$it.tooltip")) Optional.of(it.translateTooltip()) else Optional.empty() }) .setTooltip(langKey(names).let { if (I18n.hasTranslation("$it.tooltip")) Optional.of(textify(it.translateTooltip())) else Optional.empty() })
.setMin(min).setMax(max) .setMin(min).setMax(max)
.setSaveConsumer { node.value = valueOverride(it) } .setSaveConsumer { node.value = valueOverride(it) }
.build() .build()

View File

@@ -39,6 +39,8 @@ class LeavesConfig(node: ConfigNode) : DelegatingConfigGroup(node) {
val hOffset by double(0.2, min = 0.0, max = 0.4, langKey = recurring) val hOffset by double(0.2, min = 0.0, max = 0.4, langKey = recurring)
val vOffset by double(0.1, min = 0.0, max = 0.4, langKey = recurring) val vOffset by double(0.1, min = 0.0, max = 0.4, langKey = recurring)
val size by double(1.4, min = 0.75, max = 2.5, langKey = recurring) val size by double(1.4, min = 0.75, max = 2.5, langKey = recurring)
val shaderWind by boolean(true, langKey = recurring)
val saturationThreshold by double(0.1, min = 0.0, max = 1.0, langKey = recurring)
} }
class ShortGrassConfig(node: ConfigNode) : DelegatingConfigGroup(node), PopulationConfigData { class ShortGrassConfig(node: ConfigNode) : DelegatingConfigGroup(node), PopulationConfigData {
@@ -52,7 +54,7 @@ class ShortGrassConfig(node: ConfigNode) : DelegatingConfigGroup(node), Populati
override val population by population(64) override val population by population(64)
val useGenerated by boolean(false) val useGenerated by boolean(false)
val shaderWind by boolean(true, langKey = recurring) val shaderWind by boolean(true, langKey = recurring)
val saturationThreshold by double(0.1, min = 0.0, max = 1.0) val saturationThreshold by double(0.1, min = 0.0, max = 1.0, langKey = recurring)
} }
class ConnectedGrassConfig(node: ConfigNode) : DelegatingConfigGroup(node) { class ConnectedGrassConfig(node: ConfigNode) : DelegatingConfigGroup(node) {
@@ -70,14 +72,14 @@ class RoundLogConfig(node: ConfigNode) : DelegatingConfigGroup(node) {
val connectGrass by boolean(true) val connectGrass by boolean(true)
val radiusSmall by double(0.25, min = 0.0, max = 0.5) 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 radiusLarge by double(0.44, min = 0.0, max = 0.5) { it.coerceAtLeast(radiusSmall) }
val dimming by double(0.7, min = 0.0, max = 1.0) val dimming by double(0.7, min = 0.0, max = 1.0)
val zProtection by double(0.99, min = 0.9, max = 1.0) val zProtection by double(0.99, min = 0.9, max = 1.0)
} }
class CactusConfig(node: ConfigNode) : DelegatingConfigGroup(node) { class CactusConfig(node: ConfigNode) : DelegatingConfigGroup(node) {
val enabled by boolean(true, langKey = recurring) val enabled by boolean(true, langKey = recurring)
val size by double(1.3, min = 0.5, max = 1.5, langKey = recurring) val size by double(1.3, min = 1.0, max = 2.0, langKey = recurring)
val sizeVariation by double(0.1, min = 0.0, max = 0.5) 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) val hOffset by double(0.1, min = 0.0, max = 0.5, langKey = recurring)
} }
@@ -86,6 +88,7 @@ class LilypadConfig(node: ConfigNode) : DelegatingConfigGroup(node), PopulationC
override val enabled by boolean(true, langKey = recurring) override val enabled by boolean(true, langKey = recurring)
val hOffset by double(0.1, min = 0.0, max = 0.25, langKey = recurring) val hOffset by double(0.1, min = 0.0, max = 0.25, langKey = recurring)
override val population by population(16) override val population by population(16)
val shaderWind by boolean(true, langKey = recurring)
} }
class ReedConfig(node: ConfigNode) : DelegatingConfigGroup(node), PopulationConfigData { class ReedConfig(node: ConfigNode) : DelegatingConfigGroup(node), PopulationConfigData {
@@ -130,6 +133,7 @@ class NetherrackConfig(node: ConfigNode) : DelegatingConfigGroup(node) {
class FallingLeavesConfig(node: ConfigNode) : DelegatingConfigGroup(node) { class FallingLeavesConfig(node: ConfigNode) : DelegatingConfigGroup(node) {
val enabled by boolean(true, langKey = recurring) val enabled by boolean(true, langKey = recurring)
val opacityHack by boolean(false)
val speed by double(0.05, min = 0.01, max = 0.15) 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 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 stormStrength by double(0.8, min = 0.1, max = 2.0) { it.coerceAtLeast(windStrength) }

View File

@@ -0,0 +1,13 @@
package mods.betterfoliage.config
import net.minecraft.block.Blocks
import net.minecraft.block.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 SALTWATER_BIOMES = listOf(Biome.Category.BEACH, Biome.Category.OCEAN)
val SNOW_MATERIALS = listOf(Material.SNOW_BLOCK)

View File

@@ -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
}
}
*/

View File

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

View File

@@ -1,9 +1,12 @@
package mods.betterfoliage.resource.model package mods.betterfoliage.model
import mods.betterfoliage.BakedQuad_sprite
import mods.betterfoliage.VertexFormat_offsets
import mods.betterfoliage.util.Double3 import mods.betterfoliage.util.Double3
import mods.betterfoliage.util.allDirections import mods.betterfoliage.util.allDirections
import mods.betterfoliage.util.findFirst import mods.betterfoliage.util.findFirst
import net.minecraft.block.BlockRenderLayer import mods.betterfoliage.util.get
import net.fabricmc.fabric.api.renderer.v1.material.BlendMode
import net.minecraft.block.BlockState import net.minecraft.block.BlockState
import net.minecraft.client.render.VertexFormat import net.minecraft.client.render.VertexFormat
import net.minecraft.client.render.VertexFormatElement import net.minecraft.client.render.VertexFormatElement
@@ -14,8 +17,8 @@ import net.minecraft.client.render.VertexFormatElement.Type.UV
import net.minecraft.client.render.VertexFormats import net.minecraft.client.render.VertexFormats
import net.minecraft.client.render.model.BakedModel import net.minecraft.client.render.model.BakedModel
import net.minecraft.client.render.model.BakedQuad import net.minecraft.client.render.model.BakedQuad
import net.minecraft.client.render.model.BasicBakedModel
import net.minecraft.util.math.Direction import net.minecraft.util.math.Direction
import java.lang.Float
import java.util.* import java.util.*
interface BakedModelConverter { interface BakedModelConverter {
@@ -24,12 +27,12 @@ interface BakedModelConverter {
* @param model Input model * @param model Input model
* @param converter Converter to use for converting nested models. * @param converter Converter to use for converting nested models.
*/ */
fun convert(model: BakedModel, converter: BakedModelConverter): BakedModel? fun convert(model: BakedModel): BakedModel?
companion object { companion object {
fun of(func: (BakedModel, BakedModelConverter)->BakedModel?) = object : BakedModelConverter { fun of(func: (BakedModel)->BakedModel?) = object : BakedModelConverter {
override fun convert(model: BakedModel, converter: BakedModelConverter) = func(model, converter) override fun convert(model: BakedModel) = func(model)
} }
val identity = of { model, _ -> model } val identity = of { model -> model }
} }
} }
@@ -39,40 +42,40 @@ interface BakedModelConverter {
*/ */
fun List<BakedModelConverter>.convert(model: BakedModel) = object : BakedModelConverter { fun List<BakedModelConverter>.convert(model: BakedModel) = object : BakedModelConverter {
val converters = this@convert + BakedModelConverter.identity val converters = this@convert + BakedModelConverter.identity
override fun convert(model: BakedModel, converter: BakedModelConverter) = converters.findFirst { it.convert(model, converter) } override fun convert(model: BakedModel) = converters.findFirst { it.convert(model) }
}.let { converterStack -> }.let { converterStack ->
// we are guaranteed a result here because of the identity converter // we are guaranteed a result here because of the identity converter
converterStack.convert(model, converterStack)!! converterStack.convert(model)!!
} }
/** 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. * Convert [BasicBakedModel] into one using fabric-rendering-api [Mesh] instead of the vanilla pipeline.
* @param renderLayerOverride Use the given [BlockRenderLayer] for the [Mesh] * @param blendMode Use the given [BlockRenderLayer] for the [Mesh]
* instead of the one declared by the corresponding [Block] * instead of the one declared by the corresponding [Block]
*/ */
fun meshifyStandard(model: BakedModel, state: BlockState, renderLayerOverride: BlockRenderLayer? = null) = fun meshifyStandard(model: BasicBakedModel, state: BlockState? = null, blendMode: BlendMode? = null) =
(COMMON_MESH_CONVERTERS + WrappedMeshModel.converter(state, renderLayerOverride = renderLayerOverride)).convert(model) WrappedMeshModel.converter(state, blendModeOverride = blendMode).convert(model)!!
fun meshifySolid(model: BasicBakedModel) = meshifyStandard(model, null, BlendMode.SOLID)
fun meshifyCutoutMipped(model: BasicBakedModel) = meshifyStandard(model, null, BlendMode.CUTOUT_MIPPED)
/** /**
* Convert a vanilla [BakedModel] into intermediate [Quad]s * Convert a vanilla [BakedModel] into intermediate [Quad]s
* Vertex normals not supported (yet) * Vertex normals not supported (yet)
* Vertex data elements not aligned to 32 bit boundaries not supported * Vertex data elements not aligned to 32 bit boundaries not supported
*/ */
fun unbakeQuads(model: BakedModel, state: BlockState, random: Random, unshade: Boolean): List<Quad> { fun unbakeQuads(model: BakedModel, state: BlockState?, random: Random, unshade: Boolean): List<Quad> {
return (allDirections.toList() + null as Direction?).flatMap { face -> return (allDirections.toList() + null as Direction?).flatMap { face ->
model.getQuads(state, face, random).mapIndexed { qIdx, bakedQuad -> model.getQuads(state, face, random).mapIndexed { qIdx, bakedQuad ->
var quad = Quad(Vertex(), Vertex(), Vertex(), Vertex(), face = face, colorIndex = bakedQuad.colorIndex, sprite = bakedQuad.sprite) var quad = Quad(Vertex(), Vertex(), Vertex(), Vertex(), face = face, colorIndex = bakedQuad.colorIndex, sprite = bakedQuad[BakedQuad_sprite])
val format = quadVertexFormat(bakedQuad) val format = quadVertexFormat(bakedQuad)
val stride = format.vertexSizeInteger val stride = format.vertexSizeInteger
format.getIntOffset(POSITION, FLOAT, 3)?.let { posOffset -> format.getIntOffset(POSITION, FLOAT, 3)?.let { posOffset ->
quad = quad.transformVI { vertex, vIdx -> vertex.copy(xyz = Double3( quad = quad.transformVI { vertex, vIdx -> vertex.copy(xyz = Double3(
x = Float.intBitsToFloat(bakedQuad.vertexData[vIdx * stride + posOffset + 0]).toDouble(), x = java.lang.Float.intBitsToFloat(bakedQuad.vertexData[vIdx * stride + posOffset + 0]).toDouble(),
y = Float.intBitsToFloat(bakedQuad.vertexData[vIdx * stride + posOffset + 1]).toDouble(), y = java.lang.Float.intBitsToFloat(bakedQuad.vertexData[vIdx * stride + posOffset + 1]).toDouble(),
z = Float.intBitsToFloat(bakedQuad.vertexData[vIdx * stride + posOffset + 2]).toDouble() z = java.lang.Float.intBitsToFloat(bakedQuad.vertexData[vIdx * stride + posOffset + 2]).toDouble()
)) } )) }
} }
format.getIntOffset(COLOR, UBYTE, 4)?.let { colorOffset -> format.getIntOffset(COLOR, UBYTE, 4)?.let { colorOffset ->
@@ -82,8 +85,8 @@ fun unbakeQuads(model: BakedModel, state: BlockState, random: Random, unshade: B
} }
format.getIntOffset(UV, FLOAT, 2, 0)?.let { uvOffset -> format.getIntOffset(UV, FLOAT, 2, 0)?.let { uvOffset ->
quad = quad.transformVI { vertex, vIdx -> vertex.copy(uv = UV( quad = quad.transformVI { vertex, vIdx -> vertex.copy(uv = UV(
u = Float.intBitsToFloat(bakedQuad.vertexData[vIdx * stride + uvOffset + 0]).toDouble(), u = java.lang.Float.intBitsToFloat(bakedQuad.vertexData[vIdx * stride + uvOffset + 0]).toDouble(),
v = Float.intBitsToFloat(bakedQuad.vertexData[vIdx * stride + uvOffset + 1]).toDouble() v = java.lang.Float.intBitsToFloat(bakedQuad.vertexData[vIdx * stride + uvOffset + 1]).toDouble()
)) } )) }
} }
@@ -97,8 +100,8 @@ fun unbakeQuads(model: BakedModel, state: BlockState, random: Random, unshade: B
/** Get the byte offset of the [VertexFormatElement] matching the given criteria */ /** 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? { fun VertexFormat.getByteOffset(type: VertexFormatElement.Type, format: VertexFormatElement.Format, count: Int, index: Int = 0): Int? {
elements.forEachIndexed { idx, element -> elements.forEachIndexed { idx, element ->
if (element.type == type && element.format == format && element.count == count && element.index == index) if (element == VertexFormatElement(index, format, type, count))
return getElementOffset(idx) return VertexFormat_offsets[this]!!.getInt(idx)
} }
return null return null
} }
@@ -111,4 +114,4 @@ fun VertexFormat.getIntOffset(type: VertexFormatElement.Type, format: VertexForm
getByteOffset(type, format, count, index)?.let { if (it % 4 == 0) it / 4 else null } getByteOffset(type, format, count, index)?.let { if (it % 4 == 0) it / 4 else null }
/** Function to determine [VertexFormat] used by [BakedQuad] */ /** Function to determine [VertexFormat] used by [BakedQuad] */
var quadVertexFormat: (BakedQuad)->VertexFormat = { VertexFormats.POSITION_COLOR_UV_LMAP } var quadVertexFormat: (BakedQuad)->VertexFormat = { VertexFormats.POSITION_COLOR_TEXTURE_LIGHT_NORMAL }

View File

@@ -1,20 +1,20 @@
package mods.betterfoliage.resource.model package mods.betterfoliage.model
import mods.betterfoliage.util.Atlas import mods.betterfoliage.util.Atlas
import mods.betterfoliage.util.* import mods.betterfoliage.util.*
import mods.betterfoliage.util.minmax import mods.betterfoliage.util.minmax
import net.fabricmc.fabric.api.renderer.v1.RendererAccess import net.fabricmc.fabric.api.renderer.v1.RendererAccess
import net.fabricmc.fabric.api.renderer.v1.material.BlendMode
import net.fabricmc.fabric.api.renderer.v1.mesh.Mesh import net.fabricmc.fabric.api.renderer.v1.mesh.Mesh
import net.minecraft.block.BlockRenderLayer
import net.minecraft.client.texture.MissingSprite import net.minecraft.client.texture.MissingSprite
import net.minecraft.client.texture.Sprite import net.minecraft.client.texture.Sprite
import net.minecraft.util.math.Direction import net.minecraft.util.math.Direction
import net.minecraft.util.math.Direction.* import net.minecraft.util.math.Direction.*
import java.lang.Math.max import java.lang.Math.max
import java.lang.Math.min import java.lang.Math.min
import java.util.Random
import kotlin.math.cos import kotlin.math.cos
import kotlin.math.sin import kotlin.math.sin
import kotlin.random.Random
/** /**
* Vertex UV coordinates * Vertex UV coordinates
@@ -166,13 +166,13 @@ fun Array<List<Quad>>.withOpposites() = map { it.withOpposites() }.toTypedArray(
/** /**
* Pour quad data into a fabric-renderer-api Mesh * Pour quad data into a fabric-renderer-api Mesh
*/ */
fun List<Quad>.build(layer: BlockRenderLayer, noDiffuse: Boolean = false, flatLighting: Boolean = false): Mesh { fun List<Quad>.build(blendMode: BlendMode, noDiffuse: Boolean = false, flatLighting: Boolean = false): Mesh {
val renderer = RendererAccess.INSTANCE.renderer val renderer = RendererAccess.INSTANCE.renderer!!
val material = renderer.materialFinder().blendMode(0, layer).disableAo(0, flatLighting).disableDiffuse(0, noDiffuse).find() val material = renderer.materialFinder().blendMode(0, blendMode).disableAo(0, flatLighting).disableDiffuse(0, noDiffuse).find()
val builder = renderer.meshBuilder() val builder = renderer.meshBuilder()
builder.emitter.apply { builder.emitter.apply {
forEach { quad -> forEach { quad ->
val sprite = quad.sprite ?: Atlas.BLOCKS.atlas[MissingSprite.getMissingSpriteId()]!! val sprite = quad.sprite ?: Atlas.BLOCKS[MissingSprite.getMissingSpriteId()]!!
quad.verts.forEachIndexed { idx, vertex -> quad.verts.forEachIndexed { idx, vertex ->
pos(idx, (vertex.xyz + Double3(0.5, 0.5, 0.5)).asVec3f) pos(idx, (vertex.xyz + Double3(0.5, 0.5, 0.5)).asVec3f)
sprite(idx, 0, sprite(idx, 0,
@@ -190,7 +190,7 @@ fun List<Quad>.build(layer: BlockRenderLayer, noDiffuse: Boolean = false, flatLi
return builder.build() return builder.build()
} }
fun Array<List<Quad>>.build(layer: BlockRenderLayer, noDiffuse: Boolean = false, flatLighting: Boolean = false) = map { it.build(layer, noDiffuse, flatLighting) }.toTypedArray() fun Array<List<Quad>>.build(blendMode: BlendMode, noDiffuse: Boolean = false, flatLighting: Boolean = false) = map { it.build(blendMode, noDiffuse, flatLighting) }.toTypedArray()
/** /**
* The model should be positioned so that (0,0,0) is the block center. * The model should be positioned so that (0,0,0) is the block center.

View File

@@ -1,4 +1,4 @@
package mods.betterfoliage.resource.model package mods.betterfoliage.model
import mods.betterfoliage.util.Atlas import mods.betterfoliage.util.Atlas
import mods.betterfoliage.util.get import mods.betterfoliage.util.get
@@ -21,8 +21,8 @@ class FixedSpriteSet(val sprites: List<Sprite>) : SpriteSet {
override fun get(idx: Int) = sprites[idx % num] override fun get(idx: Int) = sprites[idx % num]
constructor(atlas: Atlas, ids: List<Identifier>) : this( constructor(atlas: Atlas, ids: List<Identifier>) : this(
ids.mapNotNull { atlas.atlas[it] }.let { sprites -> ids.mapNotNull { atlas[it] }.let { sprites ->
if (sprites.isNotEmpty()) sprites else listOf(atlas.atlas[MissingSprite.getMissingSpriteId()]!!) if (sprites.isNotEmpty()) sprites else listOf(atlas[MissingSprite.getMissingSpriteId()]!!)
} }
) )
} }
@@ -42,12 +42,17 @@ class SpriteDelegate(val atlas: Atlas, val idFunc: ()->Identifier) : ReadOnlyPro
value?.let { return it } value?.let { return it }
synchronized(this) { synchronized(this) {
value?.let { return it } value?.let { return it }
atlas.atlas[id!!]!!.let { value = it; return it } 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 {
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 idList: List<Identifier> = emptyList()
private var spriteSet: SpriteSet? = null private var spriteSet: SpriteSet? = null
init { ClientSpriteRegistryCallback.event(atlas.resourceId).register(this) } init { ClientSpriteRegistryCallback.event(atlas.resourceId).register(this) }
@@ -55,7 +60,7 @@ class SpriteSetDelegate(val atlas: Atlas, val idRegister: (Identifier)->Identifi
override fun registerSprites(atlasTexture: SpriteAtlasTexture, registry: ClientSpriteRegistryCallback.Registry) { override fun registerSprites(atlasTexture: SpriteAtlasTexture, registry: ClientSpriteRegistryCallback.Registry) {
spriteSet = null spriteSet = null
val manager = MinecraftClient.getInstance().resourceManager val manager = MinecraftClient.getInstance().resourceManager
idList = (0 until 16).map(idFunc).filter { manager.containsResource(atlas.wrap(it)) }.map(idRegister) idList = (0 until 16).map(idFunc).filter { manager.containsResource(atlas.file(it)) }.map(idRegister)
idList.forEach { registry.register(it) } idList.forEach { registry.register(it) }
} }

View File

@@ -1,11 +1,8 @@
package mods.betterfoliage.resource.model package mods.betterfoliage.model
import mods.betterfoliage.util.Atlas
import mods.betterfoliage.util.* import mods.betterfoliage.util.*
import net.fabricmc.fabric.api.renderer.v1.RendererAccess import net.fabricmc.fabric.api.renderer.v1.material.BlendMode
import net.fabricmc.fabric.api.renderer.v1.mesh.Mesh import net.fabricmc.fabric.api.renderer.v1.mesh.Mesh
import net.minecraft.block.BlockRenderLayer
import net.minecraft.block.BlockRenderLayer.CUTOUT_MIPPED
import net.minecraft.client.texture.Sprite import net.minecraft.client.texture.Sprite
import net.minecraft.util.Identifier import net.minecraft.util.Identifier
import net.minecraft.util.math.Direction.UP import net.minecraft.util.math.Direction.UP
@@ -34,22 +31,22 @@ 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) verticalRectangle(x1 = -0.5 * size, z1 = 0.5 * size, x2 = 0.5 * size, z2 = -0.5 * size, yBottom = 0.5, yTop = 0.5 + height)
.mirrorUV(flipU, false) .mirrorUV(flipU, false)
fun tuftModelSet(shapes: Array<TuftShapeKey>, overrideColor: Int?, spriteGetter: (Int)->Sprite) = shapes.mapIndexed { idx, shape -> fun tuftModelSet(shapes: Array<TuftShapeKey>, tintIndex: Int, spriteGetter: (Int)->Sprite) = shapes.mapIndexed { idx, shape ->
listOf( listOf(
tuftQuadSingle(shape.size, shape.height, shape.flipU1), tuftQuadSingle(shape.size, shape.height, shape.flipU1),
tuftQuadSingle(shape.size, shape.height, shape.flipU2).rotate(rot(UP)) tuftQuadSingle(shape.size, shape.height, shape.flipU2).rotate(rot(UP))
).map { it.move(shape.offset) } ).map { it.move(shape.offset) }
.map { it.colorAndIndex(overrideColor) } .map { it.colorIndex(tintIndex) }
.map { it.sprite(spriteGetter(idx)) } .map { it.sprite(spriteGetter(idx)) }
}.toTypedArray() }.toTypedArray()
fun fullCubeTextured(spriteId: Identifier, overrideColor: Int?, scrambleUV: Boolean = true): Mesh { fun fullCubeTextured(spriteId: Identifier, tintIndex: Int, scrambleUV: Boolean = true): Mesh {
val sprite = Atlas.BLOCKS.atlas[spriteId]!! val sprite = Atlas.BLOCKS[spriteId]!!
return allDirections.map { faceQuad(it) } return allDirections.map { faceQuad(it) }
.map { if (!scrambleUV) it else it.rotateUV(randomI(max = 4)) } .map { if (!scrambleUV) it else it.rotateUV(randomI(max = 4)) }
.map { it.sprite(sprite) } .map { it.sprite(sprite) }
.map { it.colorAndIndex(overrideColor) } .map { it.colorIndex(tintIndex) }
.build(BlockRenderLayer.SOLID) .build(BlendMode.SOLID, noDiffuse = true)
} }
fun crossModelsRaw(num: Int, size: Double, hOffset: Double, vOffset: Double): Array<List<Quad>> { fun crossModelsRaw(num: Int, size: Double, hOffset: Double, vOffset: Double): Array<List<Quad>> {
@@ -64,11 +61,20 @@ fun crossModelsRaw(num: Int, size: Double, hOffset: Double, vOffset: Double): Ar
} }
} }
fun crossModelsTextured(leafBase: Array<List<Quad>>, overrideColor: Int?, scrambleUV: Boolean, spriteGetter: (Int)->Sprite) = leafBase.map { leaf -> fun crossModelSingle(base: List<Quad>, sprite: Sprite, tintIndex: Int,scrambleUV: Boolean) =
leaf.map { if (scrambleUV) it.scrambleUV(random, canFlipU = true, canFlipV = true, canRotate = true) else it } base.map { if (scrambleUV) it.scrambleUV(random, canFlipU = true, canFlipV = true, canRotate = true) else it }
.map { it.colorAndIndex(overrideColor) } .map { it.colorIndex(tintIndex) }
.mapIndexed { idx, quad -> quad.sprite(spriteGetter(idx)) } .mapIndexed { idx, quad -> quad.sprite(sprite) }
.withOpposites().build(CUTOUT_MIPPED) .withOpposites()
.build(BlendMode.CUTOUT_MIPPED)
fun crossModelsTextured(
leafBase: Array<List<Quad>>,
tintIndex: Int,
scrambleUV: Boolean,
spriteGetter: (Int) -> Identifier
) = leafBase.mapIndexed { idx, leaf ->
crossModelSingle(leaf, Atlas.BLOCKS[spriteGetter(idx)], tintIndex, scrambleUV)
}.toTypedArray() }.toTypedArray()
fun Array<List<Quad>>.buildTufts() = withOpposites().build(CUTOUT_MIPPED) fun Array<List<Quad>>.buildTufts() = withOpposites().build(BlendMode.CUTOUT_MIPPED)

View File

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

View File

@@ -1,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
}
}

View File

@@ -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)

View File

@@ -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)
}
}
*/

View File

@@ -1,64 +1,54 @@
package mods.betterfoliage.render package mods.betterfoliage.render
import mods.betterfoliage.BetterFoliage import mods.betterfoliage.BetterFoliage
import mods.betterfoliage.util.get import mods.betterfoliage.render.lighting.getBufferBuilder
import net.minecraft.block.BlockRenderType import mods.betterfoliage.util.HasLogger
import net.minecraft.block.BlockRenderType.MODEL import mods.betterfoliage.util.getAllMethods
import net.fabricmc.fabric.api.renderer.v1.render.RenderContext
import net.minecraft.block.BlockState import net.minecraft.block.BlockState
import net.minecraft.block.Blocks import net.minecraft.block.Blocks
import net.minecraft.util.math.BlockPos import net.minecraft.client.render.BufferBuilder
import net.minecraft.world.ExtendedBlockView import net.minecraft.client.render.RenderLayer
import org.apache.logging.log4j.Level.INFO
/** /**
* Integration for ShadersMod. * Integration for ShadersMod.
*/ */
/* object ShadersModIntegration : HasLogger() {
object ShadersModIntegration {
@JvmStatic val isAvailable = allAvailable(SVertexBuilder, SVertexBuilder.pushState, SVertexBuilder.pushNum, SVertexBuilder.pop) val BufferBuilder_SVertexBuilder = BufferBuilder::class.java.fields.find { it.name == "sVertexBuilder" }
val SVertexBuilder_pushState = getAllMethods("net.optifine.shaders.SVertexBuilder", "pushEntity").find { it.parameterCount == 1 }
val SVertexBuilder_popState = getAllMethods("net.optifine.shaders.SVertexBuilder", "popEntity").find { it.parameterCount == 0 }
val BlockAliases_getAliasBlockId = getAllMethods("net.optifine.shaders.BlockAliases", "getAliasBlockId").firstOrNull()
@JvmStatic val isAvailable =
listOf(BufferBuilder_SVertexBuilder).all { it != null } &&
listOf(SVertexBuilder_pushState, SVertexBuilder_popState, BlockAliases_getAliasBlockId).all { it != null }
val defaultLeaves = Blocks.OAK_LEAVES.defaultState val defaultLeaves = Blocks.OAK_LEAVES.defaultState
val defaultGrass = Blocks.TALL_GRASS.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 { init {
BetterFoliage.log(INFO, "ShadersMod integration is ${if (isAvailable) "enabled" else "disabled" }") logger.info("[BetterFoliage] 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. */ /** 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) { inline fun renderAs(ctx: RenderContext, state: BlockState, layer: RenderLayer, enabled: Boolean = true, func: ()->Unit) {
if (isAvailable && enabled) { if (isAvailable && enabled) {
val buffer = ctx.renderCtx.renderBuffer val sVertexBuilder = BufferBuilder_SVertexBuilder!!.get(ctx.getBufferBuilder(layer))
val sVertexBuilder = buffer[BufferBuilder_sVertexBuilder] val aliasBlockId = BlockAliases_getAliasBlockId!!.invoke(null, state)
SVertexBuilder.pushState.invoke(sVertexBuilder!!, ctx.state, ctx.pos, ctx.world, buffer) SVertexBuilder_pushState!!.invoke(sVertexBuilder, aliasBlockId)
func() func()
SVertexBuilder.pop.invoke(sVertexBuilder) SVertexBuilder_popState!!.invoke(sVertexBuilder)
} else { } else {
func() func()
} }
} }
/** Quads rendered inside this block will behave as tallgrass blocks in shader programs. */ /** Quads rendered inside this block will behave as tallgrass blocks in shader programs. */
inline fun grass(ctx: CombinedContext, enabled: Boolean = true, func: ()->Unit) = inline fun grass(ctx: RenderContext, enabled: Boolean = true, func: ()->Unit) =
renderAs(ctx, defaultGrass, MODEL, enabled, func) renderAs(ctx, defaultGrass, RenderLayer.getCutoutMipped(), enabled, func)
/** Quads rendered inside this block will behave as leaf blocks in shader programs. */ /** Quads rendered inside this block will behave as leaf blocks in shader programs. */
inline fun leaves(ctx: CombinedContext, enabled: Boolean = true, func: ()->Unit) = inline fun leaves(ctx: RenderContext, enabled: Boolean = true, func: ()->Unit) =
renderAs(ctx, defaultLeaves, MODEL, enabled, func) renderAs(ctx, defaultLeaves, RenderLayer.getCutoutMipped(), enabled, func)
} }
*/

View File

@@ -1,52 +1,71 @@
package mods.betterfoliage.render.block.vanilla package mods.betterfoliage.render.block.vanilla
import mods.betterfoliage.BetterFoliage import mods.betterfoliage.BetterFoliage
import mods.betterfoliage.render.lighting.withLighting import mods.betterfoliage.config.CACTUS_BLOCKS
import mods.betterfoliage.model.Color
import mods.betterfoliage.model.ModelWrapKey
import mods.betterfoliage.model.SpriteDelegate
import mods.betterfoliage.model.SpriteSetDelegate
import mods.betterfoliage.model.WrappedBakedModel
import mods.betterfoliage.model.buildTufts
import mods.betterfoliage.model.crossModelsRaw
import mods.betterfoliage.model.crossModelsTextured
import mods.betterfoliage.model.meshifyCutoutMipped
import mods.betterfoliage.model.meshifyStandard
import mods.betterfoliage.model.transform
import mods.betterfoliage.model.tuftModelSet
import mods.betterfoliage.model.tuftShapeSet
import mods.betterfoliage.render.lighting.grassTuftLighting import mods.betterfoliage.render.lighting.grassTuftLighting
import mods.betterfoliage.render.lighting.roundLeafLighting import mods.betterfoliage.render.lighting.roundLeafLighting
import mods.betterfoliage.render.lighting.withLighting
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.util.Atlas
import mods.betterfoliage.resource.discovery.* import mods.betterfoliage.util.LazyInvalidatable
import mods.betterfoliage.resource.model.* import mods.betterfoliage.util.Rotation
import mods.betterfoliage.util.* import mods.betterfoliage.util.get
import mods.betterfoliage.util.horizontalDirections
import mods.betterfoliage.util.randomD
import mods.betterfoliage.util.randomI
import net.fabricmc.fabric.api.renderer.v1.model.FabricBakedModel import net.fabricmc.fabric.api.renderer.v1.model.FabricBakedModel
import net.fabricmc.fabric.api.renderer.v1.render.RenderContext import net.fabricmc.fabric.api.renderer.v1.render.RenderContext
import net.minecraft.block.BlockState import net.minecraft.block.BlockState
import net.minecraft.block.CactusBlock import net.minecraft.block.Blocks
import net.minecraft.client.render.model.BakedModel import net.minecraft.client.render.model.BakedModel
import net.minecraft.client.render.model.BasicBakedModel
import net.minecraft.client.render.model.json.JsonUnbakedModel
import net.minecraft.util.Identifier import net.minecraft.util.Identifier
import net.minecraft.util.math.BlockPos import net.minecraft.util.math.BlockPos
import net.minecraft.util.math.Direction.DOWN import net.minecraft.util.math.Direction.DOWN
import net.minecraft.world.ExtendedBlockView import net.minecraft.world.BlockRenderView
import java.util.* import java.util.Random
import java.util.function.Consumer
import java.util.function.Supplier import java.util.function.Supplier
interface CactusKey : BlockRenderKey { object StandardCactusDiscovery : AbstractModelDiscovery() {
val cactusTop: Identifier override fun processModel(ctx: ModelDiscoveryContext) {
val cactusBottom: Identifier val model = ctx.getUnbaked()
val cactusSide: Identifier if (model is JsonUnbakedModel && ctx.blockState.block in CACTUS_BLOCKS) {
BetterFoliage.blockTypes.dirt.add(ctx.blockState)
ctx.addReplacement(StandardCactusKey)
ctx.sprites.add(StandardCactusModel.cactusCrossSprite)
} }
super.processModel(ctx)
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])
} }
} }
class CactusModel(val key: Key, wrapped: BakedModel) : WrappedBakedModel(wrapped), FabricBakedModel { object StandardCactusKey : ModelWrapKey() {
override fun bake(ctx: ModelBakingContext, wrapped: BasicBakedModel) = StandardCactusModel(meshifyCutoutMipped(wrapped))
}
class StandardCactusModel(wrapped: BakedModel) : WrappedBakedModel(wrapped), FabricBakedModel {
val crossModels by cactusCrossModels.delegate(key)
val armModels by cactusArmModels.delegate(key)
val armLighting = horizontalDirections.map { grassTuftLighting(it) } val armLighting = horizontalDirections.map { grassTuftLighting(it) }
val crossLighting = roundLeafLighting() val crossLighting = roundLeafLighting()
override fun emitBlockQuads(blockView: ExtendedBlockView, state: BlockState, pos: BlockPos, randomSupplier: Supplier<Random>, context: RenderContext) { override fun emitBlockQuads(blockView: BlockRenderView, state: BlockState, pos: BlockPos, randomSupplier: Supplier<Random>, context: RenderContext) {
(wrapped as FabricBakedModel).emitBlockQuads(blockView, state, pos, randomSupplier, context) (wrapped as FabricBakedModel).emitBlockQuads(blockView, state, pos, randomSupplier, context)
if (!BetterFoliage.config.enabled || !BetterFoliage.config.cactus.enabled) return if (!BetterFoliage.config.enabled || !BetterFoliage.config.cactus.enabled) return
@@ -54,36 +73,26 @@ class CactusModel(val key: Key, wrapped: BakedModel) : WrappedBakedModel(wrapped
val armSide = random.nextInt() and 3 val armSide = random.nextInt() and 3
context.withLighting(armLighting[armSide]) { context.withLighting(armLighting[armSide]) {
it.accept(armModels[armSide][random]) it.accept(cactusArmModels[armSide][random])
} }
context.withLighting(crossLighting) { context.withLighting(crossLighting) {
it.accept(crossModels[random]) it.accept(cactusCrossModels[random])
} }
} }
data class Key(
override val cactusTop: Identifier,
override val cactusBottom: Identifier,
override val cactusSide: Identifier
) : CactusKey {
override fun replace(model: BakedModel, state: BlockState) = CactusModel(this, meshifyStandard(model, state))
}
companion object { companion object {
val cactusCrossSprite by SpriteDelegate(Atlas.BLOCKS) { val cactusCrossSprite = Identifier(BetterFoliage.MOD_ID, "blocks/better_cactus")
Identifier(BetterFoliage.MOD_ID, "blocks/better_cactus")
}
val cactusArmSprites by SpriteSetDelegate(Atlas.BLOCKS) { idx -> val cactusArmSprites by SpriteSetDelegate(Atlas.BLOCKS) { idx ->
Identifier(BetterFoliage.MOD_ID, "blocks/better_cactus_arm_$idx") Identifier(BetterFoliage.MOD_ID, "blocks/better_cactus_arm_$idx")
} }
val cactusArmModels = LazyMap(BetterFoliage.modelReplacer) { key: CactusKey -> val cactusArmModels by LazyInvalidatable(BakeWrapperManager) {
val shapes = BetterFoliage.config.cactus.let { tuftShapeSet(0.8, 0.8, 0.8, 0.2) } 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 models = tuftModelSet(shapes, Color.white.asInt) { cactusArmSprites[randomI()] }
horizontalDirections.map { side -> horizontalDirections.map { side ->
models.transform { move(0.0625 to DOWN).rotate(Rotation.fromUp[side.ordinal]) }.buildTufts() models.transform { move(0.0625 to DOWN).rotate(Rotation.fromUp[side.ordinal]) }.buildTufts()
}.toTypedArray() }.toTypedArray()
} }
val cactusCrossModels = LazyMap(BetterFoliage.modelReplacer) { key: CactusKey -> val cactusCrossModels by LazyInvalidatable(BakeWrapperManager) {
val models = BetterFoliage.config.cactus.let { config -> val models = BetterFoliage.config.cactus.let { config ->
crossModelsRaw(64, config.size, 0.0, 0.0) crossModelsRaw(64, config.size, 0.0, 0.0)
.transform { rotateZ(randomD(-config.sizeVariation, config.sizeVariation)) } .transform { rotateZ(randomD(-config.sizeVariation, config.sizeVariation)) }

View File

@@ -2,42 +2,72 @@ package mods.betterfoliage.render.block.vanilla
import mods.betterfoliage.BetterFoliage import mods.betterfoliage.BetterFoliage
import mods.betterfoliage.chunk.BasicBlockCtx import mods.betterfoliage.chunk.BasicBlockCtx
import mods.betterfoliage.render.DIRT_BLOCKS import mods.betterfoliage.config.DIRT_BLOCKS
import mods.betterfoliage.render.SALTWATER_BIOMES import mods.betterfoliage.model.Color
import mods.betterfoliage.render.lighting.withLighting import mods.betterfoliage.model.ModelWrapKey
import mods.betterfoliage.model.SpriteSetDelegate
import mods.betterfoliage.model.WrappedBakedModel
import mods.betterfoliage.model.build
import mods.betterfoliage.model.meshifyStandard
import mods.betterfoliage.model.tuftModelSet
import mods.betterfoliage.model.tuftShapeSet
import mods.betterfoliage.model.withOpposites
import mods.betterfoliage.config.SALTWATER_BIOMES
import mods.betterfoliage.model.getUnderlyingModel
import mods.betterfoliage.model.meshifySolid
import mods.betterfoliage.render.ShadersModIntegration
import mods.betterfoliage.render.lighting.grassTuftLighting import mods.betterfoliage.render.lighting.grassTuftLighting
import mods.betterfoliage.render.lighting.reedLighting import mods.betterfoliage.render.lighting.reedLighting
import mods.betterfoliage.render.lighting.renderMasquerade import mods.betterfoliage.render.lighting.renderMasquerade
import mods.betterfoliage.util.Atlas import mods.betterfoliage.render.lighting.withLighting
import mods.betterfoliage.resource.discovery.BlockRenderKey import mods.betterfoliage.resource.discovery.AbstractModelDiscovery
import mods.betterfoliage.resource.discovery.ModelDiscoveryBase import mods.betterfoliage.resource.discovery.BakeWrapperManager
import mods.betterfoliage.resource.discovery.ModelBakingContext
import mods.betterfoliage.resource.discovery.ModelDiscoveryContext import mods.betterfoliage.resource.discovery.ModelDiscoveryContext
import mods.betterfoliage.resource.generated.CenteredSprite import mods.betterfoliage.resource.generated.CenteredSprite
import mods.betterfoliage.resource.model.* import mods.betterfoliage.util.Atlas
import mods.betterfoliage.util.* import mods.betterfoliage.util.Int3
import mods.betterfoliage.util.LazyInvalidatable
import mods.betterfoliage.util.get
import mods.betterfoliage.util.randomI
import net.fabricmc.fabric.api.renderer.v1.material.BlendMode
import net.fabricmc.fabric.api.renderer.v1.render.RenderContext import net.fabricmc.fabric.api.renderer.v1.render.RenderContext
import net.minecraft.block.BlockRenderLayer
import net.minecraft.block.BlockState import net.minecraft.block.BlockState
import net.minecraft.block.Blocks
import net.minecraft.block.Material import net.minecraft.block.Material
import net.minecraft.client.MinecraftClient import net.minecraft.client.render.RenderLayer
import net.minecraft.client.render.model.BakedModel import net.minecraft.client.render.model.BakedModel
import net.minecraft.client.render.model.BasicBakedModel
import net.minecraft.client.render.model.json.JsonUnbakedModel
import net.minecraft.util.Identifier import net.minecraft.util.Identifier
import net.minecraft.util.math.BlockPos import net.minecraft.util.math.BlockPos
import net.minecraft.util.math.Direction.UP import net.minecraft.util.math.Direction.UP
import net.minecraft.world.ExtendedBlockView import net.minecraft.world.BlockRenderView
import java.util.* import java.util.Random
import java.util.function.Consumer
import java.util.function.Supplier import java.util.function.Supplier
object DirtKey : BlockRenderKey { object StandardDirtDiscovery : AbstractModelDiscovery() {
override fun replace(model: BakedModel, state: BlockState) = DirtModel(meshifyStandard(model, state)) fun canRenderInLayer(layer: RenderLayer) = when {
!BetterFoliage.config.enabled -> layer == RenderLayer.getSolid()
(!BetterFoliage.config.connectedGrass.enabled &&
!BetterFoliage.config.algae.enabled &&
!BetterFoliage.config.reed.enabled
) -> layer == RenderLayer.getSolid()
else -> layer == RenderLayer.getCutoutMipped()
} }
object DirtDiscovery : ModelDiscoveryBase() { override fun processModel(ctx: ModelDiscoveryContext) {
override val logger = BetterFoliage.logDetail if (ctx.getUnbaked() is JsonUnbakedModel && ctx.blockState.block in DIRT_BLOCKS) {
BetterFoliage.blockTypes.dirt.add(ctx.blockState)
ctx.addReplacement(StandardDirtKey)
// RenderTypeLookup.setRenderLayer(ctx.blockState.block, ::canRenderInLayer)
}
super.processModel(ctx)
}
}
override fun processModel(ctx: ModelDiscoveryContext, atlas: Consumer<Identifier>) = object StandardDirtKey : ModelWrapKey() {
if (ctx.state.block in DIRT_BLOCKS) DirtKey else null override fun bake(ctx: ModelBakingContext, wrapped: BasicBakedModel) = DirtModel(meshifySolid(wrapped))
} }
class DirtModel(wrapped: BakedModel) : WrappedBakedModel(wrapped) { class DirtModel(wrapped: BakedModel) : WrappedBakedModel(wrapped) {
@@ -45,36 +75,46 @@ class DirtModel(wrapped: BakedModel) : WrappedBakedModel(wrapped) {
val algaeLighting = grassTuftLighting(UP) val algaeLighting = grassTuftLighting(UP)
val reedLighting = reedLighting() val reedLighting = reedLighting()
override fun emitBlockQuads(blockView: ExtendedBlockView, state: BlockState, pos: BlockPos, randomSupplier: Supplier<Random>, context: RenderContext) { override fun emitBlockQuads(
blockView: BlockRenderView,
state: BlockState,
pos: BlockPos,
randomSupplier: Supplier<Random>,
context: RenderContext
) {
if (!BetterFoliage.config.enabled) return super.emitBlockQuads(blockView, state, pos, randomSupplier, context) if (!BetterFoliage.config.enabled) return super.emitBlockQuads(blockView, state, pos, randomSupplier, context)
val ctx = BasicBlockCtx(blockView, pos) val ctx = BasicBlockCtx(blockView, pos)
val stateUp = ctx.offset(UP).state val stateUp = ctx.offset(UP).state
val keyUp = BetterFoliage.modelReplacer[stateUp] val isGrassUp = stateUp in BetterFoliage.blockTypes.grass
val isWater = stateUp.material == Material.WATER val isWater = stateUp.material == Material.WATER
val isDeepWater = isWater && ctx.offset(Int3(2 to UP)).state.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 isShallowWater = isWater && ctx.offset(Int3(2 to UP)).state.isAir
val isSaltWater = isWater && ctx.biome.category in SALTWATER_BIOMES val isSaltWater = isWater && ctx.biome?.category in SALTWATER_BIOMES
if (BetterFoliage.config.connectedGrass.enabled && keyUp is GrassKey) { val random = randomSupplier.get()
val grassBaseModel = (ctx.model(UP) as WrappedBakedModel).wrapped if (BetterFoliage.config.connectedGrass.enabled && isGrassUp) {
val grassBaseModel = getUnderlyingModel(ctx.model(UP), random)
context.renderMasquerade(grassBaseModel, blockView, stateUp, pos, randomSupplier, context) context.renderMasquerade(grassBaseModel, blockView, stateUp, pos, randomSupplier, context)
} else { } else {
super.emitBlockQuads(blockView, state, pos, randomSupplier, context) super.emitBlockQuads(blockView, state, pos, randomSupplier, context)
} }
val random = randomSupplier.get()
if (BetterFoliage.config.algae.enabled(random) && isDeepWater) { if (BetterFoliage.config.algae.enabled(random) && isDeepWater) {
ShadersModIntegration.grass(context, BetterFoliage.config.algae.shaderWind) {
context.withLighting(algaeLighting) { context.withLighting(algaeLighting) {
it.accept(algaeModels[random]) it.accept(algaeModels[random])
} }
}
} else if (BetterFoliage.config.reed.enabled(random) && isShallowWater && !isSaltWater) { } else if (BetterFoliage.config.reed.enabled(random) && isShallowWater && !isSaltWater) {
ShadersModIntegration.grass(context, BetterFoliage.config.reed.shaderWind) {
context.withLighting(reedLighting) { context.withLighting(reedLighting) {
it.accept(reedModels[random]) it.accept(reedModels[random])
} }
} }
} }
}
companion object { companion object {
val algaeSprites by SpriteSetDelegate(Atlas.BLOCKS) { idx -> val algaeSprites by SpriteSetDelegate(Atlas.BLOCKS) { idx ->
@@ -84,18 +124,19 @@ class DirtModel(wrapped: BakedModel) : WrappedBakedModel(wrapped) {
idFunc = { idx -> Identifier(BetterFoliage.MOD_ID, "blocks/better_reed_$idx") }, idFunc = { idx -> Identifier(BetterFoliage.MOD_ID, "blocks/better_reed_$idx") },
idRegister = { id -> CenteredSprite(id, aspectHeight = 2).register(BetterFoliage.generatedPack) } idRegister = { id -> CenteredSprite(id, aspectHeight = 2).register(BetterFoliage.generatedPack) }
) )
val algaeModels by LazyInvalidatable(BetterFoliage.modelReplacer) { val algaeModels by LazyInvalidatable(BakeWrapperManager) {
val shapes = BetterFoliage.config.algae.let { tuftShapeSet(it.size, it.heightMin, it.heightMax, it.hOffset) } val shapes =
BetterFoliage.config.algae.let { tuftShapeSet(it.size, it.heightMin, it.heightMax, it.hOffset) }
tuftModelSet(shapes, Color.white.asInt) { algaeSprites[randomI()] } tuftModelSet(shapes, Color.white.asInt) { algaeSprites[randomI()] }
.withOpposites() .withOpposites()
.build(BlockRenderLayer.CUTOUT_MIPPED, flatLighting = false) .build(BlendMode.CUTOUT_MIPPED, flatLighting = false)
} }
val reedModels by LazyInvalidatable(BetterFoliage.modelReplacer) { val reedModels by LazyInvalidatable(BakeWrapperManager) {
val shapes = BetterFoliage.config.reed.let { tuftShapeSet(2.0, it.heightMin, it.heightMax, it.hOffset) } val shapes = BetterFoliage.config.reed.let { tuftShapeSet(2.0, it.heightMin, it.heightMax, it.hOffset) }
tuftModelSet(shapes, Color.white.asInt) { reedSprites[randomI()] } tuftModelSet(shapes, Color.white.asInt) { reedSprites[randomI()] }
.withOpposites() .withOpposites()
.build(BlockRenderLayer.CUTOUT_MIPPED, flatLighting = false) .build(BlendMode.CUTOUT_MIPPED, flatLighting = false)
} }
} }
} }

View File

@@ -2,53 +2,54 @@ package mods.betterfoliage.render.block.vanilla
import mods.betterfoliage.BetterFoliage import mods.betterfoliage.BetterFoliage
import mods.betterfoliage.chunk.BasicBlockCtx import mods.betterfoliage.chunk.BasicBlockCtx
import mods.betterfoliage.render.SNOW_MATERIALS import mods.betterfoliage.config.BlockConfig
import mods.betterfoliage.render.lighting.withLighting import mods.betterfoliage.config.SNOW_MATERIALS
import mods.betterfoliage.render.ShadersModIntegration
import mods.betterfoliage.render.lighting.grassTuftLighting import mods.betterfoliage.render.lighting.grassTuftLighting
import mods.betterfoliage.util.Atlas import mods.betterfoliage.render.lighting.withLighting
import mods.betterfoliage.resource.discovery.* import mods.betterfoliage.resource.discovery.*
import mods.betterfoliage.resource.model.* import mods.betterfoliage.model.*
import mods.betterfoliage.util.* import mods.betterfoliage.util.*
import net.fabricmc.fabric.api.renderer.v1.material.BlendMode
import net.fabricmc.fabric.api.renderer.v1.model.FabricBakedModel import net.fabricmc.fabric.api.renderer.v1.model.FabricBakedModel
import net.fabricmc.fabric.api.renderer.v1.render.RenderContext import net.fabricmc.fabric.api.renderer.v1.render.RenderContext
import net.minecraft.block.BlockRenderLayer
import net.minecraft.block.BlockState import net.minecraft.block.BlockState
import net.minecraft.client.render.model.BakedModel import net.minecraft.client.render.model.BakedModel
import net.minecraft.tag.BlockTags import net.minecraft.client.render.model.BasicBakedModel
import net.minecraft.util.Identifier import net.minecraft.util.Identifier
import net.minecraft.util.math.BlockPos import net.minecraft.util.math.BlockPos
import net.minecraft.util.math.Direction.* import net.minecraft.util.math.Direction.*
import net.minecraft.world.ExtendedBlockView import net.minecraft.world.BlockRenderView
import java.util.* import java.util.*
import java.util.function.Consumer import java.util.function.Consumer
import java.util.function.Supplier 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?
}
object StandardGrassDiscovery : ConfigurableModelDiscovery() { object StandardGrassDiscovery : ConfigurableModelDiscovery() {
override val logger = BetterFoliage.logDetail
override val matchClasses: ConfigurableBlockMatcher get() = BetterFoliage.blockConfig.grassBlocks override val matchClasses: ConfigurableBlockMatcher get() = BetterFoliage.blockConfig.grassBlocks
override val modelTextures: List<ModelTextureList> get() = BetterFoliage.blockConfig.grassModels.modelList override val modelTextures: List<ModelTextureList> get() = BetterFoliage.blockConfig.grassModels.modelList
override fun processModel(state: BlockState, textures: List<String>, atlas: Consumer<Identifier>): BlockRenderKey? { override fun processModel(ctx: ModelDiscoveryContext, textureMatch: List<Identifier>) {
val grassId = Identifier(textures[0]) ctx.addReplacement(StandardGrassKey(textureMatch[0], null))
log(" block state $state") BetterFoliage.blockTypes.grass.add(ctx.blockState)
log(" texture $grassId")
return GrassBlockModel.Key(grassId, getAndLogColorOverride(grassId, Atlas.BLOCKS, BetterFoliage.config.shortGrass.saturationThreshold))
} }
} }
class GrassBlockModel(val key: Key, wrapped: BakedModel) : WrappedBakedModel(wrapped), FabricBakedModel { data class StandardGrassKey(
val grassLocation: Identifier,
val overrideColor: Color?
) : ModelWrapKey() {
val tintIndex: Int get() = if (overrideColor == null) 0 else -1
override fun bake(ctx: ModelBakingContext, wrapped: BasicBakedModel): BakedModel {
val grassSpriteColor = Atlas.BLOCKS[grassLocation].averageColor.let { hsb ->
logColorOverride(detailLogger, BetterFoliage.config.shortGrass.saturationThreshold, hsb)
hsb.colorOverride(BetterFoliage.config.shortGrass.saturationThreshold)
}
return StandardGrassModel(meshifyCutoutMipped(wrapped), this.copy(overrideColor = grassSpriteColor))
}
}
class StandardGrassModel(wrapped: BakedModel, val key: StandardGrassKey) : WrappedBakedModel(wrapped) {
val tuftNormal by grassTuftMeshesNormal.delegate(key) val tuftNormal by grassTuftMeshesNormal.delegate(key)
val tuftSnowed by grassTuftMeshesSnowed.delegate(key) val tuftSnowed by grassTuftMeshesSnowed.delegate(key)
@@ -56,7 +57,7 @@ class GrassBlockModel(val key: Key, wrapped: BakedModel) : WrappedBakedModel(wra
val tuftLighting = grassTuftLighting(UP) val tuftLighting = grassTuftLighting(UP)
override fun emitBlockQuads(blockView: ExtendedBlockView, state: BlockState, pos: BlockPos, randomSupplier: Supplier<Random>, context: RenderContext) { override fun emitBlockQuads(blockView: BlockRenderView, state: BlockState, pos: BlockPos, randomSupplier: Supplier<Random>, context: RenderContext) {
if (!BetterFoliage.config.enabled) return super.emitBlockQuads(blockView, state, pos, randomSupplier, context) if (!BetterFoliage.config.enabled) return super.emitBlockQuads(blockView, state, pos, randomSupplier, context)
val ctx = BasicBlockCtx(blockView, pos) val ctx = BasicBlockCtx(blockView, pos)
@@ -65,10 +66,8 @@ class GrassBlockModel(val key: Key, wrapped: BakedModel) : WrappedBakedModel(wra
val isSnowed = stateAbove.material in SNOW_MATERIALS val isSnowed = stateAbove.material in SNOW_MATERIALS
val connected = BetterFoliage.config.connectedGrass.enabled && val connected = BetterFoliage.config.connectedGrass.enabled &&
(!isSnowed || BetterFoliage.config.connectedGrass.snowEnabled) && ( (!isSnowed || BetterFoliage.config.connectedGrass.snowEnabled) &&
BlockTags.DIRT_LIKE.contains(stateBelow.block) || (stateBelow in BetterFoliage.blockTypes.dirt || stateBelow in BetterFoliage.blockTypes.grass)
BetterFoliage.modelReplacer.getTyped<GrassKey>(stateBelow) != null
)
val random = randomSupplier.get() val random = randomSupplier.get()
if (connected) { if (connected) {
@@ -78,17 +77,12 @@ class GrassBlockModel(val key: Key, wrapped: BakedModel) : WrappedBakedModel(wra
} }
if (BetterFoliage.config.shortGrass.enabled(random) && !ctx.isNeighborSolid(UP)) { if (BetterFoliage.config.shortGrass.enabled(random) && !ctx.isNeighborSolid(UP)) {
ShadersModIntegration.grass(context, BetterFoliage.config.shortGrass.shaderWind) {
context.withLighting(tuftLighting) { context.withLighting(tuftLighting) {
it.accept(if (isSnowed) tuftSnowed[random] else tuftNormal[random]) it.accept(if (isSnowed) tuftSnowed[random] else tuftNormal[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 { companion object {
@@ -98,24 +92,24 @@ class GrassBlockModel(val key: Key, wrapped: BakedModel) : WrappedBakedModel(wra
val grassTuftSpritesSnowed by SpriteSetDelegate(Atlas.BLOCKS) { idx -> val grassTuftSpritesSnowed by SpriteSetDelegate(Atlas.BLOCKS) { idx ->
Identifier(BetterFoliage.MOD_ID, "blocks/better_grass_long_$idx") Identifier(BetterFoliage.MOD_ID, "blocks/better_grass_long_$idx")
} }
val grassTuftShapes = LazyMap(BetterFoliage.modelReplacer) { key: GrassKey -> val grassTuftShapes = LazyMap(BakeWrapperManager) { key: StandardGrassKey ->
BetterFoliage.config.shortGrass.let { tuftShapeSet(it.size, it.heightMin, it.heightMax, it.hOffset) } BetterFoliage.config.shortGrass.let { tuftShapeSet(it.size, it.heightMin, it.heightMax, it.hOffset) }
} }
val grassTuftMeshesNormal = LazyMap(BetterFoliage.modelReplacer) { key: GrassKey -> val grassTuftMeshesNormal = LazyMap(BakeWrapperManager) { key: StandardGrassKey ->
tuftModelSet(grassTuftShapes[key], key.overrideColor) { idx -> grassTuftSpritesNormal[randomI()] } tuftModelSet(grassTuftShapes[key], key.tintIndex) { idx -> grassTuftSpritesNormal[randomI()] }
.withOpposites() .withOpposites()
.build(BlockRenderLayer.CUTOUT_MIPPED, flatLighting = false) .build(BlendMode.CUTOUT_MIPPED, flatLighting = false)
} }
val grassTuftMeshesSnowed = LazyMap(BetterFoliage.modelReplacer) { key: GrassKey -> val grassTuftMeshesSnowed = LazyMap(BakeWrapperManager) { key: StandardGrassKey ->
tuftModelSet(grassTuftShapes[key], Color.white.asInt) { idx -> grassTuftSpritesSnowed[randomI()] } tuftModelSet(grassTuftShapes[key], Color.white.asInt) { idx -> grassTuftSpritesSnowed[randomI()] }
.withOpposites() .withOpposites()
.build(BlockRenderLayer.CUTOUT_MIPPED, flatLighting = false) .build(BlendMode.CUTOUT_MIPPED, flatLighting = false)
} }
val grassFullBlockMeshes = LazyMap(BetterFoliage.modelReplacer) { key: GrassKey -> val grassFullBlockMeshes = LazyMap(BakeWrapperManager) { key: StandardGrassKey ->
Array(64) { fullCubeTextured(key.grassTopTexture, key.overrideColor) } Array(64) { fullCubeTextured(key.grassLocation, key.tintIndex) }
} }
val snowFullBlockMeshes by LazyInvalidatable(BetterFoliage.modelReplacer) { val snowFullBlockMeshes by LazyInvalidatable(BakeWrapperManager) {
Array(64) { fullCubeTextured(Identifier("block/snow"), Color.white.asInt) } Array(64) { fullCubeTextured(Identifier("block/snow"), -1) }
} }
} }
} }

View File

@@ -2,81 +2,94 @@ package mods.betterfoliage.render.block.vanilla
import mods.betterfoliage.BetterFoliage import mods.betterfoliage.BetterFoliage
import mods.betterfoliage.chunk.BasicBlockCtx import mods.betterfoliage.chunk.BasicBlockCtx
import mods.betterfoliage.render.SNOW_MATERIALS import mods.betterfoliage.config.SNOW_MATERIALS
import mods.betterfoliage.render.lighting.withLighting import mods.betterfoliage.model.Color
import mods.betterfoliage.model.ModelWrapKey
import mods.betterfoliage.model.SpriteSetDelegate
import mods.betterfoliage.model.WrappedBakedModel
import mods.betterfoliage.model.crossModelsRaw
import mods.betterfoliage.model.crossModelsTextured
import mods.betterfoliage.model.meshifyCutoutMipped
import mods.betterfoliage.render.ShadersModIntegration
import mods.betterfoliage.render.lighting.roundLeafLighting import mods.betterfoliage.render.lighting.roundLeafLighting
import mods.betterfoliage.render.lighting.withLighting
import mods.betterfoliage.render.particle.LeafParticleRegistry import mods.betterfoliage.render.particle.LeafParticleRegistry
import mods.betterfoliage.util.Atlas import mods.betterfoliage.resource.discovery.BakeWrapperManager
import mods.betterfoliage.resource.discovery.* 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.generated.GeneratedLeafSprite
import mods.betterfoliage.resource.model.* import mods.betterfoliage.util.Atlas
import mods.betterfoliage.util.* import mods.betterfoliage.util.LazyMap
import net.fabricmc.fabric.api.renderer.v1.model.FabricBakedModel import mods.betterfoliage.util.averageColor
import mods.betterfoliage.util.colorOverride
import mods.betterfoliage.util.get
import mods.betterfoliage.util.logColorOverride
import net.fabricmc.fabric.api.renderer.v1.render.RenderContext 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.BlockState
import net.minecraft.client.render.model.BakedModel import net.minecraft.client.render.model.BakedModel
import net.minecraft.client.render.model.BasicBakedModel
import net.minecraft.util.Identifier import net.minecraft.util.Identifier
import net.minecraft.util.math.BlockPos import net.minecraft.util.math.BlockPos
import net.minecraft.util.math.Direction.* import net.minecraft.util.math.Direction.UP
import net.minecraft.world.ExtendedBlockView import net.minecraft.world.BlockRenderView
import java.util.* import org.apache.logging.log4j.Level
import java.util.function.Consumer import java.util.Random
import java.util.function.Supplier import java.util.function.Supplier
interface LeafKey : BlockRenderKey { interface LeafBlockModel {
val roundLeafTexture: Identifier val key: LeafParticleKey
}
/** Type of the leaf block (configurable by user). */ interface LeafParticleKey {
val leafType: String val leafType: String
val overrideColor: Color?
/** Average color of the round leaf texture. */
val overrideColor: Int?
} }
object StandardLeafDiscovery : ConfigurableModelDiscovery() { object StandardLeafDiscovery : ConfigurableModelDiscovery() {
override val logger = BetterFoliage.logDetail
override val matchClasses: ConfigurableBlockMatcher get() = BetterFoliage.blockConfig.leafBlocks override val matchClasses: ConfigurableBlockMatcher get() = BetterFoliage.blockConfig.leafBlocks
override val modelTextures: List<ModelTextureList> get() = BetterFoliage.blockConfig.leafModels.modelList override val modelTextures: List<ModelTextureList> get() = BetterFoliage.blockConfig.leafModels.modelList
override fun processModel(state: BlockState, textures: List<String>, atlas: Consumer<Identifier>) = override fun processModel(ctx: ModelDiscoveryContext, textureMatch: List<Identifier>) {
defaultRegisterLeaf(Identifier(textures[0]), atlas) val leafType = LeafParticleRegistry.typeMappings.getType(textureMatch[0]) ?: "default"
val generated = GeneratedLeafSprite(textureMatch[0], leafType)
.register(BetterFoliage.generatedPack)
.apply { ctx.sprites.add(this) }
} detailLogger.log(Level.INFO, " particle $leafType")
ctx.addReplacement(StandardLeafKey(generated, leafType, null))
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
} }
} }
class NormalLeavesModel(val key: Key, wrapped: BakedModel) : WrappedBakedModel(wrapped), FabricBakedModel { data class StandardLeafKey(
val roundLeafTexture: Identifier,
override val leafType: String,
override val overrideColor: Color?
) : ModelWrapKey(), LeafParticleKey {
val tintIndex: Int get() = if (overrideColor == null) 0 else -1
override fun bake(ctx: ModelBakingContext, wrapped: BasicBakedModel): BakedModel {
val leafSpriteColor = Atlas.BLOCKS[roundLeafTexture].averageColor.let { hsb ->
logColorOverride(detailLogger, BetterFoliage.config.leaves.saturationThreshold, hsb)
hsb.colorOverride(BetterFoliage.config.leaves.saturationThreshold)
}
return StandardLeafModel(meshifyCutoutMipped(wrapped), this.copy(overrideColor = leafSpriteColor))
}
}
class StandardLeafModel(
wrapped: BakedModel,
override val key: StandardLeafKey
) : WrappedBakedModel(wrapped), LeafBlockModel {
val leafNormal by leafModelsNormal.delegate(key) val leafNormal by leafModelsNormal.delegate(key)
val leafSnowed by leafModelsSnowed.delegate(key) val leafSnowed by leafModelsSnowed.delegate(key)
val leafLighting = roundLeafLighting() val leafLighting = roundLeafLighting()
override fun emitBlockQuads(blockView: ExtendedBlockView, state: BlockState, pos: BlockPos, randomSupplier: Supplier<Random>, context: RenderContext) { override fun emitBlockQuads(blockView: BlockRenderView, state: BlockState, pos: BlockPos, randomSupplier: Supplier<Random>, context: RenderContext) {
ShadersModIntegration.leaves(context, BetterFoliage.config.leaves.shaderWind) {
super.emitBlockQuads(blockView, state, pos, randomSupplier, context) super.emitBlockQuads(blockView, state, pos, randomSupplier, context)
if (!BetterFoliage.config.enabled || !BetterFoliage.config.leaves.enabled) return if (!BetterFoliage.config.enabled || !BetterFoliage.config.leaves.enabled) return
@@ -90,27 +103,20 @@ class NormalLeavesModel(val key: Key, wrapped: BakedModel) : WrappedBakedModel(w
if (isSnowed) it.accept(leafSnowed[random]) if (isSnowed) it.accept(leafSnowed[random])
} }
} }
data class Key(
override val roundLeafTexture: Identifier,
override val leafType: String,
override val overrideColor: Int?
) : LeafKey {
override fun replace(model: BakedModel, state: BlockState) = NormalLeavesModel(this, meshifyStandard(model, state, renderLayerOverride = CUTOUT_MIPPED))
} }
companion object { companion object {
val leafSpritesSnowed by SpriteSetDelegate(Atlas.BLOCKS) { idx -> val leafSpritesSnowed by SpriteSetDelegate(Atlas.BLOCKS) { idx ->
Identifier(BetterFoliage.MOD_ID, "blocks/better_leaves_snowed_$idx") Identifier(BetterFoliage.MOD_ID, "blocks/better_leaves_snowed_$idx")
} }
val leafModelsBase = LazyMap(BetterFoliage.modelReplacer) { key: LeafKey -> val leafModelsBase = LazyMap(BakeWrapperManager) { key: StandardLeafKey ->
BetterFoliage.config.leaves.let { crossModelsRaw(64, it.size, it.hOffset, it.vOffset) } BetterFoliage.config.leaves.let { crossModelsRaw(64, it.size, it.hOffset, it.vOffset) }
} }
val leafModelsNormal = LazyMap(BetterFoliage.modelReplacer) { key: LeafKey -> val leafModelsNormal = LazyMap(BakeWrapperManager) { key: StandardLeafKey ->
crossModelsTextured(leafModelsBase[key], key.overrideColor, true) { Atlas.BLOCKS.atlas[key.roundLeafTexture]!! } crossModelsTextured(leafModelsBase[key], key.tintIndex, true) { key.roundLeafTexture }
} }
val leafModelsSnowed = LazyMap(BetterFoliage.modelReplacer) { key: LeafKey -> val leafModelsSnowed = LazyMap(BakeWrapperManager) { key: StandardLeafKey ->
crossModelsTextured(leafModelsBase[key], Color.white.asInt, false) { leafSpritesSnowed[it] } crossModelsTextured(leafModelsBase[key], Color.white.asInt, false) { leafSpritesSnowed[it].id }
} }
} }
} }

View File

@@ -1,45 +1,61 @@
package mods.betterfoliage.render.block.vanilla package mods.betterfoliage.render.block.vanilla
import mods.betterfoliage.BetterFoliage import mods.betterfoliage.BetterFoliage
import mods.betterfoliage.util.Atlas import mods.betterfoliage.model.Color
import mods.betterfoliage.resource.discovery.BlockRenderKey import mods.betterfoliage.model.ModelWrapKey
import mods.betterfoliage.resource.discovery.ModelDiscoveryBase import mods.betterfoliage.model.SpriteSetDelegate
import mods.betterfoliage.model.WrappedBakedModel
import mods.betterfoliage.model.buildTufts
import mods.betterfoliage.model.meshifyCutoutMipped
import mods.betterfoliage.model.meshifyStandard
import mods.betterfoliage.model.transform
import mods.betterfoliage.model.tuftModelSet
import mods.betterfoliage.model.tuftShapeSet
import mods.betterfoliage.render.ShadersModIntegration
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.ModelDiscoveryContext
import mods.betterfoliage.resource.discovery.RenderKeyFactory import mods.betterfoliage.util.Atlas
import mods.betterfoliage.resource.model.*
import mods.betterfoliage.util.LazyInvalidatable import mods.betterfoliage.util.LazyInvalidatable
import mods.betterfoliage.util.get 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.fabricmc.fabric.api.renderer.v1.render.RenderContext
import net.minecraft.block.BlockState import net.minecraft.block.BlockState
import net.minecraft.block.Blocks import net.minecraft.block.Blocks
import net.minecraft.client.render.model.BakedModel import net.minecraft.client.render.model.BakedModel
import net.minecraft.client.render.model.BasicBakedModel
import net.minecraft.client.render.model.json.JsonUnbakedModel
import net.minecraft.util.Identifier import net.minecraft.util.Identifier
import net.minecraft.util.math.BlockPos import net.minecraft.util.math.BlockPos
import net.minecraft.util.math.Direction.DOWN import net.minecraft.util.math.Direction.DOWN
import net.minecraft.world.ExtendedBlockView import net.minecraft.world.BlockRenderView
import java.util.* import java.util.Random
import java.util.function.Consumer
import java.util.function.Supplier import java.util.function.Supplier
object LilypadKey : BlockRenderKey { object StandardLilypadDiscovery : AbstractModelDiscovery() {
override fun replace(model: BakedModel, state: BlockState) = LilypadModel(meshifyStandard(model, state)) val LILYPAD_BLOCKS = listOf(Blocks.LILY_PAD)
override fun processModel(ctx: ModelDiscoveryContext) {
if (ctx.getUnbaked() is JsonUnbakedModel && ctx.blockState.block in LILYPAD_BLOCKS) {
ctx.addReplacement(StandardLilypadKey)
}
super.processModel(ctx)
}
} }
object LilyPadDiscovery : ModelDiscoveryBase() { object StandardLilypadKey : ModelWrapKey() {
override val logger = BetterFoliage.logDetail override fun bake(ctx: ModelBakingContext, wrapped: BasicBakedModel) = StandardLilypadModel(meshifyCutoutMipped(wrapped))
override fun processModel(ctx: ModelDiscoveryContext, atlas: Consumer<Identifier>) =
if (ctx.state.block == Blocks.LILY_PAD) LilypadKey else null
} }
class LilypadModel(wrapped: BakedModel) : WrappedBakedModel(wrapped) { class StandardLilypadModel(wrapped: BakedModel) : WrappedBakedModel(wrapped) {
override fun emitBlockQuads(blockView: ExtendedBlockView, state: BlockState, pos: BlockPos, randomSupplier: Supplier<Random>, context: RenderContext) { override fun emitBlockQuads(blockView: BlockRenderView, state: BlockState, pos: BlockPos, randomSupplier: Supplier<Random>, context: RenderContext) {
super.emitBlockQuads(blockView, state, pos, randomSupplier, context) super.emitBlockQuads(blockView, state, pos, randomSupplier, context)
if (!BetterFoliage.config.enabled || !BetterFoliage.config.lilypad.enabled) return if (!BetterFoliage.config.enabled || !BetterFoliage.config.lilypad.enabled) return
val random = randomSupplier.get() val random = randomSupplier.get()
ShadersModIntegration.grass(context, BetterFoliage.config.lilypad.shaderWind) {
context.meshConsumer().accept(lilypadRootModels[random]) context.meshConsumer().accept(lilypadRootModels[random])
}
if (random.nextInt(64) < BetterFoliage.config.lilypad.population) { if (random.nextInt(64) < BetterFoliage.config.lilypad.population) {
context.meshConsumer().accept(lilypadFlowerModels[random]) context.meshConsumer().accept(lilypadFlowerModels[random])
} }
@@ -52,13 +68,13 @@ class LilypadModel(wrapped: BakedModel) : WrappedBakedModel(wrapped) {
val lilypadFlowerSprites by SpriteSetDelegate(Atlas.BLOCKS) { idx -> val lilypadFlowerSprites by SpriteSetDelegate(Atlas.BLOCKS) { idx ->
Identifier(BetterFoliage.MOD_ID, "blocks/better_lilypad_flower_$idx") Identifier(BetterFoliage.MOD_ID, "blocks/better_lilypad_flower_$idx")
} }
val lilypadRootModels by LazyInvalidatable(BetterFoliage.modelReplacer) { val lilypadRootModels by LazyInvalidatable(BakeWrapperManager) {
val shapes = tuftShapeSet(1.0, 1.0, 1.0, BetterFoliage.config.lilypad.hOffset) val shapes = tuftShapeSet(1.0, 1.0, 1.0, BetterFoliage.config.lilypad.hOffset)
tuftModelSet(shapes, Color.white.asInt) { lilypadRootSprites[it] } tuftModelSet(shapes, Color.white.asInt) { lilypadRootSprites[it] }
.transform { move(2.0 to DOWN) } .transform { move(2.0 to DOWN) }
.buildTufts() .buildTufts()
} }
val lilypadFlowerModels by LazyInvalidatable(BetterFoliage.modelReplacer) { val lilypadFlowerModels by LazyInvalidatable(BakeWrapperManager) {
val shapes = tuftShapeSet(0.5, 0.5, 0.5, BetterFoliage.config.lilypad.hOffset) val shapes = tuftShapeSet(0.5, 0.5, 0.5, BetterFoliage.config.lilypad.hOffset)
tuftModelSet(shapes, Color.white.asInt) { lilypadFlowerSprites[it] } tuftModelSet(shapes, Color.white.asInt) { lilypadFlowerSprites[it] }
.transform { move(1.0 to DOWN) } .transform { move(1.0 to DOWN) }

View File

@@ -1,43 +1,62 @@
package mods.betterfoliage.render.block.vanilla package mods.betterfoliage.render.block.vanilla
import mods.betterfoliage.BetterFoliage import mods.betterfoliage.BetterFoliage
import mods.betterfoliage.render.lighting.withLighting import mods.betterfoliage.model.Color
import mods.betterfoliage.model.ModelWrapKey
import mods.betterfoliage.model.SpriteSetDelegate
import mods.betterfoliage.model.WrappedBakedModel
import mods.betterfoliage.model.buildTufts
import mods.betterfoliage.model.meshifyCutoutMipped
import mods.betterfoliage.model.meshifyStandard
import mods.betterfoliage.model.tuftModelSet
import mods.betterfoliage.model.tuftShapeSet
import mods.betterfoliage.render.ShadersModIntegration
import mods.betterfoliage.render.lighting.grassTuftLighting import mods.betterfoliage.render.lighting.grassTuftLighting
import mods.betterfoliage.util.Atlas import mods.betterfoliage.render.lighting.withLighting
import mods.betterfoliage.resource.discovery.BlockRenderKey import mods.betterfoliage.resource.discovery.AbstractModelDiscovery
import mods.betterfoliage.resource.discovery.ModelDiscoveryBase import mods.betterfoliage.resource.discovery.BakeWrapperManager
import mods.betterfoliage.resource.discovery.ModelBakingContext
import mods.betterfoliage.resource.discovery.ModelDiscoveryContext import mods.betterfoliage.resource.discovery.ModelDiscoveryContext
import mods.betterfoliage.resource.discovery.RenderKeyFactory import mods.betterfoliage.util.Atlas
import mods.betterfoliage.resource.model.* import mods.betterfoliage.util.LazyInvalidatable
import mods.betterfoliage.util.* import mods.betterfoliage.util.get
import mods.betterfoliage.util.offset
import mods.betterfoliage.util.plus
import mods.betterfoliage.util.randomI
import net.fabricmc.fabric.api.renderer.v1.render.RenderContext import net.fabricmc.fabric.api.renderer.v1.render.RenderContext
import net.minecraft.block.BlockState import net.minecraft.block.BlockState
import net.minecraft.block.Blocks import net.minecraft.block.Blocks
import net.minecraft.client.render.model.BakedModel import net.minecraft.client.render.model.BakedModel
import net.minecraft.client.render.model.BasicBakedModel
import net.minecraft.client.render.model.json.JsonUnbakedModel
import net.minecraft.util.Identifier import net.minecraft.util.Identifier
import net.minecraft.util.math.BlockPos import net.minecraft.util.math.BlockPos
import net.minecraft.util.math.Direction.UP import net.minecraft.util.math.Direction.UP
import net.minecraft.world.ExtendedBlockView import net.minecraft.world.BlockRenderView
import java.util.* import java.util.Random
import java.util.function.Consumer
import java.util.function.Supplier import java.util.function.Supplier
object MyceliumKey : BlockRenderKey { object StandardMyceliumDiscovery : AbstractModelDiscovery() {
override fun replace(model: BakedModel, state: BlockState) = MyceliumModel(meshifyStandard(model, state)) val MYCELIUM_BLOCKS = listOf(Blocks.MYCELIUM)
override fun processModel(ctx: ModelDiscoveryContext) {
if (ctx.getUnbaked() is JsonUnbakedModel && ctx.blockState.block in MYCELIUM_BLOCKS) {
ctx.addReplacement(StandardMyceliumKey)
// RenderTypeLookup.setRenderLayer(ctx.blockState.block, RenderType.getCutout())
}
super.processModel(ctx)
}
} }
object MyceliumDiscovery : ModelDiscoveryBase() { object StandardMyceliumKey : ModelWrapKey() {
override val logger = BetterFoliage.logDetail override fun bake(ctx: ModelBakingContext, wrapped: BasicBakedModel) = StandardMyceliumModel(meshifyCutoutMipped(wrapped))
val myceliumBlocks = listOf(Blocks.MYCELIUM)
override fun processModel(ctx: ModelDiscoveryContext, atlas: Consumer<Identifier>) =
if (ctx.state.block in myceliumBlocks) MyceliumKey else null
} }
class MyceliumModel(wrapped: BakedModel) : WrappedBakedModel(wrapped) { class StandardMyceliumModel(wrapped: BakedModel) : WrappedBakedModel(wrapped) {
val tuftLighting = grassTuftLighting(UP) val tuftLighting = grassTuftLighting(UP)
override fun emitBlockQuads(blockView: ExtendedBlockView, state: BlockState, pos: BlockPos, randomSupplier: Supplier<Random>, context: RenderContext) { override fun emitBlockQuads(blockView: BlockRenderView, state: BlockState, pos: BlockPos, randomSupplier: Supplier<Random>, context: RenderContext) {
super.emitBlockQuads(blockView, state, pos, randomSupplier, context) super.emitBlockQuads(blockView, state, pos, randomSupplier, context)
val random = randomSupplier.get() val random = randomSupplier.get()
@@ -45,17 +64,19 @@ class MyceliumModel(wrapped: BakedModel) : WrappedBakedModel(wrapped) {
BetterFoliage.config.shortGrass.let { it.myceliumEnabled && random.nextInt(64) < it.population } && BetterFoliage.config.shortGrass.let { it.myceliumEnabled && random.nextInt(64) < it.population } &&
blockView.getBlockState(pos + UP.offset).isAir blockView.getBlockState(pos + UP.offset).isAir
) { ) {
ShadersModIntegration.grass(context, BetterFoliage.config.shortGrass.shaderWind) {
context.withLighting(tuftLighting) { context.withLighting(tuftLighting) {
it.accept(myceliumTuftModels[random]) it.accept(myceliumTuftModels[random])
} }
} }
} }
}
companion object { companion object {
val myceliumTuftSprites by SpriteSetDelegate(Atlas.BLOCKS) { idx -> val myceliumTuftSprites by SpriteSetDelegate(Atlas.BLOCKS) { idx ->
Identifier(BetterFoliage.MOD_ID, "blocks/better_mycel_$idx") Identifier(BetterFoliage.MOD_ID, "blocks/better_mycel_$idx")
} }
val myceliumTuftModels by LazyInvalidatable(BetterFoliage.modelReplacer) { val myceliumTuftModels by LazyInvalidatable(BakeWrapperManager) {
val shapes = BetterFoliage.config.shortGrass.let { tuftShapeSet(it.size, it.heightMin, it.heightMax, it.hOffset) } val shapes = BetterFoliage.config.shortGrass.let { tuftShapeSet(it.size, it.heightMin, it.heightMax, it.hOffset) }
tuftModelSet(shapes, Color.white.asInt) { idx -> myceliumTuftSprites[randomI()] }.buildTufts() tuftModelSet(shapes, Color.white.asInt) { idx -> myceliumTuftSprites[randomI()] }.buildTufts()
} }

View File

@@ -1,44 +1,72 @@
package mods.betterfoliage.render.block.vanilla package mods.betterfoliage.render.block.vanilla
import mods.betterfoliage.BetterFoliage import mods.betterfoliage.BetterFoliage
import mods.betterfoliage.render.lighting.withLighting import mods.betterfoliage.config.NETHERRACK_BLOCKS
import mods.betterfoliage.model.Color
import mods.betterfoliage.model.ModelWrapKey
import mods.betterfoliage.model.SpriteSetDelegate
import mods.betterfoliage.model.WrappedBakedModel
import mods.betterfoliage.model.build
import mods.betterfoliage.model.meshifyCutoutMipped
import mods.betterfoliage.model.meshifyStandard
import mods.betterfoliage.model.transform
import mods.betterfoliage.model.tuftModelSet
import mods.betterfoliage.model.tuftShapeSet
import mods.betterfoliage.model.withOpposites
import mods.betterfoliage.render.lighting.grassTuftLighting import mods.betterfoliage.render.lighting.grassTuftLighting
import mods.betterfoliage.util.Atlas import mods.betterfoliage.render.lighting.withLighting
import mods.betterfoliage.resource.discovery.BlockRenderKey import mods.betterfoliage.resource.discovery.AbstractModelDiscovery
import mods.betterfoliage.resource.discovery.ModelDiscoveryBase import mods.betterfoliage.resource.discovery.BakeWrapperManager
import mods.betterfoliage.resource.discovery.ModelBakingContext
import mods.betterfoliage.resource.discovery.ModelDiscoveryContext import mods.betterfoliage.resource.discovery.ModelDiscoveryContext
import mods.betterfoliage.resource.discovery.RenderKeyFactory import mods.betterfoliage.util.Atlas
import mods.betterfoliage.resource.model.* import mods.betterfoliage.util.LazyInvalidatable
import mods.betterfoliage.util.* import mods.betterfoliage.util.Rotation
import mods.betterfoliage.util.get
import mods.betterfoliage.util.offset
import mods.betterfoliage.util.plus
import mods.betterfoliage.util.randomI
import net.fabricmc.fabric.api.renderer.v1.material.BlendMode
import net.fabricmc.fabric.api.renderer.v1.render.RenderContext import net.fabricmc.fabric.api.renderer.v1.render.RenderContext
import net.minecraft.block.BlockRenderLayer
import net.minecraft.block.BlockState import net.minecraft.block.BlockState
import net.minecraft.block.Blocks import net.minecraft.client.render.RenderLayer
import net.minecraft.client.render.model.BakedModel import net.minecraft.client.render.model.BakedModel
import net.minecraft.client.render.model.BasicBakedModel
import net.minecraft.client.render.model.json.JsonUnbakedModel
import net.minecraft.util.Identifier import net.minecraft.util.Identifier
import net.minecraft.util.math.BlockPos import net.minecraft.util.math.BlockPos
import net.minecraft.util.math.Direction.DOWN import net.minecraft.util.math.Direction.DOWN
import net.minecraft.world.ExtendedBlockView import net.minecraft.world.BlockRenderView
import java.util.* import java.util.Random
import java.util.function.Consumer
import java.util.function.Supplier import java.util.function.Supplier
object NetherrackKey : BlockRenderKey { object StandardNetherrackDiscovery : AbstractModelDiscovery() {
override fun replace(model: BakedModel, state: BlockState) = NetherrackModel(meshifyStandard(model, state))
fun canRenderInLayer(layer: RenderLayer) = when {
!BetterFoliage.config.enabled -> layer == RenderLayer.getSolid()
!BetterFoliage.config.netherrack.enabled -> layer == RenderLayer.getSolid()
else -> layer == RenderLayer.getCutoutMipped()
} }
object NetherrackDiscovery : ModelDiscoveryBase() { override fun processModel(ctx: ModelDiscoveryContext) {
override val logger = BetterFoliage.logDetail if (ctx.getUnbaked() is JsonUnbakedModel && ctx.blockState.block in NETHERRACK_BLOCKS) {
val netherrackBlocks = listOf(Blocks.NETHERRACK) BetterFoliage.blockTypes.dirt.add(ctx.blockState)
override fun processModel(ctx: ModelDiscoveryContext, atlas: Consumer<Identifier>) = ctx.addReplacement(StandardNetherrackKey)
if (ctx.state.block in netherrackBlocks) NetherrackKey else null // RenderTypeLookup.setRenderLayer(ctx.blockState.block, ::canRenderInLayer)
}
super.processModel(ctx)
}
} }
class NetherrackModel(wrapped: BakedModel) : WrappedBakedModel(wrapped) { object StandardNetherrackKey : ModelWrapKey() {
override fun bake(ctx: ModelBakingContext, wrapped: BasicBakedModel) = StandardNetherrackModel(meshifyCutoutMipped(wrapped))
}
class StandardNetherrackModel(wrapped: BakedModel) : WrappedBakedModel(wrapped) {
val tuftLighting = grassTuftLighting(DOWN) val tuftLighting = grassTuftLighting(DOWN)
override fun emitBlockQuads(blockView: ExtendedBlockView, state: BlockState, pos: BlockPos, randomSupplier: Supplier<Random>, context: RenderContext) { override fun emitBlockQuads(blockView: BlockRenderView, state: BlockState, pos: BlockPos, randomSupplier: Supplier<Random>, context: RenderContext) {
super.emitBlockQuads(blockView, state, pos, randomSupplier, context) super.emitBlockQuads(blockView, state, pos, randomSupplier, context)
if (BetterFoliage.config.enabled && if (BetterFoliage.config.enabled &&
BetterFoliage.config.netherrack.enabled && BetterFoliage.config.netherrack.enabled &&
@@ -55,12 +83,12 @@ class NetherrackModel(wrapped: BakedModel) : WrappedBakedModel(wrapped) {
val netherrackTuftSprites by SpriteSetDelegate(Atlas.BLOCKS) { idx -> val netherrackTuftSprites by SpriteSetDelegate(Atlas.BLOCKS) { idx ->
Identifier(BetterFoliage.MOD_ID, "blocks/better_netherrack_$idx") Identifier(BetterFoliage.MOD_ID, "blocks/better_netherrack_$idx")
} }
val netherrackTuftModels by LazyInvalidatable(BetterFoliage.modelReplacer) { val netherrackTuftModels by LazyInvalidatable(BakeWrapperManager) {
val shapes = BetterFoliage.config.netherrack.let { tuftShapeSet(it.size, it.heightMin, it.heightMax, it.hOffset) } val shapes = BetterFoliage.config.netherrack.let { tuftShapeSet(it.size, it.heightMin, it.heightMax, it.hOffset) }
tuftModelSet(shapes, Color.white.asInt) { netherrackTuftSprites[randomI()] } tuftModelSet(shapes, Color.white.asInt) { netherrackTuftSprites[randomI()] }
.transform { rotate(Rotation.fromUp[DOWN.ordinal]).rotateUV(2) } .transform { rotate(Rotation.fromUp[DOWN.ordinal]).rotateUV(2) }
.withOpposites() .withOpposites()
.build(BlockRenderLayer.CUTOUT_MIPPED, flatLighting = false) .build(BlendMode.CUTOUT_MIPPED, flatLighting = false)
} }
} }
} }

View File

@@ -1,41 +1,54 @@
package mods.betterfoliage.render.block.vanilla package mods.betterfoliage.render.block.vanilla
import mods.betterfoliage.BetterFoliage import mods.betterfoliage.BetterFoliage
import mods.betterfoliage.render.column.* import mods.betterfoliage.model.ModelWrapKey
import mods.betterfoliage.model.meshifySolid
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.util.Atlas
import mods.betterfoliage.resource.discovery.*
import mods.betterfoliage.resource.model.meshifyStandard
import mods.betterfoliage.util.LazyMap import mods.betterfoliage.util.LazyMap
import mods.betterfoliage.util.get
import mods.betterfoliage.util.tryDefault import mods.betterfoliage.util.tryDefault
import net.minecraft.block.BlockState import net.minecraft.block.BlockState
import net.minecraft.block.LogBlock import net.minecraft.block.PillarBlock
import net.minecraft.client.render.model.BakedModel import net.minecraft.client.render.model.BakedModel
import net.minecraft.client.texture.SpriteAtlasTexture import net.minecraft.client.render.model.BasicBakedModel
import net.minecraft.util.Identifier import net.minecraft.util.Identifier
import net.minecraft.util.math.Direction.Axis import net.minecraft.util.math.Direction.Axis
import java.util.function.Consumer import org.apache.logging.log4j.Level
interface RoundLogKey : ColumnBlockKey, ModelBakingKey {
val barkSprite: Identifier
val endSprite: Identifier
}
object RoundLogOverlayLayer : ColumnRenderLayer() { object RoundLogOverlayLayer : ColumnRenderLayer() {
override fun getColumnKey(state: BlockState) = BetterFoliage.modelReplacer.getTyped<ColumnBlockKey>(state) override fun getColumnKey(state: BlockState) = BetterFoliage.blockTypes.getTyped<ColumnBlockKey>(state)
override val connectSolids: Boolean get() = BetterFoliage.config.roundLogs.connectSolids override val connectSolids: Boolean get() = BetterFoliage.config.roundLogs.connectSolids
override val lenientConnect: Boolean get() = BetterFoliage.config.roundLogs.lenientConnect override val lenientConnect: Boolean get() = BetterFoliage.config.roundLogs.lenientConnect
override val defaultToY: Boolean get() = BetterFoliage.config.roundLogs.defaultY override val defaultToY: Boolean get() = BetterFoliage.config.roundLogs.defaultY
} }
object StandardLogDiscovery : ConfigurableModelDiscovery() { object StandardRoundLogDiscovery : ConfigurableModelDiscovery() {
override val logger = BetterFoliage.logDetail
override val matchClasses: ConfigurableBlockMatcher get() = BetterFoliage.blockConfig.logBlocks override val matchClasses: ConfigurableBlockMatcher get() = BetterFoliage.blockConfig.logBlocks
override val modelTextures: List<ModelTextureList> get() = BetterFoliage.blockConfig.logModels.modelList override val modelTextures: List<ModelTextureList> get() = BetterFoliage.blockConfig.logModels.modelList
override fun processModel(state: BlockState, textures: List<String>, atlas: Consumer<Identifier>): BlockRenderKey? { override fun processModel(ctx: ModelDiscoveryContext, textureMatch: List<Identifier>) {
val axis = getAxis(state) val axis = getAxis(ctx.blockState)
log(" axis $axis") detailLogger.log(Level.INFO, " axis $axis")
return RoundLogModel.Key(axis, Identifier(textures[0]), Identifier(textures[1])) ctx.addReplacement(StandardRoundLogKey(axis, textureMatch[0], textureMatch[1]))
} }
fun getAxis(state: BlockState): Axis? { fun getAxis(state: BlockState): Axis? {
val axis = tryDefault(null) { state.get(LogBlock.AXIS).toString() } ?: val axis = tryDefault(null) { state.get(PillarBlock.AXIS).toString() } ?:
state.entries.entries.find { it.key.getName().toLowerCase() == "axis" }?.value?.toString() state.entries.entries.find { it.key.getName().toLowerCase() == "axis" }?.value?.toString()
return when (axis) { return when (axis) {
"x" -> Axis.X "x" -> Axis.X
@@ -46,12 +59,15 @@ object StandardLogDiscovery : ConfigurableModelDiscovery() {
} }
} }
interface RoundLogKey : ColumnBlockKey, BlockRenderKey { data class StandardRoundLogKey(
val barkSprite: Identifier override val axis: Axis?,
val endSprite: Identifier override val barkSprite: Identifier,
override val endSprite: Identifier
) : RoundLogKey, ModelWrapKey() {
override fun bake(ctx: ModelBakingContext, wrapped: BasicBakedModel) = StandardRoundLogModel(meshifySolid(wrapped), this)
} }
class RoundLogModel(val key: Key, wrapped: BakedModel) : ColumnModelBase(wrapped) { class StandardRoundLogModel(wrapped: BakedModel, val key: StandardRoundLogKey) : ColumnModelBase(wrapped) {
override val enabled: Boolean get() = BetterFoliage.config.enabled && BetterFoliage.config.roundLogs.enabled override val enabled: Boolean get() = BetterFoliage.config.enabled && BetterFoliage.config.roundLogs.enabled
override val overlayLayer: ColumnRenderLayer get() = RoundLogOverlayLayer override val overlayLayer: ColumnRenderLayer get() = RoundLogOverlayLayer
override val connectPerpendicular: Boolean get() = BetterFoliage.config.roundLogs.connectPerpendicular override val connectPerpendicular: Boolean get() = BetterFoliage.config.roundLogs.connectPerpendicular
@@ -59,18 +75,10 @@ class RoundLogModel(val key: Key, wrapped: BakedModel) : ColumnModelBase(wrapped
val modelSet by modelSets.delegate(key) val modelSet by modelSets.delegate(key)
override fun getMeshSet(axis: Axis, quadrant: Int) = modelSet override fun getMeshSet(axis: Axis, quadrant: Int) = modelSet
data class Key(
override val axis: Axis?,
override val barkSprite: Identifier,
override val endSprite: Identifier
) : RoundLogKey {
override fun replace(model: BakedModel, state: BlockState) = RoundLogModel(this, meshifyStandard(model, state))
}
companion object { companion object {
val modelSets = LazyMap(BetterFoliage.modelReplacer) { key: Key -> val modelSets = LazyMap(BakeWrapperManager) { key: StandardRoundLogKey ->
val barkSprite = Atlas.BLOCKS.atlas[key.barkSprite]!! val barkSprite = Atlas.BLOCKS[key.barkSprite]!!
val endSprite = Atlas.BLOCKS.atlas[key.endSprite]!! val endSprite = Atlas.BLOCKS[key.endSprite]!!
BetterFoliage.config.roundLogs.let { config -> BetterFoliage.config.roundLogs.let { config ->
ColumnMeshSet( ColumnMeshSet(
config.radiusSmall, config.radiusLarge, config.zProtection, config.radiusSmall, config.radiusLarge, config.zProtection,

View File

@@ -2,52 +2,78 @@ package mods.betterfoliage.render.block.vanilla
import mods.betterfoliage.BetterFoliage import mods.betterfoliage.BetterFoliage
import mods.betterfoliage.chunk.CachedBlockCtx import mods.betterfoliage.chunk.CachedBlockCtx
import mods.betterfoliage.render.SALTWATER_BIOMES import mods.betterfoliage.config.SALTWATER_BIOMES
import mods.betterfoliage.render.SAND_BLOCKS import mods.betterfoliage.config.SAND_BLOCKS
import mods.betterfoliage.render.lighting.withLighting import mods.betterfoliage.model.Color
import mods.betterfoliage.model.ModelWrapKey
import mods.betterfoliage.model.SpriteSetDelegate
import mods.betterfoliage.model.WrappedBakedModel
import mods.betterfoliage.model.build
import mods.betterfoliage.model.horizontalRectangle
import mods.betterfoliage.model.meshifySolid
import mods.betterfoliage.model.meshifyStandard
import mods.betterfoliage.model.transform
import mods.betterfoliage.model.tuftModelSet
import mods.betterfoliage.model.tuftShapeSet
import mods.betterfoliage.model.withOpposites
import mods.betterfoliage.render.lighting.grassTuftLighting import mods.betterfoliage.render.lighting.grassTuftLighting
import mods.betterfoliage.util.Atlas import mods.betterfoliage.render.lighting.withLighting
import mods.betterfoliage.resource.discovery.BlockRenderKey import mods.betterfoliage.resource.discovery.AbstractModelDiscovery
import mods.betterfoliage.resource.discovery.ModelDiscoveryBase import mods.betterfoliage.resource.discovery.BakeWrapperManager
import mods.betterfoliage.resource.discovery.ModelBakingContext
import mods.betterfoliage.resource.discovery.ModelDiscoveryContext import mods.betterfoliage.resource.discovery.ModelDiscoveryContext
import mods.betterfoliage.resource.model.* import mods.betterfoliage.util.Atlas
import mods.betterfoliage.util.* import mods.betterfoliage.util.LazyInvalidatable
import mods.betterfoliage.util.Rotation
import mods.betterfoliage.util.allDirections
import mods.betterfoliage.util.get
import mods.betterfoliage.util.randomB
import mods.betterfoliage.util.randomD
import mods.betterfoliage.util.randomI
import net.fabricmc.fabric.api.renderer.v1.material.BlendMode
import net.fabricmc.fabric.api.renderer.v1.render.RenderContext 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.BlockState
import net.minecraft.block.Blocks
import net.minecraft.block.Material import net.minecraft.block.Material
import net.minecraft.client.render.model.BakedModel import net.minecraft.client.render.model.BakedModel
import net.minecraft.client.render.model.BasicBakedModel
import net.minecraft.client.render.model.json.JsonUnbakedModel
import net.minecraft.util.Identifier import net.minecraft.util.Identifier
import net.minecraft.util.math.BlockPos import net.minecraft.util.math.BlockPos
import net.minecraft.util.math.Direction.UP import net.minecraft.util.math.Direction.UP
import net.minecraft.world.ExtendedBlockView import net.minecraft.world.BlockRenderView
import java.util.* import java.util.Random
import java.util.function.Consumer
import java.util.function.Supplier import java.util.function.Supplier
object SandKey : BlockRenderKey { object StandardSandDiscovery : AbstractModelDiscovery() {
override fun replace(model: BakedModel, state: BlockState) = SandModel(meshifyStandard(model, state))
override fun processModel(ctx: ModelDiscoveryContext) {
if (ctx.getUnbaked() is JsonUnbakedModel && ctx.blockState.block in SAND_BLOCKS) {
BetterFoliage.blockTypes.dirt.add(ctx.blockState)
ctx.addReplacement(StandardSandKey)
// RenderTypeLookup.setRenderLayer(ctx.blockState.block, RenderType.getCutoutMipped())
}
super.processModel(ctx)
}
} }
object SandDiscovery : ModelDiscoveryBase() { object StandardSandKey : ModelWrapKey() {
override val logger = BetterFoliage.logDetail override fun bake(ctx: ModelBakingContext, wrapped: BasicBakedModel) = StandardSandModel(meshifySolid(wrapped))
override fun processModel(ctx: ModelDiscoveryContext, atlas: Consumer<Identifier>) =
if (ctx.state.block in SAND_BLOCKS) SandKey else null
} }
class SandModel(wrapped: BakedModel) : WrappedBakedModel(wrapped) { class StandardSandModel(wrapped: BakedModel) : WrappedBakedModel(wrapped) {
val coralLighting = allDirections.map { grassTuftLighting(it) }.toTypedArray() val coralLighting = allDirections.map { grassTuftLighting(it) }.toTypedArray()
override fun emitBlockQuads(blockView: ExtendedBlockView, state: BlockState, pos: BlockPos, randomSupplier: Supplier<Random>, context: RenderContext) { override fun emitBlockQuads(blockView: BlockRenderView, state: BlockState, pos: BlockPos, randomSupplier: Supplier<Random>, context: RenderContext) {
super.emitBlockQuads(blockView, state, pos, randomSupplier, context) super.emitBlockQuads(blockView, state, pos, randomSupplier, context)
val ctx = CachedBlockCtx(blockView, pos) val ctx = CachedBlockCtx(blockView, pos)
val random = randomSupplier.get() val random = randomSupplier.get()
if (!BetterFoliage.config.enabled || !BetterFoliage.config.coral.enabled(random)) return if (!BetterFoliage.config.enabled || !BetterFoliage.config.coral.enabled(random)) return
if (ctx.biome.category !in SALTWATER_BIOMES) return if (ctx.biome?.category !in SALTWATER_BIOMES) return
allDirections.filter { random.nextInt(64) < BetterFoliage.config.coral.chance }.forEach { face -> allDirections.filter { random.nextInt(64) < BetterFoliage.config.coral.chance }.forEach { face ->
val isWater = ctx.state(face).material == Material.WATER val isWater = ctx.state(face).material == Material.WATER
@@ -70,16 +96,16 @@ class SandModel(wrapped: BakedModel) : WrappedBakedModel(wrapped) {
val coralCrustSprites by SpriteSetDelegate(Atlas.BLOCKS) { idx -> val coralCrustSprites by SpriteSetDelegate(Atlas.BLOCKS) { idx ->
Identifier(BetterFoliage.MOD_ID, "blocks/better_crust_$idx") Identifier(BetterFoliage.MOD_ID, "blocks/better_crust_$idx")
} }
val coralTuftModels by LazyInvalidatable(BetterFoliage.modelReplacer) { val coralTuftModels by LazyInvalidatable(BakeWrapperManager) {
val shapes = BetterFoliage.config.coral.let { tuftShapeSet(it.size, 1.0, 1.0, it.hOffset) } val shapes = BetterFoliage.config.coral.let { tuftShapeSet(it.size, 1.0, 1.0, it.hOffset) }
allDirections.map { face -> allDirections.map { face ->
tuftModelSet(shapes, Color.white.asInt) { coralTuftSprites[randomI()] } tuftModelSet(shapes, Color.white.asInt) { coralTuftSprites[randomI()] }
.transform { rotate(Rotation.fromUp[face]) } .transform { rotate(Rotation.fromUp[face]) }
.withOpposites() .withOpposites()
.build(CUTOUT_MIPPED) .build(BlendMode.CUTOUT_MIPPED)
}.toTypedArray() }.toTypedArray()
} }
val coralCrustModels by LazyInvalidatable(BetterFoliage.modelReplacer) { val coralCrustModels by LazyInvalidatable(BakeWrapperManager) {
allDirections.map { face -> allDirections.map { face ->
Array(64) { idx -> Array(64) { idx ->
listOf(horizontalRectangle(x1 = -0.5, x2 = 0.5, z1 = -0.5, z2 = 0.5, y = 0.0) listOf(horizontalRectangle(x1 = -0.5, x2 = 0.5, z1 = -0.5, z2 = 0.5, y = 0.0)
@@ -88,7 +114,7 @@ class SandModel(wrapped: BakedModel) : WrappedBakedModel(wrapped) {
.rotate(Rotation.fromUp[face]) .rotate(Rotation.fromUp[face])
.mirrorUV(randomB(), randomB()).rotateUV(randomI(max = 4)) .mirrorUV(randomB(), randomB()).rotateUV(randomI(max = 4))
.sprite(coralCrustSprites[idx]).colorAndIndex(null) .sprite(coralCrustSprites[idx]).colorAndIndex(null)
).build(CUTOUT_MIPPED) ).build(BlendMode.CUTOUT_MIPPED)
} }
}.toTypedArray() }.toTypedArray()
} }

View File

@@ -2,13 +2,25 @@ package mods.betterfoliage.render.column
import mods.betterfoliage.BetterFoliage import mods.betterfoliage.BetterFoliage
import mods.betterfoliage.render.column.ColumnLayerData.SpecialRender.QuadrantType import mods.betterfoliage.render.column.ColumnLayerData.SpecialRender.QuadrantType
import mods.betterfoliage.render.column.ColumnLayerData.SpecialRender.QuadrantType.* import mods.betterfoliage.render.column.ColumnLayerData.SpecialRender.QuadrantType.INVISIBLE
import mods.betterfoliage.resource.model.* 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.model.Color
import mods.betterfoliage.model.Quad
import mods.betterfoliage.model.UV
import mods.betterfoliage.model.Vertex
import mods.betterfoliage.model.build
import mods.betterfoliage.model.horizontalRectangle
import mods.betterfoliage.model.verticalRectangle
import mods.betterfoliage.util.Double3 import mods.betterfoliage.util.Double3
import mods.betterfoliage.util.Rotation import mods.betterfoliage.util.Rotation
import net.minecraft.block.BlockRenderLayer.SOLID import net.fabricmc.fabric.api.renderer.v1.material.BlendMode.SOLID
import net.minecraft.client.texture.Sprite import net.minecraft.client.texture.Sprite
import net.minecraft.util.math.Direction.* import net.minecraft.util.math.Direction.Axis
import net.minecraft.util.math.Direction.EAST
import net.minecraft.util.math.Direction.SOUTH
import net.minecraft.util.math.Direction.UP
/** /**
* Collection of dynamically generated meshes used to render rounded columns. * Collection of dynamically generated meshes used to render rounded columns.

View File

@@ -5,13 +5,13 @@ import mods.betterfoliage.chunk.ChunkOverlayManager
import mods.betterfoliage.render.column.ColumnLayerData.NormalRender import mods.betterfoliage.render.column.ColumnLayerData.NormalRender
import mods.betterfoliage.render.column.ColumnLayerData.SpecialRender.BlockType.* import mods.betterfoliage.render.column.ColumnLayerData.SpecialRender.BlockType.*
import mods.betterfoliage.render.column.ColumnLayerData.SpecialRender.QuadrantType.* import mods.betterfoliage.render.column.ColumnLayerData.SpecialRender.QuadrantType.*
import mods.betterfoliage.resource.model.WrappedBakedModel import mods.betterfoliage.model.WrappedBakedModel
import net.fabricmc.fabric.api.renderer.v1.render.RenderContext import net.fabricmc.fabric.api.renderer.v1.render.RenderContext
import net.minecraft.block.BlockState import net.minecraft.block.BlockState
import net.minecraft.client.render.model.BakedModel import net.minecraft.client.render.model.BakedModel
import net.minecraft.util.math.BlockPos import net.minecraft.util.math.BlockPos
import net.minecraft.util.math.Direction.Axis import net.minecraft.util.math.Direction.Axis
import net.minecraft.world.ExtendedBlockView import net.minecraft.world.BlockRenderView
import java.util.* import java.util.*
import java.util.function.Supplier import java.util.function.Supplier
@@ -21,7 +21,8 @@ abstract class ColumnModelBase(wrapped: BakedModel) : WrappedBakedModel(wrapped)
abstract val connectPerpendicular: Boolean abstract val connectPerpendicular: Boolean
abstract fun getMeshSet(axis: Axis, quadrant: Int): ColumnMeshSet abstract fun getMeshSet(axis: Axis, quadrant: Int): ColumnMeshSet
override fun emitBlockQuads(blockView: ExtendedBlockView, state: BlockState, pos: BlockPos, randomSupplier: Supplier<Random>, context: RenderContext) { override fun emitBlockQuads(blockView: BlockRenderView, state: BlockState, pos: BlockPos, randomSupplier: Supplier<Random>, context: RenderContext) {
if (!enabled) return super.emitBlockQuads(blockView, state, pos, randomSupplier, context)
val ctx = CachedBlockCtx(blockView, pos) val ctx = CachedBlockCtx(blockView, pos)
val roundLog = ChunkOverlayManager.get(overlayLayer, ctx) val roundLog = ChunkOverlayManager.get(overlayLayer, ctx)

View File

@@ -1,19 +1,31 @@
package mods.betterfoliage.render.column package mods.betterfoliage.render.column
import mods.betterfoliage.BetterFoliage import mods.betterfoliage.BetterFoliage
import mods.betterfoliage.chunk.BlockCtx
import mods.betterfoliage.chunk.ChunkOverlayLayer import mods.betterfoliage.chunk.ChunkOverlayLayer
import mods.betterfoliage.chunk.ChunkOverlayManager import mods.betterfoliage.chunk.ChunkOverlayManager
import mods.betterfoliage.chunk.dimType import mods.betterfoliage.chunk.dimType
import mods.betterfoliage.render.column.ColumnLayerData.SpecialRender.BlockType.* import mods.betterfoliage.render.block.vanilla.RoundLogKey
import mods.betterfoliage.render.column.ColumnLayerData.SpecialRender.BlockType.NONSOLID
import mods.betterfoliage.render.column.ColumnLayerData.SpecialRender.BlockType.PARALLEL
import mods.betterfoliage.render.column.ColumnLayerData.SpecialRender.BlockType.PERPENDICULAR
import mods.betterfoliage.render.column.ColumnLayerData.SpecialRender.BlockType.SOLID
import mods.betterfoliage.render.column.ColumnLayerData.SpecialRender.QuadrantType import mods.betterfoliage.render.column.ColumnLayerData.SpecialRender.QuadrantType
import mods.betterfoliage.render.column.ColumnLayerData.SpecialRender.QuadrantType.* import mods.betterfoliage.render.column.ColumnLayerData.SpecialRender.QuadrantType.INVISIBLE
import mods.betterfoliage.chunk.BlockCtx import mods.betterfoliage.render.column.ColumnLayerData.SpecialRender.QuadrantType.LARGE_RADIUS
import mods.betterfoliage.util.* 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.get
import mods.betterfoliage.util.plus
import net.minecraft.block.BlockState import net.minecraft.block.BlockState
import net.minecraft.util.math.BlockPos import net.minecraft.util.math.BlockPos
import net.minecraft.util.math.Direction.Axis import net.minecraft.util.math.Direction.Axis
import net.minecraft.util.math.Direction.AxisDirection import net.minecraft.util.math.Direction.AxisDirection
import net.minecraft.world.ExtendedBlockView import net.minecraft.world.WorldView
/** Index of SOUTH-EAST quadrant. */ /** Index of SOUTH-EAST quadrant. */
const val SE = 0 const val SE = 0
@@ -73,13 +85,14 @@ abstract class ColumnRenderLayer : ChunkOverlayLayer<ColumnLayerData> {
val allNeighborOffsets = (-1..1).flatMap { offsetX -> (-1..1).flatMap { offsetY -> (-1..1).map { offsetZ -> Int3(offsetX, offsetY, offsetZ) }}} val allNeighborOffsets = (-1..1).flatMap { offsetX -> (-1..1).flatMap { offsetY -> (-1..1).map { offsetZ -> Int3(offsetX, offsetY, offsetZ) }}}
override fun onBlockUpdate(world: ExtendedBlockView, pos: BlockPos) { override fun onBlockUpdate(world: WorldView, pos: BlockPos) {
allNeighborOffsets.forEach { offset -> ChunkOverlayManager.clear(world.dimType, this, pos + offset) } allNeighborOffsets.forEach { offset -> ChunkOverlayManager.clear(world.dimType, this, pos + offset) }
} }
override fun calculate(ctx: BlockCtx): ColumnLayerData { override fun calculate(ctx: BlockCtx): ColumnLayerData {
if (allDirections.all { ctx.offset(it).isNormalCube }) return ColumnLayerData.SkipRender if (allDirections.all { dir ->
// val columnTextures = registry[ctx] ?: return ColumnLayerData.ResolveError ctx.offset(dir).let { it.isNormalCube && !BetterFoliage.blockTypes.hasTyped<RoundLogKey>(it.state) }
}) return ColumnLayerData.SkipRender
val columnTextures = getColumnKey(ctx.state) ?: return ColumnLayerData.ResolveError val columnTextures = getColumnKey(ctx.state) ?: return ColumnLayerData.ResolveError
// if log axis is not defined and "Default to vertical" config option is not set, render normally // if log axis is not defined and "Default to vertical" config option is not set, render normally

View File

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

View File

@@ -0,0 +1,102 @@
package mods.betterfoliage.render.particle
import mods.betterfoliage.util.Double3
import net.minecraft.client.MinecraftClient
import net.minecraft.client.particle.SpriteBillboardParticle
import net.minecraft.client.render.Camera
import net.minecraft.client.render.VertexConsumer
import net.minecraft.client.texture.Sprite
import net.minecraft.client.util.math.Vector3f
import net.minecraft.client.world.ClientWorld
import net.minecraft.util.math.MathHelper
import net.minecraft.world.World
abstract class AbstractParticle(world: ClientWorld, 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;
}
/** 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(vertexConsumer: VertexConsumer, camera: Camera, tickDelta: Float) {
renderParticleQuad(vertexConsumer, 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: VertexConsumer,
camera: Camera,
tickDelta: Float,
currentPos: Double3 = this.currentPos,
prevPos: Double3 = this.prevPos,
size: Double = scale.toDouble(),
currentAngle: Float = this.angle,
prevAngle: Float = this.prevAngle,
sprite: Sprite = this.sprite,
alpha: Float = this.colorAlpha) {
val center = Double3.lerp(tickDelta.toDouble(), prevPos, currentPos)
val angle = MathHelper.lerp(tickDelta, prevAngle, currentAngle)
val rotation = camera.rotation.copy().apply { hamiltonProduct(Vector3f.POSITIVE_Z.getRadialQuaternion(angle)) }
val lightmapCoord = getColorMultiplier(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.pos.x, camera.pos.y, camera.pos.z) }
fun renderVertex(vertex: Double3, u: Float, v: Float) = vertexConsumer
.vertex(vertex.x, vertex.y, vertex.z).texture(u, v)
.color(colorRed, colorGreen, colorBlue, alpha).light(lightmapCoord)
.next()
renderVertex(coords[0], sprite.maxU, sprite.maxV)
renderVertex(coords[1], sprite.maxU, sprite.minV)
renderVertex(coords[2], sprite.minU, sprite.minV)
renderVertex(coords[3], sprite.minU, sprite.maxV)
}
fun setColor(color: Int) {
colorBlue = (color and 255) / 256.0f
colorGreen = ((color shr 8) and 255) / 256.0f
colorRed = ((color shr 16) and 255) / 256.0f
}
}

View File

@@ -2,23 +2,27 @@ package mods.betterfoliage.render.particle
import mods.betterfoliage.BetterFoliage import mods.betterfoliage.BetterFoliage
import mods.betterfoliage.ClientWorldLoadCallback import mods.betterfoliage.ClientWorldLoadCallback
import mods.betterfoliage.render.AbstractParticle import mods.betterfoliage.render.block.vanilla.LeafParticleKey
import mods.betterfoliage.render.block.vanilla.LeafKey import mods.betterfoliage.util.Double3
import mods.betterfoliage.util.* 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.fabricmc.fabric.api.event.world.WorldTickCallback import net.fabricmc.fabric.api.event.world.WorldTickCallback
import net.minecraft.client.MinecraftClient import net.minecraft.client.particle.ParticleTextureSheet
import net.minecraft.client.render.BufferBuilder
import net.minecraft.client.world.ClientWorld import net.minecraft.client.world.ClientWorld
import net.minecraft.util.math.BlockPos import net.minecraft.util.math.BlockPos
import net.minecraft.util.math.MathHelper import net.minecraft.util.math.MathHelper
import net.minecraft.world.World import net.minecraft.world.World
import java.util.* import java.util.Random
import kotlin.math.abs import kotlin.math.abs
import kotlin.math.cos import kotlin.math.cos
import kotlin.math.sin import kotlin.math.sin
class FallingLeafParticle( class FallingLeafParticle(
world: World, pos: BlockPos, leafKey: LeafKey world: ClientWorld, pos: BlockPos, leaf: LeafParticleKey, blockColor: Int, random: Random
) : AbstractParticle( ) : AbstractParticle(
world, pos.x.toDouble() + 0.5, pos.y.toDouble(), pos.z.toDouble() + 0.5 world, pos.x.toDouble() + 0.5, pos.y.toDouble(), pos.z.toDouble() + 0.5
) { ) {
@@ -27,32 +31,34 @@ class FallingLeafParticle(
@JvmStatic val biomeBrightnessMultiplier = 0.5f @JvmStatic val biomeBrightnessMultiplier = 0.5f
} }
var rotationSpeed = randomF(min = PI2 / 80.0, max = PI2 / 50.0) var rotationSpeed = random.randomF(min = PI2 / 80.0, max = PI2 / 50.0)
var rotPositive = true
val isMirrored = randomB() val isMirrored = randomB()
var wasCollided = false var wasCollided = false
init { init {
angle = randomF(max = PI2) angle = random.randomF(max = PI2)
prevAngle = angle - rotationSpeed
maxAge = MathHelper.floor(randomD(0.6, 1.0) * BetterFoliage.config.fallingLeaves.lifetime * 20.0) maxAge = MathHelper.floor(randomD(0.6, 1.0) * BetterFoliage.config.fallingLeaves.lifetime * 20.0)
velocityY = -BetterFoliage.config.fallingLeaves.speed velocityY = -BetterFoliage.config.fallingLeaves.speed
scale = BetterFoliage.config.fallingLeaves.size.toFloat() * 0.1f scale = BetterFoliage.config.fallingLeaves.size.toFloat() * 0.1f
val state = world.getBlockState(pos) val state = world.getBlockState(pos)
val blockColor = MinecraftClient.getInstance().blockColorMap.getColorMultiplier(state, world, pos, 0)
sprite = LeafParticleRegistry[leafKey.leafType][randomI(max = 1024)] setColor(leaf.overrideColor?.asInt ?: blockColor)
setParticleColor(leafKey.overrideColor, blockColor) sprite = LeafParticleRegistry[leaf.leafType][randomI(max = 1024)]
} }
override val isValid: Boolean get() = (sprite != null) override val isValid: Boolean get() = (sprite != null)
override fun update() { override fun update() {
if (randomF() > 0.95f) rotPositive = !rotPositive if (randomF() > 0.95f) rotationSpeed = -rotationSpeed
// if (age > maxAge - 20) colorAlpha = 0.05f * (maxAge - age) if (age > maxAge - 20) colorAlpha = 0.05f * (maxAge - age)
if (onGround || wasCollided) { if (onGround || wasCollided) {
velocity.setTo(0.0, 0.0, 0.0) velocity.setTo(0.0, 0.0, 0.0)
prevAngle = angle
if (!wasCollided) { if (!wasCollided) {
age = age.coerceAtLeast(maxAge - 20) age = age.coerceAtLeast(maxAge - 20)
wasCollided = true wasCollided = true
@@ -61,19 +67,19 @@ class FallingLeafParticle(
val cosRotation = cos(angle).toDouble(); val sinRotation = sin(angle).toDouble() val cosRotation = cos(angle).toDouble(); val sinRotation = sin(angle).toDouble()
velocity.setTo(cosRotation, 0.0, sinRotation).mul(BetterFoliage.config.fallingLeaves.perturb) velocity.setTo(cosRotation, 0.0, sinRotation).mul(BetterFoliage.config.fallingLeaves.perturb)
.add(LeafWindTracker.current).add(0.0, -1.0, 0.0).mul(BetterFoliage.config.fallingLeaves.speed) .add(LeafWindTracker.current).add(0.0, -1.0, 0.0).mul(BetterFoliage.config.fallingLeaves.speed)
angle += if (rotPositive) rotationSpeed else -rotationSpeed prevAngle = angle
angle += 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) { fun setParticleColor(overrideColor: Int?, blockColor: Int) {
val color = overrideColor ?: blockColor val color = overrideColor ?: blockColor
setColor(color) setColor(color)
} }
override fun getType() =
if (BetterFoliage.config.fallingLeaves.opacityHack) ParticleTextureSheet.PARTICLE_SHEET_OPAQUE
else ParticleTextureSheet.PARTICLE_SHEET_TRANSLUCENT
} }
object LeafWindTracker : WorldTickCallback, ClientWorldLoadCallback { object LeafWindTracker : WorldTickCallback, ClientWorldLoadCallback {

View File

@@ -1,42 +1,76 @@
package mods.betterfoliage.render.particle package mods.betterfoliage.render.particle
import mods.betterfoliage.BetterFoliage import mods.betterfoliage.BetterFoliage
import mods.betterfoliage.resource.model.FixedSpriteSet import mods.betterfoliage.model.FixedSpriteSet
import mods.betterfoliage.resource.model.SpriteSet import mods.betterfoliage.model.SpriteSet
import mods.betterfoliage.util.* import mods.betterfoliage.resource.VeryEarlyReloadListener
import 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.fabricmc.fabric.api.event.client.ClientSpriteRegistryCallback import net.fabricmc.fabric.api.event.client.ClientSpriteRegistryCallback
import net.minecraft.client.texture.MissingSprite
import net.minecraft.client.texture.SpriteAtlasTexture import net.minecraft.client.texture.SpriteAtlasTexture
import net.minecraft.resource.ResourceManager
import net.minecraft.util.Identifier import net.minecraft.util.Identifier
import org.apache.logging.log4j.Level
import kotlin.collections.MutableList
import kotlin.collections.distinct
import kotlin.collections.filter
import kotlin.collections.firstOrNull
import kotlin.collections.forEach
import kotlin.collections.isNotEmpty
import kotlin.collections.joinToString
import kotlin.collections.listOf
import kotlin.collections.map
import kotlin.collections.mutableListOf
import kotlin.collections.mutableMapOf
import kotlin.collections.plus
import kotlin.collections.set
object LeafParticleRegistry : ClientSpriteRegistryCallback { object LeafParticleRegistry : HasLogger(), ClientSpriteRegistryCallback, VeryEarlyReloadListener {
val typeMappings = TextureMatcher() 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 spriteSets = mutableMapOf<String, SpriteSet>()
override fun registerSprites(atlasTexture: SpriteAtlasTexture, registry: ClientSpriteRegistryCallback.Registry) { override fun getFabricId() = Identifier(BetterFoliage.MOD_ID, "leaf-particles")
ids.clear()
spriteSets.clear() override fun onReloadStarted(resourceManager: ResourceManager) {
typeMappings.loadMappings(Identifier(BetterFoliage.MOD_ID, "leaf_texture_mappings.cfg")) typeMappings.loadMappings(Identifier(BetterFoliage.MOD_ID, "leaf_texture_mappings.cfg"))
(typeMappings.mappings.map { it.type } + "default").distinct().forEach { leafType -> detailLogger.log(Level.INFO, "Loaded leaf particle mappings, types = [${allTypes.joinToString(", ")}]")
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 override fun registerSprites(atlasTexture: SpriteAtlasTexture, registry: ClientSpriteRegistryCallback.Registry) {
spriteSets.clear()
allTypes.forEach { leafType ->
val validIds = (0 until 16).map { idx -> Identifier(BetterFoliage.MOD_ID, "particle/falling_leaf_${leafType}_$idx") }
.filter { resourceManager.containsResource(Atlas.PARTICLES.file(it)) }
validIds.forEach { registry.register(it) } validIds.forEach { registry.register(it) }
} }
} }
operator fun get(type: String): SpriteSet { operator fun get(leafType: String): SpriteSet {
spriteSets[type]?.let { return it } spriteSets[leafType]?.let { return it }
ids[type]?.let {
return FixedSpriteSet(Atlas.PARTICLES, it).apply { spriteSets[type] = this } val sprites = (0 until 16)
} .map { idx -> Identifier(BetterFoliage.MOD_ID, "particle/falling_leaf_${leafType}_$idx") }
return if (type == "default") FixedSpriteSet(Atlas.PARTICLES, emptyList()).apply { spriteSets[type] = this } .map { Atlas.PARTICLES[it] }
else get("default") .filter { it !is MissingSprite }
detailLogger.log(Level.INFO, "Leaf particle type [$leafType], ${sprites.size} sprites in atlas")
if (sprites.isNotEmpty()) return FixedSpriteSet(sprites).apply { spriteSets[leafType] = this }
return if (leafType == "default")
FixedSpriteSet(listOf(Atlas.PARTICLES[MissingSprite.getMissingSpriteId()])).apply { spriteSets[leafType] = this }
else
get("default").apply { spriteSets[leafType] = this }
} }
init { init {
ClientSpriteRegistryCallback.event(SpriteAtlasTexture.PARTICLE_ATLAS_TEX).register(this) ClientSpriteRegistryCallback.event(SpriteAtlasTexture.PARTICLE_ATLAS_TEXTURE).register(this)
} }
} }

View File

@@ -1,27 +1,33 @@
package mods.betterfoliage.render.particle package mods.betterfoliage.render.particle
import mods.betterfoliage.BetterFoliage import mods.betterfoliage.BetterFoliage
import mods.betterfoliage.render.AbstractParticle import mods.betterfoliage.model.SpriteDelegate
import mods.betterfoliage.resource.model.SpriteDelegate import mods.betterfoliage.model.SpriteSetDelegate
import mods.betterfoliage.resource.model.SpriteSetDelegate import mods.betterfoliage.util.Atlas
import mods.betterfoliage.util.* 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.ParticleTextureSheet import net.minecraft.client.particle.ParticleTextureSheet
import net.minecraft.client.render.BufferBuilder import net.minecraft.client.render.Camera
import net.minecraft.client.render.VertexConsumer
import net.minecraft.client.world.ClientWorld
import net.minecraft.util.Identifier import net.minecraft.util.Identifier
import net.minecraft.util.math.BlockPos import net.minecraft.util.math.BlockPos
import net.minecraft.util.math.MathHelper import net.minecraft.util.math.MathHelper
import net.minecraft.world.World import java.util.Deque
import java.util.* import java.util.LinkedList
import kotlin.math.cos import kotlin.math.cos
import kotlin.math.sin import kotlin.math.sin
class RisingSoulParticle( class RisingSoulParticle(
world: World, pos: BlockPos world: ClientWorld, pos: BlockPos
) : AbstractParticle( ) : AbstractParticle(
world, pos.x.toDouble() + 0.5, pos.y.toDouble() + 1.0, pos.z.toDouble() + 0.5 world, pos.x.toDouble() + 0.5, pos.y.toDouble() + 1.0, pos.z.toDouble() + 0.5
) { ) {
val particleTrail: Deque<Double3> = LinkedList<Double3>() val particleTrail: Deque<Double3> = LinkedList()
val initialPhase = randomD(max = PI2) val initialPhase = randomD(max = PI2)
init { init {
@@ -35,7 +41,8 @@ class RisingSoulParticle(
override fun update() { override fun update() {
val phase = initialPhase + (age.toDouble() * PI2 / 64.0) val phase = initialPhase + (age.toDouble() * PI2 / 64.0)
val cosPhase = cos(phase); val sinPhase = sin(phase) val cosPhase = cos(phase);
val sinPhase = sin(phase)
velocity.setTo(BetterFoliage.config.risingSoul.perturb.let { Double3(cosPhase * it, 0.1, sinPhase * it) }) velocity.setTo(BetterFoliage.config.risingSoul.perturb.let { Double3(cosPhase * it, 0.1, sinPhase * it) })
particleTrail.addFirst(currentPos.copy()) particleTrail.addFirst(currentPos.copy())
@@ -44,11 +51,12 @@ class RisingSoulParticle(
if (!BetterFoliage.config.enabled) markDead() if (!BetterFoliage.config.enabled) markDead()
} }
override fun render(worldRenderer: BufferBuilder, partialTickTime: Float) { override fun buildGeometry(vertexConsumer: VertexConsumer, camera: Camera, tickDelta: Float) {
var alpha = BetterFoliage.config.risingSoul.opacity.toFloat() var alpha = BetterFoliage.config.risingSoul.opacity.toFloat()
if (age > maxAge - 40) alpha *= (maxAge - age) / 40.0f if (age > maxAge - 40) alpha *= (maxAge - age) / 40.0f
renderParticleQuad(worldRenderer, partialTickTime, renderParticleQuad(
vertexConsumer, camera, tickDelta,
size = BetterFoliage.config.risingSoul.headSize * 0.25, size = BetterFoliage.config.risingSoul.headSize * 0.25,
alpha = alpha alpha = alpha
) )
@@ -57,7 +65,9 @@ class RisingSoulParticle(
particleTrail.forEachPairIndexed { idx, current, previous -> particleTrail.forEachPairIndexed { idx, current, previous ->
scale *= BetterFoliage.config.risingSoul.sizeDecay scale *= BetterFoliage.config.risingSoul.sizeDecay
alpha *= BetterFoliage.config.risingSoul.opacityDecay.toFloat() alpha *= BetterFoliage.config.risingSoul.opacityDecay.toFloat()
if (idx % BetterFoliage.config.risingSoul.trailDensity == 0) renderParticleQuad(worldRenderer, partialTickTime, if (idx % BetterFoliage.config.risingSoul.trailDensity == 0)
renderParticleQuad(
vertexConsumer, camera, tickDelta,
currentPos = current, currentPos = current,
prevPos = previous, prevPos = previous,
size = scale, size = scale,
@@ -70,7 +80,9 @@ class RisingSoulParticle(
override fun getType() = ParticleTextureSheet.PARTICLE_SHEET_TRANSLUCENT override fun getType() = ParticleTextureSheet.PARTICLE_SHEET_TRANSLUCENT
companion object { companion object {
val headIcons by SpriteSetDelegate(Atlas.PARTICLES) { idx -> Identifier(BetterFoliage.MOD_ID, "rising_soul_$idx") } val headIcons by SpriteSetDelegate(Atlas.PARTICLES) { idx ->
val trackIcon by SpriteDelegate(Atlas.PARTICLES) { Identifier(BetterFoliage.MOD_ID, "soul_track") } Identifier(BetterFoliage.MOD_ID, "particle/rising_soul_$idx")
}
val trackIcon by SpriteDelegate(Atlas.PARTICLES) { Identifier(BetterFoliage.MOD_ID, "particle/soul_track") }
} }
} }

View File

@@ -0,0 +1,28 @@
package mods.betterfoliage.resource
import net.fabricmc.fabric.api.resource.IdentifiableResourceReloadListener
import net.minecraft.resource.ResourceManager
import net.minecraft.resource.ResourceReloadListener
import net.minecraft.util.profiler.Profiler
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 : ResourceReloadListener, IdentifiableResourceReloadListener {
override fun reload(
synchronizer: ResourceReloadListener.Synchronizer,
resourceManager: ResourceManager,
preparationsProfiler: Profiler,
reloadProfiler: Profiler,
backgroundExecutor: Executor,
gameExecutor: Executor
): CompletableFuture<Void> {
onReloadStarted(resourceManager)
return synchronizer.whenPrepared(null)
}
fun onReloadStarted(resourceManager: ResourceManager) {}
}

View File

@@ -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)
}
}

View File

@@ -0,0 +1,124 @@
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 net.fabricmc.fabric.api.event.client.ClientSpriteRegistryCallback
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.ModelBakeSettings
import net.minecraft.client.render.model.ModelLoader
import net.minecraft.client.render.model.UnbakedModel
import net.minecraft.client.texture.Sprite
import net.minecraft.client.texture.SpriteAtlasTexture
import net.minecraft.client.util.SpriteIdentifier
import net.minecraft.resource.ResourceManager
import net.minecraft.util.Identifier
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 ModelDiscoveryContext(
val bakery: ModelLoader,
val blockState: BlockState,
val modelLocation: Identifier,
val sprites: MutableSet<Identifier>,
val replacements: MutableMap<Identifier, ModelBakingKey>,
val logger: Logger
) {
fun getUnbaked(location: Identifier = modelLocation) = bakery.getOrLoadModel(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")
}
}
interface ModelDiscovery {
fun onModelsLoaded(
bakery: ModelLoader,
sprites: MutableSet<Identifier>,
replacements: MutableMap<Identifier, ModelBakingKey>
)
}
data class ModelBakingContext(
val bakery: ModelLoader,
val spriteGetter: Function<SpriteIdentifier, Sprite>,
val location: Identifier,
val transform: ModelBakeSettings,
val logger: Logger
) {
fun getUnbaked() = bakery.getOrLoadModel(location)
fun getBaked() = bakery.bake(location, transform)
}
interface ModelBakingKey {
fun bake(ctx: ModelBakingContext): BakedModel? =
ctx.getUnbaked().bake(ctx.bakery, ctx.spriteGetter, ctx.transform, ctx.location)
}
object BakeWrapperManager : HasLogger(), Invalidator, ModelLoadingCallback, ClientSpriteRegistryCallback, BlockModelsReloadCallback {
init {
ModelLoadingCallback.EVENT.register(this)
ClientSpriteRegistryCallback.event(SpriteAtlasTexture.BLOCK_ATLAS_TEXTURE).register(this)
}
val discoverers = mutableListOf<ModelDiscovery>()
override val callbacks = mutableListOf<WeakReference<()->Unit>>()
private val replacements = mutableMapOf<Identifier, ModelBakingKey>()
private val sprites = mutableSetOf<Identifier>()
override fun beginLoadModels(loader: ModelLoader, manager: ResourceManager) {
val startTime = System.currentTimeMillis()
replacements.clear()
sprites.clear()
invalidate()
BetterFoliage.blockTypes = BlockTypeCache()
logger.log(INFO, "starting model discovery (${discoverers.size} listeners)")
discoverers.forEach { listener ->
val replacementsLocal = mutableMapOf<Identifier, ModelBakingKey>()
listener.onModelsLoaded(loader, sprites, replacements)
}
val elapsed = System.currentTimeMillis() - startTime
logger.log(INFO, "finished model discovery in $elapsed ms, ${replacements.size} top-level replacements")
}
override fun registerSprites(atlas: SpriteAtlasTexture, registry: ClientSpriteRegistryCallback.Registry) {
logger.log(INFO, "Adding ${sprites.size} sprites to block atlas")
sprites.forEach { registry.register(it) }
sprites.clear()
}
override fun reloadBlockModels(blockModels: BlockModels) {
replacements.clear()
}
fun onBake(
unbaked: UnbakedModel,
bakery: ModelLoader,
spriteGetter: Function<SpriteIdentifier, Sprite>,
transform: ModelBakeSettings,
location: Identifier
): BakedModel? {
val ctx = ModelBakingContext(bakery, spriteGetter, location, transform, detailLogger)
// bake replacement if available
replacements[location]?.let { replacement ->
detailLogger.log(INFO, "Baking replacement for [${unbaked::class.java.simpleName}] $location -> $replacement")
try {
return replacement.bake(ctx)
} catch (e: Exception) {
detailLogger.log(WARN, "Error while baking $replacement", e)
logger.log(WARN, "Error while baking $replacement", e)
}
}
return unbaked.bake(bakery, spriteGetter, transform, location)
}
}

View File

@@ -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> getTyped(state: BlockState) = stateKeys[state] as? T
inline fun <reified T> hasTyped(state: BlockState) = stateKeys[state] is T
}

View File

@@ -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
}
}

View File

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

View File

@@ -0,0 +1,99 @@
package mods.betterfoliage.resource.discovery
import com.google.common.base.Joiner
import mods.betterfoliage.util.HasLogger
import mods.betterfoliage.util.YarnHelper
import mods.betterfoliage.util.get
import net.minecraft.block.Block
import net.minecraft.client.render.block.BlockModels
import net.minecraft.client.render.model.ModelLoader
import net.minecraft.client.render.model.json.JsonUnbakedModel
import net.minecraft.client.render.model.json.WeightedUnbakedModel
import net.minecraft.client.texture.MissingSprite
import net.minecraft.util.Identifier
import net.minecraft.util.registry.Registry
import org.apache.logging.log4j.Level
abstract class AbstractModelDiscovery : HasLogger(), ModelDiscovery {
override fun onModelsLoaded(
bakery: ModelLoader,
sprites: MutableSet<Identifier>,
replacements: MutableMap<Identifier, ModelBakingKey>
) {
(Registry.BLOCK as Iterable<Block>)
.flatMap { block -> block.stateManager.states }
.forEach { state ->
val location = BlockModels.getModelId(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 WeightedUnbakedModel) {
// 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<Identifier, ModelBakingKey>()
model.variants.forEach { variant ->
processModel(ctx.copy(modelLocation = variant.location, 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<Identifier>
)
override fun processModel(ctx: ModelDiscoveryContext) {
val model = ctx.getUnbaked()
if (model is JsonUnbakedModel) {
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}")
modelTextures
.filter { matcher -> ctx.bakery.modelDerivesFrom(model, ctx.modelLocation, matcher.modelLocation) }
.forEach { match ->
detailLogger.log(Level.INFO, " model $model matches ${match.modelLocation}")
val materials = match.textureNames.map { it to model.resolveSprite(it) }
val texMapString = Joiner.on(", ").join(materials.map { "${it.first}=${it.second.textureId}" })
detailLogger.log(Level.INFO, " sprites [$texMapString]")
if (materials.all { it.second.textureId != MissingSprite.getMissingSpriteId() }) {
// found a valid model (all required textures exist)
processModel(ctx, materials.map { it.second.textureId })
}
}
}
return super.processModel(ctx)
}
}
// 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 ModelLoader.modelDerivesFrom(model: JsonUnbakedModel, location: Identifier, target: Identifier): Boolean =
if (location == target) true
else model[JsonUnbakedModel_parentId]
?.let { getOrLoadModel(it) as? JsonUnbakedModel }
?.let { parent -> modelDerivesFrom(parent, model[JsonUnbakedModel_parentId]!!, target) }
?: false

View File

@@ -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?
}

View File

@@ -0,0 +1,61 @@
package mods.betterfoliage.resource.discovery
import mods.betterfoliage.model.WeightedModelWrapper
import mods.betterfoliage.model.WrappedBakedModel
import mods.betterfoliage.model.WrappedMeshModel
import mods.betterfoliage.util.HasLogger
import net.fabricmc.fabric.api.renderer.v1.material.BlendMode
import net.minecraft.client.render.model.BakedModel
import net.minecraft.client.render.model.BasicBakedModel
import net.minecraft.client.render.model.json.WeightedUnbakedModel
import net.minecraft.util.Identifier
import org.apache.logging.log4j.Level.INFO
import org.apache.logging.log4j.Level.WARN
class WeightedUnbakedKey(
val replacements: Map<Identifier, ModelBakingKey>
) : ModelBakingKey {
override fun bake(ctx: ModelBakingContext): BakedModel? {
val unbaked = ctx.getUnbaked()
if (unbaked !is WeightedUnbakedModel) return super.bake(ctx)
// bake all variants, replace as needed
val bakedModels = unbaked.variants.mapNotNull {
val variantCtx = ctx.copy(location = it.location, transform = it)
val replacement = replacements[it.location]
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 WrappedBakedModel -> 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 BasicBakedModel -> it to WrappedMeshModel.converter(
state = null, unshade = false, noDiffuse = true, blendModeOverride = BlendMode.CUTOUT_MIPPED
).convert(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() = "[WeightedUnbakedKey, ${replacements.size} replacements]"
companion object : HasLogger()
}

View File

@@ -13,7 +13,7 @@ data class CenteredSprite(val sprite: Identifier, val atlas: Atlas = Atlas.BLOCK
fun register(pack: GeneratedBlockTexturePack) = pack.register(this, this::draw) fun register(pack: GeneratedBlockTexturePack) = pack.register(this, this::draw)
fun draw(resourceManager: ResourceManager): ByteArray { fun draw(resourceManager: ResourceManager): ByteArray {
val baseTexture = resourceManager.loadSprite(atlas.wrap(sprite)) val baseTexture = resourceManager.loadSprite(atlas.file(sprite))
val frameWidth = baseTexture.width val frameWidth = baseTexture.width
val frameHeight = baseTexture.width * aspectHeight / aspectWidth val frameHeight = baseTexture.width * aspectHeight / aspectWidth

View File

@@ -3,23 +3,20 @@ package mods.betterfoliage.resource.generated
import mods.betterfoliage.util.Atlas import mods.betterfoliage.util.Atlas
import mods.betterfoliage.util.HasLogger import mods.betterfoliage.util.HasLogger
import net.fabricmc.fabric.api.resource.IdentifiableResourceReloadListener import net.fabricmc.fabric.api.resource.IdentifiableResourceReloadListener
import net.minecraft.client.resource.ClientResourcePackContainer
import net.minecraft.resource.* import net.minecraft.resource.*
import net.minecraft.resource.ResourcePackContainer.InsertionPosition
import net.minecraft.resource.ResourceType.CLIENT_RESOURCES import net.minecraft.resource.ResourceType.CLIENT_RESOURCES
import net.minecraft.resource.metadata.ResourceMetadataReader import net.minecraft.resource.metadata.ResourceMetadataReader
import net.minecraft.text.LiteralText import net.minecraft.text.LiteralText
import net.minecraft.util.Identifier import net.minecraft.util.Identifier
import net.minecraft.util.profiler.Profiler import net.minecraft.util.profiler.Profiler
import org.apache.logging.log4j.Logger import org.apache.logging.log4j.Level.INFO
import java.io.IOException import java.io.IOException
import java.lang.IllegalStateException
import java.util.* import java.util.*
import java.util.concurrent.CompletableFuture import java.util.concurrent.CompletableFuture
import java.util.concurrent.ExecutionException import java.util.concurrent.ExecutionException
import java.util.concurrent.Executor import java.util.concurrent.Executor
import java.util.function.Consumer
import java.util.function.Predicate import java.util.function.Predicate
import java.util.function.Supplier
/** /**
* [ResourcePack] containing generated block textures * [ResourcePack] containing generated block textures
@@ -30,13 +27,16 @@ import java.util.function.Supplier
* @param[packDesc] Description of pack * @param[packDesc] Description of pack
* @param[logger] Logger to log to when generating resources * @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 { class GeneratedBlockTexturePack(
val reloadId: Identifier, val nameSpace: String, val packName: String, val packDesc: String
) : HasLogger(), ResourcePack {
override fun getName() = reloadId.toString() override fun getName() = reloadId.toString()
override fun getNamespaces(type: ResourceType) = setOf(nameSpace) override fun getNamespaces(type: ResourceType) = setOf(nameSpace)
override fun <T : Any?> parseMetadata(deserializer: ResourceMetadataReader<T>) = null override fun <T : Any?> parseMetadata(deserializer: ResourceMetadataReader<T>) = null
override fun openRoot(id: String) = null override fun openRoot(id: String) = null
override fun findResources(type: ResourceType, path: String, maxDepth: Int, filter: Predicate<String>) = emptyList<Identifier>() override fun findResources(type: ResourceType, path: String, prefix: String, maxDepth: Int, filter: Predicate<String>) = emptyList<Identifier>()
override fun close() {} override fun close() {}
protected var manager: ResourceManager? = null protected var manager: ResourceManager? = null
@@ -51,8 +51,8 @@ class GeneratedBlockTexturePack(val reloadId: Identifier, val nameSpace: String,
val resource = func(manager!!) val resource = func(manager!!)
identifiers[key] = id identifiers[key] = id
resources[Atlas.BLOCKS.wrap(id)] = resource resources[Atlas.BLOCKS.file(id)] = resource
log("generated resource $key -> $id") detailLogger.log(INFO, "generated resource $key -> $id")
return id return id
} }
@@ -64,29 +64,32 @@ class GeneratedBlockTexturePack(val reloadId: Identifier, val nameSpace: String,
override fun contains(type: ResourceType, id: Identifier) = override fun contains(type: ResourceType, id: Identifier) =
type == CLIENT_RESOURCES && resources.containsKey(id) type == CLIENT_RESOURCES && resources.containsKey(id)
/**
* Provider for this resource pack. Adds pack as always-on and hidden.
*/
val finder = object : ResourcePackProvider {
val packInfo = ResourcePackProfile(
packName, true, { this@GeneratedBlockTexturePack },
LiteralText(packName),
LiteralText(packDesc),
ResourcePackCompatibility.COMPATIBLE, ResourcePackProfile.InsertionPosition.TOP, true, ResourcePackSource.field_25347
)
override fun register(consumer: Consumer<ResourcePackProfile>, factory: ResourcePackProfile.Factory) {
consumer.accept(packInfo)
}
}
val reloader = object : IdentifiableResourceReloadListener {
override fun getFabricId() = reloadId
override fun reload(synchronizer: ResourceReloadListener.Synchronizer, manager: ResourceManager, prepareProfiler: Profiler, applyProfiler: Profiler, prepareExecutor: Executor, applyExecutor: Executor): CompletableFuture<Void> { override fun reload(synchronizer: ResourceReloadListener.Synchronizer, manager: ResourceManager, prepareProfiler: Profiler, applyProfiler: Profiler, prepareExecutor: Executor, applyExecutor: Executor): CompletableFuture<Void> {
this.manager = manager this@GeneratedBlockTexturePack.manager = manager
return synchronizer.whenPrepared(null).thenRun { return synchronizer.whenPrepared(null).thenRun {
this.manager = null this@GeneratedBlockTexturePack.manager = null
identifiers.clear() identifiers.clear()
resources.clear() resources.clear()
} }
} }
override fun getFabricId() = reloadId
/**
* Supplier for this resource pack. Adds pack as always-on and hidden.
*/
val finder = object : ResourcePackCreator {
val packInfo = ClientResourcePackContainer(
packName, true, Supplier { this@GeneratedBlockTexturePack },
LiteralText(packName),
LiteralText(packDesc),
ResourcePackCompatibility.COMPATIBLE, InsertionPosition.TOP, true, null
)
override fun <T : ResourcePackContainer> registerContainer(nameToPackMap: MutableMap<String, T>, packInfoFactory: ResourcePackContainer.Factory<T>) {
(nameToPackMap as MutableMap<String, ResourcePackContainer>)[reloadId.toString()] = packInfo
}
} }
} }

View File

@@ -17,7 +17,7 @@ data class GeneratedGrassSprite(val sprite: Identifier, val isSnowed: Boolean, v
fun register(pack: GeneratedBlockTexturePack) = pack.register(this, this::draw) fun register(pack: GeneratedBlockTexturePack) = pack.register(this, this::draw)
fun draw(resourceManager: ResourceManager): ByteArray { fun draw(resourceManager: ResourceManager): ByteArray {
val baseTexture = resourceManager.loadSprite(atlas.wrap(sprite)) val baseTexture = resourceManager.loadSprite(atlas.file(sprite))
val result = BufferedImage(baseTexture.width, baseTexture.height, BufferedImage.TYPE_4BYTE_ABGR) val result = BufferedImage(baseTexture.width, baseTexture.height, BufferedImage.TYPE_4BYTE_ABGR)
val graphics = result.createGraphics() val graphics = result.createGraphics()

View File

@@ -20,7 +20,7 @@ data class GeneratedLeafSprite(val sprite: Identifier, val leafType: String, val
fun register(pack: GeneratedBlockTexturePack) = pack.register(this, this::draw) fun register(pack: GeneratedBlockTexturePack) = pack.register(this, this::draw)
fun draw(resourceManager: ResourceManager): ByteArray { fun draw(resourceManager: ResourceManager): ByteArray {
val baseTexture = resourceManager.loadSprite(atlas.wrap(sprite)) val baseTexture = resourceManager.loadSprite(atlas.file(sprite))
val size = baseTexture.width val size = baseTexture.width
val frames = baseTexture.height / size val frames = baseTexture.height / size
@@ -67,7 +67,7 @@ data class GeneratedLeafSprite(val sprite: Identifier, val leafType: String, val
* @param[maxSize] Preferred mask size. * @param[maxSize] Preferred mask size.
*/ */
fun getLeafMask(type: String, maxSize: Int) = getMultisizeTexture(maxSize) { size -> fun getLeafMask(type: String, maxSize: Int) = getMultisizeTexture(maxSize) { size ->
Atlas.BLOCKS.wrap(Identifier(BetterFoliage.MOD_ID, "blocks/leafmask_${size}_${type}")) Atlas.BLOCKS.file(Identifier(BetterFoliage.MOD_ID, "blocks/leafmask_${size}_${type}"))
} }
/** /**

View File

@@ -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) }
}
}
}

View File

@@ -7,6 +7,7 @@ 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.AxisDirection.* import net.minecraft.util.math.Direction.AxisDirection.*
import net.minecraft.util.math.Direction.* import net.minecraft.util.math.Direction.*
import net.minecraft.util.math.Quaternion
// ================================ // ================================
// Axes and directions // Axes and directions
@@ -52,6 +53,7 @@ data class Double3(var x: Double, var y: Double, var z: Double) {
val zero: Double3 get() = Double3(0.0, 0.0, 0.0) val zero: Double3 get() = Double3(0.0, 0.0, 0.0)
fun weight(v1: Double3, weight1: Double, v2: Double3, weight2: Double) = fun weight(v1: Double3, weight1: Double, v2: Double3, weight2: Double) =
Double3(v1.x * weight1 + v2.x * weight2, v1.y * weight1 + v2.y * weight2, v1.z * weight1 + v2.z * weight2) 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 // immutable operations
@@ -68,6 +70,13 @@ data class Double3(var x: Double, var y: Double, var z: Double) {
rot.rotatedComponent(SOUTH, x, y, z) rot.rotatedComponent(SOUTH, x, y, z)
) )
/** Rotate vector by the given [Quaternion] */
fun rotate(quat: Quaternion) =
quat.copy()
.apply { hamiltonProduct(Quaternion(this@Double3.x.toFloat(), this@Double3.y.toFloat(), this@Double3.z.toFloat(), 0.0F)) }
.apply { hamiltonProduct(quat.copy().apply(Quaternion::conjugate)) }
.let { Double3(it.x, it.y, it.z) }
// mutable operations // mutable operations
fun setTo(other: Double3): Double3 { x = other.x; y = other.y; z = other.z; return this } fun setTo(other: Double3): Double3 { x = other.x; y = other.y; z = other.z; return this }
fun setTo(x: Double, y: Double, z: Double): Double3 { this.x = x; this.y = y; this.z = z; return this } fun setTo(x: Double, y: Double, z: Double): Double3 { this.x = x; this.y = y; this.z = z; return this }

View File

@@ -1,14 +1,13 @@
@file:Suppress("NOTHING_TO_INLINE") @file:Suppress("NOTHING_TO_INLINE")
package mods.betterfoliage.util package mods.betterfoliage.util
import mods.betterfoliage.BetterFoliage
import net.minecraft.text.LiteralText import net.minecraft.text.LiteralText
import net.minecraft.text.Style import net.minecraft.text.Style
import net.minecraft.util.Formatting import net.minecraft.util.Formatting
import net.minecraft.util.Identifier import net.minecraft.util.Identifier
import net.minecraft.util.math.BlockPos import net.minecraft.util.math.BlockPos
import net.minecraft.world.World import net.minecraft.world.World
import org.apache.logging.log4j.Level
import org.apache.logging.log4j.Logger
import java.lang.Math.* import java.lang.Math.*
import kotlin.reflect.KProperty import kotlin.reflect.KProperty
@@ -63,16 +62,13 @@ fun nextPowerOf2(x: Int): Int {
// else -> false // else -> false
//} //}
interface HasLogger { @Suppress("LeakingThis")
val logger: Logger abstract class HasLogger {
val logName: String get() = this::class.simpleName!! val logger = BetterFoliage.logger(this)
fun log(msg: String) = log(Level.INFO, msg) val detailLogger = BetterFoliage.detailLogger(this)
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 { fun textComponent(msg: String, color: Formatting = Formatting.GRAY): LiteralText {
val style = Style().apply { this.color = color } val style = Style.EMPTY.withColor(color)
return LiteralText(msg).apply { this.style = style } return LiteralText(msg).apply { this.style = style }
} }

View File

@@ -1,15 +1,18 @@
package mods.betterfoliage.util package mods.betterfoliage.util
import net.minecraft.util.math.BlockPos import net.minecraft.util.math.BlockPos
import kotlin.random.Random import java.util.Random
val random = Random(System.nanoTime()) val random = Random(System.nanoTime())
fun randomB() = random.nextBoolean() fun randomB() = random.nextBoolean()
fun randomI(min: Int = 0, max: Int = Int.MAX_VALUE) = random.nextInt(min, max) fun randomI(min: Int = 0, max: Int = Int.MAX_VALUE) = min + random.nextInt(max - min)
fun randomL(min: Long = 0, max: Long = Long.MAX_VALUE) = random.nextLong(min, max) fun randomF(min: Float = 0.0f, max: Float = 1.0f) = random.randomF(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) = random.randomD(min, max)
fun randomD(min: Double = 0.0, max: Double = 1.0) = if (min == max) min else random.nextDouble(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 { 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)) var value = (x * x + y * y + z * z + x * y + y * z + z * x + (seed * seed))

View File

@@ -3,6 +3,7 @@ package mods.betterfoliage.util
import java.lang.reflect.Field import java.lang.reflect.Field
import java.lang.reflect.Method import java.lang.reflect.Method
import net.fabricmc.loader.api.FabricLoader import net.fabricmc.loader.api.FabricLoader
import net.fabricmc.mappings.EntryTriple
import org.apache.logging.log4j.Level import org.apache.logging.log4j.Level
import org.apache.logging.log4j.LogManager import org.apache.logging.log4j.LogManager
import java.lang.Exception import java.lang.Exception
@@ -16,18 +17,27 @@ fun <T> Any.reflectField(name: String) = getFieldRecursive(this::class.java, nam
it.get(this) as T it.get(this) as T
} }
/** Get the field on the class with the given name.
* Does not handle overloads, suitable only for unique field names (like Yarn intermediate names)
* */
fun getFieldRecursive(cls: Class<*>, name: String): Field = try { fun getFieldRecursive(cls: Class<*>, name: String): Field = try {
cls.getDeclaredField(name) cls.getDeclaredField(name)
} catch (e: NoSuchFieldException) { } catch (e: NoSuchFieldException) {
cls.superclass?.let { getFieldRecursive(it, name) } ?: throw e cls.superclass?.let { getFieldRecursive(it, name) } ?: throw IllegalArgumentException(e)
} }
/** Get the method on the class with the given name.
* Does not handle overloads, suitable only for unique field names (like Yarn intermediate names)
* */
fun getMethodRecursive(cls: Class<*>, name: String): Method = try { fun getMethodRecursive(cls: Class<*>, name: String): Method = try {
cls.declaredMethods.find { it.name == name } ?: throw NoSuchMethodException() cls.declaredMethods.find { it.name == name } ?: throw NoSuchMethodException()
} catch (e: NoSuchMethodException) { } catch (e: NoSuchMethodException) {
cls.superclass?.let { getMethodRecursive(it, name) } ?: throw e cls.superclass?.let { getMethodRecursive(it, name) } ?: throw IllegalArgumentException(e)
} }
fun getAllMethods(className: String, methodName: String): List<Method> =
tryDefault(null) { Class.forName(className) }?.declaredMethods?.filter { it.name == methodName }
?: emptyList()
interface FieldRef<T> { interface FieldRef<T> {
val field: Field? val field: Field?
@@ -59,14 +69,14 @@ object YarnHelper {
val resolver = FabricLoader.getInstance().mappingResolver val resolver = FabricLoader.getInstance().mappingResolver
fun <T> requiredField(className: String, fieldName: String, descriptor: String) = Field<T>(false, className, fieldName, descriptor) fun <T> requiredField(className: String, fieldName: String, descriptor: String) = Field<T>(false, className, fieldName, descriptor)
fun <T> requiredMethod(className: String, methodName: String, descriptor: String, vararg params: String) = Method<T>(false, className, methodName, descriptor) fun <T> requiredMethod(className: String, methodName: String, descriptor: String) = Method<T>(false, className, methodName, descriptor)
class Field<T>(val optional: Boolean, val className: String, val fieldName: String, descriptor: String) : FieldRef<T> { class Field<T>(val optional: Boolean, val className: String, val fieldName: String, descriptor: String) : FieldRef<T> {
override val field = FabricLoader.getInstance().mappingResolver.let { resolver -> override val field = FabricLoader.getInstance().mappingResolver.let { resolver ->
try { try {
val classMapped = resolver.mapClassName(INTERMEDIARY, className) val classMapped = resolver.mapClassName(INTERMEDIARY, className)
val fieldMapped = resolver.mapFieldName(INTERMEDIARY, className, fieldName, descriptor) val fieldMapped = resolver.mapFieldName(INTERMEDIARY, className, fieldName, descriptor)
Class.forName(classMapped)?.let { cls -> getFieldRecursive(cls, fieldMapped).apply { isAccessible = true } } Class.forName(classMapped).let { cls -> getFieldRecursive(cls, fieldMapped).apply { isAccessible = true } }
} catch (e: Exception) { } catch (e: Exception) {
logger.log( logger.log(
if (optional) Level.DEBUG else Level.ERROR, if (optional) Level.DEBUG else Level.ERROR,
@@ -82,7 +92,7 @@ object YarnHelper {
try { try {
val classMapped = resolver.mapClassName(INTERMEDIARY, className) val classMapped = resolver.mapClassName(INTERMEDIARY, className)
val methodMapped = resolver.mapMethodName(INTERMEDIARY, className, methodName, descriptor) val methodMapped = resolver.mapMethodName(INTERMEDIARY, className, methodName, descriptor)
Class.forName(classMapped)?.let { cls -> getMethodRecursive(cls, methodMapped).apply { isAccessible = true } } Class.forName(classMapped).let { cls -> getMethodRecursive(cls, methodMapped).apply { isAccessible = true } }
} catch (e: Exception) { } catch (e: Exception) {
logger.log( logger.log(
if (optional) Level.DEBUG else Level.ERROR, if (optional) Level.DEBUG else Level.ERROR,
@@ -94,8 +104,6 @@ object YarnHelper {
} }
} }
//fun Any.isInstance(cls: ClassRefOld<*>) = cls.isInstance(this)
interface ReflectionCallable<T> { interface ReflectionCallable<T> {
operator fun invoke(vararg args: Any): T operator fun invoke(vararg args: Any): T
} }

View File

@@ -1,30 +1,34 @@
package mods.betterfoliage.util package mods.betterfoliage.util
import mods.betterfoliage.resource.model.HSB import mods.betterfoliage.model.Color
import mods.betterfoliage.model.HSB
import net.minecraft.client.MinecraftClient import net.minecraft.client.MinecraftClient
import net.minecraft.client.texture.NativeImage
import net.minecraft.client.texture.Sprite import net.minecraft.client.texture.Sprite
import net.minecraft.client.texture.SpriteAtlasTexture import net.minecraft.client.texture.SpriteAtlasTexture
import net.minecraft.resource.Resource import net.minecraft.resource.Resource
import net.minecraft.resource.ResourceManager import net.minecraft.resource.ResourceManager
import net.minecraft.util.Identifier import net.minecraft.util.Identifier
import org.apache.logging.log4j.Level
import org.apache.logging.log4j.Logger
import java.awt.image.BufferedImage import java.awt.image.BufferedImage
import java.io.ByteArrayOutputStream import java.io.ByteArrayOutputStream
import java.io.IOException import java.io.IOException
import javax.imageio.ImageIO import javax.imageio.ImageIO
import kotlin.math.atan2 import kotlin.math.atan2
enum class Atlas(val basePath: String, val resourceId: Identifier) { enum class Atlas(val resourceId: Identifier) {
BLOCKS("textures", SpriteAtlasTexture.BLOCK_ATLAS_TEX), BLOCKS(SpriteAtlasTexture.BLOCK_ATLAS_TEXTURE),
PARTICLES("textures/particle", SpriteAtlasTexture.PARTICLE_ATLAS_TEX); PARTICLES(SpriteAtlasTexture.PARTICLE_ATLAS_TEXTURE);
/** Get the fully-qualified resource name for sprites belonging to this atlas */ /** Get the fully-qualified resource name for sprites belonging to this atlas */
fun wrap(resource: Identifier) = Identifier(resource.namespace, "$basePath/${resource.path}.png") fun file(resource: Identifier) = Identifier(resource.namespace, "textures/${resource.path}.png")
/** Get the short resource name for sprites belonging to this atlas*/
fun unwrap(resource: Identifier) = resource.stripStart("$basePath/").stripEnd(".png")
/** Reference to the atlas itself */ /** Reference to the atlas itself */
val atlas: SpriteAtlasTexture get() = MinecraftClient.getInstance().textureManager.getTexture(resourceId) as SpriteAtlasTexture private val atlas: SpriteAtlasTexture get() = MinecraftClient.getInstance().textureManager.getTexture(resourceId) as SpriteAtlasTexture
/** Get a sprite from this atlas */
operator fun get(location: Identifier) = atlas.getSprite(location)
} }
operator fun SpriteAtlasTexture.get(res: Identifier): Sprite? = getSprite(res) operator fun SpriteAtlasTexture.get(res: Identifier): Sprite? = getSprite(res)
@@ -48,15 +52,17 @@ val BufferedImage.bytes: ByteArray get() =
* Only non-transparent pixels are considered. Averages are taken in the HSB color space (note: Hue is a circular average), * Only non-transparent pixels are considered. Averages are taken in the HSB color space (note: Hue is a circular average),
* and the result transformed back to the RGB color space. * and the result transformed back to the RGB color space.
*/ */
fun ResourceManager.averageImageColorHSB(id: Identifier, atlas: Atlas) = loadSprite(atlas.wrap(id)).let { image -> val Sprite_images = YarnHelper.requiredField<Array<NativeImage>>("net.minecraft.class_1058", "field_5262", "[Lnet/minecraft/class_1011;")
val Sprite.averageColor: HSB get() {
var numOpaque = 0 var numOpaque = 0
var sumHueX = 0.0 var sumHueX = 0.0
var sumHueY = 0.0 var sumHueY = 0.0
var sumSaturation = 0.0f var sumSaturation = 0.0f
var sumBrightness = 0.0f var sumBrightness = 0.0f
for (x in 0 until image.width) for (x in 0 until width)
for (y in 0 until image.height) { for (y in 0 until height) {
val pixel = image.get(x, y) val pixel = this[Sprite_images]!![0].getPixelColor(x, y)
val alpha = (pixel shr 24) and 255 val alpha = (pixel shr 24) and 255
val hsb = HSB.fromColor(pixel) val hsb = HSB.fromColor(pixel)
if (alpha == 255) { if (alpha == 255) {
@@ -70,7 +76,7 @@ fun ResourceManager.averageImageColorHSB(id: Identifier, atlas: Atlas) = loadSpr
// circular average - transform sum vector to polar angle // circular average - transform sum vector to polar angle
val avgHue = (atan2(sumHueY, sumHueX) / PI2 + 0.5).toFloat() val avgHue = (atan2(sumHueY, sumHueX) / PI2 + 0.5).toFloat()
HSB(avgHue, sumSaturation / numOpaque.toFloat(), sumBrightness / numOpaque.toFloat()) return HSB(avgHue, sumSaturation / numOpaque.toFloat(), sumBrightness / numOpaque.toFloat())
} }
/** Weighted blend of 2 packed RGB colors */ /** Weighted blend of 2 packed RGB colors */
@@ -82,3 +88,15 @@ fun blendRGB(rgb1: Int, rgb2: Int, weight1: Int, weight2: Int): Int {
val result = ((a shl 24) or (r shl 16) or (g shl 8) or b) val result = ((a shl 24) or (r shl 16) or (g shl 8) or b)
return result return result
} }
fun logColorOverride(logger: Logger, threshold: Double, hsb: HSB) {
return if (hsb.saturation >= threshold) {
logger.log(Level.INFO, " brightness ${hsb.brightness}")
logger.log(Level.INFO, " saturation ${hsb.saturation} >= ${threshold}, will use texture color")
} else {
logger.log(Level.INFO, " saturation ${hsb.saturation} < ${threshold}, will use block color")
}
}
fun HSB.colorOverride(threshold: Double) =
if (saturation < threshold) null else copy(brightness = (brightness * 2.0f).coerceAtMost(0.9f)).asColor.let { Color(it) }

View File

@@ -1,16 +1,24 @@
package net.fabricmc.fabric.impl.client.indigo.renderer.render package net.fabricmc.fabric.impl.client.indigo.renderer.render
import mods.betterfoliage.render.lighting.CustomLightingMeshConsumer import mods.betterfoliage.render.lighting.AbstractQuadRenderer_aoCalc
import mods.betterfoliage.render.lighting.AbstractQuadRenderer_blockInfo2
import mods.betterfoliage.render.lighting.AbstractQuadRenderer_bufferFunc2
import mods.betterfoliage.render.lighting.AbstractQuadRenderer_transform
import mods.betterfoliage.render.lighting.CustomLighting import mods.betterfoliage.render.lighting.CustomLighting
import mods.betterfoliage.util.* import mods.betterfoliage.render.lighting.CustomLightingMeshConsumer
import net.fabricmc.fabric.api.renderer.v1.render.RenderContext import mods.betterfoliage.util.YarnHelper
import net.fabricmc.fabric.impl.client.indigo.renderer.aocalc.AoCalculator import mods.betterfoliage.util.cornerDirFromAo
import mods.betterfoliage.util.get
import mods.betterfoliage.util.offset
import mods.betterfoliage.util.plus
import net.fabricmc.fabric.impl.client.indigo.renderer.mesh.MutableQuadViewImpl import net.fabricmc.fabric.impl.client.indigo.renderer.mesh.MutableQuadViewImpl
import net.minecraft.client.render.RenderLayer
import net.minecraft.client.render.WorldRenderer
import net.minecraft.util.math.Direction import net.minecraft.util.math.Direction
val AoCalculator_computeFace = YarnHelper.requiredMethod<Any>( val AoCalculator_computeFace = YarnHelper.requiredMethod<Any>(
"net.fabricmc.fabric.impl.client.indigo.renderer.aocalc.AoCalculator", "computeFace", "net.fabricmc.fabric.impl.client.indigo.renderer.aocalc.AoCalculator", "computeFace",
"(Lnet/minecraft/util/math/Direction;Z)Lnet/fabricmc/fabric/impl/client/indigo/renderer/aocalc/AoFaceData;" "(Lnet/minecraft/util/math/Direction;ZZ)Lnet/fabricmc/fabric/impl/client/indigo/renderer/aocalc/AoFaceData;"
) )
val AoFaceData_toArray = YarnHelper.requiredMethod<Unit>( val AoFaceData_toArray = YarnHelper.requiredMethod<Unit>(
"net.fabricmc.fabric.impl.client.indigo.renderer.aocalc.AoFaceData", "toArray", "net.fabricmc.fabric.impl.client.indigo.renderer.aocalc.AoFaceData", "toArray",
@@ -18,11 +26,16 @@ val AoFaceData_toArray = YarnHelper.requiredMethod<Unit>(
) )
open class ModifiedTerrainMeshConsumer( open class ModifiedTerrainMeshConsumer(
blockInfo: TerrainBlockRenderInfo, val original: AbstractMeshConsumer
chunkInfo: ChunkRenderInfo, ) : AbstractMeshConsumer(
aoCalc: AoCalculator, original[AbstractQuadRenderer_blockInfo2],
transform: RenderContext.QuadTransform original[AbstractQuadRenderer_bufferFunc2],
) : TerrainMeshConsumer(blockInfo, chunkInfo, aoCalc, transform), CustomLightingMeshConsumer { original[AbstractQuadRenderer_aoCalc],
original[AbstractQuadRenderer_transform]
), CustomLightingMeshConsumer {
override fun matrix() = original.matrix()
override fun normalMatrix() = original.normalMatrix()
override fun overlay() = original.overlay()
/** Custom lighting to use */ /** Custom lighting to use */
var lighter: CustomLighting? = null var lighter: CustomLighting? = null
@@ -34,21 +47,23 @@ open class ModifiedTerrainMeshConsumer(
/** Cached block brightness values for all neighbors */ /** Cached block brightness values for all neighbors */
val brNeighbor = IntArray(6) val brNeighbor = IntArray(6)
/** Cache validity for block brightness values (neighbors + self) */ /** Cache validity for block brightness values (neighbors + self) */
val brValid = Array(7) { false } val brValid = Array(7) { false }
override var brSelf: Int = -1 override var brSelf: Int = -1
get() { if (brValid[6]) return field else get() {
field = blockInfo.blockView.getBlockState(blockInfo.blockPos).getBlockBrightness(blockInfo.blockView, blockInfo.blockPos) if (brValid[6]) return field else {
field = WorldRenderer.getLightmapCoordinates(blockInfo.blockView, blockInfo.blockPos)
brValid[6] = true brValid[6] = true
return field return field
} }
}
protected set protected set
override fun brNeighbor(dir: Direction): Int { override fun brNeighbor(dir: Direction): Int {
if (brValid[dir.ordinal]) return brNeighbor[dir.ordinal] if (brValid[dir.ordinal]) return brNeighbor[dir.ordinal]
blockInfo.blockView.getBlockState(blockInfo.blockPos) WorldRenderer.getLightmapCoordinates(blockInfo.blockView, blockInfo.blockPos + dir.offset)
.getBlockBrightness(blockInfo.blockView, blockInfo.blockPos + dir.offset)
.let { brNeighbor[dir.ordinal] = it; brValid[dir.ordinal] = true; return it } .let { brNeighbor[dir.ordinal] = it; brValid[dir.ordinal] = true; return it }
} }
@@ -62,7 +77,12 @@ open class ModifiedTerrainMeshConsumer(
override fun fillAoData(lightFace: Direction) { override fun fillAoData(lightFace: Direction) {
if (!aoValid[lightFace.ordinal]) { if (!aoValid[lightFace.ordinal]) {
AoFaceData_toArray.invoke(AoCalculator_computeFace.invoke(aoCalc, lightFace, true), aoFull, lightFull, cornerDirFromAo[lightFace.ordinal]) AoFaceData_toArray.invoke(
AoCalculator_computeFace.invoke(aoCalc, lightFace, true, false),
aoFull,
lightFull,
cornerDirFromAo[lightFace.ordinal]
)
aoValid[lightFace.ordinal] = true aoValid[lightFace.ordinal] = true
} }
} }
@@ -72,32 +92,23 @@ open class ModifiedTerrainMeshConsumer(
aoCalc.light[vIdx] = light aoCalc.light[vIdx] = light
} }
override fun applyOffsets(quad: MutableQuadViewImpl) { override fun tesselateFlat(q: MutableQuadViewImpl, renderLayer: RenderLayer, blockColorIndex: Int) {
// Moved farther back in the pipeline, after custom lighting is applied
// Might possibly mess emissive multitexturing up, but seems to be OK for now
}
override fun tesselateFlat(q: MutableQuadViewImpl, renderLayer: Int, blockColorIndex: Int) {
lighter?.applyLighting(this, q, flat = true, emissive = false) lighter?.applyLighting(this, q, flat = true, emissive = false)
super.applyOffsets(q)
super.tesselateSmooth(q, renderLayer, blockColorIndex) super.tesselateSmooth(q, renderLayer, blockColorIndex)
} }
override fun tesselateFlatEmissive(q: MutableQuadViewImpl, renderLayer: Int, blockColorIndex: Int, lightmaps: IntArray) { override fun tesselateFlatEmissive(q: MutableQuadViewImpl, renderLayer: RenderLayer, blockColorIndex: Int) {
lighter?.applyLighting(this, q, flat = true, emissive = true) lighter?.applyLighting(this, q, flat = true, emissive = true)
super.applyOffsets(q)
super.tesselateSmoothEmissive(q, renderLayer, blockColorIndex) super.tesselateSmoothEmissive(q, renderLayer, blockColorIndex)
} }
override fun tesselateSmooth(q: MutableQuadViewImpl, renderLayer: Int, blockColorIndex: Int) { override fun tesselateSmooth(q: MutableQuadViewImpl, renderLayer: RenderLayer, blockColorIndex: Int) {
lighter?.applyLighting(this, q, flat = false, emissive = false) lighter?.applyLighting(this, q, flat = false, emissive = false)
super.applyOffsets(q)
super.tesselateSmooth(q, renderLayer, blockColorIndex) super.tesselateSmooth(q, renderLayer, blockColorIndex)
} }
override fun tesselateSmoothEmissive(q: MutableQuadViewImpl, renderLayer: Int, blockColorIndex: Int) { override fun tesselateSmoothEmissive(q: MutableQuadViewImpl, renderLayer: RenderLayer, blockColorIndex: Int) {
lighter?.applyLighting(this, q, flat = false, emissive = true) lighter?.applyLighting(this, q, flat = false, emissive = true)
super.applyOffsets(q)
super.tesselateSmoothEmissive(q, renderLayer, blockColorIndex) super.tesselateSmoothEmissive(q, renderLayer, blockColorIndex)
} }
} }

View File

@@ -1,2 +1,6 @@
Manifest-Version: 1.0 Manifest-Version: 1.0
Implementation-Title: betterfoliage Specification-Title: BetterFoliage
Implementation-Title: BetterFoliage
Specification-Vendor: octarine-noise
Implementation-Vendor: octarine-noise
MixinConnector: mods.betterfoliage.MixinConnector

View File

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

View File

@@ -1,10 +1,11 @@
// Vanilla // Vanilla
net.minecraft.block.TallGrassBlock //net.minecraft.block.FernBlock
net.minecraft.block.CropsBlock net.minecraft.class_2526
-net.minecraft.block.ReedBlock //net.minecraft.block.CropBlock
-net.minecraft.block.DoublePlantBlock net.minecraft.class_2302
-net.minecraft.block.CarrotBlock
-net.minecraft.block.PotatoBlock //-net.minecraft.block.TallPlantBlock
-net.minecraft.class_2320
// Biomes O'Plenty // Biomes O'Plenty
biomesoplenty.common.block.BlockBOPFlower biomesoplenty.common.block.BlockBOPFlower

View File

@@ -26,91 +26,11 @@
"betterfoliage.shaderWind.tooltip": "Apply wind effects from ShaderMod shaders to this element?", "betterfoliage.shaderWind.tooltip": "Apply wind effects from ShaderMod shaders to this element?",
"betterfoliage.distance": "Distance limit", "betterfoliage.distance": "Distance limit",
"betterfoliage.distance.tooltip": "Maximum distance from player at which to render this feature", "betterfoliage.distance.tooltip": "Maximum distance from player at which to render this feature",
"betterfoliage.saturationThreshold": "Saturation threshold",
"betterfoliage.saturationThreshold.tooltip": "Color saturation cutoff between \"colorless\" blocks (using biome color) and \"colorful\" blocks (using their own specific color)",
"betterfoliage.rendererror": "§a[BetterFoliage]§f Error rendering block %s at position %s", "betterfoliage.rendererror": "§a[BetterFoliage]§f Error rendering block %s at position %s",
"betterfoliage.blocks": "Block Types",
"betterfoliage.blocks.tooltip": "Configure lists of block classes that will have specific features applied to them",
"betterfoliage.blocks.dirtWhitelist": "Dirt Whitelist",
"betterfoliage.blocks.dirtBlacklist": "Dirt Blacklist",
"betterfoliage.blocks.dirtWhitelist.arrayEntry": "%d entries",
"betterfoliage.blocks.dirtBlacklist.arrayEntry": "%d entries",
"betterfoliage.blocks.dirtWhitelist.tooltip": "Blocks recognized as Dirt. Has an impact on Reeds, Algae, Connected Grass",
"betterfoliage.blocks.dirtBlacklist.tooltip": "Blocks never accepted as Dirt. Has an impact on Reeds, Algae, Connected Grass",
"betterfoliage.blocks.grassClassesWhitelist": "Grass Whitelist",
"betterfoliage.blocks.grassClassesBlacklist": "Grass Blacklist",
"betterfoliage.blocks.grassClassesWhitelist.arrayEntry": "%d entries",
"betterfoliage.blocks.grassClassesBlacklist.arrayEntry": "%d entries",
"betterfoliage.blocks.grassModels": "Grass Models",
"betterfoliage.blocks.grassModels.arrayEntry": "%d entries",
"betterfoliage.blocks.grassWhitelist.tooltip": "Blocks recognized as Grass. Has an impact on Short Grass, Connected Grass",
"betterfoliage.blocks.grassBlacklist.tooltip": "Blocks never accepted as Grass. Has an impact on Short Grass, Connected Grass",
"betterfoliage.blocks.grassModels.tooltip": "Models and textures recognized for grass blocks",
"betterfoliage.blocks.leavesClassesWhitelist": "Leaves Whitelist",
"betterfoliage.blocks.leavesClassesBlacklist": "Leaves Blacklist",
"betterfoliage.blocks.leavesClassesWhitelist.arrayEntry": "%d entries",
"betterfoliage.blocks.leavesClassesBlacklist.arrayEntry": "%d entries",
"betterfoliage.blocks.leavesModels": "Leaves Models",
"betterfoliage.blocks.leavesModels.arrayEntry": "%d entries",
"betterfoliage.blocks.leavesClassesWhitelist.tooltip": "Blocks recognized as Leaves. Has an impact on Extra Leaves, Falling Leaves. Leaves will render with leaves block ID in shader programs",
"betterfoliage.blocks.leavesClassesBlacklist.tooltip": "Blocks never accepted as Leaves. Has an impact on Extra Leaves, Falling Leaves. Leaves will render with leaves block ID in shader programs",
"betterfoliage.blocks.leavesModels.tooltip": "Models and textures recognized for leaves blocks",
"betterfoliage.blocks.cropsWhitelist": "Crop Whitelist",
"betterfoliage.blocks.cropsBlacklist": "Crop Blacklist",
"betterfoliage.blocks.cropsWhitelist.arrayEntry": "%d entries",
"betterfoliage.blocks.cropsBlacklist.arrayEntry": "%d entries",
"betterfoliage.blocks.cropsWhitelist.tooltip": "Blocks recognized as crops. Crops will render with tallgrass block ID in shader programs",
"betterfoliage.blocks.cropsBlacklist.tooltip": "Blocks never accepted as crops. Crops will render with tallgrass block ID in shader programs",
"betterfoliage.blocks.logClassesWhitelist": "Wood Log Whitelist",
"betterfoliage.blocks.logClassesBlacklist": "Wood Log Blacklist",
"betterfoliage.blocks.logClassesWhitelist.arrayEntry": "%d entries",
"betterfoliage.blocks.logClassesBlacklist.arrayEntry": "%d entries",
"betterfoliage.blocks.logModels": "Wood Log Models",
"betterfoliage.blocks.logModels.arrayEntry": "%d entries",
"betterfoliage.blocks.logClassesWhitelist.tooltip": "Blocks recognized as wooden logs. Has an impact on Rounded Logs",
"betterfoliage.blocks.logClassesBlacklist.tooltip": "Blocks never accepted as wooden logs. Has an impact on Rounded Logs",
"betterfoliage.blocks.logModels.tooltip": "Models and textures recognized for wood log blocks",
"betterfoliage.blocks.sandWhitelist": "Sand Whitelist",
"betterfoliage.blocks.sandBlacklist": "Sand Blacklist",
"betterfoliage.blocks.sandWhitelist.arrayEntry": "%d entries",
"betterfoliage.blocks.sandBlacklist.arrayEntry": "%d entries",
"betterfoliage.blocks.sandWhitelist.tooltip": "Blocks recognized as Sand. Has an impact on Coral",
"betterfoliage.blocks.sandBlacklist.tooltip": "Blocks never accepted Sand. Has an impact on Coral",
"betterfoliage.blocks.lilypadWhitelist": "Lilypad Whitelist",
"betterfoliage.blocks.lilypadBlacklist": "Lilypad Blacklist",
"betterfoliage.blocks.lilypadWhitelist.arrayEntry": "%d entries",
"betterfoliage.blocks.lilypadBlacklist.arrayEntry": "%d entries",
"betterfoliage.blocks.lilypadWhitelist.tooltip": "Blocks recognized as Lilypad. Has an impact on Better Lilypad",
"betterfoliage.blocks.lilypadBlacklist.tooltip": "Blocks never accepted Lilypad. Has an impact on Better Lilypad",
"betterfoliage.blocks.cactusWhitelist": "Cactus Whitelist",
"betterfoliage.blocks.cactusBlacklist": "Cactus Blacklist",
"betterfoliage.blocks.cactusWhitelist.arrayEntry": "%d entries",
"betterfoliage.blocks.cactusBlacklist.arrayEntry": "%d entries",
"betterfoliage.blocks.cactusWhitelist.tooltip": "Blocks recognized as Cactus. Has an impact on Better Cactus",
"betterfoliage.blocks.cactusBlacklist.tooltip": "Blocks never accepted Cactus. Has an impact on Better Cactus",
"betterfoliage.blocks.myceliumWhitelist": "Mycelium Whitelist",
"betterfoliage.blocks.myceliumBlacklist": "Mycelium Blacklist",
"betterfoliage.blocks.myceliumWhitelist.arrayEntry": "%d entries",
"betterfoliage.blocks.myceliumBlacklist.arrayEntry": "%d entries",
"betterfoliage.blocks.myceliumWhitelist.tooltip": "Blocks recognized as Mycelium. Has an impact on Better Grass",
"betterfoliage.blocks.myceliumBlacklist.tooltip": "Blocks never accepted Mycelium. Has an impact on Better Grass",
"betterfoliage.blocks.netherrackWhitelist": "Netherrack Whitelist",
"betterfoliage.blocks.netherrackBlacklist": "Netherrack Blacklist",
"betterfoliage.blocks.netherrackWhitelist.arrayEntry": "%d entries",
"betterfoliage.blocks.netherrackBlacklist.arrayEntry": "%d entries",
"betterfoliage.blocks.netherrackWhitelist.tooltip": "Blocks recognized as Netherrack. Has an impact on Netherrack Vines",
"betterfoliage.blocks.netherrackBlacklist.tooltip": "Blocks never accepted Netherrack. Has an impact on Netherrack Vines",
"betterfoliage.shaders": "Shader configuration", "betterfoliage.shaders": "Shader configuration",
"betterfoliage.shaders.tooltip": "Configure integration with shaders", "betterfoliage.shaders.tooltip": "Configure integration with shaders",
"betterfoliage.shaders.leavesId": "Leaves ID", "betterfoliage.shaders.leavesId": "Leaves ID",
@@ -137,8 +57,6 @@
"betterfoliage.shortGrass.grassEnabled.tooltip": "Is this feature enabled for grass blocks?", "betterfoliage.shortGrass.grassEnabled.tooltip": "Is this feature enabled for grass blocks?",
"betterfoliage.shortGrass.snowEnabled": "Enable under snow", "betterfoliage.shortGrass.snowEnabled": "Enable under snow",
"betterfoliage.shortGrass.snowEnabled.tooltip": "Enable on snowed grass blocks?", "betterfoliage.shortGrass.snowEnabled.tooltip": "Enable on snowed grass blocks?",
"betterfoliage.shortGrass.saturationThreshold": "Saturation threshold",
"betterfoliage.shortGrass.saturationThreshold.tooltip": "Color saturation cutoff between \"colorless\" blocks (using biome color) and \"colorful\" blocks (using their own specific color)",
"betterfoliage.connectedGrass.snowEnabled": "Enable under snow", "betterfoliage.connectedGrass.snowEnabled": "Enable under snow",
"betterfoliage.connectedGrass.snowEnabled.tooltip": "Enable on snowed grass blocks?", "betterfoliage.connectedGrass.snowEnabled.tooltip": "Enable on snowed grass blocks?",

View File

@@ -22,67 +22,8 @@
"betterfoliage.shaderWind.tooltip": "바람효과를 쉐이더에 적용시키겠습니까?", "betterfoliage.shaderWind.tooltip": "바람효과를 쉐이더에 적용시키겠습니까?",
"betterfoliage.distance": "거리 제한", "betterfoliage.distance": "거리 제한",
"betterfoliage.distance.tooltip": "이 기능을 렌더링하는 플레이어의 최대거리", "betterfoliage.distance.tooltip": "이 기능을 렌더링하는 플레이어의 최대거리",
"betterfoliage.saturationThreshold": "채도 임계값",
"betterfoliage.blocks": "블록 타입", "betterfoliage.saturationThreshold.tooltip": "(특정 색상을 사용하여)\"무채색\"블록과 (바이옴 색을 사용하여)\"화려한\"블록 사이의 채도 차단",
"betterfoliage.blocks.tooltip": "세팅 된 것에 따라 블록이 바뀔 것입니다.",
"betterfoliage.blocks.dirtWhitelist": "흙 허용목록",
"betterfoliage.blocks.dirtBlacklist": "흙 차단목록",
"betterfoliage.blocks.dirtWhitelist.arrayEntry": "%d entries",
"betterfoliage.blocks.dirtBlacklist.arrayEntry": "%d entries",
"betterfoliage.blocks.grassWhitelist": "잔디 허용목록",
"betterfoliage.blocks.grassBlacklist": "잔디 차단목록",
"betterfoliage.blocks.grassWhitelist.arrayEntry": "%d entries",
"betterfoliage.blocks.grassBlacklist.arrayEntry": "%d entries",
"betterfoliage.blocks.leavesWhitelist": "잎 허용목록",
"betterfoliage.blocks.leavesBlacklist": "잎 차단목록",
"betterfoliage.blocks.leavesWhitelist.arrayEntry": "%d entries",
"betterfoliage.blocks.leavesBlacklist.arrayEntry": "%d entries",
"betterfoliage.blocks.cropsWhitelist": "농작물 허용목록",
"betterfoliage.blocks.cropsBlacklist": "농작물 차단목록",
"betterfoliage.blocks.cropsWhitelist.arrayEntry": "%d entries",
"betterfoliage.blocks.cropsBlacklist.arrayEntry": "%d entries",
"betterfoliage.blocks.logsWhitelist": "나무 허용목록",
"betterfoliage.blocks.logsBlacklist": "나무 차단목록",
"betterfoliage.blocks.logsWhitelist.arrayEntry": "%d entries",
"betterfoliage.blocks.logsBlacklist.arrayEntry": "%d entries",
"betterfoliage.blocks.sandWhitelist": "모래 허용목록",
"betterfoliage.blocks.sandBlacklist": "모래 차단목록",
"betterfoliage.blocks.sandWhitelist.arrayEntry": "%d entries",
"betterfoliage.blocks.sandBlacklist.arrayEntry": "%d entries",
"betterfoliage.blocks.lilypadWhitelist": "연꽃 허용목록",
"betterfoliage.blocks.lilypadBlacklist": "연꽃 차단목록",
"betterfoliage.blocks.lilypadWhitelist.arrayEntry": "%d entries",
"betterfoliage.blocks.lilypadBlacklist.arrayEntry": "%d entries",
"betterfoliage.blocks.cactusWhitelist": "선인장 허용목록",
"betterfoliage.blocks.cactusBlacklist": "선인장 차단목록",
"betterfoliage.blocks.cactusWhitelist.arrayEntry": "%d entries",
"betterfoliage.blocks.cactusBlacklist.arrayEntry": "%d entries",
"betterfoliage.blocks.dirtWhitelist.tooltip": "흙으로 인식됩니다. 갈대, 조류, 잔디 텍스쳐 연결에 영향을 줍니다.",
"betterfoliage.blocks.dirtBlacklist.tooltip": "흙으로 인식 되지 않습니다. 갈대, 조류, 잔디 텍스쳐 연결에 영향을 주지 않습니다.",
"betterfoliage.blocks.grassWhitelist.tooltip": "잔디로 인식됩니다. 짧은 잔디, 잔디 텍스쳐 연결에 영향을 줍니다.",
"betterfoliage.blocks.grassBlacklist.tooltip": "잔디로 인식 되지 않습니다. 짧은 잔디, 잔디 텍스쳐 연결에 영향을 주지 않습니다.",
"betterfoliage.blocks.leavesWhitelist.tooltip": "잎으로 인식됩니다. 추가 잎, 떨어지는 잎에 영향을 줍니다. ",
"betterfoliage.blocks.leavesBlacklist.tooltip": "잎으로 인식 되지 않습니다. 추가 잎, 떨어지는 잎에 영향을 주지 않습니다.",
"betterfoliage.blocks.cropsWhitelist.tooltip": "농작물로 인식됩니다. 농작물은 쉐이더 적용하면 큰잔디로 렌더링 됩니다. ",
"betterfoliage.blocks.cropsBlacklist.tooltip": "농작물로 인식 되지 않습니다. 농작물은 쉐이더 적용하면 큰잔디로 렌더링 되지 않습니다.",
"betterfoliage.blocks.logsWhitelist.tooltip": "나무로 인식됩니다. 둥근 나무에 영향을 줍니다.",
"betterfoliage.blocks.logsBlacklist.tooltip": "나무로 인식 되지 않습니다. 둥근 나무에 영향을 주지 않습니다.",
"betterfoliage.blocks.sandWhitelist.tooltip": "모래로 인식됩니다. 산호에 영향을 줍니다",
"betterfoliage.blocks.sandBlacklist.tooltip": "모래로 인식되지 않습니다. 산호에 영향을 주지 않습니다.",
"betterfoliage.blocks.lilypadWhitelist.tooltip": "연꽃으로 인식됩니다. 보다 나은 연꽃에 영향을 줍니다.",
"betterfoliage.blocks.lilypadBlacklist.tooltip": "연꽃으로 인식되지 않습니다. 보다 나은 연꽃에 영향을 주지 않습니다.",
"betterfoliage.blocks.cactusWhitelist.tooltip": "선인장으로 인식됩니다. 보다 나은 선인장에 영향을 줍니다.",
"betterfoliage.blocks.cactusBlacklist.tooltip": "선인장으로 인식되지 않습니다. 보다 나은 선인장에 영향을 주지 않습니다.",
"betterfoliage.leaves": "잎 추가", "betterfoliage.leaves": "잎 추가",
"betterfoliage.leaves.tooltip": "둥글게 나뭇잎을 추가시켜줍니다.", "betterfoliage.leaves.tooltip": "둥글게 나뭇잎을 추가시켜줍니다.",
@@ -99,8 +40,6 @@
"betterfoliage.shortGrass.grassEnabled.tooltip": "잔디 블록에 있는 기능을 활성화 시키겠습니까?", "betterfoliage.shortGrass.grassEnabled.tooltip": "잔디 블록에 있는 기능을 활성화 시키겠습니까?",
"betterfoliage.shortGrass.snowEnabled": "눈 활성화", "betterfoliage.shortGrass.snowEnabled": "눈 활성화",
"betterfoliage.shortGrass.snowEnabled.tooltip": "잔디 블록위에 있는 눈을 활성화 시키겠습니까?", "betterfoliage.shortGrass.snowEnabled.tooltip": "잔디 블록위에 있는 눈을 활성화 시키겠습니까?",
"betterfoliage.shortGrass.saturationThreshold": "채도 임계값",
"betterfoliage.shortGrass.saturationThreshold.tooltip": "(특정 색상을 사용하여)\"무채색\"블록과 (바이옴 색을 사용하여)\"화려한\"블록 사이의 채도 차단",
"betterfoliage.hangingGrass": "매달려있는 잔디", "betterfoliage.hangingGrass": "매달려있는 잔디",
"betterfoliage.hangingGrass.tooltip": "잔디 블록 상단 가장자리에서 아래로 매달려 있는 잔디 다발", "betterfoliage.hangingGrass.tooltip": "잔디 블록 상단 가장자리에서 아래로 매달려 있는 잔디 다발",

View File

@@ -22,67 +22,8 @@
"betterfoliage.shaderWind.tooltip": "Применить эффекты ветра с ShaderMod для этого элемента?", "betterfoliage.shaderWind.tooltip": "Применить эффекты ветра с ShaderMod для этого элемента?",
"betterfoliage.distance": "Лимит дистанции", "betterfoliage.distance": "Лимит дистанции",
"betterfoliage.distance.tooltip": "Максимальное расстояние от игрока для рендеринга этой функции", "betterfoliage.distance.tooltip": "Максимальное расстояние от игрока для рендеринга этой функции",
"betterfoliage.saturationThreshold": "Порог насыщения",
"betterfoliage.blocks": "Типы блоков", "betterfoliage.saturationThreshold.tooltip": "Насыщенность цвета разделяется на: \"обесцвеченные\" блоки (используя цвет биома) и \"цветные\" блоки (используя их собственный цвет)",
"betterfoliage.blocks.tooltip": "Настройки списка классов блоков, которые будут иметь примененные к ним функции",
"betterfoliage.blocks.dirtWhitelist": "Белый список земли",
"betterfoliage.blocks.dirtBlacklist": "Черный список земли",
"betterfoliage.blocks.dirtWhitelist.arrayEntry": "%d записей",
"betterfoliage.blocks.dirtBlacklist.arrayEntry": "%d записей",
"betterfoliage.blocks.grassWhitelist": "Белый список травы",
"betterfoliage.blocks.grassBlacklist": "Черный список травы",
"betterfoliage.blocks.grassWhitelist.arrayEntry": "%d записей",
"betterfoliage.blocks.grassBlacklist.arrayEntry": "%d записей",
"betterfoliage.blocks.leavesWhitelist": "Белый список листвы",
"betterfoliage.blocks.leavesBlacklist": "Черный список листвы",
"betterfoliage.blocks.leavesWhitelist.arrayEntry": "%d записей",
"betterfoliage.blocks.leavesBlacklist.arrayEntry": "%d записей",
"betterfoliage.blocks.cropsWhitelist": "Белый список урожая",
"betterfoliage.blocks.cropsBlacklist": "Черный список урожая",
"betterfoliage.blocks.cropsWhitelist.arrayEntry": "%d записей",
"betterfoliage.blocks.cropsBlacklist.arrayEntry": "%d записей",
"betterfoliage.blocks.logsWhitelist": "Белый список древесины",
"betterfoliage.blocks.logsBlacklist": "Черный список древесины",
"betterfoliage.blocks.logsWhitelist.arrayEntry": "%d записей",
"betterfoliage.blocks.logsBlacklist.arrayEntry": "%d записей",
"betterfoliage.blocks.sandWhitelist": "Белый список песка",
"betterfoliage.blocks.sandBlacklist": "Черный список песка",
"betterfoliage.blocks.sandWhitelist.arrayEntry": "%d записей",
"betterfoliage.blocks.sandBlacklist.arrayEntry": "%d записей",
"betterfoliage.blocks.lilypadWhitelist": "Белый список кувшинок",
"betterfoliage.blocks.lilypadBlacklist": "Черный список кувшинок",
"betterfoliage.blocks.lilypadWhitelist.arrayEntry": "%d записей",
"betterfoliage.blocks.lilypadBlacklist.arrayEntry": "%d записей",
"betterfoliage.blocks.cactusWhitelist": "Белый список кактусов",
"betterfoliage.blocks.cactusBlacklist": "Черный список кактусов",
"betterfoliage.blocks.cactusWhitelist.arrayEntry": "%d записей",
"betterfoliage.blocks.cactusBlacklist.arrayEntry": "%d записей",
"betterfoliage.blocks.dirtWhitelist.tooltip": "Блоки, которые будут восприниматься в качестве земли. Влияет на камыши, водоросли, соединенную траву.",
"betterfoliage.blocks.dirtBlacklist.tooltip": "Блоки, которые не будут восприниматься в качестве земли. Влияет на камыши, водоросли, соединенную траву.",
"betterfoliage.blocks.grassWhitelist.tooltip": "Блоки, которые будут восприниматься в качестве травы. Влияет на короткую и соединенную траву.",
"betterfoliage.blocks.grassBlacklist.tooltip": "Блоки, которые не будут восприниматься в качестве травы. Влияет на короткую и соединенную траву.",
"betterfoliage.blocks.leavesWhitelist.tooltip": "Блоки, которые будут восприниматься в качестве листвы. Влияет на дополнительную листву, падающую листву. Листва будут рендериться с ID листвы в шейдер-программах.",
"betterfoliage.blocks.leavesBlacklist.tooltip": "Блоки, которые никогда не будут восприниматься как листва. Влияет на дополнительную листву, падающую листву. Листва будут рендериться с ID листвы в шейдер-программах.",
"betterfoliage.blocks.cropsWhitelist.tooltip": "Блоки, которые будут восприниматься как культуры. Культуры будут рендериться с ID высокой травы в шейдер-программах.",
"betterfoliage.blocks.cropsBlacklist.tooltip": " Блоки, которые никогда не будут восприниматься как культуры. Культуры будут рендериться с ID высокой травы в шейдер-программах.",
"betterfoliage.blocks.logsWhitelist.tooltip": "Блоки, которые будут восприниматься в качестве деревянных брёвен. Влияет на цилиндрические брёвна.",
"betterfoliage.blocks.logsBlacklist.tooltip": "Блоки, которые никогда не будут восприниматься в качестве деревянных брёвен. Влияет на цилиндрические брёвна.",
"betterfoliage.blocks.sandWhitelist.tooltip": "Блоки, которые будут восприниматься в качестве песка. Влияет на кораллы.",
"betterfoliage.blocks.sandBlacklist.tooltip": "Блоки, которые никогда не будут восприниматься в качестве песка. Влияет на кораллы.",
"betterfoliage.blocks.lilypadWhitelist.tooltip": "Блоки, которые будут восприниматься в качестве кувшинок. Влияет на улучшенные кувшинки.",
"betterfoliage.blocks.lilypadBlacklist.tooltip": "Блоки, которые никогда не будут восприниматься в качестве кувшинок. Влияет на улучшенные кувшинки.",
"betterfoliage.blocks.cactusWhitelist.tooltip": "Блоки, которые будут восприниматься в качестве кактусов. Влияет на улучшенные кактусы.",
"betterfoliage.blocks.cactusBlacklist.tooltip": "Блоки, которые никогда не будут восприниматься в качестве кувшинок. Влияет на улучшенные кактусы.",
"betterfoliage.leaves": "Улучшенная листва", "betterfoliage.leaves": "Улучшенная листва",
"betterfoliage.leaves.tooltip": "Дополнительное округление листьев на блоках листвы.", "betterfoliage.leaves.tooltip": "Дополнительное округление листьев на блоках листвы.",
@@ -99,8 +40,6 @@
"betterfoliage.shortGrass.grassEnabled.tooltip": "Включить эту особенность для блоков травы?", "betterfoliage.shortGrass.grassEnabled.tooltip": "Включить эту особенность для блоков травы?",
"betterfoliage.shortGrass.snowEnabled": "Включить траву под снегом", "betterfoliage.shortGrass.snowEnabled": "Включить траву под снегом",
"betterfoliage.shortGrass.snowEnabled.tooltip": "Включить эту особенность для заснеженных блоков травы?", "betterfoliage.shortGrass.snowEnabled.tooltip": "Включить эту особенность для заснеженных блоков травы?",
"betterfoliage.shortGrass.saturationThreshold": "Порог насыщения",
"betterfoliage.shortGrass.saturationThreshold.tooltip": "Насыщенность цвета разделяется на: \"обесцвеченные\" блоки (используя цвет биома) и \"цветные\" блоки (используя их собственный цвет)",
"betterfoliage.hangingGrass": "Висячая трава", "betterfoliage.hangingGrass": "Висячая трава",
"betterfoliage.hangingGrass.tooltip": "Пучки травы свисают вниз с верхних краев блока травы.", "betterfoliage.hangingGrass.tooltip": "Пучки травы свисают вниз с верхних краев блока травы.",

View File

@@ -0,0 +1,175 @@
{
"key.betterfoliage.gui": "打开BF设置",
"betterfoliage.global.enabled": "模组使用",
"betterfoliage.global.enabled.tooltip": "如果关闭,BetterFoliage不会呈现任何东西",
"betterfoliage.global.nVidia": "nVidia GPU",
"betterfoliage.global.nVidia.tooltip": "明确你是否有一个nVidia GPU",
"betterfoliage.enabled": "启用",
"betterfoliage.enabled.tooltip": "要启用这个功能吗?",
"betterfoliage.hOffset": "水平偏移",
"betterfoliage.hOffset.tooltip": "在方块中这个元素是横向距离移动的",
"betterfoliage.vOffset": "垂直偏移",
"betterfoliage.vOffset.tooltip": "在方块中这个元素是横向距离移动的",
"betterfoliage.size": "尺寸",
"betterfoliage.size.tooltip": "该元素的尺寸",
"betterfoliage.heightMin": "最小高度",
"betterfoliage.heightMin.tooltip": "最小元素的高度",
"betterfoliage.heightMax": "最大高度",
"betterfoliage.heightMax.tooltip": "最大元素的高度",
"betterfoliage.population": "物体密度",
"betterfoliage.population.tooltip": "生成的概率为64分之1",
"betterfoliage.shaderWind": "光影水反中的 风的影响",
"betterfoliage.shaderWind.tooltip": "能适用于光影水反中的\"风的影响\" 这一元素?",
"betterfoliage.distance": "距离限制",
"betterfoliage.distance.tooltip": "离游戏者的最大距离,在这个范围内呈现这个特征",
"betterfoliage.saturationThreshold": "饱和度阈值",
"betterfoliage.saturationThreshold.tooltip": "色彩饱和度将介于\"无色\"方块(使用生物群系的颜色)和\"有色\"方块(使用材质特定颜色)之间",
"betterfoliage.rendererror": "§a[BF更好的叶子]§f 错误:渲染方块 %s 此位置 %s",
"betterfoliage.shaders": "着色器配置",
"betterfoliage.shaders.tooltip": "Configure integration with shaders",
"betterfoliage.shaders.leavesId": "叶子 ID",
"betterfoliage.shaders.leavesId.tooltip": "方块 ID 将作为所有叶片都使用的相同ID提供给光影水反 如果你的光影水反文件使用了一个 §6block.properties§e 文件, 你可能需要修改这个以匹配光影水反的映射",
"betterfoliage.shaders.grassId": "草 ID",
"betterfoliage.shaders.grassId.tooltip": "方块 ID 将作为所有的草和作物都使用的相同ID提供给光影水反 如果你的光影水反文件使用了一个 §6block.properties§e 文件, 你可能需要修改这个以匹配光影水反的映射",
"betterfoliage.leaves": "额外的叶片",
"betterfoliage.leaves.tooltip": "叶方块上额外的密叶",
"betterfoliage.leaves.dense": "浓密模式",
"betterfoliage.leaves.dense.tooltip": "浓密的叶子会有更多额外的叶片",
"betterfoliage.leaves.snowEnabled": "启用雪覆盖叶片",
"betterfoliage.leaves.snowEnabled.tooltip": "是否启用被雪覆盖的额外叶片?",
"betterfoliage.leaves.hideInternal": "隐藏内部的叶片",
"betterfoliage.leaves.hideInternal.tooltip": "如果该叶方块完全被其他叶方块或者固体方块包围,将不渲染额外的叶片",
"betterfoliage.shortGrass": "短草和菌丝",
"betterfoliage.shortGrass.tooltip": "一簇小草或者一丛菌丝长在合适的方块的顶部",
"betterfoliage.shortGrass.useGenerated": "为草使用mod创建的材质",
"betterfoliage.shortGrass.useGenerated.tooltip": "mod创建的材质指的是将已选用的资源包中高草的材质切一半来用",
"betterfoliage.shortGrass.myceliumEnabled": "使用在菌丝上",
"betterfoliage.shortGrass.myceliumEnabled.tooltip": "将这个特性利用到菌丝中吗?",
"betterfoliage.shortGrass.grassEnabled": "使用在草方块上",
"betterfoliage.shortGrass.grassEnabled.tooltip": "将这个特性利用到草方块中吗?",
"betterfoliage.shortGrass.snowEnabled": "使用在雪地里",
"betterfoliage.shortGrass.snowEnabled.tooltip": "使用到被雪覆盖的草方块中去?",
"betterfoliage.connectedGrass": "草的纹理的连接",
"betterfoliage.connectedGrass.tooltip": "使得草方块侧面拥有像顶面的材质",
"betterfoliage.connectedGrass.enabled": "启用",
"betterfoliage.connectedGrass.enabled.tooltip": "如果泥土上有草方块:所有的草方块的侧面换成草方块顶部的材质,",
"betterfoliage.connectedGrass.snowEnabled": "使用在被雪覆盖的草方块上",
"betterfoliage.connectedGrass.snowEnabled.tooltip": "是否应用于被雪覆盖的草方块上?",
"betterfoliage.hangingGrass": "悬挂的草",
"betterfoliage.hangingGrass.tooltip": "草会在边缘的草方块上垂下来",
"betterfoliage.hangingGrass.separation": "垂下来的距离",
"betterfoliage.hangingGrass.separation.tooltip": "有多长的挂草会从草方块上垂下来",
"betterfoliage.cactus": "更好的仙人掌",
"betterfoliage.cactus.tooltip": "用额外的仙人掌刺和圆润的形状来提高仙人掌的真实性",
"betterfoliage.cactus.sizeVariation": "尺寸变化",
"betterfoliage.cactus.sizeVariation.tooltip": "仙人掌的尺寸随机变化的范围",
"betterfoliage.lilypad": "更好的莲叶与荷花",
"betterfoliage.lilypad.tooltip": "用莲叶的根和随机的莲花以提高莲叶的真实性",
"betterfoliage.lilypad.flowerChance": "花的生成概率",
"betterfoliage.lilypad.flowerChance.tooltip": "(64个莲叶中 N 个)莲叶具有花了",
"betterfoliage.reed": "芦苇",
"betterfoliage.reed.tooltip": "芦苇会生成在浅水中的泥土块上",
"betterfoliage.reed.biomes": "生物群系的列表",
"betterfoliage.reed.biomes.tooltip": "配置允许出现芦苇的生物群系",
"betterfoliage.reed.biomes.tooltip.element": "芦苇应该出现在%s生物群落?",
"betterfoliage.algae": "藻类",
"betterfoliage.algae.tooltip": "藻类会生成在深水中的泥土块上",
"betterfoliage.algae.biomes": "生物群系的列表",
"betterfoliage.algae.biomes.tooltip": "配置允许出现藻类的生物群系",
"betterfoliage.algae.biomes.tooltip.element": "藻类应该出现在%s生物群落?",
"betterfoliage.coral": "珊瑚",
"betterfoliage.coral.tooltip": "珊瑚会生成在深水中的沙块上",
"betterfoliage.coral.size": "珊瑚的大小",
"betterfoliage.coral.size.tooltip": "珊瑚伸出的部分的大小",
"betterfoliage.coral.crustSize": "外壳尺寸",
"betterfoliage.coral.crustSize.tooltip": "珊瑚的平的部分的尺寸",
"betterfoliage.coral.chance": "珊瑚的生成概率",
"betterfoliage.coral.chance.tooltip": "64个特殊表面的方块中有N个有机会出现珊瑚",
"betterfoliage.coral.biomes": "生物群落的列表",
"betterfoliage.coral.biomes.tooltip": "配置允许出现珊瑚的生物群系",
"betterfoliage.coral.biomes.tooltip.element": "珊瑚应该出现在%s生物群落?",
"betterfoliage.coral.shallowWater": "浅海珊瑚",
"betterfoliage.coral.shallowWater.tooltip": "珊瑚应该生成在深水1格?",
"betterfoliage.netherrack": "下界岩下面的藤蔓",
"betterfoliage.netherrack.tooltip": "下界岩下面将挂上藤蔓",
"betterfoliage.fallingLeaves": "落叶",
"betterfoliage.fallingLeaves.tooltip": "落叶粒子效果 FX 会从叶方块底部下落",
"betterfoliage.fallingLeaves.speed": "粒子速度",
"betterfoliage.fallingLeaves.speed.tooltip": "总体的粒子速度",
"betterfoliage.fallingLeaves.windStrength": "柔风的强度",
"betterfoliage.fallingLeaves.windStrength.tooltip": "在晴天中体现的风的效果(默认以0为中心散播)",
"betterfoliage.fallingLeaves.stormStrength": "风暴的强度",
"betterfoliage.fallingLeaves.stormStrength.tooltip": "附加在阴天里体现的风的效果(默认以0为中心散播)",
"betterfoliage.fallingLeaves.size": "粒子大小",
"betterfoliage.fallingLeaves.chance": "粒子生成机率",
"betterfoliage.fallingLeaves.chance.tooltip": "随机渲染刻激发叶方块生成粒子的概率",
"betterfoliage.fallingLeaves.perturb": "扰动",
"betterfoliage.fallingLeaves.perturb.tooltip": "扰动的大小.增加了一个与之旋转同步的螺旋状动量",
"betterfoliage.fallingLeaves.lifetime": "最长存在时间",
"betterfoliage.fallingLeaves.lifetime.tooltip": "粒子的最长存在时间,仅在几秒内,最短时间为这个值60%%",
"betterfoliage.fallingLeaves.opacityHack": "粒子不透明",
"betterfoliage.fallingLeaves.opacityHack.tooltip": "阻止透明方块隐藏粒子,即使粒子已经在前面了.警告:可能会导致错误",
"betterfoliage.risingSoul": "灵魂沙上飘起的魂灵",
"betterfoliage.risingSoul.tooltip": "灵魂沙的顶部飘出灵魂粒子 FX",
"betterfoliage.risingSoul.chance": "粒子的生成机率",
"betterfoliage.risingSoul.chance.tooltip": "随机渲染刻激发灵魂沙生成一个粒子的概率",
"betterfoliage.risingSoul.speed": "粒子速度",
"betterfoliage.risingSoul.speed.tooltip": "灵颗粒的垂直速度",
"betterfoliage.risingSoul.perturb": "扰动",
"betterfoliage.risingSoul.perturb.tooltip": "动的大小.增加了一个与之旋转同步的螺旋状动量",
"betterfoliage.risingSoul.headSize": "魂灵的大小",
"betterfoliage.risingSoul.headSize.tooltip": "灵魂粒子的大小",
"betterfoliage.risingSoul.trailSize": "魂灵轨迹的大小",
"betterfoliage.risingSoul.trailSize.tooltip": "灵魂粒子的轨迹的初始大小",
"betterfoliage.risingSoul.opacity": "不透明度",
"betterfoliage.risingSoul.opacity.tooltip": "粒子效果的不透明度",
"betterfoliage.risingSoul.sizeDecay": "大小的衰减",
"betterfoliage.risingSoul.sizeDecay.tooltip": "灵魂轨迹粒子大小与前一刻他的大小相关",
"betterfoliage.risingSoul.opacityDecay": "不透明度的衰减",
"betterfoliage.risingSoul.opacityDecay.tooltip": "灵魂轨迹粒子不透明度与前一刻他的不透明度相关",
"betterfoliage.risingSoul.lifetime": "最长存在时间",
"betterfoliage.risingSoul.lifetime.tooltip": "粒子的最长存在时间,仅在几秒内,最短时间为这个值60%%",
"betterfoliage.risingSoul.trailLength": "魂灵轨迹长度",
"betterfoliage.risingSoul.trailLength.tooltip": "在游戏刻内粒子记录的轨迹位置的数量",
"betterfoliage.risingSoul.trailDensity": "魂灵轨迹密集程度",
"betterfoliage.risingSoul.trailDensity.tooltip": "整条轨迹中渲染每第N个轨迹位置",
"betterfoliage.roundLogs": "圆木",
"betterfoliage.roundLogs.tooltip": "使木方块拥有八角形的横截面",
"betterfoliage.roundLogs.plantsOnly": "仅仅应用于植物性材料",
"betterfoliage.roundLogs.plantsOnly.tooltip": "是否仅仅应用于木质材料和干草堆,而不是所有圆柱方块?",
"betterfoliage.roundLogs.connectSolids": "连接到固体方块上",
"betterfoliage.roundLogs.connectSolids.tooltip": "要使圆木连接到完整的固体方块上吗?",
"betterfoliage.roundLogs.connectPerpendicular": "连接到垂直的圆木上",
"betterfoliage.roundLogs.connectPerpendicular.tooltip": "要使圆木根据它的角度连接到垂直圆木上吗?",
"betterfoliage.roundLogs.lenientConnect": "巨大的圆木",
"betterfoliage.roundLogs.lenientConnect.tooltip": "在L形中也连接成巨大的圆木, 不只是2x2形",
"betterfoliage.roundLogs.connectGrass": "连接到草方块上",
"betterfoliage.roundLogs.connectGrass.tooltip": "如果树木下的泥土附近有草方块,那么它也会被渲染成草方块",
"betterfoliage.roundLogs.radiusSmall": "圆木斜面的半径",
"betterfoliage.roundLogs.radiusSmall.tooltip": "从圆木的外角砍掉多大的部分",
"betterfoliage.roundLogs.radiusLarge": "连接圆木的斜面半径",
"betterfoliage.roundLogs.radiusLarge.tooltip": "从连接着的巨大圆木的外角砍掉多大的部分",
"betterfoliage.roundLogs.dimming": "调光变暗",
"betterfoliage.roundLogs.dimming.tooltip": "将圆木阴暗面变黑的程度",
"betterfoliage.roundLogs.zProtection": "Z-Protection [Z保护]",
"betterfoliage.roundLogs.zProtection.tooltip": "用多少倍放大平行圆木连接处的面 去停止 Z-fighting (闪烁)[斑驳,两个多边形共面所出现的效果].在没有发生故障的情况下,试着将这个值设置得尽可能的高[这是Z-fighting出现的原因:多个多面体的面重叠在一起,会一直闪烁]",
"betterfoliage.roundLogs.defaultY": "默认垂直",
"betterfoliage.roundLogs.defaultY.tooltip": "如果开启, 方向不确定的圆木将会渲染成垂直的.否则, 它们仅仅渲染为cube型方块."
}

View File

@@ -1,8 +1,6 @@
// Vanilla // Vanilla
net.minecraft.block.BlockLilyPad //net.minecraft.block.LilyPadBlock
net.minecraft.class_2553
// Biomes O'Plenty // Biomes O'Plenty
biomesoplenty.common.block.BlockBOPLilypad biomesoplenty.common.block.BlockBOPLilypad
// TerraFirmaCraft
com.bioxx.tfc.Blocks.Vanilla.BlockCustomLilyPad

View File

@@ -1,2 +1,3 @@
// Vanilla // Vanilla
net.minecraft.block.LogBlock //net.minecraft.block.PillarBlock
net.minecraft.class_2465

View File

@@ -1,4 +1,4 @@
// Vanilla // Vanilla
block/column_side,side,end
block/cube_column,side,end block/cube_column,side,end
block/cube_column_horizontal,side,end
block/cube_all,all,all block/cube_all,all,all

View File

@@ -1,2 +1,3 @@
// Vanilla // Vanilla
net.minecraft.block.MyceliumBlock //net.minecraft.block.MyceliumBlock
net.minecraft.class_2418

View File

@@ -1,2 +0,0 @@
// Vanilla
net.minecraft.block.NetherrackBlock

View File

@@ -1,5 +1,3 @@
// Vanilla // Vanilla
net.minecraft.block.BlockSand //net.minecraft.block.SandBlock
net.minecraft.class_2468
// TerraFirmaCraft
com.bioxx.tfc.Blocks.Terrain.BlockSand

View File

@@ -4,6 +4,7 @@
"refmap": "betterfoliage-refmap.json", "refmap": "betterfoliage-refmap.json",
"compatibilityLevel": "JAVA_8", "compatibilityLevel": "JAVA_8",
"minVersion": "0.8-SNAPSHOT", "minVersion": "0.8-SNAPSHOT",
"plugin": "mods.betterfoliage.MixinConfigPlugin",
"mixins": [ "mixins": [
], ],
"client": [ "client": [
@@ -13,7 +14,9 @@
"MixinClientWorld", "MixinClientWorld",
"MixinClientChunkManager", "MixinClientChunkManager",
"MixinClientChunkManagerChunkMap", "MixinClientChunkManagerChunkMap",
"MixinModelLoader" "MixinModelLoader",
"MixinModelLoaderVanilla",
"MixinModelLoaderOptifine"
], ],
"server": [ "server": [
], ],

Binary file not shown.

Before

Width:  |  Height:  |  Size: 682 B

View File

@@ -39,9 +39,9 @@
], ],
"depends": { "depends": {
"fabricloader": ">=0.7.3", "fabricloader": ">=0.11.3",
"fabric": "*", "fabric": "*",
"fabric-language-kotlin": "*", "fabric-language-kotlin": "*",
"minecraft": "1.14.4" "minecraft": "1.16.5"
} }
} }