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 blockEntityWithoutLevelRenderer) { this.modelManager = modelManager; this.itemModelShaper = new ItemModelShaper(modelManager); this.blockEntityRenderer = blockEntityWithoutLevelRenderer; this.itemColors = itemColors; } private void renderModelLists(BakedModel model, ItemStack stack, int combinedLight, int combinedOverlay, 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, combinedLight, combinedOverlay); } randomSource.setSeed(42L); this.renderQuadList(poseStack, buffer, model.getQuads(null, null, randomSource), stack, combinedLight, combinedOverlay); } public void render( ItemStack itemStack, ItemDisplayContext displayContext, boolean leftHand, PoseStack poseStack, MultiBufferSource bufferSource, int combinedLight, int combinedOverlay, BakedModel model ) { if (!itemStack.isEmpty()) { this.renderSimpleItemModel( itemStack, displayContext, leftHand, poseStack, bufferSource, combinedLight, combinedOverlay, model, shouldRenderItemFlat(displayContext) ); } } public void renderBundleItem( ItemStack itemStack, ItemDisplayContext itemDisplayContext, boolean bl, PoseStack poseStack, MultiBufferSource multiBufferSource, int i, int j, BakedModel bakedModel, @Nullable Level level, @Nullable LivingEntity livingEntity, int k ) { if (itemStack.getItem() instanceof BundleItem bundleItem) { if (BundleItem.hasSelectedItem(itemStack)) { boolean bl2 = shouldRenderItemFlat(itemDisplayContext); BakedModel bakedModel2 = this.resolveModelOverride(this.itemModelShaper.getItemModel(bundleItem.openBackModel()), itemStack, level, livingEntity, k); this.renderItemModelRaw(itemStack, itemDisplayContext, bl, poseStack, multiBufferSource, i, j, bakedModel2, bl2, -1.5F); ItemStack itemStack2 = BundleItem.getSelectedItemStack(itemStack); BakedModel bakedModel3 = this.getModel(itemStack2, level, livingEntity, k); this.renderSimpleItemModel(itemStack2, itemDisplayContext, bl, poseStack, multiBufferSource, i, j, bakedModel3, bl2); BakedModel bakedModel4 = this.resolveModelOverride(this.itemModelShaper.getItemModel(bundleItem.openFrontModel()), itemStack, level, livingEntity, k); this.renderItemModelRaw(itemStack, itemDisplayContext, bl, poseStack, multiBufferSource, i, j, bakedModel4, bl2, 0.5F); } else { this.render(itemStack, itemDisplayContext, bl, poseStack, multiBufferSource, i, j, bakedModel); } } } private void renderSimpleItemModel( ItemStack itemStack, ItemDisplayContext itemDisplayContext, boolean bl, PoseStack poseStack, MultiBufferSource multiBufferSource, int i, int j, BakedModel bakedModel, boolean bl2 ) { if (bl2) { if (itemStack.is(Items.TRIDENT)) { bakedModel = this.modelManager.getModel(TRIDENT_MODEL); } else if (itemStack.is(Items.SPYGLASS)) { bakedModel = this.modelManager.getModel(SPYGLASS_MODEL); } } this.renderItemModelRaw(itemStack, itemDisplayContext, bl, poseStack, multiBufferSource, i, j, bakedModel, bl2, -0.5F); } private void renderItemModelRaw( ItemStack itemStack, ItemDisplayContext itemDisplayContext, boolean bl, PoseStack poseStack, MultiBufferSource multiBufferSource, int i, int j, BakedModel bakedModel, boolean bl2, float f ) { poseStack.pushPose(); bakedModel.getTransforms().getTransform(itemDisplayContext).apply(bl, poseStack); poseStack.translate(-0.5F, -0.5F, f); this.renderItem(itemStack, itemDisplayContext, poseStack, multiBufferSource, i, j, bakedModel, bl2); poseStack.popPose(); } private void renderItem( ItemStack itemStack, ItemDisplayContext itemDisplayContext, PoseStack poseStack, MultiBufferSource multiBufferSource, int i, int j, BakedModel bakedModel, boolean bl ) { if (!bakedModel.isCustomRenderer() && (!itemStack.is(Items.TRIDENT) || bl)) { RenderType renderType = ItemBlockRenderTypes.getRenderType(itemStack); VertexConsumer vertexConsumer; if (hasAnimatedTexture(itemStack) && itemStack.hasFoil()) { PoseStack.Pose pose = poseStack.last().copy(); if (itemDisplayContext == ItemDisplayContext.GUI) { MatrixUtil.mulComponentWise(pose.pose(), 0.5F); } else if (itemDisplayContext.firstPerson()) { MatrixUtil.mulComponentWise(pose.pose(), 0.75F); } vertexConsumer = getCompassFoilBuffer(multiBufferSource, renderType, pose); } else { vertexConsumer = getFoilBuffer(multiBufferSource, renderType, true, itemStack.hasFoil()); } this.renderModelLists(bakedModel, itemStack, i, j, poseStack, vertexConsumer); } else { this.blockEntityRenderer.renderByItem(itemStack, itemDisplayContext, poseStack, multiBufferSource, i, j); } } private static boolean shouldRenderItemFlat(ItemDisplayContext itemDisplayContext) { return itemDisplayContext == ItemDisplayContext.GUI || itemDisplayContext == ItemDisplayContext.GROUND || itemDisplayContext == 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 itemStack, LivingEntity livingEntity, ItemDisplayContext itemDisplayContext) { return itemStack.isEmpty() ? null : this.getModel(itemStack, livingEntity.level(), livingEntity, livingEntity.getId() + itemDisplayContext.ordinal()); } private BakedModel resolveModelOverride(BakedModel bakedModel, ItemStack itemStack, @Nullable Level level, @Nullable LivingEntity livingEntity, int i) { ClientLevel clientLevel = level instanceof ClientLevel ? (ClientLevel)level : null; BakedModel bakedModel2 = bakedModel.overrides().findOverride(itemStack, clientLevel, livingEntity, i); return bakedModel2 == null ? bakedModel : bakedModel2; } }