Finish porting Model Loading, FRAPI, and Indigo ()

This commit is contained in:
PepperCode1 2024-08-21 08:05:05 -07:00 committed by GitHub
parent 1bb677a646
commit 236902d814
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
37 changed files with 379 additions and 652 deletions

View file

@ -20,9 +20,7 @@ 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.util.Identifier;
/**
* Block state resolvers are responsible for mapping each {@link BlockState} of a block to an {@link UnbakedModel}.
@ -69,18 +67,5 @@ public interface BlockStateResolver {
* @param model the unbaked model for this block state
*/
void setModel(BlockState state, UnbakedModel model);
/**
* Loads a model using an {@link Identifier}, 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.
*/
ModelLoader loader();
}
}

View file

@ -16,8 +16,6 @@
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;
@ -37,7 +35,6 @@ import net.minecraft.util.Identifier;
*/
public final class DelegatingUnbakedModel implements UnbakedModel {
private final Identifier delegate;
private final List<Identifier> dependencies;
/**
* Constructs a new delegating model.
@ -46,23 +43,11 @@ public final class DelegatingUnbakedModel implements UnbakedModel {
*/
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) {
modelLoader.apply(delegate).setParents(modelLoader);
}*/
// TODO 24w44a
@Override
public void method_62326(class_10103 arg, class_10102 arg2) {
arg.method_62642(delegate);
}
@Override

View file

@ -47,7 +47,7 @@ public interface ModelLoadingPlugin {
* 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);
void initialize(Context pluginContext);
@ApiStatus.NonExtendable
interface Context {

View file

@ -25,7 +25,6 @@ import org.jetbrains.annotations.UnknownNullability;
import net.minecraft.client.render.model.BakedModel;
import net.minecraft.client.render.model.Baker;
import net.minecraft.client.render.model.ModelBakeSettings;
import net.minecraft.client.render.model.ModelLoader;
import net.minecraft.client.render.model.UnbakedModel;
import net.minecraft.client.texture.Sprite;
import net.minecraft.client.util.ModelIdentifier;
@ -109,19 +108,6 @@ public final class ModelModifier {
*/
@UnknownNullability("#resourceId() != null")
ModelIdentifier topLevelId();
/**
* Loads a model using an {@link Identifier}, 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.
*/
ModelLoader loader();
}
}
@ -177,11 +163,6 @@ public final class ModelModifier {
* {@linkplain Baker#bake load baked models}.
*/
Baker baker();
/**
* The current model loader instance, which changes between resource reloads.
*/
ModelLoader loader();
}
}
@ -250,11 +231,6 @@ public final class ModelModifier {
* {@linkplain Baker#bake load baked models}.
*/
Baker baker();
/**
* The current model loader instance, which changes between resource reloads.
*/
ModelLoader loader();
}
}

View file

@ -19,7 +19,6 @@ 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.util.Identifier;
@ -61,18 +60,5 @@ public interface ModelResolver {
* The identifier of the model to be loaded.
*/
Identifier id();
/**
* Loads a model using an {@link Identifier}, 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.
*/
ModelLoader loader();
}
}

View file

