mirror of
https://github.com/FabricMC/fabric.git
synced 2024-11-24 08:38:17 -05:00
Model Loading API v1 (#3145)
This PR deprecates and supersedes the old `fabric-models-v0` module. ## Refactors compared to v0 ### Model loading hooks Mods now register a single `ModelLoadingPlugin` for any change they want to make to the model loading process. (Or multiple plugins if they want to). This replaces the old `ModelLoadingRegistry`. Here is an example: ```java ModelLoadingPlugin.register(pluginContext -> { // ResourceManager access is provided like in the v0 API, but in a central place, and can be used for shared processing in the plugin. ResourceManager manager = pluginContext.resourceManager(); // Model resource providers still exist, but they are called model resolvers now pluginContext.resolveModel().register(context -> { // ... }); }); ``` #### `ModelVariantProvider` -> `BlockStateResolver` `ModelVariantProvider` was heavily reworked, and is now replaced by the `BlockStateResolver`, with a much better defined contract. #### `ModelResourceProvider` -> `ModelResolver` The resource provider is mostly unchanged. The biggest difference is that it is now registered as an event listener. This allows mods to use event phases for ordering between conflicting ~~providers~~ resolvers. #### Removed custom exception Additionally, `ModelProviderException` could be thrown by a variant or resource provider in the v0 API. This was not explained in the documentation, and would according to the code stop further processing of the providers and log an error. In the new API, any `Exception` is caught and logged. If that happens, the other resolvers are still processed. There is no custom `Exception` subclass anymore. ### Helper method to get a `BakedModel` by `Identifier` from the manager The v0 had such a method in a helper class: `BakedModelManagerHelper#getBakedModel`. It is now interface-injected instead. See `FabricBakedModelManager`. ## New model wrapping hooks New hooks are added for the various needs of mods that want to override or wrap specific models. Thanks to @embeddedt for contributing an initial version of them! Here is an example of wrapping the gold model to remove the bottom quads, for example: ```java ModelLoadingPlugin.register(pluginContext -> { // remove bottom face of gold blocks pluginContext.modifyModelAfterBake().register(ModelModifier.WRAP_PHASE, (model, context) -> { if (context.identifier().getPath().equals("block/gold_block")) { return new DownQuadRemovingModel(model); } else { return model; } }); }); ``` There are 3 events, for the following use cases: - Wrapping `UnbakedModel`s right when they are loaded. This allows replacing them entirely in dependent models too. - Wrapping `UnbakedModel`s right before they are baked. This allows replacing them without affecting dependent models (which might not be expecting a model class change). - Wrapping `BakedModel`s right when they are baked. Multiple mods have implemented their own version of them. Providing them in Fabric API will make it easier on these mods, and will additionally allow optimization mods that perform on-demand model loading to simply fire the hooks themselves instead of working around injections performed by other mods. Co-authored-by: embeddedt <42941056+embeddedt@users.noreply.github.com> Co-authored-by: PepperCode1 <44146161+PepperCode1@users.noreply.github.com> Co-authored-by: Juuz <6596629+Juuxel@users.noreply.github.com> Signed-off-by: modmuss50 <modmuss50@gmail.com>
This commit is contained in:
parent
1e0223ee28
commit
a025543665
52 changed files with 2406 additions and 583 deletions
7
deprecated/fabric-models-v0/build.gradle
Normal file
7
deprecated/fabric-models-v0/build.gradle
Normal file
|
@ -0,0 +1,7 @@
|
|||
archivesBaseName = "fabric-models-v0"
|
||||
version = getSubprojectVersion(project)
|
||||
|
||||
moduleDependencies(project, [
|
||||
'fabric-api-base',
|
||||
'fabric-model-loading-api-v1'
|
||||
])
|
|
@ -23,8 +23,12 @@ import net.minecraft.client.render.model.BakedModelManager;
|
|||
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() { }
|
|
@ -22,6 +22,12 @@ import net.minecraft.client.util.ModelIdentifier;
|
|||
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 {
|
||||
/**
|
|
@ -21,8 +21,13 @@ import java.util.function.Function;
|
|||
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();
|
||||
|
|
@ -20,9 +20,13 @@ import net.minecraft.client.render.model.UnbakedModel;
|
|||
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}, ...
|
|
@ -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);
|
|
@ -21,6 +21,8 @@ import org.jetbrains.annotations.Nullable;
|
|||
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 @@ import net.minecraft.util.Identifier;
|
|||
*
|
||||
* <ul><li>Only load files with a mod-suffixed name, such as .architect.obj,
|
||||
* <li>Only load files from an explicit list of namespaces, registered elsewhere.</ul>
|
||||
*
|
||||
* @deprecated Use {@link ModelLoadingPlugin} and related classes instead.
|
||||
*/
|
||||
@Deprecated
|
||||
@FunctionalInterface
|
||||
public interface ModelResourceProvider {
|
||||
/**
|
|
@ -21,6 +21,8 @@ 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.loading.v1.ModelLoadingPlugin;
|
||||
|
||||
/**
|
||||
* Interface for model variant providers.
|
||||
*
|
||||
|
@ -37,7 +39,10 @@ import net.minecraft.client.util.ModelIdentifier;
|
|||
*
|
||||
* <p>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 {
|
||||
/**
|
|
@ -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<ExtraModelProvider> modelProviders = new ArrayList<>();
|
||||
private final List<ModelAppender> modelAppenders = new ArrayList<>();
|
||||
private final List<Function<ResourceManager, ModelResourceProvider>> resourceProviderSuppliers = new ArrayList<>();
|
||||
private final List<Function<ResourceManager, ModelVariantProvider>> 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<Identifier> extraModelConsumer = pluginContext::addModels;
|
||||
Consumer<ModelIdentifier> 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<ResourceManager, ModelResourceProvider> 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<ResourceManager, ModelVariantProvider> 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<ResourceManager, ModelResourceProvider> providerSupplier) {
|
||||
resourceProviderSuppliers.add(providerSupplier);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerVariantProvider(Function<ResourceManager, ModelVariantProvider> providerSupplier) {
|
||||
variantProviderSuppliers.add(providerSupplier);
|
||||
}
|
||||
}
|
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.5 KiB |
|
@ -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"
|
||||
}
|
||||
}
|
|
@ -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'
|
||||
])
|
|
@ -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.
|
||||
*
|
||||
* <p>Block state resolvers can be used to create custom block state formats or dynamically resolve block state models.
|
||||
*
|
||||
* <p>Use {@link ModelResolver} instead of this interface if interacting with the block and block states directly is not
|
||||
* necessary. This includes custom model deserializers and loaders.
|
||||
*
|
||||
* @see ModelResolver
|
||||
* @see ModelModifier.OnLoad
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface BlockStateResolver {
|
||||
/**
|
||||
* Resolves the models for all block states of the block.
|
||||
*
|
||||
* <p>For each block state, call {@link Context#setModel} to set its unbaked model.
|
||||
* This method must be called exactly once for each block state.
|
||||
*
|
||||
* <p>Note that if multiple block states share the same unbaked model instance, it will be baked multiple times
|
||||
* (once per block state that has the model set), which is not efficient. To improve efficiency in this case, the
|
||||
* model should be delegated to using {@link DelegatingUnbakedModel} to ensure that it is only baked once. The inner
|
||||
* model can be loaded using {@link ModelResolver} if custom loading logic is necessary.
|
||||
*/
|
||||
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.
|
||||
*
|
||||
* <p>Do <b>not</b> call {@link ModelLoader#getOrLoadModel} as it does not supported nested model resolution;
|
||||
* use {@link #getOrLoadModel} from the context instead.
|
||||
*/
|
||||
ModelLoader loader();
|
||||
}
|
||||
}
|
|
@ -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<Identifier> 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<Identifier> getModelDependencies() {
|
||||
return dependencies;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setParents(Function<Identifier, UnbakedModel> modelLoader) {
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public BakedModel bake(Baker baker, Function<SpriteIdentifier, Sprite> textureGetter, ModelBakeSettings rotationContainer, Identifier modelId) {
|
||||
return baker.bake(delegate, rotationContainer);
|
||||
}
|
||||
}
|
|
@ -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}.
|
||||
*
|
||||
* <p>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.
|
||||
*
|
||||
* <p><b>This method, as well as its vanilla counterpart, should only be used after the
|
||||
* {@link BakedModelManager} has completed reloading.</b> Otherwise, the result will be
|
||||
* outdated or null.
|
||||
*
|
||||
* @param id the id of the model
|
||||
* @return the model
|
||||
*/
|
||||
@Nullable
|
||||
default BakedModel getModel(Identifier id) {
|
||||
throw new UnsupportedOperationException("Implemented via mixin.");
|
||||
}
|
||||
}
|
|
@ -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.
|
||||
*
|
||||
* <p>{@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<? extends Identifier> ids);
|
||||
|
||||
/**
|
||||
* Registers a block state resolver for a block.
|
||||
*
|
||||
* <p>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<ModelResolver> resolveModel();
|
||||
|
||||
/**
|
||||
* Event access to monitor unbaked model loads and replace the loaded model.
|
||||
*/
|
||||
Event<ModelModifier.OnLoad> modifyModelOnLoad();
|
||||
|
||||
/**
|
||||
* Event access to replace the unbaked model used for baking without replacing the cached model.
|
||||
*
|
||||
* <p>This is useful for mods which wish to wrap a model without affecting other models that use it as a parent
|
||||
* (e.g. wrap a block's model into a non-{@link JsonUnbakedModel} class but still allow the item model to be
|
||||
* loaded and baked without exceptions).
|
||||
*/
|
||||
Event<ModelModifier.BeforeBake> modifyModelBeforeBake();
|
||||
|
||||
/**
|
||||
* Event access to monitor baked model loads and replace the loaded model.
|
||||
*/
|
||||
Event<ModelModifier.AfterBake> modifyModelAfterBake();
|
||||
}
|
||||
}
|
|
@ -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.
|
||||
*
|
||||
* <p>Example use cases:
|
||||
* <ul>
|
||||
* <li>Overriding a model for a particular block state - check if the given identifier is a {@link ModelIdentifier},
|
||||
* and then check if it has the appropriate variant for that block state. If so, return your desired model,
|
||||
* otherwise return the given model.</li>
|
||||
* <li>Wrapping a model to override certain behaviors - simply return a new model instance and delegate calls
|
||||
* to the original model as needed.</li>
|
||||
* </ul>
|
||||
*
|
||||
* <p>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.
|
||||
*
|
||||
* <p>These callbacks are invoked for <b>every single model that is loaded or baked</b>, 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}).
|
||||
*
|
||||
* <p>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.
|
||||
*
|
||||
* <p>Do <b>not</b> 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<SpriteIdentifier, Sprite> textureGetter();
|
||||
|
||||
/**
|
||||
* The settings this model is being baked with.
|
||||
*/
|
||||
ModelBakeSettings settings();
|
||||
|
||||
/**
|
||||
* The baker being used to bake this model.
|
||||
* It can be used to {@linkplain Baker#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.
|
||||
*
|
||||
* <p>For further information, see the docs of {@link ModelLoadingPlugin.Context#modifyModelAfterBake()}.
|
||||
*
|
||||
* @param model the current baked model instance
|
||||
* @param context context with additional information about the model/loader
|
||||
* @return the model that should be used in this scenario. If no changes are needed, just return {@code model} as-is.
|
||||
* @see ModelLoadingPlugin.Context#modifyModelAfterBake
|
||||
*/
|
||||
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<SpriteIdentifier, Sprite> textureGetter();
|
||||
|
||||
/**
|
||||
* The settings this model is being baked with.
|
||||
*/
|
||||
ModelBakeSettings settings();
|
||||
|
||||
/**
|
||||
* The baker being used to bake this model.
|
||||
* It can be used to {@linkplain Baker#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() { }
|
||||
}
|
|
@ -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.
|
||||
*
|
||||
* <p>Only one resolver may provide a custom model for a certain {@link Identifier}.
|
||||
* Thus, resolvers that load models using a custom format could conflict. To avoid
|
||||
* conflicts, such resolvers may want to only load files with a mod-suffixed name
|
||||
* or only load files that have been explicitly defined elsewhere.
|
||||
*
|
||||
* <p>If it is necessary to load and bake an arbitrary model that is not referenced
|
||||
* normally, a model resolver can be used in conjunction with
|
||||
* {@link ModelLoadingPlugin.Context#addModels} to directly load and bake custom model
|
||||
* instances.
|
||||
*
|
||||
* <p>Model resolvers are invoked for <b>every single model that will be loaded</b>,
|
||||
* so implementations should be as efficient as possible.
|
||||
*
|
||||
* @see ModelLoadingPlugin.Context#addModels
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface ModelResolver {
|
||||
/**
|
||||
* @return the resolved {@link UnbakedModel}, or {@code null} if this resolver does not handle the current {@link Identifier}
|
||||
*/
|
||||
@Nullable
|
||||
UnbakedModel resolveModel(Context context);
|
||||
|
||||
/**
|
||||
* The context for model resolution.
|
||||
*/
|
||||
@ApiStatus.NonExtendable
|
||||
interface Context {
|
||||
/**
|
||||
* The identifier of the model to be loaded.
|
||||
*/
|
||||
Identifier id();
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* <p>Do <b>not</b> call {@link ModelLoader#getOrLoadModel} as it does not supported nested model resolution;
|
||||
* use {@link #getOrLoadModel} from the context instead.
|
||||
*/
|
||||
ModelLoader loader();
|
||||
}
|
||||
}
|
|
@ -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.
|
||||
*
|
||||
* <p>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<T> {
|
||||
/**
|
||||
* Registers a preparable model loading plugin.
|
||||
*/
|
||||
static <T> void register(DataLoader<T> loader, PreparableModelLoadingPlugin<T> 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<T> {
|
||||
/**
|
||||
* 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 <b>must</b> be used to schedule any completable future.
|
||||
*/
|
||||
CompletableFuture<T> load(ResourceManager resourceManager, Executor executor);
|
||||
}
|
||||
}
|
|
@ -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) {
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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 org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
|
||||
import net.minecraft.client.render.model.BakedModel;
|
||||
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.util.Identifier;
|
||||
|
||||
import net.fabricmc.fabric.impl.client.model.BakedModelManagerHooks;
|
||||
public interface ModelLoaderHooks {
|
||||
ModelLoadingEventDispatcher fabric_getDispatcher();
|
||||
|
||||
@Mixin(BakedModelManager.class)
|
||||
public class BakedModelManagerMixin implements BakedModelManagerHooks {
|
||||
@Shadow
|
||||
private Map<Identifier, BakedModel> models;
|
||||
UnbakedModel fabric_getMissingModel();
|
||||
|
||||
@Override
|
||||
public BakedModel fabric_getModel(Identifier id) {
|
||||
return models.get(id);
|
||||
}
|
||||
UnbakedModel fabric_getOrLoadModel(Identifier id);
|
||||
|
||||
void fabric_putModel(Identifier id, UnbakedModel model);
|
||||
|
||||
void fabric_putModelDirectly(Identifier id, UnbakedModel model);
|
||||
|
||||
void fabric_queueModelDependencies(UnbakedModel model);
|
||||
|
||||
JsonUnbakedModel fabric_loadModelFromJson(Identifier id);
|
||||
}
|
|
@ -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<Identifier> extraModels = new LinkedHashSet<>();
|
||||
|
||||
private final Map<BlockKey, BlockStateResolverHolder> blockStateResolvers = new HashMap<>();
|
||||
private final BlockKey lookupKey = new BlockKey();
|
||||
|
||||
private final Event<ModelResolver> modelResolvers = EventFactory.createArrayBacked(ModelResolver.class, resolvers -> context -> {
|
||||
for (ModelResolver resolver : resolvers) {
|
||||
try {
|
||||
UnbakedModel model = resolver.resolveModel(context);
|
||||
|
||||
if (model != null) {
|
||||
return model;
|
||||
}
|
||||
} catch (Exception exception) {
|
||||
LOGGER.error("Failed to resolve model", exception);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
});
|
||||
|
||||
private static final Identifier[] MODEL_MODIFIER_PHASES = new Identifier[] { ModelModifier.OVERRIDE_PHASE, ModelModifier.DEFAULT_PHASE, ModelModifier.WRAP_PHASE, ModelModifier.WRAP_LAST_PHASE };
|
||||
|
||||
private final Event<ModelModifier.OnLoad> onLoadModifiers = EventFactory.createWithPhases(ModelModifier.OnLoad.class, modifiers -> (model, context) -> {
|
||||
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<ModelModifier.BeforeBake> beforeBakeModifiers = EventFactory.createWithPhases(ModelModifier.BeforeBake.class, modifiers -> (model, context) -> {
|
||||
for (ModelModifier.BeforeBake modifier : modifiers) {
|
||||
try {
|
||||
model = modifier.modifyModelBeforeBake(model, context);
|
||||
} catch (Exception exception) {
|
||||
LOGGER.error("Failed to modify unbaked model before bake", exception);
|
||||
}
|
||||
}
|
||||
|
||||
return model;
|
||||
}, MODEL_MODIFIER_PHASES);
|
||||
private final Event<ModelModifier.AfterBake> afterBakeModifiers = EventFactory.createWithPhases(ModelModifier.AfterBake.class, modifiers -> (model, context) -> {
|
||||
for (ModelModifier.AfterBake modifier : modifiers) {
|
||||
try {
|
||||
model = modifier.modifyModelAfterBake(model, context);
|
||||
} catch (Exception exception) {
|
||||
LOGGER.error("Failed to modify baked model after bake", exception);
|
||||
}
|
||||
}
|
||||
|
||||
return model;
|
||||
}, MODEL_MODIFIER_PHASES);
|
||||
|
||||
/**
|
||||
* This field is used by the v0 wrapper to avoid constantly wrapping the context in hot code.
|
||||
*/
|
||||
public final Function<Identifier, UnbakedModel> modelGetter;
|
||||
|
||||
public ModelLoaderPluginContextImpl(Function<Identifier, UnbakedModel> modelGetter) {
|
||||
this.modelGetter = modelGetter;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addModels(Identifier... ids) {
|
||||
for (Identifier id : ids) {
|
||||
extraModels.add(id);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addModels(Collection<? extends Identifier> 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<RegistryKey<Block>> 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<ModelResolver> resolveModel() {
|
||||
return modelResolvers;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Event<ModelModifier.OnLoad> modifyModelOnLoad() {
|
||||
return onLoadModifiers;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Event<ModelModifier.BeforeBake> modifyModelBeforeBake() {
|
||||
return beforeBakeModifiers;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Event<ModelModifier.AfterBake> modifyModelAfterBake() {
|
||||
return afterBakeModifiers;
|
||||
}
|
||||
|
||||
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<LegacyModelVariantProvider> 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<LegacyModelVariantProvider> legacyVariantProviders() {
|
||||
return legacyVariantProviders;
|
||||
}
|
||||
}
|
|
@ -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<ModelResolverContext> modelResolverContextStack = new ObjectArrayList<>();
|
||||
|
||||
private final ObjectArrayList<BlockStateResolverContext> blockStateResolverContextStack = new ObjectArrayList<>();
|
||||
private final ReferenceSet<Block> resolvingBlocks = new ReferenceOpenHashSet<>();
|
||||
|
||||
private final ObjectArrayList<OnLoadModifierContext> onLoadModifierContextStack = new ObjectArrayList<>();
|
||||
private final ObjectArrayList<BeforeBakeModifierContext> beforeBakeModifierContextStack = new ObjectArrayList<>();
|
||||
private final ObjectArrayList<AfterBakeModifierContext> afterBakeModifierContextStack = new ObjectArrayList<>();
|
||||
|
||||
public ModelLoadingEventDispatcher(ModelLoader loader, List<ModelLoadingPlugin> 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<Identifier> 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<BlockState, UnbakedModel> resolvedModels = context.models;
|
||||
ImmutableList<BlockState> 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<SpriteIdentifier, Sprite> 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<SpriteIdentifier, Sprite> 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<BlockState, UnbakedModel> 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<SpriteIdentifier, Sprite> textureGetter;
|
||||
private ModelBakeSettings settings;
|
||||
private Baker baker;
|
||||
|
||||
private void prepare(Identifier id, Function<SpriteIdentifier, Sprite> 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<SpriteIdentifier, Sprite> 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<SpriteIdentifier, Sprite> textureGetter;
|
||||
private ModelBakeSettings settings;
|
||||
private Baker baker;
|
||||
|
||||
private void prepare(Identifier id, UnbakedModel sourceModel, Function<SpriteIdentifier, Sprite> 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<SpriteIdentifier, Sprite> textureGetter() {
|
||||
return textureGetter;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ModelBakeSettings settings() {
|
||||
return settings;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Baker baker() {
|
||||
return baker;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ModelLoader loader() {
|
||||
return loader;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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<ModelLoadingPlugin> PLUGINS = new ArrayList<>();
|
||||
private static final List<PreparablePluginHolder<?>> PREPARABLE_PLUGINS = new ArrayList<>();
|
||||
|
||||
public static final ThreadLocal<List<ModelLoadingPlugin>> CURRENT_PLUGINS = new ThreadLocal<>();
|
||||
|
||||
public static void registerPlugin(ModelLoadingPlugin plugin) {
|
||||
Objects.requireNonNull(plugin, "plugin must not be null");
|
||||
|
||||
PLUGINS.add(plugin);
|
||||
}
|
||||
|
||||
public static <T> void registerPlugin(PreparableModelLoadingPlugin.DataLoader<T> loader, PreparableModelLoadingPlugin<T> 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<List<ModelLoadingPlugin>> preparePlugins(ResourceManager resourceManager, Executor executor) {
|
||||
List<CompletableFuture<ModelLoadingPlugin>> 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 <T> CompletableFuture<ModelLoadingPlugin> preparePlugin(PreparablePluginHolder<T> holder, ResourceManager resourceManager, Executor executor) {
|
||||
CompletableFuture<T> dataFuture = holder.loader.load(resourceManager, executor);
|
||||
return dataFuture.thenApply(data -> pluginContext -> holder.plugin.onInitializeModelLoader(data, pluginContext));
|
||||
}
|
||||
|
||||
private ModelLoadingPluginManager() { }
|
||||
|
||||
private record PreparablePluginHolder<T>(PreparableModelLoadingPlugin.DataLoader<T> loader, PreparableModelLoadingPlugin<T> plugin) { }
|
||||
}
|
|
@ -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<Identifier, BakedModel> 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<ModelLoader> loadModelPluginData(
|
||||
CompletableFuture<Map<Identifier, JsonUnbakedModel>> self,
|
||||
CompletionStage<Map<Identifier, List<ModelLoader.SourceTrackedData>>> otherFuture,
|
||||
BiFunction<Map<Identifier, JsonUnbakedModel>, Map<Identifier, List<ModelLoader.SourceTrackedData>>, ModelLoader> modelLoaderConstructor,
|
||||
Executor executor,
|
||||
// reload args
|
||||
ResourceReloader.Synchronizer synchronizer,
|
||||
ResourceManager manager,
|
||||
Profiler prepareProfiler,
|
||||
Profiler applyProfiler,
|
||||
Executor prepareExecutor,
|
||||
Executor applyExecutor) {
|
||||
CompletableFuture<List<ModelLoadingPlugin>> pluginsFuture = ModelLoadingPluginManager.preparePlugins(manager, prepareExecutor);
|
||||
CompletableFuture<Pair<Map<Identifier, JsonUnbakedModel>, Map<Identifier, List<ModelLoader.SourceTrackedData>>>> 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);
|
||||
}
|
||||
}
|
|
@ -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<SpriteIdentifier, Sprite> 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<SpriteIdentifier, Sprite> 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<SpriteIdentifier, Sprite> 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);
|
||||
}
|
||||
}
|
|
@ -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<Identifier> modelsToLoad;
|
||||
@Final
|
||||
@Shadow
|
||||
private Map<Identifier, UnbakedModel> unbakedModels;
|
||||
@Shadow
|
||||
@Final
|
||||
private Map<Identifier, UnbakedModel> 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 = "<init>", 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<Identifier, JsonUnbakedModel> jsonUnbakedModels, Map<Identifier, List<ModelLoader.SourceTrackedData>> 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<UnbakedModel> 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.
|
||||
*
|
||||
* <p>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);
|
||||
}
|
||||
}
|
Binary file not shown.
After Width: | Height: | Size: 1.5 KiB |
|
@ -0,0 +1,13 @@
|
|||
{
|
||||
"required": true,
|
||||
"package": "net.fabricmc.fabric.mixin.client.model.loading",
|
||||
"compatibilityLevel": "JAVA_17",
|
||||
"client": [
|
||||
"BakedModelManagerMixin",
|
||||
"ModelLoaderMixin",
|
||||
"ModelLoaderBakerImplMixin"
|
||||
],
|
||||
"injectors": {
|
||||
"defaultRequire": 1
|
||||
}
|
||||
}
|
|
@ -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" ]
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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.client.util.math.MatrixStack;
|
|||
import net.minecraft.entity.LivingEntity;
|
||||
|
||||
public class BakedModelFeatureRenderer<T extends LivingEntity, M extends EntityModel<T>> extends FeatureRenderer<T, M> {
|
||||
private Supplier<BakedModel> modelSupplier;
|
||||
private final Supplier<BakedModel> modelSupplier;
|
||||
|
||||
public BakedModelFeatureRenderer(FeatureRendererContext<T, M> context, Supplier<BakedModel> modelSupplier) {
|
||||
super(context);
|
||||
|
@ -50,7 +52,7 @@ public class BakedModelFeatureRenderer<T extends LivingEntity, M extends EntityM
|
|||
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();
|
||||
}
|
||||
}
|
|
@ -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<Random> randomSupplier, RenderContext context) {
|
||||
context.pushTransform(q -> q.cullFace() != Direction.DOWN);
|
||||
super.emitBlockQuads(blockView, state, pos, randomSupplier, context);
|
||||
context.popTransform();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onInitializeClient() {
|
||||
ModelLoadingPlugin.register(pluginContext -> {
|
||||
pluginContext.addModels(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));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
|
@ -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.
|
||||
*
|
||||
* <p>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<Map<Identifier, JsonUnbakedModel>> loadModelReplacements(ResourceManager resourceManager, Executor executor) {
|
||||
return CompletableFuture.supplyAsync(() -> MODEL_REPLACEMENTS_FINDER.findResources(resourceManager), executor).thenCompose(models2 -> {
|
||||
ArrayList<CompletableFuture<Pair<Identifier, JsonUnbakedModel>>> list = new ArrayList<>(models2.size());
|
||||
|
||||
for (Map.Entry<Identifier, Resource> 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)));
|
||||
});
|
||||
}
|
||||
}
|
|
@ -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.Identifier;
|
|||
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 @@ public class SpecificModelReloadListener extends SinglePreparationResourceReload
|
|||
|
||||
@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
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"parent": "minecraft:block/cube_all",
|
||||
"textures": {
|
||||
"all": "minecraft:block/diamond_block"
|
||||
}
|
||||
}
|
|
@ -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"
|
||||
]
|
||||
}
|
||||
}
|
|
@ -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<T> {
|
||||
UnbakedModel load(T obj) throws ModelProviderException;
|
||||
}
|
||||
|
||||
public static class LoaderInstance implements ModelProviderContext {
|
||||
private final Logger logger;
|
||||
private final ResourceManager manager;
|
||||
private final List<ModelVariantProvider> modelVariantProviders;
|
||||
private final List<ModelResourceProvider> modelResourceProviders;
|
||||
private final List<ExtraModelProvider> 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<Identifier> addModel) {
|
||||
for (ExtraModelProvider appender : modelAppenders) {
|
||||
appender.provideExtraModels(manager, addModel);
|
||||
}
|
||||
}
|
||||
|
||||
private <T> UnbakedModel loadCustomModel(CustomModelItf<T> function, Collection<T> 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<T> 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<Function<ResourceManager, ModelVariantProvider>> variantProviderSuppliers = new ArrayList<>();
|
||||
private final List<Function<ResourceManager, ModelResourceProvider>> resourceProviderSuppliers = new ArrayList<>();
|
||||
private final List<ExtraModelProvider> 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<ResourceManager, ModelResourceProvider> providerSupplier) {
|
||||
resourceProviderSuppliers.add(providerSupplier);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerVariantProvider(Function<ResourceManager, ModelVariantProvider> providerSupplier) {
|
||||
variantProviderSuppliers.add(providerSupplier);
|
||||
}
|
||||
|
||||
public static LoaderInstance begin(ModelLoader loader, ResourceManager manager) {
|
||||
return new LoaderInstance((ModelLoadingRegistryImpl) INSTANCE, loader, manager);
|
||||
}
|
||||
}
|
|
@ -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<Identifier> modelsToLoad;
|
||||
@Final
|
||||
@Shadow
|
||||
private Map<Identifier, UnbakedModel> unbakedModels;
|
||||
@Shadow
|
||||
@Final
|
||||
private Map<Identifier, UnbakedModel> 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 = "<init>")
|
||||
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);
|
||||
}
|
||||
}
|
|
@ -1,12 +0,0 @@
|
|||
{
|
||||
"required": true,
|
||||
"package": "net.fabricmc.fabric.mixin.client.model",
|
||||
"compatibilityLevel": "JAVA_16",
|
||||
"client": [
|
||||
"BakedModelManagerMixin",
|
||||
"ModelLoaderMixin"
|
||||
],
|
||||
"injectors": {
|
||||
"defaultRequire": 1
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
|
@ -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"
|
||||
]
|
||||
}
|
||||
}
|
|
@ -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',
|
||||
|
|
|
@ -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<Identifier> FRAME_MODELS = new HashSet<>();
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public UnbakedModel loadModelResource(Identifier resourceId, ModelProviderContext context) {
|
||||
if (FRAME_MODELS.contains(resourceId)) {
|
||||
return new FrameUnbakedModel();
|
||||
}
|
||||
|
||||
return 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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -18,30 +18,47 @@ package net.fabricmc.fabric.test.renderer.simple.client;
|
|||
|
||||
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<Identifier> 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());
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -38,6 +38,7 @@ fabric-loot-api-v2-version=1.1.31
|
|||
fabric-loot-tables-v1-version=1.1.35
|
||||
fabric-message-api-v1-version=5.1.4
|
||||
fabric-mining-level-api-v1-version=2.1.43
|
||||
fabric-model-loading-api-v1-version=1.0.0
|
||||
fabric-models-v0-version=0.3.33
|
||||
fabric-networking-api-v1-version=1.3.4
|
||||
fabric-networking-v0-version=0.3.44
|
||||
|
|
|
@ -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'
|
||||
|
|
Loading…
Reference in a new issue