From aa84b2b8cd745791c365c347d45f1c138fb759c3 Mon Sep 17 00:00:00 2001 From: Electroblob77 <35599699+Electroblob77@users.noreply.github.com> Date: Thu, 4 Jun 2020 15:35:50 +0100 Subject: [PATCH] Introduce abstract superclass for LayerFrost and LayerStone, and fix some weirdness in the process --- .../wizardry/client/ClientProxy.java | 4 +- .../wizardry/client/renderer/LayerFrost.java | 154 ++-------- .../wizardry/client/renderer/LayerStone.java | 131 ++------- .../client/renderer/LayerTiledOverlay.java | 263 ++++++++++++++++++ 4 files changed, 307 insertions(+), 245 deletions(-) create mode 100644 src/main/java/electroblob/wizardry/client/renderer/LayerTiledOverlay.java diff --git a/src/main/java/electroblob/wizardry/client/ClientProxy.java b/src/main/java/electroblob/wizardry/client/ClientProxy.java index 5246f4a7..fa249025 100644 --- a/src/main/java/electroblob/wizardry/client/ClientProxy.java +++ b/src/main/java/electroblob/wizardry/client/ClientProxy.java @@ -689,8 +689,8 @@ public class ClientProxy extends CommonProxy { @Override public void initialiseLayers(){ - LayerStone.initialiseLayers(); - LayerFrost.initialiseLayers(); + LayerTiledOverlay.initialiseLayers(LayerStone::new); + LayerTiledOverlay.initialiseLayers(LayerFrost::new); } @Override diff --git a/src/main/java/electroblob/wizardry/client/renderer/LayerFrost.java b/src/main/java/electroblob/wizardry/client/renderer/LayerFrost.java index a51606e3..44b7063e 100644 --- a/src/main/java/electroblob/wizardry/client/renderer/LayerFrost.java +++ b/src/main/java/electroblob/wizardry/client/renderer/LayerFrost.java @@ -14,7 +14,6 @@ import net.minecraft.client.renderer.OpenGlHelper; import net.minecraft.client.renderer.entity.Render; import net.minecraft.client.renderer.entity.RenderLivingBase; import net.minecraft.client.renderer.entity.RenderPlayer; -import net.minecraft.client.renderer.entity.layers.LayerRenderer; import net.minecraft.entity.Entity; import net.minecraft.entity.EntityLivingBase; import net.minecraft.util.ResourceLocation; @@ -23,154 +22,39 @@ import net.minecraft.util.math.MathHelper; import org.lwjgl.opengl.GL11; /** - * Layer used to render the frost texture on a creature with the frostbite effect. Handles dynamic tiling of the texture. - * + * Layer used to render the frost texture on creatures with the frostbite effect. + * * @author Electroblob * @since Wizardry 1.2 */ -public class LayerFrost implements LayerRenderer { +public class LayerFrost extends LayerTiledOverlay { - protected ModelBase model; - private final RenderLivingBase renderer; - - private static final ResourceLocation texture = new ResourceLocation(Wizardry.MODID, "textures/entity/frost_overlay.png"); - - public static void initialiseLayers(){ - - for(Render renderer : Minecraft.getMinecraft().getRenderManager().entityRenderMap.values()){ - // Because the zombie classes are now split properly, their renderers play nicely like everything else. - if(renderer instanceof RenderLivingBase){ - // Adds a frost layer to all the living entity renderers in the game. Whether it is actually rendered - // is decided in doRenderLayer below on a per-entity basis. - ((RenderLivingBase)renderer).addLayer(new LayerFrost((RenderLivingBase)renderer)); - } - } - - for(RenderPlayer renderer : Minecraft.getMinecraft().getRenderManager().getSkinMap().values()){ - renderer.addLayer(new LayerFrost(renderer)); - } - } + private static final ResourceLocation TEXTURE = new ResourceLocation(Wizardry.MODID, "textures/entity/frost_overlay.png"); public LayerFrost(RenderLivingBase renderer){ - this.renderer = renderer; - this.model = renderer.getMainModel(); + super(renderer); + } + + @Override + public boolean shouldRender(EntityLivingBase entity, float partialTicks){ + return entity.isPotionActive(WizardryPotions.frost) || entity.getEntityData().getBoolean(BlockStatue.FROZEN_NBT_KEY); + } + + @Override + public ResourceLocation getTexture(EntityLivingBase entity, float partialTicks){ + return TEXTURE; } @Override public void doRenderLayer(EntityLivingBase entity, float limbSwing, float limbSwingAmount, float partialTicks, float ageInTicks, float netHeadYaw, float headPitch, float scale){ - if(entity.isPotionActive(WizardryPotions.frost) || entity.getEntityData().getBoolean(BlockStatue.FROZEN_NBT_KEY)){ + GlStateManager.enableBlend(); + GlStateManager.blendFunc(SourceFactor.SRC_ALPHA, DestFactor.ONE_MINUS_SRC_ALPHA); - GlStateManager.enableLighting(); - int i = this.getBlockBrightnessForEntity(entity, partialTicks); + super.doRenderLayer(entity, limbSwing, limbSwingAmount, partialTicks, ageInTicks, netHeadYaw, headPitch, scale); - int j = i % 65536; - int k = i / 65536; - OpenGlHelper.setLightmapTextureCoords(OpenGlHelper.lightmapTexUnit, (float)j / 1.0F, (float)k / 1.0F); - - // Frost texture - GlStateManager.enableBlend(); - GlStateManager.blendFunc(SourceFactor.SRC_ALPHA, DestFactor.ONE_MINUS_SRC_ALPHA); - this.renderer.bindTexture(texture); - this.renderEntityModel(entity, limbSwing, limbSwingAmount, partialTicks, ageInTicks, netHeadYaw, - headPitch, scale); - GlStateManager.disableBlend(); - - } + GlStateManager.disableBlend(); } - private int getBlockBrightnessForEntity(Entity entity, float partialTicks){ - - BlockPos.MutableBlockPos pos = new BlockPos.MutableBlockPos(MathHelper.floor(entity.posX), 0, - MathHelper.floor(entity.posZ)); - - if(entity.world.isBlockLoaded(pos)){ - pos.setY(MathHelper.floor(entity.posY + (double)entity.getEyeHeight())); - return entity.world.getCombinedLight(pos, 0); - }else{ - return 0; - } - } - - private void renderEntityModel(EntityLivingBase entity, float limbSwing, float limbSwingAmount, float partialTicks, - float ageInTicks, float netHeadYaw, float headPitch, float scale){ - - GlStateManager.pushMatrix(); - // Enables tiling (Also used for guardian beam, beacon beam and ender crystal beam) - GlStateManager.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_WRAP_S, GL11.GL_REPEAT); - GlStateManager.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_WRAP_T, GL11.GL_REPEAT); - - GlStateManager.depthMask(true); // Some entities set depth mask to false (i.e. no sorting of faces by depth) - // In particular, LayerSpiderEyes sets it to false when the spider is invisible, for some reason. - - // Changes the scale at which the texture is applied to the model. See LayerCreeper for a similar example, - // but with translation instead of scaling. - // NOTE: You can do all sorts of fun stuff with this, just by applying transformations in the 2D texture space. - GlStateManager.matrixMode(GL11.GL_TEXTURE); - GlStateManager.loadIdentity(); - double scaleX = 1, scaleY = 1; - // It's more logical to use the model's texture size, but some classes don't bother setting it properly - // (e.g. ModelVillager), so to get the correct dimensions I'm getting them from the first box instead. - if(model.boxList != null && model.boxList.size() >= 1 && model.boxList.get(0) != null){ - scaleX = (double)model.boxList.get(0).textureWidth / 16d; - scaleY = (double)model.boxList.get(0).textureHeight / 16d; - }else{ // Fallback to model fields; should never be needed - scaleX = (double)model.textureWidth / 16d; - scaleY = (double)model.textureHeight / 16d; - } - GlStateManager.scale(scaleX, scaleY, 1); - GlStateManager.matrixMode(GL11.GL_MODELVIEW); - - boolean headWearHidden = false; - boolean bodyWearHidden = false; - boolean leftArmWearHidden = false; - boolean rightArmWearHidden = false; - boolean leftLegWearHidden = false; - boolean rightLegWearHidden = false; - - // Hides the hat layer for bipeds - if(this.model instanceof ModelBiped){ - headWearHidden = ((ModelBiped)this.model).bipedHeadwear.isHidden; - ((ModelBiped)this.model).bipedHeadwear.isHidden = true; - } - - if(this.model instanceof ModelPlayer){ - bodyWearHidden = ((ModelPlayer)this.model).bipedBodyWear.isHidden; - leftArmWearHidden = ((ModelPlayer)this.model).bipedLeftArmwear.isHidden; - rightArmWearHidden = ((ModelPlayer)this.model).bipedRightArmwear.isHidden; - leftLegWearHidden = ((ModelPlayer)this.model).bipedLeftLegwear.isHidden; - rightLegWearHidden = ((ModelPlayer)this.model).bipedRightLegwear.isHidden; - ((ModelPlayer)this.model).bipedBodyWear.isHidden = true; - ((ModelPlayer)this.model).bipedLeftArmwear.isHidden = true; - ((ModelPlayer)this.model).bipedRightArmwear.isHidden = true; - ((ModelPlayer)this.model).bipedLeftLegwear.isHidden = true; - ((ModelPlayer)this.model).bipedRightLegwear.isHidden = true; - } - - this.model.setLivingAnimations(entity, limbSwing, limbSwingAmount, partialTicks); - this.model.render(entity, limbSwing, limbSwingAmount, ageInTicks, netHeadYaw, headPitch, scale); - - if(this.model instanceof ModelBiped) ((ModelBiped)this.model).bipedHeadwear.isHidden = headWearHidden; - - if(this.model instanceof ModelPlayer){ - ((ModelPlayer)this.model).bipedBodyWear.isHidden = bodyWearHidden; - ((ModelPlayer)this.model).bipedLeftArmwear.isHidden = leftArmWearHidden; - ((ModelPlayer)this.model).bipedRightArmwear.isHidden = rightArmWearHidden; - ((ModelPlayer)this.model).bipedLeftLegwear.isHidden = leftLegWearHidden; - ((ModelPlayer)this.model).bipedRightLegwear.isHidden = rightLegWearHidden; - } - - // Undoes the texture scaling - GlStateManager.matrixMode(GL11.GL_TEXTURE); - GlStateManager.loadIdentity(); - GlStateManager.matrixMode(GL11.GL_MODELVIEW); - - GlStateManager.popMatrix(); - } - - @Override - public boolean shouldCombineTextures(){ - return false; - } } \ No newline at end of file diff --git a/src/main/java/electroblob/wizardry/client/renderer/LayerStone.java b/src/main/java/electroblob/wizardry/client/renderer/LayerStone.java index ed1d5669..f9ccf368 100644 --- a/src/main/java/electroblob/wizardry/client/renderer/LayerStone.java +++ b/src/main/java/electroblob/wizardry/client/renderer/LayerStone.java @@ -2,16 +2,13 @@ package electroblob.wizardry.client.renderer; import electroblob.wizardry.block.BlockStatue; import electroblob.wizardry.client.ClientProxy; -import net.minecraft.client.Minecraft; import net.minecraft.client.model.ModelBase; import net.minecraft.client.model.ModelBiped; import net.minecraft.client.renderer.GlStateManager; import net.minecraft.client.renderer.GlStateManager.DestFactor; import net.minecraft.client.renderer.GlStateManager.SourceFactor; import net.minecraft.client.renderer.OpenGlHelper; -import net.minecraft.client.renderer.entity.Render; import net.minecraft.client.renderer.entity.RenderLivingBase; -import net.minecraft.client.renderer.entity.layers.LayerRenderer; import net.minecraft.entity.Entity; import net.minecraft.entity.EntityLivingBase; import net.minecraft.util.ResourceLocation; @@ -20,35 +17,30 @@ import net.minecraft.util.math.MathHelper; import org.lwjgl.opengl.GL11; /** - * Layer used to render the stone texture on a petrified creature. Handles dynamic tiling of the stone texture. + * Layer used to render the stone texture on a petrified creature. * * @author Electroblob * @since Wizardry 1.2 */ -public class LayerStone implements LayerRenderer { +public class LayerStone extends LayerTiledOverlay { - protected ModelBase model; - private final RenderLivingBase renderer; - - private static final ResourceLocation texture = new ResourceLocation("textures/blocks/stone.png"); - - public static void initialiseLayers(){ - - for(Render renderer : Minecraft.getMinecraft().getRenderManager().entityRenderMap.values()){ - // Because the zombie classes are now split properly, their renderers play nicely like everything else. - if(renderer instanceof RenderLivingBase){ - // Adds a stone layer to all the living entity renderers in the game. Whether it is actually rendered - // is decided in doRenderLayer below on a per-entity basis. - ((RenderLivingBase)renderer).addLayer(new LayerStone((RenderLivingBase)renderer)); - } - } - } + private static final ResourceLocation TEXTURE = new ResourceLocation("textures/blocks/stone.png"); public LayerStone(RenderLivingBase renderer){ - this.renderer = renderer; - this.model = renderer.getMainModel(); + super(renderer); } - + + @Override + public boolean shouldRender(EntityLivingBase entity, float partialTicks){ + return entity.getEntityData().getBoolean(BlockStatue.PETRIFIED_NBT_KEY); + } + + @Override + public ResourceLocation getTexture(EntityLivingBase entity, float partialTicks){ + ResourceLocation breakingTexture = ClientProxy.renderStatue.getBlockBreakingTexture(); + return breakingTexture == null ? TEXTURE : breakingTexture; + } + // FIXME: Does not work with zombie pigmen, I have no idea why. // I believe the issue is with the TESR actually, since LayerFrost works fine @@ -56,96 +48,19 @@ public class LayerStone implements LayerRenderer { public void doRenderLayer(EntityLivingBase entity, float limbSwing, float limbSwingAmount, float partialTicks, float ageInTicks, float netHeadYaw, float headPitch, float scale){ - if(entity.getEntityData().getBoolean(BlockStatue.PETRIFIED_NBT_KEY)){ + // N.B. It's one or the other because this effectively gets called twice via RenderStatue + if(ClientProxy.renderStatue.getBlockBreakingTexture() != null){ - GlStateManager.enableLighting(); - int i = this.getBlockBrightnessForEntity(entity, partialTicks); + GlStateManager.enableBlend(); + GlStateManager.blendFunc(SourceFactor.SRC_ALPHA, DestFactor.ONE_MINUS_SRC_ALPHA); - int j = i % 65536; - int k = i / 65536; - OpenGlHelper.setLightmapTextureCoords(OpenGlHelper.lightmapTexUnit, (float)j / 1.0F, (float)k / 1.0F); + super.doRenderLayer(entity, limbSwing, limbSwingAmount, partialTicks, ageInTicks, netHeadYaw, headPitch, scale); - ResourceLocation breakingTexture = ClientProxy.renderStatue.getBlockBreakingTexture(); + GlStateManager.disableBlend(); - if(breakingTexture != null){ - // Block breaking animation - // TODO: Spider eyes and enderman eyes (any others?) show through the stone when the block is being - // broken... - GlStateManager.enableBlend(); - GlStateManager.blendFunc(SourceFactor.SRC_ALPHA, DestFactor.ONE_MINUS_SRC_ALPHA); - this.renderer.bindTexture(breakingTexture); - this.renderEntityModel(entity, limbSwing, limbSwingAmount, partialTicks, ageInTicks, netHeadYaw, - headPitch, scale); - GlStateManager.disableBlend(); - }else{ - // Stone texture - this.renderer.bindTexture(texture); - this.renderEntityModel(entity, limbSwing, limbSwingAmount, partialTicks, ageInTicks, netHeadYaw, - headPitch, scale); - } - } - } - - private int getBlockBrightnessForEntity(Entity entity, float partialTicks){ - - BlockPos.MutableBlockPos pos = new BlockPos.MutableBlockPos(MathHelper.floor(entity.posX), 0, - MathHelper.floor(entity.posZ)); - - if(entity.world.isBlockLoaded(pos)){ - pos.setY(MathHelper.floor(entity.posY + (double)entity.getEyeHeight())); - return entity.world.getCombinedLight(pos, 0); }else{ - return 0; + super.doRenderLayer(entity, limbSwing, limbSwingAmount, partialTicks, ageInTicks, netHeadYaw, headPitch, scale); } } - private void renderEntityModel(EntityLivingBase entity, float limbSwing, float limbSwingAmount, float partialTicks, - float ageInTicks, float netHeadYaw, float headPitch, float scale){ - - GlStateManager.pushMatrix(); - // Enables tiling (Also used for guardian beam, beacon beam and ender crystal beam) - GlStateManager.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_WRAP_S, GL11.GL_REPEAT); - GlStateManager.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_WRAP_T, GL11.GL_REPEAT); - - GlStateManager.depthMask(true); // Some entities set depth mask to false (i.e. no sorting of faces by depth) - // In particular, LayerSpiderEyes sets it to false when the spider is invisible, for some reason. - - // Changes the scale at which the texture is applied to the model. See LayerCreeper for a similar example, - // but with translation instead of scaling. - // NOTE: You can do all sorts of fun stuff with this, just by applying transformations in the 2D texture space. - GlStateManager.matrixMode(GL11.GL_TEXTURE); - GlStateManager.loadIdentity(); - double scaleX = 1, scaleY = 1; - // It's more logical to use the model's texture size, but some classes don't bother setting it properly - // (e.g. ModelVillager), so to get the correct dimensions I'm getting them from the first box instead. - if(model.boxList != null && model.boxList.get(0) != null){ - scaleX = (double)model.boxList.get(0).textureWidth / 16d; - scaleY = (double)model.boxList.get(0).textureHeight / 16d; - }else{ // Fallback to model fields; should never be needed - scaleX = (double)model.textureWidth / 16d; - scaleY = (double)model.textureHeight / 16d; - } - GlStateManager.scale(scaleX, scaleY, 1); - GlStateManager.matrixMode(GL11.GL_MODELVIEW); - - // Hides the hat layer for bipeds - if(this.model instanceof ModelBiped) ((ModelBiped)this.model).bipedHeadwear.isHidden = true; - - this.model.setLivingAnimations(entity, limbSwing, limbSwingAmount, partialTicks); - this.model.render(entity, limbSwing, limbSwingAmount, ageInTicks, netHeadYaw, headPitch, scale); - - if(this.model instanceof ModelBiped) ((ModelBiped)this.model).bipedHeadwear.isHidden = false; - - // Undoes the texture scaling - GlStateManager.matrixMode(GL11.GL_TEXTURE); - GlStateManager.loadIdentity(); - GlStateManager.matrixMode(GL11.GL_MODELVIEW); - - GlStateManager.popMatrix(); - } - - @Override - public boolean shouldCombineTextures(){ - return false; - } } \ No newline at end of file diff --git a/src/main/java/electroblob/wizardry/client/renderer/LayerTiledOverlay.java b/src/main/java/electroblob/wizardry/client/renderer/LayerTiledOverlay.java new file mode 100644 index 00000000..0ab001d5 --- /dev/null +++ b/src/main/java/electroblob/wizardry/client/renderer/LayerTiledOverlay.java @@ -0,0 +1,263 @@ +package electroblob.wizardry.client.renderer; + +import net.minecraft.client.Minecraft; +import net.minecraft.client.model.ModelBase; +import net.minecraft.client.model.ModelBiped; +import net.minecraft.client.model.ModelPlayer; +import net.minecraft.client.renderer.GlStateManager; +import net.minecraft.client.renderer.OpenGlHelper; +import net.minecraft.client.renderer.entity.Render; +import net.minecraft.client.renderer.entity.RenderLivingBase; +import net.minecraft.client.renderer.entity.RenderPlayer; +import net.minecraft.client.renderer.entity.layers.LayerRenderer; +import net.minecraft.entity.Entity; +import net.minecraft.entity.EntityLivingBase; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.util.ResourceLocation; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.MathHelper; +import org.lwjgl.opengl.GL11; + +import java.util.function.Function; + +/** + * Base class for layers that overlay a tiled texture onto the entity model. Handles dynamic tiling of the texture, + * scaling to preserve in-world pixel size (1/16 of a block), and the necessary GL state changes. + * + * @author Electroblob + * @since Wizardry 4.3 + */ +public abstract class LayerTiledOverlay implements LayerRenderer { + + private final RenderLivingBase renderer; + + public LayerTiledOverlay(RenderLivingBase renderer){ + this.renderer = renderer; + } + + /** + * Returns true if this layer should be rendered for the given entity, false if not. + * @param entity The entity being rendered + * @param partialTicks The current partial tick time + * @return True if this layer should be rendered, false if not. + */ + public abstract boolean shouldRender(T entity, float partialTicks); + + /** + * Returns the texture to use for the overlay. + * @param entity The entity being rendered + * @param partialTicks The current partial tick time + * @return A {@link ResourceLocation} specifying the texture to use. + */ + public abstract ResourceLocation getTexture(T entity, float partialTicks); + + /** + * Returns the model to use for the overlay. Defaults to the renderer's main model. + * @param entity The entity being rendered + * @param partialTicks The current partial tick time + * @return The model to use. Swapping different models in dynamically is fine, but don't create new ones each time. + */ + public ModelBase getModel(T entity, float partialTicks){ + return renderer.getMainModel(); + } + + /** + * Returns the width of the texture to use for the overlay. Defaults to 16. + * @param entity The entity being rendered + * @param partialTicks The current partial tick time + * @return The width of the texture in pixels. + */ + public int getTextureWidth(T entity, float partialTicks){ + return 16; + } + + /** + * Returns the height of the texture to use for the overlay. Defaults to 16. + * @param entity The entity being rendered + * @param partialTicks The current partial tick time + * @return The height of the texture in pixels. + */ + public int getTextureHeight(T entity, float partialTicks){ + return 16; + } + + /** + * Returns whether to render the second layer of mob/player skins (which is not an actual render layer, but + * part of the model) as part of this layer. Defaults to false. + * @param entity The entity being rendered + * @param partialTicks The current partial tick time + * @return True to render any non-hidden second layer boxes, false to always hide them from this layer. + */ + public boolean renderSecondLayer(T entity, float partialTicks){ + return false; // Normally it looks kind of weird because second layers typically have holes in them + } + + @Override + public void doRenderLayer(T entity, float limbSwing, float limbSwingAmount, float partialTicks, + float ageInTicks, float netHeadYaw, float headPitch, float scale){ + + if(shouldRender(entity, partialTicks)){ + + GlStateManager.enableLighting(); + // TODO: Why is this here? Why don't other layers have it? What does RenderLivingBase do about brightness? + // And why was there a partialTicks parameter? + int i = getBlockBrightnessForEntity(entity); + int j = i % 65536; + int k = i / 65536; + OpenGlHelper.setLightmapTextureCoords(OpenGlHelper.lightmapTexUnit, (float)j / 1.0F, (float)k / 1.0F); + + this.renderer.bindTexture(getTexture(entity, partialTicks)); + this.renderEntityModel(entity, limbSwing, limbSwingAmount, partialTicks, ageInTicks, netHeadYaw, + headPitch, scale); + } + } + + @Override + public boolean shouldCombineTextures(){ + return false; + } + + protected static int getBlockBrightnessForEntity(Entity entity){ + + BlockPos.MutableBlockPos pos = new BlockPos.MutableBlockPos(MathHelper.floor(entity.posX), 0, + MathHelper.floor(entity.posZ)); + + if(entity.world.isBlockLoaded(pos)){ + pos.setY(MathHelper.floor(entity.posY + (double)entity.getEyeHeight())); + return entity.world.getCombinedLight(pos, 0); + }else{ + return 0; + } + } + + private void renderEntityModel(T entity, float limbSwing, float limbSwingAmount, float partialTicks, + float ageInTicks, float netHeadYaw, float headPitch, float scale){ + + GlStateManager.pushMatrix(); + // Enables texture tiling (Also used for guardian beam, beacon beam and ender crystal beam, amongst others) + GlStateManager.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_WRAP_S, GL11.GL_REPEAT); + GlStateManager.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_WRAP_T, GL11.GL_REPEAT); + + GlStateManager.depthMask(true); // Some entities set depth mask to false (i.e. no sorting of faces by depth) + // In particular, LayerSpiderEyes sets it to false when the spider is invisible, for some reason. + + // Changes the scale at which the texture is applied to the model. See LayerCreeper for a similar example, + // but with translation instead of scaling. + // You can do all sorts of fun stuff with this, just by applying transformations in the 2D texture space. + GlStateManager.matrixMode(GL11.GL_TEXTURE); // Switch to the 2D texture space + GlStateManager.loadIdentity(); + + ModelBase model = getModel(entity, partialTicks); + + int width = getTextureWidth(entity, partialTicks); + int height = getTextureHeight(entity, partialTicks); + + // Calculate the scaling required in each direction + double scaleX = 1, scaleY = 1; + // It's more logical to use the model's texture size, but some classes don't bother setting it properly + // (e.g. ModelVillager), so to get the correct dimensions I'm getting them from the first box instead. + if(model.boxList != null && model.boxList.get(0) != null){ + scaleX = (double)model.boxList.get(0).textureWidth / width; + scaleY = (double)model.boxList.get(0).textureHeight / height; + }else{ // Fallback to model fields; should never be needed + scaleX = (double)model.textureWidth / width; + scaleY = (double)model.textureHeight / height; + } + + GlStateManager.scale(scaleX, scaleY, 1); + + GlStateManager.matrixMode(GL11.GL_MODELVIEW); // Switch back to the 3D model space + + boolean headWearHidden = false; + boolean bodyWearHidden = false; + boolean leftArmWearHidden = false; + boolean rightArmWearHidden = false; + boolean leftLegWearHidden = false; + boolean rightLegWearHidden = false; + + if(!renderSecondLayer(entity, partialTicks)){ + // Hide the hat layer for bipeds, and all the other body part overlays for players + if(model instanceof ModelBiped){ + headWearHidden = ((ModelBiped)model).bipedHeadwear.isHidden; + ((ModelBiped)model).bipedHeadwear.isHidden = true; + } + + if(model instanceof ModelPlayer){ + bodyWearHidden = ((ModelPlayer)model).bipedBodyWear.isHidden; + leftArmWearHidden = ((ModelPlayer)model).bipedLeftArmwear.isHidden; + rightArmWearHidden = ((ModelPlayer)model).bipedRightArmwear.isHidden; + leftLegWearHidden = ((ModelPlayer)model).bipedLeftLegwear.isHidden; + rightLegWearHidden = ((ModelPlayer)model).bipedRightLegwear.isHidden; + ((ModelPlayer)model).bipedBodyWear.isHidden = true; + ((ModelPlayer)model).bipedLeftArmwear.isHidden = true; + ((ModelPlayer)model).bipedRightArmwear.isHidden = true; + ((ModelPlayer)model).bipedLeftLegwear.isHidden = true; + ((ModelPlayer)model).bipedRightLegwear.isHidden = true; + } + } + + // Actually render the model + model.setLivingAnimations(entity, limbSwing, limbSwingAmount, partialTicks); + model.render(entity, limbSwing, limbSwingAmount, ageInTicks, netHeadYaw, headPitch, scale); + + if(!renderSecondLayer(entity, partialTicks)){ + // Reset the hidden status of everything to how it was before + if(model instanceof ModelBiped){ + ((ModelBiped)model).bipedHeadwear.isHidden = headWearHidden; + } + + if(model instanceof ModelPlayer){ + ((ModelPlayer)model).bipedBodyWear.isHidden = bodyWearHidden; + ((ModelPlayer)model).bipedLeftArmwear.isHidden = leftArmWearHidden; + ((ModelPlayer)model).bipedRightArmwear.isHidden = rightArmWearHidden; + ((ModelPlayer)model).bipedLeftLegwear.isHidden = leftLegWearHidden; + ((ModelPlayer)model).bipedRightLegwear.isHidden = rightLegWearHidden; + } + } + + // Undo the texture scaling + GlStateManager.matrixMode(GL11.GL_TEXTURE); + GlStateManager.loadIdentity(); + GlStateManager.matrixMode(GL11.GL_MODELVIEW); + + GlStateManager.popMatrix(); + } + + /** + * Helper method to initialise a layer renderer for all applicable renderers. This method will work for any layer + * renderer, not just instances of {@code LayerTiledOverlay} (it is stored here purely for convenience). + * @param entityType The class of entity to initialise this layer renderer for (including subclasses) + * @param layerFactory A function that creates a layer for a given renderer, usually a constructor reference + * @param The type of entity this layer renderer is applicable for + */ + @SuppressWarnings("unchecked") + public static void initialiseLayers(Class entityType, Function, LayerRenderer> layerFactory){ + + for(Class c : Minecraft.getMinecraft().getRenderManager().entityRenderMap.keySet()){ + if(entityType.isAssignableFrom(c)){ + Render renderer = Minecraft.getMinecraft().getRenderManager().getEntityClassRenderObject(c); + if(renderer instanceof RenderLivingBase){ // Should always be true + // For some reason IntelliJ is quite happy with this cast, even without suppress warnings + ((RenderLivingBase)renderer).addLayer(layerFactory.apply((RenderLivingBase)renderer)); + } + } + } + + // Players have a separate renderer map + if(entityType.isAssignableFrom(EntityPlayer.class)){ + for(RenderPlayer renderer : Minecraft.getMinecraft().getRenderManager().getSkinMap().values()){ + renderer.addLayer(layerFactory.apply((RenderLivingBase)renderer)); + } + } + } + + /** + * Helper method to initialise a layer renderer for all living entity renderers. This is a shorthand version of + * {@link LayerTiledOverlay#initialiseLayers(Class, Function)}. + * @param layerFactory A function that creates a layer for a given renderer, usually a constructor reference + */ + public static void initialiseLayers(Function, LayerRenderer> layerFactory){ + initialiseLayers(EntityLivingBase.class, layerFactory); + } + +} \ No newline at end of file