package graphics.raytracer;

class SphereTarget implements Target {
  Vec center;
  double radius, radiusSq; // precompute radiusSq since we use it a lot
  static Targets scene;
  static SceneConsts sConsts;

  public SphereTarget(double x, double y, double z, double r) {
    center = new Vec(x, y, z);
    radius = r;
    radiusSq = r * r;

    if (sConsts == null)
      sConsts = new SceneConsts();
  }

  public SphereTarget(Vec v, double r) {
    center = new Vec(v);
    radius = r;
    radiusSq = r * r;

    if (sConsts == null)
      sConsts = new SceneConsts();
  }

  public SphereTarget() {
    center = new Vec(0, 0, 0);
    radius = radiusSq = 1;

    if (sConsts == null)
      sConsts = new SceneConsts();
  }

  public void setScene(Targets s) {
    scene = s;
  }

  public boolean isBelongingToScene() {
    return ((scene == null) ? false : true);
  }

  public double intersectTest(Vec R0, Vec R1, int object) {

    double t,          /* where the ray intersects */
        loc,        /* square distance from center of sphere to projP */
        tca,        /* how far is the 'closest approach' from VRP */
        thc;        /* length sqare of the half chord */
    Vec vecoc;      /* vector to the center of the sphere from VRP */
    boolean inside = false;

    /* use the closest approach algorithm */
    vecoc = new Vec(center);
    vecoc.sub(R0);
    loc = vecoc.dotProduct(vecoc);

    if (loc <= radiusSq)
      inside = true;

    tca = vecoc.dotProduct(R1);   /* find the closest approach */

    if ((inside != true) && (tca <= 0.0))
      return (0.0);  /* object is behind the VRP */

    /* compute the half chord square from the ray intersection to the
       intersection normal. */
    thc = (tca * tca) + radiusSq - loc;
    if (thc < 0.0)
      return (0.0);   /* ray misses the sphere */

    /* find the ray intersection */
    if (inside == true)
      t = tca + Math.sqrt(thc);
    else
      t = tca - Math.sqrt(thc);

    return (t);
  }

  public int shade(int object, Vec R1, double t[]) {

    Vec intersection, normal, lightSource;
    double intensity;
    double tShadow[] = new double[1];
    tShadow[0] = 0;

    /* calculate the intersection POINT on the object */
    intersection = new Vec(R1);
    intersection.mult(t[0]);
    intersection.add(scene.VRP);

    /* find the normal vector from sphere's center to the intersection */
    normal = new Vec(intersection);
    normal.sub(center);
    normal.normalize();

    /* locate the light source from intersection */
    lightSource = new Vec(scene.light);
    lightSource.sub(intersection);
    lightSource.normalize();

    /* check if the light can be "seen" by the intersection point */
    intersectObjects(intersection, lightSource, tShadow, object, true);

    intensity = lightSource.dotProduct(normal);
    if (intensity < 0.0)
      intensity = 0.0;

    if (tShadow[0] > 0.0) /* something is in the way */
      intensity = sConsts.globalShadowReflectance *
          intensity;  /* pixel gets ambient
                              light only */
    else {   /* pixel gets all kinds of light */
      intensity = intensity * sConsts.Ip
          * sConsts.globalObjectReflectance;
    }

    intensity = intensity + sConsts.ambientLightIntensity
        * sConsts.ambientLightReflectance;
    if (intensity > 1.0)
      intensity = 1.0;

    /* find the corresponding color in the color lookup table */
    intensity = intensity * 255;

    return ((int) intensity);
  }

  int intersectObjects(Vec R0, Vec R1, double result[], int object,
                       boolean shadowCheck) {
    double minDist = 0.0, dist;
    int hit = -1;

    for (int i = 0; i < scene.getSize(); i++) {
      if ((shadowCheck == true) && (object == i))
        continue;
      dist = ((Target) scene.getElementAt(i)).intersectTest(R0,
                                                            R1, i);

      if (dist == 0.0)
        continue;

      /* save the first t */
      if ((minDist == 0.0) && (dist > 0.0)) {
        minDist = dist;
        hit = i;
      } else if ((dist > 0.0) && (dist < minDist)) {
        minDist = dist;
        hit = i;
      }
    }
    result[0] = minDist;
    return hit;
  }

  public void debug_test() {
    System.out.println("SphereTarget.debug_test(): center = " +
                       center.toString() + ", r = " + radius + ", r^2 = "
                       + radiusSq);
  }
}