/* 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.texture.*;
import artofillusion.ui.*;
import buoy.event.*;
import buoy.widget.*;
import java.awt.*;
import java.util.Vector;

/** The SplineMeshEditorWindow class represents the window for editing SplineMesh objects. */
public class SplineMeshEditorWindow extends MeshEditorWindow
{
  ToolPalette modes;
  boolean topology;

  public SplineMeshEditorWindow(EditingWindow parent, String title, ObjectInfo obj, Runnable onClose, boolean allowTopology)
  {
    super(parent, title, obj, onClose);
    topology = allowTopology;
    
    //TODO(MB) Remove this menu creation
    
    MenuStructure ms = new MenuStructure();
    ms.loadStructure("SplineMesh", false);
    menubar = ms.createMenuBar(this);
    setMenuBar(menubar);
    menuitemsCache = null;  // The menubar was rebuild so invalidate cache
   
    //menubar = new MenuBar();
    //setMenuBar(menubar);
//    createEditMenu();
//    createMeshMenu((SplineMesh) obj.object);
//    createSkeletonMenu((SplineMesh) obj.object);
//    createViewMenu();
    
    UIUtilities.recursivelyAddKeyPressedListeners(this);
    Dimension d1 = Toolkit.getDefaultToolkit().getScreenSize(), d2;
    d2 = new Dimension((d1.width*3)/4, (d1.height*3)/4);
    setBounds(new Rectangle((d1.width-d2.width)/2, (d1.height-d2.height)/2, d2.width, d2.height));
    updateMenus();
  }

//  void createEditMenu()
//  {
    BMenuItem item;
//
//    editMenu = Translate.menu("edit");
//    menubar.add(editMenu);
//    editMenuItem = new MenuItem [2];
//    editMenu.add(Translate.menuItem("undo", this));
//    editMenu.add(Translate.menuItem("selectAll", this));
//    editMenu.add(editMenuItem[0] = Translate.menuItem("extendSelection", this));
//    editMenu.add(editMenuItem[1] = Translate.checkboxMenuItem("freehandSelection", this, false));
//    editMenu.addSeparator();
//    editMenu.add(Translate.menuItem("meshTension", this));
//  }
//
//  void createMeshMenu(SplineMesh obj)
//  {
//    MenuItem item;
//    Menu smoothMenu, closedMenu;
//
//    meshMenu = Translate.menu("mesh");
//    menubar.add(meshMenu);
//    meshMenuItem = new MenuItem [8];
//    meshMenuItem[0] = Translate.menuItem("deleteCurves", this);
//    if (topology)
//      meshMenu.add(meshMenuItem[0]);
//    meshMenuItem[1] = Translate.menuItem("subdivide", this);
//    if (topology)
//      meshMenu.add(meshMenuItem[1]);
//    meshMenu.add(meshMenuItem[2] = Translate.menuItem("editPoints", this));
//    meshMenu.add(meshMenuItem[3] = Translate.menuItem("transformPoints", this));
//    meshMenu.add(meshMenuItem[4] = Translate.menuItem("randomize", this));
//    meshMenu.add(meshMenuItem[5] = Translate.menuItem("parameters", this));
//    meshMenu.add(Translate.menuItem("centerMesh", this));
//    meshMenu.add(meshMenuItem[6] = Translate.menuItem("extractCurve", this));
//    meshMenu.addSeparator();
//    meshMenu.add(meshMenuItem[7] = Translate.menuItem("smoothness", this));
//    meshMenu.add(smoothMenu = Translate.menu("smoothingMethod"));
//    smoothItem = new CheckboxMenuItem [2];
//    smoothMenu.add(smoothItem[0] = Translate.checkboxMenuItem("interpolating", this, obj.getSmoothingMethod() == TriangleMesh.INTERPOLATING));
//    smoothMenu.add(smoothItem[1] = Translate.checkboxMenuItem("approximating", this, obj.getSmoothingMethod() == TriangleMesh.APPROXIMATING));
//    closedMenu = Translate.menu("closed");
//    if (topology)
//      meshMenu.add(closedMenu);
//    closedItem = new CheckboxMenuItem [4];
//    closedMenu.add(closedItem[0] = Translate.checkboxMenuItem("udirection", this, (obj.isUClosed() && !obj.isVClosed())));
//    closedMenu.add(closedItem[1] = Translate.checkboxMenuItem("vdirection", this, (!obj.isUClosed() && obj.isVClosed())));
//    closedMenu.add(closedItem[2] = Translate.checkboxMenuItem("both", this, (obj.isUClosed() && obj.isVClosed())));
//    closedMenu.add(closedItem[3] = Translate.checkboxMenuItem("neither", this, (!obj.isUClosed() && !obj.isVClosed())));
//    if (topology)
//      meshMenu.add(item = Translate.menuItem("invertNormals", this));
//    if (ModellingApp.getPreferences().getObjectPreviewRenderer() != null)
//      {
//	meshMenu.addSeparator();
//	meshMenu.add(Translate.menuItem("renderPreview", this));
//      }
//  }
//
//  void createSkeletonMenu(SplineMesh obj)
//  {
//    MenuItem item;
//
//    skeletonMenu = Translate.menu("skeleton");
//    menubar.add(skeletonMenu);
//    skeletonMenuItem = new MenuItem [6];
//    skeletonMenu.add(skeletonMenuItem[0] = Translate.menuItem("editBone", this));
//    skeletonMenu.add(skeletonMenuItem[1] = Translate.menuItem("deleteBone", this));
//    skeletonMenu.add(skeletonMenuItem[2] = Translate.menuItem("setParentBone", this));
//    skeletonMenu.add(skeletonMenuItem[3] = Translate.menuItem("importSkeleton", this));
//    skeletonMenu.addSeparator();
//    skeletonMenu.add(skeletonMenuItem[4] = Translate.menuItem("bindSkeleton", this));
//    skeletonMenu.add(skeletonMenuItem[5] = Translate.checkboxMenuItem("detachSkeleton", this, false));
//  }

