From 238a273818d892bbf6d19eb8434f5e7d8fff352d Mon Sep 17 00:00:00 2001 From: embeddedt <42941056+embeddedt@users.noreply.github.com> Date: Thu, 9 May 2024 17:07:08 -0400 Subject: [PATCH] Add more accurate culling for single quad particles (#885) --- .../client/particle/Particle.java.patch | 8 +-- .../client/particle/ParticleEngine.java.patch | 14 +++- .../particle/SingleQuadParticle.java.patch | 15 +++++ .../blockentity/PistonHeadRenderer.java.patch | 2 +- .../StructureBlockRenderer.java.patch | 2 +- .../TheEndGatewayRenderer.java.patch | 2 +- .../renderer/culling/Frustum.java.patch | 2 +- .../net/minecraft/world/phys/AABB.java.patch | 24 +++++++ .../client/ParticleBoundsDebugRenderer.java | 65 +++++++++++++++++++ .../IBlockEntityRendererExtension.java | 9 +-- 10 files changed, 126 insertions(+), 17 deletions(-) create mode 100644 patches/net/minecraft/client/particle/SingleQuadParticle.java.patch create mode 100644 patches/net/minecraft/world/phys/AABB.java.patch create mode 100644 src/main/java/net/neoforged/neoforge/client/ParticleBoundsDebugRenderer.java diff --git a/patches/net/minecraft/client/particle/Particle.java.patch b/patches/net/minecraft/client/particle/Particle.java.patch index b3280b7ed0..a3587365f5 100644 --- a/patches/net/minecraft/client/particle/Particle.java.patch +++ b/patches/net/minecraft/client/particle/Particle.java.patch @@ -5,11 +5,11 @@ } + /** -+ * Forge added method that controls if a particle should be culled to it's bounding box. -+ * Default behaviour is culling enabled ++ * Returns the bounding box that should be used for particle culling. {@link AABB#INFINITE} can be ++ * returned for particles that should not be culled. + */ -+ public boolean shouldCull() { -+ return true; ++ public AABB getRenderBoundingBox(float partialTicks) { ++ return getBoundingBox().inflate(1.0); + } + + public Vec3 getPos() { diff --git a/patches/net/minecraft/client/particle/ParticleEngine.java.patch b/patches/net/minecraft/client/particle/ParticleEngine.java.patch index 8eee5707c4..a6a863a67a 100644 --- a/patches/net/minecraft/client/particle/ParticleEngine.java.patch +++ b/patches/net/minecraft/client/particle/ParticleEngine.java.patch @@ -80,7 +80,7 @@ particlerendertype.begin(bufferbuilder, this.textureManager); for (Particle particle : iterable) { -+ if (frustum != null && particle.shouldCull() && !frustum.isVisible(particle.getBoundingBox().inflate(1.0))) continue; ++ if (frustum != null && !frustum.isVisible(particle.getRenderBoundingBox(p_107341_))) continue; try { particle.render(bufferbuilder, p_107340_, p_107341_); } catch (Throwable throwable) { @@ -102,7 +102,7 @@ ); } } -@@ -556,12 +_,18 @@ +@@ -556,12 +_,28 @@ d0 = (double)i + aabb.maxX + 0.1F; } @@ -115,6 +115,16 @@ return String.valueOf(this.particles.values().stream().mapToInt(Collection::size).sum()); + } + ++ public void iterateParticles(java.util.function.Consumer consumer) { ++ for (ParticleRenderType particlerendertype : this.particles.keySet()) { ++ if (particlerendertype == ParticleRenderType.NO_RENDER) continue; ++ Iterable iterable = this.particles.get(particlerendertype); ++ if (iterable != null) { ++ iterable.forEach(consumer); ++ } ++ } ++ } ++ + public void addBlockHitEffects(BlockPos pos, net.minecraft.world.phys.BlockHitResult target) { + BlockState state = level.getBlockState(pos); + if (!net.neoforged.neoforge.client.extensions.common.IClientBlockExtensions.of(state).addHitEffects(state, level, target, this)) diff --git a/patches/net/minecraft/client/particle/SingleQuadParticle.java.patch b/patches/net/minecraft/client/particle/SingleQuadParticle.java.patch new file mode 100644 index 0000000000..57275ec8b7 --- /dev/null +++ b/patches/net/minecraft/client/particle/SingleQuadParticle.java.patch @@ -0,0 +1,15 @@ +--- a/net/minecraft/client/particle/SingleQuadParticle.java ++++ b/net/minecraft/client/particle/SingleQuadParticle.java +@@ -81,6 +_,12 @@ + .endVertex(); + } + ++ @Override ++ public net.minecraft.world.phys.AABB getRenderBoundingBox(float partialTicks) { ++ float size = getQuadSize(partialTicks); ++ return new net.minecraft.world.phys.AABB(this.x - size, this.y - size, this.z - size, this.x + size, this.y + size, this.z + size); ++ } ++ + public float getQuadSize(float p_107681_) { + return this.quadSize; + } diff --git a/patches/net/minecraft/client/renderer/blockentity/PistonHeadRenderer.java.patch b/patches/net/minecraft/client/renderer/blockentity/PistonHeadRenderer.java.patch index f2fda5062b..f9d3c72db7 100644 --- a/patches/net/minecraft/client/renderer/blockentity/PistonHeadRenderer.java.patch +++ b/patches/net/minecraft/client/renderer/blockentity/PistonHeadRenderer.java.patch @@ -19,6 +19,6 @@ + + @Override + public net.minecraft.world.phys.AABB getRenderBoundingBox(PistonMovingBlockEntity blockEntity) { -+ return INFINITE_EXTENT_AABB; ++ return net.minecraft.world.phys.AABB.INFINITE; } } diff --git a/patches/net/minecraft/client/renderer/blockentity/StructureBlockRenderer.java.patch b/patches/net/minecraft/client/renderer/blockentity/StructureBlockRenderer.java.patch index 748d38f506..21e5b205b0 100644 --- a/patches/net/minecraft/client/renderer/blockentity/StructureBlockRenderer.java.patch +++ b/patches/net/minecraft/client/renderer/blockentity/StructureBlockRenderer.java.patch @@ -7,6 +7,6 @@ + + @Override + public net.minecraft.world.phys.AABB getRenderBoundingBox(StructureBlockEntity blockEntity) { -+ return INFINITE_EXTENT_AABB; ++ return net.minecraft.world.phys.AABB.INFINITE; + } } diff --git a/patches/net/minecraft/client/renderer/blockentity/TheEndGatewayRenderer.java.patch b/patches/net/minecraft/client/renderer/blockentity/TheEndGatewayRenderer.java.patch index e495d572fd..fc2de34b8f 100644 --- a/patches/net/minecraft/client/renderer/blockentity/TheEndGatewayRenderer.java.patch +++ b/patches/net/minecraft/client/renderer/blockentity/TheEndGatewayRenderer.java.patch @@ -7,6 +7,6 @@ + + @Override + public net.minecraft.world.phys.AABB getRenderBoundingBox(TheEndGatewayBlockEntity blockEntity) { -+ return blockEntity.isSpawning() || blockEntity.isCoolingDown() ? INFINITE_EXTENT_AABB : super.getRenderBoundingBox(blockEntity); ++ return blockEntity.isSpawning() || blockEntity.isCoolingDown() ? net.minecraft.world.phys.AABB.INFINITE : super.getRenderBoundingBox(blockEntity); + } } diff --git a/patches/net/minecraft/client/renderer/culling/Frustum.java.patch b/patches/net/minecraft/client/renderer/culling/Frustum.java.patch index 7c82c0e5b5..92a6b521cb 100644 --- a/patches/net/minecraft/client/renderer/culling/Frustum.java.patch +++ b/patches/net/minecraft/client/renderer/culling/Frustum.java.patch @@ -5,7 +5,7 @@ public boolean isVisible(AABB p_113030_) { + // FORGE: exit early for infinite bounds, these would otherwise fail in the intersection test at certain camera angles (GH-9321) -+ if (p_113030_.equals(net.neoforged.neoforge.client.extensions.IBlockEntityRendererExtension.INFINITE_EXTENT_AABB)) return true; ++ if (p_113030_.isInfinite()) return true; return this.cubeInFrustum(p_113030_.minX, p_113030_.minY, p_113030_.minZ, p_113030_.maxX, p_113030_.maxY, p_113030_.maxZ); } diff --git a/patches/net/minecraft/world/phys/AABB.java.patch b/patches/net/minecraft/world/phys/AABB.java.patch new file mode 100644 index 0000000000..6f8f6d4698 --- /dev/null +++ b/patches/net/minecraft/world/phys/AABB.java.patch @@ -0,0 +1,24 @@ +--- a/net/minecraft/world/phys/AABB.java ++++ b/net/minecraft/world/phys/AABB.java +@@ -9,6 +_,7 @@ + + public class AABB { + private static final double EPSILON = 1.0E-7; ++ public static final AABB INFINITE = new AABB(Double.NEGATIVE_INFINITY, Double.NEGATIVE_INFINITY, Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY); + public final double minX; + public final double minY; + public final double minZ; +@@ -511,5 +_,13 @@ + p_165883_.y + p_165885_ / 2.0, + p_165883_.z + p_165886_ / 2.0 + ); ++ } ++ ++ /** ++ * {@return true if this AABB is infinite in all directions} ++ */ ++ public boolean isInfinite() { ++ return this == INFINITE || (Double.isInfinite(this.minX) && Double.isInfinite(this.minY) && Double.isInfinite(this.minZ) ++ && Double.isInfinite(this.maxX) && Double.isInfinite(this.maxY) && Double.isInfinite(this.maxZ)); + } + } diff --git a/src/main/java/net/neoforged/neoforge/client/ParticleBoundsDebugRenderer.java b/src/main/java/net/neoforged/neoforge/client/ParticleBoundsDebugRenderer.java new file mode 100644 index 0000000000..e22522540a --- /dev/null +++ b/src/main/java/net/neoforged/neoforge/client/ParticleBoundsDebugRenderer.java @@ -0,0 +1,65 @@ +/* + * Copyright (c) NeoForged and contributors + * SPDX-License-Identifier: LGPL-2.1-only + */ + +package net.neoforged.neoforge.client; + +import com.mojang.blaze3d.vertex.PoseStack; +import com.mojang.blaze3d.vertex.VertexConsumer; +import com.mojang.brigadier.Command; +import com.mojang.brigadier.arguments.BoolArgumentType; +import net.minecraft.client.Minecraft; +import net.minecraft.client.renderer.LevelRenderer; +import net.minecraft.client.renderer.RenderType; +import net.minecraft.commands.Commands; +import net.neoforged.api.distmarker.Dist; +import net.neoforged.bus.api.SubscribeEvent; +import net.neoforged.fml.common.EventBusSubscriber; +import net.neoforged.neoforge.client.event.RegisterClientCommandsEvent; +import net.neoforged.neoforge.client.event.RenderLevelStageEvent; +import net.neoforged.neoforge.internal.versions.neoforge.NeoForgeVersion; + +@EventBusSubscriber(value = Dist.CLIENT, bus = EventBusSubscriber.Bus.GAME, modid = NeoForgeVersion.MOD_ID) +public final class ParticleBoundsDebugRenderer { + private static boolean enabled = false; + + @SubscribeEvent + public static void onRenderLevelStage(RenderLevelStageEvent event) { + if (!enabled || event.getStage() != RenderLevelStageEvent.Stage.AFTER_PARTICLES) { + return; + } + + var camPos = event.getCamera().getPosition(); + + PoseStack poseStack = event.getPoseStack(); + poseStack.pushPose(); + poseStack.translate(-camPos.x, -camPos.y, -camPos.z); + + VertexConsumer consumer = Minecraft.getInstance().renderBuffers().bufferSource().getBuffer(RenderType.lines()); + + Minecraft.getInstance().particleEngine.iterateParticles(particle -> { + var bb = particle.getRenderBoundingBox(event.getPartialTick()); + if (!bb.isInfinite() && event.getFrustum().isVisible(bb)) { + LevelRenderer.renderLineBox(poseStack, consumer, bb, 1F, 0F, 0F, 1F); + } + }); + + poseStack.popPose(); + } + + @SubscribeEvent + public static void onRegisterClientCommands(RegisterClientCommandsEvent event) { + event.getDispatcher().register( + Commands.literal("neoforge") + .then(Commands.literal("debug_particle_renderbounds") + .requires(src -> src.hasPermission(Commands.LEVEL_ADMINS)) + .then(Commands.argument("enable", BoolArgumentType.bool()) + .executes(ctx -> { + enabled = BoolArgumentType.getBool(ctx, "enable"); + return Command.SINGLE_SUCCESS; + })))); + } + + private ParticleBoundsDebugRenderer() {} +} diff --git a/src/main/java/net/neoforged/neoforge/client/extensions/IBlockEntityRendererExtension.java b/src/main/java/net/neoforged/neoforge/client/extensions/IBlockEntityRendererExtension.java index a893ac9863..99e7faef59 100644 --- a/src/main/java/net/neoforged/neoforge/client/extensions/IBlockEntityRendererExtension.java +++ b/src/main/java/net/neoforged/neoforge/client/extensions/IBlockEntityRendererExtension.java @@ -10,15 +10,10 @@ import net.minecraft.world.phys.AABB; public interface IBlockEntityRendererExtension { - /** - * Bounding box with infinite scope. Used as the render bounding box for blocks with dynamic render bounds which - * can't be trivially determined - */ - AABB INFINITE_EXTENT_AABB = new AABB(Double.NEGATIVE_INFINITY, Double.NEGATIVE_INFINITY, Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY); - /** * Return an {@link AABB} that controls the visible scope of this {@link BlockEntityRenderer}. - * Defaults to the unit cube at the given position. + * Defaults to the unit cube at the given position. {@link AABB#INFINITE} can be used to declare the BER + * should be visible everywhere. * * @return an appropriately sized {@link AABB} for the {@link BlockEntityRenderer} */