// Projection.java
// Performs a number of 3D to 2D operations for graphics

import java.awt.*;
//import java.lang.*;

public final class Projection {
  private float scale_x, scale_y, scale_z;       // 3D scaling factor
  private float distance;                        // distance to object
  private float _2D_scale;                       // 2D scaling factor
  private float azimuth, elevation;             // azimuth and elevation angle
  private float sin_azimuth, cos_azimuth;      // sin and cos of azimuth angle
  private float sin_elevation, cos_elevation;    // sin and cos of elevation angle
  private int   _2D_trans_x, _2D_trans_y;        // 2D translation
  private int   x1, x2, y1, y2;                  // projection area 
  private int   center_x, center_y;              // center of projection area           
  
  private int   trans_x, trans_y;
  private float factor;
  private double new_long;
  private float sx_cos, sy_cos, sz_cos;
  private float sx_sin, sy_sin, sz_sin;

  private final float DEGTORAD  = (float)Math.PI / 180;
   
  Projection() {
    setScaling(1); 
    setAzimuthAngle(0); 
    setElevationAngle(0); 
    setDistance(10);
    set2DScaling(1);
    set2DTranslation(0,0);
  }

  public void setProjectionArea(Rectangle r) {
    x1 = r.x; x2 = x1 + r.width;
    y1 = r.y; y2 = y1 + r.height;
    center_x = (x1 + x2) / 2;
    center_y = (y1 + y2) / 2;        

    trans_x = center_x + _2D_trans_x;
    trans_y = center_y + _2D_trans_y;
  }
 
  public void setAzimuthAngle(float angle) {
    azimuth = angle;
    sin_azimuth = (float)Math.sin(angle * DEGTORAD);
    cos_azimuth = (float)Math.cos(angle * DEGTORAD);
 
    sx_cos = -scale_x * cos_azimuth;
    sx_sin = -scale_x * sin_azimuth;
    sy_cos = -scale_y * cos_azimuth;
    sy_sin =  scale_y * sin_azimuth;
  }
   
  public float getAzimuthAngle() {
    return azimuth;
  }

   
  public float getSinAzimuthAngle() {
    return sin_azimuth;
  }

  public float getCosAzimuthAngle() {
    return cos_azimuth;
  }

  public void setElevationAngle(float angle) {
    elevation = angle;
    sin_elevation = (float)Math.sin(angle * DEGTORAD);
    cos_elevation = (float)Math.cos(angle * DEGTORAD);
    sz_cos =  scale_z * cos_elevation;
    sz_sin =  scale_z * sin_elevation;
  }

  public float getElevationAngle() {
    return elevation;
  }

  public float getSinElevationAngle() {
    return sin_elevation;
  }

  public float getCosElevationAngle() {
    return cos_elevation;
  }

  public void setDistance(float new_distance) {
    distance = new_distance;
    factor = distance * _2D_scale;
  }
  
  public double getLong(double new_length) {
      new_long = new_length * _2D_scale;
      return new_long;
  }
   
  public float getDistance() {
    return distance;
  }
   
  public void setXScaling(float scaling) {
    scale_x = scaling;
    sx_cos = -scale_x * cos_azimuth;
    sx_sin = -scale_x * sin_azimuth;
  }

  public float getXScaling() {
    return scale_x;
  }

  public void setYScaling(float scaling) {
    scale_y = scaling;
    sy_cos = -scale_y * cos_azimuth;
    sy_sin =  scale_y * sin_azimuth;
  }

  public float getYScaling() {
    return scale_y;
  }

  public void setZScaling(float scaling) {
    scale_z = scaling;
    sz_cos =  scale_z * cos_elevation;
    sz_sin =  scale_z * sin_elevation;
  }

  public float getZScaling() {
    return scale_z;
  }

  public void setScaling(float x, float y, float z) {
    scale_x = x; scale_y = y; scale_z = z;

    sx_cos = -scale_x * cos_azimuth;
    sx_sin = -scale_x * sin_azimuth;
    sy_cos = -scale_y * cos_azimuth;
    sy_sin =  scale_y * sin_azimuth;
    sz_cos =  scale_z * cos_elevation;
    sz_sin =  scale_z * sin_elevation;
  }

  public void setScaling(float scaling) {
    scale_x = scale_y = scale_z = scaling;

    sx_cos = -scale_x * cos_azimuth;
    sx_sin = -scale_x * sin_azimuth;
    sy_cos = -scale_y * cos_azimuth;
    sy_sin =  scale_y * sin_azimuth;
    sz_cos =  scale_z * cos_elevation;
    sz_sin =  scale_z * sin_elevation;
  }

  public void set2DScaling(float scaling) {
    _2D_scale = scaling;
    factor = distance * _2D_scale;
  }

  public float get2DScaling() {
    return _2D_scale;
  }

  public void set2DTranslation(int x, int y) {
    _2D_trans_x = x; _2D_trans_y = y;

    trans_x = center_x + _2D_trans_x;
    trans_y = center_y + _2D_trans_y;
  }
  
  public void set2D_xTranslation(int x) {
    _2D_trans_x = x;
    trans_x = center_x + _2D_trans_x;
  }

  public int get2D_xTranslation() {
    return _2D_trans_x;
  }
  
  public void set2D_yTranslation(int y) {
    _2D_trans_y = y;
    trans_y = center_y + _2D_trans_y;
  }

  public int get2D_yTranslation() {
    return _2D_trans_y;
  }

  public final Point project(float x, float y, float z) {
    float temp;

    // rotates

    temp = x; 
    x = x * sx_cos + y * sy_sin;
    y = temp * sx_sin + y * sy_cos;

    // elevates and projects

    temp = factor / (y * cos_elevation - z * sz_sin + distance);
    return new Point((int)(Math.round(x * temp) + trans_x),
                     (int)(Math.round((y * sin_elevation + z * sz_cos) * -temp) + trans_y));
  }
}
