Skip to content

Instantly share code, notes, and snippets.

@liach
Last active March 11, 2021 17:17
Show Gist options
  • Save liach/4eb7724175bc2d5a5e0485f91fcc64db to your computer and use it in GitHub Desktop.
Save liach/4eb7724175bc2d5a5e0485f91fcc64db to your computer and use it in GitHub Desktop.
BlockPos refactor for mojang

BlockPos Refactoring

In one of the recent snapshots, I observed a large-scale refactor on BlockPos. However, the changes failed to resolve one of the essential problems of BlockPos: immutability.

What refactor

Before, the class hierarchy is like

Vec3i <- BlockPos <- BlockPos.Mutable

Now the class hierarchy will be

BlockPos(interface) <- ImmutableBlockPos, MutableBlockPos

Vec3i <- ImmutableBlockPos

Usage refactor:

  • BlockPos wherever the old block pos is used as a method parameter (input, etc)
  • ImmutableBlockPos wherever the old block pos is used as return type to indicate immutability of the result
  • MutableBlockPos wherever the old BlockPos.Mutable is used as return type (easy change)

The x,y,z fields in Vec3i will be final again, and MutableBlockPos will have 3 non-final x,y,z fields instead.

If there are cases where BlockPos or MutableBlockPos needs to be converted to Vec3i, simply add a toImmutable call.

It is encouraged for users to declare explicitly ImmutableBlockPos or MutableBlockPos in method bodies/local usage, as having a grasp on the type's immutability information is helpful.

Why refactor

To guarantee the type-safety of immutable BlockPos through java inheritance.

When you obtain an ImmutableBlockPos or Vec3i, now you know the object is immutable, unlike previously, you always need to make a toImmutable call (and you cannot make the call on Vec3i, leading to possible pollutions)

For examples, maps tracking block positions will just use ImmutableBlockPos as keys to ensure the reliability of keys than having to remember to call toImmutable at every put call (now java compiler will ask you to call it), etc.

With this change, some builtin BlockPos methods can be more reliable. For example, BlockPos.up() is intended to return an immutable block pos. With this addition, BlockPos.up() forces returning an ImmutableBlockPos, the java compiler will just spit an error when you try to return a mutable block pos for up method, instead of having subtle bugs that are hardly noticed and looking around to find which methods to override and add a toImmutable call to the super result.

Since mutable block positions are mutable, they should not implement Comparable or override equals/hashcode.

