package mekanism.client.render.ctm;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import javax.annotation.Nullable;
import javax.vecmath.Matrix4f;
import javax.vecmath.Vector3f;
import mekanism.common.block.states.BlockStateBasic;
import net.minecraft.block.Block;
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.ItemOverrideList;
import net.minecraft.client.renderer.texture.TextureAtlasSprite;
import net.minecraft.entity.EntityLivingBase;
import net.minecraft.item.ItemBlock;
import net.minecraft.item.ItemStack;
import net.minecraft.util.BlockRenderLayer;
import net.minecraft.util.EnumFacing;
import net.minecraft.world.World;
import net.minecraftforge.client.MinecraftForgeClient;
import net.minecraftforge.client.model.IPerspectiveAwareModel;
import net.minecraftforge.common.model.TRSRTransformation;
import net.minecraftforge.common.property.IExtendedBlockState;
import org.apache.commons.lang3.tuple.Pair;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import com.google.common.collect.FluentIterable;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ListMultimap;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.MultimapBuilder;
import com.google.common.collect.Table;
import com.google.common.collect.Tables;
/**
* Model for all CTM blocks, credit to Chisel
*/
@SuppressWarnings("deprecation")
public class CTMModelFactory implements IPerspectiveAwareModel
{
private ListMultimap<BlockRenderLayer, BakedQuad> genQuads = MultimapBuilder.enumKeys(BlockRenderLayer.class).arrayListValues().build();
private Table<BlockRenderLayer, EnumFacing, List<BakedQuad>> faceQuads = Tables.newCustomTable(Maps.newEnumMap(BlockRenderLayer.class), () -> Maps.newEnumMap(EnumFacing.class));
private ModelCTM model;
private CTMOverride override = new CTMOverride();
public CTMModelFactory(ModelCTM m)
{
model = m;
}
private static Cache<Integer, CTMModelFactory> ctmCache = CacheBuilder.newBuilder().expireAfterAccess(1, TimeUnit.MINUTES).maximumSize(500).<Integer, CTMModelFactory>build();
@Override
public List<BakedQuad> getQuads(IBlockState stateIn, EnumFacing side, long rand)
{
CTMModelFactory baked;
if(stateIn != null && stateIn.getBlock() instanceof ICTMBlock && stateIn instanceof IExtendedBlockState)
{
IExtendedBlockState state = (IExtendedBlockState)stateIn;
IBlockState clean = state.getClean();
CTMBlockRenderContext ctxList = state.getValue(BlockStateBasic.ctmProperty);
try {
if(ctxList == null)
{
baked = ctmCache.get(Objects.hash(clean, -1), () -> createModel(state, model, null));
}
else {
long serialized = ctxList.serialize();
baked = ctmCache.get(Objects.hash(clean, serialized), () -> createModel(state, model, ctxList));
}
} catch(Exception e) {
e.printStackTrace();
return Lists.newArrayList();
}
}
else {
baked = this;
}
BlockRenderLayer layer = MinecraftForgeClient.getRenderLayer();
return side == null ? baked.genQuads.get(layer) : layer == null ? baked.faceQuads.column(side).values().stream().flatMap(List::stream).collect(Collectors.toList()) : baked.faceQuads.get(layer, side);
}
@Override
public boolean isAmbientOcclusion()
{
return true;
}
@Override
public boolean isGui3d()
{
return true;
}
@Override
public boolean isBuiltInRenderer()
{
return false;
}
@Override
public TextureAtlasSprite getParticleTexture()
{
return model.getDefaultFace().getParticle();
}
@Override
public ItemCameraTransforms getItemCameraTransforms()
{
return ItemCameraTransforms.DEFAULT;
}
private CTMModelFactory createModel(IBlockState state, ModelCTM model, CTMBlockRenderContext ctx)
{
CTMModelFactory ret = new CTMModelFactory(model);
IBakedModel baked = model.getModel(state);
int quadGoal = ctx == null ? 1 : CTM.QUADS_PER_SIDE;
List<BakedQuad> quads = Lists.newArrayList();
for(BlockRenderLayer layer : BlockRenderLayer.values())
{
for(EnumFacing facing : EnumFacing.VALUES)
{
TextureCTM face = model.getFace(facing);
if(ctx == null || layer == state.getBlock().getBlockLayer())
{
ICTMBlock block = (ICTMBlock)state.getBlock();
CTMData data = block.getCTMData(state);
if(block.getOverrideTexture(state, facing) != null)
{
face = CTMRegistry.textureCache.get(block.getOverrideTexture(state, facing));
}
List<BakedQuad> temp = baked.getQuads(state, facing, 0);
addAllQuads(temp, face, ctx, state, quadGoal, quads);
ret.faceQuads.put(layer, facing, ImmutableList.copyOf(quads));
temp = FluentIterable.from(baked.getQuads(state, null, 0)).filter(q -> q.getFace() == facing).toList();
addAllQuads(temp, face, ctx, state, quadGoal, quads);
ret.genQuads.putAll(layer, temp);
}
else {
ret.faceQuads.put(layer, facing, Lists.newArrayList());
}
}
}
return ret;
}
private void addAllQuads(List<BakedQuad> from, TextureCTM tex, @Nullable CTMBlockRenderContext ctx, IBlockState state, int quadGoal, List<BakedQuad> to)
{
to.clear();
for(BakedQuad q : from)
{
to.addAll(tex.transformQuad(q, ctx == null ? null : ctx, quadGoal));
}
}
private static TRSRTransformation get(float tx, float ty, float tz, float ax, float ay, float az, float s)
{
return new TRSRTransformation(
new Vector3f(tx / 16, ty / 16, tz / 16),
TRSRTransformation.quatFromXYZDegrees(new Vector3f(ax, ay, az)),
new Vector3f(s, s, s),
null);
}
public static Map<TransformType, TRSRTransformation> transforms = ImmutableMap.<TransformType, TRSRTransformation>builder()
.put(TransformType.GUI, get(0, 0, 0, 30, 225, 0, 0.625f))
.put(TransformType.THIRD_PERSON_RIGHT_HAND, get(0, 2.5f, 0, 75, 45, 0, 0.375f))
.put(TransformType.THIRD_PERSON_LEFT_HAND, get(0, 2.5f, 0, 75, 45, 0, 0.375f))
.put(TransformType.FIRST_PERSON_RIGHT_HAND, get(0, 0, 0, 0, 45, 0, 0.4f))
.put(TransformType.FIRST_PERSON_LEFT_HAND, get(0, 0, 0, 0, 225, 0, 0.4f))
.put(TransformType.GROUND, get(0, 2, 0, 0, 0, 0, 0.25f))
.put(TransformType.HEAD, get(0, 0, 0, 0, 0, 0, 1))
.put(TransformType.FIXED, get(0, 0, 0, 0, 0, 0, 1))
.put(TransformType.NONE, get(0, 0, 0, 0, 0, 0, 0))
.build();
@Override
public Pair<? extends IPerspectiveAwareModel, Matrix4f> handlePerspective(ItemCameraTransforms.TransformType cameraTransformType)
{
return Pair.of(this, transforms.get(cameraTransformType).getMatrix());
}
@Override
public ItemOverrideList getOverrides()
{
return override;
}
private class CTMOverride extends ItemOverrideList
{
public CTMOverride()
{
super(Lists.newArrayList());
}
@Override
public IBakedModel handleItemState(IBakedModel originalModel, ItemStack stack, World world, EntityLivingBase entity)
{
Block block = ((ItemBlock)stack.getItem()).getBlock();
return createModel(block.getDefaultState(), model, null);
}
}
}