// SPDX-License-Identifier: GPL-2.0-or-later
/** \file
   A group of classes and functions for manipulating mesh gradients.

   A mesh is made up of an array of patches. Each patch has four sides and four corners. The sides can
   be shared between two patches and the corners between up to four.

   The order of the points for each side always goes from left to right or top to bottom.
   For sides 2 and 3 the points must be reversed when used (as in calls to cairo functions).

   Two patches: (C=corner, S=side, H=handle, T=tensor)

                         C0   H1  H2 C1 C0 H1  H2  C1
                          + ---------- + ---------- +
                          |     S0     |     S0     |
                       H1 |  T0    T1  |H1 T0   T1  | H1
                          |S3        S1|S3        S1|
                       H2 |  T3    T2  |H2 T3   T2  | H2
                          |     S2     |     S2     |
                          + ---------- + ---------- +
                         C3   H1  H2 C2 C3 H1  H2   C2

   The mesh is stored internally as an array of nodes that includes the tensor nodes.

   Note: This code uses tensor points which are not part of the SVG2 plan at the moment.
   Including tensor points was motivated by a desire to experiment with their usefulness
   in smoothing color transitions. There doesn't seem to be much advantage for that
   purpose. However including them internally allows for storing all the points in
   an array which simplifies things like inserting new rows or columns.
*/

/*
 * Authors:
 *   Tavmjong Bah <tavmjong@free.fr>
 *
 * Copyright  (C) 2012, 2015 Tavmjong Bah
 *
 * Released under GNU GPL v2+, read the file 'COPYING' for more information.
 */

#include <glibmm.h>
#include <set>

// For color picking
#include "display/drawing.h"
#include "display/drawing-context.h"
#include "display/cairo-utils.h"
#include "document.h"
#include "sp-root.h"

#include "sp-mesh-gradient.h"
#include "sp-mesh-array.h"
#include "sp-mesh-row.h"
#include "sp-mesh-patch.h"
#include "sp-stop.h"
#include "display/curve.h"

// For new mesh creation
#include "preferences.h"
#include "sp-ellipse.h"
#include "sp-star.h"

// For writing color/opacity to style
#include "svg/css-ostringstream.h"

// For default color
#include "style.h"
#include "svg/svg-color.h"


// Includes bezier-curve.h, ray.h, crossing.h
#include "2geom/line.h"

#include "xml/repr.h"
#include <cmath>
#include <algorithm>

enum { ROW, COL };

SPMeshPatchI::SPMeshPatchI( std::vector<std::vector< SPMeshNode* > > * n, int r, int c ) {

    nodes = n;
    row = r*3; // Convert from patch array to node array
    col = c*3;

    guint i = 0;
    if( row != 0 ) i = 1;
    for( ; i < 4; ++i ) {
        if( nodes->size() < row+i+1 ) {
            std::vector< SPMeshNode* > row;
            nodes->push_back( row );
        }

        guint j = 0;
        if( col != 0 ) j = 1;
        for( ; j < 4; ++j ) {
            if( (*nodes)[row+i].size() < col+j+1 ){
                SPMeshNode* node = new SPMeshNode;
                // Ensure all nodes know their type.
                node->node_type = MG_NODE_TYPE_HANDLE;
                if( (i == 0 || i == 3) && (j == 0 || j == 3 ) ) node->node_type = MG_NODE_TYPE_CORNER;
                if( (i == 1 || i == 2) && (j == 1 || j == 2 ) ) node->node_type = MG_NODE_TYPE_TENSOR;
                (*nodes)[row+i].push_back( node );
            }
        }
    }
}

/**
   Returns point for side in proper order for patch
*/
Geom::Point SPMeshPatchI::getPoint( guint s, guint pt ) {

    assert( s < 4 );
    assert( pt < 4 );

    Geom::Point p;
    switch ( s ) {
        case 0:
            p = (*nodes)[ row      ][ col+pt   ]->p;
            break;
        case 1:
            p = (*nodes)[ row+pt   ][ col+3    ]->p;
            break;
        case 2:
            p = (*nodes)[ row+3    ][ col+3-pt ]->p;
            break;
        case 3:
            p = (*nodes)[ row+3-pt ][ col      ]->p;
            break;
    }
    return p;

};

/**
   Returns vector of points for a side in proper order for a patch (clockwise order).
*/
std::vector< Geom::Point > SPMeshPatchI::getPointsForSide( guint i ) {

    assert( i < 4 );

    std::vector< Geom::Point> points;
    points.push_back( getPoint( i, 0 ) );
    points.push_back( getPoint( i, 1 ) );
    points.push_back( getPoint( i, 2 ) );
    points.push_back( getPoint( i, 3 ) );
    return points;
};


/**
   Set point for side in proper order for patch
*/
void SPMeshPatchI::setPoint( guint s, guint pt, Geom::Point p, bool set ) {

    assert( s < 4 );
    assert( pt < 4 );

    NodeType node_type = MG_NODE_TYPE_CORNER;
    if( pt == 1 || pt == 2 ) node_type = MG_NODE_TYPE_HANDLE;

    // std::cout << "SPMeshPatchI::setPoint: s: " << s 
    //           << " pt: " << pt
    //           << " p: " << p
    //           << " node_type: " << node_type
    //           << " set: " << set
    //           << " row: " << row
    //           << " col: " << col << std::endl;
    switch ( s ) {
        case 0:
            (*nodes)[ row      ][ col+pt   ]->p = p;
            (*nodes)[ row      ][ col+pt   ]->set = set;
            (*nodes)[ row      ][ col+pt   ]->node_type = node_type;
            break;
        case 1:
            (*nodes)[ row+pt   ][ col+3    ]->p = p;
            (*nodes)[ row+pt   ][ col+3    ]->set = set;
            (*nodes)[ row+pt   ][ col+3    ]->node_type = node_type;
            break;
        case 2:
            (*nodes)[ row+3    ][ col+3-pt ]->p = p;
            (*nodes)[ row+3    ][ col+3-pt ]->set = set;
            (*nodes)[ row+3    ][ col+3-pt ]->node_type = node_type;
            break;
        case 3:
            (*nodes)[ row+3-pt ][ col      ]->p = p;
            (*nodes)[ row+3-pt ][ col      ]->set = set;
            (*nodes)[ row+3-pt ][ col      ]->node_type = node_type;
            break;
    }

};

/**
   Get path type for side (stored in handle nodes).
*/
gchar SPMeshPatchI::getPathType( guint s ) {

    assert( s < 4 );

    gchar type = 'x';

    switch ( s ) {
        case 0:
            type = (*nodes)[ row   ][ col+1 ]->path_type;
            break;
        case 1:
            type = (*nodes)[ row+1 ][ col+3 ]->path_type;
            break;
        case 2:
            type = (*nodes)[ row+3 ][ col+2 ]->path_type;
            break;
        case 3:
            type = (*nodes)[ row+2 ][ col   ]->path_type;
            break;
    }

    return type;
};

/**
   Set path type for side (stored in handle nodes).
*/
void SPMeshPatchI::setPathType( guint s, gchar t ) {

    assert( s < 4 );

    switch ( s ) {
        case 0:
            (*nodes)[ row   ][ col+1 ]->path_type = t;
            (*nodes)[ row   ][ col+2 ]->path_type = t;
            break;
        case 1:
            (*nodes)[ row+1 ][ col+3 ]->path_type = t;
            (*nodes)[ row+2 ][ col+3 ]->path_type = t;
            break;
        case 2:
            (*nodes)[ row+3 ][ col+1 ]->path_type = t;
            (*nodes)[ row+3 ][ col+2 ]->path_type = t;
            break;
        case 3:
            (*nodes)[ row+1 ][ col   ]->path_type = t;
            (*nodes)[ row+2 ][ col   ]->path_type = t;
            break;
    }

};

/**
   Set tensor control point for "corner" i.
 */
void SPMeshPatchI::setTensorPoint( guint i, Geom::Point p ) {

    assert( i < 4 );
    switch ( i ) {
        case 0:
            (*nodes)[ row + 1 ][ col + 1 ]->p = p;
            (*nodes)[ row + 1 ][ col + 1 ]->set = true;
            (*nodes)[ row + 1 ][ col + 1 ]->node_type = MG_NODE_TYPE_TENSOR;
            break;
        case 1:
            (*nodes)[ row + 1 ][ col + 2 ]->p = p;
            (*nodes)[ row + 1 ][ col + 2 ]->set = true;
            (*nodes)[ row + 1 ][ col + 2 ]->node_type = MG_NODE_TYPE_TENSOR;
            break;
        case 2:
            (*nodes)[ row + 2 ][ col + 2 ]->p = p;
            (*nodes)[ row + 2 ][ col + 2 ]->set = true;
            (*nodes)[ row + 2 ][ col + 2 ]->node_type = MG_NODE_TYPE_TENSOR;
            break;
        case 3:
            (*nodes)[ row + 2 ][ col + 1 ]->p = p;
            (*nodes)[ row + 2 ][ col + 1 ]->set = true;
            (*nodes)[ row + 2 ][ col + 1 ]->node_type = MG_NODE_TYPE_TENSOR;
            break;
    }
}

/**
   Return if any tensor control point is set.
 */
bool SPMeshPatchI::tensorIsSet() {
    for( guint i = 0; i < 4; ++i ) {
        if( tensorIsSet( i ) ) {
            return true;
        }
    }
    return false;
}

/**
   Return if tensor control point for "corner" i is set.
 */
bool SPMeshPatchI::tensorIsSet( unsigned int i ) {

    assert( i < 4 );

    bool set = false;
    switch ( i ) {
        case 0:
            set = (*nodes)[ row + 1 ][ col + 1 ]->set;
            break;
        case 1:
            set = (*nodes)[ row + 1 ][ col + 2 ]->set;
            break;
        case 2:
            set = (*nodes)[ row + 2 ][ col + 2 ]->set;
            break;
        case 3:
            set = (*nodes)[ row + 2 ][ col + 1 ]->set;
            break;
    }
    return set;
}

/**
   Return tensor control point for "corner" i.
   If not set, returns calculated (Coons) point.
 */
Geom::Point SPMeshPatchI::getTensorPoint( guint k ) {

    assert( k < 4 );

    guint i = 0;
    guint j = 0;
    

    switch ( k ) {
        case 0:
            i = 1;
            j = 1;
            break;
        case 1:
            i = 1;
            j = 2;
            break;
        case 2:
            i = 2;
            j = 2;
            break;
        case 3:
            i = 2;
            j = 1;
            break;
    }

    Geom::Point p;
    if( (*nodes)[ row + i ][ col + j ]->set ) {
        p = (*nodes)[ row + i ][ col + j ]->p;
    } else {
        p = coonsTensorPoint( k );
    }
    return p;
}

/**
   Find default tensor point (equivalent point to Coons Patch).
   Formulas defined in PDF spec.
   Equivalent to 1/3 of side length from corner for square patch.
 */
Geom::Point SPMeshPatchI::coonsTensorPoint( guint i ) {

    Geom::Point t;
    Geom::Point p[4][4]; // Points in PDF notation

    p[0][0] = getPoint( 0, 0 );
    p[0][1] = getPoint( 0, 1 );
    p[0][2] = getPoint( 0, 2 );
    p[0][3] = getPoint( 0, 3 );
    p[1][0] = getPoint( 3, 2 );
    p[1][3] = getPoint( 1, 1 );
    p[2][0] = getPoint( 3, 1 );
    p[2][3] = getPoint( 1, 2 );
    p[3][0] = getPoint( 2, 3 );
    p[3][1] = getPoint( 2, 2 );
    p[3][2] = getPoint( 2, 1 );
    p[3][3] = getPoint( 2, 0 );

    switch ( i ) {
        case 0:
            t = ( -4.0 *   p[0][0] +
                   6.0 * ( p[0][1] + p[1][0] ) +
                  -2.0 * ( p[0][3] + p[3][0] ) +
                   3.0 * ( p[3][1] + p[1][3] ) +
                  -1.0 *   p[3][3] ) / 9.0;
            break;

        case 1:
            t = ( -4.0 *   p[0][3] +
                   6.0 * ( p[0][2] + p[1][3] ) +
                  -2.0 * ( p[0][0] + p[3][3] ) +
                   3.0 * ( p[3][2] + p[1][0] ) +
                  -1.0 *   p[3][0] ) / 9.0;
            break;

        case 2:
            t = ( -4.0 *   p[3][3] +
                   6.0 * ( p[3][2] + p[2][3] ) +
                  -2.0 * ( p[3][0] + p[0][3] ) +
                   3.0 * ( p[0][2] + p[2][0] ) +
                  -1.0 *   p[0][0] ) / 9.0;
            break;

        case 3:
            t = ( -4.0 *   p[3][0] +
                   6.0 * ( p[3][1] + p[2][0] ) +
                  -2.0 * ( p[3][3] + p[0][0] ) +
                   3.0 * ( p[0][1] + p[2][3] ) +
                  -1.0 *   p[0][3] ) / 9.0;
            break;

        default:

            g_warning( "Impossible!" );

    }
    return t;
}

