/* 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.animation.*;
import artofillusion.math.*;
import artofillusion.object.*;
import artofillusion.object.Mesh.*;
import artofillusion.ui.*;
import artofillusion.view.*;
import buoy.event.*;
import buoy.widget.*;
import java.awt.*;


/** The SplineMeshViewer class is a component which displays a SplineMesh object and 
    allow the user to edit it. */

public class SplineMeshViewer extends MeshViewer
{
  boolean visible[], draggingSelectionBox, dragging;
  Point screenVert[];
  double screenZ[];

  public static final int POINT_MODE = 0;
  public static final int CURVE_MODE = 1;

  static final RGBColor surfaceRGB = new RGBColor(0.8f, 0.8f, 1.0f);
  static final Color surfaceColor = new Color(0.8f, 0.8f, 1.0f);

  public SplineMeshViewer(ObjectInfo obj, RowContainer p)
  {
    super(obj, p);
    SplineMesh mesh = (SplineMesh) obj.object;
    visible = new boolean [mesh.getVertices().length];
    ((SplineMesh)obj.getObject3D()).getSkeleton().getModelEvent()
        .addListener(this);
  }

  public void dispose()
  {
    if (theObject != null)
      ((SplineMesh)theObject).getSkeleton().getModelEvent()
          .removeListener(this);
    super.dispose();
  }
  
  protected boolean handleModelEvent(ModelEvent event)
  {
    if (!super.handleModelEvent(event))
    {
      if (event.getSource() == ((SplineMesh)theObject).getSkeleton())
      {
        if (event.hasKey("skeleton changed"))
        {
          if (getSkeletonDetached() && draggingAutosync)  // TODO(MB) Perhaps change event concept to remove this
          {
            updateInternalState();
            if (isVisible())
            {
              updateImage();
              repaint();
            }
          }
        }
        else
          return false;
      }
      else
        return false;
    }
    return true;
  }

  protected void drawObject(Graphics g)
  {
    MeshVertex v[] = ((Mesh) theObject).getVertices();
    Vec2 p[];

    // Calculate the screen coordinates of every vertex.

    screenVert = new Point [v.length];
    screenZ = new double [v.length];
    p = new Vec2 [v.length];
    double clipDist = (theCamera.isPerspective() ? theCamera.getClipDistance() : -Double.MAX_VALUE);
    for (int i = 0; i < v.length; i++)
      {
	p[i] = theCamera.getObjectToScreen().timesXY(v[i].r);
	screenVert[i] = new Point((int) p[i].x, (int) p[i].y);
	screenZ[i] = theCamera.getObjectToView().timesZ(v[i].r);
	visible[i] = (screenZ[i] > clipDist);
      }

    // Draw the object surface.

    drawSurface(g);
    
    // Displace the camera slightly, so edges and points will show through the surface.
    
    Vec3 displace = new Vec3(0.0, 0.0, -0.01);
    theCamera.getViewToWorld().transformDirection(displace);
    getDisplayCoordinates().toLocal().transformDirection(displace);
    Mat4 oldTransform = theCamera.getObjectToWorld();
    theCamera.setObjectTransform(oldTransform.times(Mat4.translation(displace.x, displace.y, displace.z)));
    
    // Draw the points, edges, and skeleton.
    
    Color meshColor, selectedColor;
    if (currentTool instanceof SkeletonTool)
      {
        meshColor = Color.gray;
        selectedColor = new Color(255, 127, 255);
      }
    else
      {
        meshColor = Color.black;
        selectedColor = Color.magenta;
        if (showSkeleton && theObject.getSkeleton() != null)
          theObject.getSkeleton().draw(this, false);
      }
    if (getSelectionMode() == POINT_MODE)
      {
        drawEdges(g, p, Color.gray, Color.gray);
        drawVertices(g, meshColor, currentTool.hilightSelection() ? selectedColor : meshColor);
      }
    else
      drawEdges(g, p, meshColor, currentTool.hilightSelection() ? selectedColor : meshColor);
    if (currentTool instanceof SkeletonTool)
      if (showSkeleton && theObject.getSkeleton() != null)
        theObject.getSkeleton().draw(this, true);
  }

  /** Draw the surface of the object. */

