/*
 * @(#)TweakableShape.java    2.4.0   8 September 2005
 *
 * Copyright 2005
 * College of Computer and Information Science
 * Northeastern University
 * Boston, MA  02115
 *
 * The Java Power Tools software may be used for educational
 * purposes as long as this copyright notice is retained intact
 * at the top of all source files.
 *
 * To discuss possible commercial use of this software, 
 * contact Richard Rasala at Northeastern University, 
 * College of Computer and Information Science,
 * 617-373-2462 or rasala@ccs.neu.edu.
 *
 * The Java Power Tools software has been designed and built
 * in collaboration with Viera Proulx and Jeff Raab.
 *
 * Should this software be modified, the words "Modified from 
 * Original" must be included as a comment below this notice.
 *
 * All publication rights are retained.  This software or its 
 * documentation may not be published in any media either
 * in whole or in part without explicit permission.
 *
 * This software was created with support from Northeastern 
 * University and from NSF grant DUE-9950829.
 */

package edu.neu.ccs.gui;

import edu.neu.ccs.util.*;

import java.awt.*;
import java.awt.geom.*;
import java.beans.*;

/**
 * <p>Class <code>TweakableShape</code> implements a <code>BaseShape</code>
 * in which the vertex array and the tangent array may be changed
 * independently of one another.</p>
 *
 * <p>The derived class <code>TweakableCurve</code> was introduced in
 * 2.4.0 to fix the path strategy to be <code>Path.BEZIER_CUBIC</code>
 * since that is the most common case.</p>
 *
 * <p>To define a shape in which the tangent array is computed from the
 * vertex array automatically, use the class <code>AutomaticShape</code>.</p>
 *
 * <p>The internal tangent array will be maintained as non-<code>null</code>
 * by creating an array of zero tangents if needed.</p>
 *
 * @author  Richard Rasala
 * @version 2.4.0
 * @since   2.3
 */
public class TweakableShape extends BaseShape {

    /** Bound property name to set one or more vertices. */
    public static final String SET_VERTEX               = "set.vertex";
    
    /** Bound property name to set one or more tangents. */
    public static final String SET_TANGENT              = "set.tangent";
    
    /** Bound property name to set one or more vertex-tangent pairs. */
    public static final String SET_VERTEX_TANGENT       = "set.vertex.tangent";
    
    /** Bound property name to add one or more vertex-tangent pairs. */
    public static final String ADD_VERTEX_TANGENT       = "add.vertex.tangent";
    
