Renderer(int line) {
      startLine = line;

      if (!scene.antiAliasing) {
        sampled = false;

        samples = Math.max(scene.raysPerPixel, DEBUG_samples);
        // Force it to be a perfect square
        samples = (int) Math.sqrt(samples);
        samples = samples * samples;

        imageSamples = new Double3D[samples];
        for (int i = 0; i < samples; i++) imageSamples[i] = new Double3D(0, 0, 0);

        // Make samples are in the range [0,1]
        Sample.multiJitter(imageSamples, samples);
        // Make samples are in the range [-2,2]
        Sample.cubicSplineFilter(imageSamples, samples);
        // Scale image samples to [-.5,.5] and adjust to worldCoords this should be +-.5 pixels
        // since we're in the middle of a pixel to begin with
        for (int i = 0; i < samples; i++) {
          imageSamples[i].x = widthRatio / 2 * (imageSamples[i].x / 4.0);
          imageSamples[i].y = heightRatio / 2 * (imageSamples[i].y / 4.0);
          // System.out.println("Jitter by: " + imageSamples[i] + " Pixel width: " + widthRatio + "
          // Pixel height: " + heightRatio );
        }
        imageSamples[0].x++;
        imageSamples[0].x--;
      }
    }
    Renderer() {
      xMin = scene.camera.viewportLeft + 0.5;
      xMax = scene.camera.viewportRight;
      yMin = scene.camera.viewportBottom + 0.5;
      yMax = scene.camera.viewportTop;

      if (scene.antiAliasing) {
        samples = Math.max(scene.raysPerPixel, DEBUG_samples);
        // Force it to be a perfect square
        samples = (int) Math.sqrt(samples);
        samples = samples * samples;

        imageSamples = new Double3D[samples];
        for (int i = 0; i < samples; i++) imageSamples[i] = new Double3D(0, 0, 0);

        Sample.multiJitter(imageSamples, samples);

        // Samples are in the range [-2,2]
        Sample.cubicSplineFilter(imageSamples, samples);

        // Scale image samples to [-1,1]
        for (int i = 0; i < samples; i++) {
          imageSamples[i].x = widthRatio * imageSamples[i].x / 2.0;
          imageSamples[i].y = heightRatio * imageSamples[i].y / 2.0;
        }
      }
    }
    // iPoint is the point of intersection with the surface.
    DoubleColor shade(Ray ray, HitRecord hit, MaterialCell material, boolean background) {
      DoubleColor color = new DoubleColor(0.0, 0.0, 0.0, 1.0);

      // Add ambient light only once
      color.plus(
          new DoubleColor(
              (double) (lights[0].ambient[0] * material.ka.r),
              (double) (lights[0].ambient[1] * material.ka.g),
              (double) (lights[0].ambient[2] * material.ka.b),
              (double) (lights[0].ambient[3] * material.ka.a)));

      // Assign material color?
      // Local light or directional? If directional then we need to see if it's shining on the
      // object
      if (!background) {
        double d = 2; // L.distanceTo(hit.hitP);

        for (int i = 0; i < lights.length; i++) {
          if (lights[i].lightSwitch == 1) {
            Double3D L =
                new Double3D(
                    (double) lights[i].position[0],
                    (double) lights[i].position[1],
                    (double) lights[i].position[2]);
            L = L.minus(hit.hitP).getUnit();
            Ray shadowRay = new Ray(hit.hitP, L);
            // trace shadow ray to light source

            // Turn shadows on and shadowRay hit nothing
            if (!scene.shadows || shadowTrace(shadowRay)) {
              double LdN = Math.max(0, hit.normal.dot(L));
              if (LdN > 0) {
                // -2(-L.N)N + -L
                Double3D R = hit.normal.sMult(-2 * hit.normal.dot(L.sMult(-1))).plus(L.sMult(-1));
                double RdV = Math.max(0, -R.dot(ray.dir));

                // If the light is free add the diffuse light
                // Intensity (Kd * (LdN) + Ks *(RdV)^(shiny)/(r + k)
                color.plus(
                    new DoubleColor(
                        (double)
                                (lights[i].diffuse[0] * LdN
                                    + lights[i].specular[0] * Math.pow(RdV, material.shiny))
                            / d,
                        (double)
                                (lights[i].diffuse[1] * LdN
                                    + lights[i].specular[1] * Math.pow(RdV, material.shiny))
                            / d,
                        (double)
                                (lights[i].diffuse[2] * LdN
                                    + lights[i].specular[2] * Math.pow(RdV, material.shiny))
                            / d,
                        1.0)); // */
              } // if(LdN > 0)
            } // if(!scene.shadows || shadowTrace(shadowRay))
          } // if(lights[i].lightSwitch == 1){
        } // for

        // Shiny Phong
        // If IdN > 0 then we find a reflection
        // If IdN < 0 then we need -normal
        if (scene.reflections
            && (material.reflectivity.r > 0
                || material.reflectivity.g > 0
                || material.reflectivity.b > 0)) {
          depth++;

          // R = I - 2 * (I.N)N
          Double3D R = new Double3D();
          Double3D I = ray.dir; // .sMult(-1.0);
          Double3D N = hit.normal;
          // double IdN = I.dot(N);

          // if (IdN > 0){
          //	N = N.sMult(-1.0);
          //	IdN = -I.dot(N);
          // }//*/

          R = I.plus(N.sMult(-2.0 * I.dot(N)));

          Ray reflect = new Ray(hit.hitP, R);
          DoubleColor reflection = trace(reflect);

          // Scale by distance?
          // reflection.scale( 1 / reflect.origin().distanceTo(hit.hitP));

          reflection.r = reflection.r * material.reflectivity.r;
          reflection.g = reflection.g * material.reflectivity.g;
          reflection.b = reflection.b * material.reflectivity.b;

          color.plus(reflection);

          depth--;
        }

        if (scene.refractions
            && (material.refractivity.r > 0
                || material.refractivity.g > 0
                || material.refractivity.b > 0)) // */
        {
          depth++;

          Ray refract = new Ray(hit.hitP, ray.dir);

          if (hit.index == ray.r.objectNum) // Hit the object we're already in
          {
            // Pop the n off the stack
            refract.r = ray.r;

            // Swap the refraction indices
            double temp = refract.r.n;
            refract.r.n = refract.r.prevR.n;
            refract.r.prevR.n = temp;
          } else // Otherwise we hit a new object push this n onto the stack and get mat index
          {
            refract.r.prevR = ray.r;
            refract.r.n = material.refractiveIndex;
            refract.r.objectNum = hit.index;
          }

          if (transmissionDirection(ray, hit, refract)) {
            DoubleColor refraction = trace(refract);

            refraction.r = refraction.r * material.refractivity.r;
            refraction.g = refraction.g * material.refractivity.g;
            refraction.b = refraction.b * material.refractivity.b;

            // Scale for distance?
            color.plus(refraction);
          }

          depth--;
        }
      }
      return color;
    }
    // All rays we deal with here are in world coordinates.
    // Should take the refractive index of the material it is currently in.
    DoubleColor trace(Ray ray) {
      DoubleColor color = new DoubleColor(0.0, 0.0, 0.0, 1.0);
      HitRecord hit = new HitRecord();

      if (depth > Math.max(DEBUG_recursion, scene.maxRecursiveDepth)) return color;

      double tMin = 0.0001;
      double tMax = 10000000;

      // Spheres only for now
      for (int i = 0; i < numObjects; i++)
        // Did I hit the bounding sphere for an object?
        if (spheres[i].hit(ray, tMin, tMax, 0, hit))
          if (scene.spheresOnly) {
            for (PMesh.SurfCell s = shapes[i].surfHead; s != null; s = s.next)
              for (PMesh.PolyCell poly = s.polyHead; poly != null; poly = poly.next)
                // Triangles only for now
                if (poly.numVerts == 3) {
                  Double3D v[] = new Double3D[3];
                  int j = 0;
                  for (PMesh.VertListCell vert = poly.vert; vert != null; vert = vert.next)
                    v[j++] = shapes[i].vertArray.get(vert.vert).viewPos;
                  // Increment j in the line post access

                  // Check for a hit on this polygon
                  if (Triangle.hit(v[0], v[1], v[2], ray, tMin, tMax, 0, hit)) {
                    tMax = hit.t;

                    hit.normal = poly.viewNorm;
                    hit.matIndex = s.material;
                    hit.index = i;
                  }
                } else
                  System.out.println(
                      "Need to intersect polygon with " + poly.numVerts + " vertices.");
          } else {
            tMax = hit.t;
            hit.matIndex = i; // May cause an error if object 10 and it only has 3 materials.
            hit.index = i;
          }

      if (hit.index >= 0) // If it intersects then multi-sample
      {
        if (!sampled && depth == 0) {
          // Only sample once per ray from the main loop
          sampled = true;

          Double3D dir = ray.dir;
          DoubleColor antiAlias = trace(ray);

          for (int i = 0; i < samples; i++) {
            // Double3D sample = new Double3D(dir.x + imageSamples[i].x, dir.y + imageSamples[i].y,
            // dir.z).getUnit();
            // ray.dir = sample;
            ray.dir.x = dir.x + imageSamples[i].x;
            ray.dir.y = dir.y + imageSamples[i].y;

            antiAlias.plus(trace(ray));
          }
          antiAlias.scale(1.0 / (samples + 1.0));

          color.plus(antiAlias);
        } else if (hit.matIndex < shapes[hit.index].materials.length)
          color = shade(ray, hit, shapes[hit.index].materials[hit.matIndex], false);
        else
          color =
              shade(
                  ray,
                  hit,
                  shapes[hit.index].materials[shapes[hit.index].materials.length - 1],
                  false);
      } else // We hit nothing check for intersection with the far clip plane for checker board
      // pattern.
      if (scene.checkerBackground) color = shade(ray, hit, checkerBackgroundHit(ray, hit), true);

      return color;
    }
  private void updateSubImageImpl(
      TextureData data,
      int newTarget,
      int mipmapLevel,
      int dstx,
      int dsty,
      int srcx,
      int srcy,
      int width,
      int height)
      throws GLException {
    GL gl = GLU.getCurrentGL();
    data.setHaveEXTABGR(gl.isExtensionAvailable("GL_EXT_abgr"));
    data.setHaveGL12(gl.isExtensionAvailable("GL_VERSION_1_2"));

    Buffer buffer = data.getBuffer();
    if (buffer == null && data.getMipmapData() == null) {
      // Assume user just wanted to get the Texture object allocated
      return;
    }

    int rowlen = data.getRowLength();
    int dataWidth = data.getWidth();
    int dataHeight = data.getHeight();
    if (data.getMipmapData() != null) {
      // Compute the width, height and row length at the specified mipmap level
      // Note we do not support specification of the row length for
      // mipmapped textures at this point
      for (int i = 0; i < mipmapLevel; i++) {
        width = Math.max(width / 2, 1);
        height = Math.max(height / 2, 1);

        dataWidth = Math.max(dataWidth / 2, 1);
        dataHeight = Math.max(dataHeight / 2, 1);
      }
      rowlen = 0;
      buffer = data.getMipmapData()[mipmapLevel];
    }

    // Clip incoming rectangles to what is available both on this
    // texture and in the incoming TextureData
    if (srcx < 0) {
      width += srcx;
      srcx = 0;
    }
    if (srcy < 0) {
      height += srcy;
      srcy = 0;
    }
    // NOTE: not sure whether the following two are the correct thing to do
    if (dstx < 0) {
      width += dstx;
      dstx = 0;
    }
    if (dsty < 0) {
      height += dsty;
      dsty = 0;
    }

    if (srcx + width > dataWidth) {
      width = dataWidth - srcx;
    }
    if (srcy + height > dataHeight) {
      height = dataHeight - srcy;
    }
    if (dstx + width > texWidth) {
      width = texWidth - dstx;
    }
    if (dsty + height > texHeight) {
      height = texHeight - dsty;
    }

    checkCompressedTextureExtensions(data);

    if (data.isDataCompressed()) {
      gl.glCompressedTexSubImage2D(
          newTarget,
          mipmapLevel,
          dstx,
          dsty,
          width,
          height,
          data.getInternalFormat(),
          buffer.remaining(),
          buffer);
    } else {
      int[] align = new int[1];
      int[] rowLength = new int[1];
      int[] skipRows = new int[1];
      int[] skipPixels = new int[1];
      gl.glGetIntegerv(GL.GL_UNPACK_ALIGNMENT, align, 0); // save alignment
      gl.glGetIntegerv(GL.GL_UNPACK_ROW_LENGTH, rowLength, 0); // save row length
      gl.glGetIntegerv(GL.GL_UNPACK_SKIP_ROWS, skipRows, 0); // save skipped rows
      gl.glGetIntegerv(GL.GL_UNPACK_SKIP_PIXELS, skipPixels, 0); // save skipped pixels
      gl.glPixelStorei(GL.GL_UNPACK_ALIGNMENT, data.getAlignment());
      if (DEBUG && VERBOSE) {
        System.out.println("Row length  = " + rowlen);
        System.out.println("skip pixels = " + srcx);
        System.out.println("skip rows   = " + srcy);
        System.out.println("dstx        = " + dstx);
        System.out.println("dsty        = " + dsty);
        System.out.println("width       = " + width);
        System.out.println("height      = " + height);
      }
      gl.glPixelStorei(GL.GL_UNPACK_ROW_LENGTH, rowlen);
      gl.glPixelStorei(GL.GL_UNPACK_SKIP_ROWS, srcy);
      gl.glPixelStorei(GL.GL_UNPACK_SKIP_PIXELS, srcx);

      gl.glTexSubImage2D(
          newTarget,
          mipmapLevel,
          dstx,
          dsty,
          width,
          height,
          data.getPixelFormat(),
          data.getPixelType(),
          buffer);
      gl.glPixelStorei(GL.GL_UNPACK_ALIGNMENT, align[0]); // restore alignment
      gl.glPixelStorei(GL.GL_UNPACK_ROW_LENGTH, rowLength[0]); // restore row length
      gl.glPixelStorei(GL.GL_UNPACK_SKIP_ROWS, skipRows[0]); // restore skipped rows
      gl.glPixelStorei(GL.GL_UNPACK_SKIP_PIXELS, skipPixels[0]); // restore skipped pixels
    }
  }
  /**
   * Updates the content area of the specified target of this texture using the data in the given
   * image. In general this is intended for construction of cube maps.
   *
   * @throws GLException if no OpenGL context was current or if any OpenGL-related errors occurred
   */
  public void updateImage(TextureData data, int target) throws GLException {
    GL gl = GLU.getCurrentGL();

    imgWidth = data.getWidth();
    imgHeight = data.getHeight();
    aspectRatio = (float) imgWidth / (float) imgHeight;
    mustFlipVertically = data.getMustFlipVertically();

    int texTarget = 0;
    int texParamTarget = this.target;

    // See whether we have automatic mipmap generation support
    boolean haveAutoMipmapGeneration =
        (gl.isExtensionAvailable("GL_VERSION_1_4")
            || gl.isExtensionAvailable("GL_SGIS_generate_mipmap"));

    // Indicate to the TextureData what functionality is available
    data.setHaveEXTABGR(gl.isExtensionAvailable("GL_EXT_abgr"));
    data.setHaveGL12(gl.isExtensionAvailable("GL_VERSION_1_2"));

    // Note that automatic mipmap generation doesn't work for
    // GL_ARB_texture_rectangle
    if ((!isPowerOfTwo(imgWidth) || !isPowerOfTwo(imgHeight)) && !haveNPOT(gl)) {
      haveAutoMipmapGeneration = false;
    }

    boolean expandingCompressedTexture = false;
    if (data.getMipmap() && !haveAutoMipmapGeneration) {
      // GLU always scales the texture's dimensions to be powers of
      // two. It also doesn't really matter exactly what the texture
      // width and height are because the texture coords are always
      // between 0.0 and 1.0.
      imgWidth = nextPowerOfTwo(imgWidth);
      imgHeight = nextPowerOfTwo(imgHeight);
      texWidth = imgWidth;
      texHeight = imgHeight;
      texTarget = GL.GL_TEXTURE_2D;
    } else if ((isPowerOfTwo(imgWidth) && isPowerOfTwo(imgHeight)) || haveNPOT(gl)) {
      if (DEBUG) {
        if (isPowerOfTwo(imgWidth) && isPowerOfTwo(imgHeight)) {
          System.err.println("Power-of-two texture");
        } else {
          System.err.println("Using GL_ARB_texture_non_power_of_two");
        }
      }

      texWidth = imgWidth;
      texHeight = imgHeight;
      texTarget = GL.GL_TEXTURE_2D;
    } else if (haveTexRect(gl) && !data.isDataCompressed()) {
      // GL_ARB_texture_rectangle does not work for compressed textures
      if (DEBUG) {
        System.err.println("Using GL_ARB_texture_rectangle");
      }

      texWidth = imgWidth;
      texHeight = imgHeight;
      texTarget = GL.GL_TEXTURE_RECTANGLE_ARB;
    } else {
      // If we receive non-power-of-two compressed texture data and
      // don't have true hardware support for compressed textures, we
      // can fake this support by producing an empty "compressed"
      // texture image, using glCompressedTexImage2D with that to
      // allocate the texture, and glCompressedTexSubImage2D with the
      // incoming data.
      if (data.isDataCompressed()) {
        if (data.getMipmapData() != null) {

          // We don't currently support expanding of compressed,
          // mipmapped non-power-of-two textures to the nearest power
          // of two; the obvious port of the non-mipmapped code didn't
          // work
          throw new GLException(
              "Mipmapped non-power-of-two compressed textures only supported on OpenGL 2.0 hardware (GL_ARB_texture_non_power_of_two)");
        }

        expandingCompressedTexture = true;
      }

      if (DEBUG) {
        System.err.println("Expanding texture to power-of-two dimensions");
      }

      if (data.getBorder() != 0) {
        throw new RuntimeException(
            "Scaling up a non-power-of-two texture which has a border won't work");
      }
      texWidth = nextPowerOfTwo(imgWidth);
      texHeight = nextPowerOfTwo(imgHeight);
      texTarget = GL.GL_TEXTURE_2D;
    }

    texParamTarget = texTarget;
    setImageSize(imgWidth, imgHeight, texTarget);

    if (target != 0) {
      // Allow user to override auto detection and skip bind step (for
      // cubemap construction)
      texTarget = target;
      if (this.target == 0) {
        throw new GLException("Override of target failed; no target specified yet");
      }
      texParamTarget = this.target;
      gl.glBindTexture(texParamTarget, texID);
    } else {
      gl.glBindTexture(texTarget, texID);
    }

    if (data.getMipmap() && !haveAutoMipmapGeneration) {
      int[] align = new int[1];
      gl.glGetIntegerv(GL.GL_UNPACK_ALIGNMENT, align, 0); // save alignment
      gl.glPixelStorei(GL.GL_UNPACK_ALIGNMENT, data.getAlignment());

      if (data.isDataCompressed()) {
        throw new GLException("May not request mipmap generation for compressed textures");
      }

      try {
        GLU glu = new GLU();
        glu.gluBuild2DMipmaps(
            texTarget,
            data.getInternalFormat(),
            data.getWidth(),
            data.getHeight(),
            data.getPixelFormat(),
            data.getPixelType(),
            data.getBuffer());
      } finally {
        gl.glPixelStorei(GL.GL_UNPACK_ALIGNMENT, align[0]); // restore alignment
      }
    } else {
      checkCompressedTextureExtensions(data);
      Buffer[] mipmapData = data.getMipmapData();
      if (mipmapData != null) {
        int width = texWidth;
        int height = texHeight;
        for (int i = 0; i < mipmapData.length; i++) {
          if (data.isDataCompressed()) {
            // Need to use glCompressedTexImage2D directly to allocate and fill this image
            // Avoid spurious memory allocation when possible
            gl.glCompressedTexImage2D(
                texTarget,
                i,
                data.getInternalFormat(),
                width,
                height,
                data.getBorder(),
                mipmapData[i].remaining(),
                mipmapData[i]);
          } else {
            // Allocate texture image at this level
            gl.glTexImage2D(
                texTarget,
                i,
                data.getInternalFormat(),
                width,
                height,
                data.getBorder(),
                data.getPixelFormat(),
                data.getPixelType(),
                null);
            updateSubImageImpl(data, texTarget, i, 0, 0, 0, 0, data.getWidth(), data.getHeight());
          }

          width = Math.max(width / 2, 1);
          height = Math.max(height / 2, 1);
        }
      } else {
        if (data.isDataCompressed()) {
          if (!expandingCompressedTexture) {
            // Need to use glCompressedTexImage2D directly to allocate and fill this image
            // Avoid spurious memory allocation when possible
            gl.glCompressedTexImage2D(
                texTarget,
                0,
                data.getInternalFormat(),
                texWidth,
                texHeight,
                data.getBorder(),
                data.getBuffer().capacity(),
                data.getBuffer());
          } else {
            ByteBuffer buf =
                DDSImage.allocateBlankBuffer(texWidth, texHeight, data.getInternalFormat());
            gl.glCompressedTexImage2D(
                texTarget,
                0,
                data.getInternalFormat(),
                texWidth,
                texHeight,
                data.getBorder(),
                buf.capacity(),
                buf);
            updateSubImageImpl(data, texTarget, 0, 0, 0, 0, 0, data.getWidth(), data.getHeight());
          }
        } else {
          if (data.getMipmap() && haveAutoMipmapGeneration) {
            // For now, only use hardware mipmapping for uncompressed 2D
            // textures where the user hasn't explicitly specified
            // mipmap data; don't know about interactions between
            // GL_GENERATE_MIPMAP and glCompressedTexImage2D
            gl.glTexParameteri(texParamTarget, GL.GL_GENERATE_MIPMAP, GL.GL_TRUE);
            usingAutoMipmapGeneration = true;
          }

          gl.glTexImage2D(
              texTarget,
              0,
              data.getInternalFormat(),
              texWidth,
              texHeight,
              data.getBorder(),
              data.getPixelFormat(),
              data.getPixelType(),
              null);
          updateSubImageImpl(data, texTarget, 0, 0, 0, 0, 0, data.getWidth(), data.getHeight());
        }
      }
    }

    int minFilter = (data.getMipmap() ? GL.GL_LINEAR_MIPMAP_LINEAR : GL.GL_LINEAR);
    int magFilter = GL.GL_LINEAR;
    int wrapMode = (gl.isExtensionAvailable("GL_VERSION_1_2") ? GL.GL_CLAMP_TO_EDGE : GL.GL_CLAMP);

    // REMIND: figure out what to do for GL_TEXTURE_RECTANGLE_ARB
    if (texTarget != GL.GL_TEXTURE_RECTANGLE_ARB) {
      gl.glTexParameteri(texParamTarget, GL.GL_TEXTURE_MIN_FILTER, minFilter);
      gl.glTexParameteri(texParamTarget, GL.GL_TEXTURE_MAG_FILTER, magFilter);
      gl.glTexParameteri(texParamTarget, GL.GL_TEXTURE_WRAP_S, wrapMode);
      gl.glTexParameteri(texParamTarget, GL.GL_TEXTURE_WRAP_T, wrapMode);
      if (this.target == GL.GL_TEXTURE_CUBE_MAP) {
        gl.glTexParameteri(texParamTarget, GL.GL_TEXTURE_WRAP_R, wrapMode);
      }
    }

    // Don't overwrite target if we're loading e.g. faces of a cube
    // map
    if ((this.target == 0)
        || (this.target == GL.GL_TEXTURE_2D)
        || (this.target == GL.GL_TEXTURE_RECTANGLE_ARB)) {
      this.target = texTarget;
    }

    // This estimate will be wrong for cube maps
    estimatedMemorySize = data.getEstimatedMemorySize();
  }