From bb311303a71ce33eaef15bcc2f69853126570f22 Mon Sep 17 00:00:00 2001 From: Milkev Date: Thu, 22 Jan 2026 11:53:23 -0700 Subject: [PATCH 1/3] implement recipe tree node culling now with no performance losses when viewing massive trees --- gradle.properties | 1 + .../java/dev/emi/emi/config/EmiConfig.java | 4 ++ .../java/dev/emi/emi/screen/BoMScreen.java | 66 +++++++++++++++++-- .../main/resources/assets/emi/lang/en_us.json | 1 + 4 files changed, 67 insertions(+), 5 deletions(-) diff --git a/gradle.properties b/gradle.properties index 84f9064e..af9836e6 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,4 +1,5 @@ org.gradle.jvmargs=-Xmx2048M +org.gradle.java.home=D:/Java/Java21/jdk-21.0.3 minecraft_version=1.21.1 enabled_platforms=fabric,neoforge diff --git a/xplat/src/main/java/dev/emi/emi/config/EmiConfig.java b/xplat/src/main/java/dev/emi/emi/config/EmiConfig.java index 3ea8cf26..50d5523d 100644 --- a/xplat/src/main/java/dev/emi/emi/config/EmiConfig.java +++ b/xplat/src/main/java/dev/emi/emi/config/EmiConfig.java @@ -506,6 +506,10 @@ public class EmiConfig { @Comment("Whether to display exclusion areas") @ConfigValue("dev.highlight-exclusion-areas") public static boolean highlightExclusionAreas = false; + + @Comment("Whether to display Recipe Tree bounding boxes for culling") + @ConfigValue("dev.recipe-tree-bounding-boxes") + public static boolean recipeTreeBoundingBoxes = false; // Persistent (currently empty) diff --git a/xplat/src/main/java/dev/emi/emi/screen/BoMScreen.java b/xplat/src/main/java/dev/emi/emi/screen/BoMScreen.java index 727944ca..157daa39 100644 --- a/xplat/src/main/java/dev/emi/emi/screen/BoMScreen.java +++ b/xplat/src/main/java/dev/emi/emi/screen/BoMScreen.java @@ -7,8 +7,8 @@ import java.util.stream.Collectors; import java.util.stream.Stream; -import org.joml.Matrix4f; import org.joml.Matrix4fStack; +import org.joml.Vector4d; import org.lwjgl.glfw.GLFW; import com.google.common.collect.Lists; @@ -33,7 +33,6 @@ import dev.emi.emi.bom.FoldState; import dev.emi.emi.bom.MaterialNode; import dev.emi.emi.bom.ProgressState; -import dev.emi.emi.bom.TreeCost; import dev.emi.emi.config.EmiConfig; import dev.emi.emi.data.EmiRecipeCategoryProperties; import dev.emi.emi.input.EmiBind; @@ -51,8 +50,6 @@ import net.minecraft.client.gui.screen.ingame.HandledScreen; import net.minecraft.client.gui.tooltip.TooltipComponent; import net.minecraft.client.sound.PositionedSoundInstance; -import net.minecraft.client.util.InputUtil; -import net.minecraft.client.util.math.MatrixStack; import net.minecraft.sound.SoundEvents; import net.minecraft.text.MutableText; import net.minecraft.text.Text; @@ -79,6 +76,8 @@ public class BoMScreen extends Screen { private int nodeHeight = 0; private int lastMouseX, lastMouseY; private double scrollAcc = 0; + private Screen screen = MinecraftClient.getInstance().currentScreen; + private boolean shouldFullRenderNodes = true; public BoMScreen(HandledScreen old) { super(EmiPort.translatable("screen.emi.recipe_tree")); @@ -96,6 +95,7 @@ public void init() { public void recalculateTree() { help = new Bounds(width - 18, height - 18, 16, 16); + shouldFullRenderNodes = true; if (BoM.tree != null) { TreeVolume volume = addNewNodes(BoM.tree.goal, BoM.tree.batches, 1, 0, ChanceState.DEFAULT); nodes = volume.nodes; @@ -212,6 +212,9 @@ public void render(DrawContext raw, int mouseX, int mouseY, float delta) { int mx = (int) ((mouseX - width / 2) / scale - offX); int my = (int) ((mouseY - height / 2) / scale - offY); + + int scaledScreenW = (int) ((screen.width / 2) / scale); + int scaledScreenH = (int) ((screen.height / 2) / scale); Matrix4fStack view = RenderSystem.getModelViewStack(); view.pushMatrix(); @@ -230,8 +233,25 @@ public void render(DrawContext raw, int mouseX, int mouseY, float delta) { cost.render(context); } for (Node node : nodes) { - node.render(context, mx, my, delta); + if(shouldFullRenderNodes) { + node.render(context, mx, my, delta); + if(EmiConfig.recipeTreeBoundingBoxes) { + node.renderBoundingBox(context); + } + } else { + Vector4d bounds = node.getBoundingBox(); + if( bounds.x + offX < scaledScreenW && + bounds.x + bounds.w + offX > -scaledScreenW && + bounds.y + offY < scaledScreenH && + bounds.y + bounds.z + offY> -scaledScreenH) { + node.render(context, mx, my, delta); + if(EmiConfig.recipeTreeBoundingBoxes) { + node.renderBoundingBox(context); + } + } + } } + shouldFullRenderNodes = false; int color = -1; if (batches.contains(mx, my)) { color = 0xff8099ff; @@ -731,6 +751,42 @@ public void render(EmiDrawContext context, int mouseX, int mouseY, float delta) batcher.render(node.ingredient, context.raw(), x + xo - 8 + midOffset, y - 8, 0); EmiRenderHelper.renderAmount(context, x + xo - 8 + midOffset, y - 8, getAmountText()); } + + public void renderBoundingBox(EmiDrawContext context) { + Vector4d bounds = getBoundingBox(); + context.push(); + + context.setColor(0.5f,0.5f,0.5f,0.2f); + drawLine(context, (int) bounds.x, (int) bounds.y, (int) this.x, (int) this.y); + if(parent != null) { + drawLine(context, ((parent.x - this.x)/2 + this.x) - 2, ((parent.y - this.y)/2 + this.y) - 2, ((parent.x - this.x)/2 + this.x) + 2, ((parent.y - this.y)/2 + this.y) + 2); + } + + context.setColor(1, 0, 0); + drawLine(context, (int) bounds.x, (int) bounds.y, (int) (bounds.x+bounds.w), (int) bounds.y); + drawLine(context, (int) bounds.x, (int) bounds.y, (int) bounds.x, (int) (bounds.y + bounds.z)); + drawLine(context, (int) (bounds.x+bounds.w), (int) bounds.y, (int) (bounds.x+bounds.w), (int) (bounds.y+bounds.z)); + drawLine(context, (int) bounds.x, (int) (bounds.y+bounds.z), (int) (bounds.x+bounds.w), (int) (bounds.y+bounds.z)); + + context.pop(); + } + + public Vector4d getBoundingBox() { + Vector4d bounds = new Vector4d(); + + if(parent != null) { + bounds.w = this.width + 10 + (Math.abs(parent.x - this.x)); + bounds.z = NODE_VERTICAL_SPACING + 10 + (Math.abs(parent.y - this.y)); + bounds.x = ((parent.x - this.x)/2 + this.x) - bounds.w/2; + bounds.y = ((parent.y - this.y)/2 + this.y) - bounds.z/2; + return bounds; + } + bounds.w = this.width + 10; + bounds.z = NODE_VERTICAL_SPACING + 10; + bounds.x = x - bounds.w/2; + bounds.y = y - bounds.z/2; + return bounds; + } public void setColor(EmiDrawContext context, MaterialNode node, boolean chanced, boolean hovered) { context.setColor(1f, 1f, 1f, 1f); diff --git a/xplat/src/main/resources/assets/emi/lang/en_us.json b/xplat/src/main/resources/assets/emi/lang/en_us.json index a07d2dcd..ddedfabc 100644 --- a/xplat/src/main/resources/assets/emi/lang/en_us.json +++ b/xplat/src/main/resources/assets/emi/lang/en_us.json @@ -179,6 +179,7 @@ "config.emi.dev.show_recipe_decorators": "Show Recipe Decorators", "config.emi.dev.highlight_defaulted": "Highlight Defaulted", "config.emi.dev.highlight_exclusion_areas": "Highlight Exclusion Areas", + "config.emi.dev.recipe-tree-bounding-boxes": "Recipe Tree Bounding Boxes", "config.emi.presets.sidebars": "Sidebars", "config.emi.presets.binds": "Binds", From 1b7461bb711b376be7f4e3027e2714a6777ac1b0 Mon Sep 17 00:00:00 2001 From: Milkev Date: Thu, 22 Jan 2026 11:58:40 -0700 Subject: [PATCH 2/3] revert changes this wasnt supposed to be uploaded, just needed it to build emi on my system --- gradle.properties | 1 - 1 file changed, 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index af9836e6..84f9064e 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,5 +1,4 @@ org.gradle.jvmargs=-Xmx2048M -org.gradle.java.home=D:/Java/Java21/jdk-21.0.3 minecraft_version=1.21.1 enabled_platforms=fabric,neoforge From 796c911614073b03e3cd6242db1a3980e4ec3e5c Mon Sep 17 00:00:00 2001 From: Milkev Date: Fri, 23 Jan 2026 10:46:59 -0700 Subject: [PATCH 3/3] Use Bounds instead of Vector4d, Remove screen reference, Fix culling bug when changing window sizes --- .../java/dev/emi/emi/screen/BoMScreen.java | 49 ++++++++----------- 1 file changed, 21 insertions(+), 28 deletions(-) diff --git a/xplat/src/main/java/dev/emi/emi/screen/BoMScreen.java b/xplat/src/main/java/dev/emi/emi/screen/BoMScreen.java index 157daa39..8df82fea 100644 --- a/xplat/src/main/java/dev/emi/emi/screen/BoMScreen.java +++ b/xplat/src/main/java/dev/emi/emi/screen/BoMScreen.java @@ -8,7 +8,6 @@ import java.util.stream.Stream; import org.joml.Matrix4fStack; -import org.joml.Vector4d; import org.lwjgl.glfw.GLFW; import com.google.common.collect.Lists; @@ -76,7 +75,6 @@ public class BoMScreen extends Screen { private int nodeHeight = 0; private int lastMouseX, lastMouseY; private double scrollAcc = 0; - private Screen screen = MinecraftClient.getInstance().currentScreen; private boolean shouldFullRenderNodes = true; public BoMScreen(HandledScreen old) { @@ -213,8 +211,7 @@ public void render(DrawContext raw, int mouseX, int mouseY, float delta) { int mx = (int) ((mouseX - width / 2) / scale - offX); int my = (int) ((mouseY - height / 2) / scale - offY); - int scaledScreenW = (int) ((screen.width / 2) / scale); - int scaledScreenH = (int) ((screen.height / 2) / scale); + Bounds scaledScreenBounds = new Bounds(-(scaledWidth/2) - (int) offX, -(scaledHeight/2) - (int) offY, scaledWidth, scaledHeight); Matrix4fStack view = RenderSystem.getModelViewStack(); view.pushMatrix(); @@ -239,11 +236,8 @@ public void render(DrawContext raw, int mouseX, int mouseY, float delta) { node.renderBoundingBox(context); } } else { - Vector4d bounds = node.getBoundingBox(); - if( bounds.x + offX < scaledScreenW && - bounds.x + bounds.w + offX > -scaledScreenW && - bounds.y + offY < scaledScreenH && - bounds.y + bounds.z + offY> -scaledScreenH) { + Bounds nodeBounds = node.getBoundingBox(); + if(!nodeBounds.overlap(scaledScreenBounds).empty()) { node.render(context, mx, my, delta); if(EmiConfig.recipeTreeBoundingBoxes) { node.renderBoundingBox(context); @@ -753,39 +747,38 @@ public void render(EmiDrawContext context, int mouseX, int mouseY, float delta) } public void renderBoundingBox(EmiDrawContext context) { - Vector4d bounds = getBoundingBox(); + Bounds bounds = getBoundingBox(); context.push(); context.setColor(0.5f,0.5f,0.5f,0.2f); - drawLine(context, (int) bounds.x, (int) bounds.y, (int) this.x, (int) this.y); + drawLine(context, bounds.x(), bounds.y(), this.x, this.y); if(parent != null) { drawLine(context, ((parent.x - this.x)/2 + this.x) - 2, ((parent.y - this.y)/2 + this.y) - 2, ((parent.x - this.x)/2 + this.x) + 2, ((parent.y - this.y)/2 + this.y) + 2); } context.setColor(1, 0, 0); - drawLine(context, (int) bounds.x, (int) bounds.y, (int) (bounds.x+bounds.w), (int) bounds.y); - drawLine(context, (int) bounds.x, (int) bounds.y, (int) bounds.x, (int) (bounds.y + bounds.z)); - drawLine(context, (int) (bounds.x+bounds.w), (int) bounds.y, (int) (bounds.x+bounds.w), (int) (bounds.y+bounds.z)); - drawLine(context, (int) bounds.x, (int) (bounds.y+bounds.z), (int) (bounds.x+bounds.w), (int) (bounds.y+bounds.z)); + drawLine(context, bounds.x(), bounds.y(), (bounds.right()), bounds.y()); + drawLine(context, bounds.x(), bounds.y(), bounds.x(), (bounds.bottom())); + drawLine(context, (bounds.right()), bounds.y(), (bounds.right()), (bounds.bottom())); + drawLine(context, bounds.x(), (bounds.bottom()), (bounds.right()), (bounds.bottom())); context.pop(); } - public Vector4d getBoundingBox() { - Vector4d bounds = new Vector4d(); + public Bounds getBoundingBox() { if(parent != null) { - bounds.w = this.width + 10 + (Math.abs(parent.x - this.x)); - bounds.z = NODE_VERTICAL_SPACING + 10 + (Math.abs(parent.y - this.y)); - bounds.x = ((parent.x - this.x)/2 + this.x) - bounds.w/2; - bounds.y = ((parent.y - this.y)/2 + this.y) - bounds.z/2; - return bounds; - } - bounds.w = this.width + 10; - bounds.z = NODE_VERTICAL_SPACING + 10; - bounds.x = x - bounds.w/2; - bounds.y = y - bounds.z/2; - return bounds; + int bw = this.width + 10 + (Math.abs(parent.x - this.x)); + int bh = NODE_VERTICAL_SPACING + 10 + (Math.abs(parent.y - this.y)); + int bx = ((parent.x - this.x)/2 + this.x) - bw/2; + int by = ((parent.y - this.y)/2 + this.y) - bh/2; + return new Bounds(bx, by, bw, bh); + } + int bw = this.width + 10; + int bh = NODE_VERTICAL_SPACING + 10; + int bx = x - bw/2; + int by = y - bh/2; + return new Bounds(bx, by, bw, bh); } public void setColor(EmiDrawContext context, MaterialNode node, boolean chanced, boolean hovered) {