From e82641760b6f0d1c112abd2e9fe9f9b056267058 Mon Sep 17 00:00:00 2001 From: Sebastian Hartte Date: Mon, 10 Oct 2016 08:53:34 +0200 Subject: [PATCH] Implemented Biometric Card Hash display. --- .../client/render/cablebus/CubeBuilder.java | 6 +- .../render/model/BiometricCardBakedModel.java | 220 ++++++++++++++++++ .../render/model/BiometricCardModel.java | 75 ++++++ .../appeng/core/api/definitions/ApiItems.java | 5 +- .../appeng/items/tools/ToolBiometricCard.java | 6 - .../tools/ToolBiometricCardRendering.java | 24 ++ .../textures/items/biometric_card.png | Bin 256 -> 2864 bytes .../textures/items/biometric_card_hash.png | Bin 0 -> 2820 bytes 8 files changed, 328 insertions(+), 8 deletions(-) create mode 100644 src/main/java/appeng/client/render/model/BiometricCardBakedModel.java create mode 100644 src/main/java/appeng/client/render/model/BiometricCardModel.java create mode 100644 src/main/java/appeng/items/tools/ToolBiometricCardRendering.java create mode 100644 src/main/resources/assets/appliedenergistics2/textures/items/biometric_card_hash.png diff --git a/src/main/java/appeng/client/render/cablebus/CubeBuilder.java b/src/main/java/appeng/client/render/cablebus/CubeBuilder.java index 70b633ec..81dca1cc 100644 --- a/src/main/java/appeng/client/render/cablebus/CubeBuilder.java +++ b/src/main/java/appeng/client/render/cablebus/CubeBuilder.java @@ -23,7 +23,6 @@ import java.util.ArrayList; import java.util.EnumMap; import java.util.EnumSet; import java.util.List; - import javax.vecmath.Vector4f; import com.google.common.base.Preconditions; @@ -434,6 +433,11 @@ public class CubeBuilder setColor( color | 0xFF000000 ); } + public void setColorRGB( float r, float g, float b ) + { + setColorRGB( (int) ( r * 255 ) << 16 | (int) ( g * 255 ) << 8 | (int) ( b * 255 ) ); + } + public void setRenderFullBright( boolean renderFullBright ) { this.renderFullBright = renderFullBright; diff --git a/src/main/java/appeng/client/render/model/BiometricCardBakedModel.java b/src/main/java/appeng/client/render/model/BiometricCardBakedModel.java new file mode 100644 index 00000000..1c77135b --- /dev/null +++ b/src/main/java/appeng/client/render/model/BiometricCardBakedModel.java @@ -0,0 +1,220 @@ +package appeng.client.render.model; + + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.ExecutionException; +import javax.annotation.Nullable; +import javax.vecmath.Matrix4f; + +import com.google.common.cache.Cache; +import com.google.common.cache.CacheBuilder; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.mojang.authlib.GameProfile; + +import org.apache.commons.lang3.tuple.Pair; + +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.ItemCameraTransforms; +import net.minecraft.client.renderer.block.model.ItemOverrideList; +import net.minecraft.client.renderer.texture.TextureAtlasSprite; +import net.minecraft.client.renderer.vertex.VertexFormat; +import net.minecraft.entity.EntityLivingBase; +import net.minecraft.item.ItemStack; +import net.minecraft.util.EnumFacing; +import net.minecraft.world.World; +import net.minecraftforge.client.model.IPerspectiveAwareModel; +import net.minecraftforge.common.model.TRSRTransformation; + +import appeng.api.implementations.items.IBiometricCard; +import appeng.api.util.AEColor; +import appeng.client.render.cablebus.CubeBuilder; +import appeng.core.AELog; + + +/** + * @author Sebastian + * @version rv3 - 10.10.2016 + * @since rv3 10.10.2016 + */ +public class BiometricCardBakedModel implements IPerspectiveAwareModel +{ + + private final VertexFormat format; + + private final IBakedModel baseModel; + + private final TextureAtlasSprite texture; + + private final int hash; + + private final ImmutableMap transforms; + + private final Cache modelCache; + + private final ImmutableList generalQuads; + + public BiometricCardBakedModel( VertexFormat format, IBakedModel baseModel, TextureAtlasSprite texture, ImmutableMap transforms ) + { + this( format, baseModel, texture, 0, transforms ); + } + + public BiometricCardBakedModel( VertexFormat format, IBakedModel baseModel, TextureAtlasSprite texture, int hash, ImmutableMap transforms ) + { + this.format = format; + this.baseModel = baseModel; + this.texture = texture; + this.hash = hash; + this.transforms = transforms; + this.generalQuads = ImmutableList.copyOf( buildGeneralQuads() ); + modelCache = CacheBuilder.newBuilder() + .maximumSize( 100 ) + .build(); + } + + @Override + public List getQuads( @Nullable IBlockState state, @Nullable EnumFacing side, long rand ) + { + + List quads = baseModel.getQuads( state, side, rand ); + + if( side != null ) + { + return quads; + } + + List result = new ArrayList<>( quads.size() + generalQuads.size() ); + result.addAll( quads ); + result.addAll( generalQuads ); + return result; + } + + private List buildGeneralQuads() + { + CubeBuilder builder = new CubeBuilder( this.format ); + + builder.setTexture( texture ); + + AEColor col = AEColor.values()[Math.abs( 3 + hash ) % AEColor.values().length]; + if( hash == 0 ) + { + col = AEColor.RED; + } + + for( int x = 0; x < 8; x++ ) + { + for( int y = 0; y < 6; y++ ) + { + final boolean isLit; + + // This makes the border always use the darker color + if( x == 0 || y == 0 || x == 7 || y == 5 ) + { + isLit = false; + } + else + { + isLit = ( hash & ( 1 << x ) ) != 0 || ( hash & ( 1 << y ) ) != 0; + } + + if( isLit ) + { + builder.setColorRGB( col.mediumVariant ); + } + else + { + final float scale = 0.3f / 255.0f; + builder.setColorRGB( ( ( col.blackVariant >> 16 ) & 0xff ) * scale, ( ( col.blackVariant >> 8 ) & 0xff ) * scale, ( col.blackVariant & 0xff ) * scale ); + } + + builder.addCube( 4 + x, 6 + y, 7.5f, 4 + x + 1, 6 + y + 1, 8.5f ); + } + } + return builder.getOutput(); + } + + @Override + public boolean isAmbientOcclusion() + { + return baseModel.isAmbientOcclusion(); + } + + @Override + public boolean isGui3d() + { + return baseModel.isGui3d(); + } + + @Override + public boolean isBuiltInRenderer() + { + return baseModel.isBuiltInRenderer(); + } + + @Override + public TextureAtlasSprite getParticleTexture() + { + return baseModel.getParticleTexture(); + } + + @Override + public ItemCameraTransforms getItemCameraTransforms() + { + return baseModel.getItemCameraTransforms(); + } + + @Override + public ItemOverrideList getOverrides() + { + return new ItemOverrideList( Collections.emptyList() ) + { + @Override + public IBakedModel handleItemState( IBakedModel originalModel, ItemStack stack, World world, EntityLivingBase entity ) + { + String username = ""; + if( stack.getItem() instanceof IBiometricCard ) + { + final GameProfile gp = ( (IBiometricCard) stack.getItem() ).getProfile( stack ); + if( gp != null ) + { + if( gp.getId() != null ) + { + username = gp.getId().toString(); + } + else + { + username = gp.getName(); + } + } + } + final int hash = !username.isEmpty() ? username.hashCode() : 0; + + // Get hash + if( hash == 0 ) + { + return BiometricCardBakedModel.this; + } + + try + { + return modelCache.get( hash, () -> new BiometricCardBakedModel( format, baseModel, texture, hash, transforms ) ); + } + catch( ExecutionException e ) + { + AELog.error( e ); + return BiometricCardBakedModel.this; + } + } + }; + } + + @Override + public Pair handlePerspective( ItemCameraTransforms.TransformType type ) + { + return IPerspectiveAwareModel.MapWrapper.handlePerspective( this, transforms, type ); + } +} diff --git a/src/main/java/appeng/client/render/model/BiometricCardModel.java b/src/main/java/appeng/client/render/model/BiometricCardModel.java new file mode 100644 index 00000000..120efa0b --- /dev/null +++ b/src/main/java/appeng/client/render/model/BiometricCardModel.java @@ -0,0 +1,75 @@ +package appeng.client.render.model; + + +import java.util.Collection; +import java.util.Collections; + +import com.google.common.base.Function; +import com.google.common.collect.ImmutableMap; + +import net.minecraft.client.renderer.block.model.IBakedModel; +import net.minecraft.client.renderer.block.model.ItemCameraTransforms; +import net.minecraft.client.renderer.texture.TextureAtlasSprite; +import net.minecraft.client.renderer.vertex.VertexFormat; +import net.minecraft.util.ResourceLocation; +import net.minecraftforge.client.model.IModel; +import net.minecraftforge.client.model.IPerspectiveAwareModel; +import net.minecraftforge.client.model.ModelLoaderRegistry; +import net.minecraftforge.common.model.IModelState; +import net.minecraftforge.common.model.TRSRTransformation; + +import appeng.core.AppEng; + + +/** + * Model wrapper for the biometric card item model, which combines a base card layer with a "visual hash" of the player name + */ +public class BiometricCardModel implements IModel +{ + + private static final ResourceLocation MODEL_BASE = new ResourceLocation( AppEng.MOD_ID, "item/biometric_card" ); + private static final ResourceLocation TEXTURE = new ResourceLocation( AppEng.MOD_ID, "items/biometric_card_hash" ); + + @Override + public Collection getDependencies() + { + return Collections.singletonList( MODEL_BASE ); + } + + @Override + public Collection getTextures() + { + return Collections.singletonList( TEXTURE ); + } + + @Override + public IBakedModel bake( IModelState state, VertexFormat format, Function bakedTextureGetter ) + { + TextureAtlasSprite texture = bakedTextureGetter.apply( TEXTURE ); + + IBakedModel baseModel = getBaseModel( state, format, bakedTextureGetter ); + + ImmutableMap map = IPerspectiveAwareModel.MapWrapper.getTransforms(state); + + return new BiometricCardBakedModel( format, baseModel, texture, map ); + } + + private IBakedModel getBaseModel( IModelState state, VertexFormat format, Function bakedTextureGetter ) + { + // Load the base model + try + { + return ModelLoaderRegistry.getModel( MODEL_BASE ).bake( state, format, bakedTextureGetter ); + } + catch( Exception e ) + { + throw new RuntimeException( e ); + } + } + + @Override + public IModelState getDefaultState() + { + return TRSRTransformation.identity(); + } +} diff --git a/src/main/java/appeng/core/api/definitions/ApiItems.java b/src/main/java/appeng/core/api/definitions/ApiItems.java index f95031b2..d8bb8676 100644 --- a/src/main/java/appeng/core/api/definitions/ApiItems.java +++ b/src/main/java/appeng/core/api/definitions/ApiItems.java @@ -44,6 +44,7 @@ import appeng.items.storage.ItemCreativeStorageCell; import appeng.items.storage.ItemSpatialStorageCell; import appeng.items.storage.ItemViewCell; import appeng.items.tools.ToolBiometricCard; +import appeng.items.tools.ToolBiometricCardRendering; import appeng.items.tools.ToolMemoryCard; import appeng.items.tools.ToolNetworkTool; import appeng.items.tools.powered.ToolChargedStaff; @@ -157,7 +158,9 @@ public final class ApiItems implements IItems .dispenserBehavior( DispenserBlockTool::new ) .build(); - this.biometricCard = registry.item( "biometric_card", ToolBiometricCard::new ).features( AEFeature.Security ).build(); + this.biometricCard = registry.item( "biometric_card", ToolBiometricCard::new ) + .rendering( new ToolBiometricCardRendering() ) + .features( AEFeature.Security ).build(); this.memoryCard = registry.item( "memory_card", ToolMemoryCard::new ).build(); this.networkTool = registry.item( "network_tool", ToolNetworkTool::new ).features( AEFeature.NetworkTool ).build(); diff --git a/src/main/java/appeng/items/tools/ToolBiometricCard.java b/src/main/java/appeng/items/tools/ToolBiometricCard.java index 7cabc602..52c4895b 100644 --- a/src/main/java/appeng/items/tools/ToolBiometricCard.java +++ b/src/main/java/appeng/items/tools/ToolBiometricCard.java @@ -48,12 +48,6 @@ public class ToolBiometricCard extends AEBaseItem implements IBiometricCard public ToolBiometricCard() { this.setMaxStackSize( 1 ); - - if( Platform.isClient() ) - { - // TODO - PORT ToolBiometricCardRender - // MinecraftForgeClient.registerItemRenderer( this, new ToolBiometricCardRender() ); - } } @Override diff --git a/src/main/java/appeng/items/tools/ToolBiometricCardRendering.java b/src/main/java/appeng/items/tools/ToolBiometricCardRendering.java new file mode 100644 index 00000000..b5214324 --- /dev/null +++ b/src/main/java/appeng/items/tools/ToolBiometricCardRendering.java @@ -0,0 +1,24 @@ +package appeng.items.tools; + + +import net.minecraft.client.renderer.block.model.ModelResourceLocation; +import net.minecraft.util.ResourceLocation; + +import appeng.bootstrap.IItemRendering; +import appeng.bootstrap.ItemRenderingCustomizer; +import appeng.client.render.model.BiometricCardModel; +import appeng.core.AppEng; + + +public class ToolBiometricCardRendering extends ItemRenderingCustomizer +{ + + public static final ResourceLocation MODEL = new ResourceLocation( AppEng.MOD_ID, "builtin/biometric_card" ); + + @Override + public void customize( IItemRendering rendering ) + { + rendering.builtInModel( "models/item/builtin/biometric_card", new BiometricCardModel() ); + rendering.model( new ModelResourceLocation( MODEL, "inventory" ) ).variants( MODEL ); + } +} diff --git a/src/main/resources/assets/appliedenergistics2/textures/items/biometric_card.png b/src/main/resources/assets/appliedenergistics2/textures/items/biometric_card.png index da67b5e8733834556c6e9496dbe498a291e7dfb9..42afaed308e2c58200e36df868b40af251f2d9c3 100644 GIT binary patch delta 2840 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 LNkvXXu0mjf%Bn;v diff --git a/src/main/resources/assets/appliedenergistics2/textures/items/biometric_card_hash.png b/src/main/resources/assets/appliedenergistics2/textures/items/biometric_card_hash.png new file mode 100644 index 0000000000000000000000000000000000000000..26b4a34aa1e2e2b442c8382d9a685905c9bc45dc GIT binary patch literal 2820 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