Skip to content

Commit

Permalink
Update Model Loading API to 1.21.4 (#4243)
Browse files Browse the repository at this point in the history
* Update Model Loading API to 1.21.4

- Split model modifier events and callbacks - one set for static models and one set for block models
  - This is necessary because static models use UnbakedModel and are baked with settings while block models use GroupableModel and are not baked with settings
  - This cleans up the Identifier/ModelIdentifier getters
  - OnLoad for block models was not added because the unbaked block model map is not a cache and block models cannot inherit from other models
- Make DelegatingUnbakedModel a record to allow accessing the delegate ID
- Remove BuiltinItemRenderer, BuiltinItemRendererRegistry, and BuiltinItemRendererRegistryImpl as they were replaced by a TAW to SpecialModelTypes.ID_MAPPER

* Add fabric_ prefix to methods in BakedModelsHooks and fix checkstyle

* Remove ModelResolver and BlockStateResolver

- The functionality of ModelResolver could be perfectly replicated with ModelModifier.OnLoad with OVERRIDE_PHASE
- The functionality of BlockStateResolver could be perfectly replicated with ModelModifier.BeforeBakeBlock with OVERRIDE_PHASE
- Fix log warning caused by half_red_sand.json not defining particle sprite

* Re-add BlockStateResolver and ModelModifier.OnLoadBlock

- BeforeBakeBlock runs too late to allow modifying how models are grouped, so OnLoadBlock is necessary to allow that
- OnLoadBlock only runs for models which were actually loaded from blockstate files, so BlockStateResolver is necessary to allow adding models for blocks without a corresponding blockstate file
- Add UnwrappableBakedModel
  - Moved and renamed from FRAPI's WrapperBakedModel (original will be deleted in separate PR)
  - Implement it and interface inject it on vanilla's WrapperBakedModel
  - Add new static UnwrappableBakedModel#unwrap method which accepts a Predicate saying when to stop unwrapping
- Add WrapperUnbakedModel which simply delegates all method calls to a different UnbakedModel

* Remove ModelModifier.*Bake*

- Remove BeforeBake, AfterBake, BeforeBakeBlock, AfterBakeBlock
- Remove DelegatingUnbakedModel
- Add WrapperGroupableModel
- Add documentation and extra constructor to WrapperUnbakedModel

* Clarify OnLoad doc about meaning of null model
  • Loading branch information
PepperCode1 authored Nov 30, 2024
1 parent bdca9ac commit 6a293bd
Show file tree
Hide file tree
Showing 32 changed files with 612 additions and 1,135 deletions.
8 changes: 6 additions & 2 deletions fabric-model-loading-api-v1/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,12 @@ version = getSubprojectVersion(project)
moduleDependencies(project, ['fabric-api-base'])

testDependencies(project, [
':fabric-renderer-api-v1',
':fabric-renderer-indigo',
// ':fabric-renderer-api-v1',
// ':fabric-renderer-indigo',
':fabric-rendering-v1',
':fabric-resource-loader-v0'
])

loom {
accessWidenerPath = file('src/client/resources/fabric-model-loading-api-v1.accesswidener')
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,20 +20,20 @@

import net.minecraft.block.Block;
import net.minecraft.block.BlockState;
import net.minecraft.client.render.model.UnbakedModel;
import net.minecraft.client.render.model.GroupableModel;

/**
* Block state resolvers are responsible for mapping each {@link BlockState} of a block to an {@link UnbakedModel}.
* Block state resolvers are responsible for mapping each {@link BlockState} of a block to a {@link GroupableModel}.
* They replace the {@code blockstates/} JSON files. One block can be mapped to only one block state resolver; multiple
* resolvers will not receive the same block.
*
* <p>Block state resolvers can be used to create custom block state formats or dynamically resolve block state models.
*
* <p>Use {@link ModelResolver} instead of this interface if interacting with the block and block states directly is not
* necessary. This includes custom model deserializers and loaders.
* <p>Use {@link ModelModifier.OnLoad} instead of this interface if interacting with the block and block states directly
* is not necessary. This includes custom model deserializers and loaders.
*
* @see ModelResolver
* @see ModelModifier.OnLoad
* @see ModelModifier.OnLoadBlock
*/
@FunctionalInterface
public interface BlockStateResolver {
Expand All @@ -44,9 +44,7 @@ public interface BlockStateResolver {
* This method must be called exactly once for each block state.
*
* <p>Note that if multiple block states share the same unbaked model instance, it will be baked multiple times
* (once per block state that has the model set), which is not efficient. To improve efficiency in this case, the
* model should be delegated to using {@link DelegatingUnbakedModel} to ensure that it is only baked once. The inner
* model can be loaded using {@link ModelResolver} if custom loading logic is necessary.
* (once per block state that has the model set).
*/
void resolveBlockStates(Context context);

Expand All @@ -66,6 +64,6 @@ interface Context {
* @param state the block state for which this model should be used
* @param model the unbaked model for this block state
*/
void setModel(BlockState state, UnbakedModel model);
void setModel(BlockState state, GroupableModel model);
}
}

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@
import org.jetbrains.annotations.ApiStatus;

import net.minecraft.block.Block;
import net.minecraft.client.render.model.json.JsonUnbakedModel;
import net.minecraft.resource.ResourceManager;
import net.minecraft.util.Identifier;

Expand Down Expand Up @@ -71,28 +70,14 @@ interface Context {
*/
void registerBlockStateResolver(Block block, BlockStateResolver resolver);

/**
* Event access to register model resolvers.
*/
Event<ModelResolver> resolveModel();

/**
* Event access to monitor unbaked model loads and replace the loaded model.
*/
Event<ModelModifier.OnLoad> modifyModelOnLoad();

/**
* Event access to replace the unbaked model used for baking without replacing the cached model.
*
* <p>This is useful for mods which wish to wrap a model without affecting other models that use it as a parent
* (e.g. wrap a block's model into a non-{@link JsonUnbakedModel} class but still allow the item model to be
* loaded and baked without exceptions).
*/
Event<ModelModifier.BeforeBake> modifyModelBeforeBake();

/**
* Event access to monitor baked model loads and replace the loaded model.
* Event access to monitor unbaked block model loads and replace the loaded model.
*/
Event<ModelModifier.AfterBake> modifyModelAfterBake();
Event<ModelModifier.OnLoadBlock> modifyBlockModelOnLoad();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,19 +16,14 @@

package net.fabricmc.fabric.api.client.model.loading.v1;

import java.util.function.Function;

import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.UnknownNullability;

import net.minecraft.client.render.model.BakedModel;
import net.minecraft.client.render.model.Baker;
import net.minecraft.client.render.model.ModelBakeSettings;
import net.minecraft.block.BlockState;
import net.minecraft.client.render.model.GroupableModel;
import net.minecraft.client.render.model.ResolvableModel;
import net.minecraft.client.render.model.UnbakedModel;
import net.minecraft.client.texture.Sprite;
import net.minecraft.client.util.ModelIdentifier;
import net.minecraft.client.util.SpriteIdentifier;
import net.minecraft.util.Identifier;

import net.fabricmc.fabric.api.event.Event;
Expand All @@ -39,9 +34,8 @@
*
* <p>Example use cases:
* <ul>
* <li>Overriding a model for a particular block state - check if the given top-level identifier is not null,
* and then check if it has the appropriate variant for that block state. If so, return your desired model,
* otherwise return the given model.</li>
* <li>Overriding the model for a particular block state - check if the given identifier matches the identifier
* for that block state. If so, return your desired model, otherwise return the given model.</li>
* <li>Wrapping a model to override certain behaviors - simply return a new model instance and delegate calls
* to the original model as needed.</li>
* </ul>
Expand All @@ -50,7 +44,7 @@
* and separate phases are provided for mods that wrap their own models and mods that need to wrap models of other mods
* or wrap models arbitrarily.
*
* <p>These callbacks are invoked for <b>every single model that is loaded or baked</b>, so implementations should be
* <p>These callbacks are invoked for <b>every single model that is loaded</b>, so implementations should be
* as efficient as possible.
*/
public final class ModelModifier {
Expand Down Expand Up @@ -78,159 +72,57 @@ public interface OnLoad {
* This handler is invoked to allow modification of an unbaked model right after it is first loaded and before
* it is cached.
*
* <p>If the given model is {@code null}, its corresponding identifier was requested during
* {@linkplain ResolvableModel#resolve resolution} but the model was not loaded normally; i.e. through a JSON
* file, possibly because that file did not exist. If a non-{@code null} model is returned in this case,
* resolution will continue without warnings or errors.
*
* @param model the current unbaked model instance
* @param context context with additional information about the model/loader
* @return the model that should be used in this scenario. If no changes are needed, just return {@code model} as-is.
* @see ModelLoadingPlugin.Context#modifyModelOnLoad
*/
UnbakedModel modifyModelOnLoad(UnbakedModel model, Context context);
@Nullable
UnbakedModel modifyModelOnLoad(@Nullable UnbakedModel model, Context context);

/**
* The context for an on load model modification event.
*/
@ApiStatus.NonExtendable
interface Context {
/**
* Models with a resource ID are loaded directly from JSON or a {@link ModelModifier}.
*
* @return the identifier of the given model as an {@link Identifier}, or null if {@link #topLevelId()} is
* not null
* The identifier of the model that was loaded.
*/
@UnknownNullability("#topLevelId() != null")
Identifier resourceId();

/**
* Models with a top-level ID are loaded from blockstate files, {@link BlockStateResolver}s, or by copying
* a previously loaded model.
*
* @return the identifier of the given model as a {@link ModelIdentifier}, or null if {@link #resourceId()}
* is not null
*/
@UnknownNullability("#resourceId() != null")
ModelIdentifier topLevelId();
Identifier id();
}
}

@FunctionalInterface
public interface BeforeBake {
public interface OnLoadBlock {
/**
* This handler is invoked to allow modification of the unbaked model instance right before it is baked.
* This handler is invoked to allow modification of an unbaked block model right after it is first loaded.
*
* @param model the current unbaked model instance
* @param context context with additional information about the model/loader
* @return the model that should be used in this scenario. If no changes are needed, just return {@code model} as-is.
* @see ModelLoadingPlugin.Context#modifyModelBeforeBake
*/
UnbakedModel modifyModelBeforeBake(UnbakedModel model, Context context);

/**
* The context for a before bake model modification event.
*/
@ApiStatus.NonExtendable
interface Context {
/**
* Models with a resource ID are loaded directly from JSON or a {@link ModelModifier}.
*
* @return the identifier of the given model as an {@link Identifier}, or null if {@link #topLevelId()} is
* not null
*/
@UnknownNullability("#topLevelId() != null")
Identifier resourceId();

/**
* Models with a top-level ID are loaded from blockstate files, {@link BlockStateResolver}s, or by copying
* a previously loaded model.
*
* @return the identifier of the given model as a {@link ModelIdentifier}, or null if {@link #resourceId()}
* is not null
*/
@UnknownNullability("#resourceId() != null")
ModelIdentifier topLevelId();

/**
* The function that can be used to retrieve sprites.
*/
Function<SpriteIdentifier, Sprite> textureGetter();

/**
* The settings this model is being baked with.
*/
ModelBakeSettings settings();

/**
* The baker being used to bake this model.
* It can be used to {@linkplain Baker#getModel get unbaked models} and
* {@linkplain Baker#bake bake models}.
*/
Baker baker();
}
}

@FunctionalInterface
public interface AfterBake {
/**
* This handler is invoked to allow modification of the baked model instance right after it is baked and before
* it is cached.
*
* <p>Note that the passed baked model may be null and that this handler may return a null baked model, since
* {@link UnbakedModel#bake} and {@link Baker#bake} may also return null baked models. Null baked models are
* automatically mapped to the missing model during model retrieval.
*
* <p>For further information, see the docs of {@link ModelLoadingPlugin.Context#modifyModelAfterBake()}.
*
* @param model the current baked model instance
* @param context context with additional information about the model/loader
* @return the model that should be used in this scenario. If no changes are needed, just return {@code model} as-is.
* @see ModelLoadingPlugin.Context#modifyModelAfterBake
* @see ModelLoadingPlugin.Context#modifyBlockModelOnLoad
*/
@Nullable
BakedModel modifyModelAfterBake(@Nullable BakedModel model, Context context);
GroupableModel modifyModelOnLoad(GroupableModel model, Context context);

/**
* The context for an after bake model modification event.
* The context for an on load block model modification event.
*/
@ApiStatus.NonExtendable
interface Context {
/**
* Models with a resource ID are loaded directly from JSON or a {@link ModelModifier}.
*
* @return the identifier of the given model as an {@link Identifier}, or null if {@link #topLevelId()} is
* not null
*/
@UnknownNullability("#topLevelId() != null")
Identifier resourceId();

/**
* Models with a top-level ID are loaded from blockstate files, {@link BlockStateResolver}s, or by copying
* a previously loaded model.
*
* @return the identifier of the given model as a {@link ModelIdentifier}, or null if {@link #resourceId()}
* is not null
*/
@UnknownNullability("#resourceId() != null")
ModelIdentifier topLevelId();

/**
* The unbaked model that is being baked.
*/
UnbakedModel sourceModel();

/**
* The function that can be used to retrieve sprites.
*/
Function<SpriteIdentifier, Sprite> textureGetter();

/**
* The settings this model is being baked with.
* The identifier of the model that was loaded.
*/
ModelBakeSettings settings();
ModelIdentifier id();

/**
* The baker being used to bake this model.
* It can be used to {@linkplain Baker#getModel get unbaked models} and
* {@linkplain Baker#bake bake models}.
* The corresponding block state of the model that was loaded.
*/
Baker baker();
BlockState state();
}
}

Expand Down
Loading

0 comments on commit 6a293bd

Please sign in to comment.