/* Copyright (C) 1999-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;

import artofillusion.math.*;
import artofillusion.object.*;
import artofillusion.ui.*;
import artofillusion.object.Mesh.*;
import buoy.event.*;
import java.awt.*;
import java.util.Vector;

/** ScaleMeshTool is an EditingTool used for scaling the vertices of TriangleMesh objects. */

public class ScaleMeshTool extends EditingTool
{
  private boolean noSelection, notOnHandle, scaleX, scaleY, scaleAll;
  private Point clickPoint;
  private double clickX, clickY, centerX, centerY;
  private Vec3 scaleCenter, baseVertPos[];
  private BoundingBox bounds;
  private UndoRecord undo;
  
  private static Image icon, selectedIcon;
  public static final int HANDLE_SIZE = 5;

  public ScaleMeshTool(EditingWindow fr)
  {
    super(fr);
    icon = loadImage("scalePoints.gif");
    selectedIcon = loadImage("selected/scalePoints.gif");
  }

  public void activate()
  {
    super.activate();
    
    MeshViewer view = (MeshViewer) theWindow.getView();
    int selected[] = view.getSelectionDistance();
    noSelection = false;
    for (int i = 0; i < selected.length; i++)
      if (selected[i] == 0)
	{
	  theWindow.setHelpText(Translate.text("scaleMeshTool.helpText"));
	  return;
	}
    theWindow.setHelpText(Translate.text("scaleMeshTool.errorText"));
    noSelection = true;
  }

  public int whichClicks()
  {
    return ALL_CLICKS;
  }

  public Image getIcon()
  {
    return icon;
  }

  public Image getSelectedIcon()
  {
    return selectedIcon;
  }

  public String getToolTipText()
  {
    return Translate.text("scaleMeshTool.tipText");
  }

  public void drawOverlay(Graphics g, ViewerCanvas view)
  {
    Mesh mesh = (Mesh) ((MeshViewer) view).getObject().object;
    MeshVertex v[] = mesh.getVertices();
    Camera cam = view.getCamera();
    Rectangle b;

    if (noSelection)
      return;
    g.setColor(Color.red);
    b = findScreenBounds(findSelectionBounds(v, cam, (MeshViewer) view), cam, (MeshViewer) view);
    if (b != null)
      {
	g.fillRect(b.x, b.y, HANDLE_SIZE, HANDLE_SIZE);
	g.fillRect(b.x+b.width-HANDLE_SIZE+1, b.y, HANDLE_SIZE, HANDLE_SIZE);
	g.fillRect(b.x, b.y+b.height-HANDLE_SIZE+1, HANDLE_SIZE, HANDLE_SIZE);
	g.fillRect(b.x+b.width-HANDLE_SIZE+1, b.y+b.height-HANDLE_SIZE+1, HANDLE_SIZE, HANDLE_SIZE);
	g.fillRect(b.x+(b.width-HANDLE_SIZE)/2, b.y, HANDLE_SIZE, HANDLE_SIZE);
	g.fillRect(b.x, b.y+(b.height-HANDLE_SIZE)/2, HANDLE_SIZE, HANDLE_SIZE);
	g.fillRect(b.x+(b.width-HANDLE_SIZE)/2, b.y+b.height-HANDLE_SIZE+1, HANDLE_SIZE, HANDLE_SIZE);
	g.fillRect(b.x+b.width-HANDLE_SIZE+1, b.y+(b.height-HANDLE_SIZE)/2, HANDLE_SIZE, HANDLE_SIZE);
      }
  }
  
