diff options
Diffstat (limited to '')
-rw-r--r-- | src/object/sp-mesh-array.cpp | 3097 |
1 files changed, 3097 insertions, 0 deletions
diff --git a/src/object/sp-mesh-array.cpp b/src/object/sp-mesh-array.cpp new file mode 100644 index 0000000..bca1dab --- /dev/null +++ b/src/object/sp-mesh-array.cpp @@ -0,0 +1,3097 @@ +// 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; + SPMeshGradient* mg_array = dynamic_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 (SP_IS_MESHROW(&ro)) { + ++rows; + if (rows == 1 ) { + for (auto& po: ro.children) { + if (SP_IS_MESHPATCH(&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 (SP_IS_MESHROW(&ro)) { + + guint icolumn = 0; // Corresponds to left of patch being read in. + for (auto& po: ro.children) { + + if (SP_IS_MESHPATCH(&po)) { + + SPMeshpatch *patch = SP_MESHPATCH(&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 (SP_IS_STOP(&so)) { + + if( istop > 3 ) { + // std::cout << " Mesh Gradient: Too many stops: " << istop << std::endl; + break; + } + + SPStop *stop = SP_STOP(&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; + + SPMeshGradient* mg_array = dynamic_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 = dynamic_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 ( SP_IS_STAR( item ) ) { + // But if it is a star... use star parameters! + SPStar* star = SP_STAR( item ); + center = star->center; + rx = star->r[0]; + ry = star->r[0]; + start = star->arg[0]; + end = start + 2.0 * M_PI; + } + + if ( SP_IS_GENERICELLIPSE( item ) ) { + // For arcs use set start/stop + SPGenericEllipse* arc = SP_GENERICELLIPSE( 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( SP_IS_GENERICELLIPSE( item ) ) { + + // std::cout << "We've got ourselves an arc!" << std::endl; + + SPGenericEllipse* arc = SP_GENERICELLIPSE( 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 ( SP_IS_STAR( 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.) + SPStar* star = SP_STAR( 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::cout << "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::cout << " 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::cout << " 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 - +} + +std::unique_ptr<SPCurve> SPMeshNodeArray::outline_path() const +{ + auto outline = std::make_unique<SPCurve>(); + + 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 outline = outline_path(); + Geom::OptRect mesh_bbox = outline->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 : |