Jump to content

[1.12.1] Question about ModelBakeEvent and "baking" in general


Recommended Posts

Posted

So I've been revisiting modding with fluids and that is going reasonably, but I wanted to try making an item fluid handler and that works fine too except I wanted to make the texture change based on whether it is full or not (like a UniversalBucket). So I looked at the ModelDynBucket and tried to follow that, but now running into a few things I have questions about.

 

First of all, I think the concept of "baking" simply means that you take in a base model and have an opportunity to transform it (or replace it) based on custom state data. Is that a correct way to describe it?

 

So I have a bunch of related classes, the IModel, and ICustomModelLoader, an IBakedModel, and an ItemOverrideList. But I'm getting confused about how these all work together.

 

Starting with the ModelRegistryEvent and ModelBakeEvent. So am I supposed to handle both of these? Like register my IModel in the ModelRegistryEvent and then do something to transform/bake it during the ModelBakeEvent? Or since I have a baked model do I skip the ModelRegistryEvent and just do something in the ModelBakeEvent? Or is the ModelBakeEvent fired after all the baking is done and meant to allow processing after the baking? ... I looked at a bunch of github repositories and most mods seem to use ModelBakeEvent for doing things like replacing other mod's models and stuff and i don't see a lot of baking going on within the event handlers....

 

IIf I look at the bake() method it takes a parameter called IModelState but this seems to be unrelated to the item state but rather is a way to represent a bunch of transforms

 

Also, should I be registering the IModel or an IBakedModel?

 

And in the ModelBakeEvent is it just about registering, or do I need to call the bake() method in my IModel somehow? The bake() method takes a bunch of parameters I don't think I really have access to. So I think the bake() method is called automatically by the handleItemState(0 method of my IItemOverrideList class which also is the one that will check the state and change the texture of my item accordingly.

 

So is handleItemState() called during the game play or is it called once (per permutation of possible custom states) during the model loading phase of starting the game? 

 

And are JSONs involved with any of this? For the UniversalBucket that I'm mostly copying there does appear to be a blockstates file for it, but it doesn't seem to have much interesting in it...

 

So many questions!!! My most pressing question is what should I register or do in the ModelRegistryEvent and ModelBakeEvent?

 

Here is my current code for the classes involved:

package com.blogspot.jabelarminecraft.examplemod.client.models;

import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.function.Function;

import javax.annotation.Nullable;
import javax.vecmath.Matrix4f;

import org.apache.commons.lang3.tuple.Pair;

import com.blogspot.jabelarminecraft.examplemod.MainMod;
import com.blogspot.jabelarminecraft.examplemod.init.ModFluids;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Maps;

import net.minecraft.block.state.IBlockState;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.block.model.BakedQuad;
import net.minecraft.client.renderer.block.model.IBakedModel;
import net.minecraft.client.renderer.block.model.ItemCameraTransforms;
import net.minecraft.client.renderer.block.model.ItemCameraTransforms.TransformType;
import net.minecraft.client.renderer.block.model.ItemOverrideList;
import net.minecraft.client.renderer.block.model.ModelResourceLocation;
import net.minecraft.client.renderer.texture.TextureAtlasSprite;
import net.minecraft.client.renderer.vertex.VertexFormat;
import net.minecraft.client.resources.IResourceManager;
import net.minecraft.entity.EntityLivingBase;
import net.minecraft.item.ItemStack;
import net.minecraft.util.EnumFacing;
import net.minecraft.util.ResourceLocation;
import net.minecraft.world.World;
import net.minecraftforge.client.model.ICustomModelLoader;
import net.minecraftforge.client.model.IModel;
import net.minecraftforge.client.model.ItemLayerModel;
import net.minecraftforge.client.model.ItemTextureQuadConverter;
import net.minecraftforge.client.model.PerspectiveMapWrapper;
import net.minecraftforge.client.model.SimpleModelState;
import net.minecraftforge.common.model.IModelState;
import net.minecraftforge.common.model.TRSRTransformation;
import net.minecraftforge.fluids.Fluid;
import net.minecraftforge.fluids.FluidRegistry;
import net.minecraftforge.fluids.FluidStack;
import net.minecraftforge.fluids.FluidUtil;

public final class ModelSlimeBag implements IModel
{
    public static final ModelResourceLocation LOCATION = new ModelResourceLocation(new ResourceLocation(MainMod.MODID, "slime_bag"), "inventory");

