From b0d1aa6d5076738965f1f0fd9df98c6cd5bf946f Mon Sep 17 00:00:00 2001 From: Anthony Hilyard Date: Sat, 14 Jan 2023 16:03:02 -0800 Subject: Ported 1.1.4 changes from Forge. --- .../com/anthonyhilyard/iceberg/IcebergClient.java | 32 + .../iceberg/events/RenderTooltipEvents.java | 4 +- .../anthonyhilyard/iceberg/mixin/ScreenMixin.java | 166 +++--- .../iceberg/mixin/TooltipRenderUtilMixin.java | 120 ++++ .../iceberg/renderer/CheckedBufferSource.java | 68 +++ .../iceberg/renderer/CustomItemRenderer.java | 656 ++++++++++++++++++++- .../iceberg/renderer/VertexCollector.java | 79 +++ .../iceberg/util/DynamicResourcePack.java | 59 +- .../com/anthonyhilyard/iceberg/util/GuiHelper.java | 5 +- .../com/anthonyhilyard/iceberg/util/Selectors.java | 97 ++- .../com/anthonyhilyard/iceberg/util/Tooltips.java | 286 ++++++--- src/main/resources/fabric.mod.json | 4 +- src/main/resources/iceberg.accesswidener | 9 +- src/main/resources/iceberg.mixins.json | 5 +- 14 files changed, 1339 insertions(+), 251 deletions(-) create mode 100644 src/main/java/com/anthonyhilyard/iceberg/mixin/TooltipRenderUtilMixin.java create mode 100644 src/main/java/com/anthonyhilyard/iceberg/renderer/CheckedBufferSource.java create mode 100644 src/main/java/com/anthonyhilyard/iceberg/renderer/VertexCollector.java (limited to 'src') diff --git a/src/main/java/com/anthonyhilyard/iceberg/IcebergClient.java b/src/main/java/com/anthonyhilyard/iceberg/IcebergClient.java index 8218f71..8e3c31b 100644 --- a/src/main/java/com/anthonyhilyard/iceberg/IcebergClient.java +++ b/src/main/java/com/anthonyhilyard/iceberg/IcebergClient.java @@ -1,12 +1,26 @@ package com.anthonyhilyard.iceberg; +import java.util.List; + +import com.anthonyhilyard.iceberg.events.RenderTooltipEvents; +import com.anthonyhilyard.iceberg.events.RenderTooltipEvents.GatherResult; +import com.anthonyhilyard.iceberg.util.Tooltips.TitleBreakComponent; +import com.mojang.datafixers.util.Either; + import net.fabricmc.api.ClientModInitializer; +import net.minecraft.network.chat.FormattedText; +import net.minecraft.world.InteractionResult; +import net.minecraft.world.inventory.tooltip.TooltipComponent; +import net.minecraft.world.item.ItemStack; public class IcebergClient implements ClientModInitializer { @Override public void onInitializeClient() { + TitleBreakComponent.registerFactory(); + RenderTooltipEvents.GATHER.register(IcebergClient::onGatherComponentsEventEnd); + // Event testing. // CriterionCallback.EVENT.register((player, advancement, criterion) -> { Loader.LOGGER.info("CriterionCallback: {}, {}, {}", player.getName().getString(), advancement.getId().toString(), criterion); }); // NewItemPickupCallback.EVENT.register((uuid, itemStack) -> { Loader.LOGGER.info("NewItemPickupCallback: {}, {}", uuid.toString(), itemStack.getDisplayName().getString()); }); @@ -23,4 +37,22 @@ public class IcebergClient implements ClientModInitializer // Loader.LOGGER.info("RenderTooltipEvents.POST: {}, {}, {}, {}, {}, {}, {}, {}, {}", stack.getDisplayName().getString(), components.stream().map(Object::toString).collect(Collectors.joining()), poseStack, x, y, font, width, height, comparison); // }); } + + public static GatherResult onGatherComponentsEventEnd(ItemStack itemStack, int screenWidth, int screenHeight, List> tooltipElements, int maxWidth, int index) + { + if (tooltipElements.size() > 1) + { + // Insert a title break component after the first formattedText component. + for (int i = 0; i < tooltipElements.size(); i++) + { + if (tooltipElements.get(i).left().isPresent()) + { + tooltipElements.add(i + 1, Either.right(new TitleBreakComponent())); + break; + } + } + } + + return new GatherResult(InteractionResult.PASS, maxWidth, tooltipElements); + } } diff --git a/src/main/java/com/anthonyhilyard/iceberg/events/RenderTooltipEvents.java b/src/main/java/com/anthonyhilyard/iceberg/events/RenderTooltipEvents.java index 622d3e0..c5a10a8 100644 --- a/src/main/java/com/anthonyhilyard/iceberg/events/RenderTooltipEvents.java +++ b/src/main/java/com/anthonyhilyard/iceberg/events/RenderTooltipEvents.java @@ -23,7 +23,7 @@ public final class RenderTooltipEvents GatherResult result = new GatherResult(InteractionResult.PASS, maxWidth, tooltipElements); for (RenderTooltipEvents.Gather callback : callbacks) { - result = callback.onGather(itemStack, screenWidth, screenHeight, tooltipElements, maxWidth, index); + result = callback.onGather(itemStack, screenWidth, screenHeight, result.tooltipElements, result.maxWidth, index); if (result.result != InteractionResult.PASS) { @@ -38,7 +38,7 @@ public final class RenderTooltipEvents PreExtResult result = new PreExtResult(InteractionResult.PASS, x, y, screenWidth, screenHeight, font); for (RenderTooltipEvents.PreExt callback : callbacks) { - result = callback.onPre(stack, components, poseStack, x, y, screenWidth, screenHeight, font, comparison, index); + result = callback.onPre(stack, components, poseStack, result.x, result.y, screenWidth, screenHeight, result.font, comparison, index); if (result.result != InteractionResult.PASS) { diff --git a/src/main/java/com/anthonyhilyard/iceberg/mixin/ScreenMixin.java b/src/main/java/com/anthonyhilyard/iceberg/mixin/ScreenMixin.java index a797c9f..bc3674f 100644 --- a/src/main/java/com/anthonyhilyard/iceberg/mixin/ScreenMixin.java +++ b/src/main/java/com/anthonyhilyard/iceberg/mixin/ScreenMixin.java @@ -9,16 +9,12 @@ import com.anthonyhilyard.iceberg.events.RenderTooltipEvents.ColorResult; import com.anthonyhilyard.iceberg.events.RenderTooltipEvents.PreExtResult; import com.anthonyhilyard.iceberg.util.Tooltips; import com.google.common.collect.Lists; -import com.mojang.blaze3d.vertex.BufferBuilder; import com.mojang.blaze3d.vertex.PoseStack; -import com.mojang.blaze3d.vertex.Tesselator; -import com.mojang.math.Matrix4f; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Shadow; import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Inject; -import org.spongepowered.asm.mixin.injection.Redirect; import org.spongepowered.asm.mixin.injection.At.Shift; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; import org.spongepowered.asm.mixin.injection.callback.LocalCapture; @@ -29,9 +25,11 @@ import net.minecraft.client.gui.components.events.GuiEventListener; import net.minecraft.client.gui.screens.Screen; import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen; import net.minecraft.client.gui.screens.inventory.tooltip.ClientTooltipComponent; +import net.minecraft.client.gui.screens.inventory.tooltip.ClientTooltipPositioner; +import net.minecraft.client.gui.screens.inventory.tooltip.TooltipRenderUtil; +import net.minecraft.network.chat.TextColor; import net.minecraft.network.chat.Component; import net.minecraft.world.InteractionResult; -import net.minecraft.world.inventory.AbstractContainerMenu; import net.minecraft.world.inventory.Slot; import net.minecraft.world.inventory.tooltip.TooltipComponent; import net.minecraft.world.item.ItemStack; @@ -60,12 +58,24 @@ public class ScreenMixin extends AbstractContainerEventHandler } @Inject(method = "renderTooltip(Lcom/mojang/blaze3d/vertex/PoseStack;Ljava/util/List;Ljava/util/Optional;II)V", - at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/screens/Screen;renderTooltipInternal(Lcom/mojang/blaze3d/vertex/PoseStack;Ljava/util/List;II)V"), - locals = LocalCapture.CAPTURE_FAILEXCEPTION) + at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/screens/Screen;renderTooltipInternal(Lcom/mojang/blaze3d/vertex/PoseStack;Ljava/util/List;IILnet/minecraft/client/gui/screens/inventory/tooltip/ClientTooltipPositioner;)V", + shift = Shift.BEFORE), locals = LocalCapture.CAPTURE_FAILEXCEPTION) public void renderTooltip(PoseStack poseStack, List textComponents, Optional itemComponent, int x, int y, CallbackInfo info, List components) { Screen self = (Screen)(Object)this; + if (self instanceof AbstractContainerScreen containerScreen) + { + Slot hoveredSlot = containerScreen.hoveredSlot; + + // If the tooltip stack is empty, try to get the stack from the slot under the mouse. + // This is needed for the creative inventory screen, which doesn't set the tooltip stack. + if (tooltipStack.isEmpty() && hoveredSlot != null) + { + tooltipStack = hoveredSlot.getItem(); + } + } + List newComponents = Tooltips.gatherTooltipComponents(tooltipStack, textComponents, itemComponent, x, self.width, self.height, null, font, -1); if (newComponents != null && !newComponents.isEmpty()) { @@ -74,93 +84,65 @@ public class ScreenMixin extends AbstractContainerEventHandler } } - @SuppressWarnings({"unchecked", "deprecation"}) + @SuppressWarnings("deprecation") @Inject(method = "renderTooltipInternal", at = @At(value = "HEAD"), cancellable = true) - private void preRenderTooltipInternal(PoseStack poseStack, List components, int x, int y, CallbackInfo info) + private void preRenderTooltipInternal(PoseStack poseStack, List components, int x, int y, ClientTooltipPositioner positioner, CallbackInfo info) { - PreExtResult eventResult = null; Screen self = (Screen)(Object)this; - if (self instanceof AbstractContainerScreen) + + if (!components.isEmpty()) { - if (!components.isEmpty()) + PreExtResult eventResult = null; + InteractionResult result = InteractionResult.PASS; + + eventResult = RenderTooltipEvents.PREEXT.invoker().onPre(tooltipStack, components, poseStack, x, y, self.width, self.height, font, false, 0); + result = eventResult.result(); + + if (result != InteractionResult.PASS) { - Slot hoveredSlot = ((AbstractContainerScreen)self).hoveredSlot; - if (hoveredSlot != null) - { - ItemStack tooltipStack = hoveredSlot.getItem(); - InteractionResult result = InteractionResult.PASS; - eventResult = RenderTooltipEvents.PREEXT.invoker().onPre(tooltipStack, components, poseStack, x, y, self.width, self.height, font, false, 0); - result = eventResult.result(); - - if (result != InteractionResult.PASS) - { - info.cancel(); - } - - // Fire a pre event as well for compatibility. - result = RenderTooltipEvents.PRE.invoker().onPre(tooltipStack, components, poseStack, x, y, self.width, self.height, -1, font, false); - if (result != InteractionResult.PASS) - { - info.cancel(); - } - } + info.cancel(); } - } - } - @SuppressWarnings("unchecked") - @Redirect(method = "renderTooltipInternal", at = @At(value = "INVOKE", - target = "Lnet/minecraft/client/gui/screens/Screen;fillGradient(Lcom/mojang/math/Matrix4f;Lcom/mojang/blaze3d/vertex/BufferBuilder;IIIIIII)V")) - private void fillGradientProxy(Matrix4f matrix4f, BufferBuilder bufferBuilder, int left, int top, int right, int bottom, int zIndex, int colorStart, int colorEnd) - { - Screen self = (Screen)(Object)this; - ItemStack tooltipStack = ItemStack.EMPTY; - if (self instanceof AbstractContainerScreen) - { - Slot hoveredSlot = ((AbstractContainerScreen)self).hoveredSlot; - - if (hoveredSlot != null) + // Fire a pre event as well for compatibility. + result = RenderTooltipEvents.PRE.invoker().onPre(tooltipStack, components, poseStack, x, y, self.width, self.height, -1, font, false); + if (result != InteractionResult.PASS) { - tooltipStack = hoveredSlot.getItem(); + info.cancel(); } } - if (tooltipStack == ItemStack.EMPTY) - { - // Do standard functionality if this isn't a container screen. - Screen.fillGradient(matrix4f, bufferBuilder, left, top, right, bottom, zIndex, colorStart, colorEnd); - } - else - { - // Otherwise do nothing to disable the default calls. - } } - @SuppressWarnings({"unchecked", "deprecation"}) - @Inject(method = "renderTooltipInternal", at = @At(value = "INVOKE", - target = "Lnet/minecraft/client/gui/screens/Screen;fillGradient(Lcom/mojang/math/Matrix4f;Lcom/mojang/blaze3d/vertex/BufferBuilder;IIIIIII)V", ordinal = 0, shift = Shift.BEFORE), - locals = LocalCapture.CAPTURE_FAILEXCEPTION) - private void preFillGradient(PoseStack poseStack, List components, int x, int y, CallbackInfo info, - int __, int ___, int left, int top, int width, int height, int background, int borderStart, int borderEnd, - int zIndex, float blitOffset, Tesselator tesselator, BufferBuilder bufferBuilder, Matrix4f matrix4f) + @SuppressWarnings("deprecation") + @Inject(method = "renderTooltipInternal", + at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/screens/inventory/tooltip/TooltipRenderUtil;renderTooltipBackground(Lnet/minecraft/client/gui/screens/inventory/tooltip/TooltipRenderUtil$BlitPainter;Lorg/joml/Matrix4f;Lcom/mojang/blaze3d/vertex/BufferBuilder;IIIII)V", + ordinal = 0, shift = Shift.BEFORE)) + private void preFillGradient(PoseStack poseStack, List components, int x, int y, ClientTooltipPositioner positioner, CallbackInfo info) { Screen self = (Screen)(Object)this; - ItemStack tooltipStack = ItemStack.EMPTY; - if (self instanceof AbstractContainerScreen) + ItemStack containerStack = ItemStack.EMPTY; + if (self instanceof AbstractContainerScreen containerScreen) { - Slot hoveredSlot = ((AbstractContainerScreen)self).hoveredSlot; - + Slot hoveredSlot = containerScreen.hoveredSlot; if (hoveredSlot != null) { - tooltipStack = hoveredSlot.getItem(); + containerStack = hoveredSlot.getItem(); } } - if (tooltipStack != ItemStack.EMPTY) + if (containerStack.isEmpty()) { + containerStack = tooltipStack; + } + + if (!containerStack.isEmpty()) + { + int background = TooltipRenderUtil.BACKGROUND_COLOR; int backgroundEnd = background; + int borderStart = TooltipRenderUtil.BORDER_COLOR_TOP; + int borderEnd = TooltipRenderUtil.BORDER_COLOR_BOTTOM; // Do colors now, sure why not. - ColorExtResult result = RenderTooltipEvents.COLOREXT.invoker().onColor(tooltipStack, components, poseStack, x, y, font, background, backgroundEnd, borderStart, borderEnd, false, 0); + ColorExtResult result = RenderTooltipEvents.COLOREXT.invoker().onColor(containerStack, components, poseStack, x, y, font, background, backgroundEnd, borderStart, borderEnd, false, 0); if (result != null) { background = result.backgroundStart(); @@ -170,7 +152,7 @@ public class ScreenMixin extends AbstractContainerEventHandler } // Fire a colors event as well for compatibility. - ColorResult colorResult = RenderTooltipEvents.COLOR.invoker().onColor(tooltipStack, components, poseStack, x, y, font, background, borderStart, borderEnd, false); + ColorResult colorResult = RenderTooltipEvents.COLOR.invoker().onColor(containerStack, components, poseStack, x, y, font, background, borderStart, borderEnd, false); if (colorResult != null) { background = colorResult.background(); @@ -178,35 +160,37 @@ public class ScreenMixin extends AbstractContainerEventHandler borderEnd = colorResult.borderEnd(); } - Screen.fillGradient(matrix4f, bufferBuilder, left - 3, top - 4, left + width + 3, top - 3, zIndex, background, background); - Screen.fillGradient(matrix4f, bufferBuilder, left - 3, top + height + 3, left + width + 3, top + height + 4, zIndex, backgroundEnd, backgroundEnd); - Screen.fillGradient(matrix4f, bufferBuilder, left - 3, top - 3, left + width + 3, top + height + 3, zIndex, background, backgroundEnd); - Screen.fillGradient(matrix4f, bufferBuilder, left - 4, top - 3, left - 3, top + height + 3, zIndex, background, backgroundEnd); - Screen.fillGradient(matrix4f, bufferBuilder, left + width + 3, top - 3, left + width + 4, top + height + 3, zIndex, background, backgroundEnd); - Screen.fillGradient(matrix4f, bufferBuilder, left - 3, top - 3 + 1, left - 3 + 1, top + height + 3 - 1, zIndex, borderStart, borderEnd); - Screen.fillGradient(matrix4f, bufferBuilder, left + width + 2, top - 3 + 1, left + width + 3, top + height + 3 - 1, zIndex, borderStart, borderEnd); - Screen.fillGradient(matrix4f, bufferBuilder, left - 3, top - 3, left + width + 3, top - 3 + 1, zIndex, borderStart, borderStart); - Screen.fillGradient(matrix4f, bufferBuilder, left - 3, top + height + 2, left + width + 3, top + height + 3, zIndex, borderEnd, borderEnd); + Tooltips.currentColors = new Tooltips.TooltipColors(TextColor.fromRgb(background), TextColor.fromRgb(backgroundEnd), TextColor.fromRgb(borderStart), TextColor.fromRgb(borderEnd)); } } - @SuppressWarnings({"unchecked", "deprecation"}) + @SuppressWarnings("deprecation") @Inject(method = "renderTooltipInternal", at = @At(value = "TAIL"), locals = LocalCapture.CAPTURE_FAILEXCEPTION) - private void renderTooltipInternal(PoseStack poseStack, List components, int x, int y, CallbackInfo info, int tooltipWidth, int tooltipHeight, int postX, int postY) + private void renderTooltipInternal(PoseStack poseStack, List components, int x, int y, ClientTooltipPositioner positioner, CallbackInfo info, int tooltipWidth, int tooltipHeight, int postX, int postY) { - if ((Screen)(Object)this instanceof AbstractContainerScreen) + Screen self = (Screen)(Object)this; + ItemStack containerStack = ItemStack.EMPTY; + if (self instanceof AbstractContainerScreen containerScreen) { - if (!components.isEmpty()) + Slot hoveredSlot = containerScreen.hoveredSlot; + if (hoveredSlot != null) { - Slot hoveredSlot = ((AbstractContainerScreen)(Object)this).hoveredSlot; - if (hoveredSlot != null) - { - ItemStack tooltipStack = hoveredSlot.getItem(); - RenderTooltipEvents.POSTEXT.invoker().onPost(tooltipStack, components, poseStack, postX, postY, font, tooltipWidth, tooltipHeight, false, 0); - RenderTooltipEvents.POST.invoker().onPost(tooltipStack, components, poseStack, postX, postY, font, tooltipWidth, tooltipHeight, false); - } + containerStack = hoveredSlot.getItem(); } } + + if (containerStack.isEmpty()) + { + containerStack = tooltipStack; + } + + if (!containerStack.isEmpty() && !components.isEmpty()) + { + RenderTooltipEvents.POSTEXT.invoker().onPost(containerStack, components, poseStack, postX, postY, font, tooltipWidth, tooltipHeight, false, 0); + RenderTooltipEvents.POST.invoker().onPost(containerStack, components, poseStack, postX, postY, font, tooltipWidth, tooltipHeight, false); + } + + tooltipStack = ItemStack.EMPTY; } @Override diff --git a/src/main/java/com/anthonyhilyard/iceberg/mixin/TooltipRenderUtilMixin.java b/src/main/java/com/anthonyhilyard/iceberg/mixin/TooltipRenderUtilMixin.java new file mode 100644 index 0000000..18ef9e4 --- /dev/null +++ b/src/main/java/com/anthonyhilyard/iceberg/mixin/TooltipRenderUtilMixin.java @@ -0,0 +1,120 @@ +package com.anthonyhilyard.iceberg.mixin; + +import org.joml.Matrix4f; +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.Unique; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +import com.anthonyhilyard.iceberg.util.Tooltips; +import com.mojang.blaze3d.vertex.BufferBuilder; + +import net.minecraft.client.gui.screens.inventory.tooltip.TooltipRenderUtil; +import net.minecraft.network.chat.TextColor; + +@Mixin(TooltipRenderUtil.class) +public class TooltipRenderUtilMixin +{ + @Unique + private static TextColor horizontalLineColor; + + @Shadow + @Final + private static int BACKGROUND_COLOR; + + @Shadow + @Final + private static int BORDER_COLOR_TOP; + + @Shadow + @Final + private static int BORDER_COLOR_BOTTOM; + + @Inject(method = "renderTooltipBackground", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/screens/inventory/tooltip/TooltipRenderUtil;renderHorizontalLine(Lnet/minecraft/client/gui/screens/inventory/tooltip/TooltipRenderUtil$BlitPainter;Lorg/joml/Matrix4f;Lcom/mojang/blaze3d/vertex/BufferBuilder;IIIII)V", shift = At.Shift.BEFORE, ordinal = 0)) + private static void icebergRenderTooltipBackgroundOne(TooltipRenderUtil.BlitPainter painter, Matrix4f matrix, BufferBuilder bufferbuilder, int x, int y, int width, int height, int z, CallbackInfo info) + { + horizontalLineColor = Tooltips.currentColors.backgroundColorStart(); + } + + @Inject(method = "renderTooltipBackground", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/screens/inventory/tooltip/TooltipRenderUtil;renderHorizontalLine(Lnet/minecraft/client/gui/screens/inventory/tooltip/TooltipRenderUtil$BlitPainter;Lorg/joml/Matrix4f;Lcom/mojang/blaze3d/vertex/BufferBuilder;IIIII)V", shift = At.Shift.BEFORE, ordinal = 1)) + private static void icebergRenderTooltipBackgroundTwo(TooltipRenderUtil.BlitPainter painter, Matrix4f matrix, BufferBuilder bufferbuilder, int x, int y, int width, int height, int z, CallbackInfo info) + { + horizontalLineColor = Tooltips.currentColors.backgroundColorEnd(); + } + + @Inject(method = "renderFrameGradient", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/screens/inventory/tooltip/TooltipRenderUtil;renderHorizontalLine(Lnet/minecraft/client/gui/screens/inventory/tooltip/TooltipRenderUtil$BlitPainter;Lorg/joml/Matrix4f;Lcom/mojang/blaze3d/vertex/BufferBuilder;IIIII)V", shift = At.Shift.BEFORE, ordinal = 0)) + private static void icebergRenderFrameGradientOne(TooltipRenderUtil.BlitPainter painter, Matrix4f matrix, BufferBuilder bufferbuilder, int x, int y, int width, int height, int z, int color1, int color2, CallbackInfo info) + { + horizontalLineColor = Tooltips.currentColors.borderColorStart(); + } + + @Inject(method = "renderFrameGradient", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/screens/inventory/tooltip/TooltipRenderUtil;renderHorizontalLine(Lnet/minecraft/client/gui/screens/inventory/tooltip/TooltipRenderUtil$BlitPainter;Lorg/joml/Matrix4f;Lcom/mojang/blaze3d/vertex/BufferBuilder;IIIII)V", shift = At.Shift.BEFORE, ordinal = 1)) + private static void icebergRenderFrameGradientTwo(TooltipRenderUtil.BlitPainter painter, Matrix4f matrix, BufferBuilder bufferbuilder, int x, int y, int width, int height, int z, int color1, int color2, CallbackInfo info) + { + horizontalLineColor = Tooltips.currentColors.borderColorEnd(); + } + + @Inject(method = "renderHorizontalLine", at = @At(value = "HEAD"), cancellable = true) + private static void icebergRenderHorizontalLine(TooltipRenderUtil.BlitPainter painter, Matrix4f matrix, BufferBuilder bufferbuilder, int x, int y, int width, int z, int color, CallbackInfo info) + { + if (color != BACKGROUND_COLOR && color != BORDER_COLOR_TOP && color != BORDER_COLOR_BOTTOM) + { + // Do default behavior so other mods that change colors can still work. + } + else + { + // Replace the rendered colors with the ones previously stored. + int renderColor = horizontalLineColor.getValue(); + painter.blit(matrix, bufferbuilder, x, y, x + width, y + 1, z, renderColor, renderColor); + info.cancel(); + } + } + + @Inject(method = "renderRectangle", at = @At(value = "HEAD"), cancellable = true) + private static void icebergRenderRectangle(TooltipRenderUtil.BlitPainter painter, Matrix4f matrix, BufferBuilder bufferbuilder, int x, int y, int width, int height, int z, int color, CallbackInfo info) + { + if (color != BACKGROUND_COLOR) + { + // Do default behavior so other mods that change colors can still work. + } + else + { + // Replace the rendered colors with the ones previously stored. + painter.blit(matrix, bufferbuilder, x, y, x + width, y + height, z, Tooltips.currentColors.backgroundColorStart().getValue(), Tooltips.currentColors.backgroundColorEnd().getValue()); + info.cancel(); + } + } + + @Inject(method = "renderVerticalLine", at = @At(value = "HEAD"), cancellable = true) + private static void icebergRenderVerticalLine(TooltipRenderUtil.BlitPainter painter, Matrix4f matrix, BufferBuilder bufferbuilder, int x, int y, int height, int z, int color, CallbackInfo info) + { + if (color != BACKGROUND_COLOR) + { + // Do default behavior so other mods that change colors can still work. + } + else + { + // Replace the rendered colors with the ones previously stored. + painter.blit(matrix, bufferbuilder, x, y, x + 1, y + height, z, Tooltips.currentColors.backgroundColorStart().getValue(), Tooltips.currentColors.backgroundColorEnd().getValue()); + info.cancel(); + } + } + + @Inject(method = "renderVerticalLineGradient", at = @At(value = "HEAD"), cancellable = true) + private static void icebergRenderVerticalLineGradient(TooltipRenderUtil.BlitPainter painter, Matrix4f matrix, BufferBuilder bufferbuilder, int x, int y, int height, int z, int startColor, int endColor, CallbackInfo info) + { + if (startColor != BORDER_COLOR_TOP || endColor != BORDER_COLOR_BOTTOM) + { + // Do default behavior so other mods that change colors can still work. + } + else + { + // Replace the rendered colors with the ones previously stored. + painter.blit(matrix, bufferbuilder, x, y, x + 1, y + height, z, Tooltips.currentColors.borderColorStart().getValue(), Tooltips.currentColors.borderColorEnd().getValue()); + info.cancel(); + } + } +} \ No newline at end of file diff --git a/src/main/java/com/anthonyhilyard/iceberg/renderer/CheckedBufferSource.java b/src/main/java/com/anthonyhilyard/iceberg/renderer/CheckedBufferSource.java new file mode 100644 index 0000000..b8f5fde --- /dev/null +++ b/src/main/java/com/anthonyhilyard/iceberg/renderer/CheckedBufferSource.java @@ -0,0 +1,68 @@ +package com.anthonyhilyard.iceberg.renderer; + +import com.mojang.blaze3d.vertex.VertexConsumer; + +import net.minecraft.client.renderer.MultiBufferSource; +import net.minecraft.client.renderer.RenderType; + +public final class CheckedBufferSource implements MultiBufferSource +{ + private boolean hasRendered = false; + private final MultiBufferSource bufferSource; + + public CheckedBufferSource(MultiBufferSource bufferSource) + { + this.bufferSource = bufferSource; + } + + @Override + public VertexConsumer getBuffer(RenderType renderType) + { + final VertexConsumer vertexConsumer = bufferSource.getBuffer(renderType); + VertexConsumer vertexConsumerWrap = new VertexConsumer() + { + @Override + public VertexConsumer vertex(double x, double y, double z) + { + hasRendered = true; + return vertexConsumer.vertex(x, y, z); + } + + @Override + public VertexConsumer color(int r, int g, int b, int a) { return vertexConsumer.color(r, g, b, a); } + + @Override + public VertexConsumer uv(float u, float v) { return vertexConsumer.uv(u, v); } + + @Override + public VertexConsumer overlayCoords(int x, int y) { return vertexConsumer.overlayCoords(x, y); } + + @Override + public VertexConsumer uv2(int u, int v) { return vertexConsumer.uv2(u, v); } + + @Override + public VertexConsumer normal(float x, float y, float z) { return vertexConsumer.normal(x, y, z); } + + @Override + public void endVertex() { vertexConsumer.endVertex(); } + + @Override + public void defaultColor(int r, int g, int b, int a) { vertexConsumer.defaultColor(r, g, b, a); } + + @Override + public void unsetDefaultColor() { vertexConsumer.unsetDefaultColor(); } + }; + + return vertexConsumerWrap; + } + + public boolean hasRendered() + { + return hasRendered; + } + + public void reset() + { + hasRendered = false; + } +} \ No newline at end of file diff --git a/src/main/java/com/anthonyhilyard/iceberg/renderer/CustomItemRenderer.java b/src/main/java/com/anthonyhilyard/iceberg/renderer/CustomItemRenderer.java index b77bb09..c3cc4b5 100644 --- a/src/main/java/com/anthonyhilyard/iceberg/renderer/CustomItemRenderer.java +++ b/src/main/java/com/anthonyhilyard/iceberg/renderer/CustomItemRenderer.java @@ -1,50 +1,122 @@ package com.anthonyhilyard.iceberg.renderer; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.function.Predicate; + +import com.google.common.collect.Maps; + +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 org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; -import org.lwjgl.opengl.GL11; - +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 com.mojang.blaze3d.platform.GlStateManager.SourceFactor; -import com.mojang.blaze3d.platform.GlStateManager.DestFactor; -import com.mojang.math.Matrix4f; - 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.MultiBufferSource.BufferSource; 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.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; + /** - * An extended ItemRenderer that allows items to be rendered to RenderTarget before drawing to screen. - * This allows alpha values to be supported properly by all item types, even semi-transparent items. + * 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 { - @SuppressWarnings("unused") - private static final Logger LOGGER = LogManager.getLogger(); + /* 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 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) @@ -56,6 +128,515 @@ public class CustomItemRenderer extends ItemRenderer } } + 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) + { + ((BufferSource)bufferSource).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) + { + Block block = ((BlockItem)itemStack.getItem()).getBlock(); + fabulous = !(block instanceof HalfTransparentBlock) && !(block instanceof StainedGlassPaneBlock); + } + else + { + fabulous = true; + } + + if (bufferSourceReady.test(bufferSource) && itemStack.getItem() instanceof BlockItem blockItem) + { + BakedModel blockModel = null; + boolean isBlockEntity = false; + + blockModel = minecraft.getBlockRenderer().getBlockModelShaper().getBlockModel(blockItem.getBlock().defaultBlockState()); + if (blockModel != modelManager.getMissingModel()) + { + // First try rendering via the BlockEntityWithoutLevelRenderer. + blockEntityRenderer.renderByItem(itemStack, transformType, poseStack, bufferSource, packedLight, packedOverlay); + } + else + { + blockModel = null; + } + + if (blockItem.getBlock().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 = blockItem.getBlock().defaultBlockState().setValue(BlockStateProperties.DOUBLE_BLOCK_HALF, DoubleBlockHalf.LOWER); + BakedModel bottomModel = minecraft.getBlockRenderer().getBlockModelShaper().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 = blockItem.getBlock().defaultBlockState().setValue(BlockStateProperties.DOUBLE_BLOCK_HALF, DoubleBlockHalf.UPPER); + BakedModel topModel = minecraft.getBlockRenderer().getBlockModelShaper().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) && itemStack.getItem() instanceof BoatItem boatItem) + { + if (updateBoat(boatItem)) + { + 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 || itemStack.getItem() instanceof BoatItem) + { + spawnsEntity = true; + } + + if (Player.getEquipmentSlotForItem(itemStack).isArmor()) + { + isArmor = true; + } + + poseStack.pushPose(); + + 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); + } + + // 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 = 1.0f / Math.max(modelBounds.height, modelBounds.radius * 2.0f); + scale *= isBlockItem ? 0.8f : 1.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.1f; + 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()); + + // 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); + } + + CheckedBufferSource checkedBufferSource = new CheckedBufferSource(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) + { + for (var model : List.of(bakedModel)) + { + for (var rendertype : List.of(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(model, 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) + { + 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 updateBoat(BoatItem item) + { + if (boat == null || boat.getVariant() != item.type || (boat instanceof ChestBoat) != item.hasChest) + { + Minecraft minecraft = Minecraft.getInstance(); + boat = item.hasChest ? new ChestBoat(minecraft.level, 0, 0, 0) : new Boat(minecraft.level, 0, 0, 0); + boat.setVariant(item.type); + } + + // 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(List 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.y - center.y) * (vertex.y - center.y))); + } + + 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 = new VertexCollector(); + 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); @@ -65,33 +646,42 @@ public class CustomItemRenderer extends ItemRenderer 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(Matrix4f.orthographic(0.0f, iconFrameBuffer.width, iconFrameBuffer.height, 0.0f, 1000.0f, 3000.0f)); - - Lighting.setupFor3DItems(); + 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); + 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); + modelViewStack.scale(96.0f, 96.0f, 96.0f); RenderSystem.applyModelViewMatrix(); PoseStack poseStack = new PoseStack(); - MultiBufferSource.BufferSource multibuffersource$buffersource = Minecraft.getInstance().renderBuffers().bufferSource(); - boolean flag = !bakedModel.usesBlockLight(); - if (flag) { Lighting.setupForFlatItems(); } + BufferSource bufferSource = mc.renderBuffers().bufferSource(); + + boolean flatLighting = !bakedModel.usesBlockLight(); + if (flatLighting) + { + Lighting.setupForFlatItems(); + } - render(stack, ItemTransforms.TransformType.GUI, false, poseStack, multibuffersource$buffersource, 0xF000F0, OverlayTexture.NO_OVERLAY, bakedModel); - multibuffersource$buffersource.endBatch(); + render(stack, ItemTransforms.TransformType.GUI, false, poseStack, bufferSource, LightTexture.FULL_BRIGHT, OverlayTexture.NO_OVERLAY, bakedModel); + bufferSource.endBatch(); RenderSystem.enableDepthTest(); - if (flag) { Lighting.setupFor3DItems(); } + if (flatLighting) + { + Lighting.setupFor3DItems(); + } modelViewStack.popPose(); RenderSystem.applyModelViewMatrix(); @@ -108,13 +698,12 @@ public class CustomItemRenderer extends ItemRenderer RenderSystem.disableCull(); RenderSystem.setShaderColor(1.0f, 1.0f, 1.0f, alpha); modelViewStack.pushPose(); - modelViewStack.scale(1.0f, -1.0f, 1.0f); modelViewStack.translate(0.0f, 0.0f, 50.0f + this.blitOffset); RenderSystem.applyModelViewMatrix(); RenderSystem.setShaderTexture(0, iconFrameBuffer.getColorTextureId()); - GuiComponent.blit(new PoseStack(), x, y - 18, 16, 16, 0, 0, iconFrameBuffer.width, iconFrameBuffer.height, iconFrameBuffer.width, iconFrameBuffer.height); + GuiComponent.blit(new PoseStack(), x, y, 16, 16, 0, 0, iconFrameBuffer.width, iconFrameBuffer.height, iconFrameBuffer.width, iconFrameBuffer.height); modelViewStack.popPose(); RenderSystem.applyModelViewMatrix(); iconFrameBuffer.unbindRead(); @@ -123,5 +712,14 @@ public class CustomItemRenderer extends ItemRenderer { iconFrameBuffer.unbindWrite(); } - } -} + } + + @Override + public void onResourceManagerReload(ResourceManager resourceManager) + { + super.onResourceManagerReload(resourceManager); + + // Clear the model bounds cache. + modelBoundsCache.clear(); + } +} \ No newline at end of file diff --git a/src/main/java/com/anthonyhilyard/iceberg/renderer/VertexCollector.java b/src/main/java/com/anthonyhilyard/iceberg/renderer/VertexCollector.java new file mode 100644 index 0000000..29ae995 --- /dev/null +++ b/src/main/java/com/anthonyhilyard/iceberg/renderer/VertexCollector.java @@ -0,0 +1,79 @@ +package com.anthonyhilyard.iceberg.renderer; + +import java.util.List; + +import org.apache.commons.compress.utils.Lists; +import org.joml.Vector3f; + +import com.mojang.blaze3d.vertex.VertexConsumer; + +import net.minecraft.client.renderer.MultiBufferSource; +import net.minecraft.client.renderer.RenderType; + +public class VertexCollector implements MultiBufferSource +{ + private final List vertices = Lists.newArrayList(); + private final Vector3f currentVertex = new Vector3f(); + private int currentAlpha = 255; + private int defaultAlpha = 255; + + @Override + public VertexConsumer getBuffer(RenderType renderType) + { + return new VertexConsumer() + { + @Override + public VertexConsumer vertex(double x, double y, double z) + { + currentVertex.set((float) x, (float) y, (float) z); + currentAlpha = defaultAlpha; + return this; + } + + @Override + public VertexConsumer color(int r, int g, int b, int a) + { + currentAlpha = a; + return this; + } + + @Override + public VertexConsumer uv(float u, float v) { return this; } + + @Override + public VertexConsumer overlayCoords(int x, int y) { return this; } + + @Override + public VertexConsumer uv2(int u, int v) { return this; } + + @Override + public VertexConsumer normal(float x, float y, float z) { return this; } + + @Override + public void endVertex() + { + if (currentAlpha >= 25) + { + vertices.add(new Vector3f(currentVertex)); + } + } + + @Override + public void defaultColor(int r, int g, int b, int a) + { + defaultAlpha = a; + } + + @Override + public void unsetDefaultColor() + { + defaultAlpha = 255; + } + }; + } + + public List getVertices() + { + return vertices; + } +} \ No newline at end of file diff --git a/src/main/java/com/anthonyhilyard/iceberg/util/DynamicResourcePack.java b/src/main/java/com/anthonyhilyard/iceberg/util/DynamicResourcePack.java index a59449a..4c0bfbf 100644 --- a/src/main/java/com/anthonyhilyard/iceberg/util/DynamicResourcePack.java +++ b/src/main/java/com/anthonyhilyard/iceberg/util/DynamicResourcePack.java @@ -3,19 +3,18 @@ package com.anthonyhilyard.iceberg.util; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; -import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; -import java.util.function.Predicate; -import java.util.function.Supplier; -import java.util.stream.Collectors; + +import javax.annotation.Nullable; import net.minecraft.resources.ResourceLocation; import net.minecraft.server.packs.PackResources; import net.minecraft.server.packs.PackType; import net.minecraft.server.packs.metadata.MetadataSectionSerializer; +import net.minecraft.server.packs.resources.IoSupplier; /** * DynamicResourcePack allows resources that are defined arbitrarily to do cool things with resources. @@ -26,7 +25,7 @@ public class DynamicResourcePack implements PackResources private record DynamicResourceKey(String type, String namespace, String path) {} private final String packName; - private Map> dynamicResourceMap = new HashMap>(); + private Map> dynamicResourceMap = new HashMap>(); public DynamicResourcePack(String packName) { @@ -52,17 +51,17 @@ public class DynamicResourcePack implements PackResources } } - public boolean registerResource(PackType type, ResourceLocation location, Supplier resourceSupplier) + public boolean registerResource(PackType type, ResourceLocation location, IoSupplier resourceSupplier) { return register(type.getDirectory(), location.getNamespace(), location.getPath(), resourceSupplier); } - public boolean registerRootResource(String path, Supplier resourceSupplier) + public boolean registerRootResource(String path, IoSupplier resourceSupplier) { return register("root", "", path, resourceSupplier); } - private boolean register(String directory, String namespace, String path, Supplier resourceSupplier) + private boolean register(String directory, String namespace, String path, IoSupplier resourceSupplier) { DynamicResourceKey key = new DynamicResourceKey(directory, namespace, path); if (!dynamicResourceMap.containsKey(key)) @@ -74,23 +73,39 @@ public class DynamicResourcePack implements PackResources } @Override - public InputStream getRootResource(String path) throws IOException + @Nullable + public IoSupplier getRootResource(String... path) { - return getResource("root", "", path); + try + { + return getResource("root", "", String.join("/", path)); + } + catch (IOException e) + { + return null; + } } @Override - public InputStream getResource(PackType type, ResourceLocation location) throws IOException + @Nullable + public IoSupplier getResource(PackType type, ResourceLocation location) { - return getResource(type.getDirectory(), location.getNamespace(), location.getPath()); + try + { + return getResource(type.getDirectory(), location.getNamespace(), location.getPath()); + } + catch (IOException e) + { + return null; + } } - private InputStream getResource(String directory, String namespace, String path) throws IOException + private IoSupplier getResource(String directory, String namespace, String path) throws IOException { DynamicResourceKey key = new DynamicResourceKey(directory, namespace, path); if (dynamicResourceMap.containsKey(key)) { - return dynamicResourceMap.get(key).get(); + return dynamicResourceMap.get(key); } else { @@ -99,21 +114,13 @@ public class DynamicResourcePack implements PackResources } @Override - public Collection getResources(PackType type, String namespace, String path, Predicate filter) + public void listResources(PackType type, String namespace, String path, ResourceOutput output) { - return dynamicResourceMap.entrySet().stream() + dynamicResourceMap.entrySet().stream() .filter(entry -> entry.getKey().namespace.contentEquals(namespace)) .filter(entry -> entry.getKey().path.startsWith(path)) .filter(entry -> entry.getKey().type.contentEquals(type.getDirectory())) - .filter(entry -> filter.test(new ResourceLocation(entry.getKey().namespace, entry.getKey().path))) - .map(entry -> new ResourceLocation(namespace, entry.getKey().path)) - .collect(Collectors.toList()); - } - - @Override - public boolean hasResource(PackType type, ResourceLocation location) - { - return dynamicResourceMap.containsKey(new DynamicResourceKey(type.getDirectory(), location.getNamespace(), location.getPath())); + .forEach(entry -> output.accept(new ResourceLocation(namespace, entry.getKey().path), entry.getValue())); } @Override @@ -139,7 +146,7 @@ public class DynamicResourcePack implements PackResources } @Override - public String getName() + public String packId() { return packName; } diff --git a/src/main/java/com/anthonyhilyard/iceberg/util/GuiHelper.java b/src/main/java/com/anthonyhilyard/iceberg/util/GuiHelper.java index d3530fe..54cf422 100644 --- a/src/main/java/com/anthonyhilyard/iceberg/util/GuiHelper.java +++ b/src/main/java/com/anthonyhilyard/iceberg/util/GuiHelper.java @@ -1,15 +1,16 @@ package com.anthonyhilyard.iceberg.util; -import com.mojang.blaze3d.systems.RenderSystem; +import org.joml.Matrix4f; import net.minecraft.client.renderer.GameRenderer; +import com.mojang.blaze3d.systems.RenderSystem; import com.mojang.blaze3d.vertex.Tesselator; import com.mojang.blaze3d.vertex.BufferBuilder; import com.mojang.blaze3d.vertex.BufferUploader; import com.mojang.blaze3d.vertex.DefaultVertexFormat; import com.mojang.blaze3d.vertex.VertexFormat; -import com.mojang.math.Matrix4f; + public class GuiHelper { diff --git a/src/main/java/com/anthonyhilyard/iceberg/util/Selec