Compare commits

...

13 Commits

Author SHA1 Message Date
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
81 changed files with 1761 additions and 1394 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.2
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)
} }
class ShortGrassConfig(node: ConfigNode) : DelegatingConfigGroup(node), PopulationConfigData { class ShortGrassConfig(node: ConfigNode) : DelegatingConfigGroup(node), PopulationConfigData {
@@ -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)
object StandardCactusDiscovery : ConfigurableModelDiscovery() { ctx.sprites.add(StandardCactusModel.cactusCrossSprite)
override val logger = BetterFoliage.logDetail }
override val matchClasses = SimpleBlockMatcher(CactusBlock::class.java) super.processModel(ctx)
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()
}
override fun processModel(ctx: ModelDiscoveryContext) {
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)
}
} }
object DirtDiscovery : ModelDiscoveryBase() { object StandardDirtKey : ModelWrapKey() {
override val logger = BetterFoliage.logDetail override fun bake(ctx: ModelBakingContext, wrapped: BasicBakedModel) = DirtModel(meshifySolid(wrapped))
override fun processModel(ctx: ModelDiscoveryContext, atlas: Consumer<Identifier>) =
if (ctx.state.block in DIRT_BLOCKS) DirtKey else null
} }
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()
}
override fun processModel(ctx: ModelDiscoveryContext) {
if (ctx.getUnbaked() is JsonUnbakedModel && ctx.blockState.block in NETHERRACK_BLOCKS) {
BetterFoliage.blockTypes.dirt.add(ctx.blockState)
ctx.addReplacement(StandardNetherrackKey)
// RenderTypeLookup.setRenderLayer(ctx.blockState.block, ::canRenderInLayer)
}
super.processModel(ctx)
}
} }
object NetherrackDiscovery : ModelDiscoveryBase() { object StandardNetherrackKey : ModelWrapKey() {
override val logger = BetterFoliage.logDetail override fun bake(ctx: ModelBakingContext, wrapped: BasicBakedModel) = StandardNetherrackModel(meshifyCutoutMipped(wrapped))
val netherrackBlocks = listOf(Blocks.NETHERRACK)
override fun processModel(ctx: ModelDiscoveryContext, atlas: Consumer<Identifier>) =
if (ctx.state.block in netherrackBlocks) NetherrackKey else null
} }
class NetherrackModel(wrapped: BakedModel) : WrappedBakedModel(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 {
@@ -34,8 +40,9 @@ class RisingSoulParticle(
override val isValid: Boolean get() = true override val isValid: Boolean get() = true
override fun update() { override fun update() {
val phase = initialPhase + (age.toDouble() * PI2 / 64.0 ) val phase = initialPhase + (age.toDouble() * PI2 / 64.0)
val cosPhase = cos(phase); val 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, null
)
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

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