/* Copyright (C) 2000-2004 by Peter Eastman

   This program is free software; you can redistribute it and/or modify it under the
   terms of the GNU General Public License as published by the Free Software
   Foundation; either version 2 of the License, or (at your option) any later version.

   This program is distributed in the hope that it will be useful, but WITHOUT ANY 
   WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A 
   PARTICULAR PURPOSE.  See the GNU General Public License for more details. */

package artofillusion.material;

import artofillusion.*;
import artofillusion.image.*;
import artofillusion.math.*;
import artofillusion.procedural.*;
import artofillusion.ui.*;
import buoy.widget.*;
import java.awt.*;
import java.io.*;

/** This is a Material3D which uses a Procedure to calculate its properties. */

public class ProceduralMaterial3D extends Material3D implements ProcedureOwner
{  
  Procedure proc;
  boolean shadows;
  double stepSize, antialiasing;
  PointInfo info;
  
  public ProceduralMaterial3D()
  {
    proc = new Procedure(new OutputModule [] {
      new OutputModule(Translate.text("MaterialColor"), Translate.text("white"), 0.0, new RGBColor(1.0f, 1.0f, 1.0f), IOPort.COLOR),
      new OutputModule(Translate.text("Transparency"), Translate.text("gray"), 0.0, new RGBColor(0.5f, 0.5f, 0.5f), IOPort.COLOR),
      new OutputModule(Translate.text("Density"), ""+0.5, 0.5, null, IOPort.NUMBER),
      new OutputModule(Translate.text("Scattering"), "0", 0.0, null, IOPort.NUMBER),
      new OutputModule(Translate.text("ScatteringColor"), Translate.text("gray"), 0.0, new RGBColor(0.5f, 0.5f, 0.5f), IOPort.COLOR),
      new OutputModule(Translate.text("Eccentricity"), "0", 0.0, null, IOPort.NUMBER)});
    shadows = true;
    stepSize = 0.1;
    antialiasing = 1.0;
    info = new PointInfo();
  }
  
  public static String getTypeName()
  {
    return "Procedural";
  }
  
  public double getStepSize()
  {
    return stepSize;
  }
  
  public void setStepSize(double step)
  {
    stepSize = step;
  }

  public void getMaterialSpec(MaterialSpec spec, double x, double y, double z, double xsize, double ysize, double zsize, double t)
  {
    OutputModule output[] = proc.getOutputModules();

    info.x = x;
    info.y = y;
    info.z = z;
    info.xsize = xsize*stepSize;
    info.ysize = ysize*stepSize;
    info.zsize = zsize*stepSize;
    info.t = t;
    info.param = null;
    proc.initForPoint(info);
    double density = output[2].getAverageValue(0, 0.0);
    double eccentricity = output[5].getAverageValue(0, 0.0);
    if (density < 0.0)
      density = 0.0;
    if (density > 1.0)
      density = 1.0;
    if (eccentricity < -1.0)
      eccentricity = -1.0;
    if (eccentricity > 1.0)
      eccentricity = 1.0;
    spec.eccentricity = eccentricity;
    output[0].getColor(0, spec.color, 0.0);
    if (density == 0.0)
      {
        spec.transparency.setRGB(1.0f, 1.0f, 1.0f);
        spec.scattering.setRGB(0.0f, 0.0f, 0.0f);
        return;
      }
    double scattering = output[3].getAverageValue(0, 0.0);
    if (scattering < 0.0)
      scattering = 0.0;
    if (scattering > 1.0)
      scattering = 1.0;
    output[1].getColor(0, spec.transparency, 0.0);
    spec.transparency.setRGB((float) Math.pow(spec.transparency.getRed(), density),
      (float) Math.pow(spec.transparency.getGreen(), density),
      (float) Math.pow(spec.transparency.getBlue(), density));
    output[4].getColor(0, spec.scattering, 0.0);
    spec.scattering.scale(density*scattering);
  }

  /* Determine whether this Material uses the specified image. */

  public boolean usesImage(ImageMap image)
  {
    Module modules[] = proc.getModules();

    for (int i = 0; i < modules.length; i++)
      if (modules[i] instanceof ImageModule && ((ImageModule) modules[i]).getMap() == image)
        return true;
    return false;
  }
  
  /* The material scatters light if there is anything connected to the scattering output. */

  public boolean isScattering()
  {
    OutputModule output[] = proc.getOutputModules();
    return output[3].inputConnected(0);
  }

  public boolean castsShadows()
  {
    return shadows;
  }

  public Material duplicate()
  {
    ProceduralMaterial3D mat = new ProceduralMaterial3D();
    
    mat.proc.copy(proc);
    mat.setName(getName());
    mat.setIndexOfRefraction(indexOfRefraction());
    mat.shadows = shadows;
    mat.antialiasing = antialiasing;
    mat.stepSize = stepSize;
    return mat;
  }
  
