1 /* ***** BEGIN LICENSE BLOCK *****
2  * This file is part of openfx-supportext <https://github.com/devernay/openfx-supportext>,
3  * Copyright (C) 2013-2018 INRIA
4  *
5  * openfx-supportext 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  * openfx-supportext 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 openfx-supportext.  If not, see <http://www.gnu.org/licenses/gpl-2.0.html>
17  * ***** END LICENSE BLOCK ***** */
18 
19 /*
20  * OFX TransformInteract.
21  */
22 
23 #include "ofxsTransformInteract.h"
24 
25 #include <memory>
26 #include <cmath>
27 #include <cfloat> // DBL_MAX
28 #include <algorithm>
29 
30 #ifdef __APPLE__
31 #include <OpenGL/gl.h>
32 #else
33 #ifdef _WIN32
34 #define WIN32_LEAN_AND_MEAN
35 #ifndef NOMINMAX
36 #define NOMINMAX
37 #endif
38 #include <windows.h>
39 #endif
40 
41 #include <GL/gl.h>
42 #endif
43 
44 #include "ofxsMatrix2D.h"
45 #include "ofxsTransform3x3.h"
46 
47 using namespace OFX;
48 
49 #define SCALE_MAX 10000.
50 
51 #define CIRCLE_RADIUS_BASE 30.
52 #define CIRCLE_RADIUS_MIN 15.
53 #define CIRCLE_RADIUS_MAX 300.
54 #define POINT_SIZE 7.
55 #define ELLIPSE_N_POINTS 50.
56 
57 namespace OFX {
58 /// add Transform params. page and group are optional
59 void
ofxsTransformDescribeParams(ImageEffectDescriptor & desc,PageParamDescriptor * page,GroupParamDescriptor * group,bool isOpen,bool oldParams,bool hasAmount,bool noTranslate)60 ofxsTransformDescribeParams(ImageEffectDescriptor &desc,
61                             PageParamDescriptor *page,
62                             GroupParamDescriptor *group,
63                             bool isOpen,
64                             bool oldParams,
65                             bool hasAmount,
66                             bool noTranslate)
67 {
68     // translate
69     if (!noTranslate) {
70         Double2DParamDescriptor* param = desc.defineDouble2DParam(oldParams ? kParamTransformTranslateOld : kParamTransformTranslate);
71         param->setLabel(kParamTransformTranslateLabel);
72         param->setHint(kParamTransformTranslateHint);
73         //param->setDoubleType(eDoubleTypeNormalisedXY); // deprecated in OpenFX 1.2
74         param->setDoubleType(eDoubleTypeXYAbsolute);
75         param->setDefaultCoordinateSystem(eCoordinatesNormalised);
76         //param->setDimensionLabels("x","y");
77         param->setDefault(0, 0);
78         param->setRange(-DBL_MAX, -DBL_MAX, DBL_MAX, DBL_MAX); // Resolve requires range and display range or values are clamped to (-1,1)
79         param->setDisplayRange(-10000, -10000, 10000, 10000); // Resolve requires display range or values are clamped to (-1,1)
80         param->setIncrement(10.);
81         if (group) {
82             param->setParent(*group);
83         }
84         if (page) {
85             page->addChild(*param);
86         }
87     }
88 
89     // rotate
90     {
91         DoubleParamDescriptor* param = desc.defineDoubleParam(oldParams ? kParamTransformRotateOld : kParamTransformRotate);
92         param->setLabel(kParamTransformRotateLabel);
93         param->setHint(kParamTransformRotateHint);
94         param->setDoubleType(eDoubleTypeAngle);
95         param->setDefault(0);
96         param->setRange(-DBL_MAX, DBL_MAX); // Resolve requires range and display range or values are clamped to (-1,1)
97         param->setDisplayRange(-180, 180);
98         param->setIncrement(0.1);
99         if (group) {
100             param->setParent(*group);
101         }
102         if (page) {
103             page->addChild(*param);
104         }
105     }
106 
107     // scale
108     {
109         Double2DParamDescriptor* param = desc.defineDouble2DParam(oldParams ? kParamTransformScaleOld : kParamTransformScale);
110         param->setLabel(kParamTransformScaleLabel);
111         param->setHint(kParamTransformScaleHint);
112         param->setDoubleType(eDoubleTypeScale);
113         //param->setDimensionLabels("w","h");
114         param->setDefault(1, 1);
115         param->setRange(-SCALE_MAX, -SCALE_MAX, SCALE_MAX, SCALE_MAX);
116         param->setDisplayRange(0.1, 0.1, 10, 10);
117         param->setIncrement(0.01);
118         param->setLayoutHint(eLayoutHintNoNewLine, 1);
119         if (group) {
120             param->setParent(*group);
121         }
122         if (page) {
123             page->addChild(*param);
124         }
125     }
126 
127     // scaleUniform
128     {
129         BooleanParamDescriptor* param = desc.defineBooleanParam(oldParams ? kParamTransformScaleUniformOld : kParamTransformScaleUniform);
130         param->setLabel(kParamTransformScaleUniformLabel);
131         param->setHint(kParamTransformScaleUniformHint);
132         // don't check it by default: it is easy to obtain Uniform scaling using the slider or the interact
133         param->setDefault(false);
134         param->setAnimates(true);
135         if (group) {
136             param->setParent(*group);
137         }
138         if (page) {
139             page->addChild(*param);
140         }
141     }
142 
143     // skewX
144     {
145         DoubleParamDescriptor* param = desc.defineDoubleParam(oldParams ? kParamTransformSkewXOld : kParamTransformSkewX);
146         param->setLabel(kParamTransformSkewXLabel);
147         param->setHint(kParamTransformSkewXHint);
148         param->setDefault(0);
149         param->setRange(-DBL_MAX, DBL_MAX); // Resolve requires range and display range or values are clamped to (-1,1)
150         param->setDisplayRange(-1., 1.);
151         param->setIncrement(0.01);
152         if (group) {
153             param->setParent(*group);
154         }
155         if (page) {
156             page->addChild(*param);
157         }
158     }
159 
160     // skewY
161     {
162         DoubleParamDescriptor* param = desc.defineDoubleParam(oldParams ? kParamTransformSkewYOld : kParamTransformSkewY);
163         param->setLabel(kParamTransformSkewYLabel);
164         param->setHint(kParamTransformSkewYHint);
165         param->setDefault(0);
166         param->setRange(-DBL_MAX, DBL_MAX); // Resolve requires range and display range or values are clamped to (-1,1)
167         param->setDisplayRange(-1., 1.);
168         param->setIncrement(0.01);
169         if (group) {
170             param->setParent(*group);
171         }
172         if (page) {
173             page->addChild(*param);
174         }
175     }
176 
177     // skewOrder
178     {
179         ChoiceParamDescriptor* param = desc.defineChoiceParam(oldParams ? kParamTransformSkewOrderOld : kParamTransformSkewOrder);
180         param->setLabel(kParamTransformSkewOrderLabel);
181         param->setHint(kParamTransformSkewOrderHint);
182         param->setDefault(0);
183         param->appendOption("XY");
184         param->appendOption("YX");
185         param->setAnimates(true);
186         if (group) {
187             param->setParent(*group);
188         }
189         if (page) {
190             page->addChild(*param);
191         }
192     }
193 
194     // amount
195     if (hasAmount) {
196         DoubleParamDescriptor* param = desc.defineDoubleParam(kParamTransformAmount);
197         param->setLabel(kParamTransformAmountLabel);
198         param->setHint(kParamTransformAmountHint);
199         param->setDoubleType(eDoubleTypeScale);
200         param->setDefault(1.);
201         param->setRange(-DBL_MAX, DBL_MAX); // Resolve requires range and display range or values are clamped to (-1,1)
202         param->setDisplayRange(0., 1.);
203         param->setIncrement(0.01);
204         if (group) {
205             param->setParent(*group);
206         }
207         if (page) {
208             page->addChild(*param);
209         }
210     }
211 
212     // center
213     {
214         Double2DParamDescriptor* param = desc.defineDouble2DParam(oldParams ? kParamTransformCenterOld : kParamTransformCenter);
215         param->setLabel(kParamTransformCenterLabel);
216         param->setHint(kParamTransformCenterHint);
217         //param->setDoubleType(eDoubleTypeNormalisedXY); // deprecated in OpenFX 1.2
218         //param->setDimensionLabels("x","y");
219         param->setDoubleType(eDoubleTypeXYAbsolute);
220         param->setDefaultCoordinateSystem(eCoordinatesNormalised);
221         param->setDefault(0.5, 0.5);
222         param->setRange(-DBL_MAX, -DBL_MAX, DBL_MAX, DBL_MAX); // Resolve requires range and display range or values are clamped to (-1,1)
223         param->setDisplayRange(-10000, -10000, 10000, 10000); // Resolve requires display range or values are clamped to (-1,1)
224         param->setIncrement(1.);
225         param->setLayoutHint(eLayoutHintNoNewLine, 1);
226         if (group) {
227             param->setParent(*group);
228         }
229         if (page) {
230             page->addChild(*param);
231         }
232     }
233 
234     // resetcenter
235     {
236         PushButtonParamDescriptor* param = desc.definePushButtonParam(oldParams ? kParamTransformResetCenterOld : kParamTransformResetCenter);
237         param->setLabel(kParamTransformResetCenterLabel);
238         param->setHint(kParamTransformResetCenterHint);
239         if (group) {
240             param->setParent(*group);
241         }
242         if (page) {
243             page->addChild(*param);
244         }
245     }
246 
247     // centerChanged
248     {
249         BooleanParamDescriptor* param = desc.defineBooleanParam(kParamTransformCenterChanged);
250         param->setDefault(false);
251         param->setIsSecretAndDisabled(true);
252         param->setAnimates(false);
253         param->setEvaluateOnChange(false);
254         if (group) {
255             param->setParent(*group);
256         }
257         if (page) {
258             page->addChild(*param);
259         }
260     }
261 
262     // interactOpen
263     {
264         BooleanParamDescriptor* param = desc.defineBooleanParam(kParamTransformInteractOpen);
265         param->setLabel(kParamTransformInteractOpenLabel);
266         param->setHint(kParamTransformInteractOpenHint);
267         param->setDefault(isOpen); // open by default
268         param->setIsSecretAndDisabled(true); // secret by default, but this can be changed for specific hosts
269         param->setAnimates(false);
270         if (group) {
271             param->setParent(*group);
272         }
273         if (page) {
274             page->addChild(*param);
275         }
276     }
277 
278     // interactive
279     {
280         BooleanParamDescriptor* param = desc.defineBooleanParam(oldParams ? kParamTransformInteractiveOld : kParamTransformInteractive);
281         param->setLabel(kParamTransformInteractiveLabel);
282         param->setHint(kParamTransformInteractiveHint);
283         param->setDefault(true);
284         param->setEvaluateOnChange(false);
285         if (group) {
286             param->setParent(*group);
287         }
288         if (page) {
289             page->addChild(*param);
290         }
291     }
292 } // ofxsTransformDescribeParams
293 
294 ////////////////////////////////////////////////////////////////////////////////
295 // stuff for the interact
296 
TransformInteractHelper(ImageEffect * effect,Interact * interact,bool oldParams)297 TransformInteractHelper::TransformInteractHelper(ImageEffect* effect,
298                                                  Interact* interact,
299                                                  bool oldParams)
300     : _drawState(eInActive)
301     , _mouseState(eReleased)
302     , _modifierStateCtrl(0)
303     , _modifierStateShift(0)
304     , _orientation(eOrientationAllDirections)
305     , _effect(effect)
306     , _interact(interact)
307     , _lastMousePos()
308     , _scaleUniformDrag(false)
309     , _rotateDrag(0)
310     , _skewXDrag(0)
311     , _skewYDrag(0)
312     , _skewOrderDrag(0)
313     , _invertedDrag(false)
314     , _interactiveDrag(false)
315     , _translate(NULL)
316     , _rotate(NULL)
317     , _scale(NULL)
318     , _scaleUniform(NULL)
319     , _skewX(NULL)
320     , _skewY(NULL)
321     , _skewOrder(NULL)
322     , _center(NULL)
323     , _invert(NULL)
324     , _interactOpen(NULL)
325     , _interactive(NULL)
326 {
327     assert(_effect && _interact);
328     _lastMousePos.x = _lastMousePos.y = 0.;
329     // NON-GENERIC
330     if (oldParams) {
331         if ( _effect->paramExists(kParamTransformTranslateOld) ) {
332             _translate = _effect->fetchDouble2DParam(kParamTransformTranslateOld);
333             assert(_translate);
334         }
335         _rotate = _effect->fetchDoubleParam(kParamTransformRotateOld);
336         _scale = _effect->fetchDouble2DParam(kParamTransformScaleOld);
337         _scaleUniform = _effect->fetchBooleanParam(kParamTransformScaleUniformOld);
338         _skewX = _effect->fetchDoubleParam(kParamTransformSkewXOld);
339         _skewY = _effect->fetchDoubleParam(kParamTransformSkewYOld);
340         _skewOrder = _effect->fetchChoiceParam(kParamTransformSkewOrderOld);
341         _center = _effect->fetchDouble2DParam(kParamTransformCenterOld);
342         _interactive = _effect->fetchBooleanParam(kParamTransformInteractiveOld);
343     } else {
344         if ( _effect->paramExists(kParamTransformTranslate) ) {
345             _translate = _effect->fetchDouble2DParam(kParamTransformTranslate);
346             assert(_translate);
347         }
348         _rotate = _effect->fetchDoubleParam(kParamTransformRotate);
349         _scale = _effect->fetchDouble2DParam(kParamTransformScale);
350         _scaleUniform = _effect->fetchBooleanParam(kParamTransformScaleUniform);
351         _skewX = _effect->fetchDoubleParam(kParamTransformSkewX);
352         _skewY = _effect->fetchDoubleParam(kParamTransformSkewY);
353         _skewOrder = _effect->fetchChoiceParam(kParamTransformSkewOrder);
354         _center = _effect->fetchDouble2DParam(kParamTransformCenter);
355         _interactive = _effect->fetchBooleanParam(kParamTransformInteractive);
356     }
357     _interactOpen = _effect->fetchBooleanParam(kParamTransformInteractOpen);
358     if ( _effect->paramExists(kParamTransform3x3Invert) ) {
359         _invert = _effect->fetchBooleanParam(kParamTransform3x3Invert);
360     }
361     assert(_rotate && _scale && _scaleUniform && _skewX && _skewY && _skewOrder && _center && _interactive);
362     if (_translate) {
363         _interact->addParamToSlaveTo(_translate);
364     }
365     if (_rotate) {
366         _interact->addParamToSlaveTo(_rotate);
367     }
368     if (_scale) {
369         _interact->addParamToSlaveTo(_scale);
370     }
371     if (_skewX) {
372         _interact->addParamToSlaveTo(_skewX);
373     }
374     if (_skewY) {
375         _interact->addParamToSlaveTo(_skewY);
376     }
377     if (_skewOrder) {
378         _interact->addParamToSlaveTo(_skewOrder);
379     }
380     if (_center) {
381         _interact->addParamToSlaveTo(_center);
382     }
383     if (_invert) {
384         _interact->addParamToSlaveTo(_invert);
385     }
386     if (!_translate) {
387         _modifierStateCtrl = 1;
388     }
389     _centerDrag.x = _centerDrag.y = 0.;
390     _translateDrag.x = _translateDrag.y = 0.;
391     _scaleParamDrag.x = _scaleParamDrag.y = 0.;
392 }
393 
394 static void
getTargetCenter(const OfxPointD & center,const OfxPointD & translate,OfxPointD * targetCenter)395 getTargetCenter(const OfxPointD &center,
396                 const OfxPointD &translate,
397                 OfxPointD *targetCenter)
398 {
399     targetCenter->x = center.x + translate.x;
400     targetCenter->y = center.y + translate.y;
401 }
402 
403 static void
getTargetRadius(const OfxPointD & scale,const OfxPointD & pixelScale,OfxPointD * targetRadius)404 getTargetRadius(const OfxPointD& scale,
405                 const OfxPointD& pixelScale,
406                 OfxPointD* targetRadius)
407 {
408     targetRadius->x = scale.x * CIRCLE_RADIUS_BASE;
409     targetRadius->y = scale.y * CIRCLE_RADIUS_BASE;
410     // don't draw too small. 15 pixels is the limit
411     if ( (std::fabs(targetRadius->x) < CIRCLE_RADIUS_MIN) && (std::fabs(targetRadius->y) < CIRCLE_RADIUS_MIN) ) {
412         targetRadius->x = targetRadius->x >= 0 ? CIRCLE_RADIUS_MIN : -CIRCLE_RADIUS_MIN;
413         targetRadius->y = targetRadius->y >= 0 ? CIRCLE_RADIUS_MIN : -CIRCLE_RADIUS_MIN;
414     } else if ( (std::fabs(targetRadius->x) > CIRCLE_RADIUS_MAX) && (std::fabs(targetRadius->y) > CIRCLE_RADIUS_MAX) ) {
415         targetRadius->x = targetRadius->x >= 0 ? CIRCLE_RADIUS_MAX : -CIRCLE_RADIUS_MAX;
416         targetRadius->y = targetRadius->y >= 0 ? CIRCLE_RADIUS_MAX : -CIRCLE_RADIUS_MAX;
417     } else {
418         if (std::fabs(targetRadius->x) < CIRCLE_RADIUS_MIN) {
419             if ( (targetRadius->x == 0.) && (targetRadius->y != 0.) ) {
420                 targetRadius->y = targetRadius->y > 0 ? CIRCLE_RADIUS_MAX : -CIRCLE_RADIUS_MAX;
421             } else {
422                 targetRadius->y *= std::fabs(CIRCLE_RADIUS_MIN / targetRadius->x);
423             }
424             targetRadius->x = targetRadius->x >= 0 ? CIRCLE_RADIUS_MIN : -CIRCLE_RADIUS_MIN;
425         }
426         if (std::fabs(targetRadius->x) > CIRCLE_RADIUS_MAX) {
427             targetRadius->y *= std::fabs(CIRCLE_RADIUS_MAX / targetRadius->x);
428             targetRadius->x = targetRadius->x > 0 ? CIRCLE_RADIUS_MAX : -CIRCLE_RADIUS_MAX;
429         }
430         if (std::fabs(targetRadius->y) < CIRCLE_RADIUS_MIN) {
431             if ( (targetRadius->y == 0.) && (targetRadius->x != 0.) ) {
432                 targetRadius->x = targetRadius->x > 0 ? CIRCLE_RADIUS_MAX : -CIRCLE_RADIUS_MAX;
433             } else {
434                 targetRadius->x *= std::fabs(CIRCLE_RADIUS_MIN / targetRadius->y);
435             }
436             targetRadius->y = targetRadius->y >= 0 ? CIRCLE_RADIUS_MIN : -CIRCLE_RADIUS_MIN;
437         }
438         if (std::fabs(targetRadius->y) > CIRCLE_RADIUS_MAX) {
439             targetRadius->x *= std::fabs(CIRCLE_RADIUS_MAX / targetRadius->x);
440             targetRadius->y = targetRadius->y > 0 ? CIRCLE_RADIUS_MAX : -CIRCLE_RADIUS_MAX;
441         }
442     }
443     // the circle axes are not aligned with the images axes, so we cannot use the x and y scales separately
444     double meanPixelScale = (pixelScale.x + pixelScale.y) / 2.;
445     targetRadius->x *= meanPixelScale;
446     targetRadius->y *= meanPixelScale;
447 }
448 
449 static void
getTargetPoints(const OfxPointD & targetCenter,const OfxPointD & targetRadius,OfxPointD * left,OfxPointD * bottom,OfxPointD * top,OfxPointD * right)450 getTargetPoints(const OfxPointD& targetCenter,
451                 const OfxPointD& targetRadius,
452                 OfxPointD *left,
453                 OfxPointD *bottom,
454                 OfxPointD *top,
455                 OfxPointD *right)
456 {
457     left->x = targetCenter.x - targetRadius.x;
458     left->y = targetCenter.y;
459     right->x = targetCenter.x + targetRadius.x;
460     right->y = targetCenter.y;
461     top->x = targetCenter.x;
462     top->y = targetCenter.y + targetRadius.y;
463     bottom->x = targetCenter.x;
464     bottom->y = targetCenter.y - targetRadius.y;
465 }
466 
467 static void
drawSquare(const OfxRGBColourD & color,const OfxPointD & center,const OfxPointD & pixelScale,bool hovered,bool althovered,int l)468 drawSquare(const OfxRGBColourD& color,
469            const OfxPointD& center,
470            const OfxPointD& pixelScale,
471            bool hovered,
472            bool althovered,
473            int l)
474 {
475     // we are not axis-aligned
476     double meanPixelScale = (pixelScale.x + pixelScale.y) / 2.;
477 
478     if (hovered) {
479         if (althovered) {
480             glColor3f(0.f * l, 1.f * l, 0.f * l);
481         } else {
482             glColor3f(1.f * l, 0.f * l, 0.f * l);
483         }
484     } else {
485         glColor3f( (float)color.r * l, (float)color.g * l, (float)color.b * l );
486     }
487     double halfWidth = (POINT_SIZE / 2.) * meanPixelScale;
488     double halfHeight = (POINT_SIZE / 2.) * meanPixelScale;
489     glPushMatrix();
490     glTranslated(center.x, center.y, 0.);
491     glBegin(GL_POLYGON);
492     glVertex2d(-halfWidth, -halfHeight);   // bottom left
493     glVertex2d(-halfWidth, +halfHeight);   // top left
494     glVertex2d(+halfWidth, +halfHeight);   // bottom right
495     glVertex2d(+halfWidth, -halfHeight);   // top right
496     glEnd();
497     glPopMatrix();
498 }
499 
500 static void
drawEllipse(const OfxRGBColourD & color,const OfxPointD & center,const OfxPointD & targetRadius,bool hovered,int l)501 drawEllipse(const OfxRGBColourD& color,
502             const OfxPointD& center,
503             const OfxPointD& targetRadius,
504             bool hovered,
505             int l)
506 {
507     if (hovered) {
508         glColor3f(1.f * l, 0.f * l, 0.f * l);
509     } else {
510         glColor3f( (float)color.r * l, (float)color.g * l, (float)color.b * l );
511     }
512 
513     glPushMatrix();
514     //  center the oval at x_center, y_center
515     glTranslatef( (float)center.x, (float)center.y, 0.f );
516     //  draw the oval using line segments
517     glBegin(GL_LINE_LOOP);
518     // we don't need to be pixel-perfect here, it's just an interact!
519     // 40 segments is enough.
520     for (int i = 0; i < 40; ++i) {
521         double theta = i * 2 * ofxsPi() / 40.;
522         glVertex2d( targetRadius.x * std::cos(theta), targetRadius.y * std::sin(theta) );
523     }
524     glEnd();
525 
526     glPopMatrix();
527 }
528 
529 static void
drawSkewBar(const OfxRGBColourD & color,const OfxPointD & center,const OfxPointD & pixelScale,double targetRadiusY,bool hovered,double angle,int l)530 drawSkewBar(const OfxRGBColourD& color,
531             const OfxPointD &center,
532             const OfxPointD& pixelScale,
533             double targetRadiusY,
534             bool hovered,
535             double angle,
536             int l)
537 {
538     if (hovered) {
539         glColor3f(1.f * l, 0.f * l, 0.f * l);
540     } else {
541         glColor3f( (float)color.r * l, (float)color.g * l, (float)color.b * l );
542     }
543 
544     // we are not axis-aligned: use the mean pixel scale
545     double meanPixelScale = (pixelScale.x + pixelScale.y) / 2.;
546     double barHalfSize = targetRadiusY + 20. * meanPixelScale;
547 
548     glPushMatrix();
549     glTranslatef( (float)center.x, (float)center.y, 0.f );
550     glRotated(angle, 0, 0, 1);
551 
552     glBegin(GL_LINES);
553     glVertex2d(0., -barHalfSize);
554     glVertex2d(0., +barHalfSize);
555 
556     if (hovered) {
557         double arrowYPosition = targetRadiusY + 10. * meanPixelScale;
558         double arrowXHalfSize = 10 * meanPixelScale;
559         double arrowHeadOffsetX = 3 * meanPixelScale;
560         double arrowHeadOffsetY = 3 * meanPixelScale;
561 
562         ///draw the central bar
563         glVertex2d(-arrowXHalfSize, -arrowYPosition);
564         glVertex2d(+arrowXHalfSize, -arrowYPosition);
565 
566         ///left triangle
567         glVertex2d(-arrowXHalfSize, -arrowYPosition);
568         glVertex2d(-arrowXHalfSize + arrowHeadOffsetX, -arrowYPosition + arrowHeadOffsetY);
569 
570         glVertex2d(-arrowXHalfSize, -arrowYPosition);
571         glVertex2d(-arrowXHalfSize + arrowHeadOffsetX, -arrowYPosition - arrowHeadOffsetY);
572 
573         ///right triangle
574         glVertex2d(+arrowXHalfSize, -arrowYPosition);
575         glVertex2d(+arrowXHalfSize - arrowHeadOffsetX, -arrowYPosition + arrowHeadOffsetY);
576 
577         glVertex2d(+arrowXHalfSize, -arrowYPosition);
578         glVertex2d(+arrowXHalfSize - arrowHeadOffsetX, -arrowYPosition - arrowHeadOffsetY);
579     }
580     glEnd();
581     glPopMatrix();
582 }
583 
584 static void
drawRotationBar(const OfxRGBColourD & color,const OfxPointD & pixelScale,double targetRadiusX,bool hovered,bool inverted,int l)585 drawRotationBar(const OfxRGBColourD& color,
586                 const OfxPointD& pixelScale,
587                 double targetRadiusX,
588                 bool hovered,
589                 bool inverted,
590                 int l)
591 {
592     // we are not axis-aligned
593     double meanPixelScale = (pixelScale.x + pixelScale.y) / 2.;
594 
595     if (hovered) {
596         glColor3f(1.f * l, 0.f * l, 0.f * l);
597     } else {
598         glColor3d(color.r * l, color.g * l, color.b * l);
599     }
600 
601     double barExtra = 30. * meanPixelScale;
602     glBegin(GL_LINES);
603     glVertex2d(0., 0.);
604     glVertex2d(0. + targetRadiusX + barExtra, 0.);
605     glEnd();
606 
607     if (hovered) {
608         double arrowCenterX = targetRadiusX + barExtra / 2.;
609 
610         ///draw an arrow slightly bended. This is an arc of circle of radius 5 in X, and 10 in Y.
611         OfxPointD arrowRadius;
612         arrowRadius.x = 5. * meanPixelScale;
613         arrowRadius.y = 10. * meanPixelScale;
614 
615         glPushMatrix();
616         //  center the oval at x_center, y_center
617         glTranslatef( (float)arrowCenterX, 0.f, 0 );
618         //  draw the oval using line segments
619         glBegin(GL_LINE_STRIP);
620         glVertex2d(0, arrowRadius.y);
621         glVertex2d(arrowRadius.x, 0.);
622         glVertex2d(0, -arrowRadius.y);
623         glEnd();
624 
625 
626         glBegin(GL_LINES);
627         ///draw the top head
628         glVertex2d(0., arrowRadius.y);
629         glVertex2d(0., arrowRadius.y - 5. * meanPixelScale);
630 
631         glVertex2d(0., arrowRadius.y);
632         glVertex2d(4. * meanPixelScale, arrowRadius.y - 3. * meanPixelScale); // 5^2 = 3^2+4^2
633 
634         ///draw the bottom head
635         glVertex2d(0., -arrowRadius.y);
636         glVertex2d(0., -arrowRadius.y + 5. * meanPixelScale);
637 
638         glVertex2d(0., -arrowRadius.y);
639         glVertex2d(4. * meanPixelScale, -arrowRadius.y + 3. * meanPixelScale); // 5^2 = 3^2+4^2
640 
641         glEnd();
642 
643         glPopMatrix();
644     }
645     if (inverted) {
646         double arrowXPosition = targetRadiusX + barExtra * 1.5;
647         double arrowXHalfSize = 10 * meanPixelScale;
648         double arrowHeadOffsetX = 3 * meanPixelScale;
649         double arrowHeadOffsetY = 3 * meanPixelScale;
650 
651         glPushMatrix();
652         glTranslatef( (float)arrowXPosition, 0, 0 );
653 
654         glBegin(GL_LINES);
655         ///draw the central bar
656         glVertex2d(-arrowXHalfSize, 0.);
657         glVertex2d(+arrowXHalfSize, 0.);
658 
659         ///left triangle
660         glVertex2d(-arrowXHalfSize, 0.);
661         glVertex2d(-arrowXHalfSize + arrowHeadOffsetX, arrowHeadOffsetY);
662 
663         glVertex2d(-arrowXHalfSize, 0.);
664         glVertex2d(-arrowXHalfSize + arrowHeadOffsetX, -arrowHeadOffsetY);
665 
666         ///right triangle
667         glVertex2d(+arrowXHalfSize, 0.);
668         glVertex2d(+arrowXHalfSize - arrowHeadOffsetX, arrowHeadOffsetY);
669 
670         glVertex2d(+arrowXHalfSize, 0.);
671         glVertex2d(+arrowXHalfSize - arrowHeadOffsetX, -arrowHeadOffsetY);
672         glEnd();
673 
674         glRotated(90., 0., 0., 1.);
675 
676         glBegin(GL_LINES);
677         ///draw the central bar
678         glVertex2d(-arrowXHalfSize, 0.);
679         glVertex2d(+arrowXHalfSize, 0.);
680 
681         ///left triangle
682         glVertex2d(-arrowXHalfSize, 0.);
683         glVertex2d(-arrowXHalfSize + arrowHeadOffsetX, arrowHeadOffsetY);
684 
685         glVertex2d(-arrowXHalfSize, 0.);
686         glVertex2d(-arrowXHalfSize + arrowHeadOffsetX, -arrowHeadOffsetY);
687 
688         ///right triangle
689         glVertex2d(+arrowXHalfSize, 0.);
690         glVertex2d(+arrowXHalfSize - arrowHeadOffsetX, arrowHeadOffsetY);
691 
692         glVertex2d(+arrowXHalfSize, 0.);
693         glVertex2d(+arrowXHalfSize - arrowHeadOffsetX, -arrowHeadOffsetY);
694         glEnd();
695 
696         glPopMatrix();
697     }
698 } // drawRotationBar
699 
700 // draw the interact
701 bool
draw(const DrawArgs & args)702 TransformInteractHelper::draw(const DrawArgs &args)
703 {
704     if ( !_interactOpen->getValueAtTime(args.time) ) {
705         return false;
706     }
707     const OfxPointD &pscale = args.pixelScale;
708     const double time = args.time;
709     OfxRGBColourD color = { 0.8, 0.8, 0.8 };
710     _interact->getSuggestedColour(color);
711     GLdouble projection[16];
712     glGetDoublev( GL_PROJECTION_MATRIX, projection);
713     GLint viewport[4];
714     glGetIntegerv(GL_VIEWPORT, viewport);
715     OfxPointD shadow; // how much to translate GL_PROJECTION to get exactly one pixel on screen
716     shadow.x = 2. / (projection[0] * viewport[2]);
717     shadow.y = 2. / (projection[5] * viewport[3]);
718 
719     OfxPointD center = { 0., 0. };
720     OfxPointD translate = { 0., 0. };
721     OfxPointD scaleParam = { 1., 1. };
722     bool scaleUniform = false;
723     double rotate = 0.;
724     double skewX = 0., skewY = 0.;
725     int skewOrder = 0;
726     bool inverted = false;
727 
728     if (_mouseState == eReleased) {
729         if (_center) {
730             _center->getValueAtTime(time, center.x, center.y);
731         }
732         if (_translate) {
733             _translate->getValueAtTime(time, translate.x, translate.y);
734         }
735         if (_scale) {
736             _scale->getValueAtTime(time, scaleParam.x, scaleParam.y);
737         }
738         if (_scaleUniform) {
739             _scaleUniform->getValueAtTime(time, scaleUniform);
740         }
741         if (_rotate) {
742             _rotate->getValueAtTime(time, rotate);
743         }
744         if (_skewX) {
745             _skewX->getValueAtTime(time, skewX);
746         }
747         if (_skewY) {
748             _skewY->getValueAtTime(time, skewY);
749         }
750         if (_skewOrder) {
751             _skewOrder->getValueAtTime(time, skewOrder);
752         }
753         if (_invert) {
754             _invert->getValueAtTime(time, inverted);
755         }
756     } else {
757         center = _centerDrag;
758         translate = _translateDrag;
759         scaleParam = _scaleParamDrag;
760         scaleUniform = _scaleUniformDrag;
761         rotate = _rotateDrag;
762         skewX = _skewXDrag;
763         skewY = _skewYDrag;
764         skewOrder = _skewOrderDrag;
765         inverted = _invertedDrag;
766     }
767 
768     OfxPointD targetCenter;
769     getTargetCenter(center, translate, &targetCenter);
770 
771     OfxPointD scale;
772     ofxsTransformGetScale(scaleParam, scaleUniform, &scale);
773 
774     OfxPointD targetRadius;
775     getTargetRadius(scale, pscale, &targetRadius);
776 
777     OfxPointD left, right, bottom, top;
778     getTargetPoints(targetCenter, targetRadius, &left, &bottom, &top, &right);
779 
780 
781     GLdouble skewMatrix[16];
782     skewMatrix[0] = ( skewOrder ? 1. : (1. + skewX * skewY) ); skewMatrix[1] = skewY; skewMatrix[2] = 0.; skewMatrix[3] = 0;
783     skewMatrix[4] = skewX; skewMatrix[5] = (skewOrder ? (1. + skewX * skewY) : 1.); skewMatrix[6] = 0.; skewMatrix[7] = 0;
784     skewMatrix[8] = 0.; skewMatrix[9] = 0.; skewMatrix[10] = 1.; skewMatrix[11] = 0;
785     skewMatrix[12] = 0.; skewMatrix[13] = 0.; skewMatrix[14] = 0.; skewMatrix[15] = 1.;
786 
787     //glPushAttrib(GL_ALL_ATTRIB_BITS); // caller is responsible for protecting attribs
788 
789     glDisable(GL_LINE_STIPPLE);
790     glEnable(GL_LINE_SMOOTH);
791     glDisable(GL_POINT_SMOOTH);
792     glEnable(GL_BLEND);
793     glHint(GL_LINE_SMOOTH_HINT, GL_DONT_CARE);
794     glLineWidth(1.5f);
795     glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
796 
797     // Draw everything twice
798     // l = 0: shadow
799     // l = 1: drawing
800     for (int l = 0; l < 2; ++l) {
801         // shadow (uses GL_PROJECTION)
802         glMatrixMode(GL_PROJECTION);
803         int direction = (l == 0) ? 1 : -1;
804         // translate (1,-1) pixels
805         glTranslated(direction * shadow.x, -direction * shadow.y, 0);
806         glMatrixMode(GL_MODELVIEW); // Modelview should be used on Nuke
807 
808         glColor3d(color.r * l, color.g * l, color.b * l);
809 
810         glPushMatrix();
811         glTranslated(targetCenter.x, targetCenter.y, 0.);
812 
813         glRotated(rotate, 0, 0., 1.);
814         drawRotationBar(color, pscale, targetRadius.x, _mouseState == eDraggingRotationBar || _drawState == eRotationBarHovered, inverted, l);
815         glMultMatrixd(skewMatrix);
816         glTranslated(-targetCenter.x, -targetCenter.y, 0.);
817 
818         drawEllipse(color, targetCenter, targetRadius, _mouseState == eDraggingCircle || _drawState == eCircleHovered, l);
819 
820         // add 180 to the angle to draw the arrows on the other side. unfortunately, this requires knowing
821         // the mouse position in the ellipse frame
822         double flip = 0.;
823         if ( (_drawState == eSkewXBarHoverered) || (_drawState == eSkewYBarHoverered) ) {
824             double rot = ofxsToRadians(rotate);
825             Matrix3x3 transformscale;
826             transformscale = ofxsMatInverseTransformCanonical(0., 0., scale.x, scale.y, skewX, skewY, (bool)skewOrder, rot, targetCenter.x, targetCenter.y);
827 
828             Point3D previousPos;
829             previousPos.x = _lastMousePos.x;
830             previousPos.y = _lastMousePos.y;
831             previousPos.z = 1.;
832             previousPos = transformscale * previousPos;
833             if (previousPos.z != 0) {
834                 previousPos.x /= previousPos.z;
835                 previousPos.y /= previousPos.z;
836             }
837             if ( ( (_drawState == eSkewXBarHoverered) && (previousPos.y > targetCenter.y) ) ||
838                  ( ( _drawState == eSkewYBarHoverered) && ( previousPos.x > targetCenter.x) ) ) {
839                 flip = 180.;
840             }
841         }
842         drawSkewBar(color, targetCenter, pscale, targetRadius.y, _mouseState == eDraggingSkewXBar || _drawState == eSkewXBarHoverered, flip, l);
843         drawSkewBar(color, targetCenter, pscale, targetRadius.x, _mouseState == eDraggingSkewYBar || _drawState == eSkewYBarHoverered, flip - 90., l);
844 
845 
846         drawSquare(color, targetCenter, pscale, _mouseState == eDraggingTranslation || _mouseState == eDraggingCenter || _drawState == eCenterPointHovered, (!_translate || _modifierStateCtrl), l);
847         drawSquare(color, left, pscale, _mouseState == eDraggingLeftPoint || _drawState == eLeftPointHovered, false, l);
848         drawSquare(color, right, pscale, _mouseState == eDraggingRightPoint || _drawState == eRightPointHovered, false, l);
849         drawSquare(color, top, pscale, _mouseState == eDraggingTopPoint || _drawState == eTopPointHovered, false, l);
850         drawSquare(color, bottom, pscale, _mouseState == eDraggingBottomPoint || _drawState == eBottomPointHovered, false, l);
851 
852         glPopMatrix();
853     }
854     //glPopAttrib();
855 
856     return true;
857 } // TransformInteractHelper::draw
858 
859 static bool
squareContains(const Point3D & pos,const OfxRectD & rect,double toleranceX=0.,double toleranceY=0.)860 squareContains(const Point3D& pos,
861                const OfxRectD& rect,
862                double toleranceX = 0.,
863                double toleranceY = 0.)
864 {
865     return ( pos.x >= (rect.x1 - toleranceX) && pos.x < (rect.x2 + toleranceX)
866              && pos.y >= (rect.y1 - toleranceY) && pos.y < (rect.y2 + toleranceY) );
867 }
868 
869 static bool
isOnEllipseBorder(const Point3D & pos,const OfxPointD & targetRadius,const OfxPointD & targetCenter,double epsilon=0.1)870 isOnEllipseBorder(const Point3D& pos,
871                   const OfxPointD& targetRadius,
872                   const OfxPointD& targetCenter,
873                   double epsilon = 0.1)
874 {
875     double v = ( (pos.x - targetCenter.x) * (pos.x - targetCenter.x) / (targetRadius.x * targetRadius.x) +
876                  (pos.y - targetCenter.y) * (pos.y - targetCenter.y) / (targetRadius.y * targetRadius.y) );
877 
878     if ( ( v <= (1. + epsilon) ) && ( v >= (1. - epsilon) ) ) {
879         return true;
880     }
881 
882     return false;
883 }
884 
885 static bool
isOnSkewXBar(const Point3D & pos,double targetRadiusY,const OfxPointD & center,const OfxPointD & pixelScale,double tolerance)886 isOnSkewXBar(const Point3D& pos,
887              double targetRadiusY,
888              const OfxPointD& center,
889              const OfxPointD& pixelScale,
890              double tolerance)
891 {
892     // we are not axis-aligned
893     double meanPixelScale = (pixelScale.x + pixelScale.y) / 2.;
894     double barHalfSize = targetRadiusY + (20. * meanPixelScale);
895 
896     if ( ( pos.x >= (center.x - tolerance) ) && ( pos.x <= (center.x + tolerance) ) &&
897          ( pos.y >= (center.y - barHalfSize - tolerance) ) && ( pos.y <= (center.y + barHalfSize + tolerance) ) ) {
898         return true;
899     }
900 
901     return false;
902 }
903 
904 static bool
isOnSkewYBar(const Point3D & pos,double targetRadiusX,const OfxPointD & center,const OfxPointD & pixelScale,double tolerance)905 isOnSkewYBar(const Point3D& pos,
906              double targetRadiusX,
907              const OfxPointD& center,
908              const OfxPointD& pixelScale,
909              double tolerance)
910 {
911     // we are not axis-aligned
912     double meanPixelScale = (pixelScale.x + pixelScale.y) / 2.;
913     double barHalfSize = targetRadiusX + (20. * meanPixelScale);
914 
915     if ( ( pos.y >= (center.y - tolerance) ) && ( pos.y <= (center.y + tolerance) ) &&
916          ( pos.x >= (center.x - barHalfSize - tolerance) ) && ( pos.x <= (center.x + barHalfSize + tolerance) ) ) {
917         return true;
918     }
919 
920     return false;
921 }
922 
923 static bool
isOnRotationBar(const Point3D & pos,double targetRadiusX,const OfxPointD & center,const OfxPointD & pixelScale,double tolerance)924 isOnRotationBar(const Point3D& pos,
925                 double targetRadiusX,
926                 const OfxPointD& center,
927                 const OfxPointD& pixelScale,
928                 double tolerance)
929 {
930     // we are not axis-aligned
931     double meanPixelScale = (pixelScale.x + pixelScale.y) / 2.;
932     double barExtra = 30. * meanPixelScale;
933 
934     if ( ( pos.x >= (center.x - tolerance) ) && ( pos.x <= (center.x + targetRadiusX + barExtra + tolerance) ) &&
935          ( pos.y >= (center.y  - tolerance) ) && ( pos.y <= (center.y + tolerance) ) ) {
936         return true;
937     }
938 
939     return false;
940 }
941 
942 static OfxRectD
rectFromCenterPoint(const OfxPointD & center,const OfxPointD & pixelScale)943 rectFromCenterPoint(const OfxPointD& center,
944                     const OfxPointD& pixelScale)
945 {
946     // we are not axis-aligned
947     double meanPixelScale = (pixelScale.x + pixelScale.y) / 2.;
948     OfxRectD ret;
949 
950     ret.x1 = center.x - (POINT_SIZE / 2.) * meanPixelScale;
951     ret.x2 = center.x + (POINT_SIZE / 2.) * meanPixelScale;
952     ret.y1 = center.y - (POINT_SIZE / 2.) * meanPixelScale;
953     ret.y2 = center.y + (POINT_SIZE / 2.) * meanPixelScale;
954 
955     return ret;
956 }
957 
958 // round to the closest int, 1/10 int, etc
959 // this make parameter editing easier
960 // pscale is args.pixelScale.x / args.renderScale.x;
961 // pscale10 is the power of 10 below pscale
962 static double
fround(double val,double pscale)963 fround(double val,
964        double pscale)
965 {
966     double pscale10 = std::pow( 10., std::floor( std::log10(pscale) ) );
967 
968     return pscale10 * std::floor(val / pscale10 + 0.5);
969 }
970 
971 // overridden functions from Interact to do things
972 bool
penMotion(const PenArgs & args)973 TransformInteractHelper::penMotion(const PenArgs &args)
974 {
975     if ( !_interactOpen->getValueAtTime(args.time) ) {
976         return false;
977     }
978     const OfxPointD &pscale = args.pixelScale;
979     const double time = args.time;
980     OfxPointD center = { 0., 0. };
981     OfxPointD translate = { 0., 0. };
982     OfxPointD scaleParam = { 1., 1. };
983     bool scaleUniform = false;
984     double rotate = 0.;
985     double skewX = 0., skewY = 0.;
986     int skewOrder = 0;
987     bool inverted = false;
988 
989     if (_mouseState == eReleased) {
990         if (_center) {
991             _center->getValueAtTime(time, center.x, center.y);
992         }
993         if (_translate) {
994             _translate->getValueAtTime(time, translate.x, translate.y);
995         }
996         if (_scale) {
997             _scale->getValueAtTime(time, scaleParam.x, scaleParam.y);
998         }
999         if (_scaleUniform) {
1000             _scaleUniform->getValueAtTime(time, scaleUniform);
1001         }
1002         if (_rotate) {
1003             _rotate->getValueAtTime(time, rotate);
1004         }
1005         if (_skewX) {
1006             _skewX->getValueAtTime(time, skewX);
1007         }
1008         if (_skewY) {
1009             _skewY->getValueAtTime(time, skewY);
1010         }
1011         if (_skewOrder) {
1012             _skewOrder->getValueAtTime(time, skewOrder);
1013         }
1014         if (_invert) {
1015             _invert->getValueAtTime(time, inverted);
1016         }
1017     } else {
1018         center = _centerDrag;
1019         translate = _translateDrag;
1020         scaleParam = _scaleParamDrag;
1021         scaleUniform = _scaleUniformDrag;
1022         rotate = _rotateDrag;
1023         skewX = _skewXDrag;
1024         skewY = _skewYDrag;
1025         skewOrder = _skewOrderDrag;
1026         inverted = _invertedDrag;
1027     }
1028 
1029     bool didSomething = false;
1030     bool centerChanged = false;
1031     bool translateChanged = false;
1032     bool scaleChanged = false;
1033     bool rotateChanged = false;
1034     bool skewXChanged = false;
1035     bool skewYChanged = false;
1036     OfxPointD targetCenter;
1037     getTargetCenter(center, translate, &targetCenter);
1038 
1039     OfxPointD scale;
1040     ofxsTransformGetScale(scaleParam, scaleUniform, &scale);
1041 
1042     OfxPointD targetRadius;
1043     getTargetRadius(scale, pscale, &targetRadius);
1044 
1045     OfxPointD left, right, bottom, top;
1046     getTargetPoints(targetCenter, targetRadius, &left, &bottom, &top, &right);
1047 
1048     OfxRectD centerPoint = rectFromCenterPoint(targetCenter, pscale);
1049     OfxRectD leftPoint = rectFromCenterPoint(left, pscale);
1050     OfxRectD rightPoint = rectFromCenterPoint(right, pscale);
1051     OfxRectD topPoint = rectFromCenterPoint(top, pscale);
1052     OfxRectD bottomPoint = rectFromCenterPoint(bottom, pscale);
1053 
1054 
1055     //double dx = args.penPosition.x - _lastMousePos.x;
1056     //double dy = args.penPosition.y - _lastMousePos.y;
1057     double rot = ofxsToRadians(rotate);
1058     Point3D penPos, prevPenPos, rotationPos, transformedPos, previousPos, currentPos;
1059     penPos.x = args.penPosition.x;
1060     penPos.y = args.penPosition.y;
1061     penPos.z = 1.;
1062     prevPenPos.x = _lastMousePos.x;
1063     prevPenPos.y = _lastMousePos.y;
1064     prevPenPos.z = 1.;
1065 
1066     Matrix3x3 rotation, transform, transformscale;
1067     ////for the rotation bar/translation/center dragging we dont use the same transform, we don't want to undo the rotation transform
1068     if ( (_mouseState != eDraggingTranslation) && (_mouseState != eDraggingCenter) ) {
1069         ///undo skew + rotation to the current position
1070         rotation = ofxsMatInverseTransformCanonical(0., 0., 1., 1., 0., 0., false, rot, targetCenter.x, targetCenter.y);
1071         transform = ofxsMatInverseTransformCanonical(0., 0., 1., 1., skewX, skewY, (bool)skewOrder, rot, targetCenter.x, targetCenter.y);
1072         transformscale = ofxsMatInverseTransformCanonical(0., 0., scale.x, scale.y, skewX, skewY, (bool)skewOrder, rot, targetCenter.x, targetCenter.y);
1073     } else {
1074         rotation = ofxsMatInverseTransformCanonical(0., 0., 1., 1., 0., 0., false, 0., targetCenter.x, targetCenter.y);
1075         transform = ofxsMatInverseTransformCanonical(0., 0., 1., 1., skewX, skewY, (bool)skewOrder, 0., targetCenter.x, targetCenter.y);
1076         transformscale = ofxsMatInverseTransformCanonical(0., 0., scale.x, scale.y, skewX, skewY, (bool)skewOrder, 0., targetCenter.x, targetCenter.y);
1077     }
1078 
1079     rotationPos = rotation * penPos;
1080     if (rotationPos.z != 0) {
1081         rotationPos.x /= rotationPos.z;
1082         rotationPos.y /= rotationPos.z;
1083     }
1084 
1085     transformedPos = transform * penPos;
1086     if (transformedPos.z != 0) {
1087         transformedPos.x /= transformedPos.z;
1088         transformedPos.y /= transformedPos.z;
1089     }
1090 
1091     previousPos = transformscale * prevPenPos;
1092     if (previousPos.z != 0) {
1093         previousPos.x /= previousPos.z;
1094         previousPos.y /= previousPos.z;
1095     }
1096 
1097     currentPos = transformscale * penPos;
1098     if (currentPos.z != 0) {
1099         currentPos.x /= currentPos.z;
1100         currentPos.y /= currentPos.z;
1101     }
1102 
1103     if (_mouseState == eReleased) {
1104         // we are not axis-aligned
1105         double meanPixelScale = (pscale.x + pscale.y) / 2.;
1106         double hoverTolerance = (POINT_SIZE / 2.) * meanPixelScale;
1107         if ( squareContains(transformedPos, centerPoint) ) {
1108             _drawState = eCenterPointHovered;
1109             didSomething = true;
1110         } else if ( squareContains(transformedPos, leftPoint) ) {
1111             _drawState = eLeftPointHovered;
1112             didSomething = true;
1113         } else if ( squareContains(transformedPos, rightPoint) ) {
1114             _drawState = eRightPointHovered;
1115             didSomething = true;
1116         } else if ( squareContains(transformedPos, topPoint) ) {
1117             _drawState = eTopPointHovered;
1118             didSomething = true;
1119         } else if ( squareContains(transformedPos, bottomPoint) ) {
1120             _drawState = eBottomPointHovered;
1121             didSomething = true;
1122         } else if ( isOnEllipseBorder(transformedPos, targetRadius, targetCenter) ) {
1123             _drawState = eCircleHovered;
1124             didSomething = true;
1125         } else if ( isOnRotationBar(rotationPos, targetRadius.x, targetCenter, pscale, hoverTolerance) ) {
1126             _drawState = eRotationBarHovered;
1127             didSomething = true;
1128         } else if ( isOnSkewXBar(transformedPos, targetRadius.y, targetCenter, pscale, hoverTolerance) ) {
1129             _drawState = eSkewXBarHoverered;
1130             didSomething = true;
1131         } else if ( isOnSkewYBar(transformedPos, targetRadius.x, targetCenter, pscale, hoverTolerance) ) {
1132             _drawState = eSkewYBarHoverered;
1133             didSomething = true;
1134         } else {
1135             _drawState = eInActive;
1136         }
1137     } else if (_mouseState == eDraggingCircle) {
1138         double minX, minY, maxX, maxY;
1139         _scale->getRange(minX, minY, maxX, maxY);
1140 
1141         // we need to compute the backtransformed points with the scale
1142 
1143         // the scale ratio is the ratio of distances to the center
1144         double prevDistSq = (targetCenter.x - previousPos.x) * (targetCenter.x - previousPos.x) + (targetCenter.y - previousPos.y) * (targetCenter.y - previousPos.y);
1145         if (prevDistSq != 0.) {
1146             const double distSq = (targetCenter.x - currentPos.x) * (targetCenter.x - currentPos.x) + (targetCenter.y - currentPos.y) * (targetCenter.y - currentPos.y);
1147             const double distRatio = std::sqrt( (std::max)(distSq / prevDistSq, 0.) );
1148             scale.x *= distRatio;
1149             scale.y *= distRatio;
1150             //_scale->setValue(scale.x, scale.y);
1151             scaleChanged = true;
1152         }
1153     } else if ( (_mouseState == eDraggingLeftPoint) || (_mouseState == eDraggingRightPoint) ) {
1154         // avoid division by zero
1155         if (targetCenter.x != previousPos.x) {
1156             double minX, minY, maxX, maxY;
1157             _scale->getRange(minX, minY, maxX, maxY);
1158             const double scaleRatio = (targetCenter.x - currentPos.x) / (targetCenter.x - previousPos.x);
1159             OfxPointD newScale;
1160             newScale.x = scale.x * scaleRatio;
1161             newScale.x = (std::max)( minX, (std::min)(newScale.x, maxX) );
1162             newScale.y = scaleUniform ? newScale.x : scale.y;
1163             scale = newScale;
1164             //_scale->setValue(scale.x, scale.y);
1165             scaleChanged = true;
1166         }
1167     } else if ( (_mouseState == eDraggingTopPoint) || (_mouseState == eDraggingBottomPoint) ) {
1168         // avoid division by zero
1169         if (targetCenter.y != previousPos.y) {
1170             double minX, minY, maxX, maxY;
1171             _scale->getRange(minX, minY, maxX, maxY);
1172             const double scaleRatio = (targetCenter.y - currentPos.y) / (targetCenter.y - previousPos.y);
1173             OfxPointD newScale;
1174             newScale.y = scale.y * scaleRatio;
1175             newScale.y = (std::max)( minY, (std::min)(newScale.y, maxY) );
1176             newScale.x = scaleUniform ? newScale.y : scale.x;
1177             scale = newScale;
1178             //_scale->setValue(scale.x, scale.y);
1179             scaleChanged = true;
1180         }
1181     } else if (_mouseState == eDraggingTranslation) {
1182         double dx = args.penPosition.x - _lastMousePos.x;
1183         double dy = args.penPosition.y - _lastMousePos.y;
1184 
1185         if ( (_orientation == eOrientationNotSet) && (_modifierStateShift > 0) ) {
1186             _orientation = std::abs(dx) > std::abs(dy) ? eOrientationHorizontal : eOrientationVertical;
1187         }
1188 
1189         dx = _orientation == eOrientationVertical ? 0 : dx;
1190         dy = _orientation == eOrientationHorizontal ? 0 : dy;
1191         double newx = translate.x + dx;
1192         double newy = translate.y + dy;
1193         // round newx/y to the closest int, 1/10 int, etc
1194         // this make parameter editing easier
1195         newx = fround(newx, pscale.x);
1196         newy = fround(newy, pscale.y);
1197         translate.x = newx;
1198         translate.y = newy;
1199         //_translate->setValue(translate.x, translate.y);
1200         translateChanged = true;
1201     } else if (_mouseState == eDraggingCenter) {
1202         OfxPointD currentCenter = center;
1203         Matrix3x3 R = ofxsMatScale(1. / scale.x, 1. / scale.y) * ofxsMatSkewXY(-skewX, -skewY, !skewOrder) * ofxsMatRotation(rot);
1204         double dx = args.penPosition.x - _lastMousePos.x;
1205         double dy = args.penPosition.y - _lastMousePos.y;
1206 
1207         if ( (_orientation == eOrientationNotSet) && (_modifierStateShift > 0) ) {
1208             _orientation = std::abs(dx) > std::abs(dy) ? eOrientationHorizontal : eOrientationVertical;
1209         }
1210 
1211         dx = _orientation == eOrientationVertical ? 0 : dx;
1212         dy = _orientation == eOrientationHorizontal ? 0 : dy;
1213 
1214         double dxrot, dyrot;
1215         if (!_translate) {
1216             dxrot = dx;
1217             dyrot = dy;
1218         } else {
1219             // if there is a _translate param (i.e. this is Transform/DirBlur and not GodRays),
1220             // compensate the rotation, because the
1221             // interact is visualized on the transformed image
1222             Point3D dRot;
1223             dRot.x = dx;
1224             dRot.y = dy;
1225             dRot.z = 1.;
1226             dRot = R * dRot;
1227             if (dRot.z != 0) {
1228                 dRot.x /= dRot.z;
1229                 dRot.y /= dRot.z;
1230             }
1231             dxrot = dRot.x;
1232             dyrot = dRot.y;
1233         }
1234         double newx = currentCenter.x + dxrot;
1235         double newy = currentCenter.y + dyrot;
1236         // round newx/y to the closest int, 1/10 int, etc
1237         // this make parameter editing easier
1238         newx = fround(newx, pscale.x);
1239         newy = fround(newy, pscale.y);
1240         center.x = newx;
1241         center.y = newy;
1242         //_effect->beginEditBlock("setCenter");
1243         //_center->setValue(center.x, center.y);
1244         centerChanged = true;
1245         if (_translate) {
1246             // recompute dxrot,dyrot after rounding
1247             Matrix3x3 Rinv;
1248             if ( R.inverse(&Rinv) ) {
1249                 dxrot = newx - currentCenter.x;
1250                 dyrot = newy - currentCenter.y;
1251                 Point3D dRot;
1252                 dRot.x = dxrot;
1253                 dRot.y = dyrot;
1254                 dRot.z = 1;
1255                 dRot = Rinv * dRot;
1256                 if (dRot.z != 0) {
1257                     dRot.x /= dRot.z;
1258                     dRot.y /= dRot.z;
1259                 }
1260                 dx = dRot.x;
1261                 dy = dRot.y;
1262                 OfxPointD newTranslation;
1263                 newTranslation.x = translate.x + dx - dxrot;
1264                 newTranslation.y = translate.y + dy - dyrot;
1265                 translate = newTranslation;
1266                 //_translate->setValue(translate.x, translate.y);
1267                 translateChanged = true;
1268             }
1269         }
1270         //_effect->endEditBlock();
1271     } else if (_mouseState == eDraggingRotationBar) {
1272         OfxPointD diffToCenter;
1273         ///the current mouse position (untransformed) is doing has a certain angle relative to the X axis
1274         ///which can be computed by : angle = arctan(opposite / adjacent)
1275         diffToCenter.y = rotationPos.y - targetCenter.y;
1276         diffToCenter.x = rotationPos.x - targetCenter.x;
1277         double angle = std::atan2(diffToCenter.y, diffToCenter.x);
1278         double angledegrees = rotate + ofxsToDegrees(angle);
1279         double closest90 = 90. * std::floor( (angledegrees + 45.) / 90. );
1280         if (std::fabs(angledegrees - closest90) < 5.) {
1281             // snap to closest multiple of 90.
1282             angledegrees = closest90;
1283         }
1284         rotate = angledegrees;
1285         //_rotate->setValue(rotate);
1286         rotateChanged = true;
1287     } else if (_mouseState == eDraggingSkewXBar) {
1288         // avoid division by zero
1289         if ( (scale.y != 0.) && (targetCenter.y != previousPos.y) ) {
1290             const double addSkew = (scale.x / scale.y) * (currentPos.x - previousPos.x) / (currentPos.y - targetCenter.y);
1291             skewX = skewX + addSkew;
1292             //_skewX->setValue(skewX);
1293             skewXChanged = true;
1294         }
1295     } else if (_mouseState == eDraggingSkewYBar) {
1296         // avoid division by zero
1297         if ( (scale.x != 0.) && (targetCenter.x != previousPos.x) ) {
1298             const double addSkew = (scale.y / scale.x) * (currentPos.y - previousPos.y) / (currentPos.x - targetCenter.x);
1299             skewY = skewY + addSkew;
1300             //_skewY->setValue(skewY + addSkew);
1301             skewYChanged = true;
1302         }
1303     } else {
1304         assert(false);
1305     }
1306 
1307     _centerDrag = center;
1308     _translateDrag = translate;
1309     _scaleParamDrag = scale;
1310     _scaleUniformDrag = scaleUniform;
1311     _rotateDrag = rotate;
1312     _skewXDrag = skewX;
1313     _skewYDrag = skewY;
1314     _skewOrderDrag = skewOrder;
1315     _invertedDrag = inverted;
1316 
1317     bool valuesChanged = (centerChanged || translateChanged || scaleChanged || rotateChanged || skewXChanged || skewYChanged);
1318 
1319     if ( (_mouseState != eReleased) && _interactiveDrag && valuesChanged ) {
1320         // no need to redraw overlay since it is slave to the paramaters
1321         bool editBlock = (centerChanged + translateChanged + scaleChanged + rotateChanged + skewXChanged + skewYChanged) > 1;
1322         if (editBlock) {
1323             _effect->beginEditBlock("Set Transform");
1324         }
1325         if (centerChanged) {
1326             _center->setValue(center.x, center.y);
1327         }
1328         if (translateChanged) {
1329             _translate->setValue(translate.x, translate.y);
1330         }
1331         if (scaleChanged) {
1332             _scale->setValue(scale.x, scale.y);
1333         }
1334         if (rotateChanged) {
1335             _rotate->setValue(rotate);
1336         }
1337         if (skewXChanged) {
1338             _skewX->setValue(skewX);
1339         }
1340         if (skewYChanged) {
1341             _skewY->setValue(skewY);
1342         }
1343         if (editBlock) {
1344             _effect->endEditBlock();
1345         }
1346     } else if (didSomething || valuesChanged) {
1347         _interact->requestRedraw();
1348     }
1349 
1350     _lastMousePos = args.penPosition;
1351 
1352     return didSomething || valuesChanged;
1353 } // TransformInteractHelper::penMotion
1354 
1355 bool
penDown(const PenArgs & args)1356 TransformInteractHelper::penDown(const PenArgs &args)
1357 {
1358     if ( !_interactOpen->getValueAtTime(args.time) ) {
1359         return false;
1360     }
1361 
1362     const OfxPointD &pscale = args.pixelScale;
1363     const double time = args.time;
1364     OfxPointD center = { 0., 0. };
1365     OfxPointD translate = { 0., 0. };
1366     OfxPointD scaleParam = { 1., 1. };
1367     bool scaleUniform = false;
1368     double rotate = 0.;
1369     double skewX = 0., skewY = 0.;
1370     int skewOrder = 0;
1371     bool inverted = false;
1372 
1373     if (_mouseState == eReleased) {
1374         if (_center) {
1375             _center->getValueAtTime(time, center.x, center.y);
1376         }
1377         if (_translate) {
1378             _translate->getValueAtTime(time, translate.x, translate.y);
1379         }
1380         if (_scale) {
1381             _scale->getValueAtTime(time, scaleParam.x, scaleParam.y);
1382         }
1383         if (_scaleUniform) {
1384             _scaleUniform->getValueAtTime(time, scaleUniform);
1385         }
1386         if (_rotate) {
1387             _rotate->getValueAtTime(time, rotate);
1388         }
1389         if (_skewX) {
1390             _skewX->getValueAtTime(time, skewX);
1391         }
1392         if (_skewY) {
1393             _skewY->getValueAtTime(time, skewY);
1394         }
1395         if (_skewOrder) {
1396             _skewOrder->getValueAtTime(time, skewOrder);
1397         }
1398         if (_invert) {
1399             _invert->getValueAtTime(time, inverted);
1400         }
1401         if (_interactive) {
1402             _interactive->getValueAtTime(args.time, _interactiveDrag);
1403         }
1404     } else {
1405         center = _centerDrag;
1406         translate = _translateDrag;
1407         scaleParam = _scaleParamDrag;
1408         scaleUniform = _scaleUniformDrag;
1409         rotate = _rotateDrag;
1410         skewX = _skewXDrag;
1411         skewY = _skewYDrag;
1412         skewOrder = _skewOrderDrag;
1413         inverted = _invertedDrag;
1414     }
1415 
1416     OfxPointD targetCenter;
1417     getTargetCenter(center, translate, &targetCenter);
1418 
1419     OfxPointD scale;
1420     ofxsTransformGetScale(scaleParam, scaleUniform, &scale);
1421 
1422     OfxPointD targetRadius;
1423     getTargetRadius(scale, pscale, &targetRadius);
1424 
1425     OfxPointD left, right, bottom, top;
1426     getTargetPoints(targetCenter, targetRadius, &left, &bottom, &top, &right);
1427 
1428     OfxRectD centerPoint = rectFromCenterPoint(targetCenter, pscale);
1429     OfxRectD leftPoint = rectFromCenterPoint(left, pscale);
1430     OfxRectD rightPoint = rectFromCenterPoint(right, pscale);
1431     OfxRectD topPoint = rectFromCenterPoint(top, pscale);
1432     OfxRectD bottomPoint = rectFromCenterPoint(bottom, pscale);
1433     Point3D transformedPos, rotationPos;
1434     transformedPos.x = args.penPosition.x;
1435     transformedPos.y = args.penPosition.y;
1436     transformedPos.z = 1.;
1437 
1438     double rot = ofxsToRadians(rotate);
1439 
1440     ///now undo skew + rotation to the current position
1441     Matrix3x3 rotation, transform;
1442     rotation = ofxsMatInverseTransformCanonical(0., 0., 1., 1., 0., 0., false, rot, targetCenter.x, targetCenter.y);
1443     transform = ofxsMatInverseTransformCanonical(0., 0., 1., 1., skewX, skewY, (bool)skewOrder, rot, targetCenter.x, targetCenter.y);
1444 
1445     rotationPos = rotation * transformedPos;
1446     if (rotationPos.z != 0) {
1447         rotationPos.x /= rotationPos.z;
1448         rotationPos.y /= rotationPos.z;
1449     }
1450     transformedPos = transform * transformedPos;
1451     if (transformedPos.z != 0) {
1452         transformedPos.x /= transformedPos.z;
1453         transformedPos.y /= transformedPos.z;
1454     }
1455 
1456     _orientation = eOrientationAllDirections;
1457 
1458     double pressToleranceX = 5 * pscale.x;
1459     double pressToleranceY = 5 * pscale.y;
1460     bool didSomething = false;
1461     if ( squareContains(transformedPos, centerPoint, pressToleranceX, pressToleranceY) ) {
1462         _mouseState = ( (!_translate || _modifierStateCtrl) ? eDraggingCenter : eDraggingTranslation );
1463         if (_modifierStateShift > 0) {
1464             _orientation = eOrientationNotSet;
1465         }
1466         didSomething = true;
1467     } else if ( squareContains(transformedPos, leftPoint, pressToleranceX, pressToleranceY) ) {
1468         _mouseState = eDraggingLeftPoint;
1469         didSomething = true;
1470     } else if ( squareContains(transformedPos, rightPoint, pressToleranceX, pressToleranceY) ) {
1471         _mouseState = eDraggingRightPoint;
1472         didSomething = true;
1473     } else if ( squareContains(transformedPos, topPoint, pressToleranceX, pressToleranceY) ) {
1474         _mouseState = eDraggingTopPoint;
1475         didSomething = true;
1476     } else if ( squareContains(transformedPos, bottomPoint, pressToleranceX, pressToleranceY) ) {
1477         _mouseState = eDraggingBottomPoint;
1478         didSomething = true;
1479     } else if ( isOnEllipseBorder(transformedPos, targetRadius, targetCenter) ) {
1480         _mouseState = eDraggingCircle;
1481         didSomething = true;
1482     } else if ( isOnRotationBar(rotationPos, targetRadius.x, targetCenter, pscale, pressToleranceY) ) {
1483         _mouseState = eDraggingRotationBar;
1484         didSomething = true;
1485     } else if ( isOnSkewXBar(transformedPos, targetRadius.y, targetCenter, pscale, pressToleranceY) ) {
1486         _mouseState = eDraggingSkewXBar;
1487         didSomething = true;
1488     } else if ( isOnSkewYBar(transformedPos, targetRadius.x, targetCenter, pscale, pressToleranceX) ) {
1489         _mouseState = eDraggingSkewYBar;
1490         didSomething = true;
1491     } else {
1492         _mouseState = eReleased;
1493     }
1494 
1495     _lastMousePos = args.penPosition;
1496 
1497     _centerDrag = center;
1498     _translateDrag = translate;
1499     _scaleParamDrag = scaleParam;
1500     _scaleUniformDrag = scaleUniform;
1501     _rotateDrag = rotate;
1502     _skewXDrag = skewX;
1503     _skewYDrag = skewY;
1504     _skewOrderDrag = skewOrder;
1505     _invertedDrag = inverted;
1506 
1507     if (didSomething) {
1508         _interact->requestRedraw();
1509     }
1510 
1511     return didSomething;
1512 } // TransformInteractHelper::penDown
1513 
1514 bool
penUp(const PenArgs & args)1515 TransformInteractHelper::penUp(const PenArgs &args)
1516 {
1517     if ( !_interactOpen->getValueAtTime(args.time) ) {
1518         return false;
1519     }
1520     bool ret = _mouseState != eReleased;
1521 
1522     if ( !_interactiveDrag && (_mouseState != eReleased) ) {
1523         // no need to redraw overlay since it is slave to the paramaters
1524         _effect->beginEditBlock("Set Transform");
1525         if (_center) {
1526             _center->setValue(_centerDrag.x, _centerDrag.y);
1527         }
1528         if (_translate) {
1529             _translate->setValue(_translateDrag.x, _translateDrag.y);
1530         }
1531         if (_scale) {
1532             _scale->setValue(_scaleParamDrag.x, _scaleParamDrag.y);
1533         }
1534         if (_rotate) {
1535             _rotate->setValue(_rotateDrag);
1536         }
1537         if (_skewX) {
1538             _skewX->setValue(_skewXDrag);
1539         }
1540         if (_skewY) {
1541             _skewY->setValue(_skewYDrag);
1542         }
1543         _effect->endEditBlock();
1544     } else if (_mouseState != eReleased) {
1545         _interact->requestRedraw();
1546     }
1547 
1548     _mouseState = eReleased;
1549     _lastMousePos = args.penPosition;
1550 
1551     return ret;
1552 }
1553 
1554 // keyDown just updates the modifier state
1555 bool
keyDown(const KeyArgs & args)1556 TransformInteractHelper::keyDown(const KeyArgs &args)
1557 {
1558     // Always process, even if interact is not open, since this concerns modifiers
1559     //if (!_interactOpen->getValueAtTime(args.time)) {
1560     //    return false;
1561     //}
1562 
1563     // Note that on the Mac:
1564     // cmd/apple/cloverleaf is kOfxKey_Control_L
1565     // ctrl is kOfxKey_Meta_L
1566     // alt/option is kOfxKey_Alt_L
1567     bool mustRedraw = false;
1568 
1569     // the two control keys may be pressed consecutively, be aware about this
1570     if ( _translate && ( (args.keySymbol == kOfxKey_Control_L) || (args.keySymbol == kOfxKey_Control_R) ) ) {
1571         mustRedraw = (_modifierStateCtrl == 0);
1572         ++_modifierStateCtrl;
1573     }
1574     if ( (args.keySymbol == kOfxKey_Shift_L) || (args.keySymbol == kOfxKey_Shift_R) ) {
1575         mustRedraw = (_modifierStateShift == 0);
1576         ++_modifierStateShift;
1577         if (_modifierStateShift > 0) {
1578             _orientation = eOrientationNotSet;
1579         }
1580     }
1581     if (mustRedraw) {
1582         _interact->requestRedraw();
1583     }
1584     //std::cout << std::hex << args.keySymbol << std::endl;
1585 
1586     // modifiers are not "caught"
1587     return false;
1588 }
1589 
1590 // keyUp just updates the modifier state
1591 bool
keyUp(const KeyArgs & args)1592 TransformInteractHelper::keyUp(const KeyArgs &args)
1593 {
1594     // Always process, even if interact is not open, since this concerns modifiers
1595     //if (!_interactOpen->getValueAtTime(args.time)) {
1596     //    return false;
1597     //}
1598 
1599     bool mustRedraw = false;
1600 
1601     if ( _translate && ( (args.keySymbol == kOfxKey_Control_L) || (args.keySymbol == kOfxKey_Control_R) ) ) {
1602         // we may have missed a keypress
1603         if (_modifierStateCtrl > 0) {
1604             --_modifierStateCtrl;
1605             mustRedraw = (_modifierStateCtrl == 0);
1606         }
1607     }
1608     if ( (args.keySymbol == kOfxKey_Shift_L) || (args.keySymbol == kOfxKey_Shift_R) ) {
1609         if (_modifierStateShift > 0) {
1610             --_modifierStateShift;
1611             mustRedraw = (_modifierStateShift == 0);
1612         }
1613         if (_modifierStateShift == 0) {
1614             _orientation = eOrientationAllDirections;
1615         }
1616     }
1617     if (mustRedraw) {
1618         _interact->requestRedraw();
1619     }
1620 
1621     // modifiers are not "caught"
1622     return false;
1623 }
1624 
1625 /** @brief Called when the interact is loses input focus */
1626 void
loseFocus(const FocusArgs &)1627 TransformInteractHelper::loseFocus(const FocusArgs & /*args*/)
1628 {
1629     // reset the modifiers state
1630     if (_translate) {
1631         _modifierStateCtrl = 0;
1632     }
1633     _modifierStateShift = 0;
1634     _interactiveDrag = false;
1635     _mouseState = eReleased;
1636     _drawState = eInActive;
1637 }
1638 } // namespace OFX
1639