/**
   Update default values for handle and tensor nodes.
*/
void SPMeshPatchI::updateNodes() {

    // std::cout << "SPMeshPatchI::updateNodes: " << row << "," << col << std::endl;
    // Handles first (tensors require update handles).
    for( guint i = 0; i < 4; ++i ) {
        for( guint j = 0; j < 4; ++j ) {
            if( (*nodes)[ row + i ][ col + j ]->set == false ) {

                if( (*nodes)[ row + i ][ col + j ]->node_type == MG_NODE_TYPE_HANDLE ) {
                    
                    // If a handle is not set it is because the side is a line.
                    // Set node points 1/3 of the way between corners.

                    if( i == 0 || i == 3 ) {
                        Geom::Point p0 = ( (*nodes)[ row + i ][ col     ]->p );
                        Geom::Point p3 = ( (*nodes)[ row + i ][ col + 3 ]->p );
                        Geom::Point dp = (p3 - p0)/3.0;
                        if( j == 2 ) dp *= 2.0;
                        (*nodes)[ row + i ][ col + j ]->p = p0 + dp;
                    }

                    if( j == 0 || j == 3 ) {
                        Geom::Point p0 = ( (*nodes)[ row     ][ col + j ]->p );
                        Geom::Point p3 = ( (*nodes)[ row + 3 ][ col + j ]->p );
                        Geom::Point dp = (p3 - p0)/3.0;
                        if( i == 2 ) dp *= 2.0;
                        (*nodes)[ row + i ][ col + j ]->p = p0 + dp;
                    }
                }
            }
        }
    }

    // Update tensor nodes
    for( guint i = 1; i < 3; ++i ) {
        for( guint j = 1; j < 3; ++j ) {
            if( (*nodes)[ row + i ][ col + j ]->set == false ) {

                (*nodes)[ row + i ][ col + j ]->node_type = MG_NODE_TYPE_TENSOR;

                guint t = 0;
                if( i == 1 && j == 2 ) t = 1;
                if( i == 2 && j == 2 ) t = 2;
                if( i == 2 && j == 1 ) t = 3;
                (*nodes)[ row + i ][ col + j ]->p = coonsTensorPoint( t );
                // std::cout << "Update node: " << i << ", " << j << " " << coonsTensorPoint( t ) << std::endl;

            }
        }
    }
}

/**
   Return color for corner of patch.
*/
SPColor SPMeshPatchI::getColor( guint i ) {

    assert( i < 4 );

    SPColor color;
    switch ( i ) {
        case 0:
            color = (*nodes)[ row   ][ col   ]->color;
            break;
        case 1:
            color = (*nodes)[ row   ][ col+3 ]->color;
            break;
        case 2:
            color = (*nodes)[ row+3 ][ col+3 ]->color;
            break;
        case 3:
            color = (*nodes)[ row+3 ][ col   ]->color;
            break;

    }

    return color;

};

/**
   Set color for corner of patch.
*/
void SPMeshPatchI::setColor( guint i, SPColor color ) {

    assert( i < 4 );

    switch ( i ) {
        case 0:
            (*nodes)[ row   ][ col   ]->color = color;
            break;                       
        case 1:                          
            (*nodes)[ row   ][ col+3 ]->color = color;
            break;                       
        case 2:                          
            (*nodes)[ row+3 ][ col+3 ]->color = color;
            break;                       
        case 3:                          
            (*nodes)[ row+3 ][ col   ]->color = color;
            break;
    }
};

/**
   Return opacity for corner of patch.
*/
gdouble SPMeshPatchI::getOpacity( guint i ) {

    assert( i < 4 );

    gdouble opacity = 0.0;
    switch ( i ) {
        case 0:
            opacity = (*nodes)[ row   ][ col   ]->opacity;
            break;
        case 1:
            opacity = (*nodes)[ row   ][ col+3 ]->opacity;
            break;
        case 2:
            opacity = (*nodes)[ row+3 ][ col+3 ]->opacity;
            break;
        case 3:
            opacity = (*nodes)[ row+3 ][ col   ]->opacity;
            break;
    }

    return opacity;
};


/**
   Set opacity for corner of patch.
*/
void SPMeshPatchI::setOpacity( guint i, gdouble opacity ) {

    assert( i < 4 );

    switch ( i ) {
        case 0:
            (*nodes)[ row   ][ col   ]->opacity = opacity;
            break;                         
        case 1:                            
            (*nodes)[ row   ][ col+3 ]->opacity = opacity;
            break;                         
        case 2:                            
            (*nodes)[ row+3 ][ col+3 ]->opacity = opacity;
            break;                         
        case 3:                            
            (*nodes)[ row+3 ][ col   ]->opacity = opacity;
            break;

    }

};


/**
   Return stop pointer for corner of patch.
*/
SPStop* SPMeshPatchI::getStopPtr( guint i ) {

    assert( i < 4 );

    SPStop* stop = nullptr;
    switch ( i ) {
        case 0:
            stop = (*nodes)[ row   ][ col   ]->stop;
            break;
        case 1:
            stop = (*nodes)[ row   ][ col+3 ]->stop;
            break;
        case 2:
            stop = (*nodes)[ row+3 ][ col+3 ]->stop;
            break;
        case 3:
            stop = (*nodes)[ row+3 ][ col   ]->stop;
            break;
    }

    return stop;
};


/**
   Set stop pointer for corner of patch.
*/
void SPMeshPatchI::setStopPtr( guint i, SPStop* stop ) {

    assert( i < 4 );

    switch ( i ) {
        case 0:
            (*nodes)[ row   ][ col   ]->stop = stop;
            break;                         
        case 1:                            
            (*nodes)[ row   ][ col+3 ]->stop = stop;
            break;                         
        case 2:                            
            (*nodes)[ row+3 ][ col+3 ]->stop = stop;
            break;                         
        case 3:                            
            (*nodes)[ row+3 ][ col   ]->stop = stop;
            break;

    }

};


SPMeshNodeArray::SPMeshNodeArray( SPMeshGradient *mg ) {

    read( mg );

};


// Copy constructor
SPMeshNodeArray::SPMeshNodeArray( const SPMeshNodeArray& rhs ) :
    nodes(rhs.nodes) // This only copies the pointers but it does size the vector of vectors.
{

    built = false;
    mg = nullptr;
    draggers_valid = false;

    for( unsigned i=0; i < nodes.size(); ++i ) {
        for( unsigned j=0; j < nodes[i].size(); ++j ) {
            nodes[i][j] = new SPMeshNode( *rhs.nodes[i][j] ); // Copy data.
        }
    }
};


// Copy assignment operator
SPMeshNodeArray& SPMeshNodeArray::operator=( const SPMeshNodeArray& rhs ) {

    if( this == &rhs ) return *this;

    clear(); // Clear any existing array.

    built = false;
    mg = nullptr;
    draggers_valid = false;

    nodes = rhs.nodes; // This only copies the pointers but it does size the vector of vectors.

    for( unsigned i=0; i < nodes.size(); ++i ) {
        for( unsigned j=0; j < nodes[i].size(); ++j ) {
            nodes[i][j] = new SPMeshNode( *rhs.nodes[i][j] ); // Copy data.
        }
    }
    
    return *this;
};

// Fill array with data from mesh objects.
// Returns true of array's dimensions unchanged.
bool SPMeshNodeArray::read( SPMeshGradient *mg_in ) {

    mg = mg_in;
    auto mg_array = cast<SPMeshGradient>(mg->getArray());
    if (!mg_array) {
        std::cerr << "SPMeshNodeArray::read: No mesh array!" << std::endl;
        return false;
    }
    // std::cout << "SPMeshNodeArray::read: " << mg_in << "  array: " << mg_array << std::endl;

    // Count rows and columns, if unchanged reuse array to keep draggers valid.
    unsigned cols = 0;
    unsigned rows = 0;
    for (auto& ro: mg_array->children) {
        if (is<SPMeshrow>(&ro)) {
            ++rows;
            if (rows == 1 ) {
                for (auto& po: ro.children) {
                    if (is<SPMeshpatch>(&po)) {
                        ++cols;
                    }
                }
            }
        }
    }
    bool same_size = true;
    if (cols != patch_columns() || rows != patch_rows() ) {
        // Draggers will be invalidated.
        same_size = false;
        clear();
        draggers_valid = false;
    }

    Geom::Point current_p( mg->x.computed, mg->y.computed );
    // std::cout << "SPMeshNodeArray::read: p: " << current_p << std::endl;

    guint max_column = 0;
    guint irow = 0; // Corresponds to top of patch being read in.
    for (auto& ro: mg_array->children) {

        if (is<SPMeshrow>(&ro)) {

            guint icolumn = 0; // Corresponds to left of patch being read in.
            for (auto& po: ro.children) {

                if (is<SPMeshpatch>(&po)) {

                    auto patch = cast<SPMeshpatch>(&po);

                    // std::cout << "SPMeshNodeArray::read: row size: " << nodes.size() << std::endl;
                    SPMeshPatchI new_patch( &nodes, irow, icolumn ); // Adds new nodes.
                    // std::cout << "                          after: " << nodes.size() << std::endl;

                    gint istop = 0;

                    // Only 'top' side defined for first row.
                    if( irow != 0 ) ++istop;

                    for (auto& so: po.children) {
                        if (is<SPStop>(&so)) {

                            if( istop > 3 ) {
                                // std::cout << " Mesh Gradient: Too many stops: " << istop << std::endl;
                                break;
                            }

                            auto stop = cast<SPStop>(&so);

                            // Handle top of first row.
                            if( istop == 0 && icolumn == 0 ) {
                                // First patch in mesh.
                                new_patch.setPoint( 0, 0, current_p );
                            }
                            // First point is always already defined by previous side (stop).
                            current_p = new_patch.getPoint( istop, 0 );

                            // If side closes patch, then we read one less point.
                            bool closed = false;
                            if( icolumn == 0 && istop == 3 ) closed = true;
                            if( icolumn  > 0 && istop == 2 ) closed = true;


                            // Copy path and then replace commas by spaces so we can use stringstream to parse
                            std::string path_string = stop->path_string.raw();
                            std::replace(path_string.begin(),path_string.end(),',',' ');

                            // std::cout << "    path_string: " << path_string << std::endl;
                            // std::cout << "    current_p: " << current_p << std::endl;

                            std::stringstream os( path_string );

                            // Determine type of path
                            char path_type;
                            os >> path_type;
                            new_patch.setPathType( istop, path_type );

                            gdouble x, y;
                            Geom::Point p, dp;
                            guint max;
                            switch ( path_type ) {
                                case 'l':
                                    if( !closed ) {
                                        os >> x >> y;
                                        if( !os.fail() ) {
                                            dp = Geom::Point( x, y ); 
                                            new_patch.setPoint( istop, 3, current_p + dp );
                                        } else {
                                            std::cerr << "Failed to read l" << std::endl;
                                        }
                                    }
                                    // To facilitate some side operations, set handles to 1/3 and
                                    // 2/3 distance between corner points but flag as unset.
                                    p = new_patch.getPoint( istop, 3 );
                                    dp = (p - current_p)/3.0;  // Calculate since may not be set if closed.
                                    // std::cout << "      istop: " << istop
                                    //           << "  dp: " << dp
                                    //           << "  p: " << p
                                    //           << " current_p: " << current_p
                                    //           << std::endl;
                                    new_patch.setPoint( istop, 1, current_p + dp,       false );
                                    new_patch.setPoint( istop, 2, current_p + 2.0 * dp, false );
                                    break;
                                case 'L':
                                    if( !closed ) {
                                        os >> x >> y;
                                        if( !os.fail() ) {
                                            p = Geom::Point( x, y );
                                            new_patch.setPoint( istop, 3, p );
                                        } else {
                                            std::cerr << "Failed to read L" << std::endl;
                                        }
                                    }
                                    // To facilitate some side operations, set handles to 1/3 and
                                    // 2/3 distance between corner points but flag as unset.
                                    p = new_patch.getPoint( istop, 3 );
                                    dp = (p - current_p)/3.0;
                                    new_patch.setPoint( istop, 1, current_p + dp,       false );
                                    new_patch.setPoint( istop, 2, current_p + 2.0 * dp, false );
                                    break;
                                case 'c':
                                    max = 4;
                                    if( closed ) max = 3;
                                    for( guint i = 1; i < max; ++i ) {
                                        os >> x >> y;
                                        if( !os.fail() ) {
                                            p = Geom::Point( x, y );
                                            p += current_p;
                                            new_patch.setPoint( istop, i, p );
                                        } else {
                                            std::cerr << "Failed to read c: " << i << std::endl;
                                        }
                                    }
                                    break;
                                case 'C':
                                    max = 4;
                                    if( closed ) max = 3;
                                    for( guint i = 1; i < max; ++i ) {
                                        os >> x >> y;
                                        if( !os.fail() ) {
                                            p = Geom::Point( x, y );
                                            new_patch.setPoint( istop, i, p );
                                        } else {
                                            std::cerr << "Failed to read C: " << i << std::endl;
                                        }
                                    }
                                    break;
                                default:
                                    // should not reach
                                    std::cerr << "Path Error: unhandled path type: " << path_type << std::endl;
                            }
                            current_p = new_patch.getPoint( istop, 3 );

                            // Color
                            if( (istop == 0 && irow == 0 && icolumn > 0) || (istop == 1 && irow > 0 ) ) {
                                // skip 
                            } else {
                                SPColor color   = stop->getColor();
                                double opacity  = stop->getOpacity();
                                new_patch.setColor( istop, color );
                                new_patch.setOpacity( istop, opacity );
                                new_patch.setStopPtr( istop, stop );
                            }
                            ++istop;
                        }
                    } // Loop over stops

                    // Read in tensor string after stops since tensor nodes defined relative to corner nodes.

                    // Copy string and then replace commas by spaces so we can use stringstream to parse XXXX
                    if( patch->tensor_string ) {
                        std::string tensor_string = patch->tensor_string->raw();
                        std::replace(tensor_string.begin(),tensor_string.end(),',',' ');

                        // std::cout << "    tensor_string: " << tensor_string << std::endl;

                        std::stringstream os( tensor_string );
                        for( guint i = 0; i < 4; ++i ) {
                            double x = 0.0;
                            double y = 0.0;
                            os >> x >> y;
                            if( !os.fail() ) {
                                new_patch.setTensorPoint( i, new_patch.getPoint( i, 0 ) + Geom::Point( x, y ) );
                            } else {
                                std::cerr << "Failed to read p: " << i << std::endl;
                                break;
                            }
                        }
                    }
                    ++icolumn;
                    if( max_column < icolumn ) max_column = icolumn;
                }
            }
            ++irow;
        }
    }

    // Insure we have a true array.
    for(auto & node : nodes) {
        node.resize( max_column * 3 + 1 );
    }

    // Set node edge.
    for( guint i = 0; i < nodes.size(); ++i ) {
        for( guint j = 0; j < nodes[i].size(); ++j ) {
            nodes[i][j]->node_edge = MG_NODE_EDGE_NONE;
            if( i == 0 )                    nodes[i][j]->node_edge |= MG_NODE_EDGE_TOP;
            if( i == nodes.size() - 1 )     nodes[i][j]->node_edge |= MG_NODE_EDGE_BOTTOM;
            if( j == 0 )                    nodes[i][j]->node_edge |= MG_NODE_EDGE_RIGHT;
            if( j == nodes[i].size() - 1 )  nodes[i][j]->node_edge |= MG_NODE_EDGE_LEFT;
        }
    }

    // std::cout << "SPMeshNodeArray::Read: result:" << std::endl;
    // print();

    built = true;

    return same_size;
};

