package net.nicgamer.automatek.utilities; import com.mojang.math.Transformation; import net.minecraft.client.renderer.RenderType; import net.minecraft.client.renderer.block.model.BakedQuad; import net.minecraft.client.renderer.block.model.ItemOverrides; import net.minecraft.client.renderer.block.model.ItemTransforms; import net.minecraft.client.renderer.texture.TextureAtlasSprite; import net.minecraft.client.resources.model.Material; import net.minecraft.client.resources.model.ModelState; import net.minecraft.core.Direction; import net.minecraft.world.level.block.state.BlockState; import net.minecraftforge.client.MinecraftForgeClient; import net.minecraftforge.client.model.data.IDynamicBakedModel; import net.minecraftforge.client.model.data.IModelData; import net.nicgamer.automatek.blocks.ConnectingBlockModelLoader; import net.nicgamer.automatek.blocks.ModelKey; import net.nicgamer.automatek.varia.ClientTools; import org.jetbrains.annotations.NotNull; import javax.annotation.Nonnull; import javax.annotation.Nullable; import java.util.*; import java.util.function.Function; import static java.lang.Boolean.TRUE; import static net.nicgamer.automatek.varia.ClientTools.v; public class ConnectingBlockBakedModel implements IDynamicBakedModel { private final ModelState modelState; private final Function spriteGetter; private final Map> quadCache = new HashMap<>(); private final ItemOverrides overrides; private final ItemTransforms itemTransforms; /** * @param modelState represents the transformation (orientation) of our model. This is generated from the FACING property that our blockstate uses * @param spriteGetter gives a way to convert materials to actual sprites on the main atlas * @param overrides this is used for using this baked model when it is rendered in an inventory (as an item) * @param itemTransforms these represent the transforms to use for the item model */ public ConnectingBlockBakedModel(ModelState modelState, Function spriteGetter, ItemOverrides overrides, ItemTransforms itemTransforms) { this.modelState = modelState; this.spriteGetter = spriteGetter; this.overrides = overrides; this.itemTransforms = itemTransforms; generateQuadCache(); } @Override public boolean usesBlockLight() { return false; } /** * Whenever a chunk where our block is in needs to be rerendered this method is called to return the quads (polygons) * for our model. Typically this will be called seven times: one time for every direction and one time in general. * If you have a block that is solid at one of the six sides it can be a good idea to render that face only for that * direction. That way Minecraft knows that it can get rid of that face when another solid block is adjacent to that. * All faces or quads that are generated for side == null are not going to be culled away like that * * @param state the blockstate for our block * @param side the six directions or null for quads that are not at a specific direction * @param rand random generator that you can use to add variations to your model (usually for textures) * @param extraData this represents the data that is given to use from our block entity * @return a list of quads */ @Nonnull @Override public List getQuads(@Nullable BlockState state, @Nullable Direction side, @Nonnull Random rand, @Nonnull IModelData extraData) { // Are we on the solid render type and are we rendering for side == null RenderType layer = MinecraftForgeClient.getRenderType(); if (side != null || (layer != null && !layer.equals(RenderType.solid()))) { return Collections.emptyList(); } // Get the data from our block entity boolean northConnected = TRUE == extraData.getData(ConnectingBlockBE.NORTH_CONNECTED); boolean eastConnected = TRUE == extraData.getData(ConnectingBlockBE.EAST_CONNECTED); boolean southConnected = TRUE == extraData.getData(ConnectingBlockBE.SOUTH_CONNECTED); boolean westConnected = TRUE == extraData.getData(ConnectingBlockBE.WEST_CONNECTED); boolean upConnected = TRUE == extraData.getData(ConnectingBlockBE.UP_CONNECTED); boolean downConnected = TRUE == extraData.getData(ConnectingBlockBE.DOWN_CONNECTED); // ModelKey represents a unique configuration. We can use this to get our cached quads ModelKey key = new ModelKey(northConnected, eastConnected, southConnected, westConnected, upConnected, downConnected, modelState); var quads = new ArrayList<>(quadCache.get(key)); return quads; } private void generateQuadCache() { boolean northConnected; boolean eastConnected; boolean southConnected; boolean westConnected; boolean upConnected; boolean downConnected; for (int i = 0; i < 2; i++) { northConnected = i != 0; for (int j = 0; j < 2; j++) { eastConnected = j != 0; for (int k = 0; k < 2; k++) { southConnected = k != 0; for (int l = 0; l < 2; l++) { westConnected = l != 0; for (int m = 0; m < 2; m++) { upConnected = m != 0; for (int n = 0; n < 2; n++) { downConnected = n != 0; quadCache.put(new ModelKey(northConnected, eastConnected, southConnected, westConnected, upConnected, downConnected, modelState), generateQuads(northConnected, eastConnected, southConnected, westConnected, upConnected, downConnected)); } } } } } } } /** * Generate the quads for a given configuration. This is done in the constructor in order to populate * our quad cache. */ @NotNull private List generateQuads(boolean northConnected, boolean eastConnected, boolean southConnected, boolean westConnected, boolean upConnected, boolean downConnected) { var quads = new ArrayList(); float l = 0; float r = 1; Transformation rotation = modelState.getRotation(); TextureAtlasSprite O = spriteGetter.apply(ConnectingBlockModelLoader.MATERIAL_CONNECTING_BLOCK_ZERO_CONNECTIONS); TextureAtlasSprite U = spriteGetter.apply(ConnectingBlockModelLoader.MATERIAL_CONNECTING_BLOCK_ONE_CONNECTION_UP); TextureAtlasSprite R = spriteGetter.apply(ConnectingBlockModelLoader.MATERIAL_CONNECTING_BLOCK_ONE_CONNECTION_RIGHT); TextureAtlasSprite D = spriteGetter.apply(ConnectingBlockModelLoader.MATERIAL_CONNECTING_BLOCK_ONE_CONNECTION_DOWN); TextureAtlasSprite L = spriteGetter.apply(ConnectingBlockModelLoader.MATERIAL_CONNECTING_BLOCK_ONE_CONNECTION_LEFT); TextureAtlasSprite LR = spriteGetter.apply(ConnectingBlockModelLoader.MATERIAL_CONNECTING_BLOCK_TWO_STRAIGHT_CONNECTION_LR); TextureAtlasSprite DU = spriteGetter.apply(ConnectingBlockModelLoader.MATERIAL_CONNECTING_BLOCK_TWO_STRAIGHT_CONNECTION_UD); TextureAtlasSprite RU = spriteGetter.apply(ConnectingBlockModelLoader.MATERIAL_CONNECTING_BLOCK_TWO_CURVE_CONNECTION_UR); TextureAtlasSprite DR = spriteGetter.apply(ConnectingBlockModelLoader.MATERIAL_CONNECTING_BLOCK_TWO_CURVE_CONNECTION_RD); TextureAtlasSprite DL = spriteGetter.apply(ConnectingBlockModelLoader.MATERIAL_CONNECTING_BLOCK_TWO_CURVE_CONNECTION_DL); TextureAtlasSprite LU = spriteGetter.apply(ConnectingBlockModelLoader.MATERIAL_CONNECTING_BLOCK_TWO_CURVE_CONNECTION_LU); TextureAtlasSprite DRU = spriteGetter.apply(ConnectingBlockModelLoader.MATERIAL_CONNECTING_BLOCK_THREE_CONNECTION_URD); TextureAtlasSprite DLR = spriteGetter.apply(ConnectingBlockModelLoader.MATERIAL_CONNECTING_BLOCK_THREE_CONNECTION_RDL); TextureAtlasSprite DLU = spriteGetter.apply(ConnectingBlockModelLoader.MATERIAL_CONNECTING_BLOCK_THREE_CONNECTION_DLU); TextureAtlasSprite LRU = spriteGetter.apply(ConnectingBlockModelLoader.MATERIAL_CONNECTING_BLOCK_THREE_CONNECTION_LUR); TextureAtlasSprite DLRU = spriteGetter.apply(ConnectingBlockModelLoader.MATERIAL_CONNECTING_BLOCK_FOUR_CONNECTION); blockMaterials.put("O", O); blockMaterials.put("D", D); blockMaterials.put("L", L); blockMaterials.put("R", R); blockMaterials.put("U", U); blockMaterials.put("DL", DL); blockMaterials.put("DR", DR); blockMaterials.put("DU", DU); blockMaterials.put("LR", LR); blockMaterials.put("LU", LU); blockMaterials.put("RU", RU); blockMaterials.put("DLR", DLR); blockMaterials.put("DLU", DLU); blockMaterials.put("DRU", DRU); blockMaterials.put("LRU", LRU); blockMaterials.put("DLRU", DLRU); var sideTextures = texturesForFaces(northConnected, eastConnected, southConnected, westConnected, upConnected, downConnected); // The base quads.add(ClientTools.createQuad(v(r, r, r), v(r, r, l), v(l, r, l), v(l, r, r), rotation, sideTextures[4])); // Top quads.add(ClientTools.createQuad(v(l, l, l), v(r, l, l), v(r, l, r), v(l, l, r), rotation, sideTextures[5])); // Bottom quads.add(ClientTools.createQuad(v(l, r, r), v(l, l, r), v(l, l, l), v(l, r, l), rotation, sideTextures[3])); // West quads.add(ClientTools.createQuad(v(r, r, l), v(r, l, l), v(r, l, r), v(r, r, r), rotation, sideTextures[1])); // East quads.add(ClientTools.createQuad(v(r, r, r), v(r, l, r), v(l, l, l), v(l, r, l), rotation, sideTextures[0])); // North quads.add(ClientTools.createQuad(v(l, r, l), v(l, l, l), v(r, l, r), v(r, r, r), rotation, sideTextures[2])); // South return quads; } @Override public boolean useAmbientOcclusion() { return true; } @Override public boolean isGui3d() { return false; } @Override public boolean isCustomRenderer() { return false; } @Override public TextureAtlasSprite getParticleIcon() { return blockMaterials.get("O"); } @Override public ItemOverrides getOverrides() { return overrides; } @Override public ItemTransforms getTransforms() { return itemTransforms; } private final Map blockMaterials = new HashMap<>(); private TextureAtlasSprite[] texturesForFaces(boolean northConnected, boolean eastConnected, boolean southConnected, boolean westConnected, boolean upConnected, boolean downConnected) { boolean[] connectedSidesBooleans = new boolean[]{northConnected, eastConnected, southConnected, westConnected, upConnected, downConnected}; TextureAtlasSprite[] sprites = new TextureAtlasSprite[6]; Character[][] connectedSides = new Character[6][4]; //[North, East, South, West, Up, Down] [U, R, D, L] int[] amountOccurrenceSide = new int[]{0, 0, 0, 0, 0, 0}; char[][] mapper = new char[][]{{0, 'L', 0, 'R', 'U', 'D'}, {'R', 0, 'L', 0, 'U', 'D'}, {0, 'R', 0, 'L', 'U', 'D'}, {'L', 0, 'R', 0, 'U', 'D'}, {'U', 'R', 'D', 'L', 0, 0}, {'U', 'L', 'D', 'R', 0, 0}}; int[] activeSide = new int[4]; for (int i = 0; i < 6; i++) { switch (i) { case 4, 5 -> { activeSide[0] = 0; activeSide[1] = 1; activeSide[2] = 2; activeSide[3] = 3; } default -> { activeSide[2] = 4; activeSide[3] = 5; switch (i) { case 0, 2 -> { activeSide[0] = 1; activeSide[1] = 3; } case 1, 3 -> { activeSide[0] = 0; activeSide[1] = 2; } } } } if (connectedSidesBooleans[i]) { connectedSides[activeSide[0]][amountOccurrenceSide[activeSide[0]]] = mapper[activeSide[0]][i]; connectedSides[activeSide[1]][amountOccurrenceSide[activeSide[1]]] = mapper[activeSide[1]][i]; connectedSides[activeSide[2]][amountOccurrenceSide[activeSide[2]]] = mapper[activeSide[2]][i]; connectedSides[activeSide[3]][amountOccurrenceSide[activeSide[3]]] = mapper[activeSide[3]][i]; } else { connectedSides[activeSide[0]][amountOccurrenceSide[activeSide[0]]] = 0; connectedSides[activeSide[1]][amountOccurrenceSide[activeSide[1]]] = 0; connectedSides[activeSide[2]][amountOccurrenceSide[activeSide[2]]] = 0; connectedSides[activeSide[3]][amountOccurrenceSide[activeSide[3]]] = 0; } amountOccurrenceSide[activeSide[0]]++; amountOccurrenceSide[activeSide[1]]++; amountOccurrenceSide[activeSide[2]]++; amountOccurrenceSide[activeSide[3]]++; } String[] keys = new String[6]; for (int j = 0; j < 6; j++) { String key = ""; var character = Arrays.stream(connectedSides[j]).sorted().map(value -> value == 0 ? "" : value).toArray(); for (int k = 0; k < 4; k++) { key = key.concat(character[k].toString()); } keys[j] = key; } for (int l = 0; l < sprites.length; l++) { if (!keys[l].equals("")) { sprites[l] = blockMaterials.get(keys[l]); } else { sprites[l] = blockMaterials.get("O"); } } return sprites; } }