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