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 ¢er,
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 ¢er,
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