/**
   Write repr using our array.
*/
void SPMeshNodeArray::write( SPMeshGradient *mg ) {

    // std::cout << "SPMeshNodeArray::write: entrance:" << std::endl;
    // print();
    using Geom::X;
    using Geom::Y;

    auto mg_array = cast<SPMeshGradient>(mg->getArray());
    if (!mg_array) {
        // std::cerr << "SPMeshNodeArray::write: missing patches!" << std::endl;
        mg_array = mg;
    }

    // First we must delete reprs for old mesh rows and patches. We only need to call the
    // deleteObject() method, which in turn calls sp_repr_unparent. Since iterators do not play
    // well with boost::intrusive::list (which ChildrenList derive from) we need to iterate over a
    // copy of the pointers to the objects.
    std::vector<SPObject*> children_pointers;
    for (auto& row : mg_array->children) {
        children_pointers.push_back(&row);
    }

    for (auto i : children_pointers) {
        i->deleteObject();
    }

    // Now we build new reprs
    Inkscape::XML::Node *mesh = mg->getRepr();
    Inkscape::XML::Node *mesh_array = mg_array->getRepr();

    SPMeshNodeArray* array = &(mg_array->array);
    SPMeshPatchI patch0( &(array->nodes), 0, 0 );
    Geom::Point current_p = patch0.getPoint( 0, 0 ); // Side 0, point 0

    mesh->setAttributeSvgDouble("x", current_p[X] );
    mesh->setAttributeSvgDouble("y", current_p[Y] );

    Geom::Point current_p2( mg->x.computed, mg->y.computed );

    Inkscape::XML::Document *xml_doc = mesh->document();
    guint rows = array->patch_rows();
    for( guint i = 0; i < rows; ++i ) {

        // Write row
        Inkscape::XML::Node *row = xml_doc->createElement("svg:meshrow");
        mesh_array->appendChild( row );  // No attributes

        guint columns = array->patch_columns();
        for( guint j = 0; j < columns; ++j ) {

            // Write patch
            Inkscape::XML::Node *patch = xml_doc->createElement("svg:meshpatch");

            SPMeshPatchI patchi( &(array->nodes), i, j );

            // Add tensor
            if( patchi.tensorIsSet() ) {

                std::stringstream is;

                for( guint k = 0; k < 4; ++k ) {
                    Geom::Point p = patchi.getTensorPoint( k ) - patchi.getPoint( k, 0 );
                    is << p[X] << "," << p[Y];
                    if( k < 3 ) is << " ";
                }

                patch->setAttribute("tensor", is.str());
                // std::cout << "  SPMeshNodeArray::write: tensor: " << is.str() << std::endl;
            }

            row->appendChild( patch );

            // Write sides
            for( guint k = 0; k < 4; ++k ) {

                // Only first row has top stop
                if( k == 0 && i != 0 ) continue;

                // Only first column has left stop
                if( k == 3 && j != 0 ) continue;

                Inkscape::XML::Node *stop = xml_doc->createElement("svg:stop");

                // Add path
                std::stringstream is;
                char path_type = patchi.getPathType( k ); 
                is << path_type;

                std::vector< Geom::Point> p = patchi.getPointsForSide( k );
                current_p = patchi.getPoint( k, 0 );

                switch ( path_type ) {
                    case 'l':
                        is << " "
                           << ( p[3][X] - current_p[X] ) << "," 
                           << ( p[3][Y] - current_p[Y] );
                        break;
                    case 'L':
                        is << " "
                           << p[3][X] << "," 
                           << p[3][Y];
                        break;
                    case 'c':
                        is << " "
                           << ( p[1][X] - current_p[X] ) << "," 
                           << ( p[1][Y] - current_p[Y] ) << "  "
                           << ( p[2][X] - current_p[X] ) << "," 
                           << ( p[2][Y] - current_p[Y] ) << "  "
                           << ( p[3][X] - current_p[X] ) << "," 
                           << ( p[3][Y] - current_p[Y] );
                        break;
                    case 'C':
                        is << " "
                           << p[1][X] << "," 
                           << p[1][Y] << "  "
                           << p[2][X] << "," 
                           << p[2][Y] << "  "
                           << p[3][X] << "," 
                           << p[3][Y];
                        break;
                    case 'z':
                    case 'Z':
                        std::cerr << "SPMeshNodeArray::write(): bad path type" << path_type << std::endl;
                        break;
                    default:
                        std::cerr << "SPMeshNodeArray::write(): unhandled path type" << path_type << std::endl;
                }
                stop->setAttribute("path", is.str());
                // std::cout << "SPMeshNodeArray::write: path:  " << is.str().c_str() << std::endl;
                // Add stop-color
                if( ( k == 0 && i == 0 && j == 0 ) ||
                    ( k == 1 && i == 0           ) ||
                    ( k == 2                     ) ||
                    ( k == 3 &&           j == 0 ) ) {

                    // Why are we setting attribute and not style?
                    //stop->setAttribute("stop-color",   patchi.getColor(k).toString() );
                    //stop->setAttribute("stop-opacity", patchi.getOpacity(k) );

                    Inkscape::CSSOStringStream os;
                    os << "stop-color:" << patchi.getColor(k).toString() << ";stop-opacity:" << patchi.getOpacity(k);
                    stop->setAttribute("style", os.str());
                }
                patch->appendChild( stop );
            }
        }
    }
}

/**
 * Find default color based on colors in existing fill.
 */
static SPColor default_color( SPItem *item ) {

    SPColor color( 0.5, 0.0, 0.5 );

    if ( item->style ) {
        SPIPaint const &paint = ( item->style->fill ); // Could pick between style.fill/style.stroke
        if ( paint.isColor() ) {
            color = paint.value.color;
        } else if ( paint.isPaintserver() ) {
            auto *server = item->style->getFillPaintServer();
            auto gradient = cast<SPGradient>(server);
            if (gradient && gradient->getVector()) {
                SPStop *firstStop = gradient->getVector()->getFirstStop();
                if ( firstStop ) {
                    color = firstStop->getColor();
                }
            }
        }
    } else {
        std::cerr << " SPMeshNodeArray: default_color(): No style" << std::endl;
    }

    return color;
}

