/* * 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.cablebus; import appeng.api.parts.IPartBakedModel; import appeng.api.parts.IPartModel; import appeng.api.util.AECableType; import appeng.api.util.AEColor; import appeng.block.networking.BlockCableBus; import com.google.common.cache.CacheBuilder; import com.google.common.cache.CacheLoader; import com.google.common.cache.LoadingCache; import com.google.common.cache.Weigher; import net.minecraft.block.state.IBlockState; import net.minecraft.client.Minecraft; 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.texture.TextureMap; import net.minecraft.util.BlockRenderLayer; import net.minecraft.util.EnumFacing; import net.minecraft.util.ResourceLocation; import net.minecraftforge.client.MinecraftForgeClient; import net.minecraftforge.common.property.IExtendedBlockState; import javax.annotation.Nonnull; import javax.annotation.Nullable; import java.util.*; import java.util.Map.Entry; public class CableBusBakedModel implements IBakedModel { private final CableBuilder cableBuilder; private final FacadeBuilder facadeBuilder; private final Map partModels; private final TextureAtlasSprite particleTexture; private final TextureMap textureMap = Minecraft.getMinecraft().getTextureMapBlocks(); private final LoadingCache> cableModelCache; CableBusBakedModel(CableBuilder cableBuilder, FacadeBuilder facadeBuilder, Map partModels, TextureAtlasSprite particleTexture) { this.cableBuilder = cableBuilder; this.facadeBuilder = facadeBuilder; this.partModels = partModels; this.particleTexture = particleTexture; this.cableModelCache = CacheBuilder.newBuilder() .maximumWeight(5000) .weigher((Weigher>) (k, v) -> v.size()) .build(new CacheLoader<>() { @Override public List load(@Nonnull CableBusRenderState rs) { final List model = new ArrayList<>(); addCableQuads(rs, model); return model; } }); } @Override public List getQuads(@Nullable IBlockState state, @Nullable EnumFacing side, long rand) { CableBusRenderState renderState = getRenderingState(state); if (renderState == null || side != null) { return Collections.emptyList(); } BlockRenderLayer layer = MinecraftForgeClient.getRenderLayer(); List quads = new ArrayList<>(); // The core parts of the cable will only be rendered in the CUTOUT layer. // Facades will add them selves to what ever the block would be rendered with, // except when transparent facades are enabled, they are forced to TRANSPARENT. if (layer == BlockRenderLayer.CUTOUT) { // First, handle the cable at the center of the cable bus final List cableModel = cableModelCache.getUnchecked(renderState); quads.addAll(cableModel); // Then handle attachments for (EnumFacing facing : EnumFacing.values()) { final IPartModel partModel = renderState.getAttachments().get(facing); if (partModel == null) { continue; } for (ResourceLocation model : partModel.getModels()) { IBakedModel bakedModel = this.partModels.get(model); if (bakedModel == null) { throw new IllegalStateException("Trying to use an unregistered part model: " + model); } List partQuads; if (bakedModel instanceof IPartBakedModel) { partQuads = ((IPartBakedModel) bakedModel).getPartQuads(renderState.getPartFlags().get(facing), rand); } else { partQuads = bakedModel.getQuads(state, null, rand); } // Rotate quads accordingly QuadRotator rotator = new QuadRotator(); partQuads = rotator.rotateQuads(partQuads, facing, EnumFacing.UP); quads.addAll(partQuads); } } } this.facadeBuilder.buildFacadeQuads(layer, renderState, rand, quads, this.partModels::get); return quads; } // Determines whether a cable is connected to exactly two sides that are opposite each other private static boolean isStraightLine(AECableType cableType, EnumMap sides) { final Iterator> it = sides.entrySet().iterator(); if (!it.hasNext()) { return false; // No connections } final Entry nextConnection = it.next(); final EnumFacing firstSide = nextConnection.getKey(); final AECableType firstType = nextConnection.getValue(); if (!it.hasNext()) { return false; // Only a single connection } if (firstSide.getOpposite() != it.next().getKey()) { return false; // Connected to two sides that are not opposite each other } if (it.hasNext()) { return false; // Must not have any other connection points } final AECableType secondType = sides.get(firstSide.getOpposite()); return firstType == secondType && cableType == firstType && cableType == secondType; } private void addCableQuads(CableBusRenderState renderState, List quadsOut) { AECableType cableType = renderState.getCableType(); if (cableType == AECableType.NONE) { return; } AEColor cableColor = renderState.getCableColor(); EnumMap connectionTypes = renderState.getConnectionTypes(); // If the connection is straight, no busses are attached, and no covered core has been forced (in case of glass // cables), then render the cable as a simplified straight line. boolean noAttachments = !renderState.getAttachments().values().stream().anyMatch(IPartModel::requireCableConnection); if (noAttachments && isStraightLine(cableType, connectionTypes)) { EnumFacing facing = connectionTypes.keySet().iterator().next(); switch (cableType) { case GLASS: this.cableBuilder.addStraightGlassConnection(facing, cableColor, quadsOut); break; case COVERED: this.cableBuilder.addStraightCoveredConnection(facing, cableColor, quadsOut); break; case SMART: this.cableBuilder.addStraightSmartConnection(facing, cableColor, renderState.getChannelsOnSide().get(facing), quadsOut); break; case DENSE_COVERED: this.cableBuilder.addStraightDenseCoveredConnection(facing, cableColor, quadsOut); break; case DENSE_SMART: this.cableBuilder.addStraightDenseSmartConnection(facing, cableColor, renderState.getChannelsOnSide().get(facing), quadsOut); break; default: break; } return; // Don't render the other form of connection } this.cableBuilder.addCableCore(renderState.getCoreType(), cableColor, quadsOut); // Render all internal connections to attachments EnumMap attachmentConnections = renderState.getAttachmentConnections(); for (EnumFacing facing : attachmentConnections.keySet()) { int distance = attachmentConnections.get(facing); int channels = renderState.getChannelsOnSide().get(facing); switch (cableType) { case GLASS: this.cableBuilder.addConstrainedGlassConnection(facing, cableColor, distance, quadsOut); break; case COVERED: this.cableBuilder.addConstrainedCoveredConnection(facing, cableColor, distance, quadsOut); break; case SMART: this.cableBuilder.addConstrainedSmartConnection(facing, cableColor, distance, channels, quadsOut); break; case DENSE_COVERED: case DENSE_SMART: // Dense cables do not render connections to parts since none can be attached break; default: break; } } // Render all outgoing connections using the appropriate type for (final Entry connection : connectionTypes.entrySet()) { final EnumFacing facing = connection.getKey(); final AECableType connectionType = connection.getValue(); final boolean cableBusAdjacent = renderState.getCableBusAdjacent().contains(facing); final int channels = renderState.getChannelsOnSide().get(facing); switch (cableType) { case GLASS: this.cableBuilder.addGlassConnection(facing, cableColor, connectionType, cableBusAdjacent, quadsOut); break; case COVERED: this.cableBuilder.addCoveredConnection(facing, cableColor, connectionType, cableBusAdjacent, quadsOut); break; case SMART: this.cableBuilder.addSmartConnection(facing, cableColor, connectionType, cableBusAdjacent, channels, quadsOut); break; case DENSE_COVERED: this.cableBuilder.addDenseCoveredConnection(facing, cableColor, connectionType, cableBusAdjacent, quadsOut); break; case DENSE_SMART: this.cableBuilder.addDenseSmartConnection(facing, cableColor, connectionType, cableBusAdjacent, channels, quadsOut); break; default: break; } } } /** * Gets a list of texture sprites appropriate for particles (digging, etc.) given the render state for a cable bus. */ public List getParticleTextures(CableBusRenderState renderState) { CableCoreType coreType = CableCoreType.fromCableType(renderState.getCableType()); AEColor cableColor = renderState.getCableColor(); List result = new ArrayList<>(); if (coreType != null) { result.add(this.cableBuilder.getCoreTexture(coreType, cableColor)); } // If no core is present, just use the first part that comes into play for (EnumFacing side : renderState.getAttachments().keySet()) { IPartModel partModel = renderState.getAttachments().get(side); for (ResourceLocation model : partModel.getModels()) { IBakedModel bakedModel = this.partModels.get(model); if (bakedModel == null) { throw new IllegalStateException("Trying to use an unregistered part model: " + model); } TextureAtlasSprite particleTexture = bakedModel.getParticleTexture(); // If a part sub-model has no particle texture (indicated by it being the missing texture), // don't add it, so we don't get ugly missing texture break particles. if (this.textureMap.getMissingSprite() != particleTexture) { result.add(particleTexture); } } } return result; } private static CableBusRenderState getRenderingState(IBlockState state) { if (state == null || !(state instanceof IExtendedBlockState)) { return null; } IExtendedBlockState extendedBlockState = (IExtendedBlockState) state; return extendedBlockState.getValue(BlockCableBus.RENDER_STATE_PROPERTY); } @Override public boolean isAmbientOcclusion() { return true; } @Override public boolean isGui3d() { return false; } @Override public boolean isBuiltInRenderer() { return false; } @Override public TextureAtlasSprite getParticleTexture() { return this.particleTexture; } @Override public ItemCameraTransforms getItemCameraTransforms() { return ItemCameraTransforms.DEFAULT; } @Override public ItemOverrideList getOverrides() { return ItemOverrideList.NONE; } }