/* * This file is part of Applied Energistics 2. * Copyright (c) 2013 - 2014, AlgorithmX2, All rights reserved. * * Applied Energistics 2 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, either version 3 of the License, or * (at your option) any later version. * * Applied Energistics 2 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 Applied Energistics 2. If not, see . */ package appeng.client.render.model; import appeng.client.render.FacingToRotation; 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 net.minecraft.block.BlockState; import net.minecraft.client.renderer.Vector4f; import net.minecraft.client.renderer.model.BakedQuad; import net.minecraft.client.renderer.model.IBakedModel; import net.minecraft.client.renderer.model.ItemCameraTransforms; import net.minecraft.client.renderer.model.ItemOverrideList; import net.minecraft.client.renderer.texture.TextureAtlasSprite; import net.minecraft.client.renderer.vertex.VertexFormat; import net.minecraft.client.renderer.vertex.VertexFormatElement; import net.minecraft.util.Direction; import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.Vec3i; import net.minecraft.world.ILightReader; import net.minecraftforge.client.model.data.EmptyModelData; import net.minecraftforge.client.model.data.IModelData; import net.minecraftforge.client.model.pipeline.BakedQuadBuilder; import net.minecraftforge.client.model.pipeline.IVertexConsumer; import net.minecraftforge.client.model.pipeline.QuadGatheringTransformer; import javax.annotation.Nonnull; import javax.annotation.Nullable; import java.util.ArrayList; import java.util.List; import java.util.Random; public class AutoRotatingBakedModel implements IBakedModel { private final IBakedModel parent; private final LoadingCache> quadCache; public AutoRotatingBakedModel(IBakedModel parent ) { this.parent = parent; // 6 (DUNSWE) * 6 (DUNSWE) * 7 (DUNSWE + null) = 252 this.quadCache = CacheBuilder.newBuilder().maximumSize( 252 ).build( new CacheLoader>() { @Override public List load( AutoRotatingCacheKey key ) { return AutoRotatingBakedModel.this.getRotatedModel( key.getBlockState(), key.getSide(), new Random(0), key.getModelData() ); } } ); } private List getRotatedModel(BlockState state, Direction side, Random rand, AEModelData modelData) { FacingToRotation f2r = FacingToRotation.get( modelData.getForward(), modelData.getUp() ); if (f2r.isRedundant()) { return AutoRotatingBakedModel.this.parent.getQuads( state, side, rand, modelData ); } List original = AutoRotatingBakedModel.this.parent.getQuads( state, f2r.resultingRotate( side ), rand, modelData ); List rotated = new ArrayList<>( original.size() ); for( BakedQuad quad : original ) { BakedQuadBuilder builder = new BakedQuadBuilder(); VertexRotator rot = new VertexRotator( f2r, quad.getFace() ); rot.setParent( builder ); quad.pipe( rot ); if( quad.getFace() != null ) { builder.setQuadOrientation( f2r.rotate( quad.getFace() ) ); } else { builder.setQuadOrientation( null ); } BakedQuad unpackedQuad = builder.build(); // Make a copy of it to resolve the vertex data and throw away the unpacked stuff // This also fixes a bug in Forge's UnpackedBakedQuad, which unpacks a byte-based normal like 0,0,-1 // to 0,0,-0.99607843. We replace these normals with the proper 0,0,-1 when rotation, which // causes a bug in the AO lighter, if an unpacked quad pipes this value back to it. // Packing it back to the vanilla vertex format will fix this inconsistency because it converts // the normal back to a byte-based format, which then re-applies Forge's own bug when piping it // to the AO lighter, thus fixing our problem. BakedQuad packedQuad = new BakedQuad( unpackedQuad.getVertexData(), quad.getTintIndex(), unpackedQuad.getFace(), quad.func_187508_a(), quad .shouldApplyDiffuseLighting() ); rotated.add( packedQuad ); } return rotated; } @Override public boolean isAmbientOcclusion() { return this.parent.isAmbientOcclusion(); } @Override public boolean isGui3d() { return this.parent.isGui3d(); } @Override public boolean func_230044_c_() { return parent.func_230044_c_(); } @Override public boolean isBuiltInRenderer() { return this.parent.isBuiltInRenderer(); } @Override public TextureAtlasSprite getParticleTexture() { return this.parent.getParticleTexture(); } @Override @Deprecated public ItemCameraTransforms getItemCameraTransforms() { return parent.getItemCameraTransforms(); } @Override public ItemOverrideList getOverrides() { return parent.getOverrides(); } @Override public List getQuads(@Nullable BlockState state, @Nullable Direction side, Random rand) { return getQuads(state, side, rand, EmptyModelData.INSTANCE); } @Nonnull @Override public List getQuads(@Nullable BlockState state, @Nullable Direction side, @Nonnull Random rand, @Nonnull IModelData extraData) { if (!(extraData instanceof AEModelData)) { return this.parent.getQuads( state, side, rand, extraData ); } AEModelData aeModelData = (AEModelData) extraData; if (aeModelData.isCacheable()) { return quadCache.getUnchecked(new AutoRotatingCacheKey(state, aeModelData, side)); } else { return this.getRotatedModel(state, side, rand, aeModelData); } } @Nonnull @Override public IModelData getModelData(@Nonnull ILightReader world, @Nonnull BlockPos pos, @Nonnull BlockState state, @Nonnull IModelData tileData) { return this.parent.getModelData(world, pos, state, tileData); } public static class VertexRotator extends QuadGatheringTransformer { private final FacingToRotation f2r; private final Direction face; public VertexRotator( FacingToRotation f2r, Direction face ) { this.f2r = f2r; this.face = face; } @Override public void setParent( IVertexConsumer parent ) { super.setParent( parent ); if( Objects.equal( this.getVertexFormat(), parent.getVertexFormat() ) ) { return; } this.setVertexFormat( parent.getVertexFormat() ); } @Override protected void processQuad() { VertexFormat format = this.parent.getVertexFormat(); ImmutableList elements = format.getElements(); for( int v = 0; v < 4; v++ ) { for( int e = 0; e < elements.size(); e++ ) { VertexFormatElement element = elements.get( e ); if( element.getUsage() == VertexFormatElement.Usage.POSITION ) { this.parent.put( e, this.transform( this.quadData[e][v] ) ); } else if( element.getUsage() == VertexFormatElement.Usage.NORMAL ) { this.parent.put( e, this.transformNormal( this.quadData[e][v] ) ); } else { this.parent.put( e, this.quadData[e][v] ); } } } } private float[] transform( float[] fs ) { switch( fs.length ) { case 3: Vector4f vec = new Vector4f( fs[0], fs[1], fs[2], 1 ); vec.setX(vec.getX() - 0.5f); vec.setY(vec.getY() - 0.5f); vec.setZ(vec.getZ() - 0.5f); vec.transform(this.f2r.getMat()); vec.setX(vec.getX() + 0.5f); vec.setY(vec.getY() + 0.5f); vec.setZ(vec.getZ() + 0.5f); return new float[] { vec.getX(), vec.getY(), vec.getZ() }; case 4: Vector4f vecc = new Vector4f( fs[0], fs[1], fs[2], fs[3] ); vecc.setX(vecc.getX() - 0.5f); vecc.setY(vecc.getY() - 0.5f); vecc.setZ(vecc.getZ() - 0.5f); vecc.transform(this.f2r.getMat()); vecc.setX(vecc.getX() + 0.5f); vecc.setY(vecc.getY() + 0.5f); vecc.setZ(vecc.getZ() + 0.5f); return new float[] { vecc.getX(), vecc.getY(), vecc.getZ(), vecc.getW() }; default: return fs; } } private float[] transformNormal( float[] fs ) { if( this.face == null ) { switch( fs.length ) { case 3: Vector4f vec = new Vector4f( fs[0], fs[1], fs[2], 0 ); vec.transform( this.f2r.getMat() ); return new float[] { vec.getX(), vec.getY(), vec.getZ() }; case 4: Vector4f vec4 = new Vector4f( fs[0], fs[1], fs[2], fs[3] ); vec4.transform( this.f2r.getMat() ); return new float[] { vec4.getX(), vec4.getY(), vec4.getZ(), 0 }; default: return fs; } } else { switch( fs.length ) { case 3: Vec3i vec = this.f2r.rotate( this.face ).getDirectionVec(); return new float[] { vec.getX(), vec.getY(), vec.getZ() }; case 4: Vector4f veccc = new Vector4f( fs[0], fs[1], fs[2], fs[3] ); Vec3i vecc = this.f2r.rotate( this.face ).getDirectionVec(); return new float[] { vecc.getX(), vecc.getY(), vecc.getZ(), veccc.getW() }; default: return fs; } } } @Override public void setQuadTint( int tint ) { this.parent.setQuadTint( tint ); } @Override public void setQuadOrientation( Direction orientation ) { this.parent.setQuadOrientation( orientation ); } @Override public void setApplyDiffuseLighting( boolean diffuse ) { this.parent.setApplyDiffuseLighting( diffuse ); } @Override public void setTexture( TextureAtlasSprite texture ) { this.parent.setTexture( texture ); } } }