    /** Bound property name to remove one or more vertex-tangent pairs. */
    public static final String REMOVE_VERTEX_TANGENT    = "remove.vertex.tangent";
    
    
    /**
     * <p>The default constructor with an empty shape.</p>
     *
     * <p>The default settings are as follows:</p>
     *
     * <ul>
     *   <li>Vertex data:      <code>new float[0][2]</code></li>
     *   <li>Tangent data:     <code>new float[0][2]</code></li>
     *   <li>Path strategy:    <code>Path.BEZIER_CUBIC</code></li>
     *   <li>Closure mode:     <code>ClosureMode.CLOSED</code></li>
     *   <li>Winding rule:     <code>WindingRule.WIND_NON_ZERO</code>.</li>
     * </ul>
     */
    public TweakableShape() {
        this(null, null, null, null, null);
    }
    
    
    /**
     * <p>The constructor with the given vertex data.</p>
     *
     * <p>Preconditions:</p>
     * <ul>
     *   <li>The given vertex array is either <code>null</code> or
     *       <code>float[N][2]</code> for some N.</li>
     * </ul>
     *
     * <p>If the given vertex array is <code>null</code> then the
     * internal vertex and tangent arrays are set to
     * <code>float[0][2]</code>.</p>
     *
     * <p>Otherwise the vertex array will be cloned and an array
     * of zero tangents will be constructed.</p>
     *
     * @param vertex the vertex data
     */
    public TweakableShape(float[][] vertex) {
        this(vertex, null, null, null, null);
    }
    
    
    /**
     * <p>The constructor with the given vertex and tangent data.</p>
     *
     * <p>Preconditions:</p>
     * <ul>
     *   <li>The given vertex array is either <code>null</code> or
     *       <code>float[N][2]</code> for some N.</li>
     *   <li>The given tangent array is either <code>null</code> or
     *       <code>float[N][2]</code> for the same N as in the vertex
     *       array.</li>
     * </ul>
     *
     * <p>If the given vertex array is <code>null</code> then the
     * given tangent array is ignored and the internal vertex and
     * tangent arrays are set to <code>float[0][2]</code>.</p>
     *
     * <p>Otherwise if the given tangent array is <code>null</code>
     * then the vertex array will be cloned and an array of zero
     * tangents will be constructed.</p>
     *
     * <p>Otherwise both the vertex and tangent array should be
     * <code>float[N][2]</code> for the same N.  These arrays will
     * be cloned to set the internal vertex and tangent data.</p>
     *
     * @param vertex  the vertex  data
     * @param tangent the tangent data
     */
    public TweakableShape(float[][] vertex, float[][] tangent) {
        this(vertex, tangent, null, null, null);
    }
    
    
    /**
     * <p>The constructor with the given vertex and tangent data
     * and the given path strategy.</p>
     *
     * <p>Preconditions:</p>
     * <ul>
     *   <li>The given vertex array is either <code>null</code> or
     *       <code>float[N][2]</code> for some N.</li>
     *   <li>The given tangent array is either <code>null</code> or
     *       <code>float[N][2]</code> for the same N as in the vertex
     *       array.</li>
     * </ul>
     *
     * <p>If the given vertex array is <code>null</code> then the
     * given tangent array is ignored and the internal vertex and
     * tangent arrays are set to <code>float[0][2]</code>.</p>
     *
     * <p>Otherwise if the given tangent array is <code>null</code>
     * then the vertex array will be cloned and an array of zero
     * tangents will be constructed.</p>
     *
     * <p>Otherwise both the vertex and tangent array should be
     * <code>float[N][2]</code> for the same N.  These arrays will
     * be cloned to set the internal vertex and tangent data.</p>
     *
     * @param vertex       the vertex  data
     * @param tangent      the tangent data
     * @param pathstrategy the path strategy
     */
    public TweakableShape
        (float[][]     vertex,
         float[][]     tangent,
         Path.Strategy pathstrategy)
    {
        this(vertex, tangent, pathstrategy, null, null);
    }
    
    
    /**
     * <p>The constructor with the given vertex and tangent data,
     * the given path strategy,
     * and the given closure mode.</p>
     *
     * <p>Preconditions:</p>
     * <ul>
     *   <li>The given vertex array is either <code>null</code> or
     *       <code>float[N][2]</code> for some N.</li>
     *   <li>The given tangent array is either <code>null</code> or
     *       <code>float[N][2]</code> for the same N as in the vertex
     *       array.</li>
     * </ul>
     *
     * <p>If the given vertex array is <code>null</code> then the
     * given tangent array is ignored and the internal vertex and
     * tangent arrays are set to <code>float[0][2]</code>.</p>
     *
     * <p>Otherwise if the given tangent array is <code>null</code>
     * then the vertex array will be cloned and an array of zero
     * tangents will be constructed.</p>
     *
     * <p>Otherwise both the vertex and tangent array should be
     * <code>float[N][2]</code> for the same N.  These arrays will
     * be cloned to set the internal vertex and tangent data.</p>
     *
     * @param vertex       the vertex  data
     * @param tangent      the tangent data
     * @param pathstrategy the path strategy
     * @param closuremode  the closure mode
     */
    public TweakableShape
        (float[][]     vertex,
         float[][]     tangent,
         Path.Strategy pathstrategy,
         ClosureMode   closuremode)
    {
        this(vertex, tangent, pathstrategy, closuremode, null);
    }
    
    
    /**
     * <p>The constructor with the given vertex and tangent data,
     * the given path strategy,
     * the given closure mode,
     * and the given winding rule.</p>
     *
     * <p>Preconditions:</p>
     * <ul>
     *   <li>The given vertex array is either <code>null</code> or
     *       <code>float[N][2]</code> for some N.</li>
     *   <li>The given tangent array is either <code>null</code> or
     *       <code>float[N][2]</code> for the same N as in the vertex
     *       array.</li>
     * </ul>
     *
     * <p>If the given vertex array is <code>null</code> then the
     * given tangent array is ignored and the internal vertex and
     * tangent arrays are set to <code>float[0][2]</code>.</p>
     *
     * <p>Otherwise if the given tangent array is <code>null</code>
     * then the vertex array will be cloned and an array of zero
     * tangents will be constructed.</p>
     *
     * <p>Otherwise both the vertex and tangent array should be
     * <code>float[N][2]</code> for the same N.  These arrays will
     * be cloned to set the internal vertex and tangent data.</p>
     *
     * @param vertex       the vertex  data
     * @param tangent      the tangent data
     * @param pathstrategy the path strategy
     * @param closuremode  the closure mode
     * @param windingrule  the winding rule
     */
    public TweakableShape
        (float[][]     vertex,
         float[][]     tangent,
         Path.Strategy pathstrategy,
         ClosureMode   closuremode,
         WindingRule   windingrule)
    {
        // initialize internal vertex and tangent data to defaults
        this.vertex  = new float[0][2];
        this.tangent = new float[0][2];
        
        // initialize a null path strategy to BEZIER_CUBIC
        if (pathstrategy == null)
            pathstrategy = Path.BEZIER_CUBIC;
        
        setPathStrategy(pathstrategy);
        setClosureMode(closuremode);
        setWindingRule(windingrule);
        setVertexTangentData(vertex, tangent);
        
        makePath();
    }
    
    
    /**
     * <p>Sets the vertex data of the shape using a 2-dimensional array
     * of single precision numbers and makes a new path.</p>
     *
     * <p>Does not change the current tangent data.</p>
     *
     * <p>Preconditions:</p>
     * <ul>
     *   <li>For some integer N, the given vertex array is
     *       non-<code>null</code> and has the form
     *       <code>float[N][2]</code>.</li>
     *   <li>The current tangent array has the form
     *       <code>float[N][2]</code> for the same N.</li>
     * </ul>
     *
     * <p>Does nothing if the preconditions fail.</p>
     *
     * <p>Fires property change: SET_VERTEX.</p>
     *
     * @param vertex the vertex data
     */
    public final void setVertexData(float[][] vertex) {
        if (! FloatArray.checkArray(vertex, 2))
            return;
        
        if (vertex.length != tangent.length)
            return;
        
        this.vertex = FloatArray.deepclone(vertex);
        
        makePath();
        
        firePropertyChange(SET_VERTEX, null, null);
    }
    
    
    /**
     * <p>Sets the tangent data of the shape using a 2-dimensional array
     * of single precision numbers and makes a new path.</p>
     *
     * <p>Does not change the current vertex data.</p>
     *
     * <p>Preconditions:</p>
     * <ul>
     *   <li>The given tangent array is either <code>null</code> or,
     *       for some integer N, has the form
     *       <code>float[N][2]</code>.</li>
     *   <li>In the latter case, the current vertex array has the
     *       form <code>float[N][2]</code> for the same N.</li>
     * </ul>
     *
     * <p>If the given tangent array is <code>null</code>, then the
     * internal tangent array will be set to a new array of zeroes.</p>
     *
     * <p>Does nothing if the preconditions fail.</p>
     *
     * <p>Fires property change: SET_TANGENT.</p>
     *
     * @param tangent the tangent data
     */
    public final void setTangentData(float[][] tangent) {
        if (tangent == null) {
            this.tangent = new float[vertex.length][2];
        }
        else {
            if (! FloatArray.checkArray(tangent, 2))
                return;
            
            if (tangent.length != vertex.length)
                return;
            
            this.tangent = FloatArray.deepclone(tangent);
        }
        
        makePath();
        
        firePropertyChange(SET_TANGENT, null, null);
    }
    
    
    /**
     * <p>Sets the vertex and tangent data of the shape using a pair
     * of 2-dimensional arrays of single precision numbers and makes
     * a new path.</p>
     *
     * <p>Preconditions:</p>
     * <ul>
     *   <li>The given vertex array is either <code>null</code> or
     *       <code>float[N][2]</code> for some N.</li>
     *   <li>The given tangent array is either <code>null</code> or
     *       <code>float[N][2]</code> for the same N as in the vertex
     *       array.</li>
     * </ul>
     *
     * <p>If the given vertex array is <code>null</code> then the
     * given tangent array is ignored and the internal vertex and
     * tangent arrays are set to <code>float[0][2]</code>.</p>
     *
     * <p>Otherwise if the given tangent array is <code>null</code>
     * then the vertex array will be cloned and an array of zero
     * tangents will be constructed.</p>
     *
     * <p>Otherwise both the vertex and tangent array should be
     * <code>float[N][2]</code> for the same N.  These arrays will
     * be cloned to set the internal vertex and tangent data.</p>
     *
     * <p>Does nothing if the preconditions fail.</p>
     *
     * <p>Fires property change: SET_VERTEX_TANGENT.</p>
     *
     * @param vertex  the vertex data
     * @param tangent the tangent data
     */
    public final void setVertexTangentData
        (float[][] vertex, float[][] tangent)
    {
        if (vertex == null) {
            this.vertex  = new float[0][2];
            this.tangent = new float[0][2];
        }
        else {
            if (! FloatArray.checkArray(vertex, 2))
                return;
            
            int N = vertex.length;
            
            if (tangent == null) {
                this.tangent = new float[N][2];
            }
            else {
                if (! FloatArray.checkArray(tangent, 2))
                    return;
                
                if (tangent.length != N)
                    return;
                
                this.tangent = FloatArray.deepclone(tangent);
            }
            
            this.vertex = FloatArray.deepclone(vertex);
        }
        
        makePath();
        
        firePropertyChange(SET_VERTEX_TANGENT, null, null);
    }
    
    
    /**
     * <p>Sets the vertex and tangent data of the shape using a
     * 2-dimensional array of single precision numbers and makes
     * a new path.</p>
     *
     * <p>Conceptually, each row of the array should contain 4
     * items (x, y, dx, dy).</p>
     *
     * <p>Preconditions:</p>
     * <ul>
     *   <li>The given vertex_tangent array is either
     *       <code>null</code> or <code>float[N][4]</code> for
     *       some N.</li>
     * </ul>
     *
     * <p>If the given vertex_tangent array is <code>null</code>
     * then the internal vertex and tangent arrays are set to
     * <code>float[0][2]</code>.</p>
     *
     * <p>Does nothing if the preconditions fail.</p>
     *
     * <p>Fires property change: SET_VERTEX_TANGENT.</p>
     *
     * @param vertex_tangent the combined vertex and tangent data
     */
    public final void setVertexTangentData(float[][] vertex_tangent)
    {
        if (vertex_tangent == null) {
            vertex  = new float[0][2];
            tangent = new float[0][2];
        }
        else {
            if (! FloatArray.checkArray(vertex_tangent, 4))
                return;
            
            int N = vertex_tangent.length;
            
            vertex  = new float[N][2];
            tangent = new float[N][2];
            
            for (int i = 0; i < N; i++) {
                vertex[i][0]  = vertex_tangent[i][0];
                vertex[i][1]  = vertex_tangent[i][1];
                tangent[i][0] = vertex_tangent[i][2];
                tangent[i][1] = vertex_tangent[i][3];
            }
        }
        
        makePath();
        
        firePropertyChange(SET_VERTEX_TANGENT, null, null);
    }
    
    
    /**
     * <p>Sets the vertex at the given index to the given point coordinates
     * and makes a new path.</p>
     *
     * <p>Does nothing if the index is not in [0, vertex.length).</p>
     *
     * <p>Does not change the tangent data.</p>
     *
     * <p>Fires property change: SET_VERTEX.</p>
     *
     * @param index the vertex index
     * @param x the new x-coordinate for the vertex
     * @param y the new y-coordinate for the vertex
     */
    public final void setVertex(int index, float x, float y) {
        if ((index < 0) || (index >= vertex.length))
            return;
        
        vertex[index][0] = x;
        vertex[index][1] = y;
        
        makePath();
        
        firePropertyChange(SET_VERTEX, null, null);
    }
    
    
    /**
     * <p>Sets the vertex at the given index to the given point
     * and makes a new path.</p>
     *
     * <p>Does nothing if the index is not in [0, vertex.length).</p>
     *
     * <p>Does nothing if point is not <code>float[2]</code>.</p>
     *
     * <p>Does not change the tangent data.</p>
     *
     * <p>Fires property change: SET_VERTEX.</p>
     *
     * @param index the vertex index
     * @param point the new position for the vertex
     */
    public final void setVertex(int index, float[] point) {
        if ((point == null) || (point.length != 2))
            return;
        
        setVertex(index, point[0], point[1]);
    }
    
    
    /**
     * <p>Sets the tangent at the given index to the given
     * displacement coordinates and makes a new path.</p>
     *
     * <p>Does nothing if the index is not in [0, vertex.length).</p>
     *
     * <p>Does not change the vertex data.</p>
     *
     * <p>Fires property change: SET_TANGENT.</p>
     *
     * @param index the tangent index
     * @param dx the new x-coordinate for the displacement
     * @param dy the new y-coordinate for the displacement
     */
    public final void setTangent(int index, float dx, float dy) {
        if ((index < 0) || (index >= vertex.length))
            return;
        
        tangent[index][0] = dx;
        tangent[index][1] = dy;
        
        makePath();
        
        firePropertyChange(SET_TANGENT, null, null);
    }
    
    
    /**
     * <p>Sets the tangent at the given index to the given
     * displacement and makes a new path.</p>
     *
     * <p>Does nothing if the index is not in [0, vertex.length).</p>
     *
     * <p>Does nothing if delta is not <code>float[2]</code>.</p>
     *
     * <p>Does not change the vertex data.</p>
     *
     * <p>Fires property change: SET_TANGENT.</p>
     *
     * @param index the tangent index
     * @param delta the new displacement for the tangent
     */
    public final void setTangent(int index, float[] delta) {
        if ((delta == null) || (delta.length != 2))
            return;
        
        setTangent(index, delta[0], delta[1]);
    }
    
    
    /**
     * <p>Sets the tangent at the given index using the given
     * coordinates for the ahead control point and makes a new path.</p>
     *
     * <p>Subtracts ((x,y) - vertex) at the given index
     * to set the tangent coordinates.</p>
     *
     * <p>Does nothing if the index is not in [0, vertex.length).</p>
     *
     * <p>Does not change the vertex data.</p>
     *
     * <p>Fires property change: SET_TANGENT.</p>
     *
     * @param index the tangent index
     * @param x the new x-coordinate for the ahead control point
     * @param y the new y-coordinate for the ahead control point
     */
    public final void setTangentViaControlA(int index, float x, float y) {
        if ((index < 0) || (index >= vertex.length))
            return;
        
        tangent[index][0] = x - vertex[index][0];
        tangent[index][1] = y - vertex[index][1];
        
        makePath();
        
        firePropertyChange(SET_TANGENT, null, null);
    }
    
    
    /**
     * <p>Sets the tangent at the given index to using the given
     * ahead control point and makes a new path.</p>
     *
     * <p>Subtracts (control - vertex) at the given index
     * to set the tangent coordinates.</p>
     *
     * <p>Does nothing if the index is not in [0, vertex.length).</p>
     *
     * <p>Does nothing if control is not <code>float[2]</code>.</p>
     *
     * <p>Does not change the vertex data.</p>
     *
     * <p>Fires property change: SET_TANGENT.</p>
     *
     * @param index   the tangent index
     * @param control the ahead control point
     */
    public final void setTangentViaControlA(int index, float[] control) {
        if ((control == null) || (control.length != 2))
            return;
        
        setTangentViaControlA(index, control[0], control[1]);
    }
    
    
    /**
     * <p>Sets the tangent at the given index using the given
     * coordinates for the behind control point and makes a new path.</p>
     *
     * <p>Subtracts (vertex - (x,y)) at the given index
     * to set the tangent coordinates.</p>
     *
     * <p>Does nothing if the index is not in [0, vertex.length).</p>
     *
     * <p>Does not change the vertex data.</p>
     *
     * <p>Fires property change: SET_TANGENT.</p>
     *
     * @param index the tangent index
     * @param x the new x-coordinate for the behind control point
     * @param y the new y-coordinate for the behind control point
     */
    public final void setTangentViaControlB(int index, float x, float y) {
        if ((index < 0) || (index >= vertex.length))
            return;
        
        tangent[index][0] = vertex[index][0] - x;
        tangent[index][1] = vertex[index][1] - y;
        
        makePath();
        
        firePropertyChange(SET_TANGENT, null, null);
    }
    
    
    /**
     * <p>Sets the tangent at the given index to using the given
     * behind control point and makes a new path.</p>
     *
     * <p>Subtracts (vertex - control) at the given index
     * to set the tangent coordinates.</p>
     *
     * <p>Does nothing if the index is not in [0, vertex.length).</p>
     *
     * <p>Does nothing if control is not <code>float[2]</code>.</p>
     *
     * <p>Does not change the vertex data.</p>
     *
     * <p>Fires property change: SET_TANGENT.</p>
     *
     * @param index   the tangent index
     * @param control the behind control point
     */
    public final void setTangentViaControlB(int index, float[] control) {
        if ((control == null) || (control.length != 2))
            return;
        
        setTangentViaControlB(index, control[0], control[1]);
    }
    
    
    /**
     * <p>Sets the vertex and tangent at the given index to the
     * given point and displacement coordinates and makes a new
     * path.</p>
     *
     * <p>Does nothing if the index is not in [0, vertex.length).</p>
     *
     * <p>Fires property change: SET_VERTEX_TANGENT.</p>
     *
     * @param index the vertex/tangent index
     * @param x  the new x-coordinate for the vertex
     * @param y  the new y-coordinate for the vertex
     * @param dx the new x-coordinate for the displacement
     * @param dy the new y-coordinate for the displacement
     */
    public final void setVertexTangent
        (int index, float x, float y, float dx, float dy)
    {
        if ((index < 0) || (index >= vertex.length))
            return;
        
        vertex[index][0] = x;
        vertex[index][1] = y;
        
        tangent[index][0] = dx;
        tangent[index][1] = dy;
        
        makePath();
        
        firePropertyChange(SET_VERTEX_TANGENT, null, null);
    }
    
    
    /**
     * <p>Sets the vertex and tangent at the given index using the
     * given point and delta arrays and makes a new path.</p>
     *
     * <p>Conceptually, the point array should contain 2 items
     * (x, y) and the delta array 2 items (dx, dy).</p>
     *
     * <p>Does nothing if the index is not in [0, vertex.length).</p>
     *
     * <p>Does nothing if the arrays are not <code>float[2]</code>.</p>
     *
     * <p>Fires property change: SET_VERTEX_TANGENT.</p>
     *
     * @param index the vertex/tangent index
     * @param point the array with x, y
     * @param delta the array with dx, dy
     */
    public final void setVertexTangent
        (int index, float[] point, float[] delta)
    {
        if ((point == null) || (point.length != 2))
            return;
        
        if ((delta == null) || (delta.length != 2))
            return;
        
        setVertexTangent(index, point[0], point[1], delta[0], delta[1]);
    }
    
    
    /**
     * <p>Sets the vertex and tangent at the given index to the
     * given array of point and displacement coordinates and
     * makes a new path.</p>
     *
     * <p>Conceptually, the array should contain 4 items
     * (x, y, dx, dy).</p>
     *
     * <p>Does nothing if the index is not in [0, vertex.length).</p>
     *
     * <p>Does nothing if the array is not <code>float[4]</code>.</p>
     *
     * <p>Fires property change: SET_VERTEX_TANGENT.</p>
     *
     * @param index the vertex/tangent index
     * @param array the array with x, y, dx, dy
     */
    public final void setVertexTangent(int index, float[] array) {
        if ((array == null) || (array.length != 4))
            return;
        
        setVertexTangent(index, array[0], array[1], array[2], array[3]);
    }
    
    
    /**
     * <p>Adds a new vertex and tangent at the given index with the
     * given point and displacement coordinates x, y, dx, dy.</p>
     *
     * <p>If index is 0, the insertion is at the front.</p>
     *
     * <p>If index is vertex.length, the insertion is at the end.</p>
     *
     * <p>Forces the given index into the range:</p>
     *
     * <pre>  0 &lt;= index &lt;= vertex.length</pre>
     *
     * <p>Fires property change: ADD_VERTEX_TANGENT.</p>
     *
     * @param index the vertex/tangent index
     * @param x  the x-coordinate for the vertex
     * @param y  the y-coordinate for the vertex
     * @param dx the x-coordinate for the tangent
     * @param dy the y-coordinate for the tangent
     */
    public final void addVertexTangent
        (int index, float x, float y, float dx, float dy)
    {
        int N = vertex.length;
        int M = N + 1;
        
        if (index < 0)
            index = 0;
        else
        if (index > N)
            index = N;
        
        float[][] oldvertex  = vertex;
        float[][] oldtangent = tangent;
        
        vertex  = new float[M][2];
        tangent = new float[M][2];
        
        for (int i = 0; i < index; i++) {
            vertex[i][0]  = oldvertex[i][0];
            vertex[i][1]  = oldvertex[i][1];
            
            tangent[i][0] = oldtangent[i][0];
            tangent[i][1] = oldtangent[i][1];
        }
        
        vertex[index][0]  = x;
        vertex[index][1]  = y;
        
        tangent[index][0] = dx;
        tangent[index][1] = dy;
        
        for (int i = (index + 1); i < M; i++) {
            int j = i - 1;
            vertex[i][0]  = oldvertex[j][0];
            vertex[i][1]  = oldvertex[j][1];
            
            tangent[i][0] = oldtangent[j][0];
            tangent[i][1] = oldtangent[j][1];
        }
        
        makePath();
        
        firePropertyChange(ADD_VERTEX_TANGENT, null, null);
    }
    
    
    /**
     * <p>Adds a new vertex and tangent at the given index using
     * the given point and delta arrays and makes a new path.</p>
     *
     * <p>Conceptually, the point array should contain 2 items
     * (x, y) and the delta array 2 items (dx, dy).</p>
     *
     * <p>If index is 0, the insertion is at the front.</p>
     *
     * <p>If index is vertex.length, the insertion is at the end.</p>
     *
     * <p>Forces the given index into the range:</p>
     *
     * <pre>  0 &lt;= index &lt;= vertex.length</pre>
     *
     * <p>Does nothing if the arrays are not <code>float[2]</code>.</p>
     *
     * <p>Fires property change: ADD_VERTEX_TANGENT.</p>
     *
     * @param index the vertex/tangent index
     * @param point the array with x, y
     * @param delta the array with dx, dy
     */
    public final void addVertexTangent
        (int index, float[] point, float[] delta)
    {
        if ((point == null) || (point.length != 2))
            return;
        
        if ((delta == null) || (delta.length != 2))
            return;
        
        addVertexTangent(index, point[0], point[1], delta[0], delta[1]);
    }
    
    
    /**
     * <p>Adds a new vertex and tangent at the given index with
     * the given array of point and displacement coordinates
     * and makes a new path.</p>
     *
     * <p>Conceptually, the array should contain 4 items
     * (x, y, dx, dy).</p>
     *
     * <p>If index is 0, the insertion is at the front.</p>
     *
     * <p>If index is vertex.length, the insertion is at the end.</p>
     *
     * <p>Forces the given index into the range:</p>
     *
     * <pre>  0 &lt;= index &lt;= vertex.length</pre>
     *
     * <p>Does nothing if array is not <code>float[4]</code>.</p>
     *
     * <p>Fires property change: ADD_VERTEX_TANGENT.</p>
     *
     * @param index the vertex index
     * @param array the array with x, y, dx, dy
     */
    public final void addVertexTangent(int index, float[] array) {
        if ((array == null) || (array.length != 4))
            return;
        
        addVertexTangent(index, array[0], array[1], array[2], array[3]);
    }
    
    
    /**
     * <p>Adds a new sequence of vertices and tangents at the
     * given index using copies of the given points and deltas
     * and then makes a new path.</p>
     *
     * <p>If index is 0, the insertion is at the front.</p>
     *
     * <p>If index is vertex.length, the insertion is at the end.</p>
     *
     * <p>Forces the given index into the range:</p>
     *
     * <pre>  0 &lt;= index &lt;= vertex.length</pre>
     *
     * <p>Does nothing if the given arrays are <code>null</code>
     * or do not have the form <code>float[K][2]</code> for the
     * same K.</p>
     *
     * <p>Fires property change: ADD_VERTEX_TANGENT.</p>
     *
     * @param index  the start index
     * @param points the new vertices to add
     * @param deltas the new tangents to add
     */
    public final void addVerticesTangents
        (int index, float[][] points, float[][] deltas)
    {
        if (!FloatArray.checkArrayPair(points, deltas, 2))
            return;
        
        int K = points.length;
        
        if (K == 0)
            return;
        
        int N = vertex.length;
        int M = N + K;
        
        float[][] oldvertex  = vertex;
        float[][] oldtangent = tangent;
        
        vertex  = new float[M][2];
        tangent = new float[M][2];
        
        if (index < 0)
            index = 0;
        else
        if (index > N)
            index = N;
        
        for (int i = 0; i < index; i++) {
            vertex[i][0]  = oldvertex[i][0];
            vertex[i][1]  = oldvertex[i][1];
            
            tangent[i][0] = oldtangent[i][0];
            tangent[i][1] = oldtangent[i][1];
        }
        
        for (int i = 0; i < K; i++) {
            int j = i + index;
            vertex[j][0]  = points[i][0];
            vertex[j][1]  = points[i][1];
            
            tangent[j][0] = deltas[i][0];
            tangent[j][1] = deltas[i][1];
        }
        
        for (int i = index; i < N; i++) {
            int j = i + K;
            vertex[j][0]  = oldvertex[i][0];
            vertex[j][1]  = oldvertex[i][1];
            
            tangent[j][0] = oldtangent[i][0];
            tangent[j][1] = oldtangent[i][1];
        }
        
        makePath();
        
        firePropertyChange(ADD_VERTEX_TANGENT, null, null);
    }
    
    
    /**
     * <p>Adds a new sequence of vertices and tangents at the
     * given index extracting data from the given array of
     * type <code>float[K][4]</code> and then makes a new path.</p>
     *
     * <p>Conceptually, the each row of the array should
     * contain 4 items (x, y, dx, dy).</p>
     *
     * <p>If index is 0, the insertion is at the front.</p>
     *
     * <p>If index is vertex.length, the insertion is at the end.</p>
     *
     * <p>Forces the given index into the range:</p>
     *
     * <pre>  0 &lt;= index &lt;= vertex.length</pre>
     *
     * <p>Does nothing if the given array is <code>null</code>
     * or does not have the form <code>float[K][4]</code>.</p>
     *
     * <p>Fires property change: ADD_VERTEX_TANGENT.</p>
     *
     * @param index the start index
     * @param array the new vertices and tangents to add
     */
    public final void addVerticesTangents(int index, float[][] array)
    {
        if (!FloatArray.checkArray(array, 4))
            return;
        
        int K = array.length;
        
        if (K == 0)
            return;
        
        int N = vertex.length;
        int M = N + K;
        
        float[][] oldvertex  = vertex;
        float[][] oldtangent = tangent;
        
        vertex  = new float[M][2];
        tangent = new float[M][2];
        
        if (index < 0)
            index = 0;
        else
        if (index > N)
            index = N;
        
        for (int i = 0; i < index; i++) {
            vertex[i][0]  = oldvertex[i][0];
            vertex[i][1]  = oldvertex[i][1];
            
            tangent[i][0] = oldtangent[i][0];
            tangent[i][1] = oldtangent[i][1];
        }
        
        for (int i = 0; i < K; i++) {
            int j = i + index;
            vertex[j][0]  = array[i][0];
            vertex[j][1]  = array[i][1];
            
            tangent[j][0] = array[i][2];
            tangent[j][1] = array[i][3];
        }
        
        for (int i = index; i < N; i++) {
            int j = i + K;
            vertex[j][0]  = oldvertex[i][0];
            vertex[j][1]  = oldvertex[i][1];
            
            tangent[j][0] = oldtangent[i][0];
            tangent[j][1] = oldtangent[i][1];
        }
        
        makePath();
        
        firePropertyChange(ADD_VERTEX_TANGENT, null, null);
    }
    
    
    /**
     * <p>Appends a new vertex and tangent with the
     * given point and displacement coordinates x, y, dx, dy.</p>
     *
     * <p>Fires property change: ADD_VERTEX_TANGENT.</p>
     *
     * @param x  the x-coordinate for the vertex
     * @param y  the y-coordinate for the vertex
     * @param dx the x-coordinate for the tangent
     * @param dy the y-coordinate for the tangent
     */
    public final void appendVertexTangent
        (float x, float y, float dx, float dy)
    {
        addVertexTangent(vertex.length, x, y, dx, dy);
    }
    
    
    /**
     * <p>Appends a new vertex and tangent using
     * the given point and delta arrays and makes a new path.</p>
     *
     * <p>Conceptually, the point array should contain 2 items
     * (x, y) and the delta array 2 items (dx, dy).</p>
     *
     * <p>Does nothing if the arrays are not <code>float[2]</code>.</p>
     *
     * <p>Fires property change: ADD_VERTEX_TANGENT.</p>
     *
     * @param point the array with x, y
     * @param delta the array with dx, dy
     */
    public final void appendVertexTangent
        (float[] point, float[] delta)
    {
        addVertexTangent(vertex.length, point, delta);
    }
    
    
    /**
     * <p>Appends a new vertex and tangent with
     * the given array of point and displacement coordinates
     * and makes a new path.</p>
     *
     * <p>Conceptually, the array should contain 4 items
     * (x, y, dx, dy).</p>
     *
     * <p>Does nothing if array is not <code>float[4]</code>.</p>
     *
     * <p>Fires property change: ADD_VERTEX_TANGENT.</p>
     *
     * @param array the array with x, y, dx, dy
     */
    public final void appendVertexTangent(float[] array) {
        addVertexTangent(vertex.length, array);
    }
    
    
    /**
     * <p>Appends a new sequence of vertices and tangents
     * using copies of the given points and deltas
     * and then makes a new path.</p>
     *
     * <p>Does nothing if the given arrays are <code>null</code>
     * or do not have the form <code>float[K][2]</code> for the
     * same K.</p>
     *
     * <p>Fires property change: ADD_VERTEX_TANGENT.</p>
     *
     * @param points the new vertices to append
     * @param deltas the new tangents to append
     */
    public final void appendVerticesTangents
        (float[][] points, float[][] deltas)
    {
        addVerticesTangents(vertex.length, points, deltas);
    }
    
    
    /**
     * <p>Appends a new sequence of vertices and tangents
     * extracting data from the given array of
     * type <code>float[K][4]</code> and then makes a new path.</p>
     *
     * <p>Conceptually, the each row of the array should
     * contain 4 items (x, y, dx, dy).</p>
     *
     * <p>Does nothing if the given array is <code>null</code>
     * or does not have the form <code>float[K][4]</code>.</p>
     *
     * <p>Fires property change: ADD_VERTEX_TANGENT.</p>
     *
     * @param array the new vertices and tangents to append
     */
    public final void appendVerticesTangents(float[][] array)
    {
        addVerticesTangents(vertex.length, array);
    }
    
    
    /**
     * <p>Removes the vertex/tangent at the given index, makes
     * a new path, and returns an array with the data from the
     * removed vertex/tangent as x, y, dx, dy.</p>
     *
     * <p>If the index is not in [0, vertex.length) then removes
     * nothing and returns <code>null</code>.</p>
     *
     * <p>Fires property change: REMOVE_VERTEX_TANGENT.</p>
     *
     * @param index the vertex index
     */
    public final float[] removeVertexTangent(int index) {
        int N = vertex.length;
        int M = N - 1;
        
        if ((index < 0) || (index >= N))
            return null;
        
        float[][] oldvertex  = vertex;
        float[][] oldtangent = tangent;
        
        vertex  = new float[M][2];
        tangent = new float[M][2];
        
        for (int i = 0; i < index; i++) {
            vertex[i][0]  = oldvertex[i][0];
            vertex[i][1]  = oldvertex[i][1];
            
            tangent[i][0] = oldtangent[i][0];
            tangent[i][1] = oldtangent[i][1];
        }
        
        for (int i = (index + 1); i < N; i++) {
            int j = i - 1;
            vertex[j][0]  = oldvertex[i][0];
            vertex[j][1]  = oldvertex[i][1];
            
            tangent[j][0] = oldtangent[i][0];
            tangent[j][1] = oldtangent[i][1];
        }
        
        makePath();
        
        firePropertyChange(REMOVE_VERTEX_TANGENT, null, null);
        
        float x  = oldvertex[index][0];
        float y  = oldvertex[index][1];
        
        float dx = oldtangent[index][0];
        float dy = oldtangent[index][1];
        
        return new float[] { x, y, dx, dy };
    }
    
    
    /**
     * <p>Removes the vertices and tangents starting at the given
     * index m inclusive and ending at the given index n exclusive,
     * makes a new path, and returns an array <code>float[K][4]</code>
     * with the vertices and tangents that were removed.</p>
     *
     * <p>Conceptually, the each row of the array returned should
     * contain 4 items (x, y, dx, dy).</p>
     *
     * <p>Forces m and n into range if needed.</p>
     *
     * <p>Returns an array of length 0 if no nodes are removed.</p>
     *
     * <p>Fires property change: REMOVE_VERTEX_TANGENT.</p>
     *
     * @param m the starting index inclusive
     * @param n the ending index exclusive
     */
    public final float[][] removeVerticesTangents(int m, int n) {
        int N = vertex.length;
        
        if (m < 0)
            m = 0;
        
        if (n > N)
            n = N;
        
        int K = n - m;
        
        if (K <= 0)
            return new float[0][4];
        
        int M = N - K;
        
        float[][] oldvertex  = vertex;
        float[][] oldtangent = tangent;
        
        vertex  = new float[M][2];
        tangent = new float[M][2];
        
        float[][] result = new float[K][4];
        
        for (int i = 0; i < m; i++) {
            vertex[i][0]  = oldvertex[i][0];
            vertex[i][1]  = oldvertex[i][1];
            
            tangent[i][0] = oldtangent[i][0];
            tangent[i][1] = oldtangent[i][1];
        }
        
        for (int i = m; i < n; i++) {
            int j = i - m;
            result[j][0] = oldvertex[i][0];
            result[j][1] = oldvertex[i][1];
            
            result[j][2] = oldtangent[i][0];
            result[j][3] = oldtangent[i][1];
        }
        
        for (int i = n; i < N; i++) {
            int j = i - K;
            vertex[j][0]  = oldvertex[i][0];
            vertex[j][1]  = oldvertex[i][1];
            
            tangent[j][0] = oldtangent[i][0];
            tangent[j][1] = oldtangent[i][1];
        }
        
        makePath();
        
        firePropertyChange(REMOVE_VERTEX_TANGENT, null, null);
        
        return result;
    }
    
    
    /**
     * <p>Removes all vertices and tangents, makes a new path,
     * and returns an array <code>float[N][4]</code> with the
     * vertices and tangents that were removed.</p>
     *
     * <p>Conceptually, the each row of the array returned should
     * contain 4 items (x, y, dx, dy).</p>
     *
     * <p>Fires property change: REMOVE_VERTEX_TANGENT.</p>
     */
    public final float[][] removeAllVerticesTangents() {
        return removeVerticesTangents(0, vertex.length);
    }
    
}
