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.

This commit is contained in:
RainWarrior 2016-01-06 22:00:43 +03:00
parent 632d8e553d
commit 949e77b46a
7 changed files with 158 additions and 163 deletions

View File

@ -134,20 +134,14 @@ public class BlockStateLoader
protected IModel runModelHooks(IModel base, ImmutableMap<String, String> textureMap, ImmutableMap<String, String> 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;

View File

@ -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<ResourceLocation> getDependencies();

View File

@ -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
{

View File

@ -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<ResourceLocation> getDependencies()
{
return model.getDependencies();
}
public Collection<ResourceLocation> getTextures()
{
return model.getTextures();
}
public IFlexibleBakedModel bake(IModelState state, VertexFormat format, Function<ResourceLocation, TextureAtlasSprite> bakedTextureGetter)
{
return model.bake(state, format, bakedTextureGetter);
}
public IModelState getDefaultState()
{
return model.getDefaultState();
}
}
private class WeightedRandomModel implements IModel
{
private final List<Variant> variants;
@ -636,11 +613,10 @@ public class ModelLoader extends ModelBakery
private final List<IModel> models = new ArrayList<IModel>();
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<MapModelState.Wrapper, IModelState> builder = ImmutableMap.builder();
ImmutableList.Builder<Pair<IModel, IModelState>> builder = ImmutableList.builder();
for (Variant v : (List<Variant>)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.<IModel, IModelState>of(missing, TRSRTransformation.identity()));
}
defaultState = new MapModelState(builder.build());
defaultState = new MultiModelState(builder.build());
}
public Collection<ResourceLocation> 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<ResourceLocation, TextureAtlasSprite> 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)

View File

@ -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;

View File

@ -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<Optional<EnumWorldBlockLayer>, IFlexibleBakedModel> buildModels(ImmutableMap<Optional<EnumWorldBlockLayer>, ModelResourceLocation> models, IModelState state, VertexFormat format, Function<ResourceLocation, TextureAtlasSprite> bakedTextureGetter)
{
ImmutableMap.Builder<Optional<EnumWorldBlockLayer>, IFlexibleBakedModel> builder = ImmutableMap.builder();
for(Optional<EnumWorldBlockLayer> 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<ResourceLocation, TextureAtlasSprite> 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<Optional<EnumWorldBlockLayer>, ModelResourceLocation> models;
private final ImmutableMap<Optional<EnumWorldBlockLayer>, IFlexibleBakedModel> models;
private final VertexFormat format;
private final ImmutableMap<TransformType, TRSRTransformation> cameraTransforms;;
private IBakedModel missing;
private IBakedModel base;
private ImmutableMap<EnumWorldBlockLayer, IBakedModel> bakedModels;
private ImmutableMap<Optional<EnumFacing>, ImmutableList<BakedQuad>> quads;
private final IFlexibleBakedModel base;
private final IFlexibleBakedModel missing;
private final ImmutableMap<Optional<EnumFacing>, ImmutableList<BakedQuad>> quads;
private static final Function<ResourceLocation, TextureAtlasSprite> defaultTextureGetter = new Function<ResourceLocation, TextureAtlasSprite>()
{
public TextureAtlasSprite apply(ResourceLocation location)
{
return Minecraft.getMinecraft().getTextureMapBlocks().getAtlasSprite(location.toString());
}
};
@Deprecated // remove 1.9
public MultiLayerBakedModel(ImmutableMap<Optional<EnumWorldBlockLayer>, ModelResourceLocation> models, VertexFormat format, ImmutableMap<TransformType, TRSRTransformation> cameraTransforms)
{
this(
buildModels(models, TRSRTransformation.identity(), format, defaultTextureGetter),
ModelLoaderRegistry.getMissingModel().bake(ModelLoaderRegistry.getMissingModel().getDefaultState(), format, defaultTextureGetter),
format,
cameraTransforms
);
}
public MultiLayerBakedModel(ImmutableMap<Optional<EnumWorldBlockLayer>, IFlexibleBakedModel> models, IFlexibleBakedModel missing, VertexFormat format, ImmutableMap<TransformType, TRSRTransformation> 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.<EnumWorldBlockLayer>absent());
ImmutableMap.Builder<EnumWorldBlockLayer, IBakedModel> 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<Optional<EnumFacing>, ImmutableList<BakedQuad>> quadBuilder = ImmutableMap.builder();
quadBuilder.put(Optional.<EnumFacing>absent(), buildQuads(Optional.<EnumFacing>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<EnumWorldBlockLayer> layer)
{
ModelResourceLocation loc = models.get(layer);
if(loc == null)
else
{
loc = new ModelResourceLocation("builtin/missing", "missing");
base = missing;
}
return manager.getModel(loc);
ImmutableMap.Builder<Optional<EnumFacing>, ImmutableList<BakedQuad>> quadBuilder = ImmutableMap.builder();
quadBuilder.put(Optional.<EnumFacing>absent(), buildQuads(models, Optional.<EnumFacing>absent()));
for(EnumFacing side: EnumFacing.values())
{
quadBuilder.put(Optional.of(side), buildQuads(models, Optional.of(side)));
}
quads = quadBuilder.build();
}
private ImmutableList<BakedQuad> buildQuads(Optional<EnumFacing> side)
private static ImmutableList<BakedQuad> buildQuads(ImmutableMap<Optional<EnumWorldBlockLayer>, IFlexibleBakedModel> models, Optional<EnumFacing> side)
{
ImmutableList.Builder<BakedQuad> 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<BakedQuad> getFaceQuads(EnumFacing side)
{
compute();
return quads.get(Optional.of(side));
}
@Override
public List<BakedQuad> 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<EnumWorldBlockLayer> layer = Optional.of(MinecraftForgeClient.getRenderLayer());
if(!models.containsKey(layer))
{
return missing;
}
return bakedModels.get(layer);
return models.get(layer);
}
@Override

View File

@ -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 }
}]
}
}