From 949e77b46ab268a2c362f838e675b81c659f80cc Mon Sep 17 00:00:00 2001 From: RainWarrior Date: Wed, 6 Jan 2016 22:00:43 +0300 Subject: [PATCH] Model loader improvements: adding custom data/textures to models that don't need them doesn't cause a error now, since it's common to put those in the defaults section of the blockstate json; you can get IModel associated with the variant now - using ModelLoaderRegistry.getModel; MultiLayerModel should now respect transformations applied to it, and respect part transformations. --- .../client/model/BlockStateLoader.java | 14 +- .../minecraftforge/client/model/IModel.java | 1 + .../client/model/MapModelState.java | 1 + .../client/model/ModelLoader.java | 74 +++-------- .../client/model/ModelLoaderRegistry.java | 98 ++++++++------ .../client/model/MultiLayerModel.java | 122 ++++++++++-------- .../blockstates/test_layer_block.json | 11 +- 7 files changed, 158 insertions(+), 163 deletions(-) diff --git a/src/main/java/net/minecraftforge/client/model/BlockStateLoader.java b/src/main/java/net/minecraftforge/client/model/BlockStateLoader.java index 2559856597..b6fb4f038f 100644 --- a/src/main/java/net/minecraftforge/client/model/BlockStateLoader.java +++ b/src/main/java/net/minecraftforge/client/model/BlockStateLoader.java @@ -134,20 +134,14 @@ public class BlockStateLoader protected IModel runModelHooks(IModel base, ImmutableMap textureMap, ImmutableMap customData) { - if (!customData.isEmpty()) + if (!customData.isEmpty() && base instanceof IModelCustomData) { - if (base instanceof IModelCustomData) - base = ((IModelCustomData)base).process(customData); - else - throw new RuntimeException("Attempted to add custom data to a model that doesn't need it: " + base); + base = ((IModelCustomData)base).process(customData); } - if (!textureMap.isEmpty()) + if (!textureMap.isEmpty() && base instanceof IRetexturableModel) { - if (base instanceof IRetexturableModel) - base = ((IRetexturableModel)base).retexture(textureMap); - else - throw new RuntimeException("Attempted to retexture a non-retexturable model: " + base); + base = ((IRetexturableModel)base).retexture(textureMap); } return base; diff --git a/src/main/java/net/minecraftforge/client/model/IModel.java b/src/main/java/net/minecraftforge/client/model/IModel.java index 3ac54704d0..6a9e1c47fd 100644 --- a/src/main/java/net/minecraftforge/client/model/IModel.java +++ b/src/main/java/net/minecraftforge/client/model/IModel.java @@ -17,6 +17,7 @@ public interface IModel /* * Returns all model locations that this model depends on. * Assume that returned collection is immutable. + * See ModelLoaderRegistry.getModel for dependency loading. */ Collection getDependencies(); diff --git a/src/main/java/net/minecraftforge/client/model/MapModelState.java b/src/main/java/net/minecraftforge/client/model/MapModelState.java index 62d93912e0..2f1469f3c9 100644 --- a/src/main/java/net/minecraftforge/client/model/MapModelState.java +++ b/src/main/java/net/minecraftforge/client/model/MapModelState.java @@ -8,6 +8,7 @@ import com.google.common.collect.ImmutableMap; /* * Simple implementation of IModelState via a map and a default value. Provides a full state for each part. + * You probably don't want to use this. */ public class MapModelState implements IModelState { diff --git a/src/main/java/net/minecraftforge/client/model/ModelLoader.java b/src/main/java/net/minecraftforge/client/model/ModelLoader.java index 9404ff5534..865d4020cb 100644 --- a/src/main/java/net/minecraftforge/client/model/ModelLoader.java +++ b/src/main/java/net/minecraftforge/client/model/ModelLoader.java @@ -330,13 +330,21 @@ public class ModelLoader extends ModelBakery } } + private IModel getVariantModel(ModelResourceLocation location) + { + loadVariants(ImmutableList.of(location)); + IModel model = stateModels.get(location); + if(model == null) model = getMissingModel(); + return model; + } + private void resolveDependencies(IModel model) throws IOException { for (ResourceLocation dep : model.getDependencies()) { if(dep instanceof ModelResourceLocation) { - loadVariants(ImmutableList.of((ModelResourceLocation)dep)); + getVariantModel((ModelResourceLocation)dep); } else { @@ -598,37 +606,6 @@ public class ModelLoader extends ModelBakery } } - // Weighted models can contain multiple copies of 1 model with different rotations - this is to make it work with IModelState (different copies will be different objects). - private static class WeightedPartWrapper implements IModel - { - private final IModel model; - - public WeightedPartWrapper(IModel model) - { - this.model = model; - } - - public Collection getDependencies() - { - return model.getDependencies(); - } - - public Collection getTextures() - { - return model.getTextures(); - } - - public IFlexibleBakedModel bake(IModelState state, VertexFormat format, Function bakedTextureGetter) - { - return model.bake(state, format, bakedTextureGetter); - } - - public IModelState getDefaultState() - { - return model.getDefaultState(); - } - } - private class WeightedRandomModel implements IModel { private final List variants; @@ -636,11 +613,10 @@ public class ModelLoader extends ModelBakery private final List models = new ArrayList(); private final IModelState defaultState; - @Deprecated @SuppressWarnings("unused") public WeightedRandomModel(Variants variants){ this(null, variants); } // Remove 1.9 public WeightedRandomModel(ModelResourceLocation parent, Variants variants) { this.variants = variants.getVariants(); - ImmutableMap.Builder builder = ImmutableMap.builder(); + ImmutableList.Builder> builder = ImmutableList.builder(); for (Variant v : (List)variants.getVariants()) { ResourceLocation loc = v.getModelLocation(); @@ -676,20 +652,18 @@ public class ModelLoader extends ModelBakery textures.addAll(model.getTextures()); // Kick this, just in case. } - model = new WeightedPartWrapper(model); models.add(model); - builder.put(MapModelState.wrap(model), v.getState()); + builder.add(Pair.of(model, v.getState())); } if (models.size() == 0) //If all variants are missing, add one with the missing model and default rotation. { IModel missing = getMissingModel(); models.add(missing); - builder.put(MapModelState.wrap(missing), TRSRTransformation.identity()); + builder.add(Pair.of(missing, TRSRTransformation.identity())); } - defaultState = new MapModelState(builder.build()); - + defaultState = new MultiModelState(builder.build()); } public Collection getDependencies() @@ -708,15 +682,6 @@ public class ModelLoader extends ModelBakery return state; } - private IModelState getState(IModelState state, IModel model) - { - if(state instanceof MapModelState) - { - return ((MapModelState)state).getState(model); - } - return state; - } - public IFlexibleBakedModel bake(IModelState state, VertexFormat format, Function bakedTextureGetter) { if(!Attributes.moreSpecific(format, Attributes.DEFAULT_BAKED_FORMAT)) @@ -727,14 +692,14 @@ public class ModelLoader extends ModelBakery { Variant v = variants.get(0); IModel model = models.get(0); - return model.bake(addUV(v.isUvLocked(), getState(state, model)), format, bakedTextureGetter); + return model.bake(addUV(v.isUvLocked(), MultiModelState.getPartState(state, model, 0)), format, bakedTextureGetter); } WeightedBakedModel.Builder builder = new WeightedBakedModel.Builder(); for(int i = 0; i < variants.size(); i++) { IModel model = models.get(i); Variant v = variants.get(i); - builder.add(model.bake(addUV(v.isUvLocked(), getState(state, model)), format, bakedTextureGetter), variants.get(i).getWeight()); + builder.add(model.bake(addUV(v.isUvLocked(), MultiModelState.getPartState(state, model, i)), format, bakedTextureGetter), variants.get(i).getWeight()); } return new FlexibleWeightedBakedModel(builder.build(), Attributes.DEFAULT_BAKED_FORMAT); } @@ -747,14 +712,11 @@ public class ModelLoader extends ModelBakery private static class FlexibleWeightedBakedModel extends WeightedBakedModel implements IFlexibleBakedModel { - @SuppressWarnings("unused") - private final WeightedBakedModel parent; private final VertexFormat format; public FlexibleWeightedBakedModel(WeightedBakedModel parent, VertexFormat format) { super(parent.models); - this.parent = parent; this.format = format; } @@ -764,12 +726,6 @@ public class ModelLoader extends ModelBakery } } - @SuppressWarnings("unused") - private boolean isBuiltinModel(ModelBlock model) - { - return model == MODEL_GENERATED || model == MODEL_COMPASS || model == MODEL_CLOCK || model == MODEL_ENTITY; - } - public IModel getMissingModel() { if (missingModel == null) diff --git a/src/main/java/net/minecraftforge/client/model/ModelLoaderRegistry.java b/src/main/java/net/minecraftforge/client/model/ModelLoaderRegistry.java index c50082751b..09d43a8227 100644 --- a/src/main/java/net/minecraftforge/client/model/ModelLoaderRegistry.java +++ b/src/main/java/net/minecraftforge/client/model/ModelLoaderRegistry.java @@ -10,6 +10,7 @@ import net.minecraft.client.Minecraft; import net.minecraft.client.resources.IReloadableResourceManager; import net.minecraft.client.resources.IResourceManager; import net.minecraft.client.resources.IResourceManagerReloadListener; +import net.minecraft.client.resources.model.ModelResourceLocation; import net.minecraft.util.ResourceLocation; import net.minecraftforge.client.model.ModelLoader.VanillaLoader; import net.minecraftforge.client.model.b3d.B3DLoader; @@ -64,58 +65,79 @@ public class ModelLoaderRegistry return new ResourceLocation(location.getResourceDomain(), "models/" + location.getResourcePath()); } + /** + * Primary method to get IModel instances. + * ResourceLocation argument will be passed directly to the custom model loaders, + * ModelResourceLocation argument will be loaded through the blockstate system. + */ public static IModel getModel(ResourceLocation location) throws IOException { - if(cache.containsKey(location)) return cache.get(location); - ResourceLocation actual = getActualLocation(location); - ICustomModelLoader accepted = null; - for(ICustomModelLoader loader : loaders) - { - try - { - if(loader.accepts(actual)) - { - if(accepted != null) - { - FMLLog.severe("2 loaders (%s and %s) want to load the same model %s", accepted, loader, location); - throw new IllegalStateException("2 loaders want to load the same model"); - } - accepted = loader; - } - } - catch(Exception e) - { - FMLLog.log(Level.ERROR, e, "Exception checking if model %s can be loaded with loader %s, skipping", location, loader); - } - } - - // no custom loaders found, try vanilla one - if(accepted == null) - { - if(VanillaLoader.instance.accepts(actual)) accepted = VanillaLoader.instance; - } - IModel model; - if(accepted == null) + if(location instanceof ModelResourceLocation) { - FMLLog.severe("no suitable loader found for the model %s, skipping", location); - model = getMissingModel(); + ModelLoader loader = ModelLoader.VanillaLoader.instance.getLoader(); + if(loader != null) + { + model = loader.getVariantModel((ModelResourceLocation)location); + } + else + { + FMLLog.log(Level.ERROR, "Loading model too early, skipping: %s", location); + model = getMissingModel(); + } } else { - try + if(cache.containsKey(location)) return cache.get(location); + ResourceLocation actual = getActualLocation(location); + ICustomModelLoader accepted = null; + for(ICustomModelLoader loader : loaders) { - model = accepted.loadModel(actual); + try + { + if(loader.accepts(actual)) + { + if(accepted != null) + { + FMLLog.severe("2 loaders (%s and %s) want to load the same model %s", accepted, loader, location); + throw new IllegalStateException("2 loaders want to load the same model"); + } + accepted = loader; + } + } + catch(Exception e) + { + FMLLog.log(Level.ERROR, e, "Exception checking if model %s can be loaded with loader %s, skipping", location, loader); + } } - catch (IOException e) + + // no custom loaders found, try vanilla one + if(accepted == null) { - throw e; + if(VanillaLoader.instance.accepts(actual)) accepted = VanillaLoader.instance; } - catch(Exception e) + + if(accepted == null) { - FMLLog.log(Level.ERROR, e, "Exception loading model %s with loader %s, skipping", location, accepted); + FMLLog.severe("no suitable loader found for the model %s, skipping", location); model = getMissingModel(); } + else + { + try + { + model = accepted.loadModel(actual); + } + catch (IOException e) + { + throw e; + } + catch(Exception e) + { + FMLLog.log(Level.ERROR, e, "Exception loading model %s with loader %s, skipping", location, accepted); + model = getMissingModel(); + } + } } cache.put(location, model); return model; diff --git a/src/main/java/net/minecraftforge/client/model/MultiLayerModel.java b/src/main/java/net/minecraftforge/client/model/MultiLayerModel.java index fa8eb1a96d..18a8eed2a7 100644 --- a/src/main/java/net/minecraftforge/client/model/MultiLayerModel.java +++ b/src/main/java/net/minecraftforge/client/model/MultiLayerModel.java @@ -1,5 +1,6 @@ package net.minecraftforge.client.model; +import java.io.IOException; import java.util.Collection; import java.util.List; @@ -14,7 +15,6 @@ import net.minecraft.client.renderer.texture.TextureAtlasSprite; import net.minecraft.client.renderer.vertex.VertexFormat; import net.minecraft.client.resources.IResourceManager; import net.minecraft.client.resources.model.IBakedModel; -import net.minecraft.client.resources.model.ModelManager; import net.minecraft.client.resources.model.ModelResourceLocation; import net.minecraft.util.EnumFacing; import net.minecraft.util.EnumWorldBlockLayer; @@ -23,6 +23,7 @@ import net.minecraftforge.client.MinecraftForgeClient; import net.minecraftforge.fml.common.FMLLog; import org.apache.commons.lang3.tuple.Pair; +import org.apache.logging.log4j.Level; import com.google.common.base.Function; import com.google.common.base.Optional; @@ -54,10 +55,36 @@ public class MultiLayerModel implements IModelCustomData return ImmutableList.of(); } + private static ImmutableMap, IFlexibleBakedModel> buildModels(ImmutableMap, ModelResourceLocation> models, IModelState state, VertexFormat format, Function bakedTextureGetter) + { + ImmutableMap.Builder, IFlexibleBakedModel> builder = ImmutableMap.builder(); + for(Optional key : models.keySet()) + { + IModel model; + try + { + model = ModelLoaderRegistry.getModel(models.get(key)); + } + catch (IOException e) + { + FMLLog.log(Level.ERROR, e, "Couldn't load MultiLayerModel dependency: %s", models.get(key)); + model = ModelLoaderRegistry.getMissingModel(); + } + builder.put(key, model.bake(new ModelStateComposition(state, model.getDefaultState()), format, bakedTextureGetter)); + } + return builder.build(); + } + @Override public IFlexibleBakedModel bake(IModelState state, VertexFormat format, Function bakedTextureGetter) { - return new MultiLayerBakedModel(models, format, IPerspectiveAwareModel.MapWrapper.getTransforms(state)); + IModel missing = ModelLoaderRegistry.getMissingModel(); + return new MultiLayerBakedModel( + buildModels(models, state, format, bakedTextureGetter), + missing.bake(missing.getDefaultState(), format, bakedTextureGetter), + format, + IPerspectiveAwareModel.MapWrapper.getTransforms(state) + ); } @Override @@ -102,63 +129,59 @@ public class MultiLayerModel implements IModelCustomData public static class MultiLayerBakedModel implements IFlexibleBakedModel, ISmartBlockModel, IPerspectiveAwareModel { - private final ImmutableMap, ModelResourceLocation> models; + private final ImmutableMap, IFlexibleBakedModel> models; private final VertexFormat format; private final ImmutableMap cameraTransforms;; - private IBakedModel missing; - private IBakedModel base; - private ImmutableMap bakedModels; - private ImmutableMap, ImmutableList> quads; + private final IFlexibleBakedModel base; + private final IFlexibleBakedModel missing; + private final ImmutableMap, ImmutableList> quads; + private static final Function defaultTextureGetter = new Function() + { + public TextureAtlasSprite apply(ResourceLocation location) + { + return Minecraft.getMinecraft().getTextureMapBlocks().getAtlasSprite(location.toString()); + } + }; + + @Deprecated // remove 1.9 public MultiLayerBakedModel(ImmutableMap, ModelResourceLocation> models, VertexFormat format, ImmutableMap cameraTransforms) + { + this( + buildModels(models, TRSRTransformation.identity(), format, defaultTextureGetter), + ModelLoaderRegistry.getMissingModel().bake(ModelLoaderRegistry.getMissingModel().getDefaultState(), format, defaultTextureGetter), + format, + cameraTransforms + ); + } + + public MultiLayerBakedModel(ImmutableMap, IFlexibleBakedModel> models, IFlexibleBakedModel missing, VertexFormat format, ImmutableMap cameraTransforms) { this.models = models; this.format = format; this.cameraTransforms = cameraTransforms; - } - - private void compute() - { - if(base == null) + this.missing = missing; + if(models.containsKey(Optional.absent())) { - ModelManager manager = Minecraft.getMinecraft().getBlockRendererDispatcher().getBlockModelShapes().getModelManager(); - missing = manager.getMissingModel(); - base = getModel(manager, Optional.absent()); - - ImmutableMap.Builder builder = ImmutableMap.builder(); - for(EnumWorldBlockLayer layer : EnumWorldBlockLayer.values()) - { - if(models.containsKey(Optional.of(layer))) - { - builder.put(layer, getModel(manager, Optional.of(layer))); - } - } - bakedModels = builder.build(); - - ImmutableMap.Builder, ImmutableList> quadBuilder = ImmutableMap.builder(); - quadBuilder.put(Optional.absent(), buildQuads(Optional.absent())); - for(EnumFacing side: EnumFacing.values()) - { - quadBuilder.put(Optional.of(side), buildQuads(Optional.of(side))); - } - quads = quadBuilder.build(); + base = models.get(Optional.absent()); } - } - - private IBakedModel getModel(ModelManager manager, Optional layer) - { - ModelResourceLocation loc = models.get(layer); - if(loc == null) + else { - loc = new ModelResourceLocation("builtin/missing", "missing"); + base = missing; } - return manager.getModel(loc); + ImmutableMap.Builder, ImmutableList> quadBuilder = ImmutableMap.builder(); + quadBuilder.put(Optional.absent(), buildQuads(models, Optional.absent())); + for(EnumFacing side: EnumFacing.values()) + { + quadBuilder.put(Optional.of(side), buildQuads(models, Optional.of(side))); + } + quads = quadBuilder.build(); } - private ImmutableList buildQuads(Optional side) + private static ImmutableList buildQuads(ImmutableMap, IFlexibleBakedModel> models, Optional side) { ImmutableList.Builder builder = ImmutableList.builder(); - for(IBakedModel model : bakedModels.values()) + for(IBakedModel model : models.values()) { if(side.isPresent()) { @@ -175,42 +198,36 @@ public class MultiLayerModel implements IModelCustomData @Override public List getFaceQuads(EnumFacing side) { - compute(); return quads.get(Optional.of(side)); } @Override public List getGeneralQuads() { - compute(); return quads.get(Optional.absent()); } @Override public boolean isAmbientOcclusion() { - compute(); return base.isAmbientOcclusion(); } @Override public boolean isGui3d() { - compute(); return base.isGui3d(); } @Override public boolean isBuiltInRenderer() { - compute(); return base.isBuiltInRenderer(); } @Override public TextureAtlasSprite getParticleTexture() { - compute(); return base.getParticleTexture(); } @@ -223,13 +240,12 @@ public class MultiLayerModel implements IModelCustomData @Override public IBakedModel handleBlockState(IBlockState state) { - compute(); - EnumWorldBlockLayer layer = MinecraftForgeClient.getRenderLayer(); - if(!bakedModels.containsKey(layer)) + Optional layer = Optional.of(MinecraftForgeClient.getRenderLayer()); + if(!models.containsKey(layer)) { return missing; } - return bakedModels.get(layer); + return models.get(layer); } @Override diff --git a/src/test/resources/assets/forgedebugmultilayermodel/blockstates/test_layer_block.json b/src/test/resources/assets/forgedebugmultilayermodel/blockstates/test_layer_block.json index 50a1efe266..15a23f01cf 100644 --- a/src/test/resources/assets/forgedebugmultilayermodel/blockstates/test_layer_block.json +++ b/src/test/resources/assets/forgedebugmultilayermodel/blockstates/test_layer_block.json @@ -4,15 +4,20 @@ "model": "forge:multi-layer", "custom": { // base is used for model properties - camera transforms, isGui3d, e.t.c. - "base": "minecraft:stone_slab#half=bottom", + "base": "forgedebugmultilayermodel:test_layer_block#aux", // per-layer models - "Solid": "minecraft:stone_slab#half=bottom", + "Solid": "forgedebugmultilayermodel:test_layer_block#aux", "Translucent": "minecraft:pink_stained_glass_pane#east=true,north=false,south=false,west=true" }, "transform": "forge:default-block" }, "variants": { "normal": [{}], - "inventory": [{}] + "inventory": [{}], + "aux": [{ + "model": "cube_all", + "textures": { "all": "blocks/slime" }, + "transform": { "scale": .5 } + }] } }