Skip to content

Commit a0cc8bf

Browse files
committed
Add Fabric Model Loading API support
1 parent e7e065f commit a0cc8bf

File tree

4 files changed

+197
-17
lines changed

4 files changed

+197
-17
lines changed

common/build.gradle

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ dependencies {
2020
}
2121

2222
modCompileOnly "curse.maven:spark-361579:${rootProject.spark_version}"
23+
24+
modCompileOnly fabricApi.module("fabric-model-loading-api-v1", rootProject.fabric_api_version)
2325
// Remove the next line if you don't want to depend on the API
2426
// modApi "me.shedaniel:architectury:${rootProject.architectury_version}"
2527
}

common/src/main/java/org/embeddedt/modernfix/dynamicresources/DynamicModelProvider.java

Lines changed: 60 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -43,13 +43,15 @@
4343
import net.minecraft.world.level.block.state.StateDefinition;
4444
import org.embeddedt.modernfix.ModernFix;
4545
import org.jetbrains.annotations.NotNull;
46+
import org.jetbrains.annotations.Nullable;
4647

4748
import java.io.Reader;
4849
import java.lang.ref.WeakReference;
4950
import java.util.AbstractSet;
5051
import java.util.ArrayList;
5152
import java.util.Collection;
5253
import java.util.Collections;
54+
import java.util.HashMap;
5355
import java.util.HashSet;
5456
import java.util.Iterator;
5557
import java.util.List;
@@ -97,6 +99,9 @@ public class DynamicModelProvider {
9799
private final Map<ModelResourceLocation, BakedModel> mrlModelOverrides = new ConcurrentHashMap<>();
98100
private final Map<ResourceLocation, ItemModel> itemStackModelOverrides = new ConcurrentHashMap<>();
99101
private final Map<ResourceLocation, BakedModel> standaloneModelOverrides = new ConcurrentHashMap<>();
102+
private final Map<ModelResourceLocation, UnbakedBlockStateModel> unbakedBlockStateModelOverrides = new ConcurrentHashMap<>();
103+
104+
private final List<DynamicModelProvider.DynamicModelPlugin> pluginList = new ArrayList<>();
100105

101106
private static final boolean DEBUG_DYNAMIC_MODEL_LOADING = Boolean.getBoolean("modernfix.debugDynamicModelLoading");
102107

@@ -129,6 +134,12 @@ public TextureAtlasSprite reportMissingReference(ModelDebugName modelDebugName,
129134
this.itemModelGenerator = new ItemModelGenerator();
130135
this.missingModel = this.bakeModel(this.unbakedMissingModel, () -> "missing");
131136
this.missingItemModel = new MissingItemModel(this.missingModel);
137+
try {
138+
Class.forName("net.fabricmc.fabric.api.client.model.loading.v1.ModelLoadingPlugin");
139+
pluginList.add(new FabricDynamicModelHandler(this));
140+
} catch(Exception ignored) {
141+
// Fabric API likely not present
142+
}
132143
}
133144

134145
public BakedModel getMissingBakedModel() {
@@ -329,7 +340,21 @@ private Optional<BlockStateModelLoader.LoadedModels> loadBlockStateDefinition(Re
329340
ModernFix.LOGGER.error("Failed to load blockstate definition {} from pack '{}'", location, resource.sourcePackId(), e);
330341
}
331342
}
332-
return Optional.of(BlockStateModelLoader.loadBlockStateDefinitionStack(location, stateDefinition, loadedDefinitions, this.unbakedMissingModel));
343+
var loadedModels = new HashMap<>(BlockStateModelLoader.loadBlockStateDefinitionStack(location, stateDefinition, loadedDefinitions, this.unbakedMissingModel).models());
344+
if (!pluginList.isEmpty()) {
345+
loadedModels.replaceAll((mrl, oldModel) -> {
346+
UnbakedBlockStateModel ubm = oldModel.model();
347+
for (var plugin : pluginList) {
348+
ubm = plugin.modifyBlockModelOnLoad(ubm, mrl, oldModel.state());
349+
}
350+
if (ubm == oldModel.model()) {
351+
return oldModel;
352+
} else {
353+
return new BlockStateModelLoader.LoadedModel(oldModel.state(), ubm);
354+
}
355+
});
356+
}
357+
return Optional.of(new BlockStateModelLoader.LoadedModels(loadedModels));
333358
}
334359

