diff --git a/deprecated/fabric-models-v0/build.gradle b/deprecated/fabric-models-v0/build.gradle new file mode 100644 index 0000000000..c5841fda4b --- /dev/null +++ b/deprecated/fabric-models-v0/build.gradle @@ -0,0 +1,7 @@ +archivesBaseName = "fabric-models-v0" +version = getSubprojectVersion(project) + +moduleDependencies(project, [ + 'fabric-api-base', + 'fabric-model-loading-api-v1' +]) diff --git a/fabric-models-v0/src/client/java/net/fabricmc/fabric/api/client/model/BakedModelManagerHelper.java b/deprecated/fabric-models-v0/src/client/java/net/fabricmc/fabric/api/client/model/BakedModelManagerHelper.java similarity index 89% rename from fabric-models-v0/src/client/java/net/fabricmc/fabric/api/client/model/BakedModelManagerHelper.java rename to deprecated/fabric-models-v0/src/client/java/net/fabricmc/fabric/api/client/model/BakedModelManagerHelper.java index 3eea16ad15..d13bec8c80 100644 --- a/fabric-models-v0/src/client/java/net/fabricmc/fabric/api/client/model/BakedModelManagerHelper.java +++ b/deprecated/fabric-models-v0/src/client/java/net/fabricmc/fabric/api/client/model/BakedModelManagerHelper.java @@ -23,8 +23,12 @@ import net.minecraft.client.util.ModelIdentifier; import net.minecraft.util.Identifier; -import net.fabricmc.fabric.impl.client.model.BakedModelManagerHooks; +import net.fabricmc.fabric.api.client.model.loading.v1.FabricBakedModelManager; +/** + * @deprecated Use {@link FabricBakedModelManager#getModel(Identifier)} instead. + */ +@Deprecated public final class BakedModelManagerHelper { /** * An alternative to {@link BakedModelManager#getModel(ModelIdentifier)} that accepts an @@ -42,7 +46,7 @@ public final class BakedModelManagerHelper { */ @Nullable public static BakedModel getModel(BakedModelManager manager, Identifier id) { - return ((BakedModelManagerHooks) manager).fabric_getModel(id); + return manager.getModel(id); } private BakedModelManagerHelper() { } diff --git a/fabric-models-v0/src/client/java/net/fabricmc/fabric/api/client/model/ExtraModelProvider.java b/deprecated/fabric-models-v0/src/client/java/net/fabricmc/fabric/api/client/model/ExtraModelProvider.java similarity index 90% rename from fabric-models-v0/src/client/java/net/fabricmc/fabric/api/client/model/ExtraModelProvider.java rename to deprecated/fabric-models-v0/src/client/java/net/fabricmc/fabric/api/client/model/ExtraModelProvider.java index 8293166531..0a6afcbed8 100644 --- a/fabric-models-v0/src/client/java/net/fabricmc/fabric/api/client/model/ExtraModelProvider.java +++ b/deprecated/fabric-models-v0/src/client/java/net/fabricmc/fabric/api/client/model/ExtraModelProvider.java @@ -22,6 +22,12 @@ import net.minecraft.resource.ResourceManager; import net.minecraft.util.Identifier; +import net.fabricmc.fabric.api.client.model.loading.v1.ModelLoadingPlugin; + +/** + * @deprecated Use {@link ModelLoadingPlugin} and related classes instead. + */ +@Deprecated @FunctionalInterface public interface ExtraModelProvider { /** diff --git a/fabric-models-v0/src/client/java/net/fabricmc/fabric/api/client/model/ModelAppender.java b/deprecated/fabric-models-v0/src/client/java/net/fabricmc/fabric/api/client/model/ModelAppender.java similarity index 100% rename from fabric-models-v0/src/client/java/net/fabricmc/fabric/api/client/model/ModelAppender.java rename to deprecated/fabric-models-v0/src/client/java/net/fabricmc/fabric/api/client/model/ModelAppender.java diff --git a/fabric-models-v0/src/client/java/net/fabricmc/fabric/api/client/model/ModelLoadingRegistry.java b/deprecated/fabric-models-v0/src/client/java/net/fabricmc/fabric/api/client/model/ModelLoadingRegistry.java similarity index 92% rename from fabric-models-v0/src/client/java/net/fabricmc/fabric/api/client/model/ModelLoadingRegistry.java rename to deprecated/fabric-models-v0/src/client/java/net/fabricmc/fabric/api/client/model/ModelLoadingRegistry.java index b0f041da0f..8f72a5d046 100644 --- a/fabric-models-v0/src/client/java/net/fabricmc/fabric/api/client/model/ModelLoadingRegistry.java +++ b/deprecated/fabric-models-v0/src/client/java/net/fabricmc/fabric/api/client/model/ModelLoadingRegistry.java @@ -21,8 +21,13 @@ import net.minecraft.resource.ResourceManager; import net.minecraft.util.Identifier; +import net.fabricmc.fabric.api.client.model.loading.v1.ModelLoadingPlugin; import net.fabricmc.fabric.impl.client.model.ModelLoadingRegistryImpl; +/** + * @deprecated Register a {@link ModelLoadingPlugin} instead. + */ +@Deprecated public interface ModelLoadingRegistry { ModelLoadingRegistry INSTANCE = new ModelLoadingRegistryImpl(); diff --git a/fabric-models-v0/src/client/java/net/fabricmc/fabric/api/client/model/ModelProviderContext.java b/deprecated/fabric-models-v0/src/client/java/net/fabricmc/fabric/api/client/model/ModelProviderContext.java similarity index 88% rename from fabric-models-v0/src/client/java/net/fabricmc/fabric/api/client/model/ModelProviderContext.java rename to deprecated/fabric-models-v0/src/client/java/net/fabricmc/fabric/api/client/model/ModelProviderContext.java index 3aafc7014a..96b097d285 100644 --- a/fabric-models-v0/src/client/java/net/fabricmc/fabric/api/client/model/ModelProviderContext.java +++ b/deprecated/fabric-models-v0/src/client/java/net/fabricmc/fabric/api/client/model/ModelProviderContext.java @@ -20,9 +20,13 @@ import net.minecraft.client.util.ModelIdentifier; import net.minecraft.util.Identifier; +import net.fabricmc.fabric.api.client.model.loading.v1.ModelLoadingPlugin; + /** * The model loading context used during model providing. + * @deprecated Use {@link ModelLoadingPlugin} and related classes instead. */ +@Deprecated public interface ModelProviderContext { /** * Load a model using a {@link Identifier}, {@link ModelIdentifier}, ... diff --git a/fabric-models-v0/src/client/java/net/fabricmc/fabric/api/client/model/ModelProviderException.java b/deprecated/fabric-models-v0/src/client/java/net/fabricmc/fabric/api/client/model/ModelProviderException.java similarity index 83% rename from fabric-models-v0/src/client/java/net/fabricmc/fabric/api/client/model/ModelProviderException.java rename to deprecated/fabric-models-v0/src/client/java/net/fabricmc/fabric/api/client/model/ModelProviderException.java index 1d7899927e..41805578cc 100644 --- a/fabric-models-v0/src/client/java/net/fabricmc/fabric/api/client/model/ModelProviderException.java +++ b/deprecated/fabric-models-v0/src/client/java/net/fabricmc/fabric/api/client/model/ModelProviderException.java @@ -16,6 +16,12 @@ package net.fabricmc.fabric.api.client.model; +import net.fabricmc.fabric.api.client.model.loading.v1.ModelLoadingPlugin; + +/** + * @deprecated Use {@link ModelLoadingPlugin} and related classes instead. + */ +@Deprecated public class ModelProviderException extends Exception { public ModelProviderException(String s) { super(s); diff --git a/fabric-models-v0/src/client/java/net/fabricmc/fabric/api/client/model/ModelResourceProvider.java b/deprecated/fabric-models-v0/src/client/java/net/fabricmc/fabric/api/client/model/ModelResourceProvider.java similarity index 92% rename from fabric-models-v0/src/client/java/net/fabricmc/fabric/api/client/model/ModelResourceProvider.java rename to deprecated/fabric-models-v0/src/client/java/net/fabricmc/fabric/api/client/model/ModelResourceProvider.java index b7e7eb60b0..caf7b1a49a 100644 --- a/fabric-models-v0/src/client/java/net/fabricmc/fabric/api/client/model/ModelResourceProvider.java +++ b/deprecated/fabric-models-v0/src/client/java/net/fabricmc/fabric/api/client/model/ModelResourceProvider.java @@ -21,6 +21,8 @@ import net.minecraft.client.render.model.UnbakedModel; import net.minecraft.util.Identifier; +import net.fabricmc.fabric.api.client.model.loading.v1.ModelLoadingPlugin; + /** * Interface for model resource providers. * @@ -40,7 +42,10 @@ * * + * + * @deprecated Use {@link ModelLoadingPlugin} and related classes instead. */ +@Deprecated @FunctionalInterface public interface ModelResourceProvider { /** diff --git a/fabric-models-v0/src/client/java/net/fabricmc/fabric/api/client/model/ModelVariantProvider.java b/deprecated/fabric-models-v0/src/client/java/net/fabricmc/fabric/api/client/model/ModelVariantProvider.java similarity index 92% rename from fabric-models-v0/src/client/java/net/fabricmc/fabric/api/client/model/ModelVariantProvider.java rename to deprecated/fabric-models-v0/src/client/java/net/fabricmc/fabric/api/client/model/ModelVariantProvider.java index b2ab8e6c5d..1fdf970b2b 100644 --- a/fabric-models-v0/src/client/java/net/fabricmc/fabric/api/client/model/ModelVariantProvider.java +++ b/deprecated/fabric-models-v0/src/client/java/net/fabricmc/fabric/api/client/model/ModelVariantProvider.java @@ -21,6 +21,8 @@ import net.minecraft.client.render.model.UnbakedModel; import net.minecraft.client.util.ModelIdentifier; +import net.fabricmc.fabric.api.client.model.loading.v1.ModelLoadingPlugin; + /** * Interface for model variant providers. * @@ -37,7 +39,10 @@ * *

Keep in mind that only *one* ModelVariantProvider may respond to a given model * at any time. + * + * @deprecated Use {@link ModelLoadingPlugin} and related classes instead. */ +@Deprecated @FunctionalInterface public interface ModelVariantProvider { /** diff --git a/deprecated/fabric-models-v0/src/client/java/net/fabricmc/fabric/impl/client/model/ModelLoadingRegistryImpl.java b/deprecated/fabric-models-v0/src/client/java/net/fabricmc/fabric/impl/client/model/ModelLoadingRegistryImpl.java new file mode 100644 index 0000000000..6ab871a580 --- /dev/null +++ b/deprecated/fabric-models-v0/src/client/java/net/fabricmc/fabric/impl/client/model/ModelLoadingRegistryImpl.java @@ -0,0 +1,111 @@ +/* + * 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; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.function.Consumer; +import java.util.function.Function; + +import net.minecraft.client.util.ModelIdentifier; +import net.minecraft.resource.ResourceManager; +import net.minecraft.util.Identifier; + +import net.fabricmc.fabric.api.client.model.ExtraModelProvider; +import net.fabricmc.fabric.api.client.model.ModelAppender; +import net.fabricmc.fabric.api.client.model.ModelLoadingRegistry; +import net.fabricmc.fabric.api.client.model.ModelProviderContext; +import net.fabricmc.fabric.api.client.model.ModelProviderException; +import net.fabricmc.fabric.api.client.model.ModelResourceProvider; +import net.fabricmc.fabric.api.client.model.ModelVariantProvider; +import net.fabricmc.fabric.api.client.model.loading.v1.ModelLoadingPlugin; +import net.fabricmc.fabric.api.client.model.loading.v1.PreparableModelLoadingPlugin; +import net.fabricmc.fabric.impl.client.model.loading.ModelLoaderPluginContextImpl; + +public class ModelLoadingRegistryImpl implements ModelLoadingRegistry { + private final List modelProviders = new ArrayList<>(); + private final List modelAppenders = new ArrayList<>(); + private final List> resourceProviderSuppliers = new ArrayList<>(); + private final List> variantProviderSuppliers = new ArrayList<>(); + + { + // Grabs the resource manager to use it in the main model loading code. + // When using the v1 API, data should be loaded in parallel before model loading starts. + PreparableModelLoadingPlugin.register( + (resourceManager, executor) -> CompletableFuture.completedFuture(resourceManager), + this::onInitializeModelLoader); + } + + private void onInitializeModelLoader(ResourceManager resourceManager, ModelLoadingPlugin.Context pluginContext) { + Consumer extraModelConsumer = pluginContext::addModels; + Consumer extraModelConsumer2 = pluginContext::addModels; + // A bit hacky, but avoids the allocation of a new context wrapper every time. + ModelProviderContext resourceProviderContext = ((ModelLoaderPluginContextImpl) pluginContext).modelGetter::apply; + + for (ExtraModelProvider provider : modelProviders) { + provider.provideExtraModels(resourceManager, extraModelConsumer); + } + + for (ModelAppender appender : modelAppenders) { + appender.appendAll(resourceManager, extraModelConsumer2); + } + + for (Function supplier : resourceProviderSuppliers) { + ModelResourceProvider provider = supplier.apply(resourceManager); + + pluginContext.resolveModel().register(resolverContext -> { + try { + return provider.loadModelResource(resolverContext.id(), resourceProviderContext); + } catch (ModelProviderException e) { + throw new RuntimeException(e); + } + }); + } + + for (Function supplier : variantProviderSuppliers) { + ModelVariantProvider provider = supplier.apply(resourceManager); + ((ModelLoaderPluginContextImpl) pluginContext).legacyVariantProviders().register(modelId -> { + try { + return provider.loadModelVariant(modelId, resourceProviderContext); + } catch (ModelProviderException e) { + throw new RuntimeException(e); + } + }); + } + } + + @Override + public void registerModelProvider(ExtraModelProvider provider) { + modelProviders.add(provider); + } + + @Override + public void registerAppender(ModelAppender appender) { + modelAppenders.add(appender); + } + + @Override + public void registerResourceProvider(Function providerSupplier) { + resourceProviderSuppliers.add(providerSupplier); + } + + @Override + public void registerVariantProvider(Function providerSupplier) { + variantProviderSuppliers.add(providerSupplier); + } +} diff --git a/fabric-models-v0/src/client/resources/assets/fabric-models-v0/icon.png b/deprecated/fabric-models-v0/src/client/resources/assets/fabric-models-v0/icon.png similarity index 100% rename from fabric-models-v0/src/client/resources/assets/fabric-models-v0/icon.png rename to deprecated/fabric-models-v0/src/client/resources/assets/fabric-models-v0/icon.png diff --git a/fabric-models-v0/src/client/resources/fabric.mod.json b/deprecated/fabric-models-v0/src/client/resources/fabric.mod.json similarity index 82% rename from fabric-models-v0/src/client/resources/fabric.mod.json rename to deprecated/fabric-models-v0/src/client/resources/fabric.mod.json index 93a4b284b5..ced3a503e8 100644 --- a/fabric-models-v0/src/client/resources/fabric.mod.json +++ b/deprecated/fabric-models-v0/src/client/resources/fabric.mod.json @@ -17,13 +17,11 @@ ], "depends": { "fabricloader": ">=0.4.0", - "fabric-api-base": "*" + "fabric-api-base": "*", + "fabric-model-loading-api-v1": "*" }, "description": "Hooks for models and model loading.", - "mixins": [ - "fabric-models-v0.mixins.json" - ], "custom": { - "fabric-api:module-lifecycle": "stable" + "fabric-api:module-lifecycle": "deprecated" } } diff --git a/fabric-models-v0/build.gradle b/fabric-model-loading-api-v1/build.gradle similarity index 69% rename from fabric-models-v0/build.gradle rename to fabric-model-loading-api-v1/build.gradle index 58fefe77b6..7831d25ef6 100644 --- a/fabric-models-v0/build.gradle +++ b/fabric-model-loading-api-v1/build.gradle @@ -1,9 +1,10 @@ -archivesBaseName = "fabric-models-v0" +archivesBaseName = "fabric-model-loading-api-v1" version = getSubprojectVersion(project) moduleDependencies(project, ['fabric-api-base']) testDependencies(project, [ + ':fabric-renderer-api-v1', ':fabric-rendering-v1', ':fabric-resource-loader-v0' ]) diff --git a/fabric-model-loading-api-v1/src/client/java/net/fabricmc/fabric/api/client/model/loading/v1/BlockStateResolver.java b/fabric-model-loading-api-v1/src/client/java/net/fabricmc/fabric/api/client/model/loading/v1/BlockStateResolver.java new file mode 100644 index 0000000000..dacd2f1a4f --- /dev/null +++ b/fabric-model-loading-api-v1/src/client/java/net/fabricmc/fabric/api/client/model/loading/v1/BlockStateResolver.java @@ -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 org.jetbrains.annotations.ApiStatus; + +import net.minecraft.block.Block; +import net.minecraft.block.BlockState; +import net.minecraft.client.render.model.ModelLoader; +import net.minecraft.client.render.model.UnbakedModel; +import net.minecraft.client.util.ModelIdentifier; +import net.minecraft.util.Identifier; + +/** + * Block state resolvers are responsible for mapping each {@link BlockState} of a block to an {@link UnbakedModel}. + * 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 can be used to create custom block state formats or dynamically resolve block state models. + * + *

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. + * + * @see ModelResolver + * @see ModelModifier.OnLoad + */ +@FunctionalInterface +public interface BlockStateResolver { + /** + * Resolves the models for all block states of the block. + * + *

For each block state, call {@link Context#setModel} to set its unbaked model. + * This method must be called exactly once for each block state. + * + *

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. + */ + void resolveBlockStates(Context context); + + /** + * The context for block state resolution. + */ + @ApiStatus.NonExtendable + interface Context { + /** + * The block for which block state models are being resolved. + */ + Block block(); + + /** + * Sets the model for a block state. + * + * @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); + + /** + * Loads a model using an {@link Identifier} or {@link ModelIdentifier}, or gets it if it was already loaded. + * + * @param id the model identifier + * @return the unbaked model, or a missing model if it is not present + */ + UnbakedModel getOrLoadModel(Identifier id); + + /** + * The current model loader instance, which changes between resource reloads. + * + *

Do not call {@link ModelLoader#getOrLoadModel} as it does not supported nested model resolution; + * use {@link #getOrLoadModel} from the context instead. + */ + ModelLoader loader(); + } +} diff --git a/fabric-model-loading-api-v1/src/client/java/net/fabricmc/fabric/api/client/model/loading/v1/DelegatingUnbakedModel.java b/fabric-model-loading-api-v1/src/client/java/net/fabricmc/fabric/api/client/model/loading/v1/DelegatingUnbakedModel.java new file mode 100644 index 0000000000..ee37263fd2 --- /dev/null +++ b/fabric-model-loading-api-v1/src/client/java/net/fabricmc/fabric/api/client/model/loading/v1/DelegatingUnbakedModel.java @@ -0,0 +1,67 @@ +/* + * 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.Collection; +import java.util.List; +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.ModelIdentifier; +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; + private final List dependencies; + + /** + * Constructs a new delegating model. + * + * @param delegate The identifier (can be a {@link ModelIdentifier}) of the underlying baked model. + */ + public DelegatingUnbakedModel(Identifier delegate) { + this.delegate = delegate; + this.dependencies = List.of(delegate); + } + + @Override + public Collection getModelDependencies() { + return dependencies; + } + + @Override + public void setParents(Function modelLoader) { + } + + @Nullable + @Override + public BakedModel bake(Baker baker, Function textureGetter, ModelBakeSettings rotationContainer, Identifier modelId) { + return baker.bake(delegate, rotationContainer); + } +} diff --git a/fabric-model-loading-api-v1/src/client/java/net/fabricmc/fabric/api/client/model/loading/v1/FabricBakedModelManager.java b/fabric-model-loading-api-v1/src/client/java/net/fabricmc/fabric/api/client/model/loading/v1/FabricBakedModelManager.java new file mode 100644 index 0000000000..dbc5e0c4c7 --- /dev/null +++ b/fabric-model-loading-api-v1/src/client/java/net/fabricmc/fabric/api/client/model/loading/v1/FabricBakedModelManager.java @@ -0,0 +1,50 @@ +/* + * 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.BakedModelManager; +import net.minecraft.client.util.ModelIdentifier; +import net.minecraft.util.Identifier; + +/** + * Fabric-provided helper methods for {@link BakedModelManager}. + * + *

Note: This interface is automatically implemented on the {@link BakedModelManager} via Mixin and interface injection. + */ +public interface FabricBakedModelManager { + /** + * An alternative to {@link BakedModelManager#getModel(ModelIdentifier)} that accepts an + * {@link Identifier} instead. Models loaded using {@link ModelLoadingPlugin.Context#addModels} + * do not have a corresponding {@link ModelIdentifier}, so the vanilla method cannot be used to + * retrieve them. The {@link Identifier} that was used to load them can be used in this method + * to retrieve them. + * + *

This method, as well as its vanilla counterpart, should only be used after the + * {@link BakedModelManager} has completed reloading. Otherwise, the result will be + * outdated or null. + * + * @param id the id of the model + * @return the model + */ + @Nullable + default BakedModel getModel(Identifier id) { + throw new UnsupportedOperationException("Implemented via mixin."); + } +} diff --git a/fabric-model-loading-api-v1/src/client/java/net/fabricmc/fabric/api/client/model/loading/v1/ModelLoadingPlugin.java b/fabric-model-loading-api-v1/src/client/java/net/fabricmc/fabric/api/client/model/loading/v1/ModelLoadingPlugin.java new file mode 100644 index 0000000000..002deb2fae --- /dev/null +++ b/fabric-model-loading-api-v1/src/client/java/net/fabricmc/fabric/api/client/model/loading/v1/ModelLoadingPlugin.java @@ -0,0 +1,98 @@ +/* + * 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.Collection; + +import org.jetbrains.annotations.ApiStatus; + +import net.minecraft.block.Block; +import net.minecraft.client.render.model.json.JsonUnbakedModel; +import net.minecraft.client.util.ModelIdentifier; +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; + +/** + * A model loading plugin is used to extend the model loading process through the passed {@link Context} object. + * + *

{@link PreparableModelLoadingPlugin} can be used if some resources need to be loaded from the + * {@link ResourceManager}. + */ +@FunctionalInterface +public interface ModelLoadingPlugin { + /** + * Registers a model loading plugin. + */ + static void register(ModelLoadingPlugin plugin) { + ModelLoadingPluginManager.registerPlugin(plugin); + } + + /** + * Called towards the beginning of the model loading process, every time resource are (re)loaded. + * Use the context object to extend model loading as desired. + */ + void onInitializeModelLoader(Context pluginContext); + + @ApiStatus.NonExtendable + interface Context { + /** + * Adds one or more models (can be {@link ModelIdentifier}s) to the list of models that will be loaded and + * baked. + */ + void addModels(Identifier... ids); + + /** + * Adds multiple models (can be {@link ModelIdentifier}s) to the list of models that will be loaded and baked. + */ + void addModels(Collection ids); + + /** + * Registers a block state resolver for a block. + * + *

The block must be registered and a block state resolver must not have been previously registered for the + * block. + */ + void registerBlockStateResolver(Block block, BlockStateResolver resolver); + + /** + * Event access to register model resolvers. + */ + Event resolveModel(); + + /** + * Event access to monitor unbaked model loads and replace the loaded model. + */ + Event modifyModelOnLoad(); + + /** + * Event access to replace the unbaked model used for baking without replacing the cached model. + * + *

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 modifyModelBeforeBake(); + + /** + * Event access to monitor baked model loads and replace the loaded model. + */ + Event modifyModelAfterBake(); + } +} diff --git a/fabric-model-loading-api-v1/src/client/java/net/fabricmc/fabric/api/client/model/loading/v1/ModelModifier.java b/fabric-model-loading-api-v1/src/client/java/net/fabricmc/fabric/api/client/model/loading/v1/ModelModifier.java new file mode 100644 index 0000000000..8a6fb0894e --- /dev/null +++ b/fabric-model-loading-api-v1/src/client/java/net/fabricmc/fabric/api/client/model/loading/v1/ModelModifier.java @@ -0,0 +1,220 @@ +/* + * 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.ApiStatus; + +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.ModelLoader; +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; + +/** + * Contains interfaces for the events that can be used to modify models at different points in the loading and baking + * process. + * + *

Example use cases: + *

+ * + *

Phases are used to ensure that modifications occur in a reasonable order, e.g. wrapping occurs after overrides, + * 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. + * + *

These callbacks are invoked for every single model that is loaded or baked, so implementations should be + * as efficient as possible. + */ +public final class ModelModifier { + /** + * Recommended phase to use when overriding models, e.g. replacing a model with another model. + */ + public static final Identifier OVERRIDE_PHASE = new Identifier("fabric", "override"); + /** + * Recommended phase to use for transformations that need to happen before wrapping, but after model overrides. + */ + public static final Identifier DEFAULT_PHASE = Event.DEFAULT_PHASE; + /** + * Recommended phase to use when wrapping models. + */ + public static final Identifier WRAP_PHASE = new Identifier("fabric", "wrap"); + /** + * Recommended phase to use when wrapping models with transformations that want to happen last, + * e.g. for connected textures or other similar visual effects that should be the final processing step. + */ + public static final Identifier WRAP_LAST_PHASE = new Identifier("fabric", "wrap_last"); + + @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. + * + * @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); + + /** + * The context for an on load model modification event. + */ + @ApiStatus.NonExtendable + interface Context { + /** + * The identifier of this model (may be a {@link ModelIdentifier}). + * + *

For item models, only the {@link ModelIdentifier} with the {@code inventory} variant is passed, and + * not the corresponding plain identifier. + */ + Identifier id(); + + /** + * Loads a model using an {@link Identifier} or {@link ModelIdentifier}, or gets it if it was already + * loaded. + * + * @param id the model identifier + * @return the unbaked model, or a missing model if it is not present + */ + UnbakedModel getOrLoadModel(Identifier id); + + /** + * The current model loader instance, which changes between resource reloads. + * + *

Do not call {@link ModelLoader#getOrLoadModel} as it does not supported nested model + * resolution; use {@link #getOrLoadModel} from the context instead. + */ + ModelLoader loader(); + } + } + + @FunctionalInterface + public interface BeforeBake { + /** + * This handler is invoked to allow modification of the unbaked 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#modifyModelBeforeBake + */ + UnbakedModel modifyModelBeforeBake(UnbakedModel model, Context context); + + /** + * The context for a before bake model modification event. + */ + @ApiStatus.NonExtendable + interface Context { + /** + * The identifier of this model (may be a {@link ModelIdentifier}). + */ + Identifier id(); + + /** + * The function that can be used to retrieve sprites. + */ + Function 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#getOrLoadModel load unbaked models} and + * {@linkplain Baker#bake load baked models}. + */ + Baker baker(); + + /** + * The current model loader instance, which changes between resource reloads. + */ + ModelLoader loader(); + } + } + + @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. + * + *

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 + */ + BakedModel modifyModelAfterBake(BakedModel model, Context context); + + /** + * The context for an after bake model modification event. + */ + @ApiStatus.NonExtendable + interface Context { + /** + * The identifier of this model (may be a {@link ModelIdentifier}). + */ + Identifier id(); + + /** + * The unbaked model that is being baked. + */ + UnbakedModel sourceModel(); + + /** + * The function that can be used to retrieve sprites. + */ + Function 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#getOrLoadModel load unbaked models} and + * {@linkplain Baker#bake load baked models}. + */ + Baker baker(); + + /** + * The current model loader instance, which changes between resource reloads. + */ + ModelLoader loader(); + } + } + + private ModelModifier() { } +} diff --git a/fabric-model-loading-api-v1/src/client/java/net/fabricmc/fabric/api/client/model/loading/v1/ModelResolver.java b/fabric-model-loading-api-v1/src/client/java/net/fabricmc/fabric/api/client/model/loading/v1/ModelResolver.java new file mode 100644 index 0000000000..8236af0319 --- /dev/null +++ b/fabric-model-loading-api-v1/src/client/java/net/fabricmc/fabric/api/client/model/loading/v1/ModelResolver.java @@ -0,0 +1,82 @@ +/* + * 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.ModelLoader; +import net.minecraft.client.render.model.UnbakedModel; +import net.minecraft.client.util.ModelIdentifier; +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. + * + *

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

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

Model resolvers are invoked for every single model that will be loaded, + * 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(); + + /** + * Loads a model using an {@link Identifier} or {@link ModelIdentifier}, or gets it if it was already loaded. + * + * @param id the model identifier + * @return the unbaked model, or a missing model if it is not present + */ + UnbakedModel getOrLoadModel(Identifier id); + + /** + * The current model loader instance, which changes between resource reloads. + * + *

Do not call {@link ModelLoader#getOrLoadModel} as it does not supported nested model resolution; + * use {@link #getOrLoadModel} from the context instead. + */ + ModelLoader loader(); + } +} diff --git a/fabric-model-loading-api-v1/src/client/java/net/fabricmc/fabric/api/client/model/loading/v1/PreparableModelLoadingPlugin.java b/fabric-model-loading-api-v1/src/client/java/net/fabricmc/fabric/api/client/model/loading/v1/PreparableModelLoadingPlugin.java new file mode 100644 index 0000000000..b900cffc51 --- /dev/null +++ b/fabric-model-loading-api-v1/src/client/java/net/fabricmc/fabric/api/client/model/loading/v1/PreparableModelLoadingPlugin.java @@ -0,0 +1,66 @@ +/* + * 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.concurrent.CompletableFuture; +import java.util.concurrent.Executor; +import java.util.function.Supplier; + +import net.minecraft.resource.ResourceManager; + +import net.fabricmc.fabric.impl.client.model.loading.ModelLoadingPluginManager; + +/** + * A model loading plugin is used to extend the model loading process through the passed + * {@link ModelLoadingPlugin.Context} object. + * + *

This version of {@link ModelLoadingPlugin} allows loading ("preparing") some data off-thread in parallel before + * the model loading process starts. Usually, this means loading some resources from the provided + * {@link ResourceManager}. + */ +@FunctionalInterface +public interface PreparableModelLoadingPlugin { + /** + * Registers a preparable model loading plugin. + */ + static void register(DataLoader loader, PreparableModelLoadingPlugin plugin) { + ModelLoadingPluginManager.registerPlugin(loader, plugin); + } + + /** + * Called towards the beginning of the model loading process, every time resource are (re)loaded. + * Use the context object to extend model loading as desired. + * + * @param data The data loaded by the {@link DataLoader}. + * @param pluginContext The context that can be used to extend model loading. + */ + void onInitializeModelLoader(T data, ModelLoadingPlugin.Context pluginContext); + + @FunctionalInterface + interface DataLoader { + /** + * Returns a {@link CompletableFuture} that will load the data. + * Do not block the thread when this function is called, rather use + * {@link CompletableFuture#supplyAsync(Supplier, Executor)} to compute the data. + * The completable future should be scheduled to run using the passed executor. + * + * @param resourceManager The resource manager that can be used to retrieve resources. + * @param executor The executor that must be used to schedule any completable future. + */ + CompletableFuture load(ResourceManager resourceManager, Executor executor); + } +} diff --git a/fabric-models-v0/src/client/java/net/fabricmc/fabric/impl/client/model/ModelLoaderHooks.java b/fabric-model-loading-api-v1/src/client/java/net/fabricmc/fabric/impl/client/model/loading/BlockStateResolverHolder.java similarity index 71% rename from fabric-models-v0/src/client/java/net/fabricmc/fabric/impl/client/model/ModelLoaderHooks.java rename to fabric-model-loading-api-v1/src/client/java/net/fabricmc/fabric/impl/client/model/loading/BlockStateResolverHolder.java index e93a75b232..3b70bc224c 100644 --- a/fabric-models-v0/src/client/java/net/fabricmc/fabric/impl/client/model/ModelLoaderHooks.java +++ b/fabric-model-loading-api-v1/src/client/java/net/fabricmc/fabric/impl/client/model/loading/BlockStateResolverHolder.java @@ -14,13 +14,12 @@ * limitations under the License. */ -package net.fabricmc.fabric.impl.client.model; +package net.fabricmc.fabric.impl.client.model.loading; -import net.minecraft.client.render.model.UnbakedModel; +import net.minecraft.block.Block; import net.minecraft.util.Identifier; -public interface ModelLoaderHooks { - void fabric_addModel(Identifier id); +import net.fabricmc.fabric.api.client.model.loading.v1.BlockStateResolver; - UnbakedModel fabric_loadModel(Identifier id); +record BlockStateResolverHolder(BlockStateResolver resolver, Block block, Identifier blockId) { } diff --git a/fabric-models-v0/src/client/java/net/fabricmc/fabric/impl/client/model/BakedModelManagerHooks.java b/fabric-model-loading-api-v1/src/client/java/net/fabricmc/fabric/impl/client/model/loading/LegacyModelVariantProvider.java similarity index 61% rename from fabric-models-v0/src/client/java/net/fabricmc/fabric/impl/client/model/BakedModelManagerHooks.java rename to fabric-model-loading-api-v1/src/client/java/net/fabricmc/fabric/impl/client/model/loading/LegacyModelVariantProvider.java index b4748ab6f2..ecc5ddc0ba 100644 --- a/fabric-models-v0/src/client/java/net/fabricmc/fabric/impl/client/model/BakedModelManagerHooks.java +++ b/fabric-model-loading-api-v1/src/client/java/net/fabricmc/fabric/impl/client/model/loading/LegacyModelVariantProvider.java @@ -14,11 +14,17 @@ * limitations under the License. */ -package net.fabricmc.fabric.impl.client.model; +package net.fabricmc.fabric.impl.client.model.loading; -import net.minecraft.client.render.model.BakedModel; -import net.minecraft.util.Identifier; +import org.jetbrains.annotations.Nullable; -public interface BakedModelManagerHooks { - BakedModel fabric_getModel(Identifier id); +import net.minecraft.client.render.model.UnbakedModel; +import net.minecraft.client.util.ModelIdentifier; + +/** + * Legacy v0 bridge - remove if the legacy v0 module is removed. + */ +public interface LegacyModelVariantProvider { + @Nullable + UnbakedModel loadModelVariant(ModelIdentifier modelId); } diff --git a/fabric-models-v0/src/client/java/net/fabricmc/fabric/mixin/client/model/BakedModelManagerMixin.java b/fabric-model-loading-api-v1/src/client/java/net/fabricmc/fabric/impl/client/model/loading/ModelLoaderHooks.java similarity index 52% rename from fabric-models-v0/src/client/java/net/fabricmc/fabric/mixin/client/model/BakedModelManagerMixin.java rename to fabric-model-loading-api-v1/src/client/java/net/fabricmc/fabric/impl/client/model/loading/ModelLoaderHooks.java index 8117abbfe7..67e1dfd264 100644 --- a/fabric-models-v0/src/client/java/net/fabricmc/fabric/mixin/client/model/BakedModelManagerMixin.java +++ b/fabric-model-loading-api-v1/src/client/java/net/fabricmc/fabric/impl/client/model/loading/ModelLoaderHooks.java @@ -14,26 +14,24 @@ * limitations under the License. */ -package net.fabricmc.fabric.mixin.client.model; +package net.fabricmc.fabric.impl.client.model.loading; -import java.util.Map; +import net.minecraft.client.render.model.UnbakedModel; +import net.minecraft.client.render.model.json.JsonUnbakedModel; +import net.minecraft.util.Identifier; -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.Shadow; +public interface ModelLoaderHooks { + ModelLoadingEventDispatcher fabric_getDispatcher(); -import net.minecraft.client.render.model.BakedModel; -import net.minecraft.client.render.model.BakedModelManager; -import net.minecraft.util.Identifier; + UnbakedModel fabric_getMissingModel(); + + UnbakedModel fabric_getOrLoadModel(Identifier id); + + void fabric_putModel(Identifier id, UnbakedModel model); -import net.fabricmc.fabric.impl.client.model.BakedModelManagerHooks; + void fabric_putModelDirectly(Identifier id, UnbakedModel model); -@Mixin(BakedModelManager.class) -public class BakedModelManagerMixin implements BakedModelManagerHooks { - @Shadow - private Map models; + void fabric_queueModelDependencies(UnbakedModel model); - @Override - public BakedModel fabric_getModel(Identifier id) { - return models.get(id); - } + JsonUnbakedModel fabric_loadModelFromJson(Identifier id); } diff --git a/fabric-model-loading-api-v1/src/client/java/net/fabricmc/fabric/impl/client/model/loading/ModelLoaderPluginContextImpl.java b/fabric-model-loading-api-v1/src/client/java/net/fabricmc/fabric/impl/client/model/loading/ModelLoaderPluginContextImpl.java new file mode 100644 index 0000000000..ba4ceec640 --- /dev/null +++ b/fabric-model-loading-api-v1/src/client/java/net/fabricmc/fabric/impl/client/model/loading/ModelLoaderPluginContextImpl.java @@ -0,0 +1,223 @@ +/* + * 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.Collection; +import java.util.HashMap; +import java.util.LinkedHashSet; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.Set; +import java.util.function.Function; + +import org.jetbrains.annotations.Nullable; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import net.minecraft.block.Block; +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; +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; + +public class ModelLoaderPluginContextImpl implements ModelLoadingPlugin.Context { + private static final Logger LOGGER = LoggerFactory.getLogger(ModelLoaderPluginContextImpl.class); + + final Set extraModels = new LinkedHashSet<>(); + + private final Map blockStateResolvers = new HashMap<>(); + private final BlockKey lookupKey = new BlockKey(); + + private final Event 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 onLoadModifiers = EventFactory.createWithPhases(ModelModifier.OnLoad.class, modifiers -> (model, context) -> { + for (ModelModifier.OnLoad modifier : modifiers) { + try { + model = modifier.modifyModelOnLoad(model, context); + } catch (Exception exception) { + LOGGER.error("Failed to modify unbaked model on load", exception); + } + } + + return model; + }, MODEL_MODIFIER_PHASES); + private final Event 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 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); + + /** + * This field is used by the v0 wrapper to avoid constantly wrapping the context in hot code. + */ + public final Function modelGetter; + + public ModelLoaderPluginContextImpl(Function modelGetter) { + this.modelGetter = modelGetter; + } + + @Override + public void addModels(Identifier... ids) { + for (Identifier id : ids) { + extraModels.add(id); + } + } + + @Override + public void addModels(Collection ids) { + extraModels.addAll(ids); + } + + @Override + public void registerBlockStateResolver(Block block, BlockStateResolver resolver) { + Objects.requireNonNull(block, "block cannot be null"); + Objects.requireNonNull(resolver, "resolver cannot be null"); + + Optional> optionalKey = Registries.BLOCK.getKey(block); + + if (optionalKey.isEmpty()) { + throw new IllegalArgumentException("Received unregistered block"); + } + + Identifier blockId = optionalKey.get().getValue(); + BlockKey key = new BlockKey(blockId.getNamespace(), blockId.getPath()); + BlockStateResolverHolder holder = new BlockStateResolverHolder(resolver, block, blockId); + + if (blockStateResolvers.put(key, holder) != null) { + throw new IllegalArgumentException("Duplicate block state resolver for block " + blockId); + } + } + + @Nullable + BlockStateResolverHolder getBlockStateResolver(ModelIdentifier modelId) { + BlockKey key = lookupKey; + key.namespace = modelId.getNamespace(); + key.path = modelId.getPath(); + + return blockStateResolvers.get(key); + } + + @Override + public Event resolveModel() { + return modelResolvers; + } + + @Override + public Event modifyModelOnLoad() { + return onLoadModifiers; + } + + @Override + public Event modifyModelBeforeBake() { + return beforeBakeModifiers; + } + + @Override + public Event modifyModelAfterBake() { + return afterBakeModifiers; + } + + private static class BlockKey { + private String namespace; + private String path; + + private BlockKey() { + } + + private BlockKey(String namespace, String path) { + this.namespace = namespace; + this.path = path; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + BlockKey blockKey = (BlockKey) o; + return namespace.equals(blockKey.namespace) && path.equals(blockKey.path); + } + + @Override + public int hashCode() { + return 31 * namespace.hashCode() + path.hashCode(); + } + } + + // Legacy v0 bridge - remove if the legacy v0 module is removed. + + private final Event legacyVariantProviders = EventFactory.createArrayBacked(LegacyModelVariantProvider.class, providers -> modelId -> { + for (LegacyModelVariantProvider provider : providers) { + try { + UnbakedModel model = provider.loadModelVariant(modelId); + + if (model != null) { + return model; + } + } catch (Exception exception) { + LOGGER.error("Failed to run legacy model variant provider", exception); + } + } + + return null; + }); + + public Event legacyVariantProviders() { + return legacyVariantProviders; + } +} diff --git a/fabric-model-loading-api-v1/src/client/java/net/fabricmc/fabric/impl/client/model/loading/ModelLoadingEventDispatcher.java b/fabric-model-loading-api-v1/src/client/java/net/fabricmc/fabric/impl/client/model/loading/ModelLoadingEventDispatcher.java new file mode 100644 index 0000000000..fba9dc19ac --- /dev/null +++ b/fabric-model-loading-api-v1/src/client/java/net/fabricmc/fabric/impl/client/model/loading/ModelLoadingEventDispatcher.java @@ -0,0 +1,465 @@ +/* + * 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.List; +import java.util.Objects; +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 it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet; +import it.unimi.dsi.fastutil.objects.ReferenceSet; +import org.jetbrains.annotations.Nullable; +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.ModelBakeSettings; +import net.minecraft.client.render.model.ModelLoader; +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.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); + + private final ModelLoader loader; + private final ModelLoaderPluginContextImpl pluginContext; + + private final ObjectArrayList modelResolverContextStack = new ObjectArrayList<>(); + + private final ObjectArrayList blockStateResolverContextStack = new ObjectArrayList<>(); + private final ReferenceSet resolvingBlocks = new ReferenceOpenHashSet<>(); + + private final ObjectArrayList onLoadModifierContextStack = new ObjectArrayList<>(); + private final ObjectArrayList beforeBakeModifierContextStack = new ObjectArrayList<>(); + private final ObjectArrayList afterBakeModifierContextStack = new ObjectArrayList<>(); + + public ModelLoadingEventDispatcher(ModelLoader loader, List plugins) { + this.loader = loader; + this.pluginContext = new ModelLoaderPluginContextImpl(((ModelLoaderHooks) loader)::fabric_getOrLoadModel); + + for (ModelLoadingPlugin plugin : plugins) { + try { + plugin.onInitializeModelLoader(pluginContext); + } catch (Exception exception) { + LOGGER.error("Failed to initialize model loading plugin", exception); + } + } + } + + public void addExtraModels(Consumer extraModelConsumer) { + for (Identifier id : pluginContext.extraModels) { + extraModelConsumer.accept(id); + } + } + + /** + * @return {@code true} to cancel the vanilla method + */ + public boolean loadModel(Identifier id) { + if (id instanceof ModelIdentifier modelId) { + if ("inventory".equals(modelId.getVariant())) { + // We ALWAYS override the vanilla inventory model code path entirely, even for vanilla item models. + // See loadItemModel for an explanation. + loadItemModel(modelId); + return true; + } else { + // Prioritize block state resolver over legacy variant provider + BlockStateResolverHolder resolver = pluginContext.getBlockStateResolver(modelId); + + if (resolver != null) { + loadBlockStateModels(resolver.resolver(), resolver.block(), resolver.blockId()); + return true; + } + + UnbakedModel legacyModel = legacyLoadModelVariant(modelId); + + if (legacyModel != null) { + ((ModelLoaderHooks) loader).fabric_putModel(id, legacyModel); + return true; + } + + return false; + } + } else { + UnbakedModel model = resolveModel(id); + + if (model != null) { + ((ModelLoaderHooks) loader).fabric_putModel(id, model); + return true; + } + + return false; + } + } + + @Nullable + private UnbakedModel legacyLoadModelVariant(ModelIdentifier modelId) { + return pluginContext.legacyVariantProviders().invoker().loadModelVariant(modelId); + } + + /** + * This function handles both modded item models and vanilla item models. + * The vanilla code path for item models is never used. + * See the long comment in the function for an explanation. + */ + private void loadItemModel(ModelIdentifier modelId) { + ModelLoaderHooks loaderHooks = (ModelLoaderHooks) loader; + + Identifier id = modelId.withPrefixedPath("item/"); + + // Legacy variant provider + UnbakedModel model = legacyLoadModelVariant(modelId); + + // Model resolver + if (model == null) { + model = resolveModel(id); + } + + // Load from the vanilla code path otherwise. + if (model == null) { + model = loaderHooks.fabric_loadModelFromJson(id); + } + + // This is a bit tricky: + // We have a single UnbakedModel now, but there are two identifiers: + // the ModelIdentifier (...#inventory) and the Identifier (...:item/...). + // So we call the on load modifier now and then directly add the model to the ModelLoader, + // reimplementing the behavior of ModelLoader#put. + // Calling ModelLoader#put is not an option as the model for the Identifier would not be replaced by an on load modifier. + // This is why we override the vanilla code path entirely. + model = modifyModelOnLoad(modelId, model); + + loaderHooks.fabric_putModelDirectly(modelId, model); + loaderHooks.fabric_putModelDirectly(id, model); + loaderHooks.fabric_queueModelDependencies(model); + } + + private void loadBlockStateModels(BlockStateResolver resolver, Block block, Identifier blockId) { + if (!resolvingBlocks.add(block)) { + throw new IllegalStateException("Circular reference while resolving models for block " + block); + } + + try { + resolveBlockStates(resolver, block, blockId); + } finally { + resolvingBlocks.remove(block); + } + } + + private void resolveBlockStates(BlockStateResolver resolver, Block block, Identifier blockId) { + // Get and prepare context + if (blockStateResolverContextStack.isEmpty()) { + blockStateResolverContextStack.add(new BlockStateResolverContext()); + } + + BlockStateResolverContext context = blockStateResolverContextStack.pop(); + context.prepare(block); + + Reference2ReferenceMap resolvedModels = context.models; + ImmutableList allStates = block.getStateManager().getStates(); + boolean thrown = false; + + // Call resolver + try { + resolver.resolveBlockStates(context); + } catch (Exception e) { + LOGGER.error("Failed to resolve block state models for block {}. Using missing model for all states.", block, e); + thrown = true; + } + + // Copy models over to the loader + if (thrown) { + UnbakedModel missingModel = ((ModelLoaderHooks) loader).fabric_getMissingModel(); + + for (BlockState state : allStates) { + ModelIdentifier modelId = BlockModels.getModelId(blockId, state); + ((ModelLoaderHooks) loader).fabric_putModelDirectly(modelId, missingModel); + } + } else if (resolvedModels.size() == allStates.size()) { + // If there are as many resolved models as total states, all states have + // been resolved and models do not need to be null-checked. + resolvedModels.forEach((state, model) -> { + ModelIdentifier modelId = BlockModels.getModelId(blockId, state); + ((ModelLoaderHooks) loader).fabric_putModel(modelId, model); + }); + } else { + UnbakedModel missingModel = ((ModelLoaderHooks) loader).fabric_getMissingModel(); + + for (BlockState state : allStates) { + ModelIdentifier modelId = BlockModels.getModelId(blockId, state); + @Nullable + UnbakedModel 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); + ((ModelLoaderHooks) loader).fabric_putModelDirectly(modelId, missingModel); + } else { + ((ModelLoaderHooks) loader).fabric_putModel(modelId, model); + } + } + } + + resolvedModels.clear(); + + // Store context for reuse + blockStateResolverContextStack.add(context); + } + + @Nullable + private UnbakedModel resolveModel(Identifier id) { + if (modelResolverContextStack.isEmpty()) { + modelResolverContextStack.add(new ModelResolverContext()); + } + + ModelResolverContext context = modelResolverContextStack.pop(); + context.prepare(id); + + UnbakedModel model = pluginContext.resolveModel().invoker().resolveModel(context); + + modelResolverContextStack.push(context); + return model; + } + + public UnbakedModel modifyModelOnLoad(Identifier id, UnbakedModel model) { + if (onLoadModifierContextStack.isEmpty()) { + onLoadModifierContextStack.add(new OnLoadModifierContext()); + } + + OnLoadModifierContext context = onLoadModifierContextStack.pop(); + context.prepare(id); + + model = pluginContext.modifyModelOnLoad().invoker().modifyModelOnLoad(model, context); + + onLoadModifierContextStack.push(context); + return model; + } + + public UnbakedModel modifyModelBeforeBake(UnbakedModel model, Identifier id, Function textureGetter, ModelBakeSettings settings, Baker baker) { + if (beforeBakeModifierContextStack.isEmpty()) { + beforeBakeModifierContextStack.add(new BeforeBakeModifierContext()); + } + + BeforeBakeModifierContext context = beforeBakeModifierContextStack.pop(); + context.prepare(id, textureGetter, settings, baker); + + model = pluginContext.modifyModelBeforeBake().invoker().modifyModelBeforeBake(model, context); + + beforeBakeModifierContextStack.push(context); + return model; + } + + public BakedModel modifyModelAfterBake(BakedModel model, Identifier id, UnbakedModel sourceModel, Function textureGetter, ModelBakeSettings settings, Baker baker) { + if (afterBakeModifierContextStack.isEmpty()) { + afterBakeModifierContextStack.add(new AfterBakeModifierContext()); + } + + AfterBakeModifierContext context = afterBakeModifierContextStack.pop(); + context.prepare(id, sourceModel, textureGetter, settings, baker); + + model = pluginContext.modifyModelAfterBake().invoker().modifyModelAfterBake(model, context); + + afterBakeModifierContextStack.push(context); + return model; + } + + private class ModelResolverContext implements ModelResolver.Context { + private Identifier id; + + private void prepare(Identifier id) { + this.id = id; + } + + @Override + public Identifier id() { + return id; + } + + @Override + public UnbakedModel getOrLoadModel(Identifier id) { + return ((ModelLoaderHooks) loader).fabric_getOrLoadModel(id); + } + + @Override + public ModelLoader loader() { + return loader; + } + } + + private class BlockStateResolverContext implements BlockStateResolver.Context { + private Block block; + private final Reference2ReferenceMap models = new Reference2ReferenceOpenHashMap<>(); + + private void prepare(Block block) { + this.block = block; + models.clear(); + } + + @Override + public Block block() { + return block; + } + + @Override + public void setModel(BlockState state, UnbakedModel model) { + Objects.requireNonNull(model, "state cannot be null"); + Objects.requireNonNull(model, "model cannot be null"); + + if (!state.isOf(block)) { + throw new IllegalArgumentException("Attempted to set model for state " + state + " on block " + block); + } + + if (models.putIfAbsent(state, model) != null) { + throw new IllegalStateException("Duplicate model for state " + state + " on block " + block); + } + } + + @Override + public UnbakedModel getOrLoadModel(Identifier id) { + return ((ModelLoaderHooks) loader).fabric_getOrLoadModel(id); + } + + @Override + public ModelLoader loader() { + return loader; + } + } + + private class OnLoadModifierContext implements ModelModifier.OnLoad.Context { + private Identifier id; + + private void prepare(Identifier id) { + this.id = id; + } + + @Override + public Identifier id() { + return id; + } + + @Override + public UnbakedModel getOrLoadModel(Identifier id) { + return ((ModelLoaderHooks) loader).fabric_getOrLoadModel(id); + } + + @Override + public ModelLoader loader() { + return loader; + } + } + + private class BeforeBakeModifierContext implements ModelModifier.BeforeBake.Context { + private Identifier id; + private Function textureGetter; + private ModelBakeSettings settings; + private Baker baker; + + private void prepare(Identifier id, Function textureGetter, ModelBakeSettings settings, Baker baker) { + this.id = id; + this.textureGetter = textureGetter; + this.settings = settings; + this.baker = baker; + } + + @Override + public Identifier id() { + return id; + } + + @Override + public Function textureGetter() { + return textureGetter; + } + + @Override + public ModelBakeSettings settings() { + return settings; + } + + @Override + public Baker baker() { + return baker; + } + + @Override + public ModelLoader loader() { + return loader; + } + } + + private class AfterBakeModifierContext implements ModelModifier.AfterBake.Context { + private Identifier id; + private UnbakedModel sourceModel; + private Function textureGetter; + private ModelBakeSettings settings; + private Baker baker; + + private void prepare(Identifier id, UnbakedModel sourceModel, Function textureGetter, ModelBakeSettings settings, Baker baker) { + this.id = id; + this.sourceModel = sourceModel; + this.textureGetter = textureGetter; + this.settings = settings; + this.baker = baker; + } + + @Override + public Identifier id() { + return id; + } + + @Override + public UnbakedModel sourceModel() { + return sourceModel; + } + + @Override + public Function textureGetter() { + return textureGetter; + } + + @Override + public ModelBakeSettings settings() { + return settings; + } + + @Override + public Baker baker() { + return baker; + } + + @Override + public ModelLoader loader() { + return loader; + } + } +} diff --git a/fabric-model-loading-api-v1/src/client/java/net/fabricmc/fabric/impl/client/model/loading/ModelLoadingPluginManager.java b/fabric-model-loading-api-v1/src/client/java/net/fabricmc/fabric/impl/client/model/loading/ModelLoadingPluginManager.java new file mode 100644 index 0000000000..8ccbee0264 --- /dev/null +++ b/fabric-model-loading-api-v1/src/client/java/net/fabricmc/fabric/impl/client/model/loading/ModelLoadingPluginManager.java @@ -0,0 +1,77 @@ +/* + * 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.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.Executor; + +import net.minecraft.resource.ResourceManager; +import net.minecraft.util.Util; + +import net.fabricmc.fabric.api.client.model.loading.v1.ModelLoadingPlugin; +import net.fabricmc.fabric.api.client.model.loading.v1.PreparableModelLoadingPlugin; + +public final class ModelLoadingPluginManager { + private static final List PLUGINS = new ArrayList<>(); + private static final List> PREPARABLE_PLUGINS = new ArrayList<>(); + + public static final ThreadLocal> CURRENT_PLUGINS = new ThreadLocal<>(); + + public static void registerPlugin(ModelLoadingPlugin plugin) { + Objects.requireNonNull(plugin, "plugin must not be null"); + + PLUGINS.add(plugin); + } + + public static void registerPlugin(PreparableModelLoadingPlugin.DataLoader loader, PreparableModelLoadingPlugin plugin) { + Objects.requireNonNull(loader, "data loader must not be null"); + Objects.requireNonNull(plugin, "plugin must not be null"); + + PREPARABLE_PLUGINS.add(new PreparablePluginHolder<>(loader, plugin)); + } + + /** + * The current exception behavior as of 1.20 is as follows. + * If getting a {@link CompletableFuture}s throws then the whole client will crash. + * If a {@link CompletableFuture} completes exceptionally then the resource reload will fail. + */ + public static CompletableFuture> preparePlugins(ResourceManager resourceManager, Executor executor) { + List> futures = new ArrayList<>(); + + for (ModelLoadingPlugin plugin : PLUGINS) { + futures.add(CompletableFuture.completedFuture(plugin)); + } + + for (PreparablePluginHolder holder : PREPARABLE_PLUGINS) { + futures.add(preparePlugin(holder, resourceManager, executor)); + } + + return Util.combine(futures); + } + + private static CompletableFuture preparePlugin(PreparablePluginHolder holder, ResourceManager resourceManager, Executor executor) { + CompletableFuture dataFuture = holder.loader.load(resourceManager, executor); + return dataFuture.thenApply(data -> pluginContext -> holder.plugin.onInitializeModelLoader(data, pluginContext)); + } + + private ModelLoadingPluginManager() { } + + private record PreparablePluginHolder(PreparableModelLoadingPlugin.DataLoader loader, PreparableModelLoadingPlugin plugin) { } +} diff --git a/fabric-model-loading-api-v1/src/client/java/net/fabricmc/fabric/mixin/client/model/loading/BakedModelManagerMixin.java b/fabric-model-loading-api-v1/src/client/java/net/fabricmc/fabric/mixin/client/model/loading/BakedModelManagerMixin.java new file mode 100644 index 0000000000..d627eb951f --- /dev/null +++ b/fabric-model-loading-api-v1/src/client/java/net/fabricmc/fabric/mixin/client/model/loading/BakedModelManagerMixin.java @@ -0,0 +1,82 @@ +/* + * 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.List; +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 org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Redirect; + +import net.minecraft.client.render.model.BakedModel; +import net.minecraft.client.render.model.BakedModelManager; +import net.minecraft.client.render.model.ModelLoader; +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.minecraft.util.Pair; +import net.minecraft.util.profiler.Profiler; + +import net.fabricmc.fabric.api.client.model.loading.v1.FabricBakedModelManager; +import net.fabricmc.fabric.api.client.model.loading.v1.ModelLoadingPlugin; +import net.fabricmc.fabric.impl.client.model.loading.ModelLoadingPluginManager; + +@Mixin(BakedModelManager.class) +public class BakedModelManagerMixin implements FabricBakedModelManager { + @Shadow + private Map models; + + @Override + public BakedModel getModel(Identifier id) { + return models.get(id); + } + + @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;", + remap = false + ), + allow = 1) + private CompletableFuture loadModelPluginData( + CompletableFuture> self, + CompletionStage>> otherFuture, + BiFunction, Map>, ModelLoader> modelLoaderConstructor, + Executor executor, + // reload args + ResourceReloader.Synchronizer synchronizer, + ResourceManager manager, + Profiler prepareProfiler, + Profiler applyProfiler, + Executor prepareExecutor, + Executor applyExecutor) { + CompletableFuture> pluginsFuture = ModelLoadingPluginManager.preparePlugins(manager, prepareExecutor); + CompletableFuture, Map>>> pairFuture = self.thenCombine(otherFuture, Pair::new); + return pairFuture.thenCombineAsync(pluginsFuture, (pair, plugins) -> { + ModelLoadingPluginManager.CURRENT_PLUGINS.set(plugins); + return modelLoaderConstructor.apply(pair.getLeft(), pair.getRight()); + }, executor); + } +} diff --git a/fabric-model-loading-api-v1/src/client/java/net/fabricmc/fabric/mixin/client/model/loading/ModelLoaderBakerImplMixin.java b/fabric-model-loading-api-v1/src/client/java/net/fabricmc/fabric/mixin/client/model/loading/ModelLoaderBakerImplMixin.java new file mode 100644 index 0000000000..ef55ae9000 --- /dev/null +++ b/fabric-model-loading-api-v1/src/client/java/net/fabricmc/fabric/mixin/client/model/loading/ModelLoaderBakerImplMixin.java @@ -0,0 +1,69 @@ +/* + * 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 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.ModifyVariable; +import org.spongepowered.asm.mixin.injection.Redirect; + +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.ModelLoader; +import net.minecraft.client.render.model.UnbakedModel; +import net.minecraft.client.render.model.json.JsonUnbakedModel; +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.ModelLoaderHooks; +import net.fabricmc.fabric.impl.client.model.loading.ModelLoadingEventDispatcher; + +@Mixin(targets = "net/minecraft/client/render/model/ModelLoader$BakerImpl") +public class ModelLoaderBakerImplMixin { + @Shadow + @Final + private ModelLoader field_40571; + @Shadow + @Final + private Function textureGetter; + + @ModifyVariable(method = "bake", at = @At(value = "INVOKE_ASSIGN", target = "Lnet/minecraft/client/render/model/ModelLoader$BakerImpl;getOrLoadModel(Lnet/minecraft/util/Identifier;)Lnet/minecraft/client/render/model/UnbakedModel;")) + private UnbakedModel invokeModifyBeforeBake(UnbakedModel model, Identifier id, ModelBakeSettings settings) { + ModelLoadingEventDispatcher dispatcher = ((ModelLoaderHooks) this.field_40571).fabric_getDispatcher(); + return dispatcher.modifyModelBeforeBake(model, id, textureGetter, settings, (Baker) this); + } + + @Redirect(method = "bake", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/render/model/UnbakedModel;bake(Lnet/minecraft/client/render/model/Baker;Ljava/util/function/Function;Lnet/minecraft/client/render/model/ModelBakeSettings;Lnet/minecraft/util/Identifier;)Lnet/minecraft/client/render/model/BakedModel;")) + private BakedModel invokeModifyAfterBake(UnbakedModel unbakedModel, Baker baker, Function textureGetter, ModelBakeSettings settings, Identifier id) { + BakedModel model = unbakedModel.bake(baker, textureGetter, settings, id); + ModelLoadingEventDispatcher dispatcher = ((ModelLoaderHooks) this.field_40571).fabric_getDispatcher(); + return dispatcher.modifyModelAfterBake(model, id, unbakedModel, textureGetter, settings, baker); + } + + @Redirect(method = "bake", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/render/model/json/JsonUnbakedModel;bake(Lnet/minecraft/client/render/model/Baker;Lnet/minecraft/client/render/model/json/JsonUnbakedModel;Ljava/util/function/Function;Lnet/minecraft/client/render/model/ModelBakeSettings;Lnet/minecraft/util/Identifier;Z)Lnet/minecraft/client/render/model/BakedModel;")) + private BakedModel invokeModifyAfterBake(JsonUnbakedModel unbakedModel, Baker baker, JsonUnbakedModel parent, Function textureGetter, ModelBakeSettings settings, Identifier id, boolean hasDepth) { + BakedModel model = unbakedModel.bake(baker, parent, textureGetter, settings, id, hasDepth); + ModelLoadingEventDispatcher dispatcher = ((ModelLoaderHooks) this.field_40571).fabric_getDispatcher(); + return dispatcher.modifyModelAfterBake(model, id, unbakedModel, textureGetter, settings, baker); + } +} diff --git a/fabric-model-loading-api-v1/src/client/java/net/fabricmc/fabric/mixin/client/model/loading/ModelLoaderMixin.java b/fabric-model-loading-api-v1/src/client/java/net/fabricmc/fabric/mixin/client/model/loading/ModelLoaderMixin.java new file mode 100644 index 0000000000..33cf18a349 --- /dev/null +++ b/fabric-model-loading-api-v1/src/client/java/net/fabricmc/fabric/mixin/client/model/loading/ModelLoaderMixin.java @@ -0,0 +1,201 @@ +/* + * 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.List; +import java.util.Map; +import java.util.Set; + +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.ModifyVariable; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +import net.minecraft.client.color.block.BlockColors; +import net.minecraft.client.render.model.ModelLoader; +import net.minecraft.client.render.model.UnbakedModel; +import net.minecraft.client.render.model.json.JsonUnbakedModel; +import net.minecraft.client.util.ModelIdentifier; +import net.minecraft.util.Identifier; +import net.minecraft.util.profiler.Profiler; + +import net.fabricmc.fabric.impl.client.model.loading.ModelLoaderHooks; +import net.fabricmc.fabric.impl.client.model.loading.ModelLoadingEventDispatcher; +import net.fabricmc.fabric.impl.client.model.loading.ModelLoadingPluginManager; + +@Mixin(ModelLoader.class) +public abstract class ModelLoaderMixin implements ModelLoaderHooks { + // The missing model is always loaded and added first. + @Final + @Shadow + public static ModelIdentifier MISSING_ID; + @Final + @Shadow + private Set modelsToLoad; + @Final + @Shadow + private Map unbakedModels; + @Shadow + @Final + private Map modelsToBake; + + @Unique + private ModelLoadingEventDispatcher fabric_eventDispatcher; + // Explicitly not @Unique to allow mods that heavily rework model loading to reimplement the guard. + // Note that this is an implementation detail; it can change at any time. + private int fabric_guardGetOrLoadModel = 0; + private boolean fabric_enableGetOrLoadModelGuard = true; + + @Shadow + private void addModel(ModelIdentifier id) { + } + + @Shadow + public abstract UnbakedModel getOrLoadModel(Identifier id); + + @Shadow + private void loadModel(Identifier id) { + } + + @Shadow + private void putModel(Identifier id, UnbakedModel unbakedModel) { + } + + @Shadow + public abstract JsonUnbakedModel loadModelFromJson(Identifier id); + + @Inject(method = "", at = @At(value = "INVOKE", target = "net/minecraft/util/profiler/Profiler.swap(Ljava/lang/String;)V", ordinal = 0)) + private void afterMissingModelInit(BlockColors blockColors, Profiler profiler, Map jsonUnbakedModels, Map> blockStates, CallbackInfo info) { + // Sanity check + if (!unbakedModels.containsKey(MISSING_ID)) { + throw new AssertionError("Missing model not initialized. This is likely a Fabric API porting bug."); + } + + profiler.swap("fabric_plugins_init"); + + fabric_eventDispatcher = new ModelLoadingEventDispatcher((ModelLoader) (Object) this, ModelLoadingPluginManager.CURRENT_PLUGINS.get()); + ModelLoadingPluginManager.CURRENT_PLUGINS.remove(); + fabric_eventDispatcher.addExtraModels(this::addModel); + } + + @Unique + private void addModel(Identifier id) { + if (id instanceof ModelIdentifier) { + addModel((ModelIdentifier) id); + } else { + // The vanilla addModel method is arbitrarily limited to ModelIdentifiers, + // but it's useful to tell the game to just load and bake a direct model path as well. + // Replicate the vanilla logic of addModel here. + UnbakedModel unbakedModel = getOrLoadModel(id); + this.unbakedModels.put(id, unbakedModel); + this.modelsToBake.put(id, unbakedModel); + } + } + + @Inject(method = "getOrLoadModel", at = @At("HEAD")) + private void fabric_preventNestedGetOrLoadModel(Identifier id, CallbackInfoReturnable cir) { + if (fabric_enableGetOrLoadModelGuard && fabric_guardGetOrLoadModel > 0) { + throw new IllegalStateException("ModelLoader#getOrLoadModel called from a ModelResolver or ModelModifier.OnBake instance. This is not allowed to prevent errors during model loading. Use getOrLoadModel from the context instead."); + } + } + + @Inject(method = "loadModel", at = @At("HEAD"), cancellable = true) + private void onLoadModel(Identifier id, CallbackInfo ci) { + // Prevent calls to getOrLoadModel from loadModel as it will cause problems. + // Mods should call getOrLoadModel on the ModelResolver.Context instead. + fabric_guardGetOrLoadModel++; + + try { + if (fabric_eventDispatcher.loadModel(id)) { + ci.cancel(); + } + } finally { + fabric_guardGetOrLoadModel--; + } + } + + @ModifyVariable(method = "putModel", at = @At("HEAD"), argsOnly = true) + private UnbakedModel onPutModel(UnbakedModel model, Identifier id) { + fabric_guardGetOrLoadModel++; + + try { + return fabric_eventDispatcher.modifyModelOnLoad(id, model); + } finally { + fabric_guardGetOrLoadModel--; + } + } + + @Override + public ModelLoadingEventDispatcher fabric_getDispatcher() { + return fabric_eventDispatcher; + } + + @Override + public UnbakedModel fabric_getMissingModel() { + return unbakedModels.get(MISSING_ID); + } + + /** + * Unlike getOrLoadModel, this method supports nested model loading. + * + *

Vanilla does not due to the iteration over modelsToLoad which causes models to be resolved multiple times, + * possibly leading to crashes. + */ + @Override + public UnbakedModel fabric_getOrLoadModel(Identifier id) { + if (this.unbakedModels.containsKey(id)) { + return this.unbakedModels.get(id); + } + + if (!modelsToLoad.add(id)) { + throw new IllegalStateException("Circular reference while loading " + id); + } + + try { + loadModel(id); + } finally { + modelsToLoad.remove(id); + } + + return unbakedModels.get(id); + } + + @Override + public void fabric_putModel(Identifier id, UnbakedModel model) { + putModel(id, model); + } + + @Override + public void fabric_putModelDirectly(Identifier id, UnbakedModel model) { + unbakedModels.put(id, model); + } + + @Override + public void fabric_queueModelDependencies(UnbakedModel model) { + modelsToLoad.addAll(model.getModelDependencies()); + } + + @Override + public JsonUnbakedModel fabric_loadModelFromJson(Identifier id) { + return loadModelFromJson(id); + } +} diff --git a/fabric-model-loading-api-v1/src/client/resources/assets/fabric-model-loading-api-v1/icon.png b/fabric-model-loading-api-v1/src/client/resources/assets/fabric-model-loading-api-v1/icon.png new file mode 100644 index 0000000000..2931efbf61 Binary files /dev/null and b/fabric-model-loading-api-v1/src/client/resources/assets/fabric-model-loading-api-v1/icon.png differ diff --git a/fabric-model-loading-api-v1/src/client/resources/fabric-model-loading-api-v1.mixins.json b/fabric-model-loading-api-v1/src/client/resources/fabric-model-loading-api-v1.mixins.json new file mode 100644 index 0000000000..133a6b4428 --- /dev/null +++ b/fabric-model-loading-api-v1/src/client/resources/fabric-model-loading-api-v1.mixins.json @@ -0,0 +1,13 @@ +{ + "required": true, + "package": "net.fabricmc.fabric.mixin.client.model.loading", + "compatibilityLevel": "JAVA_17", + "client": [ + "BakedModelManagerMixin", + "ModelLoaderMixin", + "ModelLoaderBakerImplMixin" + ], + "injectors": { + "defaultRequire": 1 + } +} diff --git a/fabric-model-loading-api-v1/src/client/resources/fabric.mod.json b/fabric-model-loading-api-v1/src/client/resources/fabric.mod.json new file mode 100644 index 0000000000..abc4ffa0e1 --- /dev/null +++ b/fabric-model-loading-api-v1/src/client/resources/fabric.mod.json @@ -0,0 +1,35 @@ +{ + "schemaVersion": 1, + "id": "fabric-model-loading-api-v1", + "name": "Fabric Model Loading API (v1)", + "version": "${version}", + "environment": "client", + "license": "Apache-2.0", + "icon": "assets/fabric-model-loading-api-v1/icon.png", + "contact": { + "homepage": "https://fabricmc.net", + "irc": "irc://irc.esper.net:6667/fabric", + "issues": "https://github.com/FabricMC/fabric/issues", + "sources": "https://github.com/FabricMC/fabric" + }, + "authors": [ + "FabricMC" + ], + "depends": { + "fabricloader": ">=0.14.21", + "fabric-api-base": "*" + }, + "description": "Provides hooks for model loading.", + "mixins": [ + { + "environment": "client", + "config": "fabric-model-loading-api-v1.mixins.json" + } + ], + "custom": { + "fabric-api:module-lifecycle": "stable", + "loom:injected_interfaces": { + "net/minecraft/class_1092": [ "net/fabricmc/fabric/api/client/model/loading/v1/FabricBakedModelManager" ] + } + } +} diff --git a/fabric-models-v0/src/testmodClient/java/net/fabricmc/fabric/test/model/BakedModelFeatureRenderer.java b/fabric-model-loading-api-v1/src/testmodClient/java/net/fabricmc/fabric/test/model/loading/BakedModelFeatureRenderer.java similarity index 86% rename from fabric-models-v0/src/testmodClient/java/net/fabricmc/fabric/test/model/BakedModelFeatureRenderer.java rename to fabric-model-loading-api-v1/src/testmodClient/java/net/fabricmc/fabric/test/model/loading/BakedModelFeatureRenderer.java index 9577ce3cfa..056ac08fe0 100644 --- a/fabric-models-v0/src/testmodClient/java/net/fabricmc/fabric/test/model/BakedModelFeatureRenderer.java +++ b/fabric-model-loading-api-v1/src/testmodClient/java/net/fabricmc/fabric/test/model/loading/BakedModelFeatureRenderer.java @@ -14,13 +14,15 @@ * limitations under the License. */ -package net.fabricmc.fabric.test.model; +package net.fabricmc.fabric.test.model.loading; import java.util.function.Supplier; import org.joml.AxisAngle4f; import org.joml.Quaternionf; +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.render.OverlayTexture; import net.minecraft.client.render.TexturedRenderLayers; import net.minecraft.client.render.VertexConsumer; import net.minecraft.client.render.VertexConsumerProvider; @@ -32,7 +34,7 @@ import net.minecraft.entity.LivingEntity; public class BakedModelFeatureRenderer> extends FeatureRenderer { - private Supplier modelSupplier; + private final Supplier modelSupplier; public BakedModelFeatureRenderer(FeatureRendererContext context, Supplier modelSupplier) { super(context); @@ -50,7 +52,7 @@ public void render(MatrixStack matrices, VertexConsumerProvider vertexConsumers, matrices.scale(-0.75F, -0.75F, 0.75F); float aboveHead = (float) (Math.sin(animationProgress * 0.08F)) * 0.5F + 0.5F; matrices.translate(-0.5F, 0.75F + aboveHead, -0.5F); - BakedModelRenderer.renderBakedModel(model, vertices, matrices.peek(), light); + MinecraftClient.getInstance().getBlockRenderManager().getModelRenderer().render(matrices.peek(), vertices, null, model, 1, 1, 1, light, OverlayTexture.DEFAULT_UV); matrices.pop(); } } diff --git a/fabric-model-loading-api-v1/src/testmodClient/java/net/fabricmc/fabric/test/model/loading/ModelTestModClient.java b/fabric-model-loading-api-v1/src/testmodClient/java/net/fabricmc/fabric/test/model/loading/ModelTestModClient.java new file mode 100644 index 0000000000..2f0ba65ab9 --- /dev/null +++ b/fabric-model-loading-api-v1/src/testmodClient/java/net/fabricmc/fabric/test/model/loading/ModelTestModClient.java @@ -0,0 +1,122 @@ +/* + * 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.test.model.loading; + +import java.util.function.Supplier; + +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.ModelLoader; +import net.minecraft.client.render.model.UnbakedModel; +import net.minecraft.client.util.ModelIdentifier; +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.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.DelegatingUnbakedModel; +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 { + public static final String ID = "fabric-model-loading-api-v1-testmod"; + + public static final Identifier MODEL_ID = new Identifier(ID, "half_red_sand"); + + static class DownQuadRemovingModel extends ForwardingBakedModel { + DownQuadRemovingModel(BakedModel model) { + wrapped = model; + } + + @Override + public void emitBlockQuads(BlockRenderView blockView, BlockState state, BlockPos pos, Supplier 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(MODEL_ID); + // remove bottom face of gold blocks + pluginContext.modifyModelAfterBake().register(ModelModifier.WRAP_PHASE, (model, context) -> { + if (context.id().getPath().equals("block/gold_block")) { + return new DownQuadRemovingModel(model); + } else { + 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) -> { + if (fenceId.equals(context.id())) { + return context.getOrLoadModel(ModelLoader.MISSING_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) -> { + if (context.id().getPath().equals("block/brown_glazed_terracotta")) { + return context.loader().getOrLoadModel(ModelLoader.MISSING_ID); + } + + 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 = new Identifier("block/wheat_stage0"); + + UnbakedModel stage0Model = new DelegatingUnbakedModel(wheatStage0Id); + + for (int age = 0; age <= 6; age++) { + context.setModel(state.with(CropBlock.AGE, age), stage0Model); + } + + context.setModel(state.with(CropBlock.AGE, 7), context.getOrLoadModel(new Identifier("block/wheat_stage7"))); + }); + }); + + 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)); + } + }); + } +} diff --git a/fabric-model-loading-api-v1/src/testmodClient/java/net/fabricmc/fabric/test/model/loading/NestedModelLoadingTest.java b/fabric-model-loading-api-v1/src/testmodClient/java/net/fabricmc/fabric/test/model/loading/NestedModelLoadingTest.java new file mode 100644 index 0000000000..7183940e40 --- /dev/null +++ b/fabric-model-loading-api-v1/src/testmodClient/java/net/fabricmc/fabric/test/model/loading/NestedModelLoadingTest.java @@ -0,0 +1,108 @@ +/* + * 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.test.model.loading; + +import com.mojang.logging.LogUtils; +import org.slf4j.Logger; + +import net.minecraft.client.render.model.ModelLoader; +import net.minecraft.client.render.model.UnbakedModel; +import net.minecraft.util.Identifier; + +import net.fabricmc.api.ClientModInitializer; +import net.fabricmc.fabric.api.client.model.loading.v1.ModelLoadingPlugin; + +/** + * Tests that deep model resolution resolve each model a single time, depth-first. + */ +public class NestedModelLoadingTest implements ClientModInitializer { + private static final Logger LOGGER = LogUtils.getLogger(); + + private static Identifier id(String path) { + return new Identifier("fabric-model-loading-api-v1-testmod", path); + } + + private static final Identifier BASE_MODEL = id("nested_base"); + private static final Identifier NESTED_MODEL_1 = id("nested_1"); + private static final Identifier NESTED_MODEL_2 = id("nested_2"); + private static final Identifier NESTED_MODEL_3 = id("nested_3"); + private static final Identifier NESTED_MODEL_4 = id("nested_4"); + private static final Identifier NESTED_MODEL_5 = id("nested_5"); + private static final Identifier TARGET_MODEL = new Identifier("minecraft", "block/stone"); + + @Override + public void onInitializeClient() { + ModelLoadingPlugin.register(pluginContext -> { + pluginContext.addModels(BASE_MODEL); + + pluginContext.resolveModel().register(context -> { + Identifier id = context.id(); + UnbakedModel ret = null; + + if (id.equals(BASE_MODEL)) { + LOGGER.info("Nested model 1 started loading"); + ret = context.getOrLoadModel(NESTED_MODEL_1); + LOGGER.info("Nested model 1 finished loading"); + } else if (id.equals(NESTED_MODEL_1)) { + LOGGER.info(" Nested model 2 started loading"); + ret = context.getOrLoadModel(NESTED_MODEL_2); + LOGGER.info(" Nested model 2 finished loading"); + } else if (id.equals(NESTED_MODEL_2)) { + LOGGER.info(" Nested model 3 started loading"); + ret = context.getOrLoadModel(NESTED_MODEL_3); + LOGGER.info(" Nested model 3 finished loading"); + } else if (id.equals(NESTED_MODEL_3)) { + // Will be overridden by the model modifier below anyway. + LOGGER.info(" Returning dummy model for nested model 3"); + ret = context.getOrLoadModel(ModelLoader.MISSING_ID); + } else if (id.equals(NESTED_MODEL_4)) { + // Will be overridden by the model modifier below anyway. + LOGGER.info(" Returning dummy model for nested model 4"); + ret = context.getOrLoadModel(ModelLoader.MISSING_ID); + } else if (id.equals(NESTED_MODEL_5)) { + LOGGER.info(" Target model started loading"); + ret = context.getOrLoadModel(TARGET_MODEL); + LOGGER.info(" Target model finished loading"); + } + + return ret; + }); + + pluginContext.modifyModelOnLoad().register((model, context) -> { + UnbakedModel ret = model; + + if (context.id().equals(NESTED_MODEL_3)) { + Identifier id = context.id(); + + LOGGER.info(" Nested model 4 started loading"); + ret = context.getOrLoadModel(NESTED_MODEL_4); + LOGGER.info(" Nested model 4 finished loading"); + + if (!id.equals(context.id())) { + throw new AssertionError("Context object should not have changed."); + } + } else if (context.id().equals(NESTED_MODEL_4)) { + LOGGER.info(" Nested model 5 started loading"); + ret = context.getOrLoadModel(NESTED_MODEL_5); + LOGGER.info(" Nested model 5 finished loading"); + } + + return ret; + }); + }); + } +} diff --git a/fabric-model-loading-api-v1/src/testmodClient/java/net/fabricmc/fabric/test/model/loading/PreparablePluginTest.java b/fabric-model-loading-api-v1/src/testmodClient/java/net/fabricmc/fabric/test/model/loading/PreparablePluginTest.java new file mode 100644 index 0000000000..eb4ec3331f --- /dev/null +++ b/fabric-model-loading-api-v1/src/testmodClient/java/net/fabricmc/fabric/test/model/loading/PreparablePluginTest.java @@ -0,0 +1,89 @@ +/* + * 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.test.model.loading; + +import java.io.BufferedReader; +import java.util.ArrayList; +import java.util.Map; +import java.util.Objects; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.Executor; +import java.util.stream.Collectors; + +import com.mojang.datafixers.util.Pair; +import com.mojang.logging.LogUtils; +import org.jetbrains.annotations.Nullable; +import org.slf4j.Logger; + +import net.minecraft.client.render.model.BakedModelManager; +import net.minecraft.client.render.model.UnbakedModel; +import net.minecraft.client.render.model.json.JsonUnbakedModel; +import net.minecraft.resource.Resource; +import net.minecraft.resource.ResourceFinder; +import net.minecraft.resource.ResourceManager; +import net.minecraft.util.Identifier; +import net.minecraft.util.Util; + +import net.fabricmc.api.ClientModInitializer; +import net.fabricmc.fabric.api.client.model.loading.v1.PreparableModelLoadingPlugin; + +/** + * Allows putting model files in {@code /model_replacements} instead of {@code /models} to override models. + * This is just a test for off-thread data loading. + * + *

The visible effect in game is that gold blocks use the diamond texture instead... + */ +public class PreparablePluginTest implements ClientModInitializer { + private static final Logger LOGGER = LogUtils.getLogger(); + private static final ResourceFinder MODEL_REPLACEMENTS_FINDER = ResourceFinder.json("model_replacements"); + + @Override + public void onInitializeClient() { + PreparableModelLoadingPlugin.register(PreparablePluginTest::loadModelReplacements, (replacementModels, pluginContext) -> { + pluginContext.modifyModelOnLoad().register((model, ctx) -> { + @Nullable + UnbakedModel replacementModel = replacementModels.get(ctx.id()); + return replacementModel == null ? model : replacementModel; + }); + }); + } + + /** + * Adaptation of the {@link BakedModelManager} method. + */ + private static CompletableFuture> loadModelReplacements(ResourceManager resourceManager, Executor executor) { + return CompletableFuture.supplyAsync(() -> MODEL_REPLACEMENTS_FINDER.findResources(resourceManager), executor).thenCompose(models2 -> { + ArrayList>> list = new ArrayList<>(models2.size()); + + for (Map.Entry entry : models2.entrySet()) { + list.add(CompletableFuture.supplyAsync(() -> { + try (BufferedReader reader = entry.getValue().getReader()) { + // Remove model_replacements/ prefix from the identifier + Identifier modelId = MODEL_REPLACEMENTS_FINDER.toResourceId(entry.getKey()); + + return Pair.of(modelId, JsonUnbakedModel.deserialize(reader)); + } catch (Exception exception) { + LOGGER.error("Failed to load model {}", entry.getKey(), exception); + return null; + } + }, executor)); + } + + return Util.combineSafe(list).thenApply(models -> models.stream().filter(Objects::nonNull).collect(Collectors.toUnmodifiableMap(Pair::getFirst, Pair::getSecond))); + }); + } +} diff --git a/fabric-models-v0/src/testmodClient/java/net/fabricmc/fabric/test/model/SpecificModelReloadListener.java b/fabric-model-loading-api-v1/src/testmodClient/java/net/fabricmc/fabric/test/model/loading/SpecificModelReloadListener.java similarity index 88% rename from fabric-models-v0/src/testmodClient/java/net/fabricmc/fabric/test/model/SpecificModelReloadListener.java rename to fabric-model-loading-api-v1/src/testmodClient/java/net/fabricmc/fabric/test/model/loading/SpecificModelReloadListener.java index 948b419b31..7a9c9f90fd 100644 --- a/fabric-models-v0/src/testmodClient/java/net/fabricmc/fabric/test/model/SpecificModelReloadListener.java +++ b/fabric-model-loading-api-v1/src/testmodClient/java/net/fabricmc/fabric/test/model/loading/SpecificModelReloadListener.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package net.fabricmc.fabric.test.model; +package net.fabricmc.fabric.test.model.loading; import java.util.Arrays; import java.util.Collection; @@ -27,7 +27,6 @@ import net.minecraft.util.Unit; import net.minecraft.util.profiler.Profiler; -import net.fabricmc.fabric.api.client.model.BakedModelManagerHelper; import net.fabricmc.fabric.api.resource.IdentifiableResourceReloadListener; import net.fabricmc.fabric.api.resource.ResourceReloadListenerKeys; @@ -48,7 +47,7 @@ protected Unit prepare(ResourceManager manager, Profiler profiler) { @Override protected void apply(Unit loader, ResourceManager manager, Profiler profiler) { - specificModel = BakedModelManagerHelper.getModel(MinecraftClient.getInstance().getBakedModelManager(), ModelTestModClient.MODEL_ID); + specificModel = MinecraftClient.getInstance().getBakedModelManager().getModel(ModelTestModClient.MODEL_ID); } @Override diff --git a/fabric-models-v0/src/testmodClient/resources/assets/fabric-models-v0-testmod/models/half_red_sand.json b/fabric-model-loading-api-v1/src/testmodClient/resources/assets/fabric-model-loading-api-v1-testmod/models/half_red_sand.json similarity index 100% rename from fabric-models-v0/src/testmodClient/resources/assets/fabric-models-v0-testmod/models/half_red_sand.json rename to fabric-model-loading-api-v1/src/testmodClient/resources/assets/fabric-model-loading-api-v1-testmod/models/half_red_sand.json diff --git a/fabric-model-loading-api-v1/src/testmodClient/resources/assets/minecraft/model_replacements/block/gold_block.json b/fabric-model-loading-api-v1/src/testmodClient/resources/assets/minecraft/model_replacements/block/gold_block.json new file mode 100644 index 0000000000..f03eb10fd5 --- /dev/null +++ b/fabric-model-loading-api-v1/src/testmodClient/resources/assets/minecraft/model_replacements/block/gold_block.json @@ -0,0 +1,6 @@ +{ + "parent": "minecraft:block/cube_all", + "textures": { + "all": "minecraft:block/diamond_block" + } +} diff --git a/fabric-model-loading-api-v1/src/testmodClient/resources/fabric.mod.json b/fabric-model-loading-api-v1/src/testmodClient/resources/fabric.mod.json new file mode 100644 index 0000000000..97eea29e4d --- /dev/null +++ b/fabric-model-loading-api-v1/src/testmodClient/resources/fabric.mod.json @@ -0,0 +1,19 @@ +{ + "schemaVersion": 1, + "id": "fabric-model-loading-api-v1-testmod", + "name": "Fabric Model Loading API (v1) Test Mod", + "version": "1.0.0", + "environment": "client", + "license": "Apache-2.0", + "depends": { + "fabric-model-loading-api-v1": "*", + "fabric-resource-loader-v0": "*" + }, + "entrypoints": { + "client": [ + "net.fabricmc.fabric.test.model.loading.ModelTestModClient", + "net.fabricmc.fabric.test.model.loading.NestedModelLoadingTest", + "net.fabricmc.fabric.test.model.loading.PreparablePluginTest" + ] + } +} diff --git a/fabric-models-v0/src/client/java/net/fabricmc/fabric/impl/client/model/ModelLoadingRegistryImpl.java b/fabric-models-v0/src/client/java/net/fabricmc/fabric/impl/client/model/ModelLoadingRegistryImpl.java deleted file mode 100644 index 4c58d8da9e..0000000000 --- a/fabric-models-v0/src/client/java/net/fabricmc/fabric/impl/client/model/ModelLoadingRegistryImpl.java +++ /dev/null @@ -1,209 +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; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; -import java.util.Objects; -import java.util.function.Consumer; -import java.util.function.Function; -import java.util.stream.Collectors; - -import com.google.common.collect.Lists; -import org.slf4j.LoggerFactory; -import org.slf4j.Logger; -import org.jetbrains.annotations.Nullable; - -import net.minecraft.client.render.model.ModelLoader; -import net.minecraft.client.render.model.UnbakedModel; -import net.minecraft.client.util.ModelIdentifier; -import net.minecraft.resource.ResourceManager; -import net.minecraft.util.Identifier; - -import net.fabricmc.fabric.api.client.model.ExtraModelProvider; -import net.fabricmc.fabric.api.client.model.ModelAppender; -import net.fabricmc.fabric.api.client.model.ModelLoadingRegistry; -import net.fabricmc.fabric.api.client.model.ModelProviderContext; -import net.fabricmc.fabric.api.client.model.ModelProviderException; -import net.fabricmc.fabric.api.client.model.ModelResourceProvider; -import net.fabricmc.fabric.api.client.model.ModelVariantProvider; -import net.fabricmc.loader.api.FabricLoader; - -public class ModelLoadingRegistryImpl implements ModelLoadingRegistry { - private static final boolean DEBUG_MODEL_LOADING = FabricLoader.getInstance().isDevelopmentEnvironment() - || Boolean.valueOf(System.getProperty("fabric.debugModelLoading", "false")); - - @FunctionalInterface - private interface CustomModelItf { - UnbakedModel load(T obj) throws ModelProviderException; - } - - public static class LoaderInstance implements ModelProviderContext { - private final Logger logger; - private final ResourceManager manager; - private final List modelVariantProviders; - private final List modelResourceProviders; - private final List modelAppenders; - private ModelLoader loader; - - private LoaderInstance(ModelLoadingRegistryImpl i, ModelLoader loader, ResourceManager manager) { - this.logger = ModelLoadingRegistryImpl.LOGGER; - this.loader = loader; - this.manager = manager; - this.modelVariantProviders = i.variantProviderSuppliers.stream().map((s) -> s.apply(manager)).collect(Collectors.toList()); - this.modelResourceProviders = i.resourceProviderSuppliers.stream().map((s) -> s.apply(manager)).collect(Collectors.toList()); - this.modelAppenders = i.appenders; - } - - @Override - public UnbakedModel loadModel(Identifier id) { - if (loader == null) { - throw new RuntimeException("Called loadModel too late!"); - } - - return ((ModelLoaderHooks) loader).fabric_loadModel(id); - } - - public void onModelPopulation(Consumer addModel) { - for (ExtraModelProvider appender : modelAppenders) { - appender.provideExtraModels(manager, addModel); - } - } - - private UnbakedModel loadCustomModel(CustomModelItf function, Collection loaders, String debugName) { - if (!DEBUG_MODEL_LOADING) { - for (T provider : loaders) { - try { - UnbakedModel model = function.load(provider); - - if (model != null) { - return model; - } - } catch (ModelProviderException e) { - logger.error("Failed to load custom model", e); - return null; - } - } - - return null; - } - - UnbakedModel modelLoaded = null; - T providerUsed = null; - List providersApplied = null; - - for (T provider : loaders) { - try { - UnbakedModel model = function.load(provider); - - if (model != null) { - if (providersApplied != null) { - providersApplied.add(provider); - } else if (providerUsed != null) { - providersApplied = Lists.newArrayList(providerUsed, provider); - } else { - modelLoaded = model; - providerUsed = provider; - } - } - } catch (ModelProviderException e) { - logger.error("Failed to load custom model", e); - return null; - } - } - - if (providersApplied != null) { - StringBuilder builder = new StringBuilder("Conflict - multiple " + debugName + "s claimed the same unbaked model:"); - - for (T loader : providersApplied) { - builder.append("\n\t - ").append(loader.getClass().getName()); - } - - logger.error(builder.toString()); - return null; - } else { - return modelLoaded; - } - } - - @Nullable - public UnbakedModel loadModelFromResource(Identifier resourceId) { - return loadCustomModel((r) -> r.loadModelResource(resourceId, this), modelResourceProviders, "resource provider"); - } - - @Nullable - public UnbakedModel loadModelFromVariant(Identifier variantId) { - if (!(variantId instanceof ModelIdentifier)) { - return loadModelFromResource(variantId); - } else { - ModelIdentifier modelId = (ModelIdentifier) variantId; - UnbakedModel model = loadCustomModel((r) -> r.loadModelVariant((ModelIdentifier) variantId, this), modelVariantProviders, "resource provider"); - - if (model != null) { - return model; - } - - // Replicating the special-case from ModelLoader as loadModelFromJson is insufficiently patchable - if (Objects.equals(modelId.getVariant(), "inventory")) { - Identifier resourceId = new Identifier(modelId.getNamespace(), "item/" + modelId.getPath()); - model = loadModelFromResource(resourceId); - - if (model != null) { - return model; - } - } - - return null; - } - } - - public void finish() { - loader = null; - } - } - - private static final Logger LOGGER = LoggerFactory.getLogger(ModelLoadingRegistryImpl.class); - - private final List> variantProviderSuppliers = new ArrayList<>(); - private final List> resourceProviderSuppliers = new ArrayList<>(); - private final List appenders = new ArrayList<>(); - - @Override - public void registerModelProvider(ExtraModelProvider appender) { - appenders.add(appender); - } - - @Override - public void registerAppender(ModelAppender appender) { - registerModelProvider((manager, consumer) -> appender.appendAll(manager, consumer::accept)); - } - - @Override - public void registerResourceProvider(Function providerSupplier) { - resourceProviderSuppliers.add(providerSupplier); - } - - @Override - public void registerVariantProvider(Function providerSupplier) { - variantProviderSuppliers.add(providerSupplier); - } - - public static LoaderInstance begin(ModelLoader loader, ResourceManager manager) { - return new LoaderInstance((ModelLoadingRegistryImpl) INSTANCE, loader, manager); - } -} diff --git a/fabric-models-v0/src/client/java/net/fabricmc/fabric/mixin/client/model/ModelLoaderMixin.java b/fabric-models-v0/src/client/java/net/fabricmc/fabric/mixin/client/model/ModelLoaderMixin.java deleted file mode 100644 index b16480a415..0000000000 --- a/fabric-models-v0/src/client/java/net/fabricmc/fabric/mixin/client/model/ModelLoaderMixin.java +++ /dev/null @@ -1,124 +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; - -import java.util.Map; -import java.util.Set; - -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.Inject; -import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; - -import net.minecraft.client.MinecraftClient; -import net.minecraft.client.render.model.ModelLoader; -import net.minecraft.client.render.model.UnbakedModel; -import net.minecraft.client.util.ModelIdentifier; -import net.minecraft.resource.ResourceManager; -import net.minecraft.util.Identifier; - -import net.fabricmc.fabric.impl.client.model.ModelLoaderHooks; -import net.fabricmc.fabric.impl.client.model.ModelLoadingRegistryImpl; - -@Mixin(ModelLoader.class) -public abstract class ModelLoaderMixin implements ModelLoaderHooks { - // this is the first one - @Final - @Shadow - public static ModelIdentifier MISSING_ID; - @Final - @Shadow - private Set modelsToLoad; - @Final - @Shadow - private Map unbakedModels; - @Shadow - @Final - private Map modelsToBake; - - private ModelLoadingRegistryImpl.LoaderInstance fabric_mlrLoaderInstance; - - @Shadow - private void addModel(ModelIdentifier id) { - } - - @Shadow - private void putModel(Identifier id, UnbakedModel unbakedModel) { - } - - @Shadow - private void loadModel(Identifier id) { - } - - @Shadow - public abstract UnbakedModel getOrLoadModel(Identifier id); - - @Inject(at = @At("HEAD"), method = "loadModel", cancellable = true) - private void loadModelHook(Identifier id, CallbackInfo ci) { - UnbakedModel customModel = fabric_mlrLoaderInstance.loadModelFromVariant(id); - - if (customModel != null) { - putModel(id, customModel); - ci.cancel(); - } - } - - @Inject(at = @At("HEAD"), method = "addModel") - private void addModelHook(ModelIdentifier id, CallbackInfo info) { - if (id == MISSING_ID) { - //noinspection RedundantCast - ModelLoaderHooks hooks = this; - - ResourceManager resourceManager = MinecraftClient.getInstance().getResourceManager(); - fabric_mlrLoaderInstance = ModelLoadingRegistryImpl.begin((ModelLoader) (Object) this, resourceManager); - fabric_mlrLoaderInstance.onModelPopulation(hooks::fabric_addModel); - } - } - - @Inject(at = @At("RETURN"), method = "") - private void initFinishedHook(CallbackInfo info) { - //noinspection ConstantConditions - fabric_mlrLoaderInstance.finish(); - } - - @Override - public void fabric_addModel(Identifier id) { - if (id instanceof ModelIdentifier) { - addModel((ModelIdentifier) id); - } else { - // The vanilla addModel method is arbitrarily limited to ModelIdentifiers, - // but it's useful to tell the game to just load and bake a direct model path as well. - // Replicate the vanilla logic of addModel here. - UnbakedModel unbakedModel = getOrLoadModel(id); - this.unbakedModels.put(id, unbakedModel); - this.modelsToBake.put(id, unbakedModel); - } - } - - @Override - public UnbakedModel fabric_loadModel(Identifier id) { - if (!modelsToLoad.add(id)) { - throw new IllegalStateException("Circular reference while loading " + id); - } - - loadModel(id); - modelsToLoad.remove(id); - return unbakedModels.get(id); - } -} diff --git a/fabric-models-v0/src/client/resources/fabric-models-v0.mixins.json b/fabric-models-v0/src/client/resources/fabric-models-v0.mixins.json deleted file mode 100644 index 4da50ed3d0..0000000000 --- a/fabric-models-v0/src/client/resources/fabric-models-v0.mixins.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "required": true, - "package": "net.fabricmc.fabric.mixin.client.model", - "compatibilityLevel": "JAVA_16", - "client": [ - "BakedModelManagerMixin", - "ModelLoaderMixin" - ], - "injectors": { - "defaultRequire": 1 - } -} diff --git a/fabric-models-v0/src/testmodClient/java/net/fabricmc/fabric/test/model/BakedModelRenderer.java b/fabric-models-v0/src/testmodClient/java/net/fabricmc/fabric/test/model/BakedModelRenderer.java deleted file mode 100644 index bf3328591b..0000000000 --- a/fabric-models-v0/src/testmodClient/java/net/fabricmc/fabric/test/model/BakedModelRenderer.java +++ /dev/null @@ -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.test.model; - -import org.apache.commons.lang3.ArrayUtils; - -import net.minecraft.client.render.OverlayTexture; -import net.minecraft.client.render.VertexConsumer; -import net.minecraft.client.render.model.BakedModel; -import net.minecraft.client.render.model.BakedQuad; -import net.minecraft.client.util.math.MatrixStack; -import net.minecraft.util.math.Direction; -import net.minecraft.util.math.random.Random; - -public class BakedModelRenderer { - private static final Direction[] CULL_FACES = ArrayUtils.add(Direction.values(), null); - private static final Random RANDOM = Random.create(); - - public static void renderBakedModel(BakedModel model, VertexConsumer vertices, MatrixStack.Entry entry, int light) { - for (Direction cullFace : CULL_FACES) { - RANDOM.setSeed(42L); - - for (BakedQuad quad : model.getQuads(null, cullFace, RANDOM)) { - vertices.quad(entry, quad, 1.0F, 1.0F, 1.0F, light, OverlayTexture.DEFAULT_UV); - } - } - } -} diff --git a/fabric-models-v0/src/testmodClient/java/net/fabricmc/fabric/test/model/ModelTestModClient.java b/fabric-models-v0/src/testmodClient/java/net/fabricmc/fabric/test/model/ModelTestModClient.java deleted file mode 100644 index 2f54acddf6..0000000000 --- a/fabric-models-v0/src/testmodClient/java/net/fabricmc/fabric/test/model/ModelTestModClient.java +++ /dev/null @@ -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.test.model; - -import net.minecraft.client.render.entity.PlayerEntityRenderer; -import net.minecraft.resource.ResourceType; -import net.minecraft.util.Identifier; - -import net.fabricmc.api.ClientModInitializer; -import net.fabricmc.fabric.api.client.model.ModelLoadingRegistry; -import net.fabricmc.fabric.api.client.rendering.v1.LivingEntityFeatureRendererRegistrationCallback; -import net.fabricmc.fabric.api.resource.ResourceManagerHelper; - -public class ModelTestModClient implements ClientModInitializer { - public static final String ID = "fabric-models-v0-testmod"; - - public static final Identifier MODEL_ID = new Identifier(ID, "half_red_sand"); - - @Override - public void onInitializeClient() { - ModelLoadingRegistry.INSTANCE.registerModelProvider((manager, out) -> { - out.accept(MODEL_ID); - }); - - ResourceManagerHelper.get(ResourceType.CLIENT_RESOURCES).registerReloadListener(SpecificModelReloadListener.INSTANCE); - - LivingEntityFeatureRendererRegistrationCallback.EVENT.register((entityType, entityRenderer, registrationHelper, context) -> { - if (entityRenderer instanceof PlayerEntityRenderer) { - registrationHelper.register(new BakedModelFeatureRenderer<>((PlayerEntityRenderer) entityRenderer, SpecificModelReloadListener.INSTANCE::getSpecificModel)); - } - }); - } -} diff --git a/fabric-models-v0/src/testmodClient/resources/fabric.mod.json b/fabric-models-v0/src/testmodClient/resources/fabric.mod.json deleted file mode 100644 index 83599a1f77..0000000000 --- a/fabric-models-v0/src/testmodClient/resources/fabric.mod.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "schemaVersion": 1, - "id": "fabric-models-v0-testmod", - "name": "Fabric Models (v0) Test Mod", - "version": "1.0.0", - "environment": "client", - "license": "Apache-2.0", - "depends": { - "fabric-models-v0": "*", - "fabric-resource-loader-v0": "*" - }, - "entrypoints": { - "client": [ - "net.fabricmc.fabric.test.model.ModelTestModClient" - ] - } -} diff --git a/fabric-renderer-api-v1/build.gradle b/fabric-renderer-api-v1/build.gradle index f8b1366eb8..26eccd8907 100644 --- a/fabric-renderer-api-v1/build.gradle +++ b/fabric-renderer-api-v1/build.gradle @@ -6,7 +6,7 @@ moduleDependencies(project, ['fabric-api-base']) testDependencies(project, [ ':fabric-block-api-v1', ':fabric-blockrenderlayer-v1', - ':fabric-models-v0', + ':fabric-model-loading-api-v1', ':fabric-object-builder-api-v1', ':fabric-renderer-indigo', ':fabric-rendering-data-attachment-v1', diff --git a/fabric-renderer-api-v1/src/testmodClient/java/net/fabricmc/fabric/test/renderer/simple/client/FrameModelResourceProvider.java b/fabric-renderer-api-v1/src/testmodClient/java/net/fabricmc/fabric/test/renderer/simple/client/FrameModelResourceProvider.java deleted file mode 100644 index 34ff995baa..0000000000 --- a/fabric-renderer-api-v1/src/testmodClient/java/net/fabricmc/fabric/test/renderer/simple/client/FrameModelResourceProvider.java +++ /dev/null @@ -1,45 +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.test.renderer.simple.client; - -import java.util.HashSet; -import java.util.Set; - -import org.jetbrains.annotations.Nullable; - -import net.minecraft.client.render.model.UnbakedModel; -import net.minecraft.util.Identifier; - -import net.fabricmc.fabric.api.client.model.ModelProviderContext; -import net.fabricmc.fabric.api.client.model.ModelResourceProvider; - -/** - * Provides the unbaked model for use with the frame block. - */ -final class FrameModelResourceProvider implements ModelResourceProvider { - static final Set FRAME_MODELS = new HashSet<>(); - - @Nullable - @Override - public UnbakedModel loadModelResource(Identifier resourceId, ModelProviderContext context) { - if (FRAME_MODELS.contains(resourceId)) { - return new FrameUnbakedModel(); - } - - return null; - } -} diff --git a/fabric-renderer-api-v1/src/testmodClient/java/net/fabricmc/fabric/test/renderer/simple/client/PillarModelVariantProvider.java b/fabric-renderer-api-v1/src/testmodClient/java/net/fabricmc/fabric/test/renderer/simple/client/PillarModelVariantProvider.java deleted file mode 100644 index 8fbd970551..0000000000 --- a/fabric-renderer-api-v1/src/testmodClient/java/net/fabricmc/fabric/test/renderer/simple/client/PillarModelVariantProvider.java +++ /dev/null @@ -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.test.renderer.simple.client; - -import org.jetbrains.annotations.Nullable; - -import net.minecraft.client.render.model.UnbakedModel; -import net.minecraft.client.util.ModelIdentifier; - -import net.fabricmc.fabric.api.client.model.ModelProviderContext; -import net.fabricmc.fabric.api.client.model.ModelVariantProvider; -import net.fabricmc.fabric.test.renderer.simple.RendererTest; - -public class PillarModelVariantProvider implements ModelVariantProvider { - @Override - @Nullable - public UnbakedModel loadModelVariant(ModelIdentifier modelId, ModelProviderContext context) { - if (RendererTest.PILLAR_ID.equals(modelId)) { - return new PillarUnbakedModel(); - } else { - return null; - } - } -} diff --git a/fabric-renderer-api-v1/src/testmodClient/java/net/fabricmc/fabric/test/renderer/simple/client/RendererClientTest.java b/fabric-renderer-api-v1/src/testmodClient/java/net/fabricmc/fabric/test/renderer/simple/client/RendererClientTest.java index 3fb47ec6fe..1e45c93a9c 100644 --- a/fabric-renderer-api-v1/src/testmodClient/java/net/fabricmc/fabric/test/renderer/simple/client/RendererClientTest.java +++ b/fabric-renderer-api-v1/src/testmodClient/java/net/fabricmc/fabric/test/renderer/simple/client/RendererClientTest.java @@ -18,30 +18,47 @@ import static net.fabricmc.fabric.test.renderer.simple.RendererTest.id; +import java.util.HashSet; +import java.util.Set; + import net.minecraft.client.render.RenderLayer; import net.minecraft.registry.Registries; +import net.minecraft.util.Identifier; import net.fabricmc.api.ClientModInitializer; import net.fabricmc.fabric.api.blockrenderlayer.v1.BlockRenderLayerMap; -import net.fabricmc.fabric.api.client.model.ModelLoadingRegistry; +import net.fabricmc.fabric.api.client.model.loading.v1.ModelLoadingPlugin; import net.fabricmc.fabric.test.renderer.simple.FrameBlock; import net.fabricmc.fabric.test.renderer.simple.RendererTest; public final class RendererClientTest implements ClientModInitializer { + private static final Set FRAME_MODELS = new HashSet<>(); + @Override public void onInitializeClient() { - ModelLoadingRegistry.INSTANCE.registerResourceProvider(manager -> new FrameModelResourceProvider()); - ModelLoadingRegistry.INSTANCE.registerVariantProvider(manager -> new PillarModelVariantProvider()); - for (FrameBlock frameBlock : RendererTest.FRAMES) { // We don't specify a material for the frame mesh, // so it will use the default material, i.e. the one from BlockRenderLayerMap. BlockRenderLayerMap.INSTANCE.putBlock(frameBlock, RenderLayer.getCutoutMipped()); String itemPath = Registries.ITEM.getId(frameBlock.asItem()).getPath(); - FrameModelResourceProvider.FRAME_MODELS.add(id("item/" + itemPath)); + FRAME_MODELS.add(id("item/" + itemPath)); } - FrameModelResourceProvider.FRAME_MODELS.add(id("block/frame")); + FRAME_MODELS.add(id("block/frame")); + + ModelLoadingPlugin.register(pluginContext -> { + pluginContext.resolveModel().register(context -> { + if (FRAME_MODELS.contains(context.id())) { + return new FrameUnbakedModel(); + } + + return null; + }); + + pluginContext.registerBlockStateResolver(RendererTest.PILLAR, context -> { + context.setModel(context.block().getDefaultState(), new PillarUnbakedModel()); + }); + }); } } diff --git a/gradle.properties b/gradle.properties index 62ed69f105..128b85f43e 100644 --- a/gradle.properties +++ b/gradle.properties @@ -38,6 +38,7 @@ fabric-loot-api-v2-version=1.1.38 fabric-loot-tables-v1-version=1.1.42 fabric-message-api-v1-version=5.1.6 fabric-mining-level-api-v1-version=2.1.48 +fabric-model-loading-api-v1-version=1.0.0 fabric-models-v0-version=0.3.35 fabric-networking-api-v1-version=1.3.8 fabric-networking-v0-version=0.3.48 diff --git a/settings.gradle b/settings.gradle index daeff6014f..7b255e985b 100644 --- a/settings.gradle +++ b/settings.gradle @@ -33,7 +33,7 @@ include 'fabric-lifecycle-events-v1' include 'fabric-loot-api-v2' include 'fabric-message-api-v1' include 'fabric-mining-level-api-v1' -include 'fabric-models-v0' +include 'fabric-model-loading-api-v1' include 'fabric-networking-api-v1' include 'fabric-object-builder-api-v1' include 'fabric-particles-v1' @@ -61,6 +61,7 @@ include 'deprecated:fabric-command-api-v1' include 'deprecated:fabric-containers-v0' include 'deprecated:fabric-events-lifecycle-v0' include 'deprecated:fabric-keybindings-v0' +include 'deprecated:fabric-models-v0' include 'deprecated:fabric-networking-v0' include 'deprecated:fabric-renderer-registries-v1' include 'deprecated:fabric-rendering-v0'