  public void mousePressed(WidgetMouseEvent e, ViewerCanvas view)
  {
    Mesh mesh = (Mesh) ((MeshViewer) view).getObject().object;
    MeshVertex v[] = mesh.getVertices();
    Camera cam = view.getCamera();
    Rectangle b;
    int j, k;
    
    if (noSelection)
      return;
    clickPoint = e.getPoint();
    
    // Determine which handle was clicked on.
    
    bounds = findSelectionBounds(v, cam, (MeshViewer) view);
    b = findScreenBounds(bounds, cam, (MeshViewer) view);
    if (!b.contains(clickPoint))
      {
	notOnHandle = true;
	return;
      }
    if (clickPoint.x <= b.x+HANDLE_SIZE)
      j = 0;
    else if (clickPoint.x >= b.x+(b.width-HANDLE_SIZE)/2 && clickPoint.x <= b.x+(b.width-HANDLE_SIZE)/2+HANDLE_SIZE)
      j = 1;
    else if (clickPoint.x >= b.x+b.width-HANDLE_SIZE)
      j = 2;
    else j = -1;
    if (clickPoint.y <= b.y+HANDLE_SIZE)
      k = 0;
    else if (clickPoint.y >= b.y+(b.height-HANDLE_SIZE)/2 && clickPoint.y <= b.y+(b.height-HANDLE_SIZE)/2+HANDLE_SIZE)
      k = 1;
    else if (clickPoint.y >= b.y+b.height-HANDLE_SIZE)
       k = 2;
    else k = -1;
    if (j == -1 || k == -1 || (j == 1 && k == 1))
      {
	notOnHandle = true;
	return;
      }
    notOnHandle = false;
    clickX = (double) clickPoint.x;
    clickY = (double) clickPoint.y;
    scaleCenter = new Vec3(0.0, 0.0, (bounds.minz+bounds.maxz)/2.0);
    if (j == 0)
      {
        scaleX = true;
	centerX = (double) (b.x + b.width);
	scaleCenter.x = bounds.minx;
      }
    else if (j == 2)
      {
        scaleX = true;
	centerX = (double) b.x;
	scaleCenter.x = bounds.maxx;
      }
    else
      {
	scaleX = false;
	scaleCenter.x = (bounds.minx+bounds.maxx);
      }
    if (k == 0)
      {
        scaleY = true;
	centerY = (double) (b.y + b.height);
	scaleCenter.y = bounds.miny;
      }
    else if (k == 2)
      {
        scaleY = true;
	centerY = (double) b.y;
	scaleCenter.y = bounds.maxy;
      }
    else
      {
	scaleY = false;
	scaleCenter.y = (bounds.miny+bounds.maxy);
      }
    if (e.isControlDown())
      {
	centerX = (double) (b.x + (b.width/2));
	centerY = (double) (b.y + (b.height/2));
	scaleCenter.x = (bounds.minx+bounds.maxx)/2.0;
	scaleCenter.y = (bounds.miny+bounds.maxy)/2.0;
      }
    scaleAll = e.isShiftDown();
    baseVertPos = mesh.getVertexPositions();
  }
  
  public void mouseDragged(WidgetMouseEvent e, ViewerCanvas view)
  {
    Mesh mesh = (Mesh) ((MeshViewer) view).getObject().object;
    MeshVertex vert[] = mesh.getVertices();
    Camera cam = view.getCamera();
    Point dragPoint = e.getPoint();
    Vec3 v[];
    Vec2 p;
    int i, vertSize = MeshViewer.HANDLE_SIZE;
    int selectDist[] = ((MeshViewer) view).getSelectionDistance();
    double size, xscale, yscale, zscale;
    
    if (noSelection || notOnHandle)
      return;
    if (undo == null)
      undo = new UndoRecord(theWindow, false, UndoRecord.COPY_VERTEX_POSITIONS, new Object [] {mesh, mesh.getVertexPositions()});
    if (scaleAll)
      xscale = yscale = 0.0;
    else
      xscale = yscale = 1.0;
    if (scaleX)
      {
	size = dragPoint.x-centerX;
	xscale = size/(clickX-centerX);
	if (xscale <= 0)
	  xscale = Math.abs(1.0/(clickX-centerX));
      }
    if (scaleY)
      {
	size = dragPoint.y-centerY;
	yscale = size/(clickY-centerY);
	if (yscale <= 0)
	  yscale = Math.abs(1.0/(clickY-centerY));
      }
    if (scaleAll)
      xscale = yscale = zscale = Math.max(xscale, yscale);
    else
      zscale = 1.0;
    v = findScaledPositions(baseVertPos, xscale, yscale, zscale, (MeshViewer) view);
    mesh.setVertexPositions(v);
    ((ObjectViewer) view).objectChanged();
    theWindow.updateImage();
    if (scaleAll)
      theWindow.setHelpText(Translate.text("scaleMeshTool.dragText", Double.toString(Math.round(xscale*1e5)/1e5)));
    else if (scaleX && !scaleY)
      theWindow.setHelpText(Translate.text("scaleMeshTool.dragText", Double.toString(Math.round(xscale*1e5)/1e5)));
    else if (scaleY && !scaleX)
      theWindow.setHelpText(Translate.text("scaleMeshTool.dragText", Double.toString(Math.round(yscale*1e5)/1e5)));
    else
      theWindow.setHelpText(Translate.text("scaleMeshTool.dragText", Math.round(xscale*1e5)/1e5+", "+Math.round(yscale*1e5)/1e5));
  }

