Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

Implement a 3D sphere mode for the Markers module #4655

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -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<RenderBlock> 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));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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");

Expand All @@ -36,15 +32,15 @@ public enum Mode {
.build()
);

// Render

private final Setting<Mode> mode = sgRender.add(new EnumSetting.Builder<Mode>()
private final Setting<Mode> mode = sgGeneral.add(new EnumSetting.Builder<Mode>()
.name("mode")
.description("What mode to use for this marker.")
.defaultValue(Mode.Full)
.defaultValue(Mode.Hollow)
.build()
);

// Render

private final Setting<ShapeMode> shapeMode = sgRender.add(new EnumSetting.Builder<ShapeMode>()
.name("shape-mode")
.description("How the shapes are rendered.")
Expand Down Expand Up @@ -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);
}
}
}
Original file line number Diff line number Diff line change
@@ -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<BlockPos> center = sgGeneral.add(new BlockPosSetting.Builder()
.name("center")
.description("Center of the sphere")
.onChanged(bp -> dirty = true)
.build()
);

private final Setting<Integer> 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<Integer> 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> mode = sgGeneral.add(new EnumSetting.Builder<Mode>()
.name("mode")
.description("What mode to use for this marker.")
.defaultValue(Mode.Hollow)
.onChanged(r -> dirty = true)
.build()
);

// Render

private final Setting<Boolean> 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<Integer> 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> shapeMode = sgRender.add(new EnumSetting.Builder<ShapeMode>()
.name("shape-mode")
.description("How the shapes are rendered.")
.defaultValue(ShapeMode.Both)
.build()
);

private final Setting<SettingColor> 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<SettingColor> 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<RenderBlock> 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<RenderBlock> hollowCircle(BlockPos center, int r) {
ObjectOpenHashSet<RenderBlock> renderBlocks = new ObjectOpenHashSet<>();

computeCircle(renderBlocks, center, 0, r);
cullInnerFaces(renderBlocks);

return new ObjectArrayList<>(renderBlocks);
}

private static List<RenderBlock> filledCircle(BlockPos center, int r) {
ObjectOpenHashSet<RenderBlock> 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<RenderBlock> 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;
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down
Loading
Loading