  private void drawSurface(Graphics g)
  {
    if (!showSurface)
      return;
    if (renderMode == RENDER_WIREFRAME)
      {
        g.setColor(surfaceColor);
        Object3D.draw(g, theCamera, objInfo.getWireframePreview(), objInfo.getBounds());
      }
    else if (renderMode == RENDER_TRANSPARENT)
      renderMeshTransparent(objInfo.getPreviewMesh(), theCamera, theCamera.getViewToWorld().timesDirection(Vec3.vz()), surfaceRGB, null);
    else
    {
      RenderingMesh mesh = objInfo.getPreviewMesh();
      Vec3 viewDir = theCamera.getViewToWorld().timesDirection(Vec3.vz());
      VertexShader shader;
      if (renderMode == RENDER_FLAT)
        shader = new FlatVertexShader(mesh, surfaceRGB, viewDir);
      else if (renderMode == RENDER_SMOOTH)
        shader = new SmoothVertexShader(mesh, surfaceRGB, viewDir);
      else
        shader = new TexturedVertexShader(mesh, objInfo.object, 0.0, viewDir).optimize();
      renderMesh(mesh, shader, theCamera, objInfo.object.isClosed(), null);
    }
  }

  /** Draw the vertices of the control mesh. */

  private void drawVertices(Graphics g, Color unselectedColor, Color selectedColor)
  {
    if (!showMesh)
      return;
    MeshVertex v[] = ((Mesh) theObject).getVertices();
    boolean[] selected = getSelection();

    // First, draw any unselected portions of the object.

    if (renderMode == RENDER_WIREFRAME || renderMode == RENDER_TRANSPARENT)
      {
        for (int i = 0; i < v.length; i++)
          if (!selected[i] && visible[i])
            drawBox(screenVert[i].x-HANDLE_SIZE/2, screenVert[i].y-HANDLE_SIZE/2, HANDLE_SIZE, HANDLE_SIZE, unselectedColor);
      }
    else
      {
        for (int i = 0; i < v.length; i++)
          if (!selected[i] && visible[i])
            renderBox(screenVert[i].x-HANDLE_SIZE/2, screenVert[i].y-HANDLE_SIZE/2, HANDLE_SIZE, HANDLE_SIZE, screenZ[i]-0.01, unselectedColor);
      }

    // Now draw the selected portions.

    if (renderMode == RENDER_WIREFRAME || renderMode == RENDER_TRANSPARENT)
      {
        for (int i = 0; i < v.length; i++)
          if (selected[i] && visible[i])
            drawBox(screenVert[i].x-HANDLE_SIZE/2, screenVert[i].y-HANDLE_SIZE/2, HANDLE_SIZE, HANDLE_SIZE, selectedColor);
      }
    else
      {
        for (int i = 0; i < v.length; i++)
          if (selected[i] && visible[i])
            renderBox(screenVert[i].x-HANDLE_SIZE/2, screenVert[i].y-HANDLE_SIZE/2, HANDLE_SIZE, HANDLE_SIZE, screenZ[i]-0.01, selectedColor);
      }
  }

  /** Draw the edges of the control mesh. */

