Skip to content

Commit fac6b00

Browse files
committed
Add debug chunk renderer
1 parent b4a22b7 commit fac6b00

12 files changed

Lines changed: 266 additions & 5 deletions

File tree

common/src/main/java/dev/ryanhcode/sable/SableClientConfig.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import dev.ryanhcode.sable.sublevel.ClientSubLevel;
1010
import dev.ryanhcode.sable.sublevel.SubLevel;
1111
import dev.ryanhcode.sable.sublevel.render.SubLevelRenderer;
12+
import foundry.veil.Veil;
1213
import foundry.veil.api.client.render.VeilRenderSystem;
1314
import net.minecraft.client.Minecraft;
1415
import net.minecraft.client.multiplayer.ClientLevel;
@@ -25,6 +26,7 @@ public final class SableClientConfig {
2526
public static final ModConfigSpec.BooleanValue SUB_LEVEL_DYNAMIC_SHADING;
2627
public static final ModConfigSpec.BooleanValue SUB_LEVEL_WATER_OCCLUSION;
2728
public static final ModConfigSpec.BooleanValue SUB_LEVEL_SKYLIGHT_SHADOWS;
29+
public static final ModConfigSpec.BooleanValue DEBUG_DRAW_LOADED_CHUNKS;
2830
public static final ModConfigSpec.DoubleValue INTERPOLATION_DELAY;
2931
public static final ModConfigSpec.EnumValue<SubLevelRenderer.SelectedRenderer> SELECTED_RENDERER;
3032
public static final ModConfigSpec.DoubleValue ZOOM_SENSITIVITY;
@@ -42,6 +44,9 @@ public final class SableClientConfig {
4244
SUB_LEVEL_SKYLIGHT_SHADOWS = builder
4345
.comment("Whether sub-levels should cast a shadow on the world")
4446
.define("sub_level_skylight_shadows", false);
47+
DEBUG_DRAW_LOADED_CHUNKS = builder
48+
.comment("Whether to draw loaded chunks on the client in the chunk debug renderer")
49+
.define("debug_draw_loaded_chunks", Veil.platform().isDevelopmentEnvironment());
4550
INTERPOLATION_DELAY = builder
4651
.comment("The distance back in game-ticks that the snapshot interpolation should operate")
4752
.defineInRange("sub_level_snapshot_interpolation_delay_ticks", 1.5, 0.0, 100.0);
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
package dev.ryanhcode.sable.mixin.loaded_chunk_debug;
2+
3+
import dev.ryanhcode.sable.mixinterface.loaded_chunk_debug.DebugLevelChunkExtension;
4+
import net.minecraft.client.Minecraft;
5+
import net.minecraft.client.multiplayer.ClientLevel;
6+
import net.minecraft.core.BlockPos;
7+
import net.minecraft.core.SectionPos;
8+
import net.minecraft.network.protocol.game.ClientGamePacketListener;
9+
import net.minecraft.network.protocol.game.ClientboundBlockUpdatePacket;
10+
import net.minecraft.world.level.chunk.LevelChunk;
11+
import org.spongepowered.asm.mixin.Final;
12+
import org.spongepowered.asm.mixin.Mixin;
13+
import org.spongepowered.asm.mixin.Shadow;
14+
import org.spongepowered.asm.mixin.injection.At;
15+
import org.spongepowered.asm.mixin.injection.Inject;
16+
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
17+
18+
@Mixin(ClientboundBlockUpdatePacket.class)
19+
public class BlockUpdatePacketMixin {
20+
21+
@Shadow
22+
@Final
23+
private BlockPos pos;
24+
25+
@Inject(at = @At("HEAD"), method = "handle(Lnet/minecraft/network/protocol/game/ClientGamePacketListener;)V")
26+
public void preHandle(final ClientGamePacketListener pHandler, final CallbackInfo ci) {
27+
final ClientLevel level = Minecraft.getInstance().level;
28+
if (level != null) {
29+
final LevelChunk chunk = level.getChunkSource().getChunk(
30+
this.pos.getX() >> SectionPos.SECTION_BITS,
31+
this.pos.getZ() >> SectionPos.SECTION_BITS,
32+
false
33+
);
34+
if (chunk instanceof final DebugLevelChunkExtension ext) {
35+
ext.sable$setUpdated();
36+
}
37+
}
38+
}
39+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package dev.ryanhcode.sable.mixin.loaded_chunk_debug;
2+
3+
import com.mojang.blaze3d.vertex.PoseStack;
4+
import dev.ryanhcode.sable.SableClientConfig;
5+
import dev.ryanhcode.sable.mixinhelpers.loaded_chunk_debug.SableChunkDebugRenderer;
6+
import net.minecraft.client.renderer.MultiBufferSource;
7+
import net.minecraft.client.renderer.debug.ChunkBorderRenderer;
8+
import org.spongepowered.asm.mixin.Mixin;
9+
import org.spongepowered.asm.mixin.injection.At;
10+
import org.spongepowered.asm.mixin.injection.Inject;
11+
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
12+
13+
@Mixin(ChunkBorderRenderer.class)
14+
public class ChunkBorderRendererMixin {
15+
16+
@Inject(at = @At("HEAD"), method = "render", cancellable = true)
17+
public void render(final PoseStack poseStack, final MultiBufferSource bufferSource, final double camX, final double camY, final double camZ, final CallbackInfo ci) {
18+
if (SableClientConfig.DEBUG_DRAW_LOADED_CHUNKS.getAsBoolean()) {
19+
ci.cancel();
20+
SableChunkDebugRenderer.render(poseStack, bufferSource, camX, camY, camZ);
21+
}
22+
}
23+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package dev.ryanhcode.sable.mixin.loaded_chunk_debug;
2+
3+
import net.minecraft.client.multiplayer.ClientChunkCache;
4+
import net.minecraft.world.level.chunk.LevelChunk;
5+
import org.spongepowered.asm.mixin.Mixin;
6+
import org.spongepowered.asm.mixin.gen.Accessor;
7+
import java.util.concurrent.atomic.AtomicReferenceArray;
8+
9+
@Mixin(ClientChunkCache.Storage.class)
10+
public interface ClientChunkCacheStorageAccessor {
11+
12+
@Accessor
13+
AtomicReferenceArray<LevelChunk> getChunks();
14+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package dev.ryanhcode.sable.mixin.loaded_chunk_debug;
2+
3+
import dev.ryanhcode.sable.mixinterface.loaded_chunk_debug.DebugLevelChunkExtension;
4+
import net.minecraft.world.level.chunk.LevelChunk;
5+
import org.spongepowered.asm.mixin.Mixin;
6+
import org.spongepowered.asm.mixin.Unique;
7+
8+
@Mixin(LevelChunk.class)
9+
public class LevelChunkMixin implements DebugLevelChunkExtension {
10+
11+
@Unique
12+
private long sable$lastUpdate;
13+
14+
@Override
15+
public void sable$setUpdated() {
16+
this.sable$lastUpdate = System.currentTimeMillis();
17+
}
18+
19+
@Override
20+
public long sable$getLastUpdate() {
21+
return this.sable$lastUpdate;
22+
}
23+
}

common/src/main/java/dev/ryanhcode/sable/mixin/plot/ClientChunkCacheMixin.java

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,18 @@
11
package dev.ryanhcode.sable.mixin.plot;
22

33
import dev.ryanhcode.sable.api.sublevel.SubLevelContainer;
4+
import dev.ryanhcode.sable.mixin.loaded_chunk_debug.ClientChunkCacheStorageAccessor;
5+
import dev.ryanhcode.sable.mixinterface.loaded_chunk_debug.DebugChunkProviderAttachments;
46
import dev.ryanhcode.sable.platform.SableChunkEventPlatform;
7+
import dev.ryanhcode.sable.sublevel.SubLevel;
8+
import dev.ryanhcode.sable.sublevel.plot.PlotChunkHolder;
59
import net.minecraft.client.multiplayer.ClientChunkCache;
610
import net.minecraft.client.multiplayer.ClientLevel;
711
import net.minecraft.nbt.CompoundTag;
812
import net.minecraft.network.FriendlyByteBuf;
913
import net.minecraft.network.protocol.game.ClientboundLevelChunkPacketData;
1014
import net.minecraft.world.level.ChunkPos;
15+
import net.minecraft.world.level.chunk.ChunkAccess;
1116
import net.minecraft.world.level.chunk.LevelChunk;
1217
import net.minecraft.world.level.chunk.status.ChunkStatus;
1318
import org.jetbrains.annotations.NotNull;
@@ -21,14 +26,17 @@
2126
import org.spongepowered.asm.mixin.injection.Inject;
2227
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
2328
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
24-
29+
import java.util.Collection;
30+
import java.util.LinkedList;
31+
import java.util.List;
32+
import java.util.concurrent.atomic.AtomicReferenceArray;
2533
import java.util.function.Consumer;
2634

2735
/**
2836
* Makes the chunk access methods in the client chunk cache use the plot system.
2937
*/
3038
@Mixin(ClientChunkCache.class)
31-
public abstract class ClientChunkCacheMixin {
39+
public abstract class ClientChunkCacheMixin implements DebugChunkProviderAttachments {
3240

3341
@Shadow
3442
@Final
@@ -45,6 +53,9 @@ private static boolean isValidChunk(@Nullable final LevelChunk levelChunk, final
4553
return false;
4654
}
4755

56+
@Shadow
57+
volatile ClientChunkCache.Storage storage;
58+
4859
@Unique
4960
private @NotNull SubLevelContainer sable$getPlotContainer() {
5061
final SubLevelContainer container = SubLevelContainer.getContainer(this.level);
@@ -98,7 +109,8 @@ private void replaceBiomes(final int x, final int z, final FriendlyByteBuf frien
98109
}
99110

100111
@Inject(method = "replaceWithPacketData", at = @At("HEAD"), cancellable = true)
101-
private void replaceWithPacketData(final int x, final int z, final FriendlyByteBuf friendlyByteBuf, final CompoundTag compoundTag, final Consumer<ClientboundLevelChunkPacketData.BlockEntityTagOutput> consumer, final CallbackInfoReturnable<LevelChunk> cir) {
112+
private void replaceWithPacketData(final int x, final int z, final FriendlyByteBuf friendlyByteBuf, final CompoundTag compoundTag,
113+
final Consumer<ClientboundLevelChunkPacketData.BlockEntityTagOutput> consumer, final CallbackInfoReturnable<LevelChunk> cir) {
102114
final SubLevelContainer container = this.sable$getPlotContainer();
103115

104116
if (container.inBounds(x, z)) {
@@ -124,5 +136,21 @@ private void replaceWithPacketData(final int x, final int z, final FriendlyByteB
124136
}
125137
}
126138

139+
@Override
140+
public Collection<LevelChunk> sable$loadedChunks() {
141+
final List<LevelChunk> loadedChunks = new LinkedList<>();
142+
143+
final ClientChunkCacheStorageAccessor accessor = (ClientChunkCacheStorageAccessor) (Object) this.storage;
144+
if (accessor != null) {
145+
final AtomicReferenceArray<LevelChunk> chunks = accessor.getChunks();
146+
for (int i = 0; i < chunks.length(); i++) {
147+
final LevelChunk chunk = chunks.get(i);
148+
if (chunk != null) {
149+
loadedChunks.add(chunk);
150+
}
151+
}
152+
}
127153

154+
return loadedChunks;
155+
}
128156
}
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
package dev.ryanhcode.sable.mixinhelpers.loaded_chunk_debug;
2+
3+
import com.mojang.blaze3d.vertex.PoseStack;
4+
import com.mojang.blaze3d.vertex.VertexConsumer;
5+
import dev.ryanhcode.sable.mixinterface.loaded_chunk_debug.DebugChunkProviderAttachments;
6+
import dev.ryanhcode.sable.mixinterface.loaded_chunk_debug.DebugLevelChunkExtension;
7+
import net.minecraft.client.Minecraft;
8+
import net.minecraft.client.multiplayer.ClientLevel;
9+
import net.minecraft.client.renderer.MultiBufferSource;
10+
import net.minecraft.client.renderer.RenderType;
11+
import net.minecraft.util.Mth;
12+
import net.minecraft.world.entity.Entity;
13+
import net.minecraft.world.level.ChunkPos;
14+
import net.minecraft.world.level.chunk.LevelChunk;
15+
import org.jetbrains.annotations.ApiStatus;
16+
import org.joml.Matrix4f;
17+
18+
@ApiStatus.Internal
19+
public class SableChunkDebugRenderer {
20+
21+
public static void render(final PoseStack poseStack, final MultiBufferSource bufferSource, final double camX, final double camY, final double camZ) {
22+
final long time = System.currentTimeMillis();
23+
24+
final Minecraft minecraft = Minecraft.getInstance();
25+
final Entity entity = minecraft.gameRenderer.getMainCamera().getEntity();
26+
final VertexConsumer builder = bufferSource.getBuffer(RenderType.debugLineStrip(1.0F));
27+
final Matrix4f pose = poseStack.last().pose();
28+
29+
final ClientLevel level = minecraft.level;
30+
final int minBuildHeight = level.getMinBuildHeight();
31+
final int maxBuildHeight = level.getMaxBuildHeight();
32+
33+
final DebugChunkProviderAttachments attachments = (DebugChunkProviderAttachments) level.getChunkSource();
34+
for (final LevelChunk chunk : attachments.sable$loadedChunks()) {
35+
final ChunkPos pos = chunk.getPos();
36+
final float diff = Mth.clamp(time - ((DebugLevelChunkExtension) chunk).sable$getLastUpdate(), 0, 1000) / 1000.0F;
37+
final float red = 1.0F - diff;
38+
final float blue = 0.0F;
39+
40+
final float x = (float) (pos.getMinBlockX() - camX);
41+
final float z = (float) (pos.getMinBlockZ() - camZ);
42+
float y = (float) (minBuildHeight - camY);
43+
if (camY > minBuildHeight) {
44+
y += (10 * ((1 - diff) / 100));
45+
} else {
46+
y -= (10 * ((1 - diff) / 100));
47+
}
48+
float y1 = (float) (maxBuildHeight - camY);
49+
if (camY < maxBuildHeight) {
50+
y1 -= (10 * ((1 - diff) / 100));
51+
} else {
52+
y1 += (10 * ((1 - diff) / 100));
53+
}
54+
builder.addVertex(pose, x, y, z).setColor(red, diff, blue, 0.0F);
55+
56+
builder.addVertex(pose, x, y, z).setColor(red, diff, blue, 1.0F);
57+
builder.addVertex(pose, x + 16, y, z).setColor(red, diff, blue, 1.0F);
58+
builder.addVertex(pose, x + 16, y, z + 16).setColor(red, diff, blue, 1.0F);
59+
builder.addVertex(pose, x, y, z + 16).setColor(red, diff, blue, 1.0F);
60+
61+
builder.addVertex(pose, x, y, z).setColor(red, diff, blue, 0.0F);
62+
63+
y = y1;
64+
builder.addVertex(pose, x, y, z).setColor(red, diff, blue, 0.0F);
65+
66+
builder.addVertex(pose, x, y, z).setColor(red, diff, blue, 1.0F);
67+
builder.addVertex(pose, x + 16, y, z).setColor(red, diff, blue, 1.0F);
68+
builder.addVertex(pose, x + 16, y, z + 16).setColor(red, diff, blue, 1.0F);
69+
builder.addVertex(pose, x, y, z + 16).setColor(red, diff, blue, 1.0F);
70+
71+
builder.addVertex(pose, x, y, z).setColor(red, diff, blue, 0.0F);
72+
}
73+
74+
final ChunkPos ckPos = entity.chunkPosition();
75+
final float x = (float) (ckPos.x * 16 - camX);
76+
float y = (float) (minBuildHeight - camY);
77+
float y1 = (float) (maxBuildHeight - camY);
78+
final float z = (float) (ckPos.z * 16 - camZ);
79+
80+
for (int xO = 0; xO < 2; xO++) {
81+
for (int zO = 0; zO < 2; zO++) {
82+
builder.addVertex(pose, x + xO * 16, y, z + zO * 16).setColor(1, 1, 0.0F, 0.0F);
83+
84+
builder.addVertex(pose, x + xO * 16, y, z + zO * 16).setColor(1, 1, 0.0F, 1.0F);
85+
builder.addVertex(pose, x + xO * 16, y1, z + zO * 16).setColor(1, 1, 0.0F, 1.0F);
86+
87+
builder.addVertex(pose, x + xO * 16, y1, z + zO * 16).setColor(1, 1, 0.0F, 0.0F);
88+
}
89+
}
90+
91+
y = minBuildHeight;
92+
y = ((int) (y / 16)) * 16;
93+
y1 = maxBuildHeight;
94+
95+
for (int yO = (int) y; yO <= y1 + 1; yO += 16) {
96+
builder.addVertex(pose, x, (float) (yO - camY), z).setColor(0, 0, 1.0F, 0.0F);
97+
98+
builder.addVertex(pose, x, (float) (yO - camY), z).setColor(0, 0, 1.0F, 1.0F);
99+
builder.addVertex(pose, x + 16, (float) (yO - camY), z).setColor(0, 0, 1.0F, 1.0F);
100+
builder.addVertex(pose, x + 16, (float) (yO - camY), z + 16).setColor(0, 0, 1.0F, 1.0F);
101+
builder.addVertex(pose, x, (float) (yO - camY), z + 16).setColor(0, 0, 1.0F, 1.0F);
102+
builder.addVertex(pose, x, (float) (yO - camY), z).setColor(0, 0, 1.0F, 1.0F);
103+
104+
builder.addVertex(pose, x, (float) (yO - camY), z).setColor(0, 0, 1.0F, 0.0F);
105+
}
106+
}
107+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package dev.ryanhcode.sable.mixinterface.loaded_chunk_debug;
2+
3+
import net.minecraft.world.level.chunk.LevelChunk;
4+
import java.util.Collection;
5+
6+
public interface DebugChunkProviderAttachments {
7+
8+
Collection<LevelChunk> sable$loadedChunks();
9+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
package dev.ryanhcode.sable.mixinterface.loaded_chunk_debug;
2+
3+
public interface DebugLevelChunkExtension {
4+
5+
void sable$setUpdated();
6+
7+
long sable$getLastUpdate();
8+
}

common/src/main/java/dev/ryanhcode/sable/sublevel/plot/LevelPlot.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ public abstract class LevelPlot {
104104
* @param logSize the log_2 of the side length of a plot
105105
* @param subLevel the sub-level using this plot
106106
*/
107-
public LevelPlot(final SubLevelContainer container, final int x, final int z, final int logSize, final SubLevel subLevel) {
107+
public LevelPlot(final SubLevelContainer container, final int x, final int z, final int logSize, final @NotNull SubLevel subLevel) {
108108
this.container = container;
109109
this.plotPos = new ChunkPos(x, z);
110110
this.logSize = logSize;

0 commit comments

Comments
 (0)