Perspective awareness for vanilla and multi models, fixes #2148.

Improved error handling in MultiModel.
This commit is contained in:
RainWarrior 2015-10-27 17:40:05 +03:00
parent cf568ae85f
commit 7c7547227c
11 changed files with 211 additions and 86 deletions

View File

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

View File

@ -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<IBakedModel, Matrix4f> handlePerspective(TransformType cameraTransformType);
public static class MapWrapper implements IFlexibleBakedModel, IPerspectiveAwareModel
{
private final IFlexibleBakedModel parent;
private final ImmutableMap<TransformType, TRSRTransformation> transforms;
public MapWrapper(IFlexibleBakedModel parent, ImmutableMap<TransformType, TRSRTransformation> transforms)
{
this.parent = parent;
this.transforms = transforms;
}
public MapWrapper(IFlexibleBakedModel parent, IPerspectiveState state, IModelPart part)
{
this(parent, getTransforms(state, part));
}
public static ImmutableMap<TransformType, TRSRTransformation> getTransforms(IPerspectiveState state, IModelPart part)
{
ImmutableMap.Builder<TransformType, TRSRTransformation> 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<BakedQuad> getFaceQuads(EnumFacing side) { return parent.getFaceQuads(side); }
public List<BakedQuad> getGeneralQuads() { return parent.getGeneralQuads(); }
public VertexFormat getFormat() { return parent.getFormat(); }
@Override
public Pair<IBakedModel, Matrix4f> 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);
}
}
}

View File

@ -94,11 +94,7 @@ public class ItemLayerModel implements IRetexturableModel {
if(state instanceof IPerspectiveState)
{
IPerspectiveState ps = (IPerspectiveState)state;
Map<TransformType, TRSRTransformation> map = Maps.newHashMap();
for(TransformType type : TransformType.values())
{
map.put(type, ps.forPerspective(type).apply(this));
}
ImmutableMap<TransformType, TRSRTransformation> map = IPerspectiveAwareModel.MapWrapper.getTransforms(ps, this);
return new BakedModel(builder.build(), particle, format, Maps.immutableEnumMap(map));
}
return new BakedModel(builder.build(), particle, format);

View File

@ -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<ResourceLocation, TextureAtlasSprite> bakedTextureGetter, boolean uvLocked)
private IFlexibleBakedModel bakeNormal(ModelBlock model, IPerspectiveState perState, TRSRTransformation state, VertexFormat format, Function<ResourceLocation, TextureAtlasSprite> 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);
}
}

View File

@ -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<String, IFlexibleBakedModel> parts;
protected final IFlexibleBakedModel internalBase;
protected final ImmutableList<BakedQuad> general;
protected final ImmutableMap<EnumFacing, ImmutableList<BakedQuad>> faces;
protected final ImmutableMap<TransformType, Pair<Baked, TRSRTransformation>> transforms;
public Baked(IFlexibleBakedModel base, ImmutableMap<String, IFlexibleBakedModel> parts)
{
this(null, false, base, parts);
}
public Baked(ResourceLocation location, boolean perspective, IFlexibleBakedModel base, ImmutableMap<String, IFlexibleBakedModel> 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<EnumFacing, ImmutableList<BakedQuad>> faces = Maps.newEnumMap(EnumFacing.class);
for (EnumFacing face : EnumFacing.values())
{
ImmutableList.Builder<BakedQuad> 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<BakedQuad> 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<TransformType, Pair<Baked, TRSRTransformation>> builder = ImmutableMap.builder();
for(TransformType type : TransformType.values())
{
Pair<IBakedModel, Matrix4f> 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<String, IFlexibleBakedModel> getParts()
{
return parts;
}
@Override
public Pair<IBakedModel, Matrix4f> handlePerspective(TransformType cameraTransformType)
{
if(transforms.isEmpty()) return Pair.of(this, null);
Pair<Baked, TRSRTransformation> 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<String, Pair<IModel, IModelState>> parts;
public MultiModel(IModel base, IModelState baseState, ImmutableMap<String, Pair<IModel, IModelState>> parts)
public MultiModel(ResourceLocation location, IModel base, IModelState baseState, ImmutableMap<String, Pair<IModel, IModelState>> parts)
{
this.location = location;
this.base = base;
this.baseState = baseState;
this.parts = parts;
}
public MultiModel(IModel base, IModelState baseState, ImmutableMap<String, Pair<IModel, IModelState>> parts)
{
this(null, base, baseState, parts);
}
public MultiModel(IModel base, IModelState baseState, Map<String, Pair<IModel, IModelState>> parts)
{
this(base, baseState, ImmutableMap.copyOf(parts));
this(null, base, baseState, ImmutableMap.copyOf(parts));
}
public MultiModel(ResourceLocation location, IModel base, IModelState baseState, Map<String, Pair<IModel, IModelState>> parts)
{
this(location, base, baseState, ImmutableMap.copyOf(parts));
}
@Override
public Collection<ResourceLocation> getDependencies()
{
Set<ResourceLocation> deps = Sets.newHashSet();
if (base != null)
deps.addAll(base.getDependencies());
for (Pair<IModel, IModelState> pair : parts.values())
deps.addAll(pair.getLeft().getDependencies());
return deps;
}
@Override
public Collection<ResourceLocation> getTextures()
{
Set<ResourceLocation> deps = Sets.newHashSet();
if (base != null)
deps.addAll(base.getTextures());
for (Pair<IModel, IModelState> pair : parts.values())
deps.addAll(pair.getLeft().getTextures());
return deps;
}
@Override
public IFlexibleBakedModel bake(IModelState state, VertexFormat format, Function<ResourceLocation, TextureAtlasSprite> bakedTextureGetter)
{
IFlexibleBakedModel bakedBase = null;
if (base != null)
bakedBase = base.bake(state, format, bakedTextureGetter);
ImmutableMap.Builder<String, IFlexibleBakedModel> mapBuilder = ImmutableMap.builder();
for (Entry<String, Pair<IModel, IModelState>> entry : parts.entrySet())
{
Pair<IModel, IModelState> 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.
*/

View File

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

View File

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

View File

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

View File

@ -1,6 +0,0 @@
{
"parent": "block/wall_inventory",
"textures": {
"wall": "blocks/cobblestone"
}
}

View File

@ -1,6 +0,0 @@
{
"parent": "block/wall_inventory",
"textures": {
"wall": "blocks/cobblestone_mossy"
}
}

View File

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