/*
 * Open Source Physics software is free software as described near the bottom of this code file.
 *
 * For additional information and documentation on Open Source Physics please see:
 * <http://www.opensourcephysics.org/>
 */

package org.opensourcephysics.display3d.simple3d;
import java.awt.Color;
import java.awt.Graphics2D;

/**
 * This is the basic class for all Elements which consist of a sequence
 * of 3D colored tiles: Ellipsoid, Cylinder, Box, ...
 * A tile is basically a collection of rectangles.
 * NOT YET FINISHED!!!!!
 */
public abstract class AbstractTile extends Element {
  // Configuration variables
  protected int numberOfTiles = 0;
  protected double corners[][][] = null;        // the numberOfTiles x vertex x 3  (vertex = 4 for 4-sided tiles
  private boolean drawQuickInterior = false;
  private int interiorTransparency = 128;
  //  private double displacementFactor = 1.0;
  // for z-coded colors

  private boolean levelBelowWhenEqual = true;
  private double levelx = 0.0, levely = 0.0, levelz = 0.0, leveldx = 0.0, leveldy = 0.0, leveldz = 1.0;
  private double[] levelZ = null;
  private Color[] levelColors = null;
  // Implementation variables
  private int a[][] = null, b[][] = null;
  private double[] pixel = new double[3];       // The output for all projections
  private double[] center = new double[3];
  private double[] pixelOrigin = new double[3]; // The projection of the origin
  private Object3D[] objects = null;

  // ----------------------------------------------
  // Configuration
  // See also below for everything related to the use of z-coded color
  // ----------------------------------------------

  /**
   * Sets an optional displacement factor to apply to the tiles when computing
   * their distance to the eye.
   * Setting it to a number bigger that 1 (say 1.03) is useful when you want
   * to draw anything on top of an object.
   * @param factor the desired displacement factor
   *
  public void setDisplacementFactor(double factor) {
    this.displacementFactor = factor;
  }

  /**
   * Gets the displacement factor
   * @return the current displacement factor
   * @see #setDisplacementFactor()
   *
  public double getDisplacementFactor() {
    return this.displacementFactor;
  }
*/

  /**
   * Draw a transparent interior when in quickDraw mode.
   * Default is <b>false</b>
   * @param draw the value desired
   * @param transparency the desired level of transparency (from 0=fully transparent to 255=opaque)
   */
  public void setDrawQuickInterior(boolean draw, int transparency) {
    this.drawQuickInterior = draw;
    this.interiorTransparency = Math.max(0, Math.min(transparency, 255));
  }

  /**
   * Whether a value equal to one of the thresholds should be drawn using the color
   * below or above
   * @param belowWhenEqual <b>true</b> to use the color below, <b>false</b> to use teh color above
   */
  public void setColorBelowWhenEqual(boolean belowWhenEqual) {
    this.levelBelowWhenEqual = belowWhenEqual;
  }

  /**
   * Sets the origin and direction of the color change.
   * Default is (0,0,0) and (0,0,1), giving z-coded regions
   *
   * @param origin double[]
   * @param direction double[]
   */
  public void setColorOriginAndDirection(double[] origin, double[] direction) {
    levelx = origin[0];
    levely = origin[1];
    levelz = origin[2];
    leveldx = direction[0];
    leveldy = direction[1];
    leveldz = direction[2];
  }

  /**
   * Set the levels and color for regional color separation
   * @param thresholds an array on n doubles that separate the n+1 regions.
   * <b>null</b> for no region separation
   * @param colors an array on n+1 colors, one for each of the regions
   */
  public void setColorRegions(double thresholds[], Color colors[]) {
    if((thresholds==null)||(colors==null)) {
      levelZ = null;
      levelColors = null;
      return;
    }
    levelZ = new double[thresholds.length];
    levelColors = new Color[thresholds.length+1];
    for(int i = 0; i<thresholds.length; i++) {
      levelZ[i] = thresholds[i];
    }
    for(int i = 0; i<thresholds.length+1; i++) {
      if(i<colors.length) {
        levelColors[i] = colors[i];
      } else {
        levelColors[i] = colors[colors.length-1];
      }
    }
    setElementChanged(true);
  }

  // ----------------------------------------------
  // part of the abstract methods in Element
  // ----------------------------------------------
  Object3D[] getObjects3D() {
    if(!isReallyVisible()) {
      return null;
    }
    if(hasChanged()) {
      computeCorners();
      projectPoints();
    } else if(needsToProject()) {
      projectPoints();
    }
    if(numberOfTiles<1) {
      return null;
    }
    return objects;
  }

