/* * Minecraft Forge * Copyright (c) 2016. * * 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; import java.util.Collection; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import javax.annotation.Nonnull; import javax.annotation.Nullable; import javax.vecmath.Matrix4f; import net.minecraft.block.state.IBlockState; import net.minecraft.client.renderer.block.model.BakedQuad; import net.minecraft.client.renderer.block.model.IBakedModel; import net.minecraft.client.renderer.block.model.ItemCameraTransforms; import net.minecraft.client.renderer.block.model.ItemCameraTransforms.TransformType; import net.minecraft.client.renderer.block.model.ItemOverride; import net.minecraft.client.renderer.block.model.ItemOverrideList; import net.minecraft.client.renderer.texture.TextureAtlasSprite; import net.minecraft.client.renderer.vertex.VertexFormat; import net.minecraft.entity.EntityLivingBase; import net.minecraft.item.ItemStack; import net.minecraft.util.EnumFacing; import net.minecraft.util.ResourceLocation; import net.minecraft.world.World; import net.minecraftforge.common.model.IModelState; import net.minecraftforge.common.model.TRSRTransformation; 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; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.Lists; import com.google.common.collect.Sets; // TODO: Switch to vanilla class, or to something similar @Deprecated public final class MultiModel implements IModel { private static final class Baked implements IPerspectiveAwareModel { private final ResourceLocation location; private final IBakedModel base; private final ImmutableMap<String, IBakedModel> parts; private final IBakedModel internalBase; private ImmutableMap<Optional<EnumFacing>, ImmutableList<BakedQuad>> quads; private final ImmutableMap<TransformType, Pair<Baked, TRSRTransformation>> transforms; private final ItemOverrideList overrides = new ItemOverrideList(Lists.<ItemOverride>newArrayList()) { @Override @Nonnull public IBakedModel handleItemState(@Nonnull IBakedModel originalModel, @Nonnull ItemStack stack, @Nullable World world, @Nullable EntityLivingBase entity) { if(originalModel != Baked.this) { return originalModel; } boolean dirty = false; IBakedModel newBase = null; if(base != null) { newBase = base.getOverrides().handleItemState(base, stack, world, entity); if(base != newBase) { dirty = true; } } ImmutableMap.Builder<String, IBakedModel> builder = ImmutableMap.builder(); for(Map.Entry<String, IBakedModel> entry : parts.entrySet()) { IBakedModel newPart = entry.getValue().getOverrides().handleItemState(entry.getValue(), stack, world, entity); builder.put(entry.getKey(), newPart); if(entry.getValue() != newPart) { dirty = true; } } if(dirty) { // TODO: caching? return new Baked(location, newBase instanceof IPerspectiveAwareModel, newBase, builder.build()); } return Baked.this; } }; public Baked(ResourceLocation location, boolean perspective, IBakedModel base, ImmutableMap<String, IBakedModel> parts) { this.location = location; this.base = base; this.parts = parts; if (base != null) internalBase = base; else { Iterator<IBakedModel> iter = parts.values().iterator(); if (iter.hasNext()) internalBase = iter.next(); else throw new IllegalArgumentException("No base model or submodel provided for MultiModel.Baked " + location + "."); } // 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<? extends IBakedModel, Matrix4f> p = perBase.handlePerspective(type); IBakedModel newBase = p.getLeft(); 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() { return internalBase.isAmbientOcclusion(); } @Override public boolean isGui3d() { return internalBase.isGui3d(); } @Override public boolean isBuiltInRenderer() { return internalBase.isBuiltInRenderer(); } @Override public TextureAtlasSprite getParticleTexture() { return internalBase.getParticleTexture(); } @Override public ItemCameraTransforms getItemCameraTransforms() { return internalBase.getItemCameraTransforms(); } @Override public List<BakedQuad> getQuads(IBlockState state, EnumFacing side, long rand) { if(quads == null) { ImmutableMap.Builder<Optional<EnumFacing>, ImmutableList<BakedQuad>> builder = ImmutableMap.builder(); for (EnumFacing face : EnumFacing.values()) { ImmutableList.Builder<BakedQuad> quads = ImmutableList.builder(); if (base != null) { quads.addAll(base.getQuads(state, face, 0)); } for (IBakedModel bakedPart : parts.values()) { quads.addAll(bakedPart.getQuads(state, face, 0)); } builder.put(Optional.of(face), quads.build()); } ImmutableList.Builder<BakedQuad> quads = ImmutableList.builder(); if (base != null) { quads.addAll(base.getQuads(state, null, 0)); } for (IBakedModel bakedPart : parts.values()) { quads.addAll(bakedPart.getQuads(state, null, 0)); } builder.put(Optional.<EnumFacing>absent(), quads.build()); this.quads = builder.build(); } return quads.get(Optional.fromNullable(side)); } @Override public Pair<? extends 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()); } @Override public ItemOverrideList getOverrides() { return overrides; } } private final ResourceLocation location; private final IModel base; private final IModelState baseState; private final Map<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(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 IBakedModel bake(IModelState state, VertexFormat format, Function<ResourceLocation, TextureAtlasSprite> bakedTextureGetter) { IBakedModel bakedBase = null; if (base != null) bakedBase = base.bake(state, format, bakedTextureGetter); ImmutableMap.Builder<String, IBakedModel> 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(new ModelStateComposition(state, pair.getRight()), format, bakedTextureGetter)); } 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; } }