Intro to Advanced Models
While simple models and blockstates are all well and good, they aren’t dynamic. For example, Forge’s Universal Bucket can hold all kinds of fluid, which may be mod added. It has to dynamically piece the model together from the base bucket model and the liquid. How is that done? Enter IModel.
In order to understand how this works, let’s go through the internals of the model system. Throughout this section, you will probably have to refer to this to grasp a clear understanding of what is happening. This is true in reverse as well. You will likely not understand everything happening here, but as you move through this section you should be able to grasp more and more of it until everything is clear.
Important
If this is your first time reading through do not skip anything! It is imperative that you read everything in order so as to build a comprehensive understanding! In the same vein, if this is your first time reading, do not follow links if they lead to pages further ahead in the section.
-
A set of
ModelResourceLocations are marked as models to be loaded throughModelLoader.-
For items, their models must be manually marked for loading with
ModelLoader.registerItemVariants. (ModelLoader.setCustomModelResourceLocationdoes this.) -
For blocks, their statemappers produce a
Map<IBlockState, ModelResourceLocation>. All blocks are iterated, and then the values of this map are marked to be loaded.
-
-
IModels are loaded from eachModelResourceLocationand cached in aMap<ModelResourceLocation, IModel>.-
An
IModelis loaded from the onlyICustomModelLoaderthat accepts it. (Multiple loaders attempting to load a model will cause aLoaderException.) If none is found and theResourceLocationis actually aModelResourceLocation(that is, this is not a normal model; it’s actually a blockstate variant), it goes to the blockstate loader (VariantLoader). Otherwise the model is a normal vanilla JSON model and is loaded the vanilla way (VanillaLoader). -
A vanilla JSON model (
models/item/*.jsonormodels/block/*.json), when loaded, is aModelBlock(yes, even for items). This is a vanilla class that is not related toIModelin any way. To rectify this, it gets wrapped into aVanillaModelWrapper, which does implementIModel. -
A vanilla/Forge blockstate variant, when loaded, first loads the entire blockstate JSON it comes from. The JSON is deserialized into a
ModelBlockDefinitionthat is then cached to the path of the JSON. A list of variant definitions is then extracted from theModelBlockDefinitionand placed into aWeightedRandomModel. -
When loading a vanilla JSON item model (
models/item/*.json), the model is requested from aModelResourceLocationwith variantinventory(e.g. the dirt block item model isminecraft:dirt#inventory); thereby causing the model to be loaded byVariantLoader(as it is aModelResourceLocation). WhenVariantLoaderfails to load the model from the blockstate JSON, it falls back toVanillaLoader.- The most important side-effect of this is that if an error occurs in
VariantLoader, it will try to also load the model viaVanillaLoader. If this also fails, then the resulting exception produces two stacktraces. The first is theVanillaLoaderstacktrace, and the second is fromVariantLoader. When debugging model errors, it is important to ensure that the right stacktrace is being analyzed.
- The most important side-effect of this is that if an error occurs in
-
An
IModelcan be loaded from aResourceLocationor retrieved from the cache by invokingModelLoaderRegistry.getModelor one of the exception handling alternatives.
-
-
All texture dependencies of the loaded models are loaded and stitched into the atlas. The atlas is a giant texture that contains all the model textures pasted together. When a model refers to a texture, during rendering, an extra UV offset is applied to match the texture’s position in the atlas.
-
Every model is baked by calling
model.bake(model.getDefaultState(), ...). The resultingIBakedModels are cached in aMap<ModelResourceLocation, IBakedModel>. -
This map is then stored in the
ModelManager. TheModelManagerfor an instance of the game is stored inMinecraft::modelManager, which is private with no getter.-
The
ModelManagermay be acquired, without reflection or access tranformation, throughMinecraft.getMinecraft().getRenderItem().getItemModelMesher().getModelManager()orMinecraft.getMinecraft().getBlockRenderDispatcher().getBlockModelShapes().getModelManager(). Contrary to their names, these are equivalent. -
One may request an
IBakedModelfrom the cache (without loading and/or baking models, only accessing the existing cache) in aModelManagerwithModelManager::getModel.
-
-
Eventually, an
IBakedModelwill be rendered. This is done by callingIBakedModel::getQuads. The result is a list ofBakedQuads (quadrilaterals: polygons with 4 vertices). These can then be passed to the GPU for rendering. Items and blocks diverge a bit here, but it’s relatively simple to follow.-
Items in vanilla have properties and overrides. To facilitate this,
IBakedModeldefinesgetOverrides, which returns anItemOverrideList.ItemOverrideListdefineshandleItemState, which takes the original model, the entity, world, and stack, to find the final model. Overrides are applied before all other operations on the model, includinggetQuads. AsIBlockStateis not applicable to items,IBakedModel::getQuadsreceivesnullas its state parameter when being rendered as an item. -
Blocks have blockstates, and when a block’s
IBakedModelis being rendered, theIBlockStateis passed directly into thegetQuadsmethod. In the context of models only, blockstates can have an extra set of properties, known as unlisted properties.
-