  /** List of names of menu items which are only allowed if {@link #topology} is true.
    Used by {@link #isMenuItemAllowed()}.*/

  final String[] MENUITEMS_ALLOWED_ON_TOPOLOGY=new String[]
    { "clear", "subdivideEdges", "simplify", "bevel", "optimize",
      "closeBoundary", "joinBoundaries", "invertNormals", "createFace"
    };

  /** List of names of menu items which should be CheckboxMenuItem.
    Used by {@link #isMenuItemNameCheckbox()}.*/

  final String[] MENUITEMS_CHECKBOXITEMS=new String[]
    { "freehandSelection", "interpolating", "approximating", "udirection",
      "vdirection", "both", "neither", "detachSkeleton"      
    };
  
  /** Tests if menu item item should be shown in the menus for
    this object editor. */
  public boolean isMenuItemAllowed(MenuWidget item)
  {
    String itemname = ((Widget)item).getName();
    if (Utilities.findIndexEqual(MENUITEMS_ALLOWED_ON_TOPOLOGY, itemname) > -1)
      return topology;   // Some items are only allowed if topology is true
    else if ("renderPreview".equals(itemname))
      return ModellingApp.getPreferences().getObjectPreviewRenderer() != null;
    else
      return super.isMenuItemAllowed(item);
  }

  /** Tests if menu item named itemname should be a checkbox item. */
  protected boolean isMenuItemNameCheckbox(String itemname)
  {
    if (Utilities.findIndexEqual(MENUITEMS_CHECKBOXITEMS, itemname) > -1)
      return true;
    else
      return super.isMenuItemNameCheckbox(itemname);
  }
  
  /* EditingWindow methods. */

  public void setTool(EditingTool tool)
  {
    for(int i = 0; i < theView.length; ++i)
    {
      if (tool instanceof GenericTool)
      {
        ((SplineMeshViewer) theView[i]).setSelectionMode(modes.getSelection()); //TODO(MB) current or theView[i] ?
        theView[i].getCurrentTool().activate();  // From original AoI
        ((SplineMeshViewer) theView[i]).informSelectionChanged(this,
        ModelEvent.CHANGEDUR_OBJECTEDITOR);
        updateMenus();
      }
      else
      {
        theView[i].setTool(tool);
        currentTool = tool;
      }
    }
  }

  /** Returns true if the view has at least one selected
    element. */
  public boolean isSomethingSelected()
  {
    int i;
    boolean selected[] = ((SplineMeshViewer) getCurrentView()).getSelection();

    for (i = 0; i < selected.length && !selected[i]; i++);

    return i < selected.length;
  }
  
  /** Updates a single menu item
    (setting enabled/disabled, (un)checked ...). */
  protected void updateMenuItem(MenuWidget item)
  {
    String itemname = ((Widget)item).getName();
    BCheckBoxMenuItem cbitem = null;
    BMenuItem mnitem = null;
    if (item instanceof BCheckBoxMenuItem)
      cbitem = (BCheckBoxMenuItem)item;
    else if (item instanceof BMenuItem)
      mnitem = (BMenuItem)item;
    else
      return;
    
    boolean curvemode = ((SplineMeshViewer) getCurrentView()).getSelectionMode()
        == SplineMeshViewer.CURVE_MODE;
    SplineMesh obj = (SplineMesh) getCurrentView().getObject().object;

    // Edit Menu

    if ("clear".equals(itemname))
      mnitem.setEnabled(isSomethingSelected());
    else if ("extendSelection".equals(itemname))
      mnitem.setEnabled(isSomethingSelected());

    // Mesh Menu

    else if ("deleteCurves".equals(itemname))
      mnitem.setEnabled(isSomethingSelected() && curvemode);
    else if ("subdivide".equals(itemname))
      mnitem.setEnabled(isSomethingSelected() && curvemode);
    else if ("editPoints".equals(itemname))
      mnitem.setEnabled(isSomethingSelected());
    else if ("transformPoints".equals(itemname))
      mnitem.setEnabled(isSomethingSelected());
    else if ("randomize".equals(itemname))
      mnitem.setEnabled(isSomethingSelected());
    else if ("parameters".equals(itemname))
      mnitem.setEnabled(isSomethingSelected());
    else if ("extractCurve".equals(itemname))
    {
      int count = 0;
      boolean selected[] = ((SplineMeshViewer) getCurrentView()).getSelection();

      for (int i = 0; i < selected.length; i++)
        if (selected[i])
          count++;

      mnitem.setEnabled(curvemode && count == 1);
    }
    else if ("smoothness".equals(itemname))
      mnitem.setEnabled(isSomethingSelected() && curvemode);

    // Smooth Menu in Mesh Menu

    else if ("interpolating".equals(itemname))
      cbitem.setState(obj.getSmoothingMethod() == TriangleMesh.INTERPOLATING);
    else if ("approximating".equals(itemname))
      cbitem.setState(obj.getSmoothingMethod() == TriangleMesh.APPROXIMATING);
    
    // Closed Menu in Mesh Menu
    else if ("udirection".equals(itemname))
      cbitem.setState(obj.isUClosed() && !obj.isVClosed());
    else if ("vdirection".equals(itemname))
      cbitem.setState(!obj.isUClosed() && obj.isVClosed());
    else if ("both".equals(itemname))
      cbitem.setState(obj.isUClosed() && obj.isVClosed());
    else if ("neither".equals(itemname))
      cbitem.setState(!obj.isUClosed() && !obj.isVClosed());

    // Skeleton Menu

    else if ("editBone".equals(itemname))
    {
      Skeleton s = obj.getSkeleton();
      Joint selJoint = s.getJoint(getCurrentView().getSelectedJoint());

      mnitem.setEnabled(selJoint != null);
    }
    else if ("deleteBone".equals(itemname))
    {
      Skeleton s = obj.getSkeleton();
      Joint selJoint = s.getJoint(getCurrentView().getSelectedJoint());

      mnitem.setEnabled(selJoint != null && selJoint.children.length == 0);
    }
    else if ("bindSkeleton".equals(itemname))
      mnitem.setEnabled(isSomethingSelected());
    else if ("setParentBone".equals(itemname))
    {
      Skeleton s = obj.getSkeleton();
      Joint selJoint = s.getJoint(getCurrentView().getSelectedJoint());

      mnitem.setEnabled(selJoint != null);
    }
    else super.updateMenuItem(item);

  }

  protected void keyPressed(KeyPressedEvent e)
  {
    int code = e.getKeyCode();

    if (code == KeyPressedEvent.VK_BACK_SPACE || code == KeyPressedEvent.VK_DELETE)
    {
      if (getCurrentView().getCurrentTool() instanceof SkeletonTool)
        deleteJointCommand();
      else
        deleteCommand();
    }
    else
      super.keyPressed(e);
  }
  
  public void actionPerformed(CommandEvent e)
  {
    String command = e.getActionCommand();

    setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
    if (handleMenuAction(command)) // Try to call the command by reflection
      ;  // Do nothing
//    else if (command.equals("undo"))
//      undoCommand();
//    else if (command.equals("selectAll"))
//      selectAllCommand();
//    else if (command.equals("extendSelection"))
//      extendSelectionCommand();
    else if (command.equals("clear"))
      deleteCommand();
//    else if (command.equals("subdivide"))
//      subdivideCommand();
    else if (command.equals("editPoints"))
      setPointsCommand();
//    else if (command.equals("transformPoints"))
//      transformPointsCommand();
//    else if (command.equals("randomize"))
//      randomizeCommand();
    else if (command.equals("parameters"))
      setParametersCommand();
    else if (command.equals("centerMesh"))
      centerCommand();
//    else if (command.equals("extractCurve"))
//      extractCurveCommand();
    else if (command.equals("smoothness"))
      setSmoothnessCommand();
    else if (command.equals("invertNormals"))
      reverseNormalsCommand();
    else if (command.equals("meshTension"))
      setTensionCommand();
    else if (command.equals("renderPreview"))
      getCurrentView().previewObject();
    else if (command.equals("editBone"))
      editJointCommand();
    else if (command.equals("deleteBone"))
      deleteJointCommand();
    else if (command.equals("setParentBone"))
      setJointParentCommand();
//    else if (command.equals("bindSkeleton"))
//      bindSkeletonCommand();
//    else if (command.equals("importSkeleton"))
//      importSkeletonCommand();
    else if (command.equals("grid"))
      setGridCommand();
    else if (command.equals("fourViews"))
      toggleViewsCommand();
    else if (command.equals("showTemplate"))
      {
        boolean wasShown = getCurrentView().getTemplateShown();
        for(int i = 0; i < theView.length; ++i)
          theView[i].setShowTemplate(!wasShown);
        updateMenus();
        updateImage();
      }
    else if (command.equals("setTemplate"))
      setTemplateCommand();
    setCursor(Cursor.getDefaultCursor());
  }

  public void itemStateChanged(CommandEvent e)
  {
    Object source = e.getSource();
    BCheckBoxMenuItem sourceitem = null;
    String itemname = "";
    int i;
    if (source instanceof BCheckBoxMenuItem)
    {
      sourceitem = (BCheckBoxMenuItem)source;
      itemname = sourceitem.getName();
    }

    if ("freehandSelection".equals(itemname))
      {
	((SplineMeshViewer) getCurrentView()).setFreehandSelection(sourceitem.getState());
	return;
      }
    if ("detachSkeleton".equals(itemname))
      {
	((SplineMeshViewer) getCurrentView()).setSkeletonDetached(sourceitem.getState());
	return;
      }
    
    final String[] SMOOTHMETHODS= new String[]{"interpolating", "approximating"};
    final String[] CLOSEDTYPES= new String[]{"udirection", "vdirection",
                                             "both", "neither"};

    i = Utilities.findIndexEqual(SMOOTHMETHODS, itemname);
    if (i > -1)
    {
        setSmoothingMethod(i+2);
      return;
  }

    i = Utilities.findIndexEqual(CLOSEDTYPES, itemname);
    if (i > -1)
  {
        setClosed(i);
      return;
  }
  
    super.itemStateChanged(e);
  }
    

  /** Creates a new view, containing the same Object3D with a
   * new VertexSelection.
   */
  protected MeshEditorWindow createNewViewNewSelection()
  {
    SplineMeshEditorWindow w = new SplineMeshEditorWindow(parentWindow, getTitle(), objInConstructor, onClose, topology);
    // w.theView.setObject(theView.getObject().getObject3D());  TODO(MB) ??? Something with editGesture ???
    return w;
  }

  /** Create a MeshViewer which is appropriate for a particular editor window.
    Here a SplineMeshViewer.*/
  
  public MeshViewer createMeshViewer(ObjectInfo obj, RowContainer p)
  {
    return new SplineMeshViewer(obj, p);
  }
  
  public void initBaseSelectionHolder(ObjectInfo obj)
  {
    baseSelHolder = new MeshSelectionHolder(obj);
    SplineMesh mesh = (SplineMesh) obj.object;
    baseSelHolder.setSelection(new boolean [mesh.getVertices().length]);
    baseSelHolder.setSelectionMode(SplineMeshViewer.POINT_MODE);
    baseSelHolder.decRefCount();  // Start with ref count 0.
    
    for (int i = 0; i < theView.length; ++i)
      ((SplineMeshViewer) theView[i]).setSelectionHolder(baseSelHolder);
  }
  
//  public void initWindowMenus(ObjectInfo obj)  // TODO(MB) Replace with dynamic menus
//  {  
//    // dummy
//  }

    
  /** Create the toolbar panel on the left of an object editor. */
  
  public BorderContainer createToolbarPanel()
  {
    BorderContainer p4 = new BorderContainer();

    p4.add(tools = new ToolPalette(1, 9), BorderContainer.NORTH);
    tools.addTool(defaultTool = new ReshapeMeshTool(this));
    tools.addTool(new ScaleMeshTool(this));
    tools.addTool(new RotateMeshTool(this, false));
    tools.addTool(new SkewMeshTool(this));
    tools.addTool(new TaperMeshTool(this));
    tools.addTool(new ThickenMeshTool(this));
    tools.addTool(new SkeletonTool(this, true));
    tools.addTool(getMetaTool() /* MoveViewTool */);
    tools.addTool(getAltTool() /* RotateViewTool */);
    tools.selectTool(defaultTool);
    p4.add(modes = new ToolPalette(1, 2), BorderContainer.SOUTH);
    modes.addTool(new GenericTool(this, "point.gif", "selected/point.gif"));
    modes.addTool(new GenericTool(this, "curve.gif", "selected/curve.gif"));

    return p4;
  }

  public void selectAllCommand()
  {
    boolean selected[] = ((SplineMeshViewer) getCurrentView()).getSelection();

    for (int i = 0; i < selected.length; i++)
      selected[i] = true;
    ((SplineMeshViewer) getCurrentView()).setSelection(selected);
    ((SplineMeshViewer) getCurrentView()).informSelectionChanged(this,
        ModelEvent.CHANGEDUR_OBJECTEDITOR);
  }

  /* Extend the selection outward by one edge. */

  public void extendSelectionCommand()
  {
    SplineMesh theMesh = (SplineMesh) getCurrentView().getObject().object;

    if (((SplineMeshViewer) getCurrentView()).getSelectionMode() == SplineMeshViewer.POINT_MODE)
    {
      int oldDist = tensionDistance;
      tensionDistance = 1;
      int dist[] = ((SplineMeshViewer) getCurrentView()).getSelectionDistance();
      boolean selected[] = new boolean [dist.length];
      tensionDistance = oldDist;
      for (int i = 0; i < dist.length; i++)
        selected[i] = (dist[i] == 0 || dist[i] == 1);
      ((SplineMeshViewer) getCurrentView()).setSelection(selected);
      ((SplineMeshViewer) getCurrentView()).informSelectionChanged(this,
          ModelEvent.CHANGEDUR_OBJECTEDITOR);
    }
    else
    {
      boolean oldSelection[] = ((SplineMeshViewer) getCurrentView()).getSelection();
      boolean selected[] = new boolean [oldSelection.length];
      int usize = theMesh.getUSize(), vsize = theMesh.getVSize();
      for (int i = 0; i < usize-1; i++)
        if (oldSelection[i] || oldSelection[i+1])
          selected[i] = selected[i+1] = true;
      if (theMesh.isUClosed() && (oldSelection[0] || oldSelection[usize-1]))
        selected[0] = selected[usize-1] = true;
      for (int i = 0; i < vsize-1; i++)
        if (oldSelection[usize+i] || oldSelection[usize+i+1])
          selected[usize+i] = selected[usize+i+1] = true;
      if (theMesh.isVClosed() && (oldSelection[usize] || oldSelection[usize+vsize-1]))
        selected[usize] = selected[usize+vsize-1] = true;
      ((SplineMeshViewer) getCurrentView()).setSelection(selected);
        ((SplineMeshViewer) getCurrentView()).informSelectionChanged(this,
            ModelEvent.CHANGEDUR_OBJECTEDITOR);
    }
  }

  public void deleteCommand()
  {
    int i, j, k, m, unum = 0, vnum = 0;
    SplineMesh theMesh = (SplineMesh) getCurrentView().getObject().object;
    float us[] = theMesh.getUSmoothness(), vs[] = theMesh.getVSmoothness(), newus[], newvs[];
    boolean selected[] = ((SplineMeshViewer) getCurrentView()).getSelection();
    MeshVertex vt[] = theMesh.getVertices(), v[][];
    int usize = theMesh.getUSize(), vsize = theMesh.getVSize();

    if (((SplineMeshViewer) getCurrentView()).getSelectionMode() != SplineMeshViewer.CURVE_MODE)
      return;
    for (i = 0; i < usize; i++)
      if (selected[i])
	unum++;
    for (i = 0; i < vsize; i++)
      if (selected[i+usize])
	vnum++;
    if (unum == 0 && vnum == 0)
      return;
    if (usize-unum < 2 || vsize-vnum < 2)
    {
      new BStandardDialog("", Translate.text("curveNeeds2Points"), BStandardDialog.INFORMATION).showMessageDialog(this);
      return;
    }
    if ((theMesh.isUClosed() && usize-unum < 3) || (theMesh.isVClosed() && vsize-vnum < 3))
    {
      new BStandardDialog("", Translate.text("curveNeeds3Points"), BStandardDialog.INFORMATION).showMessageDialog(this);
      return;
    }
    setUndoRecord(new UndoRecord(this, false, UndoRecord.COPY_OBJECT, new Object [] {theMesh, theMesh.duplicate()}));
    v = new MeshVertex [usize-unum][vsize-vnum];
    newus = new float [usize-unum];
    newvs = new float [vsize-vnum];
    for (i = 0, j = 0; i < usize; i++)
    {
      if (!selected[i])
      {
        for (k = 0, m = 0; k < vsize; k++)
        {
          if (!selected[k+usize])
          {
            newvs[m] = vs[k];
            v[j][m++] = vt[i+k*usize];
          }
        }
        newus[j++] = us[i];
      }
    }
    theMesh.setShape(v, newus, newvs);
    object3DChangedDuringEditor();
  }

  public void subdivideCommand()
  {
    SplineMesh theMesh = (SplineMesh) getCurrentView().getObject().object;
    MeshVertex vt[] = theMesh.getVertices();
    float us[] = theMesh.getUSmoothness(), vs[] = theMesh.getVSmoothness(), newus[], newvs[];
    boolean selected[] = ((SplineMeshViewer) getCurrentView()).getSelection(), newsel[], splitu[], splitv[];
    MeshVertex v[][], newv[][];
    double param[][][], newparam[][][];
    int i, j, usplitcount = 0, vsplitcount = 0;
    int method = theMesh.getSmoothingMethod(), usize = theMesh.getUSize(), vsize = theMesh.getVSize();
    int numParam = (theMesh.getParameters() == null ? 0 : theMesh.getParameters().length);

    if (((SplineMeshViewer) getCurrentView()).getSelectionMode() != SplineMeshViewer.CURVE_MODE)
      return;
    for (i = 0; !selected[i] && i < selected.length; i++);
    if (i == selected.length)
      return;

    // Determine which parts need to be subdivided.

    if (theMesh.isUClosed())
      splitu = new boolean [usize];
    else
      splitu = new boolean [usize-1];
    for (i = 0; i < splitu.length; i++)
      if (selected[i] && selected[(i+1)%usize])
      {
        splitu[i] = true;
        usplitcount++;
      }
    if (theMesh.isVClosed())
      splitv = new boolean [vsize];
    else
      splitv = new boolean [vsize-1];
    for (i = 0; i < splitv.length; i++)
      if (selected[i+usize] && selected[(i+1)%vsize+usize])
      {
        splitv[i] = true;
        vsplitcount++;
      }
    newus = new float [usize+usplitcount];
    newvs = new float [vsize+vsplitcount];
    newsel = new boolean [selected.length+usplitcount+vsplitcount];

    // Do the subdivision along the u direction.

    v = new MeshVertex [vsize][usize];
    for (i = 0; i < usize; i++)
      for (j = 0; j < vsize; j++)
	v[j][i] = vt[i+j*usize];
    newv = new MeshVertex [vsize][usize+usplitcount];
    param = new double [vsize][usize][numParam];
    for (int k = 0; k < numParam; k++)
      if (theMesh.getParameterValues()[k] instanceof VertexParameterValue)
      {
        double val[] = ((VertexParameterValue) theMesh.getParameterValues()[k]).getValue();
        for (i = 0; i < usize; i++)
          for (j = 0; j < vsize; j++)
            param[j][i][k] = val[i+usize*j];
      }
    newparam = new double [vsize][usize+usplitcount][numParam];
    splitOneAxis(v, newv, us, newus, splitu, param, newparam, theMesh.isUClosed());

    // Do the subdivision along the v direction.

    v = new MeshVertex [usize+usplitcount][vsize];
    for (i = 0; i < v.length; i++)
      for (j = 0; j < v[i].length; j++)
	v[i][j] = newv[j][i];
    newv = new MeshVertex [usize+usplitcount][vsize+vsplitcount];
    param = new double [usize+usplitcount][vsize][numParam];
    for (i = 0; i < param.length; i++)
      for (j = 0; j < param[i].length; j++)
        for (int k = 0; k < param[i][j].length; k++)
	  param[i][j][k] = newparam[j][i][k];
    newparam = new double [usize+usplitcount][vsize+vsplitcount][numParam];
    splitOneAxis(v, newv, vs, newvs, splitv, param, newparam, theMesh.isVClosed());

    // Determine the new selection, and update the mesh.

    for (i = 0, j = 0; i < usize; i++)
    {
      if (selected[i])
        newsel[j] = true;
      if (i < splitu.length && splitu[i])
        newsel[++j] = true;
      j++;
    }
    for (i = 0, j = 0; i < vsize; i++)
    {
      if (selected[i+usize])
        newsel[j+usize+usplitcount] = true;
      if (i < splitv.length && splitv[i])
        newsel[++j+usize+usplitcount] = true;
      j++;
    }
    setUndoRecord(new UndoRecord(this, false, UndoRecord.COPY_OBJECT, new Object [] {theMesh, theMesh.duplicate()}));
    theMesh.setShape(newv, newus, newvs);
    for (int k = 0; k < numParam; k++)
      if (theMesh.getParameterValues()[k] instanceof VertexParameterValue)
      {
        double val[] = new double [newus.length*newvs.length];
        for (i = 0; i < newus.length; i++)
          for (j = 0; j < newvs.length; j++)
            val[i+newus.length*j] = newparam[i][j][k];
        theMesh.setParameterValue(theMesh.getParameters()[k], new VertexParameterValue(val));
      }

    object3DChangedDuringEditor();
    ((SplineMeshViewer) getCurrentView()).setSelection(newsel);
    ((SplineMeshViewer) getCurrentView()).informSelectionChanged(this,
        ModelEvent.CHANGEDUR_OBJECTEDITOR);
  }

  // Perform the subdivision along one axis of the mesh.

  private void splitOneAxis(MeshVertex v[][], MeshVertex newv[][], float s[], float news[], boolean split[], double param[][][], double newparam[][][], boolean closed)
  {
    SplineMesh theMesh = (SplineMesh) getCurrentView().getObject().object;
    int method = theMesh.getSmoothingMethod();
    int numParam = param[0][0].length;
    double paramTemp[] = new double [numParam];
    int i, j, k, p1, p3, p4;

    for (i = 0, j = 0; i < split.length; i++)
    {
      p1 = i-1;
      if (p1 < 0)
      {
        if (closed)
          p1 = v[0].length-1;
        else
          p1 = 0;
      }
      if (i < v[0].length-1)
        p3 = i+1;
      else
      {
        if (closed)
          p3 = 0;
        else
          p3 = v[0].length-1;
      }
      if ((split[i] || split[p1]) && method == Mesh.APPROXIMATING)
        for (k = 0; k < v.length; k++)
        {
          newv[k][j] = SplineMesh.calcApproxPoint(v[k], s, param[k], paramTemp, p1, i, p3);
          for (int m = 0; m < numParam; m++)
            newparam[k][j][m] = paramTemp[m];
        }
      else
        for (k = 0; k < v.length; k++)
        {
          newv[k][j] = v[k][i];
          for (int m = 0; m < numParam; m++)
            newparam[k][j][m] = param[k][i][m];
        }
      if (split[i] || split[p1])
        news[j] = Math.min(s[i]*2.0f, 1.0f);
      else
        news[j] = s[i];
      if (!split[i])
      {
        j++;
        continue;
      }
      if (method == Mesh.NO_SMOOTHING)
        for (k = 0; k < v.length; k++)
        {
          newv[k][j+1] = MeshVertex.blend(v[k][i], v[k][p3], 0.5, 0.5);
          for (int m = 0; m < numParam; m++)
            newparam[k][j+1][m] = 0.5*(param[k][i][m]+param[k][p3][m]);
        }
      else if (method == Mesh.INTERPOLATING)
      {
        if (i < v[0].length-2)
          p4 = i+2;
        else
        {
          if (closed)
            p4 = (i+2)%v[0].length;
          else
            p4 = v[0].length-1;
        }
        for (k = 0; k < v.length; k++)
        {
          newv[k][j+1] = SplineMesh.calcInterpPoint(v[k], s, param[k], paramTemp, p1, i, p3, p4);
          for (int m = 0; m < numParam; m++)
            newparam[k][j+1][m] = paramTemp[m];
        }
      }
      else
        for (k = 0; k < v.length; k++)
        {
          newv[k][j+1] = MeshVertex.blend(v[k][i], v[k][p3], 0.5, 0.5);
          for (int m = 0; m < numParam; m++)
            newparam[k][j+1][m] = 0.5*(param[k][i][m]+param[k][p3][m]);
        }
      news[j+1] = 1.0f;
      j += 2;
    }
    if (!closed)
    {
      for (k = 0; k < v.length; k++)
      {
        newv[k][0] = v[k][0];
        newv[k][j] = v[k][i];
        for (int m = 0; m < numParam; m++)
        {
          newparam[k][0][m] = param[k][0][m];
          newparam[k][j][m] = param[k][i][m];
        }
      }
      news[j] = s[i];
    }
  }

  public void extractCurveCommand()
  {
    SplineMesh theMesh = (SplineMesh) getCurrentView().getObject().object;
    MeshVertex vt[] = theMesh.getVertices();
    boolean selected[] = ((SplineMeshViewer) getCurrentView()).getSelection();
    int usize = theMesh.getUSize(), vsize = theMesh.getVSize();
    int i, which;

    if (((SplineMeshViewer) getCurrentView()).getSelectionMode() != SplineMeshViewer.CURVE_MODE)
      return;
    for (which = 0; which < selected.length && !selected[which]; which++);
    if (which == selected.length)
      return;

    // Now find the sequence of vertices.

    boolean closed;
    Vec3 v[];
    float smoothness[];
    if (which < usize)
    {
      closed = theMesh.isVClosed();
      v = new Vec3 [vsize];
    }
    else
    {
      closed = theMesh.isUClosed();
      v = new Vec3 [usize];
    }
    smoothness = new float [v.length];
    float usmoothness[] = theMesh.getUSmoothness();
    float vsmoothness[] = theMesh.getVSmoothness();
    for (i = 0; i < v.length; i++)
    {
      if (which < usize)
      {
        v[i] = vt[which+i*usize].r;
        smoothness[i] = vsmoothness[i];
      }
      else
      {
        v[i] = vt[(which-usize)*usize+i].r;
        smoothness[i] = usmoothness[i];
      }
    }
    Curve cv = new Curve(v, smoothness, theMesh.getSmoothingMethod(), closed);
    Widget parent = parentWindow.getFrame();
    while (parent != null && !(parent instanceof LayoutWindow))
      parent = parent.getParent();
    if (parent != null)
    {
      ((LayoutWindow) parent).addObject(cv, getCurrentView().thisObjectInScene.coords.duplicate(), 
          "Extracted Curve", null);
      ((LayoutWindow) parent).updateImage();
    }
  }

  public void setSmoothnessCommand()
  {
    final SplineMesh theMesh = (SplineMesh) getCurrentView().getObject().object;
    SplineMesh oldMesh = (SplineMesh) theMesh.duplicate();
    final MeshVertex vt[] = theMesh.getVertices();
    final boolean selected[] = ((SplineMeshViewer) getCurrentView()).getSelection();
    final float usmoothness[] = theMesh.getUSmoothness(), vsmoothness[] = theMesh.getVSmoothness();
    float value;
    int i;

    if (((SplineMeshViewer) getCurrentView()).getSelectionMode() != SplineMeshViewer.CURVE_MODE)
      return;
    for (i = 0; i < selected.length && !selected[i]; i++);
    if (i == selected.length)
      return;
    if (i < theMesh.getUSize())
      value = usmoothness[i];
    else
      value = vsmoothness[i-theMesh.getUSize()];
    value = 0.001f * (Math.round(value*1000.0f));
    final ValueSlider smoothness = new ValueSlider(0.0, 1.0, 100, (double) value);
    smoothness.addEventLink(ValueChangedEvent.class, new Object() {
      void processEvent()
      {
        float s = (float) smoothness.getValue();
        for (int i = 0; i < selected.length; i++)
          if (selected[i])
          {
            if (i < theMesh.getUSize())
              usmoothness[i] = s;
            else
              vsmoothness[i-theMesh.getUSize()] = s;
          }
        theMesh.setSmoothness(usmoothness, vsmoothness);
        getCurrentView().objectChanged();  // TODO(MB) Use message mechanism
        getCurrentView().updateImage();
        getCurrentView().repaint();
      }
    } );
    ComponentsDialog dlg = new ComponentsDialog(this, Translate.text("setCurveSmoothness"), 
        new Widget [] {smoothness}, new String [] {Translate.text("Smoothness")});
    if (dlg.clickedOk())
    {
      setUndoRecord(new UndoRecord(this, false, UndoRecord.COPY_OBJECT, new Object [] {theMesh, oldMesh}));
      object3DChangedDuringEditor();
    }
    else
    {
      theMesh.copyObject(oldMesh);
      getCurrentView().objectChanged();
      getCurrentView().updateImage();
      getCurrentView().repaint();
    }
  }

  public void reverseNormalsCommand()
  {
    SplineMesh theMesh = (SplineMesh) getCurrentView().getObject().object;
    setUndoRecord(new UndoRecord(this, false, UndoRecord.COPY_OBJECT, new Object [] {theMesh, theMesh.duplicate()}));
    theMesh.reverseOrientation();
    object3DChangedDuringEditor();
  }

  void setSmoothingMethod(int method)
  {
    SplineMesh theMesh = (SplineMesh) getCurrentView().getObject().object;

    setUndoRecord(new UndoRecord(this, false, UndoRecord.COPY_OBJECT, new Object [] {theMesh, theMesh.duplicate()}));
    updateMenus();
    theMesh.setSmoothingMethod(method);
    object3DChangedDuringEditor();
  }

  void setClosed(int item)
  {
    SplineMesh theMesh = (SplineMesh) getCurrentView().getObject().object;

    setUndoRecord(new UndoRecord(this, false, UndoRecord.COPY_OBJECT, new Object [] {theMesh, theMesh.duplicate()}));
    updateMenus();
    theMesh.setClosed((item == 0 || item == 2), (item == 1 || item == 2));
    object3DChangedDuringEditor();
  }

  /* Given a list of deltas which will be added to the selected vertices, calculate the
     corresponding deltas for the unselected vertices according to the mesh tension. */

  public void adjustDeltas(Vec3 delta[])
  {
    int dist[] = getCurrentView().getSelectionDistance(), count[] = new int [delta.length];
    SplineMesh theMesh = (SplineMesh) getCurrentView().getObject().object;
    int maxDistance = getTensionDistance(), usize = theMesh.getUSize(), vsize = theMesh.getVSize();
    double tension = getMeshTension(), scale[] = new double [maxDistance+1];


    for (int i = 0; i < delta.length; i++)
      if (dist[i] != 0)
        delta[i].set(0.0, 0.0, 0.0);
    for (int i = 0; i < maxDistance; i++)
    {
      for (int j = 0; j < count.length; j++)
        count[j] = 0;
      for (int j = 0; j < usize; j++)
        for (int k = 0; k < vsize; k++)
          if (dist[j+k*usize] == i)
          {
            if (j == 0)
            {
              if (theMesh.isUClosed() && dist[usize-1+k*usize] == i+1)
              {
                count[usize-1+k*usize]++;
                delta[usize-1+k*usize].add(delta[j+k*usize]);
              }
            }
            else
              if (dist[j-1+k*usize] == i+1)
              {
                count[j-1+k*usize]++;
                delta[j-1+k*usize].add(delta[j+k*usize]);
              }
            if (j == usize-1)
            {
              if (theMesh.isUClosed() && dist[k*usize] == i+1)
              {
                count[k*usize]++;
                delta[k*usize].add(delta[j+k*usize]);
              }
            }
            else
              if (dist[j+1+k*usize] == i+1)
              {
                count[j+1+k*usize]++;
                delta[j+1+k*usize].add(delta[j+k*usize]);
              }
            if (k == 0)
            {
              if (theMesh.isVClosed() && dist[j+(vsize-1)*usize] == i+1)
              {
                count[j+(vsize-1)*usize]++;
                delta[j+(vsize-1)*usize].add(delta[j+k*usize]);
              }
            }
            else
              if (dist[j+(k-1)*usize] == i+1)
              {
                count[j+(k-1)*usize]++;
                delta[j+(k-1)*usize].add(delta[j+k*usize]);
              }
            if (k == vsize-1)
            {
              if (theMesh.isVClosed() && dist[j] == i+1)
              {
                dist[j+k*usize] = i+1;
                count[j]++;
                delta[j].add(delta[j+k*usize]);
              }
            }
            else
              if (dist[j+(k+1)*usize] == i+1)
              {
                count[j+(k+1)*usize]++;
                delta[j+(k+1)*usize].add(delta[j+k*usize]);
              }
          }
      for (int j = 0; j < count.length; j++)
        if (count[j] > 1)
          delta[j].scale(1.0/count[j]);
    }
    for (int i = 0; i < scale.length; i++)
      scale[i] = Math.pow((maxDistance-i+1.0)/(maxDistance+1.0), tension);
    for (int i = 0; i < delta.length; i++)
      if (dist[i] > 0)
        delta[i].scale(scale[dist[i]]);
  }

}