  private void drawEdges(Graphics g, Vec2 p[], Color unselectedColor, Color selectedColor)
  {
    if (!showMesh)
      return;

    // First, draw any unselected portions of the object.

    MeshVertex v[] = ((Mesh) theObject).getVertices();
    int i, j, usize = ((SplineMesh) theObject).getUSize(), vsize = ((SplineMesh) theObject).getVSize();
    boolean uclosed = ((SplineMesh) theObject).isUClosed(), vclosed = ((SplineMesh) theObject).isVClosed();

    int selectMode = getSelectionMode();
    boolean[] selected = getSelection();
    if (renderMode == RENDER_WIREFRAME || renderMode == RENDER_TRANSPARENT)
      {
        if (selectMode == POINT_MODE)
          {
            for (i = 0; i < usize; i++)
              {
                for (j = 0; j < vsize-1; j++)
                  drawLine(screenVert[i+j*usize], screenVert[i+(j+1)*usize], unselectedColor);
                if (vclosed)
                  drawLine(screenVert[i+j*usize], screenVert[i], unselectedColor);
              }
            for (j = 0; j < vsize; j++)
              {
                for (i = 0; i < usize-1; i++)
                  drawLine(screenVert[i+j*usize], screenVert[i+1+j*usize], unselectedColor);
                if (uclosed)
                  drawLine(screenVert[i+j*usize], screenVert[j*usize], unselectedColor);
              }
          }
        else
          {
            for (i = 0; i < usize; i++)
              if (!selected[i])
                {
                  for (j = 0; j < vsize-1; j++)
                    drawLine(screenVert[i+j*usize], screenVert[i+(j+1)*usize], unselectedColor);
                  if (vclosed)
                    drawLine(screenVert[i+j*usize], screenVert[i], unselectedColor);
                }
            for (j = 0; j < vsize; j++)
              if (!selected[j+usize])
                {
                  for (i = 0; i < usize-1; i++)
                    drawLine(screenVert[i+j*usize], screenVert[i+1+j*usize], unselectedColor);
                  if (uclosed)
                    drawLine(screenVert[i+j*usize], screenVert[j*usize], unselectedColor);
                }
          }
      }
    else
      {
        if (selectMode == POINT_MODE)
          {
            for (i = 0; i < usize; i++)
              {
                for (j = 0; j < vsize-1; j++)
                  renderLine(v[i+j*usize].r, v[i+(j+1)*usize].r, theCamera, unselectedColor);
                if (vclosed)
                  renderLine(v[i+j*usize].r, v[i].r, theCamera, unselectedColor);
              }
            for (j = 0; j < vsize; j++)
              {
                for (i = 0; i < usize-1; i++)
                  renderLine(v[i+j*usize].r, v[i+1+j*usize].r, theCamera, unselectedColor);
                if (uclosed)
                  renderLine(v[i+j*usize].r, v[j*usize].r, theCamera, unselectedColor);
              }
          }
        else
          {
            for (i = 0; i < usize; i++)
              if (!selected[i])
                {
                  for (j = 0; j < vsize-1; j++)
                    renderLine(v[i+j*usize].r, v[i+(j+1)*usize].r, theCamera, unselectedColor);
                  if (vclosed)
                    renderLine(v[i+j*usize].r, v[i].r, theCamera, unselectedColor);
                }
            for (j = 0; j < vsize; j++)
              if (!selected[j+usize])
                {
                  for (i = 0; i < usize-1; i++)
                    renderLine(v[i+j*usize].r, v[i+1+j*usize].r, theCamera, unselectedColor);
                  if (uclosed)
                    renderLine(v[i+j*usize].r, v[j*usize].r, theCamera, unselectedColor);
                }
          }
      }

    // Now draw the selected portions.

    if (selectMode == POINT_MODE)
      return;
    if (renderMode == RENDER_WIREFRAME || renderMode == RENDER_TRANSPARENT)
      {
        for (i = 0; i < usize; i++)
          if (selected[i])
            {
              for (j = 0; j < vsize-1; j++)
                drawLine(screenVert[i+j*usize], screenVert[i+(j+1)*usize], selectedColor);
              if (vclosed)
                drawLine(screenVert[i+j*usize], screenVert[i], selectedColor);
            }
        for (j = 0; j < vsize; j++)
          if (selected[j+usize])
            {
              for (i = 0; i < usize-1; i++)
                drawLine(screenVert[i+j*usize], screenVert[i+1+j*usize], selectedColor);
              if (uclosed)
                drawLine(screenVert[i+j*usize], screenVert[j*usize], selectedColor);
            }
      }
    else
      {
        for (i = 0; i < usize; i++)
          if (selected[i])
            {
              for (j = 0; j < vsize-1; j++)
                renderLine(v[i+j*usize].r, v[i+(j+1)*usize].r, theCamera, selectedColor);
              if (vclosed)
                renderLine(v[i+j*usize].r, v[i].r, theCamera, selectedColor);
            }
        for (j = 0; j < vsize; j++)
          if (selected[j+usize])
            {
              for (i = 0; i < usize-1; i++)
                renderLine(v[i+j*usize].r, v[i+1+j*usize].r, theCamera, selectedColor);
              if (uclosed)
                renderLine(v[i+j*usize].r, v[j*usize].r, theCamera, selectedColor);
            }
      }
  }

