1 /* ***** BEGIN LICENSE BLOCK *****
2  * This file is part of openfx-supportext <https://github.com/devernay/openfx-supportext>,
3  * Copyright (C) 2013-2016 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 TransformInteractCustom.
21  *
22  * Based on ofxTransformInteract from openfx-supportext, modified for OCL.ofx
23  *
24  */
25 
26 #include "ofxsTransformInteractCustom.h"
27 
28 #include <memory>
29 #include <cmath>
30 #include <cfloat> // DBL_MAX
31 #include <algorithm>
32 
33 #ifdef __APPLE__
34 #include <OpenGL/gl.h>
35 #else
36 #include <GL/gl.h>
37 #endif
38 
39 #include "ofxsMatrix2D.h"
40 #include "ofxsTransform3x3.h"
41 
42 #define SCALE_MAX 10000.
43 
44 #define CIRCLE_RADIUS_BASE 30.
45 #define CIRCLE_RADIUS_MIN 15.
46 #define CIRCLE_RADIUS_MAX 300.
47 #define POINT_SIZE 7.
48 #define ELLIPSE_N_POINTS 50.
49 
50 namespace OFX {
51 /// add Transform params. page and group are optional
52 void
ofxsTransformDescribeParams(OFX::ImageEffectDescriptor & desc,OFX::PageParamDescriptor * page,OFX::GroupParamDescriptor * group,bool isOpen,bool oldParams,bool noTranslate,bool uniform,double rotateDefault)53 ofxsTransformDescribeParams(OFX::ImageEffectDescriptor &desc,
54                             OFX::PageParamDescriptor *page,
55                             OFX::GroupParamDescriptor *group,
56                             bool isOpen,
57                             bool oldParams,
58                             bool noTranslate,
59                             bool uniform,
60                             double rotateDefault)
61 {
62     // translate
63     if (!noTranslate) {
64         Double2DParamDescriptor* param = desc.defineDouble2DParam(oldParams ? kParamTransformTranslateOld : kParamTransformTranslate);
65         param->setLabel(kParamTransformTranslateLabel);
66         //param->setDoubleType(eDoubleTypeNormalisedXY); // deprecated in OpenFX 1.2
67         param->setDoubleType(eDoubleTypeXYAbsolute);
68         param->setDefaultCoordinateSystem(eCoordinatesNormalised);
69         //param->setDimensionLabels("x","y");
70         param->setDefault(0, 0);
71         param->setRange(-DBL_MAX, -DBL_MAX, DBL_MAX, DBL_MAX); // Resolve requires range and display range or values are clamped to (-1,1)
72         param->setDisplayRange(-10000, -10000, 10000, 10000); // Resolve requires display range or values are clamped to (-1,1)
73         param->setIncrement(10.);
74         if (group) {
75             param->setParent(*group);
76         }
77         if (page) {
78             page->addChild(*param);
79         }
80     }
81 
82     // rotate
83     {
84         DoubleParamDescriptor* param = desc.defineDoubleParam(oldParams ? kParamTransformRotateOld : kParamTransformRotate);
85         param->setLabel(kParamTransformRotateLabel);
86         param->setDoubleType(eDoubleTypeAngle);
87         param->setDefault(rotateDefault);
88         param->setRange(-DBL_MAX, DBL_MAX); // Resolve requires range and display range or values are clamped to (-1,1)
89         param->setDisplayRange(-180, 180);
90         param->setIncrement(0.1);
91         if (group) {
92             param->setParent(*group);
93         }
94         if (page) {
95             page->addChild(*param);
96         }
97     }
98 
99     // scale
100     {
101         Double2DParamDescriptor* param = desc.defineDouble2DParam(oldParams ? kParamTransformScaleOld : kParamTransformScale);
102         param->setLabel(kParamTransformScaleLabel);
103         param->setDoubleType(eDoubleTypeScale);
104         //param->setDimensionLabels("w","h");
105         param->setDefault(1, 1);
106         param->setRange(-SCALE_MAX, -SCALE_MAX, SCALE_MAX, SCALE_MAX);
107         param->setDisplayRange(0.1, 0.1, 10, 10);
108         param->setIncrement(0.01);
109         param->setLayoutHint(OFX::eLayoutHintNoNewLine, 1);
110         if (group) {
111             param->setParent(*group);
112         }
113         if (page) {
114             page->addChild(*param);
115         }
116     }
117 
118     // scaleUniform
119     {
120         BooleanParamDescriptor* param = desc.defineBooleanParam(oldParams ? kParamTransformScaleUniformOld : kParamTransformScaleUniform);
121         param->setLabel(kParamTransformScaleUniformLabel);
122         param->setHint(kParamTransformScaleUniformHint);
123         param->setDefault(uniform);
124         param->setAnimates(true);
125         if (group) {
126             param->setParent(*group);
127         }
128         if (page) {
129             page->addChild(*param);
130         }
131     }
132 
133     // center
134     {
135         Double2DParamDescriptor* param = desc.defineDouble2DParam(oldParams ? kParamTransformCenterOld : kParamTransformCenter);
136         param->setLabel(kParamTransformCenterLabel);
137         //param->setDoubleType(eDoubleTypeNormalisedXY); // deprecated in OpenFX 1.2
138         //param->setDimensionLabels("x","y");
139         param->setDoubleType(eDoubleTypeXYAbsolute);
140         param->setDefaultCoordinateSystem(eCoordinatesNormalised);
141         param->setDefault(0.5, 0.5);
142         param->setRange(-DBL_MAX, -DBL_MAX, DBL_MAX, DBL_MAX); // Resolve requires range and display range or values are clamped to (-1,1)
143         param->setDisplayRange(-10000, -10000, 10000, 10000); // Resolve requires display range or values are clamped to (-1,1)
144         param->setIncrement(1.);
145         param->setLayoutHint(eLayoutHintNoNewLine, 1);
146         if (group) {
147             param->setParent(*group);
148         }
149         if (page) {
150             page->addChild(*param);
151         }
152     }
153 
154     // resetcenter
155     {
156         PushButtonParamDescriptor* param = desc.definePushButtonParam(oldParams ? kParamTransformResetCenterOld : kParamTransformResetCenter);
157         param->setLabel(kParamTransformResetCenterLabel);
158         param->setHint(kParamTransformResetCenterHint);
159         if (group) {
160             param->setParent(*group);
161         }
162         if (page) {
163             page->addChild(*param);
164         }
165     }
166 
167     // interactOpen
168     {
169         BooleanParamDescriptor* param = desc.defineBooleanParam(kParamTransformInteractCustomOpen);
170         param->setLabel(kParamTransformInteractCustomOpenLabel);
171         param->setHint(kParamTransformInteractCustomOpenHint);
172         param->setDefault(isOpen); // open by default
173         param->setIsSecret(true); // secret by default, but this can be changed for specific hosts
174         param->setAnimates(false);
175         if (group) {
176             param->setParent(*group);
177         }
178         if (page) {
179             page->addChild(*param);
180         }
181     }
182 
183     // interactive
184     {
185         BooleanParamDescriptor* param = desc.defineBooleanParam(oldParams ? kParamTransformInteractCustomiveOld : kParamTransformInteractCustomive);
186         param->setLabel(kParamTransformInteractCustomiveLabel);
187         param->setHint(kParamTransformInteractCustomiveHint);
188         param->setDefault(true);
189         param->setEvaluateOnChange(false);
190         param->setLayoutHint(OFX::eLayoutHintDivider);
191         if (group) {
192             param->setParent(*group);
193         }
194         if (page) {
195             page->addChild(*param);
196         }
197     }
198 } // ofxsTransformDescribeParams
199 
200 ////////////////////////////////////////////////////////////////////////////////
201 // stuff for the interact
202 
TransformInteractCustomHelper(OFX::ImageEffect * effect,OFX::Interact * interact,bool oldParams)203 TransformInteractCustomHelper::TransformInteractCustomHelper(OFX::ImageEffect* effect,
204                                                  OFX::Interact* interact,
205                                                  bool oldParams)
206     : _drawState(eInActive)
207     , _mouseState(eReleased)
208     , _modifierStateCtrl(0)
209     , _modifierStateShift(0)
210     , _orientation(eOrientationAllDirections)
211     , _effect(effect)
212     , _interact(interact)
213     , _lastMousePos()
214     , _scaleUniformDrag(0)
215     , _rotateDrag(0)
216     , _invertedDrag(0)
217     , _interactiveDrag(false)
218     , _translate(0)
219     , _rotate(0)
220     , _scale(0)
221     , _scaleUniform(0)
222     , _center(0)
223     , _invert(0)
224     , _interactOpen(0)
225     , _interactive(0)
226 {
227     assert(_effect && _interact);
228     _lastMousePos.x = _lastMousePos.y = 0.;
229     // NON-GENERIC
230     if (oldParams) {
231         if ( _effect->paramExists(kParamTransformTranslateOld) ) {
232             _translate = _effect->fetchDouble2DParam(kParamTransformTranslateOld);
233             assert(_translate);
234         }
235         _rotate = _effect->fetchDoubleParam(kParamTransformRotateOld);
236         _scale = _effect->fetchDouble2DParam(kParamTransformScaleOld);
237         _scaleUniform = _effect->fetchBooleanParam(kParamTransformScaleUniformOld);
238         _center = _effect->fetchDouble2DParam(kParamTransformCenterOld);
239         _interactive = _effect->fetchBooleanParam(kParamTransformInteractCustomiveOld);
240     } else {
241         if ( _effect->paramExists(kParamTransformTranslate) ) {
242             _translate = _effect->fetchDouble2DParam(kParamTransformTranslate);
243             assert(_translate);
244         }
245         _rotate = _effect->fetchDoubleParam(kParamTransformRotate);
246         _scale = _effect->fetchDouble2DParam(kParamTransformScale);
247         _scaleUniform = _effect->fetchBooleanParam(kParamTransformScaleUniform);
248         _center = _effect->fetchDouble2DParam(kParamTransformCenter);
249         _interactive = _effect->fetchBooleanParam(kParamTransformInteractCustomive);
250     }
251     _interactOpen = _effect->fetchBooleanParam(kParamTransformInteractCustomOpen);
252     if ( _effect->paramExists(kParamTransform3x3Invert) ) {
253         _invert = _effect->fetchBooleanParam(kParamTransform3x3Invert);
254     }
255     assert(_rotate && _scale && _scaleUniform && _center && _interactive);
256     if (_translate) {
257         _interact->addParamToSlaveTo(_translate);
258     }
259     if (_rotate) {
260         _interact->addParamToSlaveTo(_rotate);
261     }
262     if (_scale) {
263         _interact->addParamToSlaveTo(_scale);
264     }
265     if (_center) {
266         _interact->addParamToSlaveTo(_center);
267     }
268     if (_invert) {
269         _interact->addParamToSlaveTo(_invert);
270     }
271     if (!_translate) {
272         _modifierStateCtrl = 1;
273     }
274     _centerDrag.x = _centerDrag.y = 0.;
275     _translateDrag.x = _translateDrag.y = 0.;
276     _scaleParamDrag.x = _scaleParamDrag.y = 0.;
277 }
278 
279 static void
getTargetCenter(const OfxPointD & center,const OfxPointD & translate,OfxPointD * targetCenter)280 getTargetCenter(const OfxPointD &center,
281                 const OfxPointD &translate,
282                 OfxPointD *targetCenter)
283 {
284     targetCenter->x = center.x + translate.x;
285     targetCenter->y = center.y + translate.y;
286 }
287 
288 static void
getTargetRadius(const OfxPointD & scale,const OfxPointD & pixelScale,OfxPointD * targetRadius)289 getTargetRadius(const OfxPointD& scale,
290                 const OfxPointD& pixelScale,
291                 OfxPointD* targetRadius)
292 {
293     targetRadius->x = scale.x * CIRCLE_RADIUS_BASE;
294     targetRadius->y = scale.y * CIRCLE_RADIUS_BASE;
295     // don't draw too small. 15 pixels is the limit
296     if ( (std::fabs(targetRadius->x) < CIRCLE_RADIUS_MIN) && (std::fabs(targetRadius->y) < CIRCLE_RADIUS_MIN) ) {
297         targetRadius->x = targetRadius->x >= 0 ? CIRCLE_RADIUS_MIN : -CIRCLE_RADIUS_MIN;
298         targetRadius->y = targetRadius->y >= 0 ? CIRCLE_RADIUS_MIN : -CIRCLE_RADIUS_MIN;
299     } else if ( (std::fabs(targetRadius->x) > CIRCLE_RADIUS_MAX) && (std::fabs(targetRadius->y) > CIRCLE_RADIUS_MAX) ) {
300         targetRadius->x = targetRadius->x >= 0 ? CIRCLE_RADIUS_MAX : -CIRCLE_RADIUS_MAX;
301         targetRadius->y = targetRadius->y >= 0 ? CIRCLE_RADIUS_MAX : -CIRCLE_RADIUS_MAX;
302     } else {
303         if (std::fabs(targetRadius->x) < CIRCLE_RADIUS_MIN) {
304             if ( (targetRadius->x == 0.) && (targetRadius->y != 0.) ) {
305                 targetRadius->y = targetRadius->y > 0 ? CIRCLE_RADIUS_MAX : -CIRCLE_RADIUS_MAX;
306             } else {
307                 targetRadius->y *= std::fabs(CIRCLE_RADIUS_MIN / targetRadius->x);
308             }
309             targetRadius->x = targetRadius->x >= 0 ? CIRCLE_RADIUS_MIN : -CIRCLE_RADIUS_MIN;
310         }
311         if (std::fabs(targetRadius->x) > CIRCLE_RADIUS_MAX) {
312             targetRadius->y *= std::fabs(CIRCLE_RADIUS_MAX / targetRadius->x);
313             targetRadius->x = targetRadius->x > 0 ? CIRCLE_RADIUS_MAX : -CIRCLE_RADIUS_MAX;
314         }
315         if (std::fabs(targetRadius->y) < CIRCLE_RADIUS_MIN) {
316             if ( (targetRadius->y == 0.) && (targetRadius->x != 0.) ) {
317                 targetRadius->x = targetRadius->x > 0 ? CIRCLE_RADIUS_MAX : -CIRCLE_RADIUS_MAX;
318             } else {
319                 targetRadius->x *= std::fabs(CIRCLE_RADIUS_MIN / targetRadius->y);
320             }
321             targetRadius->y = targetRadius->y >= 0 ? CIRCLE_RADIUS_MIN : -CIRCLE_RADIUS_MIN;
322         }
323         if (std::fabs(targetRadius->y) > CIRCLE_RADIUS_MAX) {
324             targetRadius->x *= std::fabs(CIRCLE_RADIUS_MAX / targetRadius->x);
325             targetRadius->y = targetRadius->y > 0 ? CIRCLE_RADIUS_MAX : -CIRCLE_RADIUS_MAX;
326         }
327     }
328     // the circle axes are not aligned with the images axes, so we cannot use the x and y scales separately
329     double meanPixelScale = (pixelScale.x + pixelScale.y) / 2.;
330     targetRadius->x *= meanPixelScale;
331     targetRadius->y *= meanPixelScale;
332 }
333 
334 static void
getTargetPoints(const OfxPointD & targetCenter,const OfxPointD & targetRadius,OfxPointD * left,OfxPointD * bottom,OfxPointD * top,OfxPointD * right)335 getTargetPoints(const OfxPointD& targetCenter,
336                 const OfxPointD& targetRadius,
337                 OfxPointD *left,
338                 OfxPointD *bottom,
339                 OfxPointD *top,
340                 OfxPointD *right)
341 {
342     left->x = targetCenter.x - targetRadius.x;
343     left->y = targetCenter.y;
344     right->x = targetCenter.x + targetRadius.x;
345     right->y = targetCenter.y;
346     top->x = targetCenter.x;
347     top->y = targetCenter.y + targetRadius.y;
348     bottom->x = targetCenter.x;
349     bottom->y = targetCenter.y - targetRadius.y;
350 }
351 
352 static void
drawSquare(const OfxRGBColourD & color,const OfxPointD & center,const OfxPointD & pixelScale,bool hovered,bool althovered,int l)353 drawSquare(const OfxRGBColourD& color,
354            const OfxPointD& center,
355            const OfxPointD& pixelScale,
356            bool hovered,
357            bool althovered,
358            int l)
359 {
360     // we are not axis-aligned
361     double meanPixelScale = (pixelScale.x + pixelScale.y) / 2.;
362 
363     if (hovered) {
364         if (althovered) {
365             glColor3f(0.f * l, 1.f * l, 0.f * l);
366         } else {
367             glColor3f(1.f * l, 0.f * l, 0.f * l);
368         }
369     } else {
370         glColor3f( (float)color.r * l, (float)color.g * l, (float)color.b * l );
371     }
372     double halfWidth = (POINT_SIZE / 2.) * meanPixelScale;
373     double halfHeight = (POINT_SIZE / 2.) * meanPixelScale;
374     glPushMatrix();
375     glTranslated(center.x, center.y, 0.);
376     glBegin(GL_POLYGON);
377     glVertex2d(-halfWidth, -halfHeight);   // bottom left
378     glVertex2d(-halfWidth, +halfHeight);   // top left
379     glVertex2d(+halfWidth, +halfHeight);   // bottom right
380     glVertex2d(+halfWidth, -halfHeight);   // top right
381     glEnd();
382     glPopMatrix();
383 }
384 
385 static void
drawEllipse(const OfxRGBColourD & color,const OfxPointD & center,const OfxPointD & targetRadius,bool hovered,int l)386 drawEllipse(const OfxRGBColourD& color,
387             const OfxPointD& center,
388             const OfxPointD& targetRadius,
389             bool hovered,
390             int l)
391 {
392     if (hovered) {
393         glColor3f(1.f * l, 0.f * l, 0.f * l);
394     } else {
395         glColor3f( (float)color.r * l, (float)color.g * l, (float)color.b * l );
396     }
397 
398     glPushMatrix();
399     //  center the oval at x_center, y_center
400     glTranslatef( (float)center.x, (float)center.y, 0.f );
401     //  draw the oval using line segments
402     glBegin(GL_LINE_LOOP);
403     // we don't need to be pixel-perfect here, it's just an interact!
404     // 40 segments is enough.
405     for (int i = 0; i < 40; ++i) {
406         double theta = i * 2 * OFX::ofxsPi() / 40.;
407         glVertex2d( targetRadius.x * std::cos(theta), targetRadius.y * std::sin(theta) );
408     }
409     glEnd();
410 
411     glPopMatrix();
412 }
413 
414 static void
drawRotationBar(const OfxRGBColourD & color,const OfxPointD & pixelScale,double targetRadiusX,bool hovered,bool inverted,int l)415 drawRotationBar(const OfxRGBColourD& color,
416                 const OfxPointD& pixelScale,
417                 double targetRadiusX,
418                 bool hovered,
419                 bool inverted,
420                 int l)
421 {
422     // we are not axis-aligned
423     double meanPixelScale = (pixelScale.x + pixelScale.y) / 2.;
424 
425     if (hovered) {
426         glColor3f(1.f * l, 0.f * l, 0.f * l);
427     } else {
428         glColor3f(color.r * l, color.g * l, color.b * l);
429     }
430 
431     double barExtra = 30. * meanPixelScale;
432     glBegin(GL_LINES);
433     glVertex2d(0., 0.);
434     glVertex2d(0. + targetRadiusX + barExtra, 0.);
435     glEnd();
436 
437     if (hovered) {
438         double arrowCenterX = targetRadiusX + barExtra / 2.;
439 
440         ///draw an arrow slightly bended. This is an arc of circle of radius 5 in X, and 10 in Y.
441         OfxPointD arrowRadius;
442         arrowRadius.x = 5. * meanPixelScale;
443         arrowRadius.y = 10. * meanPixelScale;
444 
445         glPushMatrix();
446         //  center the oval at x_center, y_center
447         glTranslatef( (float)arrowCenterX, 0.f, 0 );
448         //  draw the oval using line segments
449         glBegin(GL_LINE_STRIP);
450         glVertex2d(0, arrowRadius.y);
451         glVertex2d(arrowRadius.x, 0.);
452         glVertex2d(0, -arrowRadius.y);
453         glEnd();
454 
455 
456         glBegin(GL_LINES);
457         ///draw the top head
458         glVertex2d(0., arrowRadius.y);
459         glVertex2d(0., arrowRadius.y - 5. * meanPixelScale);
460 
461         glVertex2d(0., arrowRadius.y);
462         glVertex2d(4. * meanPixelScale, arrowRadius.y - 3. * meanPixelScale); // 5^2 = 3^2+4^2
463 
464         ///draw the bottom head
465         glVertex2d(0., -arrowRadius.y);
466         glVertex2d(0., -arrowRadius.y + 5. * meanPixelScale);
467 
468         glVertex2d(0., -arrowRadius.y);
469         glVertex2d(4. * meanPixelScale, -arrowRadius.y + 3. * meanPixelScale); // 5^2 = 3^2+4^2
470 
471         glEnd();
472 
473         glPopMatrix();
474     }
475     if (inverted) {
476         double arrowXPosition = targetRadiusX + barExtra * 1.5;
477         double arrowXHalfSize = 10 * meanPixelScale;
478         double arrowHeadOffsetX = 3 * meanPixelScale;
479         double arrowHeadOffsetY = 3 * meanPixelScale;
480 
481         glPushMatrix();
482         glTranslatef( (float)arrowXPosition, 0, 0 );
483 
484         glBegin(GL_LINES);
485         ///draw the central bar
486         glVertex2d(-arrowXHalfSize, 0.);
487         glVertex2d(+arrowXHalfSize, 0.);
488 
489         ///left triangle
490         glVertex2d(-arrowXHalfSize, 0.);
491         glVertex2d(-arrowXHalfSize + arrowHeadOffsetX, arrowHeadOffsetY);
492 
493         glVertex2d(-arrowXHalfSize, 0.);
494         glVertex2d(-arrowXHalfSize + arrowHeadOffsetX, -arrowHeadOffsetY);
495 
496         ///right triangle
497         glVertex2d(+arrowXHalfSize, 0.);
498         glVertex2d(+arrowXHalfSize - arrowHeadOffsetX, arrowHeadOffsetY);
499 
500         glVertex2d(+arrowXHalfSize, 0.);
501         glVertex2d(+arrowXHalfSize - arrowHeadOffsetX, -arrowHeadOffsetY);
502         glEnd();
503 
504         glRotated(90., 0., 0., 1.);
505 
506         glBegin(GL_LINES);
507         ///draw the central bar
508         glVertex2d(-arrowXHalfSize, 0.);
509         glVertex2d(+arrowXHalfSize, 0.);
510 
511         ///left triangle
512         glVertex2d(-arrowXHalfSize, 0.);
513         glVertex2d(-arrowXHalfSize + arrowHeadOffsetX, arrowHeadOffsetY);
514 
515         glVertex2d(-arrowXHalfSize, 0.);
516         glVertex2d(-arrowXHalfSize + arrowHeadOffsetX, -arrowHeadOffsetY);
517 
518         ///right triangle
519         glVertex2d(+arrowXHalfSize, 0.);
520         glVertex2d(+arrowXHalfSize - arrowHeadOffsetX, arrowHeadOffsetY);
521 
522         glVertex2d(+arrowXHalfSize, 0.);
523         glVertex2d(+arrowXHalfSize - arrowHeadOffsetX, -arrowHeadOffsetY);
524         glEnd();
525 
526         glPopMatrix();
527     }
528 } // drawRotationBar
529 
530 // draw the interact
531 bool
draw(const OFX::DrawArgs & args)532 TransformInteractCustomHelper::draw(const OFX::DrawArgs &args)
533 {
534     if ( !_interactOpen->getValueAtTime(args.time) ) {
535         return false;
536     }
537     const OfxPointD &pscale = args.pixelScale;
538     const double time = args.time;
539     OfxRGBColourD color = { 0.8, 0.8, 0.8 };
540     _interact->getSuggestedColour(color);
541     GLdouble projection[16];
542     glGetDoublev( GL_PROJECTION_MATRIX, projection);
543     GLint viewport[4];
544     glGetIntegerv(GL_VIEWPORT, viewport);
545     OfxPointD shadow; // how much to translate GL_PROJECTION to get exactly one pixel on screen
546     shadow.x = 2. / (projection[0] * viewport[2]);
547     shadow.y = 2. / (projection[5] * viewport[3]);
548 
549     OfxPointD center = { 0., 0. };
550     OfxPointD translate = { 0., 0. };
551     OfxPointD scaleParam = { 1., 1. };
552     bool scaleUniform = false;
553     double rotate = 0.;
554     bool inverted = false;
555 
556     if (_mouseState == eReleased) {
557         if (_center) {
558             _center->getValueAtTime(time, center.x, center.y);
559         }
560         if (_translate) {
561             _translate->getValueAtTime(time, translate.x, translate.y);
562         }
563         if (_scale) {
564             _scale->getValueAtTime(time, scaleParam.x, scaleParam.y);
565         }
566         if (_scaleUniform) {
567             _scaleUniform->getValueAtTime(time, scaleUniform);
568         }
569         if (_rotate) {
570             _rotate->getValueAtTime(time, rotate);
571         }
572         if (_invert) {
573             _invert->getValueAtTime(time, inverted);
574         }
575     } else {
576         center = _centerDrag;
577         translate = _translateDrag;
578         scaleParam = _scaleParamDrag;
579         scaleUniform = _scaleUniformDrag;
580         rotate = _rotateDrag;
581         inverted = _invertedDrag;
582     }
583 
584     OfxPointD targetCenter;
585     getTargetCenter(center, translate, &targetCenter);
586 
587     OfxPointD scale;
588     ofxsTransformGetScale(scaleParam, scaleUniform, &scale);
589 
590     OfxPointD targetRadius;
591     getTargetRadius(scale, pscale, &targetRadius);
592 
593     OfxPointD left, right, bottom, top;
594     getTargetPoints(targetCenter, targetRadius, &left, &bottom, &top, &right);
595 
596     //glPushAttrib(GL_ALL_ATTRIB_BITS); // caller is responsible for protecting attribs
597 
598     glDisable(GL_LINE_STIPPLE);
599     glEnable(GL_LINE_SMOOTH);
600     glDisable(GL_POINT_SMOOTH);
601     glEnable(GL_BLEND);
602     glHint(GL_LINE_SMOOTH_HINT, GL_DONT_CARE);
603     glLineWidth(1.5f);
604     glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
605 
606     // Draw everything twice
607     // l = 0: shadow
608     // l = 1: drawing
609     for (int l = 0; l < 2; ++l) {
610         // shadow (uses GL_PROJECTION)
611         glMatrixMode(GL_PROJECTION);
612         int direction = (l == 0) ? 1 : -1;
613         // translate (1,-1) pixels
614         glTranslated(direction * shadow.x, -direction * shadow.y, 0);
615         glMatrixMode(GL_MODELVIEW); // Modelview should be used on Nuke
616 
617         glColor3f(color.r * l, color.g * l, color.b * l);
618 
619         glPushMatrix();
620         glTranslated(targetCenter.x, targetCenter.y, 0.);
621 
622         glRotated(rotate, 0, 0., 1.);
623         drawRotationBar(color, pscale, targetRadius.x, _mouseState == eDraggingRotationBar || _drawState == eRotationBarHovered, inverted, l);
624         //glMultMatrixd(skewMatrix);
625         glTranslated(-targetCenter.x, -targetCenter.y, 0.);
626 
627         drawEllipse(color, targetCenter, targetRadius, _mouseState == eDraggingCircle || _drawState == eCircleHovered, l);
628 
629         drawSquare(color, targetCenter, pscale, _mouseState == eDraggingTranslation || _mouseState == eDraggingCenter || _drawState == eCenterPointHovered, (!_translate || _modifierStateCtrl), l);
630 
631         glPopMatrix();
632     }
633     //glPopAttrib();
634 
635     return true;
636 } // TransformInteractCustomHelper::draw
637 
638 static bool
squareContains(const OFX::Point3D & pos,const OfxRectD & rect,double toleranceX=0.,double toleranceY=0.)639 squareContains(const OFX::Point3D& pos,
640                const OfxRectD& rect,
641                double toleranceX = 0.,
642                double toleranceY = 0.)
643 {
644     return ( pos.x >= (rect.x1 - toleranceX) && pos.x < (rect.x2 + toleranceX)
645              && pos.y >= (rect.y1 - toleranceY) && pos.y < (rect.y2 + toleranceY) );
646 }
647 
648 static bool
isOnEllipseBorder(const OFX::Point3D & pos,const OfxPointD & targetRadius,const OfxPointD & targetCenter,double epsilon=0.1)649 isOnEllipseBorder(const OFX::Point3D& pos,
650                   const OfxPointD& targetRadius,
651                   const OfxPointD& targetCenter,
652                   double epsilon = 0.1)
653 {
654     double v = ( (pos.x - targetCenter.x) * (pos.x - targetCenter.x) / (targetRadius.x * targetRadius.x) +
655                  (pos.y - targetCenter.y) * (pos.y - targetCenter.y) / (targetRadius.y * targetRadius.y) );
656 
657     if ( ( v <= (1. + epsilon) ) && ( v >= (1. - epsilon) ) ) {
658         return true;
659     }
660 
661     return false;
662 }
663 
664 static bool
isOnRotationBar(const OFX::Point3D & pos,double targetRadiusX,const OfxPointD & center,const OfxPointD & pixelScale,double tolerance)665 isOnRotationBar(const OFX::Point3D& pos,
666                 double targetRadiusX,
667                 const OfxPointD& center,
668                 const OfxPointD& pixelScale,
669                 double tolerance)
670 {
671     // we are not axis-aligned
672     double meanPixelScale = (pixelScale.x + pixelScale.y) / 2.;
673     double barExtra = 30. * meanPixelScale;
674 
675     if ( ( pos.x >= (center.x - tolerance) ) && ( pos.x <= (center.x + targetRadiusX + barExtra + tolerance) ) &&
676          ( pos.y >= (center.y  - tolerance) ) && ( pos.y <= (center.y + tolerance) ) ) {
677         return true;
678     }
679 
680     return false;
681 }
682 
683 static OfxRectD
rectFromCenterPoint(const OfxPointD & center,const OfxPointD & pixelScale)684 rectFromCenterPoint(const OfxPointD& center,
685                     const OfxPointD& pixelScale)
686 {
687     // we are not axis-aligned
688     double meanPixelScale = (pixelScale.x + pixelScale.y) / 2.;
689     OfxRectD ret;
690 
691     ret.x1 = center.x - (POINT_SIZE / 2.) * meanPixelScale;
692     ret.x2 = center.x + (POINT_SIZE / 2.) * meanPixelScale;
693     ret.y1 = center.y - (POINT_SIZE / 2.) * meanPixelScale;
694     ret.y2 = center.y + (POINT_SIZE / 2.) * meanPixelScale;
695 
696     return ret;
697 }
698 
699 // round to the closest int, 1/10 int, etc
700 // this make parameter editing easier
701 // pscale is args.pixelScale.x / args.renderScale.x;
702 // pscale10 is the power of 10 below pscale
703 static double
fround(double val,double pscale)704 fround(double val,
705        double pscale)
706 {
707     double pscale10 = std::pow( 10., std::floor( std::log10(pscale) ) );
708 
709     return pscale10 * std::floor(val / pscale10 + 0.5);
710 }
711 
712 // overridden functions from OFX::Interact to do things
713 bool
penMotion(const OFX::PenArgs & args)714 TransformInteractCustomHelper::penMotion(const OFX::PenArgs &args)
715 {
716     if ( !_interactOpen->getValueAtTime(args.time) ) {
717         return false;
718     }
719     const OfxPointD &pscale = args.pixelScale;
720     const double time = args.time;
721     OfxPointD center = { 0., 0. };
722     OfxPointD translate = { 0., 0. };
723     OfxPointD scaleParam = { 1., 1. };
724     bool scaleUniform = false;
725     double rotate = 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 (_invert) {
745             _invert->getValueAtTime(time, inverted);
746         }
747     } else {
748         center = _centerDrag;
749         translate = _translateDrag;
750         scaleParam = _scaleParamDrag;
751         scaleUniform = _scaleUniformDrag;
752         rotate = _rotateDrag;
753         inverted = _invertedDrag;
754     }
755 
756     bool didSomething = false;
757     bool centerChanged = false;
758     bool translateChanged = false;
759     bool scaleChanged = false;
760     bool rotateChanged = false;
761 
762     OfxPointD targetCenter;
763     getTargetCenter(center, translate, &targetCenter);
764 
765     OfxPointD scale;
766     ofxsTransformGetScale(scaleParam, scaleUniform, &scale);
767 
768     OfxPointD targetRadius;
769     getTargetRadius(scale, pscale, &targetRadius);
770 
771     OfxPointD left, right, bottom, top;
772     getTargetPoints(targetCenter, targetRadius, &left, &bottom, &top, &right);
773 
774     OfxRectD centerPoint = rectFromCenterPoint(targetCenter, pscale);
775     OfxRectD leftPoint = rectFromCenterPoint(left, pscale);
776     OfxRectD rightPoint = rectFromCenterPoint(right, pscale);
777     OfxRectD topPoint = rectFromCenterPoint(top, pscale);
778     OfxRectD bottomPoint = rectFromCenterPoint(bottom, pscale);
779 
780 
781     //double dx = args.penPosition.x - _lastMousePos.x;
782     //double dy = args.penPosition.y - _lastMousePos.y;
783     double rot = OFX::ofxsToRadians(rotate);
784     OFX::Point3D penPos, prevPenPos, rotationPos, transformedPos, previousPos, currentPos;
785     penPos.x = args.penPosition.x;
786     penPos.y = args.penPosition.y;
787     penPos.z = 1.;
788     prevPenPos.x = _lastMousePos.x;
789     prevPenPos.y = _lastMousePos.y;
790     prevPenPos.z = 1.;
791 
792     OFX::Matrix3x3 rotation, transform, transformscale;
793     ////for the rotation bar/translation/center dragging we dont use the same transform, we don't want to undo the rotation transform
794     if ( (_mouseState != eDraggingTranslation) && (_mouseState != eDraggingCenter) ) {
795         ///undo skew + rotation to the current position
796         rotation = OFX::ofxsMatInverseTransformCanonical(0., 0., 1., 1., 0., 0., false, rot, targetCenter.x, targetCenter.y);
797         transform = OFX::ofxsMatInverseTransformCanonical(0., 0., 1., 1., 0, 0, false, rot, targetCenter.x, targetCenter.y);
798         transformscale = OFX::ofxsMatInverseTransformCanonical(0., 0., scale.x, scale.y, 0, 0, false, rot, targetCenter.x, targetCenter.y);
799     } else {
800         rotation = OFX::ofxsMatInverseTransformCanonical(0., 0., 1., 1., 0., 0., false, 0., targetCenter.x, targetCenter.y);
801         transform = OFX::ofxsMatInverseTransformCanonical(0., 0., 1., 1., 0, 0, false, 0., targetCenter.x, targetCenter.y);
802         transformscale = OFX::ofxsMatInverseTransformCanonical(0., 0., scale.x, scale.y, 0, 0, false, 0., targetCenter.x, targetCenter.y);
803     }
804 
805     rotationPos = rotation * penPos;
806     if (rotationPos.z != 0) {
807         rotationPos.x /= rotationPos.z;
808         rotationPos.y /= rotationPos.z;
809     }
810 
811     transformedPos = transform * penPos;
812     if (transformedPos.z != 0) {
813         transformedPos.x /= transformedPos.z;
814         transformedPos.y /= transformedPos.z;
815     }
816 
817     previousPos = transformscale * prevPenPos;
818     if (previousPos.z != 0) {
819         previousPos.x /= previousPos.z;
820         previousPos.y /= previousPos.z;
821     }
822 
823     currentPos = transformscale * penPos;
824     if (currentPos.z != 0) {
825         currentPos.x /= currentPos.z;
826         currentPos.y /= currentPos.z;
827     }
828 
829     if (_mouseState == eReleased) {
830         // we are not axis-aligned
831         double meanPixelScale = (pscale.x + pscale.y) / 2.;
832         double hoverTolerance = (POINT_SIZE / 2.) * meanPixelScale;
833         if ( squareContains(transformedPos, centerPoint) ) {
834             _drawState = eCenterPointHovered;
835             didSomething = true;
836         } else if ( squareContains(transformedPos, leftPoint) ) {
837             _drawState = eLeftPointHovered;
838             didSomething = true;
839         } else if ( squareContains(transformedPos, rightPoint) ) {
840             _drawState = eRightPointHovered;
841             didSomething = true;
842         } else if ( squareContains(transformedPos, topPoint) ) {
843             _drawState = eTopPointHovered;
844             didSomething = true;
845         } else if ( squareContains(transformedPos, bottomPoint) ) {
846             _drawState = eBottomPointHovered;
847             didSomething = true;
848         } else if ( isOnEllipseBorder(transformedPos, targetRadius, targetCenter) ) {
849             _drawState = eCircleHovered;
850             didSomething = true;
851         } else if ( isOnRotationBar(rotationPos, targetRadius.x, targetCenter, pscale, hoverTolerance) ) {
852             _drawState = eRotationBarHovered;
853             didSomething = true;
854         } else {
855             _drawState = eInActive;
856         }
857     } else if (_mouseState == eDraggingCircle) {
858         double minX, minY, maxX, maxY;
859         _scale->getRange(minX, minY, maxX, maxY);
860 
861         // we need to compute the backtransformed points with the scale
862 
863         // the scale ratio is the ratio of distances to the center
864         double prevDistSq = (targetCenter.x - previousPos.x) * (targetCenter.x - previousPos.x) + (targetCenter.y - previousPos.y) * (targetCenter.y - previousPos.y);
865         if (prevDistSq != 0.) {
866             const double distSq = (targetCenter.x - currentPos.x) * (targetCenter.x - currentPos.x) + (targetCenter.y - currentPos.y) * (targetCenter.y - currentPos.y);
867             const double distRatio = std::sqrt(distSq / prevDistSq);
868             scale.x *= distRatio;
869             scale.y *= distRatio;
870             //_scale->setValue(scale.x, scale.y);
871             scaleChanged = true;
872         }
873     } else if ( (_mouseState == eDraggingLeftPoint) || (_mouseState == eDraggingRightPoint) ) {
874         // avoid division by zero
875         if (targetCenter.x != previousPos.x) {
876             double minX, minY, maxX, maxY;
877             _scale->getRange(minX, minY, maxX, maxY);
878             const double scaleRatio = (targetCenter.x - currentPos.x) / (targetCenter.x - previousPos.x);
879             OfxPointD newScale;
880             newScale.x = scale.x * scaleRatio;
881             newScale.x = (std::max)( minX, (std::min)(newScale.x, maxX) );
882             newScale.y = scaleUniform ? newScale.x : scale.y;
883             scale = newScale;
884             //_scale->setValue(scale.x, scale.y);
885             scaleChanged = true;
886         }
887     } else if ( (_mouseState == eDraggingTopPoint) || (_mouseState == eDraggingBottomPoint) ) {
888         // avoid division by zero
889         if (targetCenter.y != previousPos.y) {
890             double minX, minY, maxX, maxY;
891             _scale->getRange(minX, minY, maxX, maxY);
892             const double scaleRatio = (targetCenter.y - currentPos.y) / (targetCenter.y - previousPos.y);
893             OfxPointD newScale;
894             newScale.y = scale.y * scaleRatio;
895             newScale.y = (std::max)( minY, (std::min)(newScale.y, maxY) );
896             newScale.x = scaleUniform ? newScale.y : scale.x;
897             scale = newScale;
898             //_scale->setValue(scale.x, scale.y);
899             scaleChanged = true;
900         }
901     } else if (_mouseState == eDraggingTranslation) {
902         double dx = args.penPosition.x - _lastMousePos.x;
903         double dy = args.penPosition.y - _lastMousePos.y;
904 
905         if ( (_orientation == eOrientationNotSet) && (_modifierStateShift > 0) ) {
906             _orientation = std::abs(dx) > std::abs(dy) ? eOrientationHorizontal : eOrientationVertical;
907         }
908 
909         dx = _orientation == eOrientationVertical ? 0 : dx;
910         dy = _orientation == eOrientationHorizontal ? 0 : dy;
911         double newx = translate.x + dx;
912         double newy = translate.y + dy;
913         // round newx/y to the closest int, 1/10 int, etc
914         // this make parameter editing easier
915         newx = fround(newx, pscale.x);
916         newy = fround(newy, pscale.y);
917         translate.x = newx;
918         translate.y = newy;
919         //_translate->setValue(translate.x, translate.y);
920         translateChanged = true;
921     } else if (_mouseState == eDraggingCenter) {
922         OfxPointD currentCenter = center;
923         OFX::Matrix3x3 R = ofxsMatScale(1. / scale.x, 1. / scale.y) * ofxsMatRotation(rot);
924         double dx = args.penPosition.x - _lastMousePos.x;
925         double dy = args.penPosition.y - _lastMousePos.y;
926 
927         if ( (_orientation == eOrientationNotSet) && (_modifierStateShift > 0) ) {
928             _orientation = std::abs(dx) > std::abs(dy) ? eOrientationHorizontal : eOrientationVertical;
929         }
930 
931         dx = _orientation == eOrientationVertical ? 0 : dx;
932         dy = _orientation == eOrientationHorizontal ? 0 : dy;
933 
934         double dxrot, dyrot;
935         if (!_translate) {
936             dxrot = dx;
937             dyrot = dy;
938         } else {
939             // if there is a _translate param (i.e. this is Transform/DirBlur and not GodRays),
940             // compensate the rotation, because the
941             // interact is visualized on the transformed image
942             OFX::Point3D dRot;
943             dRot.x = dx;
944             dRot.y = dy;
945             dRot.z = 1.;
946             dRot = R * dRot;
947             if (dRot.z != 0) {
948                 dRot.x /= dRot.z;
949                 dRot.y /= dRot.z;
950             }
951             dxrot = dRot.x;
952             dyrot = dRot.y;
953         }
954         double newx = currentCenter.x + dxrot;
955         double newy = currentCenter.y + dyrot;
956         // round newx/y to the closest int, 1/10 int, etc
957         // this make parameter editing easier
958         newx = fround(newx, pscale.x);
959         newy = fround(newy, pscale.y);
960         center.x = newx;
961         center.y = newy;
962         //_effect->beginEditBlock("setCenter");
963         //_center->setValue(center.x, center.y);
964         centerChanged = true;
965         if (_translate) {
966             // recompute dxrot,dyrot after rounding
967             OFX::Matrix3x3 Rinv;
968             if ( R.inverse(&Rinv) ) {
969                 dxrot = newx - currentCenter.x;
970                 dyrot = newy - currentCenter.y;
971                 OFX::Point3D dRot;
972                 dRot.x = dxrot;
973                 dRot.y = dyrot;
974                 dRot.z = 1;
975                 dRot = Rinv * dRot;
976                 if (dRot.z != 0) {
977                     dRot.x /= dRot.z;
978                     dRot.y /= dRot.z;
979                 }
980                 dx = dRot.x;
981                 dy = dRot.y;
982                 OfxPointD newTranslation;
983                 newTranslation.x = translate.x + dx - dxrot;
984                 newTranslation.y = translate.y + dy - dyrot;
985                 translate = newTranslation;
986                 //_translate->setValue(translate.x, translate.y);
987                 translateChanged = true;
988             }
989         }
990         //_effect->endEditBlock();
991     } else if (_mouseState == eDraggingRotationBar) {
992         OfxPointD diffToCenter;
993         ///the current mouse position (untransformed) is doing has a certain angle relative to the X axis
994         ///which can be computed by : angle = arctan(opposite / adjacent)
995         diffToCenter.y = rotationPos.y - targetCenter.y;
996         diffToCenter.x = rotationPos.x - targetCenter.x;
997         double angle = std::atan2(diffToCenter.y, diffToCenter.x);
998         double angledegrees = rotate + OFX::ofxsToDegrees(angle);
999         double closest90 = 90. * std::floor( (angledegrees + 45.) / 90. );
1000         if (std::fabs(angledegrees - closest90) < 5.) {
1001             // snap to closest multiple of 90.
1002             angledegrees = closest90;
1003         }
1004         rotate = angledegrees;
1005         //_rotate->setValue(rotate);
1006         rotateChanged = true;
1007     } else {
1008         assert(false);
1009     }
1010 
1011     _centerDrag = center;
1012     _translateDrag = translate;
1013     _scaleParamDrag = scale;
1014     _scaleUniformDrag = scaleUniform;
1015     _rotateDrag = rotate;
1016     _invertedDrag = inverted;
1017 
1018     bool valuesChanged = (centerChanged || translateChanged || scaleChanged || rotateChanged);
1019 
1020     if ( (_mouseState != eReleased) && _interactiveDrag && valuesChanged ) {
1021         // no need to redraw overlay since it is slave to the paramaters
1022         _effect->beginEditBlock("setTransform");
1023         if (centerChanged) {
1024             _center->setValue(center.x, center.y);
1025         }
1026         if (translateChanged) {
1027             _translate->setValue(translate.x, translate.y);
1028         }
1029         if (scaleChanged) {
1030             _scale->setValue(scale.x, scale.y);
1031         }
1032         if (rotateChanged) {
1033             _rotate->setValue(rotate);
1034         }
1035         _effect->endEditBlock();
1036     } else if (didSomething || valuesChanged) {
1037         _interact->requestRedraw();
1038     }
1039 
1040     _lastMousePos = args.penPosition;
1041 
1042     return didSomething || valuesChanged;
1043 } // TransformInteractCustomHelper::penMotion
1044 
1045 bool
penDown(const OFX::PenArgs & args)1046 TransformInteractCustomHelper::penDown(const OFX::PenArgs &args)
1047 {
1048     if ( !_interactOpen->getValueAtTime(args.time) ) {
1049         return false;
1050     }
1051     using OFX::Matrix3x3;
1052 
1053     const OfxPointD &pscale = args.pixelScale;
1054     const double time = args.time;
1055     OfxPointD center = { 0., 0. };
1056     OfxPointD translate = { 0., 0. };
1057     OfxPointD scaleParam = { 1., 1. };
1058     bool scaleUniform = false;
1059     double rotate = 0.;
1060     bool inverted = false;
1061 
1062     if (_mouseState == eReleased) {
1063         if (_center) {
1064             _center->getValueAtTime(time, center.x, center.y);
1065         }
1066         if (_translate) {
1067             _translate->getValueAtTime(time, translate.x, translate.y);
1068         }
1069         if (_scale) {
1070             _scale->getValueAtTime(time, scaleParam.x, scaleParam.y);
1071         }
1072         if (_scaleUniform) {
1073             _scaleUniform->getValueAtTime(time, scaleUniform);
1074         }
1075         if (_rotate) {
1076             _rotate->getValueAtTime(time, rotate);
1077         }
1078         if (_invert) {
1079             _invert->getValueAtTime(time, inverted);
1080         }
1081         if (_interactive) {
1082             _interactive->getValueAtTime(args.time, _interactiveDrag);
1083         }
1084     } else {
1085         center = _centerDrag;
1086         translate = _translateDrag;
1087         scaleParam = _scaleParamDrag;
1088         scaleUniform = _scaleUniformDrag;
1089         rotate = _rotateDrag;
1090         inverted = _invertedDrag;
1091     }
1092 
1093     OfxPointD targetCenter;
1094     getTargetCenter(center, translate, &targetCenter);
1095 
1096     OfxPointD scale;
1097     ofxsTransformGetScale(scaleParam, scaleUniform, &scale);
1098 
1099     OfxPointD targetRadius;
1100     getTargetRadius(scale, pscale, &targetRadius);
1101 
1102     OfxPointD left, right, bottom, top;
1103     getTargetPoints(targetCenter, targetRadius, &left, &bottom, &top, &right);
1104 
1105     OfxRectD centerPoint = rectFromCenterPoint(targetCenter, pscale);
1106     OfxRectD leftPoint = rectFromCenterPoint(left, pscale);
1107     OfxRectD rightPoint = rectFromCenterPoint(right, pscale);
1108     OfxRectD topPoint = rectFromCenterPoint(top, pscale);
1109     OfxRectD bottomPoint = rectFromCenterPoint(bottom, pscale);
1110     OFX::Point3D transformedPos, rotationPos;
1111     transformedPos.x = args.penPosition.x;
1112     transformedPos.y = args.penPosition.y;
1113     transformedPos.z = 1.;
1114 
1115     double rot = OFX::ofxsToRadians(rotate);
1116 
1117     ///now undo skew + rotation to the current position
1118     OFX::Matrix3x3 rotation, transform;
1119     rotation = OFX::ofxsMatInverseTransformCanonical(0., 0., 1., 1., 0., 0., false, rot, targetCenter.x, targetCenter.y);
1120     transform = OFX::ofxsMatInverseTransformCanonical(0., 0., 1., 1., 0, 0, false, rot, targetCenter.x, targetCenter.y);
1121 
1122     rotationPos = rotation * transformedPos;
1123     if (rotationPos.z != 0) {
1124         rotationPos.x /= rotationPos.z;
1125         rotationPos.y /= rotationPos.z;
1126     }
1127     transformedPos = transform * transformedPos;
1128     if (transformedPos.z != 0) {
1129         transformedPos.x /= transformedPos.z;
1130         transformedPos.y /= transformedPos.z;
1131     }
1132 
1133     _orientation = eOrientationAllDirections;
1134 
1135     double pressToleranceX = 5 * pscale.x;
1136     double pressToleranceY = 5 * pscale.y;
1137     bool didSomething = false;
1138     if ( squareContains(transformedPos, centerPoint, pressToleranceX, pressToleranceY) ) {
1139         _mouseState = ( (!_translate || _modifierStateCtrl) ? eDraggingCenter : eDraggingTranslation );
1140         if (_modifierStateShift > 0) {
1141             _orientation = eOrientationNotSet;
1142         }
1143         didSomething = true;
1144     } else if ( squareContains(transformedPos, leftPoint, pressToleranceX, pressToleranceY) ) {
1145         _mouseState = eDraggingLeftPoint;
1146         didSomething = true;
1147     } else if ( squareContains(transformedPos, rightPoint, pressToleranceX, pressToleranceY) ) {
1148         _mouseState = eDraggingRightPoint;
1149         didSomething = true;
1150     } else if ( squareContains(transformedPos, topPoint, pressToleranceX, pressToleranceY) ) {
1151         _mouseState = eDraggingTopPoint;
1152         didSomething = true;
1153     } else if ( squareContains(transformedPos, bottomPoint, pressToleranceX, pressToleranceY) ) {
1154         _mouseState = eDraggingBottomPoint;
1155         didSomething = true;
1156     } else if ( isOnEllipseBorder(transformedPos, targetRadius, targetCenter) ) {
1157         _mouseState = eDraggingCircle;
1158         didSomething = true;
1159     } else if ( isOnRotationBar(rotationPos, targetRadius.x, targetCenter, pscale, pressToleranceY) ) {
1160         _mouseState = eDraggingRotationBar;
1161         didSomething = true;
1162     } else {
1163         _mouseState = eReleased;
1164     }
1165 
1166     _lastMousePos = args.penPosition;
1167 
1168     _centerDrag = center;
1169     _translateDrag = translate;
1170     _scaleParamDrag = scaleParam;
1171     _scaleUniformDrag = scaleUniform;
1172     _rotateDrag = rotate;
1173     _invertedDrag = inverted;
1174 
1175     if (didSomething) {
1176         _interact->requestRedraw();
1177     }
1178 
1179     return didSomething;
1180 } // TransformInteractCustomHelper::penDown
1181 
1182 bool
penUp(const OFX::PenArgs & args)1183 TransformInteractCustomHelper::penUp(const OFX::PenArgs &args)
1184 {
1185     if ( !_interactOpen->getValueAtTime(args.time) ) {
1186         return false;
1187     }
1188     bool ret = _mouseState != eReleased;
1189 
1190     if ( !_interactiveDrag && (_mouseState != eReleased) ) {
1191         // no need to redraw overlay since it is slave to the paramaters
1192         _effect->beginEditBlock("setTransform");
1193         if (_center) {
1194             _center->setValue(_centerDrag.x, _centerDrag.y);
1195         }
1196         if (_translate) {
1197             _translate->setValue(_translateDrag.x, _translateDrag.y);
1198         }
1199         if (_scale) {
1200             _scale->setValue(_scaleParamDrag.x, _scaleParamDrag.y);
1201         }
1202         if (_rotate) {
1203             _rotate->setValue(_rotateDrag);
1204         }
1205         _effect->endEditBlock();
1206     } else if (_mouseState != eReleased) {
1207         _interact->requestRedraw();
1208     }
1209 
1210     _mouseState = eReleased;
1211     _lastMousePos = args.penPosition;
1212 
1213     return ret;
1214 }
1215 
1216 // keyDown just updates the modifier state
1217 bool
keyDown(const OFX::KeyArgs & args)1218 TransformInteractCustomHelper::keyDown(const OFX::KeyArgs &args)
1219 {
1220     // Always process, even if interact is not open, since this concerns modifiers
1221     //if (!_interactOpen->getValueAtTime(args.time)) {
1222     //    return false;
1223     //}
1224 
1225     // Note that on the Mac:
1226     // cmd/apple/cloverleaf is kOfxKey_Control_L
1227     // ctrl is kOfxKey_Meta_L
1228     // alt/option is kOfxKey_Alt_L
1229     bool mustRedraw = false;
1230 
1231     // the two control keys may be pressed consecutively, be aware about this
1232     if ( _translate && ( (args.keySymbol == kOfxKey_Control_L) || (args.keySymbol == kOfxKey_Control_R) ) ) {
1233         mustRedraw = (_modifierStateCtrl == 0);
1234         ++_modifierStateCtrl;
1235     }
1236     if ( (args.keySymbol == kOfxKey_Shift_L) || (args.keySymbol == kOfxKey_Shift_R) ) {
1237         mustRedraw = (_modifierStateShift == 0);
1238         ++_modifierStateShift;
1239         if (_modifierStateShift > 0) {
1240             _orientation = eOrientationNotSet;
1241         }
1242     }
1243     if (mustRedraw) {
1244         _interact->requestRedraw();
1245     }
1246     //std::cout << std::hex << args.keySymbol << std::endl;
1247 
1248     // modifiers are not "caught"
1249     return false;
1250 }
1251 
1252 // keyUp just updates the modifier state
1253 bool
keyUp(const OFX::KeyArgs & args)1254 TransformInteractCustomHelper::keyUp(const OFX::KeyArgs &args)
1255 {
1256     // Always process, even if interact is not open, since this concerns modifiers
1257     //if (!_interactOpen->getValueAtTime(args.time)) {
1258     //    return false;
1259     //}
1260 
1261     bool mustRedraw = false;
1262 
1263     if ( _translate && ( (args.keySymbol == kOfxKey_Control_L) || (args.keySymbol == kOfxKey_Control_R) ) ) {
1264         // we may have missed a keypress
1265         if (_modifierStateCtrl > 0) {
1266             --_modifierStateCtrl;
1267             mustRedraw = (_modifierStateCtrl == 0);
1268         }
1269     }
1270     if ( (args.keySymbol == kOfxKey_Shift_L) || (args.keySymbol == kOfxKey_Shift_R) ) {
1271         if (_modifierStateShift > 0) {
1272             --_modifierStateShift;
1273             mustRedraw = (_modifierStateShift == 0);
1274         }
1275         if (_modifierStateShift == 0) {
1276             _orientation = eOrientationAllDirections;
1277         }
1278     }
1279     if (mustRedraw) {
1280         _interact->requestRedraw();
1281     }
1282 
1283     // modifiers are not "caught"
1284     return false;
1285 }
1286 
1287 /** @brief Called when the interact is loses input focus */
1288 void
loseFocus(const FocusArgs &)1289 TransformInteractCustomHelper::loseFocus(const FocusArgs & /*args*/)
1290 {
1291     // reset the modifiers state
1292     if (_translate) {
1293         _modifierStateCtrl = 0;
1294     }
1295     _modifierStateShift = 0;
1296     _interactiveDrag = false;
1297     _mouseState = eReleased;
1298     _drawState = eInActive;
1299 }
1300 } // namespace OFX
1301