code: package org.sunflow.math;
  
  public final class Vector3 {
      private static final float[] COS_THETA = new float[256];
      private static final float[] SIN_THETA = new float[256];
      private static final float[] COS_PHI = new float[256];
      private static final float[] SIN_PHI = new float[256];
  
      public float x, y, z;
  
      static {
          // precompute tables to compress unit vectors
          for (int i = 0; i < 256; i++) {
              double angle = (i * Math.PI) / 256.0;
              COS_THETA[i] = (float) Math.cos(angle);
              SIN_THETA[i] = (float) Math.sin(angle);
              COS_PHI[i] = (float) Math.cos(2 * angle);
              SIN_PHI[i] = (float) Math.sin(2 * angle);
          }
      }
  
      public Vector3() {
      }
  
      public Vector3(float x, float y, float z) {
          this.x = x;
          this.y = y;
          this.z = z;
      }
  
      public Vector3(Vector3 v) {
          x = v.x;
          y = v.y;
          z = v.z;
      }
  
      public static final Vector3 decode(short n, Vector3 dest) {
          int t = (n & 0xFF00) >>> 8;
          int p = n & 0xFF;
          dest.x = SIN_THETA[t] * COS_PHI[p];
          dest.y = SIN_THETA[t] * SIN_PHI[p];
          dest.z = COS_THETA[t];
          return dest;
      }
  
      public static final Vector3 decode(short n) {
          return decode(n, new Vector3());
      }
  
      public final short encode() {
          int theta = (int) (Math.acos(z) * (256.0 / Math.PI));
          if (theta > 255)
              theta = 255;
          int phi = (int) (Math.atan2(y, x) * (128.0 / Math.PI));
          if (phi < 0)
              phi += 256;
          else if (phi > 255)
              phi = 255;
          return (short) (((theta & 0xFF) << 8) | (phi & 0xFF));
      }
  
      public float get(int i) {
          switch (i) {
              case 0:
                  return x;
              case 1:
                  return y;
              default:
                  return z;
          }
      }
  
      public final float length() {
          return (float) Math.sqrt((x * x) + (y * y) + (z * z));
      }
  
      public final float lengthSquared() {
          return (x * x) + (y * y) + (z * z);
      }
  
      public final Vector3 negate() {
          x = -x;
          y = -y;
          z = -z;
          return this;
      }
  
      public final Vector3 negate(Vector3 dest) {
          dest.x = -x;
          dest.y = -y;
          dest.z = -z;
          return dest;
      }
  
      public final Vector3 mul(float s) {
          x *= s;
          y *= s;
          z *= s;
          return this;
      }
  
      public final Vector3 mul(float s, Vector3 dest) {
          dest.x = x * s;
          dest.y = y * s;
          dest.z = z * s;
          return dest;
      }
  
      public final Vector3 div(float d) {
          x /= d;
          y /= d;
          z /= d;
          return this;
      }
  
      public final Vector3 div(float d, Vector3 dest) {
          dest.x = x / d;
          dest.y = y / d;
          dest.z = z / d;
          return dest;
      }
  
      public final float normalizeLength() {
          float n = (float) Math.sqrt(x * x + y * y + z * z);
          float in = 1.0f / n;
          x *= in;
          y *= in;
          z *= in;
          return n;
      }
  
      public final Vector3 normalize() {
          float in = 1.0f / (float) Math.sqrt((x * x) + (y * y) + (z * z));
          x *= in;
          y *= in;
          z *= in;
          return this;
      }
  
      public final Vector3 normalize(Vector3 dest) {
          float in = 1.0f / (float) Math.sqrt((x * x) + (y * y) + (z * z));
          dest.x = x * in;
          dest.y = y * in;
          dest.z = z * in;
          return dest;
      }
  
      public final Vector3 set(float x, float y, float z) {
          this.x = x;
          this.y = y;
          this.z = z;
          return this;
      }
  
      public final Vector3 set(Vector3 v) {
          x = v.x;
          y = v.y;
          z = v.z;
          return this;
      }
  
      public final float dot(float vx, float vy, float vz) {
          return vx * x + vy * y + vz * z;
      }
  
      public static final float dot(Vector3 v1, Vector3 v2) {
          return (v1.x * v2.x) + (v1.y * v2.y) + (v1.z * v2.z);
      }
  
      public static final Vector3 cross(Vector3 v1, Vector3 v2, Vector3 dest) {
          dest.x = (v1.y * v2.z) - (v1.z * v2.y);
          dest.y = (v1.z * v2.x) - (v1.x * v2.z);
          dest.z = (v1.x * v2.y) - (v1.y * v2.x);
          return dest;
      }
  
      public static final Vector3 add(Vector3 v1, Vector3 v2, Vector3 dest) {
          dest.x = v1.x + v2.x;
          dest.y = v1.y + v2.y;
          dest.z = v1.z + v2.z;
          return dest;
      }
  
      public static final Vector3 sub(Vector3 v1, Vector3 v2, Vector3 dest) {
          dest.x = v1.x - v2.x;
          dest.y = v1.y - v2.y;
          dest.z = v1.z - v2.z;
          return dest;
      }
  
      @Override
      public final String toString() {
          return String.format("(%.2f, %.2f, %.2f)", x, y, z);
      }
  }