1 // SPDX-License-Identifier: GPL-2.0-or-later
2 /** \file
3 A group of classes and functions for manipulating mesh gradients.
4
5 A mesh is made up of an array of patches. Each patch has four sides and four corners. The sides can
6 be shared between two patches and the corners between up to four.
7
8 The order of the points for each side always goes from left to right or top to bottom.
9 For sides 2 and 3 the points must be reversed when used (as in calls to cairo functions).
10
11 Two patches: (C=corner, S=side, H=handle, T=tensor)
12
13 C0 H1 H2 C1 C0 H1 H2 C1
14 + ---------- + ---------- +
15 | S0 | S0 |
16 H1 | T0 T1 |H1 T0 T1 | H1
17 |S3 S1|S3 S1|
18 H2 | T3 T2 |H2 T3 T2 | H2
19 | S2 | S2 |
20 + ---------- + ---------- +
21 C3 H1 H2 C2 C3 H1 H2 C2
22
23 The mesh is stored internally as an array of nodes that includes the tensor nodes.
24
25 Note: This code uses tensor points which are not part of the SVG2 plan at the moment.
26 Including tensor points was motivated by a desire to experiment with their usefulness
27 in smoothing color transitions. There doesn't seem to be much advantage for that
28 purpose. However including them internally allows for storing all the points in
29 an array which simplifies things like inserting new rows or columns.
30 */
31
32 /*
33 * Authors:
34 * Tavmjong Bah <tavmjong@free.fr>
35 *
36 * Copyright (C) 2012, 2015 Tavmjong Bah
37 *
38 * Released under GNU GPL v2+, read the file 'COPYING' for more information.
39 */
40
41 #include <glibmm.h>
42 #include <set>
43
44 // For color picking
45 #include "display/drawing.h"
46 #include "display/drawing-context.h"
47 #include "display/cairo-utils.h"
48 #include "document.h"
49 #include "sp-root.h"
50
51 #include "sp-mesh-gradient.h"
52 #include "sp-mesh-array.h"
53 #include "sp-mesh-row.h"
54 #include "sp-mesh-patch.h"
55 #include "sp-stop.h"
56 #include "display/curve.h"
57
58 // For new mesh creation
59 #include "preferences.h"
60 #include "sp-ellipse.h"
61 #include "sp-star.h"
62
63 // For writing color/opacity to style
64 #include "svg/css-ostringstream.h"
65
66 // For default color
67 #include "style.h"
68 #include "svg/svg-color.h"
69
70
71 // Includes bezier-curve.h, ray.h, crossing.h
72 #include "2geom/line.h"
73
74 #include "xml/repr.h"
75 #include <cmath>
76 #include <algorithm>
77
78 enum { ROW, COL };
79
SPMeshPatchI(std::vector<std::vector<SPMeshNode * >> * n,int r,int c)80 SPMeshPatchI::SPMeshPatchI( std::vector<std::vector< SPMeshNode* > > * n, int r, int c ) {
81
82 nodes = n;
83 row = r*3; // Convert from patch array to node array
84 col = c*3;
85
86 guint i = 0;
87 if( row != 0 ) i = 1;
88 for( ; i < 4; ++i ) {
89 if( nodes->size() < row+i+1 ) {
90 std::vector< SPMeshNode* > row;
91 nodes->push_back( row );
92 }
93
94 guint j = 0;
95 if( col != 0 ) j = 1;
96 for( ; j < 4; ++j ) {
97 if( (*nodes)[row+i].size() < col+j+1 ){
98 SPMeshNode* node = new SPMeshNode;
99 // Ensure all nodes know their type.
100 node->node_type = MG_NODE_TYPE_HANDLE;
101 if( (i == 0 || i == 3) && (j == 0 || j == 3 ) ) node->node_type = MG_NODE_TYPE_CORNER;
102 if( (i == 1 || i == 2) && (j == 1 || j == 2 ) ) node->node_type = MG_NODE_TYPE_TENSOR;
103 (*nodes)[row+i].push_back( node );
104 }
105 }
106 }
107 }
108
109 /**
110 Returns point for side in proper order for patch
111 */
getPoint(guint s,guint pt)112 Geom::Point SPMeshPatchI::getPoint( guint s, guint pt ) {
113
114 assert( s < 4 );
115 assert( pt < 4 );
116
117 Geom::Point p;
118 switch ( s ) {
119 case 0:
120 p = (*nodes)[ row ][ col+pt ]->p;
121 break;
122 case 1:
123 p = (*nodes)[ row+pt ][ col+3 ]->p;
124 break;
125 case 2:
126 p = (*nodes)[ row+3 ][ col+3-pt ]->p;
127 break;
128 case 3:
129 p = (*nodes)[ row+3-pt ][ col ]->p;
130 break;
131 }
132 return p;
133
134 };
135
136 /**
137 Returns vector of points for a side in proper order for a patch (clockwise order).
138 */
getPointsForSide(guint i)139 std::vector< Geom::Point > SPMeshPatchI::getPointsForSide( guint i ) {
140
141 assert( i < 4 );
142
143 std::vector< Geom::Point> points;
144 points.push_back( getPoint( i, 0 ) );
145 points.push_back( getPoint( i, 1 ) );
146 points.push_back( getPoint( i, 2 ) );
147 points.push_back( getPoint( i, 3 ) );
148 return points;
149 };
150
151
152 /**
153 Set point for side in proper order for patch
154 */
setPoint(guint s,guint pt,Geom::Point p,bool set)155 void SPMeshPatchI::setPoint( guint s, guint pt, Geom::Point p, bool set ) {
156
157 assert( s < 4 );
158 assert( pt < 4 );
159
160 NodeType node_type = MG_NODE_TYPE_CORNER;
161 if( pt == 1 || pt == 2 ) node_type = MG_NODE_TYPE_HANDLE;
162
163 // std::cout << "SPMeshPatchI::setPoint: s: " << s
164 // << " pt: " << pt
165 // << " p: " << p
166 // << " node_type: " << node_type
167 // << " set: " << set
168 // << " row: " << row
169 // << " col: " << col << std::endl;
170 switch ( s ) {
171 case 0:
172 (*nodes)[ row ][ col+pt ]->p = p;
173 (*nodes)[ row ][ col+pt ]->set = set;
174 (*nodes)[ row ][ col+pt ]->node_type = node_type;
175 break;
176 case 1:
177 (*nodes)[ row+pt ][ col+3 ]->p = p;
178 (*nodes)[ row+pt ][ col+3 ]->set = set;
179 (*nodes)[ row+pt ][ col+3 ]->node_type = node_type;
180 break;
181 case 2:
182 (*nodes)[ row+3 ][ col+3-pt ]->p = p;
183 (*nodes)[ row+3 ][ col+3-pt ]->set = set;
184 (*nodes)[ row+3 ][ col+3-pt ]->node_type = node_type;
185 break;
186 case 3:
187 (*nodes)[ row+3-pt ][ col ]->p = p;
188 (*nodes)[ row+3-pt ][ col ]->set = set;
189 (*nodes)[ row+3-pt ][ col ]->node_type = node_type;
190 break;
191 }
192
193 };
194
195 /**
196 Get path type for side (stored in handle nodes).
197 */
getPathType(guint s)198 gchar SPMeshPatchI::getPathType( guint s ) {
199
200 assert( s < 4 );
201
202 gchar type = 'x';
203
204 switch ( s ) {
205 case 0:
206 type = (*nodes)[ row ][ col+1 ]->path_type;
207 break;
208 case 1:
209 type = (*nodes)[ row+1 ][ col+3 ]->path_type;
210 break;
211 case 2:
212 type = (*nodes)[ row+3 ][ col+2 ]->path_type;
213 break;
214 case 3:
215 type = (*nodes)[ row+2 ][ col ]->path_type;
216 break;
217 }
218
219 return type;
220 };
221
222 /**
223 Set path type for side (stored in handle nodes).
224 */
setPathType(guint s,gchar t)225 void SPMeshPatchI::setPathType( guint s, gchar t ) {
226
227 assert( s < 4 );
228
229 switch ( s ) {
230 case 0:
231 (*nodes)[ row ][ col+1 ]->path_type = t;
232 (*nodes)[ row ][ col+2 ]->path_type = t;
233 break;
234 case 1:
235 (*nodes)[ row+1 ][ col+3 ]->path_type = t;
236 (*nodes)[ row+2 ][ col+3 ]->path_type = t;
237 break;
238 case 2:
239 (*nodes)[ row+3 ][ col+1 ]->path_type = t;
240 (*nodes)[ row+3 ][ col+2 ]->path_type = t;
241 break;
242 case 3:
243 (*nodes)[ row+1 ][ col ]->path_type = t;
244 (*nodes)[ row+2 ][ col ]->path_type = t;
245 break;
246 }
247
248 };
249
250 /**
251 Set tensor control point for "corner" i.
252 */
setTensorPoint(guint i,Geom::Point p)253 void SPMeshPatchI::setTensorPoint( guint i, Geom::Point p ) {
254
255 assert( i < 4 );
256 switch ( i ) {
257 case 0:
258 (*nodes)[ row + 1 ][ col + 1 ]->p = p;
259 (*nodes)[ row + 1 ][ col + 1 ]->set = true;
260 (*nodes)[ row + 1 ][ col + 1 ]->node_type = MG_NODE_TYPE_TENSOR;
261 break;
262 case 1:
263 (*nodes)[ row + 1 ][ col + 2 ]->p = p;
264 (*nodes)[ row + 1 ][ col + 2 ]->set = true;
265 (*nodes)[ row + 1 ][ col + 2 ]->node_type = MG_NODE_TYPE_TENSOR;
266 break;
267 case 2:
268 (*nodes)[ row + 2 ][ col + 2 ]->p = p;
269 (*nodes)[ row + 2 ][ col + 2 ]->set = true;
270 (*nodes)[ row + 2 ][ col + 2 ]->node_type = MG_NODE_TYPE_TENSOR;
271 break;
272 case 3:
273 (*nodes)[ row + 2 ][ col + 1 ]->p = p;
274 (*nodes)[ row + 2 ][ col + 1 ]->set = true;
275 (*nodes)[ row + 2 ][ col + 1 ]->node_type = MG_NODE_TYPE_TENSOR;
276 break;
277 }
278 }
279
280 /**
281 Return if any tensor control point is set.
282 */
tensorIsSet()283 bool SPMeshPatchI::tensorIsSet() {
284 for( guint i = 0; i < 4; ++i ) {
285 if( tensorIsSet( i ) ) {
286 return true;
287 }
288 }
289 return false;
290 }
291
292 /**
293 Return if tensor control point for "corner" i is set.
294 */
tensorIsSet(unsigned int i)295 bool SPMeshPatchI::tensorIsSet( unsigned int i ) {
296
297 assert( i < 4 );
298
299 bool set = false;
300 switch ( i ) {
301 case 0:
302 set = (*nodes)[ row + 1 ][ col + 1 ]->set;
303 break;
304 case 1:
305 set = (*nodes)[ row + 1 ][ col + 2 ]->set;
306 break;
307 case 2:
308 set = (*nodes)[ row + 2 ][ col + 2 ]->set;
309 break;
310 case 3:
311 set = (*nodes)[ row + 2 ][ col + 1 ]->set;
312 break;
313 }
314 return set;
315 }
316
317 /**
318 Return tensor control point for "corner" i.
319 If not set, returns calculated (Coons) point.
320 */
getTensorPoint(guint k)321 Geom::Point SPMeshPatchI::getTensorPoint( guint k ) {
322
323 assert( k < 4 );
324
325 guint i = 0;
326 guint j = 0;
327
328
329 switch ( k ) {
330 case 0:
331 i = 1;
332 j = 1;
333 break;
334 case 1:
335 i = 1;
336 j = 2;
337 break;
338 case 2:
339 i = 2;
340 j = 2;
341 break;
342 case 3:
343 i = 2;
344 j = 1;
345 break;
346 }
347
348 Geom::Point p;
349 if( (*nodes)[ row + i ][ col + j ]->set ) {
350 p = (*nodes)[ row + i ][ col + j ]->p;
351 } else {
352 p = coonsTensorPoint( k );
353 }
354 return p;
355 }
356
357 /**
358 Find default tensor point (equivalent point to Coons Patch).
359 Formulas defined in PDF spec.
360 Equivalent to 1/3 of side length from corner for square patch.
361 */
coonsTensorPoint(guint i)362 Geom::Point SPMeshPatchI::coonsTensorPoint( guint i ) {
363
364 Geom::Point t;
365 Geom::Point p[4][4]; // Points in PDF notation
366
367 p[0][0] = getPoint( 0, 0 );
368 p[0][1] = getPoint( 0, 1 );
369 p[0][2] = getPoint( 0, 2 );
370 p[0][3] = getPoint( 0, 3 );
371 p[1][0] = getPoint( 3, 2 );
372 p[1][3] = getPoint( 1, 1 );
373 p[2][0] = getPoint( 3, 1 );
374 p[2][3] = getPoint( 1, 2 );
375 p[3][0] = getPoint( 2, 3 );
376 p[3][1] = getPoint( 2, 2 );
377 p[3][2] = getPoint( 2, 1 );
378 p[3][3] = getPoint( 2, 0 );
379
380 switch ( i ) {
381 case 0:
382 t = ( -4.0 * p[0][0] +
383 6.0 * ( p[0][1] + p[1][0] ) +
384 -2.0 * ( p[0][3] + p[3][0] ) +
385 3.0 * ( p[3][1] + p[1][3] ) +
386 -1.0 * p[3][3] ) / 9.0;
387 break;
388
389 case 1:
390 t = ( -4.0 * p[0][3] +
391 6.0 * ( p[0][2] + p[1][3] ) +
392 -2.0 * ( p[0][0] + p[3][3] ) +
393 3.0 * ( p[3][2] + p[1][0] ) +
394 -1.0 * p[3][0] ) / 9.0;
395 break;
396
397 case 2:
398 t = ( -4.0 * p[3][3] +
399 6.0 * ( p[3][2] + p[2][3] ) +
400 -2.0 * ( p[3][0] + p[0][3] ) +
401 3.0 * ( p[0][2] + p[2][0] ) +
402 -1.0 * p[0][0] ) / 9.0;
403 break;
404
405 case 3:
406 t = ( -4.0 * p[3][0] +
407 6.0 * ( p[3][1] + p[2][0] ) +
408 -2.0 * ( p[3][3] + p[0][0] ) +
409 3.0 * ( p[0][1] + p[2][3] ) +
410 -1.0 * p[0][3] ) / 9.0;
411 break;
412
413 default:
414
415 g_warning( "Impossible!" );
416
417 }
418 return t;
419 }
420
421 /**
422 Update default values for handle and tensor nodes.
423 */
updateNodes()424 void SPMeshPatchI::updateNodes() {
425
426 // std::cout << "SPMeshPatchI::updateNodes: " << row << "," << col << std::endl;
427 // Handles first (tensors require update handles).
428 for( guint i = 0; i < 4; ++i ) {
429 for( guint j = 0; j < 4; ++j ) {
430 if( (*nodes)[ row + i ][ col + j ]->set == false ) {
431
432 if( (*nodes)[ row + i ][ col + j ]->node_type == MG_NODE_TYPE_HANDLE ) {
433
434 // If a handle is not set it is because the side is a line.
435 // Set node points 1/3 of the way between corners.
436
437 if( i == 0 || i == 3 ) {
438 Geom::Point p0 = ( (*nodes)[ row + i ][ col ]->p );
439 Geom::Point p3 = ( (*nodes)[ row + i ][ col + 3 ]->p );
440 Geom::Point dp = (p3 - p0)/3.0;
441 if( j == 2 ) dp *= 2.0;
442 (*nodes)[ row + i ][ col + j ]->p = p0 + dp;
443 }
444
445 if( j == 0 || j == 3 ) {
446 Geom::Point p0 = ( (*nodes)[ row ][ col + j ]->p );
447 Geom::Point p3 = ( (*nodes)[ row + 3 ][ col + j ]->p );
448 Geom::Point dp = (p3 - p0)/3.0;
449 if( i == 2 ) dp *= 2.0;
450 (*nodes)[ row + i ][ col + j ]->p = p0 + dp;
451 }
452 }
453 }
454 }
455 }
456
457 // Update tensor nodes
458 for( guint i = 1; i < 3; ++i ) {
459 for( guint j = 1; j < 3; ++j ) {
460 if( (*nodes)[ row + i ][ col + j ]->set == false ) {
461
462 (*nodes)[ row + i ][ col + j ]->node_type = MG_NODE_TYPE_TENSOR;
463
464 guint t = 0;
465 if( i == 1 && j == 2 ) t = 1;
466 if( i == 2 && j == 2 ) t = 2;
467 if( i == 2 && j == 1 ) t = 3;
468 (*nodes)[ row + i ][ col + j ]->p = coonsTensorPoint( t );
469 // std::cout << "Update node: " << i << ", " << j << " " << coonsTensorPoint( t ) << std::endl;
470
471 }
472 }
473 }
474 }
475
476 /**
477 Return color for corner of patch.
478 */
getColor(guint i)479 SPColor SPMeshPatchI::getColor( guint i ) {
480
481 assert( i < 4 );
482
483 SPColor color;
484 switch ( i ) {
485 case 0:
486 color = (*nodes)[ row ][ col ]->color;
487 break;
488 case 1:
489 color = (*nodes)[ row ][ col+3 ]->color;
490 break;
491 case 2:
492 color = (*nodes)[ row+3 ][ col+3 ]->color;
493 break;
494 case 3:
495 color = (*nodes)[ row+3 ][ col ]->color;
496 break;
497
498 }
499
500 return color;
501
502 };
503
504 /**
505 Set color for corner of patch.
506 */
setColor(guint i,SPColor color)507 void SPMeshPatchI::setColor( guint i, SPColor color ) {
508
509 assert( i < 4 );
510
511 switch ( i ) {
512 case 0:
513 (*nodes)[ row ][ col ]->color = color;
514 break;
515 case 1:
516 (*nodes)[ row ][ col+3 ]->color = color;
517 break;
518 case 2:
519 (*nodes)[ row+3 ][ col+3 ]->color = color;
520 break;
521 case 3:
522 (*nodes)[ row+3 ][ col ]->color = color;
523 break;
524 }
525 };
526
527 /**
528 Return opacity for corner of patch.
529 */
getOpacity(guint i)530 gdouble SPMeshPatchI::getOpacity( guint i ) {
531
532 assert( i < 4 );
533
534 gdouble opacity = 0.0;
535 switch ( i ) {
536 case 0:
537 opacity = (*nodes)[ row ][ col ]->opacity;
538 break;
539 case 1:
540 opacity = (*nodes)[ row ][ col+3 ]->opacity;
541 break;
542 case 2:
543 opacity = (*nodes)[ row+3 ][ col+3 ]->opacity;
544 break;
545 case 3:
546 opacity = (*nodes)[ row+3 ][ col ]->opacity;
547 break;
548 }
549
550 return opacity;
551 };
552
553
554 /**
555 Set opacity for corner of patch.
556 */
setOpacity(guint i,gdouble opacity)557 void SPMeshPatchI::setOpacity( guint i, gdouble opacity ) {
558
559 assert( i < 4 );
560
561 switch ( i ) {
562 case 0:
563 (*nodes)[ row ][ col ]->opacity = opacity;
564 break;
565 case 1:
566 (*nodes)[ row ][ col+3 ]->opacity = opacity;
567 break;
568 case 2:
569 (*nodes)[ row+3 ][ col+3 ]->opacity = opacity;
570 break;
571 case 3:
572 (*nodes)[ row+3 ][ col ]->opacity = opacity;
573 break;
574
575 }
576
577 };
578
579
580 /**
581 Return stop pointer for corner of patch.
582 */
getStopPtr(guint i)583 SPStop* SPMeshPatchI::getStopPtr( guint i ) {
584
585 assert( i < 4 );
586
587 SPStop* stop = nullptr;
588 switch ( i ) {
589 case 0:
590 stop = (*nodes)[ row ][ col ]->stop;
591 break;
592 case 1:
593 stop = (*nodes)[ row ][ col+3 ]->stop;
594 break;
595 case 2:
596 stop = (*nodes)[ row+3 ][ col+3 ]->stop;
597 break;
598 case 3:
599 stop = (*nodes)[ row+3 ][ col ]->stop;
600 break;
601 }
602
603 return stop;
604 };
605
606
607 /**
608 Set stop pointer for corner of patch.
609 */
setStopPtr(guint i,SPStop * stop)610 void SPMeshPatchI::setStopPtr( guint i, SPStop* stop ) {
611
612 assert( i < 4 );
613
614 switch ( i ) {
615 case 0:
616 (*nodes)[ row ][ col ]->stop = stop;
617 break;
618 case 1:
619 (*nodes)[ row ][ col+3 ]->stop = stop;
620 break;
621 case 2:
622 (*nodes)[ row+3 ][ col+3 ]->stop = stop;
623 break;
624 case 3:
625 (*nodes)[ row+3 ][ col ]->stop = stop;
626 break;
627
628 }
629
630 };
631
632
SPMeshNodeArray(SPMeshGradient * mg)633 SPMeshNodeArray::SPMeshNodeArray( SPMeshGradient *mg ) {
634
635 read( mg );
636
637 };
638
639
640 // Copy constructor
SPMeshNodeArray(const SPMeshNodeArray & rhs)641 SPMeshNodeArray::SPMeshNodeArray( const SPMeshNodeArray& rhs ) :
642 nodes(rhs.nodes) // This only copies the pointers but it does size the vector of vectors.
643 {
644
645 built = false;
646 mg = nullptr;
647 draggers_valid = false;
648
649 for( unsigned i=0; i < nodes.size(); ++i ) {
650 for( unsigned j=0; j < nodes[i].size(); ++j ) {
651 nodes[i][j] = new SPMeshNode( *rhs.nodes[i][j] ); // Copy data.
652 }
653 }
654 };
655
656
657 // Copy assignment operator
operator =(const SPMeshNodeArray & rhs)658 SPMeshNodeArray& SPMeshNodeArray::operator=( const SPMeshNodeArray& rhs ) {
659
660 if( this == &rhs ) return *this;
661
662 clear(); // Clear any existing array.
663
664 built = false;
665 mg = nullptr;
666 draggers_valid = false;
667
668 nodes = rhs.nodes; // This only copies the pointers but it does size the vector of vectors.
669
670 for( unsigned i=0; i < nodes.size(); ++i ) {
671 for( unsigned j=0; j < nodes[i].size(); ++j ) {
672 nodes[i][j] = new SPMeshNode( *rhs.nodes[i][j] ); // Copy data.
673 }
674 }
675
676 return *this;
677 };
678
679 // Fill array with data from mesh objects.
680 // Returns true of array's dimensions unchanged.
read(SPMeshGradient * mg_in)681 bool SPMeshNodeArray::read( SPMeshGradient *mg_in ) {
682
683 mg = mg_in;
684 SPMeshGradient* mg_array = dynamic_cast<SPMeshGradient*>(mg->getArray());
685 if (!mg_array) {
686 std::cerr << "SPMeshNodeArray::read: No mesh array!" << std::endl;
687 return false;
688 }
689 // std::cout << "SPMeshNodeArray::read: " << mg_in << " array: " << mg_array << std::endl;
690
691 // Count rows and columns, if unchanged reuse array to keep draggers valid.
692 unsigned cols = 0;
693 unsigned rows = 0;
694 for (auto& ro: mg_array->children) {
695 if (SP_IS_MESHROW(&ro)) {
696 ++rows;
697 if (rows == 1 ) {
698 for (auto& po: ro.children) {
699 if (SP_IS_MESHPATCH(&po)) {
700 ++cols;
701 }
702 }
703 }
704 }
705 }
706 bool same_size = true;
707 if (cols != patch_columns() || rows != patch_rows() ) {
708 // Draggers will be invalidated.
709 same_size = false;
710 clear();
711 draggers_valid = false;
712 }
713
714 Geom::Point current_p( mg->x.computed, mg->y.computed );
715 // std::cout << "SPMeshNodeArray::read: p: " << current_p << std::endl;
716
717 guint max_column = 0;
718 guint irow = 0; // Corresponds to top of patch being read in.
719 for (auto& ro: mg_array->children) {
720
721 if (SP_IS_MESHROW(&ro)) {
722
723 guint icolumn = 0; // Corresponds to left of patch being read in.
724 for (auto& po: ro.children) {
725
726 if (SP_IS_MESHPATCH(&po)) {
727
728 SPMeshpatch *patch = SP_MESHPATCH(&po);
729
730 // std::cout << "SPMeshNodeArray::read: row size: " << nodes.size() << std::endl;
731 SPMeshPatchI new_patch( &nodes, irow, icolumn ); // Adds new nodes.
732 // std::cout << " after: " << nodes.size() << std::endl;
733
734 gint istop = 0;
735
736 // Only 'top' side defined for first row.
737 if( irow != 0 ) ++istop;
738
739 for (auto& so: po.children) {
740 if (SP_IS_STOP(&so)) {
741
742 if( istop > 3 ) {
743 // std::cout << " Mesh Gradient: Too many stops: " << istop << std::endl;
744 break;
745 }
746
747 SPStop *stop = SP_STOP(&so);
748
749 // Handle top of first row.
750 if( istop == 0 && icolumn == 0 ) {
751 // First patch in mesh.
752 new_patch.setPoint( 0, 0, current_p );
753 }
754 // First point is always already defined by previous side (stop).
755 current_p = new_patch.getPoint( istop, 0 );
756
757 // If side closes patch, then we read one less point.
758 bool closed = false;
759 if( icolumn == 0 && istop == 3 ) closed = true;
760 if( icolumn > 0 && istop == 2 ) closed = true;
761
762
763 // Copy path and then replace commas by spaces so we can use stringstream to parse
764 std::string path_string = stop->path_string->raw();
765 std::replace(path_string.begin(),path_string.end(),',',' ');
766
767 // std::cout << " path_string: " << path_string << std::endl;
768 // std::cout << " current_p: " << current_p << std::endl;
769
770 std::stringstream os( path_string );
771
772 // Determine type of path
773 char path_type;
774 os >> path_type;
775 new_patch.setPathType( istop, path_type );
776
777 gdouble x, y;
778 Geom::Point p, dp;
779 guint max;
780 switch ( path_type ) {
781 case 'l':
782 if( !closed ) {
783 os >> x >> y;
784 if( !os.fail() ) {
785 dp = Geom::Point( x, y );
786 new_patch.setPoint( istop, 3, current_p + dp );
787 } else {
788 std::cerr << "Failed to read l" << std::endl;
789 }
790 }
791 // To facilitate some side operations, set handles to 1/3 and
792 // 2/3 distance between corner points but flag as unset.
793 p = new_patch.getPoint( istop, 3 );
794 dp = (p - current_p)/3.0; // Calculate since may not be set if closed.
795 // std::cout << " istop: " << istop
796 // << " dp: " << dp
797 // << " p: " << p
798 // << " current_p: " << current_p
799 // << std::endl;
800 new_patch.setPoint( istop, 1, current_p + dp, false );
801 new_patch.setPoint( istop, 2, current_p + 2.0 * dp, false );
802 break;
803 case 'L':
804 if( !closed ) {
805 os >> x >> y;
806 if( !os.fail() ) {
807 p = Geom::Point( x, y );
808 new_patch.setPoint( istop, 3, p );
809 } else {
810 std::cerr << "Failed to read L" << std::endl;
811 }
812 }
813 // To facilitate some side operations, set handles to 1/3 and
814 // 2/3 distance between corner points but flag as unset.
815 p = new_patch.getPoint( istop, 3 );
816 dp = (p - current_p)/3.0;
817 new_patch.setPoint( istop, 1, current_p + dp, false );
818 new_patch.setPoint( istop, 2, current_p + 2.0 * dp, false );
819 break;
820 case 'c':
821 max = 4;
822 if( closed ) max = 3;
823 for( guint i = 1; i < max; ++i ) {
824 os >> x >> y;
825 if( !os.fail() ) {
826 p = Geom::Point( x, y );
827 p += current_p;
828 new_patch.setPoint( istop, i, p );
829 } else {
830 std::cerr << "Failed to read c: " << i << std::endl;
831 }
832 }
833 break;
834 case 'C':
835 max = 4;
836 if( closed ) max = 3;
837 for( guint i = 1; i < max; ++i ) {
838 os >> x >> y;
839 if( !os.fail() ) {
840 p = Geom::Point( x, y );
841 new_patch.setPoint( istop, i, p );
842 } else {
843 std::cerr << "Failed to read C: " << i << std::endl;
844 }
845 }
846 break;
847 default:
848 // should not reach
849 std::cerr << "Path Error: unhandled path type: " << path_type << std::endl;
850 }
851 current_p = new_patch.getPoint( istop, 3 );
852
853 // Color
854 if( (istop == 0 && irow == 0 && icolumn > 0) || (istop == 1 && irow > 0 ) ) {
855 // skip
856 } else {
857 SPColor color = stop->getColor();
858 double opacity = stop->getOpacity();
859 new_patch.setColor( istop, color );
860 new_patch.setOpacity( istop, opacity );
861 new_patch.setStopPtr( istop, stop );
862 }
863 ++istop;
864 }
865 } // Loop over stops
866
867 // Read in tensor string after stops since tensor nodes defined relative to corner nodes.
868
869 // Copy string and then replace commas by spaces so we can use stringstream to parse XXXX
870 if( patch->tensor_string ) {
871 std::string tensor_string = patch->tensor_string->raw();
872 std::replace(tensor_string.begin(),tensor_string.end(),',',' ');
873
874 // std::cout << " tensor_string: " << tensor_string << std::endl;
875
876 std::stringstream os( tensor_string );
877 for( guint i = 0; i < 4; ++i ) {
878 double x = 0.0;
879 double y = 0.0;
880 os >> x >> y;
881 if( !os.fail() ) {
882 new_patch.setTensorPoint( i, new_patch.getPoint( i, 0 ) + Geom::Point( x, y ) );
883 } else {
884 std::cerr << "Failed to read p: " << i << std::endl;
885 break;
886 }
887 }
888 }
889 ++icolumn;
890 if( max_column < icolumn ) max_column = icolumn;
891 }
892 }
893 ++irow;
894 }
895 }
896
897 // Insure we have a true array.
898 for(auto & node : nodes) {
899 node.resize( max_column * 3 + 1 );
900 }
901
902 // Set node edge.
903 for( guint i = 0; i < nodes.size(); ++i ) {
904 for( guint j = 0; j < nodes[i].size(); ++j ) {
905 nodes[i][j]->node_edge = MG_NODE_EDGE_NONE;
906 if( i == 0 ) nodes[i][j]->node_edge |= MG_NODE_EDGE_TOP;
907 if( i == nodes.size() - 1 ) nodes[i][j]->node_edge |= MG_NODE_EDGE_BOTTOM;
908 if( j == 0 ) nodes[i][j]->node_edge |= MG_NODE_EDGE_RIGHT;
909 if( j == nodes[i].size() - 1 ) nodes[i][j]->node_edge |= MG_NODE_EDGE_LEFT;
910 }
911 }
912
913 // std::cout << "SPMeshNodeArray::Read: result:" << std::endl;
914 // print();
915
916 built = true;
917
918 return same_size;
919 };
920
921 /**
922 Write repr using our array.
923 */
write(SPMeshGradient * mg)924 void SPMeshNodeArray::write( SPMeshGradient *mg ) {
925
926 // std::cout << "SPMeshNodeArray::write: entrance:" << std::endl;
927 // print();
928 using Geom::X;
929 using Geom::Y;
930
931 SPMeshGradient* mg_array = dynamic_cast<SPMeshGradient*>(mg->getArray());
932 if (!mg_array) {
933 // std::cerr << "SPMeshNodeArray::write: missing patches!" << std::endl;
934 mg_array = mg;
935 }
936
937 // First we must delete reprs for old mesh rows and patches. We only need to call the
938 // deleteObject() method, which in turn calls sp_repr_unparent. Since iterators do not play
939 // well with boost::intrusive::list (which ChildrenList derive from) we need to iterate over a
940 // copy of the pointers to the objects.
941 std::vector<SPObject*> children_pointers;
942 for (auto& row : mg_array->children) {
943 children_pointers.push_back(&row);
944 }
945
946 for (auto i : children_pointers) {
947 i->deleteObject();
948 }
949
950 // Now we build new reprs
951 Inkscape::XML::Node *mesh = mg->getRepr();
952 Inkscape::XML::Node *mesh_array = mg_array->getRepr();
953
954 SPMeshNodeArray* array = &(mg_array->array);
955 SPMeshPatchI patch0( &(array->nodes), 0, 0 );
956 Geom::Point current_p = patch0.getPoint( 0, 0 ); // Side 0, point 0
957
958 sp_repr_set_svg_double( mesh, "x", current_p[X] );
959 sp_repr_set_svg_double( mesh, "y", current_p[Y] );
960
961 Geom::Point current_p2( mg->x.computed, mg->y.computed );
962
963 Inkscape::XML::Document *xml_doc = mesh->document();
964 guint rows = array->patch_rows();
965 for( guint i = 0; i < rows; ++i ) {
966
967 // Write row
968 Inkscape::XML::Node *row = xml_doc->createElement("svg:meshrow");
969 mesh_array->appendChild( row ); // No attributes
970
971 guint columns = array->patch_columns();
972 for( guint j = 0; j < columns; ++j ) {
973
974 // Write patch
975 Inkscape::XML::Node *patch = xml_doc->createElement("svg:meshpatch");
976
977 SPMeshPatchI patchi( &(array->nodes), i, j );
978
979 // Add tensor
980 if( patchi.tensorIsSet() ) {
981
982 std::stringstream is;
983
984 for( guint k = 0; k < 4; ++k ) {
985 Geom::Point p = patchi.getTensorPoint( k ) - patchi.getPoint( k, 0 );
986 is << p[X] << "," << p[Y];
987 if( k < 3 ) is << " ";
988 }
989
990 patch->setAttribute("tensor", is.str());
991 // std::cout << " SPMeshNodeArray::write: tensor: " << is.str() << std::endl;
992 }
993
994 row->appendChild( patch );
995
996 // Write sides
997 for( guint k = 0; k < 4; ++k ) {
998
999 // Only first row has top stop
1000 if( k == 0 && i != 0 ) continue;
1001
1002 // Only first column has left stop
1003 if( k == 3 && j != 0 ) continue;
1004
1005 Inkscape::XML::Node *stop = xml_doc->createElement("svg:stop");
1006
1007 // Add path
1008 std::stringstream is;
1009 char path_type = patchi.getPathType( k );
1010 is << path_type;
1011
1012 std::vector< Geom::Point> p = patchi.getPointsForSide( k );
1013 current_p = patchi.getPoint( k, 0 );
1014
1015 switch ( path_type ) {
1016 case 'l':
1017 is << " "
1018 << ( p[3][X] - current_p[X] ) << ","
1019 << ( p[3][Y] - current_p[Y] );
1020 break;
1021 case 'L':
1022 is << " "
1023 << p[3][X] << ","
1024 << p[3][Y];
1025 break;
1026 case 'c':
1027 is << " "
1028 << ( p[1][X] - current_p[X] ) << ","
1029 << ( p[1][Y] - current_p[Y] ) << " "
1030 << ( p[2][X] - current_p[X] ) << ","
1031 << ( p[2][Y] - current_p[Y] ) << " "
1032 << ( p[3][X] - current_p[X] ) << ","
1033 << ( p[3][Y] - current_p[Y] );
1034 break;
1035 case 'C':
1036 is << " "
1037 << p[1][X] << ","
1038 << p[1][Y] << " "
1039 << p[2][X] << ","
1040 << p[2][Y] << " "
1041 << p[3][X] << ","
1042 << p[3][Y];
1043 break;
1044 case 'z':
1045 case 'Z':
1046 std::cerr << "SPMeshNodeArray::write(): bad path type" << path_type << std::endl;
1047 break;
1048 default:
1049 std::cerr << "SPMeshNodeArray::write(): unhandled path type" << path_type << std::endl;
1050 }
1051 stop->setAttribute("path", is.str());
1052 // std::cout << "SPMeshNodeArray::write: path: " << is.str().c_str() << std::endl;
1053 // Add stop-color
1054 if( ( k == 0 && i == 0 && j == 0 ) ||
1055 ( k == 1 && i == 0 ) ||
1056 ( k == 2 ) ||
1057 ( k == 3 && j == 0 ) ) {
1058
1059 // Why are we setting attribute and not style?
1060 //stop->setAttribute("stop-color", patchi.getColor(k).toString() );
1061 //stop->setAttribute("stop-opacity", patchi.getOpacity(k) );
1062
1063 Inkscape::CSSOStringStream os;
1064 os << "stop-color:" << patchi.getColor(k).toString() << ";stop-opacity:" << patchi.getOpacity(k);
1065 stop->setAttribute("style", os.str());
1066 }
1067 patch->appendChild( stop );
1068 }
1069 }
1070 }
1071 }
1072
1073 /**
1074 * Find default color based on colors in existing fill.
1075 */
default_color(SPItem * item)1076 static SPColor default_color( SPItem *item ) {
1077
1078 SPColor color( 0.5, 0.0, 0.5 );
1079
1080 if ( item->style ) {
1081 SPIPaint const &paint = ( item->style->fill ); // Could pick between style.fill/style.stroke
1082 if ( paint.isColor() ) {
1083 color = paint.value.color;
1084 } else if ( paint.isPaintserver() ) {
1085 SPObject const *server = item->style->getFillPaintServer();
1086 if ( SP_IS_GRADIENT(server) && SP_GRADIENT(server)->getVector() ) {
1087 SPStop *firstStop = SP_GRADIENT(server)->getVector()->getFirstStop();
1088 if ( firstStop ) {
1089 color = firstStop->getColor();
1090 }
1091 }
1092 }
1093 } else {
1094 std::cerr << " SPMeshNodeArray: default_color(): No style" << std::endl;
1095 }
1096
1097 return color;
1098 }
1099
1100 /**
1101 Create a default mesh.
1102 */
create(SPMeshGradient * mg,SPItem * item,Geom::OptRect bbox)1103 void SPMeshNodeArray::create( SPMeshGradient *mg, SPItem *item, Geom::OptRect bbox ) {
1104
1105 // std::cout << "SPMeshNodeArray::create: Entrance" << std::endl;
1106
1107 if( !bbox ) {
1108 // Set default size to bounding box if size not given.
1109 std::cerr << "SPMeshNodeArray::create(): bbox empty" << std::endl;
1110 bbox = item->geometricBounds();
1111
1112 if( !bbox ) {
1113 std::cerr << "SPMeshNodeArray::create: ERROR: No bounding box!" << std::endl;
1114 return;
1115 }
1116 }
1117
1118 Geom::Coord const width = bbox->dimensions()[Geom::X];
1119 Geom::Coord const height = bbox->dimensions()[Geom::Y];
1120 Geom::Point center = bbox->midpoint();
1121
1122 // Must keep repr and array in sync. We have two choices:
1123 // Build the repr first and then "read" it.
1124 // Construct the array and then "write" it.
1125 // We'll do the second.
1126
1127 // Remove any existing mesh. We could choose to simply scale an existing mesh...
1128 //clear();
1129
1130 // We get called twice when a new mesh is created...WHY?
1131 // return if we've already constructed the mesh.
1132 if( !nodes.empty() ) return;
1133
1134 // Set 'gradientUnits'. Our calculations assume "userSpaceOnUse".
1135 Inkscape::XML::Node *repr = mg->getRepr();
1136 repr->setAttribute("gradientUnits", "userSpaceOnUse");
1137
1138 // Get default color
1139 SPColor color = default_color( item );
1140
1141 // Set some corners to white so we can see the mesh.
1142 SPColor white( 1.0, 1.0, 1.0 );
1143 if (color == white) {
1144 // If default color is white, set other color to black.
1145 white = SPColor( 0.0, 0.0, 0.0 );
1146 }
1147
1148 // Get preferences
1149 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
1150 guint prows = prefs->getInt("/tools/mesh/mesh_rows", 1);
1151 guint pcols = prefs->getInt("/tools/mesh/mesh_cols", 1);
1152
1153 SPMeshGeometry mesh_type =
1154 (SPMeshGeometry) prefs->getInt("/tools/mesh/mesh_geometry", SP_MESH_GEOMETRY_NORMAL);
1155
1156 if( mesh_type == SP_MESH_GEOMETRY_CONICAL ) {
1157
1158 // Conical gradient.. for any shape/path using geometric bounding box.
1159
1160 gdouble rx = width/2.0;
1161 gdouble ry = height/2.0;
1162
1163 // Start and end angles
1164 gdouble start = 0.0;
1165 gdouble end = 2.0 * M_PI;
1166
1167 if ( SP_IS_STAR( item ) ) {
1168 // But if it is a star... use star parameters!
1169 SPStar* star = SP_STAR( item );
1170 center = star->center;
1171 rx = star->r[0];
1172 ry = star->r[0];
1173 start = star->arg[0];
1174 end = start + 2.0 * M_PI;
1175 }
1176
1177 if ( SP_IS_GENERICELLIPSE( item ) ) {
1178 // For arcs use set start/stop
1179 SPGenericEllipse* arc = SP_GENERICELLIPSE( item );
1180 center[Geom::X] = arc->cx.computed;
1181 center[Geom::Y] = arc->cy.computed;
1182 rx = arc->rx.computed;
1183 ry = arc->ry.computed;
1184 start = arc->start;
1185 end = arc->end;
1186 if( end <= start ) {
1187 end += 2.0 * M_PI;
1188 }
1189 }
1190
1191 // std::cout << " start: " << start << " end: " << end << std::endl;
1192
1193 // IS THIS NECESSARY?
1194 sp_repr_set_svg_double( repr, "x", center[Geom::X] + rx * cos(start) );
1195 sp_repr_set_svg_double( repr, "y", center[Geom::Y] + ry * sin(start) );
1196
1197 guint sections = pcols;
1198
1199 // If less sections, arc approximation error too great. (Check!)
1200 if( sections < 4 ) sections = 4;
1201
1202 double arc = (end - start) / (double)sections;
1203
1204 // See: http://en.wikipedia.org/wiki/B%C3%A9zier_curve
1205 gdouble kappa = 4.0/3.0 * tan(arc/4.0);
1206 gdouble lenx = rx * kappa;
1207 gdouble leny = ry * kappa;
1208
1209 gdouble s = start;
1210 for( guint i = 0; i < sections; ++i ) {
1211
1212 SPMeshPatchI patch( &nodes, 0, i );
1213
1214 gdouble x0 = center[Geom::X] + rx * cos(s);
1215 gdouble y0 = center[Geom::Y] + ry * sin(s);
1216 gdouble x1 = x0 - lenx * sin(s);
1217 gdouble y1 = y0 + leny * cos(s);
1218
1219 s += arc;
1220 gdouble x3 = center[Geom::X] + rx * cos(s);
1221 gdouble y3 = center[Geom::Y] + ry * sin(s);
1222 gdouble x2 = x3 + lenx * sin(s);
1223 gdouble y2 = y3 - leny * cos(s);
1224
1225 patch.setPoint( 0, 0, Geom::Point( x0, y0 ) );
1226 patch.setPoint( 0, 1, Geom::Point( x1, y1 ) );
1227 patch.setPoint( 0, 2, Geom::Point( x2, y2 ) );
1228 patch.setPoint( 0, 3, Geom::Point( x3, y3 ) );
1229
1230 patch.setPoint( 2, 0, center );
1231 patch.setPoint( 3, 0, center );
1232
1233 for( guint k = 0; k < 4; ++k ) {
1234 patch.setPathType( k, 'l' );
1235 patch.setColor( k, (i+k)%2 ? color : white );
1236 patch.setOpacity( k, 1.0 );
1237 }
1238 patch.setPathType( 0, 'c' );
1239
1240 // Set handle and tensor nodes.
1241 patch.updateNodes();
1242
1243 }
1244
1245 split_row( 0, prows );
1246
1247 } else {
1248
1249 // Normal grid meshes
1250
1251 if( SP_IS_GENERICELLIPSE( item ) ) {
1252
1253 // std::cout << "We've got ourselves an arc!" << std::endl;
1254
1255 SPGenericEllipse* arc = SP_GENERICELLIPSE( item );
1256 center[Geom::X] = arc->cx.computed;
1257 center[Geom::Y] = arc->cy.computed;
1258 gdouble rx = arc->rx.computed;
1259 gdouble ry = arc->ry.computed;
1260
1261 gdouble s = -3.0/2.0 * M_PI_2;
1262
1263 sp_repr_set_svg_double( repr, "x", center[Geom::X] + rx * cos(s) );
1264 sp_repr_set_svg_double( repr, "y", center[Geom::Y] + ry * sin(s) );
1265
1266 gdouble lenx = rx * 4*tan(M_PI_2/4)/3;
1267 gdouble leny = ry * 4*tan(M_PI_2/4)/3;
1268
1269 SPMeshPatchI patch( &nodes, 0, 0 );
1270 for( guint i = 0; i < 4; ++i ) {
1271
1272 gdouble x0 = center[Geom::X] + rx * cos(s);
1273 gdouble y0 = center[Geom::Y] + ry * sin(s);
1274 gdouble x1 = x0 + lenx * cos(s + M_PI_2);
1275 gdouble y1 = y0 + leny * sin(s + M_PI_2);
1276
1277 s += M_PI_2;
1278 gdouble x3 = center[Geom::X] + rx * cos(s);
1279 gdouble y3 = center[Geom::Y] + ry * sin(s);
1280 gdouble x2 = x3 + lenx * cos(s - M_PI_2);
1281 gdouble y2 = y3 + leny * sin(s - M_PI_2);
1282
1283 Geom::Point p1( x1, y1 );
1284 Geom::Point p2( x2, y2 );
1285 Geom::Point p3( x3, y3 );
1286 patch.setPoint( i, 1, p1 );
1287 patch.setPoint( i, 2, p2 );
1288 patch.setPoint( i, 3, p3 );
1289
1290 patch.setPathType( i, 'c' );
1291
1292 patch.setColor( i, i%2 ? color : white );
1293 patch.setOpacity( i, 1.0 );
1294 }
1295
1296 // Fill out tensor points
1297 patch.updateNodes();
1298
1299 split_row( 0, prows );
1300 split_column( 0, pcols );
1301
1302 // END Arc
1303
1304 } else if ( SP_IS_STAR( item ) ) {
1305
1306 // Do simplest thing... assume star is not rounded or randomized.
1307 // (It should be easy to handle the rounded/randomized cases by making
1308 // the appropriate star class function public.)
1309 SPStar* star = SP_STAR( item );
1310 guint sides = star->sides;
1311
1312 // std::cout << "We've got ourselves an star! Sides: " << sides << std::endl;
1313
1314 Geom::Point p0 = sp_star_get_xy( star, SP_STAR_POINT_KNOT1, 0 );
1315 sp_repr_set_svg_double( repr, "x", p0[Geom::X] );
1316 sp_repr_set_svg_double( repr, "y", p0[Geom::Y] );
1317
1318 for( guint i = 0; i < sides; ++i ) {
1319
1320 if( star->flatsided ) {
1321
1322 SPMeshPatchI patch( &nodes, 0, i );
1323
1324 patch.setPoint( 0, 0, sp_star_get_xy( star, SP_STAR_POINT_KNOT1, i ) );
1325 guint ii = i+1;
1326 if( ii == sides ) ii = 0;
1327 patch.setPoint( 1, 0, sp_star_get_xy( star, SP_STAR_POINT_KNOT1, ii ) );
1328 patch.setPoint( 2, 0, star->center );
1329 patch.setPoint( 3, 0, star->center );
1330
1331 for( guint s = 0; s < 4; ++s ) {
1332 patch.setPathType( s, 'l' );
1333 patch.setColor( s, (i+s)%2 ? color : white );
1334 patch.setOpacity( s, 1.0 );
1335 }
1336
1337 // Set handle and tensor nodes.
1338 patch.updateNodes();
1339
1340 } else {
1341
1342 SPMeshPatchI patch0( &nodes, 0, 2*i );
1343
1344 patch0.setPoint( 0, 0, sp_star_get_xy( star, SP_STAR_POINT_KNOT1, i ) );
1345 patch0.setPoint( 1, 0, sp_star_get_xy( star, SP_STAR_POINT_KNOT2, i ) );
1346 patch0.setPoint( 2, 0, star->center );
1347 patch0.setPoint( 3, 0, star->center );
1348
1349 guint ii = i+1;
1350 if( ii == sides ) ii = 0;
1351
1352 SPMeshPatchI patch1( &nodes, 0, 2*i+1 );
1353
1354 patch1.setPoint( 0, 0, sp_star_get_xy( star, SP_STAR_POINT_KNOT2, i ) );
1355 patch1.setPoint( 1, 0, sp_star_get_xy( star, SP_STAR_POINT_KNOT1, ii ) );
1356 patch1.setPoint( 2, 0, star->center );
1357 patch1.setPoint( 3, 0, star->center );
1358
1359 for( guint s = 0; s < 4; ++s ) {
1360 patch0.setPathType( s, 'l' );
1361 patch0.setColor( s, s%2 ? color : white );
1362 patch0.setOpacity( s, 1.0 );
1363 patch1.setPathType( s, 'l' );
1364 patch1.setColor( s, s%2 ? white : color );
1365 patch1.setOpacity( s, 1.0 );
1366 }
1367
1368 // Set handle and tensor nodes.
1369 patch0.updateNodes();
1370 patch1.updateNodes();
1371
1372 }
1373 }
1374
1375 //print();
1376
1377 split_row( 0, prows );
1378 //split_column( 0, pcols );
1379
1380 } else {
1381
1382 // Generic
1383
1384 sp_repr_set_svg_double(repr, "x", bbox->min()[Geom::X]);
1385 sp_repr_set_svg_double(repr, "y", bbox->min()[Geom::Y]);
1386
1387 // Get node array size
1388 guint nrows = prows * 3 + 1;
1389 guint ncols = pcols * 3 + 1;
1390
1391 gdouble dx = width / (gdouble)(ncols-1.0);
1392 gdouble dy = height / (gdouble)(nrows-1.0);
1393
1394 Geom::Point p0( mg->x.computed, mg->y.computed );
1395
1396 for( guint i = 0; i < nrows; ++i ) {
1397 std::vector< SPMeshNode* > row;
1398 for( guint j = 0; j < ncols; ++j ) {
1399 SPMeshNode* node = new SPMeshNode;
1400 node->p = p0 + Geom::Point( j * dx, i * dy );
1401
1402 node->node_edge = MG_NODE_EDGE_NONE;
1403 if( i == 0 ) node->node_edge |= MG_NODE_EDGE_TOP;
1404 if( i == nrows -1 ) node->node_edge |= MG_NODE_EDGE_BOTTOM;
1405 if( j == 0 ) node->node_edge |= MG_NODE_EDGE_LEFT;
1406 if( j == ncols -1 ) node->node_edge |= MG_NODE_EDGE_RIGHT;
1407
1408 if( i%3 == 0 ) {
1409
1410 if( j%3 == 0) {
1411 // Corner
1412 node->node_type = MG_NODE_TYPE_CORNER;
1413 node->set = true;
1414 node->color = (i+j)%2 ? color : white;
1415 node->opacity = 1.0;
1416
1417 } else {
1418 // Side
1419 node->node_type = MG_NODE_TYPE_HANDLE;
1420 node->set = true;
1421 node->path_type = 'c';
1422 }
1423
1424 } else {
1425
1426 if( j%3 == 0) {
1427 // Side
1428 node->node_type = MG_NODE_TYPE_HANDLE;
1429 node->set = true;
1430 node->path_type = 'c';
1431 } else {
1432 // Tensor
1433 node->node_type = MG_NODE_TYPE_TENSOR;
1434 node->set = false;
1435 }
1436
1437 }
1438
1439 row.push_back( node );
1440 }
1441 nodes.push_back( row );
1442 }
1443 // End normal
1444 }
1445
1446 } // If conical
1447
1448 //print();
1449
1450 // Write repr
1451 write( mg );
1452 }
1453
1454
1455 /**
1456 Clear mesh gradient.
1457 */
clear()1458 void SPMeshNodeArray::clear() {
1459
1460 for(auto & node : nodes) {
1461 for(auto & j : node) {
1462 if( j ) {
1463 delete j;
1464 }
1465 }
1466 }
1467 nodes.clear();
1468 };
1469
1470
1471 /**
1472 Print mesh gradient (for debugging).
1473 */
print()1474 void SPMeshNodeArray::print() {
1475 for( guint i = 0; i < nodes.size(); ++i ) {
1476 std::cout << "New node row:" << std::endl;
1477 for( guint j = 0; j < nodes[i].size(); ++j ) {
1478 if( nodes[i][j] ) {
1479 std::cout.width(4);
1480 std::cout << " Node: " << i << "," << j << ": "
1481 << nodes[i][j]->p
1482 << " Node type: " << nodes[i][j]->node_type
1483 << " Node edge: " << nodes[i][j]->node_edge
1484 << " Set: " << nodes[i][j]->set
1485 << " Path type: " << nodes[i][j]->path_type
1486 << " Stop: " << nodes[i][j]->stop
1487 << std::endl;
1488 } else {
1489 std::cout << "Error: missing mesh node." << std::endl;
1490 }
1491 } // Loop over patches
1492 } // Loop over rows
1493 };
1494
1495
1496
1497 /*
1498 double hermite( const double p0, const double p1, const double m0, const double m1, const double t ) {
1499 double t2 = t*t;
1500 double t3 = t2*t;
1501
1502 double result = (2.0*t3 - 3.0*t2 +1.0) * p0
1503 + (t3 - 2.0*t2 + t) * m0
1504 + (-2.0*t3 + 3.0*t2) * p1
1505 + (t3 -t2) * m1;
1506
1507 return result;
1508 }
1509 */
1510
1511 class SPMeshSmoothCorner {
1512
1513 public:
SPMeshSmoothCorner()1514 SPMeshSmoothCorner() {
1515 for(auto & i : g) {
1516 for( unsigned j = 0; j < 4; ++j ) {
1517 i[j] = 0;
1518 }
1519 }
1520 }
1521
1522 double g[3][8]; // 3 colors, 8 parameters: see enum.
1523 Geom::Point p; // Location of point
1524 };
1525
1526 // Find slope at point 1 given values at previous and next points
1527 // Return value is slope in user space
find_slope1(const double & p0,const double & p1,const double & p2,const double & d01,const double & d12)1528 double find_slope1( const double &p0, const double &p1, const double &p2,
1529 const double &d01, const double &d12 ) {
1530
1531 double slope = 0;
1532
1533 if( d01 > 0 && d12 > 0 ) {
1534 slope = 0.5 * ( (p1 - p0)/d01 + (p2 - p1)/d12 );
1535
1536 if( ( p0 > p1 && p1 < p2 ) ||
1537 ( p0 < p1 && p1 > p2 ) ) {
1538 // At minimum or maximum, use slope of zero
1539 slope = 0;
1540 } else {
1541 // Ensure we don't overshoot
1542 if( fabs(slope) > fabs(3*(p1-p0)/d01) ) {
1543 slope = 3*(p1-p0)/d01;
1544 }
1545 if( fabs(slope) > fabs(3*(p2-p1)/d12) ) {
1546 slope = 3*(p2-p1)/d12;
1547 }
1548 }
1549 } else {
1550 // Do something clever
1551 }
1552 return slope;
1553 };
1554
1555
1556 /*
1557 // Find slope at point 0 given values at previous and next points
1558 // TO DO: TAKE DISTANCE BETWEEN POINTS INTO ACCOUNT
1559 double find_slope2( double pmm, double ppm, double pmp, double ppp, double p0 ) {
1560
1561 // pmm == d[i-1][j-1], ... 'm' is minus, 'p' is plus
1562 double slope = (ppp - ppm - pmp + pmm)/2.0;
1563 if( (ppp > p0 && ppm > p0 && pmp > p0 && pmm > 0) ||
1564 (ppp < p0 && ppm < p0 && pmp < p0 && pmm < 0) ) {
1565 // At minimum or maximum, use slope of zero
1566 slope = 0;
1567 } else {
1568 // Don't really know what to do here
1569 if( fabs(slope) > fabs(3*(ppp-p0)) ) {
1570 slope = 3*(ppp-p0);
1571 }
1572 if( fabs(slope) > fabs(3*(pmp-p0)) ) {
1573 slope = 3*(pmp-p0);
1574 }
1575 if( fabs(slope) > fabs(3*(ppm-p0)) ) {
1576 slope = 3*(ppm-p0);
1577 }
1578 if( fabs(slope) > fabs(3*(pmm-p0)) ) {
1579 slope = 3*(pmm-p0);
1580 }
1581 }
1582 return slope;
1583 }
1584 */
1585
1586 // https://en.wikipedia.org/wiki/Bicubic_interpolation
invert(const double v[16],double alpha[16])1587 void invert( const double v[16], double alpha[16] ) {
1588
1589 const double A[16][16] = {
1590
1591 { 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
1592 { 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
1593 {-3, 3, 0, 0, -2,-1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
1594 { 2,-2, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
1595 { 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0 },
1596 { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0 },
1597 { 0, 0, 0, 0, 0, 0, 0, 0, -3, 3, 0, 0, -2,-1, 0, 0 },
1598 { 0, 0, 0, 0, 0, 0, 0, 0, 2,-2, 0, 0, 1, 1, 0, 0 },
1599 {-3, 0, 3, 0, 0, 0, 0, 0, -2, 0,-1, 0, 0, 0, 0, 0 },
1600 { 0, 0, 0, 0, -3, 0, 3, 0, 0, 0, 0, 0, -2, 0,-1, 0 },
1601 { 9,-9,-9, 9, 6, 3,-6,-3, 6,-6, 3,-3, 4, 2, 2, 1 },
1602 {-6, 6, 6,-6, -3,-3, 3, 3, -4, 4,-2, 2, -2,-2,-1,-1 },
1603 { 2, 0,-2, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0 },
1604 { 0, 0, 0, 0, 2, 0,-2, 0, 0, 0, 0, 0, 1, 0, 1, 0 },
1605 {-6, 6, 6,-6, -4,-2, 4, 2, -3, 3,-3, 3, -2,-1,-2,-1 },
1606 { 4,-4,-4, 4, 2, 2,-2,-2, 2,-2, 2,-2, 1, 1, 1, 1 }
1607 };
1608
1609 for( unsigned i = 0; i < 16; ++i ) {
1610 alpha[i] = 0;
1611 for( unsigned j = 0; j < 16; ++j ) {
1612 alpha[i] += A[i][j]*v[j];
1613 }
1614 }
1615 }
1616
sum(const double alpha[16],const double & x,const double & y)1617 double sum( const double alpha[16], const double& x, const double& y ) {
1618
1619 double result = 0;
1620
1621 double xx = x*x;
1622 double xxx = xx * x;
1623 double yy = y*y;
1624 double yyy = yy * y;
1625
1626 result += alpha[ 0 ];
1627 result += alpha[ 1 ] * x;
1628 result += alpha[ 2 ] * xx;
1629 result += alpha[ 3 ] * xxx;
1630 result += alpha[ 4 ] * y;
1631 result += alpha[ 5 ] * y * x;
1632 result += alpha[ 6 ] * y * xx;
1633 result += alpha[ 7 ] * y * xxx;
1634 result += alpha[ 8 ] * yy;
1635 result += alpha[ 9 ] * yy * x;
1636 result += alpha[ 10 ] * yy * xx;
1637 result += alpha[ 11 ] * yy * xxx;
1638 result += alpha[ 12 ] * yyy;
1639 result += alpha[ 13 ] * yyy * x;
1640 result += alpha[ 14 ] * yyy * xx;
1641 result += alpha[ 15 ] * yyy * xxx;
1642
1643 return result;
1644 }
1645
1646 /**
1647 Fill 'smooth' with a smoothed version of the array by subdividing each patch into smaller patches.
1648 */
bicubic(SPMeshNodeArray * smooth,SPMeshType type)1649 void SPMeshNodeArray::bicubic( SPMeshNodeArray* smooth, SPMeshType type ) {
1650
1651
1652 *smooth = *this; // Deep copy via copy assignment constructor, smooth cleared before copy
1653 // std::cout << "SPMeshNodeArray::smooth2(): " << this->patch_rows() << " " << smooth->patch_columns() << std::endl;
1654 // std::cout << " " << smooth << " " << this << std::endl;
1655
1656 // Find derivatives at corners
1657
1658 // Create array of corner points
1659 std::vector< std::vector <SPMeshSmoothCorner> > d;
1660 d.resize( smooth->patch_rows() + 1 );
1661 for( unsigned i = 0; i < d.size(); ++i ) {
1662 d[i].resize( smooth->patch_columns() + 1 );
1663 for( unsigned j = 0; j < d[i].size(); ++j ) {
1664 float rgb_color[3];
1665 this->nodes[ i*3 ][ j*3 ]->color.get_rgb_floatv(rgb_color);
1666 d[i][j].g[0][0] = rgb_color[ 0 ];
1667 d[i][j].g[1][0] = rgb_color[ 1 ];
1668 d[i][j].g[2][0] = rgb_color[ 2 ];
1669 d[i][j].p = this->nodes[ i*3 ][ j*3 ]->p;
1670 }
1671 }
1672
1673 // Calculate interior derivatives
1674 for( unsigned i = 0; i < d.size(); ++i ) {
1675 for( unsigned j = 0; j < d[i].size(); ++j ) {
1676 for( unsigned k = 0; k < 3; ++k ) { // Loop over colors
1677
1678 // dx
1679
1680 if( i != 0 && i != d.size()-1 ) {
1681 double lm = Geom::distance( d[i-1][j].p, d[i][j].p );
1682 double lp = Geom::distance( d[i+1][j].p, d[i][j].p );
1683 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 );
1684 }
1685
1686 // dy
1687 if( j != 0 && j != d[i].size()-1 ) {
1688 double lm = Geom::distance( d[i][j-1].p, d[i][j].p );
1689 double lp = Geom::distance( d[i][j+1].p, d[i][j].p );
1690 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 );
1691 }
1692
1693 // dxdy if needed, need to take lengths into account
1694 // if( i != 0 && i != d.size()-1 && j != 0 && j != d[i].size()-1 ) {
1695 // d[i][j].g[k][3] = find_slope2( d[i-1][j-1].g[k][0], d[i+1][j-1].g[k][0],
1696 // d[i-1][j+1].g[k][0], d[i-1][j-1].g[k][0],
1697 // d[i][j].g[k][0] );
1698 // }
1699
1700 }
1701 }
1702 }
1703
1704 // Calculate exterior derivatives
1705 // We need to do this after calculating interior derivatives as we need to already
1706 // have the non-exterior derivative calculated for finding the parabola.
1707 for( unsigned j = 0; j< d[0].size(); ++j ) {
1708 for( unsigned k = 0; k < 3; ++k ) { // Loop over colors
1709
1710 // Parabolic
1711 double d0 = Geom::distance( d[1][j].p, d[0 ][j].p );
1712 if( d0 > 0 ) {
1713 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];
1714 } else {
1715 d[0][j].g[k][1] = 0;
1716 }
1717
1718 unsigned z = d.size()-1;
1719 double dz = Geom::distance( d[z][j].p, d[z-1][j].p );
1720 if( dz > 0 ) {
1721 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];
1722 } else {
1723 d[z][j].g[k][1] = 0;
1724 }
1725 }
1726 }
1727
1728 for( unsigned i = 0; i< d.size(); ++i ) {
1729 for( unsigned k = 0; k < 3; ++k ) { // Loop over colors
1730
1731 // Parabolic
1732 double d0 = Geom::distance( d[i][1].p, d[i][0 ].p );
1733 if( d0 > 0 ) {
1734 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];
1735 } else {
1736 d[i][0].g[k][2] = 0;
1737 }
1738
1739 unsigned z = d[0].size()-1;
1740 double dz = Geom::distance( d[i][z].p, d[i][z-1].p );
1741 if( dz > 0 ) {
1742 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];
1743 } else {
1744 d[i][z].g[k][2] = 0;
1745 }
1746 }
1747 }
1748
1749 // Leave outside corner cross-derivatives at zero.
1750
1751 // Next split each patch into 8x8 smaller patches.
1752
1753 // Split each row into eight rows.
1754 // Must do it from end so inserted rows don't mess up indexing
1755 for( int i = smooth->patch_rows() - 1; i >= 0; --i ) {
1756 smooth->split_row( i, unsigned(8) );
1757 }
1758
1759 // Split each column into eight columns.
1760 // Must do it from end so inserted columns don't mess up indexing
1761 for( int i = smooth->patch_columns() - 1; i >= 0; --i ) {
1762 smooth->split_column( i, (unsigned)8 );
1763 }
1764
1765 // Fill new patches
1766 for( unsigned i = 0; i < this->patch_rows(); ++i ) {
1767 for( unsigned j = 0; j < this->patch_columns(); ++j ) {
1768
1769 double dx0 = Geom::distance( d[i ][j ].p, d[i+1][j ].p );
1770 double dx1 = Geom::distance( d[i ][j+1].p, d[i+1][j+1].p );
1771 double dy0 = Geom::distance( d[i ][j ].p, d[i ][j+1].p );
1772 double dy1 = Geom::distance( d[i+1][j ].p, d[i+1][j+1].p );
1773
1774 // Temp loop over 0..8 to get last column/row edges
1775 float r[3][9][9]; // result
1776 for( unsigned m = 0; m < 3; ++m ) {
1777
1778 double v[16];
1779 v[ 0] = d[i ][j ].g[m][0];
1780 v[ 1] = d[i+1][j ].g[m][0];
1781 v[ 2] = d[i ][j+1].g[m][0];
1782 v[ 3] = d[i+1][j+1].g[m][0];
1783 v[ 4] = d[i ][j ].g[m][1]*dx0;
1784 v[ 5] = d[i+1][j ].g[m][1]*dx0;
1785 v[ 6] = d[i ][j+1].g[m][1]*dx1;
1786 v[ 7] = d[i+1][j+1].g[m][1]*dx1;
1787 v[ 8] = d[i ][j ].g[m][2]*dy0;
1788 v[ 9] = d[i+1][j ].g[m][2]*dy1;
1789 v[10] = d[i ][j+1].g[m][2]*dy0;
1790 v[11] = d[i+1][j+1].g[m][2]*dy1;
1791 v[12] = d[i ][j ].g[m][3];
1792 v[13] = d[i+1][j ].g[m][3];
1793 v[14] = d[i ][j+1].g[m][3];
1794 v[15] = d[i+1][j+1].g[m][3];
1795
1796 double alpha[16];
1797 invert( v, alpha );
1798
1799 for( unsigned k = 0; k < 9; ++k ) {
1800 for( unsigned l = 0; l < 9; ++l ) {
1801 double x = k/8.0;
1802 double y = l/8.0;
1803 r[m][k][l] = sum( alpha, x, y );
1804 // Clamp to allowed values
1805 if( r[m][k][l] > 1.0 )
1806 r[m][k][l] = 1.0;
1807 if( r[m][k][l] < 0.0 )
1808 r[m][k][l] = 0.0;
1809 }
1810 }
1811
1812 } // Loop over colors
1813
1814 for( unsigned k = 0; k < 9; ++k ) {
1815 for( unsigned l = 0; l < 9; ++l ) {
1816 // Every third node is a corner node
1817 smooth->nodes[ (i*8+k)*3 ][(j*8+l)*3 ]->color.set( r[0][k][l], r[1][k][l], r[2][k][l] );
1818 }
1819 }
1820 }
1821 }
1822 }
1823
1824 /**
1825 Number of patch rows.
1826 */
patch_rows()1827 guint SPMeshNodeArray::patch_rows() {
1828
1829 return nodes.size()/3;
1830 }
1831
1832 /**
1833 Number of patch columns.
1834 */
patch_columns()1835 guint SPMeshNodeArray::patch_columns() {
1836 if (nodes.empty()) {
1837 return 0;
1838 }
1839 return nodes[0].size()/3;
1840 }
1841
1842 /**
1843 Inputs:
1844 i, j: Corner draggable indices.
1845 Returns:
1846 true if corners adjacent.
1847 n[] is array of nodes in top/bottom or left/right order.
1848 */
adjacent_corners(guint i,guint j,SPMeshNode * n[4])1849 bool SPMeshNodeArray::adjacent_corners( guint i, guint j, SPMeshNode* n[4] ) {
1850
1851 // This works as all corners have indices and they
1852 // are numbered in order by row and column (and
1853 // the node array is rectangular).
1854
1855 bool adjacent = false;
1856
1857 guint c1 = i;
1858 guint c2 = j;
1859 if( j < i ) {
1860 c1 = j;
1861 c2 = i;
1862 }
1863
1864 // Number of corners in a row of patches.
1865 guint ncorners = patch_columns() + 1;
1866
1867 guint crow1 = c1 / ncorners;
1868 guint crow2 = c2 / ncorners;
1869 guint ccol1 = c1 % ncorners;
1870 guint ccol2 = c2 % ncorners;
1871
1872 guint nrow = crow1 * 3;
1873 guint ncol = ccol1 * 3;
1874
1875 // std::cout << " i: " << i
1876 // << " j: " << j
1877 // << " ncorners: " << ncorners
1878 // << " c1: " << c1
1879 // << " crow1: " << crow1
1880 // << " ccol1: " << ccol1
1881 // << " c2: " << c2
1882 // << " crow2: " << crow2
1883 // << " ccol2: " << ccol2
1884 // << " nrow: " << nrow
1885 // << " ncol: " << ncol
1886 // << std::endl;
1887
1888 // Check for horizontal neighbors
1889 if ( crow1 == crow2 && (ccol2 - ccol1) == 1 ) {
1890 adjacent = true;
1891 for( guint k = 0; k < 4; ++k ) {
1892 n[k] = nodes[nrow][ncol+k];
1893 }
1894 }
1895
1896 // Check for vertical neighbors
1897 if ( ccol1 == ccol2 && (crow2 - crow1) == 1 ) {
1898 adjacent = true;
1899 for( guint k = 0; k < 4; ++k ) {
1900 n[k] = nodes[nrow+k][ncol];
1901 }
1902 }
1903
1904 return adjacent;
1905 }
1906
1907 /**
1908 Toggle sides between lineto and curve to if both corners selected.
1909 Input is a list of selected corner draggable indices.
1910 */
side_toggle(std::vector<guint> corners)1911 guint SPMeshNodeArray::side_toggle( std::vector<guint> corners ) {
1912
1913 guint toggled = 0;
1914
1915 if( corners.size() < 2 ) return 0;
1916
1917 for( guint i = 0; i < corners.size()-1; ++i ) {
1918 for( guint j = i+1; j < corners.size(); ++j ) {
1919
1920 SPMeshNode* n[4];
1921 if( adjacent_corners( corners[i], corners[j], n ) ) {
1922
1923 gchar path_type = n[1]->path_type;
1924 switch (path_type)
1925 {
1926 case 'L':
1927 n[1]->path_type = 'C';
1928 n[2]->path_type = 'C';
1929 n[1]->set = true;
1930 n[2]->set = true;
1931 break;
1932
1933 case 'l':
1934 n[1]->path_type = 'c';
1935 n[2]->path_type = 'c';
1936 n[1]->set = true;
1937 n[2]->set = true;
1938 break;
1939
1940 case 'C': {
1941 n[1]->path_type = 'L';
1942 n[2]->path_type = 'L';
1943 n[1]->set = false;
1944 n[2]->set = false;
1945 // 'L' acts as if handles are 1/3 of path length from corners.
1946 Geom::Point dp = (n[3]->p - n[0]->p)/3.0;
1947 n[1]->p = n[0]->p + dp;
1948 n[2]->p = n[3]->p - dp;
1949 break;
1950 }
1951 case 'c': {
1952 n[1]->path_type = 'l';
1953 n[2]->path_type = 'l';
1954 n[1]->set = false;
1955 n[2]->set = false;
1956 // 'l' acts as if handles are 1/3 of path length from corners.
1957 Geom::Point dp = (n[3]->p - n[0]->p)/3.0;
1958 n[1]->p = n[0]->p + dp;
1959 n[2]->p = n[3]->p - dp;
1960 // std::cout << "Toggle sides: "
1961 // << n[0]->p << " "
1962 // << n[1]->p << " "
1963 // << n[2]->p << " "
1964 // << n[3]->p << " "
1965 // << dp << std::endl;
1966 break;
1967 }
1968 default:
1969 std::cout << "Toggle sides: Invalid path type: " << path_type << std::endl;
1970 }
1971 ++toggled;
1972 }
1973 }
1974 }
1975 if( toggled > 0 ) built = false;
1976 return toggled;
1977 }
1978
1979 /**
1980 * Converts generic Beziers to Beziers approximating elliptical arcs, preserving handle direction.
1981 * There are infinite possible solutions. The solution chosen here is to generate a section of an
1982 * ellipse that is centered on the intersection of the two lines passing through the two nodes but
1983 * parallel to the other node's handle direction. This is the section of an ellipse that
1984 * corresponds to a quarter of a circle squished and then skewed.
1985 */
side_arc(std::vector<guint> corners)1986 guint SPMeshNodeArray::side_arc( std::vector<guint> corners ) {
1987
1988 if( corners.size() < 2 ) return 0;
1989
1990 guint arced = 0;
1991 for( guint i = 0; i < corners.size()-1; ++i ) {
1992 for( guint j = i+1; j < corners.size(); ++j ) {
1993
1994 SPMeshNode* n[4];
1995 if( adjacent_corners( corners[i], corners[j], n ) ) {
1996
1997 gchar path_type = n[1]->path_type;
1998 switch (path_type)
1999 {
2000 case 'L':
2001 case 'l':
2002 std::cerr << "SPMeshNodeArray::side_arc: Can't convert straight lines to arcs." << std::endl;
2003 break;
2004
2005 case 'C':
2006 case 'c': {
2007
2008 Geom::Ray ray1( n[0]->p, n[1]->p );
2009 Geom::Ray ray2( n[3]->p, n[2]->p );
2010 if( !are_parallel( (Geom::Line)ray1, (Geom::Line)ray2 ) ) {
2011
2012 Geom::OptCrossing crossing = intersection( ray1, ray2 );
2013
2014 if( crossing ) {
2015
2016 Geom::Point intersection = ray1.pointAt( (*crossing).ta );
2017
2018 const double f = 4.0/3.0 * tan( M_PI/2.0/4.0 );
2019
2020 Geom::Point h1 = intersection - n[0]->p;
2021 Geom::Point h2 = intersection - n[3]->p;
2022
2023 n[1]->p = n[0]->p + f*h1;
2024 n[2]->p = n[3]->p + f*h2;
2025 ++arced;
2026
2027 } else {
2028 std::cerr << "SPMeshNodeArray::side_arc: No crossing, can't turn into arc." << std::endl;
2029 }
2030 } else {
2031 std::cerr << "SPMeshNodeArray::side_arc: Handles parallel, can't turn into arc." << std::endl;
2032 }
2033 break;
2034 }
2035 default:
2036 std::cerr << "SPMeshNodeArray::side_arc: Invalid path type: " << n[1]->path_type << std::endl;
2037 }
2038 }
2039 }
2040 }
2041 if( arced > 0 ) built = false;
2042 return arced;
2043 }
2044
2045 /**
2046 Toggle sides between lineto and curve to if both corners selected.
2047 Input is a list of selected corner draggable indices.
2048 */
tensor_toggle(std::vector<guint> corners)2049 guint SPMeshNodeArray::tensor_toggle( std::vector<guint> corners ) {
2050
2051 // std::cout << "SPMeshNodeArray::tensor_toggle" << std::endl;
2052
2053 if( corners.size() < 4 ) return 0;
2054
2055 guint toggled = 0;
2056
2057 // Number of corners in a row of patches.
2058 guint ncorners = patch_columns() + 1;
2059
2060 for( guint i = 0; i < corners.size()-3; ++i ) {
2061 for( guint j = i+1; j < corners.size()-2; ++j ) {
2062 for( guint k = j+1; k < corners.size()-1; ++k ) {
2063 for( guint l = k+1; l < corners.size(); ++l ) {
2064
2065 guint c[4];
2066 c[0] = corners[i];
2067 c[1] = corners[j];
2068 c[2] = corners[k];
2069 c[3] = corners[l];
2070 std::sort( c, c+4 );
2071
2072 // Check we have four corners of one patch selected
2073 if( c[1]-c[0] == 1 &&
2074 c[3]-c[2] == 1 &&
2075 c[2]-c[0] == ncorners &&
2076 c[3]-c[1] == ncorners &&
2077 c[0] % ncorners < ncorners - 1 ) {
2078
2079 // Patch
2080 guint prow = c[0] / ncorners;
2081 guint pcol = c[0] % ncorners;
2082
2083 // Upper left node of patch
2084 guint irow = prow * 3;
2085 guint jcol = pcol * 3;
2086
2087 // std::cout << "tensor::toggle: "
2088 // << c[0] << ", "
2089 // << c[1] << ", "
2090 // << c[2] << ", "
2091 // << c[3] << std::endl;
2092
2093 // std::cout << "tensor::toggle: "
2094 // << " irow: " << irow
2095 // << " jcol: " << jcol
2096 // << " prow: " << prow
2097 // << " pcol: " << pcol
2098 // << std::endl;
2099
2100 SPMeshPatchI patch( &nodes, prow, pcol );
2101 patch.updateNodes();
2102
2103 if( patch.tensorIsSet() ) {
2104 // Unset tensor points
2105 nodes[irow+1][jcol+1]->set = false;
2106 nodes[irow+1][jcol+2]->set = false;
2107 nodes[irow+2][jcol+1]->set = false;
2108 nodes[irow+2][jcol+2]->set = false;
2109 } else {
2110 // Set tensor points
2111 nodes[irow+1][jcol+1]->set = true;
2112 nodes[irow+1][jcol+2]->set = true;
2113 nodes[irow+2][jcol+1]->set = true;
2114 nodes[irow+2][jcol+2]->set = true;
2115 }
2116
2117 ++toggled;
2118 }
2119 }
2120 }
2121 }
2122 }
2123 if( toggled > 0 ) built = false;
2124 return toggled;
2125 }
2126
2127 /**
2128 Attempts to smooth color transitions across corners.
2129 Input is a list of selected corner draggable indices.
2130 */
color_smooth(std::vector<guint> corners)2131 guint SPMeshNodeArray::color_smooth( std::vector<guint> corners ) {
2132
2133 // std::cout << "SPMeshNodeArray::color_smooth" << std::endl;
2134
2135 guint smoothed = 0;
2136
2137 // Number of corners in a row of patches.
2138 guint ncorners = patch_columns() + 1;
2139
2140 // Number of node rows and columns
2141 guint ncols = patch_columns() * 3 + 1;
2142 guint nrows = patch_rows() * 3 + 1;
2143
2144 for(unsigned int corner : corners) {
2145
2146 // std::cout << "SPMeshNodeArray::color_smooth: " << i << " " << corner << std::endl;
2147
2148 // Node row & col
2149 guint nrow = (corner / ncorners) * 3;
2150 guint ncol = (corner % ncorners) * 3;
2151
2152 SPMeshNode* n[7];
2153 for( guint s = 0; s < 2; ++s ) {
2154
2155 bool smooth = false;
2156
2157 // Find neighboring nodes
2158 if( s == 0 ) {
2159
2160 // Horizontal
2161 if( ncol > 2 && ncol+3 < ncols) {
2162 for( guint j = 0; j < 7; ++j ) {
2163 n[j] = nodes[ nrow ][ ncol - 3 + j ];
2164 }
2165 smooth = true;
2166 }
2167
2168 } else {
2169
2170 // Vertical
2171 if( nrow > 2 && nrow+3 < nrows) {
2172 for( guint j = 0; j < 7; ++j ) {
2173 n[j] = nodes[ nrow - 3 + j ][ ncol ];
2174 }
2175 smooth = true;
2176 }
2177 }
2178
2179 if( smooth ) {
2180
2181 // Let the smoothing begin
2182 // std::cout << " checking: " << ncol << " " << nrow << std::endl;
2183
2184 // Get initial slopes using closest handles.
2185 double slope[2][3];
2186 double slope_ave[3];
2187 double slope_diff[3];
2188
2189 // Color of corners
2190 SPColor color0 = n[0]->color;
2191 SPColor color3 = n[3]->color;
2192 SPColor color6 = n[6]->color;
2193
2194 // Distance nodes from selected corner
2195 Geom::Point d[7];
2196 for( guint k = 0; k < 7; ++k ) {
2197 d[k]= n[k]->p - n[3]->p;
2198 // std::cout << " d[" << k << "]: " << d[k].length() << std::endl;
2199 }
2200
2201 double sdm = -1.0; // Slope Diff Max
2202 guint cdm = 0; // Color Diff Max (Which color has the maximum difference in slopes)
2203 for( guint c = 0; c < 3; ++c ) {
2204 if( d[2].length() != 0.0 ) {
2205 slope[0][c] = (color3.v.c[c] - color0.v.c[c]) / d[2].length();
2206 }
2207 if( d[4].length() != 0.0 ) {
2208 slope[1][c] = (color6.v.c[c] - color3.v.c[c]) / d[4].length();
2209 }
2210 slope_ave[c] = (slope[0][c]+slope[1][c]) / 2.0;
2211 slope_diff[c] = (slope[0][c]-slope[1][c]);
2212 // std::cout << " color: " << c << " :"
2213 // << color0.v.c[c] << " "
2214 // << color3.v.c[c] << " "
2215 // << color6.v.c[c]
2216 // << " slope: "
2217 // << slope[0][c] << " "
2218 // << slope[1][c]
2219 // << " slope_ave: " << slope_ave[c]
2220 // << " slope_diff: " << slope_diff[c]
2221 // << std::endl;
2222
2223 // Find color with maximum difference
2224 if( std::abs( slope_diff[c] ) > sdm ) {
2225 sdm = std::abs( slope_diff[c] );
2226 cdm = c;
2227 }
2228 }
2229 // std::cout << " cdm: " << cdm << std::endl;
2230
2231 // Find new handle positions:
2232 double length_left = d[0].length();
2233 double length_right = d[6].length();
2234 if( slope_ave[ cdm ] != 0.0 ) {
2235 length_left = std::abs( (color3.v.c[cdm] - color0.v.c[cdm]) / slope_ave[ cdm ] );
2236 length_right = std::abs( (color6.v.c[cdm] - color3.v.c[cdm]) / slope_ave[ cdm ] );
2237 }
2238
2239 // Move closest handle a maximum of mid point... but don't shorten
2240 double max = 0.8;
2241 if( length_left > max * d[0].length() && length_left > d[2].length() ) {
2242 std::cout << " Can't smooth left side" << std::endl;
2243 length_left = std::max( max * d[0].length(), d[2].length() );
2244 }
2245 if( length_right > max * d[6].length() && length_right > d[4].length() ) {
2246 std::cout << " Can't smooth right side" << std::endl;
2247 length_right = std::max( max * d[6].length(), d[4].length() );
2248 }
2249
2250 if( d[2].length() != 0.0 ) d[2] *= length_left/d[2].length();
2251 if( d[4].length() != 0.0 ) d[4] *= length_right/d[4].length();
2252
2253 // std::cout << " length_left: " << length_left
2254 // << " d[0]: " << d[0].length()
2255 // << " length_right: " << length_right
2256 // << " d[6]: " << d[6].length()
2257 // << std::endl;
2258
2259 n[2]->p = n[3]->p + d[2];
2260 n[4]->p = n[3]->p + d[4];
2261
2262 ++smoothed;
2263 }
2264 }
2265
2266 }
2267
2268 if( smoothed > 0 ) built = false;
2269 return smoothed;
2270 }
2271
2272 /**
2273 Pick color from background for selected corners.
2274 */
color_pick(std::vector<guint> icorners,SPItem * item)2275 guint SPMeshNodeArray::color_pick( std::vector<guint> icorners, SPItem* item ) {
2276
2277 // std::cout << "SPMeshNodeArray::color_pick" << std::endl;
2278
2279 guint picked = 0;
2280
2281 // Code inspired from clone tracing
2282
2283 // Setup...
2284
2285 // We need a copy of the drawing so we can hide the mesh.
2286 Inkscape::Drawing *pick_drawing = new Inkscape::Drawing();
2287 unsigned pick_visionkey = SPItem::display_key_new(1);
2288
2289 SPDocument *pick_doc = mg->document;
2290
2291 pick_drawing->setRoot(pick_doc->getRoot()->invoke_show(*pick_drawing, pick_visionkey, SP_ITEM_SHOW_DISPLAY));
2292
2293 item->invoke_hide(pick_visionkey);
2294
2295 pick_doc->getRoot()->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
2296 pick_doc->ensureUpToDate();
2297
2298 //gdouble pick_zoom = 1.0; // zoom;
2299 //pick_drawing->root()->setTransform(Geom::Scale(pick_zoom));
2300 pick_drawing->update();
2301
2302 // std::cout << " transform: " << std::endl;
2303 // std::cout << item->transform << std::endl;
2304 // std::cout << " i2doc: " << std::endl;
2305 // std::cout << item->i2doc_affine() << std::endl;
2306 // std::cout << " i2dt: " << std::endl;
2307 // std::cout << item->i2dt_affine() << std::endl;
2308 // std::cout << " dt2i: " << std::endl;
2309 // std::cout << item->dt2i_affine() << std::endl;
2310 SPGradient* gr = SP_GRADIENT( mg );
2311 // if( gr->gradientTransform_set ) {
2312 // std::cout << " gradient transform set: " << std::endl;
2313 // std::cout << gr->gradientTransform << std::endl;
2314 // } else {
2315 // std::cout << " gradient transform not set! " << std::endl;
2316 // }
2317
2318 // Do picking
2319 for(unsigned int corner : icorners) {
2320
2321 SPMeshNode* n = corners[ corner ];
2322
2323 // Region to average over
2324 Geom::Point p = n->p;
2325 // std::cout << " before transform: p: " << p << std::endl;
2326 p *= gr->gradientTransform;
2327 // std::cout << " after transform: p: " << p << std::endl;
2328 p *= item->i2doc_affine();
2329 // std::cout << " after transform: p: " << p << std::endl;
2330
2331 // If on edge, move inward
2332 guint cols = patch_columns()+1;
2333 guint rows = patch_rows()+1;
2334 guint col = corner % cols;
2335 guint row = corner / cols;
2336 guint ncol = col * 3;
2337 guint nrow = row * 3;
2338
2339 const double size = 3.0;
2340
2341 // Top edge
2342 if( row == 0 ) {
2343 Geom::Point dp = nodes[nrow+1][ncol]->p - p;
2344 p += unit_vector( dp ) * size;
2345 }
2346 // Right edge
2347 if( col == cols-1 ) {
2348 Geom::Point dp = nodes[nrow][ncol-1]->p - p;
2349 p += unit_vector( dp ) * size;
2350 }
2351 // Bottom edge
2352 if( row == rows-1 ) {
2353 Geom::Point dp = nodes[nrow-1][ncol]->p - p;
2354 p += unit_vector( dp ) * size;
2355 }
2356 // Left edge
2357 if( col == 0 ) {
2358 Geom::Point dp = nodes[nrow][ncol+1]->p - p;
2359 p += unit_vector( dp ) * size;
2360 }
2361
2362 Geom::Rect box( p[Geom::X]-size/2.0, p[Geom::Y]-size/2.0,
2363 p[Geom::X]+size/2.0, p[Geom::Y]+size/2.0 );
2364
2365 /* Item integer bbox in points */
2366 Geom::IntRect ibox = box.roundOutwards();
2367
2368 /* Find visible area */
2369 cairo_surface_t *s = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, ibox.width(), ibox.height());
2370 Inkscape::DrawingContext dc(s, ibox.min());
2371
2372 /* Render copy and pick color */
2373 pick_drawing->render(dc, ibox);
2374 double R = 0, G = 0, B = 0, A = 0;
2375 ink_cairo_surface_average_color(s, R, G, B, A);
2376 cairo_surface_destroy(s);
2377
2378 // std::cout << " p: " << p
2379 // << " box: " << ibox
2380 // << " R: " << R
2381 // << " G: " << G
2382 // << " B: " << B
2383 // << std::endl;
2384 n->color.set( R, G, B );
2385 }
2386
2387 pick_doc->getRoot()->invoke_hide(pick_visionkey);
2388 delete pick_drawing;
2389
2390 picked = 1; // Picking always happens
2391 if( picked > 0 ) built = false;
2392 return picked;
2393 }
2394
2395 /**
2396 Splits selected rows and/or columns in half (according to the path 't' parameter).
2397 Input is a list of selected corner draggable indices.
2398 */
insert(std::vector<guint> corners)2399 guint SPMeshNodeArray::insert( std::vector<guint> corners ) {
2400
2401 guint inserted = 0;
2402
2403 if( corners.size() < 2 ) return 0;
2404
2405 std::set<guint> columns;
2406 std::set<guint> rows;
2407
2408 for( guint i = 0; i < corners.size()-1; ++i ) {
2409 for( guint j = i+1; j < corners.size(); ++j ) {
2410
2411 // This works as all corners have indices and they
2412 // are numbered in order by row and column (and
2413 // the node array is rectangular).
2414
2415 guint c1 = corners[i];
2416 guint c2 = corners[j];
2417 if (c2 < c1) {
2418 c1 = corners[j];
2419 c2 = corners[i];
2420 }
2421
2422 // Number of corners in a row of patches.
2423 guint ncorners = patch_columns() + 1;
2424
2425 guint crow1 = c1 / ncorners;
2426 guint crow2 = c2 / ncorners;
2427 guint ccol1 = c1 % ncorners;
2428 guint ccol2 = c2 % ncorners;
2429
2430 // Check for horizontal neighbors
2431 if ( crow1 == crow2 && (ccol2 - ccol1) == 1 ) {
2432 columns.insert( ccol1 );
2433 }
2434
2435 // Check for vertical neighbors
2436 if ( ccol1 == ccol2 && (crow2 - crow1) == 1 ) {
2437 rows.insert( crow1 );
2438 }
2439 }
2440 }
2441
2442 // Iterate backwards so column/row numbers are not invalidated.
2443 std::set<guint>::reverse_iterator rit;
2444 for (rit=columns.rbegin(); rit != columns.rend(); ++rit) {
2445 split_column( *rit, 0.5);
2446 ++inserted;
2447 }
2448 for (rit=rows.rbegin(); rit != rows.rend(); ++rit) {
2449 split_row( *rit, 0.5);
2450 ++inserted;
2451 }
2452
2453 if( inserted > 0 ) built = false;
2454 return inserted;
2455 }
2456
2457 /**
2458 Moves handles in response to a corner node move.
2459 p_old: original position of moved corner node.
2460 corner: the corner node moved (draggable index, i.e. point_i).
2461 selected: list of all corners selected (draggable indices).
2462 op: how other corners should be moved.
2463 Corner node must already have been moved!
2464 */
update_handles(guint corner,std::vector<guint>,Geom::Point p_old,MeshNodeOperation)2465 void SPMeshNodeArray::update_handles( guint corner, std::vector< guint > /*selected*/, Geom::Point p_old, MeshNodeOperation /*op*/ )
2466 {
2467 if (!draggers_valid) {
2468 std::cerr << "SPMeshNodeArray::update_handles: Draggers not valid!" << std::endl;
2469 return;
2470 }
2471 // assert( draggers_valid );
2472
2473 // std::cout << "SPMeshNodeArray::update_handles: "
2474 // << " corner: " << corner
2475 // << " op: " << op
2476 // << std::endl;
2477
2478 // Find number of patch rows and columns
2479 guint mrow = patch_rows();
2480 guint mcol = patch_columns();
2481
2482 // Number of corners in a row of patches.
2483 guint ncorners = mcol + 1;
2484
2485 // Find corner row/column
2486 guint crow = corner / ncorners;
2487 guint ccol = corner % ncorners;
2488
2489 // Find node row/column
2490 guint nrow = crow * 3;
2491 guint ncol = ccol * 3;
2492
2493 // std::cout << " mrow: " << mrow
2494 // << " mcol: " << mcol
2495 // << " crow: " << crow
2496 // << " ccol: " << ccol
2497 // << " ncorners: " << ncorners
2498 // << " nrow: " << nrow
2499 // << " ncol: " << ncol
2500 // << std::endl;
2501
2502 // New corner mesh coordinate.
2503 Geom::Point p_new = nodes[nrow][ncol]->p;
2504
2505 // Corner point move dpg in mesh coordinate system.
2506 Geom::Point dp = p_new - p_old;
2507
2508 // std::cout << " p_old: " << p_old << std::endl;
2509 // std::cout << " p_new: " << p_new << std::endl;
2510 // std::cout << " dp: " << dp << std::endl;
2511
2512 // STEP 1: ONLY DO DIRECT MOVE
2513 bool patch[4];
2514 patch[0] = patch[1] = patch[2] = patch[3] = false;
2515 if( ccol > 0 && crow > 0 ) patch[0] = true;
2516 if( ccol < mcol && crow > 0 ) patch[1] = true;
2517 if( ccol < mcol && crow < mrow ) patch[2] = true;
2518 if( ccol > 0 && crow < mrow ) patch[3] = true;
2519
2520 // std::cout << patch[0] << " "
2521 // << patch[1] << " "
2522 // << patch[2] << " "
2523 // << patch[3] << std::endl;
2524
2525 // Move handles
2526 if( patch[0] || patch[1] ) {
2527 if( nodes[nrow-1][ncol]->path_type == 'l' ||
2528 nodes[nrow-1][ncol]->path_type == 'L' ) {
2529 Geom::Point s = (nodes[nrow-3][ncol]->p - nodes[nrow][ncol]->p)/3.0;
2530 nodes[nrow-1][ncol ]->p = nodes[nrow][ncol]->p + s;
2531 nodes[nrow-2][ncol ]->p = nodes[nrow-3][ncol]->p - s;
2532 } else {
2533 nodes[nrow-1][ncol ]->p += dp;
2534 }
2535 }
2536
2537 if( patch[1] || patch[2] ) {
2538 if( nodes[nrow ][ncol+1]->path_type == 'l' ||
2539 nodes[nrow ][ncol+1]->path_type == 'L' ) {
2540 Geom::Point s = (nodes[nrow][ncol+3]->p - nodes[nrow][ncol]->p)/3.0;
2541 nodes[nrow ][ncol+1]->p = nodes[nrow][ncol]->p + s;
2542 nodes[nrow ][ncol+2]->p = nodes[nrow][ncol+3]->p - s;
2543 } else {
2544 nodes[nrow ][ncol+1]->p += dp;
2545 }
2546 }
2547
2548 if( patch[2] || patch[3] ) {
2549 if( nodes[nrow+1][ncol ]->path_type == 'l' ||
2550 nodes[nrow+1][ncol ]->path_type == 'L' ) {
2551 Geom::Point s = (nodes[nrow+3][ncol]->p - nodes[nrow][ncol]->p)/3.0;
2552 nodes[nrow+1][ncol ]->p = nodes[nrow][ncol]->p + s;
2553 nodes[nrow+2][ncol ]->p = nodes[nrow+3][ncol]->p - s;
2554 } else {
2555 nodes[nrow+1][ncol ]->p += dp;
2556 }
2557 }
2558
2559 if( patch[3] || patch[0] ) {
2560 if( nodes[nrow ][ncol-1]->path_type == 'l' ||
2561 nodes[nrow ][ncol-1]->path_type == 'L' ) {
2562 Geom::Point s = (nodes[nrow][ncol-3]->p - nodes[nrow][ncol]->p)/3.0;
2563 nodes[nrow ][ncol-1]->p = nodes[nrow][ncol]->p + s;
2564 nodes[nrow ][ncol-2]->p = nodes[nrow][ncol-3]->p - s;
2565 } else {
2566 nodes[nrow ][ncol-1]->p += dp;
2567 }
2568 }
2569
2570
2571 // Move tensors
2572 if( patch[0] ) nodes[nrow-1][ncol-1]->p += dp;
2573 if( patch[1] ) nodes[nrow-1][ncol+1]->p += dp;
2574 if( patch[2] ) nodes[nrow+1][ncol+1]->p += dp;
2575 if( patch[3] ) nodes[nrow+1][ncol-1]->p += dp;
2576
2577 // // Check if neighboring corners are selected.
2578
2579 // bool do_scale = false;
2580
2581 // bool do_scale_xp = do_scale;
2582 // bool do_scale_xn = do_scale;
2583 // bool do_scale_yp = do_scale;
2584 // bool do_scale_yn = do_scale;
2585
2586 // if( ccol < mcol+1 ) {
2587 // if( std::find( sc.begin(), sc.end(), point_i + 1 ) != sc.end() ) {
2588 // do_scale_xp = false;
2589 // std::cout << " Not scaling x+" << std::endl;
2590 // }
2591 // }
2592
2593 // if( ccol > 0 ) {
2594 // if( std::find( sc.begin(), sc.end(), point_i - 1 ) != sc.end() ) {
2595 // do_scale_xn = false;
2596 // std::cout << " Not scaling x-" << std::endl;
2597 // }
2598 // }
2599
2600 // if( crow < mrow+1 ) {
2601 // if( std::find( sc.begin(), sc.end(), point_i + ncorners ) != sc.end() ) {
2602 // do_scale_yp = false;
2603 // std::cout << " Not scaling y+" << std::endl;
2604 // }
2605 // }
2606
2607 // if( crow > 0 ) {
2608 // if( std::find( sc.begin(), sc.end(), point_i - ncorners ) != sc.end() ) {
2609 // do_scale_yn = false;
2610 // std::cout << " Not scaling y-" << std::endl;
2611 // }
2612 // }
2613
2614 // // We have four patches to adjust...
2615 // for ( guint k = 0; k < 4; ++k ) {
2616
2617 // bool do_scale_x = do_scale;
2618 // bool do_scale_y = do_scale;
2619
2620 // SPMeshNode* pnodes[4][4];
2621
2622 // // Load up matrix
2623 // switch (k) {
2624
2625 // case 0:
2626 // if( crow < mrow+1 && ccol < mcol+1 ) {
2627 // // Bottom right patch
2628
2629 // do_scale_x = do_scale_xp;
2630 // do_scale_y = do_scale_yp;
2631
2632 // for( guint i = 0; i < 4; ++i ) {
2633 // for( guint j = 0; j< 4; ++j ) {
2634 // pnodes[i][j] = mg->array.nodes[nrow+i][nrow+j];
2635 // }
2636 // }
2637 // }
2638 // break;
2639
2640 // case 1:
2641 // if( crow < mrow+1 && ccol > 0 ) {
2642 // // Bottom left patch (note x, y swapped)
2643
2644 // do_scale_y = do_scale_xn;
2645 // do_scale_x = do_scale_yp;
2646
2647 // for( guint i = 0; i < 4; ++i ) {
2648 // for( guint j = 0; j< 4; ++j ) {
2649 // pnodes[j][i] = mg->array.nodes[nrow+i][nrow-j];
2650 // }
2651 // }
2652 // }
2653 // break;
2654
2655 // case 2:
2656 // if( crow > 0 && ccol > 0 ) {
2657 // // Top left patch
2658
2659 // do_scale_x = do_scale_xn;
2660 // do_scale_y = do_scale_yn;
2661
2662 // for( guint i = 0; i < 4; ++i ) {
2663 // for( guint j = 0; j< 4; ++j ) {
2664 // pnodes[i][j] = mg->array.nodes[nrow-i][nrow-j];
2665 // }
2666 // }
2667 // }
2668 // break;
2669
2670 // case 3:
2671 // if( crow > 0 && ccol < mcol+1 ) {
2672 // // Top right patch (note x, y swapped)
2673
2674 // do_scale_y = do_scale_xp;
2675 // do_scale_x = do_scale_yn;
2676
2677 // for( guint i = 0; i < 4; ++i ) {
2678 // for( guint j = 0; j< 4; ++j ) {
2679 // pnodes[j][i] = mg->array.nodes[nrow-i][nrow+j];
2680 // }
2681 // }
2682 // }
2683 // break;
2684 // }
2685
2686 // // Now we must move points in both x and y.
2687 // // There are upto six points to move: P01, P02, P11, P12, P21, P22.
2688 // // (The points P10, P20 will be moved in another branch of the loop.
2689 // // The points P03, P13, P23, P33, P32, P31, P30 are not moved.)
2690 // //
2691 // // P00 P01 P02 P03
2692 // // P10 P11 P12 P13
2693 // // P20 P21 P22 P23
2694 // // P30 P31 P32 P33
2695 // //
2696 // // The goal is to preserve the direction of the handle!
2697
2698
2699 // Geom::Point dsx_new = pnodes[0][3]->p - pnodes[0][0]->p; // New side x
2700 // Geom::Point dsy_new = pnodes[3][0]->p - pnodes[0][0]->p; // New side y
2701 // Geom::Point dsx_old = pnodes[0][3]->p - pcg_old; // Old side x
2702 // Geom::Point dsy_old = pnodes[3][0]->p - pcg_old; // Old side y
2703
2704
2705 // double scale_factor_x = 1.0;
2706 // if( dsx_old.length() != 0.0 ) scale_factor_x = dsx_new.length()/dsx_old.length();
2707
2708 // double scale_factor_y = 1.0;
2709 // if( dsy_old.length() != 0.0 ) scale_factor_y = dsy_new.length()/dsy_old.length();
2710
2711
2712 // if( do_scalex && do_scaley ) {
2713
2714 // // We have six point to move.
2715
2716 // // P01
2717 // Geom::Point dp01 = pnodes[0][1] - pcg_old;
2718 // dp01 *= scale_factor_x;
2719 // pnodes[0][1] = pnodes[0][0] + dp01;
2720
2721 // // P02
2722 // Geom::Point dp02 = pnodes[0][2] - pnodes[0][3];
2723 // dp02 *= scale_factor_x;
2724 // pnodes[0][2] = pnodes[0][3] + dp02;
2725
2726 // // P11
2727 // Geom::Point dp11 = pnodes[1][1] - pcg_old;
2728 // dp11 *= scale_factor_x;
2729 // pnodes[1][1] = pnodes[0][0] + dp11;
2730
2731
2732
2733 // // P21
2734 // Geom::Point dp21 = pnodes[2][1] - pnodes[3][0];
2735 // dp21 *= scale_factor_x;
2736 // dp21 *= scale_factor_y;
2737 // pnodes[2][1] = pnodes[3][0] + dp21;
2738
2739
2740 // Geom::Point dsx1 = pnodes[0][1]->p -
2741 }
2742
outline_path() const2743 std::unique_ptr<SPCurve> SPMeshNodeArray::outline_path() const
2744 {
2745 auto outline = std::make_unique<SPCurve>();
2746
2747 if (nodes.empty() ) {
2748 std::cerr << "SPMeshNodeArray::outline_path: empty array!" << std::endl;
2749 return outline;
2750 }
2751
2752 outline->moveto( nodes[0][0]->p );
2753
2754 int ncol = nodes[0].size();
2755 int nrow = nodes.size();
2756
2757 // Top
2758 for (int i = 1; i < ncol; i += 3 ) {
2759 outline->curveto( nodes[0][i]->p, nodes[0][i+1]->p, nodes[0][i+2]->p);
2760 }
2761
2762 // Right
2763 for (int i = 1; i < nrow; i += 3 ) {
2764 outline->curveto( nodes[i][ncol-1]->p, nodes[i+1][ncol-1]->p, nodes[i+2][ncol-1]->p);
2765 }
2766
2767 // Bottom (right to left)
2768 for (int i = 1; i < ncol; i += 3 ) {
2769 outline->curveto( nodes[nrow-1][ncol-i-1]->p, nodes[nrow-1][ncol-i-2]->p, nodes[nrow-1][ncol-i-3]->p);
2770 }
2771
2772 // Left (bottom to top)
2773 for (int i = 1; i < nrow; i += 3 ) {
2774 outline->curveto( nodes[nrow-i-1][0]->p, nodes[nrow-i-2][0]->p, nodes[nrow-i-3][0]->p);
2775 }
2776
2777 outline->closepath();
2778
2779 return outline;
2780 }
2781
transform(Geom::Affine const & m)2782 void SPMeshNodeArray::transform(Geom::Affine const &m) {
2783
2784 for (int i = 0; i < nodes[0].size(); ++i) {
2785 for (auto & node : nodes) {
2786 node[i]->p *= m;
2787 }
2788 }
2789 }
2790
2791 // Transform mesh to fill box. Return true if mesh transformed.
fill_box(Geom::OptRect & box)2792 bool SPMeshNodeArray::fill_box(Geom::OptRect &box) {
2793
2794 // If gradientTransfor is set (as happens when an object is transformed
2795 // with the "optimized" preferences set true), we need to remove it.
2796 if (mg->gradientTransform_set) {
2797 Geom::Affine gt = mg->gradientTransform;
2798 transform( gt );
2799 mg->gradientTransform_set = false;
2800 mg->gradientTransform.setIdentity();
2801 }
2802
2803 auto outline = outline_path();
2804 Geom::OptRect mesh_bbox = outline->get_pathvector().boundsExact();
2805
2806 if ((*mesh_bbox).width() == 0 || (*mesh_bbox).height() == 0) {
2807 return false;
2808 }
2809
2810 double scale_x = (*box).width() /(*mesh_bbox).width() ;
2811 double scale_y = (*box).height()/(*mesh_bbox).height();
2812
2813 Geom::Translate t1(-(*mesh_bbox).min());
2814 Geom::Scale scale(scale_x,scale_y);
2815 Geom::Translate t2((*box).min());
2816 Geom::Affine trans = t1 * scale * t2;
2817 if (!trans.isIdentity() ) {
2818 transform(trans);
2819 write( mg );
2820 mg->requestModified(SP_OBJECT_MODIFIED_FLAG);
2821 return true;
2822 }
2823
2824 return false;
2825 }
2826
2827 // Defined in gradient-chemistry.cpp
2828 guint32 average_color(guint32 c1, guint32 c2, gdouble p);
2829
2830 /**
2831 Split a row into n equal parts.
2832 */
split_row(unsigned int row,unsigned int n)2833 void SPMeshNodeArray::split_row( unsigned int row, unsigned int n ) {
2834
2835 double nn = n;
2836 if( n > 1 ) split_row( row, (nn-1)/nn );
2837 if( n > 2 ) split_row( row, n-1 );
2838 }
2839
2840 /**
2841 Split a column into n equal parts.
2842 */
split_column(unsigned int col,unsigned int n)2843 void SPMeshNodeArray::split_column( unsigned int col, unsigned int n ) {
2844
2845 double nn = n;
2846 if( n > 1 ) split_column( col, (nn-1)/nn );
2847 if( n > 2 ) split_column( col, n-1 );
2848 }
2849
2850 /**
2851 Split a row into two rows at coord (fraction of row height).
2852 */
split_row(unsigned int row,double coord)2853 void SPMeshNodeArray::split_row( unsigned int row, double coord ) {
2854
2855 // std::cout << "Splitting row: " << row << " at " << coord << std::endl;
2856 // print();
2857 assert( coord >= 0.0 && coord <= 1.0 );
2858 assert( row < patch_rows() );
2859
2860 built = false;
2861
2862 // First step is to ensure that handle and tensor points are up-to-date if they are not set.
2863 // (We can't do this on the fly as we overwrite the necessary points to do the calculation
2864 // during the update.)
2865 for( guint j = 0; j < patch_columns(); ++ j ) {
2866 SPMeshPatchI patch( &nodes, row, j );
2867 patch.updateNodes();
2868 }
2869
2870 // Add three new rows of empty nodes
2871 for( guint i = 0; i < 3; ++i ) {
2872 std::vector< SPMeshNode* > new_row;
2873 for( guint j = 0; j < nodes[0].size(); ++j ) {
2874 SPMeshNode* new_node = new SPMeshNode;
2875 new_row.push_back( new_node );
2876 }
2877 nodes.insert( nodes.begin()+3*(row+1), new_row );
2878 }
2879
2880 guint i = 3 * row; // Convert from patch row to node row
2881 for( guint j = 0; j < nodes[i].size(); ++j ) {
2882
2883 // std::cout << "Splitting row: column: " << j << std::endl;
2884
2885 Geom::Point p[4];
2886 for( guint k = 0; k < 4; ++k ) {
2887 guint n = k;
2888 if( k == 3 ) n = 6; // Bottom patch row has been shifted by new rows
2889 p[k] = nodes[i+n][j]->p;
2890 // std::cout << p[k] << std::endl;
2891 }
2892
2893 Geom::BezierCurveN<3> b( p[0], p[1], p[2], p[3] );
2894
2895 std::pair<Geom::BezierCurveN<3>, Geom::BezierCurveN<3> > b_new =
2896 b.subdivide( coord );
2897
2898 // Update points
2899 for( guint n = 0; n < 4; ++n ) {
2900 nodes[i+n ][j]->p = b_new.first[n];
2901 nodes[i+n+3][j]->p = b_new.second[n];
2902 // std::cout << b_new.first[n] << " " << b_new.second[n] << std::endl;
2903 }
2904
2905 if( nodes[i][j]->node_type == MG_NODE_TYPE_CORNER ) {
2906 // We are splitting a side
2907
2908 // Path type stored in handles.
2909 gchar path_type = nodes[i+1][j]->path_type;
2910 nodes[i+4][j]->path_type = path_type;
2911 nodes[i+5][j]->path_type = path_type;
2912 bool set = nodes[i+1][j]->set;
2913 nodes[i+4][j]->set = set;
2914 nodes[i+5][j]->set = set;
2915 nodes[i+4][j]->node_type = MG_NODE_TYPE_HANDLE;
2916 nodes[i+5][j]->node_type = MG_NODE_TYPE_HANDLE;
2917
2918 // Color stored in corners
2919 guint c0 = nodes[i ][j]->color.toRGBA32( 1.0 );
2920 guint c1 = nodes[i+6][j]->color.toRGBA32( 1.0 );
2921 gdouble o0 = nodes[i ][j]->opacity;
2922 gdouble o1 = nodes[i+6][j]->opacity;
2923 guint cnew = average_color( c0, c1, coord );
2924 gdouble onew = o0 * (1.0 - coord) + o1 * coord;
2925 nodes[i+3][j]->color.set( cnew );
2926 nodes[i+3][j]->opacity = onew;
2927 nodes[i+3][j]->node_type = MG_NODE_TYPE_CORNER;
2928 nodes[i+3][j]->set = true;
2929
2930 } else {
2931 // We are splitting a middle
2932
2933 bool set = nodes[i+1][j]->set || nodes[i+2][j]->set;
2934 nodes[i+4][j]->set = set;
2935 nodes[i+5][j]->set = set;
2936 nodes[i+4][j]->node_type = MG_NODE_TYPE_TENSOR;
2937 nodes[i+5][j]->node_type = MG_NODE_TYPE_TENSOR;
2938
2939 // Path type, if different, choose l -> L -> c -> C.
2940 gchar path_type0 = nodes[i ][j]->path_type;
2941 gchar path_type1 = nodes[i+6][j]->path_type;
2942 gchar path_type = 'l';
2943 if( path_type0 == 'L' || path_type1 == 'L') path_type = 'L';
2944 if( path_type0 == 'c' || path_type1 == 'c') path_type = 'c';
2945 if( path_type0 == 'C' || path_type1 == 'C') path_type = 'C';
2946 nodes[i+3][j]->path_type = path_type;
2947 nodes[i+3][j]->node_type = MG_NODE_TYPE_HANDLE;
2948 if( path_type == 'c' || path_type == 'C' ) nodes[i+3][j]->set = true;
2949
2950 }
2951
2952 nodes[i+3][j]->node_edge = MG_NODE_EDGE_NONE;
2953 nodes[i+4][j]->node_edge = MG_NODE_EDGE_NONE;
2954 nodes[i+5][j]->node_edge = MG_NODE_EDGE_NONE;;
2955 if( j == 0 ) {
2956 nodes[i+3][j]->node_edge |= MG_NODE_EDGE_LEFT;
2957 nodes[i+4][j]->node_edge |= MG_NODE_EDGE_LEFT;
2958 nodes[i+5][j]->node_edge |= MG_NODE_EDGE_LEFT;
2959 }
2960 if( j == nodes[i].size() - 1 ) {
2961 nodes[i+3][j]->node_edge |= MG_NODE_EDGE_RIGHT;
2962 nodes[i+4][j]->node_edge |= MG_NODE_EDGE_RIGHT;
2963 nodes[i+5][j]->node_edge |= MG_NODE_EDGE_RIGHT;
2964 }
2965 }
2966
2967 // std::cout << "Splitting row: result:" << std::endl;
2968 // print();
2969 }
2970
2971
2972
2973 /**
2974 Split a column into two columns at coord (fraction of column width).
2975 */
split_column(unsigned int col,double coord)2976 void SPMeshNodeArray::split_column( unsigned int col, double coord ) {
2977
2978 // std::cout << "Splitting column: " << col << " at " << coord << std::endl;
2979 // print();
2980 assert( coord >= 0.0 && coord <= 1.0 );
2981 assert( col < patch_columns() );
2982
2983 built = false;
2984
2985 // First step is to ensure that handle and tensor points are up-to-date if they are not set.
2986 // (We can't do this on the fly as we overwrite the necessary points to do the calculation
2987 // during the update.)
2988 for( guint i = 0; i < patch_rows(); ++ i ) {
2989 SPMeshPatchI patch( &nodes, i, col );
2990 patch.updateNodes();
2991 }
2992
2993 guint j = 3 * col; // Convert from patch column to node column
2994 for( guint i = 0; i < nodes.size(); ++i ) {
2995
2996 // std::cout << "Splitting column: row: " << i << std::endl;
2997
2998 Geom::Point p[4];
2999 for( guint k = 0; k < 4; ++k ) {
3000 p[k] = nodes[i][j+k]->p;
3001 }
3002
3003 Geom::BezierCurveN<3> b( p[0], p[1], p[2], p[3] );
3004
3005 std::pair<Geom::BezierCurveN<3>, Geom::BezierCurveN<3> > b_new =
3006 b.subdivide( coord );
3007
3008 // Add three new nodes
3009 for( guint n = 0; n < 3; ++n ) {
3010 SPMeshNode* new_node = new SPMeshNode;
3011 nodes[i].insert( nodes[i].begin()+j+3, new_node );
3012 }
3013
3014 // Update points
3015 for( guint n = 0; n < 4; ++n ) {
3016 nodes[i][j+n]->p = b_new.first[n];
3017 nodes[i][j+n+3]->p = b_new.second[n];
3018 }
3019
3020 if( nodes[i][j]->node_type == MG_NODE_TYPE_CORNER ) {
3021 // We are splitting a side
3022
3023 // Path type stored in handles.
3024 gchar path_type = nodes[i][j+1]->path_type;
3025 nodes[i][j+4]->path_type = path_type;
3026 nodes[i][j+5]->path_type = path_type;
3027 bool set = nodes[i][j+1]->set;
3028 nodes[i][j+4]->set = set;
3029 nodes[i][j+5]->set = set;
3030 nodes[i][j+4]->node_type = MG_NODE_TYPE_HANDLE;
3031 nodes[i][j+5]->node_type = MG_NODE_TYPE_HANDLE;
3032
3033 // Color stored in corners
3034 guint c0 = nodes[i][j ]->color.toRGBA32( 1.0 );
3035 guint c1 = nodes[i][j+6]->color.toRGBA32( 1.0 );
3036 gdouble o0 = nodes[i][j ]->opacity;
3037 gdouble o1 = nodes[i][j+6]->opacity;
3038 guint cnew = average_color( c0, c1, coord );
3039 gdouble onew = o0 * (1.0 - coord) + o1 * coord;
3040 nodes[i][j+3]->color.set( cnew );
3041 nodes[i][j+3]->opacity = onew;
3042 nodes[i][j+3]->node_type = MG_NODE_TYPE_CORNER;
3043 nodes[i][j+3]->set = true;
3044
3045 } else {
3046 // We are splitting a middle
3047
3048 bool set = nodes[i][j+1]->set || nodes[i][j+2]->set;
3049 nodes[i][j+4]->set = set;
3050 nodes[i][j+5]->set = set;
3051 nodes[i][j+4]->node_type = MG_NODE_TYPE_TENSOR;
3052 nodes[i][j+5]->node_type = MG_NODE_TYPE_TENSOR;
3053
3054 // Path type, if different, choose l -> L -> c -> C.
3055 gchar path_type0 = nodes[i][j ]->path_type;
3056 gchar path_type1 = nodes[i][j+6]->path_type;
3057 gchar path_type = 'l';
3058 if( path_type0 == 'L' || path_type1 == 'L') path_type = 'L';
3059 if( path_type0 == 'c' || path_type1 == 'c') path_type = 'c';
3060 if( path_type0 == 'C' || path_type1 == 'C') path_type = 'C';
3061 nodes[i][j+3]->path_type = path_type;
3062 nodes[i][j+3]->node_type = MG_NODE_TYPE_HANDLE;
3063 if( path_type == 'c' || path_type == 'C' ) nodes[i][j+3]->set = true;
3064
3065 }
3066
3067 nodes[i][j+3]->node_edge = MG_NODE_EDGE_NONE;
3068 nodes[i][j+4]->node_edge = MG_NODE_EDGE_NONE;
3069 nodes[i][j+5]->node_edge = MG_NODE_EDGE_NONE;;
3070 if( i == 0 ) {
3071 nodes[i][j+3]->node_edge |= MG_NODE_EDGE_TOP;
3072 nodes[i][j+4]->node_edge |= MG_NODE_EDGE_TOP;
3073 nodes[i][j+5]->node_edge |= MG_NODE_EDGE_TOP;
3074 }
3075 if( i == nodes.size() - 1 ) {
3076 nodes[i][j+3]->node_edge |= MG_NODE_EDGE_BOTTOM;
3077 nodes[i][j+4]->node_edge |= MG_NODE_EDGE_BOTTOM;
3078 nodes[i][j+5]->node_edge |= MG_NODE_EDGE_BOTTOM;
3079 }
3080
3081 }
3082
3083 // std::cout << "Splitting col: result:" << std::endl;
3084 // print();
3085 }
3086
3087 /*
3088 Local Variables:
3089 mode:c++
3090 c-file-style:"stroustrup"
3091 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
3092 indent-tabs-mode:nil
3093 fill-column:99
3094 End:
3095 */
3096 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
3097