New facade system (#3702)

Completely new system to render facades.

It will now support many more cases compared to the old system.
For example connected textures are now possible, as well as multilayer models and so on.
This commit is contained in:
covers1624
2018-09-02 19:29:37 +09:30
committed by yueh
parent ed8baf8c15
commit 7ba2893635
31 changed files with 2965 additions and 599 deletions
@@ -61,10 +61,12 @@ import net.minecraftforge.fml.common.registry.GameRegistry;
import net.minecraftforge.fml.relauncher.Side;
import net.minecraftforge.fml.relauncher.SideOnly;
import appeng.api.AEApi;
import appeng.api.parts.IFacadeContainer;
import appeng.api.parts.IFacadePart;
import appeng.api.parts.PartItemStack;
import appeng.api.parts.SelectedPart;
import appeng.api.util.AEColor;
import appeng.api.util.AEPartLocation;
import appeng.block.AEBaseTileBlock;
import appeng.client.UnlistedProperty;
import appeng.client.render.cablebus.CableBusBakedModel;
@@ -74,6 +76,7 @@ import appeng.core.AppEng;
import appeng.core.sync.network.NetworkHandler;
import appeng.core.sync.packets.PacketClick;
import appeng.helpers.AEGlassMaterial;
import appeng.integration.abstraction.IAEFacade;
import appeng.parts.ICableBusContainer;
import appeng.parts.NullCableBusContainer;
import appeng.tile.AEBaseTile;
@@ -83,7 +86,7 @@ import appeng.tile.networking.TileCableBusTESR;
import appeng.util.Platform;
public class BlockCableBus extends AEBaseTileBlock
public class BlockCableBus extends AEBaseTileBlock implements IAEFacade
{
public static final UnlistedProperty<CableBusRenderState> RENDER_STATE_PROPERTY = new UnlistedProperty<>( "cable_bus_render_state", CableBusRenderState.class );
@@ -122,6 +125,8 @@ public class BlockCableBus extends AEBaseTileBlock
public IBlockState getExtendedState( IBlockState state, IBlockAccess world, BlockPos pos )
{
CableBusRenderState renderState = this.cb( world, pos ).getRenderState();
renderState.setWorld( world );
renderState.setPos( pos );
return ( (IExtendedBlockState) state ).withProperty( RENDER_STATE_PROPERTY, renderState );
}
@@ -317,13 +322,14 @@ public class BlockCableBus extends AEBaseTileBlock
for( int l = 0; l < 4; ++l )
{
// Randomly select one of the textures if the cable bus has more than just one possibility here
TextureAtlasSprite texture = Platform.pickRandom( textures );
final TextureAtlasSprite texture = Platform.pickRandom( textures );
double d0 = pos.getX() + ( j + 0.5D ) / 4.0D;
double d1 = pos.getY() + ( k + 0.5D ) / 4.0D;
double d2 = pos.getZ() + ( l + 0.5D ) / 4.0D;
ParticleDigging particle = new DestroyFX( world, d0, d1, d2, d0 - pos.getX() - 0.5D, d1 - pos
final double d0 = pos.getX() + ( j + 0.5D ) / 4.0D;
final double d1 = pos.getY() + ( k + 0.5D ) / 4.0D;
final double d2 = pos.getZ() + ( l + 0.5D ) / 4.0D;
final ParticleDigging particle = new DestroyFX( world, d0, d1, d2, d0 - pos.getX() - 0.5D, d1 - pos
.getY() - 0.5D, d2 - pos.getZ() - 0.5D, this.getDefaultState() ).setBlockPos( pos );
particle.setParticleTexture( texture );
effectRenderer.addEffect( particle );
}
@@ -356,6 +362,20 @@ public class BlockCableBus extends AEBaseTileBlock
return out == null ? NULL_CABLE_BUS : out;
}
@Nullable
private IFacadeContainer fc( final IBlockAccess w, final BlockPos pos )
{
final TileEntity te = w.getTileEntity( pos );
IFacadeContainer out = null;
if( te instanceof TileCableBus )
{
out = ( (TileCableBus) te ).getCableBus().getFacadeContainer();
}
return out;
}
@Override
public void onBlockClicked( World worldIn, BlockPos pos, EntityPlayer playerIn )
{
@@ -436,14 +456,25 @@ public class BlockCableBus extends AEBaseTileBlock
@Override
public boolean canRenderInLayer( IBlockState state, BlockRenderLayer layer )
{
if( AEApi.instance().partHelper().getCableRenderMode().transparentFacades )
return true;
}
@Override
public IBlockState getFacadeState( IBlockAccess world, BlockPos pos, EnumFacing side )
{
if( side != null )
{
return layer == BlockRenderLayer.CUTOUT || layer == BlockRenderLayer.TRANSLUCENT;
}
else
{
return layer == BlockRenderLayer.CUTOUT;
IFacadeContainer container = this.fc( world, pos );
if( container != null )
{
IFacadePart facade = container.getFacade( AEPartLocation.fromFacing( side ) );
if( facade != null )
{
return facade.getBlockState();
}
}
}
return world.getBlockState( pos );
}
public static Class<? extends AEBaseTile> getNoTesrTile()
@@ -0,0 +1,86 @@
/*
* This file is part of Applied Energistics 2.
* Copyright (c) 2013 - 2018, 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;
import java.util.ArrayList;
import java.util.Collections;
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.ItemOverrideList;
import net.minecraft.item.ItemStack;
import net.minecraft.util.EnumFacing;
import appeng.client.render.cablebus.FacadeBuilder;
/**
* This model used the provided FacadeBuilder to "slice" the item quads for the facade provided.
*
* @author covers1624
*/
public class FacadeBakedItemModel extends DelegateBakedModel
{
private final ItemStack textureStack;
private final FacadeBuilder facadeBuilder;
protected FacadeBakedItemModel( IBakedModel base, ItemStack textureStack, FacadeBuilder facadeBuilder )
{
super( base );
this.textureStack = textureStack;
this.facadeBuilder = facadeBuilder;
}
@Override
public List<BakedQuad> getQuads( @Nullable IBlockState state, @Nullable EnumFacing side, long rand )
{
if( side != null )
{
return Collections.emptyList();
}
List<BakedQuad> quads = new ArrayList<>();
quads.addAll( this.facadeBuilder.buildFacadeItemQuads( this.textureStack, EnumFacing.NORTH ) );
quads.addAll( this.getBaseModel().getQuads( state, side, rand ) );
return quads;
}
@Override
public boolean isGui3d()
{
return false;
}
@Override
public boolean isBuiltInRenderer()
{
return false;
}
@Override
public ItemOverrideList getOverrides()
{
return ItemOverrideList.NONE;
}
}
@@ -34,6 +34,7 @@ import net.minecraft.item.ItemStack;
import net.minecraft.util.EnumFacing;
import net.minecraft.world.World;
import appeng.client.render.cablebus.FacadeBuilder;
import appeng.items.parts.ItemFacade;
@@ -45,11 +46,13 @@ import appeng.items.parts.ItemFacade;
public class FacadeDispatcherBakedModel extends DelegateBakedModel
{
private final VertexFormat format;
private final FacadeBuilder facadeBuilder;
public FacadeDispatcherBakedModel( IBakedModel baseModel, VertexFormat format )
public FacadeDispatcherBakedModel( IBakedModel baseModel, VertexFormat format, FacadeBuilder facadeBuilder )
{
super( baseModel );
this.format = format;
this.facadeBuilder = facadeBuilder;
}
// This is never used. See the item override list below.
@@ -86,11 +89,9 @@ public class FacadeDispatcherBakedModel extends DelegateBakedModel
ItemFacade itemFacade = (ItemFacade) stack.getItem();
IBlockState state = itemFacade.getTextureBlockState( stack );
ItemStack textureItem = itemFacade.getTextureItem( stack );
return new FacadeWithBlockBakedModel( FacadeDispatcherBakedModel.this
.getBaseModel(), state, textureItem, FacadeDispatcherBakedModel.this.format );
return new FacadeBakedItemModel( FacadeDispatcherBakedModel.this.getBaseModel(), textureItem, FacadeDispatcherBakedModel.this.facadeBuilder );
}
};
}
@@ -31,6 +31,7 @@ import net.minecraftforge.client.model.IModel;
import net.minecraftforge.client.model.ModelLoaderRegistry;
import net.minecraftforge.common.model.IModelState;
import appeng.client.render.cablebus.FacadeBuilder;
import appeng.core.AppEng;
@@ -71,8 +72,9 @@ public class FacadeItemModel implements IModel
public IBakedModel bake( IModelState state, VertexFormat format, Function<ResourceLocation, TextureAtlasSprite> bakedTextureGetter )
{
IBakedModel bakedBaseModel = this.getBaseModel().bake( state, format, bakedTextureGetter );
FacadeBuilder facadeBuilder = new FacadeBuilder();
return new FacadeDispatcherBakedModel( bakedBaseModel, format );
return new FacadeDispatcherBakedModel( bakedBaseModel, format, facadeBuilder );
}
@Override
@@ -1,106 +0,0 @@
/*
* 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;
import java.util.ArrayList;
import java.util.EnumSet;
import java.util.List;
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.ItemOverrideList;
import net.minecraft.client.renderer.vertex.VertexFormat;
import net.minecraft.item.ItemStack;
import net.minecraft.util.EnumFacing;
import appeng.client.render.cablebus.CubeBuilder;
import appeng.client.render.cablebus.FacadeBuilder;
/**
* This is the actual baked model that will combine the north face of a given block state
* with the base facade item model to achieve what is then actually rendered on screen.
*/
public class FacadeWithBlockBakedModel extends DelegateBakedModel
{
private final IBlockState blockState;
private final IBakedModel textureModel;
private final VertexFormat format;
private final ItemStack textureItem;
public FacadeWithBlockBakedModel( IBakedModel baseModel, IBlockState blockState, ItemStack textureItem, VertexFormat format )
{
super( baseModel );
this.blockState = blockState;
this.textureItem = textureItem;
this.textureModel = Minecraft.getMinecraft().getBlockRendererDispatcher().getModelForState( blockState );
this.format = format;
}
@Override
public List<BakedQuad> getQuads( @Nullable IBlockState state, @Nullable EnumFacing side, long rand )
{
// Only the north side is actually read from the base model for item models
if( side == EnumFacing.NORTH )
{
List<BakedQuad> quads = new ArrayList<>( 1 );
CubeBuilder builder = new CubeBuilder( this.format, quads );
FacadeBuilder.TextureAtlasAndTint sprite = FacadeBuilder.getSprite( this.textureModel, this.blockState, side, rand );
if( sprite != null && sprite.getSprite() != null )
{
if( sprite.getTint() != -1 )
{
builder.setColor( Minecraft.getMinecraft().getItemColors().colorMultiplier( this.textureItem, sprite.getTint() ) );
}
builder.setTexture( sprite.getSprite() );
builder.setDrawFaces( EnumSet.of( EnumFacing.NORTH ) );
builder.addCube( 0, 0, 0, 16, 16, 16 );
return quads;
}
}
return this.getBaseModel().getQuads( state, side, rand );
}
@Override
public boolean isGui3d()
{
return false;
}
@Override
public boolean isBuiltInRenderer()
{
return false;
}
@Override
public ItemOverrideList getOverrides()
{
return ItemOverrideList.NONE;
}
}
@@ -88,8 +88,9 @@ public class CableBusBakedModel implements IBakedModel
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.
// 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 )
{
@@ -138,14 +139,7 @@ public class CableBusBakedModel implements IBakedModel
}
}
}
this.facadeBuilder.addFacades(
layer,
renderState.getFacades(),
renderState.getBoundingBoxes(),
renderState.getAttachments().keySet(),
rand,
quads );
this.facadeBuilder.buildFacadeQuads( layer, renderState, rand, quads, this.partModels::get );
return quads;
}
@@ -65,7 +65,6 @@ public class CableBusModel implements IModel
{
return ImmutableList.<ResourceLocation>builder()
.addAll( CableBuilder.getTextures() )
.addAll( FacadeBuilder.getTextures() )
.build();
}
@@ -75,7 +74,7 @@ public class CableBusModel implements IModel
Map<ResourceLocation, IBakedModel> partModels = this.loadPartModels( state, format, bakedTextureGetter );
CableBuilder cableBuilder = new CableBuilder( format, bakedTextureGetter );
FacadeBuilder facadeBuilder = new FacadeBuilder( format, bakedTextureGetter );
FacadeBuilder facadeBuilder = new FacadeBuilder();
// This should normally not be used, but we *have* to provide a particle texture or otherwise damage models will
// crash
@@ -19,6 +19,7 @@
package appeng.client.render.cablebus;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.EnumMap;
import java.util.EnumSet;
@@ -27,6 +28,8 @@ import java.util.Objects;
import net.minecraft.util.EnumFacing;
import net.minecraft.util.math.AxisAlignedBB;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.IBlockAccess;
import appeng.api.parts.IPartModel;
import appeng.api.util.AECableType;
@@ -69,6 +72,10 @@ public class CableBusRenderState
// Contains the facade to use for each side that has a facade attached
private EnumMap<EnumFacing, FacadeRenderState> facades = new EnumMap<>( EnumFacing.class );
// Used for Facades.
private WeakReference<IBlockAccess> world;
private BlockPos pos;
// Contains the bounding boxes of all parts on the cable bus to allow facades to cut out holes for the parts. This
// list is only populated if there are
// facades on this cable bus
@@ -151,6 +158,26 @@ public class CableBusRenderState
return this.facades;
}
public IBlockAccess getWorld()
{
return this.world.get();
}
public void setWorld( IBlockAccess world )
{
this.world = new WeakReference<>( world );
}
public BlockPos getPos()
{
return this.pos;
}
public void setPos( BlockPos pos )
{
this.pos = pos;
}
public List<AxisAlignedBB> getBoundingBoxes()
{
return this.boundingBoxes;
@@ -196,9 +223,8 @@ public class CableBusRenderState
final CableBusRenderState other = (CableBusRenderState) obj;
return this.cableColor == other.cableColor && this.cableType == other.cableType && this.coreType == other.coreType && Objects
.equals( this.attachmentConnections, other.attachmentConnections ) && Objects.equals( this.cableBusAdjacent,
other.cableBusAdjacent ) && Objects.equals( this.channelsOnSide, other.channelsOnSide ) && Objects.equals( this.connectionTypes,
other.connectionTypes ) && Objects.equals( this.partFlags, other.partFlags );
.equals( this.attachmentConnections, other.attachmentConnections ) && Objects.equals( this.cableBusAdjacent, other.cableBusAdjacent ) && Objects
.equals( this.channelsOnSide, other.channelsOnSide ) && Objects.equals( this.connectionTypes, other.connectionTypes ) && Objects
.equals( this.partFlags, other.partFlags );
}
}
@@ -0,0 +1,115 @@
/*
* This file is part of Applied Energistics 2.
* Copyright (c) 2013 - 2018, 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 javax.annotation.Nullable;
import net.minecraft.block.state.IBlockState;
import net.minecraft.tileentity.TileEntity;
import net.minecraft.util.EnumFacing;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.IBlockAccess;
import net.minecraft.world.WorldType;
import net.minecraft.world.biome.Biome;
/**
* This is used to retrieve the ExtendedState of a block for facade rendering.
* It fakes the block at BlockPos provided as the IBlockState provided.
*
* @author covers1624
*/
public class FacadeBlockAccess implements IBlockAccess
{
private final IBlockAccess world;
private final BlockPos pos;
private final EnumFacing side;
private final IBlockState state;
public FacadeBlockAccess( IBlockAccess world, BlockPos pos, EnumFacing side, IBlockState state )
{
this.world = world;
this.pos = pos;
this.side = side;
this.state = state;
}
@Nullable
@Override
public TileEntity getTileEntity( BlockPos pos )
{
return this.world.getTileEntity( pos );
}
@Override
public int getCombinedLight( BlockPos pos, int lightValue )
{
return this.world.getCombinedLight( pos, lightValue );
}
@Override
public IBlockState getBlockState( BlockPos pos )
{
if( this.pos == pos )
{
return this.state;
}
return this.world.getBlockState( pos );
}
@Override
public boolean isAirBlock( BlockPos pos )
{
IBlockState state = this.getBlockState( pos );
return state.getBlock().isAir( state, this.world, pos );
}
@Override
public Biome getBiome( BlockPos pos )
{
return this.world.getBiome( pos );
}
@Override
public int getStrongPower( BlockPos pos, EnumFacing direction )
{
return this.world.getStrongPower( pos, direction );
}
@Override
public WorldType getWorldType()
{
return this.world.getWorldType();
}
@Override
public boolean isSideSolid( BlockPos pos, EnumFacing side, boolean _default )
{
if( pos.getX() < -30000000 || pos.getZ() < -30000000 || pos.getX() >= 30000000 || pos.getZ() >= 30000000 )
{
return _default;
}
else
{
return this.getBlockState( pos ).isSideSolid( this, pos, side );
}
}
}
@@ -1,6 +1,6 @@
/*
* This file is part of Applied Energistics 2.
* Copyright (c) 2013 - 2014, AlgorithmX2, All rights reserved.
* Copyright (c) 2013 - 2018, 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
@@ -19,370 +19,372 @@
package appeng.client.render.cablebus;
import java.util.Collection;
import java.util.ArrayList;
import java.util.Collections;
import java.util.EnumSet;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.function.Function;
import javax.annotation.Nullable;
import javax.vecmath.Vector3f;
import net.minecraft.block.state.IBlockState;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.BlockRendererDispatcher;
import net.minecraft.client.renderer.block.model.BakedQuad;
import net.minecraft.client.renderer.block.model.IBakedModel;
import net.minecraft.client.renderer.texture.TextureAtlasSprite;
import net.minecraft.client.renderer.vertex.VertexFormat;
import net.minecraft.client.renderer.color.BlockColors;
import net.minecraft.item.ItemStack;
import net.minecraft.util.BlockRenderLayer;
import net.minecraft.util.EnumFacing;
import net.minecraft.util.EnumFacing.Axis;
import net.minecraft.util.ResourceLocation;
import net.minecraft.util.math.AxisAlignedBB;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.IBlockAccess;
import net.minecraftforge.client.ForgeHooksClient;
import net.minecraftforge.client.MinecraftForgeClient;
import appeng.api.AEApi;
import appeng.api.util.AEAxisAlignedBB;
import appeng.core.AELog;
import appeng.core.AppEng;
import appeng.parts.misc.PartCableAnchor;
import appeng.thirdparty.codechicken.lib.model.CachedFormat;
import appeng.thirdparty.codechicken.lib.model.Quad;
import appeng.thirdparty.codechicken.lib.model.pipeline.BakedPipeline;
import appeng.thirdparty.codechicken.lib.model.pipeline.transformers.QuadAlphaOverride;
import appeng.thirdparty.codechicken.lib.model.pipeline.transformers.QuadClamper;
import appeng.thirdparty.codechicken.lib.model.pipeline.transformers.QuadCornerKicker;
import appeng.thirdparty.codechicken.lib.model.pipeline.transformers.QuadFaceStripper;
import appeng.thirdparty.codechicken.lib.model.pipeline.transformers.QuadReInterpolator;
import appeng.thirdparty.codechicken.lib.model.pipeline.transformers.QuadTinter;
/**
* Handles creating the quads for facades attached to cable busses.
* The FacadeBuilder builds for facades..
*
* @author covers1624
*/
public class FacadeBuilder
{
private static final ResourceLocation TEXTURE_FACADE = new ResourceLocation( AppEng.MOD_ID, "parts/cable_anchor" );
public static double THICK_THICKNESS = 2D / 16D;
public static double THIN_THICKNESS = 1D / 16D;
private final VertexFormat format;
public static final AxisAlignedBB[] THICK_FACADE_BOXES = new AxisAlignedBB[] {
new AxisAlignedBB( 0.0, 0.0, 0.0, 1.0, THICK_THICKNESS, 1.0 ),
new AxisAlignedBB( 0.0, 1.0 - THICK_THICKNESS, 0.0, 1.0, 1.0, 1.0 ),
new AxisAlignedBB( 0.0, 0.0, 0.0, 1.0, 1.0, THICK_THICKNESS ),
new AxisAlignedBB( 0.0, 0.0, 1.0 - THICK_THICKNESS, 1.0, 1.0, 1.0 ),
new AxisAlignedBB( 0.0, 0.0, 0.0, THICK_THICKNESS, 1.0, 1.0 ),
new AxisAlignedBB( 1.0 - THICK_THICKNESS, 0.0, 0.0, 1.0, 1.0, 1.0 )
};
private final TextureAtlasSprite facadeTexture;
public static final AxisAlignedBB[] THIN_FACADE_BOXES = new AxisAlignedBB[] {
new AxisAlignedBB( 0.0, 0.0, 0.0, 1.0, THIN_THICKNESS, 1.0 ),
new AxisAlignedBB( 0.0, 1.0 - THIN_THICKNESS, 0.0, 1.0, 1.0, 1.0 ),
new AxisAlignedBB( 0.0, 0.0, 0.0, 1.0, 1.0, THIN_THICKNESS ),
new AxisAlignedBB( 0.0, 0.0, 1.0 - THIN_THICKNESS, 1.0, 1.0, 1.0 ),
new AxisAlignedBB( 0.0, 0.0, 0.0, THIN_THICKNESS, 1.0, 1.0 ),
new AxisAlignedBB( 1.0 - THIN_THICKNESS, 0.0, 0.0, 1.0, 1.0, 1.0 )
};
private static final Set<ResourceLocation> warnedFor = new HashSet<>();
private ThreadLocal<BakedPipeline> pipelines = ThreadLocal.withInitial( () -> BakedPipeline.builder()
// Clamper is responsible for clamping the vertex to the bounds specified.
.addElement( "clamper", QuadClamper.FACTORY )
// Strips faces if they match a mask.
.addElement( "face_stripper", QuadFaceStripper.FACTORY )
// Kicks the edge inner corners in, solves Z fighting
.addElement( "corner_kicker", QuadCornerKicker.FACTORY )
// Re-Interpolates the UV's for the quad.
.addElement( "interp", QuadReInterpolator.FACTORY )
// Tints the quad if we need it to. Disabled by default.
.addElement( "tinter", QuadTinter.FACTORY, false )
// Overrides the quad's alpha if we are forcing transparent facades.
.addElement( "transparent", QuadAlphaOverride.FACTORY, false, e -> e.setAlphaOverride( 0x4C / 255F ) )
.build()//
);
private ThreadLocal<Quad> collectors = ThreadLocal.withInitial( Quad::new );
FacadeBuilder( VertexFormat format, Function<ResourceLocation, TextureAtlasSprite> bakedTextureGetter )
{
this.format = format;
this.facadeTexture = bakedTextureGetter.apply( TEXTURE_FACADE );
}
static Collection<ResourceLocation> getTextures()
{
return Collections.singletonList( TEXTURE_FACADE );
}
void addFacades( BlockRenderLayer layer, Map<EnumFacing, FacadeRenderState> facadesState, List<AxisAlignedBB> partBoxes, Set<EnumFacing> sidesWithParts, long rand, List<BakedQuad> quads )
public void buildFacadeQuads( BlockRenderLayer layer, CableBusRenderState renderState, long rand, List<BakedQuad> quads, Function<ResourceLocation, IBakedModel> modelLookup )
{
BakedPipeline pipeline = this.pipelines.get();
Quad collectorQuad = this.collectors.get();
boolean transparent = AEApi.instance().partHelper().getCableRenderMode().transparentFacades;
Map<EnumFacing, FacadeRenderState> facadeStates = renderState.getFacades();
List<AxisAlignedBB> partBoxes = renderState.getBoundingBoxes();
Set<EnumFacing> sidesWithParts = renderState.getAttachments().keySet();
IBlockAccess parentWorld = renderState.getWorld();
BlockPos pos = renderState.getPos();
BlockColors blockColors = Minecraft.getMinecraft().getBlockColors();
boolean thinFacades = isUseThinFacades( partBoxes );
CubeBuilder builder = new CubeBuilder( this.format, quads );
facadesState.forEach( ( side, state ) ->
for( Entry<EnumFacing, FacadeRenderState> entry : facadeStates.entrySet() )
{
AxisAlignedBB facadeBox = getFacadeBox( side, thinFacades );
AEAxisAlignedBB cutOutBox = getCutOutBox( facadeBox, partBoxes );
EnumFacing side = entry.getKey();
int sideIndex = side.ordinal();
FacadeRenderState facadeRenderState = entry.getValue();
boolean renderStilt = !sidesWithParts.contains( side );
if( layer == BlockRenderLayer.CUTOUT && renderStilt )
{
for( ResourceLocation part : PartCableAnchor.FACADE_MODELS.getModels() )
{
IBakedModel partModel = modelLookup.apply( part );
QuadRotator rotator = new QuadRotator();
quads.addAll( rotator.rotateQuads( gatherQuads( partModel, null, rand ), side, EnumFacing.UP ) );
}
}
// If we are forcing transparency and this isn't the Translucent layer.
if( transparent && layer != BlockRenderLayer.TRANSLUCENT )
{
continue;
}
IBlockState blockState = facadeRenderState.getSourceBlock();
// If we aren't forcing transparency let the block decide if it should render.
if( !transparent )
{
if( !blockState.getBlock().canRenderInLayer( blockState, layer ) )
{
continue;
}
}
AxisAlignedBB fullBounds = thinFacades ? THIN_FACADE_BOXES[sideIndex] : THICK_FACADE_BOXES[sideIndex];
AxisAlignedBB facadeBox = fullBounds;
// If we are a transparent facade, we need to modify out BB.
if( facadeRenderState.isTransparent() )
{
double offset = thinFacades ? THIN_THICKNESS : THICK_THICKNESS;
AEAxisAlignedBB tmpBB = null;
for( EnumFacing face : EnumFacing.VALUES )
{
// Only faces that aren't on our axis
if( face.getAxis() != side.getAxis() )
{
FacadeRenderState otherState = facadeStates.get( face );
if( otherState != null && !otherState.isTransparent() )
{
if( tmpBB == null )
{
tmpBB = AEAxisAlignedBB.fromBounds( facadeBox );
}
switch( face )
{
case DOWN:
tmpBB.minY += offset;
break;
case UP:
tmpBB.maxY -= offset;
break;
case NORTH:
tmpBB.minZ += offset;
break;
case SOUTH:
tmpBB.maxZ -= offset;
break;
case WEST:
tmpBB.minX += offset;
break;
case EAST:
tmpBB.maxX -= offset;
break;
default:
throw new RuntimeException( "Switch falloff. " + String.valueOf( face ) );
}
}
}
}
if( tmpBB != null )
{
facadeBox = tmpBB.getBoundingBox();
}
}
AEAxisAlignedBB cutOutBox = getCutOutBox( facadeBox, partBoxes );
List<AxisAlignedBB> holeStrips = getBoxes( facadeBox, cutOutBox, side.getAxis() );
IBlockAccess facadeAccess = new FacadeBlockAccess( parentWorld, pos, side, blockState );
BlockRendererDispatcher dispatcher = Minecraft.getMinecraft().getBlockRendererDispatcher();
try
{
this.addFacade( layer, facadesState, side, cutOutBox, thinFacades, renderStilt, rand, builder );
blockState = blockState.getActualState( facadeAccess, pos );
}
catch( Throwable t )
catch( Exception ignored )
{
AELog.debug( t );
}
} );
}
public static TextureAtlasAndTint getSprite( IBakedModel blockModel, IBlockState state, EnumFacing facing, long rand )
{
TextureAtlasAndTint firstFound = null;
BlockRenderLayer orgLayer = MinecraftForgeClient.getRenderLayer();
try
{
// Some other mods also distinguish between layers, so we're doing this in a loop from most likely to least
// likely
for( BlockRenderLayer layer : BlockRenderLayer.values() )
IBakedModel model = dispatcher.getModelForState( blockState );
try
{
blockState = blockState.getBlock().getExtendedState( blockState, facadeAccess, pos );
}
catch( Exception ignored )
{
}
List<BakedQuad> modelQuads = new ArrayList<>();
// If we are forcing transparent facades, fake the render layer, and grab all quads.
if( transparent )
{
for( BlockRenderLayer forcedLayer : BlockRenderLayer.values() )
{
// Check if the block renders on the layer we want to force.
if( blockState.getBlock().canRenderInLayer( blockState, forcedLayer ) )
{
// Force the layer and gather quads.
ForgeHooksClient.setRenderLayer( forcedLayer );
modelQuads.addAll( gatherQuads( model, blockState, rand ) );
}
}
// Reset.
ForgeHooksClient.setRenderLayer( layer );
}
else
{
modelQuads.addAll( gatherQuads( model, blockState, rand ) );
}
for( BakedQuad bakedQuad : blockModel.getQuads( state, facing, rand ) )
{
return new TextureAtlasAndTint( bakedQuad );
}
// No quads.. Cool, next!
if( modelQuads.isEmpty() )
{
continue;
}
for( BakedQuad bakedQuad : blockModel.getQuads( state, null, rand ) )
// Grab out pipeline elements.
QuadClamper clamper = pipeline.getElement( "clamper", QuadClamper.class );
QuadFaceStripper edgeStripper = pipeline.getElement( "face_stripper", QuadFaceStripper.class );
QuadTinter tinter = pipeline.getElement( "tinter", QuadTinter.class );
QuadCornerKicker kicker = pipeline.getElement( "corner_kicker", QuadCornerKicker.class );
// Set global element states.
// calculate the side mask.
int facadeMask = 0;
for( Entry<EnumFacing, FacadeRenderState> ent : facadeStates.entrySet() )
{
EnumFacing s = ent.getKey();
if( s.getAxis() != side.getAxis() )
{
if( firstFound == null )
FacadeRenderState otherState = ent.getValue();
if( !otherState.isTransparent() )
{
firstFound = new TextureAtlasAndTint( bakedQuad );
facadeMask |= 1 << s.ordinal();
}
if( bakedQuad.getFace() == facing )
}
}
// Setup the edge stripper.
edgeStripper.setBounds( fullBounds );
edgeStripper.setMask( facadeMask );
// Setup the kicker.
kicker.setSide( sideIndex );
kicker.setFacadeMask( facadeMask );
kicker.setBox( fullBounds );
kicker.setThickness( thinFacades ? THIN_THICKNESS : THICK_THICKNESS );
for( BakedQuad quad : modelQuads )
{
// lookup the format in CachedFormat.
CachedFormat format = CachedFormat.lookup( quad.getFormat() );
// If this quad has a tint index, setup the tinter.
if( quad.hasTintIndex() )
{
tinter.setTint( blockColors.colorMultiplier( blockState, facadeAccess, pos, quad.getTintIndex() ) );
}
for( AxisAlignedBB box : holeStrips )
{
// setup the clamper for this box
clamper.setClampBounds( box );
// Reset the pipeline, clears all enabled/disabled states.
pipeline.reset( format );
// Reset out collector.
collectorQuad.reset( format );
// Enable / disable the optional elements
pipeline.setElementState( "tinter", quad.hasTintIndex() );
pipeline.setElementState( "transparent", transparent );
// Prepare the pipeline for a quad.
pipeline.prepare( collectorQuad );
// Pipe our quad into the pipeline.
quad.pipe( pipeline );
// Check if the collector got any data.
if( collectorQuad.full )
{
return new TextureAtlasAndTint( bakedQuad );
// Add the result.
quads.add( collectorQuad.bake() );
}
}
}
}
catch( Exception e )
{
if( warnedFor.add( state.getBlock().getRegistryName() ) )
{
AELog.warn( "Unable to get facade sprite for blockstate %s. Supressing further warnings for this block.", state );
AELog.debug( e );
}
}
finally
{
ForgeHooksClient.setRenderLayer( orgLayer );
}
// Fall back to the particle texture, if we havent found anything else so far.
if( firstFound == null )
{
try
{
return new TextureAtlasAndTint( blockModel.getParticleTexture(), -1 );
}
catch( Exception e )
{
if( warnedFor.add( state.getBlock().getRegistryName() ) )
{
AELog.warn( "Unable to get facade sprite particle texture fallback for blockstate %s. Supressing further warnings for this block.", state );
AELog.debug( e );
}
}
}
return firstFound;
}
private void addFacade( BlockRenderLayer layer, Map<EnumFacing, FacadeRenderState> facades, EnumFacing side, AEAxisAlignedBB busBounds, boolean thinFacades, boolean renderStilt, long rand, CubeBuilder builder )
/**
* This is slow, so should be cached.
*
* @return The model.
*/
public List<BakedQuad> buildFacadeItemQuads( ItemStack textureItem, EnumFacing side )
{
List<BakedQuad> facadeQuads = new ArrayList<>();
IBakedModel model = Minecraft.getMinecraft().getRenderItem().getItemModelWithOverrides( textureItem, null, null );
List<BakedQuad> modelQuads = gatherQuads( model, null, 0 );
FacadeRenderState facadeState = facades.get( side );
IBlockState blockState = facadeState.getSourceBlock();
BakedPipeline pipeline = this.pipelines.get();
Quad collectorQuad = this.collectors.get();
builder.setDrawFaces( EnumSet.allOf( EnumFacing.class ) );
// Grab pipeline elements.
QuadClamper clamper = pipeline.getElement( "clamper", QuadClamper.class );
QuadTinter tinter = pipeline.getElement( "tinter", QuadTinter.class );
// Reset to no color multiplicator
builder.setColorRGB( 0xFFFFFF );
builder.useStandardUV();
// We only render the stilt if we don't intersect with any part directly, and if there's no part on our side
if( renderStilt && busBounds == null && layer == BlockRenderLayer.CUTOUT )
for( BakedQuad quad : modelQuads )
{
builder.setTexture( this.facadeTexture );
switch( side )
// Lookup the CachedFormat for this quads format.
CachedFormat format = CachedFormat.lookup( quad.getFormat() );
// Reset the pipeline.
pipeline.reset( format );
// Reset the collector.
collectorQuad.reset( format );
// If we have a tint index, setup the tinter and enable it.
if( quad.hasTintIndex() )
{
case DOWN:
builder.addCube( 7, 1, 7, 9, 6, 9 );
break;
case UP:
builder.addCube( 7, 10, 7, 9, 15, 9 );
break;
case NORTH:
builder.addCube( 7, 7, 1, 9, 9, 6 );
break;
case SOUTH:
builder.addCube( 7, 7, 10, 9, 9, 15 );
break;
case WEST:
builder.addCube( 1, 7, 7, 6, 9, 9 );
break;
case EAST:
builder.addCube( 10, 7, 7, 15, 9, 9 );
break;
}
}
// Do not add the translucent facade in any other layer than translucent
boolean translucent = AEApi.instance().partHelper().getCableRenderMode().transparentFacades;
if( translucent && layer != BlockRenderLayer.TRANSLUCENT )
{
return;
}
final float thickness = thinFacades ? 1 : 2;
BlockRendererDispatcher blockRendererDispatcher = Minecraft.getMinecraft().getBlockRendererDispatcher();
IBakedModel blockModel = blockRendererDispatcher.getModelForState( blockState );
final int color;
if( translucent )
{
color = 0x4CFFFFFF;
}
else
{
color = 0xFFFFFFFF;
}
// TODO: Cache this
for( EnumFacing facing : facadeState.getOpenFaces() )
{
TextureAtlasAndTint spriteAndTint = getSprite( blockModel, blockState, facing, rand );
if( spriteAndTint != null && spriteAndTint.sprite != null )
{
// Use the tint color from the item stack here, which is based upon the assumption that the
// model used for the block will use the same meaning for tint indices as the item model does
if( spriteAndTint.tint != -1 )
{
int tintColor = facadeState.resolveTintColor( spriteAndTint.tint );
// Still apply the transparency color
tintColor &= 0xFFFFFF;
tintColor |= color & 0xFF000000;
builder.setColor( tintColor );
}
else
{
builder.setColor( color );
}
builder.setTexture( facing, spriteAndTint.sprite );
}
else
{
builder.setColor( color );
builder.setTexture( facing, this.facadeTexture );
}
}
builder.setDrawFaces( facadeState.getOpenFaces() );
AxisAlignedBB primaryBox = getFacadeBox( side, thinFacades );
Vector3f min = new Vector3f( (float) primaryBox.minX * 16, (float) primaryBox.minY * 16, (float) primaryBox.minZ * 16 );
Vector3f max = new Vector3f( (float) primaryBox.maxX * 16, (float) primaryBox.maxY * 16, (float) primaryBox.maxZ * 16 );
if( busBounds == null )
{
// Adjust the facade for neighboring facades so that facade cubes dont overlap with each other
if( side == EnumFacing.NORTH || side == EnumFacing.SOUTH )
{
if( facades.containsKey( EnumFacing.UP ) )
{
max.y -= thickness;
}
if( facades.containsKey( EnumFacing.DOWN ) )
{
min.y += thickness;
}
}
else if( side == EnumFacing.EAST || side == EnumFacing.WEST )
{
if( facades.containsKey( EnumFacing.UP ) )
{
max.y -= thickness;
}
if( facades.containsKey( EnumFacing.DOWN ) )
{
min.y += thickness;
}
if( facades.containsKey( EnumFacing.SOUTH ) )
{
max.z -= thickness;
}
if( facades.containsKey( EnumFacing.NORTH ) )
{
min.z += thickness;
}
}
builder.addCube( min.x, min.y, min.z, max.x, max.y, max.z );
}
else
{
Vector3f busMin = new Vector3f( (float) busBounds.minX * 16, (float) busBounds.minY * 16, (float) busBounds.minZ * 16 );
Vector3f busMax = new Vector3f( (float) busBounds.maxX * 16, (float) busBounds.maxY * 16, (float) busBounds.maxZ * 16 );
if( side == EnumFacing.UP || side == EnumFacing.DOWN )
{
this.renderSegmentBlockCurrentBounds( builder, min, max, 0.0f, 0.0f, busMax.z, 16.0f, 16.0f, 16.0f );
this.renderSegmentBlockCurrentBounds( builder, min, max, 0.0f, 0.0f, 0.0f, 16.0f, 16.0f, busMin.z );
this.renderSegmentBlockCurrentBounds( builder, min, max, 0.0f, 0.0f, busMin.z, busMin.x, 16.0f, busMax.z );
this.renderSegmentBlockCurrentBounds( builder, min, max, busMax.x, 0.0f, busMin.z, 16.0f, 16.0f, busMax.z );
}
else if( side == EnumFacing.NORTH || side == EnumFacing.SOUTH )
{
if( facades.get( EnumFacing.UP ) != null )
{
max.y -= thickness;
}
if( facades.get( EnumFacing.DOWN ) != null )
{
min.y += thickness;
}
this.renderSegmentBlockCurrentBounds( builder, min, max, busMax.x, 0.0f, 0.0f, 16.0f, 16.0f, 16.0f );
this.renderSegmentBlockCurrentBounds( builder, min, max, 0.0f, 0.0f, 0.0f, busMin.x, 16.0f, 16.0f );
this.renderSegmentBlockCurrentBounds( builder, min, max, busMin.x, 0.0f, 0.0f, busMax.x, busMin.y, 16.0f );
this.renderSegmentBlockCurrentBounds( builder, min, max, busMin.x, busMax.y, 0.0f, busMax.x, 16.0f, 16.0f );
}
else
{
if( facades.get( EnumFacing.UP ) != null )
{
max.y -= thickness;
}
if( facades.get( EnumFacing.DOWN ) != null )
{
min.y += thickness;
}
if( facades.get( EnumFacing.SOUTH ) != null )
{
max.z -= thickness;
}
if( facades.get( EnumFacing.NORTH ) != null )
{
min.z += thickness;
}
this.renderSegmentBlockCurrentBounds( builder, min, max, 0.0f, 0.0f, busMax.z, 16.0f, 16.0f, 16.0f );
this.renderSegmentBlockCurrentBounds( builder, min, max, 0.0f, 0.0f, 0.0f, 16.0f, 16.0f, busMin.z );
this.renderSegmentBlockCurrentBounds( builder, min, max, 0.0f, 0.0f, busMin.z, 16.0f, busMin.y, busMax.z );
this.renderSegmentBlockCurrentBounds( builder, min, max, 0.0f, busMax.y, busMin.z, 16.0f, 16.0f, busMax.z );
tinter.setTint( Minecraft.getMinecraft().getItemColors().colorMultiplier( textureItem, quad.getTintIndex() ) );
pipeline.enableElement( "tinter" );
}
// Disable elements we don't need for items.
pipeline.disableElement( "face_stripper" );
pipeline.disableElement( "corner_kicker" );
// Setup the clamper
clamper.setClampBounds( THICK_FACADE_BOXES[side.ordinal()] );
// Prepare the pipeline.
pipeline.prepare( collectorQuad );
// Pipe our quad into the pipeline.
quad.pipe( pipeline );
// Check the collector for data and add the quad if there was.
if( collectorQuad.full )
{
facadeQuads.add( collectorQuad.bakeUnpacked() );
}
}
return facadeQuads;
}
private void renderSegmentBlockCurrentBounds( CubeBuilder builder, Vector3f min, Vector3f max, float minX, float minY, float minZ, float maxX, float maxY, float maxZ )
// Helper to gather all quads from a model into a list.
private static List<BakedQuad> gatherQuads( IBakedModel model, IBlockState state, long rand )
{
minX = Math.max( min.x, minX );
minY = Math.max( min.y, minY );
minZ = Math.max( min.z, minZ );
maxX = Math.min( max.x, maxX );
maxY = Math.min( max.y, maxY );
maxZ = Math.min( max.z, maxZ );
// don't draw it if its not at least a pixel wide...
if( maxX - minX >= 1.0 && maxY - minY >= 1.0 && maxZ - minZ >= 1.0 )
List<BakedQuad> modelQuads = new ArrayList<>();
for( EnumFacing face : EnumFacing.VALUES )
{
builder.addCube( minX, minY, minZ, maxX, maxY, maxZ );
modelQuads.addAll( model.getQuads( state, face, rand ) );
}
modelQuads.addAll( model.getQuads( state, null, rand ) );
return modelQuads;
}
/**
* Given the actual facade bounding box, and the bounding boxes of all parts, determine the biggest union of AABB
* that intersect with the
* facade's bounding box. This AABB will need to be "cut out" when the facade is rendered.
* that intersect with the facade's bounding
* box. This AABB will need to be "cut out" when the facade is rendered.
*/
@Nullable
private static AEAxisAlignedBB getCutOutBox( AxisAlignedBB facadeBox, List<AxisAlignedBB> partBoxes )
@@ -411,8 +413,58 @@ public class FacadeBuilder
}
/**
* Determines if any of the part's bounding boxes intersects with the outside 2 voxel wide layer.
* If so, we should use thinner facades (1 voxel deep).
* Generates the box segments around the specified hole. If the specified hole is null, a Singleton of the Facade
* box is returned.
*
* @param fb The Facade's box.
* @param hole The hole to 'cut'.
* @param axis The axis the facade is on.
*
* @return The box segments.
*/
private static List<AxisAlignedBB> getBoxes( AxisAlignedBB fb, AEAxisAlignedBB hole, Axis axis )
{
if( hole == null )
{
return Collections.singletonList( fb );
}
List<AxisAlignedBB> boxes = new ArrayList<>();
switch( axis )
{
case Y:
boxes.add( new AxisAlignedBB( fb.minX, fb.minY, fb.minZ, hole.minX, fb.maxY, fb.maxZ ) );
boxes.add( new AxisAlignedBB( hole.maxX, fb.minY, fb.minZ, fb.maxX, fb.maxY, fb.maxZ ) );
boxes.add( new AxisAlignedBB( hole.minX, fb.minY, fb.minZ, hole.maxX, fb.maxY, hole.minZ ) );
boxes.add( new AxisAlignedBB( hole.minX, fb.minY, hole.maxZ, hole.maxX, fb.maxY, fb.maxZ ) );
break;
case Z:
boxes.add( new AxisAlignedBB( fb.minX, fb.minY, fb.minZ, fb.maxX, hole.minY, fb.maxZ ) );
boxes.add( new AxisAlignedBB( fb.minX, hole.maxY, fb.minZ, fb.maxX, fb.maxY, fb.maxZ ) );
boxes.add( new AxisAlignedBB( fb.minX, hole.minY, fb.minZ, hole.minX, hole.maxY, fb.maxZ ) );
boxes.add( new AxisAlignedBB( hole.maxX, hole.minY, fb.minZ, fb.maxX, hole.maxY, fb.maxZ ) );
break;
case X:
boxes.add( new AxisAlignedBB( fb.minX, fb.minY, fb.minZ, fb.maxX, hole.minY, fb.maxZ ) );
boxes.add( new AxisAlignedBB( fb.minX, hole.maxY, fb.minZ, fb.maxX, fb.maxY, fb.maxZ ) );
boxes.add( new AxisAlignedBB( fb.minX, hole.minY, fb.minZ, fb.maxX, hole.maxY, hole.minZ ) );
boxes.add( new AxisAlignedBB( fb.minX, hole.minY, hole.maxZ, fb.maxX, hole.maxY, fb.maxZ ) );
break;
default:
// should never happen.
throw new RuntimeException( "switch falloff. " + String.valueOf( axis ) );
}
return boxes;
}
/**
* Determines if any of the part's bounding boxes intersects with the outside 2 voxel wide layer. If so, we should
* use thinner facades (1 voxel deep).
*/
private static boolean isUseThinFacades( List<AxisAlignedBB> partBoxes )
{
@@ -436,57 +488,4 @@ public class FacadeBuilder
}
return false;
}
private static AxisAlignedBB getFacadeBox( EnumFacing side, boolean thinFacades )
{
double thickness = ( thinFacades ? 1 : 2 ) / 16.0;
switch( side )
{
case DOWN:
return new AxisAlignedBB( 0.0, 0.0, 0.0, 1.0, thickness, 1.0 );
case EAST:
return new AxisAlignedBB( 1.0 - thickness, 0.0, 0.0, 1.0, 1.0, 1.0 );
case NORTH:
return new AxisAlignedBB( 0.0, 0.0, 0.0, 1.0, 1.0, thickness );
case SOUTH:
return new AxisAlignedBB( 0.0, 0.0, 1.0 - thickness, 1.0, 1.0, 1.0 );
case UP:
return new AxisAlignedBB( 0.0, 1.0 - thickness, 0.0, 1.0, 1.0, 1.0 );
case WEST:
return new AxisAlignedBB( 0.0, 0.0, 0.0, thickness, 1.0, 1.0 );
default:
throw new IllegalArgumentException( "Unsupported face: " + side );
}
}
public static class TextureAtlasAndTint
{
private final TextureAtlasSprite sprite;
private final int tint;
private TextureAtlasAndTint( BakedQuad quad )
{
this.sprite = quad.getSprite();
this.tint = quad.getTintIndex();
}
private TextureAtlasAndTint( TextureAtlasSprite sprite, int tint )
{
this.sprite = sprite;
this.tint = tint;
}
public TextureAtlasSprite getSprite()
{
return this.sprite;
}
public int getTint()
{
return this.tint;
}
}
}
@@ -2,12 +2,7 @@
package appeng.client.render.cablebus;
import java.util.EnumSet;
import net.minecraft.block.state.IBlockState;
import net.minecraft.client.Minecraft;
import net.minecraft.item.ItemStack;
import net.minecraft.util.EnumFacing;
/**
@@ -19,17 +14,12 @@ public class FacadeRenderState
// The block state to use for rendering this facade
private final IBlockState sourceBlock;
// Which faces of the cube should be rendered for this particular facade
private final EnumSet<EnumFacing> openFaces;
private final boolean transparent;
// For resolving the tint indices of a facade
private final ItemStack textureItem;
public FacadeRenderState( IBlockState sourceBlock, EnumSet<EnumFacing> openFaces, ItemStack textureItem )
public FacadeRenderState( IBlockState sourceBlock, boolean transparent )
{
this.sourceBlock = sourceBlock;
this.openFaces = openFaces;
this.textureItem = textureItem;
this.transparent = transparent;
}
public IBlockState getSourceBlock()
@@ -37,14 +27,8 @@ public class FacadeRenderState
return this.sourceBlock;
}
public EnumSet<EnumFacing> getOpenFaces()
public boolean isTransparent()
{
return this.openFaces;
return this.transparent;
}
public int resolveTintColor( int tintIndex )
{
return Minecraft.getMinecraft().getItemColors().colorMultiplier( this.textureItem, tintIndex );
}
}
@@ -0,0 +1,60 @@
/*
* This file is part of Applied Energistics 2.
* Copyright (c) 2013 - 2018, 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.integration.abstraction;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import net.minecraft.block.state.IBlockState;
import net.minecraft.util.EnumFacing;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.IBlockAccess;
import net.minecraftforge.fml.common.Optional;
import team.chisel.ctm.api.IFacade;
/**
* Neat abstraction class for All the IFacade interfaces.
*
* @author covers1624
*/
@Optional.Interface( iface = "team.chisel.ctm.api.IFacade", modid = "ctm-api" )
public interface IAEFacade extends IFacade
{
IBlockState getFacadeState( IBlockAccess world, BlockPos pos, EnumFacing side );
@Nonnull
@Override
@Optional.Method( modid = "ctm-api" )
default IBlockState getFacade( @Nonnull IBlockAccess world, @Nonnull BlockPos pos, @Nullable EnumFacing side, @Nonnull BlockPos connection )
{
return getFacadeState( world, pos, side );
}
@Nonnull
@Override
@Optional.Method( modid = "ctm-api" )
default IBlockState getFacade( @Nonnull IBlockAccess world, @Nonnull BlockPos pos, @Nullable EnumFacing side )
{
return getFacadeState( world, pos, side );
}
}
@@ -23,8 +23,6 @@ import java.util.ArrayList;
import java.util.List;
import net.minecraft.block.Block;
import net.minecraft.block.BlockGlass;
import net.minecraft.block.BlockStainedGlass;
import net.minecraft.block.state.IBlockState;
import net.minecraft.creativetab.CreativeTabs;
import net.minecraft.entity.player.EntityPlayer;
@@ -50,7 +48,6 @@ import appeng.api.parts.IAlphaPassItem;
import appeng.api.util.AEPartLocation;
import appeng.core.AELog;
import appeng.core.FacadeConfig;
import appeng.decorative.solid.BlockQuartzOre;
import appeng.facade.FacadePart;
import appeng.facade.IFacadeItem;
import appeng.items.AEBaseItem;
@@ -167,8 +164,6 @@ public class ItemFacade extends AEBaseItem implements IFacadeItem, IAlphaPassIte
final int metadata = l.getItem().getMetadata( l.getItemDamage() );
final boolean hasTile = b.hasTileEntity( b.getDefaultState() );
final boolean enableGlass = b instanceof BlockGlass || b instanceof BlockStainedGlass;
final boolean disableOre = b instanceof BlockQuartzOre;
// Try to get the block state based on the item stack's meta. If this fails, don't consider it for a facade
// This for example fails for Pistons because they hardcoded an invalid meta value in vanilla
@@ -183,9 +178,7 @@ public class ItemFacade extends AEBaseItem implements IFacadeItem, IAlphaPassIte
return ItemStack.EMPTY;
}
final boolean defaultValue = ( b
.isTopSolid( blockState ) && hasSimpleModel( blockState ) && !b.getTickRandomly() && !hasTile && !disableOre ) || enableGlass;
if( FacadeConfig.instance().checkEnabled( b, metadata, defaultValue ) )
if( blockState.getRenderType() == EnumBlockRenderType.MODEL && !hasTile )
{
if( returnItem )
{
@@ -260,7 +253,6 @@ public class ItemFacade extends AEBaseItem implements IFacadeItem, IAlphaPassIte
}
return new ItemStack( baseItem, 1, itemDamage );
}
@Override
@@ -292,7 +284,6 @@ public class ItemFacade extends AEBaseItem implements IFacadeItem, IAlphaPassIte
AELog.warn( "Block %s has broken getStateFromMeta method for meta %d", block.getRegistryName().toString(), baseItemStack.getItemDamage() );
return Blocks.GLASS.getDefaultState();
}
}
public List<ItemStack> getFacades()
@@ -1291,79 +1291,10 @@ public class CableBusContainer extends CableBusStorage implements AEMultiTile, I
if( blockState != null && textureItem != null )
{
final EnumSet<EnumFacing> openFaces = this.calculateFaceOpenFaces( side );
return new FacadeRenderState( blockState, openFaces, textureItem );
return new FacadeRenderState( blockState, !facade.getBlockState().isOpaqueCube() );
}
}
return null;
}
private EnumSet<EnumFacing> calculateFaceOpenFaces( EnumFacing side )
{
final EnumSet<EnumFacing> out = EnumSet.of( side, side.getOpposite() );
final IFacadePart facade = this.getFacade( side.ordinal() );
final IBlockAccess blockAccess = this.getTile().getWorld();
final BlockPos pos = this.getTile().getPos();
for( final EnumFacing it : EnumFacing.values() )
{
if( !out.contains( it ) && this.hasAlphaDiff( blockAccess.getTileEntity( pos.offset( it ) ), side, facade ) )
{
out.add( it );
}
}
if( out.contains( EnumFacing.UP ) && ( side.getFrontOffsetX() != 0 || side.getFrontOffsetZ() != 0 ) )
{
final IFacadePart fp = this.getFacade( EnumFacing.UP.ordinal() );
if( fp != null && ( fp.isTransparent() == facade.isTransparent() ) )
{
out.remove( EnumFacing.UP );
}
}
if( out.contains( EnumFacing.DOWN ) && ( side.getFrontOffsetX() != 0 || side.getFrontOffsetZ() != 0 ) )
{
final IFacadePart fp = this.getFacade( EnumFacing.DOWN.ordinal() );
if( fp != null && ( fp.isTransparent() == facade.isTransparent() ) )
{
out.remove( EnumFacing.DOWN );
}
}
if( out.contains( EnumFacing.SOUTH ) && ( side.getFrontOffsetX() != 0 ) )
{
final IFacadePart fp = this.getFacade( EnumFacing.SOUTH.ordinal() );
if( fp != null && ( fp.isTransparent() == facade.isTransparent() ) )
{
out.remove( EnumFacing.SOUTH );
}
}
if( out.contains( EnumFacing.NORTH ) && ( side.getFrontOffsetX() != 0 ) )
{
final IFacadePart fp = this.getFacade( EnumFacing.NORTH.ordinal() );
if( fp != null && ( fp.isTransparent() == facade.isTransparent() ) )
{
out.remove( EnumFacing.NORTH );
}
}
return out;
}
private boolean hasAlphaDiff( final TileEntity tileEntity, final EnumFacing side, final IFacadePart facade )
{
if( tileEntity instanceof IPartHost )
{
final IPartHost ph = (IPartHost) tileEntity;
final IFacadePart fp = ph.getFacadeContainer().getFacade( AEPartLocation.fromFacing( side ) );
return fp == null || ( fp.isTransparent() != facade.isTransparent() );
}
return true;
}
}
@@ -0,0 +1,129 @@
/*
* This file is part of CodeChickenLib.
* Copyright (c) 2018, covers1624, All rights reserved.
*
* CodeChickenLib 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 2.1 of the License, or
* (at your option) any later version.
*
* CodeChickenLib 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 CodeChickenLib. If not, see <http://www.gnu.org/licenses/lgpl>.
*/
package appeng.thirdparty.codechicken.lib.math;
/**
* @author covers1624
*/
public class InterpHelper
{
private float[][] posCache = new float[4][2];
private float[] valCache = new float[4];
private float x0;
private float x1;
private float y0;
private float y1;
private float rX;
private float rY;
private int p00;
private int p10;
private int p11;
private int p01;
/**
* Resets the interp helper with the given quad. Does not care what order the vertices are in.
*/
public void reset( float dx0, float dy0, float dx1, float dy1, float dx2, float dy2, float dx3, float dy3 )
{
float[] vec0 = this.posCache[0];
float[] vec1 = this.posCache[1];
float[] vec2 = this.posCache[2];
float[] vec3 = this.posCache[3];
vec0[0] = dx0;
vec1[0] = dx1;
vec2[0] = dx2;
vec3[0] = dx3;
vec0[1] = dy0;
vec1[1] = dy1;
vec2[1] = dy2;
vec3[1] = dy3;
}
/**
* Call when you are ready to use the InterpHelper.
*/
public void setup()
{
this.p00 = 0;// Bottom Left is always first.
this.x0 = this.posCache[this.p00][0];
this.y0 = this.posCache[this.p00][1];
for( int i = 1; i < 4; i++ )
{
float x = this.posCache[i][0];
float y = this.posCache[i][1];
if( this.y0 == y )
{
this.p10 = i;// Bottom right.
this.x1 = x;
}
else if( this.x0 == x )
{
this.p01 = i;// Top left.
this.y1 = y;
}
else
{
// Top right.
this.p11 = i;
}
}
}
/**
* Computes the coefficients for the interpolation.
*
* @param x X interp location.
* @param y Y interp location.
*/
public void locate( float x, float y )
{
this.rX = ( x - this.x0 ) / ( this.x1 - this.x0 );
this.rY = ( y - this.y0 ) / ( this.y1 - this.y0 );
}
/**
* Interpolates using the already computed coefficients.
*
* @param q0 Value at dx0 dy0
* @param q1 Value at dx1 dy1
* @param q2 Value at dx2 dy2
* @param q3 Value at dx3 dy3
*
* @return The result.
*/
public float interpolate( float q0, float q1, float q2, float q3 )
{
this.valCache[0] = q0;
this.valCache[1] = q1;
this.valCache[2] = q2;
this.valCache[3] = q3;
float f0 = ( this.valCache[this.p00] * ( 1 - this.rX ) ) + ( this.valCache[this.p10] * this.rX );
float f1 = ( this.valCache[this.p01] * ( 1 - this.rX ) ) + ( this.valCache[this.p11] * this.rX );
return ( f0 * ( 1 - this.rY ) ) + ( f1 * this.rY );
}
}
@@ -0,0 +1,164 @@
/*
* This file is part of CodeChickenLib.
* Copyright (c) 2018, covers1624, All rights reserved.
*
* CodeChickenLib 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 2.1 of the License, or
* (at your option) any later version.
*
* CodeChickenLib 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 CodeChickenLib. If not, see <http://www.gnu.org/licenses/lgpl>.
*/
package appeng.thirdparty.codechicken.lib.model;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import net.minecraft.client.renderer.vertex.VertexFormat;
import net.minecraft.client.renderer.vertex.VertexFormatElement;
/**
* A simple VertexFormat cache.
* This caches the existence of attributes and their indexes.
*
* @author covers1624
*/
public class CachedFormat
{
public static final Map<VertexFormat, CachedFormat> formatCache = new ConcurrentHashMap<>();
/**
* Lookup or create the CachedFormat for a given VertexFormat.
*
* @param format The format to lookup.
*
* @return The CachedFormat.
*/
public static CachedFormat lookup( VertexFormat format )
{
return formatCache.computeIfAbsent( format, CachedFormat::new );
}
public VertexFormat format;
public boolean hasPosition;
public boolean hasNormal;
public boolean hasColor;
public boolean hasUV;
public boolean hasLightMap;
public int positionIndex = -1;
public int normalIndex = -1;
public int colorIndex = -1;
public int uvIndex = -1;
public int lightMapIndex = -1;
public int elementCount;
/**
* Caches the vertex format element indexes for efficiency.
*
* @param format The format.
*/
public CachedFormat( VertexFormat format )
{
this.format = format;
this.elementCount = format.getElementCount();
for( int i = 0; i < this.elementCount; i++ )
{
VertexFormatElement element = format.getElement( i );
switch( element.getUsage() )
{
case POSITION:
if( this.hasPosition )
{
throw new IllegalStateException( "Found 2 position elements.." );
}
this.hasPosition = true;
this.positionIndex = i;
break;
case NORMAL:
if( this.hasNormal )
{
throw new IllegalStateException( "Found 2 normal elements.." );
}
this.hasNormal = true;
this.normalIndex = i;
break;
case COLOR:
if( this.hasColor )
{
throw new IllegalStateException( "Found 2 color elements.." );
}
this.hasColor = true;
this.colorIndex = i;
break;
case UV:
if( element.getIndex() == 0 )
{
if( this.hasUV )
{
throw new IllegalStateException( "Found 2 UV elements.." );
}
this.hasUV = true;
this.uvIndex = i;
break;
}
else if( element.getIndex() == 1 )
{
if( this.hasLightMap )
{
throw new IllegalStateException( "Found 2 LightMap elements.." );
}
this.hasLightMap = true;
this.lightMapIndex = i;
break;
}
break;
}
}
}
@Override
public boolean equals( Object obj )
{
if( this == obj )
{
return true;
}
if( !( obj instanceof CachedFormat ) )
{
return false;
}
CachedFormat other = (CachedFormat) obj;
return other.elementCount == this.elementCount && //
other.positionIndex == this.positionIndex && //
other.normalIndex == this.normalIndex && //
other.colorIndex == this.colorIndex && //
other.uvIndex == this.uvIndex && //
other.lightMapIndex == this.lightMapIndex;
}
@Override
public int hashCode()
{
int result = 1;
result = 31 * result + this.elementCount;
result = 31 * result + this.positionIndex;
result = 31 * result + this.normalIndex;
result = 31 * result + this.colorIndex;
result = 31 * result + this.uvIndex;
result = 31 * result + this.lightMapIndex;
return result;
}
}
@@ -0,0 +1,42 @@
/*
* This file is part of CodeChickenLib.
* Copyright (c) 2018, covers1624, All rights reserved.
*
* CodeChickenLib 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 2.1 of the License, or
* (at your option) any later version.
*
* CodeChickenLib 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 CodeChickenLib. If not, see <http://www.gnu.org/licenses/lgpl>.
*/
package appeng.thirdparty.codechicken.lib.model;
import net.minecraftforge.client.model.pipeline.IVertexConsumer;
/**
* Marks a standard IVertexConsumer as compatible with {@link Quad}.
*
* @author covers1624
*/
public interface ISmartVertexConsumer extends IVertexConsumer
{
/**
* Assumes the data is already completely unpacked.
* You must always copy the data from the quad provided to an internal cache.
* basically:
* this.quad.put(quad);
*
* @param quad The quad to copy data from.
*/
void put( Quad quad );
}
@@ -0,0 +1,551 @@
/*
* This file is part of CodeChickenLib.
* Copyright (c) 2018, covers1624, All rights reserved.
*
* CodeChickenLib 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 2.1 of the License, or
* (at your option) any later version.
*
* CodeChickenLib 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 CodeChickenLib. If not, see <http://www.gnu.org/licenses/lgpl>.
*/
package appeng.thirdparty.codechicken.lib.model;
import javax.vecmath.Vector3f;
import net.minecraft.client.renderer.block.model.BakedQuad;
import net.minecraft.client.renderer.texture.TextureAtlasSprite;
import net.minecraft.client.renderer.vertex.VertexFormat;
import net.minecraft.util.EnumFacing;
import net.minecraft.util.math.AxisAlignedBB;
import net.minecraft.util.math.MathHelper;
import net.minecraftforge.client.model.pipeline.IVertexConsumer;
import net.minecraftforge.client.model.pipeline.IVertexProducer;
import net.minecraftforge.client.model.pipeline.LightUtil;
import net.minecraftforge.client.model.pipeline.UnpackedBakedQuad;
import appeng.thirdparty.codechicken.lib.math.InterpHelper;
/**
* A simple easy to manipulate quad format. Can be reset and then used on a different format.
*
* @author covers1624
*/
public class Quad implements IVertexProducer, ISmartVertexConsumer
{
public CachedFormat format;
public int tintIndex = -1;
// TODO, sometimes this is null because people don't do models properly.
public EnumFacing orientation;
public boolean diffuseLighting = true;
public TextureAtlasSprite sprite;
public Vertex[] vertices = new Vertex[4];
public boolean full;
// Not copied.
private int vertexIndex = 0;
// Cache for normal computation.
private Vector3f v1 = new Vector3f();
private Vector3f v2 = new Vector3f();
private Vector3f t = new Vector3f();
private Vector3f normal = new Vector3f();
/**
* Use this if you reset the quad each time you use it.
*/
public Quad()
{
}
/**
* use this if you want to initialize the quad with a format.
*
* @param format The format.
*/
public Quad( CachedFormat format )
{
this.format = format;
}
@Override
public VertexFormat getVertexFormat()
{
return this.format.format;
}
@Override
public void setQuadTint( int tint )
{
this.tintIndex = tint;
}
@Override
public void setQuadOrientation( EnumFacing orientation )
{
this.orientation = orientation;
}
@Override
public void setApplyDiffuseLighting( boolean diffuse )
{
this.diffuseLighting = diffuse;
}
@Override
public void setTexture( TextureAtlasSprite texture )
{
this.sprite = texture;
}
@Override
public void put( int element, float... data )
{
if( this.full )
{
throw new RuntimeException( "Unable to add data when full." );
}
Vertex v = this.vertices[this.vertexIndex];
if( v == null )
{
v = new Vertex( this.format );
this.vertices[this.vertexIndex] = v;
}
System.arraycopy( data, 0, v.raw[element], 0, data.length );
if( element == ( this.format.elementCount - 1 ) )
{
this.vertexIndex++;
if( this.vertexIndex == 4 )
{
this.vertexIndex = 0;
this.full = true;
}
}
}
@Override
public void put( Quad quad )
{
this.copyFrom( quad );
}
@Override
public void pipe( IVertexConsumer consumer )
{
if( consumer instanceof ISmartVertexConsumer )
{
( (ISmartVertexConsumer) consumer ).put( this );
}
else
{
consumer.setQuadTint( this.tintIndex );
consumer.setQuadOrientation( this.orientation );
consumer.setApplyDiffuseLighting( this.diffuseLighting );
consumer.setTexture( this.sprite );
for( Vertex v : this.vertices )
{
for( int e = 0; e < this.format.elementCount; e++ )
{
consumer.put( e, v.raw[e] );
}
}
}
}
/**
* Used to reset the interpolation values inside the provided helper.
*
* @param helper The helper.
* @param s The axis. side >> 1;
*
* @return The same helper.
*/
public InterpHelper resetInterp( InterpHelper helper, int s )
{
helper.reset( //
this.vertices[0].dx( s ), this.vertices[0].dy( s ), //
this.vertices[1].dx( s ), this.vertices[1].dy( s ), //
this.vertices[2].dx( s ), this.vertices[2].dy( s ), //
this.vertices[3].dx( s ), this.vertices[3].dy( s ) );
return helper;
}
/**
* Clamps the Quad inside the box.
*
* @param bb The box.
*/
public void clamp( AxisAlignedBB bb )
{
for( Vertex vertex : this.vertices )
{
float[] vec = vertex.vec;
vec[0] = (float) MathHelper.clamp( vec[0], bb.minX, bb.maxX );
vec[1] = (float) MathHelper.clamp( vec[1], bb.minY, bb.maxY );
vec[2] = (float) MathHelper.clamp( vec[2], bb.minZ, bb.maxZ );
}
this.v1.set( this.vertices[3].vec );
this.t.set( this.vertices[1].vec );
this.v1.sub( this.t );
this.v2.set( this.vertices[2].vec );
this.t.set( this.vertices[0].vec );
this.v2.sub( this.t );
this.normal.cross( this.v2, this.v1 );
this.normal.normalize();
if( this.format.hasNormal )
{
for( Vertex vertex : this.vertices )
{
vertex.normal[0] = this.normal.x;
vertex.normal[1] = this.normal.y;
vertex.normal[2] = this.normal.z;
vertex.normal[3] = 0;
}
}
this.orientation = EnumFacing.getFacingFromVector( this.normal.x, this.normal.y, this.normal.z );
}
/**
* Used to create a new quad complete copy of this one.
*
* @return The new quad.
*/
public Quad copy()
{
if( !this.full )
{
throw new RuntimeException( "Only copying full quads is supported." );
}
Quad quad = new Quad( this.format );
quad.tintIndex = this.tintIndex;
quad.orientation = this.orientation;
quad.diffuseLighting = this.diffuseLighting;
quad.sprite = this.sprite;
quad.full = true;
for( int i = 0; i < 4; i++ )
{
quad.vertices[i] = this.vertices[i].copy();
}
return quad;
}
/**
* Copies the data inside the given quad to this one. This ignores VertexFormat, please make sure your quads are in
* the same format.
*
* @param quad The Quad to copy from.
*
* @return This quad.
*/
public Quad copyFrom( Quad quad )
{
this.tintIndex = quad.tintIndex;
this.orientation = quad.orientation;
this.diffuseLighting = quad.diffuseLighting;
this.sprite = quad.sprite;
this.full = quad.full;
for( int v = 0; v < 4; v++ )
{
for( int e = 0; e < this.format.elementCount; e++ )
{
System.arraycopy( quad.vertices[v].raw[e], 0, this.vertices[v].raw[e], 0, 4 );
}
}
return this;
}
/**
* Reset the quad to the new format.
*
* @param format The new format.
*/
public void reset( CachedFormat format )
{
this.format = format;
this.tintIndex = -1;
this.orientation = null;
this.diffuseLighting = true;
this.sprite = null;
for( int i = 0; i < this.vertices.length; i++ )
{
Vertex v = this.vertices[i];
if( v == null )
{
this.vertices[i] = v = new Vertex( format );
}
v.reset( format );
}
this.vertexIndex = 0;
this.full = false;
}
/**
* Bakes this Quad to a BakedQuad.
*
* @return The BakedQuad.
*/
public BakedQuad bake()
{
int[] packedData = new int[this.format.format.getNextOffset()];
for( int v = 0; v < 4; v++ )
{
for( int e = 0; e < this.format.elementCount; e++ )
{
LightUtil.pack( this.vertices[v].raw[e], packedData, this.format.format, v, e );
}
}
return new BakedQuad( packedData, this.tintIndex, this.orientation, this.sprite, this.diffuseLighting, this.format.format );
}
/**
* Bakes this quad to an UnpackedBakedQuad.
*
* @return The UnpackedBakedQuad.
*/
public UnpackedBakedQuad bakeUnpacked()
{
UnpackedBakedQuad.Builder quad = new UnpackedBakedQuad.Builder( this.format.format );
this.pipe( quad );
return quad.build();
}
/**
* A simple vertex format.
*/
public static class Vertex
{
public CachedFormat format;
/**
* The raw data.
*/
public float[][] raw;
// References to the arrays inside raw.
public float[] vec;
public float[] normal;
public float[] color;
public float[] uv;
public float[] lightmap;
/**
* Create a new Vertex.
*
* @param format The format for the vertex.
*/
public Vertex( CachedFormat format )
{
this.format = format;
this.raw = new float[format.elementCount][4];
this.preProcess();
}
/**
* Creates a new Vertex using the data inside the other. A copy!
*
* @param other The other.
*/
public Vertex( Vertex other )
{
this.format = other.format;
this.raw = other.raw.clone();
for( int v = 0; v < this.format.elementCount; v++ )
{
this.raw[v] = other.raw[v].clone();
}
this.preProcess();
}
/**
* Pulls references to the individual element's arrays inside raw. Modifying the individual element arrays will
* update raw.
*/
public void preProcess()
{
if( this.format.hasPosition )
{
this.vec = this.raw[this.format.positionIndex];
}
if( this.format.hasNormal )
{
this.normal = this.raw[this.format.normalIndex];
}
if( this.format.hasColor )
{
this.color = this.raw[this.format.colorIndex];
}
if( this.format.hasUV )
{
this.uv = this.raw[this.format.uvIndex];
}
if( this.format.hasLightMap )
{
this.lightmap = this.raw[this.format.lightMapIndex];
}
}
/**
* Gets the 2d X coord for the given axis.
*
* @param s The axis. side >> 1
*
* @return The x coord.
*/
public float dx( int s )
{
if( s <= 1 )
{
return this.vec[0];
}
else
{
return this.vec[2];
}
}
/**
* Gets the 2d Y coord for the given axis.
*
* @param s The axis. side >> 1
*
* @return The y coord.
*/
public float dy( int s )
{
if( s > 0 )
{
return this.vec[1];
}
else
{
return this.vec[2];
}
}
/**
* Interpolates the new color values for this Vertex using the others as a reference.
*
* @param interpHelper The InterpHelper to use.
* @param others The other Vertices to use as a template.
*
* @return The same Vertex.
*/
public Vertex interpColorFrom( InterpHelper interpHelper, Vertex[] others )
{
for( int e = 0; e < 4; e++ )
{
float p1 = others[0].color[e];
float p2 = others[1].color[e];
float p3 = others[2].color[e];
float p4 = others[3].color[e];
// Only interpolate if colors are different.
if( p1 != p2 || p2 != p3 || p3 != p4 )
{
this.color[e] = interpHelper.interpolate( p1, p2, p3, p4 );
}
}
return this;
}
/**
* Interpolates the new UV values for this Vertex using the others as a reference.
*
* @param interpHelper The InterpHelper to use.
* @param others The other Vertices to use as a template.
*
* @return The same Vertex.
*/
public Vertex interpUVFrom( InterpHelper interpHelper, Vertex[] others )
{
for( int e = 0; e < 2; e++ )
{
float p1 = others[0].uv[e];
float p2 = others[1].uv[e];
float p3 = others[2].uv[e];
float p4 = others[3].uv[e];
if( p1 != p2 || p2 != p3 || p3 != p4 )
{
this.uv[e] = interpHelper.interpolate( p1, p2, p3, p4 );
}
}
return this;
}
/**
* Interpolates the new LightMap values for this Vertex using the others as a reference.
*
* @param interpHelper The InterpHelper to use.
* @param others The other Vertices to use as a template.
*
* @return The same Vertex.
*/
public Vertex interpLightMapFrom( InterpHelper interpHelper, Vertex[] others )
{
for( int e = 0; e < 2; e++ )
{
float p1 = others[0].lightmap[e];
float p2 = others[1].lightmap[e];
float p3 = others[2].lightmap[e];
float p4 = others[3].lightmap[e];
if( p1 != p2 || p2 != p3 || p3 != p4 )
{
this.lightmap[e] = interpHelper.interpolate( p1, p2, p3, p4 );
}
}
return this;
}
/**
* Copies this Vertex to a new one.
*
* @return The new Vertex.
*/
public Vertex copy()
{
return new Vertex( this );
}
/**
* Resets the Vertex to a new format. Expands the raw array if needed.
*
* @param format The format to reset to.
*/
public void reset( CachedFormat format )
{
// If the format is different and our raw array is smaller, then expand it.
if( !this.format.equals( format ) && format.elementCount > this.raw.length )
{
this.raw = new float[format.elementCount][4];
}
this.format = format;
this.vec = null;
this.normal = null;
this.color = null;
this.uv = null;
this.lightmap = null;
// for (float[] f : raw) {
// Arrays.fill(f, 0F);
// }
this.preProcess();
}
}
}
@@ -0,0 +1,436 @@
/*
* This file is part of CodeChickenLib.
* Copyright (c) 2018, covers1624, All rights reserved.
*
* CodeChickenLib 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 2.1 of the License, or
* (at your option) any later version.
*
* CodeChickenLib 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 CodeChickenLib. If not, see <http://www.gnu.org/licenses/lgpl>.
*/
package appeng.thirdparty.codechicken.lib.model.pipeline;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.Map;
import java.util.function.Consumer;
import java.util.stream.Collectors;
import net.minecraft.client.renderer.texture.TextureAtlasSprite;
import net.minecraft.client.renderer.vertex.VertexFormat;
import net.minecraft.util.EnumFacing;
import net.minecraftforge.client.model.pipeline.IVertexConsumer;
import net.minecraftforge.client.model.pipeline.UnpackedBakedQuad;
import appeng.thirdparty.codechicken.lib.model.CachedFormat;
import appeng.thirdparty.codechicken.lib.model.ISmartVertexConsumer;
import appeng.thirdparty.codechicken.lib.model.Quad;
/**
* The BakedPipeline!
* Basically this allows us to efficiently transform a BakedQuad,
* the Pipeline has Elements, each element has a name, state and a transformer,
* you can enable and disable elements easily, you can also grab the underlying
* transformer for the element if you need to set its state before rendering.
*
* The BakedPipeline is final once created, you cannot add or remove elements,
* you should not need to add or remove them runtime, enable and disable exist.
*
* You must use the Builder class to construct a BakedPipeline, see {@link #builder}
*
* Transformers run on a mutable state inside each transformer, allowing for easy reuse.
* It is recommended to store your pipeline inside a ThreadLocal because 'minecraft'.
*
* Each Transformer should be smart enough to expand itself for each newly sized VertexFormat it comes across,
* meaning that the internal states for the transformers can be safely shared across VertexFormats, this reduces
* array creations, and generally makes the system as efficient as it is.
*
* To use the system:
* Grab any elements you need to set state data on first, using {@link #getElement(String, Class)}
* transformers should NOT clear their state on pipeline Reset's so set any global data on elements now.
* Assuming you are looping over a set of quads to transform, next you need to {@link #reset} the pipeline,
* Now you should disable / enable any optional elements that are needed, NOTE: Element states are reset when resetting
* the pipeline.
* Now you will need to call {@link #prepare(IVertexConsumer)} on the pipeline, here you will pass your collector,
* usually this is some form of (Unpacked)BakedQuadBuilder, See {@link QuadBuilder} for a simple and fast implementation
* for standard BakedQuads, and {@link UnpackedBakedQuad.Builder} for UnpackedBakedQuads.
* Now final step, simply pipe the quad you want to transform INTO the pipeline 'quad.pipe(pipeline)'
* And that's it! hell, Pipe a pipeline into each other for all i care, the system is efficient enough that there
* would be no performance penalty for doing so.
*
* @author covers1624
*/
public class BakedPipeline implements ISmartVertexConsumer
{
private PipelineElement[] elements;
private Map<String, PipelineElement> nameLookup;
private IPipelineConsumer first;
private Quad unpacker = new Quad();
private BakedPipeline( PipelineElement[] elements )
{
this.elements = elements;
this.nameLookup = Arrays.stream( elements ).collect( Collectors.toMap( e -> e.name, e -> e ) );
}
/**
* Used to create a BakedPipeline.
*
* @return The builder.
*/
public static Builder builder()
{
return new Builder();
}
/**
* Used to reset the pipeline for the next quad.
* MUST be called between quads.
*
* @param format The format.
*/
public void reset( VertexFormat format )
{
this.reset( CachedFormat.lookup( format ) );
}
/**
* Used to reset the pipeline for the next quad.
* MUST be called between quads.
*
* @param format The format.
*/
public void reset( CachedFormat format )
{
this.unpacker.reset( format );
for( PipelineElement element : this.elements )
{
element.reset( format );
}
this.first = null;
}
/**
* Get an element from the pipeline.
*
* @param name The name of the element.
* @param clazz The Class of the element, used to safe cast.
*
* @return The element.
*/
public <T extends IPipelineConsumer> T getElement( String name, Class<T> clazz )
{
PipelineElement element = this.nameLookup.get( name );
if( element != null )
{
if( !clazz.isAssignableFrom( element.consumer.getClass() ) )
{
throw new IllegalArgumentException( "Element with name " + name + " is not assignable from reference class." );
}
return clazz.cast( element.consumer );
}
throw new IllegalArgumentException( "Element with name " + name + " does not exist." );
}
/**
* Used to enable an element on the pipeline with the specified name.
*
* @param name The elements name.
*/
public void enableElement( String name )
{
this.setElementState( name, true );
}
/**
* Used to disable an element on the pipeline with the specified name.
*
* @param name The elements name.
*/
public void disableElement( String name )
{
this.setElementState( name, false );
}
/**
* Used to set the state of an element on the pipeline.
*
* @param name The name of the element.
* @param enabled The state to set it to.
*/
public void setElementState( String name, boolean enabled )
{
PipelineElement element = this.nameLookup.get( name );
if( element != null )
{
element.isEnabled = enabled;
return;
}
throw new IllegalArgumentException( "Element with name " + name + " does not exist." );
}
/**
* Call when you are ready to use the pipeline.
* This builds the internal state of the Elements getting things ready to transform.
*
* @param collector The IVertexConsumer that should collect the transformed quad.
*/
public void prepare( IVertexConsumer collector )
{
IPipelineConsumer next = null;
for( PipelineElement element : this.elements )
{
if( element.isEnabled )
{
if( this.first == null )
{
this.first = element.consumer;
}
else
{
next.setParent( element.consumer );
}
next = element.consumer;
}
}
next.setParent( collector );
}
@Override
public VertexFormat getVertexFormat()
{
this.check();
return this.first.getVertexFormat();
}
@Override
public void setQuadTint( int tint )
{
this.check();
this.unpacker.setQuadTint( tint );
}
@Override
public void setQuadOrientation( EnumFacing orientation )
{
this.check();
this.unpacker.setQuadOrientation( orientation );
}
@Override
public void setApplyDiffuseLighting( boolean diffuse )
{
this.check();
this.unpacker.setApplyDiffuseLighting( diffuse );
}
@Override
public void setTexture( TextureAtlasSprite texture )
{
this.check();
this.unpacker.setTexture( texture );
}
@Override
public void put( int element, float... data )
{
this.check();
this.unpacker.put( element, data );
if( this.unpacker.full )
{
this.onFull();
}
}
@Override
public void put( Quad quad )
{
this.check();
this.unpacker.put( quad );
}
private void check()
{
if( this.first == null )
{
throw new IllegalStateException( "Pipeline used before prepare was called." );
}
}
private void onFull()
{
this.first.setInputQuad( this.unpacker );
this.first.put( this.unpacker );
}
/**
* Internal class, used to hold a PipelineElement's state.
*/
public static class PipelineElement<T extends IPipelineConsumer>
{
public String name;
public boolean defaultState;
public T consumer;
public boolean isEnabled;
public void reset( CachedFormat format )
{
this.isEnabled = this.defaultState;
this.consumer.setParent( null );
this.consumer.reset( format );
}
}
/**
* The builder associated with the BakedPipeline.
* You must create a BakedPipeline with this,
* once created a pipeline cannot be modified,
* modifying should not be needed as you can enable
* and disable elements with ease.
*/
public static class Builder
{
private LinkedList<PipelineElement> elements = new LinkedList<>();
/**
* Inserts an element to the front of the list, Useful if you have a more complex system
* and each system need to be independent from each other, but this element must be first.
*
* @param name The name to identify this element, used as an identifier when setting state, and retrieving the
* element.
* @param factory The factory used to create the Transformer.
*
* @return The same builder.
*/
public Builder addFirst( String name, IPipelineElementFactory<?> factory )
{
return this.addFirst( name, factory, true );
}
/**
* Inserts an element to the front of the list, Useful if you have a more complex system
* and each system need to be independent from each other, but this element must be first.
*
* @param name The name to identify this element, used as an identifier when setting state, and retrieving the
* element.
* @param factory The factory used to create the Transformer.
* @param defaultState The default state for this element.
*
* @return The same builder.
*/
public Builder addFirst( String name, IPipelineElementFactory<?> factory, boolean defaultState )
{
return this.addFirst( name, factory, defaultState, e ->
{
} );
}
/**
* Inserts an element to the front of the list, Useful if you have a more complex system
* and each system need to be independent from each other, but this element must be first.
*
* @param name The name to identify this element, used as an identifier when setting state, and retrieving the
* element.
* @param factory The factory used to create the Transformer.
* @param defaultState The default state for this element.
* @param defaultsSetter A callback used to set any defaults on the transformer.
*
* @return The same builder.
*/
public <T extends IPipelineConsumer> Builder addFirst( String name, IPipelineElementFactory<T> factory, boolean defaultState, Consumer<T> defaultsSetter )
{
PipelineElement<T> element = this.makeElement( name, factory, defaultState );
defaultsSetter.accept( element.consumer );
this.elements.addFirst( element );
return this;
}
/**
* Adds an element at the end of the transform list, Suitable for 99% of cases.
*
* @param name The name to identify this element, used as an identifier when setting state, and retrieving the
* element.
* @param factory The factory used to create the Transformer.
*
* @return The same builder.
*/
public Builder addElement( String name, IPipelineElementFactory<?> factory )
{
return this.addElement( name, factory, true );
}
/**
* Adds an element at the end of the transform list, Suitable for 99% of cases.
*
* @param name The name to identify this element, used as an identifier when setting state, and retrieving the
* element.
* @param factory The factory used to create the Transformer.
* @param defaultState The default state for this element.
*
* @return The same builder.
*/
public Builder addElement( String name, IPipelineElementFactory<?> factory, boolean defaultState )
{
return this.addElement( name, factory, defaultState, e ->
{
} );
}
/**
* Adds an element at the end of the transform list, Suitable for 99% of cases.
*
* @param name The name to identify this element, used as an identifier when setting state, and retrieving the
* element.
* @param factory The factory used to create the Transformer.
* @param defaultState The default state for this element.
* @param defaultsSetter A callback used to set any defaults on the transformer.
*
* @return The same builder.
*/
public <T extends IPipelineConsumer> Builder addElement( String name, IPipelineElementFactory<T> factory, boolean defaultState, Consumer<T> defaultsSetter )
{
PipelineElement<T> element = this.makeElement( name, factory, defaultState );
defaultsSetter.accept( element.consumer );
this.elements.add( element );
return this;
}
// Internal method, used to construct the PipelineElement class.
private <T extends IPipelineConsumer> PipelineElement<T> makeElement( String name, IPipelineElementFactory<T> factory, boolean defaultState )
{
if( this.elements.stream().anyMatch( p -> p.name.equals( name ) ) )
{
throw new IllegalArgumentException( "Unable to add element with duplicate name: " + name );
}
PipelineElement<T> element = new PipelineElement<>();
element.name = name;
element.consumer = factory.create();
element.defaultState = defaultState;
return element;
}
/**
* Call this once you are finished to build your BakedPipeline!
*
* @return The new Pipeline.
*/
public BakedPipeline build()
{
return new BakedPipeline( this.elements.toArray( new PipelineElement[0] ) );
}
}
}
@@ -0,0 +1,64 @@
/*
* This file is part of CodeChickenLib.
* Copyright (c) 2018, covers1624, All rights reserved.
*
* CodeChickenLib 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 2.1 of the License, or
* (at your option) any later version.
*
* CodeChickenLib 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 CodeChickenLib. If not, see <http://www.gnu.org/licenses/lgpl>.
*/
package appeng.thirdparty.codechicken.lib.model.pipeline;
import net.minecraftforge.client.model.pipeline.IVertexConsumer;
import appeng.thirdparty.codechicken.lib.model.CachedFormat;
import appeng.thirdparty.codechicken.lib.model.ISmartVertexConsumer;
import appeng.thirdparty.codechicken.lib.model.Quad;
import appeng.thirdparty.codechicken.lib.model.pipeline.transformers.QuadReInterpolator;
/**
* Anything implementing this may be used in the BakedPipeline.
*
* @author covers1624
*/
public interface IPipelineConsumer extends ISmartVertexConsumer
{
/**
* The quad at the start of the transformation.
* This is useful for obtaining the vertex data before any transformations have been applied,
* such as interpolation, See {@link QuadReInterpolator}.
* When overriding this make sure you call setInputQuad on your parent consumer too.
*
* @param quad The quad.
*/
void setInputQuad( Quad quad );
/**
* Resets the Consumer to the new format.
* This should resize any internal arrays if needed, ready for the new vertex data.
*
* @param format The format to reset to.
*/
void reset( CachedFormat format );
/**
* Sets the parent consumer.
* This consumer may choose to not pipe any data,
* that's fine, but if it does, it MUST pipe the data to the one provided here.
*
* @param parent The parent.
*/
void setParent( IVertexConsumer parent );
}
@@ -0,0 +1,30 @@
/*
* This file is part of CodeChickenLib.
* Copyright (c) 2018, covers1624, All rights reserved.
*
* CodeChickenLib 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 2.1 of the License, or
* (at your option) any later version.
*
* CodeChickenLib 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 CodeChickenLib. If not, see <http://www.gnu.org/licenses/lgpl>.
*/
package appeng.thirdparty.codechicken.lib.model.pipeline;
/**
* @author covers1624
*/
@FunctionalInterface
public interface IPipelineElementFactory<T extends IPipelineConsumer>
{
T create();
}
@@ -0,0 +1,175 @@
/*
* This file is part of CodeChickenLib.
* Copyright (c) 2018, covers1624, All rights reserved.
*
* CodeChickenLib 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 2.1 of the License, or
* (at your option) any later version.
*
* CodeChickenLib 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 CodeChickenLib. If not, see <http://www.gnu.org/licenses/lgpl>.
*/
package appeng.thirdparty.codechicken.lib.model.pipeline;
import javax.annotation.OverridingMethodsMustInvokeSuper;
import net.minecraft.client.renderer.texture.TextureAtlasSprite;
import net.minecraft.client.renderer.vertex.VertexFormat;
import net.minecraft.util.EnumFacing;
import net.minecraftforge.client.model.pipeline.IVertexConsumer;
import appeng.thirdparty.codechicken.lib.model.CachedFormat;
import appeng.thirdparty.codechicken.lib.model.ISmartVertexConsumer;
import appeng.thirdparty.codechicken.lib.model.Quad;
/**
* Base class for a simple QuadTransformer.
* Operates on BakedQuads.
* QuadTransformers can be piped into each other at no performance penalty.
*
* @author covers1624
*/
public abstract class QuadTransformer implements IVertexConsumer, ISmartVertexConsumer, IPipelineConsumer
{
protected CachedFormat format;
protected IVertexConsumer consumer;
protected Quad quad;
/**
* Used for the BakedPipeline.
*/
protected QuadTransformer()
{
this.quad = new Quad();
}
public QuadTransformer( IVertexConsumer consumer )
{
this( consumer.getVertexFormat(), consumer );
}
public QuadTransformer( VertexFormat format, IVertexConsumer consumer )
{
this( CachedFormat.lookup( format ), consumer );
}
public QuadTransformer( CachedFormat format, IVertexConsumer consumer )
{
this.format = format;
this.consumer = consumer;
this.quad = new Quad( format );
}
@Override
@OverridingMethodsMustInvokeSuper
public void reset( CachedFormat format )
{
this.format = format;
this.quad.reset( format );
}
@Override
public void setParent( IVertexConsumer parent )
{
this.consumer = parent;
}
@Override
@OverridingMethodsMustInvokeSuper
public void setInputQuad( Quad quad )
{
if( this.consumer instanceof IPipelineConsumer )
{
( (IPipelineConsumer) this.consumer ).setInputQuad( quad );
}
}
// @formatter:off
@Override
public VertexFormat getVertexFormat()
{
return this.format.format;
}
@Override
public void setQuadTint( int tint )
{
this.quad.setQuadTint( tint );
}
@Override
public void setQuadOrientation( EnumFacing orientation )
{
this.quad.setQuadOrientation( orientation );
}
@Override
public void setApplyDiffuseLighting( boolean diffuse )
{
this.quad.setApplyDiffuseLighting( diffuse );
}
@Override
public void setTexture( TextureAtlasSprite texture )
{
this.quad.setTexture( texture );
}
// @formatter:on
@Override
public void put( int element, float... data )
{
this.quad.put( element, data );
if( this.quad.full )
{
this.onFull();
}
}
@Override
public void put( Quad quad )
{
this.quad.put( quad );
this.onFull();
}
/**
* Called to transform the vertices.
*
* @return If the transformer should pipe the quad.
*/
public abstract boolean transform();
public void onFull()
{
if( this.transform() )
{
this.quad.pipe( this.consumer );
}
}
// Should be small enough.
private final static double EPSILON = 0.00001;
public static boolean epsComp( float a, float b )
{
if( a == b )
{
return true;
}
else
{
return Math.abs( a - b ) < EPSILON;
}
}
}
@@ -0,0 +1,71 @@
/*
* This file is part of CodeChickenLib.
* Copyright (c) 2018, covers1624, All rights reserved.
*
* CodeChickenLib 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 2.1 of the License, or
* (at your option) any later version.
*
* CodeChickenLib 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 CodeChickenLib. If not, see <http://www.gnu.org/licenses/lgpl>.
*/
package appeng.thirdparty.codechicken.lib.model.pipeline.transformers;
import net.minecraftforge.client.model.pipeline.IVertexConsumer;
import appeng.thirdparty.codechicken.lib.model.Quad.Vertex;
import appeng.thirdparty.codechicken.lib.model.pipeline.IPipelineElementFactory;
import appeng.thirdparty.codechicken.lib.model.pipeline.QuadTransformer;
/**
* This transformer simply overrides the alpha of the quad.
* Only operates if the format has color.
*
* @author covers1624
*/
public class QuadAlphaOverride extends QuadTransformer
{
public static final IPipelineElementFactory<QuadAlphaOverride> FACTORY = QuadAlphaOverride::new;
private float alphaOverride;
QuadAlphaOverride()
{
super();
}
public QuadAlphaOverride( IVertexConsumer consumer, float alphaOverride )
{
super( consumer );
this.alphaOverride = alphaOverride;
}
public QuadAlphaOverride setAlphaOverride( float alphaOverride )
{
this.alphaOverride = alphaOverride;
return this;
}
@Override
public boolean transform()
{
if( this.format.hasColor )
{
for( Vertex v : this.quad.vertices )
{
v.color[3] = this.alphaOverride;
}
}
return true;
}
}
@@ -0,0 +1,83 @@
/*
* This file is part of CodeChickenLib.
* Copyright (c) 2018, covers1624, All rights reserved.
*
* CodeChickenLib 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 2.1 of the License, or
* (at your option) any later version.
*
* CodeChickenLib 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 CodeChickenLib. If not, see <http://www.gnu.org/licenses/lgpl>.
*/
package appeng.thirdparty.codechicken.lib.model.pipeline.transformers;
import net.minecraft.util.math.AxisAlignedBB;
import net.minecraftforge.client.model.pipeline.IVertexConsumer;
import appeng.thirdparty.codechicken.lib.model.Quad.Vertex;
import appeng.thirdparty.codechicken.lib.model.pipeline.IPipelineElementFactory;
import appeng.thirdparty.codechicken.lib.model.pipeline.QuadTransformer;
/**
* This transformer simply clamps the vertices inside the provided box.
* You probably want to Re-Interpolate the UV's, Color, and Lmap, see {@link QuadReInterpolator}
*
* @author covers1624
*/
public class QuadClamper extends QuadTransformer
{
public static IPipelineElementFactory<QuadClamper> FACTORY = QuadClamper::new;
private AxisAlignedBB clampBounds;
QuadClamper()
{
super();
}
public QuadClamper( IVertexConsumer parent, AxisAlignedBB bounds )
{
super( parent );
this.clampBounds = bounds;
}
public void setClampBounds( AxisAlignedBB bounds )
{
this.clampBounds = bounds;
}
@Override
public boolean transform()
{
int s = this.quad.orientation.ordinal() >> 1;
this.quad.clamp( this.clampBounds );
// Check if the quad would be invisible and cull it.
Vertex[] vertices = this.quad.vertices;
float x1 = vertices[0].dx( s );
float x2 = vertices[1].dx( s );
float x3 = vertices[2].dx( s );
float x4 = vertices[3].dx( s );
float y1 = vertices[0].dy( s );
float y2 = vertices[1].dy( s );
float y3 = vertices[2].dy( s );
float y4 = vertices[3].dy( s );
// These comparisons are safe as we are comparing clamped values.
boolean flag1 = x1 == x2 && x2 == x3 && x3 == x4;
boolean flag2 = y1 == y2 && y2 == y3 && y3 == y4;
return !flag1 && !flag2;
}
}
@@ -0,0 +1,219 @@
/*
* This file is part of CodeChickenLib.
* Copyright (c) 2018, covers1624, All rights reserved.
*
* CodeChickenLib 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 2.1 of the License, or
* (at your option) any later version.
*
* CodeChickenLib 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 CodeChickenLib. If not, see <http://www.gnu.org/licenses/lgpl>.
*/
package appeng.thirdparty.codechicken.lib.model.pipeline.transformers;
import static net.minecraft.util.EnumFacing.AxisDirection.NEGATIVE;
import static net.minecraft.util.EnumFacing.AxisDirection.POSITIVE;
import net.minecraft.util.EnumFacing;
import net.minecraft.util.EnumFacing.AxisDirection;
import net.minecraft.util.math.AxisAlignedBB;
import net.minecraft.util.math.Vec3i;
import appeng.thirdparty.codechicken.lib.model.Quad.Vertex;
import appeng.thirdparty.codechicken.lib.model.pipeline.IPipelineElementFactory;
import appeng.thirdparty.codechicken.lib.model.pipeline.QuadTransformer;
/**
* This transformer is a little complicated.
* Basically a Facade / Cover can use this to 'kick' the edges
* in of quads to fix z-Fighting in the corners.
* Use it by specifying the side of the block you are on,
* the bitmask for where the other Facades / Cover's are,
* the bounding box of the facade, NOT the hole piece,
* and the thickness of your Facade / Cover, this is used
* as the kick amount.
*
* @author covers1624
*/
public class QuadCornerKicker extends QuadTransformer
{
// The factory for pipeline creation.
public static final IPipelineElementFactory<QuadCornerKicker> FACTORY = QuadCornerKicker::new;
// Simple horizonal lookups.
public static int[][] horizonals = new int[][] {
// Around Y axis, NSWE.
{ 2, 3, 4, 5 }, //
{ 2, 3, 4, 5 }, //
// Around Z axis, DUWE.
{ 0, 1, 4, 5 }, //
{ 0, 1, 4, 5 }, //
// Around X axis, DUNS.
{ 0, 1, 2, 3 }, //
{ 0, 1, 2, 3 }
};
private int mySide;
private int facadeMask;
private AxisAlignedBB box;
private double thickness;
QuadCornerKicker()
{
super();
}
/**
* Set's the side this Facade / Cover is attached to.
*
* @param side The side.
*/
public void setSide( int side )
{
this.mySide = side;
}
/**
* Sets the bitmask of Facades / Covers in the blockspace.
* This is as simple as, mask = (1 << side)
*
* @param mask The mask.
*/
public void setFacadeMask( int mask )
{
this.facadeMask = mask;
}
/**
* Sets the bounding box of the Facade / Cover,
* this should be the full box, not just a piece
* of the hole's 'ring'.
*
* @param box The BoundingBox.
*/
public void setBox( AxisAlignedBB box )
{
this.box = box;
}
/**
* Sets the amount to kick the vertex in by,
* this is your facades thickness.
*
* @param thickness The thickness.
*/
public void setThickness( double thickness )
{
this.thickness = thickness;
}
@Override
public boolean transform()
{
int side = this.quad.orientation.ordinal();
if( side != this.mySide && side != ( this.mySide ^ 1 ) )
{
for( int hoz : horizonals[this.mySide] )
{
if( side != hoz && side != ( hoz ^ 1 ) )
{
if( ( this.facadeMask & ( 1 << hoz ) ) != 0 )
{
Corner corner = Corner.fromSides( this.mySide ^ 1, side, hoz );
for( Vertex vertex : this.quad.vertices )
{
float x = vertex.vec[0];
float y = vertex.vec[1];
float z = vertex.vec[2];
if( epsComp( x, corner.pX( this.box ) ) && epsComp( y, corner.pY( this.box ) ) && epsComp( z, corner.pZ( this.box ) ) )
{
Vec3i vec = EnumFacing.VALUES[hoz].getDirectionVec();
x -= vec.getX() * this.thickness;
y -= vec.getY() * this.thickness;
z -= vec.getZ() * this.thickness;
vertex.vec[0] = x;
vertex.vec[1] = y;
vertex.vec[2] = z;
}
}
}
}
}
}
return true;
}
public enum Corner
{
MIN_X_MIN_Y_MIN_Z( NEGATIVE, NEGATIVE, NEGATIVE ),
MIN_X_MIN_Y_MAX_Z( NEGATIVE, NEGATIVE, POSITIVE ),
MIN_X_MAX_Y_MIN_Z( NEGATIVE, POSITIVE, NEGATIVE ),
MIN_X_MAX_Y_MAX_Z( NEGATIVE, POSITIVE, POSITIVE ),
MAX_X_MIN_Y_MIN_Z( POSITIVE, NEGATIVE, NEGATIVE ),
MAX_X_MIN_Y_MAX_Z( POSITIVE, NEGATIVE, POSITIVE ),
MAX_X_MAX_Y_MIN_Z( POSITIVE, POSITIVE, NEGATIVE ),
MAX_X_MAX_Y_MAX_Z( POSITIVE, POSITIVE, POSITIVE );
private AxisDirection xAxis;
private AxisDirection yAxis;
private AxisDirection zAxis;
private static final int[] sideMask = { 0, 2, 0, 1, 0, 4 };
Corner( AxisDirection xAxis, AxisDirection yAxis, AxisDirection zAxis )
{
this.xAxis = xAxis;
this.yAxis = yAxis;
this.zAxis = zAxis;
}
/**
* Used to find what corner is at the 3 sides.
* This method assumes you pass in the X axis side, Y axis side, and Z axis side,
* it will NOT complain about an invalid side, you will just get garbage data.
* This method also does not care what order the 3 axes are in.
*
* @param sideA Side one.
* @param sideB Side two.
* @param sideC Side three.
*
* @return The corner at the 3 sides.
*/
public static Corner fromSides( int sideA, int sideB, int sideC )
{
// <3 Chicken-Bones.
return values()[sideMask[sideA] | sideMask[sideB] | sideMask[sideC]];
}
public float pX( AxisAlignedBB box )
{
return (float) ( this.xAxis == NEGATIVE ? box.minX : box.maxX );
}
public float pY( AxisAlignedBB box )
{
return (float) ( this.yAxis == NEGATIVE ? box.minY : box.maxY );
}
public float pZ( AxisAlignedBB box )
{
return (float) ( this.zAxis == NEGATIVE ? box.minZ : box.maxZ );
}
}
}
@@ -0,0 +1,128 @@
/*
* This file is part of CodeChickenLib.
* Copyright (c) 2018, covers1624, All rights reserved.
*
* CodeChickenLib 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 2.1 of the License, or
* (at your option) any later version.
*
* CodeChickenLib 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 CodeChickenLib. If not, see <http://www.gnu.org/licenses/lgpl>.
*/
package appeng.thirdparty.codechicken.lib.model.pipeline.transformers;
import static net.minecraft.util.EnumFacing.AxisDirection.POSITIVE;
import net.minecraft.util.EnumFacing.AxisDirection;
import net.minecraft.util.math.AxisAlignedBB;
import net.minecraftforge.client.model.pipeline.IVertexConsumer;
import appeng.thirdparty.codechicken.lib.model.Quad.Vertex;
import appeng.thirdparty.codechicken.lib.model.pipeline.IPipelineElementFactory;
import appeng.thirdparty.codechicken.lib.model.pipeline.QuadTransformer;
/**
* This transformer strips quads that are on faces.
* Simply set the bounds for the faces, and the strip mask.
*
* @author covers1624
*/
public class QuadFaceStripper extends QuadTransformer
{
public static final IPipelineElementFactory<QuadFaceStripper> FACTORY = QuadFaceStripper::new;
private AxisAlignedBB bounds;
private int mask;
QuadFaceStripper()
{
super();
}
public QuadFaceStripper( IVertexConsumer parent, AxisAlignedBB bounds, int mask )
{
super( parent );
this.bounds = bounds;
this.mask = mask;
}
/**
* The bounds of the faces,
* used as the .. bounds, if all vertices of a quad
* lay on the bounds, it is up for stripping.
*
* @param bounds The bounds.
*/
public void setBounds( AxisAlignedBB bounds )
{
this.bounds = bounds;
}
/**
* The mask to strip edges.
* This is an opt in system,
* the mask is simple 'mask = (1 << side)'.
*
* @param mask The mask.
*/
public void setMask( int mask )
{
this.mask = mask;
}
@Override
public boolean transform()
{
if( this.mask == 0 )
{
return true;// No mask, nothing changes.
}
// If the bit for this quad is set, then check if we should strip.
if( ( this.mask & ( 1 << this.quad.orientation.ordinal() ) ) != 0 )
{
AxisDirection dir = this.quad.orientation.getAxisDirection();
Vertex[] vertices = this.quad.vertices;
switch( this.quad.orientation.getAxis() )
{
case X:
{
float bound = (float) ( dir == POSITIVE ? this.bounds.maxX : this.bounds.minX );
float x1 = vertices[0].vec[0];
float x2 = vertices[1].vec[0];
float x3 = vertices[2].vec[0];
float x4 = vertices[3].vec[0];
return x1 != x2 || x2 != x3 || x3 != x4 || x4 != bound;
}
case Y:
{
float bound = (float) ( dir == POSITIVE ? this.bounds.maxY : this.bounds.minY );
float y1 = vertices[0].vec[1];
float y2 = vertices[1].vec[1];
float y3 = vertices[2].vec[1];
float y4 = vertices[3].vec[1];
return y1 != y2 || y2 != y3 || y3 != y4 || y4 != bound;
}
case Z:
{
float bound = (float) ( dir == POSITIVE ? this.bounds.maxZ : this.bounds.minZ );
float z1 = vertices[0].vec[2];
float z2 = vertices[1].vec[2];
float z3 = vertices[2].vec[2];
float z4 = vertices[3].vec[2];
return z1 != z2 || z2 != z3 || z3 != z4 || z4 != bound;
}
}
}
return true;
}
}
@@ -0,0 +1,92 @@
/*
* This file is part of CodeChickenLib.
* Copyright (c) 2018, covers1624, All rights reserved.
*
* CodeChickenLib 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 2.1 of the License, or
* (at your option) any later version.
*
* CodeChickenLib 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 CodeChickenLib. If not, see <http://www.gnu.org/licenses/lgpl>.
*/
package appeng.thirdparty.codechicken.lib.model.pipeline.transformers;
import appeng.thirdparty.codechicken.lib.math.InterpHelper;
import appeng.thirdparty.codechicken.lib.model.CachedFormat;
import appeng.thirdparty.codechicken.lib.model.Quad;
import appeng.thirdparty.codechicken.lib.model.Quad.Vertex;
import appeng.thirdparty.codechicken.lib.model.pipeline.IPipelineElementFactory;
import appeng.thirdparty.codechicken.lib.model.pipeline.QuadTransformer;
/**
* This transformer Re-Interpolates the Color, UV's and LightMaps.
* Use this after all transformations that translate vertices in the pipeline.
*
* This Transformation can only be used in the BakedPipeline.
*
* @author covers1624
*/
public class QuadReInterpolator extends QuadTransformer
{
public static final IPipelineElementFactory<QuadReInterpolator> FACTORY = QuadReInterpolator::new;
private Quad interpCache = new Quad();
private InterpHelper interpHelper = new InterpHelper();
QuadReInterpolator()
{
super();
}
@Override
public void reset( CachedFormat format )
{
super.reset( format );
this.interpCache.reset( format );
}
@Override
public void setInputQuad( Quad quad )
{
super.setInputQuad( quad );
quad.resetInterp( this.interpHelper, quad.orientation.ordinal() >> 1 );
}
@Override
public boolean transform()
{
int s = this.quad.orientation.ordinal() >> 1;
if( this.format.hasColor || this.format.hasUV || this.format.hasLightMap )
{
this.interpCache.copyFrom( this.quad );
this.interpHelper.setup();
for( Vertex v : this.quad.vertices )
{
this.interpHelper.locate( v.dx( s ), v.dy( s ) );
if( this.format.hasColor )
{
v.interpColorFrom( this.interpHelper, this.interpCache.vertices );
}
if( this.format.hasUV )
{
v.interpUVFrom( this.interpHelper, this.interpCache.vertices );
}
if( this.format.hasLightMap )
{
v.interpLightMapFrom( this.interpHelper, this.interpCache.vertices );
}
}
}
return true;
}
}
@@ -0,0 +1,78 @@
/*
* This file is part of CodeChickenLib.
* Copyright (c) 2018, covers1624, All rights reserved.
*
* CodeChickenLib 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 2.1 of the License, or
* (at your option) any later version.
*
* CodeChickenLib 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 CodeChickenLib. If not, see <http://www.gnu.org/licenses/lgpl>.
*/
package appeng.thirdparty.codechicken.lib.model.pipeline.transformers;
import net.minecraftforge.client.model.pipeline.IVertexConsumer;
import appeng.thirdparty.codechicken.lib.model.Quad.Vertex;
import appeng.thirdparty.codechicken.lib.model.pipeline.IPipelineElementFactory;
import appeng.thirdparty.codechicken.lib.model.pipeline.QuadTransformer;
/**
* This transformer tints quads..
* Feed it the output of BlockColors.colorMultiplier.
*
* @author covers1624
*/
public class QuadTinter extends QuadTransformer
{
public static final IPipelineElementFactory<QuadTinter> FACTORY = QuadTinter::new;
private int tint;
QuadTinter()
{
super();
}
public QuadTinter( IVertexConsumer consumer, int tint )
{
super( consumer );
this.tint = tint;
}
public QuadTinter setTint( int tint )
{
this.tint = tint;
return this;
}
@Override
public boolean transform()
{
// Nuke tintIndex.
this.quad.tintIndex = -1;
if( this.format.hasColor )
{
float r = ( this.tint >> 0x10 & 0xFF ) / 255F;
float g = ( this.tint >> 0x08 & 0xFF ) / 255F;
float b = ( this.tint & 0xFF ) / 255F;
for( Vertex v : this.quad.vertices )
{
v.color[0] *= r;
v.color[1] *= g;
v.color[2] *= b;
}
}
return true;
}
}