/**
   Create a default mesh.
*/
void SPMeshNodeArray::create( SPMeshGradient *mg, SPItem *item, Geom::OptRect bbox ) {

    // std::cout << "SPMeshNodeArray::create: Entrance" << std::endl;

    if( !bbox ) {
        // Set default size to bounding box if size not given.
        std::cerr << "SPMeshNodeArray::create(): bbox empty" << std::endl;
        bbox = item->geometricBounds();

        if( !bbox ) {
            std::cerr << "SPMeshNodeArray::create: ERROR: No bounding box!" << std::endl;
            return;
        }
    }

    Geom::Coord const width = bbox->dimensions()[Geom::X];
    Geom::Coord const height = bbox->dimensions()[Geom::Y];
    Geom::Point       center = bbox->midpoint();

    // Must keep repr and array in sync. We have two choices:
    //  Build the repr first and then "read" it.
    //  Construct the array and then "write" it.
    // We'll do the second.

    // Remove any existing mesh.  We could choose to simply scale an existing mesh...
    //clear();

    // We get called twice when a new mesh is created...WHY?
    //  return if we've already constructed the mesh.
    if( !nodes.empty() ) return;

    // Set 'gradientUnits'. Our calculations assume "userSpaceOnUse".
    Inkscape::XML::Node *repr = mg->getRepr();
    repr->setAttribute("gradientUnits", "userSpaceOnUse");

    // Get default color
    SPColor color = default_color( item );

    // Set some corners to white so we can see the mesh.
    SPColor white( 1.0, 1.0, 1.0 );
    if (color == white) {
        // If default color is white, set other color to black.
        white = SPColor( 0.0, 0.0, 0.0 );
    }

    // Get preferences
    Inkscape::Preferences *prefs = Inkscape::Preferences::get();
    guint prows = prefs->getInt("/tools/mesh/mesh_rows", 1);
    guint pcols = prefs->getInt("/tools/mesh/mesh_cols", 1);

    SPMeshGeometry mesh_type =
        (SPMeshGeometry) prefs->getInt("/tools/mesh/mesh_geometry", SP_MESH_GEOMETRY_NORMAL);

    if( mesh_type == SP_MESH_GEOMETRY_CONICAL ) {

        // Conical gradient.. for any shape/path using geometric bounding box.

        gdouble rx = width/2.0;
        gdouble ry = height/2.0;

        // Start and end angles
        gdouble start = 0.0;
        gdouble end   = 2.0 * M_PI;

        if ( is<SPStar>( item ) ) {
            // But if it is a star... use star parameters!
            auto star = cast<SPStar>( item );
            center = star->center;
            rx = star->r[0];
            ry = star->r[0];
            start = star->arg[0];
            end   = start + 2.0 * M_PI;
        }

        if ( is<SPGenericEllipse>( item ) ) {
            // For arcs use set start/stop
            auto arc = cast<SPGenericEllipse>( item );
            center[Geom::X] = arc->cx.computed;
            center[Geom::Y] = arc->cy.computed;
            rx = arc->rx.computed;
            ry = arc->ry.computed;
            start = arc->start;
            end   = arc->end;
            if( end <= start ) {
                end += 2.0 * M_PI;
            }
        }

        // std::cout << " start: " << start << "  end: " << end << std::endl;

        // IS THIS NECESSARY?
        repr->setAttributeSvgDouble("x", center[Geom::X] + rx * cos(start) );
        repr->setAttributeSvgDouble("y", center[Geom::Y] + ry * sin(start) );

        guint sections = pcols;

        // If less sections, arc approximation error too great. (Check!)
        if( sections < 4 ) sections = 4;

        double arc = (end - start) / (double)sections;

        // See: http://en.wikipedia.org/wiki/B%C3%A9zier_curve
        gdouble kappa = 4.0/3.0 * tan(arc/4.0);
        gdouble lenx = rx * kappa;
        gdouble leny = ry * kappa;

        gdouble s = start;
        for( guint i = 0; i < sections; ++i ) {

            SPMeshPatchI patch( &nodes, 0, i );

            gdouble x0 = center[Geom::X] + rx * cos(s);
            gdouble y0 = center[Geom::Y] + ry * sin(s);
            gdouble x1 = x0 - lenx * sin(s);
            gdouble y1 = y0 + leny * cos(s);

            s += arc;
            gdouble x3 = center[Geom::X] + rx * cos(s);
            gdouble y3 = center[Geom::Y] + ry * sin(s);
            gdouble x2 = x3 + lenx * sin(s);
            gdouble y2 = y3 - leny * cos(s);

            patch.setPoint( 0, 0, Geom::Point( x0, y0 ) );
            patch.setPoint( 0, 1, Geom::Point( x1, y1 ) );
            patch.setPoint( 0, 2, Geom::Point( x2, y2 ) );
            patch.setPoint( 0, 3, Geom::Point( x3, y3 ) );

            patch.setPoint( 2, 0, center );
            patch.setPoint( 3, 0, center );

            for( guint k = 0; k < 4; ++k ) {
                patch.setPathType( k, 'l' );
                patch.setColor( k, (i+k)%2 ? color : white );
                patch.setOpacity( k, 1.0 );
            }
            patch.setPathType( 0, 'c' );

            // Set handle and tensor nodes.
            patch.updateNodes();

        }

        split_row( 0, prows );

    } else {

        // Normal grid meshes

        if( is<SPGenericEllipse>( item ) ) {

            // std::cout << "We've got ourselves an arc!" << std::endl;

            auto arc = cast<SPGenericEllipse>( item );
            center[Geom::X] = arc->cx.computed;
            center[Geom::Y] = arc->cy.computed;
            gdouble rx = arc->rx.computed;
            gdouble ry = arc->ry.computed;

            gdouble s = -3.0/2.0 * M_PI_2;

            repr->setAttributeSvgDouble("x", center[Geom::X] + rx * cos(s) );
            repr->setAttributeSvgDouble("y", center[Geom::Y] + ry * sin(s) );

            gdouble lenx = rx * 4*tan(M_PI_2/4)/3;
            gdouble leny = ry * 4*tan(M_PI_2/4)/3;

            SPMeshPatchI patch( &nodes, 0, 0 );
            for( guint i = 0; i < 4; ++i ) {

                gdouble x0 = center[Geom::X] + rx * cos(s);
                gdouble y0 = center[Geom::Y] + ry * sin(s);
                gdouble x1 = x0 + lenx * cos(s + M_PI_2);
                gdouble y1 = y0 + leny * sin(s + M_PI_2);

                s += M_PI_2;
                gdouble x3 = center[Geom::X] + rx * cos(s);
                gdouble y3 = center[Geom::Y] + ry * sin(s);
                gdouble x2 = x3 + lenx * cos(s - M_PI_2);
                gdouble y2 = y3 + leny * sin(s - M_PI_2);

                Geom::Point p1( x1, y1 );
                Geom::Point p2( x2, y2 );
                Geom::Point p3( x3, y3 );
                patch.setPoint( i, 1, p1 );
                patch.setPoint( i, 2, p2 );
                patch.setPoint( i, 3, p3 );

                patch.setPathType( i, 'c' );

                patch.setColor( i, i%2 ? color : white );
                patch.setOpacity( i, 1.0 );
            }

            // Fill out tensor points
            patch.updateNodes();

            split_row( 0, prows );
            split_column( 0, pcols );

            // END Arc

        } else if ( is<SPStar>( item ) ) {

            // Do simplest thing... assume star is not rounded or randomized.
            // (It should be easy to handle the rounded/randomized cases by making
            //  the appropriate star class function public.)
            auto star = cast<SPStar>( item );
            guint sides =  star->sides;

            // std::cout << "We've got ourselves an star! Sides: " << sides << std::endl;

            Geom::Point p0 = sp_star_get_xy( star, SP_STAR_POINT_KNOT1, 0 );
            repr->setAttributeSvgDouble("x", p0[Geom::X] );
            repr->setAttributeSvgDouble("y", p0[Geom::Y] );

            for( guint i = 0; i < sides; ++i ) {

                if( star->flatsided ) {

                    SPMeshPatchI patch( &nodes, 0, i );

                    patch.setPoint( 0, 0, sp_star_get_xy( star, SP_STAR_POINT_KNOT1, i ) );
                    guint ii = i+1;
                    if( ii == sides ) ii = 0;
                    patch.setPoint( 1, 0, sp_star_get_xy( star, SP_STAR_POINT_KNOT1, ii ) );
                    patch.setPoint( 2, 0, star->center );
                    patch.setPoint( 3, 0, star->center );

                    for( guint s = 0; s < 4; ++s ) {
                        patch.setPathType( s, 'l' );
                        patch.setColor( s, (i+s)%2 ? color : white );
                        patch.setOpacity( s, 1.0 );
                    }

                    // Set handle and tensor nodes.
                    patch.updateNodes();

                } else {

                    SPMeshPatchI patch0( &nodes, 0, 2*i );

                    patch0.setPoint( 0, 0, sp_star_get_xy( star, SP_STAR_POINT_KNOT1, i ) );
                    patch0.setPoint( 1, 0, sp_star_get_xy( star, SP_STAR_POINT_KNOT2, i ) );
                    patch0.setPoint( 2, 0, star->center );
                    patch0.setPoint( 3, 0, star->center );

                    guint ii = i+1;
                    if( ii == sides ) ii = 0;

                    SPMeshPatchI patch1( &nodes, 0, 2*i+1 );

                    patch1.setPoint( 0, 0, sp_star_get_xy( star, SP_STAR_POINT_KNOT2, i ) );
                    patch1.setPoint( 1, 0, sp_star_get_xy( star, SP_STAR_POINT_KNOT1, ii ) );
                    patch1.setPoint( 2, 0, star->center );
                    patch1.setPoint( 3, 0, star->center );

                    for( guint s = 0; s < 4; ++s ) {
                        patch0.setPathType( s, 'l' );
                        patch0.setColor( s, s%2 ? color : white );
                        patch0.setOpacity( s, 1.0 );
                        patch1.setPathType( s, 'l' );
                        patch1.setColor( s, s%2 ? white : color );
                        patch1.setOpacity( s, 1.0 );
                    }

                    // Set handle and tensor nodes.
                    patch0.updateNodes();
                    patch1.updateNodes();

                }
            }
        
            //print();

            split_row( 0, prows );
            //split_column( 0, pcols );

        } else {

            // Generic

            repr->setAttributeSvgDouble("x", bbox->min()[Geom::X]);
            repr->setAttributeSvgDouble("y", bbox->min()[Geom::Y]);

            // Get node array size
            guint nrows = prows * 3 + 1;
            guint ncols = pcols * 3 + 1;

            gdouble dx = width  / (gdouble)(ncols-1.0);
            gdouble dy = height / (gdouble)(nrows-1.0);

            Geom::Point p0( mg->x.computed, mg->y.computed );

            for( guint i = 0; i < nrows; ++i ) {
                std::vector< SPMeshNode* > row;
                for( guint j = 0; j < ncols; ++j ) {
                    SPMeshNode* node = new SPMeshNode;
                    node->p = p0 + Geom::Point( j * dx, i * dy );

                    node->node_edge = MG_NODE_EDGE_NONE;
                    if( i == 0        ) node->node_edge |= MG_NODE_EDGE_TOP;
                    if( i == nrows -1 ) node->node_edge |= MG_NODE_EDGE_BOTTOM;
                    if( j == 0        ) node->node_edge |= MG_NODE_EDGE_LEFT;
                    if( j == ncols -1 ) node->node_edge |= MG_NODE_EDGE_RIGHT;

                    if( i%3 == 0 ) {

                        if( j%3 == 0) {
                            // Corner
                            node->node_type = MG_NODE_TYPE_CORNER;
                            node->set = true;
                            node->color = (i+j)%2 ? color : white;
                            node->opacity = 1.0;

                        } else {
                            // Side
                            node->node_type = MG_NODE_TYPE_HANDLE;
                            node->set = true;
                            node->path_type = 'c';
                        }

                    } else {

                        if( j%3 == 0) {
                            // Side
                            node->node_type = MG_NODE_TYPE_HANDLE;
                            node->set = true;
                            node->path_type = 'c';
                        } else {
                            // Tensor
                            node->node_type = MG_NODE_TYPE_TENSOR;
                            node->set = false;
                        }

                    }

                    row.push_back( node );
                }
                nodes.push_back( row );
            }
            // End normal
        }

    } // If conical

    //print();

    // Write repr
    write( mg );
}


/**
   Clear mesh gradient.
*/
void SPMeshNodeArray::clear() {

    for(auto & node : nodes) {
        for(auto & j : node) {
            if( j ) {
                delete j;
            }
        }
    }
    nodes.clear();
};


/**
   Print mesh gradient (for debugging).
*/
void SPMeshNodeArray::print() {
    for( guint i = 0; i < nodes.size(); ++i ) {
        std::cout << "New node row:" << std::endl;
        for( guint j = 0; j < nodes[i].size(); ++j ) {
            if( nodes[i][j] ) {
                std::cout.width(4);
                std::cout << "  Node: " << i << "," << j << ":  "
                          << nodes[i][j]->p
                          << "  Node type: " << nodes[i][j]->node_type
                          << "  Node edge: " << nodes[i][j]->node_edge
                          << "  Set: "  << nodes[i][j]->set
                          << "  Path type: " << nodes[i][j]->path_type
                          << "  Stop: " << nodes[i][j]->stop
                          << std::endl;
            } else {
                std::cout << "Error: missing mesh node." << std::endl;
            }
        } // Loop over patches
    } // Loop over rows
};



/*
double hermite( const double p0, const double p1, const double m0, const double m1, const double t ) {
    double t2 = t*t;
    double t3 = t2*t;

    double result = (2.0*t3 - 3.0*t2 +1.0) * p0
                  + (t3 - 2.0*t2 + t)      * m0
                  + (-2.0*t3 + 3.0*t2)     * p1
                  + (t3 -t2)               * m1;

    return result;
}
*/

class SPMeshSmoothCorner {

public:
    SPMeshSmoothCorner() {
        for(auto & i : g) {
            for( unsigned j = 0; j < 4; ++j ) {
                i[j] = 0;
            }
        }
    }
    
    double g[3][8]; // 3 colors, 8 parameters: see enum.
    Geom::Point p;  // Location of point
};

// Find slope at point 1 given values at previous and next points
// Return value is slope in user space
double find_slope1( const double &p0, const double &p1, const double &p2,
                    const double &d01, const double &d12 ) {

    double slope = 0;

    if( d01 > 0 && d12 > 0 ) {
        slope = 0.5 * ( (p1 - p0)/d01 + (p2 - p1)/d12 );

        if( ( p0 > p1 && p1 < p2 ) ||
            ( p0 < p1 && p1 > p2 ) ) {
            // At minimum or maximum, use slope of zero
            slope = 0;
        } else {
            // Ensure we don't overshoot
            if( fabs(slope) > fabs(3*(p1-p0)/d01) ) {
                slope = 3*(p1-p0)/d01;
            }
            if( fabs(slope) > fabs(3*(p2-p1)/d12) ) {
                slope = 3*(p2-p1)/d12;
            }
        }
    } else {
        // Do something clever
    }
    return slope;
};


/*
// Find slope at point 0 given values at previous and next points
// TO DO: TAKE DISTANCE BETWEEN POINTS INTO ACCOUNT
double find_slope2( double pmm, double ppm, double pmp, double ppp, double p0 ) {

    // pmm == d[i-1][j-1], ...  'm' is minus, 'p' is plus
    double slope = (ppp - ppm - pmp + pmm)/2.0;
    if( (ppp > p0 && ppm > p0 && pmp > p0 && pmm > 0) ||
        (ppp < p0 && ppm < p0 && pmp < p0 && pmm < 0) ) {
        // At minimum or maximum, use slope of zero
        slope = 0;
    } else {
        // Don't really know what to do here
        if( fabs(slope) > fabs(3*(ppp-p0)) ) {
            slope = 3*(ppp-p0);
        }
        if( fabs(slope) > fabs(3*(pmp-p0)) ) {
            slope = 3*(pmp-p0);
        }
        if( fabs(slope) > fabs(3*(ppm-p0)) ) {
            slope = 3*(ppm-p0);
        }
        if( fabs(slope) > fabs(3*(pmm-p0)) ) {
            slope = 3*(pmm-p0);
        }
    }
    return slope;
}
*/

// https://en.wikipedia.org/wiki/Bicubic_interpolation
void invert( const double v[16], double alpha[16] ) {

    const double  A[16][16] = {

        { 1, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0 },
        { 0, 0, 0, 0,  1, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0 },
        {-3, 3, 0, 0, -2,-1, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0 },
        { 2,-2, 0, 0,  1, 1, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0 },
        { 0, 0, 0, 0,  0, 0, 0, 0,  1, 0, 0, 0,  0, 0, 0, 0 },
        { 0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0,  1, 0, 0, 0 },
        { 0, 0, 0, 0,  0, 0, 0, 0, -3, 3, 0, 0, -2,-1, 0, 0 },
        { 0, 0, 0, 0,  0, 0, 0, 0,  2,-2, 0, 0,  1, 1, 0, 0 },
        {-3, 0, 3, 0,  0, 0, 0, 0, -2, 0,-1, 0,  0, 0, 0, 0 },
        { 0, 0, 0, 0, -3, 0, 3, 0,  0, 0, 0, 0, -2, 0,-1, 0 },
        { 9,-9,-9, 9,  6, 3,-6,-3,  6,-6, 3,-3,  4, 2, 2, 1 },
        {-6, 6, 6,-6, -3,-3, 3, 3, -4, 4,-2, 2, -2,-2,-1,-1 },
        { 2, 0,-2, 0,  0, 0, 0, 0,  1, 0, 1, 0,  0, 0, 0, 0 },
        { 0, 0, 0, 0,  2, 0,-2, 0,  0, 0, 0, 0,  1, 0, 1, 0 },
        {-6, 6, 6,-6, -4,-2, 4, 2, -3, 3,-3, 3, -2,-1,-2,-1 },
        { 4,-4,-4, 4,  2, 2,-2,-2,  2,-2, 2,-2,  1, 1, 1, 1 }
    };

    for( unsigned i = 0; i < 16; ++i ) {
        alpha[i] = 0;
        for( unsigned j = 0; j < 16; ++j ) {
            alpha[i] += A[i][j]*v[j];
        }
    }
}

