From 7c7547227c9bc2d05e01e0136678fd657ccdf663 Mon Sep 17 00:00:00 2001 From: RainWarrior Date: Tue, 27 Oct 2015 17:40:05 +0300 Subject: [PATCH] Perspective awareness for vanilla and multi models, fixes #2148. Improved error handling in MultiModel. --- .../client/model/BlockStateLoader.java | 2 +- .../client/model/IPerspectiveAwareModel.java | 55 ++++++++ .../client/model/ItemLayerModel.java | 6 +- .../client/model/ModelLoader.java | 29 ++-- .../client/model/MultiModel.java | 130 +++++++++++++----- .../client/model/TRSRTransformation.java | 9 +- .../blockstates/cobblestone_wall.json | 11 +- .../blockstates/mossy_cobblestone_wall.json | 11 +- .../models/item/cobblestone_wall.json | 6 - .../models/item/mossy_cobblestone_wall.json | 6 - .../blockstates/CustomModelBlock.json | 32 ++--- 11 files changed, 211 insertions(+), 86 deletions(-) delete mode 100644 src/test/resources/assets/forgeblockstatesloader/models/item/cobblestone_wall.json delete mode 100644 src/test/resources/assets/forgeblockstatesloader/models/item/mossy_cobblestone_wall.json diff --git a/src/main/java/net/minecraftforge/client/model/BlockStateLoader.java b/src/main/java/net/minecraftforge/client/model/BlockStateLoader.java index 186f9e1d7c..cd3e1d221e 100644 --- a/src/main/java/net/minecraftforge/client/model/BlockStateLoader.java +++ b/src/main/java/net/minecraftforge/client/model/BlockStateLoader.java @@ -198,7 +198,7 @@ public class BlockStateLoader models.put(entry.getKey(), Pair.of(runModelHooks(model, part.getTextures(), part.getCustomData()), partState)); } - return new MultiModel(hasBase ? base : null, baseTr, models.build()); + return new MultiModel(getModelLocation(), hasBase ? base : null, baseTr, models.build()); } @Override diff --git a/src/main/java/net/minecraftforge/client/model/IPerspectiveAwareModel.java b/src/main/java/net/minecraftforge/client/model/IPerspectiveAwareModel.java index cb10dc9ebb..d28c175427 100644 --- a/src/main/java/net/minecraftforge/client/model/IPerspectiveAwareModel.java +++ b/src/main/java/net/minecraftforge/client/model/IPerspectiveAwareModel.java @@ -1,12 +1,22 @@ package net.minecraftforge.client.model; +import java.util.List; + import javax.vecmath.Matrix4f; +import net.minecraft.client.renderer.block.model.BakedQuad; +import net.minecraft.client.renderer.block.model.ItemCameraTransforms; import net.minecraft.client.renderer.block.model.ItemCameraTransforms.TransformType; +import net.minecraft.client.renderer.texture.TextureAtlasSprite; +import net.minecraft.client.renderer.vertex.VertexFormat; import net.minecraft.client.resources.model.IBakedModel; +import net.minecraft.util.EnumFacing; import org.apache.commons.lang3.tuple.Pair; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; + /* * Model that changes based on the rendering perspective * (first-person, GUI, e.t.c - see TransformType) @@ -18,4 +28,49 @@ public interface IPerspectiveAwareModel extends IBakedModel * that should be applied to the GL state before rendering it (matrix may be null). */ Pair handlePerspective(TransformType cameraTransformType); + + public static class MapWrapper implements IFlexibleBakedModel, IPerspectiveAwareModel + { + private final IFlexibleBakedModel parent; + private final ImmutableMap transforms; + + public MapWrapper(IFlexibleBakedModel parent, ImmutableMap transforms) + { + this.parent = parent; + this.transforms = transforms; + } + + public MapWrapper(IFlexibleBakedModel parent, IPerspectiveState state, IModelPart part) + { + this(parent, getTransforms(state, part)); + } + + public static ImmutableMap getTransforms(IPerspectiveState state, IModelPart part) + { + ImmutableMap.Builder builder = ImmutableMap.builder(); + for(TransformType type : TransformType.values()) + { + builder.put(type, state.forPerspective(type).apply(part)); + } + return builder.build(); + } + + public boolean isAmbientOcclusion() { return parent.isAmbientOcclusion(); } + public boolean isGui3d() { return parent.isGui3d(); } + public boolean isBuiltInRenderer() { return parent.isBuiltInRenderer(); } + public TextureAtlasSprite getTexture() { return parent.getTexture(); } + public ItemCameraTransforms getItemCameraTransforms() { return ItemCameraTransforms.DEFAULT; } + public List getFaceQuads(EnumFacing side) { return parent.getFaceQuads(side); } + public List getGeneralQuads() { return parent.getGeneralQuads(); } + public VertexFormat getFormat() { return parent.getFormat(); } + + @Override + public Pair handlePerspective(TransformType cameraTransformType) + { + TRSRTransformation tr = transforms.get(cameraTransformType); + Matrix4f mat = null; + if(tr != null && tr != TRSRTransformation.identity()) mat = TRSRTransformation.blockCornerToCenter(tr).getMatrix(); + return Pair.of((IBakedModel)this, mat); + } + } } diff --git a/src/main/java/net/minecraftforge/client/model/ItemLayerModel.java b/src/main/java/net/minecraftforge/client/model/ItemLayerModel.java index 6a875f4303..89301016fa 100644 --- a/src/main/java/net/minecraftforge/client/model/ItemLayerModel.java +++ b/src/main/java/net/minecraftforge/client/model/ItemLayerModel.java @@ -94,11 +94,7 @@ public class ItemLayerModel implements IRetexturableModel { if(state instanceof IPerspectiveState) { IPerspectiveState ps = (IPerspectiveState)state; - Map map = Maps.newHashMap(); - for(TransformType type : TransformType.values()) - { - map.put(type, ps.forPerspective(type).apply(this)); - } + ImmutableMap map = IPerspectiveAwareModel.MapWrapper.getTransforms(ps, this); return new BakedModel(builder.build(), particle, format, Maps.immutableEnumMap(map)); } return new BakedModel(builder.build(), particle, format); diff --git a/src/main/java/net/minecraftforge/client/model/ModelLoader.java b/src/main/java/net/minecraftforge/client/model/ModelLoader.java index 63e7128edb..3c36588ebf 100644 --- a/src/main/java/net/minecraftforge/client/model/ModelLoader.java +++ b/src/main/java/net/minecraftforge/client/model/ModelLoader.java @@ -209,7 +209,7 @@ public class ModelLoader extends ModelBakery { return new ResourceLocation(model.getResourceDomain(), model.getResourcePath() + ".json"); } - + private void loadAnyModel(ResourceLocation location) throws IOException { if(loadingModels.contains(location)) @@ -324,17 +324,23 @@ public class ModelLoader extends ModelBakery ModelBlock model = this.model; if(model == null) return getMissingModel().bake(state, format, bakedTextureGetter); ItemCameraTransforms transforms = new ItemCameraTransforms(model.getThirdPersonTransform(), model.getFirstPersonTransform(), model.getHeadTransform(), model.getInGuiTransform()); + boolean uvlock = false; + if(state instanceof UVLock) + { + uvlock = true; + state = ((UVLock)state).getParent(); + } + IPerspectiveState perState = state instanceof IPerspectiveState ? (IPerspectiveState)state : new IPerspectiveState.Impl(state, transforms); if(hasItemModel(model)) { - IPerspectiveState perState = state instanceof IPerspectiveState ? (IPerspectiveState)state : new IPerspectiveState.Impl(state, transforms); return new ItemLayerModel(model).bake(perState, format, bakedTextureGetter); } if(isCustomRenderer(model)) return new IFlexibleBakedModel.Wrapper(new BuiltInModel(transforms), format); // TODO perspective awareness for this - return bakeNormal(model, state.apply(this), format, bakedTextureGetter, state instanceof UVLock); + return bakeNormal(model, perState, state.apply(this), format, bakedTextureGetter, uvlock); } - private IFlexibleBakedModel bakeNormal(ModelBlock model, TRSRTransformation state, VertexFormat format, Function bakedTextureGetter, boolean uvLocked) + private IFlexibleBakedModel bakeNormal(ModelBlock model, IPerspectiveState perState, TRSRTransformation state, VertexFormat format, Function bakedTextureGetter, boolean uvLocked) { TextureAtlasSprite particle = bakedTextureGetter.apply(new ResourceLocation(model.resolveTextureName("particle"))); SimpleBakedModel.Builder builder = (new SimpleBakedModel.Builder(model)).setTexture(particle); @@ -355,7 +361,7 @@ public class ModelLoader extends ModelBakery } } - return new IFlexibleBakedModel.Wrapper(builder.makeBakedModel(), format); + return new IPerspectiveAwareModel.MapWrapper(new IFlexibleBakedModel.Wrapper(builder.makeBakedModel(), format), perState, this); } public IModelState getDefaultState() @@ -427,16 +433,21 @@ public class ModelLoader extends ModelBakery public static class UVLock implements IModelState { - private final IModelState state; + private final IModelState parent; - public UVLock(IModelState state) + public UVLock(IModelState parent) { - this.state = state; + this.parent = parent; + } + + public IModelState getParent() + { + return parent; } public TRSRTransformation apply(IModelPart part) { - return state.apply(part); + return parent.apply(part); } } diff --git a/src/main/java/net/minecraftforge/client/model/MultiModel.java b/src/main/java/net/minecraftforge/client/model/MultiModel.java index 221398e62e..18a36c8ec4 100644 --- a/src/main/java/net/minecraftforge/client/model/MultiModel.java +++ b/src/main/java/net/minecraftforge/client/model/MultiModel.java @@ -8,14 +8,20 @@ import java.util.Map; import java.util.Map.Entry; import java.util.Set; +import javax.vecmath.Matrix4f; + import net.minecraft.client.renderer.block.model.BakedQuad; import net.minecraft.client.renderer.block.model.ItemCameraTransforms; +import net.minecraft.client.renderer.block.model.ItemCameraTransforms.TransformType; import net.minecraft.client.renderer.texture.TextureAtlasSprite; import net.minecraft.client.renderer.vertex.VertexFormat; +import net.minecraft.client.resources.model.IBakedModel; import net.minecraft.util.EnumFacing; import net.minecraft.util.ResourceLocation; +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.collect.ImmutableList; @@ -25,20 +31,26 @@ import com.google.common.collect.Sets; public class MultiModel implements IModel { - public static class Baked implements IFlexibleBakedModel + public static class Baked implements IFlexibleBakedModel, IPerspectiveAwareModel { protected final IFlexibleBakedModel base; protected final ImmutableMap parts; - + protected final IFlexibleBakedModel internalBase; protected final ImmutableList general; protected final ImmutableMap> faces; - + protected final ImmutableMap> transforms; + public Baked(IFlexibleBakedModel base, ImmutableMap parts) + { + this(null, false, base, parts); + } + + public Baked(ResourceLocation location, boolean perspective, IFlexibleBakedModel base, ImmutableMap parts) { this.base = base; this.parts = parts; - + if (base != null) internalBase = base; else @@ -47,12 +59,12 @@ public class MultiModel implements IModel if (iter.hasNext()) internalBase = iter.next(); else - throw new RuntimeException("No base model or submodel provided for this MultiModel.Baked."); + throw new IllegalArgumentException("No base model or submodel provided for MultiModel.Baked " + location + "."); } - + // Create map of each face's quads. EnumMap> faces = Maps.newEnumMap(EnumFacing.class); - + for (EnumFacing face : EnumFacing.values()) { ImmutableList.Builder faceQuads = ImmutableList.builder(); @@ -62,9 +74,9 @@ public class MultiModel implements IModel faceQuads.addAll(bakedPart.getFaceQuads(face)); faces.put(face, faceQuads.build()); } - + this.faces = Maps.immutableEnumMap(faces); - + // Create list of general quads. ImmutableList.Builder genQuads = ImmutableList.builder(); if (base != null) @@ -72,8 +84,34 @@ public class MultiModel implements IModel for (IFlexibleBakedModel bakedPart : parts.values()) genQuads.addAll(bakedPart.getGeneralQuads()); general = genQuads.build(); + + // Only changes the base model based on perspective, may recurse for parts in the future. + if(perspective && base instanceof IPerspectiveAwareModel) + { + IPerspectiveAwareModel perBase = (IPerspectiveAwareModel)base; + ImmutableMap.Builder> builder = ImmutableMap.builder(); + for(TransformType type : TransformType.values()) + { + Pair p = perBase.handlePerspective(type); + IFlexibleBakedModel newBase; + if(p.getLeft() instanceof IFlexibleBakedModel) + { + newBase = (IFlexibleBakedModel)p.getLeft(); + } + else + { + newBase = new IFlexibleBakedModel.Wrapper(p.getLeft(), base.getFormat()); + } + builder.put(type, Pair.of(new Baked(location, false, newBase, parts), new TRSRTransformation(p.getRight()))); + } + transforms = builder.build(); + } + else + { + transforms = ImmutableMap.of(); + } } - + @Override public boolean isAmbientOcclusion() { @@ -121,87 +159,113 @@ public class MultiModel implements IModel { return internalBase.getFormat(); } - + public IFlexibleBakedModel getBaseModel() { return base; } - + public Map getParts() { return parts; } + + @Override + public Pair handlePerspective(TransformType cameraTransformType) + { + if(transforms.isEmpty()) return Pair.of(this, null); + Pair p = transforms.get(cameraTransformType); + return Pair.of(p.getLeft(), p.getRight().getMatrix()); + } } - + + protected final ResourceLocation location; protected final IModel base; protected final IModelState baseState; protected final Map> parts; - - public MultiModel(IModel base, IModelState baseState, ImmutableMap> parts) + + public MultiModel(ResourceLocation location, IModel base, IModelState baseState, ImmutableMap> parts) { + this.location = location; this.base = base; this.baseState = baseState; this.parts = parts; } + public MultiModel(IModel base, IModelState baseState, ImmutableMap> parts) + { + this(null, base, baseState, parts); + } + public MultiModel(IModel base, IModelState baseState, Map> parts) { - this(base, baseState, ImmutableMap.copyOf(parts)); + this(null, base, baseState, ImmutableMap.copyOf(parts)); } - + + public MultiModel(ResourceLocation location, IModel base, IModelState baseState, Map> parts) + { + this(location, base, baseState, ImmutableMap.copyOf(parts)); + } + @Override public Collection getDependencies() { Set deps = Sets.newHashSet(); - + if (base != null) deps.addAll(base.getDependencies()); - + for (Pair pair : parts.values()) deps.addAll(pair.getLeft().getDependencies()); - + return deps; } - + @Override public Collection getTextures() { Set deps = Sets.newHashSet(); - + if (base != null) deps.addAll(base.getTextures()); - + for (Pair pair : parts.values()) deps.addAll(pair.getLeft().getTextures()); - + return deps; } - + @Override public IFlexibleBakedModel bake(IModelState state, VertexFormat format, Function bakedTextureGetter) { IFlexibleBakedModel bakedBase = null; - + if (base != null) bakedBase = base.bake(state, format, bakedTextureGetter); - + ImmutableMap.Builder mapBuilder = ImmutableMap.builder(); - + for (Entry> entry : parts.entrySet()) { Pair pair = entry.getValue(); mapBuilder.put(entry.getKey(), pair.getLeft().bake(pair.getRight(), format, bakedTextureGetter)); } - - return new Baked(bakedBase, mapBuilder.build()); + + if(bakedBase == null && parts.isEmpty()) + { + FMLLog.log(Level.ERROR, "MultiModel %s is empty (no base model or parts were provided/resolved)", location); + IModel missing = ModelLoaderRegistry.getMissingModel(); + return missing.bake(missing.getDefaultState(), format, bakedTextureGetter); + } + return new Baked(location, true, bakedBase, mapBuilder.build()); } - + @Override public IModelState getDefaultState() { return baseState; } - + /** * @return The base model of this MultiModel. May be null. */ @@ -209,7 +273,7 @@ public class MultiModel implements IModel { return base; } - + /** * @return A map of the submodel name to its IModel and IModelState. */ diff --git a/src/main/java/net/minecraftforge/client/model/TRSRTransformation.java b/src/main/java/net/minecraftforge/client/model/TRSRTransformation.java index 697dc86aa2..34d7962ea6 100644 --- a/src/main/java/net/minecraftforge/client/model/TRSRTransformation.java +++ b/src/main/java/net/minecraftforge/client/model/TRSRTransformation.java @@ -40,7 +40,14 @@ public class TRSRTransformation implements IModelState, ITransformation public TRSRTransformation(Matrix4f matrix) { - this.matrix = matrix; + if(matrix == null) + { + this.matrix = identity.matrix; + } + else + { + this.matrix = matrix; + } } public TRSRTransformation(Vector3f translation, Quat4f leftRot, Vector3f scale, Quat4f rightRot) diff --git a/src/test/resources/assets/forgeblockstatesloader/blockstates/cobblestone_wall.json b/src/test/resources/assets/forgeblockstatesloader/blockstates/cobblestone_wall.json index 9ee3db701d..77d640332c 100644 --- a/src/test/resources/assets/forgeblockstatesloader/blockstates/cobblestone_wall.json +++ b/src/test/resources/assets/forgeblockstatesloader/blockstates/cobblestone_wall.json @@ -3,7 +3,8 @@ "defaults": { "textures": {"wall": "blocks/cobblestone"}, "model": "cobblestone_wall_post", - "uvlock": true // This and all other properties of "defaults" will be inherited by simple submodels. They will NOT be inherited by named submodels. + "uvlock": true, // This and all other properties of "defaults" will be inherited by simple submodels. They will NOT be inherited by named submodels. + "transform": "forge:default-block" }, "variants": { "north": { @@ -24,6 +25,12 @@ }, "up": {"true": {}, "false": {}}, // Must have this in here or the blockstates loader will not know of all the properties and values, and it will create the wrong vanilla state strings. "east=false,north=true,south=true,up=false,west=false": {"model": null}, // Fully specified variant, will inherit from variants above, but remove the model set in "defaults", removing the wall post. - "east=true,north=false,south=false,up=false,west=true": {"model": null} + "east=true,north=false,south=false,up=false,west=true": {"model": null}, + "inventory": [{ // inventory variant can be specified here too, and it will inherit properties from "defaults" + submodel: { + "north": { "model": "forgeblockstatesloader:wall_connect" }, + "south": { "model": "forgeblockstatesloader:wall_connect", "y": 180 } + } + }] } } diff --git a/src/test/resources/assets/forgeblockstatesloader/blockstates/mossy_cobblestone_wall.json b/src/test/resources/assets/forgeblockstatesloader/blockstates/mossy_cobblestone_wall.json index 853c9a3429..bf225df82e 100644 --- a/src/test/resources/assets/forgeblockstatesloader/blockstates/mossy_cobblestone_wall.json +++ b/src/test/resources/assets/forgeblockstatesloader/blockstates/mossy_cobblestone_wall.json @@ -3,7 +3,8 @@ "defaults": { "textures": {"wall": "blocks/cobblestone_mossy"}, "model": "cobblestone_wall_post", - "uvlock": true + "uvlock": true, + "transform": "forge:default-block" }, "variants": { "north": { @@ -24,6 +25,12 @@ }, "up": {"true": {}, "false": {}}, "east=false,north=true,south=true,up=false,west=false": {"model": null}, - "east=true,north=false,south=false,up=false,west=true": {"model": null} + "east=true,north=false,south=false,up=false,west=true": {"model": null}, + "inventory": [{ // inventory variant can be specified here too, and it will inherit properties from "defaults" + submodel: { + "north": { "model": "forgeblockstatesloader:wall_connect" }, + "south": { "model": "forgeblockstatesloader:wall_connect", "y": 180 } + } + }] } } diff --git a/src/test/resources/assets/forgeblockstatesloader/models/item/cobblestone_wall.json b/src/test/resources/assets/forgeblockstatesloader/models/item/cobblestone_wall.json deleted file mode 100644 index 079cb13fec..0000000000 --- a/src/test/resources/assets/forgeblockstatesloader/models/item/cobblestone_wall.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "parent": "block/wall_inventory", - "textures": { - "wall": "blocks/cobblestone" - } -} diff --git a/src/test/resources/assets/forgeblockstatesloader/models/item/mossy_cobblestone_wall.json b/src/test/resources/assets/forgeblockstatesloader/models/item/mossy_cobblestone_wall.json deleted file mode 100644 index f5f126f53a..0000000000 --- a/src/test/resources/assets/forgeblockstatesloader/models/item/mossy_cobblestone_wall.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "parent": "block/wall_inventory", - "textures": { - "wall": "blocks/cobblestone_mossy" - } -} diff --git a/src/test/resources/assets/forgedebugmodelloaderregistry/blockstates/CustomModelBlock.json b/src/test/resources/assets/forgedebugmodelloaderregistry/blockstates/CustomModelBlock.json index 6bd2f3faf1..b1926396b1 100644 --- a/src/test/resources/assets/forgedebugmodelloaderregistry/blockstates/CustomModelBlock.json +++ b/src/test/resources/assets/forgedebugmodelloaderregistry/blockstates/CustomModelBlock.json @@ -2,34 +2,24 @@ "forge_marker": 1, "defaults": { "textures": { - "#texture": "forgedebugmodelloaderregistry:texture", + //"#texture": "forgedebugmodelloaderregistry:texture", "#chest": "entity/chest/normal" }, - "model": "forgedebugmodelloaderregistry:chest.b3d" + "model": "forgedebugmodelloaderregistry:chest.b3d", + "transform": "forge:default-block" }, "variants": { - "normal": [{ - /*"transform": { - "rotation": {"y": 45} - }*/ - }], + "normal": [{}], "inventory": [{ - /*"transform": { - "thirdperson": { - "translation": [ 0, 0.09375, -0.171875 ], - "rotation": [ {"y": -45}, {"x": 10}, {"z": 170} ], - "scale": 0.375 - } - }*/ - "transform": "forge:default-block" + "y": 180 }], "facing": { - "down": {"model": "forgedebugmodelloaderregistry:chest.b3d", "x": 90}, - "up": {"model": "forgedebugmodelloaderregistry:chest.b3d", "x": 270}, - "north": {"model": "forgedebugmodelloaderregistry:chest.b3d"}, - "south": {"model": "forgedebugmodelloaderregistry:chest.b3d", "y": 180}, - "west": {"model": "forgedebugmodelloaderregistry:chest.b3d", "y": 270}, - "east": {"model": "forgedebugmodelloaderregistry:chest.b3d", "y": 90} + "down": {"x": 90}, + "up": {"x": 270}, + "north": {"y": 180}, + "south": {}, + "west": {"y": 90}, + "east": {"y": 270} } } }