@ -48,7 +48,7 @@ public interface PreparableModelLoadingPlugin<T> {
* @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);
void initialize(T data, ModelLoadingPlugin.Context pluginContext);
@FunctionalInterface
interface DataLoader<T> {

View file

@ -16,16 +16,6 @@
package net.fabricmc.fabric.impl.client.model.loading;
import net.minecraft.client.render.model.UnbakedModel;
import net.minecraft.client.util.ModelIdentifier;
import net.minecraft.util.Identifier;
public interface ModelLoaderHooks {
ModelLoadingEventDispatcher fabric_getDispatcher();
UnbakedModel fabric_getMissingModel();
UnbakedModel fabric_getOrLoadModel(Identifier id);
void fabric_add(ModelIdentifier id, UnbakedModel model);
}

View file

@ -16,8 +16,12 @@
package net.fabricmc.fabric.impl.client.model.loading;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.function.Function;
@ -35,13 +39,14 @@ import net.minecraft.block.BlockState;
import net.minecraft.client.render.block.BlockModels;
import net.minecraft.client.render.model.BakedModel;
import net.minecraft.client.render.model.Baker;
import net.minecraft.client.render.model.BlockStatesLoader;
import net.minecraft.client.render.model.ModelBakeSettings;
import net.minecraft.client.render.model.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.state.StateManager;
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;
@ -51,24 +56,23 @@ import net.fabricmc.fabric.api.client.model.loading.v1.ModelResolver;
public class ModelLoadingEventDispatcher {
private static final Logger LOGGER = LoggerFactory.getLogger(ModelLoadingEventDispatcher.class);
public static final ThreadLocal<ModelLoadingEventDispatcher> CURRENT = new ThreadLocal<>();
private final ModelLoader loader;
private final ModelLoadingPluginContextImpl pluginContext;
private final ObjectArrayList<ModelResolverContext> modelResolverContextStack = new ObjectArrayList<>();
private final ModelResolverContext modelResolverContext = new ModelResolverContext();
private final BlockStateResolverContext blockStateResolverContext = new BlockStateResolverContext();
private final ObjectArrayList<OnLoadModifierContext> onLoadModifierContextStack = new ObjectArrayList<>();
private final OnLoadModifierContext onLoadModifierContext = new OnLoadModifierContext();
private final ObjectArrayList<BeforeBakeModifierContext> beforeBakeModifierContextStack = new ObjectArrayList<>();
private final ObjectArrayList<AfterBakeModifierContext> afterBakeModifierContextStack = new ObjectArrayList<>();
public ModelLoadingEventDispatcher(ModelLoader loader, List<ModelLoadingPlugin> plugins) {
this.loader = loader;
public ModelLoadingEventDispatcher(List<ModelLoadingPlugin> plugins) {
this.pluginContext = new ModelLoadingPluginContextImpl();
for (ModelLoadingPlugin plugin : plugins) {
try {
plugin.onInitializeModelLoader(pluginContext);
plugin.initialize(pluginContext);
} catch (Exception exception) {
LOGGER.error("Failed to initialize model loading plugin", exception);
}
@ -81,18 +85,30 @@ public class ModelLoadingEventDispatcher {
}
}
public boolean loadBlockStateModels(Identifier id, StateManager<Block, BlockState> stateManager) {
BlockStateResolver resolver = pluginContext.blockStateResolvers.get(id);
public BlockStatesLoader.class_10095 loadBlockStateModels() {
Map<ModelIdentifier, BlockStatesLoader.BlockModel> map = new HashMap<>();
if (resolver != null) {
resolveBlockStates(resolver, stateManager.getOwner(), id);
return true;
} else {
return false;
}
pluginContext.blockStateResolvers.forEach((block, resolver) -> {
Optional<RegistryKey<Block>> optionalKey = Registries.BLOCK.getKey(block);
if (optionalKey.isEmpty()) {
return;
}
Identifier blockId = optionalKey.get().getValue();
BiConsumer<BlockState, UnbakedModel> output = (state, model) -> {
ModelIdentifier modelId = BlockModels.getModelId(blockId, state);
map.put(modelId, new BlockStatesLoader.BlockModel(state, model));
};
resolveBlockStates(resolver, block, output);
});
return new BlockStatesLoader.class_10095(map);
}
private void resolveBlockStates(BlockStateResolver resolver, Block block, Identifier blockId) {
private void resolveBlockStates(BlockStateResolver resolver, Block block, BiConsumer<BlockState, UnbakedModel> output) {
BlockStateResolverContext context = blockStateResolverContext;
context.prepare(block);
@ -100,7 +116,6 @@ public class ModelLoadingEventDispatcher {
ImmutableList<BlockState> allStates = block.getStateManager().getStates();
boolean thrown = false;
// Call resolver
try {
resolver.resolveBlockStates(context);
} catch (Exception e) {
@ -108,34 +123,21 @@ public class ModelLoadingEventDispatcher {
thrown = true;
}
// Copy models over to the loader
if (thrown) {
UnbakedModel missingModel = ((ModelLoaderHooks) loader).fabric_getMissingModel();
if (!thrown) {
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(output);
} else {
for (BlockState state : allStates) {
@Nullable
UnbakedModel model = resolvedModels.get(state);
for (BlockState state : allStates) {
ModelIdentifier modelId = BlockModels.getModelId(blockId, state);
((ModelLoaderHooks) loader).fabric_add(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_add(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_add(modelId, missingModel);
} else {
((ModelLoaderHooks) loader).fabric_add(modelId, model);
if (model == null) {
LOGGER.error("Block state resolver did not provide a model for state {} in block {}. Using missing model.", state, block);
} else {
output.accept(state, model);
}
}
}
}
@ -145,31 +147,13 @@ public class ModelLoadingEventDispatcher {
@Nullable
public 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;
modelResolverContext.prepare(id);
return pluginContext.resolveModel().invoker().resolveModel(modelResolverContext);
}
public UnbakedModel modifyModelOnLoad(UnbakedModel model, @UnknownNullability Identifier resourceId, @UnknownNullability ModelIdentifier topLevelId) {
if (onLoadModifierContextStack.isEmpty()) {
onLoadModifierContextStack.add(new OnLoadModifierContext());
}
OnLoadModifierContext context = onLoadModifierContextStack.pop();
context.prepare(resourceId, topLevelId);
model = pluginContext.modifyModelOnLoad().invoker().modifyModelOnLoad(model, context);
onLoadModifierContextStack.push(context);
return model;
onLoadModifierContext.prepare(resourceId, topLevelId);
return pluginContext.modifyModelOnLoad().invoker().modifyModelOnLoad(model, onLoadModifierContext);
}
public UnbakedModel modifyModelBeforeBake(UnbakedModel model, @UnknownNullability Identifier resourceId, @UnknownNullability ModelIdentifier topLevelId, Function<SpriteIdentifier, Sprite> textureGetter, ModelBakeSettings settings, Baker baker) {
@ -201,7 +185,7 @@ public class ModelLoadingEventDispatcher {
return model;
}
private class ModelResolverContext implements ModelResolver.Context {
private static class ModelResolverContext implements ModelResolver.Context {
private Identifier id;
private void prepare(Identifier id) {
@ -212,19 +196,9 @@ public class ModelLoadingEventDispatcher {
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 static class BlockStateResolverContext implements BlockStateResolver.Context {
private Block block;
private final Reference2ReferenceMap<BlockState, UnbakedModel> models = new Reference2ReferenceOpenHashMap<>();
@ -251,19 +225,9 @@ public class ModelLoadingEventDispatcher {
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 static class OnLoadModifierContext implements ModelModifier.OnLoad.Context {
@UnknownNullability
private Identifier resourceId;
@UnknownNullability
@ -285,19 +249,9 @@ public class ModelLoadingEventDispatcher {
public ModelIdentifier topLevelId() {
return topLevelId;
}
@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 static class BeforeBakeModifierContext implements ModelModifier.BeforeBake.Context {
@UnknownNullability
private Identifier resourceId;
@UnknownNullability
@ -340,14 +294,9 @@ public class ModelLoadingEventDispatcher {
public Baker baker() {
return baker;
}
@Override
public ModelLoader loader() {
return loader;
}
}
private class AfterBakeModifierContext implements ModelModifier.AfterBake.Context {
private static class AfterBakeModifierContext implements ModelModifier.AfterBake.Context {
@UnknownNullability
private Identifier resourceId;
@UnknownNullability
@ -397,10 +346,5 @@ public class ModelLoadingEventDispatcher {
public Baker baker() {
return baker;
}
@Override
public ModelLoader loader() {
return loader;
}
}
}

View file

@ -17,7 +17,7 @@
package net.fabricmc.fabric.impl.client.model.loading;
import java.util.Collection;
import java.util.HashMap;
import java.util.IdentityHashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Objects;
@ -44,7 +44,7 @@ public class ModelLoadingPluginContextImpl implements ModelLoadingPlugin.Context
private static final Logger LOGGER = LoggerFactory.getLogger(ModelLoadingPluginContextImpl.class);
final Set<Identifier> extraModels = new LinkedHashSet<>();
final Map<Identifier, BlockStateResolver> blockStateResolvers = new HashMap<>();
final Map<Block, BlockStateResolver> blockStateResolvers = new IdentityHashMap<>();
private final Event<ModelResolver> modelResolvers = EventFactory.createArrayBacked(ModelResolver.class, resolvers -> context -> {
for (ModelResolver resolver : resolvers) {
@ -124,9 +124,7 @@ public class ModelLoadingPluginContextImpl implements ModelLoadingPlugin.Context
throw new IllegalArgumentException("Received unregistered block");
}
Identifier blockId = optionalKey.get().getValue();
if (blockStateResolvers.put(blockId, resolver) != null) {
if (blockStateResolvers.put(block, resolver) != null) {
throw new IllegalArgumentException("Duplicate block state resolver for " + block);
}
}

View file

@ -47,11 +47,6 @@ public final class ModelLoadingPluginManager {
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<>();
@ -63,12 +58,12 @@ public final class ModelLoadingPluginManager {
futures.add(preparePlugin(holder, resourceManager, executor));
}
return Util.combine(futures);
return Util.combineSafe(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));
return dataFuture.thenApply(data -> pluginContext -> holder.plugin.initialize(data, pluginContext));
}
private ModelLoadingPluginManager() { }

View file

@ -16,17 +16,24 @@
package net.fabricmc.fabric.mixin.client.model.loading;
import java.util.List;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionStage;
import java.util.concurrent.Executor;
import java.util.function.BiFunction;
import java.util.function.Function;
import com.llamalad7.mixinextras.injector.ModifyExpressionValue;
import com.llamalad7.mixinextras.injector.ModifyReturnValue;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.Unique;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.ModifyArg;
import org.spongepowered.asm.mixin.injection.Redirect;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
import net.minecraft.class_10097;
import net.minecraft.client.render.model.BakedModel;
@ -41,12 +48,15 @@ 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.ModelLoadingConstants;
import net.fabricmc.fabric.impl.client.model.loading.ModelLoadingEventDispatcher;
import net.fabricmc.fabric.impl.client.model.loading.ModelLoadingPluginManager;
@Mixin(BakedModelManager.class)
abstract class BakedModelManagerMixin implements FabricBakedModelManager {
@Unique
private volatile CompletableFuture<ModelLoadingEventDispatcher> eventDispatcherFuture;
@Shadow
private Map<ModelIdentifier, BakedModel> models;
@ -55,33 +65,64 @@ abstract class BakedModelManagerMixin implements FabricBakedModelManager {
return models.get(ModelLoadingConstants.toResourceModelId(id));
}
@Inject(method = "reload", at = @At("HEAD"))
private void onHeadReload(ResourceReloader.Synchronizer synchronizer, ResourceManager manager, Profiler prepareProfiler, Profiler applyProfiler, Executor prepareExecutor, Executor applyExecutor, CallbackInfoReturnable<CompletableFuture<Void>> cir) {
eventDispatcherFuture = ModelLoadingPluginManager.preparePlugins(manager, prepareExecutor).thenApplyAsync(ModelLoadingEventDispatcher::new);
}
@ModifyReturnValue(method = "reload", at = @At("RETURN"))
private CompletableFuture<Void> resetEventDispatcherFuture(CompletableFuture<Void> future) {
return future.thenApplyAsync(v -> {
eventDispatcherFuture = null;
return v;
});
}
@ModifyExpressionValue(method = "reload", at = @At(value = "INVOKE", target = "net/minecraft/client/render/model/BakedModelManager.reloadBlockStates(Lnet/minecraft/client/render/model/BlockStatesLoader;Lnet/minecraft/resource/ResourceManager;Ljava/util/concurrent/Executor;)Ljava/util/concurrent/CompletableFuture;"))
private CompletableFuture<BlockStatesLoader.class_10095> hookBlockStateModelLoading(CompletableFuture<BlockStatesLoader.class_10095> modelsFuture) {
CompletableFuture<BlockStatesLoader.class_10095> resolvedModelsFuture = eventDispatcherFuture.thenApplyAsync(ModelLoadingEventDispatcher::loadBlockStateModels);
return modelsFuture.thenCombine(resolvedModelsFuture, (models, resolvedModels) -> {
Map<ModelIdentifier, BlockStatesLoader.BlockModel> map = models.models();
if (!(map instanceof HashMap)) {
map = new HashMap<>(map);
models = new BlockStatesLoader.class_10095(map);
}
map.putAll(resolvedModels.models());
return models;
});
}
@Redirect(
method = "reload",
at = @At(
value = "INVOKE",
target = "java/util/concurrent/CompletableFuture.thenCombineAsync(Ljava/util/concurrent/CompletionStage;Ljava/util/function/BiFunction;Ljava/util/concurrent/Executor;)Ljava/util/concurrent/CompletableFuture;",
ordinal = 0,
remap = false
),
allow = 1)
private CompletableFuture<class_10097> loadModelPluginData(
))
private CompletableFuture<class_10097> hookModelDiscovery(
CompletableFuture<BlockStatesLoader.class_10095> self,
CompletionStage<Map<Identifier, UnbakedModel>> otherFuture,
BiFunction<BlockStatesLoader.class_10095, Map<Identifier, UnbakedModel>, class_10097> function,
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);
Executor executor) {
CompletableFuture<Pair<BlockStatesLoader.class_10095, Map<Identifier, UnbakedModel>>> pairFuture = self.thenCombine(otherFuture, Pair::new);
return pairFuture.thenCombineAsync(pluginsFuture, (pair, plugins) -> {
ModelLoadingPluginManager.CURRENT_PLUGINS.set(plugins);
class_10097 modelLoader = function.apply(pair.getLeft(), pair.getRight());
ModelLoadingPluginManager.CURRENT_PLUGINS.remove();
return modelLoader;
return pairFuture.thenCombineAsync(eventDispatcherFuture, (pair, eventDispatcher) -> {
ModelLoadingEventDispatcher.CURRENT.set(eventDispatcher);
class_10097 class_10097 = function.apply(pair.getLeft(), pair.getRight());
ModelLoadingEventDispatcher.CURRENT.remove();
return class_10097;
}, executor);
}
@ModifyArg(method = "reload", at = @At(value = "INVOKE", target = "java/util/concurrent/CompletableFuture.thenApplyAsync (Ljava/util/function/Function;Ljava/util/concurrent/Executor;)Ljava/util/concurrent/CompletableFuture;", ordinal = 1), index = 0)
private Function<Void, Object> hookModelBaking(Function<Void, Object> function) {
return v -> {
ModelLoadingEventDispatcher.CURRENT.set(eventDispatcherFuture.join());
Object bakingResult = function.apply(v);
ModelLoadingEventDispatcher.CURRENT.remove();
return bakingResult;
};
}
}

View file

@ -1,56 +0,0 @@
/*
* Copyright (c) 2016, 2017, 2018, 2019 FabricMC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.fabricmc.fabric.mixin.client.model.loading;
import org.jetbrains.annotations.Nullable;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Unique;
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.block.Block;
import net.minecraft.block.BlockState;
import net.minecraft.client.render.model.BlockStatesLoader;
import net.minecraft.state.StateManager;
import net.minecraft.util.Identifier;
import net.fabricmc.fabric.impl.client.model.loading.BlockStatesLoaderHooks;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
import java.util.List;
@Mixin(BlockStatesLoader.class)
abstract class BlockStatesLoaderMixin implements BlockStatesLoaderHooks {
@Unique
@Nullable
private LoadingOverride loadingOverride;
// TODO 24w33a - Mappings
@Inject(method = "method_62627", at = @At("HEAD"), cancellable = true)
private void onHeadLoadBlockStates(Identifier id, StateManager<Block, BlockState> stateManager, List<BlockStatesLoader.class_10094> list, CallbackInfoReturnable<BlockStatesLoader.class_10095> cir) {
if (loadingOverride != null && loadingOverride.loadBlockStates(id, stateManager)) {
cir.cancel();
}
}
@Override
public void fabric_setLoadingOverride(LoadingOverride override) {
loadingOverride = override;
}
}

View file

@ -16,162 +16,54 @@
package net.fabricmc.fabric.mixin.client.model.loading;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
import com.llamalad7.mixinextras.sugar.Local;
import it.unimi.dsi.fastutil.objects.ObjectLinkedOpenHashSet;
import org.spongepowered.asm.mixin.Final;
import org.jetbrains.annotations.Nullable;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.Unique;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Coerce;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.ModifyVariable;
import org.spongepowered.asm.mixin.injection.Redirect;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
import net.minecraft.client.color.block.BlockColors;
import net.minecraft.class_10096;
import net.minecraft.client.render.model.BakedModel;
import net.minecraft.client.render.model.Baker;
import net.minecraft.client.render.model.BlockStatesLoader;
import net.minecraft.client.render.model.ModelBakeSettings;
import net.minecraft.client.render.model.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.ModelIdentifier;
import net.minecraft.client.util.SpriteIdentifier;
import net.minecraft.util.Identifier;
import net.minecraft.util.profiler.Profiler;
import net.fabricmc.fabric.impl.client.model.loading.BakerImplHooks;
import net.fabricmc.fabric.impl.client.model.loading.BlockStatesLoaderHooks;
import net.fabricmc.fabric.impl.client.model.loading.ModelLoaderHooks;
import net.fabricmc.fabric.impl.client.model.loading.ModelLoadingConstants;
import net.fabricmc.fabric.impl.client.model.loading.ModelLoadingEventDispatcher;
import net.fabricmc.fabric.impl.client.model.loading.ModelLoadingPluginManager;
@Mixin(ModelLoader.class)
abstract class ModelLoaderMixin implements ModelLoaderHooks {
@Final
@Shadow
private Set<Identifier> modelsToLoad;
@Final
@Shadow
private Map<Identifier, UnbakedModel> unbakedModels;
@Shadow
@Final
private Map<ModelIdentifier, UnbakedModel> modelsToBake;
@Shadow
@Final
private UnbakedModel missingModel;
@Unique
@Nullable
private ModelLoadingEventDispatcher fabric_eventDispatcher;
@Unique
private final ObjectLinkedOpenHashSet<Identifier> modelLoadingStack = new ObjectLinkedOpenHashSet<>();
@Shadow
abstract UnbakedModel getOrLoadModel(Identifier id);
@Shadow
abstract void add(ModelIdentifier id, UnbakedModel unbakedModel);
@Shadow
abstract JsonUnbakedModel loadModelFromJson(Identifier id);
@Inject(method = "<init>", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/render/model/BlockStatesLoader;load()V"))
private void afterMissingModelInit(BlockColors blockColors, Profiler profiler, Map<Identifier, JsonUnbakedModel> jsonUnbakedModels, Map<Identifier, List<BlockStatesLoader.SourceTrackedData>> blockStates, CallbackInfo info, @Local BlockStatesLoader blockStatesLoader) {
// Sanity check
if (missingModel == null || !modelsToBake.containsKey(ModelLoader.MISSING_MODEL_ID)) {
throw new AssertionError("Missing model not initialized. This is likely a Fabric API porting bug.");
}
// Add the missing model to the cache since vanilla doesn't. Mods may load/bake the missing model directly.
unbakedModels.put(ModelLoader.MISSING_ID, missingModel);
profiler.swap("fabric_plugins_init");
fabric_eventDispatcher = new ModelLoadingEventDispatcher((ModelLoader) (Object) this, ModelLoadingPluginManager.CURRENT_PLUGINS.get());
fabric_eventDispatcher.addExtraModels(this::addExtraModel);
((BlockStatesLoaderHooks) blockStatesLoader).fabric_setLoadingOverride(fabric_eventDispatcher::loadBlockStateModels);
}
@Unique
private void addExtraModel(Identifier id) {
ModelIdentifier modelId = ModelLoadingConstants.toResourceModelId(id);
UnbakedModel unbakedModel = getOrLoadModel(id);
add(modelId, unbakedModel);
}
@Inject(method = "getOrLoadModel", at = @At("HEAD"), cancellable = true)
private void allowRecursiveLoading(Identifier id, CallbackInfoReturnable<UnbakedModel> cir) {
// If the stack is empty, this is the top-level call, so proceed as normal.
if (!modelLoadingStack.isEmpty()) {
if (unbakedModels.containsKey(id)) {
cir.setReturnValue(unbakedModels.get(id));
} else if (modelLoadingStack.contains(id)) {
throw new IllegalStateException("Circular reference while loading model '" + id + "' (" + modelLoadingStack.stream().map(i -> i + "->").collect(Collectors.joining()) + id + ")");
} else {
UnbakedModel model = loadModel(id);
unbakedModels.put(id, model);
// These will be loaded at the top-level call.
modelsToLoad.addAll(model.getModelDependencies());
cir.setReturnValue(model);
}
}
}
// This is the call that needs to be redirected to support ModelResolvers, but it returns a JsonUnbakedModel.
// Redirect it to always return null and handle the logic in a ModifyVariable right after the call.
@Redirect(method = "getOrLoadModel", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/render/model/ModelLoader;loadModelFromJson(Lnet/minecraft/util/Identifier;)Lnet/minecraft/client/render/model/json/JsonUnbakedModel;"))
private JsonUnbakedModel cancelLoadModelFromJson(ModelLoader self, Identifier id) {
return null;
}
@ModifyVariable(method = "getOrLoadModel", at = @At(value = "INVOKE_ASSIGN", target = "Lnet/minecraft/client/render/model/ModelLoader;loadModelFromJson(Lnet/minecraft/util/Identifier;)Lnet/minecraft/client/render/model/json/JsonUnbakedModel;"))
private UnbakedModel doLoadModel(UnbakedModel model, @Local(ordinal = 1) Identifier id) {
return loadModel(id);
}
@Unique
private UnbakedModel loadModel(Identifier id) {
modelLoadingStack.add(id);
try {
UnbakedModel model = fabric_eventDispatcher.resolveModel(id);
if (model == null) {
model = loadModelFromJson(id);
}
return fabric_eventDispatcher.modifyModelOnLoad(model, id, null);
} finally {
modelLoadingStack.removeLast();
}
}
@ModifyVariable(method = "add", at = @At("HEAD"), argsOnly = true)
private UnbakedModel onAdd(UnbakedModel model, ModelIdentifier id) {
if (ModelLoadingConstants.isResourceModelId(id)) {
return model;
}
return fabric_eventDispatcher.modifyModelOnLoad(model, null, id);
@Inject(method = "<init>", at = @At("RETURN"))
private void onReturnInit(CallbackInfo ci) {
fabric_eventDispatcher = ModelLoadingEventDispatcher.CURRENT.get();
}
@WrapOperation(method = "method_61072(Lnet/minecraft/client/render/model/ModelLoader$SpriteGetter;Lnet/minecraft/client/util/ModelIdentifier;Lnet/minecraft/client/render/model/UnbakedModel;)V", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/render/model/ModelLoader$BakerImpl;bake(Lnet/minecraft/client/render/model/UnbakedModel;Lnet/minecraft/client/render/model/ModelBakeSettings;)Lnet/minecraft/client/render/model/BakedModel;"))
private BakedModel wrapSingleOuterBake(@Coerce Baker baker, UnbakedModel unbakedModel, ModelBakeSettings settings, Operation<BakedModel> operation, ModelLoader.SpriteGetter spriteGetter, ModelIdentifier id) {
if (ModelLoadingConstants.isResourceModelId(id) || id.equals(ModelLoader.MISSING_MODEL_ID)) {
if (fabric_eventDispatcher == null) {
return operation.call(baker, unbakedModel, settings);
}
if (ModelLoadingConstants.isResourceModelId(id) || id.equals(class_10096.field_53661)) {
// Call the baker instead of the operation to ensure the baked model is cached and doesn't end up going
// through events twice.
// This ignores the UnbakedModel in modelsToBake but it should be the same as the one in unbakedModels.
// This ignores the UnbakedModel in field_53662 (top-level model map) but it should be the same as the one in field_53663 (resource model map).
return baker.bake(id.id(), settings);
}
@ -185,19 +77,4 @@ abstract class ModelLoaderMixin implements ModelLoaderHooks {
public ModelLoadingEventDispatcher fabric_getDispatcher() {
return fabric_eventDispatcher;
}
@Override
public UnbakedModel fabric_getMissingModel() {
return missingModel;
}
@Override
public UnbakedModel fabric_getOrLoadModel(Identifier id) {
return getOrLoadModel(id);
}
@Override
public void fabric_add(ModelIdentifier id, UnbakedModel model) {
add(id, model);
}
}

View file

@ -0,0 +1,95 @@
/*
* Copyright (c) 2016, 2017, 2018, 2019 FabricMC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.fabricmc.fabric.mixin.client.model.loading;
import org.jetbrains.annotations.Nullable;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.Unique;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.ModifyVariable;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import net.minecraft.class_10097;
import net.minecraft.client.render.model.BlockStatesLoader;
import net.minecraft.client.render.model.UnbakedModel;
import net.minecraft.client.util.ModelIdentifier;
import net.minecraft.util.Identifier;
import net.fabricmc.fabric.impl.client.model.loading.ModelLoadingConstants;
import net.fabricmc.fabric.impl.client.model.loading.ModelLoadingEventDispatcher;
@Mixin(class_10097.class)
abstract class class_10097Mixin {
@Unique
@Nullable
private ModelLoadingEventDispatcher fabric_eventDispatcher;
@Shadow
abstract UnbakedModel method_62638(Identifier identifier);
@Shadow
abstract void method_62635(ModelIdentifier modelIdentifier, UnbakedModel unbakedModel);
@Inject(method = "<init>", at = @At("RETURN"))
private void onReturnInit(CallbackInfo ci) {
fabric_eventDispatcher = ModelLoadingEventDispatcher.CURRENT.get();
}
@Inject(method = "method_62632", at = @At("RETURN"))
private void onAddStandardModels(BlockStatesLoader.class_10095 blockStateModels, CallbackInfo ci) {
if (fabric_eventDispatcher == null) {
return;
}
fabric_eventDispatcher.addExtraModels(id -> {
ModelIdentifier modelId = ModelLoadingConstants.toResourceModelId(id);
UnbakedModel unbakedModel = method_62638(id);
method_62635(modelId, unbakedModel);
});
}
@ModifyVariable(method = "method_62640", at = @At(value = "STORE", ordinal = 0), ordinal = 0)
@Nullable
private UnbakedModel onLoadResourceModel(@Nullable UnbakedModel model, Identifier id) {
if (fabric_eventDispatcher == null) {
return model;
}
UnbakedModel resolvedModel = fabric_eventDispatcher.resolveModel(id);
if (resolvedModel != null) {
model = resolvedModel;
}
return fabric_eventDispatcher.modifyModelOnLoad(model, id, null);
}
@ModifyVariable(method = "method_62635", at = @At("HEAD"), argsOnly = true)
private UnbakedModel onAddTopLevelModel(UnbakedModel model, ModelIdentifier modelId) {
if (fabric_eventDispatcher == null) {
return model;
}
if (ModelLoadingConstants.isResourceModelId(modelId)) {
return model;
}
return fabric_eventDispatcher.modifyModelOnLoad(model, null, modelId);
}
}

View file

@ -4,9 +4,9 @@
"compatibilityLevel": "JAVA_21",
"client": [
"BakedModelManagerMixin",
"BlockStatesLoaderMixin",
"ModelLoaderMixin",
"ModelLoaderBakerImplMixin"
"class_10097Mixin",
"ModelLoaderBakerImplMixin",
"ModelLoaderMixin"
],
"injectors": {
"defaultRequire": 1

View file

@ -18,8 +18,6 @@ package net.fabricmc.fabric.test.model.loading;
import java.util.function.Supplier;
import net.minecraft.client.render.entity.state.EntityRenderState;
import org.joml.AxisAngle4f;
import org.joml.Quaternionf;
@ -31,29 +29,27 @@ import net.minecraft.client.render.VertexConsumerProvider;
import net.minecraft.client.render.entity.feature.FeatureRenderer;
import net.minecraft.client.render.entity.feature.FeatureRendererContext;
import net.minecraft.client.render.entity.model.EntityModel;
import net.minecraft.client.render.entity.state.LivingEntityRenderState;
import net.minecraft.client.render.model.BakedModel;
import net.minecraft.client.util.math.MatrixStack;
import net.minecraft.entity.LivingEntity;
import net.minecraft.util.math.MathHelper;
public class BakedModelFeatureRenderer<T extends EntityRenderState, M extends EntityModel<T>> extends FeatureRenderer<T, M> {
public class BakedModelFeatureRenderer<S extends LivingEntityRenderState, M extends EntityModel<S>> extends FeatureRenderer<S, M> {
private final Supplier<BakedModel> modelSupplier;
public BakedModelFeatureRenderer(FeatureRendererContext<T, M> context, Supplier<BakedModel> modelSupplier) {
public BakedModelFeatureRenderer(FeatureRendererContext<S, M> context, Supplier<BakedModel> modelSupplier) {
super(context);
this.modelSupplier = modelSupplier;
}
@Override
public void render(MatrixStack matrices, VertexConsumerProvider vertexConsumers, int light, T state, float limbAngle, float limbDistance) {
public void render(MatrixStack matrices, VertexConsumerProvider vertexConsumers, int light, S state, float limbAngle, float limbDistance) {
BakedModel model = modelSupplier.get();
VertexConsumer vertices = vertexConsumers.getBuffer(TexturedRenderLayers.getEntityCutout());
matrices.push();
//matrices.multiply(Vector3f.POSITIVE_Y.getDegreesQuaternion(headYaw));
//matrices.multiply(Vector3f.POSITIVE_X.getDegreesQuaternion(headPitch));
// TODO 24w33a Is limbAngle the same as animationProgress? Probably not
matrices.multiply(new Quaternionf(new AxisAngle4f(limbAngle * 0.07F, 0, 1, 0)));
matrices.multiply(new Quaternionf(new AxisAngle4f(state.age * 0.07F - state.bodyYaw * MathHelper.RADIANS_PER_DEGREE, 0, 1, 0)));
matrices.scale(-0.75F, -0.75F, 0.75F);
float aboveHead = (float) (Math.sin(limbAngle * 0.08F)) * 0.5F + 0.5F;
float aboveHead = (float) (Math.sin(state.age * 0.08F)) * 0.5F + 0.5F;
matrices.translate(-0.5F, 0.75F + aboveHead, -0.5F);
MinecraftClient.getInstance().getBlockRenderManager().getModelRenderer().render(matrices.peek(), vertices, null, model, 1, 1, 1, light, OverlayTexture.DEFAULT_UV);
matrices.pop();

View file

@ -26,7 +26,6 @@ import net.minecraft.class_10096;
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;
@ -69,6 +68,7 @@ public class ModelTestModClient implements ClientModInitializer {
public void onInitializeClient() {
ModelLoadingPlugin.register(pluginContext -> {
pluginContext.addModels(HALF_RED_SAND_MODEL_ID);
// remove bottom face of gold blocks
pluginContext.modifyModelAfterBake().register(ModelModifier.WRAP_PHASE, (model, context) -> {
Identifier id = context.resourceId();
@ -79,17 +79,19 @@ public class ModelTestModClient implements ClientModInitializer {
return model;
});
// make fences with west: true and everything else false appear to be a missing model visually
ModelIdentifier fenceId = BlockModels.getModelId(Blocks.OAK_FENCE.getDefaultState().with(HorizontalConnectingBlock.WEST, true));
pluginContext.modifyModelOnLoad().register(ModelModifier.OVERRIDE_PHASE, (model, context) -> {
ModelIdentifier id = context.topLevelId();
if (id != null && id.equals(fenceId)) {
return context.getOrLoadModel(class_10096.field_53660);
return new DelegatingUnbakedModel(class_10096.field_53660);
}
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) -> {
@ -109,14 +111,15 @@ public class ModelTestModClient implements ClientModInitializer {
// All the block state models are top-level...
// Use a delegating unbaked model to make sure the identical models only get baked a single time.
Identifier wheatStage0Id = Identifier.ofVanilla("block/wheat_stage0");
UnbakedModel stage0Model = new DelegatingUnbakedModel(wheatStage0Id);
Identifier wheatStage7Id = Identifier.ofVanilla("block/wheat_stage7");
UnbakedModel wheatStage0Model = new DelegatingUnbakedModel(wheatStage0Id);
UnbakedModel wheatStage7Model = new DelegatingUnbakedModel(wheatStage7Id);
for (int age = 0; age <= 6; age++) {
context.setModel(state.with(CropBlock.AGE, age), stage0Model);
context.setModel(state.with(CropBlock.AGE, age), wheatStage0Model);
}
context.setModel(state.with(CropBlock.AGE, 7), context.getOrLoadModel(Identifier.ofVanilla("block/wheat_stage7")));
context.setModel(state.with(CropBlock.AGE, 7), wheatStage7Model);
});
});

View file

@ -1,110 +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.loading;
import static net.fabricmc.fabric.test.model.loading.ModelTestModClient.id;
import com.mojang.logging.LogUtils;
import net.minecraft.class_10096;
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 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 = Identifier.ofVanilla("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(class_10096.field_53660);
} 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(class_10096.field_53660);
} 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;
Identifier id = context.resourceId();
if (id != null) {
if (id.equals(NESTED_MODEL_3)) {
LOGGER.info(" Nested model 4 started loading");
ret = context.getOrLoadModel(NESTED_MODEL_4);
LOGGER.info(" Nested model 4 finished loading");
if (!id.equals(context.resourceId())) {
throw new AssertionError("Context object should not have changed.");
}
} else if (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;
});
});
}
}

View file

@ -12,7 +12,6 @@
"entrypoints": {
"client": [
"net.fabricmc.fabric.test.model.loading.ModelTestModClient",
"net.fabricmc.fabric.test.model.loading.NestedModelLoadingTest",
"net.fabricmc.fabric.test.model.loading.PreparablePluginTest"
]
}

View file

@ -16,6 +16,7 @@
package net.fabricmc.fabric.api.renderer.v1.mesh;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.Nullable;
import org.joml.Vector2f;
import org.joml.Vector2fc;
@ -292,14 +293,39 @@ public interface MutableQuadView extends QuadView {
/**
* Enables bulk vertex data transfer using the standard Minecraft quad format.
*
* <p>The material applied to this quad view might be slightly different from the {@code material} parameter regarding diffuse shading.
* If either the baked quad {@link BakedQuad#hasShade() does not have shade} or the material {@link MaterialFinder#disableDiffuse(boolean) does not have shade},
* diffuse shading will be disabled for this quad view.
* This is reflected in the quad view's {@link #material()}, but the {@code material} parameter is unchanged (it is immutable anyway).
*
* <p>The {@linkplain BakedQuad#getLightEmission() baked quad's light emission} will be applied to the lightmap values from the vertex data
* after copying.
*
* <p>Calling this method does not emit the quad.
*/
default MutableQuadView fromVanilla(BakedQuad quad, RenderMaterial material, @Nullable Direction cullFace) {
return fromVanilla(quad, material, cullFace, true);
}
// TODO: If this is unmarked as experimental, update the javadoc of the other overloads.
/**
* Enables bulk vertex data transfer using the standard Minecraft quad format.
*
* <p>The material applied to this quad view might be slightly different from the {@code material} parameter regarding diffuse shading.
* If either the baked quad {@link BakedQuad#hasShade() does not have shade} or the material {@link MaterialFinder#disableDiffuse(boolean) does not have shade},
* diffuse shading will be disabled for this quad view.
* This is reflected in the quad view's {@link #material()}, but the {@code material} parameter is unchanged (it is immutable anyway).
*
* <p>If {@code applyLightEmission} is {@code true}, the {@linkplain BakedQuad#getLightEmission() baked quad's light emission} will be applied
* to the lightmap values from the vertex data after copying. Otherwise, the light emission will be ignored.
*
* <p>Calling this method does not emit the quad.
*
* @apiNote This method is marked as experimental because future snapshots may change the item renderer to also respect quad light emission,
* in which case this method will be removed. See <a href="https://bugs.mojang.com/browse/MC-275296">MC-275296</a>.
*/
MutableQuadView fromVanilla(BakedQuad quad, RenderMaterial material, @Nullable Direction cullFace);
@ApiStatus.Experimental
MutableQuadView fromVanilla(BakedQuad quad, RenderMaterial material, @Nullable Direction cullFace, boolean applyLightEmission);
/**
* @deprecated Use {@link #color(int, int)} instead.

View file

@ -16,6 +16,7 @@
package net.fabricmc.fabric.api.renderer.v1.mesh;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.Nullable;
import org.joml.Vector2f;
import org.joml.Vector2fc;
@ -138,7 +139,14 @@ public interface QuadEmitter extends MutableQuadView {
QuadEmitter fromVanilla(int[] quadData, int startIndex);
@Override
QuadEmitter fromVanilla(BakedQuad quad, RenderMaterial material, @Nullable Direction cullFace);
default QuadEmitter fromVanilla(BakedQuad quad, RenderMaterial material, @Nullable Direction cullFace) {
MutableQuadView.super.fromVanilla(quad, material, cullFace);
return this;
}
@ApiStatus.Experimental
@Override
QuadEmitter fromVanilla(BakedQuad quad, RenderMaterial material, @Nullable Direction cullFace, boolean applyLightEmission);
/**
* Tolerance for determining if the depth parameter to {@link #square(Direction, float, float, float, float, float)}

View file

@ -21,6 +21,7 @@ import org.jetbrains.annotations.Nullable;
import org.joml.Vector2f;
import org.joml.Vector3f;
import net.minecraft.client.render.LightmapTextureManager;
import net.minecraft.client.render.VertexFormats;
import net.minecraft.client.render.model.BakedQuad;
import net.minecraft.client.texture.Sprite;
@ -200,7 +201,23 @@ public interface QuadView {
// Mimic material properties to the largest possible extent
int outputColorIndex = material().disableColorIndex() ? -1 : colorIndex();
boolean outputShade = !material().disableDiffuse();
return new BakedQuad(vertexData, outputColorIndex, lightFace(), sprite, outputShade, 0 /* TODO 24w33a */);
// The output light emission is equal to the minimum of all four sky light values and all four block light values.
int outputLightEmission = 15;
for (int i = 0; i < 4; i++) {
int lightmap = lightmap(i);
if (lightmap == 0) {
outputLightEmission = 0;
break;
}
int blockLight = LightmapTextureManager.getBlockLightCoordinates(lightmap);
int skyLight = LightmapTextureManager.getSkyLightCoordinates(lightmap);
outputLightEmission = Math.min(outputLightEmission, Math.min(blockLight, skyLight));
}
return new BakedQuad(vertexData, outputColorIndex, lightFace(), sprite, outputShade, outputLightEmission);
}
/**

View file

@ -78,7 +78,7 @@ public class VanillaModelEncoder {
for (int j = 0; j < count; j++) {
final BakedQuad q = quads.get(j);
emitter.fromVanilla(q, STANDARD_MATERIAL, cullFace);
emitter.fromVanilla(q, STANDARD_MATERIAL, cullFace, false);
emitter.emit();
}
}

View file

@ -16,8 +16,6 @@
package net.fabricmc.fabric.test.renderer;
import net.minecraft.util.ActionResult;
import org.jetbrains.annotations.Nullable;
import net.minecraft.block.Block;
@ -27,6 +25,7 @@ import net.minecraft.block.Blocks;
import net.minecraft.block.entity.BlockEntity;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.item.ItemStack;
import net.minecraft.util.ActionResult;
import net.minecraft.util.Hand;
import net.minecraft.util.hit.BlockHitResult;
import net.minecraft.util.math.BlockPos;
@ -46,8 +45,6 @@ public class FrameBlock extends Block implements BlockEntityProvider, FabricBloc
@Override
public ActionResult onUseWithItem(ItemStack stack, BlockState blockState, World world, BlockPos pos, PlayerEntity player, Hand hand, BlockHitResult blockHitResult) {
if (world.getBlockEntity(pos) instanceof FrameBlockEntity frame) {
Block handBlock = Block.getBlockFromItem(stack.getItem());
@Nullable
Block currentBlock = frame.getBlock();
@ -59,26 +56,31 @@ public class FrameBlock extends Block implements BlockEntityProvider, FabricBloc
frame.setBlock(null);
}
if (world.isClient)
return ActionResult.SUCCESS;
return ActionResult.SUCCESS;
} else {
return ActionResult.PASS;
}
return ActionResult.PASS_TO_DEFAULT_BLOCK_ACTION;
}
Block handBlock = Block.getBlockFromItem(stack.getItem());
// getBlockFromItem will return air if we do not have a block item in hand
if (handBlock == Blocks.AIR) {
return ActionResult.FAIL;
return ActionResult.PASS;
}
// Do not allow blocks that may have a block entity
if (handBlock instanceof BlockEntityProvider) {
return ActionResult.FAIL;
return ActionResult.PASS;
}
stack.decrement(1);
if (currentBlock == handBlock) {
return ActionResult.PASS;
}
if (!world.isClient()) {
stack.decrementUnlessCreative(1, player);
if (currentBlock != null) {
player.getInventory().offerOrDrop(new ItemStack(currentBlock));
}
@ -86,11 +88,10 @@ public class FrameBlock extends Block implements BlockEntityProvider, FabricBloc
frame.setBlock(handBlock);
}
if (world.isClient())
return ActionResult.SUCCESS;
return ActionResult.SUCCESS;
}
return ActionResult.FAIL;
return ActionResult.PASS_TO_DEFAULT_BLOCK_ACTION;
}
@Nullable

View file

@ -16,8 +16,6 @@
package net.fabricmc.fabric.test.renderer.client;
import java.util.Collection;
import java.util.Collections;
import java.util.function.Function;
import org.jetbrains.annotations.Nullable;
@ -41,19 +39,8 @@ import net.fabricmc.fabric.api.renderer.v1.mesh.QuadEmitter;
public class FrameUnbakedModel implements UnbakedModel {
private static final SpriteIdentifier OBSIDIAN_SPRITE_ID = new SpriteIdentifier(SpriteAtlasTexture.BLOCK_ATLAS_TEXTURE, Identifier.ofVanilla("block/obsidian"));
// TODO 24w33a
/*@Override
public Collection<Identifier> getModelDependencies() {
return Collections.emptySet();
}
@Override
public void setParents(Function<Identifier, UnbakedModel> modelLoader) {
}*/
@Override
public void method_62326(class_10103 arg, class_10102 arg2) {
}
/*

View file

@ -16,8 +16,6 @@
package net.fabricmc.fabric.test.renderer.client;
import java.util.Collection;
import java.util.Collections;
import java.util.function.Function;
import org.jetbrains.annotations.Nullable;
@ -56,19 +54,8 @@ public class OctagonalColumnUnbakedModel implements UnbakedModel {
this.shadeMode = shadeMode;
}
// TODO 24w33a
/*@Override
public Collection<Identifier> getModelDependencies() {
return Collections.emptySet();
}
@Override
public void setParents(Function<Identifier, UnbakedModel> modelLoader) {
}*/
@Override
public void method_62326(class_10103 arg, class_10102 arg2) {
}
@Override

View file

@ -16,8 +16,6 @@
package net.fabricmc.fabric.test.renderer.client;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.function.Function;
import java.util.stream.Stream;
@ -31,7 +29,6 @@ import net.minecraft.client.render.model.UnbakedModel;
import net.minecraft.client.texture.Sprite;
import net.minecraft.client.util.SpriteIdentifier;
import net.minecraft.screen.PlayerScreenHandler;
import net.minecraft.util.Identifier;
import net.fabricmc.fabric.test.renderer.RendererTest;
@ -40,19 +37,8 @@ public class PillarUnbakedModel implements UnbakedModel {
.map(suffix -> new SpriteIdentifier(PlayerScreenHandler.BLOCK_ATLAS_TEXTURE, RendererTest.id("block/pillar_" + suffix)))
.toList();
// TODO 24w33a
/*@Override
public Collection<Identifier> getModelDependencies() {
return Collections.emptySet();
}
@Override
public void setParents(Function<Identifier, UnbakedModel> modelLoader) {
}*/
@Override
public void method_62326(class_10103 arg, class_10102 arg2) {
}
@Nullable

View file

@ -16,8 +16,6 @@
package net.fabricmc.fabric.test.renderer.client;
import java.util.Collection;
import java.util.Collections;
import java.util.function.Function;
import org.jetbrains.annotations.Nullable;
@ -34,21 +32,10 @@ public class RiverstoneUnbakedModel implements UnbakedModel {
private static final Identifier STONE_MODEL_ID = Identifier.ofVanilla("block/stone");
private static final Identifier GOLD_BLOCK_MODEL_ID = Identifier.ofVanilla("block/gold_block");
// TODO 24w33a
/*@Override
public Collection<Identifier> getModelDependencies() {
return Collections.emptySet();
}
@Override
public void setParents(Function<Identifier, UnbakedModel> modelLoader) {
modelLoader.apply(STONE_MODEL_ID).setParents(modelLoader);
modelLoader.apply(GOLD_BLOCK_MODEL_ID).setParents(modelLoader);
}*/
@Override
public void method_62326(class_10103 arg, class_10102 arg2) {
arg.method_62642(STONE_MODEL_ID);
arg.method_62642(GOLD_BLOCK_MODEL_ID);
}
@Nullable

View file

@ -25,7 +25,6 @@ import net.fabricmc.fabric.api.renderer.v1.material.MaterialFinder;
import net.fabricmc.fabric.api.renderer.v1.material.RenderMaterial;
import net.fabricmc.fabric.api.renderer.v1.mesh.MeshBuilder;
import net.fabricmc.fabric.impl.client.indigo.renderer.material.MaterialFinderImpl;
import net.fabricmc.fabric.impl.client.indigo.renderer.material.RenderMaterialImpl;
import net.fabricmc.fabric.impl.client.indigo.renderer.mesh.MeshBuilderImpl;
/**

View file

@ -36,6 +36,7 @@ import net.minecraft.block.BlockState;
import net.minecraft.client.render.LightmapTextureManager;
import net.minecraft.client.render.WorldRenderer;
import net.minecraft.client.render.block.BlockModelRenderer;
import net.minecraft.client.render.model.BakedQuad;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Direction;
import net.minecraft.util.math.MathHelper;
@ -147,17 +148,18 @@ public abstract class AoCalculator {
// Because this instance is effectively thread-local, we preserve instances
// to avoid making a new allocation each call.
private final float[] vanillaAoData = new float[Direction.values().length * 2];
private final BitSet vanillaAoControlBits = new BitSet(3);
private final BitSet vanillaAoFlags = new BitSet(3);
private final int[] vertexData = new int[EncodingFormat.QUAD_STRIDE];
private final DummyBakedQuad dummyBakedQuad = new DummyBakedQuad();
private void calcVanilla(QuadViewImpl quad, float[] aoDest, int[] lightDest) {
vanillaAoControlBits.clear();
vanillaAoFlags.clear();
final Direction lightFace = quad.lightFace();
quad.toVanilla(vertexData, 0);
dummyBakedQuad.prepare(quad.lightFace(), quad.hasShade());
VanillaAoHelper.updateShape(blockInfo.blockView, blockInfo.blockState, blockInfo.blockPos, vertexData, lightFace, vanillaAoData, vanillaAoControlBits);
// TODO 24w33a
//vanillaCalc.apply(blockInfo.blockView, blockInfo.blockState, blockInfo.blockPos, lightFace, vanillaAoData, vanillaAoControlBits, quad.hasShade());
VanillaAoHelper.getQuadDimensions(blockInfo.blockView, blockInfo.blockState, blockInfo.blockPos, vertexData, lightFace, vanillaAoData, vanillaAoFlags);
vanillaCalc.apply(blockInfo.blockView, blockInfo.blockState, blockInfo.blockPos, vanillaAoData, vanillaAoFlags, dummyBakedQuad);
System.arraycopy(vanillaCalc.brightness, 0, aoDest, 0, 4);
System.arraycopy(vanillaCalc.light, 0, lightDest, 0, 4);
@ -582,4 +584,31 @@ public abstract class AoCalculator {
if (b == 0) return a;
return Math.min(a, b);
}
// This quad is passed to the vanilla AO calc. It only calls getFace, isEmissive, getLightEmission, and hasShade.
// Since Indigo already applies vanilla's light emission value in MutableQuadView#fromVanilla, this quad should not
// provide its own light emission.
private static class DummyBakedQuad extends BakedQuad {
private Direction lightFace;
private boolean shade;
DummyBakedQuad() {
super(new int[32], -1, Direction.UP, null, true, 0);
}
public void prepare(Direction lightFace, boolean shade) {
this.lightFace = lightFace;
this.shade = shade;
}
@Override
public Direction getFace() {
return lightFace;
}
@Override
public boolean hasShade() {
return shade;
}
}
}

View file

@ -19,21 +19,18 @@ package net.fabricmc.fabric.impl.client.indigo.renderer.aocalc;
import java.util.BitSet;
import net.minecraft.block.BlockState;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.render.block.BlockModelRenderer;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Direction;
import net.minecraft.world.BlockRenderView;
public class VanillaAoHelper {
public final class VanillaAoHelper {
// Renderer method we call isn't declared as static, but uses no
// instance data and is called from multiple threads in vanilla also.
private static BlockModelRenderer blockRenderer;
private static BlockModelRenderer BLOCK_RENDERER = MinecraftClient.getInstance().getBlockRenderManager().getModelRenderer();
public static void initialize(BlockModelRenderer instance) {
blockRenderer = instance;
}
public static void updateShape(BlockRenderView blockRenderView, BlockState blockState, BlockPos pos, int[] vertexData, Direction face, float[] aoData, BitSet controlBits) {
blockRenderer.getQuadDimensions(blockRenderView, blockState, pos, vertexData, face, aoData, controlBits);
public static void getQuadDimensions(BlockRenderView blockRenderView, BlockState blockState, BlockPos pos, int[] vertexData, Direction face, float[] aoData, BitSet controlBits) {
BLOCK_RENDERER.getQuadDimensions(blockRenderView, blockState, pos, vertexData, face, aoData, controlBits);
}
}

View file

@ -30,6 +30,7 @@ import static net.fabricmc.fabric.impl.client.indigo.renderer.mesh.EncodingForma
import org.jetbrains.annotations.Nullable;
import net.minecraft.client.render.LightmapTextureManager;
import net.minecraft.client.render.model.BakedQuad;
import net.minecraft.client.texture.Sprite;
import net.minecraft.util.math.Direction;
@ -194,7 +195,7 @@ public abstract class MutableQuadViewImpl extends QuadViewImpl implements QuadEm
}
@Override
public final MutableQuadViewImpl fromVanilla(BakedQuad quad, RenderMaterial material, @Nullable Direction cullFace) {
public final MutableQuadViewImpl fromVanilla(BakedQuad quad, RenderMaterial material, @Nullable Direction cullFace, boolean applyLightEmission) {
fromVanilla(quad.getVertexData(), 0);
data[baseIndex + HEADER_BITS] = EncodingFormat.cullFace(0, cullFace);
nominalFace(quad.getFace());
@ -204,6 +205,14 @@ public abstract class MutableQuadViewImpl extends QuadViewImpl implements QuadEm
material = RenderMaterialImpl.setDisableDiffuse((RenderMaterialImpl) material, true);
}
if (applyLightEmission && quad.isEmissive()) {
int lightEmission = quad.getLightEmission();
for (int i = 0; i < 4; i++) {
lightmap(i, LightmapTextureManager.applyEmission(lightmap(i), lightEmission));
}
}
material(material);
tag(0);
return this;

View file

@ -111,9 +111,7 @@ public class BlockRenderInfo {
if ((cullCompletionFlags & mask) == 0) {
cullCompletionFlags |= mask;
// TODO 24w33a - Double check this
//if (Block.shouldDrawSide(blockState, blockView, blockPos, face, searchPos.set(blockPos, face))) {
if (Block.shouldDrawSide(blockState, blockState, face)) {
if (Block.shouldDrawSide(blockState, blockView.getBlockState(searchPos.set(blockPos, face)), face)) {
cullResultFlags |= mask;
return true;
} else {

View file

@ -120,7 +120,7 @@ public class ItemRenderContext extends AbstractRenderContext {
return vanillaModelConsumer;
}
public void renderModel(ItemStack itemStack, ModelTransformationMode transformMode, boolean invert, MatrixStack matrixStack, VertexConsumerProvider vertexConsumerProvider, int lightmap, int overlay, BakedModel model) {
public void renderModel(ItemStack itemStack, ModelTransformationMode transformMode, MatrixStack matrixStack, VertexConsumerProvider vertexConsumerProvider, int lightmap, int overlay, BakedModel model) {
this.itemStack = itemStack;
this.transformMode = transformMode;
this.matrixStack = matrixStack;

View file

@ -31,7 +31,6 @@ import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.random.Random;
import net.minecraft.world.BlockRenderView;
import net.fabricmc.fabric.impl.client.indigo.renderer.aocalc.VanillaAoHelper;
import net.fabricmc.fabric.impl.client.indigo.renderer.render.BlockRenderContext;
@Mixin(BlockModelRenderer.class)
@ -47,9 +46,4 @@ public abstract class BlockModelRendererMixin {
ci.cancel();
}
}
@Inject(at = @At("RETURN"), method = "<init>*")
private void onInit(CallbackInfo ci) {
VanillaAoHelper.initialize((BlockModelRenderer) (Object) this);
}
}

View file

@ -43,12 +43,10 @@ public abstract class ItemRendererMixin {
@Unique
private final ThreadLocal<ItemRenderContext> fabric_contexts = ThreadLocal.withInitial(() -> new ItemRenderContext(colors));
// FIXME: Method is unmapped in Yarn
@Inject(at = @At(value = "INVOKE", target = "Lnet/minecraft/client/render/model/BakedModel;isBuiltin()Z"), method = "method_62476", cancellable = true)
public void hook_renderItem(ItemStack stack, ModelTransformationMode transformMode, MatrixStack matrixStack, VertexConsumerProvider vertexConsumerProvider, int light, int overlay, BakedModel model, boolean invert, CallbackInfo ci) {
@Inject(method = "method_62476", at = @At(value = "HEAD"), cancellable = true)
public void hook_renderItem(ItemStack stack, ModelTransformationMode transformMode, MatrixStack matrixStack, VertexConsumerProvider vertexConsumerProvider, int light, int overlay, BakedModel model, boolean notInHand, CallbackInfo ci) {
if (!model.isVanillaAdapter()) {
fabric_contexts.get().renderModel(stack, transformMode, invert, matrixStack, vertexConsumerProvider, light, overlay, model);
matrixStack.pop();
fabric_contexts.get().renderModel(stack, transformMode, matrixStack, vertexConsumerProvider, light, overlay, model);
ci.cancel();
}
}

View file

@ -38,14 +38,14 @@ include 'fabric-key-binding-api-v1'
include 'fabric-lifecycle-events-v1'
include 'fabric-loot-api-v3'
include 'fabric-message-api-v1'
//include 'fabric-model-loading-api-v1'
include 'fabric-model-loading-api-v1'
include 'fabric-networking-api-v1'
include 'fabric-object-builder-api-v1'
include 'fabric-particles-v1'
include 'fabric-recipe-api-v1'
include 'fabric-registry-sync-v0'
//include 'fabric-renderer-api-v1'
//include 'fabric-renderer-indigo'
include 'fabric-renderer-api-v1'
include 'fabric-renderer-indigo'
include 'fabric-rendering-fluids-v1'
include 'fabric-rendering-v1'
include 'fabric-resource-conditions-api-v1'