/* * 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 <http://www.gnu.org/licenses/lgpl>. */ package appeng.client.render.cablebus; import java.util.ArrayList; import java.util.Collections; import java.util.EnumMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Map.Entry; import javax.annotation.Nullable; 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 appeng.api.parts.IPartModel; import appeng.api.util.AECableType; import appeng.api.util.AEColor; import appeng.block.networking.BlockCableBus; public class CableBusBakedModel implements IBakedModel { private final CableBuilder cableBuilder; private final FacadeBuilder facadeBuilder; private final Map<ResourceLocation, IBakedModel> partModels; private final TextureAtlasSprite particleTexture; private final TextureMap textureMap = Minecraft.getMinecraft().getTextureMapBlocks(); CableBusBakedModel( CableBuilder cableBuilder, FacadeBuilder facadeBuilder, Map<ResourceLocation, IBakedModel> partModels, TextureAtlasSprite particleTexture ) { this.cableBuilder = cableBuilder; this.facadeBuilder = facadeBuilder; this.partModels = partModels; this.particleTexture = particleTexture; } @Override public List<BakedQuad> 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<BakedQuad> quads = new ArrayList<>(); // The core parts of the cable will only be rendered in the CUTOUT layer. TRANSLUCENT is used only for // translucent facades further down below. if( layer == BlockRenderLayer.CUTOUT ) { // First, handle the cable at the center of the cable bus addCableQuads( renderState, quads ); // 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 = partModels.get( model ); if( bakedModel == null ) { throw new IllegalStateException( "Trying to use an unregistered part model: " + model ); } List<BakedQuad> partQuads = bakedModel.getQuads( state, null, rand ); // Rotate quads accordingly QuadRotator rotator = new QuadRotator(); partQuads = rotator.rotateQuads( partQuads, facing, EnumFacing.UP ); quads.addAll( partQuads ); } } } facadeBuilder.addFacades( layer, renderState.getFacades(), renderState.getBoundingBoxes(), renderState.getAttachments().keySet(), rand, quads ); return quads; } // Determines whether a cable is connected to exactly two sides that are opposite each other private static boolean isStraightLine( AECableType cableType, EnumMap<EnumFacing, AECableType> sides ) { final Iterator<Entry<EnumFacing, AECableType>> it = sides.entrySet().iterator(); if( !it.hasNext() ) { return false; // No connections } final Entry<EnumFacing, AECableType> 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() ); // Certain cable types have restrictions on when they're rendered as a straight connection switch( cableType ) { case GLASS: return firstType == AECableType.GLASS && secondType == AECableType.GLASS; case DENSE: return firstType == AECableType.DENSE && secondType == AECableType.DENSE; } return true; } private void addCableQuads( CableBusRenderState renderState, List<BakedQuad> quadsOut ) { AECableType cableType = renderState.getCableType(); if( cableType == AECableType.NONE ) { return; } AEColor cableColor = renderState.getCableColor(); EnumMap<EnumFacing, AECableType> 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: cableBuilder.addStraightGlassConnection( facing, cableColor, quadsOut ); break; case COVERED: cableBuilder.addStraightCoveredConnection( facing, cableColor, quadsOut ); break; case SMART: cableBuilder.addStraightSmartConnection( facing, cableColor, renderState.getChannelsOnSide().get( facing ), quadsOut ); break; case DENSE: cableBuilder.addStraightDenseConnection( facing, cableColor, renderState.getChannelsOnSide().get( facing ), quadsOut ); break; } return; // Don't render the other form of connection } cableBuilder.addCableCore( renderState.getCoreType(), cableColor, quadsOut ); // Render all internal connections to attachments EnumMap<EnumFacing, Integer> attachmentConnections = renderState.getAttachmentConnections(); for( EnumFacing facing : attachmentConnections.keySet() ) { int distance = attachmentConnections.get( facing ); int channels = renderState.getChannelsOnSide().get( facing ); switch( cableType ) { case GLASS: cableBuilder.addConstrainedGlassConnection( facing, cableColor, distance, quadsOut ); break; case COVERED: cableBuilder.addConstrainedCoveredConnection( facing, cableColor, distance, quadsOut ); break; case SMART: cableBuilder.addConstrainedSmartConnection( facing, cableColor, distance, channels, quadsOut ); break; case DENSE: // Dense cables do not render connections to parts since none can be attached break; } } // Render all outgoing connections using the appropriate type for( final Entry<EnumFacing, AECableType> 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: cableBuilder.addGlassConnection( facing, cableColor, connectionType, cableBusAdjacent, quadsOut ); break; case COVERED: cableBuilder.addCoveredConnection( facing, cableColor, connectionType, cableBusAdjacent, quadsOut ); break; case SMART: cableBuilder.addSmartConnection( facing, cableColor, connectionType, cableBusAdjacent, channels, quadsOut ); break; case DENSE: cableBuilder.addDenseConnection( facing, cableColor, connectionType, cableBusAdjacent, channels, quadsOut ); break; } } } /** * Gets a list of texture sprites appropriate for particles (digging, etc.) given the render state for a cable bus. */ public List<TextureAtlasSprite> getParticleTextures( CableBusRenderState renderState ) { CableCoreType coreType = CableCoreType.fromCableType( renderState.getCableType() ); AEColor cableColor = renderState.getCableColor(); List<TextureAtlasSprite> result = new ArrayList<>(); if( coreType != null ) { result.add( 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 = 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( 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 false; } @Override public boolean isGui3d() { return false; } @Override public boolean isBuiltInRenderer() { return false; } @Override public TextureAtlasSprite getParticleTexture() { return particleTexture; } @Override public ItemCameraTransforms getItemCameraTransforms() { return ItemCameraTransforms.DEFAULT; } @Override public ItemOverrideList getOverrides() { return ItemOverrideList.NONE; } }