  /* Draw any parts of the mesh which have been dragged. */

  public void drawDraggedSelection(Graphics g, Camera cam, Vec3[] v, boolean broadcast)
  {
    MeshVertex vert[] = ((SplineMesh) theObject).getVertices();
    int i, j, usize = ((SplineMesh) theObject).getUSize(), vsize = ((SplineMesh) theObject).getVSize();
    boolean uclosed = ((SplineMesh) theObject).isUClosed(), vclosed = ((SplineMesh) theObject).isVClosed();

    if (getSelectionMode() == POINT_MODE)
      for (i = 0; i < vert.length; i++)
        if (vert[i].r != v[i])
          {
            Vec2 p = cam.getObjectToScreen().timesXY(v[i]);
            g.fillRect(((int) p.x) - HANDLE_SIZE/2, ((int) p.y) - HANDLE_SIZE/2, HANDLE_SIZE, HANDLE_SIZE);
          }
    for (i = 0; i < usize; i++)
      {
	for (j = 0; j < vsize-1; j++)
	  if (vert[i+j*usize].r != v[i+j*usize] || vert[i+(j+1)*usize].r != v[i+(j+1)*usize])
	    cam.drawClippedLine(g, v[i+j*usize], v[i+(j+1)*usize]);
	if (vclosed && (vert[i+j*usize].r != v[i+j*usize] || vert[i].r != v[i]))
	  cam.drawClippedLine(g, v[i+j*usize], v[i]);
      }
    for (j = 0; j < vsize; j++)
      {
	for (i = 0; i < usize-1; i++)
	  if (vert[i+j*usize].r != v[i+j*usize] || vert[i+1+j*usize].r != v[i+1+j*usize])
	    cam.drawClippedLine(g, v[i+j*usize], v[i+1+j*usize]);
	if (uclosed && (vert[i+j*usize].r != v[i+j*usize] || vert[j*usize].r != v[j*usize]))
	  cam.drawClippedLine(g, v[i+j*usize], v[j*usize]);
      }
    
    broadcastDraggedSelection(v, broadcast);
  }

  /* When the selection mode changes, do our best to convert the old selection to the
     new mode. */

  public void setSelectionMode(int mode)
  {
    MeshVertex vert[] = ((SplineMesh) theObject).getVertices();
    int i, j, usize = ((SplineMesh) theObject).getUSize(), vsize = ((SplineMesh) theObject).getVSize();
    boolean uclosed = ((SplineMesh) theObject).isUClosed(), vclosed = ((SplineMesh) theObject).isVClosed();
    boolean newSel[];
    
    int selectMode = getSelectionMode();
    boolean[] selected = getSelection();

    if (mode == selectMode)
      return;
    if (mode == POINT_MODE)
      {
	newSel = new boolean [vert.length];
	for (i = 0; i < usize; i++)
	  if (selected[i])
	    for (j = 0; j < vsize; j++)
	      newSel[i+j*usize] = true;
	for (i = 0; i < vsize; i++)
	  if (selected[i+usize])
	    for (j = 0; j < usize; j++)
	      newSel[j+i*usize] = true;
      }
    else
      {
	newSel = new boolean [usize+vsize];
	for (i = 0; i < newSel.length; newSel[i++] = true);
	for (i = 0; i < usize; i++)
	  for (j = 0; j < vsize; j++)
	    if (!selected[i+j*usize])
	      newSel[i] = newSel[j+usize] = false;
      }
    getSelectionHolder().setSelectionMode(mode);
    getSelectionHolder().setSelection(newSel);
    //?? getSelectionHolder().informChanged(this, ModelEvent.CHANGEDUR_OBJECTEDITOR);
  }

  public void setSelection(boolean sel[])
  {
    getSelectionHolder().setSelection(sel);
    findSelectionDistance();
    currentTool.getWindow().updateMenus();
    updateImage();
    repaint();
  }

  /* Calculate the distance (in edges) between each vertex and the nearest selected vertex. */