double sum( const double alpha[16], const double& x, const double& y ) {

    double result = 0;
    
    double xx = x*x;
    double xxx = xx * x;
    double yy = y*y;
    double yyy = yy * y;

    result += alpha[  0 ];
    result += alpha[  1 ] * x;
    result += alpha[  2 ] * xx;
    result += alpha[  3 ] * xxx;
    result += alpha[  4 ] * y;
    result += alpha[  5 ] * y * x;
    result += alpha[  6 ] * y * xx;
    result += alpha[  7 ] * y * xxx;
    result += alpha[  8 ] * yy;
    result += alpha[  9 ] * yy * x;
    result += alpha[ 10 ] * yy * xx;
    result += alpha[ 11 ] * yy * xxx;
    result += alpha[ 12 ] * yyy;
    result += alpha[ 13 ] * yyy * x;
    result += alpha[ 14 ] * yyy * xx;
    result += alpha[ 15 ] * yyy * xxx;
    
    return result;
}

/**
   Fill 'smooth' with a smoothed version of the array by subdividing each patch into smaller patches.
*/
void SPMeshNodeArray::bicubic( SPMeshNodeArray* smooth, SPMeshType type ) {

    
    *smooth = *this;  // Deep copy via copy assignment constructor, smooth cleared before copy
    // std::cout << "SPMeshNodeArray::smooth2(): " << this->patch_rows() << " " << smooth->patch_columns() << std::endl;
    // std::cout << "  " << smooth << " " << this << std::endl;

    // Find derivatives at corners

    // Create array of corner points
    std::vector< std::vector <SPMeshSmoothCorner> > d; 
    d.resize( smooth->patch_rows() + 1 );
    for( unsigned i = 0; i < d.size(); ++i ) {
        d[i].resize( smooth->patch_columns() + 1 );
        for( unsigned j = 0; j < d[i].size(); ++j ) {
            float rgb_color[3];
            this->nodes[ i*3 ][ j*3 ]->color.get_rgb_floatv(rgb_color);
            d[i][j].g[0][0] =  rgb_color[ 0 ];
            d[i][j].g[1][0] =  rgb_color[ 1 ];
            d[i][j].g[2][0] =  rgb_color[ 2 ];
            d[i][j].p = this->nodes[ i*3 ][ j*3 ]->p;
        }
    }

    // Calculate interior derivatives
    for( unsigned i = 0; i < d.size(); ++i ) {
        for( unsigned j = 0; j < d[i].size(); ++j ) {
            for( unsigned k = 0; k < 3; ++k ) { // Loop over colors

                // dx

                if( i != 0 && i != d.size()-1 ) {
                    double lm = Geom::distance(  d[i-1][j].p, d[i][j].p );
                    double lp = Geom::distance(  d[i+1][j].p, d[i][j].p );
                    d[i][j].g[k][1] = find_slope1( d[i-1][j].g[k][0], d[i][j].g[k][0], d[i+1][j].g[k][0], lm, lp );
                }

                // dy
                if( j != 0 && j != d[i].size()-1 ) {
                    double lm = Geom::distance(  d[i][j-1].p, d[i][j].p );
                    double lp = Geom::distance(  d[i][j+1].p, d[i][j].p );
                    d[i][j].g[k][2] = find_slope1( d[i][j-1].g[k][0], d[i][j].g[k][0], d[i][j+1].g[k][0], lm, lp );
                }

                // dxdy  if needed, need to take lengths into account
                // if( i != 0 && i != d.size()-1 && j != 0 && j != d[i].size()-1 ) {
                //     d[i][j].g[k][3] = find_slope2( d[i-1][j-1].g[k][0], d[i+1][j-1].g[k][0],
                //                                    d[i-1][j+1].g[k][0], d[i-1][j-1].g[k][0],
                //                                    d[i][j].g[k][0] );
                // }

            }
        }
    }
    
    // Calculate exterior derivatives
    // We need to do this after calculating interior derivatives as we need to already
    // have the non-exterior derivative calculated for finding the parabola.
    for( unsigned j = 0; j< d[0].size(); ++j ) {
        for( unsigned k = 0; k < 3; ++k ) { // Loop over colors

            // Parabolic
            double d0 = Geom::distance( d[1][j].p, d[0  ][j].p );
            if( d0 > 0 ) {
                d[0][j].g[k][1] = 2.0*(d[1][j].g[k][0] - d[0  ][j].g[k][0])/d0 - d[1][j].g[k][1];
            } else {
                d[0][j].g[k][1] = 0;
            }

            unsigned z = d.size()-1;
            double dz = Geom::distance( d[z][j].p, d[z-1][j].p );
            if( dz > 0 ) {
                d[z][j].g[k][1] = 2.0*(d[z][j].g[k][0] - d[z-1][j].g[k][0])/dz - d[z-1][j].g[k][1];
            } else {
                d[z][j].g[k][1] = 0;
            }
        }
    }

    for( unsigned i = 0; i< d.size(); ++i ) {
        for( unsigned k = 0; k < 3; ++k ) { // Loop over colors

            // Parabolic
            double d0 = Geom::distance( d[i][1].p, d[i][0  ].p );
            if( d0 > 0 ) {
                d[i][0].g[k][2] = 2.0*(d[i][1].g[k][0] - d[i][0  ].g[k][0])/d0 - d[i][1].g[k][2];
            } else {
                d[i][0].g[k][2] = 0;
            }

            unsigned z = d[0].size()-1;
            double dz = Geom::distance( d[i][z].p, d[i][z-1].p );
            if( dz > 0 ) {
                d[i][z].g[k][2] = 2.0*(d[i][z].g[k][0] - d[i][z-1].g[k][0])/dz - d[i][z-1].g[k][2];
            } else {
                d[i][z].g[k][2] = 0;
            }
        }
    }

    // Leave outside corner cross-derivatives at zero.
    
    // Next split each patch into 8x8 smaller patches.
    
    // Split each row into eight rows.
    // Must do it from end so inserted rows don't mess up indexing 
    for( int i = smooth->patch_rows() - 1; i >= 0; --i ) {
        smooth->split_row( i, unsigned(8) );
    }

    // Split each column into eight columns.
    // Must do it from end so inserted columns don't mess up indexing 
    for( int i = smooth->patch_columns() - 1; i >= 0; --i ) {
        smooth->split_column( i, (unsigned)8 );
    }

    // Fill new patches
    for( unsigned i = 0; i < this->patch_rows(); ++i ) {
        for( unsigned j = 0; j < this->patch_columns(); ++j ) {

            double dx0 = Geom::distance( d[i  ][j  ].p, d[i+1][j  ].p );
            double dx1 = Geom::distance( d[i  ][j+1].p, d[i+1][j+1].p );
            double dy0 = Geom::distance( d[i  ][j  ].p, d[i  ][j+1].p );
            double dy1 = Geom::distance( d[i+1][j  ].p, d[i+1][j+1].p );

            // Temp loop over 0..8 to get last column/row edges
            float r[3][9][9]; // result
            for( unsigned m = 0; m < 3; ++m ) {

                double v[16];
                v[ 0] = d[i  ][j  ].g[m][0];
                v[ 1] = d[i+1][j  ].g[m][0];
                v[ 2] = d[i  ][j+1].g[m][0];
                v[ 3] = d[i+1][j+1].g[m][0];
                v[ 4] = d[i  ][j  ].g[m][1]*dx0;
                v[ 5] = d[i+1][j  ].g[m][1]*dx0;
                v[ 6] = d[i  ][j+1].g[m][1]*dx1;
                v[ 7] = d[i+1][j+1].g[m][1]*dx1;
                v[ 8] = d[i  ][j  ].g[m][2]*dy0;
                v[ 9] = d[i+1][j  ].g[m][2]*dy1;
                v[10] = d[i  ][j+1].g[m][2]*dy0;
                v[11] = d[i+1][j+1].g[m][2]*dy1;
                v[12] = d[i  ][j  ].g[m][3];
                v[13] = d[i+1][j  ].g[m][3];
                v[14] = d[i  ][j+1].g[m][3];
                v[15] = d[i+1][j+1].g[m][3];

                double alpha[16];
                invert( v, alpha );
                
                for( unsigned k = 0; k < 9; ++k ) {
                    for( unsigned l = 0; l < 9; ++l ) {
                        double x = k/8.0;
                        double y = l/8.0;
                        r[m][k][l] = sum( alpha, x, y );
                        // Clamp to allowed values
                        if( r[m][k][l] > 1.0 )
                            r[m][k][l] = 1.0;
                        if( r[m][k][l] < 0.0 )
                            r[m][k][l] = 0.0;
                    }
                }

            } // Loop over colors

            for( unsigned k = 0; k < 9; ++k ) {
                for( unsigned l = 0; l < 9; ++l ) {
                    // Every third node is a corner node
                    smooth->nodes[ (i*8+k)*3 ][(j*8+l)*3 ]->color.set( r[0][k][l], r[1][k][l], r[2][k][l] );
                }
            }
        }
    }
}

/**
   Number of patch rows.
*/
guint SPMeshNodeArray::patch_rows() {

    return nodes.size()/3;
}

/**
   Number of patch columns.
*/
guint SPMeshNodeArray::patch_columns() {
    if (nodes.empty()) {
        return 0;
    }
    return nodes[0].size()/3;
}

/**
   Inputs:
     i, j: Corner draggable indices.
   Returns:
     true if corners adjacent.
     n[] is array of nodes in top/bottom or left/right order.
*/
bool SPMeshNodeArray::adjacent_corners( guint i, guint j, SPMeshNode* n[4] ) {

    // This works as all corners have indices and they
    // are numbered in order by row and column (and
    // the node array is rectangular).

    bool adjacent = false;

    guint c1 = i;
    guint c2 = j;
    if( j < i ) {
        c1 = j;
        c2 = i;
    }

    // Number of corners in a row of patches.
    guint ncorners = patch_columns() + 1;

    guint crow1 = c1 / ncorners;
    guint crow2 = c2 / ncorners;
    guint ccol1 = c1 % ncorners;
    guint ccol2 = c2 % ncorners;

    guint nrow  = crow1 * 3;
    guint ncol  = ccol1 * 3;

    // std::cout << "  i: " << i
    //           << "  j: " << j
    //           << "  ncorners: " << ncorners
    //           << "  c1: " << c1
    //           << "  crow1: " << crow1
    //           << "  ccol1: " << ccol1
    //           << "  c2: " << c2
    //           << "  crow2: " << crow2
    //           << "  ccol2: " << ccol2
    //           << "  nrow: " << nrow
    //           << "  ncol: " << ncol
    //           << std::endl;

    // Check for horizontal neighbors
    if ( crow1 == crow2 && (ccol2 - ccol1) == 1 ) {
        adjacent = true;
        for( guint k = 0; k < 4; ++k ) {
            n[k] = nodes[nrow][ncol+k];
        }
    }

    // Check for vertical neighbors
    if ( ccol1 == ccol2 && (crow2 - crow1) == 1 ) {
        adjacent = true;
        for( guint k = 0; k < 4; ++k ) {
            n[k] = nodes[nrow+k][ncol];
        }
    }

    return adjacent;
}

/**
   Toggle sides between lineto and curve to if both corners selected.
   Input is a list of selected corner draggable indices.
*/
guint SPMeshNodeArray::side_toggle( std::vector<guint> corners ) {

    guint toggled = 0;

    if( corners.size() < 2 ) return 0;

    for( guint i = 0; i < corners.size()-1; ++i ) {
        for( guint j = i+1; j < corners.size(); ++j ) {

            SPMeshNode* n[4];
            if( adjacent_corners( corners[i], corners[j], n ) ) {

                gchar path_type = n[1]->path_type;
                switch (path_type)
                {
                    case 'L':
                        n[1]->path_type = 'C';
                        n[2]->path_type = 'C';
                        n[1]->set = true;
                        n[2]->set = true;
                        break;

                    case 'l':
                        n[1]->path_type = 'c';
                        n[2]->path_type = 'c';
                        n[1]->set = true;
                        n[2]->set = true;
                        break;
                    
                    case 'C': {
                        n[1]->path_type = 'L';
                        n[2]->path_type = 'L';
                        n[1]->set = false;
                        n[2]->set = false;
                        // 'L' acts as if handles are 1/3 of path length from corners.
                        Geom::Point dp = (n[3]->p - n[0]->p)/3.0;
                        n[1]->p = n[0]->p + dp;
                        n[2]->p = n[3]->p - dp;
                        break;
                    }
                    case 'c': {
                        n[1]->path_type = 'l';
                        n[2]->path_type = 'l';
                        n[1]->set = false;
                        n[2]->set = false;
                        // 'l' acts as if handles are 1/3 of path length from corners.
                        Geom::Point dp = (n[3]->p - n[0]->p)/3.0;
                        n[1]->p = n[0]->p + dp;
                        n[2]->p = n[3]->p - dp;
                        // std::cout << "Toggle sides: "
                        //           << n[0]->p << " "
                        //           << n[1]->p << " "
                        //           << n[2]->p << " "
                        //           << n[3]->p << " "
                        //           << dp << std::endl;
                        break;
                    }
                    default:
                        std::cerr << "Toggle sides: Invalid path type: " << path_type << std::endl;
                }
                ++toggled;
            }
        }
    }
    if( toggled > 0 ) built = false;
    return toggled;
}

/**
 * Converts generic Beziers to Beziers approximating elliptical arcs, preserving handle direction.
 * There are infinite possible solutions. The solution chosen here is to generate a section of an
 * ellipse that is centered on the intersection of the two lines passing through the two nodes but
 * parallel to the other node's handle direction. This is the section of an ellipse that
 * corresponds to a quarter of a circle squished and then skewed.
 */
