package net.minecraft.client.renderer.entity; import com.mojang.blaze3d.vertex.PoseStack; import com.mojang.blaze3d.vertex.SheetedDecalTextureGenerator; import com.mojang.blaze3d.vertex.VertexConsumer; import com.mojang.blaze3d.vertex.VertexMultiConsumer; import com.mojang.math.MatrixUtil; import java.util.List; import net.fabricmc.api.EnvType; import net.fabricmc.api.Environment; import net.minecraft.client.Minecraft; import net.minecraft.client.color.item.ItemColors; import net.minecraft.client.multiplayer.ClientLevel; import net.minecraft.client.renderer.BlockEntityWithoutLevelRenderer; import net.minecraft.client.renderer.ItemBlockRenderTypes; import net.minecraft.client.renderer.ItemModelShaper; import net.minecraft.client.renderer.MultiBufferSource; import net.minecraft.client.renderer.RenderType; import net.minecraft.client.renderer.Sheets; import net.minecraft.client.renderer.block.model.BakedQuad; import net.minecraft.client.resources.model.BakedModel; import net.minecraft.client.resources.model.ModelManager; import net.minecraft.client.resources.model.ModelResourceLocation; import net.minecraft.core.Direction; import net.minecraft.resources.ResourceLocation; import net.minecraft.server.packs.resources.ResourceManager; import net.minecraft.server.packs.resources.ResourceManagerReloadListener; import net.minecraft.tags.ItemTags; import net.minecraft.util.ARGB; import net.minecraft.util.RandomSource; import net.minecraft.world.entity.LivingEntity; import net.minecraft.world.item.BundleItem; import net.minecraft.world.item.ItemDisplayContext; import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.Items; import net.minecraft.world.level.Level; import org.jetbrains.annotations.Nullable; @Environment(EnvType.CLIENT) public class ItemRenderer implements ResourceManagerReloadListener { public static final ResourceLocation ENCHANTED_GLINT_ENTITY = ResourceLocation.withDefaultNamespace("textures/misc/enchanted_glint_entity.png"); public static final ResourceLocation ENCHANTED_GLINT_ITEM = ResourceLocation.withDefaultNamespace("textures/misc/enchanted_glint_item.png"); public static final int GUI_SLOT_CENTER_X = 8; public static final int GUI_SLOT_CENTER_Y = 8; public static final int ITEM_DECORATION_BLIT_OFFSET = 200; public static final float COMPASS_FOIL_UI_SCALE = 0.5F; public static final float COMPASS_FOIL_FIRST_PERSON_SCALE = 0.75F; public static final float COMPASS_FOIL_TEXTURE_SCALE = 0.0078125F; public static final ModelResourceLocation TRIDENT_MODEL = ModelResourceLocation.inventory(ResourceLocation.withDefaultNamespace("trident")); public static final ModelResourceLocation SPYGLASS_MODEL = ModelResourceLocation.inventory(ResourceLocation.withDefaultNamespace("spyglass")); private final ModelManager modelManager; private final ItemModelShaper itemModelShaper; private final ItemColors itemColors; private final BlockEntityWithoutLevelRenderer blockEntityRenderer; public ItemRenderer(ModelManager modelManager, ItemColors itemColors, BlockEntityWithoutLevelRenderer blockEntityRenderer) { this.modelManager = modelManager; this.itemModelShaper = new ItemModelShaper(modelManager); this.blockEntityRenderer = blockEntityRenderer; this.itemColors = itemColors; } private void renderModelLists(BakedModel model, ItemStack stack, int packedLight, int packedOverlay, PoseStack poseStack, VertexConsumer buffer) { RandomSource randomSource = RandomSource.create(); long l = 42L; for (Direction direction : Direction.values()) { randomSource.setSeed(42L); this.renderQuadList(poseStack, buffer, model.getQuads(null, direction, randomSource), stack, packedLight, packedOverlay); } randomSource.setSeed(42L); this.renderQuadList(poseStack, buffer, model.getQuads(null, null, randomSource), stack, packedLight, packedOverlay); } public void render( ItemStack itemStack, ItemDisplayContext displayContext, boolean leftHand, PoseStack poseStack, MultiBufferSource bufferSource, int packedLIght, int packedOverlay, BakedModel model ) { if (!itemStack.isEmpty()) { this.renderSimpleItemModel( itemStack, displayContext, leftHand, poseStack, bufferSource, packedLIght, packedOverlay, model, shouldRenderItemFlat(displayContext) ); } } public void renderBundleItem( ItemStack itemStack, ItemDisplayContext displayContext, boolean leftHand, PoseStack poseStack, MultiBufferSource bufferSource, int packedLight, int packedOverlay, BakedModel model, @Nullable Level level, @Nullable LivingEntity entity, int seed ) { if (itemStack.getItem() instanceof BundleItem bundleItem) { if (BundleItem.hasSelectedItem(itemStack)) { boolean bl = shouldRenderItemFlat(displayContext); BakedModel bakedModel = this.resolveModelOverride(this.itemModelShaper.getItemModel(bundleItem.openBackModel()), itemStack, level, entity, seed); this.renderItemModelRaw(itemStack, displayContext, leftHand, poseStack, bufferSource, packedLight, packedOverlay, bakedModel, bl, -1.5F); ItemStack itemStack2 = BundleItem.getSelectedItemStack(itemStack); BakedModel bakedModel2 = this.getModel(itemStack2, level, entity, seed); this.renderSimpleItemModel(itemStack2, displayContext, leftHand, poseStack, bufferSource, packedLight, packedOverlay, bakedModel2, bl); BakedModel bakedModel3 = this.resolveModelOverride(this.itemModelShaper.getItemModel(bundleItem.openFrontModel()), itemStack, level, entity, seed); this.renderItemModelRaw(itemStack, displayContext, leftHand, poseStack, bufferSource, packedLight, packedOverlay, bakedModel3, bl, 0.5F); } else { this.render(itemStack, displayContext, leftHand, poseStack, bufferSource, packedLight, packedOverlay, model); } } } private void renderSimpleItemModel( ItemStack itemStack, ItemDisplayContext displayContext, boolean leftHand, PoseStack poseStack, MultiBufferSource bufferSource, int packedLight, int packedOverlay, BakedModel model, boolean renderOpenBundle ) { if (renderOpenBundle) { if (itemStack.is(Items.TRIDENT)) { model = this.modelManager.getModel(TRIDENT_MODEL); } else if (itemStack.is(Items.SPYGLASS)) { model = this.modelManager.getModel(SPYGLASS_MODEL); } } this.renderItemModelRaw(itemStack, displayContext, leftHand, poseStack, bufferSource, packedLight, packedOverlay, model, renderOpenBundle, -0.5F); } private void renderItemModelRaw( ItemStack itemStack, ItemDisplayContext displayContext, boolean leftHand, PoseStack poseStack, MultiBufferSource bufferSource, int packedLight, int packedOverlay, BakedModel model, boolean renderOpenBundle, float z ) { poseStack.pushPose(); model.getTransforms().getTransform(displayContext).apply(leftHand, poseStack); poseStack.translate(-0.5F, -0.5F, z); this.renderItem(itemStack, displayContext, poseStack, bufferSource, packedLight, packedOverlay, model, renderOpenBundle); poseStack.popPose(); } private void renderItem( ItemStack itemStack, ItemDisplayContext displayContext, PoseStack poseStack, MultiBufferSource bufferSource, int packedLight, int packedOverlay, BakedModel model, boolean renderOpenBundle ) { if (!model.isCustomRenderer() && (!itemStack.is(Items.TRIDENT) || renderOpenBundle)) { RenderType renderType = ItemBlockRenderTypes.getRenderType(itemStack); VertexConsumer vertexConsumer; if (hasAnimatedTexture(itemStack) && itemStack.hasFoil()) { PoseStack.Pose pose = poseStack.last().copy(); if (displayContext == ItemDisplayContext.GUI) { MatrixUtil.mulComponentWise(pose.pose(), 0.5F); } else if (displayContext.firstPerson()) { MatrixUtil.mulComponentWise(pose.pose(), 0.75F); } vertexConsumer = getCompassFoilBuffer(bufferSource, renderType, pose); } else { vertexConsumer = getFoilBuffer(bufferSource, renderType, true, itemStack.hasFoil()); } this.renderModelLists(model, itemStack, packedLight, packedOverlay, poseStack, vertexConsumer); } else { this.blockEntityRenderer.renderByItem(itemStack, displayContext, poseStack, bufferSource, packedLight, packedOverlay); } } private static boolean shouldRenderItemFlat(ItemDisplayContext displayContent) { return displayContent == ItemDisplayContext.GUI || displayContent == ItemDisplayContext.GROUND || displayContent == ItemDisplayContext.FIXED; } private static boolean hasAnimatedTexture(ItemStack stack) { return stack.is(ItemTags.COMPASSES) || stack.is(Items.CLOCK); } public static VertexConsumer getArmorFoilBuffer(MultiBufferSource bufferSource, RenderType renderType, boolean hasFoil) { return hasFoil ? VertexMultiConsumer.create(bufferSource.getBuffer(RenderType.armorEntityGlint()), bufferSource.getBuffer(renderType)) : bufferSource.getBuffer(renderType); } public static VertexConsumer getCompassFoilBuffer(MultiBufferSource bufferSource, RenderType renderType, PoseStack.Pose pose) { return VertexMultiConsumer.create( new SheetedDecalTextureGenerator(bufferSource.getBuffer(RenderType.glint()), pose, 0.0078125F), bufferSource.getBuffer(renderType) ); } public static VertexConsumer getFoilBuffer(MultiBufferSource bufferSource, RenderType renderType, boolean isItem, boolean glint) { if (glint) { return Minecraft.useShaderTransparency() && renderType == Sheets.translucentItemSheet() ? VertexMultiConsumer.create(bufferSource.getBuffer(RenderType.glintTranslucent()), bufferSource.getBuffer(renderType)) : VertexMultiConsumer.create(bufferSource.getBuffer(isItem ? RenderType.glint() : RenderType.entityGlint()), bufferSource.getBuffer(renderType)); } else { return bufferSource.getBuffer(renderType); } } private void renderQuadList(PoseStack poseStack, VertexConsumer buffer, List quads, ItemStack itemStack, int combinedLight, int combinedOverlay) { boolean bl = !itemStack.isEmpty(); PoseStack.Pose pose = poseStack.last(); for (BakedQuad bakedQuad : quads) { int i = -1; if (bl && bakedQuad.isTinted()) { i = this.itemColors.getColor(itemStack, bakedQuad.getTintIndex()); } float f = ARGB.alpha(i) / 255.0F; float g = ARGB.red(i) / 255.0F; float h = ARGB.green(i) / 255.0F; float j = ARGB.blue(i) / 255.0F; buffer.putBulkData(pose, bakedQuad, g, h, j, f, combinedLight, combinedOverlay); } } public BakedModel getModel(ItemStack stack, @Nullable Level level, @Nullable LivingEntity entity, int seed) { BakedModel bakedModel = this.itemModelShaper.getItemModel(stack); return this.resolveModelOverride(bakedModel, stack, level, entity, seed); } public void renderStatic( ItemStack stack, ItemDisplayContext displayContext, int combinedLight, int combinedOverlay, PoseStack poseStack, MultiBufferSource bufferSource, @Nullable Level level, int seed ) { this.renderStatic(null, stack, displayContext, false, poseStack, bufferSource, level, combinedLight, combinedOverlay, seed); } public void renderStatic( @Nullable LivingEntity entity, ItemStack itemStack, ItemDisplayContext diplayContext, boolean leftHand, PoseStack poseStack, MultiBufferSource bufferSource, @Nullable Level level, int combinedLight, int combinedOverlay, int seed ) { if (!itemStack.isEmpty()) { BakedModel bakedModel = this.getModel(itemStack, level, entity, seed); this.render(itemStack, diplayContext, leftHand, poseStack, bufferSource, combinedLight, combinedOverlay, bakedModel); } } @Override public void onResourceManagerReload(ResourceManager resourceManager) { this.itemModelShaper.invalidateCache(); } @Nullable public BakedModel resolveItemModel(ItemStack stack, LivingEntity entity, ItemDisplayContext displayContext) { return stack.isEmpty() ? null : this.getModel(stack, entity.level(), entity, entity.getId() + displayContext.ordinal()); } private BakedModel resolveModelOverride(BakedModel model, ItemStack stack, @Nullable Level level, @Nullable LivingEntity entity, int seed) { ClientLevel clientLevel = level instanceof ClientLevel ? (ClientLevel)level : null; BakedModel bakedModel = model.overrides().findOverride(stack, clientLevel, entity, seed); return bakedModel == null ? model : bakedModel; } }