CTM support

This commit is contained in:
embeddedt 2023-04-09 13:52:40 -04:00
parent 8937ca020d
commit e5cd9f57b5
No known key found for this signature in database
GPG Key ID: A69433EC199B5613
6 changed files with 182 additions and 4 deletions

View File

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

View File

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

View File

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

View File

@ -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<IRegistryDelegate<Block>, Predicate<RenderType>> blockRenderChecks;
@Shadow protected abstract Predicate<RenderType> getLayerCheck(BlockState state, BakedModel model);
@Shadow protected abstract Predicate<RenderType> getExistingRenderCheck(Block block);
private Map<ModelResourceLocation, BlockState> locationToState = new Object2ObjectOpenHashMap<>();
@Inject(method = "<init>", 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<RenderType> newPredicate = this.getLayerCheck(state, event.getModel());
if(newPredicate != null) {
blockRenderChecks.put(block.delegate, this.getExistingRenderCheck(block));
ItemBlockRenderTypes.setRenderLayer(block, newPredicate);
}
}
}

View File

@ -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<ResourceLocation> dependencies = new ArrayDeque<>();
Set<ResourceLocation> seenModels = new HashSet<>();
dependencies.push(rl);
seenModels.add(rl);
boolean shouldWrap = false;
Set<Pair<String, String>> 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<Material> textures = model.getMaterials(event.getModelLoader()::getModel, errors);
Collection<ResourceLocation> 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);
}
}
}
}
}

View File

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