guint SPMeshNodeArray::side_arc( std::vector<guint> corners ) {

    if( corners.size() < 2 ) return 0;

    guint arced = 0;
    for( guint i = 0; i < corners.size()-1; ++i ) {
        for( guint j = i+1; j < corners.size(); ++j ) {

            SPMeshNode* n[4];
            if( adjacent_corners( corners[i], corners[j], n ) ) {

                gchar path_type = n[1]->path_type;
                switch (path_type)
                {
                    case 'L':
                    case 'l':
                        std::cerr << "SPMeshNodeArray::side_arc: Can't convert straight lines to arcs." << std::endl;
                        break;

                    case 'C':
                    case 'c': {

                        Geom::Ray  ray1( n[0]->p, n[1]->p );
                        Geom::Ray  ray2( n[3]->p, n[2]->p );
                        if( !are_parallel( (Geom::Line)ray1, (Geom::Line)ray2 ) ) {

                            Geom::OptCrossing crossing = intersection( ray1, ray2 );

                            if( crossing ) {

                                Geom::Point intersection = ray1.pointAt( (*crossing).ta );

                                const double f = 4.0/3.0 * tan( M_PI/2.0/4.0 );

                                Geom::Point h1 = intersection - n[0]->p;
                                Geom::Point h2 = intersection - n[3]->p;

                                n[1]->p = n[0]->p + f*h1;
                                n[2]->p = n[3]->p + f*h2;
                                ++arced;

                            } else {
                                std::cerr << "SPMeshNodeArray::side_arc: No crossing, can't turn into arc." << std::endl;
                            }
                        } else {
                            std::cerr << "SPMeshNodeArray::side_arc: Handles parallel, can't turn into arc." << std::endl;
                        }
                        break;
                    }
                    default:
                        std::cerr << "SPMeshNodeArray::side_arc: Invalid path type: " << n[1]->path_type << std::endl;
                }
            }
        }
    }
    if( arced > 0 ) built = false;
    return arced;
}

/**
   Toggle sides between lineto and curve to if both corners selected.
   Input is a list of selected corner draggable indices.
*/
guint SPMeshNodeArray::tensor_toggle( std::vector<guint> corners ) {

    // std::cout << "SPMeshNodeArray::tensor_toggle" << std::endl;

    if( corners.size() < 4 ) return 0;

    guint toggled = 0;

    // Number of corners in a row of patches.
    guint ncorners = patch_columns() + 1;

    for( guint i = 0; i < corners.size()-3; ++i ) {
        for( guint j = i+1; j < corners.size()-2; ++j ) {
            for( guint k = j+1; k < corners.size()-1; ++k ) {
                for( guint l = k+1; l < corners.size(); ++l ) {

                    guint c[4];
                    c[0] = corners[i];
                    c[1] = corners[j];
                    c[2] = corners[k];
                    c[3] = corners[l];
                    std::sort( c, c+4 );

                    // Check we have four corners of one patch selected
                    if( c[1]-c[0] == 1 &&
                        c[3]-c[2] == 1 &&
                        c[2]-c[0] == ncorners &&
                        c[3]-c[1] == ncorners &&
                        c[0] % ncorners < ncorners - 1 ) {

                        // Patch
                        guint prow = c[0] / ncorners;
                        guint pcol = c[0] % ncorners;

                        // Upper left node of patch
                        guint irow = prow * 3;
                        guint jcol = pcol * 3;

                        // std::cout << "tensor::toggle: "
                        //           << c[0] << ", "
                        //           << c[1] << ", "
                        //           << c[2] << ", "
                        //           << c[3] << std::endl;

                        // std::cout << "tensor::toggle: "
                        //           << " irow: " << irow
                        //           << " jcol: " << jcol
                        //           << " prow: " << prow
                        //           << " pcol: " << pcol
                        //           << std::endl;

                        SPMeshPatchI patch( &nodes, prow, pcol );
                        patch.updateNodes();

                        if( patch.tensorIsSet() ) {
                            // Unset tensor points
                            nodes[irow+1][jcol+1]->set = false;
                            nodes[irow+1][jcol+2]->set = false;
                            nodes[irow+2][jcol+1]->set = false;
                            nodes[irow+2][jcol+2]->set = false;
                        } else {
                            // Set tensor points
                            nodes[irow+1][jcol+1]->set = true;
                            nodes[irow+1][jcol+2]->set = true;
                            nodes[irow+2][jcol+1]->set = true;
                            nodes[irow+2][jcol+2]->set = true;
                        }

                        ++toggled;
                    }
                }
            }
        }
    }
    if( toggled > 0 ) built = false;
    return toggled;
}

/**
   Attempts to smooth color transitions across corners.
   Input is a list of selected corner draggable indices.
*/
guint SPMeshNodeArray::color_smooth( std::vector<guint> corners ) {

    // std::cout << "SPMeshNodeArray::color_smooth" << std::endl;

    guint smoothed = 0;

    // Number of corners in a row of patches.
    guint ncorners = patch_columns() + 1;

    // Number of node rows and columns
    guint ncols = patch_columns() * 3 + 1;
    guint nrows = patch_rows() * 3 + 1;

    for(unsigned int corner : corners) {

        // std::cout << "SPMeshNodeArray::color_smooth: " << i << " " << corner << std::endl;

        // Node row & col
        guint nrow = (corner / ncorners) * 3;
        guint ncol = (corner % ncorners) * 3;

        SPMeshNode* n[7];
        for( guint s = 0; s < 2; ++s ) {

            bool smooth = false;

            // Find neighboring nodes
            if( s == 0 ) {

                // Horizontal
                if( ncol > 2 && ncol+3 < ncols) {
                    for( guint j = 0; j < 7; ++j ) {
                        n[j] = nodes[ nrow ][ ncol - 3 + j ];
                    }
                    smooth = true;
                }

            } else {

                // Vertical
                if( nrow > 2 && nrow+3 < nrows) {
                    for( guint j = 0; j < 7; ++j ) {
                        n[j] = nodes[ nrow - 3 + j ][ ncol ];
                    }
                    smooth = true;
                }
            }

            if( smooth ) {

                // Let the smoothing begin
                // std::cout << " checking: " << ncol << " " << nrow << std::endl;

                // Get initial slopes using closest handles.
                double slope[2][3];
                double slope_ave[3];
                double slope_diff[3];

                // Color of corners
                SPColor color0 = n[0]->color;
                SPColor color3 = n[3]->color;
                SPColor color6 = n[6]->color;
                
                // Distance nodes from selected corner
                Geom::Point d[7];
                for( guint k = 0; k < 7; ++k ) {
                    d[k]= n[k]->p - n[3]->p;
                    // std::cout << " d[" << k << "]: " << d[k].length() << std::endl;
                }

                double sdm = -1.0; // Slope Diff Max
                guint cdm = 0; // Color Diff Max  (Which color has the maximum difference in slopes)
                for( guint c = 0; c < 3; ++c ) {
                    if( d[2].length() != 0.0 ) {
                        slope[0][c] = (color3.v.c[c] - color0.v.c[c]) / d[2].length();
                    } 
                    if( d[4].length() != 0.0 ) {
                        slope[1][c] = (color6.v.c[c] - color3.v.c[c]) / d[4].length();
                    }
                    slope_ave[c]  = (slope[0][c]+slope[1][c]) / 2.0;
                    slope_diff[c] = (slope[0][c]-slope[1][c]);
                    // std::cout << "  color: " << c << " :"
                    //           << color0.v.c[c] << " "
                    //           << color3.v.c[c] << " "
                    //           << color6.v.c[c]
                    //           << "  slope: "
                    //           << slope[0][c] << " "
                    //           << slope[1][c]
                    //           << "  slope_ave: " << slope_ave[c]
                    //           << "  slope_diff: " << slope_diff[c]
                    //           << std::endl;

                    // Find color with maximum difference
                    if( std::abs( slope_diff[c] ) > sdm ) {
                        sdm = std::abs( slope_diff[c] );
                        cdm = c;
                    }
                }
                // std::cout << " cdm: " << cdm << std::endl;

                // Find new handle positions:
                double length_left  = d[0].length();
                double length_right = d[6].length();
                if( slope_ave[ cdm ] != 0.0 ) {
                    length_left  = std::abs( (color3.v.c[cdm] - color0.v.c[cdm]) / slope_ave[ cdm ] );
                    length_right = std::abs( (color6.v.c[cdm] - color3.v.c[cdm]) / slope_ave[ cdm ] );
                }

                // Move closest handle a maximum of mid point... but don't shorten
                double max = 0.8;
                if( length_left  > max * d[0].length() && length_left > d[2].length() ) {
                    std::cerr << " Can't smooth left side" << std::endl;
                    length_left  = std::max( max * d[0].length(), d[2].length() );
                }
                if( length_right  > max * d[6].length() && length_right > d[4].length() ) {
                    std::cerr << " Can't smooth right side" << std::endl;
                    length_right  = std::max( max * d[6].length(), d[4].length() );
                }

                if( d[2].length() != 0.0 ) d[2] *= length_left/d[2].length();
                if( d[4].length() != 0.0 ) d[4] *= length_right/d[4].length();

                // std::cout << "  length_left: " << length_left
                //           << "  d[0]: " << d[0].length()
                //           << "  length_right: " << length_right
                //           << "  d[6]: " << d[6].length()
                //           << std::endl;

                n[2]->p = n[3]->p + d[2];
                n[4]->p = n[3]->p + d[4];

                ++smoothed;
            }
        }

    }

    if( smoothed > 0 ) built = false;
    return smoothed;
}

/**
   Pick color from background for selected corners.
*/
guint SPMeshNodeArray::color_pick( std::vector<guint> icorners, SPItem* item ) {

    // std::cout << "SPMeshNodeArray::color_pick" << std::endl;

    guint picked = 0;

    // Code inspired from clone tracing

    // Setup...

    // We need a copy of the drawing so we can hide the mesh.
    Inkscape::Drawing *pick_drawing = new Inkscape::Drawing();
    unsigned pick_visionkey = SPItem::display_key_new(1);

    SPDocument *pick_doc = mg->document;

    pick_drawing->setRoot(pick_doc->getRoot()->invoke_show(*pick_drawing, pick_visionkey, SP_ITEM_SHOW_DISPLAY));

    item->invoke_hide(pick_visionkey);

    pick_doc->getRoot()->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
    pick_doc->ensureUpToDate();

    //gdouble pick_zoom = 1.0; // zoom;
    //pick_drawing->root()->setTransform(Geom::Scale(pick_zoom));
    pick_drawing->update();

    // std::cout << " transform: " << std::endl;
    // std::cout << item->transform << std::endl;
    // std::cout << " i2doc: " << std::endl;
    // std::cout << item->i2doc_affine() << std::endl;
    // std::cout << " i2dt: " << std::endl;
    // std::cout << item->i2dt_affine() << std::endl;
    // std::cout << " dt2i: " << std::endl;
    // std::cout << item->dt2i_affine() << std::endl;
    SPGradient* gr = mg;
    // if( gr->gradientTransform_set ) {
    //     std::cout << " gradient transform set: " << std::endl;
    //     std::cout << gr->gradientTransform << std::endl;
    // } else {
    //     std::cout << " gradient transform not set! " << std::endl;
    // }

    // Do picking
    for(unsigned int corner : icorners) {

        SPMeshNode* n = corners[ corner ];

        // Region to average over
        Geom::Point p = n->p;
        // std::cout << " before transform: p: " << p << std::endl;
        p *= gr->gradientTransform;
        // std::cout << " after transform:  p: " << p << std::endl;
        p *= item->i2doc_affine();
        // std::cout << " after transform:  p: " << p << std::endl;
        
        // If on edge, move inward
        guint cols = patch_columns()+1;
        guint rows = patch_rows()+1;
        guint col = corner % cols;
        guint row = corner / cols;
        guint ncol  = col * 3;
        guint nrow  = row * 3;

        const double size = 3.0;

        // Top edge
        if( row == 0 ) {
            Geom::Point dp = nodes[nrow+1][ncol]->p - p;
            p += unit_vector( dp ) * size;
        }
        // Right edge
        if( col == cols-1 ) {
            Geom::Point dp = nodes[nrow][ncol-1]->p - p;
            p += unit_vector( dp ) * size;
        }
        // Bottom edge
        if( row == rows-1 ) {
            Geom::Point dp = nodes[nrow-1][ncol]->p - p;
            p += unit_vector( dp ) * size;
        }
        // Left edge
        if( col == 0 ) {
            Geom::Point dp = nodes[nrow][ncol+1]->p - p;
            p += unit_vector( dp ) * size;
        }

        Geom::Rect box( p[Geom::X]-size/2.0, p[Geom::Y]-size/2.0,
                        p[Geom::X]+size/2.0, p[Geom::Y]+size/2.0 );

        /* Item integer bbox in points */
        Geom::IntRect ibox = box.roundOutwards();

        /* Find visible area */
        cairo_surface_t *s = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, ibox.width(), ibox.height());
        Inkscape::DrawingContext dc(s, ibox.min());

        /* Render copy and pick color */
        pick_drawing->render(dc, ibox);
        double R = 0, G = 0, B = 0, A = 0;
        ink_cairo_surface_average_color(s, R, G, B, A);
        cairo_surface_destroy(s);

        // std::cout << " p: " << p
        //           << " box: " << ibox
        //           << " R: " << R
        //           << " G: " << G
        //           << " B: " << B
        //           << std::endl;
        n->color.set( R, G, B );
    }

    pick_doc->getRoot()->invoke_hide(pick_visionkey);
    delete pick_drawing;

    picked = 1; // Picking always happens
    if( picked > 0 ) built = false;
    return picked;
}