335360
private BakedModel bakeModel(UnbakedModel model, ModelDebugName name) {
@@ -364,15 +389,18 @@ private Optional<BakedModel> loadBakedModel(ModelResourceLocation location) {
364389
if (location.variant().equals("standalone") || location.variant().equals("fabric_resource")) {
365390
return this.loadStandaloneModel(location.id());
366391
} else {
367-
var optLoadedModels = this.loadedStateDefinitions.getUnchecked(location.id());
368-
Optional<UnbakedBlockStateModel> unbakedModelOpt = optLoadedModels.map(loadedModels -> {
369-
var loadedModel = loadedModels.models().get(location);
370-
if(loadedModel != null) {
371-
return loadedModel.model();
372-
} else {
373-
return null;
374-
}
375-
});
392+
Optional<UnbakedBlockStateModel> unbakedModelOpt = Optional.ofNullable(this.unbakedBlockStateModelOverrides.get(location));
393+
if (unbakedModelOpt.isEmpty()) {
394+
var optLoadedModels = this.loadedStateDefinitions.getUnchecked(location.id());
395+
unbakedModelOpt = optLoadedModels.map(loadedModels -> {
396+
var loadedModel = loadedModels.models().get(location);
397+
if(loadedModel != null) {
398+
return loadedModel.model();
399+
} else {
400+
return null;
401+
}
402+
});
403+
}
376404
return unbakedModelOpt.map(unbakedModel -> {
377405
return this.bakeModel(unbakedModel, location::toString);
378406
});
@@ -389,12 +417,14 @@ private Optional<BakedModel> loadStandaloneModel(ResourceLocation location) {
389417
});
390418
}
391419

392-
private Optional<UnbakedModel> loadBlockModel(ResourceLocation location) {
420+
private Optional<UnbakedModel> loadBlockModelDefault(ResourceLocation location) {
393421
if (DEBUG_DYNAMIC_MODEL_LOADING) {
394422
ModernFix.LOGGER.info("Loading block model '{}'", location);
395423
}
396424
if (location.equals(ItemModelGenerator.GENERATED_ITEM_MODEL_ID)) {
397425
return Optional.of(this.itemModelGenerator);
426+
} else if (location.equals(MissingBlockModel.LOCATION)) {
427+
return Optional.of(this.unbakedMissingModel);
398428
}
399429
var resource = this.resourceManager.getResource(ResourceLocation.fromNamespaceAndPath(location.getNamespace(), "models/" + location.getPath() + ".json"));
400430
if(resource.isPresent()) {
@@ -411,6 +441,15 @@ private Optional<UnbakedModel> loadBlockModel(ResourceLocation location) {
411441
}
412442
}
413443

444+
private Optional<UnbakedModel> loadBlockModel(ResourceLocation location) {
445+
Optional<UnbakedModel> value = loadBlockModelDefault(location);
446+
for (var plugin : this.pluginList) {
447+
value = plugin.modifyModelOnLoad(value, location);
448+
}
449+
return value;
450+
}
451+
452+
414453
private Optional<ClientItem> loadClientItemProperties(ResourceLocation location) {
415454
if (DEBUG_DYNAMIC_MODEL_LOADING) {
416455
ModernFix.LOGGER.info("Loading client item '{}'", location);
@@ -460,6 +499,10 @@ public BakedModel getStandaloneModel(ResourceLocation location) {
460499
return this.loadedStandaloneModels.getUnchecked(location).orElse(this.missingModel);
461500
}
462501

502+
public void addUnbakedBlockStateOverride(ModelResourceLocation location, UnbakedBlockStateModel model) {
503+
this.unbakedBlockStateModelOverrides.put(location, model);
504+
}
505+
463506
private class DynamicBaker implements ModelBaker {
464507
private final ModelDebugName modelDebugName;
465508

@@ -469,7 +512,7 @@ private DynamicBaker(ModelDebugName modelDebugName) {
469512

470513
@Override
471514
public BakedModel bake(ResourceLocation location, ModelState transform) {
472-
return DynamicModelProvider.this.loadBlockModel(location).map(unbakedModel -> {
515+
return DynamicModelProvider.this.loadedBlockModels.getUnchecked(location).map(unbakedModel -> {
473516
DynamicModelProvider.this.resolver.clearResolver();
474517
unbakedModel.resolveDependencies(DynamicModelProvider.this.resolver);
475518
return UnbakedModel.bakeWithTopModelValues(unbakedModel, this, transform);
@@ -526,4 +569,9 @@ public void clearResolver() {
526569
public interface ModelManagerExtension {
527570
DynamicModelProvider mfix$getModelProvider();
528571
}
572+
573+
public interface DynamicModelPlugin {
574+
Optional<UnbakedModel> modifyModelOnLoad(Optional<UnbakedModel> model, ResourceLocation id);
575+
UnbakedBlockStateModel modifyBlockModelOnLoad(UnbakedBlockStateModel model, ModelResourceLocation id, BlockState state);
576+
}
529577
}
Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
package org.embeddedt.modernfix.dynamicresources;
2+
3+
import net.fabricmc.fabric.api.client.model.loading.v1.BlockStateResolver;
4+
import net.fabricmc.fabric.api.client.model.loading.v1.ModelLoadingPlugin;
5+
import net.fabricmc.fabric.api.client.model.loading.v1.ModelModifier;
6+
import net.fabricmc.fabric.api.event.Event;
7+
import net.fabricmc.fabric.api.event.EventFactory;
8+
import net.minecraft.client.renderer.block.BlockModelShaper;
9+
import net.minecraft.client.renderer.block.model.UnbakedBlockStateModel;
10+
import net.minecraft.client.resources.model.ModelResourceLocation;
11+
import net.minecraft.client.resources.model.UnbakedModel;
12+
import net.minecraft.resources.ResourceLocation;
13+
import net.minecraft.world.level.block.Block;
14+
import net.minecraft.world.level.block.state.BlockState;
15+
import org.embeddedt.modernfix.ModernFix;
16+
import org.jetbrains.annotations.Nullable;
17+
18+
import java.util.Collection;
19+
import java.util.HashMap;
20+
import java.util.List;
21+
import java.util.Map;
22+
import java.util.Optional;
23+
24+
public class FabricDynamicModelHandler implements DynamicModelProvider.DynamicModelPlugin {
25+
private final List<ModelLoadingPlugin> pluginList;
26+
27+
// Borrowed from Fabric API, this dispatching logic is extremely trivial
28+
29+
private static final ResourceLocation[] MODEL_MODIFIER_PHASES = new ResourceLocation[] { ModelModifier.OVERRIDE_PHASE, ModelModifier.DEFAULT_PHASE, ModelModifier.WRAP_PHASE, ModelModifier.WRAP_LAST_PHASE };
30+
31+
private final Event<ModelModifier.OnLoad> onLoadModifiers = EventFactory.createWithPhases(ModelModifier.OnLoad.class, modifiers -> (model, context) -> {
32+
for (ModelModifier.OnLoad modifier : modifiers) {
33+
try {
34+
model = modifier.modifyModelOnLoad(model, context);
35+
} catch (Exception exception) {
36+
ModernFix.LOGGER.error("Failed to modify unbaked model on load", exception);
37+
}
38+
}
39+
40+
return model;
41+
}, MODEL_MODIFIER_PHASES);
42+
43+
private final Event<ModelModifier.OnLoadBlock> onLoadBlockModifiers = EventFactory.createWithPhases(ModelModifier.OnLoadBlock.class, modifiers -> (model, context) -> {
44+
for (ModelModifier.OnLoadBlock modifier : modifiers) {
45+
try {
46+
model = modifier.modifyModelOnLoad(model, context);
47+
} catch (Exception exception) {
48+
ModernFix.LOGGER.error("Failed to modify unbaked block model on load", exception);
49+
}
50+
}
51+
52+
return model;
53+
}, MODEL_MODIFIER_PHASES);
54+
55+
public FabricDynamicModelHandler(DynamicModelProvider provider) {
56+
this.pluginList = ModelLoadingPlugin.getAll();
57+
var context = new PluginContext(provider);
58+
for (var plugin : this.pluginList) {
59+
plugin.initialize(context);
60+
}
61+
context.fireResolvers();
62+
}
63+
64+
@Override
65+
public Optional<UnbakedModel> modifyModelOnLoad(Optional<UnbakedModel> model, ResourceLocation id) {
66+
return Optional.ofNullable(this.onLoadModifiers.invoker().modifyModelOnLoad(model.orElse(null), () -> id));
67+
}
68+
69+
@Override
70+
public UnbakedBlockStateModel modifyBlockModelOnLoad(UnbakedBlockStateModel model, ModelResourceLocation id, BlockState state) {
71+
return this.onLoadBlockModifiers.invoker().modifyModelOnLoad(model, new ModelModifier.OnLoadBlock.Context() {
72+
@Override
73+
public ModelResourceLocation id() {
74+
return id;
75+
}
76+
77+
@Override
78+
public BlockState state() {
79+
return state;
80+
}
81+
});
82+
}
83+
84+
private class PluginContext implements ModelLoadingPlugin.Context {
85+
private final DynamicModelProvider provider;
86+
private final Map<Block, BlockStateResolver> resolvers = new HashMap<>();
87+
88+
private PluginContext(DynamicModelProvider provider) {
89+
this.provider = provider;
90+
}
91+
92+
@Override
93+
public void addModels(ResourceLocation... ids) {
94+
/* no-op on dynamic model loader */
95+
}
96+
97+
@Override
98+
public void addModels(Collection<? extends ResourceLocation> ids) {
99+
/* no-op on dynamic model loader */
100+
}
101+
102+
@Override
103+
public void registerBlockStateResolver(Block block, BlockStateResolver resolver) {
104+
resolvers.put(block, resolver);
105+
}
106+
107+
public void fireResolvers() {
108+
resolvers.forEach((block, resolver) -> {
109+
resolver.resolveBlockStates(new BlockStateResolver.Context() {
110+
@Override
111+
public Block block() {
112+
return block;
113+
}
114+
115+
@Override
116+
public void setModel(BlockState state, UnbakedBlockStateModel model) {
117+
provider.addUnbakedBlockStateOverride(BlockModelShaper.stateToModelLocation(state), model);
118+
}
119+
});
120+
});
121+
}
122+
123+
@Override
124+
public Event<ModelModifier.OnLoad> modifyModelOnLoad() {
125+
return onLoadModifiers;
126+
}
127+
128+
@Override
129+
public Event<ModelModifier.OnLoadBlock> modifyBlockModelOnLoad() {
130+
return onLoadBlockModifiers;
131+
}
132+
}
133+
}

fabric/build.gradle

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -36,13 +36,10 @@ dependencies {
3636
modCompileOnly(fabricApi.module("fabric-model-loading-api-v1", rootProject.fabric_api_version)) { exclude group: 'net.fabricmc', module: 'fabric-loader' }
3737
modCompileOnly(fabricApi.module("fabric-resource-loader-v0", rootProject.fabric_api_version)) { exclude group: 'net.fabricmc', module: 'fabric-loader' }
3838
modCompileOnly(fabricApi.module("fabric-data-generation-api-v1", rootProject.fabric_api_version)) { exclude group: 'net.fabricmc', module: 'fabric-loader' }
39+
modCompileOnly("com.terraformersmc:modmenu:${rootProject.modmenu_version}") { transitive false }
40+
modCompileOnly "curse.maven:spark-361579:${rootProject.spark_version}"
3941
if(project.use_fabric_api_at_runtime.toBoolean()) {
40-
modImplementation("com.terraformersmc:modmenu:${rootProject.modmenu_version}") { transitive false }
41-
modImplementation "curse.maven:spark-361579:${rootProject.spark_version}"
4242
modRuntimeOnly("net.fabricmc.fabric-api:fabric-api:${rootProject.fabric_api_version}") { exclude group: 'net.fabricmc', module: 'fabric-loader' }
43-
} else {
44-
modCompileOnly("com.terraformersmc:modmenu:${rootProject.modmenu_version}") { transitive false }
45-
modCompileOnly "curse.maven:spark-361579:${rootProject.spark_version}"
4643
}
4744

4845
// Remove the next line if you don't want to depend on the API

0 commit comments

Comments
 (0)