/// Evolver Copyright 2000, Morgan McGuire
/// matrix@alum.mit.edu   http://graphics3d.com/matrix
import java.net.*;
import java.awt.*;
import javax.swing.*;

/**
 * Argument order is chosen to preserve the semantics of Distort,
 * where the first argument gives color and the second gives shape.
 */
public class EnvironmentMap extends TwoArgumentOperator implements ELOperator
{
  Grayscale grayscale = new Grayscale();
  Blur blur = new Blur();
  Image icon = null;

  public void apply(ELMatrix argument[], ELMatrix out, ELEnvironment environment)
  {
    checkArguments(argument);

    int width = out.getWidth();
    int height = out.getHeight();

    if ((width == 128) && (height == 128)) {
      fastApply(argument, out, environment);
      return;
    }

    ELMatrix temp = ELMatrix.alloc(width, height);

    // Take the first argument to grayscale
    grayscale.apply(new ELMatrix[]{argument[1]}, temp, environment);

    // RGB are all the same after grayscale
    float terrain[] = temp.red;

    // Now compute the derivatives.  Use the out matrix to store them temporarily
    float nx[] = out.red;
    float ny[] = out.grn;
    for(int y = height - 1; y >= 0; y--) {
      for(int x = width - 1; x >= 0; x--) {
        int i = x + (y * width);
        
        // Compute the x and y surface "normals"
        float center = terrain[i];

        nx[i] = -(terrain[((x + 1) % width) + (y * width)] - center);
        ny[i] = -(terrain[x + (((y + 1) % height) * width)] - center);
      }
    }

    // Blur the derivatives out
    blur.apply(new ELMatrix[]{out}, temp, environment);
    
    // Pick up the blurred normals
    nx = temp.red;
    ny = temp.grn;

    // For extra performance, pull the arrays into locals so we avoid
    // excess dereferences.
    float red[] = out.red;
    float grn[] = out.grn;
    float blu[] = out.blu;

    ELMatrix color = argument[0];
    float cRed[] = color.red;
    float cGrn[] = color.grn;
    float cBlu[] = color.blu;

    double s = Math.max(width, height) / 3;

    float xShift = width / 2.0f + width * 10;
    float yShift = height / 2.0f + height * 10;

    float xScale = 5.0f * width;
    float yScale = 5.0f * height;

    for(int y = height - 1; y >= 0; y--) {
      for(int x = width - 1; x >= 0; x--) {
        int i = x + (y * width);
        
        // Environment map.  Emphasize the contours by multiplying the
        // normals by a large amount before mapping.
        int xx = (int)(nx[i] * xScale + xShift) % width;
        int yy = (int)(ny[i] * yScale + yShift) % height;
        int j = xx + (yy * width);

        red[i] = cRed[j];
        grn[i] = cGrn[j];
        blu[i] = cBlu[j];
      }
    }

    // Free the temporary
    temp = temp.free();
  }


  public void fastApply(ELMatrix argument[], ELMatrix out, ELEnvironment environment)
  {
    checkArguments(argument);

    int width = out.getWidth();
    int height = out.getHeight();
    if (!((width == 128) && (height == 128))) {
      throw new Error("Not available for optimization");
    }


    ELMatrix temp = ELMatrix.alloc(width, height);

    // Take the first argument to grayscale
    grayscale.apply(new ELMatrix[]{argument[1]}, temp, environment);

    // RGB are all the same after grayscale
    float terrain[] = temp.red;

    // Now compute the derivatives.  Use the out matrix to store them temporarily
    float nx[] = out.red;
    float ny[] = out.grn;
    for(int y = height - 1; y >= 0; y--) {
      int yTimesWidth = (y * width);
      int yc1 = (((y + 1) & 127) * width);
      for(int x = width - 1; x >= 0; x--) {
        int i = x + yTimesWidth;
        
        // Compute the x and y surface "normals"
        float center = terrain[i];

        nx[i] = -(terrain[((x + 1) & 127) + yTimesWidth] - center);
        ny[i] = -(terrain[x + yc1] - center);
      }
    }

    // Blur the derivatives out
    blur.apply(new ELMatrix[]{out}, temp, environment);
    
    // Pick up the blurred normals
    nx = temp.red;
    ny = temp.grn;

    // For extra performance, pull the arrays into locals so we avoid
    // excess dereferences.
    float red[] = out.red;
    float grn[] = out.grn;
    float blu[] = out.blu;

    ELMatrix color = argument[0];
    float cRed[] = color.red;
    float cGrn[] = color.grn;
    float cBlu[] = color.blu;

    double s = Math.max(width, height) / 3;

    float xShift = width / 2.0f + width * 6;
    float yShift = height / 2.0f + height * 6;

    float xScale = 5.0f * width;
    float yScale = 5.0f * height;

    for(int y = height - 1; y >= 0; y--) {
      for(int x = width - 1; x >= 0; x--) {
        int i = x + (y * width);
        
        // Environment map.  Emphasize the contours by multiplying the
        // normals by a large amount before mapping.
        int xx = (int)(nx[i] * xScale + xShift) & 127;
        int yy = (int)(ny[i] * yScale + yShift) & 127;
        
        int j = xx + (yy << 7);

        red[i] = cRed[j];
        grn[i] = cGrn[j];
        blu[i] = cBlu[j];
      }
    }

    // Free the temporary
    temp = temp.free();
  }

  public String name()
  {
    return "EnvironmentMap";
  }

  public String description()
  {
    return "Interprets the first argument as a height map and uses the second as a color " +
      "environment map (as if reflecting the second image).";
  }

  protected String iconString()
  {
    return "Evt";
  }

  public void paintIcon(Graphics2D graphics, int x, int y, int width, int height)
  {
    if (icon == null) {
      try {
        icon = new ImageIcon(new URL("file:evtmap.gif")).getImage();
      } catch (MalformedURLException e) {
        System.err.println("Warning: " + e); 
      }
      
      while ((icon.getWidth(null) == -1) || (icon.getHeight(null) == -1));
      // empty loop
    }

    graphics.drawImage(icon, x, y, x + width, y + height, 
                  0, 0, icon.getWidth(null), icon.getHeight(null), null);    
  }
}