  protected void findSelectionDistance()
  {
    int i, j, k, usize = ((SplineMesh) theObject).getUSize(), vsize = ((SplineMesh) theObject).getVSize();
    boolean uclosed = ((SplineMesh) theObject).isUClosed(), vclosed = ((SplineMesh) theObject).isVClosed();
    int dist[] = new int [((SplineMesh) theObject).getVertices().length];
    int selectMode = getSelectionMode();
    boolean[] selected = getSelection();

    maxDistance = MeshEditorWindow.getTensionDistance();

    // First, set each distance to 0 or -1, depending on whether that vertex is part of the
    // current selection.

    if (selectMode == POINT_MODE)
      for (i = 0; i < dist.length; i++)
	dist[i] = selected[i] ? 0 : -1;
    else
      {
	for (i = 0; i < usize; i++)
	  for (j = 0; j < vsize; j++)
	    dist[i+j*usize] = (selected[i] || selected[j+usize]) ? 0 : -1;
      }

    // Now extend this outward up to maxDistance.

    for (i = 0; i < maxDistance; i++)
      for (j = 0; j < usize; j++)
	for (k = 0; k < vsize; k++)
	  if (dist[j+k*usize] == -1)
	    {
	      if (j == 0)
		{
		  if (uclosed && dist[usize-1+k*usize] == i)
		    dist[j+k*usize] = i+1;
		}
	      else
	        if (dist[j-1+k*usize] == i)
	          dist[j+k*usize] = i+1;
	      if (j == usize-1)
		{
		  if (uclosed && dist[k*usize] == i)
		    dist[j+k*usize] = i+1;
		}
	      else
	        if (dist[j+1+k*usize] == i)
	          dist[j+k*usize] = i+1;
	      if (k == 0)
		{
		  if (vclosed && dist[j+(vsize-1)*usize] == i)
		    dist[j+k*usize] = i+1;
		}
	      else
	        if (dist[j+(k-1)*usize] == i)
	          dist[j+k*usize] = i+1;
	      if (k == vsize-1)
		{
		  if (vclosed && dist[j] == i)
		    dist[j+k*usize] = i+1;
		}
	      else
	        if (dist[j+(k+1)*usize] == i)
	          dist[j+k*usize] = i+1;
	    }
    selectionDistance = dist;
  }

  public void extendDistance(int dist[], int maxDistance)  //Ignore, does not work
  {
    int i, j, k, usize = ((SplineMesh) theObject).getUSize(), vsize = ((SplineMesh) theObject).getVSize();
    boolean uclosed = ((SplineMesh) theObject).isUClosed(), vclosed = ((SplineMesh) theObject).isVClosed();
    boolean canStop;
    
    for (i = 0; (maxDistance == 0) || (i < maxDistance); ++i)
    {
    // Now extend this outward up to maxDistance.

    for (i = 0; i < maxDistance; i++)
      for (j = 0; j < usize; j++)
	for (k = 0; k < vsize; k++)
	  if (dist[j+k*usize] == -1)
	    {
	      if (j == 0)
		{
		  if (uclosed && dist[usize-1+k*usize] == i)
		    dist[j+k*usize] = i+1;
		}
	      else
	        if (dist[j-1+k*usize] == i)
	          dist[j+k*usize] = i+1;
	      if (j == usize-1)
		{
		  if (uclosed && dist[k*usize] == i)
		    dist[j+k*usize] = i+1;
		}
	      else
	        if (dist[j+1+k*usize] == i)
	          dist[j+k*usize] = i+1;
	      if (k == 0)
		{
		  if (vclosed && dist[j+(vsize-1)*usize] == i)
		    dist[j+k*usize] = i+1;
		}
	      else
	        if (dist[j+(k-1)*usize] == i)
	          dist[j+k*usize] = i+1;
	      if (k == vsize-1)
		{
		  if (vclosed && dist[j] == i)
		    dist[j+k*usize] = i+1;
		}
	      else
	        if (dist[j+(k+1)*usize] == i)
	          dist[j+k*usize] = i+1;
	    }
    }
    selectionDistance = dist;
  }
  
