1 package me.shedaniel.lightoverlay.common;
3 import com.google.common.collect.Maps;
4 import com.mojang.blaze3d.platform.GlStateManager;
5 import com.mojang.blaze3d.platform.InputConstants;
6 import com.mojang.blaze3d.systems.RenderSystem;
7 import com.mojang.blaze3d.vertex.Tesselator;
8 import com.mojang.math.Transformation;
9 import it.unimi.dsi.fastutil.longs.Long2ReferenceMap;
10 import it.unimi.dsi.fastutil.longs.Long2ReferenceOpenHashMap;
11 import me.shedaniel.architectury.event.events.GuiEvent;
12 import me.shedaniel.architectury.event.events.client.ClientTickEvent;
13 import me.shedaniel.architectury.platform.Platform;
14 import me.shedaniel.architectury.registry.KeyBindings;
15 import net.minecraft.client.Camera;
16 import net.minecraft.client.KeyMapping;
17 import net.minecraft.client.Minecraft;
18 import net.minecraft.client.gui.Font;
19 import net.minecraft.client.multiplayer.ClientLevel;
20 import net.minecraft.client.player.LocalPlayer;
21 import net.minecraft.client.renderer.MultiBufferSource;
22 import net.minecraft.client.renderer.culling.Frustum;
23 import net.minecraft.core.BlockPos;
24 import net.minecraft.core.Direction;
25 import net.minecraft.resources.ResourceLocation;
26 import net.minecraft.tags.BlockTags;
27 import net.minecraft.util.LazyLoadedValue;
28 import net.minecraft.util.Mth;
29 import net.minecraft.world.entity.Entity;
30 import net.minecraft.world.entity.EntityType;
31 import net.minecraft.world.entity.MobCategory;
32 import net.minecraft.world.level.BlockGetter;
33 import net.minecraft.world.level.ChunkPos;
34 import net.minecraft.world.level.Level;
35 import net.minecraft.world.level.LightLayer;
36 import net.minecraft.world.level.block.Block;
37 import net.minecraft.world.level.block.state.BlockState;
38 import net.minecraft.world.level.chunk.ChunkStatus;
39 import net.minecraft.world.level.chunk.LevelChunk;
40 import net.minecraft.world.level.lighting.LayerLightEventListener;
41 import net.minecraft.world.phys.shapes.CollisionContext;
42 import net.minecraft.world.phys.shapes.VoxelShape;
43 import org.apache.logging.log4j.LogManager;
44 import org.lwjgl.opengl.GL11;
47 import java.io.FileInputStream;
48 import java.io.FileOutputStream;
49 import java.io.IOException;
50 import java.lang.invoke.MethodHandle;
51 import java.lang.invoke.MethodHandles;
52 import java.lang.invoke.MethodType;
53 import java.text.DecimalFormat;
55 import java.util.concurrent.Executors;
56 import java.util.concurrent.ThreadPoolExecutor;
58 public class LightOverlay {
59 public static final DecimalFormat FORMAT = new DecimalFormat("#.#");
60 private static final String KEYBIND_CATEGORY = "key.lightoverlay.category";
61 private static final ResourceLocation ENABLE_OVERLAY_KEYBIND = new ResourceLocation("lightoverlay", "enable_overlay");
62 public static int reach = 12;
63 public static int crossLevel = 7;
64 public static int secondaryLevel = -1;
65 public static int lowerCrossLevel = -1;
66 public static int higherCrossLevel = -1;
67 public static boolean caching = false;
68 public static boolean showNumber = false;
69 public static boolean smoothLines = true;
70 public static boolean underwater = false;
71 public static float lineWidth = 1.0F;
72 public static int yellowColor = 0xFFFF00, redColor = 0xFF0000, secondaryColor = 0x0000FF;
73 public static File configFile;
74 private static KeyMapping enableOverlay;
75 private static boolean enabled = false;
76 private static final LazyLoadedValue<EntityType<Entity>> TESTING_ENTITY_TYPE = new LazyLoadedValue<>(() ->
77 EntityType.Builder.createNothing(MobCategory.MONSTER).sized(0f, 0f).noSave().build(null));
78 private static int threadNumber = 0;
79 public static Frustum frustum;
80 private static final ThreadPoolExecutor EXECUTOR = (ThreadPoolExecutor) Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors(), r -> {
81 Thread thread = new Thread(r, "light-overlay-" + threadNumber++);
82 thread.setDaemon(true);
85 private static final Set<ChunkPos> POS = Collections.synchronizedSet(new HashSet<>());
86 private static final Set<ChunkPos> CALCULATING_POS = Collections.synchronizedSet(new HashSet<>());
87 private static final Map<ChunkPos, Long2ReferenceMap<Object>> CHUNK_MAP = Maps.newConcurrentMap();
88 private static final Minecraft CLIENT = Minecraft.getInstance();
89 private static long ticks = 0;
91 public static void register() {
93 configFile = new File(Platform.getConfigFolder().toFile(), "lightoverlay.properties");
94 loadConfig(configFile);
96 enableOverlay = createKeyBinding(ENABLE_OVERLAY_KEYBIND, InputConstants.Type.KEYSYM, 296, KEYBIND_CATEGORY);
97 KeyBindings.registerKeyBinding(enableOverlay);
99 registerDebugRenderer(() -> {
101 LocalPlayer playerEntity = CLIENT.player;
102 int playerPosX = ((int) playerEntity.getX()) >> 4;
103 int playerPosZ = ((int) playerEntity.getZ()) >> 4;
104 CollisionContext collisionContext = CollisionContext.of(playerEntity);
105 Level world = CLIENT.level;
106 BlockPos playerPos = new BlockPos(playerEntity.getX(), playerEntity.getY(), playerEntity.getZ());
107 Camera camera = CLIENT.gameRenderer.getMainCamera();
110 RenderSystem.enableTexture();
111 RenderSystem.depthMask(true);
112 BlockPos.MutableBlockPos mutable = new BlockPos.MutableBlockPos();
113 BlockPos.MutableBlockPos downMutable = new BlockPos.MutableBlockPos();
114 for (Map.Entry<ChunkPos, Long2ReferenceMap<Object>> entry : CHUNK_MAP.entrySet()) {
115 if (caching && (Mth.abs(entry.getKey().x - playerPosX) > getChunkRange() || Mth.abs(entry.getKey().z - playerPosZ) > getChunkRange())) {
118 for (Long2ReferenceMap.Entry<Object> objectEntry : entry.getValue().long2ReferenceEntrySet()) {
119 if (objectEntry.getValue() instanceof Byte) {
120 mutable.set(BlockPos.getX(objectEntry.getLongKey()), BlockPos.getY(objectEntry.getLongKey()), BlockPos.getZ(objectEntry.getLongKey()));
121 if (mutable.closerThan(playerPos, reach)) {
122 if (frustum == null || isFrustumVisible(frustum, mutable.getX(), mutable.getY(), mutable.getZ(), mutable.getX() + 1, mutable.getX() + 1, mutable.getX() + 1)) {
123 downMutable.set(mutable.getX(), mutable.getY() - 1, mutable.getZ());
124 renderLevel(CLIENT, camera, world, mutable, downMutable, (Byte) objectEntry.getValue(), collisionContext);
130 RenderSystem.enableDepthTest();
132 RenderSystem.enableDepthTest();
133 RenderSystem.disableTexture();
134 RenderSystem.enableBlend();
135 RenderSystem.blendFunc(GlStateManager.SourceFactor.SRC_ALPHA, GlStateManager.DestFactor.ONE_MINUS_SRC_ALPHA);
136 if (smoothLines) GL11.glEnable(GL11.GL_LINE_SMOOTH);
137 GL11.glLineWidth(lineWidth);
138 GL11.glBegin(GL11.GL_LINES);
139 BlockPos.MutableBlockPos mutable = new BlockPos.MutableBlockPos();
140 for (Map.Entry<ChunkPos, Long2ReferenceMap<Object>> entry : CHUNK_MAP.entrySet()) {
141 if (caching && (Mth.abs(entry.getKey().x - playerPosX) > getChunkRange() || Mth.abs(entry.getKey().z - playerPosZ) > getChunkRange())) {
144 for (Long2ReferenceMap.Entry<Object> objectEntry : entry.getValue().long2ReferenceEntrySet()) {
145 if (objectEntry.getValue() instanceof CrossType) {
146 mutable.set(BlockPos.getX(objectEntry.getLongKey()), BlockPos.getY(objectEntry.getLongKey()), BlockPos.getZ(objectEntry.getLongKey()));
147 if (mutable.closerThan(playerPos, reach)) {
148 if (frustum == null || isFrustumVisible(frustum, mutable.getX(), mutable.getY(), mutable.getZ(), mutable.getX() + 1, mutable.getX() + 1, mutable.getX() + 1)) {
149 int color = objectEntry.getValue() == CrossType.RED ? redColor : objectEntry.getValue() == CrossType.YELLOW ? yellowColor : secondaryColor;
150 renderCross(camera, world, mutable, color, collisionContext);
157 RenderSystem.disableBlend();
158 RenderSystem.enableTexture();
159 if (smoothLines) GL11.glDisable(GL11.GL_LINE_SMOOTH);
164 GuiEvent.DEBUG_TEXT_LEFT.register(list -> {
167 list.add(String.format("[Light Overlay] Chunks to queue: %02d", POS.size()));
169 list.add("[Light Overlay] Enabled");
172 list.add("[Light Overlay] Disabled");
175 ClientTickEvent.CLIENT_POST.register(LightOverlay::tick);
178 private static void processChunk(ChunkPos pos, int playerPosX, int playerPosZ, CollisionContext context) {
179 CALCULATING_POS.remove(pos);
180 if (Mth.abs(pos.x - playerPosX) > getChunkRange() || Mth.abs(pos.z - playerPosZ) > getChunkRange() || POS.contains(pos)) {
184 calculateChunk(CLIENT.level.getChunkSource().getChunk(pos.x, pos.z, ChunkStatus.FULL, false), CLIENT.level, pos, context);
185 } catch (Throwable throwable) {
186 LogManager.getLogger().throwing(throwable);
190 public static void queueChunkAndNear(ChunkPos pos) {
191 for (int xOffset = -1; xOffset <= 1; xOffset++) {
192 for (int zOffset = -1; zOffset <= 1; zOffset++) {
193 queueChunk(new ChunkPos(pos.x + xOffset, pos.z + zOffset));
198 public static void queueChunk(ChunkPos pos) {
199 if (enabled && caching && !CALCULATING_POS.contains(pos)) {
204 public static int getChunkRange() {
205 return Math.max(Mth.ceil(reach / 16f), 1);
208 private static void calculateChunk(LevelChunk chunk, Level world, ChunkPos chunkPos, CollisionContext entityContext) {
209 if (world != null && chunk != null) {
210 Long2ReferenceMap<Object> map = new Long2ReferenceOpenHashMap<>();
211 LayerLightEventListener block = world.getLightEngine().getLayerListener(LightLayer.BLOCK);
212 LayerLightEventListener sky = showNumber ? null : world.getLightEngine().getLayerListener(LightLayer.SKY);
213 for (BlockPos pos : BlockPos.betweenClosed(chunkPos.getMinBlockX(), 0, chunkPos.getMinBlockZ(), chunkPos.getMaxBlockX(), 256, chunkPos.getMaxBlockZ())) {
214 BlockPos down = pos.below();
216 int level = getCrossLevel(pos, down, chunk, block, entityContext);
218 map.put(pos.asLong(), Byte.valueOf((byte) level));
221 CrossType type = getCrossType(pos, down, chunk, block, sky, entityContext);
222 if (type != CrossType.NONE) {
223 map.put(pos.asLong(), type);
227 CHUNK_MAP.put(chunkPos, map);
229 CHUNK_MAP.remove(chunkPos);
233 public static CrossType getCrossType(BlockPos pos, BlockPos down, BlockGetter world, LayerLightEventListener block, LayerLightEventListener sky, CollisionContext entityContext) {
234 BlockState blockBelowState = world.getBlockState(down);
235 BlockState blockUpperState = world.getBlockState(pos);
236 VoxelShape upperCollisionShape = blockUpperState.getCollisionShape(world, pos, entityContext);
237 if (!underwater && !blockUpperState.getFluidState().isEmpty())
238 return CrossType.NONE;
239 // Check if the outline is full
240 if (Block.isFaceFull(upperCollisionShape, Direction.UP))
241 return CrossType.NONE;
242 // TODO: Not to hard code no redstone
243 if (blockUpperState.isSignalSource())
244 return CrossType.NONE;
245 // Check if the collision has a bump
246 if (upperCollisionShape.max(Direction.Axis.Y) > 0)
247 return CrossType.NONE;
248 if (blockUpperState.getBlock().is(BlockTags.RAILS))
249 return CrossType.NONE;
250 // Check block state allow spawning (excludes bedrock and barriers automatically)
251 if (!blockBelowState.isValidSpawn(world, down, TESTING_ENTITY_TYPE.get()))
252 return CrossType.NONE;
253 int blockLightLevel = block.getLightValue(pos);
254 int skyLightLevel = sky.getLightValue(pos);
255 if (blockLightLevel > higherCrossLevel)
256 return CrossType.NONE;
257 if (skyLightLevel > higherCrossLevel)
258 return CrossType.YELLOW;
259 return lowerCrossLevel >= 0 && blockLightLevel > lowerCrossLevel ? CrossType.SECONDARY : CrossType.RED;
262 public static int getCrossLevel(BlockPos pos, BlockPos down, BlockGetter world, LayerLightEventListener view, CollisionContext collisionContext) {
263 BlockState blockBelowState = world.getBlockState(down);
264 BlockState blockUpperState = world.getBlockState(pos);
265 VoxelShape collisionShape = blockBelowState.getCollisionShape(world, down, collisionContext);
266 VoxelShape upperCollisionShape = blockUpperState.getCollisionShape(world, pos, collisionContext);
267 if (!underwater && !blockUpperState.getFluidState().isEmpty())
269 if (!blockBelowState.getFluidState().isEmpty())
271 if (blockBelowState.isAir())
273 if (Block.isFaceFull(upperCollisionShape, Direction.DOWN))
275 return view.getLightValue(pos);
278 public static void renderCross(Camera camera, Level world, BlockPos pos, int color, CollisionContext collisionContext) {
279 double d0 = camera.getPosition().x;
280 double d1 = camera.getPosition().y - .005D;
281 VoxelShape upperOutlineShape = world.getBlockState(pos).getShape(world, pos, collisionContext);
282 if (!upperOutlineShape.isEmpty())
283 d1 -= upperOutlineShape.max(Direction.Axis.Y);
284 double d2 = camera.getPosition().z;
286 int red = (color >> 16) & 255;
287 int green = (color >> 8) & 255;
288 int blue = color & 255;
292 RenderSystem.color4f(red / 255f, green / 255f, blue / 255f, 1f);
293 GL11.glVertex3d(x + .01 - d0, y - d1, z + .01 - d2);
294 GL11.glVertex3d(x - .01 + 1 - d0, y - d1, z - .01 + 1 - d2);
295 GL11.glVertex3d(x - .01 + 1 - d0, y - d1, z + .01 - d2);
296 GL11.glVertex3d(x + .01 - d0, y - d1, z - .01 + 1 - d2);
299 @SuppressWarnings("deprecation")
300 public static void renderLevel(Minecraft client, Camera camera, Level world, BlockPos pos, BlockPos down, int level, CollisionContext collisionContext) {
301 String text = String.valueOf(level);
302 Font textRenderer_1 = client.font;
303 double double_4 = camera.getPosition().x;
304 double double_5 = camera.getPosition().y;
305 VoxelShape upperOutlineShape = world.getBlockState(down).getShape(world, down, collisionContext);
306 if (!upperOutlineShape.isEmpty())
307 double_5 += 1 - upperOutlineShape.max(Direction.Axis.Y);
308 double double_6 = camera.getPosition().z;
309 RenderSystem.pushMatrix();
310 RenderSystem.translatef((float) (pos.getX() + 0.5f - double_4), (float) (pos.getY() - double_5) + 0.005f, (float) (pos.getZ() + 0.5f - double_6));
311 RenderSystem.rotatef(90, 1, 0, 0);
312 RenderSystem.normal3f(0.0F, 1.0F, 0.0F);
314 RenderSystem.scalef(-size, -size, size);
315 float float_3 = (float) (-textRenderer_1.width(text)) / 2.0F + 0.4f;
316 RenderSystem.enableAlphaTest();
317 MultiBufferSource.BufferSource immediate = MultiBufferSource.immediate(Tesselator.getInstance().getBuilder());
318 textRenderer_1.drawInBatch(text, float_3, -3.5f, level > higherCrossLevel ? 0xff042404 : (lowerCrossLevel >= 0 && level > lowerCrossLevel ? 0xff0066ff : 0xff731111), false, Transformation.identity().getMatrix(), immediate, false, 0, 15728880);
319 immediate.endBatch();
320 RenderSystem.popMatrix();
323 public static void loadConfig(File file) {
326 yellowColor = 0xFFFF00;
327 secondaryColor = 0x0000FF;
328 if (!file.exists() || !file.canRead())
330 FileInputStream fis = new FileInputStream(file);
331 Properties properties = new Properties();
332 properties.load(fis);
334 reach = Integer.parseInt((String) properties.computeIfAbsent("reach", a -> "12"));
335 crossLevel = Integer.parseInt((String) properties.computeIfAbsent("crossLevel", a -> "7"));
336 secondaryLevel = Integer.parseInt((String) properties.computeIfAbsent("secondaryLevel", a -> "-1"));
337 caching = ((String) properties.computeIfAbsent("caching", a -> "false")).equalsIgnoreCase("true");
338 showNumber = ((String) properties.computeIfAbsent("showNumber", a -> "false")).equalsIgnoreCase("true");
339 smoothLines = ((String) properties.computeIfAbsent("smoothLines", a -> "true")).equalsIgnoreCase("true");
340 underwater = ((String) properties.computeIfAbsent("underwater", a -> "false")).equalsIgnoreCase("true");
341 lineWidth = Float.parseFloat((String) properties.computeIfAbsent("lineWidth", a -> "1"));
344 r = Integer.parseInt((String) properties.computeIfAbsent("yellowColorRed", a -> "255"));
345 g = Integer.parseInt((String) properties.computeIfAbsent("yellowColorGreen", a -> "255"));
346 b = Integer.parseInt((String) properties.computeIfAbsent("yellowColorBlue", a -> "0"));
347 yellowColor = (r << 16) + (g << 8) + b;
351 r = Integer.parseInt((String) properties.computeIfAbsent("redColorRed", a -> "255"));
352 g = Integer.parseInt((String) properties.computeIfAbsent("redColorGreen", a -> "0"));
353 b = Integer.parseInt((String) properties.computeIfAbsent("redColorBlue", a -> "0"));
354 redColor = (r << 16) + (g << 8) + b;
358 r = Integer.parseInt((String) properties.computeIfAbsent("secondaryColorRed", a -> "0"));
359 g = Integer.parseInt((String) properties.computeIfAbsent("secondaryColorGreen", a -> "0"));
360 b = Integer.parseInt((String) properties.computeIfAbsent("secondaryColorBlue", a -> "255"));
361 secondaryColor = (r << 16) + (g << 8) + b;
364 } catch (Exception e) {
371 yellowColor = 0xFFFF00;
372 secondaryColor = 0x0000FF;
379 } catch (IOException ex) {
380 ex.printStackTrace();
383 if (secondaryLevel >= crossLevel) System.err.println("[Light Overlay] Secondary Level is higher than Cross Level");
384 lowerCrossLevel = Math.min(crossLevel, secondaryLevel);
385 higherCrossLevel = Math.max(crossLevel, secondaryLevel);
390 public static void saveConfig(File file) throws IOException {
391 FileOutputStream fos = new FileOutputStream(file, false);
392 fos.write("# Light Overlay Config".getBytes());
393 fos.write("\n".getBytes());
394 fos.write(("reach=" + reach).getBytes());
395 fos.write("\n".getBytes());
396 fos.write(("crossLevel=" + crossLevel).getBytes());
397 fos.write("\n".getBytes());
398 fos.write(("secondaryLevel=" + secondaryLevel).getBytes());
399 fos.write("\n".getBytes());
400 fos.write(("caching=" + caching).getBytes());
401 fos.write("\n".getBytes());
402 fos.write(("showNumber=" + showNumber).getBytes());
403 fos.write("\n".getBytes());
404 fos.write(("smoothLines=" + smoothLines).getBytes());
405 fos.write("\n".getBytes());
406 fos.write(("underwater=" + underwater).getBytes());
407 fos.write("\n".getBytes());
408 fos.write(("lineWidth=" + FORMAT.format(lineWidth)).getBytes());
409 fos.write("\n".getBytes());
410 fos.write(("yellowColorRed=" + ((yellowColor >> 16) & 255)).getBytes());
411 fos.write("\n".getBytes());
412 fos.write(("yellowColorGreen=" + ((yellowColor >> 8) & 255)).getBytes());
413 fos.write("\n".getBytes());
414 fos.write(("yellowColorBlue=" + (yellowColor & 255)).getBytes());
415 fos.write("\n".getBytes());
416 fos.write(("redColorRed=" + ((redColor >> 16) & 255)).getBytes());
417 fos.write("\n".getBytes());
418 fos.write(("redColorGreen=" + ((redColor >> 8) & 255)).getBytes());
419 fos.write("\n".getBytes());
420 fos.write(("redColorBlue=" + (redColor & 255)).getBytes());
421 fos.write("\n".getBytes());
422 fos.write(("secondaryColorRed=" + ((secondaryColor >> 16) & 255)).getBytes());
423 fos.write("\n".getBytes());
424 fos.write(("secondaryColorGreen=" + ((secondaryColor >> 8) & 255)).getBytes());
425 fos.write("\n".getBytes());
426 fos.write(("secondaryColorBlue=" + (secondaryColor & 255)).getBytes());
430 private static KeyMapping createKeyBinding(ResourceLocation id, InputConstants.Type type, int code, String category) {
431 return new KeyMapping("key." + id.getNamespace() + "." + id.getPath(), type, code, category);
434 private static final LazyLoadedValue<MethodHandle> IS_FRUSTUM_VISIBLE = new LazyLoadedValue<>(() -> {
436 return MethodHandles.lookup().findStatic(Class.forName("me.shedaniel.lightoverlay." + Platform.getModLoader() + ".LightOverlayImpl"), "isFrustumVisible",
437 MethodType.methodType(boolean.class, Frustum.class, double.class, double.class, double.class, double.class, double.class, double.class));
438 } catch (NoSuchMethodException | IllegalAccessException | ClassNotFoundException e) {
439 throw new RuntimeException(e);
443 private static boolean isFrustumVisible(Frustum frustum, double minX, double minY, double minZ, double maxX, double maxY, double maxZ) {
445 return (boolean) IS_FRUSTUM_VISIBLE.get().invokeExact(frustum, minX, minY, minZ, maxX, maxY, maxZ);
446 } catch (Throwable throwable) {
447 throw new RuntimeException(throwable);
451 private static void registerDebugRenderer(Runnable runnable) {
453 Class.forName("me.shedaniel.lightoverlay." + Platform.getModLoader() + ".LightOverlayImpl").getDeclaredField("debugRenderer").set(null, runnable);
454 } catch (Throwable throwable) {
455 throw new RuntimeException(throwable);
459 private static void tick(Minecraft minecraft) {
460 while (enableOverlay.consumeClick())
465 if (CLIENT.player == null || !enabled) {
467 CALCULATING_POS.clear();
468 EXECUTOR.getQueue().clear();
471 LocalPlayer player = CLIENT.player;
472 ClientLevel world = CLIENT.level;
473 CollisionContext collisionContext = CollisionContext.of(player);
476 CALCULATING_POS.clear();
479 BlockPos playerPos = player.blockPosition();
480 LayerLightEventListener block = world.getLightEngine().getLayerListener(LightLayer.BLOCK);
481 LayerLightEventListener sky = showNumber ? null : world.getLightEngine().getLayerListener(LightLayer.SKY);
482 BlockPos.MutableBlockPos downPos = new BlockPos.MutableBlockPos();
483 Iterable<BlockPos> iterate = BlockPos.betweenClosed(playerPos.getX() - reach, playerPos.getY() - reach, playerPos.getZ() - reach,
484 playerPos.getX() + reach, playerPos.getY() + reach, playerPos.getZ() + reach);
485 Long2ReferenceMap<Object> map = new Long2ReferenceOpenHashMap<>();
486 CHUNK_MAP.put(new ChunkPos(0, 0), map);
487 for (BlockPos blockPos : iterate) {
488 downPos.set(blockPos.getX(), blockPos.getY() - 1, blockPos.getZ());
490 int level = getCrossLevel(blockPos, downPos, world, block, collisionContext);
492 map.put(blockPos.asLong(), Byte.valueOf((byte) level));
495 CrossType type = getCrossType(blockPos, downPos, world, block, sky, collisionContext);
496 if (type != CrossType.NONE) {
497 map.put(blockPos.asLong(), type);
502 int playerPosX = ((int) player.getX()) >> 4;
503 int playerPosZ = ((int) player.getZ()) >> 4;
504 for (int chunkX = playerPosX - getChunkRange(); chunkX <= playerPosX + getChunkRange(); chunkX++) {
505 for (int chunkZ = playerPosZ - getChunkRange(); chunkZ <= playerPosZ + getChunkRange(); chunkZ++) {
506 if (Mth.abs(chunkX - playerPosX) > getChunkRange() || Mth.abs(chunkZ - playerPosZ) > getChunkRange())
508 ChunkPos chunkPos = new ChunkPos(chunkX, chunkZ);
509 if (!CHUNK_MAP.containsKey(chunkPos))
510 queueChunk(chunkPos);
513 for (int p = 0; p < 3; p++) {
514 if (EXECUTOR.getQueue().size() >= Runtime.getRuntime().availableProcessors()) break;
515 double d1 = Double.MAX_VALUE, d2 = Double.MAX_VALUE, d3 = Double.MAX_VALUE;
516 ChunkPos c1 = null, c2 = null, c3 = null;
518 Iterator<ChunkPos> iterator = POS.iterator();
519 while (iterator.hasNext()) {
520 ChunkPos pos = iterator.next();
521 if (Mth.abs(pos.x - playerPosX) > getChunkRange() || Mth.abs(pos.z - playerPosZ) > getChunkRange() || CALCULATING_POS.contains(pos)) {
524 if (isFrustumVisible(frustum, pos.getMinBlockX(), 0, pos.getMinBlockZ(), pos.getMaxBlockX(), 256, pos.getMaxBlockZ())) {
525 int i = Math.abs(pos.x - playerPosX);
526 int j = Math.abs(pos.z - playerPosZ);
527 double distance = Math.sqrt(i * i + j * j);
536 } else if (distance < d2) {
542 } else if (distance < d3) {
551 ChunkPos finalC1 = c1;
552 ChunkPos finalC2 = c2;
553 ChunkPos finalC3 = c3;
554 if (finalC1 != null) {
555 CALCULATING_POS.add(finalC1);
556 if (finalC2 != null) {
557 CALCULATING_POS.add(finalC2);
558 if (finalC3 != null) {
559 CALCULATING_POS.add(finalC3);
562 EXECUTOR.submit(() -> {
563 int playerPosX1 = ((int) CLIENT.player.getX()) >> 4;
564 int playerPosZ1 = ((int) CLIENT.player.getZ()) >> 4;
565 if (finalC1 != null) processChunk(finalC1, playerPosX1, playerPosZ1, collisionContext);
566 if (finalC2 != null) processChunk(finalC2, playerPosX1, playerPosZ1, collisionContext);
567 if (finalC3 != null) processChunk(finalC3, playerPosX1, playerPosZ1, collisionContext);
571 if (ticks % 50 == 0) {
572 CHUNK_MAP.entrySet().removeIf(pos -> Mth.abs(pos.getKey().x - playerPosX) > getChunkRange() * 2 || Mth.abs(pos.getKey().z - playerPosZ) > getChunkRange() * 2);
576 } catch (Throwable throwable) {
577 LogManager.getLogger().throwing(throwable);
581 private enum CrossType {