/* Copyright (C) 2003-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.*;
import artofillusion.math.*;
import artofillusion.object.*;
import artofillusion.ui.*;
import buoy.event.*;
import java.awt.*;
import java.util.Vector;

/** CreateVertexTool is an EditingTool used for adding vertices to triangle meshes. */

public class CreateVertexTool extends EditingTool
{
  private static Image icon, selectedIcon;
  private Point clickPoint;
  private Vec3 clickPos, constrainDir;
  private int target;
  private int vertexToMove;
  
  public CreateVertexTool(EditingWindow fr)
  {
    super(fr);
    icon = loadImage("createVertex.gif");
    selectedIcon = loadImage("selected/createVertex.gif");
  }

  public void activate()
  {
    super.activate();
    setHelpText();
  }
  
  /**
   * Set the help text based on the current selection mode.
   */
  
  private void setHelpText()
  {
    int mode = ((TriMeshViewer) ((TriMeshEditorWindow) theWindow).getView()).getSelectionMode();
    if (mode == TriMeshViewer.POINT_MODE)
      theWindow.setHelpText(Translate.text("createVertexTool.helpText.point"));
    else if (mode == TriMeshViewer.EDGE_MODE)
      theWindow.setHelpText(Translate.text("createVertexTool.helpText.edge"));
    else
      theWindow.setHelpText(Translate.text("createVertexTool.helpText.face"));
  }

  public int whichClicks()
  {
    return ALL_CLICKS;
  }

  public Image getIcon()
  {
    return icon;
  }

  public Image getSelectedIcon()
  {
    return selectedIcon;
  }

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

  public void mousePressed(WidgetMouseEvent e, ViewerCanvas view)
  {
    TriMeshViewer tmv = (TriMeshViewer) view;
    TriangleMesh mesh = (TriangleMesh) tmv.getObject().object;
    MeshVertex vert[] = mesh.getVertices();
    int mode = tmv.getSelectionMode();
    Camera cam = tmv.getCamera();
    Vec3 uvw = new Vec3();

    clickPoint = e.getPoint();
    target = tmv.findClickTarget(clickPoint, uvw);
    if (target == -1)
      return;
    theWindow.setUndoRecord(new UndoRecord(theWindow, false, UndoRecord.COPY_OBJECT, new Object [] {mesh, mesh.duplicate()}));
    if (mode == TriMeshViewer.POINT_MODE)
    {
      // Create a new vertex if this is a boundary vertex.  Otherwise, just move it.
      
      constrainDir = mesh.getNormals()[target];
      if (createBoundaryVertex(mesh))
      {
        vertexToMove = mesh.getVertices().length-1;
//        tmv.setMesh(mesh);
//        tmv.objectChanged();
//        theWindow.updateImage();
        mesh.informChanged(this, ModelEvent.CHANGEDUR_OBJECTEDITOR);
        theWindow.updateMenus();
      }
      else
        vertexToMove = target;
      clickPos = mesh.getVertices()[vertexToMove].r;
    }
    else
    {
      boolean[] split;
      TriangleMesh newmesh = null;
      if (mode == TriMeshViewer.EDGE_MODE)
      {
        // Subdivide an edge.
        
        TriangleMesh.Edge edge[] = mesh.getEdges();
        Vec3 v1 = vert[edge[target].v1].r;
        Vec3 v2 = vert[edge[target].v2].r;
        clickPos = v1.times(uvw.x).plus(v2.times(uvw.y));
        split = new boolean[edge.length];
        split[target] = true;
        newmesh = TriangleMesh.subdivideLinear(mesh, split);
        constrainDir = v1.minus(v2);
        constrainDir.normalize();
      }
      else if (mode == TriMeshViewer.FACE_MODE)
      {
        // Subdivide a face.
        
        TriangleMesh.Face face[] = mesh.getFaces();
        Vec3 v1 = vert[face[target].v1].r;
        Vec3 v2 = vert[face[target].v2].r;
        Vec3 v3 = vert[face[target].v3].r;
        clickPos = v1.times(uvw.x).plus(v2.times(uvw.y)).plus(v3.times(uvw.z));
        split = new boolean[face.length];
        split[target] = true;
        newmesh = TriangleMesh.subdivideFaces(mesh, split);
        constrainDir = v1.minus(v2).cross(v3.minus(v2));
        constrainDir.normalize();
      }
      vertexToMove = newmesh.getVertices().length-1;
      newmesh.getVertices()[vertexToMove].r.set(clickPos);
      mesh.copyObject(newmesh);
//      tmv.setMesh(mesh);
//      tmv.objectChanged();
//      theWindow.updateImage();
      mesh.informChanged(this, ModelEvent.CHANGEDUR_OBJECTEDITOR);
      theWindow.updateMenus();
    }
  }
  
  public void mouseDragged(WidgetMouseEvent e, ViewerCanvas view)
  {
    if (target == -1)
      return;
    TriMeshViewer tmv = (TriMeshViewer) view;
    TriangleMesh mesh = (TriangleMesh) tmv.getObject().object;
    MeshVertex vert[] = mesh.getVertices();
    Graphics g = view.getComponent().getGraphics();
    Camera cam = view.getCamera();
    Point dragPoint = e.getPoint();
    Vec3 v[], drag;
    Vec2 p;
    int i, dx, dy, size = MeshViewer.HANDLE_SIZE;

    view.drawImage(g);
    dx = dragPoint.x - clickPoint.x;
    dy = dragPoint.y - clickPoint.y;
    drag = findDragVector(dx, dy, tmv, e.isShiftDown());
    v = findDraggedPositions(mesh, drag);
    g.setColor(Color.gray);
    ((MeshViewer) view).drawDraggedSelection(g, cam, v, true);
    theWindow.setHelpText(Translate.text("reshapeMeshTool.dragText",
        Math.round(drag.x*1e5)/1e5+", "+Math.round(drag.y*1e5)/1e5+", "+Math.round(drag.z*1e5)/1e5));
    g.dispose();
  }