  public void setMesh(Mesh mesh)
  {
    SplineMesh obj = (SplineMesh) mesh;
    setObject(obj);
    int selectMode = getSelectionMode();
//    boolean[] selected = getSelection();
    //System.out.println("SplineMeshViewer setMesh1 "+obj.getVertices().length+" "+visible.length+" "+selectMode+" "+selected.length+" "+obj.getUSize()+" "+obj.getVSize());
   
    int newsellength = -1;
    if (selectMode == POINT_MODE)
      newsellength = obj.getVertices().length;
    else if (selectMode == CURVE_MODE)
      newsellength = obj.getUSize()+obj.getVSize();
      
    getSelectionHolder().setSelection(
        Utilities.arrayEnsureLength(getSelection(), newsellength));
    
    visible = Utilities.arrayEnsureLength(visible, obj.getVertices().length);
//    if (selectMode == POINT_MODE && selected.length != obj.getVertices().length)
//      {
//        getSelectionHolder().setSelection(new boolean [obj.getVertices().length]);
//	visible = new boolean [obj.getVertices().length];
//      }
//    if (selectMode == CURVE_MODE && selected.length != (obj.getUSize()+obj.getVSize()))
//      {
//        System.out.println("SplineMeshViewer setMesh2 "+obj.getVertices().length);
//        getSelectionHolder().setSelection(new boolean [obj.getUSize()+obj.getVSize()]);
//        visible = new boolean [obj.getVertices().length];
//      }
    findSelectionDistance();
    currentTool.getWindow().updateMenus();
  }

  /* When the user presses the mouse, forward events to the current tool as appropriate.
     If this is a vertex based tool, allow them to select or deselect vertices. */

  protected void mousePressed(WidgetMouseEvent e)
  {
    MeshVertex v[] = ((SplineMesh) theObject).getVertices();
    int usize = ((SplineMesh) theObject).getUSize(), vsize = ((SplineMesh) theObject).getVSize();
    int i, j, k, dist, closest;
    Point pos = e.getPoint();
    int selectMode = getSelectionMode();
    boolean[] selected = getSelection();

    requestFocus();
    sentClick = true;
    deselect = -1;
    dragging = false;
    clickPoint = e.getPoint();

    // Determine which tool is active.

    if (metaTool != null && e.isMetaDown())
      activeTool = metaTool;
    else if (altTool != null && e.isAltDown())
      activeTool = altTool;
    else
      activeTool = currentTool;

    // If the current tool wants all clicks, just forward the event and return.

    if (activeTool.whichClicks() == EditingTool.ALL_CLICKS)
      {
	activeTool.mousePressed(e, this);
	dragging = true;
	return;
      }

    // Determine what the click was on.

    i = findClickTarget(e.getPoint());
    
    // If the click was not on an object, start dragging a selection box.

    if (i == -1)
      {
	draggingSelectionBox = true;
	beginDraggingSelection(pos, false);
	sentClick = false;
	return;
      }

    // If we are in curve selection mode, find the nearest vertex of the clicked curve,
    // so that it can be passed to editing tools.

    if (selectMode == CURVE_MODE)
      {
	j = 0;
	closest = Integer.MAX_VALUE;
	if (i < usize)
	  {
	    for (k = 0; k < vsize; k++)
	      {
		dist = Math.abs(pos.x-screenVert[i+usize*k].x) + Math.abs(pos.y-screenVert[i+usize*k].y);
		if (dist < closest)
		  j = i+usize*k;
	      }
	  }
	else
	  {
	    for (k = 0; k < usize; k++)
	      {
		dist = Math.abs(pos.x-screenVert[k+usize*(i-usize)].x) + Math.abs(pos.y-screenVert[k+usize*(i-usize)].y);
		if (dist < closest)
		  j = k+usize*(i-usize);
	      }
	  }
      }
    else
      j = i;

    // If the click was on a selected object, forward it to the current tool.  If it was a
    // shift-click, the user may want to deselect it, so set a flag.

    if (selected[i])
      {
	if (e.isShiftDown())
	  deselect = i;
	activeTool.mousePressedOnHandle(e, this, 0, j);
	return;
      }

    // The click was on an unselected object.  Select it and send an event to the current tool.

    if (!e.isShiftDown())
      for (k = 0; k < selected.length; k++)
	selected[k] = false;
    selected[i] = true;
    getSelectionHolder().informChanged(this, ModelEvent.CHANGEDUR_OBJECTEDITOR);
    findSelectionDistance();
    currentTool.getWindow().updateMenus();
    if (e.isShiftDown())
      {
	sentClick = false;
	updateImage();
	repaint();
      }
    else
      activeTool.mousePressedOnHandle(e, this, 0, j);
  }

