package net.minecraft.core; import com.google.common.collect.Iterators; import com.mojang.serialization.Codec; import com.mojang.serialization.DataResult; import io.netty.buffer.ByteBuf; import java.util.Arrays; import java.util.Collection; import java.util.Comparator; import java.util.Iterator; import java.util.List; import java.util.function.IntFunction; import java.util.function.Predicate; import java.util.stream.Stream; import net.minecraft.Util; import net.minecraft.network.codec.ByteBufCodecs; import net.minecraft.network.codec.StreamCodec; import net.minecraft.util.ByIdMap; import net.minecraft.util.Mth; import net.minecraft.util.RandomSource; import net.minecraft.util.StringRepresentable; import net.minecraft.util.ByIdMap.OutOfBoundsStrategy; import net.minecraft.world.entity.Entity; import net.minecraft.world.phys.Vec3; import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.Nullable; import org.joml.Matrix4fc; import org.joml.Quaternionf; import org.joml.Vector3f; import org.joml.Vector3fc; public enum Direction implements StringRepresentable { DOWN(0, 1, -1, "down", Direction.AxisDirection.NEGATIVE, Direction.Axis.Y, new Vec3i(0, -1, 0)), UP(1, 0, -1, "up", Direction.AxisDirection.POSITIVE, Direction.Axis.Y, new Vec3i(0, 1, 0)), NORTH(2, 3, 2, "north", Direction.AxisDirection.NEGATIVE, Direction.Axis.Z, new Vec3i(0, 0, -1)), SOUTH(3, 2, 0, "south", Direction.AxisDirection.POSITIVE, Direction.Axis.Z, new Vec3i(0, 0, 1)), WEST(4, 5, 1, "west", Direction.AxisDirection.NEGATIVE, Direction.Axis.X, new Vec3i(-1, 0, 0)), EAST(5, 4, 3, "east", Direction.AxisDirection.POSITIVE, Direction.Axis.X, new Vec3i(1, 0, 0)); public static final StringRepresentable.EnumCodec CODEC = StringRepresentable.fromEnum(Direction::values); public static final Codec VERTICAL_CODEC = CODEC.validate(Direction::verifyVertical); public static final IntFunction BY_ID = ByIdMap.continuous(Direction::get3DDataValue, values(), OutOfBoundsStrategy.WRAP); public static final StreamCodec STREAM_CODEC = ByteBufCodecs.idMapper(BY_ID, Direction::get3DDataValue); @Deprecated public static final Codec LEGACY_ID_CODEC = Codec.BYTE.xmap(Direction::from3DDataValue, direction -> (byte)direction.get3DDataValue()); @Deprecated public static final Codec LEGACY_ID_CODEC_2D = Codec.BYTE.xmap(Direction::from2DDataValue, direction -> (byte)direction.get2DDataValue()); /** * Ordering index for D-U-N-S-W-E */ private final int data3d; /** * Index of the opposite Direction in the VALUES array */ private final int oppositeIndex; /** * Ordering index for the HORIZONTALS field (S-W-N-E) */ private final int data2d; private final String name; private final Direction.Axis axis; private final Direction.AxisDirection axisDirection; /** * Normalized vector that points in the direction of this Direction */ private final Vec3i normal; private final Vec3 normalVec3; private final Vector3fc normalVec3f; private static final Direction[] VALUES = values(); private static final Direction[] BY_3D_DATA = (Direction[])Arrays.stream(VALUES) .sorted(Comparator.comparingInt(direction -> direction.data3d)) .toArray(Direction[]::new); /** * All Facings with horizontal axis in order S-W-N-E */ private static final Direction[] BY_2D_DATA = (Direction[])Arrays.stream(VALUES) .filter(direction -> direction.getAxis().isHorizontal()) .sorted(Comparator.comparingInt(direction -> direction.data2d)) .toArray(Direction[]::new); private Direction( final int data3d, final int oppositeIndex, final int data2d, final String name, final Direction.AxisDirection axisDirection, final Direction.Axis axis, final Vec3i normal ) { this.data3d = data3d; this.data2d = data2d; this.oppositeIndex = oppositeIndex; this.name = name; this.axis = axis; this.axisDirection = axisDirection; this.normal = normal; this.normalVec3 = Vec3.atLowerCornerOf(normal); this.normalVec3f = new Vector3f(normal.getX(), normal.getY(), normal.getZ()); } /** * Gets the {@code Direction} values for the provided entity's * looking direction. Dependent on yaw and pitch of entity looking. */ public static Direction[] orderedByNearest(Entity entity) { float f = entity.getViewXRot(1.0F) * (float) (Math.PI / 180.0); float g = -entity.getViewYRot(1.0F) * (float) (Math.PI / 180.0); float h = Mth.sin(f); float i = Mth.cos(f); float j = Mth.sin(g); float k = Mth.cos(g); boolean bl = j > 0.0F; boolean bl2 = h < 0.0F; boolean bl3 = k > 0.0F; float l = bl ? j : -j; float m = bl2 ? -h : h; float n = bl3 ? k : -k; float o = l * i; float p = n * i; Direction direction = bl ? EAST : WEST; Direction direction2 = bl2 ? UP : DOWN; Direction direction3 = bl3 ? SOUTH : NORTH; if (l > n) { if (m > o) { return makeDirectionArray(direction2, direction, direction3); } else { return p > m ? makeDirectionArray(direction, direction3, direction2) : makeDirectionArray(direction, direction2, direction3); } } else if (m > p) { return makeDirectionArray(direction2, direction3, direction); } else { return o > m ? makeDirectionArray(direction3, direction, direction2) : makeDirectionArray(direction3, direction2, direction); } } private static Direction[] makeDirectionArray(Direction first, Direction second, Direction third) { return new Direction[]{first, second, third, third.getOpposite(), second.getOpposite(), first.getOpposite()}; } public static Direction rotate(Matrix4fc matrix, Direction direction) { Vector3f vector3f = matrix.transformDirection(direction.normalVec3f, new Vector3f()); return getApproximateNearest(vector3f.x(), vector3f.y(), vector3f.z()); } public static Collection allShuffled(RandomSource random) { return Util.shuffledCopy(values(), random); } public static Stream stream() { return Stream.of(VALUES); } public static float getYRot(Direction direction) { return switch (direction) { case NORTH -> 180.0F; case SOUTH -> 0.0F; case WEST -> 90.0F; case EAST -> -90.0F; default -> throw new IllegalStateException("No y-Rot for vertical axis: " + direction); }; } public Quaternionf getRotation() { return switch (this) { case DOWN -> new Quaternionf().rotationX((float) Math.PI); case UP -> new Quaternionf(); case NORTH -> new Quaternionf().rotationXYZ((float) (Math.PI / 2), 0.0F, (float) Math.PI); case SOUTH -> new Quaternionf().rotationX((float) (Math.PI / 2)); case WEST -> new Quaternionf().rotationXYZ((float) (Math.PI / 2), 0.0F, (float) (Math.PI / 2)); case EAST -> new Quaternionf().rotationXYZ((float) (Math.PI / 2), 0.0F, (float) (-Math.PI / 2)); }; } /** * @return the index of this Direction (0-5). The order is D-U-N-S-W-E */ public int get3DDataValue() { return this.data3d; } /** * @return the index of this horizontal facing (0-3). The order is S-W-N-E */ public int get2DDataValue() { return this.data2d; } public Direction.AxisDirection getAxisDirection() { return this.axisDirection; } public static Direction getFacingAxis(Entity entity, Direction.Axis axis) { return switch (axis) { case X -> EAST.isFacingAngle(entity.getViewYRot(1.0F)) ? EAST : WEST; case Y -> entity.getViewXRot(1.0F) < 0.0F ? UP : DOWN; case Z -> SOUTH.isFacingAngle(entity.getViewYRot(1.0F)) ? SOUTH : NORTH; }; } /** * @return the opposite Direction (e.g. DOWN => UP) */ public Direction getOpposite() { return from3DDataValue(this.oppositeIndex); } public Direction getClockWise(Direction.Axis axis) { return switch (axis) { case X -> this != WEST && this != EAST ? this.getClockWiseX() : this; case Y -> this != UP && this != DOWN ? this.getClockWise() : this; case Z -> this != NORTH && this != SOUTH ? this.getClockWiseZ() : this; }; } public Direction getCounterClockWise(Direction.Axis axis) { return switch (axis) { case X -> this != WEST && this != EAST ? this.getCounterClockWiseX() : this; case Y -> this != UP && this != DOWN ? this.getCounterClockWise() : this; case Z -> this != NORTH && this != SOUTH ? this.getCounterClockWiseZ() : this; }; } /** * Rotate this Direction around the Y axis clockwise (NORTH => EAST => SOUTH => WEST => NORTH) */ public Direction getClockWise() { return switch (this) { case NORTH -> EAST; case SOUTH -> WEST; case WEST -> NORTH; case EAST -> SOUTH; default -> throw new IllegalStateException("Unable to get Y-rotated facing of " + this); }; } private Direction getClockWiseX() { return switch (this) { case DOWN -> SOUTH; case UP -> NORTH; case NORTH -> DOWN; case SOUTH -> UP; default -> throw new IllegalStateException("Unable to get X-rotated facing of " + this); }; } private Direction getCounterClockWiseX() { return switch (this) { case DOWN -> NORTH; case UP -> SOUTH; case NORTH -> UP; case SOUTH -> DOWN; default -> throw new IllegalStateException("Unable to get X-rotated facing of " + this); }; } private Direction getClockWiseZ() { return switch (this) { case DOWN -> WEST; case UP -> EAST; default -> throw new IllegalStateException("Unable to get Z-rotated facing of " + this); case WEST -> UP; case EAST -> DOWN; }; } private Direction getCounterClockWiseZ() { return switch (this) { case DOWN -> EAST; case UP -> WEST; default -> throw new IllegalStateException("Unable to get Z-rotated facing of " + this); case WEST -> DOWN; case EAST -> UP; }; } /** * Rotate this Direction around the Y axis counter-clockwise (NORTH => WEST => SOUTH => EAST => NORTH) */ public Direction getCounterClockWise() { return switch (this) { case NORTH -> WEST; case SOUTH -> EAST; case WEST -> SOUTH; case EAST -> NORTH; default -> throw new IllegalStateException("Unable to get CCW facing of " + this); }; } /** * @return the offset in the x direction */ public int getStepX() { return this.normal.getX(); } /** * @return the offset in the y direction */ public int getStepY() { return this.normal.getY(); } /** * @return the offset in the z direction */ public int getStepZ() { return this.normal.getZ(); } public Vector3f step() { return new Vector3f(this.normalVec3f); } public String getName() { return this.name; } public Direction.Axis getAxis() { return this.axis; } /** * @return the Direction specified by the given name or null if no such Direction exists */ @Nullable public static Direction byName(@Nullable String name) { return (Direction)CODEC.byName(name); } /** * @return the {@code Direction} corresponding to the given index (0-5). Out of bounds values are wrapped around. The order is D-U-N-S-W-E. * @see #get3DDataValue */ public static Direction from3DDataValue(int index) { return BY_3D_DATA[Mth.abs(index % BY_3D_DATA.length)]; } /** * @return the Direction corresponding to the given horizontal index (0-3). Out of bounds values are wrapped around. The order is S-W-N-E. * @see #get2DDataValue */ public static Direction from2DDataValue(int horizontalIndex) { return BY_2D_DATA[Mth.abs(horizontalIndex % BY_2D_DATA.length)]; } /** * @return the Direction corresponding to the given angle in degrees (0-360). Out of bounds values are wrapped around. An angle of 0 is SOUTH, an angle of 90 would be WEST. */ public static Direction fromYRot(double angle) { return from2DDataValue(Mth.floor(angle / 90.0 + 0.5) & 3); } public static Direction fromAxisAndDirection(Direction.Axis axis, Direction.AxisDirection axisDirection) { return switch (axis) { case X -> axisDirection == Direction.AxisDirection.POSITIVE ? EAST : WEST; case Y -> axisDirection == Direction.AxisDirection.POSITIVE ? UP : DOWN; case Z -> axisDirection == Direction.AxisDirection.POSITIVE ? SOUTH : NORTH; }; } /** * @return the angle in degrees corresponding to this Direction. * @see #fromYRot */ public float toYRot() { return (this.data2d & 3) * 90; } public static Direction getRandom(RandomSource random) { return Util.getRandom(VALUES, random); } public static Direction getApproximateNearest(double x, double y, double z) { return getApproximateNearest((float)x, (float)y, (float)z); } public static Direction getApproximateNearest(float x, float y, float z) { Direction direction = NORTH; float f = Float.MIN_VALUE; for (Direction direction2 : VALUES) { float g = x * direction2.normal.getX() + y * direction2.normal.getY() + z * direction2.normal.getZ(); if (g > f) { f = g; direction = direction2; } } return direction; } public static Direction getApproximateNearest(Vec3 vector) { return getApproximateNearest(vector.x, vector.y, vector.z); } @Nullable @Contract("_,_,_,!null->!null;_,_,_,_->_") public static Direction getNearest(int x, int y, int z, @Nullable Direction defaultValue) { int i = Math.abs(x); int j = Math.abs(y); int k = Math.abs(z); if (i > k && i > j) { return x < 0 ? WEST : EAST; } else if (k > i && k > j) { return z < 0 ? NORTH : SOUTH; } else if (j > i && j > k) { return y < 0 ? DOWN : UP; } else { return defaultValue; } } @Nullable @Contract("_,!null->!null;_,_->_") public static Direction getNearest(Vec3i vector, @Nullable Direction defaultValue) { return getNearest(vector.getX(), vector.getY(), vector.getZ(), defaultValue); } public String toString() { return this.name; } @Override public String getSerializedName() { return this.name; } private static DataResult verifyVertical(Direction direction) { return direction.getAxis().isVertical() ? DataResult.success(direction) : DataResult.error(() -> "Expected a vertical direction"); } public static Direction get(Direction.AxisDirection axisDirection, Direction.Axis axis) { for (Direction direction : VALUES) { if (direction.getAxisDirection() == axisDirection && direction.getAxis() == axis) { return direction; } } throw new IllegalArgumentException("No such direction: " + axisDirection + " " + axis); } public Vec3i getUnitVec3i() { return this.normal; } public Vec3 getUnitVec3() { return this.normalVec3; } public Vector3fc getUnitVec3f() { return this.normalVec3f; } public boolean isFacingAngle(float degrees) { float f = degrees * (float) (Math.PI / 180.0); float g = -Mth.sin(f); float h = Mth.cos(f); return this.normal.getX() * g + this.normal.getZ() * h > 0.0F; } public static enum Axis implements StringRepresentable, Predicate { X("X", 0, "x"), Y("Y", 1, "y"), Z("Z", 2, "z"); public static final Direction.Axis[] VALUES = values(); public static final StringRepresentable.EnumCodec CODEC = StringRepresentable.fromEnum(Direction.Axis::values); private final String name; Axis(final String name) { this.name = name; } /** * @return the Axis specified by the given name or {@code null} if no such Axis exists */ @Nullable public static Direction.Axis byName(String name) { return (Direction.Axis)CODEC.byName(name); } public String getName() { return this.name; } public boolean isVertical() { return this == Y; } /** * @return whether this Axis is on the horizontal plane (true for X and Z) */ public boolean isHorizontal() { return this == X || this == Z; } public abstract Direction getPositive(); public abstract Direction getNegative(); public Direction[] getDirections() { return new Direction[]{this.getPositive(), this.getNegative()}; } public String toString() { return this.name; } public static Direction.Axis getRandom(RandomSource random) { return Util.getRandom(VALUES, random); } public boolean test(@Nullable Direction direction) { return direction != null && direction.getAxis() == this; } /** * @return this Axis' Plane (VERTICAL for Y, HORIZONTAL for X and Z) */ public Direction.Plane getPlane() { return switch (this) { case X, Z -> Direction.Plane.HORIZONTAL; case Y -> Direction.Plane.VERTICAL; }; } @Override public String getSerializedName() { return this.name; } public abstract int choose(int x, int y, int z); public abstract double choose(double x, double y, double z); public abstract boolean choose(boolean x, boolean y, boolean z); } public static enum AxisDirection { POSITIVE(1, "Towards positive"), NEGATIVE(-1, "Towards negative"); private final int step; private final String name; private AxisDirection(final int step, final String name) { this.step = step; this.name = name; } /** * @return the offset for this AxisDirection. 1 for POSITIVE, -1 for NEGATIVE */ public int getStep() { return this.step; } public String getName() { return this.name; } public String toString() { return this.name; } public Direction.AxisDirection opposite() { return this == POSITIVE ? NEGATIVE : POSITIVE; } } public static enum Plane implements Iterable, Predicate { HORIZONTAL(new Direction[]{Direction.NORTH, Direction.EAST, Direction.SOUTH, Direction.WEST}, new Direction.Axis[]{Direction.Axis.X, Direction.Axis.Z}), VERTICAL(new Direction[]{Direction.UP, Direction.DOWN}, new Direction.Axis[]{Direction.Axis.Y}); private final Direction[] faces; private final Direction.Axis[] axis; private Plane(final Direction[] faces, final Direction.Axis[] axis) { this.faces = faces; this.axis = axis; } public Direction getRandomDirection(RandomSource random) { return Util.getRandom(this.faces, random); } public Direction.Axis getRandomAxis(RandomSource random) { return Util.getRandom(this.axis, random); } public boolean test(@Nullable Direction direction) { return direction != null && direction.getAxis().getPlane() == this; } public Iterator iterator() { return Iterators.forArray(this.faces); } public Stream stream() { return Arrays.stream(this.faces); } public List shuffledCopy(RandomSource random) { return Util.shuffledCopy(this.faces, random); } public int length() { return this.faces.length; } } }