  public void mouseReleased(WidgetMouseEvent e, ViewerCanvas view)
  {
    Object3D meshobj = ((MeshViewer) view).getObject().object;
    Mesh mesh = (Mesh) meshobj;
    MeshVertex vert[] = mesh.getVertices();
    Camera cam = view.getCamera();
    Point dragPoint = e.getPoint();
    int i;
    Vec3 v[];
    double size, xscale, yscale, zscale;

    if (noSelection || notOnHandle)
      return;
    if (scaleAll)
      xscale = yscale = 0.0;
    else
      xscale = yscale = 1.0;
    if (scaleX)
      {
	size = dragPoint.x-centerX;
	xscale = size/(clickX-centerX);
	if (xscale <= 0)
	  xscale = Math.abs(1.0/(clickX-centerX));
      }
    if (scaleY)
      {
	size = dragPoint.y-centerY;
	yscale = size/(clickY-centerY);
	if (yscale <= 0)
	  yscale = Math.abs(1.0/(clickY-centerY));
      }
    if (scaleAll)
      xscale = yscale = zscale = Math.max(xscale, yscale);
    else
      zscale = 1.0;
    if (undo != null)
      theWindow.setUndoRecord(undo);
    v = findScaledPositions(baseVertPos, xscale, yscale, zscale, (MeshViewer) view);
    mesh.setVertexPositions(v);
    meshobj.informChanged(this, ModelEvent.CHANGEDUR_OBJECTEDITOR);
    theWindow.setHelpText(Translate.text("scaleMeshTool.helpText"));
    undo = null;
    baseVertPos = null;
  }

  /* This method returns a bounding box for the selected vertices in view coordinates. */
  
  BoundingBox findSelectionBounds(MeshVertex vert[], Camera cam, MeshViewer view)
  {
    int selected[] = view.getSelectionDistance();
    double minx, miny, minz, maxx, maxy, maxz;
    Vec3 v;
    int i;
    
    minx = miny = minz = Double.MAX_VALUE;
    maxx = maxy = maxz = -Double.MAX_VALUE;
    for (i = 0; i < vert.length; i++)
      {
	if (selected[i] == 0)
	  {
	    v = cam.getObjectToView().times(vert[i].r);
	    if (v.x < minx) minx = v.x;
	    if (v.x > maxx) maxx = v.x;
	    if (v.y < miny) miny = v.y;
	    if (v.y > maxy) maxy = v.y;
	    if (v.z < minz) minz = v.z;
	    if (v.z > maxz) maxz = v.z;
	  }
      }
    return new BoundingBox(minx, maxx, miny, maxy, minz, maxz);
  }
  
  /* Given a bounding box in view coordinates, find the corresponding rectangle in
     screen coordinates. */
  
  Rectangle findScreenBounds(BoundingBox b, Camera cam, MeshViewer view)
  {
    CoordinateSystem coords = view.getObject().coords;
    Mat4 m = cam.getObjectToWorld();
    Rectangle r;
    
    cam.setObjectTransform(cam.getViewToWorld());
    r = cam.findScreenBounds(b);
    cam.setObjectTransform(m);
    if (r != null)
      r.setBounds(r.x-10, r.y-10, r.width+20, r.height+20);
    return r;
  }

  /* Find the new positions of the vertices after scaling. */

  Vec3 [] findScaledPositions(Vec3 vert[], double xscale, double yscale, double zscale, MeshViewer view)
  {
    Vec3 v[] = new Vec3 [vert.length];
    int selected[] = view.getSelectionDistance();
    Camera cam = view.getCamera();
    Mat4 m;
    int i;
    
    // Find the transformation matrix.
    
    m = cam.getObjectToView();
    m = Mat4.translation(-scaleCenter.x, -scaleCenter.y, -scaleCenter.z).times(m);
    m = Mat4.scale(xscale, yscale, zscale).times(m);
    m = Mat4.translation(scaleCenter.x, scaleCenter.y, scaleCenter.z).times(m);
    m = cam.getViewToWorld().times(m);
    m = view.getDisplayCoordinates().toLocal().times(m);
    
    // Determine the deltas.
    
    for (i = 0; i < vert.length; i++)
      {
	if (selected[i] == 0)
	  v[i] = m.times(vert[i]).minus(vert[i]);
	else
	  v[i] = new Vec3();
      }
    if (theFrame instanceof MeshEditorWindow)
      ((MeshEditorWindow) theFrame).adjustDeltas(v);
    for (i = 0; i < vert.length; i++)
      v[i].add(vert[i]);
    return v;
  }
}