/**
   Splits selected rows and/or columns in half (according to the path 't' parameter).
   Input is a list of selected corner draggable indices.
*/
guint SPMeshNodeArray::insert( std::vector<guint> corners ) {

    guint inserted = 0;

    if( corners.size() < 2 ) return 0;

    std::set<guint> columns;
    std::set<guint> rows;

    for( guint i = 0; i < corners.size()-1; ++i ) {
        for( guint j = i+1; j < corners.size(); ++j ) {

            // This works as all corners have indices and they
            // are numbered in order by row and column (and
            // the node array is rectangular).

            guint c1 = corners[i];
            guint c2 = corners[j];
            if (c2 < c1) {
                c1 = corners[j];
                c2 = corners[i];
            }

            // Number of corners in a row of patches.
            guint ncorners = patch_columns() + 1;

            guint crow1 = c1 / ncorners;
            guint crow2 = c2 / ncorners;
            guint ccol1 = c1 % ncorners;
            guint ccol2 = c2 % ncorners;

            // Check for horizontal neighbors
            if ( crow1 == crow2 && (ccol2 - ccol1) == 1 ) {
                columns.insert( ccol1 );
            }
        
            // Check for vertical neighbors
            if ( ccol1 == ccol2 && (crow2 - crow1) == 1 ) {
                rows.insert( crow1 );
            }
        }
    }

    // Iterate backwards so column/row numbers are not invalidated.
    std::set<guint>::reverse_iterator rit;
    for (rit=columns.rbegin(); rit != columns.rend(); ++rit) {
        split_column( *rit, 0.5);
        ++inserted;
    }
    for (rit=rows.rbegin(); rit != rows.rend(); ++rit) {
        split_row( *rit, 0.5);
        ++inserted;
    }

    if( inserted > 0 ) built = false;
    return inserted;
}

/**
   Moves handles in response to a corner node move.
   p_old: original position of moved corner node.
   corner: the corner node moved (draggable index, i.e. point_i).
   selected: list of all corners selected (draggable indices).
   op: how other corners should be moved.
   Corner node must already have been moved!
*/
void SPMeshNodeArray::update_handles( guint corner, std::vector< guint > /*selected*/, Geom::Point p_old, MeshNodeOperation /*op*/ )
{
    if (!draggers_valid) {
        std::cerr << "SPMeshNodeArray::update_handles: Draggers not valid!" << std::endl;
        return;
    }
    // assert( draggers_valid );

    // std::cout << "SPMeshNodeArray::update_handles: "
    //           << "  corner: " << corner
    //           << "  op: " << op
    //           << std::endl;

    // Find number of patch rows and columns
    guint mrow = patch_rows();
    guint mcol = patch_columns();

    // Number of corners in a row of patches.
    guint ncorners = mcol + 1;

    // Find corner row/column
    guint crow = corner / ncorners;
    guint ccol = corner % ncorners;

    // Find node row/column
    guint nrow  = crow * 3;
    guint ncol  = ccol * 3;

    // std::cout << "  mrow: " << mrow
    //           << "  mcol: " << mcol
    //           << "  crow: " << crow
    //           << "  ccol: " << ccol
    //           << "  ncorners: " << ncorners
    //           << "  nrow: " << nrow
    //           << "  ncol: " << ncol
    //           << std::endl;

    // New corner mesh coordinate.
    Geom::Point p_new = nodes[nrow][ncol]->p;

    // Corner point move dpg in mesh coordinate system.
    Geom::Point dp = p_new - p_old;

    // std::cout << "  p_old: " << p_old << std::endl;
    // std::cout << "  p_new: " << p_new << std::endl;
    // std::cout << "     dp: " << dp << std::endl;

    // STEP 1: ONLY DO DIRECT MOVE
    bool patch[4];
    patch[0] = patch[1] = patch[2] = patch[3] = false;
    if( ccol > 0    && crow > 0    ) patch[0] = true;
    if( ccol < mcol && crow > 0    ) patch[1] = true;
    if( ccol < mcol && crow < mrow ) patch[2] = true;
    if( ccol > 0    && crow < mrow ) patch[3] = true;

    // std::cout << patch[0] << " "
    //           << patch[1] << " "
    //           << patch[2] << " "
    //           << patch[3] << std::endl;

    // Move handles
    if( patch[0] || patch[1] ) {
        if( nodes[nrow-1][ncol]->path_type == 'l' ||
            nodes[nrow-1][ncol]->path_type == 'L' ) {
            Geom::Point s = (nodes[nrow-3][ncol]->p - nodes[nrow][ncol]->p)/3.0;
            nodes[nrow-1][ncol  ]->p = nodes[nrow][ncol]->p + s;
            nodes[nrow-2][ncol  ]->p = nodes[nrow-3][ncol]->p - s;
        } else {
            nodes[nrow-1][ncol  ]->p += dp;
        }
    }

    if( patch[1] || patch[2] )  {
        if( nodes[nrow  ][ncol+1]->path_type == 'l' ||
            nodes[nrow  ][ncol+1]->path_type == 'L' ) {
            Geom::Point s = (nodes[nrow][ncol+3]->p - nodes[nrow][ncol]->p)/3.0;
            nodes[nrow  ][ncol+1]->p = nodes[nrow][ncol]->p + s;
            nodes[nrow  ][ncol+2]->p = nodes[nrow][ncol+3]->p - s;
        } else {
            nodes[nrow  ][ncol+1]->p += dp;
        }
    }

    if( patch[2] || patch[3] ) {
        if( nodes[nrow+1][ncol  ]->path_type == 'l' ||
            nodes[nrow+1][ncol  ]->path_type == 'L' ) {
            Geom::Point s = (nodes[nrow+3][ncol]->p - nodes[nrow][ncol]->p)/3.0;
            nodes[nrow+1][ncol  ]->p = nodes[nrow][ncol]->p + s;
            nodes[nrow+2][ncol  ]->p = nodes[nrow+3][ncol]->p - s;
        } else {
            nodes[nrow+1][ncol  ]->p += dp;
        }
    }

    if( patch[3] || patch[0] ) {
        if( nodes[nrow  ][ncol-1]->path_type == 'l' ||
            nodes[nrow  ][ncol-1]->path_type == 'L' ) {
            Geom::Point s = (nodes[nrow][ncol-3]->p - nodes[nrow][ncol]->p)/3.0;
            nodes[nrow  ][ncol-1]->p = nodes[nrow][ncol]->p + s;
            nodes[nrow  ][ncol-2]->p = nodes[nrow][ncol-3]->p - s;
        } else {
            nodes[nrow  ][ncol-1]->p += dp;
        }
    }


    // Move tensors
    if( patch[0] ) nodes[nrow-1][ncol-1]->p += dp;
    if( patch[1] ) nodes[nrow-1][ncol+1]->p += dp;
    if( patch[2] ) nodes[nrow+1][ncol+1]->p += dp;
    if( patch[3] ) nodes[nrow+1][ncol-1]->p += dp;

    // // Check if neighboring corners are selected.

    // bool do_scale = false;

    // bool do_scale_xp = do_scale;
    // bool do_scale_xn = do_scale;
    // bool do_scale_yp = do_scale;
    // bool do_scale_yn = do_scale;

    // if( ccol < mcol+1 ) {
    //     if( std::find( sc.begin(), sc.end(), point_i + 1 ) != sc.end() ) {
    //         do_scale_xp = false;
    //         std::cout << "  Not scaling x+" << std::endl;
    //     }
    // }                        

    // if( ccol > 0 ) {
    //     if( std::find( sc.begin(), sc.end(), point_i - 1 ) != sc.end() ) {
    //         do_scale_xn = false;
    //         std::cout << "  Not scaling x-" << std::endl;
    //     }
    // }                        

    // if( crow < mrow+1 ) {
    //     if( std::find( sc.begin(), sc.end(), point_i + ncorners ) != sc.end() ) {
    //         do_scale_yp = false;
    //         std::cout << "  Not scaling y+" << std::endl;
    //     }
    // }                        

    // if( crow > 0 ) {
    //     if( std::find( sc.begin(), sc.end(), point_i - ncorners ) != sc.end() ) {
    //         do_scale_yn = false;
    //         std::cout << "  Not scaling y-" << std::endl;
    //     }
    // }                        

    // // We have four patches to adjust...
    // for ( guint k = 0; k < 4;  ++k ) {

    //     bool do_scale_x = do_scale;
    //     bool do_scale_y = do_scale;

    //     SPMeshNode* pnodes[4][4];

    //     // Load up matrix
    //     switch (k) {

    //         case 0:
    //             if( crow < mrow+1 && ccol < mcol+1 ) {
    //                 // Bottom right patch

    //                 do_scale_x = do_scale_xp;
    //                 do_scale_y = do_scale_yp;

    //                 for( guint i = 0; i < 4; ++i ) {
    //                     for( guint j = 0; j< 4; ++j ) {
    //                         pnodes[i][j] = mg->array.nodes[nrow+i][nrow+j];
    //                     }
    //                 }
    //             }
    //             break;

    //         case 1:
    //             if( crow < mrow+1 && ccol > 0 ) {
    //                 // Bottom left patch (note x, y swapped)

    //                 do_scale_y = do_scale_xn;
    //                 do_scale_x = do_scale_yp;

    //                 for( guint i = 0; i < 4; ++i ) {
    //                     for( guint j = 0; j< 4; ++j ) {
    //                         pnodes[j][i] = mg->array.nodes[nrow+i][nrow-j];
    //                     }
    //                 }
    //             }
    //             break;

    //         case 2:
    //             if( crow > 0 && ccol > 0 ) {
    //                 // Top left patch

    //                 do_scale_x = do_scale_xn;
    //                 do_scale_y = do_scale_yn;

    //                 for( guint i = 0; i < 4; ++i ) {
    //                     for( guint j = 0; j< 4; ++j ) {
    //                         pnodes[i][j] = mg->array.nodes[nrow-i][nrow-j];
    //                     }
    //                 }
    //             }
    //             break;

    //         case 3:
    //             if( crow > 0 && ccol < mcol+1 ) {
    //                 // Top right patch (note x, y swapped)

    //                 do_scale_y = do_scale_xp;
    //                 do_scale_x = do_scale_yn;

    //                 for( guint i = 0; i < 4; ++i ) {
    //                     for( guint j = 0; j< 4; ++j ) {
    //                         pnodes[j][i] = mg->array.nodes[nrow-i][nrow+j];
    //                     }
    //                 }
    //             }
    //             break;
    //     }

    //     // Now we must move points in both x and y.
    //     // There are upto six points to move: P01, P02, P11, P12, P21, P22.
    //     // (The points P10, P20 will be moved in another branch of the loop.
    //     // The points P03, P13, P23, P33, P32, P31, P30 are not moved.)
    //     //
    //     //   P00  P01  P02  P03
    //     //   P10  P11  P12  P13
    //     //   P20  P21  P22  P23
    //     //   P30  P31  P32  P33
    //     //
    //     // The goal is to preserve the direction of the handle!


    //     Geom::Point dsx_new = pnodes[0][3]->p - pnodes[0][0]->p; // New side x
    //     Geom::Point dsy_new = pnodes[3][0]->p - pnodes[0][0]->p; // New side y
    //     Geom::Point dsx_old = pnodes[0][3]->p - pcg_old; // Old side x
    //     Geom::Point dsy_old = pnodes[3][0]->p - pcg_old; // Old side y


    //     double scale_factor_x = 1.0;
    //     if( dsx_old.length() != 0.0 ) scale_factor_x = dsx_new.length()/dsx_old.length();

    //     double scale_factor_y = 1.0;
    //     if( dsy_old.length() != 0.0 ) scale_factor_y = dsy_new.length()/dsy_old.length();


    //     if( do_scalex && do_scaley ) {

    //         // We have six point to move.

    //         // P01
    //         Geom::Point dp01 = pnodes[0][1] - pcg_old;
    //         dp01 *= scale_factor_x;
    //         pnodes[0][1] = pnodes[0][0] + dp01;

    //         // P02
    //         Geom::Point dp02 = pnodes[0][2] - pnodes[0][3];
    //         dp02 *= scale_factor_x;
    //         pnodes[0][2] = pnodes[0][3] + dp02;

    //         // P11
    //         Geom::Point dp11 = pnodes[1][1] - pcg_old;
    //         dp11 *= scale_factor_x;
    //         pnodes[1][1] = pnodes[0][0] + dp11;



    //         // P21
    //         Geom::Point dp21 = pnodes[2][1] - pnodes[3][0];
    //         dp21 *= scale_factor_x;
    //         dp21 *= scale_factor_y;
    //         pnodes[2][1] = pnodes[3][0] + dp21;


    //         Geom::Point dsx1 = pnodes[0][1]->p - 
}