    // minimal Z offset to prevent depth-fighting
    private static final float NORTH_Z_FLUID = 7.498f / 16f;
    private static final float SOUTH_Z_FLUID = 8.502f / 16f;

    public static final ModelSlimeBag MODEL = new ModelSlimeBag();

    @Nullable
    private final ResourceLocation emptyLocation = new ResourceLocation(MainMod.MODID, "slime_bag_empty");
    @Nullable
    private final ResourceLocation filledLocation = new ResourceLocation(MainMod.MODID, "slime_bag");
    @Nullable
    private final Fluid fluid;

    public ModelSlimeBag()
    {
    	this(ModFluids.SLIME);
    }
    
    public ModelSlimeBag(Fluid parFluid)
    {
    	fluid = parFluid;
    }

    @Override
    public Collection<ResourceLocation> getTextures()
    {
        ImmutableSet.Builder<ResourceLocation> builder = ImmutableSet.builder();
        if (emptyLocation != null)
            builder.add(emptyLocation);
        if (filledLocation != null)
            builder.add(filledLocation);

        return builder.build();
    }

    @Override
    public IBakedModel bake(IModelState state, VertexFormat format,
                                    Function<ResourceLocation, TextureAtlasSprite> bakedTextureGetter)
    {

        ImmutableMap<TransformType, TRSRTransformation> transformMap = PerspectiveMapWrapper.getTransforms(state);

        TRSRTransformation transform = state.apply(Optional.empty()).orElse(TRSRTransformation.identity());
        TextureAtlasSprite fluidSprite = null;
        ImmutableList.Builder<BakedQuad> builder = ImmutableList.builder();

        if(fluid != null) {
            fluidSprite = bakedTextureGetter.apply(fluid.getStill());
        }

        if (emptyLocation != null)
        {
            // build base (insidest)
            IBakedModel model = (new ItemLayerModel(ImmutableList.of(emptyLocation))).bake(state, format, bakedTextureGetter);
            builder.addAll(model.getQuads(null, null, 0));
        }
        if (filledLocation != null && fluidSprite != null)
        {
            TextureAtlasSprite liquid = bakedTextureGetter.apply(filledLocation);
            // build liquid layer (inside)
            builder.addAll(ItemTextureQuadConverter.convertTexture(format, transform, liquid, fluidSprite, NORTH_Z_FLUID, EnumFacing.NORTH, fluid.getColor()));
            builder.addAll(ItemTextureQuadConverter.convertTexture(format, transform, liquid, fluidSprite, SOUTH_Z_FLUID, EnumFacing.SOUTH, fluid.getColor()));
        }


        return new Baked(this, builder.build(), fluidSprite, format, Maps.immutableEnumMap(transformMap), Maps.newHashMap());
    }

    /**
     * Sets the liquid in the model.
     * fluid - Name of the fluid in the FluidRegistry
     * If the fluid can't be found, water is used
     */
    @Override
    public ModelSlimeBag process(ImmutableMap<String, String> customData)
    {
        String fluidName = customData.get("fluid");
        Fluid fluid = FluidRegistry.getFluid(fluidName);

        if (fluid == null) fluid = this.fluid;

        // create new model with correct liquid
        return new ModelSlimeBag(fluid);
    }

    /**
     * Allows to use different textures for the model.
     * There are 3 layers:
     * base - The empty bucket/container
     * fluid - A texture representing the liquid portion. Non-transparent = liquid
     * cover - An overlay that's put over the liquid (optional)
     * <p/>
     * If no liquid is given a hardcoded variant for the bucket is used.
     */
    @Override
    public ModelSlimeBag retexture(ImmutableMap<String, String> textures)
    {
    	// don't allow retexturing

        return new ModelSlimeBag(fluid);
    }

    public enum CustomModelLoader implements ICustomModelLoader
    {
        INSTANCE;

        @Override
        public boolean accepts(ResourceLocation modelLocation)
        {
            return modelLocation.getResourceDomain().equals(MainMod.MODID) && modelLocation.getResourcePath().contains("slime_bag");
        }

        @Override
        public IModel loadModel(ResourceLocation modelLocation)
        {
            return MODEL;
        }

        @Override
        public void onResourceManagerReload(IResourceManager resourceManager)
        {
            // no need to clear cache since we create a new model instance
        }
    }

