package appeng.client.render.crafting; import java.util.ArrayList; import java.util.Collections; import java.util.EnumSet; import java.util.List; import javax.annotation.Nullable; 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.ItemOverrideList; import net.minecraft.client.renderer.texture.TextureAtlasSprite; import net.minecraft.client.renderer.vertex.VertexFormat; import net.minecraft.util.EnumFacing; import net.minecraftforge.common.property.IExtendedBlockState; import appeng.block.crafting.BlockCraftingUnit; import appeng.client.render.cablebus.CubeBuilder; /** * The base model for baked models used by components of the crafting cube multi-block in it's formed state. * Primarily this base class handles adding the "ring" that frames the multi-block structure and delegates * rendering of the "inner" part of each block to the subclasses of this class. */ abstract class CraftingCubeBakedModel implements IBakedModel { private final VertexFormat format; private final TextureAtlasSprite ringCorner; private final TextureAtlasSprite ringHor; private final TextureAtlasSprite ringVer; CraftingCubeBakedModel( VertexFormat format, TextureAtlasSprite ringCorner, TextureAtlasSprite ringHor, TextureAtlasSprite ringVer ) { this.format = format; this.ringCorner = ringCorner; this.ringHor = ringHor; this.ringVer = ringVer; } @Override public List getQuads( @Nullable IBlockState state, @Nullable EnumFacing side, long rand ) { if( side == null ) { return Collections.emptyList(); // No generic quads for this model } EnumSet connections = getConnections( state ); List quads = new ArrayList<>(); CubeBuilder builder = new CubeBuilder( format, quads ); builder.setDrawFaces( EnumSet.of( side ) ); // Add the quads for the ring that frames the entire multi-block structure addRing( builder, side, connections ); // Calculate the bounds of the "inner" block that is framed by the border drawn above float x2 = connections.contains( EnumFacing.EAST ) ? 16 : 13.01f; float x1 = connections.contains( EnumFacing.WEST ) ? 0 : 2.99f; float y2 = connections.contains( EnumFacing.UP ) ? 16 : 13.01f; float y1 = connections.contains( EnumFacing.DOWN ) ? 0 : 2.99f; float z2 = connections.contains( EnumFacing.SOUTH ) ? 16 : 13.01f; float z1 = connections.contains( EnumFacing.NORTH ) ? 0 : 2.99f; // On the axis of the side that we're currently drawing, extend the dimensions // out to the outer face of the block switch( side ) { case DOWN: case UP: y1 = 0; y2 = 16; break; case NORTH: case SOUTH: z1 = 0; z2 = 16; break; case WEST: case EAST: x1 = 0; x2 = 16; break; } addInnerCube( side, state, builder, x1, y1, z1, x2, y2, z2 ); return quads; } private void addRing( CubeBuilder builder, @Nullable EnumFacing side, EnumSet connections ) { // Fill in the corners builder.setTexture( ringCorner ); addCornerCap( builder, connections, side, EnumFacing.UP, EnumFacing.EAST, EnumFacing.NORTH ); addCornerCap( builder, connections, side, EnumFacing.UP, EnumFacing.EAST, EnumFacing.SOUTH ); addCornerCap( builder, connections, side, EnumFacing.UP, EnumFacing.WEST, EnumFacing.NORTH ); addCornerCap( builder, connections, side, EnumFacing.UP, EnumFacing.WEST, EnumFacing.SOUTH ); addCornerCap( builder, connections, side, EnumFacing.DOWN, EnumFacing.EAST, EnumFacing.NORTH ); addCornerCap( builder, connections, side, EnumFacing.DOWN, EnumFacing.EAST, EnumFacing.SOUTH ); addCornerCap( builder, connections, side, EnumFacing.DOWN, EnumFacing.WEST, EnumFacing.NORTH ); addCornerCap( builder, connections, side, EnumFacing.DOWN, EnumFacing.WEST, EnumFacing.SOUTH ); // Fill in the remaining stripes of the face for( EnumFacing a : EnumFacing.values() ) { if( a == side || a == side.getOpposite() ) { continue; } // Select the horizontal or vertical ring texture depending on which side we're filling in if( ( side.getAxis() != EnumFacing.Axis.Y ) && ( a == EnumFacing.NORTH || a == EnumFacing.EAST || a == EnumFacing.WEST || a == EnumFacing.SOUTH ) ) { builder.setTexture( ringVer ); } else if( side.getAxis() == EnumFacing.Axis.Y && ( a == EnumFacing.EAST || a == EnumFacing.WEST ) ) { builder.setTexture( ringVer ); } else { builder.setTexture( ringHor ); } // If there's an adjacent crafting cube block on side a, then the core of the block already extends // fully to this side. So only bother drawing the stripe, if there's no connection. if( !connections.contains( a ) ) { // Note that since we're drawing something that "looks" 2-dimensional, // two of the following will always be 0 and 16. float x1 = 0, y1 = 0, z1 = 0, x2 = 16, y2 = 16, z2 = 16; switch( a ) { case DOWN: y1 = 0; y2 = 3; break; case UP: y1 = 13.0f; y2 = 16; break; case WEST: x1 = 0; x2 = 3; break; case EAST: x1 = 13; x2 = 16; break; case NORTH: z1 = 0; z2 = 3; break; case SOUTH: z1 = 13; z2 = 16; break; } // Constraint the stripe in the two directions perpendicular to a in case there has been a corner // drawn in those directions. Since a corner is drawn if the three touching faces dont have adjacent // crafting cube blocks, we'd have to check for a, side, and the perpendicular direction. But in this // block, we've already checked for side (due to face culling) and a (see above). EnumFacing perpendicular = a.rotateAround( side.getAxis() ); for ( EnumFacing cornerCandidate: EnumSet.of(perpendicular, perpendicular.getOpposite())) { if( !connections.contains( cornerCandidate ) ) { // There's a cap in this direction switch( cornerCandidate ) { case DOWN: y1 = 3; break; case UP: y2 = 13; break; case NORTH: z1 = 3; break; case SOUTH: z2 = 13; break; case WEST: x1 = 3; break; case EAST: x2 = 13; break; } } } builder.addCube( x1, y1, z1, x2, y2, z2 ); } } } /** * Adds a 3x3x3 corner cap to the cube builder if there are no adjacent crafting cubes on that corner. */ private void addCornerCap( CubeBuilder builder, EnumSet connections, EnumFacing side, EnumFacing down, EnumFacing west, EnumFacing north ) { if( connections.contains( down ) || connections.contains( west ) || connections.contains( north ) ) { return; } // Only add faces for sides that can actually be seen (the outside of the cube) if( side != down && side != west && side != north ) { return; } float x1 = (west == EnumFacing.WEST ? 0 : 13); float y1 = (down == EnumFacing.DOWN ? 0 : 13); float z1 = (north == EnumFacing.NORTH ? 0 : 13); float x2 = (west == EnumFacing.WEST ? 3 : 16); float y2 = (down == EnumFacing.DOWN ? 3 : 16); float z2 = (north == EnumFacing.NORTH ? 3 : 16); builder.addCube( x1, y1, z1, x2, y2, z2 ); } // Retrieve the cube connection state from the block state // If none is present, just assume there are no adjacent crafting cube blocks private static EnumSet getConnections( @Nullable IBlockState state ) { if( !( state instanceof IExtendedBlockState ) ) { return EnumSet.noneOf( EnumFacing.class ); } IExtendedBlockState extState = (IExtendedBlockState) state; CraftingCubeState cubeState = extState.getValue( BlockCraftingUnit.STATE ); if( cubeState == null ) { return EnumSet.noneOf( EnumFacing.class ); } return cubeState.getConnections(); } protected abstract void addInnerCube( EnumFacing facing, IBlockState state, CubeBuilder builder, float x1, float y1, float z1, float x2, float y2, float z2 ); @Override public boolean isAmbientOcclusion() { return false; } @Override public boolean isGui3d() { return false; } @Override public boolean isBuiltInRenderer() { return false; } @Override public TextureAtlasSprite getParticleTexture() { return ringCorner; } @Override public ItemCameraTransforms getItemCameraTransforms() { return ItemCameraTransforms.DEFAULT; } @Override public ItemOverrideList getOverrides() { return ItemOverrideList.NONE; } }