  protected void mouseDragged(WidgetMouseEvent e)
  {
    if (!dragging)
      {
	Point p = e.getPoint();
	if (Math.abs(p.x-clickPoint.x) < 2 && Math.abs(p.y-clickPoint.y) < 2)
	  return;
      }
    dragging = true;
    deselect = -1;
    super.mouseDragged(e);
  }

  protected void mouseReleased(WidgetMouseEvent e)
  {
    int usize = ((SplineMesh) theObject).getUSize(), vsize = ((SplineMesh) theObject).getVSize();
    Rectangle r;
    int i, j, x, y;
    Vec2 pos;
    int selectMode = getSelectionMode();
    boolean[] selected = getSelection();

    moveToGrid(e);
    endDraggingSelection();
    if (draggingSelectionBox && !e.isShiftDown() && !e.isControlDown())
      for (i = 0; i < selected.length; i++)
        selected[i] = false;

    // If the user was dragging a selection box, then select or deselect anything
    // it intersects.

    if (selectBounds != null)
      {
	boolean newsel = !e.isControlDown();
	if (selectMode == POINT_MODE)
	  {
	    for (i = 0; i < selected.length; i++)
	      if (selectionRegionContains(screenVert[i]))
		selected[i] = newsel;
	  }
	else
	  {
	    for (i = 0; i < usize; i++)
	      {
		for (j = 0; j < vsize && selectionRegionContains(screenVert[i+j*usize]); j++);
		if (j == vsize)
		  selected[i] = newsel;
	      }
	    for (i = 0; i < vsize; i++)
	      {
		for (j = 0; j < usize && selectionRegionContains(screenVert[j+i*usize]); j++);
		if (j == usize)
		  selected[i+usize] = newsel;
	      }
	  }
      }
    draggingBox = draggingSelectionBox = false;

    // Send the event to the current tool, if appropriate.

    if (sentClick)
      {
	if (!dragging)
	  {
	    Point p = e.getPoint();
	    e.translatePoint(clickPoint.x-p.x, clickPoint.y-p.y);
	  }
	activeTool.mouseReleased(e, this);
      }

    // If the user shift-clicked a selected point and released the mouse without dragging,
    // then deselect the point.

    if (deselect > -1)
      selected[deselect] = false;
    getSelectionHolder().informChanged(this, ModelEvent.CHANGEDUR_OBJECTEDITOR);
    findSelectionDistance();
    currentTool.getWindow().updateMenus();
    //updateImage();
    //repaint();
  }

  /* Determine which vertex or curve (depending on the current selection mode) the
     mouse was clicked on.  If the click was on top of multiple objects, priority is given
     to ones which are currently selected, and then to ones which are in front.  If the
     click is not over any object, -1 is returned. */

