1 /*
2  *  Copyright (c) 2010 Cyrille Berger <cberger@cberger.net>
3  *  Copyright (c) 2013 Dmitry Kazakov <dimula73@gmail.com>
4  *
5  *  This program is free software; you can redistribute it and/or modify
6  *  it under the terms of the GNU General Public License as published by
7  *  the Free Software Foundation; either version 2 of the License, or
8  *  (at your option) any later version.
9  *
10  *  This program is distributed in the hope that it will be useful,
11  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
12  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  *  GNU General Public License for more details.
14  *
15  *  You should have received a copy of the GNU General Public License
16  *  along with this program; if not, write to the Free Software
17  *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18  */
19 
20 #include <kis_distance_information.h>
21 #include <brushengine/kis_paint_information.h>
22 #include "kis_spacing_information.h"
23 #include "kis_timing_information.h"
24 #include "kis_debug.h"
25 #include <QtCore/qmath.h>
26 #include <QVector2D>
27 #include <QTransform>
28 #include "kis_algebra_2d.h"
29 #include "kis_dom_utils.h"
30 
31 #include "kis_lod_transform.h"
32 
33 const qreal MIN_DISTANCE_SPACING = 0.5;
34 
35 // Smallest allowed interval when timed spacing is enabled, in milliseconds.
36 const qreal MIN_TIMED_INTERVAL = 0.5;
37 
38 // Largest allowed interval when timed spacing is enabled, in milliseconds.
39 const qreal MAX_TIMED_INTERVAL = LONG_TIME;
40 
41 struct Q_DECL_HIDDEN KisDistanceInformation::Private {
PrivateKisDistanceInformation::Private42     Private() :
43         accumDistance(),
44         accumTime(0.0),
45         spacingUpdateInterval(LONG_TIME),
46         timeSinceSpacingUpdate(0.0),
47         timingUpdateInterval(LONG_TIME),
48         timeSinceTimingUpdate(0.0),
49         lastDabInfoValid(false),
50         lastPaintInfoValid(false),
51         totalDistance(0.0),
52         currentDabSeqNo(0),
53         levelOfDetail(0)
54     {
55     }
56 
57     // Accumulators of time/distance passed since the last painted dab
58     QPointF accumDistance;
59     qreal accumTime;
60 
61     KisSpacingInformation spacing;
62     qreal spacingUpdateInterval;
63     // Accumulator of time passed since the last spacing update
64     qreal timeSinceSpacingUpdate;
65 
66     KisTimingInformation timing;
67     qreal timingUpdateInterval;
68     // Accumulator of time passed since the last timing update
69     qreal timeSinceTimingUpdate;
70 
71     // Information about the last position considered (not necessarily a painted dab)
72     QPointF lastPosition;
73     qreal lastAngle;
74     bool lastDabInfoValid;
75 
76     // Information about the last painted dab
77     KisPaintInformation lastPaintInformation;
78     bool lastPaintInfoValid;
79 
80     qreal totalDistance;
81     boost::optional<qreal> lockedDrawingAngleOptional;
82 
83     int currentDabSeqNo;
84     int levelOfDetail;
85 
86     qreal lastMaxPressure = 0.0;
87 };
88 
89 struct Q_DECL_HIDDEN KisDistanceInitInfo::Private {
PrivateKisDistanceInitInfo::Private90     Private() :
91         hasLastInfo(false),
92         lastPosition(),
93         lastAngle(0.0),
94         spacingUpdateInterval(LONG_TIME),
95         timingUpdateInterval(LONG_TIME),
96         currentDabSeqNo(0)
97     {
98     }
99 
100 
101     // Indicates whether lastPosition,  and lastAngle are valid or not.
102     bool hasLastInfo;
103 
104     QPointF lastPosition;
105     qreal lastAngle;
106 
107     qreal spacingUpdateInterval;
108     qreal timingUpdateInterval;
109 
110     int currentDabSeqNo;
111 };
112 
KisDistanceInitInfo()113 KisDistanceInitInfo::KisDistanceInitInfo()
114     : m_d(new Private)
115 {
116 }
117 
KisDistanceInitInfo(qreal spacingUpdateInterval,qreal timingUpdateInterval,int currentDabSeqNo)118 KisDistanceInitInfo::KisDistanceInitInfo(qreal spacingUpdateInterval, qreal timingUpdateInterval, int currentDabSeqNo)
119     : m_d(new Private)
120 {
121     m_d->spacingUpdateInterval = spacingUpdateInterval;
122     m_d->timingUpdateInterval = timingUpdateInterval;
123     m_d->currentDabSeqNo = currentDabSeqNo;
124 }
125 
KisDistanceInitInfo(const QPointF & lastPosition,qreal lastAngle,int currentDabSeqNo)126 KisDistanceInitInfo::KisDistanceInitInfo(const QPointF &lastPosition,
127                                          qreal lastAngle, int currentDabSeqNo)
128     : m_d(new Private)
129 {
130     m_d->hasLastInfo = true;
131     m_d->lastPosition = lastPosition;
132     m_d->lastAngle = lastAngle;
133     m_d->currentDabSeqNo = currentDabSeqNo;
134 }
135 
KisDistanceInitInfo(const QPointF & lastPosition,qreal lastAngle,qreal spacingUpdateInterval,qreal timingUpdateInterval,int currentDabSeqNo)136 KisDistanceInitInfo::KisDistanceInitInfo(const QPointF &lastPosition,
137                                          qreal lastAngle, qreal spacingUpdateInterval,
138                                          qreal timingUpdateInterval,
139                                          int currentDabSeqNo)
140     : m_d(new Private)
141 {
142     m_d->hasLastInfo = true;
143     m_d->lastPosition = lastPosition;
144     m_d->lastAngle = lastAngle;
145     m_d->spacingUpdateInterval = spacingUpdateInterval;
146     m_d->timingUpdateInterval = timingUpdateInterval;
147     m_d->currentDabSeqNo = currentDabSeqNo;
148 }
149 
KisDistanceInitInfo(const KisDistanceInitInfo & rhs)150 KisDistanceInitInfo::KisDistanceInitInfo(const KisDistanceInitInfo &rhs)
151     : m_d(new Private(*rhs.m_d))
152 {
153 }
154 
~KisDistanceInitInfo()155 KisDistanceInitInfo::~KisDistanceInitInfo()
156 {
157     delete m_d;
158 }
159 
operator ==(const KisDistanceInitInfo & other) const160 bool KisDistanceInitInfo::operator==(const KisDistanceInitInfo &other) const
161 {
162     if (m_d->spacingUpdateInterval != other.m_d->spacingUpdateInterval
163         || m_d->timingUpdateInterval != other.m_d->timingUpdateInterval
164         || m_d->hasLastInfo != other.m_d->hasLastInfo)
165     {
166         return false;
167     }
168     if (m_d->hasLastInfo) {
169         if (m_d->lastPosition != other.m_d->lastPosition
170             || m_d->lastAngle != other.m_d->lastAngle)
171         {
172             return false;
173         }
174     }
175 
176     if (m_d->currentDabSeqNo != other.m_d->currentDabSeqNo) {
177         return false;
178     }
179 
180     return true;
181 }
182 
operator !=(const KisDistanceInitInfo & other) const183 bool KisDistanceInitInfo::operator!=(const KisDistanceInitInfo &other) const
184 {
185     return !(*this == other);
186 }
187 
operator =(const KisDistanceInitInfo & rhs)188 KisDistanceInitInfo &KisDistanceInitInfo::operator=(const KisDistanceInitInfo &rhs)
189 {
190     *m_d = *rhs.m_d;
191     return *this;
192 }
193 
makeDistInfo()194 KisDistanceInformation KisDistanceInitInfo::makeDistInfo()
195 {
196     if (m_d->hasLastInfo) {
197         return KisDistanceInformation(m_d->lastPosition, m_d->lastAngle,
198                                       m_d->spacingUpdateInterval, m_d->timingUpdateInterval,
199                                       m_d->currentDabSeqNo);
200     }
201     else {
202         return KisDistanceInformation(m_d->spacingUpdateInterval, m_d->timingUpdateInterval, m_d->currentDabSeqNo);
203     }
204 }
205 
toXML(QDomDocument & doc,QDomElement & elt) const206 void KisDistanceInitInfo::toXML(QDomDocument &doc, QDomElement &elt) const
207 {
208     elt.setAttribute("spacingUpdateInterval", QString::number(m_d->spacingUpdateInterval, 'g', 15));
209     elt.setAttribute("timingUpdateInterval", QString::number(m_d->timingUpdateInterval, 'g', 15));
210     elt.setAttribute("currentDabSeqNo", QString::number(m_d->currentDabSeqNo));
211     if (m_d->hasLastInfo) {
212         QDomElement lastInfoElt = doc.createElement("LastInfo");
213         lastInfoElt.setAttribute("lastPosX", QString::number(m_d->lastPosition.x(), 'g', 15));
214         lastInfoElt.setAttribute("lastPosY", QString::number(m_d->lastPosition.y(), 'g', 15));
215         lastInfoElt.setAttribute("lastAngle", QString::number(m_d->lastAngle, 'g', 15));
216         elt.appendChild(lastInfoElt);
217     }
218 }
219 
fromXML(const QDomElement & elt)220 KisDistanceInitInfo KisDistanceInitInfo::fromXML(const QDomElement &elt)
221 {
222     const qreal spacingUpdateInterval = qreal(KisDomUtils::toDouble(elt.attribute("spacingUpdateInterval",
223                                                                                   QString::number(LONG_TIME, 'g', 15))));
224     const qreal timingUpdateInterval = qreal(KisDomUtils::toDouble(elt.attribute("timingUpdateInterval",
225                                                                                   QString::number(LONG_TIME, 'g', 15))));
226     const qreal currentDabSeqNo = KisDomUtils::toInt(elt.attribute("currentDabSeqNo", "0"));
227 
228     const QDomElement lastInfoElt = elt.firstChildElement("LastInfo");
229     const bool hasLastInfo = !lastInfoElt.isNull();
230 
231     if (hasLastInfo) {
232         const qreal lastPosX = qreal(KisDomUtils::toDouble(lastInfoElt.attribute("lastPosX",
233                                                                                  "0.0")));
234         const qreal lastPosY = qreal(KisDomUtils::toDouble(lastInfoElt.attribute("lastPosY",
235                                                                                  "0.0")));
236         const qreal lastAngle = qreal(KisDomUtils::toDouble(lastInfoElt.attribute("lastAngle",
237                                                                                   "0.0")));
238 
239         return KisDistanceInitInfo(QPointF(lastPosX, lastPosY), lastAngle,
240                                    spacingUpdateInterval, timingUpdateInterval,
241                                    currentDabSeqNo);
242     }
243     else {
244         return KisDistanceInitInfo(spacingUpdateInterval, timingUpdateInterval,
245                                    currentDabSeqNo);
246     }
247 }
248 
KisDistanceInformation()249 KisDistanceInformation::KisDistanceInformation()
250     : m_d(new Private)
251 {
252 }
253 
KisDistanceInformation(qreal spacingUpdateInterval,qreal timingUpdateInterval,int currentDabSeqNo)254 KisDistanceInformation::KisDistanceInformation(qreal spacingUpdateInterval,
255                                                qreal timingUpdateInterval,
256                                                int currentDabSeqNo)
257     : m_d(new Private)
258 {
259     m_d->spacingUpdateInterval = spacingUpdateInterval;
260     m_d->timingUpdateInterval = timingUpdateInterval;
261     m_d->currentDabSeqNo = currentDabSeqNo;
262 }
263 
KisDistanceInformation(const QPointF & lastPosition,qreal lastAngle)264 KisDistanceInformation::KisDistanceInformation(const QPointF &lastPosition,
265                                                qreal lastAngle)
266     : m_d(new Private)
267 {
268     m_d->lastPosition = lastPosition;
269     m_d->lastAngle = lastAngle;
270 
271     m_d->lastDabInfoValid = true;
272 }
273 
KisDistanceInformation(const QPointF & lastPosition,qreal lastAngle,qreal spacingUpdateInterval,qreal timingUpdateInterval,int currentDabSeqNo)274 KisDistanceInformation::KisDistanceInformation(const QPointF &lastPosition,
275                                                qreal lastAngle,
276                                                qreal spacingUpdateInterval,
277                                                qreal timingUpdateInterval,
278                                                int currentDabSeqNo)
279     : KisDistanceInformation(lastPosition, lastAngle)
280 {
281     m_d->spacingUpdateInterval = spacingUpdateInterval;
282     m_d->timingUpdateInterval = timingUpdateInterval;
283     m_d->currentDabSeqNo = currentDabSeqNo;
284 }
285 
KisDistanceInformation(const KisDistanceInformation & rhs)286 KisDistanceInformation::KisDistanceInformation(const KisDistanceInformation &rhs)
287     : m_d(new Private(*rhs.m_d))
288 {
289 
290 }
291 
KisDistanceInformation(const KisDistanceInformation & rhs,int levelOfDetail)292 KisDistanceInformation::KisDistanceInformation(const KisDistanceInformation &rhs, int levelOfDetail)
293     : m_d(new Private(*rhs.m_d))
294 {
295     KIS_ASSERT_RECOVER_NOOP(!m_d->lastPaintInfoValid &&
296                             "The distance information "
297                             "should be cloned before the "
298                             "actual painting is started");
299 
300     m_d->levelOfDetail = levelOfDetail;
301 
302     KisLodTransform t(levelOfDetail);
303     m_d->lastPosition = t.map(m_d->lastPosition);
304 }
305 
operator =(const KisDistanceInformation & rhs)306 KisDistanceInformation& KisDistanceInformation::operator=(const KisDistanceInformation &rhs)
307 {
308     *m_d = *rhs.m_d;
309     return *this;
310 }
311 
overrideLastValues(const QPointF & lastPosition,qreal lastAngle)312 void KisDistanceInformation::overrideLastValues(const QPointF &lastPosition,
313                                                 qreal lastAngle)
314 {
315     m_d->lastPosition = lastPosition;
316     m_d->lastAngle = lastAngle;
317 
318     m_d->lastDabInfoValid = true;
319 }
320 
~KisDistanceInformation()321 KisDistanceInformation::~KisDistanceInformation()
322 {
323     delete m_d;
324 }
325 
currentSpacing() const326 const KisSpacingInformation& KisDistanceInformation::currentSpacing() const
327 {
328     return m_d->spacing;
329 }
330 
updateSpacing(const KisSpacingInformation & spacing)331 void KisDistanceInformation::updateSpacing(const KisSpacingInformation &spacing)
332 {
333     m_d->spacing = spacing;
334     m_d->timeSinceSpacingUpdate = 0.0;
335 }
336 
needsSpacingUpdate() const337 bool KisDistanceInformation::needsSpacingUpdate() const
338 {
339     return m_d->timeSinceSpacingUpdate >= m_d->spacingUpdateInterval;
340 }
341 
currentTiming() const342 const KisTimingInformation &KisDistanceInformation::currentTiming() const
343 {
344     return m_d->timing;
345 }
346 
updateTiming(const KisTimingInformation & timing)347 void KisDistanceInformation::updateTiming(const KisTimingInformation &timing)
348 {
349     m_d->timing = timing;
350     m_d->timeSinceTimingUpdate = 0.0;
351 }
352 
needsTimingUpdate() const353 bool KisDistanceInformation::needsTimingUpdate() const
354 {
355     return m_d->timeSinceTimingUpdate >= m_d->timingUpdateInterval;
356 }
357 
hasLastDabInformation() const358 bool KisDistanceInformation::hasLastDabInformation() const
359 {
360     return m_d->lastDabInfoValid;
361 }
362 
lastPosition() const363 QPointF KisDistanceInformation::lastPosition() const
364 {
365     return m_d->lastPosition;
366 }
367 
lastDrawingAngle() const368 qreal KisDistanceInformation::lastDrawingAngle() const
369 {
370     return m_d->lastAngle;
371 }
372 
hasLastPaintInformation() const373 bool KisDistanceInformation::hasLastPaintInformation() const
374 {
375     return m_d->lastPaintInfoValid;
376 }
377 
lastPaintInformation() const378 const KisPaintInformation& KisDistanceInformation::lastPaintInformation() const
379 {
380     return m_d->lastPaintInformation;
381 }
382 
currentDabSeqNo() const383 int KisDistanceInformation::currentDabSeqNo() const
384 {
385     return m_d->currentDabSeqNo;
386 }
387 
maxPressure() const388 qreal KisDistanceInformation::maxPressure() const
389 {
390     return m_d->lastMaxPressure;
391 }
392 
isStarted() const393 bool KisDistanceInformation::isStarted() const
394 {
395     return m_d->lastPaintInfoValid;
396 }
397 
registerPaintedDab(const KisPaintInformation & info,const KisSpacingInformation & spacing,const KisTimingInformation & timing)398 void KisDistanceInformation::registerPaintedDab(const KisPaintInformation &info,
399                                                 const KisSpacingInformation &spacing,
400                                                 const KisTimingInformation &timing)
401 {
402     m_d->totalDistance +=
403         KisAlgebra2D::norm(info.pos() - m_d->lastPosition) *
404         KisLodTransform::lodToInvScale(m_d->levelOfDetail);
405 
406     m_d->lastPaintInformation = info;
407     m_d->lastPaintInfoValid = true;
408 
409     m_d->lastAngle = info.drawingAngle(false);
410     m_d->lastPosition = info.pos();
411     m_d->lastDabInfoValid = true;
412 
413     m_d->spacing = spacing;
414     m_d->timing = timing;
415 
416     m_d->currentDabSeqNo++;
417 
418     m_d->lastMaxPressure = qMax(info.pressure(), m_d->lastMaxPressure);
419 }
420 
getNextPointPosition(const QPointF & start,const QPointF & end,qreal startTime,qreal endTime)421 qreal KisDistanceInformation::getNextPointPosition(const QPointF &start,
422                                                    const QPointF &end,
423                                                    qreal startTime,
424                                                    qreal endTime)
425 {
426     // Compute interpolation factor based on distance.
427     qreal distanceFactor = -1.0;
428     if (m_d->spacing.isDistanceSpacingEnabled()) {
429         distanceFactor = m_d->spacing.isIsotropic() ?
430             getNextPointPositionIsotropic(start, end) :
431             getNextPointPositionAnisotropic(start, end);
432     }
433 
434     // Compute interpolation factor based on time.
435     qreal timeFactor = -1.0;
436     if (m_d->timing.isTimedSpacingEnabled()) {
437         timeFactor = getNextPointPositionTimed(startTime, endTime);
438     }
439 
440     // Return the distance-based or time-based factor, whichever is smallest.
441     qreal t = -1.0;
442     if (distanceFactor < 0.0) {
443         t = timeFactor;
444     } else if (timeFactor < 0.0) {
445         t = distanceFactor;
446     } else {
447         t = qMin(distanceFactor, timeFactor);
448     }
449 
450     // If we aren't ready to paint a dab, accumulate time for the spacing/timing updates that might
451     // be needed between dabs.
452     if (t < 0.0) {
453         m_d->timeSinceSpacingUpdate += endTime - startTime;
454         m_d->timeSinceTimingUpdate += endTime - startTime;
455     }
456 
457     // If we are ready to paint a dab, reset the accumulated time for spacing/timing updates.
458     else {
459         m_d->timeSinceSpacingUpdate = 0.0;
460         m_d->timeSinceTimingUpdate = 0.0;
461     }
462 
463     return t;
464 }
465 
getSpacingInterval() const466 qreal KisDistanceInformation::getSpacingInterval() const
467 {
468     return m_d->spacingUpdateInterval;
469 }
470 
getTimingUpdateInterval() const471 qreal KisDistanceInformation::getTimingUpdateInterval() const
472 {
473     return m_d->timingUpdateInterval;
474 }
475 
getNextPointPositionIsotropic(const QPointF & start,const QPointF & end)476 qreal KisDistanceInformation::getNextPointPositionIsotropic(const QPointF &start,
477                                                             const QPointF &end)
478 {
479     qreal distance = m_d->accumDistance.x();
480     qreal spacing = qMax(MIN_DISTANCE_SPACING, m_d->spacing.distanceSpacing().x());
481 
482     if (start == end) {
483         return -1;
484     }
485 
486     qreal dragVecLength = QVector2D(end - start).length();
487     qreal nextPointDistance = spacing - distance;
488 
489     qreal t;
490 
491     // nextPointDistance can sometimes be negative if the spacing info has been modified since the
492     // last interpolation attempt. In that case, have a point painted immediately.
493     if (nextPointDistance <= 0.0) {
494         resetAccumulators();
495         t = 0.0;
496     }
497     else if (nextPointDistance <= dragVecLength) {
498         t = nextPointDistance / dragVecLength;
499         resetAccumulators();
500     } else {
501         t = -1;
502         m_d->accumDistance.rx() += dragVecLength;
503     }
504 
505     return t;
506 }
507 
getNextPointPositionAnisotropic(const QPointF & start,const QPointF & end)508 qreal KisDistanceInformation::getNextPointPositionAnisotropic(const QPointF &start,
509                                                               const QPointF &end)
510 {
511     if (start == end) {
512         return -1;
513     }
514 
515     qreal a_rev = 1.0 / qMax(MIN_DISTANCE_SPACING, m_d->spacing.distanceSpacing().x());
516     qreal b_rev = 1.0 / qMax(MIN_DISTANCE_SPACING, m_d->spacing.distanceSpacing().y());
517 
518     qreal x = m_d->accumDistance.x();
519     qreal y = m_d->accumDistance.y();
520 
521     qreal gamma = pow2(x * a_rev) + pow2(y * b_rev) - 1;
522 
523     // If the distance accumulator is already past the spacing ellipse, have a point painted
524     // immediately. This can happen if the spacing info has been modified since the last
525     // interpolation attempt.
526     if (gamma >= 0.0) {
527         resetAccumulators();
528         return 0.0;
529     }
530 
531     static const qreal eps = 2e-3; // < 0.2 deg
532 
533     qreal currentRotation = m_d->spacing.rotation();
534     if (m_d->spacing.coordinateSystemFlipped()) {
535         currentRotation = 2 * M_PI - currentRotation;
536     }
537 
538     QPointF diff = end - start;
539 
540     if (currentRotation > eps) {
541         QTransform rot;
542         // since the ellipse is symmetrical, the sign
543         // of rotation doesn't matter
544         rot.rotateRadians(currentRotation);
545         diff = rot.map(diff);
546     }
547 
548     qreal dx = qAbs(diff.x());
549     qreal dy = qAbs(diff.y());
550 
551     qreal alpha = pow2(dx * a_rev) + pow2(dy * b_rev);
552     qreal beta = x * dx * a_rev * a_rev + y * dy * b_rev * b_rev;
553     qreal D_4 = pow2(beta) - alpha * gamma;
554 
555     qreal t = -1.0;
556 
557     if (D_4 >= 0) {
558         qreal k = (-beta + qSqrt(D_4)) / alpha;
559 
560         if (k >= 0.0 && k <= 1.0) {
561             t = k;
562             resetAccumulators();
563         } else {
564             m_d->accumDistance += KisAlgebra2D::abs(diff);
565         }
566     } else {
567         warnKrita << "BUG: No solution for elliptical spacing equation has been found. This shouldn't have happened.";
568     }
569 
570     return t;
571 }
572 
getNextPointPositionTimed(qreal startTime,qreal endTime)573 qreal KisDistanceInformation::getNextPointPositionTimed(qreal startTime,
574                                                         qreal endTime)
575 {
576     // If start time is not before end time, do not interpolate.
577     if (!(startTime < endTime)) {
578         return -1.0;
579     }
580 
581     qreal timedSpacingInterval = qBound(MIN_TIMED_INTERVAL, m_d->timing.timedSpacingInterval(),
582                                         MAX_TIMED_INTERVAL);
583     qreal nextPointInterval = timedSpacingInterval - m_d->accumTime;
584 
585     qreal t = -1.0;
586 
587     // nextPointInterval can sometimes be negative if the spacing info has been modified since the
588     // last interpolation attempt. In that case, have a point painted immediately.
589     if (nextPointInterval <= 0.0) {
590         resetAccumulators();
591         t = 0.0;
592     }
593     else if (nextPointInterval <= endTime - startTime) {
594         resetAccumulators();
595         t = nextPointInterval / (endTime - startTime);
596     }
597     else {
598         m_d->accumTime += endTime - startTime;
599         t = -1.0;
600     }
601 
602     return t;
603 }
604 
resetAccumulators()605 void KisDistanceInformation::resetAccumulators()
606 {
607     m_d->accumDistance = QPointF();
608     m_d->accumTime = 0.0;
609 }
610 
lockedDrawingAngleOptional() const611 boost::optional<qreal> KisDistanceInformation::lockedDrawingAngleOptional() const
612 {
613     return m_d->lockedDrawingAngleOptional;
614 }
615 
lockCurrentDrawingAngle(const KisPaintInformation & info) const616 void KisDistanceInformation::lockCurrentDrawingAngle(const KisPaintInformation &info) const
617 {
618     const qreal angle = info.drawingAngle(false);
619 
620     qreal newAngle = angle;
621 
622     if (m_d->lockedDrawingAngleOptional) {
623         const qreal stabilizingCoeff = 20.0;
624         const qreal dist = stabilizingCoeff * m_d->spacing.scalarApprox();
625         const qreal alpha = qMax(0.0, dist - scalarDistanceApprox()) / dist;
626 
627         const qreal oldAngle = *m_d->lockedDrawingAngleOptional;
628 
629         if (shortestAngularDistance(oldAngle, newAngle) < M_PI / 6) {
630             newAngle = (1.0 - alpha) * oldAngle + alpha * newAngle;
631         } else {
632             newAngle = oldAngle;
633         }
634     }
635 
636     m_d->lockedDrawingAngleOptional = newAngle;
637 }
638 
639 
scalarDistanceApprox() const640 qreal KisDistanceInformation::scalarDistanceApprox() const
641 {
642     return m_d->totalDistance;
643 }
644 
645