minecraft-src/net/minecraft/core/Direction.java
2025-07-04 02:49:36 +03:00

600 lines
18 KiB
Java

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.Matrix4f;
import org.joml.Quaternionf;
import org.joml.Vector3f;
import org.joml.Vector4f;
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<Direction> CODEC = StringRepresentable.fromEnum(Direction::values);
public static final Codec<Direction> VERTICAL_CODEC = CODEC.validate(Direction::verifyVertical);
public static final IntFunction<Direction> BY_ID = ByIdMap.continuous(Direction::get3DDataValue, values(), OutOfBoundsStrategy.WRAP);
public static final StreamCodec<ByteBuf, Direction> STREAM_CODEC = ByteBufCodecs.idMapper(BY_ID, Direction::get3DDataValue);
/**
* 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 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);
}
/**
* 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(Matrix4f matrix, Direction direction) {
Vec3i vec3i = direction.getUnitVec3i();
Vector4f vector4f = matrix.transform(new Vector4f(vec3i.getX(), vec3i.getY(), vec3i.getZ(), 0.0F));
return getApproximateNearest(vector4f.x(), vector4f.y(), vector4f.z());
}
public static Collection<Direction> allShuffled(RandomSource random) {
return Util.<Direction>shuffledCopy(values(), random);
}
public static Stream<Direction> 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.getStepX(), this.getStepY(), this.getStepZ());
}
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<Direction> 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 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<Direction> {
X("X", 0, "x"),
Y("Y", 1, "y"),
Z("Z", 2, "z");
public static final Direction.Axis[] VALUES = values();
public static final StringRepresentable.EnumCodec<Direction.Axis> 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 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<Direction>, Predicate<Direction> {
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<Direction> iterator() {
return Iterators.forArray(this.faces);
}
public Stream<Direction> stream() {
return Arrays.stream(this.faces);
}
public List<Direction> shuffledCopy(RandomSource random) {
return Util.shuffledCopy(this.faces, random);
}
public int length() {
return this.faces.length;
}
}
}