  int findClickTarget(Point pos)
  {
    MeshVertex vt[] = ((SplineMesh) theObject).getVertices();
    int selectMode = getSelectionMode();
    boolean[] selected = getSelection();
    double u, v, w, z, closestz = Double.MAX_VALUE;
    boolean sel = false;
    Point v1, v2, v3;
    int i, j, which = -1;
    boolean uclosed = ((SplineMesh) theObject).isUClosed(), vclosed = ((SplineMesh) theObject).isVClosed();
    int usize = ((SplineMesh) theObject).getUSize(), vsize = ((SplineMesh) theObject).getVSize();

    if (selectMode == POINT_MODE)
      {
	for (i = 0; i < vt.length; i++)
	  {
	    if (!visible[i])
	      continue;
	    if (sel && !selected[i])
	      continue;
	    v1 = screenVert[i];
	    if (pos.x < v1.x-HANDLE_SIZE/2 || pos.x > v1.x+HANDLE_SIZE/2 ||
		pos.y < v1.y-HANDLE_SIZE/2 || pos.y > v1.y+HANDLE_SIZE/2)
	      continue;
	    z = theCamera.getObjectToView().timesZ(vt[i].r);
	    if ( (!sel && selected[i]) || z < closestz)
	      {
		which = i;
		closestz = z;
		sel = selected[i];
	      }
	  }
      }
    else
      {
	for (i = 0; i < usize; i++)
	  {
	    if (sel && !selected[i])
	      continue;
	    for (j = 1; j < vsize; j++)
	      {
		z = lineClickDepth(pos, vt, i+(j-1)*usize, i+j*usize);
                if (z == Double.MAX_VALUE)
                  continue;
                if ( (!sel && selected[i]) || z < closestz)
		  {
		    which = i;
		    closestz = z;
		    sel = selected[i];
		  }
	      }
	    if (vclosed)
	      {
		z = lineClickDepth(pos, vt, i+(j-1)*usize, i);
                if (z == Double.MAX_VALUE)
                  continue;
		if ( (!sel && selected[i]) || z < closestz)
		  {
		    which = i;
		    closestz = z;
		    sel = selected[i];
		  }
	      }
	  }
	for (i = 0; i < vsize; i++)
	  {
	    if (sel && !selected[i+usize])
	      continue;
	    for (j = 1; j < usize; j++)
	      {
		z = lineClickDepth(pos, vt, j-1+i*usize, j+i*usize);
                if (z == Double.MAX_VALUE)
                  continue;
		if ( (!sel && selected[i+usize]) || z < closestz)
		  {
		    which = i+usize;
		    closestz = z;
		    sel = selected[i+usize];
		  }
	      }
	    if (vclosed)
	      {
		z = lineClickDepth(pos, vt, j-1+i*usize, i*usize);
                if (z == Double.MAX_VALUE)
                  continue;
		if ( (!sel && selected[i+usize]) || z < closestz)
		  {
		    which = i+usize;
		    closestz = z;
		    sel = selected[i+usize];
		  }
	      }
	  }
      }
    return which;
  }

  /* Given a click position and the endpoints of a line, this method determines whether the
     click was on top of the line.  If so, it returns the z coordinate of the line at the
     point where it was clicked.  Otherwise, it returns Double.MAX_VALUE. */

  private double lineClickDepth(Point pos, MeshVertex vt[], int p1, int p2)
  {
    Point v1, v2;
    double u, v, w;

    if (!visible[p1] || !visible[p2])
      return Double.MAX_VALUE;
    v1 = screenVert[p1];
    v2 = screenVert[p2];
    if ((pos.x < v1.x-HANDLE_SIZE/2 && pos.x < v2.x-HANDLE_SIZE/2) ||
    	(pos.x > v1.x+HANDLE_SIZE/2 && pos.x > v2.x+HANDLE_SIZE/2) ||
    	(pos.y < v1.y-HANDLE_SIZE/2 && pos.y < v2.y-HANDLE_SIZE/2) ||
    	(pos.y > v1.y+HANDLE_SIZE/2 && pos.y > v2.y+HANDLE_SIZE/2))
      return Double.MAX_VALUE;

    // Determine the distance of the click point from the line.

    if (Math.abs(v1.x-v2.x) > Math.abs(v1.y-v2.y))
      {
	if (v2.x > v1.x)
	  {
	    v = ((double) pos.x-v1.x)/(v2.x-v1.x);
	    u = 1.0-v;
	  }
	else
	  {
	    u = ((double) pos.x-v2.x)/(v1.x-v2.x);
	    v = 1.0-u;
	  }
	w = u*v1.y + v*v2.y - pos.y;
      }
    else
      {
	if (v2.y > v1.y)
	  {
	    v = ((double) pos.y-v1.y)/(v2.y-v1.y);
	    u = 1.0-v;
	  }
	else
	  {
	    u = ((double) pos.y-v2.y)/(v1.y-v2.y);
	    v = 1.0-u;
	  }
	w = u*v1.x + v*v2.x - pos.x;
      }
    if (Math.abs(w) > HANDLE_SIZE/2)
      return Double.MAX_VALUE;
    return u*theCamera.getObjectToView().timesZ(vt[p1].r) + v*theCamera.getObjectToView().timesZ(vt[p2].r);
  }
}
