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
ModelResourceLocation
s are marked as models to be loaded throughModelLoader
.-
For items, their models must be manually marked for loading with
ModelLoader.registerItemVariants
. (ModelLoader.setCustomModelResourceLocation
does 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.
-
-
IModel
s are loaded from eachModelResourceLocation
and cached in aMap<ModelResourceLocation, IModel>
.-
An
IModel
is loaded from the onlyICustomModelLoader
that accepts it. (Multiple loaders attempting to load a model will cause aLoaderException
.) If none is found and theResourceLocation
is 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/*.json
ormodels/block/*.json
), when loaded, is aModelBlock
(yes, even for items). This is a vanilla class that is not related toIModel
in 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
ModelBlockDefinition
that is then cached to the path of the JSON. A list of variant definitions is then extracted from theModelBlockDefinition
and placed into aWeightedRandomModel
. -
When loading a vanilla JSON item model (
models/item/*.json
), the model is requested from aModelResourceLocation
with variantinventory
(e.g. the dirt block item model isminecraft:dirt#inventory
); thereby causing the model to be loaded byVariantLoader
(as it is aModelResourceLocation
). WhenVariantLoader
fails 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 theVanillaLoader
stacktrace, 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
IModel
can be loaded from aResourceLocation
or retrieved from the cache by invokingModelLoaderRegistry.getModel
or 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 resultingIBakedModel
s are cached in aMap<ModelResourceLocation, IBakedModel>
. -
This map is then stored in the
ModelManager
. TheModelManager
for an instance of the game is stored inMinecraft::modelManager
, which is private with no getter.-
The
ModelManager
may 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
IBakedModel
from the cache (without loading and/or baking models, only accessing the existing cache) in aModelManager
withModelManager::getModel
.
-
-
Eventually, an
IBakedModel
will be rendered. This is done by callingIBakedModel::getQuads
. The result is a list ofBakedQuad
s (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,
IBakedModel
definesgetOverrides
, which returns anItemOverrideList
.ItemOverrideList
defineshandleItemState
, 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
. AsIBlockState
is not applicable to items,IBakedModel::getQuads
receivesnull
as its state parameter when being rendered as an item. -
Blocks have blockstates, and when a block’s
IBakedModel
is being rendered, theIBlockState
is passed directly into thegetQuads
method. In the context of models only, blockstates can have an extra set of properties, known as unlisted properties.
-