Update Model Loading API to 1.21.5 ()

* Update Model Loading API to 25w07a

* Update Model Loading API to 25w08a

* Fix merge

* Move model wrappers to separate package

* Remove UnwrappableBlockStateModel

* Disallow ModelModifier.OnLoad from receiving or returning null models

- Move the point at which OnLoad is invoked earlier to be inline with how OnLoadBlock is invoked

* Temporarily remove extra models API

---------

Co-authored-by: modmuss <modmuss50@gmail.com>
This commit is contained in:
PepperCode1 2025-03-06 02:29:35 -08:00 committed by GitHub
parent f16bad8479
commit 4e7a6c5738
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
30 changed files with 581 additions and 1096 deletions

View file

@ -3,12 +3,8 @@ 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')
}

View file

@ -20,20 +20,21 @@ import org.jetbrains.annotations.ApiStatus;
import net.minecraft.block.Block;
import net.minecraft.block.BlockState;
import net.minecraft.client.render.model.GroupableModel;
import net.minecraft.client.render.model.BlockStateModel;
/**
* 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.
* Block state resolvers are responsible for mapping each {@link BlockState} of a block to a
* {@link BlockStateModel.UnbakedGrouped}. 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 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.
* is not necessary. Use {@link UnbakedModelDeserializer} for custom model deserializers and loaders.
*
* @see ModelModifier.OnLoad
* @see ModelModifier.OnLoadBlock
* @see UnbakedModelDeserializer
*/
@FunctionalInterface
public interface BlockStateResolver {
@ -64,6 +65,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, GroupableModel model);
void setModel(BlockState state, BlockStateModel.UnbakedGrouped model);
}
}

View file

@ -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.model.loading.v1;
import net.minecraft.client.render.model.BakedModel;
import net.minecraft.client.render.model.BakedModelManager;
import net.minecraft.client.util.ModelIdentifier;
import net.minecraft.util.Identifier;
/**
* Fabric-provided helper methods for {@link BakedModelManager}.
*
* <p>Note: This interface is automatically implemented on the {@link BakedModelManager} via Mixin and interface injection.
*/
public interface FabricBakedModelManager {
/**
* Similar to {@link BakedModelManager#getModel(ModelIdentifier)}, but accepts an {@link Identifier} instead of a
* {@link ModelIdentifier}. Use this method to retrieve models loaded using
* {@link ModelLoadingPlugin.Context#addModels}, since those models do not have corresponding
* {@link ModelIdentifier}s.
*
* <p><b>This method, as well as its vanilla counterpart, should only be used after the
* {@link BakedModelManager} has completed reloading.</b> Otherwise, the result will be
* outdated or an exception will be thrown.
*
* @param id the id of the model
* @return the model
*/
default BakedModel getModel(Identifier id) {
throw new UnsupportedOperationException("Implemented via mixin.");
}
}

View file

