ForgePatch/src/main/java/net/minecraftforge/client/model/b3d/B3DLoader.java

837 lines
32 KiB
Java

/*
* Minecraft Forge
* Copyright (c) 2016-2019.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation version 2.1
* of the License.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
package net.minecraftforge.client.model.b3d;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Random;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import javax.annotation.Nullable;
import com.mojang.blaze3d.matrix.MatrixStack;
import net.minecraft.client.renderer.Matrix4f;
import net.minecraft.client.renderer.TransformationMatrix;
import net.minecraft.client.renderer.Vector3f;
import net.minecraft.client.renderer.model.*;
import net.minecraft.client.renderer.texture.AtlasTexture;
import net.minecraft.client.renderer.texture.MissingTextureSprite;
import net.minecraft.client.renderer.vertex.DefaultVertexFormats;
import net.minecraft.client.renderer.vertex.VertexFormatElement;
import net.minecraftforge.client.model.*;
import net.minecraftforge.common.model.*;
import net.minecraftforge.resource.IResourceType;
import net.minecraftforge.resource.ISelectiveResourceReloadListener;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.tuple.Triple;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import com.google.common.base.Objects;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.gson.JsonElement;
import com.google.gson.JsonParser;
import net.minecraft.block.BlockState;
import net.minecraft.client.renderer.model.ItemCameraTransforms.TransformType;
import net.minecraft.client.renderer.texture.TextureAtlasSprite;
import net.minecraft.resources.IResource;
import net.minecraft.resources.IResourceManager;
import net.minecraft.util.Direction;
import net.minecraft.util.ResourceLocation;
import net.minecraft.util.math.MathHelper;
import net.minecraftforge.client.model.b3d.B3DModel.Animation;
import net.minecraftforge.client.model.b3d.B3DModel.Face;
import net.minecraftforge.client.model.b3d.B3DModel.Key;
import net.minecraftforge.client.model.b3d.B3DModel.Mesh;
import net.minecraftforge.client.model.b3d.B3DModel.Node;
import net.minecraftforge.client.model.b3d.B3DModel.Texture;
import net.minecraftforge.client.model.b3d.B3DModel.Vertex;
import net.minecraftforge.client.model.data.IDynamicBakedModel;
import net.minecraftforge.client.model.data.IModelData;
import net.minecraftforge.client.model.pipeline.BakedQuadBuilder;
import net.minecraftforge.client.model.pipeline.IVertexConsumer;
import net.minecraftforge.common.model.animation.IClip;
import net.minecraftforge.common.model.animation.IJoint;
import net.minecraftforge.common.property.Properties;
/*
* Loader for Blitz3D models.
* To enable for your mod call instance.addDomain(modId).
* If you need more control over accepted resources - extend the class, and register a new instance with ModelLoaderRegistry.
*/
// TODO: Implement as a new model loader
public enum B3DLoader implements ISelectiveResourceReloadListener
{
INSTANCE;
private static final Logger LOGGER = LogManager.getLogger();
private IResourceManager manager;
private final Set<String> enabledDomains = new HashSet<>();
private final Map<ResourceLocation, B3DModel> cache = new HashMap<>();
@Override
public void onResourceManagerReload(IResourceManager manager, Predicate<IResourceType> resourcePredicate)
{
this.manager = manager;
cache.clear();
}
@SuppressWarnings("unchecked")
public IUnbakedModel loadModel(ResourceLocation modelLocation) throws Exception
{
ResourceLocation file = new ResourceLocation(modelLocation.getNamespace(), modelLocation.getPath());
if(!cache.containsKey(file))
{
IResource resource = null;
try
{
try
{
resource = manager.getResource(file);
}
catch(FileNotFoundException e)
{
if(modelLocation.getPath().startsWith("models/block/"))
resource = manager.getResource(new ResourceLocation(file.getNamespace(), "models/item/" + file.getPath().substring("models/block/".length())));
else if(modelLocation.getPath().startsWith("models/item/"))
resource = manager.getResource(new ResourceLocation(file.getNamespace(), "models/block/" + file.getPath().substring("models/item/".length())));
else throw e;
}
B3DModel.Parser parser = new B3DModel.Parser(resource.getInputStream());
B3DModel model = parser.parse();
cache.put(file, model);
}
catch(IOException e)
{
cache.put(file, null);
throw e;
}
finally
{
IOUtils.closeQuietly(resource);
}
}
B3DModel model = cache.get(file);
if(model == null) throw new ModelLoadingException("Error loading model previously: " + file);
if(!(model.getRoot().getKind() instanceof Mesh))
{
return new ModelWrapper(modelLocation, model, ImmutableSet.of(), true, true, 1);
}
return new ModelWrapper(modelLocation, model, ImmutableSet.of(model.getRoot().getName()), true, true, 1);
}
public static final class B3DState implements IModelTransform
{
@Nullable
private final Animation animation;
private final int frame;
private final int nextFrame;
private final float progress;
@Nullable
private final IModelTransform parent;
public B3DState(@Nullable Animation animation, int frame)
{
this(animation, frame, frame, 0);
}
public B3DState(@Nullable Animation animation, int frame, IModelTransform parent)
{
this(animation, frame, frame, 0, parent);
}
public B3DState(@Nullable Animation animation, int frame, int nextFrame, float progress)
{
this(animation, frame, nextFrame, progress, null);
}
public B3DState(@Nullable Animation animation, int frame, int nextFrame, float progress, @Nullable IModelTransform parent)
{
this.animation = animation;
this.frame = frame;
this.nextFrame = nextFrame;
this.progress = MathHelper.clamp(progress, 0, 1);
this.parent = getParent(parent);
}
@Nullable
private IModelTransform getParent(@Nullable IModelTransform parent)
{
if (parent == null) return null;
else if (parent instanceof B3DState) return ((B3DState)parent).parent;
return parent;
}
@Nullable
public Animation getAnimation()
{
return animation;
}
public int getFrame()
{
return frame;
}
public int getNextFrame()
{
return nextFrame;
}
public float getProgress()
{
return progress;
}
@Nullable
public IModelTransform getParent()
{
return parent;
}
@Override
public TransformationMatrix func_225615_b_()
{
if(parent != null)
{
return parent.func_225615_b_();
}
return TransformationMatrix.func_227983_a_();
}
@Override
public TransformationMatrix getPartTransformation(Object part)
{
// TODO make more use of Optional
if(!(part instanceof NodeJoint))
{
return TransformationMatrix.func_227983_a_();
}
Node<?> node = ((NodeJoint)part).getNode();
TransformationMatrix nodeTransform;
if(progress < 1e-5 || frame == nextFrame)
{
nodeTransform = getNodeMatrix(node, frame);
}
else if(progress > 1 - 1e-5)
{
nodeTransform = getNodeMatrix(node, nextFrame);
}
else
{
nodeTransform = getNodeMatrix(node, frame);
nodeTransform = TransformationHelper.slerp(nodeTransform,getNodeMatrix(node, nextFrame), progress);
}
if(parent != null && node.getParent() == null)
{
return parent.getPartTransformation(part).compose(nodeTransform);
}
return nodeTransform;
}
private static LoadingCache<Triple<Animation, Node<?>, Integer>, TransformationMatrix> cache = CacheBuilder.newBuilder()
.maximumSize(16384)
.expireAfterAccess(2, TimeUnit.MINUTES)
.build(new CacheLoader<Triple<Animation, Node<?>, Integer>, TransformationMatrix>()
{
@Override
public TransformationMatrix load(Triple<Animation, Node<?>, Integer> key) throws Exception
{
return getNodeMatrix(key.getLeft(), key.getMiddle(), key.getRight());
}
});
public TransformationMatrix getNodeMatrix(Node<?> node)
{
return getNodeMatrix(node, frame);
}
public TransformationMatrix getNodeMatrix(Node<?> node, int frame)
{
return cache.getUnchecked(Triple.of(animation, node, frame));
}
public static TransformationMatrix getNodeMatrix(@Nullable Animation animation, Node<?> node, int frame)
{
TransformationMatrix ret = TransformationMatrix.func_227983_a_();
Key key = null;
if(animation != null) key = animation.getKeys().get(frame, node);
else if(node.getAnimation() != null) key = node.getAnimation().getKeys().get(frame, node);
if(key != null)
{
Node<?> parent = node.getParent();
if(parent != null)
{
// parent model-global current pose
TransformationMatrix pm = cache.getUnchecked(Triple.of(animation, node.getParent(), frame));
ret = ret.compose(pm);
// joint offset in the parent coords
ret = ret.compose(new TransformationMatrix(parent.getPos(), parent.getRot(), parent.getScale(), null));
}
// current node local pose
ret = ret.compose(new TransformationMatrix(key.getPos(), key.getRot(), key.getScale(), null));
// this part moved inside the model
// inverse bind of the current node
/*Matrix4f rm = new TRSRTransformation(node.getPos(), node.getRot(), node.getScale(), null).getMatrix();
rm.invert();
ret = ret.compose(new TRSRTransformation(rm));
if(parent != null)
{
// inverse bind of the parent
rm = new TRSRTransformation(parent.getPos(), parent.getRot(), parent.getScale(), null).getMatrix();
rm.invert();
ret = ret.compose(new TRSRTransformation(rm));
}*/
// TODO cache
TransformationMatrix invBind = new NodeJoint(node).getInvBindPose();
ret = ret.compose(invBind);
}
else
{
Node<?> parent = node.getParent();
if(parent != null)
{
// parent model-global current pose
TransformationMatrix pm = cache.getUnchecked(Triple.of(animation, node.getParent(), frame));
ret = ret.compose(pm);
// joint offset in the parent coords
ret = ret.compose(new TransformationMatrix(parent.getPos(), parent.getRot(), parent.getScale(), null));
}
ret = ret.compose(new TransformationMatrix(node.getPos(), node.getRot(), node.getScale(), null));
// TODO cache
TransformationMatrix invBind = new NodeJoint(node).getInvBindPose();
ret = ret.compose(invBind);
}
return ret;
}
}
static final class NodeJoint implements IJoint
{
private final Node<?> node;
public NodeJoint(Node<?> node)
{
this.node = node;
}
@Override
public TransformationMatrix getInvBindPose()
{
Matrix4f m = new TransformationMatrix(node.getPos(), node.getRot(), node.getScale(), null).func_227988_c_();
m.func_226600_c_();
TransformationMatrix pose = new TransformationMatrix(m);
if(node.getParent() != null)
{
TransformationMatrix parent = new NodeJoint(node.getParent()).getInvBindPose();
pose = pose.compose(parent);
}
return pose;
}
@Override
public Optional<NodeJoint> getParent()
{
// FIXME cache?
if(node.getParent() == null) return Optional.empty();
return Optional.of(new NodeJoint(node.getParent()));
}
public Node<?> getNode()
{
return node;
}
@Override
public int hashCode()
{
return node.hashCode();
}
@Override
public boolean equals(Object obj)
{
if (this == obj) return true;
if (!super.equals(obj)) return false;
if (getClass() != obj.getClass()) return false;
NodeJoint other = (NodeJoint) obj;
return Objects.equal(node, other.node);
}
}
private static final class ModelWrapper implements IUnbakedModel
{
private final ResourceLocation modelLocation;
private final B3DModel model;
private final ImmutableSet<String> meshes;
private final ImmutableMap<String, String> textures;
private final boolean smooth;
private final boolean gui3d;
private final int defaultKey;
public ModelWrapper(ResourceLocation modelLocation, B3DModel model, ImmutableSet<String> meshes, boolean smooth, boolean gui3d, int defaultKey)
{
this(modelLocation, model, meshes, smooth, gui3d, defaultKey, buildTextures(model.getTextures()));
}
public ModelWrapper(ResourceLocation modelLocation, B3DModel model, ImmutableSet<String> meshes, boolean smooth, boolean gui3d, int defaultKey, ImmutableMap<String, String> textures)
{
this.modelLocation = modelLocation;
this.model = model;
this.meshes = meshes;
this.textures = textures;
this.smooth = smooth;
this.gui3d = gui3d;
this.defaultKey = defaultKey;
}
private static ImmutableMap<String, String> buildTextures(List<Texture> textures)
{
ImmutableMap.Builder<String, String> builder = ImmutableMap.builder();
for(Texture t : textures)
{
String path = t.getPath();
String location = getLocation(path);
if(!location.startsWith("#")) location = "#" + location;
builder.put(path, location);
}
return builder.build();
}
private static String getLocation(String path)
{
if(path.endsWith(".png")) path = path.substring(0, path.length() - ".png".length());
return path;
}
@Override
public Collection<Material> func_225614_a_(Function<ResourceLocation, IUnbakedModel> p_225614_1_, Set<com.mojang.datafixers.util.Pair<String, String>> p_225614_2_)
{
return textures.values().stream().filter(loc -> !loc.startsWith("#"))
.map(t -> new Material(AtlasTexture.LOCATION_BLOCKS_TEXTURE, new ResourceLocation(t)))
.collect(Collectors.toList());
}
@Override
public Collection<ResourceLocation> getDependencies()
{
return Collections.emptyList();
}
@Nullable
@Override
public IBakedModel func_225613_a_(ModelBakery bakery, Function<Material, TextureAtlasSprite> spriteGetter, IModelTransform modelTransform, ResourceLocation modelLocation)
{
ImmutableMap.Builder<String, TextureAtlasSprite> builder = ImmutableMap.builder();
TextureAtlasSprite missing = spriteGetter.apply(new Material(AtlasTexture.LOCATION_BLOCKS_TEXTURE, MissingTextureSprite.getLocation()));
for(Map.Entry<String, String> e : textures.entrySet())
{
if(e.getValue().startsWith("#"))
{
LOGGER.fatal("unresolved texture '{}' for b3d model '{}'", e.getValue(), this.modelLocation);
builder.put(e.getKey(), missing);
}
else
{
builder.put(e.getKey(), spriteGetter.apply(new Material(AtlasTexture.LOCATION_BLOCKS_TEXTURE, new ResourceLocation(e.getValue()))));
}
}
builder.put("missingno", missing);
return new BakedWrapper(model.getRoot(), modelTransform, smooth, gui3d, meshes, builder.build());
}
public ModelWrapper retexture(ImmutableMap<String, String> textures)
{
ImmutableMap.Builder<String, String> builder = ImmutableMap.builder();
for(Map.Entry<String, String> e : this.textures.entrySet())
{
String path = e.getKey();
String loc = getLocation(path);
// FIXME: Backward compatibilty: support finding textures that start with #, even though this is not how vanilla works
if(loc.startsWith("#") && (textures.containsKey(loc) || textures.containsKey(loc.substring(1))))
{
String alt = loc.substring(1);
String newLoc = textures.get(loc);
if(newLoc == null) newLoc = textures.get(alt);
if(newLoc == null) newLoc = path.substring(1);
builder.put(e.getKey(), newLoc);
}
else
{
builder.put(e);
}
}
return new ModelWrapper(modelLocation, model, meshes, smooth, gui3d, defaultKey, builder.build());
}
public ModelWrapper process(ImmutableMap<String, String> data)
{
ImmutableSet<String> newMeshes = this.meshes;
int newDefaultKey = this.defaultKey;
boolean hasChanged = false;
if(data.containsKey("mesh"))
{
JsonElement e = new JsonParser().parse(data.get("mesh"));
if(e.isJsonPrimitive() && e.getAsJsonPrimitive().isString())
{
return new ModelWrapper(modelLocation, model, ImmutableSet.of(e.getAsString()), smooth, gui3d, defaultKey, textures);
}
else if (e.isJsonArray())
{
ImmutableSet.Builder<String> builder = ImmutableSet.builder();
for(JsonElement s : e.getAsJsonArray())
{
if(s.isJsonPrimitive() && s.getAsJsonPrimitive().isString())
{
builder.add(s.getAsString());
}
else
{
LOGGER.fatal("unknown mesh definition '{}' in array for b3d model '{}'", s.toString(), modelLocation);
return this;
}
}
newMeshes = builder.build();
hasChanged = true;
}
else
{
LOGGER.fatal("unknown mesh definition '{}' for b3d model '{}'", e.toString(), modelLocation);
return this;
}
}
if(data.containsKey("key"))
{
JsonElement e = new JsonParser().parse(data.get("key"));
if(e.isJsonPrimitive() && e.getAsJsonPrimitive().isNumber())
{
newDefaultKey = e.getAsNumber().intValue();
hasChanged = true;
}
else
{
LOGGER.fatal("unknown keyframe definition '{}' for b3d model '{}'", e.toString(), modelLocation);
return this;
}
}
return hasChanged ? new ModelWrapper(modelLocation, model, newMeshes, smooth, gui3d, newDefaultKey, textures) : this;
}
@Override
public Optional<IClip> getClip(String name)
{
if(name.equals("main"))
{
return Optional.of(B3DClip.INSTANCE);
}
return Optional.empty();
}
public IModelTransform getDefaultState()
{
return new B3DState(model.getRoot().getAnimation(), defaultKey, defaultKey, 0);
}
public ModelWrapper smoothLighting(boolean value)
{
if(value == smooth)
{
return this;
}
return new ModelWrapper(modelLocation, model, meshes, value, gui3d, defaultKey, textures);
}
public ModelWrapper gui3d(boolean value)
{
if(value == gui3d)
{
return this;
}
return new ModelWrapper(modelLocation, model, meshes, smooth, value, defaultKey, textures);
}
}
private static final class BakedWrapper implements IDynamicBakedModel
{
private final Node<?> node;
private final IModelTransform state;
private final boolean smooth;
private final boolean gui3d;
private final ImmutableSet<String> meshes;
private final ImmutableMap<String, TextureAtlasSprite> textures;
private final LoadingCache<Integer, B3DState> cache;
private ImmutableList<BakedQuad> quads;
public BakedWrapper(final Node<?> node, final IModelTransform state, final boolean smooth, final boolean gui3d, final ImmutableSet<String> meshes, final ImmutableMap<String, TextureAtlasSprite> textures)
{
this(node, state, smooth, gui3d, meshes, textures, CacheBuilder.newBuilder()
.maximumSize(128)
.expireAfterAccess(2, TimeUnit.MINUTES)
.build(new CacheLoader<Integer, B3DState>()
{
@Override
public B3DState load(Integer frame) throws Exception
{
IModelTransform parent = state;
Animation newAnimation = node.getAnimation();
if(parent instanceof B3DState)
{
B3DState ps = (B3DState)parent;
parent = ps.getParent();
}
return new B3DState(newAnimation, frame, frame, 0, parent);
}
}));
}
public BakedWrapper(Node<?> node, IModelTransform state, boolean smooth, boolean gui3d, ImmutableSet<String> meshes, ImmutableMap<String, TextureAtlasSprite> textures, LoadingCache<Integer, B3DState> cache)
{
this.node = node;
this.state = state;
this.smooth = smooth;
this.gui3d = gui3d;
this.meshes = meshes;
this.textures = textures;
this.cache = cache;
}
@Override
public List<BakedQuad> getQuads(@Nullable BlockState state, @Nullable Direction side, Random rand, IModelData data)
{
if(side != null) return ImmutableList.of();
IModelTransform modelState = this.state;
IModelTransform newState = data.getData(Properties.AnimationProperty);
if(newState != null)
{
// FIXME: should animation state handle the parent state, or should it remain here?
IModelTransform parent = this.state;
if(parent instanceof B3DState)
{
B3DState ps = (B3DState)parent;
parent = ps.getParent();
}
if (parent == null)
{
modelState = newState;
}
else
{
modelState = new ModelTransformComposition(parent, newState);
}
}
if(quads == null)
{
ImmutableList.Builder<BakedQuad> builder = ImmutableList.builder();
generateQuads(builder, node, this.state, ImmutableList.of());
quads = builder.build();
}
// TODO: caching?
if(this.state != modelState)
{
ImmutableList.Builder<BakedQuad> builder = ImmutableList.builder();
generateQuads(builder, node, modelState, ImmutableList.of());
return builder.build();
}
return quads;
}
private void generateQuads(ImmutableList.Builder<BakedQuad> builder, Node<?> node, final IModelTransform state, ImmutableList<String> path)
{
ImmutableList.Builder<String> pathBuilder = ImmutableList.builder();
pathBuilder.addAll(path);
pathBuilder.add(node.getName());
ImmutableList<String> newPath = pathBuilder.build();
for(Node<?> child : node.getNodes().values())
{
generateQuads(builder, child, state, newPath);
}
if(node.getKind() instanceof Mesh && meshes.contains(node.getName()) && state.getPartTransformation(Models.getHiddenModelPart(newPath)).isIdentity())
{
Mesh mesh = (Mesh)node.getKind();
Collection<Face> faces = mesh.bake(new Function<Node<?>, Matrix4f>()
{
private final TransformationMatrix global = state.func_225615_b_();
private final LoadingCache<Node<?>, TransformationMatrix> localCache = CacheBuilder.newBuilder()
.maximumSize(32)
.build(new CacheLoader<Node<?>, TransformationMatrix>()
{
@Override
public TransformationMatrix load(Node<?> node) throws Exception
{
return state.getPartTransformation(new NodeJoint(node));
}
});
@Override
public Matrix4f apply(Node<?> node)
{
return global.compose(localCache.getUnchecked(node)).func_227988_c_();
}
});
for(Face f : faces)
{
List<Texture> textures = null;
if(f.getBrush() != null) textures = f.getBrush().getTextures();
TextureAtlasSprite sprite;
if(textures == null || textures.isEmpty()) sprite = this.textures.get("missingno");
else if(textures.get(0) == B3DModel.Texture.White) sprite = ModelLoader.White.instance();
else sprite = this.textures.get(textures.get(0).getPath());
BakedQuadBuilder quadBuilder = new BakedQuadBuilder(sprite);
quadBuilder.setContractUVs(true);
quadBuilder.setQuadOrientation(Direction.getFacingFromVector(f.getNormal().getX(), f.getNormal().getY(), f.getNormal().getZ()));
putVertexData(quadBuilder, f.getV1(), f.getNormal(), sprite);
putVertexData(quadBuilder, f.getV2(), f.getNormal(), sprite);
putVertexData(quadBuilder, f.getV3(), f.getNormal(), sprite);
putVertexData(quadBuilder, f.getV3(), f.getNormal(), sprite);
builder.add(quadBuilder.build());
}
}
}
private final void putVertexData(IVertexConsumer consumer, Vertex v, Vector3f faceNormal, TextureAtlasSprite sprite)
{
// TODO handle everything not handled (texture transformations, bones, transformations, normals, e.t.c)
ImmutableList<VertexFormatElement> vertexFormatElements = consumer.getVertexFormat().func_227894_c_();
for(int e = 0; e < vertexFormatElements.size(); e++)
{
switch(vertexFormatElements.get(e).getUsage())
{
case POSITION:
consumer.put(e, v.getPos().getX(), v.getPos().getY(), v.getPos().getZ(), 1);
break;
case COLOR:
if(v.getColor() != null)
{
consumer.put(e, v.getColor().getX(), v.getColor().getY(), v.getColor().getZ(), v.getColor().getW());
}
else
{
consumer.put(e, 1, 1, 1, 1);
}
break;
case UV:
// TODO handle more brushes
if(vertexFormatElements.get(e).getIndex() < v.getTexCoords().length)
{
consumer.put(e,
sprite.getInterpolatedU(v.getTexCoords()[0].getX() * 16),
sprite.getInterpolatedV(v.getTexCoords()[0].getY() * 16),
0,
1
);
}
else
{
consumer.put(e, 0, 0, 0, 1);
}
break;
case NORMAL:
if(v.getNormal() != null)
{
consumer.put(e, v.getNormal().getX(), v.getNormal().getY(), v.getNormal().getZ(), 0);
}
else
{
consumer.put(e, faceNormal.getX(), faceNormal.getY(), faceNormal.getZ(), 0);
}
break;
default:
consumer.put(e);
}
}
}
@Override
public boolean isAmbientOcclusion()
{
return smooth;
}
@Override
public boolean isGui3d()
{
return gui3d;
}
@Override
public boolean func_230044_c_()
{
// TODO: Forge: Auto-generated method stub
return false;
}
@Override
public boolean isBuiltInRenderer()
{
return false;
}
@Override
public TextureAtlasSprite getParticleTexture()
{
// FIXME somehow specify particle texture in the model
return textures.values().asList().get(0);
}
@Override
public boolean doesHandlePerspectives()
{
return true;
}
@Override
public IBakedModel handlePerspective(TransformType cameraTransformType, MatrixStack mat)
{
return PerspectiveMapWrapper.handlePerspective(this, state, cameraTransformType, mat);
}
@Override
public ItemOverrideList getOverrides()
{
// TODO handle items
return ItemOverrideList.EMPTY;
}
}
}