  public void mouseReleased(WidgetMouseEvent e, ViewerCanvas view)
  {
    if (target == -1)
      return;
    TriMeshViewer tmv = (TriMeshViewer) view;
    Object3D meshobj = tmv.getObject().object;
    TriangleMesh mesh = (TriangleMesh) meshobj;
    MeshVertex vert[] = mesh.getVertices();
    Camera cam = view.getCamera();
    Point dragPoint = e.getPoint();
    int i, dx, dy;
    Vec3 v[], drag;

    dx = dragPoint.x - clickPoint.x;
    dy = dragPoint.y - clickPoint.y;
    drag = findDragVector(dx, dy, tmv, e.isShiftDown());
    v = findDraggedPositions(mesh, drag);

    mesh.setVertexPositions(v);
    // ((ObjectViewer) view).objectChanged();
    mesh.informChanged(this, ModelEvent.CHANGEDUR_OBJECTEDITOR);
    // theWindow.updateImage();
    setHelpText();
  }
  
  /**
   * Return the new positions of every vertex.
   */
  
  private Vec3 [] findDraggedPositions(TriangleMesh mesh, Vec3 drag)
  {
    MeshVertex vert[] = mesh.getVertices();
    Vec3 pos[] = new Vec3 [vert.length];

    for (int i = 0; i < pos.length; i++)
      pos[i] = vert[i].r;
    pos[vertexToMove] = clickPos.plus(drag);
    return pos;
  }
  
  /**
   * Find the displacement vector for the vertex being dragged.
   */
  
  private Vec3 findDragVector(int dx, int dy, TriMeshViewer view, boolean constrain)
  {
    Camera cam = view.getCamera();
    Vec3 drag = view.getCamera().findDragVector(clickPos, dx, dy);
    if (constrain)
    {
      if (view.getSelectionMode() == TriMeshViewer.EDGE_MODE)
        drag = constrainDir.times(drag.dot(constrainDir));
      else
        drag.subtract(constrainDir.times(drag.dot(constrainDir)));
    }
    return drag;
  }

  /**
   * Try to create a new vertex when a boundary vertex is dragged.  Returns true if a new vertex was
   * created, false otherwise.
   */
  
  private boolean createBoundaryVertex(TriangleMesh mesh)
  {
    TriangleMesh.Vertex vert[] = (TriangleMesh.Vertex []) mesh.getVertices();
    TriangleMesh.Vertex v = (TriangleMesh.Vertex) vert[target];
    TriangleMesh.Edge edge[] = mesh.getEdges();
    TriangleMesh.Face face[] = mesh.getFaces();
    int vertEdge[] = mesh.getEdgesForVertex(v);

    if (edge[vertEdge[0]].f2 != -1)
      return false;
    theWindow.setUndoRecord(new UndoRecord(theWindow, false, UndoRecord.COPY_OBJECT, new Object [] {mesh, mesh.duplicate()}));

    // Create a new vertex and two new faces.
    
    TriangleMesh.Vertex newvert[] = new TriangleMesh.Vertex [vert.length+1];
    int newface[][] = new int [face.length+2][];
    for (int i = 0; i < vert.length; i++)
      newvert[i] = vert[i];
    newvert[vert.length] = new TriangleMesh.Vertex(v);
    for (int i = 0; i < face.length; i++)
      newface[i] = new int [] {face[i].v1, face[i].v2, face[i].v3};
    TriangleMesh.Edge e = edge[vertEdge[0]];
    TriangleMesh.Face f = face[e.f1];
    if ((f.v1 == e.v1 && f.v2 == e.v2) || (f.v2 == e.v1 && f.v3 == e.v2) || (f.v3 == e.v1 && f.v1 == e.v2))
      newface[face.length] = new int [] {e.v2, e.v1, vert.length};
    else
      newface[face.length] = new int [] {e.v1, e.v2, vert.length};
    e = edge[vertEdge[vertEdge.length-1]];
    f = face[e.f1];
    if ((f.v1 == e.v1 && f.v2 == e.v2) || (f.v2 == e.v1 && f.v3 == e.v2) || (f.v3 == e.v1 && f.v1 == e.v2))
      newface[face.length+1] = new int [] {e.v2, e.v1, vert.length};
    else
      newface[face.length+1] = new int [] {e.v1, e.v2, vert.length};
    mesh.setShape(newvert, newface);
    
    // Copy over the edge smoothness values.
    
    TriangleMesh.Edge newedge[] = mesh.getEdges();
    for (int i = 0; i < newedge.length; i++)
      for (int j = 0; j < edge.length; j++)
        if ((newedge[i].v1 == edge[j].v1 && newedge[i].v2 == edge[j].v2) || (newedge[i].v1 == edge[j].v2 && newedge[i].v2 == edge[j].v1))
          newedge[i].smoothness = edge[j].smoothness;
    return true;
  }
}