SPCurve SPMeshNodeArray::outline_path() const
{
    SPCurve outline;

    if (nodes.empty() ) {
        std::cerr << "SPMeshNodeArray::outline_path: empty array!" << std::endl;
        return outline;
    }

    outline.moveto( nodes[0][0]->p );

    int ncol = nodes[0].size();
    int nrow = nodes.size();

    // Top
    for (int i = 1; i < ncol; i += 3 ) {
        outline.curveto( nodes[0][i]->p, nodes[0][i+1]->p, nodes[0][i+2]->p);
    }

    // Right
    for (int i = 1; i < nrow; i += 3 ) {
        outline.curveto( nodes[i][ncol-1]->p, nodes[i+1][ncol-1]->p, nodes[i+2][ncol-1]->p);
    }

    // Bottom (right to left)
    for (int i = 1; i < ncol; i += 3 ) {
        outline.curveto( nodes[nrow-1][ncol-i-1]->p, nodes[nrow-1][ncol-i-2]->p, nodes[nrow-1][ncol-i-3]->p);
    }

    // Left (bottom to top)
    for (int i = 1; i < nrow; i += 3 ) {
        outline.curveto( nodes[nrow-i-1][0]->p, nodes[nrow-i-2][0]->p, nodes[nrow-i-3][0]->p);
    }

    outline.closepath();

    return outline;
}

void SPMeshNodeArray::transform(Geom::Affine const &m) {

    for (int i = 0; i < nodes[0].size(); ++i) {
        for (auto & node : nodes) {
            node[i]->p *= m;
        }
    }
}

// Transform mesh to fill box. Return true if mesh transformed.
bool SPMeshNodeArray::fill_box(Geom::OptRect &box) {

    // If gradientTransfor is set (as happens when an object is transformed
    // with the "optimized" preferences set true), we need to remove it.
    if (mg->gradientTransform_set) {
        Geom::Affine gt = mg->gradientTransform;
        transform( gt );
        mg->gradientTransform_set = false;
        mg->gradientTransform.setIdentity();
    }

    auto mesh_bbox = outline_path().get_pathvector().boundsExact();

    if (mesh_bbox->width() == 0 || mesh_bbox->height() == 0) {
        return false;
    }            

    double scale_x = (*box).width() /(*mesh_bbox).width() ;
    double scale_y = (*box).height()/(*mesh_bbox).height();

    Geom::Translate t1(-(*mesh_bbox).min());
    Geom::Scale scale(scale_x,scale_y);
    Geom::Translate t2((*box).min());
    Geom::Affine trans = t1 * scale * t2;
    if (!trans.isIdentity() ) {
        transform(trans);
        write( mg );
        mg->requestModified(SP_OBJECT_MODIFIED_FLAG);
        return true;
    }

    return false;
}

// Defined in gradient-chemistry.cpp
guint32 average_color(guint32 c1, guint32 c2, gdouble p);

/**
   Split a row into n equal parts.
*/
void SPMeshNodeArray::split_row( unsigned int row, unsigned int n ) {

    double nn = n;
    if( n > 1 ) split_row( row, (nn-1)/nn );
    if( n > 2 ) split_row( row, n-1 );
}

/**
   Split a column into n equal parts.
*/
void SPMeshNodeArray::split_column( unsigned int col, unsigned int n ) {

    double nn = n;
    if( n > 1 ) split_column( col, (nn-1)/nn );
    if( n > 2 ) split_column( col, n-1 );
}

/**
   Split a row into two rows at coord (fraction of row height).
*/
void SPMeshNodeArray::split_row( unsigned int row, double coord ) {

    // std::cout << "Splitting row: " << row << " at " << coord << std::endl;
    // print();
    assert( coord >= 0.0 && coord <= 1.0 );
    assert( row < patch_rows() );

    built = false;

    // First step is to ensure that handle and tensor points are up-to-date if they are not set.
    // (We can't do this on the fly as we overwrite the necessary points to do the calculation
    //  during the update.)
    for( guint j = 0; j < patch_columns(); ++ j ) {
        SPMeshPatchI patch( &nodes, row, j );
        patch.updateNodes();
    }

    // Add three new rows of empty nodes
    for( guint i = 0; i < 3; ++i ) {
        std::vector< SPMeshNode* > new_row;
        for( guint j = 0; j < nodes[0].size(); ++j ) {
            SPMeshNode* new_node = new SPMeshNode;
            new_row.push_back( new_node );
        }
        nodes.insert( nodes.begin()+3*(row+1), new_row );
    }

    guint i = 3 * row; // Convert from patch row to node row
    for( guint j = 0; j < nodes[i].size(); ++j ) {

        // std::cout << "Splitting row: column: " << j << std::endl;

        Geom::Point p[4];
        for( guint k = 0; k < 4; ++k ) {
            guint n = k;
            if( k == 3 ) n = 6; // Bottom patch row has been shifted by new rows
            p[k] = nodes[i+n][j]->p;
            // std::cout << p[k] << std::endl;
        }

        Geom::BezierCurveN<3> b( p[0], p[1], p[2], p[3] );

        std::pair<Geom::BezierCurveN<3>, Geom::BezierCurveN<3> > b_new =
            b.subdivide( coord );

        // Update points
        for( guint n = 0; n < 4; ++n ) {
            nodes[i+n  ][j]->p = b_new.first[n];
            nodes[i+n+3][j]->p = b_new.second[n];
            // std::cout << b_new.first[n] << "  " << b_new.second[n] << std::endl;
        }

        if( nodes[i][j]->node_type == MG_NODE_TYPE_CORNER ) {
            // We are splitting a side

            // Path type stored in handles.
            gchar path_type = nodes[i+1][j]->path_type; 
            nodes[i+4][j]->path_type = path_type;
            nodes[i+5][j]->path_type = path_type;
            bool  set = nodes[i+1][j]->set; 
            nodes[i+4][j]->set = set;
            nodes[i+5][j]->set = set;
            nodes[i+4][j]->node_type = MG_NODE_TYPE_HANDLE;
            nodes[i+5][j]->node_type = MG_NODE_TYPE_HANDLE;

            // Color stored in corners
            guint c0 = nodes[i  ][j]->color.toRGBA32( 1.0 );
            guint c1 = nodes[i+6][j]->color.toRGBA32( 1.0 );
            gdouble o0 = nodes[i  ][j]->opacity;
            gdouble o1 = nodes[i+6][j]->opacity;
            guint cnew = average_color( c0, c1, coord );
            gdouble onew = o0 * (1.0 - coord) + o1 * coord;
            nodes[i+3][j]->color.set( cnew );
            nodes[i+3][j]->opacity = onew;
            nodes[i+3][j]->node_type = MG_NODE_TYPE_CORNER;
            nodes[i+3][j]->set = true;

        } else {
            // We are splitting a middle

            bool set = nodes[i+1][j]->set || nodes[i+2][j]->set;
            nodes[i+4][j]->set = set;
            nodes[i+5][j]->set = set;
            nodes[i+4][j]->node_type = MG_NODE_TYPE_TENSOR;
            nodes[i+5][j]->node_type = MG_NODE_TYPE_TENSOR;

            // Path type, if different, choose l -> L -> c -> C.
            gchar path_type0 = nodes[i  ][j]->path_type;
            gchar path_type1 = nodes[i+6][j]->path_type;
            gchar path_type = 'l';
            if( path_type0 == 'L' || path_type1 == 'L') path_type = 'L';
            if( path_type0 == 'c' || path_type1 == 'c') path_type = 'c';
            if( path_type0 == 'C' || path_type1 == 'C') path_type = 'C';
            nodes[i+3][j]->path_type = path_type;
            nodes[i+3][j]->node_type = MG_NODE_TYPE_HANDLE;
            if( path_type == 'c' || path_type == 'C' ) nodes[i+3][j]->set = true;

        }

        nodes[i+3][j]->node_edge = MG_NODE_EDGE_NONE;
        nodes[i+4][j]->node_edge = MG_NODE_EDGE_NONE;
        nodes[i+5][j]->node_edge = MG_NODE_EDGE_NONE;;
        if( j == 0 ) {
            nodes[i+3][j]->node_edge |= MG_NODE_EDGE_LEFT;
            nodes[i+4][j]->node_edge |= MG_NODE_EDGE_LEFT;
            nodes[i+5][j]->node_edge |= MG_NODE_EDGE_LEFT;
        }
        if( j == nodes[i].size() - 1 ) {
            nodes[i+3][j]->node_edge |= MG_NODE_EDGE_RIGHT;
            nodes[i+4][j]->node_edge |= MG_NODE_EDGE_RIGHT;
            nodes[i+5][j]->node_edge |= MG_NODE_EDGE_RIGHT;
        }
    }

    // std::cout << "Splitting row: result:" << std::endl;
    // print();
}



/**
   Split a column into two columns at coord (fraction of column width).
*/
void SPMeshNodeArray::split_column( unsigned int col, double coord ) {

    // std::cout << "Splitting column: " << col << " at " << coord << std::endl;
    // print();
    assert( coord >= 0.0 && coord <= 1.0 );
    assert( col < patch_columns() );

    built = false;

    // First step is to ensure that handle and tensor points are up-to-date if they are not set.
    // (We can't do this on the fly as we overwrite the necessary points to do the calculation
    //  during the update.)
    for( guint i = 0; i < patch_rows(); ++ i ) {
        SPMeshPatchI patch( &nodes, i, col );
        patch.updateNodes();
    }

    guint j = 3 * col; // Convert from patch column to node column
    for( guint i = 0; i < nodes.size(); ++i ) {

        // std::cout << "Splitting column: row: " << i << std::endl;

        Geom::Point p[4];
        for( guint k = 0; k < 4; ++k ) {
            p[k] = nodes[i][j+k]->p;
        }

        Geom::BezierCurveN<3> b( p[0], p[1], p[2], p[3] );

        std::pair<Geom::BezierCurveN<3>, Geom::BezierCurveN<3> > b_new =
            b.subdivide( coord );

        // Add three new nodes
        for( guint n = 0; n < 3; ++n ) {
            SPMeshNode* new_node = new SPMeshNode;
            nodes[i].insert( nodes[i].begin()+j+3, new_node );
        }

        // Update points
        for( guint n = 0; n < 4; ++n ) {
            nodes[i][j+n]->p = b_new.first[n];
            nodes[i][j+n+3]->p = b_new.second[n];
        }

        if( nodes[i][j]->node_type == MG_NODE_TYPE_CORNER ) {
            // We are splitting a side

            // Path type stored in handles.
            gchar path_type = nodes[i][j+1]->path_type; 
            nodes[i][j+4]->path_type = path_type;
            nodes[i][j+5]->path_type = path_type;
            bool  set = nodes[i][j+1]->set; 
            nodes[i][j+4]->set = set;
            nodes[i][j+5]->set = set;
            nodes[i][j+4]->node_type = MG_NODE_TYPE_HANDLE;
            nodes[i][j+5]->node_type = MG_NODE_TYPE_HANDLE;

            // Color stored in corners
            guint c0 = nodes[i][j  ]->color.toRGBA32( 1.0 );
            guint c1 = nodes[i][j+6]->color.toRGBA32( 1.0 );
            gdouble o0 = nodes[i][j  ]->opacity;
            gdouble o1 = nodes[i][j+6]->opacity;
            guint cnew = average_color( c0, c1, coord );
            gdouble onew = o0 * (1.0 - coord) + o1 * coord;
            nodes[i][j+3]->color.set( cnew );
            nodes[i][j+3]->opacity = onew;
            nodes[i][j+3]->node_type = MG_NODE_TYPE_CORNER;
            nodes[i][j+3]->set = true;

        } else {
            // We are splitting a middle

            bool  set = nodes[i][j+1]->set || nodes[i][j+2]->set; 
            nodes[i][j+4]->set = set;
            nodes[i][j+5]->set = set;
            nodes[i][j+4]->node_type = MG_NODE_TYPE_TENSOR;
            nodes[i][j+5]->node_type = MG_NODE_TYPE_TENSOR;

            // Path type, if different, choose l -> L -> c -> C.
            gchar path_type0 = nodes[i][j  ]->path_type;
            gchar path_type1 = nodes[i][j+6]->path_type;
            gchar path_type = 'l';
            if( path_type0 == 'L' || path_type1 == 'L') path_type = 'L';
            if( path_type0 == 'c' || path_type1 == 'c') path_type = 'c';
            if( path_type0 == 'C' || path_type1 == 'C') path_type = 'C';
            nodes[i][j+3]->path_type = path_type;
            nodes[i][j+3]->node_type = MG_NODE_TYPE_HANDLE;
            if( path_type == 'c' || path_type == 'C' ) nodes[i][j+3]->set = true;

        }

        nodes[i][j+3]->node_edge = MG_NODE_EDGE_NONE;
        nodes[i][j+4]->node_edge = MG_NODE_EDGE_NONE;
        nodes[i][j+5]->node_edge = MG_NODE_EDGE_NONE;;
        if( i == 0 ) {
            nodes[i][j+3]->node_edge |= MG_NODE_EDGE_TOP;
            nodes[i][j+4]->node_edge |= MG_NODE_EDGE_TOP;
            nodes[i][j+5]->node_edge |= MG_NODE_EDGE_TOP;
        }
        if( i == nodes.size() - 1 ) {
            nodes[i][j+3]->node_edge |= MG_NODE_EDGE_BOTTOM;
            nodes[i][j+4]->node_edge |= MG_NODE_EDGE_BOTTOM;
            nodes[i][j+5]->node_edge |= MG_NODE_EDGE_BOTTOM;
        }

    }

    // std::cout << "Splitting col: result:" << std::endl;
    // print();
}

/*
  Local Variables:
  mode:c++
  c-file-style:"stroustrup"
  c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
  indent-tabs-mode:nil
  fill-column:99
  End:
*/
// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
