-
Notifications
You must be signed in to change notification settings - Fork 35
/
LightOverlayRenderer.java
182 lines (166 loc) · 9.55 KB
/
LightOverlayRenderer.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
package me.shedaniel.lightoverlay.common;
import com.google.common.base.Suppliers;
import com.mojang.blaze3d.systems.RenderSystem;
import com.mojang.blaze3d.vertex.*;
import com.mojang.math.Matrix4f;
import com.mojang.math.Vector3f;
import dev.architectury.injectables.targets.ArchitecturyTarget;
import it.unimi.dsi.fastutil.longs.Long2ByteMap;
import net.minecraft.client.Camera;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.Font;
import net.minecraft.client.player.LocalPlayer;
import net.minecraft.client.renderer.GameRenderer;
import net.minecraft.client.renderer.MultiBufferSource;
import net.minecraft.client.renderer.culling.Frustum;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.util.Mth;
import net.minecraft.world.level.Level;
import net.minecraft.world.phys.shapes.CollisionContext;
import net.minecraft.world.phys.shapes.VoxelShape;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.util.Map;
import java.util.function.Consumer;
import java.util.function.Supplier;
public class LightOverlayRenderer implements Consumer<PoseStack> {
private final Minecraft minecraft = Minecraft.getInstance();
public Frustum frustum;
public LightOverlayTicker ticker;
public LightOverlayRenderer(LightOverlayTicker ticker) {
this.ticker = ticker;
}
@Override
public void accept(PoseStack poses) {
if (LightOverlay.enabled) {
LocalPlayer playerEntity = minecraft.player;
BlockPos playerPos = new BlockPos(playerEntity.getX(), playerEntity.getY(), playerEntity.getZ());
int playerPosX = playerPos.getX() >> 4;
int playerPosY = playerPos.getY() >> 5;
int playerPosZ = playerPos.getZ() >> 4;
CollisionContext collisionContext = CollisionContext.of(playerEntity);
Camera camera = minecraft.gameRenderer.getMainCamera();
int chunkRange = LightOverlay.getChunkRange();
if (LightOverlay.showNumber) {
renderLevels(new PoseStack(), camera, playerPos, playerPosX, playerPosY, playerPosZ, chunkRange, collisionContext);
} else {
renderCrosses(poses, camera, playerPos, playerPosX, playerPosY, playerPosZ, chunkRange, collisionContext);
}
}
}
private void renderLevels(PoseStack poses, Camera camera, BlockPos playerPos, int playerPosX, int playerPosY, int playerPosZ, int chunkRange, CollisionContext collisionContext) {
RenderSystem.enableTexture();
RenderSystem.depthMask(true);
BlockPos.MutableBlockPos mutable = new BlockPos.MutableBlockPos();
BlockPos.MutableBlockPos downMutable = new BlockPos.MutableBlockPos();
MultiBufferSource.BufferSource source = MultiBufferSource.immediate(Tesselator.getInstance().getBuilder());
for (Map.Entry<CubicChunkPos, Long2ByteMap> entry : ticker.CHUNK_MAP.entrySet()) {
CubicChunkPos chunkPos = entry.getKey();
if (LightOverlay.caching && (Mth.abs(chunkPos.x - playerPosX) > chunkRange || Mth.abs(chunkPos.y - playerPosY) > Math.max(1, chunkRange >> 1) || Mth.abs(chunkPos.z - playerPosZ) > chunkRange)) {
continue;
}
for (Long2ByteMap.Entry objectEntry : entry.getValue().long2ByteEntrySet()) {
mutable.set(objectEntry.getLongKey());
if (mutable.closerThan(playerPos, LightOverlay.reach)) {
if (isFrustumVisible(mutable.getX(), mutable.getY(), mutable.getZ(), mutable.getX() + 1, mutable.getX() + 1, mutable.getX() + 1)) {
downMutable.set(mutable.getX(), mutable.getY() - 1, mutable.getZ());
renderLevel(poses, source, camera, minecraft.level, mutable, downMutable, objectEntry.getByteValue(), collisionContext);
}
}
}
}
RenderSystem.enableDepthTest();
source.endBatch();
}
public void renderLevel(PoseStack poses, MultiBufferSource.BufferSource source, Camera camera, Level world, BlockPos pos, BlockPos down, byte level, CollisionContext collisionContext) {
String text = String.valueOf(level);
Font font = minecraft.font;
double cameraX = camera.getPosition().x;
double cameraY = camera.getPosition().y;
VoxelShape upperOutlineShape = world.getBlockState(down).getShape(world, down, collisionContext);
if (!upperOutlineShape.isEmpty())
cameraY += 1 - upperOutlineShape.max(Direction.Axis.Y);
double cameraZ = camera.getPosition().z;
poses.pushPose();
poses.translate(pos.getX() + 0.5 - cameraX, pos.getY() - cameraY + 0.005, pos.getZ() + 0.5 - cameraZ);
poses.mulPose(Vector3f.XP.rotationDegrees(90));
// poses.glNormal3f(0.0F, 1.0F, 0.0F);
float size = 0.07F;
poses.scale(-size, -size, size);
float float_3 = (float) (-font.width(text)) / 2.0F + 0.4f;
font.drawInBatch(text, float_3, -3.5f, level > LightOverlay.higherCrossLevel ? 0xff042404 : (LightOverlay.lowerCrossLevel >= 0 && level > LightOverlay.lowerCrossLevel ? 0xff0066ff : 0xff731111), false, poses.last().pose(), source, false, 0, 15728880);
poses.popPose();
}
private void renderCrosses(PoseStack poses, Camera camera, BlockPos playerPos, int playerPosX, int playerPosY, int playerPosZ, int chunkRange, CollisionContext collisionContext) {
RenderSystem.enableDepthTest();
RenderSystem.disableTexture();
RenderSystem.disableBlend();
RenderSystem.setShader(GameRenderer::getPositionColorShader);
RenderSystem.lineWidth(LightOverlay.lineWidth);
Tesselator tesselator = Tesselator.getInstance();
BufferBuilder builder = tesselator.getBuilder();
builder.begin(VertexFormat.Mode.DEBUG_LINES, DefaultVertexFormat.POSITION_COLOR);
BlockPos.MutableBlockPos mutable = new BlockPos.MutableBlockPos();
for (Map.Entry<CubicChunkPos, Long2ByteMap> entry : ticker.CHUNK_MAP.entrySet()) {
CubicChunkPos chunkPos = entry.getKey();
if (LightOverlay.caching && (Mth.abs(chunkPos.x - playerPosX) > chunkRange || Mth.abs(chunkPos.y - playerPosY) > Math.max(1, chunkRange >> 1) || Mth.abs(chunkPos.z - playerPosZ) > chunkRange)) {
continue;
}
for (Long2ByteMap.Entry objectEntry : entry.getValue().long2ByteEntrySet()) {
byte crossType = objectEntry.getByteValue();
mutable.set(objectEntry.getLongKey());
if (mutable.closerThan(playerPos, LightOverlay.reach)) {
if (isFrustumVisible(mutable.getX(), mutable.getY(), mutable.getZ(), mutable.getX() + 1, mutable.getX() + 1, mutable.getX() + 1)) {
int color = switch (crossType) {
case LightOverlay.CROSS_RED -> LightOverlay.redColor;
case LightOverlay.CROSS_YELLOW -> LightOverlay.yellowColor;
default -> LightOverlay.secondaryColor;
};
renderCross(poses.last().pose(), builder, camera, minecraft.level, mutable, color, collisionContext);
}
}
}
}
tesselator.end();
RenderSystem.lineWidth(1.0F);
RenderSystem.enableBlend();
RenderSystem.enableTexture();
}
public void renderCross(Matrix4f pose, BufferBuilder builder, Camera camera, Level world, BlockPos pos, int color, CollisionContext collisionContext) {
double cameraX = camera.getPosition().x;
double cameraY = camera.getPosition().y - .005D;
double blockOffset = 0;
VoxelShape upperOutlineShape = world.getBlockState(pos).getShape(world, pos, collisionContext);
if (!upperOutlineShape.isEmpty()) {
blockOffset += upperOutlineShape.max(Direction.Axis.Y);
}
double cameraZ = camera.getPosition().z;
int red = (color >> 16) & 255;
int green = (color >> 8) & 255;
int blue = color & 255;
double x = pos.getX() - cameraX;
double y = pos.getY() - cameraY + blockOffset;
double z = pos.getZ() - cameraZ;
builder.vertex(x + .01, y, z + .01).color(red, green, blue, 255).endVertex();
builder.vertex(x + .99, y, z + .99).color(red, green, blue, 255).endVertex();
builder.vertex(x + .99, y, z + .01).color(red, green, blue, 255).endVertex();
builder.vertex(x + .01, y, z + .99).color(red, green, blue, 255).endVertex();
}
private static final Supplier<MethodHandle> IS_FRUSTUM_VISIBLE = Suppliers.memoize(() -> {
try {
return MethodHandles.lookup().findStatic(Class.forName("me.shedaniel.lightoverlay." + ArchitecturyTarget.getCurrentTarget() + ".LightOverlayImpl"), "isFrustumVisible",
MethodType.methodType(boolean.class, Frustum.class, double.class, double.class, double.class, double.class, double.class, double.class));
} catch (NoSuchMethodException | IllegalAccessException | ClassNotFoundException e) {
throw new RuntimeException(e);
}
});
public boolean isFrustumVisible(double minX, double minY, double minZ, double maxX, double maxY, double maxZ) {
try {
return frustum == null || (boolean) IS_FRUSTUM_VISIBLE.get().invokeExact(frustum, minX, minY, minZ, maxX, maxY, maxZ);
} catch (Throwable throwable) {
throw new RuntimeException(throwable);
}
}
}