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