From e5cd9f57b56e845866ea051891e10a3c1330257a Mon Sep 17 00:00:00 2001 From: embeddedt <42941056+embeddedt@users.noreply.github.com> Date: Sun, 9 Apr 2023 13:52:40 -0400 Subject: [PATCH] CTM support --- build.gradle | 1 + .../DynamicModelBakeEvent.java | 20 ++++- .../dynamic_resources/ModelBakeryMixin.java | 6 +- .../ctm/CTMPackReloadListenerMixin.java | 75 +++++++++++++++++ .../ctm/TextureMetadataHandlerMixin.java | 82 +++++++++++++++++++ src/main/resources/modernfix.mixins.json | 2 + 6 files changed, 182 insertions(+), 4 deletions(-) create mode 100644 src/main/java/org/embeddedt/modernfix/mixin/perf/dynamic_resources/ctm/CTMPackReloadListenerMixin.java create mode 100644 src/main/java/org/embeddedt/modernfix/mixin/perf/dynamic_resources/ctm/TextureMetadataHandlerMixin.java diff --git a/build.gradle b/build.gradle index 31dd054f..00e55d3f 100644 --- a/build.gradle +++ b/build.gradle @@ -96,6 +96,7 @@ dependencies { modCompileOnly("curse.maven:babel-436964:3196072") modCompileOnly("curse.maven:twforest-227639:3575220") modRuntimeOnly("curse.maven:ferritecore-429235:4074330") + modCompileOnly("team.chisel.ctm:CTM:${ctm_version}") } tasks.withType(JavaCompile) { diff --git a/src/main/java/org/embeddedt/modernfix/dynamicresources/DynamicModelBakeEvent.java b/src/main/java/org/embeddedt/modernfix/dynamicresources/DynamicModelBakeEvent.java index 8a199d43..76b6c3d0 100644 --- a/src/main/java/org/embeddedt/modernfix/dynamicresources/DynamicModelBakeEvent.java +++ b/src/main/java/org/embeddedt/modernfix/dynamicresources/DynamicModelBakeEvent.java @@ -1,8 +1,12 @@ package org.embeddedt.modernfix.dynamicresources; import net.minecraft.client.resources.model.BakedModel; +import net.minecraft.client.resources.model.ModelManager; +import net.minecraft.client.resources.model.UnbakedModel; import net.minecraft.resources.ResourceLocation; +import net.minecraftforge.client.model.ModelLoader; import net.minecraftforge.eventbus.api.Event; +import net.minecraftforge.fml.event.lifecycle.IModBusEvent; /** * Fired when a model is baked dynamically. Intended to be used as a replacement for ModelBakeEvent @@ -11,12 +15,16 @@ import net.minecraftforge.eventbus.api.Event; * Note that this event can fire many times for the same resource location, as models are unloaded * if unused/under memory pressure. */ -public class DynamicModelBakeEvent extends Event { +public class DynamicModelBakeEvent extends Event implements IModBusEvent { private final ResourceLocation location; private BakedModel model; - public DynamicModelBakeEvent(ResourceLocation location, BakedModel model) { + private final UnbakedModel unbakedModel; + private final ModelLoader modelLoader; + public DynamicModelBakeEvent(ResourceLocation location, UnbakedModel unbakedModel, BakedModel model, ModelLoader loader) { this.location = location; this.model = model; + this.unbakedModel = unbakedModel; + this.modelLoader = loader; } public ResourceLocation getLocation() { @@ -27,6 +35,14 @@ public class DynamicModelBakeEvent extends Event { return this.model; } + public UnbakedModel getUnbakedModel() { + return this.unbakedModel; + } + + public ModelLoader getModelLoader() { + return this.modelLoader; + } + public void setModel(BakedModel model) { this.model = model; } diff --git a/src/main/java/org/embeddedt/modernfix/mixin/perf/dynamic_resources/ModelBakeryMixin.java b/src/main/java/org/embeddedt/modernfix/mixin/perf/dynamic_resources/ModelBakeryMixin.java index f1a6b8f4..56522a38 100644 --- a/src/main/java/org/embeddedt/modernfix/mixin/perf/dynamic_resources/ModelBakeryMixin.java +++ b/src/main/java/org/embeddedt/modernfix/mixin/perf/dynamic_resources/ModelBakeryMixin.java @@ -29,8 +29,10 @@ import net.minecraft.world.level.block.Block; import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.block.state.StateDefinition; import net.minecraftforge.client.ForgeHooksClient; +import net.minecraftforge.client.model.ModelLoader; import net.minecraftforge.client.model.ModelLoaderRegistry; import net.minecraftforge.common.MinecraftForge; +import net.minecraftforge.fml.ModLoader; import org.apache.commons.lang3.tuple.Triple; import org.apache.logging.log4j.Logger; import org.embeddedt.modernfix.ModernFix; @@ -291,8 +293,8 @@ public abstract class ModelBakeryMixin { if(ibakedmodel == null) { ibakedmodel = iunbakedmodel.bake((ModelBakery) (Object) this, textureGetter, arg2, arg); } - DynamicModelBakeEvent event = new DynamicModelBakeEvent(arg, ibakedmodel); - MinecraftForge.EVENT_BUS.post(event); + DynamicModelBakeEvent event = new DynamicModelBakeEvent(arg, iunbakedmodel, ibakedmodel, (ModelLoader)(Object)this); + ModLoader.get().postEvent(event); this.bakedCache.put(triple, event.getModel()); return event.getModel(); } diff --git a/src/main/java/org/embeddedt/modernfix/mixin/perf/dynamic_resources/ctm/CTMPackReloadListenerMixin.java b/src/main/java/org/embeddedt/modernfix/mixin/perf/dynamic_resources/ctm/CTMPackReloadListenerMixin.java new file mode 100644 index 00000000..2fcd6028 --- /dev/null +++ b/src/main/java/org/embeddedt/modernfix/mixin/perf/dynamic_resources/ctm/CTMPackReloadListenerMixin.java @@ -0,0 +1,75 @@ +package org.embeddedt.modernfix.mixin.perf.dynamic_resources.ctm; + +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; +import net.minecraft.client.renderer.ItemBlockRenderTypes; +import net.minecraft.client.renderer.RenderType; +import net.minecraft.client.renderer.block.BlockModelShaper; +import net.minecraft.client.resources.model.BakedModel; +import net.minecraft.client.resources.model.ModelResourceLocation; +import net.minecraft.client.resources.model.MultiPartBakedModel; +import net.minecraft.client.resources.model.WeightedBakedModel; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraftforge.common.MinecraftForge; +import net.minecraftforge.eventbus.api.EventPriority; +import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext; +import net.minecraftforge.registries.ForgeRegistries; +import net.minecraftforge.registries.IRegistryDelegate; +import org.embeddedt.modernfix.dynamicresources.DynamicModelBakeEvent; +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Overwrite; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; +import team.chisel.ctm.client.model.AbstractCTMBakedModel; +import team.chisel.ctm.client.util.CTMPackReloadListener; + +import java.util.Map; +import java.util.function.Predicate; + +@Mixin(CTMPackReloadListener.class) +public abstract class CTMPackReloadListenerMixin { + @Shadow @Final private static Map, Predicate> blockRenderChecks; + + @Shadow protected abstract Predicate getLayerCheck(BlockState state, BakedModel model); + + @Shadow protected abstract Predicate getExistingRenderCheck(Block block); + + private Map locationToState = new Object2ObjectOpenHashMap<>(); + + @Inject(method = "", at = @At("RETURN")) + private void onInit(CallbackInfo ci) { + FMLJavaModLoadingContext.get().getModEventBus().addListener(EventPriority.LOW, this::onModelBake); + } + + @Overwrite(remap = false) + private void refreshLayerHacks() { + blockRenderChecks.forEach((b, p) -> ItemBlockRenderTypes.setRenderLayer((Block) b.get(), p)); + blockRenderChecks.clear(); + if(locationToState.isEmpty()) { + for(Block block : ForgeRegistries.BLOCKS.getValues()) { + for(BlockState state : block.getStateDefinition().getPossibleStates()) { + locationToState.put(BlockModelShaper.stateToModelLocation(state), state); + } + } + } + } + + private void onModelBake(DynamicModelBakeEvent event) { + if(!(event.getModel() instanceof AbstractCTMBakedModel || event.getModel() instanceof WeightedBakedModel || event.getModel() instanceof MultiPartBakedModel)) + return; + BlockState state = locationToState.get(event.getLocation()); + if(state == null) + return; + Block block = state.getBlock(); + if(blockRenderChecks.containsKey(block.delegate)) + return; + Predicate newPredicate = this.getLayerCheck(state, event.getModel()); + if(newPredicate != null) { + blockRenderChecks.put(block.delegate, this.getExistingRenderCheck(block)); + ItemBlockRenderTypes.setRenderLayer(block, newPredicate); + } + } +} diff --git a/src/main/java/org/embeddedt/modernfix/mixin/perf/dynamic_resources/ctm/TextureMetadataHandlerMixin.java b/src/main/java/org/embeddedt/modernfix/mixin/perf/dynamic_resources/ctm/TextureMetadataHandlerMixin.java new file mode 100644 index 00000000..4d52de4e --- /dev/null +++ b/src/main/java/org/embeddedt/modernfix/mixin/perf/dynamic_resources/ctm/TextureMetadataHandlerMixin.java @@ -0,0 +1,82 @@ +package org.embeddedt.modernfix.mixin.perf.dynamic_resources.ctm; + +import com.mojang.datafixers.util.Pair; +import it.unimi.dsi.fastutil.objects.Object2BooleanMap; +import net.minecraft.client.resources.model.BakedModel; +import net.minecraft.client.resources.model.Material; +import net.minecraft.client.resources.model.UnbakedModel; +import net.minecraft.resources.ResourceLocation; +import net.minecraftforge.client.model.ModelLoader; +import net.minecraftforge.eventbus.api.SubscribeEvent; +import org.embeddedt.modernfix.dynamicresources.DynamicModelBakeEvent; +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import team.chisel.ctm.CTM; +import team.chisel.ctm.client.model.AbstractCTMBakedModel; +import team.chisel.ctm.client.texture.IMetadataSectionCTM; +import team.chisel.ctm.client.util.ResourceUtil; +import team.chisel.ctm.client.util.TextureMetadataHandler; + +import javax.annotation.Nonnull; +import java.io.IOException; +import java.util.*; + +@Mixin(TextureMetadataHandler.class) +public abstract class TextureMetadataHandlerMixin { + + @Shadow @Nonnull protected abstract BakedModel wrap(ResourceLocation loc, UnbakedModel model, BakedModel object, ModelLoader loader) throws IOException; + + @SubscribeEvent + public void onDynamicModelBake(DynamicModelBakeEvent event) { + UnbakedModel rootModel = event.getUnbakedModel(); + BakedModel baked = event.getModel(); + ResourceLocation rl = event.getLocation(); + if (!(baked instanceof AbstractCTMBakedModel) && !baked.isCustomRenderer()) { + Deque dependencies = new ArrayDeque<>(); + Set seenModels = new HashSet<>(); + dependencies.push(rl); + seenModels.add(rl); + boolean shouldWrap = false; + Set> errors = new HashSet<>(); + // Breadth-first loop through dependencies, exiting as soon as a CTM texture is found, and skipping duplicates/cycles + while (!shouldWrap && !dependencies.isEmpty()) { + ResourceLocation dep = dependencies.pop(); + UnbakedModel model; + try { + model = dep == rl ? rootModel : event.getModelLoader().getModel(dep); + } catch (Exception e) { + continue; + } + + Collection textures = model.getMaterials(event.getModelLoader()::getModel, errors); + Collection newDependencies = model.getDependencies(); + for (Material tex : textures) { + IMetadataSectionCTM meta = null; + // Cache all dependent texture metadata + try { + meta = ResourceUtil.getMetadata(ResourceUtil.spriteToAbsolute(tex.texture())); + } catch (IOException e) {} // Fallthrough + if (meta != null) { + // At least one texture has CTM metadata, so we should wrap this model + shouldWrap = true; + } + } + + for (ResourceLocation newDep : newDependencies) { + if (seenModels.add(newDep)) { + dependencies.push(newDep); + } + } + } + if (shouldWrap) { + try { + event.setModel(wrap(rl, rootModel, baked, event.getModelLoader())); + dependencies.clear(); + } catch (IOException e) { + CTM.logger.error("Could not wrap model " + rl + ". Aborting...", e); + } + } + } + } +} diff --git a/src/main/resources/modernfix.mixins.json b/src/main/resources/modernfix.mixins.json index 36ad8afb..0d212903 100644 --- a/src/main/resources/modernfix.mixins.json +++ b/src/main/resources/modernfix.mixins.json @@ -78,6 +78,8 @@ "perf.dynamic_resources.BlockModelShaperMixin", "perf.dynamic_resources.ItemModelShaperMixin", "perf.dynamic_resources.ModelBakeryMixin", + "perf.dynamic_resources.ctm.TextureMetadataHandlerMixin", + "perf.dynamic_resources.ctm.CTMPackReloadListenerMixin", "perf.model_optimizations.OBJLoaderMixin", "perf.model_optimizations.SelectorMixin", "perf.model_optimizations.TransformationMatrixMixin",