  public void edit(BFrame fr, Scene sc)
  {
    new ProcedureEditor(proc, this, sc);
  }

  public ProceduralMaterial3D(DataInputStream in, Scene theScene) throws IOException, InvalidObjectException
  {
    short version = in.readShort();
    
    if (version != 0)
      throw new InvalidObjectException("");
    setName(in.readUTF());
    proc = new Procedure(new OutputModule [] {
      new OutputModule(Translate.text("MaterialColor"), Translate.text("white"), 0.0, new RGBColor(1.0f, 1.0f, 1.0f), IOPort.COLOR),
      new OutputModule(Translate.text("Transparency"), Translate.text("gray"), 0.0, new RGBColor(0.5f, 0.5f, 0.5f), IOPort.COLOR),
      new OutputModule(Translate.text("Density"), ""+0.5, 0.5, null, IOPort.NUMBER),
      new OutputModule(Translate.text("Scattering"), "0", 0.0, null, IOPort.NUMBER),
      new OutputModule(Translate.text("ScatteringColor"), Translate.text("gray"), 0.0, new RGBColor(0.5f, 0.5f, 0.5f), IOPort.COLOR),
      new OutputModule(Translate.text("Eccentricity"), "0", 0.0, null, IOPort.NUMBER)});
    setIndexOfRefraction(in.readDouble());
    shadows = in.readBoolean();
    antialiasing = in.readDouble();
    stepSize = in.readDouble();
    proc.readFromStream(in, theScene);
    info = new PointInfo();
  }
  
  public void writeToFile(DataOutputStream out, Scene theScene) throws IOException
  {
    out.writeShort(0);
    out.writeUTF(getName());
    out.writeDouble(indexOfRefraction());
    out.writeBoolean(shadows);
    out.writeDouble(antialiasing);
    out.writeDouble(stepSize);
    proc.writeToStream(out, theScene);
  }

  /** Get the title of the procedure's editing window. */
  
  public String getWindowTitle()
  {
    return "Procedural Material";
  }
  
  /** Create an object which displays a preview of the procedure. */
  
  public Object getPreview(ProcedureEditor editor)
  {
    FloatingDialog dlg = new FloatingDialog(editor.getParentFrame(), "Preview", false);
    MaterialPreviewer preview = new MaterialPreviewer(null, this, 200, 160);
    dlg.setContent(preview);
    dlg.pack();
    Rectangle parentBounds = editor.getParentFrame().getBounds();
    Rectangle location = dlg.getBounds();
    location.y = parentBounds.y;
    location.x = parentBounds.x+parentBounds.width;
    dlg.setBounds(location);
    dlg.setVisible(true);
    return preview;
  }
  
  /** Update the display of the preview. */
  
  public void updatePreview(Object preview)
  {
    ((MaterialPreviewer) preview).render();
  }
  
  /** Dispose of the preview object when the editor is closed. */
  
  public void disposePreview(Object preview)
  {
    ((FloatingDialog) ((MaterialPreviewer) preview).getParent()).dispose();
  }
  
  /** Determine whether the procedure may contain View Angle modules. */
  
  public boolean allowViewAngle()
  {
    return false;
  }
  
  /** Determine whether the procedure may contain Parameter modules. */
  
  public boolean allowParameters()
  {
    return false;
  }
  
  /** Determine whether the procedure may be renamed. */
  
  public boolean canEditName()
  {
    return true;
  }
  
  /** This is called when the user clicks OK in the procedure editor. */
  
  public void acceptEdits(ProcedureEditor editor)
  {
    int i = editor.getScene().indexOf(this);
    if (i > -1)
      editor.getScene().changeMaterial(i);
  }
  
  /** Display the Properties dialog. */
  
  public void editProperties(ProcedureEditor editor)
  {
    ValueField refractField = new ValueField(indexOfRefraction(), ValueField.POSITIVE);
    ValueField stepField = new ValueField(stepSize, ValueField.POSITIVE);
    ValueField aliasField = new ValueField(antialiasing, ValueField.NONNEGATIVE);
    BCheckBox shadowBox = new BCheckBox(Translate.text("CastsShadows"), shadows);
    ComponentsDialog dlg = new ComponentsDialog(editor.getParentFrame(), Translate.text("editMaterialTitle"), 
      new Widget [] {refractField, stepField, aliasField, shadowBox},
      new String [] {Translate.text("IndexOfRefraction"), Translate.text("integrationStepSize"),
      Translate.text("Antialiasing"), ""});
    if (!dlg.clickedOk())
      return;
    editor.saveState(false);
    setIndexOfRefraction(refractField.getValue());
    setStepSize(stepField.getValue());
    antialiasing = aliasField.getValue();
    shadows = shadowBox.getState();
    editor.updatePreview();
  }
}
