/* * 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.List; import java.util.Map; import javax.annotation.Nonnull; import javax.annotation.Nullable; import javax.vecmath.Matrix4f; import javax.vecmath.Quat4f; import net.minecraft.block.state.IBlockState; import net.minecraft.client.Minecraft; 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.block.model.ModelResourceLocation; import net.minecraft.client.renderer.texture.TextureAtlasSprite; import net.minecraft.client.renderer.vertex.VertexFormat; import net.minecraft.client.resources.IResourceManager; 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.ForgeVersion; import net.minecraftforge.common.model.IModelPart; import net.minecraftforge.common.model.IModelState; import net.minecraftforge.common.model.TRSRTransformation; import net.minecraftforge.fluids.Fluid; import net.minecraftforge.fluids.FluidRegistry; import net.minecraftforge.fluids.FluidStack; import net.minecraftforge.fluids.FluidUtil; import org.apache.commons.lang3.tuple.Pair; 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.ImmutableSet; import com.google.common.collect.Maps; public final class ModelDynBucket implements IModel, IModelCustomData, IRetexturableModel { public static final ModelResourceLocation LOCATION = new ModelResourceLocation(new ResourceLocation(ForgeVersion.MOD_ID, "dynbucket"), "inventory"); // minimal Z offset to prevent depth-fighting private static final float NORTH_Z_BASE = 7.496f / 16f; private static final float SOUTH_Z_BASE = 8.504f / 16f; private static final float NORTH_Z_FLUID = 7.498f / 16f; private static final float SOUTH_Z_FLUID = 8.502f / 16f; public static final IModel MODEL = new ModelDynBucket(); @Nullable private final ResourceLocation baseLocation; @Nullable private final ResourceLocation liquidLocation; @Nullable private final ResourceLocation coverLocation; @Nullable private final Fluid fluid; private final boolean flipGas; public ModelDynBucket() { this(null, null, null, null, false); } public ModelDynBucket(@Nullable ResourceLocation baseLocation, @Nullable ResourceLocation liquidLocation, @Nullable ResourceLocation coverLocation, @Nullable Fluid fluid, boolean flipGas) { this.baseLocation = baseLocation; this.liquidLocation = liquidLocation; this.coverLocation = coverLocation; this.fluid = fluid; this.flipGas = flipGas; } @Override public Collection<ResourceLocation> getDependencies() { return ImmutableList.of(); } @Override public Collection<ResourceLocation> getTextures() { ImmutableSet.Builder<ResourceLocation> builder = ImmutableSet.builder(); if (baseLocation != null) builder.add(baseLocation); if (liquidLocation != null) builder.add(liquidLocation); if (coverLocation != null) builder.add(coverLocation); return builder.build(); } @Override public IBakedModel bake(IModelState state, VertexFormat format, Function<ResourceLocation, TextureAtlasSprite> bakedTextureGetter) { ImmutableMap<TransformType, TRSRTransformation> transformMap = IPerspectiveAwareModel.MapWrapper.getTransforms(state); // if the fluid is a gas wi manipulate the initial state to be rotated 180° to turn it upside down if (flipGas && fluid != null && fluid.isGaseous()) { state = new ModelStateComposition(state, TRSRTransformation.blockCenterToCorner(new TRSRTransformation(null, new Quat4f(0, 0, 1, 0), null, null))); } TRSRTransformation transform = state.apply(Optional.<IModelPart>absent()).or(TRSRTransformation.identity()); TextureAtlasSprite fluidSprite = null; ImmutableList.Builder<BakedQuad> builder = ImmutableList.builder(); if(fluid != null) { fluidSprite = bakedTextureGetter.apply(fluid.getStill()); } if (baseLocation != null) { // build base (insidest) IBakedModel model = (new ItemLayerModel(ImmutableList.of(baseLocation))).bake(state, format, bakedTextureGetter); builder.addAll(model.getQuads(null, null, 0)); } if (liquidLocation != null && fluidSprite != null) { TextureAtlasSprite liquid = bakedTextureGetter.apply(liquidLocation); // build liquid layer (inside) builder.addAll(ItemTextureQuadConverter.convertTexture(format, transform, liquid, fluidSprite, NORTH_Z_FLUID, EnumFacing.NORTH, fluid.getColor())); builder.addAll(ItemTextureQuadConverter.convertTexture(format, transform, liquid, fluidSprite, SOUTH_Z_FLUID, EnumFacing.SOUTH, fluid.getColor())); } if (coverLocation != null) { // cover (the actual item around the other two) TextureAtlasSprite base = bakedTextureGetter.apply(coverLocation); builder.add(ItemTextureQuadConverter.genQuad(format, transform, 0, 0, 16, 16, NORTH_Z_BASE, base, EnumFacing.NORTH, 0xffffffff)); builder.add(ItemTextureQuadConverter.genQuad(format, transform, 0, 0, 16, 16, SOUTH_Z_BASE, base, EnumFacing.SOUTH, 0xffffffff)); } return new BakedDynBucket(this, builder.build(), fluidSprite, format, Maps.immutableEnumMap(transformMap), Maps.<String, IBakedModel>newHashMap()); } @Override public IModelState getDefaultState() { return TRSRTransformation.identity(); } /** * Sets the liquid in the model. * fluid - Name of the fluid in the FluidRegistry * flipGas - If "true" the model will be flipped upside down if the liquid is a gas. If "false" it wont * <p/> * If the fluid can't be found, water is used */ @Override public ModelDynBucket process(ImmutableMap<String, String> customData) { String fluidName = customData.get("fluid"); Fluid fluid = FluidRegistry.getFluid(fluidName); if (fluid == null) fluid = this.fluid; boolean flip = flipGas; if (customData.containsKey("flipGas")) { String flipStr = customData.get("flipGas"); if (flipStr.equals("true")) flip = true; else if (flipStr.equals("false")) flip = false; else throw new IllegalArgumentException(String.format("DynBucket custom data \"flipGas\" must have value \'true\' or \'false\' (was \'%s\')", flipStr)); } // create new model with correct liquid return new ModelDynBucket(baseLocation, liquidLocation, coverLocation, fluid, flip); } /** * Allows to use different textures for the model. * There are 3 layers: * base - The empty bucket/container * fluid - A texture representing the liquid portion. Non-transparent = liquid * cover - An overlay that's put over the liquid (optional) * <p/> * If no liquid is given a hardcoded variant for the bucket is used. */ @Override public ModelDynBucket retexture(ImmutableMap<String, String> textures) { ResourceLocation base = baseLocation; ResourceLocation liquid = liquidLocation; ResourceLocation cover = coverLocation; if (textures.containsKey("base")) base = new ResourceLocation(textures.get("base")); if (textures.containsKey("fluid")) liquid = new ResourceLocation(textures.get("fluid")); if (textures.containsKey("cover")) cover = new ResourceLocation(textures.get("cover")); return new ModelDynBucket(base, liquid, cover, fluid, flipGas); } public enum LoaderDynBucket implements ICustomModelLoader { INSTANCE; @Override public boolean accepts(ResourceLocation modelLocation) { return modelLocation.getResourceDomain().equals(ForgeVersion.MOD_ID) && modelLocation.getResourcePath().contains("forgebucket"); } @Override public IModel loadModel(ResourceLocation modelLocation) { return MODEL; } @Override public void onResourceManagerReload(IResourceManager resourceManager) { // no need to clear cache since we create a new model instance } } private static final class BakedDynBucketOverrideHandler extends ItemOverrideList { public static final BakedDynBucketOverrideHandler INSTANCE = new BakedDynBucketOverrideHandler(); private BakedDynBucketOverrideHandler() { super(ImmutableList.<ItemOverride>of()); } @Override @Nonnull public IBakedModel handleItemState(@Nonnull IBakedModel originalModel, @Nonnull ItemStack stack, @Nullable World world, @Nullable EntityLivingBase entity) { FluidStack fluidStack = FluidUtil.getFluidContained(stack); // not a fluid item apparently if (fluidStack == null) { // empty bucket return originalModel; } BakedDynBucket model = (BakedDynBucket)originalModel; Fluid fluid = fluidStack.getFluid(); String name = fluid.getName(); if (!model.cache.containsKey(name)) { IModel parent = model.parent.process(ImmutableMap.of("fluid", name)); Function<ResourceLocation, TextureAtlasSprite> textureGetter; textureGetter = new Function<ResourceLocation, TextureAtlasSprite>() { public TextureAtlasSprite apply(ResourceLocation location) { return Minecraft.getMinecraft().getTextureMapBlocks().getAtlasSprite(location.toString()); } }; IBakedModel bakedModel = parent.bake(new SimpleModelState(model.transforms), model.format, textureGetter); model.cache.put(name, bakedModel); return bakedModel; } return model.cache.get(name); } } // the dynamic bucket is based on the empty bucket private static final class BakedDynBucket implements IPerspectiveAwareModel { private final ModelDynBucket parent; // FIXME: guava cache? private final Map<String, IBakedModel> cache; // contains all the baked models since they'll never change private final ImmutableMap<TransformType, TRSRTransformation> transforms; private final ImmutableList<BakedQuad> quads; private final TextureAtlasSprite particle; private final VertexFormat format; public BakedDynBucket(ModelDynBucket parent, ImmutableList<BakedQuad> quads, TextureAtlasSprite particle, VertexFormat format, ImmutableMap<ItemCameraTransforms.TransformType, TRSRTransformation> transforms, Map<String, IBakedModel> cache) { this.quads = quads; this.particle = particle; this.format = format; this.parent = parent; this.transforms = transforms; this.cache = cache; } @Override public ItemOverrideList getOverrides() { return BakedDynBucketOverrideHandler.INSTANCE; } @Override public Pair<? extends IBakedModel, Matrix4f> handlePerspective(TransformType cameraTransformType) { return IPerspectiveAwareModel.MapWrapper.handlePerspective(this, transforms, cameraTransformType); } @Override public List<BakedQuad> getQuads(IBlockState state, EnumFacing side, long rand) { if(side == null) return quads; return ImmutableList.of(); } public boolean isAmbientOcclusion() { return true; } public boolean isGui3d() { return false; } public boolean isBuiltInRenderer() { return false; } public TextureAtlasSprite getParticleTexture() { return particle; } public ItemCameraTransforms getItemCameraTransforms() { return ItemCameraTransforms.DEFAULT; } } }