package com.anthonyhilyard.iceberg.renderer; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Map; import java.util.Set; import java.util.function.Predicate; import java.util.function.Supplier; import com.anthonyhilyard.iceberg.Loader; import com.google.common.collect.Maps; import org.apache.commons.lang3.exception.ExceptionUtils; import org.joml.Matrix4f; import org.joml.Quaternionf; import org.joml.Vector3f; import org.lwjgl.opengl.GL11; import com.mojang.blaze3d.vertex.PoseStack; import com.mojang.blaze3d.vertex.VertexConsumer; import com.mojang.datafixers.util.Pair; import com.mojang.math.MatrixUtil; import com.mojang.blaze3d.systems.RenderSystem; import com.mojang.blaze3d.pipeline.MainTarget; import com.mojang.blaze3d.pipeline.RenderTarget; import com.mojang.blaze3d.platform.Lighting; import com.mojang.blaze3d.platform.GlStateManager.SourceFactor; import com.mojang.blaze3d.platform.GlStateManager.DestFactor; import net.minecraft.CrashReport; import net.minecraft.CrashReportCategory; import net.minecraft.ReportedException; import net.minecraft.client.Minecraft; import net.minecraft.client.renderer.entity.EntityRenderDispatcher; import net.minecraft.client.renderer.entity.ItemRenderer; import net.minecraft.client.renderer.texture.OverlayTexture; import net.minecraft.client.renderer.texture.TextureManager; import net.minecraft.client.color.item.ItemColors; import net.minecraft.client.gui.GuiComponent; import net.minecraft.client.renderer.BlockEntityWithoutLevelRenderer; import net.minecraft.client.renderer.ItemBlockRenderTypes; import net.minecraft.client.renderer.LightTexture; import net.minecraft.client.renderer.MultiBufferSource; import net.minecraft.client.renderer.RenderType; import net.minecraft.client.renderer.MultiBufferSource.BufferSource; import net.minecraft.client.renderer.block.BlockModelShaper; import net.minecraft.client.renderer.block.model.ItemTransforms; import net.minecraft.client.renderer.block.model.ItemTransforms.TransformType; import net.minecraft.client.renderer.blockentity.BlockEntityRenderer; import net.minecraft.client.resources.model.BakedModel; import net.minecraft.client.resources.model.ModelManager; import net.minecraft.core.BlockPos; import net.minecraft.core.Direction; import net.minecraft.core.registries.BuiltInRegistries; import net.minecraft.nbt.CompoundTag; import net.minecraft.server.packs.resources.ReloadableResourceManager; import net.minecraft.server.packs.resources.ResourceManager; import net.minecraft.tags.ItemTags; import net.minecraft.world.entity.Entity; import net.minecraft.world.entity.EntityType; import net.minecraft.world.entity.EquipmentSlot; import net.minecraft.world.entity.animal.horse.Horse; import net.minecraft.world.entity.decoration.ArmorStand; import net.minecraft.world.entity.player.Player; import net.minecraft.world.entity.vehicle.AbstractMinecart; import net.minecraft.world.entity.vehicle.Boat; import net.minecraft.world.entity.vehicle.ChestBoat; import net.minecraft.world.inventory.InventoryMenu; import net.minecraft.world.item.BlockItem; import net.minecraft.world.item.BoatItem; import net.minecraft.world.item.HorseArmorItem; import net.minecraft.world.item.Item; import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.Items; import net.minecraft.world.item.MinecartItem; import net.minecraft.world.level.Level; import net.minecraft.world.level.block.Block; import net.minecraft.world.level.block.EntityBlock; import net.minecraft.world.level.block.HalfTransparentBlock; import net.minecraft.world.level.block.StainedGlassPaneBlock; import net.minecraft.world.level.block.entity.BlockEntity; import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.block.state.properties.BlockStateProperties; import net.minecraft.world.level.block.state.properties.DoubleBlockHalf; import net.minecraft.world.phys.BlockHitResult; import net.minecraft.world.phys.HitResult; import net.minecraft.world.phys.Vec3; /** * An extended ItemRenderer with extended functionality, such as allowing items to be rendered to a RenderTarget * before drawing to screen for alpha support, and allowing handheld item models to be rendered into the gui. */ public class CustomItemRenderer extends ItemRenderer { /* Cylindrical bounds for a model. */ private record ModelBounds(Vector3f center, float height, float radius) {} private static RenderTarget iconFrameBuffer = null; private static ArmorStand armorStand = null; private static AbstractMinecart minecart = null; private static Boat boat = null; private static Horse horse = null; private static Pair cachedArmorStandItem = null; private static Item cachedBoatItem = null; private static Pair cachedHorseArmorItem = null; private static Map modelBoundsCache = Maps.newHashMap(); private static final List quadDirections; static { quadDirections = new ArrayList<>(Arrays.asList(Direction.values())); quadDirections.add(null); } private Minecraft mc; private final ModelManager modelManager; private final BlockEntityWithoutLevelRenderer blockEntityRenderer; public CustomItemRenderer(TextureManager textureManagerIn, ModelManager modelManagerIn, ItemColors itemColorsIn, BlockEntityWithoutLevelRenderer blockEntityRendererIn, Minecraft mcIn) { super(textureManagerIn, modelManagerIn, itemColorsIn, blockEntityRendererIn); mc = mcIn; modelManager = modelManagerIn; blockEntityRenderer = blockEntityRendererIn; if (mc.getResourceManager() instanceof ReloadableResourceManager resourceManager) { resourceManager.registerReloadListener(this); } // Initialize the icon framebuffer if needed. if (iconFrameBuffer == null) { // Use 96 x 96 pixels for the icon frame buffer so at 1.5 scale we get 4x resolution (for smooth icons on larger gui scales). iconFrameBuffer = new MainTarget(96, 96); iconFrameBuffer.setClearColor(0.0f, 0.0f, 0.0f, 0.0f); iconFrameBuffer.clear(Minecraft.ON_OSX); } } private void renderGuiModel(ItemStack itemStack, int x, int y, Quaternionf rotation, BakedModel bakedModel) { mc.getTextureManager().getTexture(InventoryMenu.BLOCK_ATLAS).setFilter(false, false); RenderSystem.setShaderTexture(0, InventoryMenu.BLOCK_ATLAS); RenderSystem.enableBlend(); RenderSystem.blendFunc(SourceFactor.SRC_ALPHA, DestFactor.ONE_MINUS_SRC_ALPHA); RenderSystem.setShaderColor(1.0f, 1.0f, 1.0f, 1.0f); PoseStack modelViewStack = RenderSystem.getModelViewStack(); modelViewStack.pushPose(); modelViewStack.translate(x, y, 100.0f + blitOffset); modelViewStack.translate(8.0f, 8.0f, 0.0f); modelViewStack.scale(1.0f, -1.0f, 1.0f); modelViewStack.scale(16.0f, 16.0f, 16.0f); RenderSystem.applyModelViewMatrix(); BufferSource bufferSource = Minecraft.getInstance().renderBuffers().bufferSource(); boolean flatLighting = !bakedModel.usesBlockLight(); if (flatLighting) { Lighting.setupForFlatItems(); } PoseStack poseStack = new PoseStack(); renderModel(itemStack, ItemTransforms.TransformType.GUI, false, poseStack, rotation, bufferSource, LightTexture.FULL_BRIGHT, OverlayTexture.NO_OVERLAY, bakedModel); poseStack.popPose(); bufferSource.endBatch(); RenderSystem.enableDepthTest(); if (flatLighting) { Lighting.setupFor3DItems(); } modelViewStack.popPose(); RenderSystem.applyModelViewMatrix(); } @SuppressWarnings("deprecation") private void renderEntityModel(Entity entity, PoseStack poseStack, MultiBufferSource bufferSource, int packedLight) { Minecraft minecraft = Minecraft.getInstance(); EntityRenderDispatcher entityRenderDispatcher = minecraft.getEntityRenderDispatcher(); Lighting.setupForEntityInInventory(); RenderSystem.enableDepthTest(); RenderSystem.setShaderColor(1.0f, 1.0f, 1.0f, 1.0f); entityRenderDispatcher.setRenderShadow(false); RenderSystem.runAsFancy(() -> entityRenderDispatcher.render(entity, 0.0, 0.0, 0.0, 0.0f, 1.0f, poseStack, bufferSource, packedLight)); if (bufferSource instanceof BufferSource source) { source.endBatch(); } entityRenderDispatcher.setRenderShadow(true); RenderSystem.applyModelViewMatrix(); Lighting.setupFor3DItems(); } private void renderModelInternal(ItemStack itemStack, ItemTransforms.TransformType transformType, boolean leftHanded, PoseStack poseStack, Quaternionf rotation, T bufferSource, int packedLight, int packedOverlay, BakedModel bakedModel, Predicate bufferSourceReady) { Minecraft minecraft = Minecraft.getInstance(); if (Player.getEquipmentSlotForItem(itemStack).isArmor()) { if (updateArmorStand(itemStack)) { renderEntityModel(armorStand, poseStack, bufferSource, packedLight); } } if (!bakedModel.isCustomRenderer() && !itemStack.is(Items.TRIDENT)) { boolean fabulous; if (transformType != ItemTransforms.TransformType.GUI && !transformType.firstPerson() && itemStack.getItem() instanceof BlockItem blockItem) { Block block = blockItem.getBlock(); fabulous = !(block instanceof HalfTransparentBlock) && !(block instanceof StainedGlassPaneBlock); } else { fabulous = true; } if (bufferSourceReady.test(bufferSource) && itemStack.getItem() instanceof BlockItem blockItem) { Block block = blockItem.getBlock(); BakedModel blockModel = null; BlockModelShaper blockModelShaper = minecraft.getBlockRenderer().getBlockModelShaper(); boolean isBlockEntity = false; blockModel = blockModelShaper.getBlockModel(block.defaultBlockState()); if (blockModel != modelManager.getMissingModel()) { // First try rendering via the BlockEntityWithoutLevelRenderer. blockEntityRenderer.renderByItem(itemStack, transformType, poseStack, bufferSource, packedLight, packedOverlay); } else { blockModel = null; } if (block.defaultBlockState().hasProperty(BlockStateProperties.DOUBLE_BLOCK_HALF)) { // This is a double block, so we'll need to render both halves. // First render the bottom half. BlockState bottomState = block.defaultBlockState().setValue(BlockStateProperties.DOUBLE_BLOCK_HALF, DoubleBlockHalf.LOWER); BakedModel bottomModel = blockModelShaper.getBlockModel(bottomState); renderBakedModel(itemStack, transformType, poseStack, bufferSource, packedLight, packedOverlay, bottomModel, fabulous); // Then render the top half. poseStack.pushPose(); poseStack.translate(0.0f, 1.0f, 0.0f); BlockState topState = block.defaultBlockState().setValue(BlockStateProperties.DOUBLE_BLOCK_HALF, DoubleBlockHalf.UPPER); BakedModel topModel = blockModelShaper.getBlockModel(topState); renderBakedModel(itemStack, transformType, poseStack, bufferSource, packedLight, packedOverlay, topModel, fabulous); poseStack.popPose(); } if (blockItem.getBlock() instanceof EntityBlock entityBlock) { isBlockEntity = true; try { renderBlockEntity(itemStack, poseStack, bufferSource, packedLight, packedOverlay, minecraft, entityBlock, blockItem.getBlock().defaultBlockState()); } catch (Exception e) { // This can fail for things like beacons that require a level. We'll just ignore it. } } // If we still haven't rendered anything or this is a block entity, try rendering the block model. if (blockModel != null && (bufferSourceReady.test(bufferSource) || isBlockEntity)) { renderBakedModel(itemStack, transformType, poseStack, bufferSource, packedLight, packedOverlay, blockModel, fabulous); } } // Now try rendering entity models for items that spawn minecarts or boats. if (bufferSourceReady.test(bufferSource) && itemStack.getItem() instanceof MinecartItem minecartItem) { if (updateMinecart(minecartItem)) { renderEntityModel(minecart, poseStack, bufferSource, packedLight); } } if (bufferSourceReady.test(bufferSource) && itemCreatesBoat(itemStack.getItem())) { if (updateBoat(itemStack.getItem())) { renderEntityModel(boat, poseStack, bufferSource, packedLight); } } // If this is horse armor, render it here. if (bufferSourceReady.test(bufferSource) && itemStack.getItem() instanceof HorseArmorItem) { if (updateHorse(itemStack)) { renderEntityModel(horse, poseStack, bufferSource, packedLight); } } // Finally, fall back to just rendering the item model. if (bufferSourceReady.test(bufferSource)) { renderBakedModel(itemStack, transformType, poseStack, bufferSource, packedLight, packedOverlay, bakedModel, fabulous); } } else if (bufferSourceReady.test(bufferSource)) { blockEntityRenderer.renderByItem(itemStack, transformType, poseStack, bufferSource, packedLight, packedOverlay); } } private void renderModel(ItemStack itemStack, TransformType transformType, boolean leftHanded, PoseStack poseStack, Quaternionf rotation, MultiBufferSource bufferSource, int packedLight, int packedOverlay, BakedModel bakedModel) { if (!itemStack.isEmpty()) { // If this model doesn't have a special transform for the given transform type (most likely GUI), default to first-person right handed instead. TransformType previewTransform = transformType; if (!bakedModel.getTransforms().hasTransform(transformType)) { previewTransform = TransformType.GROUND; } boolean isBlockItem = false, spawnsEntity = false, isArmor = false; if (itemStack.getItem() instanceof BlockItem blockItem) { isBlockItem = true; } else if (itemStack.getItem() instanceof MinecartItem || itemCreatesBoat(itemStack.getItem())) { spawnsEntity = true; } if (Player.getEquipmentSlotForItem(itemStack).isArmor()) { isArmor = true; } poseStack.pushPose(); poseStack.translate(0.5f, 0.5f, 0.5f); if (isBlockItem || spawnsEntity) { // Apply the standard block rotation so block entities match other blocks. poseStack.mulPose(new Quaternionf().rotationXYZ((float)Math.toRadians(30.0f), (float)Math.toRadians(225.0f), 0.0f)); } else { bakedModel.getTransforms().getTransform(previewTransform).apply(leftHanded, poseStack); } poseStack.translate(-0.5f, -0.5f, -0.5f); // Get the model bounds. ModelBounds modelBounds = getModelBounds(itemStack, previewTransform, leftHanded, poseStack, rotation, bufferSource, packedLight, packedOverlay, bakedModel); // Undo the camera transforms now that we have the model bounds. poseStack.popPose(); poseStack.pushPose(); // Rotate the model. poseStack.mulPose(rotation); // Scale the model to fit. float scale = 0.8f / Math.max(modelBounds.height, modelBounds.radius * 2.0f); // Adjust the scale based on the armor type. if (isArmor) { switch (Player.getEquipmentSlotForItem(itemStack)) { case HEAD: scale *= 0.75f; break; case LEGS: scale *= 1.3f; break; case FEET: scale *= 0.85f; break; default: break; } } poseStack.scale(scale, scale, scale); // Translate the model to the center of the item. poseStack.translate(-modelBounds.center.x(), -modelBounds.center.y(), -modelBounds.center.z()); poseStack.translate(0.5f, 0.5f, 0.5f); // Reapply the camera transforms. if (isBlockItem || spawnsEntity) { // Apply the standard block rotation so block entities match other blocks. poseStack.mulPose(new Quaternionf().rotationXYZ((float)Math.toRadians(30.0f), (float)Math.toRadians(225.0f), 0.0f)); } else { bakedModel.getTransforms().getTransform(previewTransform).apply(leftHanded, poseStack); } poseStack.translate(-0.5f, -0.5f, -0.5f); CheckedBufferSource checkedBufferSource = CheckedBufferSource.create(bufferSource); renderModelInternal(itemStack, previewTransform, leftHanded, poseStack, rotation, checkedBufferSource, packedLight, packedOverlay, bakedModel, b -> !b.hasRendered()); poseStack.popPose(); } } private void renderBlockEntity(ItemStack itemStack, PoseStack poseStack, MultiBufferSource bufferSource, int packedLight, int packedOverlay, Minecraft minecraft, EntityBlock entityBlock, BlockState blockState) throws Exception { // If we didn't render via the BlockEntityWithoutLevelRenderer, now check if this is a block entity and render that. BlockEntity blockEntity = entityBlock.newBlockEntity(BlockPos.ZERO, blockState); if (blockEntity != null) { if (itemStack.getTag() != null) { blockEntity.load(itemStack.getTag()); } BlockEntityRenderer renderer = minecraft.getBlockEntityRenderDispatcher().getRenderer(blockEntity); if (renderer != null) { renderer.render(blockEntity, minecraft.getFrameTime(), poseStack, bufferSource, packedLight, packedOverlay); } } } private void renderBakedModel(ItemStack itemStack, ItemTransforms.TransformType transformType, PoseStack poseStack, MultiBufferSource bufferSource, int packedLight, int packedOverlay, BakedModel bakedModel, boolean fabulous) { RenderType renderType = ItemBlockRenderTypes.getRenderType(itemStack, fabulous); VertexConsumer vertexConsumer; if (itemStack.is(ItemTags.COMPASSES) && itemStack.hasFoil()) { poseStack.pushPose(); PoseStack.Pose posestack$pose = poseStack.last(); if (transformType == ItemTransforms.TransformType.GUI) { MatrixUtil.mulComponentWise(posestack$pose.pose(), 0.5F); } else if (transformType.firstPerson()) { MatrixUtil.mulComponentWise(posestack$pose.pose(), 0.75F); } if (fabulous) { vertexConsumer = getCompassFoilBufferDirect(bufferSource, renderType, posestack$pose); } else { vertexConsumer = getCompassFoilBuffer(bufferSource, renderType, posestack$pose); } poseStack.popPose(); } else if (fabulous) { vertexConsumer = getFoilBufferDirect(bufferSource, renderType, true, itemStack.hasFoil()); } else { vertexConsumer = getFoilBuffer(bufferSource, renderType, true, itemStack.hasFoil()); } renderModelLists(bakedModel, itemStack, packedLight, packedOverlay, poseStack, vertexConsumer); } private boolean updateArmorStand(ItemStack itemStack) { EquipmentSlot equipmentSlot = Player.getEquipmentSlotForItem(itemStack); if (!equipmentSlot.isArmor()) { // This isn't armor, so don't render anything. return false; } if (armorStand == null) { Minecraft minecraft = Minecraft.getInstance(); armorStand = EntityType.ARMOR_STAND.create(minecraft.level); armorStand.setInvisible(true); } // If somehow the armor stand is still null, then we can't render anything. if (armorStand == null) { return false; } // If the item has changed, then we need to update the armor stand. if (cachedArmorStandItem != Pair.of(itemStack.getItem(), itemStack.getTag())) { // Clear the armor stand. for (EquipmentSlot slot : EquipmentSlot.values()) { armorStand.setItemSlot(slot, ItemStack.EMPTY); } // Equip the armor stand with the armor. armorStand.setItemSlot(equipmentSlot, itemStack); cachedArmorStandItem = Pair.of(itemStack.getItem(), itemStack.getTag()); } return true; } private boolean updateMinecart(MinecartItem item) { // We don't support modded minecarts right now. if (!item.getClass().equals(MinecartItem.class)) { minecart = null; } else { if (minecart == null || minecart.getMinecartType() != item.type) { Minecraft minecraft = Minecraft.getInstance(); minecart = AbstractMinecart.createMinecart(minecraft.level, 0, 0, 0, item.type); } } // If somehow the minecart is still null, then we can't render anything. return minecart != null; } private boolean itemCreatesBoat(Item item) { // Vanilla and modded subclass BoatItems obviously create boats. if (item instanceof BoatItem) { return true; } // More complex modded items may create a boat subclass entity, so check for that next. // To do so, we'll look for a method that returns a subclass of Boat. try { Method[] methods = item.getClass().getDeclaredMethods(); for (Method method : methods) { if (Boat.class.isAssignableFrom(method.getReturnType()) && !method.getReturnType().equals(Boat.class)) { return true; } } } catch (Exception e) {} // This doesn't appear to be a boat-creating item. return false; } /// Attempts to create a modded boat entity from a modded boat item using reflection. /// Returns null if we failed to create the entity. private Boat createModdedBoat(Item item) { Boat moddedBoat = null; // Ensure this is actually a modded boat item before continuing. //if (itemCreatesBoat(item)) { Minecraft minecraft = Minecraft.getInstance(); // Since this boat is modded, first we will look for a method in the BoatItem subclass that returns an instance of a Boat subclass. // If we find it, we will use it to create the boat entity. try { Method[] methods = item.getClass().getDeclaredMethods(); for (Method method : methods) { // If the signature matches, call it. if (Boat.class.isAssignableFrom(method.getReturnType()) && !method.getReturnType().equals(Boat.class)) { // First supported signature: createBoat(Level level, HitResult hit) if (method.getParameterCount() == 2 && method.getParameterTypes()[0].equals(Level.class) && method.getParameterTypes()[1].equals(HitResult.class)) { method.setAccessible(true); moddedBoat = (Boat) method.invoke(item, minecraft.level, new BlockHitResult(Vec3.ZERO, Direction.DOWN, BlockPos.ZERO, false)); break; } // Second supported signature: createBoat(Level level, Vec3 pos, float yaw) else if (method.getParameterCount() == 3 && method.getParameterTypes()[0].equals(Level.class) && method.getParameterTypes()[1].equals(Vec3.class) && method.getParameterTypes()[2].equals(float.class)) { method.setAccessible(true); moddedBoat = (Boat) method.invoke(item, minecraft.level, Vec3.ZERO, 30.0f); break; } } } // Now, if we've created an entity we need to set the "wood type", so we will first look for an enum field on this item that matches // the parameter type of a method on the boat subclass, then call that method. if (moddedBoat != null) { Field[] fields = item.getClass().getDeclaredFields(); Method[] boatMethods = moddedBoat.getClass().getDeclaredMethods(); boolean enumFound = false; for (Field field : fields) { if (field.getType().isEnum()) { // Check if this enum field is the same type as the first parameter of a method on the boat subclass. for (Method method : boatMethods) { if (method.getParameterCount() == 1 && method.getParameterTypes()[0].equals(field.getType())) { method.setAccessible(true); field.setAccessible(true); method.invoke(moddedBoat, field.get(item)); enumFound = true; break; } } } } // If we didn't find any enum fields to try, now try with suppliers. if (!enumFound) { for (Field field : fields) { if (field.getType().isAssignableFrom(Supplier.class)) { field.setAccessible(true); Object fieldValue = ((Supplier)field.get(item)).get(); // Check if this enum field is the same type as the first parameter of a method on the boat subclass. for (Method method : boatMethods) { if (method.getParameterCount() == 1 && method.getParameterTypes()[0].equals(fieldValue.getClass())) { method.setAccessible(true); method.invoke(moddedBoat, fieldValue); break; } } } } } } } catch (Exception e) { // If we fail to create the boat entity by looking for a "getBoat"-esque method, we can't render this modded boat. // This will fallback to just rendering the item icon. moddedBoat = null; Loader.LOGGER.info(ExceptionUtils.getStackTrace(e)); } } return moddedBoat; } private boolean updateBoat(Item item) { boolean moddedBoat = false; // Check if this is a modded boat item, that is if it is a subclass of BoatItem (or not a BoatItem at all). if (!item.getClass().equals(BoatItem.class)) { // This is a modded boat item, so we need to create the boat entity in a different way. moddedBoat = true; } if (boat == null || cachedBoatItem != item) { Minecraft minecraft = Minecraft.getInstance(); if (moddedBoat) { boat = createModdedBoat(item); } else if (item instanceof BoatItem boatItem) { boat = boatItem.hasChest ? new ChestBoat(minecraft.level, 0, 0, 0) : new Boat(minecraft.level, 0, 0, 0); boat.setVariant(boatItem.type); } cachedBoatItem = item; } // If somehow the boat is still null, then we can't render anything. return boat != null; } private boolean updateHorse(ItemStack horseArmorItem) { // If this isn't a horse armor item, we can't render anything. if (!(horseArmorItem.getItem() instanceof HorseArmorItem)) { return false; } if (horse == null) { Minecraft minecraft = Minecraft.getInstance(); horse = EntityType.HORSE.create(minecraft.level); horse.setInvisible(true); } // If somehow the horse is still null, then we can't render anything. if (horse == null) { return false; } // If the item has changed, then we need to update the horse. if (cachedHorseArmorItem != Pair.of(horseArmorItem.getItem(), horseArmorItem.getTag())) { // Equip the horse with the armor. horse.setItemSlot(EquipmentSlot.CHEST, horseArmorItem); cachedHorseArmorItem = Pair.of(horseArmorItem.getItem(), horseArmorItem.getTag()); } return true; } private ModelBounds boundsFromVertices(Set vertices) { Vector3f center = new Vector3f(); float radius = 0.0f; float height = 0.0f; float minX = Float.MAX_VALUE; float minY = Float.MAX_VALUE; float minZ = Float.MAX_VALUE; float maxX = Float.MIN_VALUE; float maxY = Float.MIN_VALUE; float maxZ = Float.MIN_VALUE; for (Vector3f vertex : vertices) { minX = Math.min(minX, vertex.x); minY = Math.min(minY, vertex.y); minZ = Math.min(minZ, vertex.z); maxX = Math.max(maxX, vertex.x); maxY = Math.max(maxY, vertex.y); maxZ = Math.max(maxZ, vertex.z); } center = new Vector3f((minX + maxX) / 2.0f, (minY + maxY) / 2.0f, (minZ + maxZ) / 2.0f); height = maxY - minY; for (Vector3f vertex : vertices) { radius = Math.max(radius, (float) Math.sqrt((vertex.x - center.x) * (vertex.x - center.x) + (vertex.z - center.z) * (vertex.z - center.z))); } return new ModelBounds(center, height, radius); } private ModelBounds getModelBounds(ItemStack itemStack, ItemTransforms.TransformType transformType, boolean leftHanded, PoseStack poseStack, Quaternionf rotation, MultiBufferSource bufferSource, int packedLight, int packedOverlay, BakedModel bakedModel) { if (!modelBoundsCache.containsKey(itemStack.getItem())) { VertexCollector vertexCollector = VertexCollector.create(); renderModelInternal(itemStack, transformType, leftHanded, poseStack, rotation, vertexCollector, packedLight, packedOverlay, bakedModel, b -> b.getVertices().isEmpty()); // Now store the bounds in the cache. modelBoundsCache.put(itemStack.getItem(), boundsFromVertices(vertexCollector.getVertices())); } return modelBoundsCache.get(itemStack.getItem()); } public void renderDetailModelIntoGUI(ItemStack stack, int x, int y, Quaternionf rotation) { Minecraft minecraft = Minecraft.getInstance(); BakedModel bakedModel = minecraft.getItemRenderer().getModel(stack, minecraft.level, minecraft.player, 0); blitOffset += 50.0f; try { renderGuiModel(stack, x, y, rotation, bakedModel); } catch (Throwable throwable) { CrashReport crashreport = CrashReport.forThrowable(throwable, "Rendering item"); CrashReportCategory crashreportcategory = crashreport.addCategory("Item being rendered"); crashreportcategory.setDetail("Item Type", () -> { return String.valueOf((Object)stack.getItem()); }); crashreportcategory.setDetail("Registry Name", () -> String.valueOf(BuiltInRegistries.ITEM.getKey(stack.getItem()))); crashreportcategory.setDetail("Item Damage", () -> { return String.valueOf(stack.getDamageValue()); }); crashreportcategory.setDetail("Item NBT", () -> { return String.valueOf((Object)stack.getTag()); }); crashreportcategory.setDetail("Item Foil", () -> { return String.valueOf(stack.hasFoil()); }); throw new ReportedException(crashreport); } blitOffset -= 50.0f; } public void renderItemModelIntoGUIWithAlpha(ItemStack stack, int x, int y, float alpha) { BakedModel bakedModel = mc.getItemRenderer().getModel(stack, null, null, 0); RenderTarget lastFrameBuffer = mc.getMainRenderTarget(); // Bind the icon framebuffer so we can render to texture. iconFrameBuffer.clear(Minecraft.ON_OSX); iconFrameBuffer.bindWrite(true); Matrix4f matrix = new Matrix4f(); matrix.setOrtho(0.0f, iconFrameBuffer.width, iconFrameBuffer.height, 0.0f, 1000.0f, 3000.0f); RenderSystem.clear(GL11.GL_DEPTH_BUFFER_BIT, Minecraft.ON_OSX); RenderSystem.backupProjectionMatrix(); RenderSystem.setProjectionMatrix(matrix); mc.getTextureManager().getTexture(InventoryMenu.BLOCK_ATLAS).setFilter(false, false); RenderSystem.setShaderTexture(0, InventoryMenu.BLOCK_ATLAS); RenderSystem.disableCull(); RenderSystem.enableBlend(); RenderSystem.blendFuncSeparate(SourceFactor.SRC_ALPHA, DestFactor.ONE_MINUS_SRC_ALPHA, SourceFactor.ONE, DestFactor.ONE_MINUS_SRC_ALPHA); RenderSystem.setShaderColor(1.0f, 1.0f, 1.0f, 1.0f); PoseStack modelViewStack = RenderSystem.getModelViewStack(); modelViewStack.pushPose(); modelViewStack.setIdentity(); modelViewStack.translate(48.0f, 48.0f, -2000.0f); modelViewStack.scale(96.0f, 96.0f, 96.0f); RenderSystem.applyModelViewMatrix(); PoseStack poseStack = new PoseStack(); BufferSource bufferSource = mc.renderBuffers().bufferSource(); boolean flatLighting = !bakedModel.usesBlockLight(); if (flatLighting) { Lighting.setupForFlatItems(); } render(stack, ItemTransforms.TransformType.GUI, false, poseStack, bufferSource, LightTexture.FULL_BRIGHT, OverlayTexture.NO_OVERLAY, bakedModel); bufferSource.endBatch(); RenderSystem.enableDepthTest(); if (flatLighting) { Lighting.setupFor3DItems(); } modelViewStack.popPose(); RenderSystem.applyModelViewMatrix(); RenderSystem.restoreProjectionMatrix(); // Rebind the previous framebuffer, if there was one. if (lastFrameBuffer != null) { lastFrameBuffer.bindWrite(true); // Blit from the texture we just rendered to, respecting the alpha value given. RenderSystem.enableBlend(); RenderSystem.defaultBlendFunc(); RenderSystem.disableCull(); RenderSystem.setShaderColor(1.0f, 1.0f, 1.0f, alpha); modelViewStack.pushPose(); modelViewStack.translate(0.0f, 0.0f, 50.0f + this.blitOffset); RenderSystem.applyModelViewMatrix(); RenderSystem.setShaderTexture(0, iconFrameBuffer.getColorTextureId()); GuiComponent.blit(new PoseStack(), x, y, 16, 16, 0, 0, iconFrameBuffer.width, iconFrameBuffer.height, iconFrameBuffer.width, iconFrameBuffer.height); modelViewStack.popPose(); RenderSystem.applyModelViewMatrix(); iconFrameBuffer.unbindRead(); } else { iconFrameBuffer.unbindWrite(); } } @Override public void onResourceManagerReload(ResourceManager resourceManager) { super.onResourceManagerReload(resourceManager); // Clear the model bounds cache. modelBoundsCache.clear(); } }