  void draw(Graphics2D _g2, int _index) {
    if(levelZ!=null) {
      drawColorCoded(_g2, _index);
      return;
    }
    int sides = corners[_index].length;
    // Allow the panel to adjust color according to depth
    if(getRealStyle().isDrawingFill()) { // First fill the inside
      _g2.setPaint(getDrawingPanel3D().projectColor(getRealStyle().getFillColor(), objects[_index].getDistance()));
      _g2.fillPolygon(a[_index], b[_index], sides);
    }
    if(getRealStyle().isDrawingLines()) {
      _g2.setStroke(getRealStyle().getLineStroke());
      _g2.setColor(getDrawingPanel3D().projectColor(getRealStyle().getLineColor(), objects[_index].getDistance()));
      _g2.drawPolygon(a[_index], b[_index], sides);
    }
  }

  void drawQuickly(Graphics2D _g2) {
    if(!isReallyVisible()) {
      return;
    }
    if(hasChanged()) {
      computeCorners();
      projectPoints();
    } else if(needsToProject()) {
      projectPoints();
    }
    if(numberOfTiles<1) {
      return;
    }
    _g2.setStroke(getRealStyle().getLineStroke());
    if(getRealStyle().isDrawingFill()||drawQuickInterior) {
      Color fillColor = getRealStyle().getFillColor();
      if(drawQuickInterior&&(fillColor.getAlpha()>interiorTransparency)) {
        fillColor = new Color(fillColor.getRed(), fillColor.getGreen(), fillColor.getBlue(), interiorTransparency);
      }
      _g2.setPaint(fillColor);
      for(int i = 0; i<numberOfTiles; i++) {
        _g2.fillPolygon(a[i], b[i], corners[i].length);
      }
    }
    if(getRealStyle().isDrawingLines()) {
      _g2.setColor(getRealStyle().getLineColor());
      for(int i = 0; i<numberOfTiles; i++) {
        _g2.drawPolygon(a[i], b[i], corners[i].length);
      }
    }
  }

  // -------------------------------------
  // Interaction
  // -------------------------------------
  protected InteractionTarget getTargetHit(int x, int y) {
    if(!isReallyVisible()) {
      return null;
    }
    if(hasChanged()) {
      computeCorners();
      projectPoints();
    } else if(needsToProject()) {
      projectPoints();
    }
    if(numberOfTiles<1) {
      return null;
    }
    if(targetPosition.isEnabled()&&(Math.abs(pixelOrigin[0]-x)<SENSIBILITY)&&(Math.abs(pixelOrigin[1]-y)<SENSIBILITY)) {
      return targetPosition;
    }
    return null;
  }

  // -------------------------------------
  // Private or protected methods
  // -------------------------------------

  /**
   * This will be used by subclasses whenever there is a need to recompute
   * the actual values of the corners before drawing.
   * Synchronization is recomended.
   */
  abstract protected void computeCorners();

  protected void setCorners(double[][][] _data) {
    corners = _data;
    if(corners==null) {
      numberOfTiles = 0;
      a = null;
      b = null;
      return;
    }
    numberOfTiles = corners.length;
    a = new int[numberOfTiles][];
    b = new int[numberOfTiles][];
    objects = new Object3D[numberOfTiles];
    for(int i = 0; i<numberOfTiles; i++) {
      int sides = corners[i].length;
      a[i] = new int[sides];
      b[i] = new int[sides];
      objects[i] = new Object3D(this, i);
    }
  }

  protected void projectPoints() {
    for(int i = 0; i<numberOfTiles; i++) {
      int sides = corners[i].length;
      for(int k = 0; k<3; k++) {
        center[k] = 0.0;                                            // Reset coordinates of the center
      }
      for(int j = 0; j<sides; j++) {
        getDrawingPanel3D().project(corners[i][j], pixel);          // Project each corner
        a[i][j] = (int) pixel[0];
        b[i][j] = (int) pixel[1];
        for(int k = 0; k<3; k++) {
          center[k] += corners[i][j][k];                            // Add to the coordinates of the center
        }
      }
      for(int k = 0; k<3; k++) {
        center[k] /= sides;
      }
      getDrawingPanel3D().project(center, pixel);                   // Project the center and take it
      objects[i].setDistance(pixel[2]*getStyle().getDepthFactor()); // as reference for the distance
    }
    getDrawingPanel3D().project(getHotSpot(targetPosition), pixelOrigin);
    setNeedToProject(false);
  }

  // ----------------------------------------------
  // Everything related to the use of z-coded color
  // ----------------------------------------------
  private double levelScalarProduct(double point[]) {
    return(point[0]-levelx)*leveldx+(point[1]-levely)*leveldy+(point[2]-levelz)*leveldz;
  }

