mirror of
https://github.com/FabricMC/fabric.git
synced 2025-04-21 03:10:54 -04:00
Update Model Loading API to 1.21.4 (#4243)
* 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
This commit is contained in:
parent
bdca9acb21
commit
6a293bdb89
32 changed files with 614 additions and 1137 deletions
fabric-model-loading-api-v1
build.gradle
src
client
java/net/fabricmc/fabric
api/client/model/loading/v1
BlockStateResolver.javaDelegatingUnbakedModel.javaModelLoadingPlugin.javaModelModifier.javaModelResolver.javaUnwrappableBakedModel.javaWrapperGroupableModel.javaWrapperUnbakedModel.java
impl/client/model/loading
BakedModelsHooks.javaBakerImplHooks.javaModelLoaderHooks.javaModelLoadingConstants.javaModelLoadingEventDispatcher.javaModelLoadingPluginContextImpl.java
mixin/client/model/loading
resources
testmodClient
java/net/fabricmc/fabric/test/model/loading
resources/assets/fabric-model-loading-api-v1-testmod/models
fabric-rendering-v1/src/client/java/net/fabricmc/fabric
api/client/rendering/v1
impl/client/rendering
|
@ -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')
|
||||
}
|
||||
|
|
|
@ -20,20 +20,20 @@ import org.jetbrains.annotations.ApiStatus;
|
|||
|
||||
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 {
|
||||
|
@ -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);
|
||||
|
||||
|
@ -66,6 +64,6 @@ public interface BlockStateResolver {
|
|||
* @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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,58 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2016, 2017, 2018, 2019 FabricMC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package net.fabricmc.fabric.api.client.model.loading.v1;
|
||||
|
||||
import java.util.function.Function;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import net.minecraft.client.render.model.BakedModel;
|
||||
import net.minecraft.client.render.model.Baker;
|
||||
import net.minecraft.client.render.model.ModelBakeSettings;
|
||||
import net.minecraft.client.render.model.UnbakedModel;
|
||||
import net.minecraft.client.texture.Sprite;
|
||||
import net.minecraft.client.util.SpriteIdentifier;
|
||||
import net.minecraft.util.Identifier;
|
||||
|
||||
/**
|
||||
* An unbaked model that returns another {@link BakedModel} at {@linkplain #bake bake time}.
|
||||
* This allows multiple {@link UnbakedModel}s to share the same {@link BakedModel} instance
|
||||
* and prevents baking the same model multiple times.
|
||||
*/
|
||||
public final class DelegatingUnbakedModel implements UnbakedModel {
|
||||
private final Identifier delegate;
|
||||
|
||||
/**
|
||||
* Constructs a new delegating model.
|
||||
*
|
||||
* @param delegate The identifier of the underlying baked model.
|
||||
*/
|
||||
public DelegatingUnbakedModel(Identifier delegate) {
|
||||
this.delegate = delegate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void resolve(Resolver resolver) {
|
||||
resolver.resolve(delegate);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public BakedModel bake(Baker baker, Function<SpriteIdentifier, Sprite> textureGetter, ModelBakeSettings rotationContainer) {
|
||||
return baker.bake(delegate, rotationContainer);
|
||||
}
|
||||
}
|
|
@ -21,7 +21,6 @@ import java.util.Collection;
|
|||
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;
|
||||
|
||||
|
@ -71,28 +70,14 @@ public interface ModelLoadingPlugin {
|
|||
*/
|
||||
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 access to monitor unbaked block model loads and replace the loaded model.
|
||||
*/
|
||||
Event<ModelModifier.BeforeBake> modifyModelBeforeBake();
|
||||
|
||||
/**
|
||||
* Event access to monitor baked model loads and replace the loaded model.
|
||||
*/
|
||||
Event<ModelModifier.AfterBake> modifyModelAfterBake();
|
||||
Event<ModelModifier.OnLoadBlock> modifyBlockModelOnLoad();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
@ -39,9 +34,8 @@ import net.fabricmc.fabric.api.event.Event;
|
|||
*
|
||||
* <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>
|
||||
|
@ -50,7 +44,7 @@ import net.fabricmc.fabric.api.event.Event;
|
|||
* 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 {
|
||||
|
@ -78,12 +72,18 @@ public final class ModelModifier {
|
|||
* 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.
|
||||
|
@ -91,146 +91,38 @@ public final class ModelModifier {
|
|||
@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
|
||||
* @see ModelLoadingPlugin.Context#modifyBlockModelOnLoad
|
||||
*/
|
||||
UnbakedModel modifyModelBeforeBake(UnbakedModel model, Context context);
|
||||
GroupableModel modifyModelOnLoad(GroupableModel model, Context context);
|
||||
|
||||
/**
|
||||
* The context for a before 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
|
||||
* The identifier of the model that was loaded.
|
||||
*/
|
||||
@UnknownNullability("#topLevelId() != null")
|
||||
Identifier resourceId();
|
||||
ModelIdentifier id();
|
||||
|
||||
/**
|
||||
* 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
|
||||
* The corresponding block state of the model that was loaded.
|
||||
*/
|
||||
@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
|
||||
*/
|
||||
@Nullable
|
||||
BakedModel modifyModelAfterBake(@Nullable BakedModel model, Context context);
|
||||
|
||||
/**
|
||||
* The context for an after 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 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.
|
||||
*/
|
||||
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();
|
||||
BlockState state();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,64 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2016, 2017, 2018, 2019 FabricMC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package net.fabricmc.fabric.api.client.model.loading.v1;
|
||||
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import net.minecraft.client.render.model.UnbakedModel;
|
||||
import net.minecraft.util.Identifier;
|
||||
|
||||
/**
|
||||
* Model resolvers are able to provide a custom model for specific {@link Identifier}s.
|
||||
* In vanilla, these {@link Identifier}s are converted to file paths and used to load
|
||||
* a model from JSON. Since model resolvers override this process, they can be used to
|
||||
* create custom model formats.
|
||||
*
|
||||
* <p>Only one resolver may provide a custom model for a certain {@link Identifier}.
|
||||
* Thus, resolvers that load models using a custom format could conflict. To avoid
|
||||
* conflicts, such resolvers may want to only load files with a mod-suffixed name
|
||||
* or only load files that have been explicitly defined elsewhere.
|
||||
*
|
||||
* <p>If it is necessary to load and bake an arbitrary model that is not referenced
|
||||
* normally, a model resolver can be used in conjunction with
|
||||
* {@link ModelLoadingPlugin.Context#addModels} to directly load and bake custom model
|
||||
* instances.
|
||||
*
|
||||
* <p>Model resolvers are invoked for <b>every single model that will be loaded</b>,
|
||||
* so implementations should be as efficient as possible.
|
||||
*
|
||||
* @see ModelLoadingPlugin.Context#addModels
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface ModelResolver {
|
||||
/**
|
||||
* @return the resolved {@link UnbakedModel}, or {@code null} if this resolver does not handle the current {@link Identifier}
|
||||
*/
|
||||
@Nullable
|
||||
UnbakedModel resolveModel(Context context);
|
||||
|
||||
/**
|
||||
* The context for model resolution.
|
||||
*/
|
||||
@ApiStatus.NonExtendable
|
||||
interface Context {
|
||||
/**
|
||||
* The identifier of the model to be loaded.
|
||||
*/
|
||||
Identifier id();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,90 @@
|
|||
/*
|
||||
* Copyright (c) 2016, 2017, 2018, 2019 FabricMC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package net.fabricmc.fabric.api.client.model.loading.v1;
|
||||
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import net.minecraft.client.render.model.BakedModel;
|
||||
import net.minecraft.client.render.model.WrapperBakedModel;
|
||||
|
||||
/**
|
||||
* An interface to be implemented by models that wrap and replace another model, such as {@link WrapperBakedModel}.
|
||||
* This allows mods to access the wrapped model without having to know the exact type of the wrapper model.
|
||||
*
|
||||
* <p>If you need to access data stored in one of your {@link BakedModel} subclasses,
|
||||
* and you would normally access the model by its identifier and then cast it:
|
||||
* call {@link #unwrap(BakedModel, Predicate)} on the model first, in case another
|
||||
* mod is wrapping your model to alter its rendering.
|
||||
*/
|
||||
public interface UnwrappableBakedModel {
|
||||
/**
|
||||
* Return the wrapped model, if there is one at the moment, or {@code null} otherwise.
|
||||
*
|
||||
* <p>If there are multiple layers of wrapping, this method does not necessarily return the innermost model.
|
||||
*/
|
||||
@Nullable
|
||||
BakedModel getWrappedModel();
|
||||
|
||||
/**
|
||||
* Iteratively unwrap the given model until the given condition returns true or all models in the hierarchy have
|
||||
* been tested. If no model passes the condition, null is returned.
|
||||
*
|
||||
* <p>A good use of this method is to safely cast a model to an expected type, by passing
|
||||
* {@code model -> model instanceof MyCustomBakedModel} as the condition.
|
||||
*/
|
||||
@Nullable
|
||||
static BakedModel unwrap(BakedModel model, Predicate<BakedModel> condition) {
|
||||
while (!condition.test(model)) {
|
||||
if (model instanceof UnwrappableBakedModel wrapper) {
|
||||
BakedModel wrapped = wrapper.getWrappedModel();
|
||||
|
||||
if (wrapped == null) {
|
||||
return null;
|
||||
} else if (wrapped == model) {
|
||||
throw new IllegalArgumentException("Model " + model + " is wrapping itself!");
|
||||
} else {
|
||||
model = wrapped;
|
||||
}
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return model;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fully unwrap a model, i.e. return the innermost model.
|
||||
*/
|
||||
static BakedModel unwrap(BakedModel model) {
|
||||
while (model instanceof UnwrappableBakedModel wrapper) {
|
||||
BakedModel wrapped = wrapper.getWrappedModel();
|
||||
|
||||
if (wrapped == null) {
|
||||
return model;
|
||||
} else if (wrapped == model) {
|
||||
throw new IllegalArgumentException("Model " + model + " is wrapping itself!");
|
||||
} else {
|
||||
model = wrapped;
|
||||
}
|
||||
}
|
||||
|
||||
return model;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,52 @@
|
|||
/*
|
||||
* Copyright (c) 2016, 2017, 2018, 2019 FabricMC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package net.fabricmc.fabric.api.client.model.loading.v1;
|
||||
|
||||
import net.minecraft.block.BlockState;
|
||||
import net.minecraft.client.render.model.BakedModel;
|
||||
import net.minecraft.client.render.model.Baker;
|
||||
import net.minecraft.client.render.model.GroupableModel;
|
||||
|
||||
/**
|
||||
* A simple implementation of {@link GroupableModel} that delegates all method calls to the {@link #wrapped} field.
|
||||
* Implementations must set the {@link #wrapped} field somehow.
|
||||
*/
|
||||
public abstract class WrapperGroupableModel implements GroupableModel {
|
||||
protected GroupableModel wrapped;
|
||||
|
||||
protected WrapperGroupableModel() {
|
||||
}
|
||||
|
||||
protected WrapperGroupableModel(GroupableModel wrapped) {
|
||||
this.wrapped = wrapped;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void resolve(Resolver resolver) {
|
||||
wrapped.resolve(resolver);
|
||||
}
|
||||
|
||||
@Override
|
||||
public BakedModel bake(Baker baker) {
|
||||
return wrapped.bake(baker);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getEqualityGroup(BlockState state) {
|
||||
return wrapped.getEqualityGroup(state);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,80 @@
|
|||
/*
|
||||
* Copyright (c) 2016, 2017, 2018, 2019 FabricMC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package net.fabricmc.fabric.api.client.model.loading.v1;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import net.minecraft.client.render.model.BakedModel;
|
||||
import net.minecraft.client.render.model.Baker;
|
||||
import net.minecraft.client.render.model.ModelBakeSettings;
|
||||
import net.minecraft.client.render.model.ModelTextures;
|
||||
import net.minecraft.client.render.model.UnbakedModel;
|
||||
import net.minecraft.client.render.model.json.ModelTransformation;
|
||||
|
||||
/**
|
||||
* A simple implementation of {@link UnbakedModel} that delegates all method calls to the {@link #wrapped} field.
|
||||
* Implementations must set the {@link #wrapped} field somehow.
|
||||
*/
|
||||
public abstract class WrapperUnbakedModel implements UnbakedModel {
|
||||
protected UnbakedModel wrapped;
|
||||
|
||||
protected WrapperUnbakedModel() {
|
||||
}
|
||||
|
||||
protected WrapperUnbakedModel(UnbakedModel wrapped) {
|
||||
this.wrapped = wrapped;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void resolve(Resolver resolver) {
|
||||
wrapped.resolve(resolver);
|
||||
}
|
||||
|
||||
@Override
|
||||
public BakedModel bake(ModelTextures textures, Baker baker, ModelBakeSettings settings, boolean ambientOcclusion, boolean isSideLit, ModelTransformation transformation) {
|
||||
return wrapped.bake(textures, baker, settings, ambientOcclusion, isSideLit, transformation);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public Boolean getAmbientOcclusion() {
|
||||
return wrapped.getAmbientOcclusion();
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public GuiLight getGuiLight() {
|
||||
return wrapped.getGuiLight();
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public ModelTransformation getTransformation() {
|
||||
return wrapped.getTransformation();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ModelTextures.Textures getTextures() {
|
||||
return wrapped.getTextures();
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public UnbakedModel getParent() {
|
||||
return wrapped.getParent();
|
||||
}
|
||||
}
|
|
@ -16,10 +16,16 @@
|
|||
|
||||
package net.fabricmc.fabric.impl.client.model.loading;
|
||||
|
||||
import net.minecraft.block.Block;
|
||||
import java.util.Map;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import net.minecraft.client.render.model.BakedModel;
|
||||
import net.minecraft.util.Identifier;
|
||||
|
||||
import net.fabricmc.fabric.api.client.model.loading.v1.BlockStateResolver;
|
||||
public interface BakedModelsHooks {
|
||||
@Nullable
|
||||
Map<Identifier, BakedModel> fabric_getExtraModels();
|
||||
|
||||
record BlockStateResolverHolder(BlockStateResolver resolver, Block block, Identifier blockId) {
|
||||
void fabric_setExtraModels(@Nullable Map<Identifier, BakedModel> extraModels);
|
||||
}
|
|
@ -1,26 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2016, 2017, 2018, 2019 FabricMC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package net.fabricmc.fabric.impl.client.model.loading;
|
||||
|
||||
import java.util.function.Function;
|
||||
|
||||
import net.minecraft.client.texture.Sprite;
|
||||
import net.minecraft.client.util.SpriteIdentifier;
|
||||
|
||||
public interface BakerImplHooks {
|
||||
Function<SpriteIdentifier, Sprite> fabric_getTextureGetter();
|
||||
}
|
|
@ -1,21 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2016, 2017, 2018, 2019 FabricMC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package net.fabricmc.fabric.impl.client.model.loading;
|
||||
|
||||
public interface ModelLoaderHooks {
|
||||
ModelLoadingEventDispatcher fabric_getDispatcher();
|
||||
}
|
|
@ -1,42 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2016, 2017, 2018, 2019 FabricMC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package net.fabricmc.fabric.impl.client.model.loading;
|
||||
|
||||
import net.minecraft.client.util.ModelIdentifier;
|
||||
import net.minecraft.util.Identifier;
|
||||
|
||||
public final class ModelLoadingConstants {
|
||||
/**
|
||||
* This variant is used to convert user-provided Identifiers for extra models to ModelIdentifiers, since top-level
|
||||
* models that will be baked must have a ModelIdentifier. Models corresponding to the Identifiers will go through
|
||||
* ModelModifier.OnLoad, but models corresponding to the ModelIdentifiers will not.
|
||||
*
|
||||
* <p>This variant must be non-empty, must not contain "=", and must not be equal to "inventory" or "missingno".
|
||||
*/
|
||||
public static final String RESOURCE_SPECIAL_VARIANT = "fabric_resource";
|
||||
|
||||
private ModelLoadingConstants() {
|
||||
}
|
||||
|
||||
public static ModelIdentifier toResourceModelId(Identifier id) {
|
||||
return new ModelIdentifier(id, RESOURCE_SPECIAL_VARIANT);
|
||||
}
|
||||
|
||||
public static boolean isResourceModelId(ModelIdentifier id) {
|
||||
return id.variant().equals(RESOURCE_SPECIAL_VARIANT);
|
||||
}
|
||||
}
|
|
@ -23,28 +23,21 @@ import java.util.Objects;
|
|||
import java.util.Optional;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
|
||||
import it.unimi.dsi.fastutil.objects.Reference2ReferenceMap;
|
||||
import it.unimi.dsi.fastutil.objects.Reference2ReferenceOpenHashMap;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.jetbrains.annotations.UnknownNullability;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import net.minecraft.block.Block;
|
||||
import net.minecraft.block.BlockState;
|
||||
import net.minecraft.client.render.block.BlockModels;
|
||||
import net.minecraft.client.render.model.BakedModel;
|
||||
import net.minecraft.client.render.model.Baker;
|
||||
import net.minecraft.client.render.model.BlockStatesLoader;
|
||||
import net.minecraft.client.render.model.ModelBakeSettings;
|
||||
import net.minecraft.client.render.model.GroupableModel;
|
||||
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.registry.Registries;
|
||||
import net.minecraft.registry.RegistryKey;
|
||||
import net.minecraft.util.Identifier;
|
||||
|
@ -52,7 +45,6 @@ import net.minecraft.util.Identifier;
|
|||
import net.fabricmc.fabric.api.client.model.loading.v1.BlockStateResolver;
|
||||
import net.fabricmc.fabric.api.client.model.loading.v1.ModelLoadingPlugin;
|
||||
import net.fabricmc.fabric.api.client.model.loading.v1.ModelModifier;
|
||||
import net.fabricmc.fabric.api.client.model.loading.v1.ModelResolver;
|
||||
|
||||
public class ModelLoadingEventDispatcher {
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(ModelLoadingEventDispatcher.class);
|
||||
|
@ -60,12 +52,10 @@ public class ModelLoadingEventDispatcher {
|
|||
|
||||
private final ModelLoadingPluginContextImpl pluginContext;
|
||||
|
||||
private final ModelResolverContext modelResolverContext = new ModelResolverContext();
|
||||
private final BlockStateResolverContext blockStateResolverContext = new BlockStateResolverContext();
|
||||
|
||||
private final OnLoadModifierContext onLoadModifierContext = new OnLoadModifierContext();
|
||||
private final ObjectArrayList<BeforeBakeModifierContext> beforeBakeModifierContextStack = new ObjectArrayList<>();
|
||||
private final ObjectArrayList<AfterBakeModifierContext> afterBakeModifierContextStack = new ObjectArrayList<>();
|
||||
private final OnLoadBlockModifierContext onLoadBlockModifierContext = new OnLoadBlockModifierContext();
|
||||
|
||||
public ModelLoadingEventDispatcher(List<ModelLoadingPlugin> plugins) {
|
||||
this.pluginContext = new ModelLoadingPluginContextImpl();
|
||||
|
@ -79,15 +69,41 @@ public class ModelLoadingEventDispatcher {
|
|||
}
|
||||
}
|
||||
|
||||
public void addExtraModels(Consumer<Identifier> extraModelConsumer) {
|
||||
for (Identifier id : pluginContext.extraModels) {
|
||||
extraModelConsumer.accept(id);
|
||||
}
|
||||
public void forEachExtraModel(Consumer<Identifier> extraModelConsumer) {
|
||||
pluginContext.extraModels.forEach(extraModelConsumer);
|
||||
}
|
||||
|
||||
public BlockStatesLoader.BlockStateDefinition loadBlockStateModels() {
|
||||
Map<ModelIdentifier, BlockStatesLoader.BlockModel> map = new HashMap<>();
|
||||
@Nullable
|
||||
public UnbakedModel modifyModelOnLoad(@Nullable UnbakedModel model, Identifier id) {
|
||||
onLoadModifierContext.prepare(id);
|
||||
return pluginContext.modifyModelOnLoad().invoker().modifyModelOnLoad(model, onLoadModifierContext);
|
||||
}
|
||||
|
||||
public BlockStatesLoader.BlockStateDefinition modifyBlockModelsOnLoad(BlockStatesLoader.BlockStateDefinition models) {
|
||||
Map<ModelIdentifier, BlockStatesLoader.BlockModel> map = models.models();
|
||||
|
||||
if (!(map instanceof HashMap)) {
|
||||
map = new HashMap<>(map);
|
||||
models = new BlockStatesLoader.BlockStateDefinition(map);
|
||||
}
|
||||
|
||||
putResolvedBlockStates(map);
|
||||
|
||||
map.replaceAll((id, blockModel) -> {
|
||||
GroupableModel original = blockModel.model();
|
||||
GroupableModel modified = modifyBlockModelOnLoad(original, id, blockModel.state());
|
||||
|
||||
if (original != modified) {
|
||||
return new BlockStatesLoader.BlockModel(blockModel.state(), modified);
|
||||
}
|
||||
|
||||
return blockModel;
|
||||
});
|
||||
|
||||
return models;
|
||||
}
|
||||
|
||||
private void putResolvedBlockStates(Map<ModelIdentifier, BlockStatesLoader.BlockModel> map) {
|
||||
pluginContext.blockStateResolvers.forEach((block, resolver) -> {
|
||||
Optional<RegistryKey<Block>> optionalKey = Registries.BLOCK.getKey(block);
|
||||
|
||||
|
@ -97,22 +113,18 @@ public class ModelLoadingEventDispatcher {
|
|||
|
||||
Identifier blockId = optionalKey.get().getValue();
|
||||
|
||||
BiConsumer<BlockState, UnbakedModel> output = (state, model) -> {
|
||||
resolveBlockStates(resolver, block, (state, model) -> {
|
||||
ModelIdentifier modelId = BlockModels.getModelId(blockId, state);
|
||||
map.put(modelId, new BlockStatesLoader.BlockModel(state, model));
|
||||
};
|
||||
|
||||
resolveBlockStates(resolver, block, output);
|
||||
});
|
||||
});
|
||||
|
||||
return new BlockStatesLoader.BlockStateDefinition(map);
|
||||
}
|
||||
|
||||
private void resolveBlockStates(BlockStateResolver resolver, Block block, BiConsumer<BlockState, UnbakedModel> output) {
|
||||
private void resolveBlockStates(BlockStateResolver resolver, Block block, BiConsumer<BlockState, GroupableModel> output) {
|
||||
BlockStateResolverContext context = blockStateResolverContext;
|
||||
context.prepare(block);
|
||||
|
||||
Reference2ReferenceMap<BlockState, UnbakedModel> resolvedModels = context.models;
|
||||
Reference2ReferenceMap<BlockState, GroupableModel> resolvedModels = context.models;
|
||||
ImmutableList<BlockState> allStates = block.getStateManager().getStates();
|
||||
boolean thrown = false;
|
||||
|
||||
|
@ -131,7 +143,7 @@ public class ModelLoadingEventDispatcher {
|
|||
} else {
|
||||
for (BlockState state : allStates) {
|
||||
@Nullable
|
||||
UnbakedModel model = resolvedModels.get(state);
|
||||
GroupableModel model = resolvedModels.get(state);
|
||||
|
||||
if (model == null) {
|
||||
LOGGER.error("Block state resolver did not provide a model for state {} in block {}. Using missing model.", state, block);
|
||||
|
@ -145,62 +157,14 @@ public class ModelLoadingEventDispatcher {
|
|||
resolvedModels.clear();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public UnbakedModel resolveModel(Identifier id) {
|
||||
modelResolverContext.prepare(id);
|
||||
return pluginContext.resolveModel().invoker().resolveModel(modelResolverContext);
|
||||
}
|
||||
|
||||
public UnbakedModel modifyModelOnLoad(UnbakedModel model, @UnknownNullability Identifier resourceId, @UnknownNullability ModelIdentifier topLevelId) {
|
||||
onLoadModifierContext.prepare(resourceId, topLevelId);
|
||||
return pluginContext.modifyModelOnLoad().invoker().modifyModelOnLoad(model, onLoadModifierContext);
|
||||
}
|
||||
|
||||
public UnbakedModel modifyModelBeforeBake(UnbakedModel model, @UnknownNullability Identifier resourceId, @UnknownNullability ModelIdentifier topLevelId, Function<SpriteIdentifier, Sprite> textureGetter, ModelBakeSettings settings, Baker baker) {
|
||||
if (beforeBakeModifierContextStack.isEmpty()) {
|
||||
beforeBakeModifierContextStack.add(new BeforeBakeModifierContext());
|
||||
}
|
||||
|
||||
BeforeBakeModifierContext context = beforeBakeModifierContextStack.pop();
|
||||
context.prepare(resourceId, topLevelId, textureGetter, settings, baker);
|
||||
|
||||
model = pluginContext.modifyModelBeforeBake().invoker().modifyModelBeforeBake(model, context);
|
||||
|
||||
beforeBakeModifierContextStack.push(context);
|
||||
return model;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public BakedModel modifyModelAfterBake(@Nullable BakedModel model, @UnknownNullability Identifier resourceId, @UnknownNullability ModelIdentifier topLevelId, UnbakedModel sourceModel, Function<SpriteIdentifier, Sprite> textureGetter, ModelBakeSettings settings, Baker baker) {
|
||||
if (afterBakeModifierContextStack.isEmpty()) {
|
||||
afterBakeModifierContextStack.add(new AfterBakeModifierContext());
|
||||
}
|
||||
|
||||
AfterBakeModifierContext context = afterBakeModifierContextStack.pop();
|
||||
context.prepare(resourceId, topLevelId, sourceModel, textureGetter, settings, baker);
|
||||
|
||||
model = pluginContext.modifyModelAfterBake().invoker().modifyModelAfterBake(model, context);
|
||||
|
||||
afterBakeModifierContextStack.push(context);
|
||||
return model;
|
||||
}
|
||||
|
||||
private static class ModelResolverContext implements ModelResolver.Context {
|
||||
private Identifier id;
|
||||
|
||||
private void prepare(Identifier id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Identifier id() {
|
||||
return id;
|
||||
}
|
||||
private GroupableModel modifyBlockModelOnLoad(GroupableModel model, ModelIdentifier id, BlockState state) {
|
||||
onLoadBlockModifierContext.prepare(id, state);
|
||||
return pluginContext.modifyBlockModelOnLoad().invoker().modifyModelOnLoad(model, onLoadBlockModifierContext);
|
||||
}
|
||||
|
||||
private static class BlockStateResolverContext implements BlockStateResolver.Context {
|
||||
private Block block;
|
||||
private final Reference2ReferenceMap<BlockState, UnbakedModel> models = new Reference2ReferenceOpenHashMap<>();
|
||||
private final Reference2ReferenceMap<BlockState, GroupableModel> models = new Reference2ReferenceOpenHashMap<>();
|
||||
|
||||
private void prepare(Block block) {
|
||||
this.block = block;
|
||||
|
@ -213,7 +177,7 @@ public class ModelLoadingEventDispatcher {
|
|||
}
|
||||
|
||||
@Override
|
||||
public void setModel(BlockState state, UnbakedModel model) {
|
||||
public void setModel(BlockState state, GroupableModel model) {
|
||||
Objects.requireNonNull(model, "state cannot be null");
|
||||
Objects.requireNonNull(model, "model cannot be null");
|
||||
|
||||
|
@ -228,123 +192,35 @@ public class ModelLoadingEventDispatcher {
|
|||
}
|
||||
|
||||
private static class OnLoadModifierContext implements ModelModifier.OnLoad.Context {
|
||||
@UnknownNullability
|
||||
private Identifier resourceId;
|
||||
@UnknownNullability
|
||||
private ModelIdentifier topLevelId;
|
||||
private Identifier id;
|
||||
|
||||
private void prepare(@UnknownNullability Identifier resourceId, @UnknownNullability ModelIdentifier topLevelId) {
|
||||
this.resourceId = resourceId;
|
||||
this.topLevelId = topLevelId;
|
||||
private void prepare(Identifier id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
@Override
|
||||
@UnknownNullability("#topLevelId() != null")
|
||||
public Identifier resourceId() {
|
||||
return resourceId;
|
||||
}
|
||||
|
||||
@Override
|
||||
@UnknownNullability("#resourceId() != null")
|
||||
public ModelIdentifier topLevelId() {
|
||||
return topLevelId;
|
||||
public Identifier id() {
|
||||
return id;
|
||||
}
|
||||
}
|
||||
|
||||
private static class BeforeBakeModifierContext implements ModelModifier.BeforeBake.Context {
|
||||
@UnknownNullability
|
||||
private Identifier resourceId;
|
||||
@UnknownNullability
|
||||
private ModelIdentifier topLevelId;
|
||||
private Function<SpriteIdentifier, Sprite> textureGetter;
|
||||
private ModelBakeSettings settings;
|
||||
private Baker baker;
|
||||
private static class OnLoadBlockModifierContext implements ModelModifier.OnLoadBlock.Context {
|
||||
private ModelIdentifier id;
|
||||
private BlockState state;
|
||||
|
||||
private void prepare(@UnknownNullability Identifier resourceId, @UnknownNullability ModelIdentifier topLevelId, Function<SpriteIdentifier, Sprite> textureGetter, ModelBakeSettings settings, Baker baker) {
|
||||
this.resourceId = resourceId;
|
||||
this.topLevelId = topLevelId;
|
||||
this.textureGetter = textureGetter;
|
||||
this.settings = settings;
|
||||
this.baker = baker;
|
||||
private void prepare(ModelIdentifier id, BlockState state) {
|
||||
this.id = id;
|
||||
this.state = state;
|
||||
}
|
||||
|
||||
@Override
|
||||
@UnknownNullability("#topLevelId() != null")
|
||||
public Identifier resourceId() {
|
||||
return resourceId;
|
||||
public ModelIdentifier id() {
|
||||
return id;
|
||||
}
|
||||
|
||||
@Override
|
||||
@UnknownNullability("#resourceId() != null")
|
||||
public ModelIdentifier topLevelId() {
|
||||
return topLevelId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Function<SpriteIdentifier, Sprite> textureGetter() {
|
||||
return textureGetter;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ModelBakeSettings settings() {
|
||||
return settings;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Baker baker() {
|
||||
return baker;
|
||||
}
|
||||
}
|
||||
|
||||
private static class AfterBakeModifierContext implements ModelModifier.AfterBake.Context {
|
||||
@UnknownNullability
|
||||
private Identifier resourceId;
|
||||
@UnknownNullability
|
||||
private ModelIdentifier topLevelId;
|
||||
private UnbakedModel sourceModel;
|
||||
private Function<SpriteIdentifier, Sprite> textureGetter;
|
||||
private ModelBakeSettings settings;
|
||||
private Baker baker;
|
||||
|
||||
private void prepare(@UnknownNullability Identifier resourceId, @UnknownNullability ModelIdentifier topLevelId, UnbakedModel sourceModel, Function<SpriteIdentifier, Sprite> textureGetter, ModelBakeSettings settings, Baker baker) {
|
||||
this.resourceId = resourceId;
|
||||
this.topLevelId = topLevelId;
|
||||
this.sourceModel = sourceModel;
|
||||
this.textureGetter = textureGetter;
|
||||
this.settings = settings;
|
||||
this.baker = baker;
|
||||
}
|
||||
|
||||
@Override
|
||||
@UnknownNullability("#topLevelId() != null")
|
||||
public Identifier resourceId() {
|
||||
return resourceId;
|
||||
}
|
||||
|
||||
@Override
|
||||
@UnknownNullability("#resourceId() != null")
|
||||
public ModelIdentifier topLevelId() {
|
||||
return topLevelId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public UnbakedModel sourceModel() {
|
||||
return sourceModel;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Function<SpriteIdentifier, Sprite> textureGetter() {
|
||||
return textureGetter;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ModelBakeSettings settings() {
|
||||
return settings;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Baker baker() {
|
||||
return baker;
|
||||
public BlockState state() {
|
||||
return state;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -28,7 +28,6 @@ import org.slf4j.Logger;
|
|||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import net.minecraft.block.Block;
|
||||
import net.minecraft.client.render.model.UnbakedModel;
|
||||
import net.minecraft.registry.Registries;
|
||||
import net.minecraft.registry.RegistryKey;
|
||||
import net.minecraft.util.Identifier;
|
||||
|
@ -36,7 +35,6 @@ import net.minecraft.util.Identifier;
|
|||
import net.fabricmc.fabric.api.client.model.loading.v1.BlockStateResolver;
|
||||
import net.fabricmc.fabric.api.client.model.loading.v1.ModelLoadingPlugin;
|
||||
import net.fabricmc.fabric.api.client.model.loading.v1.ModelModifier;
|
||||
import net.fabricmc.fabric.api.client.model.loading.v1.ModelResolver;
|
||||
import net.fabricmc.fabric.api.event.Event;
|
||||
import net.fabricmc.fabric.api.event.EventFactory;
|
||||
|
||||
|
@ -46,22 +44,6 @@ public class ModelLoadingPluginContextImpl implements ModelLoadingPlugin.Context
|
|||
final Set<Identifier> extraModels = new LinkedHashSet<>();
|
||||
final Map<Block, BlockStateResolver> blockStateResolvers = new IdentityHashMap<>();
|
||||
|
||||
private final Event<ModelResolver> modelResolvers = EventFactory.createArrayBacked(ModelResolver.class, resolvers -> context -> {
|
||||
for (ModelResolver resolver : resolvers) {
|
||||
try {
|
||||
UnbakedModel model = resolver.resolveModel(context);
|
||||
|
||||
if (model != null) {
|
||||
return model;
|
||||
}
|
||||
} catch (Exception exception) {
|
||||
LOGGER.error("Failed to resolve model", exception);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
});
|
||||
|
||||
private static final Identifier[] MODEL_MODIFIER_PHASES = new Identifier[] { ModelModifier.OVERRIDE_PHASE, ModelModifier.DEFAULT_PHASE, ModelModifier.WRAP_PHASE, ModelModifier.WRAP_LAST_PHASE };
|
||||
|
||||
private final Event<ModelModifier.OnLoad> onLoadModifiers = EventFactory.createWithPhases(ModelModifier.OnLoad.class, modifiers -> (model, context) -> {
|
||||
|
@ -75,31 +57,17 @@ public class ModelLoadingPluginContextImpl implements ModelLoadingPlugin.Context
|
|||
|
||||
return model;
|
||||
}, MODEL_MODIFIER_PHASES);
|
||||
private final Event<ModelModifier.BeforeBake> beforeBakeModifiers = EventFactory.createWithPhases(ModelModifier.BeforeBake.class, modifiers -> (model, context) -> {
|
||||
for (ModelModifier.BeforeBake modifier : modifiers) {
|
||||
private final Event<ModelModifier.OnLoadBlock> onLoadBlockModifiers = EventFactory.createWithPhases(ModelModifier.OnLoadBlock.class, modifiers -> (model, context) -> {
|
||||
for (ModelModifier.OnLoadBlock modifier : modifiers) {
|
||||
try {
|
||||
model = modifier.modifyModelBeforeBake(model, context);
|
||||
model = modifier.modifyModelOnLoad(model, context);
|
||||
} catch (Exception exception) {
|
||||
LOGGER.error("Failed to modify unbaked model before bake", exception);
|
||||
LOGGER.error("Failed to modify unbaked block model on load", exception);
|
||||
}
|
||||
}
|
||||
|
||||
return model;
|
||||
}, MODEL_MODIFIER_PHASES);
|
||||
private final Event<ModelModifier.AfterBake> afterBakeModifiers = EventFactory.createWithPhases(ModelModifier.AfterBake.class, modifiers -> (model, context) -> {
|
||||
for (ModelModifier.AfterBake modifier : modifiers) {
|
||||
try {
|
||||
model = modifier.modifyModelAfterBake(model, context);
|
||||
} catch (Exception exception) {
|
||||
LOGGER.error("Failed to modify baked model after bake", exception);
|
||||
}
|
||||
}
|
||||
|
||||
return model;
|
||||
}, MODEL_MODIFIER_PHASES);
|
||||
|
||||
public ModelLoadingPluginContextImpl() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addModels(Identifier... ids) {
|
||||
|
@ -129,23 +97,13 @@ public class ModelLoadingPluginContextImpl implements ModelLoadingPlugin.Context
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Event<ModelResolver> resolveModel() {
|
||||
return modelResolvers;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Event<ModelModifier.OnLoad> modifyModelOnLoad() {
|
||||
return onLoadModifiers;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Event<ModelModifier.BeforeBake> modifyModelBeforeBake() {
|
||||
return beforeBakeModifiers;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Event<ModelModifier.AfterBake> modifyModelAfterBake() {
|
||||
return afterBakeModifiers;
|
||||
public Event<ModelModifier.OnLoadBlock> modifyBlockModelOnLoad() {
|
||||
return onLoadBlockModifiers;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,52 +16,60 @@
|
|||
|
||||
package net.fabricmc.fabric.mixin.client.model.loading;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.CompletionStage;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.function.Function;
|
||||
|
||||
import com.llamalad7.mixinextras.injector.ModifyExpressionValue;
|
||||
import com.llamalad7.mixinextras.injector.ModifyReturnValue;
|
||||
import com.llamalad7.mixinextras.sugar.Local;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.spongepowered.asm.mixin.Final;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
import org.spongepowered.asm.mixin.Unique;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.ModifyArg;
|
||||
import org.spongepowered.asm.mixin.injection.Redirect;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
|
||||
|
||||
import net.minecraft.client.render.model.BakedModel;
|
||||
import net.minecraft.client.render.model.BakedModelManager;
|
||||
import net.minecraft.client.render.model.BlockStatesLoader;
|
||||
import net.minecraft.client.render.model.ModelBaker;
|
||||
import net.minecraft.client.render.model.ReferencedModelsCollector;
|
||||
import net.minecraft.client.render.model.UnbakedModel;
|
||||
import net.minecraft.client.util.ModelIdentifier;
|
||||
import net.minecraft.resource.ResourceManager;
|
||||
import net.minecraft.resource.ResourceReloader;
|
||||
import net.minecraft.util.Identifier;
|
||||
import net.minecraft.util.Pair;
|
||||
|
||||
import net.fabricmc.fabric.api.client.model.loading.v1.FabricBakedModelManager;
|
||||
import net.fabricmc.fabric.impl.client.model.loading.ModelLoadingConstants;
|
||||
import net.fabricmc.fabric.impl.client.model.loading.BakedModelsHooks;
|
||||
import net.fabricmc.fabric.impl.client.model.loading.ModelLoadingEventDispatcher;
|
||||
import net.fabricmc.fabric.impl.client.model.loading.ModelLoadingPluginManager;
|
||||
|
||||
@Mixin(BakedModelManager.class)
|
||||
abstract class BakedModelManagerMixin implements FabricBakedModelManager {
|
||||
@Shadow
|
||||
@Final
|
||||
private BakedModel missingBlockModel;
|
||||
|
||||
@Unique
|
||||
@Nullable
|
||||
private volatile CompletableFuture<ModelLoadingEventDispatcher> eventDispatcherFuture;
|
||||
|
||||
@Shadow
|
||||
private Map<ModelIdentifier, BakedModel> models;
|
||||
@Unique
|
||||
@Nullable
|
||||
private Map<Identifier, BakedModel> extraModels;
|
||||
|
||||
@Override
|
||||
public BakedModel getModel(Identifier id) {
|
||||
return models.get(ModelLoadingConstants.toResourceModelId(id));
|
||||
if (extraModels == null) {
|
||||
return missingBlockModel;
|
||||
}
|
||||
|
||||
return extraModels.getOrDefault(id, missingBlockModel);
|
||||
}
|
||||
|
||||
@Inject(method = "reload", at = @At("HEAD"))
|
||||
|
@ -77,51 +85,45 @@ abstract class BakedModelManagerMixin implements FabricBakedModelManager {
|
|||
});
|
||||
}
|
||||
|
||||
@ModifyExpressionValue(method = "reload", at = @At(value = "INVOKE", target = "net/minecraft/client/render/model/BakedModelManager.reloadBlockStates(Lnet/minecraft/client/render/model/BlockStatesLoader;Lnet/minecraft/resource/ResourceManager;Ljava/util/concurrent/Executor;)Ljava/util/concurrent/CompletableFuture;"))
|
||||
private CompletableFuture<BlockStatesLoader.BlockStateDefinition> hookBlockStateModelLoading(CompletableFuture<BlockStatesLoader.BlockStateDefinition> modelsFuture) {
|
||||
CompletableFuture<BlockStatesLoader.BlockStateDefinition> resolvedModelsFuture = eventDispatcherFuture.thenApplyAsync(ModelLoadingEventDispatcher::loadBlockStateModels);
|
||||
return modelsFuture.thenCombine(resolvedModelsFuture, (models, resolvedModels) -> {
|
||||
Map<ModelIdentifier, BlockStatesLoader.BlockModel> map = models.models();
|
||||
@ModifyExpressionValue(method = "reload", at = @At(value = "INVOKE", target = "net/minecraft/client/render/model/BlockStatesLoader.load(Lnet/minecraft/client/render/model/UnbakedModel;Lnet/minecraft/resource/ResourceManager;Ljava/util/concurrent/Executor;)Ljava/util/concurrent/CompletableFuture;"))
|
||||
private CompletableFuture<BlockStatesLoader.BlockStateDefinition> hookBlockStateModels(CompletableFuture<BlockStatesLoader.BlockStateDefinition> modelsFuture) {
|
||||
return modelsFuture.thenCombine(eventDispatcherFuture, (models, eventDispatcher) -> eventDispatcher.modifyBlockModelsOnLoad(models));
|
||||
}
|
||||
|
||||
if (!(map instanceof HashMap)) {
|
||||
map = new HashMap<>(map);
|
||||
models = new BlockStatesLoader.BlockStateDefinition(map);
|
||||
@ModifyArg(method = "reload", at = @At(value = "INVOKE", target = "java/util/concurrent/CompletableFuture.thenApplyAsync(Ljava/util/function/Function;Ljava/util/concurrent/Executor;)Ljava/util/concurrent/CompletableFuture;", ordinal = 1), index = 0)
|
||||
private Function<Void, ReferencedModelsCollector> hookModelDiscovery(Function<Void, ReferencedModelsCollector> function) {
|
||||
return v -> {
|
||||
CompletableFuture<ModelLoadingEventDispatcher> future = eventDispatcherFuture;
|
||||
|
||||
if (future == null) {
|
||||
return function.apply(v);
|
||||
}
|
||||
|
||||
map.putAll(resolvedModels.models());
|
||||
return models;
|
||||
});
|
||||
}
|
||||
|
||||
@Redirect(
|
||||
method = "reload",
|
||||
at = @At(
|
||||
value = "INVOKE",
|
||||
target = "java/util/concurrent/CompletableFuture.thenCombineAsync(Ljava/util/concurrent/CompletionStage;Ljava/util/function/BiFunction;Ljava/util/concurrent/Executor;)Ljava/util/concurrent/CompletableFuture;",
|
||||
ordinal = 0,
|
||||
remap = false
|
||||
))
|
||||
private CompletableFuture<ReferencedModelsCollector> hookModelDiscovery(
|
||||
CompletableFuture<BlockStatesLoader.BlockStateDefinition> self,
|
||||
CompletionStage<Map<Identifier, UnbakedModel>> otherFuture,
|
||||
BiFunction<BlockStatesLoader.BlockStateDefinition, Map<Identifier, UnbakedModel>, ReferencedModelsCollector> function,
|
||||
Executor executor) {
|
||||
CompletableFuture<Pair<BlockStatesLoader.BlockStateDefinition, Map<Identifier, UnbakedModel>>> pairFuture = self.thenCombine(otherFuture, Pair::new);
|
||||
return pairFuture.thenCombineAsync(eventDispatcherFuture, (pair, eventDispatcher) -> {
|
||||
ModelLoadingEventDispatcher.CURRENT.set(eventDispatcher);
|
||||
ReferencedModelsCollector referencedModelsCollector = function.apply(pair.getLeft(), pair.getRight());
|
||||
ModelLoadingEventDispatcher.CURRENT.set(future.join());
|
||||
ReferencedModelsCollector referencedModelsCollector = function.apply(v);
|
||||
ModelLoadingEventDispatcher.CURRENT.remove();
|
||||
return referencedModelsCollector;
|
||||
}, executor);
|
||||
};
|
||||
}
|
||||
|
||||
@ModifyArg(method = "reload", at = @At(value = "INVOKE", target = "java/util/concurrent/CompletableFuture.thenApplyAsync (Ljava/util/function/Function;Ljava/util/concurrent/Executor;)Ljava/util/concurrent/CompletableFuture;", ordinal = 1), index = 0)
|
||||
@ModifyArg(method = "reload", at = @At(value = "INVOKE", target = "java/util/concurrent/CompletableFuture.thenApplyAsync(Ljava/util/function/Function;Ljava/util/concurrent/Executor;)Ljava/util/concurrent/CompletableFuture;", ordinal = 3), index = 0)
|
||||
private Function<Void, Object> hookModelBaking(Function<Void, Object> function) {
|
||||
return v -> {
|
||||
ModelLoadingEventDispatcher.CURRENT.set(eventDispatcherFuture.join());
|
||||
CompletableFuture<ModelLoadingEventDispatcher> future = eventDispatcherFuture;
|
||||
|
||||
if (future == null) {
|
||||
return function.apply(v);
|
||||
}
|
||||
|
||||
ModelLoadingEventDispatcher.CURRENT.set(future.join());
|
||||
Object bakingResult = function.apply(v);
|
||||
ModelLoadingEventDispatcher.CURRENT.remove();
|
||||
return bakingResult;
|
||||
};
|
||||
}
|
||||
|
||||
@Inject(method = "upload", at = @At(value = "INVOKE_STRING", target = "net/minecraft/util/profiler/Profiler.swap(Ljava/lang/String;)V", args = "ldc=cache"))
|
||||
private void onUpload(CallbackInfo ci, @Local ModelBaker.BakedModels bakedModels) {
|
||||
extraModels = ((BakedModelsHooks) (Object) bakedModels).fabric_getExtraModels();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,47 @@
|
|||
/*
|
||||
* Copyright (c) 2016, 2017, 2018, 2019 FabricMC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package net.fabricmc.fabric.mixin.client.model.loading;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Unique;
|
||||
|
||||
import net.minecraft.client.render.model.BakedModel;
|
||||
import net.minecraft.client.render.model.ModelBaker;
|
||||
import net.minecraft.util.Identifier;
|
||||
|
||||
import net.fabricmc.fabric.impl.client.model.loading.BakedModelsHooks;
|
||||
|
||||
@Mixin(ModelBaker.BakedModels.class)
|
||||
abstract class ModelBakerBakedModelsMixin implements BakedModelsHooks {
|
||||
@Unique
|
||||
@Nullable
|
||||
private Map<Identifier, BakedModel> extraModels;
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public Map<Identifier, BakedModel> fabric_getExtraModels() {
|
||||
return extraModels;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void fabric_setExtraModels(@Nullable Map<Identifier, BakedModel> extraModels) {
|
||||
this.extraModels = extraModels;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,74 @@
|
|||
/*
|
||||
* Copyright (c) 2016, 2017, 2018, 2019 FabricMC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package net.fabricmc.fabric.mixin.client.model.loading;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.slf4j.Logger;
|
||||
import org.spongepowered.asm.mixin.Final;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
import org.spongepowered.asm.mixin.Unique;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
|
||||
|
||||
import net.minecraft.client.render.model.BakedModel;
|
||||
import net.minecraft.client.render.model.ModelBaker;
|
||||
import net.minecraft.client.render.model.ModelRotation;
|
||||
import net.minecraft.util.Identifier;
|
||||
|
||||
import net.fabricmc.fabric.impl.client.model.loading.BakedModelsHooks;
|
||||
import net.fabricmc.fabric.impl.client.model.loading.ModelLoadingEventDispatcher;
|
||||
|
||||
@Mixin(ModelBaker.class)
|
||||
abstract class ModelBakerMixin {
|
||||
@Shadow
|
||||
@Final
|
||||
static Logger LOGGER;
|
||||
|
||||
@Unique
|
||||
@Nullable
|
||||
private ModelLoadingEventDispatcher fabric_eventDispatcher;
|
||||
|
||||
@Inject(method = "<init>", at = @At("RETURN"))
|
||||
private void onReturnInit(CallbackInfo ci) {
|
||||
fabric_eventDispatcher = ModelLoadingEventDispatcher.CURRENT.get();
|
||||
}
|
||||
|
||||
@Inject(method = "bake", at = @At("RETURN"))
|
||||
private void onReturnBake(ModelBaker.ErrorCollectingSpriteGetter spriteGetter, CallbackInfoReturnable<ModelBaker.BakedModels> cir) {
|
||||
if (fabric_eventDispatcher == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
ModelBaker.BakedModels models = cir.getReturnValue();
|
||||
Map<Identifier, BakedModel> extraModels = new HashMap<>();
|
||||
fabric_eventDispatcher.forEachExtraModel(id -> {
|
||||
try {
|
||||
BakedModel model = ((ModelBaker) (Object) this).new BakerImpl(spriteGetter, id::toString).bake(id, ModelRotation.X0_Y0);
|
||||
extraModels.put(id, model);
|
||||
} catch (Exception e) {
|
||||
LOGGER.warn("Unable to bake extra model: '{}': {}", id, e);
|
||||
}
|
||||
});
|
||||
((BakedModelsHooks) (Object) models).fabric_setExtraModels(extraModels);
|
||||
}
|
||||
}
|
|
@ -1,63 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2016, 2017, 2018, 2019 FabricMC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package net.fabricmc.fabric.mixin.client.model.loading;
|
||||
|
||||
import java.util.function.Function;
|
||||
|
||||
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
|
||||
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
|
||||
import org.spongepowered.asm.mixin.Final;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Coerce;
|
||||
|
||||
import net.minecraft.client.render.model.BakedModel;
|
||||
import net.minecraft.client.render.model.Baker;
|
||||
import net.minecraft.client.render.model.ModelBakeSettings;
|
||||
import net.minecraft.client.render.model.ModelBaker;
|
||||
import net.minecraft.client.render.model.UnbakedModel;
|
||||
import net.minecraft.client.texture.Sprite;
|
||||
import net.minecraft.client.util.SpriteIdentifier;
|
||||
import net.minecraft.util.Identifier;
|
||||
|
||||
import net.fabricmc.fabric.impl.client.model.loading.BakerImplHooks;
|
||||
import net.fabricmc.fabric.impl.client.model.loading.ModelLoaderHooks;
|
||||
import net.fabricmc.fabric.impl.client.model.loading.ModelLoadingEventDispatcher;
|
||||
|
||||
@Mixin(targets = "net/minecraft/client/render/model/ModelBaker$BakerImpl")
|
||||
abstract class ModelLoaderBakerImplMixin implements BakerImplHooks {
|
||||
@Shadow
|
||||
@Final
|
||||
private ModelBaker field_40571;
|
||||
@Shadow
|
||||
@Final
|
||||
private Function<SpriteIdentifier, Sprite> textureGetter;
|
||||
|
||||
@WrapOperation(method = "bake(Lnet/minecraft/util/Identifier;Lnet/minecraft/client/render/model/ModelBakeSettings;)Lnet/minecraft/client/render/model/BakedModel;", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/render/model/ModelBaker$BakerImpl;bake(Lnet/minecraft/client/render/model/UnbakedModel;Lnet/minecraft/client/render/model/ModelBakeSettings;)Lnet/minecraft/client/render/model/BakedModel;"))
|
||||
private BakedModel wrapInnerBake(@Coerce Baker self, UnbakedModel unbakedModel, ModelBakeSettings settings, Operation<BakedModel> operation, Identifier id) {
|
||||
ModelLoadingEventDispatcher dispatcher = ((ModelLoaderHooks) this.field_40571).fabric_getDispatcher();
|
||||
unbakedModel = dispatcher.modifyModelBeforeBake(unbakedModel, id, null, textureGetter, settings, self);
|
||||
BakedModel model = operation.call(self, unbakedModel, settings);
|
||||
return dispatcher.modifyModelAfterBake(model, id, null, unbakedModel, textureGetter, settings, self);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Function<SpriteIdentifier, Sprite> fabric_getTextureGetter() {
|
||||
return textureGetter;
|
||||
}
|
||||
}
|
|
@ -1,80 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2016, 2017, 2018, 2019 FabricMC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package net.fabricmc.fabric.mixin.client.model.loading;
|
||||
|
||||
import java.util.function.Function;
|
||||
|
||||
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
|
||||
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Unique;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Coerce;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
|
||||
import net.minecraft.client.render.model.BakedModel;
|
||||
import net.minecraft.client.render.model.Baker;
|
||||
import net.minecraft.client.render.model.MissingModel;
|
||||
import net.minecraft.client.render.model.ModelBakeSettings;
|
||||
import net.minecraft.client.render.model.ModelBaker;
|
||||
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.fabricmc.fabric.impl.client.model.loading.BakerImplHooks;
|
||||
import net.fabricmc.fabric.impl.client.model.loading.ModelLoaderHooks;
|
||||
import net.fabricmc.fabric.impl.client.model.loading.ModelLoadingConstants;
|
||||
import net.fabricmc.fabric.impl.client.model.loading.ModelLoadingEventDispatcher;
|
||||
|
||||
@Mixin(ModelBaker.class)
|
||||
abstract class ModelLoaderMixin implements ModelLoaderHooks {
|
||||
@Unique
|
||||
@Nullable
|
||||
private ModelLoadingEventDispatcher fabric_eventDispatcher;
|
||||
|
||||
@Inject(method = "<init>", at = @At("RETURN"))
|
||||
private void onReturnInit(CallbackInfo ci) {
|
||||
fabric_eventDispatcher = ModelLoadingEventDispatcher.CURRENT.get();
|
||||
}
|
||||
|
||||
@WrapOperation(method = "method_61072", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/render/model/ModelBaker$BakerImpl;bake(Lnet/minecraft/client/render/model/UnbakedModel;Lnet/minecraft/client/render/model/ModelBakeSettings;)Lnet/minecraft/client/render/model/BakedModel;"))
|
||||
private BakedModel wrapSingleOuterBake(@Coerce Baker baker, UnbakedModel unbakedModel, ModelBakeSettings settings, Operation<BakedModel> operation, ModelBaker.SpriteGetter spriteGetter, ModelIdentifier id) {
|
||||
if (fabric_eventDispatcher == null) {
|
||||
return operation.call(baker, unbakedModel, settings);
|
||||
}
|
||||
|
||||
if (ModelLoadingConstants.isResourceModelId(id) || id.equals(MissingModel.MODEL_ID)) {
|
||||
// Call the baker instead of the operation to ensure the baked model is cached and doesn't end up going
|
||||
// through events twice.
|
||||
// This ignores the UnbakedModel in field_53662 (top-level model map) but it should be the same as the one in field_53663 (resource model map).
|
||||
return baker.bake(id.id(), settings);
|
||||
}
|
||||
|
||||
Function<SpriteIdentifier, Sprite> textureGetter = ((BakerImplHooks) baker).fabric_getTextureGetter();
|
||||
unbakedModel = fabric_eventDispatcher.modifyModelBeforeBake(unbakedModel, null, id, textureGetter, settings, baker);
|
||||
BakedModel model = operation.call(baker, unbakedModel, settings);
|
||||
return fabric_eventDispatcher.modifyModelAfterBake(model, null, id, unbakedModel, textureGetter, settings, baker);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ModelLoadingEventDispatcher fabric_getDispatcher() {
|
||||
return fabric_eventDispatcher;
|
||||
}
|
||||
}
|
|
@ -25,13 +25,10 @@ import org.spongepowered.asm.mixin.injection.Inject;
|
|||
import org.spongepowered.asm.mixin.injection.ModifyVariable;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
|
||||
import net.minecraft.client.render.model.BlockStatesLoader;
|
||||
import net.minecraft.client.render.model.ReferencedModelsCollector;
|
||||
import net.minecraft.client.render.model.UnbakedModel;
|
||||
import net.minecraft.client.util.ModelIdentifier;
|
||||
import net.minecraft.util.Identifier;
|
||||
|
||||
import net.fabricmc.fabric.impl.client.model.loading.ModelLoadingConstants;
|
||||
import net.fabricmc.fabric.impl.client.model.loading.ModelLoadingEventDispatcher;
|
||||
|
||||
@Mixin(ReferencedModelsCollector.class)
|
||||
|
@ -41,55 +38,24 @@ abstract class ReferencedModelsCollectorMixin {
|
|||
private ModelLoadingEventDispatcher fabric_eventDispatcher;
|
||||
|
||||
@Shadow
|
||||
abstract UnbakedModel computeResolvedModel(Identifier identifier);
|
||||
|
||||
@Shadow
|
||||
abstract void addTopLevelModel(ModelIdentifier modelIdentifier, UnbakedModel unbakedModel);
|
||||
abstract UnbakedModel computeResolvedModel(Identifier id);
|
||||
|
||||
@Inject(method = "<init>", at = @At("RETURN"))
|
||||
private void onReturnInit(CallbackInfo ci) {
|
||||
fabric_eventDispatcher = ModelLoadingEventDispatcher.CURRENT.get();
|
||||
}
|
||||
|
||||
@Inject(method = "addBlockStates", at = @At("RETURN"))
|
||||
private void onAddStandardModels(BlockStatesLoader.BlockStateDefinition blockStateModels, CallbackInfo ci) {
|
||||
if (fabric_eventDispatcher == null) {
|
||||
return;
|
||||
if (fabric_eventDispatcher != null) {
|
||||
fabric_eventDispatcher.forEachExtraModel(this::computeResolvedModel);
|
||||
}
|
||||
|
||||
fabric_eventDispatcher.addExtraModels(id -> {
|
||||
ModelIdentifier modelId = ModelLoadingConstants.toResourceModelId(id);
|
||||
UnbakedModel unbakedModel = computeResolvedModel(id);
|
||||
addTopLevelModel(modelId, unbakedModel);
|
||||
});
|
||||
}
|
||||
|
||||
@ModifyVariable(method = "getModel", at = @At(value = "STORE", ordinal = 0), ordinal = 0)
|
||||
@Nullable
|
||||
private UnbakedModel onLoadResourceModel(@Nullable UnbakedModel model, Identifier id) {
|
||||
private UnbakedModel onLoadModel(@Nullable UnbakedModel model, Identifier id) {
|
||||
if (fabric_eventDispatcher == null) {
|
||||
return model;
|
||||
}
|
||||
|
||||
UnbakedModel resolvedModel = fabric_eventDispatcher.resolveModel(id);
|
||||
|
||||
if (resolvedModel != null) {
|
||||
model = resolvedModel;
|
||||
}
|
||||
|
||||
return fabric_eventDispatcher.modifyModelOnLoad(model, id, null);
|
||||
}
|
||||
|
||||
@ModifyVariable(method = "addTopLevelModel", at = @At("HEAD"), argsOnly = true)
|
||||
private UnbakedModel onAddTopLevelModel(UnbakedModel model, ModelIdentifier modelId) {
|
||||
if (fabric_eventDispatcher == null) {
|
||||
return model;
|
||||
}
|
||||
|
||||
if (ModelLoadingConstants.isResourceModelId(modelId)) {
|
||||
return model;
|
||||
}
|
||||
|
||||
return fabric_eventDispatcher.modifyModelOnLoad(model, null, modelId);
|
||||
return fabric_eventDispatcher.modifyModelOnLoad(model, id);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,17 +14,25 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package net.fabricmc.fabric.impl.client.model.loading;
|
||||
package net.fabricmc.fabric.mixin.client.model.loading;
|
||||
|
||||
import net.minecraft.block.Block;
|
||||
import net.minecraft.block.BlockState;
|
||||
import net.minecraft.state.StateManager;
|
||||
import net.minecraft.util.Identifier;
|
||||
import org.spongepowered.asm.mixin.Final;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
|
||||
public interface BlockStatesLoaderHooks {
|
||||
void fabric_setLoadingOverride(LoadingOverride override);
|
||||
import net.minecraft.client.render.model.BakedModel;
|
||||
import net.minecraft.client.render.model.WrapperBakedModel;
|
||||
|
||||
interface LoadingOverride {
|
||||
boolean loadBlockStates(Identifier id, StateManager<Block, BlockState> stateManager);
|
||||
import net.fabricmc.fabric.api.client.model.loading.v1.UnwrappableBakedModel;
|
||||
|
||||
@Mixin(WrapperBakedModel.class)
|
||||
abstract class WrapperBakedModelMixin implements UnwrappableBakedModel {
|
||||
@Shadow
|
||||
@Final
|
||||
protected BakedModel wrapped;
|
||||
|
||||
@Override
|
||||
public BakedModel getWrappedModel() {
|
||||
return wrapped;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
accessWidener v2 named
|
||||
accessible class net/minecraft/client/render/model/ModelBaker$BakerImpl
|
||||
accessible method net/minecraft/client/render/model/ModelBaker$BakerImpl <init> (Lnet/minecraft/client/render/model/ModelBaker;Lnet/minecraft/client/render/model/ModelBaker$ErrorCollectingSpriteGetter;Lnet/minecraft/client/model/ModelNameSupplier;)V
|
|
@ -4,9 +4,10 @@
|
|||
"compatibilityLevel": "JAVA_21",
|
||||
"client": [
|
||||
"BakedModelManagerMixin",
|
||||
"ModelBakerBakedModelsMixin",
|
||||
"ModelBakerMixin",
|
||||
"ReferencedModelsCollectorMixin",
|
||||
"ModelLoaderBakerImplMixin",
|
||||
"ModelLoaderMixin"
|
||||
"WrapperBakedModelMixin"
|
||||
],
|
||||
"injectors": {
|
||||
"defaultRequire": 1
|
||||
|
|
|
@ -26,10 +26,12 @@
|
|||
"config": "fabric-model-loading-api-v1.mixins.json"
|
||||
}
|
||||
],
|
||||
"accessWidener": "fabric-model-loading-api-v1.accesswidener",
|
||||
"custom": {
|
||||
"fabric-api:module-lifecycle": "stable",
|
||||
"loom:injected_interfaces": {
|
||||
"net/minecraft/class_1092": [ "net/fabricmc/fabric/api/client/model/loading/v1/FabricBakedModelManager" ]
|
||||
"net/minecraft/class_1092": [ "net/fabricmc/fabric/api/client/model/loading/v1/FabricBakedModelManager" ],
|
||||
"net/minecraft/class_10200": [ "net/fabricmc/fabric/api/client/model/loading/v1/UnwrappableBakedModel" ]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,32 +16,31 @@
|
|||
|
||||
package net.fabricmc.fabric.test.model.loading;
|
||||
|
||||
import java.util.function.Supplier;
|
||||
import java.util.List;
|
||||
|
||||
import net.minecraft.block.BlockState;
|
||||
import net.minecraft.block.Blocks;
|
||||
import net.minecraft.block.CropBlock;
|
||||
import net.minecraft.block.HorizontalConnectingBlock;
|
||||
import net.minecraft.client.render.block.BlockModels;
|
||||
import net.minecraft.client.render.entity.PlayerEntityRenderer;
|
||||
import net.minecraft.client.render.model.BakedModel;
|
||||
import net.minecraft.client.render.model.Baker;
|
||||
import net.minecraft.client.render.model.GroupableModel;
|
||||
import net.minecraft.client.render.model.MissingModel;
|
||||
import net.minecraft.client.render.model.UnbakedModel;
|
||||
import net.minecraft.client.util.ModelIdentifier;
|
||||
import net.minecraft.client.render.model.ModelBakeSettings;
|
||||
import net.minecraft.client.render.model.ModelTextures;
|
||||
import net.minecraft.client.render.model.json.ModelTransformation;
|
||||
import net.minecraft.client.render.model.json.ModelVariant;
|
||||
import net.minecraft.client.render.model.json.WeightedUnbakedModel;
|
||||
import net.minecraft.resource.ResourceType;
|
||||
import net.minecraft.util.Identifier;
|
||||
import net.minecraft.util.math.BlockPos;
|
||||
import net.minecraft.util.math.Direction;
|
||||
import net.minecraft.util.math.random.Random;
|
||||
import net.minecraft.world.BlockRenderView;
|
||||
import net.minecraft.util.math.AffineTransformation;
|
||||
|
||||
import net.fabricmc.api.ClientModInitializer;
|
||||
import net.fabricmc.fabric.api.client.model.loading.v1.DelegatingUnbakedModel;
|
||||
import net.fabricmc.fabric.api.client.model.loading.v1.ModelLoadingPlugin;
|
||||
import net.fabricmc.fabric.api.client.model.loading.v1.ModelModifier;
|
||||
import net.fabricmc.fabric.api.client.model.loading.v1.WrapperUnbakedModel;
|
||||
import net.fabricmc.fabric.api.client.rendering.v1.LivingEntityFeatureRendererRegistrationCallback;
|
||||
import net.fabricmc.fabric.api.renderer.v1.model.ForwardingBakedModel;
|
||||
import net.fabricmc.fabric.api.renderer.v1.render.RenderContext;
|
||||
import net.fabricmc.fabric.api.resource.ResourceManagerHelper;
|
||||
|
||||
public class ModelTestModClient implements ClientModInitializer {
|
||||
|
@ -51,69 +50,32 @@ public class ModelTestModClient implements ClientModInitializer {
|
|||
public static final Identifier GOLD_BLOCK_MODEL_ID = Identifier.ofVanilla("block/gold_block");
|
||||
public static final Identifier BROWN_GLAZED_TERRACOTTA_MODEL_ID = Identifier.ofVanilla("block/brown_glazed_terracotta");
|
||||
|
||||
static class DownQuadRemovingModel extends ForwardingBakedModel {
|
||||
DownQuadRemovingModel(BakedModel model) {
|
||||
wrapped = model;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void emitBlockQuads(BlockRenderView blockView, BlockState state, BlockPos pos, Supplier<Random> randomSupplier, RenderContext context) {
|
||||
context.pushTransform(q -> q.cullFace() != Direction.DOWN);
|
||||
super.emitBlockQuads(blockView, state, pos, randomSupplier, context);
|
||||
context.popTransform();
|
||||
}
|
||||
}
|
||||
//static class DownQuadRemovingModel extends ForwardingBakedModel {
|
||||
// DownQuadRemovingModel(BakedModel model) {
|
||||
// wrapped = model;
|
||||
// }
|
||||
//
|
||||
// @Override
|
||||
// public void emitBlockQuads(BlockRenderView blockView, BlockState state, BlockPos pos, Supplier<Random> randomSupplier, RenderContext context) {
|
||||
// context.pushTransform(q -> q.cullFace() != Direction.DOWN);
|
||||
// super.emitBlockQuads(blockView, state, pos, randomSupplier, context);
|
||||
// context.popTransform();
|
||||
// }
|
||||
//}
|
||||
|
||||
@Override
|
||||
public void onInitializeClient() {
|
||||
ModelLoadingPlugin.register(pluginContext -> {
|
||||
pluginContext.addModels(HALF_RED_SAND_MODEL_ID);
|
||||
|
||||
// remove bottom face of gold blocks
|
||||
pluginContext.modifyModelAfterBake().register(ModelModifier.WRAP_PHASE, (model, context) -> {
|
||||
Identifier id = context.resourceId();
|
||||
|
||||
if (id != null && id.equals(GOLD_BLOCK_MODEL_ID)) {
|
||||
return new DownQuadRemovingModel(model);
|
||||
}
|
||||
|
||||
return model;
|
||||
});
|
||||
|
||||
// make fences with west: true and everything else false appear to be a missing model visually
|
||||
ModelIdentifier fenceId = BlockModels.getModelId(Blocks.OAK_FENCE.getDefaultState().with(HorizontalConnectingBlock.WEST, true));
|
||||
pluginContext.modifyModelOnLoad().register(ModelModifier.OVERRIDE_PHASE, (model, context) -> {
|
||||
ModelIdentifier id = context.topLevelId();
|
||||
|
||||
if (id != null && id.equals(fenceId)) {
|
||||
return new DelegatingUnbakedModel(MissingModel.ID);
|
||||
}
|
||||
|
||||
return model;
|
||||
});
|
||||
|
||||
// make brown glazed terracotta appear to be a missing model visually, but without affecting the item, by using pre-bake
|
||||
// using load here would make the item also appear missing
|
||||
pluginContext.modifyModelBeforeBake().register(ModelModifier.OVERRIDE_PHASE, (model, context) -> {
|
||||
Identifier id = context.resourceId();
|
||||
|
||||
if (id != null && id.equals(BROWN_GLAZED_TERRACOTTA_MODEL_ID)) {
|
||||
return MissingModel.create();
|
||||
}
|
||||
|
||||
return model;
|
||||
});
|
||||
|
||||
// Make wheat stages 1->6 use the same model as stage 0. This can be done with resource packs, this is just a test.
|
||||
pluginContext.registerBlockStateResolver(Blocks.WHEAT, context -> {
|
||||
BlockState state = context.block().getDefaultState();
|
||||
|
||||
// All the block state models are top-level...
|
||||
// Use a delegating unbaked model to make sure the identical models only get baked a single time.
|
||||
Identifier wheatStage0Id = Identifier.ofVanilla("block/wheat_stage0");
|
||||
Identifier wheatStage7Id = Identifier.ofVanilla("block/wheat_stage7");
|
||||
UnbakedModel wheatStage0Model = new DelegatingUnbakedModel(wheatStage0Id);
|
||||
UnbakedModel wheatStage7Model = new DelegatingUnbakedModel(wheatStage7Id);
|
||||
GroupableModel wheatStage0Model = simpleGroupableModel(wheatStage0Id);
|
||||
GroupableModel wheatStage7Model = simpleGroupableModel(wheatStage7Id);
|
||||
|
||||
for (int age = 0; age <= 6; age++) {
|
||||
context.setModel(state.with(CropBlock.AGE, age), wheatStage0Model);
|
||||
|
@ -121,6 +83,52 @@ public class ModelTestModClient implements ClientModInitializer {
|
|||
|
||||
context.setModel(state.with(CropBlock.AGE, 7), wheatStage7Model);
|
||||
});
|
||||
|
||||
// Replace the brown glazed terracotta model with a missing model without affecting child models.
|
||||
// Since 1.21.4, the item model is not a child model, so it is also affected.
|
||||
pluginContext.modifyModelOnLoad().register(ModelModifier.WRAP_PHASE, (model, context) -> {
|
||||
if (context.id().equals(BROWN_GLAZED_TERRACOTTA_MODEL_ID)) {
|
||||
return new WrapperUnbakedModel(model) {
|
||||
@Override
|
||||
public void resolve(Resolver resolver) {
|
||||
super.resolve(resolver);
|
||||
resolver.resolve(MissingModel.ID);
|
||||
}
|
||||
|
||||
@Override
|
||||
public BakedModel bake(ModelTextures textures, Baker baker, ModelBakeSettings settings, boolean ambientOcclusion, boolean isSideLit, ModelTransformation transformation) {
|
||||
return baker.bake(MissingModel.ID, settings);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
return model;
|
||||
});
|
||||
|
||||
// Make oak fences with west: true and everything else false appear to be a missing model visually.
|
||||
BlockState westOakFence = Blocks.OAK_FENCE.getDefaultState().with(HorizontalConnectingBlock.WEST, true);
|
||||
pluginContext.modifyBlockModelOnLoad().register(ModelModifier.OVERRIDE_PHASE, (model, context) -> {
|
||||
if (context.state() == westOakFence) {
|
||||
return simpleGroupableModel(MissingModel.ID);
|
||||
}
|
||||
|
||||
return model;
|
||||
});
|
||||
|
||||
// TODO 1.21.4: reintroduce test once FRAPI+Indigo are ported
|
||||
// remove bottom face of gold blocks
|
||||
//pluginContext.modifyModelOnLoad().register(ModelModifier.WRAP_PHASE, (model, context) -> {
|
||||
// if (context.id().equals(GOLD_BLOCK_MODEL_ID)) {
|
||||
// return new WrapperUnbakedModel(model) {
|
||||
// @Override
|
||||
// public BakedModel bake(ModelTextures textures, Baker baker, ModelBakeSettings settings, boolean ambientOcclusion, boolean isSideLit, ModelTransformation transformation) {
|
||||
// return new DownQuadRemovingModel(super.bake(textures, baker, settings, ambientOcclusion, isSideLit, transformation));
|
||||
// }
|
||||
// };
|
||||
// }
|
||||
//
|
||||
// return model;
|
||||
//});
|
||||
});
|
||||
|
||||
ResourceManagerHelper.get(ResourceType.CLIENT_RESOURCES).registerReloadListener(SpecificModelReloadListener.INSTANCE);
|
||||
|
@ -135,4 +143,8 @@ public class ModelTestModClient implements ClientModInitializer {
|
|||
public static Identifier id(String path) {
|
||||
return Identifier.of(ID, path);
|
||||
}
|
||||
|
||||
private static GroupableModel simpleGroupableModel(Identifier model) {
|
||||
return new WeightedUnbakedModel(List.of(new ModelVariant(model, AffineTransformation.identity(), false, 1)));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -55,15 +55,11 @@ public class PreparablePluginTest implements ClientModInitializer {
|
|||
public void onInitializeClient() {
|
||||
PreparableModelLoadingPlugin.register(PreparablePluginTest::loadModelReplacements, (replacementModels, pluginContext) -> {
|
||||
pluginContext.modifyModelOnLoad().register((model, ctx) -> {
|
||||
Identifier id = ctx.resourceId();
|
||||
@Nullable
|
||||
UnbakedModel replacementModel = replacementModels.get(ctx.id());
|
||||
|
||||
if (id != null) {
|
||||
@Nullable
|
||||
UnbakedModel replacementModel = replacementModels.get(id);
|
||||
|
||||
if (replacementModel != null) {
|
||||
return replacementModel;
|
||||
}
|
||||
if (replacementModel != null) {
|
||||
return replacementModel;
|
||||
}
|
||||
|
||||
return model;
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
{
|
||||
"textures": {
|
||||
"sand": "minecraft:block/sand",
|
||||
"red_sand": "minecraft:block/red_sand"
|
||||
"red_sand": "minecraft:block/red_sand",
|
||||
"particle": "#sand"
|
||||
},
|
||||
"elements": [
|
||||
{
|
||||
|
|
|
@ -1,46 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2016, 2017, 2018, 2019 FabricMC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package net.fabricmc.fabric.api.client.rendering.v1;
|
||||
|
||||
import net.minecraft.client.render.VertexConsumerProvider;
|
||||
import net.minecraft.client.util.math.MatrixStack;
|
||||
import net.minecraft.item.Item;
|
||||
import net.minecraft.item.ItemStack;
|
||||
|
||||
/**
|
||||
* Builtin item renderers render items with custom code.
|
||||
* They allow using non-model rendering, such as BERs, for items.
|
||||
*
|
||||
* <p>An item with a builtin renderer must have a model extending {@code minecraft:builtin/entity}.
|
||||
* The renderers are registered with {@link BuiltinItemRendererRegistry#register(Item, BuiltinItemRenderer)}.
|
||||
*
|
||||
* @deprecated Please use {@link BuiltinItemRendererRegistry.DynamicItemRenderer} instead.
|
||||
*/
|
||||
@Deprecated
|
||||
@FunctionalInterface
|
||||
public interface BuiltinItemRenderer {
|
||||
/**
|
||||
* Renders an item stack.
|
||||
*
|
||||
* @param stack the rendered item stack
|
||||
* @param matrices the matrix stack
|
||||
* @param vertexConsumers the vertex consumer provider
|
||||
* @param light the color light multiplier at the rendering position
|
||||
* @param overlay the overlay UV passed to {@link net.minecraft.client.render.VertexConsumer#overlay(int)}
|
||||
*/
|
||||
void render(ItemStack stack, MatrixStack matrices, VertexConsumerProvider vertexConsumers, int light, int overlay);
|
||||
}
|
|
@ -1,108 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2016, 2017, 2018, 2019 FabricMC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package net.fabricmc.fabric.api.client.rendering.v1;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import net.minecraft.client.render.VertexConsumerProvider;
|
||||
import net.minecraft.client.util.math.MatrixStack;
|
||||
import net.minecraft.item.Item;
|
||||
import net.minecraft.item.ItemConvertible;
|
||||
import net.minecraft.item.ItemStack;
|
||||
import net.minecraft.item.ModelTransformationMode;
|
||||
|
||||
import net.fabricmc.fabric.impl.client.rendering.BuiltinItemRendererRegistryImpl;
|
||||
|
||||
/**
|
||||
* This registry holds {@linkplain DynamicItemRenderer builtin item renderers} for items.
|
||||
*/
|
||||
// TODO 1.21.4, class_10444/class_10515
|
||||
public interface BuiltinItemRendererRegistry {
|
||||
/**
|
||||
* The singleton instance of the renderer registry.
|
||||
* Use this instance to call the methods in this interface.
|
||||
*/
|
||||
BuiltinItemRendererRegistry INSTANCE = new BuiltinItemRendererRegistryImpl();
|
||||
|
||||
/**
|
||||
* Registers the renderer for the item.
|
||||
*
|
||||
* <p>Note that the item's JSON model must also extend {@code minecraft:builtin/entity}.
|
||||
*
|
||||
* @param item the item
|
||||
* @param renderer the renderer
|
||||
* @throws IllegalArgumentException if the item already has a registered renderer
|
||||
* @throws NullPointerException if either the item or the renderer is null
|
||||
* @deprecated Please use {@link BuiltinItemRendererRegistry#register(ItemConvertible, DynamicItemRenderer)} instead.
|
||||
*/
|
||||
@Deprecated
|
||||
void register(Item item, BuiltinItemRenderer renderer);
|
||||
|
||||
/**
|
||||
* Registers the renderer for the item.
|
||||
*
|
||||
* <p>Note that the item's JSON model must also extend {@code minecraft:builtin/entity}.
|
||||
*
|
||||
* @param item the item
|
||||
* @param renderer the renderer
|
||||
* @throws IllegalArgumentException if the item already has a registered renderer
|
||||
* @throws NullPointerException if either the item or the renderer is null
|
||||
* @deprecated Please use {@link BuiltinItemRendererRegistry#register(ItemConvertible, DynamicItemRenderer)} instead.
|
||||
*/
|
||||
@Deprecated
|
||||
void register(ItemConvertible item, BuiltinItemRenderer renderer);
|
||||
|
||||
/**
|
||||
* Registers the renderer for the item.
|
||||
*
|
||||
* <p>Note that the item's JSON model must also extend {@code minecraft:builtin/entity}.
|
||||
*
|
||||
* @param item the item
|
||||
* @param renderer the renderer
|
||||
* @throws IllegalArgumentException if the item already has a registered renderer
|
||||
* @throws NullPointerException if either the item or the renderer is null
|
||||
*/
|
||||
void register(ItemConvertible item, DynamicItemRenderer renderer);
|
||||
|
||||
/**
|
||||
* Returns the renderer for the item, or {@code null} if the item has no renderer.
|
||||
*/
|
||||
@Nullable
|
||||
DynamicItemRenderer get(ItemConvertible item);
|
||||
|
||||
/**
|
||||
* Dynamic item renderers render items with custom code.
|
||||
* They allow using non-model rendering, such as BERs, for items.
|
||||
*
|
||||
* <p>An item with a dynamic renderer must have a model extending {@code minecraft:builtin/entity}.
|
||||
* The renderers are registered with {@link BuiltinItemRendererRegistry#register(ItemConvertible, DynamicItemRenderer)}.
|
||||
*/
|
||||
@FunctionalInterface
|
||||
interface DynamicItemRenderer {
|
||||
/**
|
||||
* Renders an item stack.
|
||||
*
|
||||
* @param stack the rendered item stack
|
||||
* @param mode the model transformation mode
|
||||
* @param matrices the matrix stack
|
||||
* @param vertexConsumers the vertex consumer provider
|
||||
* @param light packed lightmap coordinates
|
||||
* @param overlay the overlay UV passed to {@link net.minecraft.client.render.VertexConsumer#overlay(int)}
|
||||
*/
|
||||
void render(ItemStack stack, ModelTransformationMode mode, MatrixStack matrices, VertexConsumerProvider vertexConsumers, int light, int overlay);
|
||||
}
|
||||
}
|
|
@ -1,68 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2016, 2017, 2018, 2019 FabricMC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package net.fabricmc.fabric.impl.client.rendering;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import net.minecraft.item.Item;
|
||||
import net.minecraft.item.ItemConvertible;
|
||||
import net.minecraft.registry.Registries;
|
||||
|
||||
import net.fabricmc.fabric.api.client.rendering.v1.BuiltinItemRenderer;
|
||||
import net.fabricmc.fabric.api.client.rendering.v1.BuiltinItemRendererRegistry;
|
||||
|
||||
public final class BuiltinItemRendererRegistryImpl implements BuiltinItemRendererRegistry {
|
||||
private static final Map<Item, DynamicItemRenderer> RENDERERS = new HashMap<>();
|
||||
|
||||
public BuiltinItemRendererRegistryImpl() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void register(Item item, BuiltinItemRenderer renderer) {
|
||||
Objects.requireNonNull(renderer, "renderer is null");
|
||||
this.register(item, (stack, mode, matrices, vertexConsumers, light, overlay) -> renderer.render(stack, matrices, vertexConsumers, light, overlay));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void register(ItemConvertible item, BuiltinItemRenderer renderer) {
|
||||
Objects.requireNonNull(item, "item is null");
|
||||
register(item.asItem(), renderer);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void register(ItemConvertible item, DynamicItemRenderer renderer) {
|
||||
Objects.requireNonNull(item, "item is null");
|
||||
Objects.requireNonNull(item.asItem(), "item is null");
|
||||
Objects.requireNonNull(renderer, "renderer is null");
|
||||
|
||||
if (RENDERERS.putIfAbsent(item.asItem(), renderer) != null) {
|
||||
throw new IllegalArgumentException("Item " + Registries.ITEM.getId(item.asItem()) + " already has a builtin renderer!");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public DynamicItemRenderer get(ItemConvertible item) {
|
||||
Objects.requireNonNull(item.asItem(), "item is null");
|
||||
|
||||
return RENDERERS.get(item.asItem());
|
||||
}
|
||||
}
|
|
@ -48,7 +48,7 @@ include 'fabric-key-binding-api-v1'
|
|||
include 'fabric-lifecycle-events-v1'
|
||||
include 'fabric-loot-api-v3'
|
||||
include 'fabric-message-api-v1'
|
||||
//include 'fabric-model-loading-api-v1'
|
||||
include 'fabric-model-loading-api-v1'
|
||||
include 'fabric-networking-api-v1'
|
||||
include 'fabric-object-builder-api-v1'
|
||||
include 'fabric-particles-v1'
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue