/* Copyright (C) 2002-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.texture;

import artofillusion.*;
import artofillusion.animation.*;
import artofillusion.math.*;
import artofillusion.object.*;
import artofillusion.ui.*;
import buoy.event.*;
import buoy.widget.*;
import java.awt.Insets;
import java.awt.Cursor;
import java.awt.Dimension;
import java.util.*;

/** UVMappingWindow is a window for editing the UV texture coordinates at
    each vertex of a mesh. */

public class UVMappingWindow extends BDialog implements EditingWindow, ModelListener
{
  private Object3D oldObj;
  private Mesh editObj;
  private UVMapping map;
  private Vec2 coord[];  //TODO(MB) Maybe remove this member
  private UVMappingViewer mapView;
  private MeshViewer meshView;
  private MaterialPreviewer preview;
  private ValueField minuField, minvField, maxuField, maxvField, uField, vField;
  private BComboBox componentChoice, resChoice;
  private BCheckBox faceBox;
  private boolean selectedVertices[];
  /** Holds the coordinates when the object was constructed.
    Needed for moving "unpinned" vertices. */
  Vec2 startCoord[];
  /** Are the start coordinates in per face mode or not? */
  boolean startPerFace;
  BCheckBox pinnedCheck;
  BButton moveVerticesButton, moveOptionsButton;
  boolean pinnedVertices[];
  
  
  public static final int PINDIST_3D = 0;
  public static final int PINDIST_2D = 1;
  public static final int PINDIST_MESH = 2;
    
  int pinningDistanceMode = PINDIST_3D;
  
  public static final int PINWEIGHT_QUADNORM = 0;
  public static final int PINWEIGHT_LINNORM = 1;
  public static final int PINWEIGHT_SQRTNORM = 2;
  
  int pinningWeightMode = PINDIST_3D;

  public UVMappingWindow(BDialog parent, Object3D obj, UVMapping map)
  {
    super(parent, Translate.text("uvCoordsTitle"), true);
    oldObj = obj;
    Object3D meshObj = oldObj;
    while (meshObj instanceof ObjectWrapper)
      meshObj = ((ObjectWrapper) meshObj).getWrappedObject();
    editObj = (Mesh) meshObj.duplicate();
    this.map = map;
    ObjectInfo editInfo = new ObjectInfo((Object3D) editObj, new CoordinateSystem(), "");
    faceBox = new BCheckBox(Translate.text("mapFacesIndependently"), false);
    findTextureVertices();

    selectedVertices = new boolean [0];

    // Find the range of coordinates displayed.

    double minu = Double.MAX_VALUE, maxu = -Double.MAX_VALUE;
    double minv = Double.MAX_VALUE, maxv = -Double.MAX_VALUE;
    for (int i = 0; i < coord.length; i++)
    {
      if (coord[i].x < minu) minu = coord[i].x;
      if (coord[i].x > maxu) maxu = coord[i].x;
      if (coord[i].y < minv) minv = coord[i].y;
      if (coord[i].y > maxv) maxv = coord[i].y;
    }
    double padu = 0.1*(maxu-minu), padv = 0.1*(maxv-minv);
    minu -= padu;
    maxu += padu;
    minv -= padv;
    maxv += padv;

    // Determine the texture.

    Texture tex = obj.getTexture();
    if (tex instanceof LayeredTexture)
    {
      LayeredMapping layered = (LayeredMapping) obj.getTextureMapping();
      for (int i = 0; i < layered.getNumLayers(); i++)
        if (layered.getLayerMapping(i) == map)
        {
          tex = layered.getLayer(i);
          break;
        }
    }

    // Record the default parameter values.

    TextureParameter param[] = map.getParameters();
    double paramVal[] = null;
    if (param != null)
    {
      paramVal = new double [param.length];
      for (int i = 0; i < param.length; i++)
        paramVal[i] = param[i].defaultVal;
    }

    // Layout the three main canvases.

    FormContainer content = new FormContainer(new double [] {1.0, 1.0}, new double [] {1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0});
    content.setDefaultLayout(new LayoutInfo(LayoutInfo.CENTER, LayoutInfo.BOTH, null, null));
    setContent(content);
    BorderContainer mapViewPanel = new BorderContainer();
    mapViewPanel.add(mapView = new UVMappingViewer((Texture2D) tex, this, minu, maxu, minv, maxv, 0, 4, 0.0, paramVal), BorderContainer.CENTER);
    mapView.setVisible(true);
    mapView.setPreferredSize(new Dimension(200, 200));
    ToolPalette tools = new ToolPalette(6, 1);
    EditingTool defaultTool, metaTool;
    mapViewPanel.add(tools, BorderContainer.NORTH);
    tools.setBackground(getBackground());
    tools.addTool(defaultTool = new ReshapeMeshTool(this));
    tools.addTool(new ScaleMeshTool(this));
    tools.addTool(new RotateMeshTool(this, true));
    tools.addTool(new SkewMeshTool(this));
    tools.addTool(new TaperMeshTool(this));
    tools.addTool(metaTool = new MoveUVViewTool(this));
    mapView.setTool(defaultTool);
    mapView.setMetaTool(metaTool);
    if (editObj instanceof TriangleMesh)
      meshView = new TriMeshViewer(editInfo, new RowContainer());
    else
      meshView = new SplineMeshViewer(editInfo, new RowContainer());
    meshView.setPreferredSize(new Dimension(150, 150));
    meshView.setTool(new GenericTool(this, "movePoints.gif", "selected/movePoints.gif", Translate.text("reshapeMeshTool.tipText")));
    meshView.setMetaTool(new MoveViewTool(this));
    meshView.setAltTool(new RotateViewTool(this));
    meshView.setVisible(true);
    BSplitPane meshViewPanel = new BSplitPane(BSplitPane.VERTICAL, meshView, preview = new MaterialPreviewer(editInfo, 150, 150));
    meshViewPanel.setResizeWeight(0.7);
    BSplitPane div = new BSplitPane(BSplitPane.HORIZONTAL, mapViewPanel, meshViewPanel);
    div.setResizeWeight(0.5);
    content.add(div, 0, 0, 2, 1);
//    gc.gridwidth = 1;

    // Layout the text fields.

    RowContainer row;
    content.add(row = new RowContainer(), 0, 1);
    row.add(Translate.label("displayedComponent"));
    row.add(componentChoice = new BComboBox(new String [] {
      Translate.text("Diffuse"),
      Translate.text("Specular"),
      Translate.text("Transparent"),
      Translate.text("Hilight"),
      Translate.text("Emissive")
    }));
    componentChoice.addEventLink(ValueChangedEvent.class, this, "rebuildImage");
    content.add(Translate.label("selectedVertexCoords"), 0, 2);
    content.add(row = new RowContainer(), 0, 3);
    row.add(new BLabel("U:"));
    row.add(uField = new ValueField(Double.NaN, ValueField.NONE, 5));
    row.add(new BLabel(" V:"));
    row.add(vField = new ValueField(Double.NaN, ValueField.NONE, 5));
//    faceBox = new BCheckBox(Translate.text("mapFacesIndependently"), false);
    if (editObj instanceof TriangleMesh)
    {
      faceBox.setState(map.isPerFaceVertex((TriangleMesh) editObj));
      if (faceBox.getState())
        ((TriMeshViewer) meshView).setSelectionMode(TriMeshViewer.FACE_MODE);
      content.add(faceBox, 0, 4);
    }
    faceBox.addEventLink(ValueChangedEvent.class, this, "faceModeChanged");
    content.add(row = new RowContainer(), 1, 1);
    row.add(new BLabel(Translate.text("Resolution")+":"));
    row.add(resChoice = new BComboBox(new String [] {
      Translate.text("Low"),
      Translate.text("Medium"),
      Translate.text("High")
    }));
    resChoice.addEventLink(ValueChangedEvent.class, this, "rebuildImage");
    content.add(Translate.label("displayedCoordRange"), 1, 2);
    content.add(row = new RowContainer(), 1, 3);
    row.add(new BLabel("U:"));
    row.add(minuField = new ValueField(minu, ValueField.NONE, 5));
    row.add(Translate.label("to"), new LayoutInfo(LayoutInfo.CENTER, LayoutInfo.NONE, new Insets(0, 5, 0, 5), null));
    row.add(maxuField = new ValueField(maxu, ValueField.NONE, 5));
    content.add(row = new RowContainer(), 1, 4);
    row.add(new BLabel("V:"));
    row.add(minvField = new ValueField(minv, ValueField.NONE, 5));
    row.add(Translate.label("to"), new LayoutInfo(LayoutInfo.CENTER, LayoutInfo.NONE, new Insets(0, 5, 0, 5), null));
    row.add(maxvField = new ValueField(maxv, ValueField.NONE, 5));
    minuField.addEventLink(ValueChangedEvent.class, this, "rebuildImage");
    minvField.addEventLink(ValueChangedEvent.class, this, "rebuildImage");
    maxuField.addEventLink(ValueChangedEvent.class, this, "rebuildImage");
    maxvField.addEventLink(ValueChangedEvent.class, this, "rebuildImage");
    uField.addEventLink(ValueChangedEvent.class, this, "coordsChanged");
    vField.addEventLink(ValueChangedEvent.class, this, "coordsChanged");
 
    //(MB) Own additions
    content.add(row = new RowContainer(), 0, 5, 2, 1);
    row.add(pinnedCheck = new BCheckBox("Pinned", false));
    pinnedCheck.addEventLink(ValueChangedEvent.class, this, "pinnedCheckValueChanged");
    row.add(moveOptionsButton = Translate.button("moveOptions", this, "actionPerformed"));
    row.add(moveVerticesButton = Translate.button("moveVertices", this, "actionPerformed"));

/* TODO(MB) rewrite to buoy
    add(p = new Panel(), gc);
    p.setLayout(new FlowLayout(FlowLayout.LEFT));
    // TODO(MB) Localization
    p.add(pinnedCheck = new Checkbox("Pinned", false));
    pinnedCheck.addItemListener(this);
    p.add(Translate.button("moveOptions", this));
    p.add(Translate.button("moveVertices", this));
*/    
    pinnedVertices = new boolean [meshView.getSelectionDistance().length];
    //(MB) End of own additions

    // Layout the buttons at the bottom.

    content.add(row = new RowContainer(), 0, 6, 2, 1, new LayoutInfo());
    row.add(Translate.button("ok", this, "doOk"));
    row.add(Translate.button("cancel", this, "dispose"));
    pack();
    UIUtilities.centerWindow(this);
    setVisible(true);
  }

  /** Determine the list of texture vertices for the mesh and set the startCoord
    if necessary. */
  
  private void findTextureVertices()
  {
    boolean isPerFace = false;
    if (editObj instanceof TriangleMesh)
      isPerFace = map.isPerFaceVertex((TriangleMesh) editObj);
    if (isPerFace)
    {
      TriangleMesh tri = (TriangleMesh) editObj;
      Vec2 faceCoord[][] = map.findFaceTextureCoordinates(tri);
      int faces = tri.getFaces().length;
      coord = new Vec2 [3*faces];
      for (int i = 0; i < 3; i++)
        for (int j = 0; j < faces; j++)
          coord[j*3+i] = faceCoord[i][j];
    }
    else
      coord = map.findTextureCoordinates(editObj);
    
    checkStartCoordinates();
  }
  
  /** Determine which texture vertices are selected in the mesh viewer.  Return true if the
      selection has changed since this was last called, false if it has not. */
  
  private boolean findSelectedVertices()
  {
    boolean newSelection[];
    if (faceBox.getState())
    {
      boolean sel[] = ((TriMeshViewer) meshView).getSelection();
      int faces = ((TriangleMesh) editObj).getFaces().length;
      newSelection = new boolean [faces*3];
      for (int i = 0; i < sel.length; i++)
        if (sel[i])
          newSelection[3*i] = newSelection[3*i+1] = newSelection[3*i+2] = true;
    }
    else
    {
      int sel[] = meshView.getSelectionDistance();
      newSelection = new boolean [sel.length];
      for (int i = 0; i < sel.length; i++)
        newSelection[i] = (sel[i] == 0);
    }
    boolean changed = (selectedVertices.length != newSelection.length);
    for (int i = 0; i < newSelection.length && !changed; i++)
      if (selectedVertices[i] != newSelection[i])
        changed = true;
    selectedVertices = newSelection;
    return changed;
  }
  
  /** Regenerate the texture image based on the current settings. */

  private void rebuildImage()
  {
    int res = 1 << (2-resChoice.getSelectedIndex());

    setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
    mapView.setParameters(minuField.getValue(), maxuField.getValue(), minvField.getValue(),
        maxvField.getValue(), componentChoice.getSelectedIndex(), res);
    setCursor(Cursor.getDefaultCursor());
  }

  private void doOk()
  {
    if (faceBox.getState())
    {
      int faces = ((TriangleMesh) editObj).getFaces().length;
      Vec2 texCoord[][] = new Vec2 [3][faces];
      for (int i = 0; i < faces; i++)
      {
        texCoord[0][i] = coord[i*3];
        texCoord[1][i] = coord[i*3+1];
        texCoord[2][i] = coord[i*3+2];
      }
      map.setFaceTextureCoordinates(oldObj, texCoord);
    }
    else
      map.setTextureCoordinates(oldObj, coord);
    dispose();
  }
  
  protected void actionPerformed(CommandEvent e)
  {
/*
    else if (command.equals("cancel"))
      dispose();
    else if (command.equals("moveOptions"))
    {
      new UVPinningDialog(this);
    }
    else if (command.equals("moveVertices"))
    {
      moveVerticesCommand();
    }
*/
    String command = e.getActionCommand();
    
    if ("cancel".equals(command))
      dispose();
    else if ("moveOptions".equals(command))
      new UVPinningDialog(this);
    else if ("moveVertices".equals(command))
      moveVerticesCommand();
  }
  

  /** Respond to selections. */

  private void faceModeChanged()
  {
    TriangleMesh mesh = (TriangleMesh) editObj;
    TriangleMesh.Face face[] = mesh.getFaces();
    if (faceBox.getState())
    {
      // Convert from per-vertex to per-face-vertex mapping.
      
      Vec2 newcoord[] = new Vec2 [face.length*3];
      for (int i = 0; i < face.length; i++)
      {
        newcoord[i*3] = new Vec2(coord[face[i].v1]);
        newcoord[i*3+1] = new Vec2(coord[face[i].v2]);
        newcoord[i*3+2] = new Vec2(coord[face[i].v3]);
      }
      setTextureCoords(newcoord);
    }
    else
    {
      // Convert from per-face-vertex to per-vertex mapping.
      
      boolean consistent = true;
      Vec2 newcoord[] = new Vec2 [mesh.getVertices().length];
      for (int i = 0; i < face.length; i++)
      {
        int index[] = new int [] {face[i].v1, face[i].v2, face[i].v3};
        for (int j = 0; j < 3; j++)
        {
          Vec2 v = new Vec2(coord[i*3+j]);
          if (newcoord[index[j]] != null && !newcoord[index[j]].equals(v))
            consistent = false;
          newcoord[index[j]] = v;
        }
      }
      if (!consistent)
      {
        String options[] = new String [] {Translate.text("button.ok"), Translate.text("button.cancel")};
        int choice = new BStandardDialog("",
            UIUtilities.breakString("Disabling per-face mapping will cause some mapping information to be lost.  Are you sure you want to do this?"),
                // TODO(MB) Localization? !!!
            BStandardDialog.QUESTION).showOptionDialog(this, options, options[1]);
        if (choice == 1)
        {
          faceBox.setState(true);
          return;
        }
      }
      setTextureCoords(newcoord);
    }
    mapView.setDisplayedVertices(coord, new boolean [coord.length]);
    ((TriMeshViewer) meshView).setSelectionMode(faceBox.getState() ? TriMeshViewer.FACE_MODE :TriMeshViewer.POINT_MODE);
    mapView.informSelection(this, ModelEvent.CHANGEDUR_OBJECTEDITOR);    
    
    pinnedCheck.setEnabled(!faceBox.getState());
    moveOptionsButton.setEnabled(!faceBox.getState());
    moveVerticesButton.setEnabled(!faceBox.getState());
    
    findSelectedVertices();
    rebuildImage();
  }
  
  protected void pinnedCheckValueChanged()
  {
    boolean[] selverts = mapView.getSelection();
    boolean[] pinnedverts = getPinnedVertices();
    boolean pinned = pinnedCheck.getState();
    for(int i = 0; i < selverts.length; ++i)
      if (selverts[i])
        pinnedverts[i] = pinned;
  }
  
/* TODO(MB) rewrite to buoy
    else if (ev.getSource() == pinnedCheck)
    {
      boolean[] selverts = mapView.getSelection();
      boolean[] pinnedverts = getPinnedVertices();
      boolean pinned = pinnedCheck.getState();
      for(int i = 0; i < selverts.length; ++i)
        if (selverts[i])
          pinnedverts[i] = pinned;
    }
  }
*/
  
  private void coordsChanged(WidgetEvent ev)
  {
    Widget source = ev.getWidget();
    boolean sel[] = mapView.getSelection();
    for (int i = 0; i < sel.length; i++)
      if (sel[i])
      {
        if (source == uField && !Double.isNaN(uField.getValue()))
          coord[i].x = uField.getValue();
        if (source == vField && !Double.isNaN(vField.getValue()))
          coord[i].y = vField.getValue();
      }
    setTextureCoords(coord);
    mapView.updateVertexPositions(coord);
    mapView.repaint();
  }
  
  /** This is called when the displayed texture range changes. */
  
  public void displayRangeChanged()
  {
    if (mapView == null)
      return;
    minuField.setValue(mapView.getMinU());
    minvField.setValue(mapView.getMinV());
    maxuField.setValue(mapView.getMaxU());
    maxvField.setValue(mapView.getMaxV());
  }
  
  public void modelEventHappened(ModelEvent event)
  {
    if (event.getSource() == mapView)  // TODO(MB) Merge with displayRangeChanged above
    {
      if (event.hasKey("parameter changed") && event.getCause() != this)
      {
	minuField.removeEventLink(ValueChangedEvent.class, this);
	minvField.removeEventLink(ValueChangedEvent.class, this);
	maxuField.removeEventLink(ValueChangedEvent.class, this);
	maxvField.removeEventLink(ValueChangedEvent.class, this);

	minuField.setValue(mapView.getMinU());
	maxuField.setValue(mapView.getMaxU());
	minvField.setValue(mapView.getMinV());
	maxvField.setValue(mapView.getMaxV());
	
        minuField.addEventLink(ValueChangedEvent.class, this, "rebuildImage");
        minvField.addEventLink(ValueChangedEvent.class, this, "rebuildImage");
        maxuField.addEventLink(ValueChangedEvent.class, this, "rebuildImage");
        maxvField.addEventLink(ValueChangedEvent.class, this, "rebuildImage");
      }	
      else if (event.hasKey("selection changed") /* && event.getCause() != this */ )
      {
        boolean[] pinnedVerts = getPinnedVertices();
        boolean[] selVerts = mapView.getSelection();

        boolean pinned = pinnedVerts.length > 0;
        for(int i = 0; i < pinnedVerts.length; ++i)
        {
          if (selVerts[i] && !pinnedVerts[i])
          {
            pinned = false;
            break;
          }
        }
        // TODO(MB) Rewrite to buoy
//        pinnedCheck.removeItemListener(this);
//        pinnedCheck.setState(pinned);
//        pinnedCheck.addItemListener(this);
        pinnedCheck.removeEventLink(ValueChangedEvent.class, this);        
        pinnedCheck.setState(pinned);
        pinnedCheck.addEventLink(ValueChangedEvent.class, this, "pinnedCheckValueChanged");        
      }
    }
  }


  /** Update the texture coordinates of the mesh. */

  public void setTextureCoords(Vec2 coords[])
  {
    coord = coords;
    if (faceBox.getState())
    {
      int faces = ((TriangleMesh) editObj).getFaces().length;
      Vec2 texCoord[][] = new Vec2 [3][faces];
      for (int i = 0; i < faces; i++)
      {
        texCoord[0][i] = coord[i*3];
        texCoord[1][i] = coord[i*3+1];
        texCoord[2][i] = coord[i*3+2];
      }
      map.setFaceTextureCoordinates((Object3D) editObj, texCoord);
      map.setFaceTextureCoordinates(preview.getObject().object, texCoord);
    }
    else
    {
      map.setTextureCoordinates((Object3D) editObj, coords);
      map.setTextureCoordinates(preview.getObject().object, coords);
    }
    preview.render();
    
    checkStartCoordinates();  
  }

  /** Set the currently selected EditingTool. */

  public void setTool(EditingTool tool)
  {
    mapView.setTool(tool);
  }

  /** Set the text to display at the bottom of the window. */

  public void setHelpText(String text)
  {
  }

  /** Get the Frame for this EditingWindow: either the EditingWindow itself if it is a
      Frame, or its parent if it is a Dialog. */

  public BFrame getFrame()
  {
    return UIUtilities.findFrame(this);
  }

  /** Return the pinned vertices. This method should be used even inside
    this class because the location where this information is stored
    could change in the future.
   */
  
  public boolean[] getPinnedVertices()
  {
    return pinnedVertices;
  }
  
  /** Update the image displayed in this window. */

  public void updateImage()
  {
  }

  // TODO(MB) Called by (Tri|Spline)MeshViewer.mouseReleased(). Therefore
  // setting of displayed vertices is done here. Should be changed.
  /** This will be called whenever the selection changes, so rebuild the mesh
      and update the text fields. */

  public void updateMenus()
  {
    boolean selChanged = findSelectedVertices();
    if (selChanged)
    {
      mapView.setDisplayedVertices(coord, selectedVertices);
      mapView.informSelection(this, "");   // TODO(MB) Inside or outside if?
    }
    updateTextFields();
  }

  /** Update the U and V text fields to reflect the current selection. */

  public void updateTextFields()
  {
    boolean sel[] = mapView.getSelection();
    boolean any = false;
    double u = 0.0, v = 0.0;
    for (int i = 0; i < sel.length; i++)
      if (sel[i])
      {
        if (!any)
        {
          u = coord[i].x;
          v = coord[i].y;
          any = true;
        }
        else
        {
          if (u != coord[i].x)
            u = Double.NaN;
          if (v != coord[i].y)
            v = Double.NaN;
        }
      }
    uField.setValue(any ? u : Double.NaN);
    vField.setValue(any ? v : Double.NaN);
    uField.setEnabled(any);
    vField.setEnabled(any);
  }

  
  /** Checks if startCoord have the right length and startPerFace
    is set right. If not, copies coord into startCoord.
   */
  
  public void checkStartCoordinates()
  {
    if (startCoord == null || startCoord.length != coord.length ||
        startPerFace != faceBox.getState())
    {
      startCoord = (Vec2[])Utilities.deepArrayCopy(coord);
      startPerFace = faceBox.getState();
    }
  } 
  
  
  /** For each UV-vertex the 2-dimensional difference between coord and
   startCoord is calculated. */
  
  public Vec2[] calcMovementsForPinning()
  {
    Vec2[] result = new Vec2[coord.length];
    
    for(int i = 0; i < result.length; ++i)
      result[i] = coord[i].minus(startCoord[i]);
    
    return result;
  }
  
  /** Calculates distance of two UV-Coordinate vertices with indexes vi1, vi2.
    The meaning of "distance" depends on set options.
    
    @return A nonegative value for a distance or -1 for infinite distance
   */

  public double calcDistanceForPinning(int vi1, int vi2)
  {
    switch (pinningDistanceMode)
    {
      case PINDIST_2D:
      {
        Vec2 v1 = startCoord[vi1];
        Vec2 v2 = startCoord[vi2];

        return v1.distance(v2);
      }        
      case PINDIST_3D:
      {
        Vec3 v1 = editObj.getVertices()[vi1].r;
        Vec3 v2 = editObj.getVertices()[vi2].r;

        return v1.distance(v2);
      }
    }
    return -1;
  }
    
  /** Called when the moveVertices button is pressed. */
  
  public void moveVerticesCommand()
  {
    // TODO(MB) Undoing
    Vec2[] movements = calcMovementsForPinning();
    boolean[] pinnedverts = getPinnedVertices();
    boolean[] selverts = mapView.getSelection();

    // Create list of indices of pinned vertices
    int[] pinnedlist = Utilities.createIndexList(pinnedverts);
    Vec2[] pinnedstarts = new Vec2[pinnedlist.length];
    Vec2[] pinnedmovs = new Vec2[pinnedlist.length];
    int[][] pinnedMeshDists = null;
    
    double[] weights = new double[pinnedlist.length];
    double weightsum, dist;
    boolean infiniteweight;
    
    int i, vi;
    
    for(i = 0; i < pinnedlist.length; ++i)
    {
      pinnedstarts[i] = startCoord[pinnedlist[i]];
      pinnedmovs[i] = movements[pinnedlist[i]];
    }
    
    // If distance mode is mesh, we need to calculate the distances
    // for each pinned vertex
    if (pinningDistanceMode == PINDIST_MESH)
    {
      pinnedMeshDists = new int[pinnedlist.length][];
      int[] meshdists;
      
      for(i = 0; i < pinnedlist.length; ++i)
      {
        meshdists = new int[movements.length];
        for(vi = 0; vi < meshdists.length; ++vi)
          meshdists[i] = (vi == pinnedlist[i]) ? 0 : -1;
        ((TriMeshViewer)meshView).extendDistance(meshdists, -1);
        pinnedMeshDists[i] = meshdists;
      }
    }
        
    // Adjust all selected, unpinned vertices
    for(vi = 0; vi < movements.length; ++vi)
    {
      if (pinnedverts[vi] || !selverts[vi])
        continue;
      
      infiniteweight = false;
      weightsum = 0;
      
      // Calculate initial weights
      for(i = 0; i < pinnedlist.length; ++i)
      {
        if (pinningDistanceMode == PINDIST_MESH)
        { // special distance calculation
          dist = pinnedMeshDists[i][vi];
        }
        else
          dist = calcDistanceForPinning(vi, pinnedlist[i]);
        
        if (dist == 0.0)
        { // Distance == 0 -> infinite weight
          infiniteweight = true;
          weights[i] = -1.0;
        }
        else if (dist == -1.0)
        {  // Infinite distance -> weight = 0
          weights[i] = 0.0;
        }
        else
        {
          switch (pinningWeightMode)
          {
            case PINWEIGHT_LINNORM:
              weights[i] = 1/dist;
              break;
            case PINWEIGHT_QUADNORM:
              weights[i] = 1/(dist*dist);
              break;
            case PINWEIGHT_SQRTNORM:
              weights[i] = 1/Math.sqrt(dist);
              break;
            default:
              weights[i] = 0;
          }
          weightsum += weights[i];
        }
      }
      
      // Special case of one or more infinite weights (distance == 0)
      // In this case infinite weight counts 1, finite weight counts 0
      if (infiniteweight)
      {
        weightsum = 0;
        for(i = 0; i < pinnedlist.length; ++i)
        {
          if (weights[i] == -1.0)
          {
            weights[i] = 1.0;
            weightsum += 1.0;
          }
          else
            weights[i] = 0.0;
        }
      }
      
      // Normalize weights, so they sum up to 1
      if (weightsum > 0)
        for(i = 0; i < pinnedlist.length; ++i)
          weights[i] /= weightsum;
      
      {     // Motion mode:
        // Calculate the resulting movement vector
        Vec2 finalmove = new Vec2();
        for(i = 0; i < pinnedlist.length; ++i)
          finalmove.add(pinnedmovs[i].times(weights[i]));

        // Apply it
        coord[vi] = startCoord[vi].plus(finalmove);
      }
          
      if (false)  
      {  //Position mode:
        // Calculate position
        Vec2 finalpos = new Vec2();
        for(i = 0; i < pinnedlist.length; ++i)
          finalpos.add(coord[pinnedlist[i]].times(weights[i]));
        
        // Apply it
        coord[vi] = finalpos;
      }
    }
    setTextureCoords(coord);
    mapView.updateVertexPositions(coord);
    mapView.repaint();
  }   

  /** Set the current UndoRecord for this EditingWindow. */

  public void setUndoRecord(UndoRecord command)
  {
  }

  /** Get the Scene which is being edited in this window.  If it is not a window for
      editing a scene, this should return null. */

  public Scene getScene()
  {
    return null;
  }

  /** Get the ViewerCanvas in which the UV coordinates are being edited. */
  // TODO(MB) Remove if possible
  
  public ViewerCanvas getView()
  {
    return mapView;
  }
  
  /** Return the mesh which mapping is edited in the dialog. */

  public Mesh getMesh()
  {
    return editObj;
  }

  /** Confirm whether this window should be closed (possibly by displaying a message to the
      user), and then close it.  If the closing is canceled, this should return false. */

  public boolean confirmClose()
  {
    return true;
  }
    
}