/*
* 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; }
}
}