Update Model Loading API to 1.21.4 ()

* 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:
PepperCode1 2024-11-30 10:18:10 -08:00 committed by GitHub
parent bdca9acb21
commit 6a293bdb89
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
32 changed files with 614 additions and 1137 deletions

View file

@ -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')
}

View file

@ -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);
}
}

View file

@ -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);
}
}

View file

@ -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();
}
}

View file

@ -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();
}
}

View file

@ -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();
}
}

View file

@ -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;
}
}

View file

@ -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);
}
}

View file

@ -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();
}
}

View file

@ -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);
}

View file

@ -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();
}

View file

@ -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();
}

View file

@ -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);
}
}

View file

@ -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;
}
}
}

View file

@ -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;
}
}

View file

@ -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();
}
}

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.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

@ -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);
}
}

View file

@ -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;
}
}

View file

@ -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;
}
}

View file

@ -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);
}
}

View file

@ -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;
}
}

View file

@ -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

View file

@ -4,9 +4,10 @@
"compatibilityLevel": "JAVA_21",
"client": [
"BakedModelManagerMixin",
"ModelBakerBakedModelsMixin",
"ModelBakerMixin",
"ReferencedModelsCollectorMixin",
"ModelLoaderBakerImplMixin",
"ModelLoaderMixin"
"WrapperBakedModelMixin"
],
"injectors": {
"defaultRequire": 1

View file

@ -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" ]
}
}
}

View file

@ -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)));
}
}

View file

@ -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;

View file

@ -1,7 +1,8 @@
{
"textures": {
"sand": "minecraft:block/sand",
"red_sand": "minecraft:block/red_sand"
"red_sand": "minecraft:block/red_sand",
"particle": "#sand"
},
"elements": [
{

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.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);
}

View file

@ -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);
}
}

View file

@ -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());
}
}

View file

@ -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'