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 70b633ec0..81dca1cca 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 000000000..1c77135b6 --- /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 000000000..120efa0bc --- /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 f95031b2a..d8bb8676c 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 7cabc6021..52c4895b6 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 000000000..b5214324d --- /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 zcmZo*+8{PTWnzLzJ(qu`vvWW|Mt(_taYlZDf~T{yLP1e}T4qiv14G5Fxxwin!7s&Z zzu)`fr+`<7;=-Gvfd>U$U0I}0Ea+hF>ucx{4b|F&MYd%ouL+|B3N4{-2ItM@PtG;QEn=_Duk zp!xWbV-KI`IW%&q6fg*IY)(-u{;SN$@T1PgMqB9s`#uK7ip$&_3Qz`~GlIyvbvLx3(rf_nY4%M1;3 zf6T0AWQd-kD9X5CB7=f(YJGuAXn*xpQWH z+xX&jU+ri6uauPZ_}|m>s|y?$7#^0?UHqq`zgeomnyn%C-y+p_EE#d!3rfFtr)}bQ z*vhcr<*o3~1)$JzT{vOq&6`i3Jh|a@#3?%QRCw*b^h5Cn!vE&{th4#^{p*8$k5n3s zVjJCAemppI=8Ml#ne&r4TaxZP>vymJvz`0@AKA-FM?8~uMresV4iqZvbD7oi*=VU! zr_&_OqpFwwr#!l^^PAc3zA3|oEIY>PiMlIdlbOGHs|!Sw9C-Adf#K8c{DW6CIG7tU znjfAo|Nq|puloX3hK4lHNremyaV{!)A*VLmALn3Ta9Pl(wZT#P%Rvd918gz}S??U= zuK$x@*5agcj}HTE=evN)dL5KdIQ)xnt56`|b6AwIM9hvGpG7NKqxg_9j3flA&JO+5^Qx#y)^b>6wcT&l%w5%-0Z62n>jVhd(p zDB2=r+hexy`GwUP{I~k;oR1qEcp<@Vd?d#}@7Q6djTSnblao(xR9eHc`k2&)ur_`qP9!$ z=Mjk{Bg6Xk2oD|QYeN0P`U2G*&PU8uggTv#)OSu`^4z#2$7^upaP<@@uNznd{{UtGgHpZw+b zD)&DBB~z>aH{@^bujOCec_P?0^Qf@h<~b)}bJVmj-$qvd`0bB@&asCboR=m}O`QI) zW#Z0@HjA&i^}6x*WFCuotnxVbSnjdKYV~YtifZ1zuYBLl3i92#OmmsFkG0S7vuDmO zI(u!V>1qgIKKc9Vl_WkV%Q58`-+g5Dbk?OhWNz}5)ciZ;lwMJP+E{zC{irwtC zcJ5l$YrEI(UM{$Nr{841)$^{UAKvhB+sSP!w@Yq#dV}rux^21JY7^6w#M8GQU3d81 zcNRB)?&$9HZaS?u-A-?|^s&1q_CE0{`C1tJu-etaPzcc@g$$ZUuk;cUr4xey-5cu?>J@@>n2HE%5)cMyt`!}Xte?weiF{Ks~WZOrc(-HVJrV}4-!iS4KT59iN|Z@uqaulzsdf7Sns z49go-8Pl0N8yy>?nSTGMs!`nEXK!^o_1mVKZykFU>~s`$>~C7z^z`6b#f%egCahKb z{(Q!{iRUu;S_A4{T)V)Y_^dv$`*0nPdTV=Yc$%S)rh$fo;lt!mU>$2%<^*f-T(9UbNi#Y zkGLOKi_g$M5nE8X@Z6yIvysO>^++@Uxim!BIeKg;dcPOB7sCvQuCmHsTP?DU${)=mG8=5=q^mz&CdGJErcHLQ`kT-%(My$FfY zxUR*%DLpkjZU5QMQbK$3*VXM+tMbimj5~O$>EKqz?AP3(Tu)nT zU*Ea@Hr78vKl1+8#9wW5rR_vJyVi-^7N0I!e?#L=#Z&3_{8Rg9-kGvxvqOu?|fIacWZUp_m|T1%+KBZvbXZ1*#B!+=ULj`-TS#J|NGJJU+;_Ett(Rhd+zk^ zo8{{7*6#7&6aU5Gt>aDSyX{x)qyOEx@%~PI)`9W`pD*5V|IXIheE#3Wul@Yato<|l z>rc1ww!NNbVP|Ri$iC?B%RkGV&!?PMn)vKyU{FZ*2=ZlMs8VHMXlQ0&sQ>w&fuZ3g14F3+1H-EX1_rAc z3=HD=lj4uMF)%Phd%8G=RNPAX@&CU)v+9h-EH)DplRxpBQaA&5EL`}|zKzj)n$FMs zycK>Tahp;&^O!j-E3YtANjQ|51kIVyB_Z+e|NcWC=0`{!ZctmnveD$LvG7g_1_p-z d|Ct#a=WXkr8Oq0E!N9=4;OXk;vd$@?2>`tvQXK#Q delta 209 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