/* * 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.crafting; import java.util.ArrayList; import java.util.Collections; import java.util.EnumSet; import java.util.List; import java.util.Random; import javax.annotation.Nonnull; import javax.annotation.Nullable; import net.minecraft.block.BlockState; import net.minecraft.client.render.model.BakedQuad; import net.minecraft.client.render.model.json.ModelOverrideList; import net.minecraft.client.texture.Sprite; import net.minecraft.util.math.Direction; import net.minecraftforge.client.model.data.IDynamicBakedModel; import appeng.client.render.cablebus.CubeBuilder; import appeng.tile.crafting.CraftingCubeModelData; import appeng.util.Platform; /** * 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 IDynamicBakedModel { private final Sprite ringCorner; private final Sprite ringHor; private final Sprite ringVer; CraftingCubeBakedModel(Sprite ringCorner, Sprite ringHor, Sprite ringVer) { this.ringCorner = ringCorner; this.ringHor = ringHor; this.ringVer = ringVer; } @Nonnull @Override public List getQuads(@Nullable BlockState state, @Nullable Direction side, @Nonnull Random rand, @Nonnull IModelData extraData) { if (side == null) { return Collections.emptyList(); // No generic quads for this model } EnumSet connections = getConnections(extraData); List quads = new ArrayList<>(); CubeBuilder builder = new CubeBuilder(quads); builder.setDrawFaces(EnumSet.of(side)); // Add the quads for the ring that frames the entire multi-block structure this.addRing(builder, side, connections); // Calculate the bounds of the "inner" block that is framed by the border drawn // above float x2 = connections.contains(Direction.EAST) ? 16 : 13.01f; float x1 = connections.contains(Direction.WEST) ? 0 : 2.99f; float y2 = connections.contains(Direction.UP) ? 16 : 13.01f; float y1 = connections.contains(Direction.DOWN) ? 0 : 2.99f; float z2 = connections.contains(Direction.SOUTH) ? 16 : 13.01f; float z1 = connections.contains(Direction.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; } this.addInnerCube(side, state, extraData, builder, x1, y1, z1, x2, y2, z2); return quads; } private void addRing(CubeBuilder builder, @Nullable Direction side, EnumSet connections) { // Fill in the corners builder.setTexture(this.ringCorner); this.addCornerCap(builder, connections, side, Direction.UP, Direction.EAST, Direction.NORTH); this.addCornerCap(builder, connections, side, Direction.UP, Direction.EAST, Direction.SOUTH); this.addCornerCap(builder, connections, side, Direction.UP, Direction.WEST, Direction.NORTH); this.addCornerCap(builder, connections, side, Direction.UP, Direction.WEST, Direction.SOUTH); this.addCornerCap(builder, connections, side, Direction.DOWN, Direction.EAST, Direction.NORTH); this.addCornerCap(builder, connections, side, Direction.DOWN, Direction.EAST, Direction.SOUTH); this.addCornerCap(builder, connections, side, Direction.DOWN, Direction.WEST, Direction.NORTH); this.addCornerCap(builder, connections, side, Direction.DOWN, Direction.WEST, Direction.SOUTH); // Fill in the remaining stripes of the face for (Direction a : Direction.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() != Direction.Axis.Y) && (a == Direction.NORTH || a == Direction.EAST || a == Direction.WEST || a == Direction.SOUTH)) { builder.setTexture(this.ringVer); } else if (side.getAxis() == Direction.Axis.Y && (a == Direction.EAST || a == Direction.WEST)) { builder.setTexture(this.ringVer); } else { builder.setTexture(this.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). Direction perpendicular = Platform.rotateAround(a, side); for (Direction 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, Direction side, Direction down, Direction west, Direction 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 == Direction.WEST ? 0 : 13); float y1 = (down == Direction.DOWN ? 0 : 13); float z1 = (north == Direction.NORTH ? 0 : 13); float x2 = (west == Direction.WEST ? 3 : 16); float y2 = (down == Direction.DOWN ? 3 : 16); float z2 = (north == Direction.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(IModelData modelData) { if (!(modelData instanceof CraftingCubeModelData)) { return EnumSet.noneOf(Direction.class); } return ((CraftingCubeModelData) modelData).getConnections(); } protected abstract void addInnerCube(Direction facing, BlockState state, IModelData modelData, CubeBuilder builder, float x1, float y1, float z1, float x2, float y2, float z2); @Override public boolean useAmbientOcclusion() { return false; } @Override public boolean hasDepth() { return false; } @Override public boolean isBuiltin() { return false; } @Override public Sprite getSprite() { return this.ringCorner; } @Override public boolean isSideLit() { return false; } @Override public ModelOverrideList getOverrides() { return ModelOverrideList.EMPTY; } }