diff --git a/src/main/java/meteordevelopment/meteorclient/systems/modules/render/marker/AbstractSphereMarker.java b/src/main/java/meteordevelopment/meteorclient/systems/modules/render/marker/AbstractSphereMarker.java new file mode 100644 index 0000000000..8c4b6a64b5 --- /dev/null +++ b/src/main/java/meteordevelopment/meteorclient/systems/modules/render/marker/AbstractSphereMarker.java @@ -0,0 +1,46 @@ +/* + * This file is part of the Meteor Client distribution (https://github.com/MeteorDevelopment/meteor-client). + * Copyright (c) Meteor Development. + */ + +package meteordevelopment.meteorclient.systems.modules.render.marker; + +import net.minecraft.util.math.BlockPos; + +import java.util.Set; + +public abstract class AbstractSphereMarker extends BaseMarker { + public AbstractSphereMarker(String name) { + super(name); + } + + protected static void computeCircle(Set renderBlocks, BlockPos center, int dY, int radius) { + int cX = center.getX(); + int cY = center.getY(); + int cZ = center.getZ(); + + int rSq = radius * radius; + + // Calculate 1 octant and transform,mirror,flip the rest + int dZ = 1; + for (int dX = 0; dX < dZ; dX++) { + dZ = (int) Math.round(Math.sqrt(rSq - (dX * dX + dY * dY))); + + // First and second octant + renderBlocks.add(new RenderBlock(cX + dX, cY + dY, cZ + dZ)); + renderBlocks.add(new RenderBlock(cX + dZ, cY + dY, cZ + dX)); + + // Fifth and sixth octant + renderBlocks.add(new RenderBlock(cX - dX, cY + dY, cZ - dZ)); + renderBlocks.add(new RenderBlock(cX - dZ, cY + dY, cZ - dX)); + + // Third and fourth octant + renderBlocks.add(new RenderBlock(cX + dX, cY + dY, cZ - dZ)); + renderBlocks.add(new RenderBlock(cX + dZ, cY + dY, cZ - dX)); + + // Seventh and eighth octant + renderBlocks.add(new RenderBlock(cX - dX, cY + dY, cZ + dZ)); + renderBlocks.add(new RenderBlock(cX - dZ, cY + dY, cZ + dX)); + } + } +} diff --git a/src/main/java/meteordevelopment/meteorclient/systems/modules/render/marker/BaseMarker.java b/src/main/java/meteordevelopment/meteorclient/systems/modules/render/marker/BaseMarker.java index 81617cd0cb..3a1dc00e82 100644 --- a/src/main/java/meteordevelopment/meteorclient/systems/modules/render/marker/BaseMarker.java +++ b/src/main/java/meteordevelopment/meteorclient/systems/modules/render/marker/BaseMarker.java @@ -103,4 +103,38 @@ public BaseMarker fromTag(NbtCompound tag) { return this; } + + public enum Mode { + Full, + Hollow + } + + protected static class RenderBlock { + public final int x, y, z; + public byte excludeDir; + + public RenderBlock(int x, int y, int z) { + this.x = x; + this.y = y; + this.z = z; + } + + @Override + public int hashCode() { + long hash = 3241; + hash = 3457689L * hash + x; + hash = 8734625L * hash + y; + hash = 2873465L * hash + z; + return (int) hash; + } + + @Override + public boolean equals(Object obj) { + if (obj == this) return true; + if (obj instanceof RenderBlock other) { + return x == other.x && y == other.y && z == other.z; + } + return false; + } + } } diff --git a/src/main/java/meteordevelopment/meteorclient/systems/modules/render/marker/CuboidMarker.java b/src/main/java/meteordevelopment/meteorclient/systems/modules/render/marker/CuboidMarker.java index e869df4538..34ef1ba6c8 100644 --- a/src/main/java/meteordevelopment/meteorclient/systems/modules/render/marker/CuboidMarker.java +++ b/src/main/java/meteordevelopment/meteorclient/systems/modules/render/marker/CuboidMarker.java @@ -15,10 +15,6 @@ public class CuboidMarker extends BaseMarker { public static final String type = "Cuboid"; - public enum Mode { - Full - } - private final SettingGroup sgGeneral = settings.getDefaultGroup(); private final SettingGroup sgRender = settings.createGroup("Render"); @@ -36,15 +32,15 @@ public enum Mode { .build() ); - // Render - - private final Setting mode = sgRender.add(new EnumSetting.Builder() + private final Setting mode = sgGeneral.add(new EnumSetting.Builder() .name("mode") .description("What mode to use for this marker.") - .defaultValue(Mode.Full) + .defaultValue(Mode.Hollow) .build() ); + // Render + private final Setting shapeMode = sgRender.add(new EnumSetting.Builder() .name("shape-mode") .description("How the shapes are rendered.") @@ -85,5 +81,12 @@ protected void render(Render3DEvent event) { int maxZ = Math.max(pos1.get().getZ(), pos2.get().getZ()); event.renderer.box(minX, minY, minZ, maxX + 1, maxY + 1, maxZ + 1, sideColor.get(), lineColor.get(), shapeMode.get(), 0); + + if (mode.get() == Mode.Hollow + && maxX - minX >= 2 + && maxY - minY >= 2 + && maxZ - minZ >= 2) { + event.renderer.box(minX + 1, minY + 1, minZ + 1, maxX, maxY, maxZ, sideColor.get(), lineColor.get(), shapeMode.get(), 0); + } } } diff --git a/src/main/java/meteordevelopment/meteorclient/systems/modules/render/marker/CylinderMarker.java b/src/main/java/meteordevelopment/meteorclient/systems/modules/render/marker/CylinderMarker.java new file mode 100644 index 0000000000..76438df84e --- /dev/null +++ b/src/main/java/meteordevelopment/meteorclient/systems/modules/render/marker/CylinderMarker.java @@ -0,0 +1,201 @@ +/* + * This file is part of the Meteor Client distribution (https://github.com/MeteorDevelopment/meteor-client). + * Copyright (c) Meteor Development. + */ + +package meteordevelopment.meteorclient.systems.modules.render.marker; + +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; +import meteordevelopment.meteorclient.events.render.Render3DEvent; +import meteordevelopment.meteorclient.renderer.ShapeMode; +import meteordevelopment.meteorclient.settings.*; +import meteordevelopment.meteorclient.utils.network.MeteorExecutor; +import meteordevelopment.meteorclient.utils.player.PlayerUtils; +import meteordevelopment.meteorclient.utils.render.color.SettingColor; +import meteordevelopment.meteorclient.utils.world.Dir; +import net.minecraft.util.math.BlockPos; +import org.jetbrains.annotations.Nullable; + +import java.util.List; +import java.util.concurrent.Future; + +public class CylinderMarker extends AbstractSphereMarker { + public static final String type = "Cylinder"; + + private final SettingGroup sgGeneral = settings.getDefaultGroup(); + private final SettingGroup sgRender = settings.createGroup("Render"); + + private final Setting center = sgGeneral.add(new BlockPosSetting.Builder() + .name("center") + .description("Center of the sphere") + .onChanged(bp -> dirty = true) + .build() + ); + + private final Setting radius = sgGeneral.add(new IntSetting.Builder() + .name("radius") + .description("Radius of the sphere") + .defaultValue(20) + .min(1) + .noSlider() + .onChanged(r -> dirty = true) + .build() + ); + + private final Setting height = sgGeneral.add(new IntSetting.Builder() + .name("height") + .description("The height of the cylinder") + .defaultValue(10) + .min(1) + .sliderRange(1, 20) + .onChanged(l -> dirty = true) + .build() + ); + + private final Setting mode = sgGeneral.add(new EnumSetting.Builder() + .name("mode") + .description("What mode to use for this marker.") + .defaultValue(Mode.Hollow) + .onChanged(r -> dirty = true) + .build() + ); + + // Render + + private final Setting limitRenderRange = sgRender.add(new BoolSetting.Builder() + .name("limit-render-range") + .description("Whether to limit rendering range (useful in very large circles)") + .defaultValue(false) + .build() + ); + + private final Setting renderRange = sgRender.add(new IntSetting.Builder() + .name("render-range") + .description("Rendering range") + .defaultValue(10) + .min(1) + .sliderRange(1, 20) + .visible(limitRenderRange::get) + .build() + ); + + private final Setting shapeMode = sgRender.add(new EnumSetting.Builder() + .name("shape-mode") + .description("How the shapes are rendered.") + .defaultValue(ShapeMode.Both) + .build() + ); + + private final Setting sideColor = sgRender.add(new ColorSetting.Builder() + .name("side-color") + .description("The color of the sides of the blocks being rendered.") + .defaultValue(new SettingColor(0, 100, 255, 50)) + .build() + ); + + private final Setting lineColor = sgRender.add(new ColorSetting.Builder() + .name("line-color") + .description("The color of the lines of the blocks being rendered.") + .defaultValue(new SettingColor(0, 100, 255, 255)) + .build() + ); + + private volatile List blocks = List.of(); + private volatile @Nullable Future task = null; + private boolean dirty = true; + + public CylinderMarker() { + super(type); + } + + @Override + protected void render(Render3DEvent event) { + if (dirty) calcCircle(); + + for (RenderBlock block : blocks) { + if (!limitRenderRange.get() || PlayerUtils.isWithin(block.x, block.y, block.z, renderRange.get())) { + event.renderer.box(block.x, block.y, block.z, block.x + 1, block.y + height.get(), block.z + 1, sideColor.get(), lineColor.get(), shapeMode.get(), block.excludeDir); + } + } + } + + @Override + public String getTypeName() { + return type; + } + + private void calcCircle() { + dirty = false; + + if (task != null) { + task.cancel(true); + task = null; + } + + Runnable action = () -> { + blocks = switch (mode.get()) { + case Full -> filledCircle(center.get(), radius.get()); + case Hollow -> hollowCircle(center.get(), radius.get()); + }; + task = null; + }; + + if (radius.get() <= 50) action.run(); + else { + task = MeteorExecutor.executeFuture(action); + } + } + + private static List hollowCircle(BlockPos center, int r) { + ObjectOpenHashSet renderBlocks = new ObjectOpenHashSet<>(); + + computeCircle(renderBlocks, center, 0, r); + cullInnerFaces(renderBlocks); + + return new ObjectArrayList<>(renderBlocks); + } + + private static List filledCircle(BlockPos center, int r) { + ObjectOpenHashSet renderBlocks = new ObjectOpenHashSet<>(); + + int rSq = r * r; + + for (int dX = 0; dX <= r; dX++) { + int dXSq = dX * dX; + for (int dZ = 0; dZ <= r; dZ++) { + int dZSq = dZ * dZ; + if (dXSq + dZSq <= rSq) { + renderBlocks.add(new RenderBlock(center.getX() + dX, center.getY(), center.getZ() + dZ)); + renderBlocks.add(new RenderBlock(center.getX() - dX, center.getY(), center.getZ() + dZ)); + renderBlocks.add(new RenderBlock(center.getX() + dX, center.getY(), center.getZ() - dZ)); + renderBlocks.add(new RenderBlock(center.getX() - dX, center.getY(), center.getZ() - dZ)); + } + } + } + + cullInnerFaces(renderBlocks); + + return new ObjectArrayList<>(renderBlocks); + } + + private static void cullInnerFaces(ObjectOpenHashSet renderBlocks) { + for (RenderBlock block : renderBlocks) { + int x = block.x; + int y = block.y; + int z = block.z; + + @Nullable RenderBlock east = renderBlocks.get(new RenderBlock(x + 1, y, z)); + if (east != null) { + block.excludeDir |= Dir.EAST; + east.excludeDir |= Dir.WEST; + } + + @Nullable RenderBlock south = renderBlocks.get(new RenderBlock(x, y, z + 1)); + if (south != null) { + block.excludeDir |= Dir.SOUTH; + south.excludeDir |= Dir.NORTH; + } + } + } +} diff --git a/src/main/java/meteordevelopment/meteorclient/systems/modules/render/marker/MarkerFactory.java b/src/main/java/meteordevelopment/meteorclient/systems/modules/render/marker/MarkerFactory.java index f3d1167cb8..ed8481241f 100644 --- a/src/main/java/meteordevelopment/meteorclient/systems/modules/render/marker/MarkerFactory.java +++ b/src/main/java/meteordevelopment/meteorclient/systems/modules/render/marker/MarkerFactory.java @@ -22,10 +22,10 @@ public MarkerFactory() { factories = new HashMap<>(); factories.put(CuboidMarker.type, CuboidMarker::new); factories.put(Sphere2dMarker.type, Sphere2dMarker::new); + factories.put(Sphere3dMarker.type, Sphere3dMarker::new); + factories.put(CylinderMarker.type, CylinderMarker::new); - names = new String[factories.size()]; - int i = 0; - for (String key : factories.keySet()) names[i++] = key; + names = factories.keySet().toArray(new String[0]); } public String[] getNames() { diff --git a/src/main/java/meteordevelopment/meteorclient/systems/modules/render/marker/Sphere2dMarker.java b/src/main/java/meteordevelopment/meteorclient/systems/modules/render/marker/Sphere2dMarker.java index 53b57e9a48..393539b560 100644 --- a/src/main/java/meteordevelopment/meteorclient/systems/modules/render/marker/Sphere2dMarker.java +++ b/src/main/java/meteordevelopment/meteorclient/systems/modules/render/marker/Sphere2dMarker.java @@ -5,6 +5,8 @@ package meteordevelopment.meteorclient.systems.modules.render.marker; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; import meteordevelopment.meteorclient.events.render.Render3DEvent; import meteordevelopment.meteorclient.renderer.ShapeMode; import meteordevelopment.meteorclient.settings.*; @@ -14,22 +16,12 @@ import meteordevelopment.meteorclient.utils.render.color.SettingColor; import meteordevelopment.meteorclient.utils.world.Dir; import net.minecraft.util.math.BlockPos; +import org.jetbrains.annotations.Nullable; -import java.util.ArrayList; import java.util.List; +import java.util.concurrent.Future; -public class Sphere2dMarker extends BaseMarker { - private static class Block { - public final int x, y, z; - public int excludeDir; - - public Block(int x, int y, int z) { - this.x = x; - this.y = y; - this.z = z; - } - } - +public class Sphere2dMarker extends AbstractSphereMarker { public static final String type = "Sphere-2D"; private final SettingGroup sgGeneral = settings.getDefaultGroup(); @@ -63,6 +55,14 @@ public Block(int x, int y, int z) { .build() ); + private final Setting mode = sgGeneral.add(new EnumSetting.Builder() + .name("mode") + .description("What mode to use for this marker.") + .defaultValue(Mode.Hollow) + .onChanged(r -> dirty = true) + .build() + ); + // Render private final Setting limitRenderRange = sgRender.add(new BoolSetting.Builder() @@ -125,8 +125,9 @@ public Block(int x, int y, int z) { .build() ); - private final List blocks = new ArrayList<>(); - private boolean dirty = true, calculating; + private volatile List blocks = List.of(); + private volatile @Nullable Future task = null; + private boolean dirty = true; public Sphere2dMarker() { super(type); @@ -134,13 +135,11 @@ public Sphere2dMarker() { @Override protected void render(Render3DEvent event) { - if (dirty && !calculating) calcCircle(); + if (dirty) calcCircle(); - synchronized (blocks) { - for (Block block : blocks) { - if (!limitRenderRange.get() || PlayerUtils.isWithin(block.x, block.y, block.z, renderRange.get())) { - event.renderer.box(block.x, block.y, block.z, block.x + 1, block.y + 1, block.z + 1, sideColor.get(), lineColor.get(), shapeMode.get(), block.excludeDir); - } + for (RenderBlock block : blocks) { + if (!limitRenderRange.get() || PlayerUtils.isWithin(block.x, block.y, block.z, renderRange.get())) { + event.renderer.box(block.x, block.y, block.z, block.x + 1, block.y + 1, block.z + 1, sideColor.get(), lineColor.get(), shapeMode.get(), block.excludeDir); } } } @@ -151,73 +150,80 @@ public String getTypeName() { } private void calcCircle() { - calculating = true; - blocks.clear(); + dirty = false; + + if (task != null) { + task.cancel(true); + task = null; + } Runnable action = () -> { - int cX = center.get().getX(); - int cY = center.get().getY(); - int cZ = center.get().getZ(); - - int rSq = radius.get() * radius.get(); - int dY = -radius.get() + layer.get(); - - // Calculate 1 octant and transform,mirror,flip the rest - int dX = 0; - while (true) { - int dZ = (int) Math.round(Math.sqrt(rSq - (dX * dX + dY * dY))); - - synchronized (blocks) { - // First and second octant - add(cX + dX, cY + dY, cZ + dZ); - add(cX + dZ, cY + dY, cZ + dX); - - // Fifth and sixth octant - add(cX - dX, cY + dY, cZ - dZ); - add(cX - dZ, cY + dY, cZ - dX); - - // Third and fourth octant - add(cX + dX, cY + dY, cZ - dZ); - add(cX + dZ, cY + dY, cZ - dX); - - // Seventh and eighth octant - add(cX - dX, cY + dY, cZ + dZ); - add(cX - dZ, cY + dY, cZ + dX); - } + blocks = switch (mode.get()) { + case Full -> filledCircle(center.get(), radius.get(), layer.get()); + case Hollow -> hollowCircle(center.get(), radius.get(), layer.get()); + }; + task = null; + }; + if (radius.get() <= 50) action.run(); + else { + task = MeteorExecutor.executeFuture(action); + } + } - // Stop when we reach the midpoint - if (dX >= dZ) break; - dX++; - } + private static List hollowCircle(BlockPos center, int r, int layer) { + int dY = -r + layer; + + ObjectOpenHashSet renderBlocks = new ObjectOpenHashSet<>(); + + computeCircle(renderBlocks, center, dY, r); + cullInnerFaces(renderBlocks); - // Calculate connected blocks - synchronized (blocks) { - for (Block block : blocks) { - for (Block b : blocks) { - if (b == block) continue; - - if (b.x == block.x + 1 && b.z == block.z) block.excludeDir |= Dir.EAST; - if (b.x == block.x - 1 && b.z == block.z) block.excludeDir |= Dir.WEST; - if (b.x == block.x && b.z == block.z + 1) block.excludeDir |= Dir.SOUTH; - if (b.x == block.x && b.z == block.z - 1) block.excludeDir |= Dir.NORTH; - } + return new ObjectArrayList<>(renderBlocks); + } + + private static List filledCircle(BlockPos center, int r, int layer) { + ObjectOpenHashSet renderBlocks = new ObjectOpenHashSet<>(); + + int rSq = r * r; + int dY = -r + layer; + int dYSq = dY * dY; + + for (int dX = 0; dX <= r; dX++) { + int dXSq = dX * dX; + for (int dZ = 0; dZ <= r; dZ++) { + int dZSq = dZ * dZ; + if (dXSq + dYSq + dZSq <= rSq) { + renderBlocks.add(new RenderBlock(center.getX() + dX, center.getY() + dY, center.getZ() + dZ)); + renderBlocks.add(new RenderBlock(center.getX() - dX, center.getY() + dY, center.getZ() + dZ)); + renderBlocks.add(new RenderBlock(center.getX() + dX, center.getY() + dY, center.getZ() - dZ)); + renderBlocks.add(new RenderBlock(center.getX() - dX, center.getY() + dY, center.getZ() - dZ)); } } + } - dirty = false; - calculating = false; - }; + cullInnerFaces(renderBlocks); - if (radius.get() <= 50) action.run(); - else MeteorExecutor.execute(action); + return new ObjectArrayList<>(renderBlocks); } - private void add(int x, int y, int z) { - for (Block b : blocks) { - if (b.x == x && b.y == y && b.z == z) return; - } + private static void cullInnerFaces(ObjectOpenHashSet renderBlocks) { + for (RenderBlock block : renderBlocks) { + int x = block.x; + int y = block.y; + int z = block.z; - blocks.add(new Block(x, y, z)); + @Nullable RenderBlock east = renderBlocks.get(new RenderBlock(x + 1, y, z)); + if (east != null) { + block.excludeDir |= Dir.EAST; + east.excludeDir |= Dir.WEST; + } + + @Nullable RenderBlock south = renderBlocks.get(new RenderBlock(x, y, z + 1)); + if (south != null) { + block.excludeDir |= Dir.SOUTH; + south.excludeDir |= Dir.NORTH; + } + } } } diff --git a/src/main/java/meteordevelopment/meteorclient/systems/modules/render/marker/Sphere3dMarker.java b/src/main/java/meteordevelopment/meteorclient/systems/modules/render/marker/Sphere3dMarker.java new file mode 100644 index 0000000000..2899295e98 --- /dev/null +++ b/src/main/java/meteordevelopment/meteorclient/systems/modules/render/marker/Sphere3dMarker.java @@ -0,0 +1,231 @@ +/* + * This file is part of the Meteor Client distribution (https://github.com/MeteorDevelopment/meteor-client). + * Copyright (c) Meteor Development. + */ + +package meteordevelopment.meteorclient.systems.modules.render.marker; + +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; +import meteordevelopment.meteorclient.events.render.Render3DEvent; +import meteordevelopment.meteorclient.renderer.ShapeMode; +import meteordevelopment.meteorclient.settings.*; +import meteordevelopment.meteorclient.utils.network.MeteorExecutor; +import meteordevelopment.meteorclient.utils.player.PlayerUtils; +import meteordevelopment.meteorclient.utils.render.color.SettingColor; +import meteordevelopment.meteorclient.utils.world.Dir; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.MathHelper; +import org.jetbrains.annotations.Nullable; + +import java.util.List; +import java.util.concurrent.Future; + +public class Sphere3dMarker extends AbstractSphereMarker { + public static final String type = "Sphere-3D"; + + private final SettingGroup sgGeneral = settings.getDefaultGroup(); + private final SettingGroup sgRender = settings.createGroup("Render"); + + private final Setting center = sgGeneral.add(new BlockPosSetting.Builder() + .name("center") + .description("Center of the sphere") + .onChanged(bp -> dirty = true) + .build() + ); + + private final Setting radius = sgGeneral.add(new IntSetting.Builder() + .name("radius") + .description("Radius of the sphere") + .defaultValue(20) + .min(1) + .sliderRange(1, 64) + .onChanged(r -> dirty = true) + .build() + ); + + private final Setting mode = sgGeneral.add(new EnumSetting.Builder() + .name("mode") + .description("What mode to use for this marker.") + .defaultValue(Mode.Hollow) + .onChanged(r -> dirty = true) + .build() + ); + + // Render + + private final Setting limitRenderRange = sgRender.add(new BoolSetting.Builder() + .name("limit-render-range") + .description("Whether to limit rendering range (useful in very large circles)") + .defaultValue(false) + .build() + ); + + private final Setting renderRange = sgRender.add(new IntSetting.Builder() + .name("render-range") + .description("Rendering range") + .defaultValue(10) + .min(1) + .sliderRange(1, 128) + .visible(limitRenderRange::get) + .build() + ); + + private final Setting shapeMode = sgRender.add(new EnumSetting.Builder() + .name("shape-mode") + .description("How the shapes are rendered.") + .defaultValue(ShapeMode.Both) + .build() + ); + + private final Setting sideColor = sgRender.add(new ColorSetting.Builder() + .name("side-color") + .description("The color of the sides of the blocks being rendered.") + .defaultValue(new SettingColor(0, 100, 255, 50)) + .build() + ); + + private final Setting lineColor = sgRender.add(new ColorSetting.Builder() + .name("line-color") + .description("The color of the lines of the blocks being rendered.") + .defaultValue(new SettingColor(0, 100, 255, 255)) + .build() + ); + + private volatile List blocks = List.of(); + private volatile @Nullable Future task = null; + private boolean dirty = true; + + public Sphere3dMarker() { + super(type); + } + + @Override + protected void render(Render3DEvent event) { + if (dirty) calcCircle(); + + for (RenderBlock block : blocks) { + if (!limitRenderRange.get() || PlayerUtils.isWithin(block.x, block.y, block.z, renderRange.get())) { + event.renderer.box(block.x, block.y, block.z, block.x + 1, block.y + 1, block.z + 1, sideColor.get(), lineColor.get(), shapeMode.get(), block.excludeDir); + } + } + } + + @Override + public String getTypeName() { + return type; + } + + private void calcCircle() { + dirty = false; + + if (task != null) { + task.cancel(true); + task = null; + } + + Runnable action = () -> { + blocks = switch (mode.get()) { + case Full -> filledSphere(center.get(), radius.get()); + case Hollow -> hollowSphere(center.get(), radius.get()); + }; + task = null; + }; + + if (radius.get() <= 30) action.run(); + else { + task = MeteorExecutor.executeFuture(action); + } + } + + private static List hollowSphere(BlockPos center, int r) { + /* + Since we're effectively copy and pasting the computed voxels, but rotated to fill in the top and bottom faces, + we skip computing those in the original pass by only going from -45 degrees to 45 degrees + */ + double sin45 = 1 / Math.sqrt(2); + int height = MathHelper.ceil(r * sin45); + int d = height * 2; + + ObjectOpenHashSet computedBlocks = new ObjectOpenHashSet<>(); + + for (int slice = 0; slice <= d; slice++) { + int dY = -height + slice; + + computeCircle(computedBlocks, center, dY, r); + } + + ObjectOpenHashSet newBlocks = new ObjectOpenHashSet<>(computedBlocks); + + int cX = center.getX(); + int cY = center.getY(); + + // Rotate the computed blocks along a horizontal axis to fill in the top and bottom faces + for (RenderBlock block : computedBlocks) { + // x/y rotation + newBlocks.add(new RenderBlock(cX + cY - block.y, cY - cX + block.x, block.z)); + } + + cullInnerFaces(newBlocks); + + return new ObjectArrayList<>(newBlocks); + } + + private static List filledSphere(BlockPos center, int r) { + ObjectOpenHashSet renderBlocks = new ObjectOpenHashSet<>(); + + int rSq = r * r; + + for (int dX = 0; dX <= r; dX++) { + int dXSq = dX * dX; + for (int dY = 0; dY <= r; dY++) { + int dYSq = dY * dY; + for (int dZ = 0; dZ <= r; dZ++) { + int dZSq = dZ * dZ; + if (dXSq + dYSq + dZSq <= rSq) { + renderBlocks.add(new RenderBlock(center.getX() + dX, center.getY() + dY, center.getZ() + dZ)); + renderBlocks.add(new RenderBlock(center.getX() - dX, center.getY() + dY, center.getZ() + dZ)); + renderBlocks.add(new RenderBlock(center.getX() + dX, center.getY() - dY, center.getZ() + dZ)); + renderBlocks.add(new RenderBlock(center.getX() - dX, center.getY() - dY, center.getZ() + dZ)); + renderBlocks.add(new RenderBlock(center.getX() + dX, center.getY() + dY, center.getZ() - dZ)); + renderBlocks.add(new RenderBlock(center.getX() - dX, center.getY() + dY, center.getZ() - dZ)); + renderBlocks.add(new RenderBlock(center.getX() + dX, center.getY() - dY, center.getZ() - dZ)); + renderBlocks.add(new RenderBlock(center.getX() - dX, center.getY() - dY, center.getZ() - dZ)); + } + } + } + } + + cullInnerFaces(renderBlocks); + + renderBlocks.removeIf(block -> block.excludeDir == 0b111111); + + return new ObjectArrayList<>(renderBlocks); + } + + private static void cullInnerFaces(ObjectOpenHashSet renderBlocks) { + for (RenderBlock block : renderBlocks) { + int x = block.x; + int y = block.y; + int z = block.z; + + @Nullable RenderBlock east = renderBlocks.get(new RenderBlock(x + 1, y, z)); + if (east != null) { + block.excludeDir |= Dir.EAST; + east.excludeDir |= Dir.WEST; + } + + @Nullable RenderBlock top = renderBlocks.get(new RenderBlock(x, y + 1, z)); + if (top != null) { + block.excludeDir |= Dir.UP; + top.excludeDir |= Dir.DOWN; + } + + @Nullable RenderBlock south = renderBlocks.get(new RenderBlock(x, y, z + 1)); + if (south != null) { + block.excludeDir |= Dir.SOUTH; + south.excludeDir |= Dir.NORTH; + } + } + } +} diff --git a/src/main/java/meteordevelopment/meteorclient/utils/network/MeteorExecutor.java b/src/main/java/meteordevelopment/meteorclient/utils/network/MeteorExecutor.java index 33b14478e9..992cddf9d1 100644 --- a/src/main/java/meteordevelopment/meteorclient/utils/network/MeteorExecutor.java +++ b/src/main/java/meteordevelopment/meteorclient/utils/network/MeteorExecutor.java @@ -9,6 +9,7 @@ import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; +import java.util.concurrent.Future; import java.util.concurrent.atomic.AtomicInteger; public class MeteorExecutor { @@ -32,4 +33,8 @@ public static void init() { public static void execute(Runnable task) { executor.execute(task); } + + public static Future executeFuture(Runnable task) { + return executor.submit(task); + } }