@ -16,7 +16,6 @@
package net.fabricmc.fabric.api.client.model.loading.v1;
import java.util.Collection;
import java.util.List;
import org.jetbrains.annotations.ApiStatus;
@ -24,7 +23,6 @@ import org.jetbrains.annotations.UnmodifiableView;
import net.minecraft.block.Block;
import net.minecraft.resource.ResourceManager;
import net.minecraft.util.Identifier;
import net.fabricmc.fabric.api.event.Event;
import net.fabricmc.fabric.impl.client.model.loading.ModelLoadingPluginManager;
@ -60,18 +58,6 @@ public interface ModelLoadingPlugin {
@ApiStatus.NonExtendable
interface Context {
/**
* Adds one or more models that will be loaded, baked, and made available through
* {@link FabricBakedModelManager#getModel(Identifier)}.
*/
void addModels(Identifier... ids);
/**
* Adds multiple models that will be loaded, baked, and made available through
* {@link FabricBakedModelManager#getModel(Identifier)}.
*/
void addModels(Collection<? extends Identifier> ids);
/**
* Registers a block state resolver for a block.
*
@ -83,24 +69,11 @@ public interface ModelLoadingPlugin {
/**
* Event access to monitor unbaked model loads and replace the loaded model.
*
* <p>Replacements done by listeners of this callback <b>do</b> affect child models (that is, models whose
* parent hierarchy contains the replaced model), unlike {@link #modifyModelBeforeBake}.
* <p>Replacements done by listeners of this callback affect child models (that is, models whose
* parent hierarchy contains the replaced model).
*/
Event<ModelModifier.OnLoad> modifyModelOnLoad();
/**
* Event access to replace the unbaked model used for baking without replacing the cached model.
*
* <p>Replacements done by listeners of this callback <b>do not</b> affect child models (that is, models whose
* parent hierarchy contains the replaced model), unlike {@link #modifyModelOnLoad}.
*/
Event<ModelModifier.BeforeBake> modifyModelBeforeBake();
/**
* Event access to replace the baked model.
*/
Event<ModelModifier.AfterBake> modifyModelAfterBake();
/**
* Event access to monitor unbaked block model loads and replace the loaded model.
*/
@ -115,5 +88,15 @@ public interface ModelLoadingPlugin {
* Event access to replace the baked block model.
*/
Event<ModelModifier.AfterBakeBlock> modifyBlockModelAfterBake();
/**
* Event access to replace the unbaked item model used for baking.
*/
Event<ModelModifier.BeforeBakeItem> modifyItemModelBeforeBake();
/**
* Event access to replace the baked item model.
*/
Event<ModelModifier.AfterBakeItem> modifyItemModelAfterBake();
}
}

View file

@ -17,16 +17,13 @@
package net.fabricmc.fabric.api.client.model.loading.v1;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.Nullable;
import net.minecraft.block.BlockState;
import net.minecraft.client.render.model.BakedModel;
import net.minecraft.client.render.item.model.ItemModel;
import net.minecraft.client.render.model.Baker;
import net.minecraft.client.render.model.GroupableModel;
import net.minecraft.client.render.model.ModelBakeSettings;
import net.minecraft.client.render.model.BlockStateModel;
import net.minecraft.client.render.model.ResolvableModel;
import net.minecraft.client.render.model.UnbakedModel;
import net.minecraft.client.util.ModelIdentifier;
import net.minecraft.util.Identifier;
import net.fabricmc.fabric.api.event.Event;
@ -37,8 +34,8 @@ import net.fabricmc.fabric.api.event.Event;
*
* <p>Example use cases:
* <ul>
* <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>Overriding the model for a particular block state - check if the given block state matches the desired 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>
@ -47,6 +44,10 @@ 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>Any event may be invoked concurrently with other invocations of the same event or other events, subject to
* reasonable constraints. For example, a block/item model and its dependencies must be loaded before the block/item
* model is baked.
*
* <p>These callbacks are invoked for <b>every single model that is loaded or baked</b>, so implementations should be
* as efficient as possible.
*/
@ -72,15 +73,7 @@ public final class ModelModifier {
@FunctionalInterface
public interface OnLoad {
/**
* This handler is invoked to allow modification of an unbaked model right after it is first loaded and before
* it is cached.
*
* <p>If the given model is {@code null}, its corresponding identifier was requested during
* {@linkplain ResolvableModel#resolve resolution} but the model was not loaded normally; i.e. through a JSON
* file, possibly because that file did not exist. If a non-{@code null} model is returned in this case,
* resolution will continue without warnings or errors. This callback can return a {@code null} model, which
* has the same meaning as described earlier, so it is unlikely that an implementor should need to return
* {@code null} unless directly returning the given model.
* This handler is invoked to allow modification of an unbaked model right after it is first loaded.
*
* <p>For further information, see the docs of {@link ModelLoadingPlugin.Context#modifyModelOnLoad()}.
*
@ -89,8 +82,7 @@ public final class ModelModifier {
* @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
*/
@Nullable
UnbakedModel modifyModelOnLoad(@Nullable UnbakedModel model, Context context);
UnbakedModel modifyModelOnLoad(UnbakedModel model, Context context);
/**
* The context for an on load model modification event.
@ -104,86 +96,6 @@ public final class ModelModifier {
}
}
@FunctionalInterface
public interface BeforeBake {
/**
* This handler is invoked to allow modification of the unbaked model instance right before it is baked.
*
* <p>For further information, see the docs of {@link ModelLoadingPlugin.Context#modifyModelBeforeBake()}.
*
* @param model the current unbaked model instance
* @param context context with additional information about the model/loader
* @return the model that should be used in this scenario. If no changes are needed, just return {@code model} as-is.
* @see ModelLoadingPlugin.Context#modifyModelBeforeBake
*/
UnbakedModel modifyModelBeforeBake(UnbakedModel model, Context context);
/**
* The context for a before bake model modification event.
*/
@ApiStatus.NonExtendable
interface Context {
/**
* The identifier of the model being baked.
*/
Identifier id();
/**
* 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#bake bake models} and
* {@linkplain Baker#getSpriteGetter get sprites}. Note that baking a model which was not previously
* {@linkplain ResolvableModel.Resolver#resolve resolved} will log a warning and return the missing model.
*/
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.
*
* @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
*/
BakedModel modifyModelAfterBake(BakedModel model, Context context);
/**
* The context for an after bake model modification event.
*/
@ApiStatus.NonExtendable
interface Context {
/**
* The identifier of the model being baked.
*/
Identifier id();
/**
* The unbaked model that is being baked.
*/
UnbakedModel sourceModel();
/**
* 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#bake bake models} and
* {@linkplain Baker#getSpriteGetter get sprites}. Note that baking a model which was not previously
* {@linkplain ResolvableModel.Resolver#resolve resolved} will log a warning and return the missing model.
*/
Baker baker();
}
}
@FunctionalInterface
public interface OnLoadBlock {
/**
@ -194,18 +106,13 @@ public final class ModelModifier {
* @return the model that should be used in this scenario. If no changes are needed, just return {@code model} as-is.
* @see ModelLoadingPlugin.Context#modifyBlockModelOnLoad
*/
GroupableModel modifyModelOnLoad(GroupableModel model, Context context);
BlockStateModel.UnbakedGrouped modifyModelOnLoad(BlockStateModel.UnbakedGrouped model, Context context);
/**
* The context for an on load block model modification event.
*/
@ApiStatus.NonExtendable
interface Context {
/**
* The identifier of the model that was loaded.
*/
ModelIdentifier id();
/**
* The corresponding block state of the model that was loaded.
*/
@ -223,7 +130,7 @@ public final class ModelModifier {
* @return the model that should be used in this scenario. If no changes are needed, just return {@code model} as-is.
* @see ModelLoadingPlugin.Context#modifyBlockModelBeforeBake
*/
GroupableModel modifyModelBeforeBake(GroupableModel model, Context context);
BlockStateModel.UnbakedGrouped modifyModelBeforeBake(BlockStateModel.UnbakedGrouped model, Context context);
/**
* The context for a before bake block model modification event.
@ -231,14 +138,16 @@ public final class ModelModifier {
@ApiStatus.NonExtendable
interface Context {
/**
* The identifier of the model being baked.
* The corresponding block state of the model being baked.
*/
ModelIdentifier id();
BlockState state();
/**
* The baker being used to bake this model. It can be used to {@linkplain Baker#bake bake models} and
* {@linkplain Baker#getSpriteGetter get sprites}. Note that baking a model which was not previously
* {@linkplain ResolvableModel.Resolver#resolve resolved} will log a warning and return the missing model.
* The baker being used to bake this model. It can be used to
* {@linkplain Baker#getModel get resolved models} and {@linkplain Baker#getSpriteGetter get sprites}. Note
* that retrieving a model which was not previously
* {@linkplain ResolvableModel.Resolver#markDependency discovered} will log a warning and return the missing
* model.
*/
Baker baker();
}
@ -254,7 +163,7 @@ public final class ModelModifier {
* @return the model that should be used in this scenario. If no changes are needed, just return {@code model} as-is.
* @see ModelLoadingPlugin.Context#modifyBlockModelAfterBake
*/
BakedModel modifyModelAfterBake(BakedModel model, Context context);
BlockStateModel modifyModelAfterBake(BlockStateModel model, Context context);
/**
* The context for an after bake block model modification event.
@ -262,23 +171,88 @@ public final class ModelModifier {
@ApiStatus.NonExtendable
interface Context {
/**
* The identifier of the model being baked.
* The corresponding block state of the model being baked.
*/
ModelIdentifier id();
BlockState state();
/**
* The unbaked model that is being baked.
*/
GroupableModel sourceModel();
BlockStateModel.UnbakedGrouped sourceModel();
/**
* The baker being used to bake this model. It can be used to {@linkplain Baker#bake bake models} and
* {@linkplain Baker#getSpriteGetter get sprites}. Note that baking a model which was not previously
* {@linkplain ResolvableModel.Resolver#resolve resolved} will log a warning and return the missing model.
* The baker being used to bake this model. It can be used to
* {@linkplain Baker#getModel get resolved models} and {@linkplain Baker#getSpriteGetter get sprites}. Note
* that retrieving a model which was not previously
* {@linkplain ResolvableModel.Resolver#markDependency discovered} will log a warning and return the missing
* model.
*/
Baker baker();
}
}
@FunctionalInterface
public interface BeforeBakeItem {
/**
* This handler is invoked to allow modification of the unbaked item model instance right before it is baked.
*
* @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#modifyItemModelBeforeBake
*/
ItemModel.Unbaked modifyModelBeforeBake(ItemModel.Unbaked model, Context context);
/**
* The context for a before bake item model modification event.
*/
@ApiStatus.NonExtendable
interface Context {
/**
* The corresponding item ID of the model being baked.
*/
Identifier itemId();
/**
* The vanilla context being used to bake this model.
*/
ItemModel.BakeContext bakeContext();
}
}
@FunctionalInterface
public interface AfterBakeItem {
/**
* This handler is invoked to allow modification of the baked item model instance right after it is baked.
*
* @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#modifyItemModelAfterBake
*/
ItemModel modifyModelAfterBake(ItemModel model, Context context);
/**
* The context for an after bake item model modification event.
*/
@ApiStatus.NonExtendable
interface Context {
/**
* The corresponding item ID of the model being baked.
*/
Identifier itemId();
/**
* The unbaked model that is being baked.
*/
ItemModel.Unbaked sourceModel();
/**
* The vanilla context being used to bake this model.
*/
ItemModel.BakeContext bakeContext();
}
}
private ModelModifier() { }
}

View file

@ -27,7 +27,6 @@ import net.minecraft.client.render.model.UnbakedModel;
import net.minecraft.client.render.model.json.JsonUnbakedModel;
import net.minecraft.client.render.model.json.ModelElement;
import net.minecraft.client.render.model.json.ModelElementFace;
import net.minecraft.client.render.model.json.ModelElementTexture;
import net.minecraft.client.render.model.json.ModelTransformation;
import net.minecraft.client.render.model.json.Transformation;
import net.minecraft.util.Identifier;
@ -87,7 +86,6 @@ public interface UnbakedModelDeserializer {
* <li>{@link UnbakedModel}</li>
* <li>{@link ModelElement}</li>
* <li>{@link ModelElementFace}</li>
* <li>{@link ModelElementTexture}</li>
* <li>{@link Transformation}</li>
* <li>{@link ModelTransformation}</li>
* </ul>

View file

@ -1,95 +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.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.
*
* <p>Note: This interface is automatically implemented on {@link WrapperBakedModel} and subclasses via Mixin and
* interface injection.
*/
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
default BakedModel getWrappedModel() {
return null;
}
/**
* 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;
}
}

View file

@ -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.api.client.model.loading.v1.wrapper;
import org.jetbrains.annotations.Nullable;
import net.minecraft.client.item.ItemModelManager;
import net.minecraft.client.render.item.ItemRenderState;
import net.minecraft.client.render.item.model.ItemModel;
import net.minecraft.client.world.ClientWorld;
import net.minecraft.entity.LivingEntity;
import net.minecraft.item.ItemDisplayContext;
import net.minecraft.item.ItemStack;
/**
* A simple implementation of {@link ItemModel} that delegates all method calls to the {@link #wrapped} field.
* Implementations must set the {@link #wrapped} field somehow.
*/
public abstract class WrapperBakedItemModel implements ItemModel {
protected ItemModel wrapped;
protected WrapperBakedItemModel() {
}
protected WrapperBakedItemModel(ItemModel wrapped) {
this.wrapped = wrapped;
}
@Override
public void update(ItemRenderState state, ItemStack stack, ItemModelManager resolver, ItemDisplayContext displayContext, @Nullable ClientWorld world, @Nullable LivingEntity user, int seed) {
wrapped.update(state, stack, resolver, displayContext, world, user, seed);
}
}

View file

@ -0,0 +1,55 @@
/*
* 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.wrapper;
import java.util.List;
import net.minecraft.client.render.model.BlockModelPart;
import net.minecraft.client.render.model.BlockStateModel;
import net.minecraft.client.texture.Sprite;
import net.minecraft.util.math.random.Random;
// TODO: FRAPI overrides
/**
* A simple implementation of {@link BlockStateModel} that delegates all method calls to the {@link #wrapped} field.
* Implementations must set the {@link #wrapped} field somehow.
*/
public abstract class WrapperBlockStateModel implements BlockStateModel {
protected BlockStateModel wrapped;
protected WrapperBlockStateModel() {
}
protected WrapperBlockStateModel(BlockStateModel wrapped) {
this.wrapped = wrapped;
}
@Override
public void addParts(Random random, List<BlockModelPart> parts) {
wrapped.addParts(random, parts);
}
@Override
public List<BlockModelPart> getParts(Random random) {
return wrapped.getParts(random);
}
@Override
public Sprite particleSprite() {
return wrapped.particleSprite();
}
}

View file

@ -14,39 +14,38 @@
* limitations under the License.
*/
package net.fabricmc.fabric.api.client.model.loading.v1;
package net.fabricmc.fabric.api.client.model.loading.v1.wrapper;
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;
import net.minecraft.client.render.model.BlockStateModel;
/**
* A simple implementation of {@link GroupableModel} that delegates all method calls to the {@link #wrapped} field.
* Implementations must set the {@link #wrapped} field somehow.
* A simple implementation of {@link BlockStateModel.UnbakedGrouped} 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;
public abstract class WrapperUnbakedGroupedBlockStateModel implements BlockStateModel.UnbakedGrouped {
protected BlockStateModel.UnbakedGrouped wrapped;
protected WrapperGroupableModel() {
protected WrapperUnbakedGroupedBlockStateModel() {
}
protected WrapperGroupableModel(GroupableModel wrapped) {
protected WrapperUnbakedGroupedBlockStateModel(BlockStateModel.UnbakedGrouped wrapped) {
this.wrapped = wrapped;
}
@Override
public void resolve(Resolver resolver) {
wrapped.resolve(resolver);
}
@Override
public BakedModel bake(Baker baker) {
return wrapped.bake(baker);
public BlockStateModel getModel(BlockState state, Baker baker) {
return wrapped.getModel(state, baker);
}
@Override
public Object getEqualityGroup(BlockState state) {
return wrapped.getEqualityGroup(state);
}
@Override
public void resolve(Resolver resolver) {
wrapped.resolve(resolver);
}
}

View file

@ -0,0 +1,51 @@
/*
* 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.wrapper;
import com.mojang.serialization.MapCodec;
import net.minecraft.client.render.item.model.ItemModel;
/**
* A simple implementation of {@link ItemModel.Unbaked} that delegates all method calls to the {@link #wrapped} field.
* Implementations must set the {@link #wrapped} field somehow.
*/
public abstract class WrapperUnbakedItemModel implements ItemModel.Unbaked {
protected ItemModel.Unbaked wrapped;
protected WrapperUnbakedItemModel() {
}
protected WrapperUnbakedItemModel(ItemModel.Unbaked wrapped) {
this.wrapped = wrapped;
}
@Override
public void resolve(Resolver resolver) {
wrapped.resolve(resolver);
}
@Override
public MapCodec<? extends ItemModel.Unbaked> getCodec() {
return wrapped.getCodec();
}
@Override
public ItemModel bake(ItemModel.BakeContext context) {
return wrapped.bake(context);
}
}

View file

@ -14,16 +14,15 @@
* limitations under the License.
*/
package net.fabricmc.fabric.api.client.model.loading.v1;
package net.fabricmc.fabric.api.client.model.loading.v1.wrapper;
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.Geometry;
import net.minecraft.client.render.model.ModelTextures;
import net.minecraft.client.render.model.UnbakedModel;
import net.minecraft.client.render.model.json.ModelTransformation;
import net.minecraft.util.Identifier;
/**
* A simple implementation of {@link UnbakedModel} that delegates all method calls to the {@link #wrapped} field.
@ -40,41 +39,37 @@ public abstract class WrapperUnbakedModel implements UnbakedModel {
}
@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);
@Nullable
public Boolean ambientOcclusion() {
return wrapped.ambientOcclusion();
}
@Override
@Nullable
public Boolean getAmbientOcclusion() {
return wrapped.getAmbientOcclusion();
public GuiLight guiLight() {
return wrapped.guiLight();
}
@Override
@Nullable
public GuiLight getGuiLight() {
return wrapped.getGuiLight();
public ModelTransformation transformations() {
return wrapped.transformations();
}
@Override
public ModelTextures.Textures textures() {
return wrapped.textures();
}
@Override
@Nullable
public ModelTransformation getTransformation() {
return wrapped.getTransformation();
}
@Override
public ModelTextures.Textures getTextures() {
return wrapped.getTextures();
public Geometry geometry() {
return wrapped.geometry();
}
@Override
@Nullable
public UnbakedModel getParent() {
return wrapped.getParent();
public Identifier parent() {
return wrapped.parent();
}
}

View file

@ -1,31 +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.Map;
import org.jetbrains.annotations.Nullable;
import net.minecraft.client.render.model.BakedModel;
import net.minecraft.util.Identifier;
public interface BakedModelsHooks {
@Nullable
Map<Identifier, BakedModel> fabric_getExtraModels();
void fabric_setExtraModels(@Nullable Map<Identifier, BakedModel> extraModels);
}

View file

@ -1,24 +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 org.jetbrains.annotations.Nullable;
public interface ModelBakerHooks {
@Nullable
ModelLoadingEventDispatcher fabric_getDispatcher();
}

View file

@ -20,12 +20,10 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import com.google.common.collect.ImmutableList;
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
import it.unimi.dsi.fastutil.objects.Reference2ReferenceMap;
import it.unimi.dsi.fastutil.objects.Reference2ReferenceOpenHashMap;
import org.jetbrains.annotations.Nullable;
@ -34,16 +32,11 @@ 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.item.model.ItemModel;
import net.minecraft.client.render.model.Baker;
import net.minecraft.client.render.model.BlockStateModel;
import net.minecraft.client.render.model.BlockStatesLoader;
import net.minecraft.client.render.model.GroupableModel;
import net.minecraft.client.render.model.ModelBakeSettings;
import net.minecraft.client.render.model.UnbakedModel;
import net.minecraft.client.util.ModelIdentifier;
import net.minecraft.registry.Registries;
import net.minecraft.registry.RegistryKey;
import net.minecraft.util.Identifier;
import net.fabricmc.fabric.api.client.model.loading.v1.BlockStateResolver;
@ -57,14 +50,8 @@ public class ModelLoadingEventDispatcher {
private final ModelLoadingPluginContextImpl pluginContext;
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();
private final BeforeBakeBlockModifierContext beforeBakeBlockModifierContext = new BeforeBakeBlockModifierContext();
private final AfterBakeBlockModifierContext afterBakeBlockModifierContext = new AfterBakeBlockModifierContext();
public ModelLoadingEventDispatcher(List<ModelLoadingPlugin> plugins) {
this.pluginContext = new ModelLoadingPluginContextImpl();
@ -78,90 +65,45 @@ public class ModelLoadingEventDispatcher {
}
}
public void forEachExtraModel(Consumer<Identifier> extraModelConsumer) {
pluginContext.extraModels.forEach(extraModelConsumer);
public Map<Identifier, UnbakedModel> modifyModelsOnLoad(Map<Identifier, UnbakedModel> models) {
if (!(models instanceof HashMap)) {
models = new HashMap<>(models);
}
models.replaceAll(this::modifyModelOnLoad);
return models;
}
@Nullable
public UnbakedModel modifyModelOnLoad(@Nullable UnbakedModel model, Identifier id) {
private UnbakedModel modifyModelOnLoad(Identifier id, UnbakedModel model) {
onLoadModifierContext.prepare(id);
return pluginContext.modifyModelOnLoad().invoker().modifyModelOnLoad(model, onLoadModifierContext);
}
public UnbakedModel modifyModelBeforeBake(UnbakedModel model, Identifier id, ModelBakeSettings settings, Baker baker) {
if (beforeBakeModifierContextStack.isEmpty()) {
beforeBakeModifierContextStack.add(new BeforeBakeModifierContext());
}
BeforeBakeModifierContext context = beforeBakeModifierContextStack.pop();
context.prepare(id, settings, baker);
model = pluginContext.modifyModelBeforeBake().invoker().modifyModelBeforeBake(model, context);
beforeBakeModifierContextStack.push(context);
return model;
}
public BakedModel modifyModelAfterBake(BakedModel model, Identifier id, UnbakedModel sourceModel, ModelBakeSettings settings, Baker baker) {
if (afterBakeModifierContextStack.isEmpty()) {
afterBakeModifierContextStack.add(new AfterBakeModifierContext());
}
AfterBakeModifierContext context = afterBakeModifierContextStack.pop();
context.prepare(id, sourceModel, settings, baker);
model = pluginContext.modifyModelAfterBake().invoker().modifyModelAfterBake(model, context);
afterBakeModifierContextStack.push(context);
return model;
}
public BlockStatesLoader.BlockStateDefinition modifyBlockModelsOnLoad(BlockStatesLoader.BlockStateDefinition models) {
Map<ModelIdentifier, BlockStatesLoader.BlockModel> map = models.models();
public BlockStatesLoader.LoadedModels modifyBlockModelsOnLoad(BlockStatesLoader.LoadedModels models) {
Map<BlockState, BlockStateModel.UnbakedGrouped> map = models.models();
if (!(map instanceof HashMap)) {
map = new HashMap<>(map);
models = new BlockStatesLoader.BlockStateDefinition(map);
models = new BlockStatesLoader.LoadedModels(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;
});
map.replaceAll(this::modifyBlockModelOnLoad);
return models;
}
private void putResolvedBlockStates(Map<ModelIdentifier, BlockStatesLoader.BlockModel> map) {
private void putResolvedBlockStates(Map<BlockState, BlockStateModel.UnbakedGrouped> map) {
pluginContext.blockStateResolvers.forEach((block, resolver) -> {
Optional<RegistryKey<Block>> optionalKey = Registries.BLOCK.getKey(block);
if (optionalKey.isEmpty()) {
return;
}
Identifier blockId = optionalKey.get().getValue();
resolveBlockStates(resolver, block, (state, model) -> {
ModelIdentifier modelId = BlockModels.getModelId(blockId, state);
map.put(modelId, new BlockStatesLoader.BlockModel(state, model));
});
resolveBlockStates(resolver, block, map::put);
});
}
private void resolveBlockStates(BlockStateResolver resolver, Block block, BiConsumer<BlockState, GroupableModel> output) {
private void resolveBlockStates(BlockStateResolver resolver, Block block, BiConsumer<BlockState, BlockStateModel.UnbakedGrouped> output) {
BlockStateResolverContext context = blockStateResolverContext;
context.prepare(block);
Reference2ReferenceMap<BlockState, GroupableModel> resolvedModels = context.models;
Reference2ReferenceMap<BlockState, BlockStateModel.UnbakedGrouped> resolvedModels = context.models;
ImmutableList<BlockState> allStates = block.getStateManager().getStates();
boolean thrown = false;
@ -180,7 +122,7 @@ public class ModelLoadingEventDispatcher {
} else {
for (BlockState state : allStates) {
@Nullable
GroupableModel model = resolvedModels.get(state);
BlockStateModel.UnbakedGrouped 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);
@ -194,24 +136,30 @@ public class ModelLoadingEventDispatcher {
resolvedModels.clear();
}
private GroupableModel modifyBlockModelOnLoad(GroupableModel model, ModelIdentifier id, BlockState state) {
onLoadBlockModifierContext.prepare(id, state);
private BlockStateModel.UnbakedGrouped modifyBlockModelOnLoad(BlockState state, BlockStateModel.UnbakedGrouped model) {
onLoadBlockModifierContext.prepare(state);
return pluginContext.modifyBlockModelOnLoad().invoker().modifyModelOnLoad(model, onLoadBlockModifierContext);
}
public GroupableModel modifyBlockModelBeforeBake(GroupableModel model, ModelIdentifier id, Baker baker) {
beforeBakeBlockModifierContext.prepare(id, baker);
return pluginContext.modifyBlockModelBeforeBake().invoker().modifyModelBeforeBake(model, beforeBakeBlockModifierContext);
public BlockStateModel modifyBlockModel(BlockStateModel.UnbakedGrouped unbakedModel, BlockState state, Baker baker, Operation<BlockStateModel> bakeOperation) {
BakeBlockModifierContext modifierContext = new BakeBlockModifierContext(state, baker);
unbakedModel = pluginContext.modifyBlockModelBeforeBake().invoker().modifyModelBeforeBake(unbakedModel, modifierContext);
BlockStateModel model = bakeOperation.call(unbakedModel, state, baker);
modifierContext.prepareAfterBake(unbakedModel);
return pluginContext.modifyBlockModelAfterBake().invoker().modifyModelAfterBake(model, modifierContext);
}
public BakedModel modifyBlockModelAfterBake(BakedModel model, ModelIdentifier id, GroupableModel sourceModel, Baker baker) {
afterBakeBlockModifierContext.prepare(id, sourceModel, baker);
return pluginContext.modifyBlockModelAfterBake().invoker().modifyModelAfterBake(model, afterBakeBlockModifierContext);
public ItemModel modifyItemModel(ItemModel.Unbaked unbakedModel, Identifier itemId, ItemModel.BakeContext bakeContext, Operation<ItemModel> bakeOperation) {
BakeItemModifierContext modifierContext = new BakeItemModifierContext(itemId, bakeContext);
unbakedModel = pluginContext.modifyItemModelBeforeBake().invoker().modifyModelBeforeBake(unbakedModel, modifierContext);
ItemModel model = bakeOperation.call(unbakedModel, bakeContext);
modifierContext.prepareAfterBake(unbakedModel);
return pluginContext.modifyItemModelAfterBake().invoker().modifyModelAfterBake(model, modifierContext);
}
private static class BlockStateResolverContext implements BlockStateResolver.Context {
private Block block;
private final Reference2ReferenceMap<BlockState, GroupableModel> models = new Reference2ReferenceOpenHashMap<>();
private final Reference2ReferenceMap<BlockState, BlockStateModel.UnbakedGrouped> models = new Reference2ReferenceOpenHashMap<>();
private void prepare(Block block) {
this.block = block;
@ -224,7 +172,7 @@ public class ModelLoadingEventDispatcher {
}
@Override
public void setModel(BlockState state, GroupableModel model) {
public void setModel(BlockState state, BlockStateModel.UnbakedGrouped model) {
Objects.requireNonNull(state, "state cannot be null");
Objects.requireNonNull(model, "model cannot be null");
@ -251,131 +199,76 @@ public class ModelLoadingEventDispatcher {
}
}
private static class BeforeBakeModifierContext implements ModelModifier.BeforeBake.Context {
private Identifier id;
private ModelBakeSettings settings;
private Baker baker;
private void prepare(Identifier id, ModelBakeSettings settings, Baker baker) {
this.id = id;
this.settings = settings;
this.baker = baker;
}
@Override
public Identifier id() {
return id;
}
@Override
public ModelBakeSettings settings() {
return settings;
}
@Override
public Baker baker() {
return baker;
}
}
private static class AfterBakeModifierContext implements ModelModifier.AfterBake.Context {
private Identifier id;
private UnbakedModel sourceModel;
private ModelBakeSettings settings;
private Baker baker;
private void prepare(Identifier id, UnbakedModel sourceModel, ModelBakeSettings settings, Baker baker) {
this.id = id;
this.sourceModel = sourceModel;
this.settings = settings;
this.baker = baker;
}
@Override
public Identifier id() {
return id;
}
@Override
public UnbakedModel sourceModel() {
return sourceModel;
}
@Override
public ModelBakeSettings settings() {
return settings;
}
@Override
public Baker baker() {
return baker;
}
}
private static class OnLoadBlockModifierContext implements ModelModifier.OnLoadBlock.Context {
private ModelIdentifier id;
private BlockState state;
private void prepare(ModelIdentifier id, BlockState state) {
this.id = id;
private void prepare(BlockState state) {
this.state = state;
}
@Override
public ModelIdentifier id() {
return id;
}
@Override
public BlockState state() {
return state;
}
}
private static class BeforeBakeBlockModifierContext implements ModelModifier.BeforeBakeBlock.Context {
private ModelIdentifier id;
private Baker baker;
private static class BakeBlockModifierContext implements ModelModifier.BeforeBakeBlock.Context, ModelModifier.AfterBakeBlock.Context {
private final BlockState state;
private final Baker baker;
private BlockStateModel.UnbakedGrouped sourceModel;
private void prepare(ModelIdentifier id, Baker baker) {
this.id = id;
private BakeBlockModifierContext(BlockState state, Baker baker) {
this.state = state;
this.baker = baker;
}
private void prepareAfterBake(BlockStateModel.UnbakedGrouped sourceModel) {
this.sourceModel = sourceModel;
}
@Override
public ModelIdentifier id() {
return id;
public BlockState state() {
return state;
}
@Override
public Baker baker() {
return baker;
}
@Override
public BlockStateModel.UnbakedGrouped sourceModel() {
return sourceModel;
}
}
private static class AfterBakeBlockModifierContext implements ModelModifier.AfterBakeBlock.Context {
private ModelIdentifier id;
private GroupableModel sourceModel;
private Baker baker;
private static class BakeItemModifierContext implements ModelModifier.BeforeBakeItem.Context, ModelModifier.AfterBakeItem.Context {
private final Identifier itemId;
private final ItemModel.BakeContext bakeContext;
private ItemModel.Unbaked sourceModel;
private void prepare(ModelIdentifier id, GroupableModel sourceModel, Baker baker) {
this.id = id;
private BakeItemModifierContext(Identifier itemId, ItemModel.BakeContext bakeContext) {
this.itemId = itemId;
this.bakeContext = bakeContext;
}
private void prepareAfterBake(ItemModel.Unbaked sourceModel) {
this.sourceModel = sourceModel;
this.baker = baker;
}
@Override
public ModelIdentifier id() {
return id;
public Identifier itemId() {
return itemId;
}
@Override
public GroupableModel sourceModel() {
public ItemModel.BakeContext bakeContext() {
return bakeContext;
}
@Override
public ItemModel.Unbaked sourceModel() {
return sourceModel;
}
@Override
public Baker baker() {
return baker;
}
}
}

View file

@ -16,13 +16,10 @@
package net.fabricmc.fabric.impl.client.model.loading;
import java.util.Collection;
import java.util.IdentityHashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -41,7 +38,6 @@ import net.fabricmc.fabric.api.event.EventFactory;
public class ModelLoadingPluginContextImpl implements ModelLoadingPlugin.Context {
private static final Logger LOGGER = LoggerFactory.getLogger(ModelLoadingPluginContextImpl.class);
final Set<Identifier> extraModels = new LinkedHashSet<>();
final Map<Block, BlockStateResolver> blockStateResolvers = new IdentityHashMap<>();
private static final Identifier[] MODEL_MODIFIER_PHASES = new Identifier[] { ModelModifier.OVERRIDE_PHASE, ModelModifier.DEFAULT_PHASE, ModelModifier.WRAP_PHASE, ModelModifier.WRAP_LAST_PHASE };
@ -57,28 +53,6 @@ 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) {
try {
model = modifier.modifyModelBeforeBake(model, context);
} catch (Exception exception) {
LOGGER.error("Failed to modify unbaked model before bake", 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);
private final Event<ModelModifier.OnLoadBlock> onLoadBlockModifiers = EventFactory.createWithPhases(ModelModifier.OnLoadBlock.class, modifiers -> (model, context) -> {
for (ModelModifier.OnLoadBlock modifier : modifiers) {
try {
@ -112,18 +86,28 @@ public class ModelLoadingPluginContextImpl implements ModelLoadingPlugin.Context
return model;
}, MODEL_MODIFIER_PHASES);
@Override
public void addModels(Identifier... ids) {
for (Identifier id : ids) {
extraModels.add(id);
private final Event<ModelModifier.BeforeBakeItem> beforeBakeItemModifiers = EventFactory.createWithPhases(ModelModifier.BeforeBakeItem.class, modifiers -> (model, context) -> {
for (ModelModifier.BeforeBakeItem modifier : modifiers) {
try {
model = modifier.modifyModelBeforeBake(model, context);
} catch (Exception exception) {
LOGGER.error("Failed to modify unbaked item model before bake", exception);
}
}
}
@Override
public void addModels(Collection<? extends Identifier> ids) {
extraModels.addAll(ids);
}
return model;
}, MODEL_MODIFIER_PHASES);
private final Event<ModelModifier.AfterBakeItem> afterBakeItemModifiers = EventFactory.createWithPhases(ModelModifier.AfterBakeItem.class, modifiers -> (model, context) -> {
for (ModelModifier.AfterBakeItem modifier : modifiers) {
try {
model = modifier.modifyModelAfterBake(model, context);
} catch (Exception exception) {
LOGGER.error("Failed to modify baked item model after bake", exception);
}
}
return model;
}, MODEL_MODIFIER_PHASES);
@Override
public void registerBlockStateResolver(Block block, BlockStateResolver resolver) {
@ -146,16 +130,6 @@ public class ModelLoadingPluginContextImpl implements ModelLoadingPlugin.Context
return onLoadModifiers;
}
@Override
public Event<ModelModifier.BeforeBake> modifyModelBeforeBake() {
return beforeBakeModifiers;
}
@Override
public Event<ModelModifier.AfterBake> modifyModelAfterBake() {
return afterBakeModifiers;
}
@Override
public Event<ModelModifier.OnLoadBlock> modifyBlockModelOnLoad() {
return onLoadBlockModifiers;
@ -170,4 +144,14 @@ public class ModelLoadingPluginContextImpl implements ModelLoadingPlugin.Context
public Event<ModelModifier.AfterBakeBlock> modifyBlockModelAfterBake() {
return afterBakeBlockModifiers;
}
@Override
public Event<ModelModifier.BeforeBakeItem> modifyItemModelBeforeBake() {
return beforeBakeItemModifiers;
}
@Override
public Event<ModelModifier.AfterBakeItem> modifyItemModelAfterBake() {
return afterBakeItemModifiers;
}
}

View file

@ -26,56 +26,32 @@ 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.render.model.json.JsonUnbakedModel;
import net.minecraft.resource.ResourceManager;
import net.minecraft.resource.ResourceReloader;
import net.minecraft.util.Identifier;
import net.fabricmc.fabric.api.client.model.loading.v1.FabricBakedModelManager;
import net.fabricmc.fabric.api.client.model.loading.v1.UnbakedModelDeserializer;
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;
abstract class BakedModelManagerMixin {
@Unique
@Nullable
private volatile CompletableFuture<ModelLoadingEventDispatcher> eventDispatcherFuture;
@Unique
@Nullable
private Map<Identifier, BakedModel> extraModels;
@Override
public BakedModel getModel(Identifier id) {
if (extraModels == null) {
return missingBlockModel;
}
return extraModels.getOrDefault(id, missingBlockModel);
}
@Inject(method = "reload", at = @At("HEAD"))
private void onHeadReload(ResourceReloader.Synchronizer synchronizer, ResourceManager manager, Executor prepareExecutor, Executor applyExecutor, CallbackInfoReturnable<CompletableFuture<Void>> cir) {
eventDispatcherFuture = ModelLoadingPluginManager.preparePlugins(manager, prepareExecutor).thenApplyAsync(ModelLoadingEventDispatcher::new);
@ -89,13 +65,18 @@ abstract class BakedModelManagerMixin implements FabricBakedModelManager {
});
}
@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) {
@ModifyExpressionValue(method = "reload", at = @At(value = "INVOKE", target = "net/minecraft/client/render/model/BakedModelManager.reloadModels(Lnet/minecraft/resource/ResourceManager;Ljava/util/concurrent/Executor;)Ljava/util/concurrent/CompletableFuture;"))
private CompletableFuture<Map<Identifier, UnbakedModel>> hookModels(CompletableFuture<Map<Identifier, UnbakedModel>> modelsFuture) {
return modelsFuture.thenCombine(eventDispatcherFuture, (models, eventDispatcher) -> eventDispatcher.modifyModelsOnLoad(models));
}
@ModifyExpressionValue(method = "reload", at = @At(value = "INVOKE", target = "net/minecraft/client/render/model/BlockStatesLoader.load(Lnet/minecraft/resource/ResourceManager;Ljava/util/concurrent/Executor;)Ljava/util/concurrent/CompletableFuture;"))
private CompletableFuture<BlockStatesLoader.LoadedModels> hookBlockStateModels(CompletableFuture<BlockStatesLoader.LoadedModels> modelsFuture) {
return modelsFuture.thenCombine(eventDispatcherFuture, (models, eventDispatcher) -> eventDispatcher.modifyBlockModelsOnLoad(models));
}
@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) {
@ModifyArg(method = "reload", at = @At(value = "INVOKE", target = "java/util/concurrent/CompletableFuture.thenComposeAsync(Ljava/util/function/Function;Ljava/util/concurrent/Executor;)Ljava/util/concurrent/CompletableFuture;", ordinal = 0), index = 0)
private Function<Void, CompletableFuture<?>> hookModelBaking(Function<Void, CompletableFuture<?>> function) {
return v -> {
CompletableFuture<ModelLoadingEventDispatcher> future = eventDispatcherFuture;
@ -104,25 +85,9 @@ abstract class BakedModelManagerMixin implements FabricBakedModelManager {
}
ModelLoadingEventDispatcher.CURRENT.set(future.join());
ReferencedModelsCollector referencedModelsCollector = function.apply(v);
CompletableFuture<?> bakingResultFuture = function.apply(v);
ModelLoadingEventDispatcher.CURRENT.remove();
return referencedModelsCollector;
};
}
@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 -> {
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;
return bakingResultFuture;
};
}
@ -142,9 +107,4 @@ abstract class BakedModelManagerMixin implements FabricBakedModelManager {
private static Object actuallyDeserializeModel(Object originalModel, @Local Reader reader) {
return UnbakedModelDeserializer.deserialize(reader);
}
@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();
}
}

View file

@ -1,47 +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.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;
}
}

View file

@ -1,55 +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 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.util.Identifier;
import net.fabricmc.fabric.impl.client.model.loading.ModelBakerHooks;
import net.fabricmc.fabric.impl.client.model.loading.ModelLoadingEventDispatcher;
@Mixin(ModelBaker.BakerImpl.class)
abstract class ModelBakerBakerImplMixin {
@Shadow
@Final
private ModelBaker field_40571;
@WrapOperation(method = "bake(Lnet/minecraft/util/Identifier;Lnet/minecraft/client/render/model/ModelBakeSettings;)Lnet/minecraft/client/render/model/BakedModel;", at = @At(value = "INVOKE", target = "net/minecraft/client/render/model/UnbakedModel.bake(Lnet/minecraft/client/render/model/UnbakedModel;Lnet/minecraft/client/render/model/Baker;Lnet/minecraft/client/render/model/ModelBakeSettings;)Lnet/minecraft/client/render/model/BakedModel;"))
private BakedModel wrapModelBake(UnbakedModel unbakedModel, @Coerce Baker baker, ModelBakeSettings settings, Operation<BakedModel> operation, Identifier id) {
ModelLoadingEventDispatcher dispatcher = ((ModelBakerHooks) this.field_40571).fabric_getDispatcher();
if (dispatcher == null) {
return operation.call(unbakedModel, baker, settings);
}
unbakedModel = dispatcher.modifyModelBeforeBake(unbakedModel, id, settings, baker);
BakedModel model = operation.call(unbakedModel, baker, settings);
return dispatcher.modifyModelAfterBake(model, id, unbakedModel, settings, baker);
}
}

View file

@ -16,11 +16,12 @@
package net.fabricmc.fabric.mixin.client.model.loading;
import java.util.HashMap;
import java.util.Map;
import java.util.function.BiFunction;
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
import com.llamalad7.mixinextras.sugar.Local;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.spongepowered.asm.mixin.Final;
@ -29,27 +30,29 @@ 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.callback.CallbackInfo;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
import net.minecraft.client.render.model.BakedModel;
import net.minecraft.block.BlockState;
import net.minecraft.client.render.item.model.ItemModel;
import net.minecraft.client.render.model.BakedSimpleModel;
import net.minecraft.client.render.model.Baker;
import net.minecraft.client.render.model.GroupableModel;
import net.minecraft.client.render.model.BlockStateModel;
import net.minecraft.client.render.model.ModelBaker;
import net.minecraft.client.render.model.ModelRotation;
import net.minecraft.client.util.ModelIdentifier;
import net.minecraft.util.Identifier;
import net.fabricmc.fabric.impl.client.model.loading.BakedModelsHooks;
import net.fabricmc.fabric.impl.client.model.loading.ModelBakerHooks;
import net.fabricmc.fabric.impl.client.model.loading.ModelLoadingEventDispatcher;
@Mixin(ModelBaker.class)
abstract class ModelBakerMixin implements ModelBakerHooks {
abstract class ModelBakerMixin {
@Shadow
@Final
static Logger LOGGER;
@Shadow
@Final
Map<Identifier, BakedSimpleModel> simpleModels;
@Unique
@Nullable
private ModelLoadingEventDispatcher fabric_eventDispatcher;
@ -59,39 +62,37 @@ abstract class ModelBakerMixin implements ModelBakerHooks {
fabric_eventDispatcher = ModelLoadingEventDispatcher.CURRENT.get();
}
@WrapOperation(method = "method_65737", at = @At(value = "INVOKE", target = "net/minecraft/client/render/model/GroupableModel.bake(Lnet/minecraft/client/render/model/Baker;)Lnet/minecraft/client/render/model/BakedModel;"))
private BakedModel wrapBlockModelBake(GroupableModel unbakedModel, Baker baker, Operation<BakedModel> operation, ModelBaker.ErrorCollectingSpriteGetter spriteGetter, Map<ModelIdentifier, BakedModel> map, ModelIdentifier id) {
@ModifyArg(method = "bake", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/render/model/FutureModel;newTask(Ljava/util/Map;Ljava/util/function/BiFunction;Ljava/util/concurrent/Executor;)Ljava/util/concurrent/CompletableFuture;", ordinal = 0), index = 1)
private BiFunction<BlockState, BlockStateModel.UnbakedGrouped, BlockStateModel> hookBlockModelBake(BiFunction<BlockState, BlockStateModel.UnbakedGrouped, BlockStateModel> bifunction) {
if (fabric_eventDispatcher == null) {
return operation.call(unbakedModel, baker);
return bifunction;
}
unbakedModel = fabric_eventDispatcher.modifyBlockModelBeforeBake(unbakedModel, id, baker);
BakedModel model = operation.call(unbakedModel, baker);
return fabric_eventDispatcher.modifyBlockModelAfterBake(model, id, unbakedModel, baker);
return (state, unbakedModel) -> {
ModelLoadingEventDispatcher.CURRENT.set(fabric_eventDispatcher);
BlockStateModel model = bifunction.apply(state, unbakedModel);
ModelLoadingEventDispatcher.CURRENT.remove();
return model;
};
}
@Inject(method = "bake", at = @At("RETURN"))
private void onReturnBake(ModelBaker.ErrorCollectingSpriteGetter spriteGetter, CallbackInfoReturnable<ModelBaker.BakedModels> cir) {
if (fabric_eventDispatcher == null) {
return;
@WrapOperation(method = "method_68018", at = @At(value = "INVOKE", target = "net/minecraft/client/render/model/BlockStateModel$UnbakedGrouped.getModel(Lnet/minecraft/block/BlockState;Lnet/minecraft/client/render/model/Baker;)Lnet/minecraft/client/render/model/BlockStateModel;"))
private static BlockStateModel wrapBlockModelBake(BlockStateModel.UnbakedGrouped unbakedModel, BlockState state, Baker baker, Operation<BlockStateModel> operation) {
ModelLoadingEventDispatcher eventDispatcher = ModelLoadingEventDispatcher.CURRENT.get();
if (eventDispatcher == null) {
return operation.call(unbakedModel, state, baker);
}
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);
return eventDispatcher.modifyBlockModel(unbakedModel, state, baker, operation);
}
@Override
@Nullable
public ModelLoadingEventDispatcher fabric_getDispatcher() {
return fabric_eventDispatcher;
@WrapOperation(method = "method_68019", at = @At(value = "INVOKE", target = "net/minecraft/client/render/item/model/ItemModel$Unbaked.bake(Lnet/minecraft/client/render/item/model/ItemModel$BakeContext;)Lnet/minecraft/client/render/item/model/ItemModel;"))
private ItemModel wrapItemModelBake(ItemModel.Unbaked unbakedModel, ItemModel.BakeContext bakeContext, Operation<ItemModel> operation, @Local Identifier itemId) {
if (fabric_eventDispatcher == null) {
return operation.call(unbakedModel, bakeContext);
}
return fabric_eventDispatcher.modifyItemModel(unbakedModel, itemId, bakeContext, operation);
}
}

View file

@ -1,65 +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 org.jetbrains.annotations.Nullable;
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.ModifyVariable;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import net.minecraft.client.render.model.ReferencedModelsCollector;
import net.minecraft.client.render.model.ResolvableModel;
import net.minecraft.client.render.model.UnbakedModel;
import net.minecraft.util.Identifier;
import net.fabricmc.fabric.impl.client.model.loading.ModelLoadingEventDispatcher;
@Mixin(ReferencedModelsCollector.class)
abstract class ReferencedModelsCollectorMixin {
@Unique
@Nullable
private ModelLoadingEventDispatcher fabric_eventDispatcher;
@Shadow
public abstract void add(ResolvableModel model);
@Shadow
abstract UnbakedModel computeResolvedModel(Identifier id);
@Inject(method = "<init>", at = @At("RETURN"))
private void onReturnInit(CallbackInfo ci) {
fabric_eventDispatcher = ModelLoadingEventDispatcher.CURRENT.get();
if (fabric_eventDispatcher != null) {
fabric_eventDispatcher.forEachExtraModel(id -> add(computeResolvedModel(id)));
}
}
@ModifyVariable(method = "getModel", at = @At(value = "STORE", ordinal = 0), ordinal = 0)
@Nullable
private UnbakedModel onLoadModel(@Nullable UnbakedModel model, Identifier id) {
if (fabric_eventDispatcher == null) {
return model;
}
return fabric_eventDispatcher.modifyModelOnLoad(model, id);
}
}

View file

@ -1,38 +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 org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import net.minecraft.client.render.model.BakedModel;
import net.minecraft.client.render.model.WrapperBakedModel;
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;
}
}

View file

@ -1,3 +0,0 @@
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

View file

@ -6,11 +6,7 @@
"BakedModelManagerMixin",
"JsonUnbakedModelAccessor",
"JsonUnbakedModelMixin",
"ModelBakerBakedModelsMixin",
"ModelBakerBakerImplMixin",
"ModelBakerMixin",
"ReferencedModelsCollectorMixin",
"WrapperBakedModelMixin"
"ModelBakerMixin"
],
"injectors": {
"defaultRequire": 1

View file

@ -25,13 +25,5 @@
"environment": "client",
"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_10200": [ "net/fabricmc/fabric/api/client/model/loading/v1/UnwrappableBakedModel" ]
}
}
]
}

View file

@ -30,28 +30,28 @@ import net.minecraft.client.render.entity.feature.FeatureRenderer;
import net.minecraft.client.render.entity.feature.FeatureRendererContext;
import net.minecraft.client.render.entity.model.EntityModel;
import net.minecraft.client.render.entity.state.LivingEntityRenderState;
import net.minecraft.client.render.model.BakedModel;
import net.minecraft.client.render.model.BlockStateModel;
import net.minecraft.client.util.math.MatrixStack;
import net.minecraft.util.math.MathHelper;
public class BakedModelFeatureRenderer<S extends LivingEntityRenderState, M extends EntityModel<S>> extends FeatureRenderer<S, M> {
private final Supplier<BakedModel> modelSupplier;
private final Supplier<BlockStateModel> modelSupplier;
public BakedModelFeatureRenderer(FeatureRendererContext<S, M> context, Supplier<BakedModel> modelSupplier) {
public BakedModelFeatureRenderer(FeatureRendererContext<S, M> context, Supplier<BlockStateModel> modelSupplier) {
super(context);
this.modelSupplier = modelSupplier;
}
@Override
public void render(MatrixStack matrices, VertexConsumerProvider vertexConsumers, int light, S state, float limbAngle, float limbDistance) {
BakedModel model = modelSupplier.get();
BlockStateModel model = modelSupplier.get();
VertexConsumer vertices = vertexConsumers.getBuffer(TexturedRenderLayers.getEntityCutout());
matrices.push();
matrices.multiply(new Quaternionf(new AxisAngle4f(state.age * 0.07F - state.bodyYaw * MathHelper.RADIANS_PER_DEGREE, 0, 1, 0)));
matrices.scale(-0.75F, -0.75F, 0.75F);
float aboveHead = (float) (Math.sin(state.age * 0.08F)) * 0.5F + 0.5F;
matrices.translate(-0.5F, 0.75F + aboveHead, -0.5F);
MinecraftClient.getInstance().getBlockRenderManager().getModelRenderer().render(matrices.peek(), vertices, null, model, 1, 1, 1, light, OverlayTexture.DEFAULT_UV);
MinecraftClient.getInstance().getBlockRenderManager().getModelRenderer().render(matrices.peek(), vertices, model, 1, 1, 1, light, OverlayTexture.DEFAULT_UV);
matrices.pop();
}
}

View file

@ -16,42 +16,22 @@
package net.fabricmc.fabric.test.model.loading;
import java.util.List;
import java.util.function.Predicate;
import java.util.function.Supplier;
import org.jetbrains.annotations.Nullable;
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.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.BlockStateModel;
import net.minecraft.client.render.model.MissingModel;
import net.minecraft.client.render.model.ModelBakeSettings;
import net.minecraft.client.render.model.ModelTextures;
import net.minecraft.client.render.model.WrapperBakedModel;
import net.minecraft.client.render.model.json.ModelTransformation;
import net.minecraft.client.render.model.SimpleBlockStateModel;
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.AffineTransformation;
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.fabricmc.api.ClientModInitializer;
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.mesh.QuadEmitter;
import net.fabricmc.fabric.api.renderer.v1.model.FabricBakedModel;
import net.fabricmc.fabric.api.resource.ResourceManagerHelper;
public class ModelTestModClient implements ClientModInitializer {
@ -64,7 +44,7 @@ public class ModelTestModClient implements ClientModInitializer {
@Override
public void onInitializeClient() {
ModelLoadingPlugin.register(pluginContext -> {
pluginContext.addModels(HALF_RED_SAND_MODEL_ID);
//pluginContext.addModels(HALF_RED_SAND_MODEL_ID);
// 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 -> {
@ -72,8 +52,8 @@ public class ModelTestModClient implements ClientModInitializer {
Identifier wheatStage0Id = Identifier.ofVanilla("block/wheat_stage0");
Identifier wheatStage7Id = Identifier.ofVanilla("block/wheat_stage7");
GroupableModel wheatStage0Model = simpleGroupableModel(wheatStage0Id);
GroupableModel wheatStage7Model = simpleGroupableModel(wheatStage7Id);
BlockStateModel.UnbakedGrouped wheatStage0Model = simpleUnbakedGroupedBlockStateModel(wheatStage0Id);
BlockStateModel.UnbakedGrouped wheatStage7Model = simpleUnbakedGroupedBlockStateModel(wheatStage7Id);
for (int age = 0; age <= 6; age++) {
context.setModel(state.with(CropBlock.AGE, age), wheatStage0Model);
@ -82,52 +62,54 @@ public class ModelTestModClient implements ClientModInitializer {
context.setModel(state.with(CropBlock.AGE, 7), wheatStage7Model);
});
// FIXME
// 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;
});
//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 simpleUnbakedGroupedBlockStateModel(MissingModel.ID);
}
return model;
});
// FIXME
// Remove bottom face of gold blocks
pluginContext.modifyModelAfterBake().register(ModelModifier.WRAP_PHASE, (model, context) -> {
if (context.id().equals(GOLD_BLOCK_MODEL_ID)) {
return new DownQuadRemovingModel(model);
}
return model;
});
//pluginContext.modifyModelAfterBake().register(ModelModifier.WRAP_PHASE, (model, context) -> {
// if (context.id().equals(GOLD_BLOCK_MODEL_ID)) {
// return new DownQuadRemovingModel(model);
// }
//
// return model;
//});
});
ResourceManagerHelper.get(ResourceType.CLIENT_RESOURCES).registerReloadListener(SpecificModelReloadListener.INSTANCE);
LivingEntityFeatureRendererRegistrationCallback.EVENT.register((entityType, entityRenderer, registrationHelper, context) -> {
if (entityRenderer instanceof PlayerEntityRenderer playerRenderer) {
registrationHelper.register(new BakedModelFeatureRenderer<>(playerRenderer, SpecificModelReloadListener.INSTANCE::getSpecificModel));
//registrationHelper.register(new BakedModelFeatureRenderer<>(playerRenderer, SpecificModelReloadListener.INSTANCE::getSpecificModel));
}
});
}
@ -136,20 +118,20 @@ public class ModelTestModClient implements ClientModInitializer {
return Identifier.of(ID, path);
}
private static GroupableModel simpleGroupableModel(Identifier model) {
return new WeightedUnbakedModel(List.of(new ModelVariant(model, AffineTransformation.identity(), false, 1)));
private static BlockStateModel.UnbakedGrouped simpleUnbakedGroupedBlockStateModel(Identifier model) {
return new SimpleBlockStateModel.Unbaked(new ModelVariant(model)).cached();
}
private static class DownQuadRemovingModel extends WrapperBakedModel implements FabricBakedModel {
DownQuadRemovingModel(BakedModel model) {
super(model);
}
@Override
public void emitBlockQuads(QuadEmitter emitter, BlockRenderView blockView, BlockState state, BlockPos pos, Supplier<Random> randomSupplier, Predicate<@Nullable Direction> cullTest) {
emitter.pushTransform(q -> q.cullFace() != Direction.DOWN);
((FabricBakedModel) wrapped).emitBlockQuads(emitter, blockView, state, pos, randomSupplier, cullTest);
emitter.popTransform();
}
}
//private static class DownQuadRemovingModel extends WrapperBakedModel implements FabricBakedModel {
// DownQuadRemovingModel(BakedModel model) {
// super(model);
// }
//
// @Override
// public void emitBlockQuads(QuadEmitter emitter, BlockRenderView blockView, BlockState state, BlockPos pos, Supplier<Random> randomSupplier, Predicate<@Nullable Direction> cullTest) {
// emitter.pushTransform(q -> q.cullFace() != Direction.DOWN);
// ((FabricBakedModel) wrapped).emitBlockQuads(emitter, blockView, state, pos, randomSupplier, cullTest);
// emitter.popTransform();
// }
//}
}

View file

@ -19,27 +19,27 @@ package net.fabricmc.fabric.test.model.loading;
import java.util.Collection;
import java.util.List;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.render.model.BakedModel;
import net.minecraft.client.render.model.BlockStateModel;
import net.minecraft.resource.ResourceManager;
import net.minecraft.util.Identifier;
import net.fabricmc.fabric.api.resource.ResourceReloadListenerKeys;
import net.fabricmc.fabric.api.resource.SimpleSynchronousResourceReloadListener;
// FIXME
public class SpecificModelReloadListener implements SimpleSynchronousResourceReloadListener {
public static final SpecificModelReloadListener INSTANCE = new SpecificModelReloadListener();
public static final Identifier ID = Identifier.of(ModelTestModClient.ID, "specific_model");
private BakedModel specificModel;
private BlockStateModel specificModel;
public BakedModel getSpecificModel() {
public BlockStateModel getSpecificModel() {
return specificModel;
}
@Override
public void reload(ResourceManager manager) {
specificModel = MinecraftClient.getInstance().getBakedModelManager().getModel(ModelTestModClient.HALF_RED_SAND_MODEL_ID);
//specificModel = MinecraftClient.getInstance().getBakedModelManager().getModel(ModelTestModClient.HALF_RED_SAND_MODEL_ID);
}
@Override

View file

@ -16,100 +16,82 @@
package net.fabricmc.fabric.test.model.loading;
import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParseException;
import com.google.gson.JsonSyntaxException;
import com.mojang.serialization.JsonOps;
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;
import net.minecraft.util.Identifier;
import net.minecraft.util.JsonHelper;
import net.minecraft.util.math.AffineTransformation;
import net.fabricmc.api.ClientModInitializer;
import net.fabricmc.fabric.api.client.model.loading.v1.UnbakedModelDeserializer;
// FIXME
public class UnbakedModelDeserializerTest implements ClientModInitializer {
@Override
public void onInitializeClient() {
UnbakedModelDeserializer.register(ModelTestModClient.id("transformed"), TransformedModelDeserializer.INSTANCE);
//UnbakedModelDeserializer.register(ModelTestModClient.id("transformed"), TransformedModelDeserializer.INSTANCE);
}
private static class TransformedModelDeserializer implements UnbakedModelDeserializer {
public static final TransformedModelDeserializer INSTANCE = new TransformedModelDeserializer();
@Override
public UnbakedModel deserialize(JsonObject jsonObject, JsonDeserializationContext context) throws JsonParseException {
JsonElement transformationElement = JsonHelper.getElement(jsonObject, "transformation");
AffineTransformation transformation = AffineTransformation.ANY_CODEC.parse(JsonOps.INSTANCE, transformationElement).getOrThrow();
JsonElement parentElement = JsonHelper.getElement(jsonObject, "parent");
if (JsonHelper.isString(parentElement)) {
Identifier parentId = Identifier.of(parentElement.getAsString());
return new TransformedUnbakedModel(transformation, parentId);
} else if (parentElement.isJsonObject()) {
UnbakedModel parent = context.deserialize(parentElement, UnbakedModel.class);
return new TransformedUnbakedModel(transformation, parent);
} else {
throw new JsonSyntaxException("parent must be string or object");
}
}
}
private static class TransformedUnbakedModel implements UnbakedModel {
private final AffineTransformation transformation;
@Nullable
private final Identifier parentId;
private UnbakedModel parent;
private TransformedUnbakedModel(AffineTransformation transformation, Identifier parentId) {
this.transformation = transformation;
this.parentId = parentId;
}
private TransformedUnbakedModel(AffineTransformation transformation, UnbakedModel parent) {
this.transformation = transformation;
parentId = null;
this.parent = parent;
}
@Override
public void resolve(Resolver resolver) {
if (parentId != null) {
parent = resolver.resolve(parentId);
}
}
@Override
public UnbakedModel getParent() {
return parent;
}
@Override
public BakedModel bake(ModelTextures textures, Baker baker, ModelBakeSettings settings, boolean ambientOcclusion, boolean isSideLit, ModelTransformation transformation) {
settings = new SimpleModelBakeSettings(settings.getRotation().multiply(this.transformation), settings.isUvLocked());
return parent.bake(textures, baker, settings, ambientOcclusion, isSideLit, transformation);
}
}
private record SimpleModelBakeSettings(AffineTransformation transformation, boolean uvLocked) implements ModelBakeSettings {
@Override
public AffineTransformation getRotation() {
return transformation;
}
@Override
public boolean isUvLocked() {
return uvLocked;
}
}
//private static class TransformedModelDeserializer implements UnbakedModelDeserializer {
// public static final TransformedModelDeserializer INSTANCE = new TransformedModelDeserializer();
//
// @Override
// public UnbakedModel deserialize(JsonObject jsonObject, JsonDeserializationContext context) throws JsonParseException {
// JsonElement transformationElement = JsonHelper.getElement(jsonObject, "transformation");
// AffineTransformation transformation = AffineTransformation.ANY_CODEC.parse(JsonOps.INSTANCE, transformationElement).getOrThrow();
//
// JsonElement parentElement = JsonHelper.getElement(jsonObject, "parent");
//
// if (JsonHelper.isString(parentElement)) {
// Identifier parentId = Identifier.of(parentElement.getAsString());
// return new TransformedUnbakedModel(transformation, parentId);
// } else if (parentElement.isJsonObject()) {
// UnbakedModel parent = context.deserialize(parentElement, UnbakedModel.class);
// return new TransformedUnbakedModel(transformation, parent);
// } else {
// throw new JsonSyntaxException("parent must be string or object");
// }
// }
//}
//
//private static class TransformedUnbakedModel implements UnbakedModel {
// private final AffineTransformation transformation;
// @Nullable
// private final Identifier parentId;
// private UnbakedModel parent;
//
// private TransformedUnbakedModel(AffineTransformation transformation, Identifier parentId) {
// this.transformation = transformation;
// this.parentId = parentId;
// }
//
// private TransformedUnbakedModel(AffineTransformation transformation, UnbakedModel parent) {
// this.transformation = transformation;
// parentId = null;
// this.parent = parent;
// }
//
// @Override
// public void resolve(Resolver resolver) {
// if (parentId != null) {
// parent = resolver.resolve(parentId);
// }
// }
//
// @Override
// public UnbakedModel getParent() {
// return parent;
// }
//
// @Override
// public BakedModel bake(ModelTextures textures, Baker baker, ModelBakeSettings settings, boolean ambientOcclusion, boolean isSideLit, ModelTransformation transformation) {
// settings = new SimpleModelBakeSettings(settings.getRotation().multiply(this.transformation), settings.isUvLocked());
// return parent.bake(textures, baker, settings, ambientOcclusion, isSideLit, transformation);
// }
//}
//
//private record SimpleModelBakeSettings(AffineTransformation transformation, boolean uvLocked) implements ModelBakeSettings {
// @Override
// public AffineTransformation getRotation() {
// return transformation;
// }
//
// @Override
// public boolean isUvLocked() {
// return uvLocked;
// }
//}
}

View file

@ -49,7 +49,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'