    private static final class BakedOverrideHandler extends ItemOverrideList
    {
        public static final BakedOverrideHandler INSTANCE = new BakedOverrideHandler();
        private BakedOverrideHandler()
        {
            super(ImmutableList.of());
        }

        @Override
        public IBakedModel handleItemState(IBakedModel originalModel, ItemStack stack, @Nullable World world, @Nullable EntityLivingBase entity)
        {
            FluidStack fluidStack = FluidUtil.getFluidContained(stack);
            
            // not a fluid item apparently
            if (fluidStack == null)
            {
            	// DEBUG
            	System.out.println("fluid stack is null, returning original model");
            	
                // empty bucket
                return originalModel;
            }
            
            // DEBUG
            System.out.print("Fluid stack was not null and fluid amount = "+fluidStack.amount);

            Baked model = (Baked)originalModel;

            Fluid fluid = fluidStack.getFluid();
            String name = fluid.getName();

            if (!model.cache.containsKey(name))
            {
            	// DEBUG
            	System.out.println("The model cache does not have key for fluid name");
            	
                IModel parent = model.parent.process(ImmutableMap.of("fluid", name));
                Function<ResourceLocation, TextureAtlasSprite> textureGetter;
                textureGetter = location -> Minecraft.getMinecraft().getTextureMapBlocks().getAtlasSprite(location.toString());

                IBakedModel bakedModel = parent.bake(new SimpleModelState(model.transforms), model.format, textureGetter);
                model.cache.put(name, bakedModel);
                return bakedModel;
            }
            
            // DEBUG
            System.out.println("The model cache already has key so returning the model");

            return model.cache.get(name);
        }
    }

    // the filled bucket is based on the empty bucket
    private static final class Baked implements IBakedModel
    {

        private final ModelSlimeBag parent;
        // FIXME: guava cache?
        private final Map<String, IBakedModel> cache; // contains all the baked models since they'll never change
        private final ImmutableMap<TransformType, TRSRTransformation> transforms;
        private final ImmutableList<BakedQuad> quads;
        private final TextureAtlasSprite particle;
        private final VertexFormat format;

        public Baked(ModelSlimeBag parent,
                              ImmutableList<BakedQuad> quads, TextureAtlasSprite particle, VertexFormat format, ImmutableMap<ItemCameraTransforms.TransformType, TRSRTransformation> transforms,
                              Map<String, IBakedModel> cache)
        {
            this.quads = quads;
            this.particle = particle;
            this.format = format;
            this.parent = parent;
            this.transforms = transforms;
            this.cache = cache;
        }

        @Override
        public ItemOverrideList getOverrides()
        {
            return BakedOverrideHandler.INSTANCE;
        }

        @Override
        public Pair<? extends IBakedModel, Matrix4f> handlePerspective(TransformType cameraTransformType)
        {
            return PerspectiveMapWrapper.handlePerspective(this, transforms, cameraTransformType);
        }

        @Override
        public List<BakedQuad> getQuads(@Nullable IBlockState state, @Nullable EnumFacing side, long rand)
        {
            if(side == null) return quads;
            return ImmutableList.of();
        }

        @Override
		public boolean isAmbientOcclusion() { return true;  }
        @Override
		public boolean isGui3d() { return false; }
        @Override
		public boolean isBuiltInRenderer() { return false; }
        @Override
		public TextureAtlasSprite getParticleTexture() { return particle; }
    }
}

 

Check out my tutorials here: http://jabelarminecraft.blogspot.com/

Posted

Okay, got my custom item model working!

 

Do have a question though. You said that "ModelBakeEvent" is virtually never needed. However, in code that I've seen (and in my case copied) that event is still handled to setCustomMeshDefinition() for the loader and registerItemVariants() to the bakery. I tried not doing that and it then doesn't find the model.

 

Are those two methods supposed to be done in the ModelBakeEvent, or are they supposed to be done in the ModelRegistryEvent? Or does it matter?

Check out my tutorials here: http://jabelarminecraft.blogspot.com/

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Guest
Unfortunately, your content contains terms that we do not allow. Please edit your content to remove the highlighted words below.
Reply to this topic...

×   Pasted as rich text.   Restore formatting

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

Announcements



×
×
  • Create New...

Important Information

By using this site, you agree to our Terms of Use.