sp-mesh-array.cpp revision 16b1e8c74378da09ee84d73987be64cd6322fff7
/** \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, read the file 'COPYING' for more information
*/
#include <glibmm.h>
// For color picking
#include "display/drawing-context.h"
#include "display/cairo-utils.h"
#include "document.h"
#include "sp-root.h"
#include "sp-mesh-array.h"
#include "sp-mesh-gradient.h"
#include "sp-mesh-row.h"
#include "sp-mesh-patch.h"
#include "sp-stop.h"
// For new mesh creation
#include "preferences.h"
#include "sp-ellipse.h"
#include "sp-star.h"
#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 <cmath>
#include <algorithm>
nodes = n;
col = c*3;
guint i = 0;
if( row != 0 ) i = 1;
for( ; i < 4; ++i ) {
}
guint j = 0;
if( col != 0 ) j = 1;
for( ; j < 4; ++j ) {
// Ensure all nodes know their type.
}
}
}
}
/**
Returns point for side in proper order for patch
*/
assert( s < 4 );
switch ( s ) {
case 0:
break;
case 1:
break;
case 2:
break;
case 3:
break;
}
return p;
};
/**
Returns vector of points for a side in proper order for a patch (clockwise order).
*/
assert( i < 4 );
return points;
};
/**
Set point for side in proper order for patch
*/
assert( s < 4 );
// 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:
break;
case 1:
break;
case 2:
break;
case 3:
break;
}
};
/**
Get path type for side (stored in handle nodes).
*/
assert( s < 4 );
switch ( s ) {
case 0:
break;
case 1:
break;
case 2:
break;
case 3:
break;
}
return type;
};
/**
Set path type for side (stored in handle nodes).
*/
assert( s < 4 );
switch ( s ) {
case 0:
break;
case 1:
break;
case 2:
break;
case 3:
break;
}
};
/**
Set tensor control point for "corner" i.
*/
assert( i < 4 );
switch ( i ) {
case 0:
break;
case 1:
break;
case 2:
break;
case 3:
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.
*/
assert( i < 4 );
bool set = false;
switch ( i ) {
case 0:
break;
case 1:
break;
case 2:
break;
case 3:
break;
}
return set;
}
/**
Return tensor control point for "corner" i.
If not sest, returns calculated (Coons) point.
*/
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;
}
} 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.
*/
p[0][0] = getPoint( 0, 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 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 ) {
}
if( j == 0 || j == 3 ) {
}
}
}
}
}
// Update tensor nodes
guint t = 0;
if( i == 1 && j == 2 ) t = 1;
if( i == 2 && j == 2 ) t = 2;
if( i == 2 && j == 1 ) t = 3;
// std::cout << "Update node: " << i << ", " << j << " " << coonsTensorPoint( t ) << std::endl;
}
}
}
}
/**
Return color for corner of patch.
*/
assert( i < 4 );
switch ( i ) {
case 0:
break;
case 1:
break;
case 2:
break;
case 3:
break;
}
return color;
};
/**
Set color for corner of patch.
*/
assert( i < 4 );
switch ( i ) {
case 0:
break;
case 1:
break;
case 2:
break;
case 3:
break;
}
};
/**
Return opacity for corner of patch.
*/
assert( i < 4 );
switch ( i ) {
case 0:
break;
case 1:
break;
case 2:
break;
case 3:
break;
}
return opacity;
};
/**
Set opacity for corner of patch.
*/
assert( i < 4 );
switch ( i ) {
case 0:
break;
case 1:
break;
case 2:
break;
case 3:
break;
}
};
};
// Copy constructor
built = false;
drag_valid = false;
}
}
};
// Copy assignment operator
if( this == &rhs ) return *this;
clear(); // Clear any existing array.
built = false;
drag_valid = false;
}
}
return *this;
};
clear();
// std::cout << "SPMeshNodeArray::read: p: " << current_p << std::endl;
guint max_column = 0;
if (SP_IS_MESHROW(ro)) {
if (SP_IS_MESHPATCH(po)) {
// std::cout << "SPMeshNodeArray::read: row size: " << nodes.size() << std::endl;
// std::cout << " after: " << nodes.size() << std::endl;
// Only 'top' side defined for first row.
if (SP_IS_STOP(so)) {
if( istop > 3 ) {
// std::cout << " Mesh Gradient: Too many stops: " << istop << std::endl;
break;
}
// Handle top of first row.
// First patch in mesh.
}
// First point is always already defined by previous side (stop).
// If side closes patch, then we read one less point.
bool closed = false;
// Copy path and then replace commas by spaces so we can use stringstream to parse
// std::cout << " path_string: " << path_string << std::endl;
// std::cout << " current_p: " << current_p << std::endl;
// Determine type of path
char path_type;
gdouble x, y;
switch ( path_type ) {
case 'l':
if( !closed ) {
os >> x >> y;
} else {
}
}
// To facilitate some side operations, set handles to 1/3 and
// 2/3 distance between corner points but flag as unset.
// std::cout << " istop: " << istop
// << " dp: " << dp
// << " p: " << p
// << " current_p: " << current_p
// << std::endl;
break;
case 'L':
if( !closed ) {
os >> x >> y;
} else {
}
}
// To facilitate some side operations, set handles to 1/3 and
// 2/3 distance between corner points but flag as unset.
break;
case 'c':
max = 4;
os >> x >> y;
p += current_p;
} else {
}
}
break;
case 'C':
max = 4;
os >> x >> y;
} else {
}
}
break;
default:
// should not reach
}
// Color
// skip
} else {
}
}
++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::cout << " tensor_string: " << tensor_string << std::endl;
for( guint i = 0; i < 4; ++i ) {
double x = 0.0;
double y = 0.0;
os >> x >> y;
} else {
break;
}
}
}
}
++icolumn;
}
}
++irow;
}
// Insure we have a true array.
}
// Set node edge.
}
}
// std::cout << "SPMeshNodeArray::Read: result:" << std::endl;
// print();
drag_valid = false;
built = true;
};
/**
Write repr using our array.
*/
// std::cout << "SPMeshNodeArray::write: entrance:" << std::endl;
// print();
using Geom::X;
using Geom::Y;
// First we must delete reprs for old mesh rows and patches.
}
}
}
}
sp_repr_unparent( repr );
}
// Now we build new reprs
// Write row
// Write patch
// Add tensor
if( patchi.tensorIsSet() ) {
for( guint k = 0; k < 4; ++k ) {
is << p[X] << "," << p[Y];
}
// std::cout << " SPMeshNodeArray::write: tensor: " << is.str() << std::endl;
}
// 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;
// Add path
switch ( path_type ) {
case 'l':
is << " "
<< ( p[3][Y] - current_p[Y] );
break;
case 'L':
is << " "
<< p[3][X] << ","
<< p[3][Y];
break;
case 'c':
is << " "
<< ( 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':
break;
default:
}
// 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().c_str() );
//stop->setAttribute("stop-opacity", patchi.getOpacity(k) );
}
}
}
}
}
/**
Find default color based on color of first stop in "vector" gradient.
This should be rewritten if dependence on "vector" is removed.
*/
// Set initial color to the color of the object before adding the mesh.
// This is a bit tricky as at the moment, a "vector" gradient is created
// before reaching here, replacing the original solid color. But the first
// stop will be that of the original object color.
} else if ( paint.isPaintserver() ) {
if ( SP_IS_GRADIENT(server) ) {
if ( firstStop ) {
if (firstStop->currentColor) {
}
} else {
}
}
}
}
} else {
}
return color;
}
/**
Create a default mesh.
*/
// std::cout << "SPMeshNodeArray::create: Entrance" << std::endl;
if( !bbox ) {
// Set default size to bounding box if size not given.
}
if( !bbox ) {
return;
}
// Must keep repr and array in sync. We have two choices:
// Build the repr first and the "read" it.
// Construct the array and the "write" it.
// We'll do the second.
// Remove any existing mesh. We could chose 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.
// Get default color
// Get preferences
if( mesh_type == SP_GRADIENT_MESH_TYPE_CONICAL ) {
// Start and end angles
if ( SP_IS_STAR( item ) ) {
// But if it is a star... use star parameters!
}
if ( SP_IS_GENERICELLIPSE( item ) ) {
}
// std::cout << " start: " << start << " end: " << end << std::endl;
// IS THIS NECESSARY?
// If less sections, arc approximation error too great. (Check!)
s += arc;
for( guint k = 0; k < 4; ++k ) {
}
// Set handle and tensor nodes.
patch.updateNodes();
}
} else {
// Normal grid meshes
if( SP_IS_GENERICELLIPSE( item ) ) {
// std::cout << "We've got ourselves an arc!" << std::endl;
for( guint i = 0; i < 4; ++i ) {
s += M_PI_2;
}
// Fill out tensor points
patch.updateNodes();
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.)
// std::cout << "We've got ourselves an star! Sides: " << sides << std::endl;
for( guint s = 0; s < 4; ++s ) {
}
// Set handle and tensor nodes.
patch.updateNodes();
} else {
for( guint s = 0; s < 4; ++s ) {
}
// Set handle and tensor nodes.
}
}
//print();
//split_column( 0, pcols );
} else {
// Generic
// Get node array size
if( i%3 == 0 ) {
if( j%3 == 0) {
// Corner
} else {
// Side
}
} else {
if( j%3 == 0) {
// Side
} else {
// Tensor
}
}
}
}
// End normal
}
} // If conical
//print();
// Write repr
}
/**
Clear mesh gradient.
*/
void SPMeshNodeArray::clear() {
if( nodes[i][j] ) {
delete nodes[i][j];
}
}
}
}
};
/**
Print mesh gradient (for debugging).
*/
void SPMeshNodeArray::print() {
if( nodes[i][j] ) {
<< nodes[i][j]->p
} else {
}
} // Loop over patches
} // Loop over rows
};
// Find the slopes at start and end for Hermite interpolation.
// Smooth using Hermite interpolation.
// Inputs are:
// pb: color value before patch
// p0: color value start of patch
// p1: color value end of patch
// pa: color value after patch
// lb0: distance between points b and 0
// l01: distance between points 0 and 1
// l1a: distance between points 1 and a
// type: Type of smoothing
// Output:
// m0: slope of Hermite function at start.
// m1: slope of Hermite function at end.
// We use Hermite interpolation. We have end points, we need tangents.
// Try various ways of finding tangents m0, m1
// Default to Catmul-Rom (assumes pb and pa already calculatedd)
// Finite differences
bool parabolic = false; // Require end patches to be parabolic
switch (type) {
case SP_MESH_SMOOTH_SMOOTH1:
// Flat
m0 = 0.0;
m1 = 0.0;
break;
case SP_MESH_SMOOTH_SMOOTH2:
if( is_first ) {
}
if( is_last ) {
}
break;
case SP_MESH_SMOOTH_SMOOTH3:
if( is_first ) {
}
if( is_last ) {
}
break;
case SP_MESH_SMOOTH_SMOOTH4:
// Catmul-Rom, Parabolic ends
parabolic = true;
break;
case SP_MESH_SMOOTH_SMOOTH5:
parabolic = true;
m0 = 0;
} else {
// ensure we don't overshoot
}
}
}
m1 = 0;
} else {
// ensure we don't overshoot
}
}
}
break;
case SP_MESH_SMOOTH_NONE:
default:
break;
}
// Force end patches to be parabolic
if( parabolic ) {
if( is_first ) {
// Constraint for parabola
m0 = 0; // Prevent overshooting start value;
}
} else if( is_last ) {
// Constraint for parabola
m1 = 0; // Prevent overshooting end value;
}
}
}
// std::cout << " pb: " << pb
// << " p0: " << p0
// << " p1: " << p1
// << " pa: " << pa
// << " m0: " << m0
// << " m1: " << m1 << std::endl;
}
double hermite( const double p0, const double p1, const double m0, const double m1, const double t ) {
double t2 = t*t;
return result;
}
/**
Fill 'smooth' with a smoothed version of the array by subdividing each patch into smaller patches.
*/
*smooth = *this; // Deep copy via copy assignment constructor, smooth cleared before copy
// std::cout << "SPMeshNodeArray::smooth(): " << this->patch_rows() << " " << smooth->patch_rows() << std::endl;
// std::cout << " " << smooth << " " << this << std::endl;
// Next split each patch into 8x8 smaller patches.
// Do rows first.
// Split each row into eight rows.
// Must do it from end so inserted rows don't mess up indexing
}
// Update color values (every third node is a corner)
for( unsigned i = 0; i < this->patch_rows(); ++i ) { // i is orignal patch index
bool is_first_row = (i == 0);
//std::cout << " last row: " << smooth->patch_rows()/8 - 1 << " " << is_last_row << std::endl;
// Can't use guint32 since delta can be negative
// Use linear distance to avoid calculation overhead of calculating true path length
double lb0 = 0.0;
double l1a = 0.0;
if( !is_first_row ) {
} else {
}
if( !is_last_row ) {
} else {
}
for( unsigned n = 0; n < 3; ++n ) { // Loop over colors
// We use Hermite interpolation. We have end points, we need tangents.
double m0 = 0;
double m1 = 0;
for( unsigned k = 1; k < 8; ++k ) {
double t = k/8.0;
// Cubic Hermite (four constraints)
// Clamp to allowed values
if( result[n][k] > 1.0 )
result[n][k] = 1.0;
if( result[n][k] < 0.0 )
result[n][k] = 0.0;
}
}
for( unsigned k = 1; k < 8; ++k ) {
}
}
}
// Split each column into eight columns.
// Must do it from end so inserted columns don't mess up indexing
}
// Update color values (every third node is a corner)
for( unsigned i = 0; i < this->patch_columns(); ++i ) { // i is orignal patch index
bool is_first_column = (i == 0);
//std::cout << " last column: " << smooth->patch_columns()/8 - 1 << " " << is_last_column << std::endl;
// Can't use guint32 since delta can be negative
// Use linear distance to avoid calculation overhead of calculating true path length
double lb0 = 0.0;
double l1a = 0.0;
if( !is_first_column ) {
} else {
}
if( !is_last_column ) {
} else {
}
for( unsigned n = 0; n < 3; ++n ) { // Loop over colors
// We use Hermite interpolation. We have end points, we need tangents.
double m0 = 0;
double m1 = 0;
for( unsigned k = 1; k < 8; ++k ) {
double t = k/8.0;
// Cubic Hermite (four constraints)
// Clamp to allowed values
if( result[n][k] > 1.0 )
result[n][k] = 1.0;
if( result[n][k] < 0.0 )
result[n][k] = 0.0;
}
}
for( unsigned k = 1; k < 8; ++k ) {
}
}
}
}
class SPMeshSmoothCorner {
enum {
AMP,
};
public:
for( unsigned i = 0; i < 3; ++i ) {
for( unsigned j = 0; j < 4; ++j ) {
g[i][j] = 0;
}
}
}
double g[3][8]; // 3 colors, 8 parameters: see enum.
};
// Find slope at point 1 given values at previous and next points
// Return value is slope in user space
double slope = 0;
// At minimum or maximum, use slope of zero
slope = 0;
} else {
// Ensure we don't overshoot
}
}
}
} 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
// pmm == d[i-1][j-1], ... 'm' is minus, 'p' is plus
// At minimum or maximum, use slope of zero
slope = 0;
} else {
// Don't really know what to do here
}
}
}
}
}
return slope;
}
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 result = 0;
double xx = x*x;
double yy = y*y;
return result;
}
/**
Fill 'smooth' with a smoothed version of the array by subdividing each patch into smaller patches.
*/
*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
for( unsigned i = 0; i < d.size(); ++i ) {
for( unsigned j = 0; j < d[i].size(); ++j ) {
float rgb_color[3];
d[i][j].g[0][0] = rgb_color[ 0 ];
}
}
// 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 ) {
}
// dy
if( j != 0 && j != d[i].size()-1 ) {
}
// 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] );
// }
} else {
// Catmul-Rom
if( i != 0 && i != d.size()-1 ) {
}
if( j != 0 && j != d[i].size()-1 ) {
}
}
}
}
}
// 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
unsigned z = d.size()-1;
// Parabolic
if( d0 > 0 ) {
} else {
d[0][j].g[k][1] = 0;
}
if( dz > 0 ) {
} else {
d[z][j].g[k][1] = 0;
}
} else {
// Catmul-Rom
if( d0 > 0 ) {
} else {
d[0][j].g[k][1] = 0;
}
if( dz > 0 ) {
} 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
unsigned z = d[0].size()-1;
// Parabolic
if( d0 > 0 ) {
} else {
d[i][0].g[k][2] = 0;
}
if( dz > 0 ) {
} else {
d[i][z].g[k][2] = 0;
}
} else {
// Catmul-Rom
if( d0 > 0 ) {
} else {
d[i][0].g[k][2] = 0;
}
if( dz > 0 ) {
} 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
}
// Split each column into eight columns.
// Must do it from end so inserted columns don't mess up indexing
}
// Fill new patches
for( unsigned i = 0; i < this->patch_rows(); ++i ) {
for( unsigned j = 0; j < this->patch_columns(); ++j ) {
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[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];
for( unsigned k = 0; k < 9; ++k ) {
for( unsigned l = 0; l < 9; ++l ) {
double x = k/8.0;
double y = l/8.0;
// 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
}
}
}
}
}
/**
Number of patch rows.
*/
}
/**
Number of patch columns.
*/
}
/**
Inputs:
i, j: Corner draggable indices.
Returns:
true if corners adjacent.
*/
// 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;
if( j < i ) {
c1 = j;
c2 = i;
}
// Number of corners in a row of patches.
// 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
adjacent = true;
for( guint k = 0; k < 4; ++k ) {
}
}
// Check for vertical neighbors
adjacent = true;
for( guint k = 0; k < 4; ++k ) {
}
}
return adjacent;
}
/**
Toggle sides between lineto and curve to if both corners selected.
Input is a list of selected corner draggable indices.
*/
SPMeshNode* n[4];
switch (path_type)
{
case 'L':
n[1]->set = true;
n[2]->set = true;
break;
case 'l':
n[1]->set = true;
n[2]->set = true;
break;
case 'C': {
n[1]->set = false;
n[2]->set = false;
// 'L' acts as if handles are 1/3 of path length from corners.
n[1]->p = n[0]->p + dp;
break;
}
case 'c': {
n[1]->set = false;
n[2]->set = false;
// 'l' acts as if handles are 1/3 of path length from corners.
n[1]->p = n[0]->p + dp;
// std::cout << "Toggle sides: "
// << n[0]->p << " "
// << n[1]->p << " "
// << n[2]->p << " "
// << n[3]->p << " "
// << dp << std::endl;
break;
}
default:
}
++toggled;
}
}
}
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.
*/
SPMeshNode* n[4];
switch (path_type)
{
case 'L':
case 'l':
break;
case 'C':
case 'c': {
if( crossing ) {
n[1]->p = n[0]->p + f*h1;
++arced;
} else {
}
} else {
}
break;
}
default:
}
}
}
}
return arced;
}
/**
Toggle sides between lineto and curve to if both corners selected.
Input is a list of selected corner draggable indices.
*/
// std::cout << "SPMeshNodeArray::tensor_toggle" << std::endl;
// Number of corners in a row of patches.
guint c[4];
c[0] = corners[i];
c[1] = corners[j];
c[2] = corners[k];
c[3] = corners[l];
// 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 &&
// Patch
// Upper left node of patch
// 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;
patch.updateNodes();
if( patch.tensorIsSet() ) {
// Unset tensor points
} else {
// Set tensor points
}
++toggled;
}
}
}
}
}
return toggled;
}
/**
Atempts to smooth color transitions across corners.
Input is a list of selected corner draggable indices.
*/
// std::cout << "SPMeshNodeArray::color_smooth" << std::endl;
// Number of corners in a row of patches.
// Number of node rows and columns
// std::cout << "SPMeshNodeArray::color_smooth: " << i << " " << corner << std::endl;
// Node row & col
SPMeshNode* n[7];
for( guint s = 0; s < 2; ++s ) {
bool smooth = false;
// Find neighboring nodes
if( s == 0 ) {
// Horizontal
for( guint j = 0; j < 7; ++j ) {
}
smooth = true;
}
} else {
// Vertical
for( guint j = 0; j < 7; ++j ) {
}
smooth = true;
}
}
if( smooth ) {
// Let the smoothing begin
// std::cout << " checking: " << ncol << " " << nrow << std::endl;
// Get initial slopes using closest handles.
double slope_ave[3];
double slope_diff[3];
// Color of corners
// Distance nodes from selected corner
for( guint k = 0; k < 7; ++k ) {
d[k]= n[k]->p - n[3]->p;
// std::cout << " d[" << k << "]: " << d[k].length() << std::endl;
}
for( guint c = 0; c < 3; ++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
cdm = c;
}
}
// std::cout << " cdm: " << cdm << std::endl;
// Find new handle positions:
double length_left = d[0].length();
}
// Move closest handle a maximum of mid point... but don't shorten
double max = 0.8;
}
}
// 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;
}
}
}
return smoothed;
}
/**
Pick color from background for selected corners.
*/
// std::cout << "SPMeshNodeArray::color_pick" << std::endl;
// Code inspired from clone tracing
// Setup...
// We need a copy of the drawing so we can hide the mesh.
pick_drawing->setRoot(pick_doc->getRoot()->invoke_show(*pick_drawing, pick_visionkey, SP_ITEM_SHOW_DISPLAY));
//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;
// 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
// Region to average over
// std::cout << " p: " << p << std::endl;
p *= gr->gradientTransform;
// std::cout << " p: " << p << std::endl;
// If on edge, move inward
double size = 3.0;
// Top edge
if( row == 0 ) {
}
// Right edge
}
// Bottom edge
}
// Left edge
if( col == 0 ) {
}
/* Item integer bbox in points */
/* Find visible area */
/* Render copy and pick color */
double R = 0, G = 0, B = 0, A = 0;
ink_cairo_surface_average_color(s, R, G, B, A);
// std::cout << " p: " << p
// << " box: " << ibox
// << " R: " << R
// << " G: " << G
// << " B: " << B
// << std::endl;
}
delete pick_drawing;
return picked;
}
/**
Moves handles in response to a corner node move.
p_old: orignal 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.
*/
void SPMeshNodeArray::update_handles( guint corner, std::vector< guint > /*selected*/, Geom::Point p_old, MeshNodeOperation /*op*/ )
{
assert( drag_valid );
// std::cout << "SPMeshNodeArray::update_handles: "
// << " corner: " << corner
// << " op: " << op
// << std::endl;
// Find number of patch rows and columns
// Number of corners in a row of patches.
// std::cout << " mrow: " << mrow
// << " mcol: " << mcol
// << " crow: " << crow
// << " ccol: " << ccol
// << " ncorners: " << ncorners
// << " nrow: " << nrow
// << " ncol: " << ncol
// << std::endl;
// New corner mesh coordinate.
// Corner point move dpg in mesh coordinate system.
// 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];
// std::cout << patch[0] << " "
// << patch[1] << " "
// << patch[2] << " "
// << patch[3] << std::endl;
// Move handles
} else {
}
}
} else {
}
}
} else {
}
}
} else {
}
}
// Move tensors
// // 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 -
}
// Defined in gradient-chemistry.cpp
/**
Split a row into n equal parts.
*/
double nn = n;
}
/**
Split a column into n equal parts.
*/
double nn = n;
}
/**
Split a row into two rows at coord (fraction of row height).
*/
// std::cout << "Splitting row: " << row << " at " << coord << std::endl;
// print();
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 ) {
patch.updateNodes();
}
// Add three new rows of empty nodes
for( guint i = 0; i < 3; ++i ) {
}
}
// std::cout << "Splitting row: column: " << j << std::endl;
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;
}
// Update points
for( guint n = 0; n < 4; ++n ) {
// std::cout << b_new.first[n] << " " << b_new.second[n] << std::endl;
}
// We are splitting a side
// Path type stored in handles.
// Color stored in corners
} else {
// We are splitting a middle
// Path type, if different, choose l -> L -> c -> C.
}
if( j == 0 ) {
}
}
}
// std::cout << "Splitting row: result:" << std::endl;
// print();
}
/**
Split a column into two columns at coord (fraction of column width).
*/
// std::cout << "Splitting column: " << col << " at " << coord << std::endl;
// print();
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 ) {
patch.updateNodes();
}
// std::cout << "Splitting column: row: " << i << std::endl;
for( guint k = 0; k < 4; ++k ) {
p[k] = nodes[i][j+k]->p;
}
// Add three new nodes
for( guint n = 0; n < 3; ++n ) {
}
// Update points
for( guint n = 0; n < 4; ++n ) {
}
// We are splitting a side
// Path type stored in handles.
// Color stored in corners
} else {
// We are splitting a middle
// Path type, if different, choose l -> L -> c -> C.
}
if( i == 0 ) {
}
}
}
// 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 :