How refactor (In Intellij Idea)

  1. Extract BlockPos methods for exposure to a new interface (don't make them abstract so the default impl will be shared by MutableBlockPos later)
  2. Remove the super class of BlockPos.Mutable and make it implement the new interface. Add fields and override getters.
  3. In what is to be ImmutableBlockPos, override down and offset methods to delegate either to BlockPos.super or super (Vec3i's impl).
  4. Finally, change where ImmutableBlockPos is used as method parameters to BlockPos, and keep ImmutableBlockPos that are parts of method return types. Some other methods returning ImmutableBlockPos may have compile error because they are returning mutable block positions. Feel free to change return type to BlockPos or MutableBlockPos (either) in that case.

Perf concern

By switching BlockPos to interface, a few method invocations will become invokeinterface than invokevirtual as before.

Even though there will be invokeinterface bytecode instructions, hotspot can optimize it easily as in most cases there is only two impls of BlockPos: ImmutableBlockPos and MutableBlockPos. As the game runs, hotspot can gradually optimize the method calls, and the performance difference will be little over time.

You can try by running the User test main class. In the first run the invokeinterface (new arrangement) is almost always slower, but for the other runs, the result varies. (To compile the test, you need to grab fabric example mod, set mc version to 20w13b and mapping to 20w13b+build.7, and paste all these classes to the default package in the main source set)

import com.google.common.collect.AbstractIterator;
import com.mojang.datafixers.types.DynamicOps;
import net.minecraft.util.BlockRotation;
import net.minecraft.util.dynamic.DynamicSerializable;
import net.minecraft.util.math.BlockBox;
import net.minecraft.util.math.Direction;
import java.util.Optional;
import java.util.function.Predicate;
import java.util.stream.IntStream;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;
public interface BlockPos extends DynamicSerializable {
// methods to implement
int getX();
int getY();
int getZ();
ImmutableBlockPos toImmutable(); // essential conversion
/**
* Returns a mutable copy of this block position.
*
* <p>If this block position is a mutable one, mutation to this block
* position won't affect the returned position.
*/
// another essential conversion
default MutableBlockPos mutableCopy() {
return new MutableBlockPos(this.getX(), this.getY(), this.getZ());
}
/**
* Iterates block positions around the {@code center}. The iteration order
* is mainly based on the manhattan distance of the position from the
* center.
*
* <p>For the same manhattan distance, the positions are iterated by y
* offset, from negative to positive. For the same y offset, the positions
* are iterated by x offset, from negative to positive. For the two
* positions with the same x and y offsets and the same manhattan distance,
* the one with a positive z offset is visited first before the one with a
* negative z offset.
*
* @param center the center of iteration
* @param xRange the maximum x difference from the center
* @param yRange the maximum y difference from the center
* @param zRange the maximum z difference from the center
*/
static Iterable<BlockPos> iterateOutwards(BlockPos center, int xRange, int yRange, int zRange) {
int i = xRange + yRange + zRange;
int j = center.getX();
int k = center.getY();
int l = center.getZ();
return () -> {
return new AbstractIterator<BlockPos>() {
private final MutableBlockPos current = new MutableBlockPos();
private int manhattanDistance;
private int limitX;
private int limitY;
private int dx;
private int dy;
private boolean flipped;
protected BlockPos computeNext() {
if (this.flipped) {
this.flipped = false;
this.current.setZ(i - (this.current.getZ() - i));
return this.current;
} else {
MutableBlockPos blockPos;
for (blockPos = null; blockPos == null; ++this.dy) {
if (this.dy > this.limitY) {
++this.dx;
if (this.dx > this.limitX) {
++this.manhattanDistance;
if (this.manhattanDistance > j) {
return this.endOfData();
}
this.limitX = Math.min(k, this.manhattanDistance);
this.dx = -this.limitX;
}
this.limitY = Math.min(l, this.manhattanDistance - Math.abs(this.dx));
this.dy = -this.limitY;
}
int ix = this.dx;
int jx = this.dy;
int kx = this.manhattanDistance - Math.abs(ix) - Math.abs(jx);
if (kx <= l) {
this.flipped = kx != 0;
blockPos = this.current.set(j + ix, k + jx, l + kx);
}
}
return blockPos;
}
}
};
};
}
static Optional<BlockPos> findClosest(BlockPos pos, int horizontalRange, int verticalRange, Predicate<BlockPos> condition) {
return streamOutwards(pos, horizontalRange, verticalRange, horizontalRange).filter(condition).findFirst();
}
static Stream<BlockPos> streamOutwards(BlockPos center, int maxX, int maxY, int maxZ) {
return StreamSupport.stream(iterateOutwards(center, maxX, maxY, maxZ).spliterator(), false);
}
static Iterable<BlockPos> iterate(BlockPos start, BlockPos end) {
return iterate(Math.min(start.getX(), end.getX()), Math.min(start.getY(), end.getY()), Math.min(start.getZ(), end.getZ()), Math.max(start.getX(), end.getX()), Math.max(start.getY(), end.getY()), Math.max(start.getZ(), end.getZ()));
}
static Stream<BlockPos> stream(BlockPos start, BlockPos end) {
return StreamSupport.stream(iterate(start, end).spliterator(), false);
}
static Stream<BlockPos> stream(BlockBox box) {
return stream(Math.min(box.minX, box.maxX), Math.min(box.minY, box.maxY), Math.min(box.minZ, box.maxZ), Math.max(box.minX, box.maxX), Math.max(box.minY, box.maxY), Math.max(box.minZ, box.maxZ));
}
static Stream<BlockPos> stream(int startX, int startY, int startZ, int endX, int endY, int endZ) {
return StreamSupport.stream(iterate(startX, startY, startZ, endX, endY, endZ).spliterator(), false);
}
static Iterable<BlockPos> iterate(int startX, int startY, int startZ, int endX, int endY, int endZ) {
int i = endX - startX + 1;
int j = endY - startY + 1;
int k = endZ - startZ + 1;
int l = i * j * k;
return () -> {
return new AbstractIterator<BlockPos>() {
private final MutableBlockPos pos = new MutableBlockPos();
private int index = 0;
protected BlockPos computeNext() {
if (this.index == l) {
return this.endOfData();
} else {
int ix = this.index % j;
int jx = this.index / j;
int kx = jx % k;
int lx = jx / k;
++this.index;
return this.pos.set(startX + ix, startY + kx, startZ + lx);
}
}
};
};
}
@Override
default <T> T serialize(DynamicOps<T> ops) {
return ops.createIntList(IntStream.of(new int[]{this.getX(), this.getY(), this.getZ()}));
}
default long asLong() {
return ImmutableBlockPos.asLong(this.getX(), this.getY(), this.getZ());
}
default ImmutableBlockPos add(double x, double y, double z) {
return x == 0.0D && y == 0.0D && z == 0.0D ? toImmutable() : new ImmutableBlockPos((double) this.getX() + x, (double) this.getY() + y, (double) this.getZ() + z);
}
default ImmutableBlockPos add(int x, int y, int z) {
return x == 0 && y == 0 && z == 0 ? toImmutable() : new ImmutableBlockPos(this.getX() + x, this.getY() + y, this.getZ() + z);
}
default ImmutableBlockPos add(Vec3i pos) {
return this.add(pos.getX(), pos.getY(), pos.getZ());
}
default ImmutableBlockPos subtract(Vec3i pos) {
return this.add(-pos.getX(), -pos.getY(), -pos.getZ());
}
default ImmutableBlockPos up() {
return offset(Direction.UP);
}
default ImmutableBlockPos up(int distance) {
return this.offset(Direction.UP, distance);
}
default ImmutableBlockPos down() {
return offset(Direction.DOWN);
}
default ImmutableBlockPos down(int distance) {
return this.offset(Direction.DOWN, distance);
}
default ImmutableBlockPos north() {
return offset(Direction.NORTH);
}
default ImmutableBlockPos north(int distance) {
return this.offset(Direction.NORTH, distance);
}
default ImmutableBlockPos south() {
return offset(Direction.SOUTH);
}
default ImmutableBlockPos south(int distance) {
return this.offset(Direction.SOUTH, distance);
}
default ImmutableBlockPos west() {
return offset(Direction.WEST);
}
default ImmutableBlockPos west(int distance) {
return this.offset(Direction.WEST, distance);
}
default ImmutableBlockPos east() {
return offset(Direction.EAST);
}
default ImmutableBlockPos east(int distance) {
return this.offset(Direction.EAST, distance);
}
default ImmutableBlockPos offset(Direction direction) {
return new ImmutableBlockPos(this.getX() + direction.getOffsetX(), this.getY() + direction.getOffsetY(), this.getZ() + direction.getOffsetZ());
}
default ImmutableBlockPos offset(Direction direction, int i) {
return i == 0 ? toImmutable() : new ImmutableBlockPos(this.getX() + direction.getOffsetX() * i, this.getY() + direction.getOffsetY() * i, this.getZ() + direction.getOffsetZ() * i);
}
default ImmutableBlockPos rotate(BlockRotation rotation) {
switch (rotation) {
case NONE:
default:
return toImmutable();
case CLOCKWISE_90:
return new ImmutableBlockPos(-this.getZ(), this.getY(), this.getX());
case CLOCKWISE_180:
return new ImmutableBlockPos(-this.getX(), this.getY(), -this.getZ());
case COUNTERCLOCKWISE_90:
return new ImmutableBlockPos(this.getZ(), this.getY(), -this.getX());
}
}
default ImmutableBlockPos crossProduct(Vec3i pos) {
return new ImmutableBlockPos(this.getY() * pos.getZ() - this.getZ() * pos.getY(), this.getZ() * pos.getX() - this.getX() * pos.getZ(), this.getX() * pos.getY() - this.getY() * pos.getX());
}
}
import com.mojang.datafixers.Dynamic;
import net.minecraft.util.math.Direction;
import net.minecraft.util.math.MathHelper;
import net.minecraft.util.math.Position;
import net.minecraft.util.math.Vec3d;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.util.Spliterator.OfInt;
import java.util.function.IntConsumer;
/**
* Represents the position of a block in a three-dimensional volume.
*
* <p>The position is integer-valued.
*/
public class ImmutableBlockPos extends Vec3i implements BlockPos {
private static final Logger LOGGER = LogManager.getLogger();
/**
* The block position which x, y, and z values are all zero.
*/
public static final ImmutableBlockPos ORIGIN = new ImmutableBlockPos(0, 0, 0);
private static final int SIZE_BITS_X = 1 + MathHelper.log2(MathHelper.smallestEncompassingPowerOfTwo(30000000));
private static final int SIZE_BITS_Z;
private static final int SIZE_BITS_Y;
private static final long BITS_X;
private static final long BITS_Y;
private static final long BITS_Z;
private static final int BIT_SHIFT_Z;
private static final int BIT_SHIFT_X;
public ImmutableBlockPos(int i, int j, int k) {
super(i, j, k);
}
public ImmutableBlockPos(double d, double e, double f) {
super(d, e, f);
}
public ImmutableBlockPos(Vec3d pos) {
this(pos.x, pos.y, pos.z);
}
public ImmutableBlockPos(Position pos) {
this(pos.getX(), pos.getY(), pos.getZ());
}
public ImmutableBlockPos(Vec3i pos) {
this(pos.getX(), pos.getY(), pos.getZ());
}
public static <T> ImmutableBlockPos deserialize(Dynamic<T> dynamic) {
OfInt ofInt = dynamic.asIntStream().spliterator();
int[] is = new int[3];
if (ofInt.tryAdvance((IntConsumer) (i) -> {
is[0] = i;
}) && ofInt.tryAdvance((IntConsumer) (i) -> {
is[1] = i;
})) {
ofInt.tryAdvance((IntConsumer) (i) -> {
is[2] = i;
});
}
return new ImmutableBlockPos(is[0], is[1], is[2]);
}
public static long offset(long value, Direction direction) {
return add(value, direction.getOffsetX(), direction.getOffsetY(), direction.getOffsetZ());
}
public static long add(long value, int x, int y, int z) {
return asLong(unpackLongX(value) + x, unpackLongY(value) + y, unpackLongZ(value) + z);
}
public static int unpackLongX(long x) {
return (int) (x << 64 - BIT_SHIFT_X - SIZE_BITS_X >> 64 - SIZE_BITS_X);
}
public static int unpackLongY(long y) {
return (int) (y << 64 - SIZE_BITS_Y >> 64 - SIZE_BITS_Y);
}
public static int unpackLongZ(long z) {
return (int) (z << 64 - BIT_SHIFT_Z - SIZE_BITS_Z >> 64 - SIZE_BITS_Z);
}
public static ImmutableBlockPos fromLong(long value) {
return new ImmutableBlockPos(unpackLongX(value), unpackLongY(value), unpackLongZ(value));
}
public static long asLong(int x, int y, int z) {
long l = 0L;
l |= ((long) x & BITS_X) << BIT_SHIFT_X;
l |= ((long) y & BITS_Y) << 0;
l |= ((long) z & BITS_Z) << BIT_SHIFT_Z;
return l;
}
public static long removeChunkSectionLocalY(long y) {
return y & -16L;
}
/**
* Returns an immutable block position with the same x, y, and z as this
* position.
*
* <p>This method should be called when a block position is used as map
* keys as to prevent side effects of mutations of mutable block positions.
*/
@Override
public ImmutableBlockPos toImmutable() {
return this;
}
@Override
public ImmutableBlockPos down() {
return BlockPos.super.down(); // added compile tricks
}
@Override
public ImmutableBlockPos down(int i) {
return BlockPos.super.down(i); // added compile tricks
}
@Override
public ImmutableBlockPos offset(Direction direction, int i) {
return BlockPos.super.offset(direction, i); // added compile tricks
}
@Override
public ImmutableBlockPos crossProduct(Vec3i vec) {
return BlockPos.super.crossProduct(vec); // added compile tricks
}
static {
SIZE_BITS_Z = SIZE_BITS_X;
SIZE_BITS_Y = 64 - SIZE_BITS_X - SIZE_BITS_Z;
BITS_X = (1L << SIZE_BITS_X) - 1L;
BITS_Y = (1L << SIZE_BITS_Y) - 1L;
BITS_Z = (1L << SIZE_BITS_Z) - 1L;
BIT_SHIFT_Z = SIZE_BITS_Y;
BIT_SHIFT_X = SIZE_BITS_Y + SIZE_BITS_Z;
}
}
import net.minecraft.util.math.AxisCycleDirection;
import net.minecraft.util.math.Direction;
import net.minecraft.util.math.MathHelper;
public class MutableBlockPos implements BlockPos {
protected int x;
protected int y;
protected int z;
public MutableBlockPos() {
this(0, 0, 0);
}
public MutableBlockPos(int i, int j, int k) {
this.x = i;
this.y = j;
this.z = k;
}
public MutableBlockPos(double d, double e, double f) {
this(MathHelper.floor(d), MathHelper.floor(e), MathHelper.floor(f));
}
/**
* Sets the x, y, and z of this mutable block position.
*/
public MutableBlockPos set(int x, int y, int z) {
this.setX(x);
this.setY(y);
this.setZ(z);
return this;
}
public MutableBlockPos set(double x, double y, double z) {
return this.set(MathHelper.floor(x), MathHelper.floor(y), MathHelper.floor(z));
}
public MutableBlockPos set(Vec3i pos) {
return this.set(pos.getX(), pos.getY(), pos.getZ());
}
public MutableBlockPos set(long pos) {
return this.set(ImmutableBlockPos.unpackLongX(pos), ImmutableBlockPos.unpackLongY(pos), ImmutableBlockPos.unpackLongZ(pos));
}
public MutableBlockPos set(AxisCycleDirection axis, int x, int y, int z) {
return this.set(axis.choose(x, y, z, Direction.Axis.X), axis.choose(x, y, z, Direction.Axis.Y), axis.choose(x, y, z, Direction.Axis.Z));
}
/**
* Sets this mutable block position to the offset position of the given
* pos by the given direction.
*/
public MutableBlockPos set(Vec3i pos, Direction direction) {
return this.set(pos.getX() + direction.getOffsetX(), pos.getY() + direction.getOffsetY(), pos.getZ() + direction.getOffsetZ());
}
/**
* Sets this mutable block position to the sum of the given position and the
* given x, y, and z.
*/
public MutableBlockPos set(Vec3i pos, int x, int y, int z) {
return this.set(pos.getX() + x, pos.getY() + y, pos.getZ() + z);
}
/**
* Moves this mutable block position by 1 block in the given direction.
*/
public MutableBlockPos move(Direction direction) {
return this.move(direction, 1);
}
/**
* Moves this mutable block position by the given distance in the given
* direction.
*/
public MutableBlockPos move(Direction direction, int distance) {
return this.set(this.getX() + direction.getOffsetX() * distance, this.getY() + direction.getOffsetY() * distance, this.getZ() + direction.getOffsetZ() * distance);
}
/**
* Moves the mutable block position by the delta x, y, and z provided.
*/
public MutableBlockPos move(int dx, int dy, int dz) {
return this.set(this.getX() + dx, this.getY() + dy, this.getZ() + dz);
}
public void setX(int x) {
this.x = x;
}
public void setY(int y) {
this.y = y;
}
public void setZ(int z) {
this.z = z;
}
public ImmutableBlockPos toImmutable() {
return new ImmutableBlockPos(x, y, z);
}
@Override
public int getX() {
return x;
}
@Override
public int getY() {
return y;
}
@Override
public int getZ() {
return z;
}
}
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
public class User {
public static void main(String... args) {
for (int i = 0; i < 10; i++) {
run();
}
}
private static void run() {
BlockPos newPos;
final ImmutableBlockPos immutablePos = ImmutableBlockPos.ORIGIN;
final MutableBlockPos mutablePos = new MutableBlockPos(0, 0, 0);
final Random random = new Random();
final int count = 1000000;
final List<BlockPos> collectNew = new ArrayList<>(count);
final long startTime = System.nanoTime();
for (int i = 0; i < count; i++) {
newPos = random.nextBoolean() ? immutablePos : mutablePos;
collectNew.add(newPos.mutableCopy());
//newPos.getX();
//immutablePos.getX();
}
final long deltaTime = System.nanoTime() - startTime;
net.minecraft.util.math.BlockPos oldPos;
final net.minecraft.util.math.BlockPos immutableOldPos = net.minecraft.util.math.BlockPos.ORIGIN;
final net.minecraft.util.math.BlockPos.Mutable mutable = new net.minecraft.util.math.BlockPos.Mutable(0, 0, 0);
final List<net.minecraft.util.math.BlockPos> collectOld = new ArrayList<>(count);
final long oldStartTime = System.nanoTime();
for (int i = 0; i < count; i++) {
oldPos = random.nextBoolean() ? immutableOldPos : mutable;
//oldPos.getX();
//immutableOldPos.getX();
collectOld.add(oldPos.mutableCopy());
}
final long oldDeltaTime = System.nanoTime() - oldStartTime;
final char sign = deltaTime < oldDeltaTime ? '<' : deltaTime == oldDeltaTime ? '=' : '>';
System.out.println(deltaTime + " " + sign + " " + oldDeltaTime);
}
public static void work() {
BlockPos pos = ImmutableBlockPos.ORIGIN;
int t = pos.up().getX();
}
public static void work2() {
net.minecraft.util.math.BlockPos pos = net.minecraft.util.math.BlockPos.ORIGIN;
int t = pos.up().getX();
}
}
import com.google.common.base.MoreObjects;
import net.fabricmc.api.EnvType;
import net.fabricmc.api.Environment;
import net.minecraft.util.math.Direction;
import net.minecraft.util.math.MathHelper;
import net.minecraft.util.math.Position;
public class Vec3i implements Comparable<Vec3i> {
public static final Vec3i ZERO = new Vec3i(0, 0, 0);
protected final int x;
protected final int y;
protected final int z;
public Vec3i(int x, int y, int z) {
this.x = x;
this.y = y;
this.z = z;
}
public Vec3i(double x, double y, double z) {
this(MathHelper.floor(x), MathHelper.floor(y), MathHelper.floor(z));
}
public boolean equals(Object object) {
if (this == object) {
return true;
} else if (!(object instanceof Vec3i)) {
return false;
} else {
Vec3i vec3i = (Vec3i) object;
if (this.getX() != vec3i.getX()) {
return false;
} else if (this.getY() != vec3i.getY()) {
return false;
} else {
return this.getZ() == vec3i.getZ();
}
}
}
public int hashCode() {
return (this.getY() + this.getZ() * 31) * 31 + this.getX();
}
public int compareTo(Vec3i vec3i) {
if (this.getY() == vec3i.getY()) {
return this.getZ() == vec3i.getZ() ? this.getX() - vec3i.getX() : this.getZ() - vec3i.getZ();
} else {
return this.getY() - vec3i.getY();
}
}
// making these final may improve perf, i guess?
public final int getX() {
return this.x;
}
public final int getY() {
return this.y;
}
public final int getZ() {
return this.z;
}
public Vec3i down() {
return this.down(1);
}
public Vec3i down(int i) {
return this.offset(Direction.DOWN, i);
}
public Vec3i offset(Direction direction, int distance) {
return distance == 0 ? this : new Vec3i(this.getX() + direction.getOffsetX() * distance, this.getY() + direction.getOffsetY() * distance, this.getZ() + direction.getOffsetZ() * distance);
}
public Vec3i crossProduct(Vec3i vec) {
return new Vec3i(this.getY() * vec.getZ() - this.getZ() * vec.getY(), this.getZ() * vec.getX() - this.getX() * vec.getZ(), this.getX() * vec.getY() - this.getY() * vec.getX());
}
public boolean isWithinDistance(Vec3i vec, double distance) {
return this.getSquaredDistance((double) vec.getX(), (double) vec.getY(), (double) vec.getZ(), false) < distance * distance;
}
public boolean isWithinDistance(Position pos, double distance) {
return this.getSquaredDistance(pos.getX(), pos.getY(), pos.getZ(), true) < distance * distance;
}
public double getSquaredDistance(Vec3i vec) {
return this.getSquaredDistance((double) vec.getX(), (double) vec.getY(), (double) vec.getZ(), true);
}
public double getSquaredDistance(Position pos, boolean treatAsBlockPos) {
return this.getSquaredDistance(pos.getX(), pos.getY(), pos.getZ(), treatAsBlockPos);
}
public double getSquaredDistance(double x, double y, double z, boolean treatAsBlockPos) {
double d = treatAsBlockPos ? 0.5D : 0.0D;
double e = (double) this.getX() + d - x;
double f = (double) this.getY() + d - y;
double g = (double) this.getZ() + d - z;
return e * e + f * f + g * g;
}
public int getManhattanDistance(Vec3i vec) {
float f = (float) Math.abs(vec.getX() - this.getX());
float g = (float) Math.abs(vec.getY() - this.getY());
float h = (float) Math.abs(vec.getZ() - this.getZ());
return (int) (f + g + h);
}
public String toString() {
return MoreObjects.toStringHelper(this).add("x", this.getX()).add("y", this.getY()).add("z", this.getZ()).toString();
}
@Environment(EnvType.CLIENT)
public String toShortString() {
return "" + this.getX() + ", " + this.getY() + ", " + this.getZ();
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment