content: package emu.grasscutter.utils;
  
  import java.io.Serializable;
  import java.util.List;
  
  import com.google.gson.annotations.SerializedName;
  import com.github.davidmoten.rtreemulti.geometry.Point;
  import dev.morphia.annotations.Entity;
  import emu.grasscutter.net.proto.VectorOuterClass.Vector;
  import lombok.Getter;
  import lombok.Setter;
  
  @Entity
  public class Position implements Serializable {
      private static final long serialVersionUID = -2001232313615923575L;
  
      @SerializedName(value="x", alternate={"_x", "X"})
      @Getter @Setter private float x;
  
      @SerializedName(value="y", alternate={"_y", "Y"})
      @Getter @Setter private float y;
  
      @SerializedName(value="z", alternate={"_z", "Z"})
      @Getter @Setter private float z;
  
      public Position() {}
  
      public Position(float x, float y) {
          set(x, y);
      }
  
      public Position(float x, float y, float z) {
          set(x, y, z);
      }
  
      public Position(List<Float> xyz) {
          switch (xyz.size()) {
              default:  // Might want to error on excess elements, but maybe we want to extend to 3+3 representation later.
              case 3:
                  this.z = xyz.get(2);  // Fall-through
              case 2:
                  this.y = xyz.get(1);  // Fall-through
              case 1:
                  this.y = xyz.get(0);  // pointless fall-through
              case 0:
                  break;
          }
      }
  
      public Position(String p) {
          String[] split = p.split(",");
          if (split.length >= 2) {
              this.x = Float.parseFloat(split[0]);
              this.y = Float.parseFloat(split[1]);
          }
          if (split.length >= 3) {
              this.z = Float.parseFloat(split[2]);
          }
      }
  
      public Position(Vector vector) {
          this.set(vector);
      }
  
      public Position(Position pos) {
          this.set(pos);
      }
  
      public Position set(float x, float y) {
          this.x = x;
          this.y = y;
          return this;
      }
  
      // Deep copy
      public Position set(Position pos) {
          return this.set(pos.getX(), pos.getY(), pos.getZ());
      }
  
      public Position set(Vector pos) {
          return this.set(pos.getX(), pos.getY(), pos.getZ());
      }
  
      public Position set(float x, float y, float z) {
          this.x = x;
          this.y = y;
          this.z = z;
          return this;
      }
  
      public Position add(Position add) {
          this.x += add.getX();
          this.y += add.getY();
          this.z += add.getZ();
          return this;
      }
  
      public Position addX(float d) {
          this.x += d;
          return this;
      }
  
      public Position addY(float d) {
          this.y += d;
          return this;
      }
  
      public Position addZ(float d) {
          this.z += d;
          return this;
      }
  
      public Position subtract(Position sub) {
          this.x -= sub.getX();
          this.y -= sub.getY();
          this.z -= sub.getZ();
          return this;
      }
  
      /** In radians
       * */
      public Position translate(float dist, float angle) {
          this.x += dist * Math.sin(angle);
          this.y += dist * Math.cos(angle);
          return this;
      }
  
      public boolean equal2d(Position other) {
          // Y is height
          return getX() == other.getX() && getZ() == other.getZ();
      }
  
      public boolean equal3d(Position other) {
          return getX() == other.getX() && getY() == other.getY() && getZ() == other.getZ();
      }
  
      public double computeDistance(Position b) {
          double detX = getX()-b.getX();
          double detY = getY()-b.getY();
          double detZ = getZ()-b.getZ();
          return Math.sqrt(detX*detX+detY*detY+detZ*detZ);
      }
  
      public Position nearby2d(float range) {
          Position position = clone();
          position.z += Utils.randomFloatRange(-range, range);
          position.x += Utils.randomFloatRange(-range, range);
          return position;
      }
      public Position translateWithDegrees(float dist, float angle) {
          angle = (float) Math.toRadians(angle);
          this.x += dist * Math.sin(angle);
          this.y += -dist * Math.cos(angle);
          return this;
      }
  
      @Override
      public Position clone() {
          return new Position(x, y, z);
      }
  
      @Override
      public String toString() {
          return "(" + this.getX() + ", " + this.getY() + ", " + this.getZ() + ")";
      }
  
      public Vector toProto() {
          return Vector.newBuilder()
              .setX(this.getX())
              .setY(this.getY())
              .setZ(this.getZ())
              .build();
      }
      public Point toPoint() {
          return Point.create(x,y,z);
      }
  
      /**
       * To XYZ array for Spatial Index
       */
      public double[] toDoubleArray() {
          return new double[]{ x, y, z};
      }
      /**
       * To XZ array for Spatial Index (Blocks)
       */
      public double[] toXZDoubleArray() {
          return new double[]{x, z};
      }
  }
  