  private void drawColorCoded(Graphics2D _g2, int _index) {
    int sides = corners[_index].length;
    // Compute in which region is each point
    int region[] = new int[sides];
    if(levelBelowWhenEqual) {
      for(int j = 0; j<sides; j++) {
        region[j] = 0;
        double level = levelScalarProduct(corners[_index][j]);
        for(int k = levelZ.length-1; k>=0; k--) {     // for each level
          if(level>levelZ[k]) {
            region[j] = k+1;
            break;
          }
        }
      }
    } else {
      for(int j = 0; j<sides; j++) {
        region[j] = levelZ.length;
        double level = levelScalarProduct(corners[_index][j]);
        for(int k = 0, l = levelZ.length; k<l; k++) { // for each level
          if(level<levelZ[k]) {
            region[j] = k;
            break;
          }
        }
      }
    }
    // Compute the subpoligon in each region
    int newCornersA[] = new int[sides*2];
    int newCornersB[] = new int[sides*2];
    for(int k = 0, l = levelZ.length; k<=l; k++) {     // for each level
      int newCornersCounter = 0;
      for(int j = 0; j<sides; j++) {                   // for each point
        int next = (j+1)%sides;
        if((region[j]<=k)&&(region[next]>=k)) {        // intersection bottom-up
          if(region[j]==k) {
            newCornersA[newCornersCounter] = a[_index][j];
            newCornersB[newCornersCounter] = b[_index][j];
            newCornersCounter++;
          } else {                                     // It started further down
            double t = levelScalarProduct(corners[_index][j]);
            t = (levelZ[k-1]-t)/(levelScalarProduct(corners[_index][next])-t);
            newCornersA[newCornersCounter] = (int) Math.round(a[_index][j]+t*(a[_index][next]-a[_index][j]));
            newCornersB[newCornersCounter] = (int) Math.round(b[_index][j]+t*(b[_index][next]-b[_index][j]));
            newCornersCounter++;
          }
          if(region[next]>k) {                         // This segment contributes with a second point
            double t = levelScalarProduct(corners[_index][j]);
            t = (levelZ[k]-t)/(levelScalarProduct(corners[_index][next])-t);
            newCornersA[newCornersCounter] = (int) Math.round(a[_index][j]+t*(a[_index][next]-a[_index][j]));
            newCornersB[newCornersCounter] = (int) Math.round(b[_index][j]+t*(b[_index][next]-b[_index][j]));
            newCornersCounter++;
          }
        } else if((region[j]>=k)&&(region[next]<=k)) { // intersection top-down
          if(region[j]==k) {
            newCornersA[newCornersCounter] = a[_index][j];
            newCornersB[newCornersCounter] = b[_index][j];
            newCornersCounter++;
          } else {                                     // It started further up
            double t = levelScalarProduct(corners[_index][j]);
            t = (levelZ[k]-t)/(levelScalarProduct(corners[_index][next])-t);
            newCornersA[newCornersCounter] = (int) Math.round(a[_index][j]+t*(a[_index][next]-a[_index][j]));
            newCornersB[newCornersCounter] = (int) Math.round(b[_index][j]+t*(b[_index][next]-b[_index][j]));
            newCornersCounter++;
          }
          if(region[next]<k) {                         // This segment contributes with a second point
            double t = levelScalarProduct(corners[_index][j]);
            t = (levelZ[k-1]-t)/(levelScalarProduct(corners[_index][next])-t);
            newCornersA[newCornersCounter] = (int) Math.round(a[_index][j]+t*(a[_index][next]-a[_index][j]));
            newCornersB[newCornersCounter] = (int) Math.round(b[_index][j]+t*(b[_index][next]-b[_index][j]));
            newCornersCounter++;
          }
        }
      }
      if(newCornersCounter>0) {                        // Draw the subpoligon
        Color theFillColor = levelColors[k];
        // if (theFillPattern instanceof Color) theFillPattern = _panel.projectColor((Color) theFillPattern,objects[_index].distance);
        _g2.setPaint(theFillColor);
        _g2.fillPolygon(newCornersA, newCornersB, newCornersCounter);
      }
    }
    _g2.setColor(getDrawingPanel3D().projectColor(getRealStyle().getLineColor(), objects[_index].getDistance()));
    _g2.setStroke(getRealStyle().getLineStroke());
    _g2.drawPolygon(a[_index], b[_index], sides);
  }

}

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

 * Code that uses any portion of the code in the org.opensourcephysics package
 * or any subpackage (subdirectory) of this package must must also be be released
 * under the GNU GPL license.
 *
 * This software 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.
 *
 * You should have received a copy of the GNU General Public License
 * along with this; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston MA 02111-1307 USA
 * or view the license online at http://www.gnu.org/copyleft/gpl.html
 *
 * Copyright (c) 2007  The Open Source Physics project
 *                     http://www.opensourcephysics.org
 */
