1
2
3 #include "controlpointselection.h"
4 #include "tvectorimage.h"
5 #include "tmathutil.h"
6
7 #include "tools/toolhandle.h"
8 #include "tools/toolutils.h"
9
10 #include "toonz/tobjecthandle.h"
11 #include "toonz/txshlevelhandle.h"
12 #include "toonz/tstageobject.h"
13
14 using namespace ToolUtils;
15
16 namespace {
17
18 //-----------------------------------------------------------------------------
19
isLinearPoint(const TPointD & p0,const TPointD & p1,const TPointD & p2)20 inline bool isLinearPoint(const TPointD &p0, const TPointD &p1,
21 const TPointD &p2) {
22 return (tdistance(p0, p1) < 0.02) && (tdistance(p1, p2) < 0.02);
23 }
24
25 //-----------------------------------------------------------------------------
26
27 //! Ritorna \b true se il punto \b p1 e' una cuspide.
isCuspPoint(const TPointD & p0,const TPointD & p1,const TPointD & p2)28 bool isCuspPoint(const TPointD &p0, const TPointD &p1, const TPointD &p2) {
29 TPointD p0_p1(p0 - p1), p2_p1(p2 - p1);
30 double n1 = norm(p0_p1), n2 = norm(p2_p1);
31
32 // Partial linear points are ALWAYS cusps (since directions from them are
33 // determined by neighbours, not by the points themselves)
34 if ((n1 < 0.02) || (n2 < 0.02)) return true;
35
36 p0_p1 = p0_p1 * (1.0 / n1);
37 p2_p1 = p2_p1 * (1.0 / n2);
38
39 return (p0_p1 * p2_p1 > 0) ||
40 (fabs(cross(p0_p1, p2_p1)) > 0.09); // more than 5° is yes
41
42 // Distance-based check. Unscalable...
43 // return
44 // !areAlmostEqual(tdistance(p0,p2),tdistance(p0,p1)+tdistance(p1,p2),2);
45 }
46
47 //-----------------------------------------------------------------------------
48
computeLinearPoint(const TThickPoint & p1,const TThickPoint & p2,double factor,bool isIn)49 TThickPoint computeLinearPoint(const TThickPoint &p1, const TThickPoint &p2,
50 double factor, bool isIn) {
51 TThickPoint p = p2 - p1;
52 TThickPoint v = p * (1 / norm(p));
53 if (isIn) return p2 - factor * v;
54 return p1 + factor * v;
55 }
56
57 //-----------------------------------------------------------------------------
58 /*! Insert a point in the most long chunk between chunk \b indexA and chunk \b
59 * indexB. */
insertPoint(TStroke * stroke,int indexA,int indexB)60 void insertPoint(TStroke *stroke, int indexA, int indexB) {
61 assert(stroke);
62 int j = 0;
63 int chunkCount = indexB - indexA;
64 if (chunkCount % 2 == 0) return;
65 double length = 0;
66 double firstW, lastW;
67 for (j = indexA; j < indexB; j++) {
68 // cerco il chunk piu' lungo
69 double w0 = stroke->getW(stroke->getChunk(j)->getP0());
70 double w1;
71 if (j == stroke->getChunkCount() - 1)
72 w1 = 1;
73 else
74 w1 = stroke->getW(stroke->getChunk(j)->getP2());
75 double length0 = stroke->getLength(w0);
76 double length1 = stroke->getLength(w1);
77 if (length < length1 - length0) {
78 firstW = w0;
79 lastW = w1;
80 length = length1 - length0;
81 }
82 }
83 stroke->insertControlPoints((firstW + lastW) * 0.5);
84 }
85
86 } // namespace
87
88 //=============================================================================
89 // ControlPointEditorStroke
90 //-----------------------------------------------------------------------------
91
clone() const92 ControlPointEditorStroke *ControlPointEditorStroke::clone() const {
93 ControlPointEditorStroke *controlPointEditorStroke =
94 new ControlPointEditorStroke();
95 controlPointEditorStroke->setStroke(m_vi->clone(), m_strokeIndex);
96 return controlPointEditorStroke;
97 }
98
99 //-----------------------------------------------------------------------------
100
nextIndex(int index) const101 int ControlPointEditorStroke::nextIndex(int index) const {
102 int cpCount = m_controlPoints.size();
103 if (++index < cpCount) return index;
104
105 if (isSelfLoop()) {
106 index %= cpCount;
107 return (index < 0) ? index + cpCount : index;
108 }
109
110 return -1;
111 }
112
113 //-----------------------------------------------------------------------------
114
prevIndex(int index) const115 int ControlPointEditorStroke::prevIndex(int index) const {
116 int cpCount = m_controlPoints.size();
117 if (--index >= 0) return index;
118
119 if (isSelfLoop()) {
120 index %= cpCount;
121 return (index < 0) ? index + cpCount : index;
122 }
123
124 return -1;
125 }
126
127 //-----------------------------------------------------------------------------
128
adjustChunkParity()129 void ControlPointEditorStroke::adjustChunkParity() {
130 TStroke *stroke = getStroke();
131 if (!stroke) return;
132 int firstChunk;
133 int secondChunk = stroke->getChunkCount();
134 int i;
135 for (i = stroke->getChunkCount() - 1; i > 0; i--) {
136 if (tdistance(stroke->getChunk(i - 1)->getP0(),
137 stroke->getChunk(i)->getP2()) < 0.5)
138 continue;
139 TPointD p0 = stroke->getChunk(i - 1)->getP1();
140 TPointD p1 = stroke->getChunk(i - 1)->getP2();
141 TPointD p2 = stroke->getChunk(i)->getP1();
142 if (isCuspPoint(p0, p1, p2) || isLinearPoint(p0, p1, p2)) {
143 firstChunk = i;
144 insertPoint(stroke, firstChunk, secondChunk);
145 secondChunk = firstChunk;
146 }
147 }
148 insertPoint(stroke, 0, secondChunk);
149 }
150
151 //-----------------------------------------------------------------------------
152
resetControlPoints()153 void ControlPointEditorStroke::resetControlPoints() {
154 TStroke *stroke = getStroke();
155 if (!stroke) return;
156 m_controlPoints.clear();
157 int i;
158 int cpCount = stroke->getControlPointCount();
159 if (cpCount == 3) {
160 const TThickQuadratic *chunk = stroke->getChunk(0);
161 if (chunk->getP0() == chunk->getP1() &&
162 chunk->getP0() == chunk->getP2()) // E' un punto
163 {
164 m_controlPoints.push_back(
165 ControlPoint(0, TPointD(0.0, 0.0), TPointD(0.0, 0.0), true));
166 return;
167 }
168 }
169 for (i = 0; i < cpCount; i = i + 4) {
170 TThickPoint speedIn, speedOut;
171 bool isPickOut = false;
172 TThickPoint p = stroke->getControlPoint(i);
173 TThickPoint precP = stroke->getControlPoint(i - 1);
174 TThickPoint nextP = stroke->getControlPoint(i + 1);
175 if (0 < i && i < cpCount - 1) // calcola speedIn e speedOut
176 {
177 speedIn = p - precP;
178 speedOut = nextP - p;
179 }
180 if (i == 0) // calcola solo lo speedOut
181 {
182 speedOut = nextP - p;
183 if (isSelfLoop()) speedIn = p - stroke->getControlPoint(cpCount - 2);
184 }
185 if (i == cpCount - 1) // calcola solo lo speedIn
186 speedIn = p - precP;
187 if (i == cpCount - 1 && isSelfLoop())
188 break; // Se lo stroke e' selfLoop inserisco solo il primo dei due punti
189 // coincidenti
190
191 bool isCusp = ((i != 0 && i != cpCount - 1) || (isSelfLoop() && i == 0))
192 ? isCuspPoint(precP, p, nextP)
193 : true;
194 m_controlPoints.push_back(ControlPoint(i, speedIn, speedOut, isCusp));
195 }
196 }
197
198 //-----------------------------------------------------------------------------
199
getPureDependentPoint(int index) const200 TThickPoint ControlPointEditorStroke::getPureDependentPoint(int index) const {
201 TStroke *stroke = getStroke();
202 if (!stroke) return TThickPoint();
203 bool selfLoop = isSelfLoop();
204 int cpCount = selfLoop ? m_controlPoints.size() + 1 : m_controlPoints.size();
205 int nextIndex = (selfLoop && index == cpCount - 2) ? 0 : index + 1;
206 int pointIndex = m_controlPoints[index].m_pointIndex;
207
208 TThickPoint oldP(stroke->getControlPoint(pointIndex + 2));
209 TPointD oldSpeedOutP = stroke->getControlPoint(pointIndex + 1);
210 TPointD oldSpeedInP = stroke->getControlPoint(pointIndex + 3);
211
212 double dist = tdistance(oldSpeedOutP, oldSpeedInP);
213 double t = (dist > 1e-4) ? tdistance(oldSpeedInP, convert(oldP)) / dist : 0.5;
214
215 TPointD speedOutPoint(getSpeedOutPoint(index));
216 TPointD nextSpeedInPoint(getSpeedInPoint(nextIndex));
217
218 return TThickPoint((1 - t) * nextSpeedInPoint + t * speedOutPoint,
219 oldP.thick);
220
221 // return TThickPoint(0.5 * (speedOutPoint + nextSpeedInPoint), oldThick);
222 }
223
224 //-----------------------------------------------------------------------------
225
getDependentPoints(int index,std::vector<std::pair<int,TThickPoint>> & points) const226 void ControlPointEditorStroke::getDependentPoints(
227 int index, std::vector<std::pair<int, TThickPoint>> &points) const {
228 TStroke *stroke = getStroke();
229 if (!stroke) return;
230
231 int cpCount = m_controlPoints.size();
232 if (index == cpCount && isSelfLoop()) // strange, but was treated...
233 index = 0;
234
235 if (index == 0 && cpCount == 1) {
236 // Single point case
237 TStroke *stroke = getStroke();
238 TThickPoint pos(stroke->getControlPoint(m_controlPoints[0].m_pointIndex));
239
240 points.push_back(std::make_pair(1, pos));
241 points.push_back(std::make_pair(2, pos));
242 return;
243 }
244
245 int prev = prevIndex(index);
246 if (prev >= 0) {
247 int prevPointIndex = m_controlPoints[prev].m_pointIndex;
248
249 if (isSpeedOutLinear(prev))
250 points.push_back(
251 std::make_pair(prevPointIndex + 1, getSpeedOutPoint(prev)));
252 points.push_back(
253 std::make_pair(prevPointIndex + 2, getPureDependentPoint(prev)));
254 points.push_back(
255 std::make_pair(prevPointIndex + 3, getSpeedInPoint(index)));
256 }
257
258 int next = nextIndex(index);
259 if (next >= 0) {
260 int pointIndex = m_controlPoints[index].m_pointIndex;
261
262 points.push_back(std::make_pair(pointIndex + 1, getSpeedOutPoint(index)));
263 points.push_back(
264 std::make_pair(pointIndex + 2, getPureDependentPoint(index)));
265 if (isSpeedInLinear(next))
266 points.push_back(std::make_pair(pointIndex + 3, getSpeedInPoint(next)));
267 }
268 }
269
270 //-----------------------------------------------------------------------------
271
updatePoints()272 void ControlPointEditorStroke::updatePoints() {
273 TStroke *stroke = getStroke();
274 if (!stroke) return;
275 bool selfLoop = isSelfLoop();
276 // Se e' rimasto un unico punto non ha senso che la stroke sia selfloop
277 if (selfLoop && m_controlPoints.size() == 1) {
278 stroke->setSelfLoop(false);
279 selfLoop = false;
280 }
281
282 // Se e' self loop devo aggiungere un punto in piu' al cpCount
283 std::vector<TThickPoint> points;
284
285 int cpCount = selfLoop ? m_controlPoints.size() + 1 : m_controlPoints.size();
286 if (cpCount == 1)
287 // Single point case
288 points.resize(3, getControlPoint(0));
289 else {
290 std::vector<std::pair<int, TThickPoint>> dependentPoints;
291
292 points.push_back(getControlPoint(0));
293 points.push_back(getSpeedOutPoint(0));
294
295 int i, pointIndex, currPointIndex = m_controlPoints[0].m_pointIndex + 1;
296 for (i = 1; i < cpCount; ++i) {
297 bool isLastSelfLoopPoint = (selfLoop && i == cpCount - 1);
298 int index = isLastSelfLoopPoint ? 0 : i;
299
300 TThickPoint p = getControlPoint(index);
301 pointIndex = isLastSelfLoopPoint ? getStroke()->getControlPointCount()
302 : m_controlPoints[index].m_pointIndex;
303
304 dependentPoints.clear();
305 getDependentPoints(index, dependentPoints);
306
307 int j;
308 for (j = 0; j < (int)dependentPoints.size() &&
309 dependentPoints[j].first < pointIndex;
310 j++) {
311 if (currPointIndex < dependentPoints[j].first) {
312 currPointIndex = dependentPoints[j].first;
313 points.push_back(dependentPoints[j].second);
314 }
315 }
316
317 points.push_back(p);
318
319 for (; j < (int)dependentPoints.size(); j++) {
320 if (currPointIndex < dependentPoints[j].first) {
321 currPointIndex = dependentPoints[j].first;
322 points.push_back(dependentPoints[j].second);
323 }
324 }
325 }
326 }
327
328 stroke->reshape(&points[0], points.size());
329 m_vi->notifyChangedStrokes(m_strokeIndex);
330 }
331
332 //-----------------------------------------------------------------------------
333
updateDependentPoint(int index)334 void ControlPointEditorStroke::updateDependentPoint(int index) {
335 TStroke *stroke = getStroke();
336 if (!stroke) return;
337
338 std::vector<std::pair<int, TThickPoint>> points;
339 getDependentPoints(index, points);
340
341 int i;
342 for (i = 0; i < (int)points.size(); i++)
343 stroke->setControlPoint(points[i].first, points[i].second);
344
345 m_vi->notifyChangedStrokes(m_strokeIndex);
346 }
347
348 //-----------------------------------------------------------------------------
349
moveSpeedOut(int index,const TPointD & delta,double minDistance)350 void ControlPointEditorStroke::moveSpeedOut(int index, const TPointD &delta,
351 double minDistance) {
352 TStroke *stroke = getStroke();
353 if (!stroke) return;
354
355 // If the next cp has linear speed in, it must be recomputed
356 bool selfLoop = isSelfLoop();
357 int cpCount = selfLoop ? m_controlPoints.size() + 1 : m_controlPoints.size();
358 int nextIndex = (selfLoop && index == cpCount - 2) ? 0 : index + 1;
359 if (m_controlPoints[nextIndex].m_isCusp && isSpeedInLinear(nextIndex))
360 setLinearSpeedIn(nextIndex, true, false);
361
362 // Update the speedOut
363 m_controlPoints[index].m_speedOut += delta;
364 TPointD newP = m_controlPoints[index].m_speedOut;
365 if (areAlmostEqual(newP.x, 0, minDistance) &&
366 areAlmostEqual(newP.y, 0, minDistance)) // Setto a linear
367 {
368 setLinearSpeedOut(index);
369 return;
370 }
371 if (!m_controlPoints[index].m_isCusp && !isSpeedInLinear(index)) {
372 // Devo ricalcolare lo SpeedIn
373 TPointD v(m_controlPoints[index].m_speedOut *
374 (1.0 / norm(m_controlPoints[index].m_speedOut)));
375 m_controlPoints[index].m_speedIn =
376 TThickPoint(v * norm(m_controlPoints[index].m_speedIn),
377 m_controlPoints[index].m_speedIn.thick);
378 }
379 }
380
381 //-----------------------------------------------------------------------------
382
moveSpeedIn(int index,const TPointD & delta,double minDistance)383 void ControlPointEditorStroke::moveSpeedIn(int index, const TPointD &delta,
384 double minDistance) {
385 TStroke *stroke = getStroke();
386 if (!stroke) return;
387
388 // If the prev cp has linear speed out, it must be recomputed
389 bool selfLoop = isSelfLoop();
390 int cpCount = selfLoop ? m_controlPoints.size() + 1 : m_controlPoints.size();
391 int prevIndex = (selfLoop && index == 0) ? cpCount - 2 : index - 1;
392 if (m_controlPoints[prevIndex].m_isCusp && isSpeedOutLinear(prevIndex))
393 setLinearSpeedOut(prevIndex, true, false);
394
395 // Update the speedOut
396 m_controlPoints[index].m_speedIn -= delta;
397 TPointD newP = m_controlPoints[index].m_speedIn;
398 if (areAlmostEqual(newP.x, 0, minDistance) &&
399 areAlmostEqual(newP.y, 0, minDistance)) // Setto a linear
400 {
401 setLinearSpeedIn(index);
402 return;
403 }
404 if (!m_controlPoints[index].m_isCusp && !isSpeedOutLinear(index)) {
405 // Devo ricalcolare lo SpeedOut
406 TPointD v(m_controlPoints[index].m_speedIn *
407 (1.0 / norm(m_controlPoints[index].m_speedIn)));
408 m_controlPoints[index].m_speedOut =
409 TThickPoint(v * norm(m_controlPoints[index].m_speedOut),
410 m_controlPoints[index].m_speedOut.thick);
411 }
412 }
413
414 //-----------------------------------------------------------------------------
415
moveSingleControlPoint(int index,const TPointD & delta)416 void ControlPointEditorStroke::moveSingleControlPoint(int index,
417 const TPointD &delta) {
418 TStroke *stroke = getStroke();
419 if (!stroke) return;
420
421 int pointIndex = m_controlPoints[index].m_pointIndex;
422 assert(stroke && 0 <= pointIndex &&
423 pointIndex < stroke->getControlPointCount());
424
425 bool selfLoop = isSelfLoop();
426 int cpCount = selfLoop ? m_controlPoints.size() + 1 : m_controlPoints.size();
427
428 TThickPoint p = stroke->getControlPoint(pointIndex);
429 p = TThickPoint(p + delta, p.thick);
430 stroke->setControlPoint(pointIndex, p);
431 if (pointIndex == 0 && selfLoop)
432 stroke->setControlPoint(stroke->getControlPointCount() - 1, p);
433
434 // Directions must be recalculated in the linear cases
435 if ((selfLoop || index > 0) && isSpeedInLinear(index)) {
436 setLinearSpeedIn(index, true, false);
437
438 // Furthermore, if the NEIGHBOUR point is linear, it has to be
439 // recalculated too
440 int prevIndex = (selfLoop && index == 0) ? cpCount - 2 : index - 1;
441 if (m_controlPoints[prevIndex].m_isCusp && isSpeedOutLinear(prevIndex))
442 setLinearSpeedOut(prevIndex, true, false);
443 }
444 if ((selfLoop || index < cpCount - 1) && isSpeedOutLinear(index)) {
445 setLinearSpeedOut(index, true, false);
446
447 int nextIndex = (selfLoop && index == cpCount - 2) ? 0 : index + 1;
448 if (m_controlPoints[nextIndex].m_isCusp && isSpeedInLinear(nextIndex))
449 setLinearSpeedIn(nextIndex, true, false);
450 }
451 }
452
453 //-----------------------------------------------------------------------------
454
setStroke(const TVectorImageP & vi,int strokeIndex)455 void ControlPointEditorStroke::setStroke(const TVectorImageP &vi,
456 int strokeIndex) {
457 m_strokeIndex = strokeIndex;
458 m_vi = vi;
459 if (!vi || strokeIndex == -1) {
460 m_controlPoints.clear();
461 return;
462 }
463 TStroke *stroke = getStroke();
464 const TThickQuadratic *chunk = stroke->getChunk(0);
465 if (stroke->getControlPointCount() == 3 && chunk->getP0() == chunk->getP1() &&
466 chunk->getP0() == chunk->getP2()) {
467 resetControlPoints();
468 return;
469 }
470 adjustChunkParity();
471 resetControlPoints();
472 }
473
474 //-----------------------------------------------------------------------------
475
getControlPoint(int index) const476 TThickPoint ControlPointEditorStroke::getControlPoint(int index) const {
477 TStroke *stroke = getStroke();
478 assert(stroke && 0 <= index && index < (int)m_controlPoints.size());
479 return stroke->getControlPoint(m_controlPoints[index].m_pointIndex);
480 }
481
482 //-----------------------------------------------------------------------------
483
getIndexPointInStroke(int index) const484 int ControlPointEditorStroke::getIndexPointInStroke(int index) const {
485 return m_controlPoints[index].m_pointIndex;
486 }
487
488 //-----------------------------------------------------------------------------
489
getSpeedInPoint(int index) const490 TThickPoint ControlPointEditorStroke::getSpeedInPoint(int index) const {
491 TStroke *stroke = getStroke();
492 assert(stroke && 0 <= index && index < (int)m_controlPoints.size());
493
494 ControlPoint cp = m_controlPoints[index];
495 return stroke->getControlPoint(cp.m_pointIndex) - cp.m_speedIn;
496 }
497
498 //-----------------------------------------------------------------------------
499
getSpeedOutPoint(int index) const500 TThickPoint ControlPointEditorStroke::getSpeedOutPoint(int index) const {
501 TStroke *stroke = getStroke();
502 assert(stroke && 0 <= index && index < (int)m_controlPoints.size());
503
504 ControlPoint cp = m_controlPoints[index];
505 return stroke->getControlPoint(cp.m_pointIndex) + cp.m_speedOut;
506 }
507
508 //-----------------------------------------------------------------------------
509
isCusp(int index) const510 bool ControlPointEditorStroke::isCusp(int index) const {
511 TStroke *stroke = getStroke();
512 assert(stroke && 0 <= index && index < (int)getControlPointCount());
513 return m_controlPoints[index].m_isCusp;
514 }
515
516 //-----------------------------------------------------------------------------
517
setCusp(int index,bool isCusp,bool setSpeedIn)518 void ControlPointEditorStroke::setCusp(int index, bool isCusp,
519 bool setSpeedIn) {
520 m_controlPoints[index].m_isCusp = isCusp;
521 if (isCusp == true) return;
522 moveSpeed(index, TPointD(0.0, 0.0), setSpeedIn, 0.0);
523 }
524
525 //-----------------------------------------------------------------------------
526
isSpeedInLinear(int index) const527 bool ControlPointEditorStroke::isSpeedInLinear(int index) const {
528 assert(index < (int)m_controlPoints.size());
529 return (fabs(m_controlPoints[index].m_speedIn.x) <= 0.02) &&
530 (fabs(m_controlPoints[index].m_speedIn.y) <= 0.02);
531 }
532
533 //-----------------------------------------------------------------------------
534
isSpeedOutLinear(int index) const535 bool ControlPointEditorStroke::isSpeedOutLinear(int index) const {
536 assert(index < (int)m_controlPoints.size());
537 return (fabs(m_controlPoints[index].m_speedOut.x) <= 0.02) &&
538 (fabs(m_controlPoints[index].m_speedOut.y) <= 0.02);
539 }
540
541 //-----------------------------------------------------------------------------
542
setLinearSpeedIn(int index,bool linear,bool updatePoints)543 void ControlPointEditorStroke::setLinearSpeedIn(int index, bool linear,
544 bool updatePoints) {
545 TStroke *stroke = getStroke();
546 if (!stroke || m_controlPoints.size() == 1) return;
547 int pointIndex = m_controlPoints[index].m_pointIndex;
548 if (pointIndex == 0) {
549 if (isSelfLoop())
550 pointIndex = stroke->getControlPointCount() - 1;
551 else
552 return;
553 }
554 int precIndex =
555 (index == 0 && isSelfLoop()) ? m_controlPoints.size() - 1 : index - 1;
556
557 TThickPoint point = stroke->getControlPoint(pointIndex);
558 TThickPoint precPoint = (pointIndex > 2)
559 ? stroke->getControlPoint(pointIndex - 3)
560 : TThickPoint();
561
562 if (linear) {
563 TThickPoint p(point - precPoint);
564 double n = norm(p);
565 TThickPoint speedIn =
566 (n != 0.0) ? (0.01 / n) * p : TThickPoint(0.001, 0.001, 0.0);
567 m_controlPoints[index].m_speedIn = speedIn;
568 } else {
569 TThickPoint newPrec2 = (precPoint + point) * 0.5;
570 TThickPoint speedIn = (point - newPrec2) * 0.5;
571 m_controlPoints[index].m_speedIn = speedIn;
572 }
573 if (updatePoints) updateDependentPoint(index);
574 }
575
576 //-----------------------------------------------------------------------------
577
setLinearSpeedOut(int index,bool linear,bool updatePoints)578 void ControlPointEditorStroke::setLinearSpeedOut(int index, bool linear,
579 bool updatePoints) {
580 TStroke *stroke = getStroke();
581 if (!stroke || m_controlPoints.size() == 1) return;
582 int cpCount = stroke->getControlPointCount();
583 int pointIndex = m_controlPoints[index].m_pointIndex;
584 if (pointIndex == cpCount - 1) {
585 if (isSelfLoop())
586 pointIndex = 0;
587 else
588 return;
589 }
590 int nextIndex =
591 (index == m_controlPoints.size() - 1 && isSelfLoop()) ? 0 : index + 1;
592
593 TThickPoint point = stroke->getControlPoint(pointIndex);
594 TThickPoint nextPoint = (pointIndex < cpCount - 3)
595 ? stroke->getControlPoint(pointIndex + 3)
596 : TThickPoint();
597
598 if (linear) {
599 TThickPoint p(nextPoint - point);
600 double n = norm(p);
601 TThickPoint speedOut =
602 (n != 0.0) ? (0.01 / n) * p : TThickPoint(0.001, 0.001, 0.0);
603 m_controlPoints[index].m_speedOut = speedOut;
604 } else {
605 TThickPoint newNext2 = (nextPoint + point) * 0.5;
606 TThickPoint speedOut = (newNext2 - point) * 0.5;
607 m_controlPoints[index].m_speedOut = speedOut;
608 }
609 if (updatePoints) updateDependentPoint(index);
610 }
611
612 //-----------------------------------------------------------------------------
613
setLinear(int index,bool isLinear,bool updatePoints)614 bool ControlPointEditorStroke::setLinear(int index, bool isLinear,
615 bool updatePoints) {
616 bool movePrec = (!isSelfLoop()) ? index > 0 : true;
617 bool moveNext = (!isSelfLoop()) ? (index < getControlPointCount() - 1) : true;
618 if (isLinear != isSpeedInLinear(index))
619 setLinearSpeedIn(index, isLinear, updatePoints);
620 else
621 movePrec = false;
622 if (isLinear != isSpeedOutLinear(index))
623 setLinearSpeedOut(index, isLinear, updatePoints);
624 else
625 moveNext = false;
626 bool ret = moveNext || movePrec;
627 if (ret) m_controlPoints[index].m_isCusp = true;
628 return ret;
629 }
630
631 //-----------------------------------------------------------------------------
632
setControlPointsLinear(std::set<int> points,bool isLinear)633 bool ControlPointEditorStroke::setControlPointsLinear(std::set<int> points,
634 bool isLinear) {
635 std::set<int>::iterator it;
636 bool isChanged = false;
637 for (it = points.begin(); it != points.end(); it++)
638 isChanged = setLinear(*it, isLinear, false) || isChanged;
639 for (it = points.begin(); it != points.end(); it++) updateDependentPoint(*it);
640 return isChanged;
641 }
642
643 //-----------------------------------------------------------------------------
644
moveControlPoint(int index,const TPointD & delta)645 void ControlPointEditorStroke::moveControlPoint(int index,
646 const TPointD &delta) {
647 TStroke *stroke = getStroke();
648 if (!stroke) return;
649 assert(stroke && 0 <= index && index < (int)getControlPointCount());
650
651 moveSingleControlPoint(index, delta);
652 updateDependentPoint(index);
653 }
654
655 //-----------------------------------------------------------------------------
656
addControlPoint(const TPointD & pos)657 int ControlPointEditorStroke::addControlPoint(const TPointD &pos) {
658 TStroke *stroke = getStroke();
659 if (!stroke) return -1;
660 double d = 0.01;
661 int indexAtPos;
662 int cpCount = stroke->getControlPointCount();
663 if (cpCount <= 3) // e' un unico chunk e in questo caso rappresenta un punto
664 {
665 getPointTypeAt(pos, d, indexAtPos);
666 return indexAtPos;
667 }
668
669 double w = stroke->getW(pos);
670 int pointIndex = stroke->getControlPointIndexAfterParameter(w);
671
672 int i, index;
673 for (i = 0; i < getControlPointCount(); i++) {
674 // Cerco il ControlPoint corrispondente all'indice pointIndex. OSS.:
675 // Effettuo il
676 // controllo da zero a getControlPointCount()-1 per gestire il caso del
677 // selfLoop
678 if (pointIndex == m_controlPoints[i].m_pointIndex + 1 ||
679 pointIndex == m_controlPoints[i].m_pointIndex + 2 ||
680 pointIndex == m_controlPoints[i].m_pointIndex + 3 ||
681 pointIndex == m_controlPoints[i].m_pointIndex + 4)
682 index = i;
683 }
684
685 ControlPoint precCp = m_controlPoints[index];
686 assert(precCp.m_pointIndex >= 0);
687 std::vector<TThickPoint> points;
688
689 for (i = 0; i < cpCount; i++) {
690 if (i != precCp.m_pointIndex + 1 && i != precCp.m_pointIndex + 2 &&
691 i != precCp.m_pointIndex + 3)
692 points.push_back(stroke->getControlPoint(i));
693 if (i == precCp.m_pointIndex + 2) {
694 bool isBeforePointLinear = isSpeedOutLinear(index);
695 int nextIndex =
696 (isSelfLoop() && index == m_controlPoints.size() - 1) ? 0 : index + 1;
697 bool isNextPointLinear =
698 nextIndex < (int)m_controlPoints.size() && isSpeedInLinear(nextIndex);
699
700 TThickPoint a0 = stroke->getControlPoint(precCp.m_pointIndex);
701 TThickPoint a1 = stroke->getControlPoint(precCp.m_pointIndex + 1);
702 TThickPoint a2 = stroke->getControlPoint(precCp.m_pointIndex + 2);
703 TThickPoint a3 = stroke->getControlPoint(precCp.m_pointIndex + 3);
704 TThickPoint a4 = stroke->getControlPoint(precCp.m_pointIndex + 4);
705 double dist2 = tdistance2(pos, TPointD(a2));
706 TThickPoint d0, d1, d2, d3, d4, d5, d6;
707
708 if (isBeforePointLinear && isNextPointLinear) {
709 // Se sono entrambi i punti lineari inserisco un punto lineare
710 d0 = a1;
711 d3 = stroke->getThickPoint(w);
712 d6 = a3;
713 d2 = computeLinearPoint(d0, d3, 0.01, true); // SpeedIn
714 d4 = computeLinearPoint(d3, d6, 0.01, false); // SpeedOut
715 d1 = 0.5 * (d0 + d2);
716 d5 = 0.5 * (d4 + d6);
717 } else if (dist2 < 32) {
718 // Sono molto vicino al punto che non viene visualizzato
719 TThickPoint b0 = 0.5 * (a0 + a1);
720 TThickPoint b1 = 0.5 * (a2 + a1);
721 TThickPoint c0 = 0.5 * (b0 + b1);
722
723 TThickPoint b2 = 0.5 * (a2 + a3);
724 TThickPoint b3 = 0.5 * (a3 + a4);
725
726 TThickPoint c1 = 0.5 * (b2 + b3);
727 d0 = b0;
728 d1 = c0;
729 d2 = b1;
730 d3 = a2;
731 d4 = b2;
732 d5 = c1;
733 d6 = b3;
734 } else {
735 bool isInFirstChunk = true;
736 if (pointIndex > precCp.m_pointIndex + 2) {
737 // nel caso in cui sono nel secondo chunk scambio i punti
738 a0 = a4;
739 std::swap(a1, a3);
740 isInFirstChunk = false;
741 }
742
743 double w0 = (isSelfLoop() && precCp.m_pointIndex + 4 == cpCount - 1 &&
744 !isInFirstChunk)
745 ? 1
746 : stroke->getW(a0);
747 double w1 = stroke->getW(a2);
748 double t = (w - w0) / (w1 - w0);
749
750 TThickPoint p = stroke->getThickPoint(w);
751 TThickPoint b0 = TThickPoint((1 - t) * a0 + t * a1,
752 (1 - t) * a0.thick + t * a1.thick);
753 TThickPoint b1 = TThickPoint((1 - t) * a1 + t * a2,
754 (1 - t) * a1.thick + t * a2.thick);
755 TThickPoint c0 =
756 TThickPoint(0.5 * a0 + 0.5 * b0, (1 - t) * a0.thick + t * b0.thick);
757 TThickPoint c1 =
758 TThickPoint(0.5 * b0 + 0.5 * p, (1 - t) * b0.thick + t * p.thick);
759 TThickPoint c2 =
760 TThickPoint(0.5 * c0 + 0.5 * c1, (1 - t) * c0.thick + t * c1.thick);
761
762 d0 = (isInFirstChunk) ? c0 : a3;
763 d1 = (isInFirstChunk) ? c2 : a2;
764 d2 = (isInFirstChunk) ? c1 : b1;
765 d3 = p;
766 d4 = (isInFirstChunk) ? b1 : c1;
767 d5 = (isInFirstChunk) ? a2 : c2;
768 d6 = (isInFirstChunk) ? a3 : c0;
769 }
770 if (isBeforePointLinear && !isNextPointLinear)
771 d1 = computeLinearPoint(d0, d2, 0.01, false);
772 else if (isNextPointLinear && !isBeforePointLinear)
773 d5 = computeLinearPoint(d4, d6, 0.01, true);
774 points.push_back(d0);
775 points.push_back(d1);
776 points.push_back(d2);
777 points.push_back(d3);
778 points.push_back(d4);
779 points.push_back(d5);
780 points.push_back(d6);
781 }
782 }
783
784 stroke->reshape(&points[0], points.size());
785 resetControlPoints();
786
787 getPointTypeAt(pos, d, indexAtPos);
788 return indexAtPos;
789 }
790
791 //-----------------------------------------------------------------------------
792
deleteControlPoint(int index)793 void ControlPointEditorStroke::deleteControlPoint(int index) {
794 TStroke *stroke = getStroke();
795 if (!stroke) return;
796
797 assert(stroke && 0 <= index && index < (int)getControlPointCount());
798 // E' un unico chunk e in questo caso rappresenta un punto
799 if (stroke->getControlPointCount() <= 3 ||
800 (isSelfLoop() && stroke->getControlPointCount() <= 5)) {
801 m_controlPoints.clear();
802 m_vi->deleteStroke(m_strokeIndex);
803 return;
804 }
805
806 QList<int> newPointsIndex;
807 int i;
808 for (i = 0; i < (int)getControlPointCount() - 1; i++)
809 newPointsIndex.push_back(m_controlPoints[i].m_pointIndex);
810
811 m_controlPoints.removeAt(index);
812 updatePoints();
813
814 // Aggiorno gli indici dei punti nella stroke
815 assert((int)newPointsIndex.size() == (int)getControlPointCount());
816 for (i = 0; i < (int)getControlPointCount(); i++)
817 m_controlPoints[i].m_pointIndex = newPointsIndex.at(i);
818
819 int prev = prevIndex(index);
820 if (prev >= 0 && isSpeedOutLinear(prev)) {
821 setLinearSpeedOut(prev);
822 updateDependentPoint(prev);
823 }
824 if (index < (int)m_controlPoints.size() && isSpeedInLinear(index)) {
825 setLinearSpeedIn(index);
826 updateDependentPoint(index);
827 }
828 }
829
830 //-----------------------------------------------------------------------------
831
moveSpeed(int index,const TPointD & delta,bool isIn,double minDistance)832 void ControlPointEditorStroke::moveSpeed(int index, const TPointD &delta,
833 bool isIn, double minDistance) {
834 if (!isIn)
835 moveSpeedOut(index, delta, minDistance);
836 else
837 moveSpeedIn(index, delta, minDistance);
838
839 updateDependentPoint(index);
840 }
841
842 //-----------------------------------------------------------------------------
843
moveSegment(int beforeIndex,int nextIndex,const TPointD & delta,const TPointD & pos)844 void ControlPointEditorStroke::moveSegment(int beforeIndex, int nextIndex,
845 const TPointD &delta,
846 const TPointD &pos) {
847 TStroke *stroke = getStroke();
848 if (!stroke) return;
849
850 int cpCount = getControlPointCount();
851 // Verifiche per il caso in cui lo stroke e' selfLoop
852 if (isSelfLoop() && beforeIndex == 0 && nextIndex == cpCount - 1)
853 std::swap(beforeIndex, nextIndex);
854
855 int beforePointIndex = m_controlPoints[beforeIndex].m_pointIndex;
856 int nextPointIndex = (isSelfLoop() && nextIndex == 0)
857 ? stroke->getControlPointCount() - 1
858 : m_controlPoints[nextIndex].m_pointIndex;
859 double w = stroke->getW(pos);
860 double w0 = stroke->getParameterAtControlPoint(beforePointIndex);
861 double w4 = stroke->getParameterAtControlPoint(nextPointIndex);
862 if (w0 > w) return;
863 assert(w0 <= w && w <= w4);
864
865 double t = 1;
866 double s = 1;
867
868 if (isSpeedOutLinear(beforeIndex)) {
869 m_controlPoints[beforeIndex].m_speedOut =
870 (stroke->getControlPoint(nextPointIndex) -
871 stroke->getControlPoint(beforePointIndex)) *
872 0.3;
873 if (!isSpeedInLinear(beforeIndex))
874 m_controlPoints[beforeIndex].m_isCusp = true;
875 } else if (!isSpeedOutLinear(beforeIndex) && !isSpeedInLinear(beforeIndex) &&
876 !isCusp(beforeIndex)) {
877 t = 1 - fabs(w - w0) / fabs(w4 - w0);
878 moveSingleControlPoint(beforeIndex, t * delta);
879 t = 1 - t;
880 }
881
882 if (isSpeedInLinear(nextIndex)) {
883 m_controlPoints[nextIndex].m_speedIn =
884 (stroke->getControlPoint(nextPointIndex) -
885 stroke->getControlPoint(beforePointIndex)) *
886 0.3;
887 if (!isSpeedOutLinear(nextIndex))
888 m_controlPoints[nextIndex].m_isCusp = true;
889 } else if (!isSpeedInLinear(nextIndex) && !isSpeedOutLinear(nextIndex) &&
890 !isCusp(nextIndex)) {
891 s = 1 - fabs(w4 - w) / fabs(w4 - w0);
892 moveSingleControlPoint(nextIndex, s * delta);
893 s = 1 - s;
894 }
895
896 moveSpeedOut(beforeIndex, delta * s, 0);
897 // updateDependentPoint(beforeIndex);
898 moveSpeedIn(nextIndex, delta * t, 0);
899 // updateDependentPoint(nextIndex);
900
901 updatePoints();
902 }
903
904 //-----------------------------------------------------------------------------
905
getPointTypeAt(const TPointD & pos,double & distance2,int & index) const906 ControlPointEditorStroke::PointType ControlPointEditorStroke::getPointTypeAt(
907 const TPointD &pos, double &distance2, int &index) const {
908 TStroke *stroke = getStroke();
909 if (!stroke) return NONE;
910 double w = stroke->getW(pos);
911 TPointD p = stroke->getPoint(w);
912 double strokeDistance = tdistance2(p, pos);
913
914 int precPointIndex = -1;
915 double minPrecDistance = 0;
916 double minDistance2 = distance2;
917 index = -1;
918 PointType type = NONE;
919 int cpCount = m_controlPoints.size();
920 int i;
921 for (i = 0; i < cpCount; i++) {
922 ControlPoint cPoint = m_controlPoints[i];
923 TPointD point = stroke->getControlPoint(cPoint.m_pointIndex);
924 double cpDistance2 = tdistance2(pos, point);
925 double distanceIn2 = !isSpeedInLinear(i)
926 ? tdistance2(pos, point - cPoint.m_speedIn)
927 : cpDistance2 + 1;
928 double distanceOut2 = !isSpeedOutLinear(i)
929 ? tdistance2(pos, point + cPoint.m_speedOut)
930 : cpDistance2 + 1;
931 if (i == 0 && !isSelfLoop())
932 distanceIn2 = std::max(cpDistance2, distanceOut2) + 1;
933 if (i == cpCount - 1 && !isSelfLoop())
934 distanceOut2 = std::max(cpDistance2, distanceIn2) + 1;
935
936 if (cpDistance2 < distanceIn2 && cpDistance2 < distanceOut2 &&
937 (cpDistance2 < minDistance2 || index < 0)) {
938 minDistance2 = cpDistance2;
939 index = i;
940 type = CONTROL_POINT;
941 } else if (distanceIn2 < cpDistance2 && distanceIn2 < distanceOut2 &&
942 (distanceIn2 < minDistance2 || index < 0)) {
943 minDistance2 = distanceIn2;
944 index = i;
945 type = SPEED_IN;
946 } else if (distanceOut2 < cpDistance2 && distanceOut2 < distanceIn2 &&
947 (distanceOut2 < minDistance2 || index < 0)) {
948 minDistance2 = distanceOut2;
949 index = i;
950 type = SPEED_OUT;
951 }
952
953 double cpw =
954 stroke->getParameterAtControlPoint(m_controlPoints[i].m_pointIndex);
955 if (w <= cpw) continue;
956 double precDistance = w - cpw;
957 if (precPointIndex < 0 || precDistance < minPrecDistance) {
958 precPointIndex = i;
959 minPrecDistance = precDistance;
960 }
961 }
962
963 if (minDistance2 < distance2)
964 distance2 = minDistance2;
965 else if (strokeDistance > distance2) {
966 distance2 = strokeDistance;
967 index = -1;
968 type = NONE;
969 } else {
970 distance2 = minPrecDistance;
971 index = precPointIndex;
972 type = SEGMENT;
973 }
974
975 return type;
976 }
977
978 //=============================================================================
979 // ControlPointSelection
980 //-----------------------------------------------------------------------------
981
isSelected(int index) const982 bool ControlPointSelection::isSelected(int index) const {
983 return m_selectedPoints.find(index) != m_selectedPoints.end();
984 }
985
986 //-----------------------------------------------------------------------------
987
select(int index)988 void ControlPointSelection::select(int index) {
989 m_selectedPoints.insert(index);
990 }
991
992 //-----------------------------------------------------------------------------
993
unselect(int index)994 void ControlPointSelection::unselect(int index) {
995 m_selectedPoints.erase(index);
996 }
997
998 //-----------------------------------------------------------------------------
999
addMenuItems(QMenu * menu)1000 void ControlPointSelection::addMenuItems(QMenu *menu) {
1001 int currentStrokeIndex = m_controlPointEditorStroke->getStrokeIndex();
1002 if (isEmpty() || currentStrokeIndex == -1 ||
1003 (m_controlPointEditorStroke &&
1004 m_controlPointEditorStroke->getControlPointCount() <= 1))
1005 return;
1006 QAction *linear = menu->addAction(tr("Set Linear Control Point"));
1007 QAction *unlinear = menu->addAction(tr("Set Nonlinear Control Point"));
1008 menu->addSeparator();
1009 bool ret = connect(linear, SIGNAL(triggered()), this, SLOT(setLinear()));
1010 ret =
1011 ret && connect(unlinear, SIGNAL(triggered()), this, SLOT(setUnlinear()));
1012 assert(ret);
1013 }
1014
1015 //-----------------------------------------------------------------------------
1016
setLinear()1017 void ControlPointSelection::setLinear() {
1018 TTool *tool = TTool::getApplication()->getCurrentTool()->getTool();
1019 int currentStrokeIndex = m_controlPointEditorStroke->getStrokeIndex();
1020 TVectorImageP vi(tool->getImage(false));
1021 if (!vi || isEmpty() || currentStrokeIndex == -1) return;
1022 TUndo *undo;
1023 if (tool->getApplication()->getCurrentObject()->isSpline())
1024 undo = new UndoPath(
1025 tool->getXsheet()->getStageObject(tool->getObjectId())->getSpline());
1026 else {
1027 TXshSimpleLevel *level =
1028 tool->getApplication()->getCurrentLevel()->getSimpleLevel();
1029 UndoControlPointEditor *cpEditorUndo =
1030 new UndoControlPointEditor(level, tool->getCurrentFid());
1031 cpEditorUndo->addOldStroke(currentStrokeIndex,
1032 vi->getVIStroke(currentStrokeIndex));
1033 undo = cpEditorUndo;
1034 }
1035 if (m_controlPointEditorStroke->getControlPointCount() == 0) return;
1036
1037 bool isChanged = m_controlPointEditorStroke->setControlPointsLinear(
1038 m_selectedPoints, true);
1039
1040 if (!isChanged) return;
1041 TUndoManager::manager()->add(undo);
1042 tool->notifyImageChanged();
1043 }
1044
1045 //-----------------------------------------------------------------------------
1046
setUnlinear()1047 void ControlPointSelection::setUnlinear() {
1048 TTool *tool = TTool::getApplication()->getCurrentTool()->getTool();
1049 int currentStrokeIndex = m_controlPointEditorStroke->getStrokeIndex();
1050 TVectorImageP vi(tool->getImage(false));
1051 if (!vi || isEmpty() || currentStrokeIndex == -1) return;
1052
1053 TUndo *undo;
1054 if (tool->getApplication()->getCurrentObject()->isSpline())
1055 undo = new UndoPath(
1056 tool->getXsheet()->getStageObject(tool->getObjectId())->getSpline());
1057 else {
1058 TXshSimpleLevel *level =
1059 tool->getApplication()->getCurrentLevel()->getSimpleLevel();
1060 UndoControlPointEditor *cpEditorUndo =
1061 new UndoControlPointEditor(level, tool->getCurrentFid());
1062 cpEditorUndo->addOldStroke(currentStrokeIndex,
1063 vi->getVIStroke(currentStrokeIndex));
1064 undo = cpEditorUndo;
1065 }
1066 if (m_controlPointEditorStroke->getControlPointCount() == 0) return;
1067
1068 bool isChanged = m_controlPointEditorStroke->setControlPointsLinear(
1069 m_selectedPoints, false);
1070
1071 if (!isChanged) return;
1072 TUndoManager::manager()->add(undo);
1073 tool->notifyImageChanged();
1074 }
1075
1076 //-----------------------------------------------------------------------------
1077
deleteControlPoints()1078 void ControlPointSelection::deleteControlPoints() {
1079 TTool *tool = TTool::getApplication()->getCurrentTool()->getTool();
1080 TVectorImageP vi(tool->getImage(false));
1081 int currentStrokeIndex = m_controlPointEditorStroke->getStrokeIndex();
1082 if (!vi || isEmpty() || currentStrokeIndex == -1) return;
1083
1084 // Inizializzo l'UNDO
1085 TUndo *undo;
1086 bool isCurrentObjectSpline =
1087 tool->getApplication()->getCurrentObject()->isSpline();
1088 if (isCurrentObjectSpline)
1089 undo = new UndoPath(
1090 tool->getXsheet()->getStageObject(tool->getObjectId())->getSpline());
1091 else {
1092 TXshSimpleLevel *level =
1093 tool->getApplication()->getCurrentLevel()->getSimpleLevel();
1094 UndoControlPointEditor *cpEditorUndo =
1095 new UndoControlPointEditor(level, tool->getCurrentFid());
1096 cpEditorUndo->addOldStroke(currentStrokeIndex,
1097 vi->getVIStroke(currentStrokeIndex));
1098 undo = cpEditorUndo;
1099 }
1100
1101 int i;
1102 for (i = m_controlPointEditorStroke->getControlPointCount() - 1; i >= 0; i--)
1103 if (isSelected(i)) m_controlPointEditorStroke->deleteControlPoint(i);
1104
1105 if (m_controlPointEditorStroke->getControlPointCount() == 0) {
1106 m_controlPointEditorStroke->setStroke((TVectorImage *)0, -1);
1107 if (!isCurrentObjectSpline) {
1108 UndoControlPointEditor *cpEditorUndo =
1109 dynamic_cast<UndoControlPointEditor *>(undo);
1110 if (cpEditorUndo) cpEditorUndo->isStrokeDelete(true);
1111 }
1112 }
1113
1114 // La spline non puo' essere cancellata completamente!!!
1115 if (vi->getStrokeCount() == 0) {
1116 if (TTool::getApplication()->getCurrentObject()->isSpline()) {
1117 std::vector<TPointD> points;
1118 double d = 10;
1119 points.push_back(TPointD(-d, 0));
1120 points.push_back(TPointD(0, 0));
1121 points.push_back(TPointD(d, 0));
1122 TStroke *stroke = new TStroke(points);
1123 vi->addStroke(stroke, false);
1124 m_controlPointEditorStroke->setStrokeIndex(0);
1125 }
1126 }
1127 tool->notifyImageChanged();
1128 selectNone();
1129 // Registro l'UNDO
1130 TUndoManager::manager()->add(undo);
1131 }
1132
1133 //-----------------------------------------------------------------------------
1134
enableCommands()1135 void ControlPointSelection::enableCommands() {
1136 enableCommand(this, "MI_Clear", &ControlPointSelection::deleteControlPoints);
1137 }
1138