1 // Copyright (C) 2012-2019 The VPaint Developers.
2 // See the COPYRIGHT file at the top-level directory of this distribution
3 // and at https://github.com/dalboris/vpaint/blob/master/COPYRIGHT
4 //
5 // Licensed under the Apache License, Version 2.0 (the "License");
6 // you may not use this file except in compliance with the License.
7 // You may obtain a copy of the License at
8 //
9 //     http://www.apache.org/licenses/LICENSE-2.0
10 //
11 // Unless required by applicable law or agreed to in writing, software
12 // distributed under the License is distributed on an "AS IS" BASIS,
13 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 // See the License for the specific language governing permissions and
15 // limitations under the License.
16 
17 #include "View.h"
18 
19 #include "Scene.h"
20 #include "Timeline.h"
21 #include "DevSettings.h"
22 #include "Global.h"
23 #include "Background/Background.h"
24 #include "Background/BackgroundRenderer.h"
25 #include "VectorAnimationComplex/VAC.h"
26 #include "VectorAnimationComplex/Cell.h"
27 #include "Layer.h"
28 
29 #include <QtDebug>
30 #include <QApplication>
31 #include <QPushButton>
32 #include <cmath>
33 
34 // define mouse actions
35 
36 #define  SELECT_ACTION                                      100
37 #define  ADDSELECT_ACTION                                   101
38 #define  DESELECT_ACTION                                    102
39 #define  TOGGLESELECT_ACTION                                103
40 #define  DESELECTALL_ACTION                                 104
41 #define  RECTANGLE_OF_SELECTION_ACTION                      105
42 #define  DRAG_AND_DROP_ACTION                               106
43 #define  SPLIT_ACTION                                       107
44 #define  TRANSFORM_SELECTION_ACTION                         108
45 
46 #define  SKETCH_ACTION                                      200
47 #define  SKETCH_CHANGE_PEN_WIDTH_ACTION                     203
48 #define  SKETCH_CHANGE_SNAP_THRESHOLD_ACTION                204
49 #define  SKETCH_CHANGE_PEN_WIDTH_AND_SNAP_THRESHOLD_ACTION  205
50 
51 #define  SCULPT_CHANGE_RADIUS_ACTION                        300
52 #define  SCULPT_DEFORM_ACTION                               301
53 #define  SCULPT_SMOOTH_ACTION                               302
54 #define  SCULPT_CHANGE_WIDTH_ACTION                         303
55 
56 #define  PAINT_ACTION                                       400
57 
View(Scene * scene,QWidget * parent)58 View::View(Scene * scene, QWidget * parent) :
59     GLWidget(parent, true),
60     scene_(scene),
61     pickingImg_(0),
62     pickingIsEnabled_(true),
63     currentAction_(0),
64     vac_(0)
65 {
66     // View settings widget
67     viewSettingsWidget_ = new ViewSettingsWidget(viewSettings_, this);
68     connect(viewSettingsWidget_, SIGNAL(changed()), this, SLOT(update()));
69     connect(viewSettingsWidget_, SIGNAL(changed()), this, SIGNAL(settingsChanged()));
70     cameraTravellingIsEnabled_ = true;
71 
72     connect(this, SIGNAL(viewIsGoingToChange(int, int)), this, SLOT(updatePicking()));
73     //connect(this, SIGNAL(viewIsGoingToChange(int, int)), this, SLOT(updateHighlightedObject(int, int)));
74     connect(this, SIGNAL(viewIsGoingToChange(int, int)), this, SLOT(update()));
75 
76     connect(this, SIGNAL(viewIsBeingChanged(int, int)), this, SLOT(updateZoomFromView()));
77     //connect(this, SIGNAL(viewIsBeingChanged(int, int)), this, SLOT(updatePicking()));
78     //connect(this, SIGNAL(viewIsBeingChanged(int, int)), this, SLOT(updateHighlightedObject(int, int)));
79     connect(this, SIGNAL(viewIsBeingChanged(int, int)), this, SLOT(update()));
80 
81     connect(this, SIGNAL(viewChanged(int, int)), this, SLOT(updateZoomFromView()));
82     connect(this, SIGNAL(viewChanged(int, int)), this, SLOT(updatePicking()));
83     connect(this, SIGNAL(viewChanged(int, int)), this, SLOT(updateHoveredObject(int, int)));
84     connect(this, SIGNAL(viewChanged(int, int)), this, SLOT(update()));
85 
86     connect(global(), SIGNAL(keyboardModifiersChanged()), this, SLOT(handleNewKeyboardModifiers()));
87 }
88 
~View()89 View::~View()
90 {
91     deletePicking();
92 }
93 
initCamera()94 void View::initCamera()
95 {
96     // Set 100% zoom and center canvas in view
97     GLWidget_Camera2D camera;
98     camera.setZoom(1.0);
99     camera.setX(scene()->left() - 0.5*(scene()->width() - width()));
100     camera.setY(scene()->top() - 0.5*(scene()->height() - height()));
101     setCamera2D(camera);
102 }
103 
scene()104 Scene * View::scene()
105 {
106     return scene_;
107 }
108 
resizeEvent(QResizeEvent * event)109 void View::resizeEvent(QResizeEvent * event)
110 {
111     if(autoCenterScene_)
112         initCamera();
113 
114     GLWidget::resizeEvent(event);
115 }
116 
resizeGL(int width,int height)117 void View::resizeGL(int width, int height)
118 {
119     GLWidget::resizeGL(width, height);
120     updatePicking();
121 }
122 
keyPressEvent(QKeyEvent * event)123 void View::keyPressEvent(QKeyEvent *event)
124 {
125     event->ignore();
126 }
127 
128 
keyReleaseEvent(QKeyEvent * event)129 void View::keyReleaseEvent(QKeyEvent *event)
130 {
131     event->ignore();
132 }
133 
handleNewKeyboardModifiers()134 void View::handleNewKeyboardModifiers()
135 {
136     vac_ = scene_->activeVAC();
137 
138     // Rectangle of selection
139     if(vac_ && currentAction_ == RECTANGLE_OF_SELECTION_ACTION)
140     {
141         vac_->setSelectedCellsFromRectangleOfSelection(global()->keyboardModifiers());
142     }
143 
144     // Update in any case, better be safe.
145     emit allViewsNeedToUpdate();
146 }
147 
mouseEvent() const148 MouseEvent View::mouseEvent() const
149 {
150     MouseEvent me;
151     me.x = mouse_Event_XScene_;
152     me.y = mouse_Event_YScene_;
153     me.left = mouse_LeftButton_;
154     me.mid = mouse_MidButton_;
155     me.right = mouse_RightButton_;
156     me.alt = mouse_AltWasDown_;
157     me.control = mouse_ControlWasDown_;
158     me.shift = mouse_ShiftWasDown_;
159     return me;
160 }
161 
update()162 void View::update()
163 {
164     GLWidget_Camera2D c = camera2D();
165     c.setZoom(viewSettings_.zoom());
166     setCamera2D(c);
167 
168     GLWidget::update();
169 }
170 
updateZoomFromView()171 void View::updateZoomFromView()
172 {
173     viewSettings_.setZoom(zoom());
174     viewSettingsWidget_->updateWidgetFromSettings();
175     viewSettingsWidget_->updateSettingsFromWidgetSilent();
176     GLWidget_Camera2D c = camera2D();
177     c.setZoom(viewSettings_.zoom());
178     setCamera2D(c);
179 }
180 
decideClicAction()181 int View::decideClicAction()
182 {
183     vac_ = scene_->activeVAC();
184     if (vac_)
185     {
186         // Selection
187         if (global()->toolMode() == Global::SELECT && mouse_LeftButton_)
188         {
189             // Left = set selection
190             if(!mouse_AltWasDown_ &&
191                !mouse_ControlWasDown_ &&
192                !mouse_ShiftWasDown_)
193             {
194                 if (vac_->hoveredCell())
195                     return SELECT_ACTION;
196                 else if (!vac_->hoveredTransformWidgetId())
197                     return DESELECTALL_ACTION;
198             }
199             // Shift + Left = add to selection
200             if(!mouse_AltWasDown_ &&
201                !mouse_ControlWasDown_ &&
202                mouse_ShiftWasDown_)
203             {
204                 return ADDSELECT_ACTION;
205             }
206             // Alt + Left = remove from selection
207             if(mouse_AltWasDown_ &&
208                !mouse_ControlWasDown_ &&
209                !mouse_ShiftWasDown_)
210             {
211                 return DESELECT_ACTION;
212             }
213             // Alt + Shift + Left = toggle selection state
214             if(mouse_AltWasDown_ &&
215                !mouse_ControlWasDown_ &&
216                mouse_ShiftWasDown_)
217             {
218                 return TOGGLESELECT_ACTION;
219             }
220         }
221 
222         // Cut edge
223         if( global()->toolMode() == Global::SELECT &&
224              mouse_LeftButton_ &&
225              !mouse_AltWasDown_ &&
226              mouse_ControlWasDown_ &&
227              !mouse_ShiftWasDown_)
228         {
229             return SPLIT_ACTION;
230         }
231 
232         // Paint
233         if( global()->toolMode() == Global::PAINT &&
234             mouse_LeftButton_ &&
235             !mouse_AltWasDown_ &&
236             !mouse_ControlWasDown_ &&
237             !mouse_ShiftWasDown_)
238         {
239             return PAINT_ACTION;
240         }
241     }
242 
243     return GLWidget::decideClicAction();
244 }
245 
decidePMRAction()246 int View::decidePMRAction()
247 {
248     vac_ = scene_->activeVAC();
249     if (vac_)
250     {
251         // Selection
252         if(global()->toolMode() == Global::SELECT)
253         {
254             // Left on cell
255             if( vac_->hoveredCell() &&
256                 mouse_LeftButton_ &&
257                 !mouse_AltWasDown_ &&
258                 !mouse_ControlWasDown_ &&
259                 !mouse_ShiftWasDown_)
260             {
261                 return DRAG_AND_DROP_ACTION;
262             }
263             // Left on transform widget
264             else if( vac_->hoveredTransformWidgetId() &&
265                      mouse_LeftButton_ &&
266                      !mouse_ControlWasDown_)
267             {
268                 return TRANSFORM_SELECTION_ACTION;
269             }
270             // Left on empty space
271             else if (hoveredObject_.isNull() &&
272                      mouse_LeftButton_ &&
273                      !mouse_ControlWasDown_ )
274             {
275                 return RECTANGLE_OF_SELECTION_ACTION;
276             }
277         }
278 
279         // Sketch
280         else if(global()->toolMode() == Global::SKETCH)
281         {
282             // Left
283             if(mouse_LeftButton_ &&
284                !mouse_AltWasDown_ &&
285                !mouse_ControlWasDown_ &&
286                !mouse_ShiftWasDown_)
287             {
288                 return SKETCH_ACTION;
289             }
290             // Ctrl + Left
291             if(mouse_LeftButton_ &&
292                !mouse_AltWasDown_ &&
293                mouse_ControlWasDown_ &&
294                !mouse_ShiftWasDown_)
295             {
296                 return SKETCH_CHANGE_PEN_WIDTH_ACTION;
297             }
298             // Alt + Left
299             if(mouse_LeftButton_ &&
300                mouse_AltWasDown_ &&
301                !mouse_ControlWasDown_ &&
302                !mouse_ShiftWasDown_)
303             {
304                 return SKETCH_CHANGE_SNAP_THRESHOLD_ACTION;
305             }
306             // Ctrl + Alt + Left
307             if(mouse_LeftButton_ &&
308                mouse_AltWasDown_ &&
309                mouse_ControlWasDown_ &&
310                !mouse_ShiftWasDown_)
311             {
312                 return SKETCH_CHANGE_PEN_WIDTH_AND_SNAP_THRESHOLD_ACTION;
313             }
314         }
315 
316         // Sculpt
317         else if(global()->toolMode() == Global::SCULPT)
318         {
319             // Left
320             if(mouse_LeftButton_ &&
321                !mouse_AltWasDown_ &&
322                !mouse_ControlWasDown_ &&
323                !mouse_ShiftWasDown_)
324             {
325                 VectorAnimationComplex::Cell * hoveredCell =
326                             vac_->hoveredCell();
327 
328                 if(hoveredCell && hoveredCell->toVertexCell())
329                 {
330                     return DRAG_AND_DROP_ACTION;
331                 }
332                 else
333                 {
334                     return SCULPT_DEFORM_ACTION;
335                 }
336             }
337             // Ctrl + Left
338             if(mouse_LeftButton_ &&
339                !mouse_AltWasDown_ &&
340                mouse_ControlWasDown_ &&
341                !mouse_ShiftWasDown_)
342             {
343                 return SCULPT_CHANGE_RADIUS_ACTION;
344             }
345             // Alt + Left
346             if(mouse_LeftButton_ &&
347                mouse_AltWasDown_ &&
348                !mouse_ControlWasDown_ &&
349                !mouse_ShiftWasDown_)
350             {
351                 return SCULPT_CHANGE_WIDTH_ACTION;
352             }
353             // Shift + Left
354             if(mouse_LeftButton_ &&
355                !mouse_AltWasDown_ &&
356                !mouse_ControlWasDown_ &&
357                mouse_ShiftWasDown_)
358             {
359                 return SCULPT_SMOOTH_ACTION;
360             }
361         }
362     }
363 
364     return GLWidget::decidePMRAction();
365 }
366 
activeFrame() const367 int View::activeFrame() const
368 {
369     return std::floor(viewSettings_.time().floatTime());
370 }
371 
activeTime() const372 Time View::activeTime() const
373 {
374     return viewSettings_.time();
375 }
376 
setActiveTime(Time t)377 void View::setActiveTime(Time t)
378 {
379     viewSettings_.setTime(t);
380     viewSettingsWidget_->updateWidgetFromSettings();
381 }
382 
setActive(bool isActive)383 void View::setActive(bool isActive)
384 {
385     viewSettingsWidget_->setActive(isActive);
386 }
387 
ClicEvent(int action,double x,double y)388 void View::ClicEvent(int action, double x, double y)
389 {
390     // It is View's responsibility to call update() or updatePicking()
391 
392     if(action==SPLIT_ACTION)
393     {
394         if(!hoveredObject_.isNull() || global()->toolMode() == Global::SKETCH)
395         {
396             vac_ = scene_->activeVAC();
397             if(vac_)
398             {
399                 vac_->split(x, y, interactiveTime(), true);
400 
401                 emit allViewsNeedToUpdatePicking();
402                 updateHoveredObject(mouse_Event_X_, mouse_Event_Y_);
403                 emit allViewsNeedToUpdate();
404             }
405         }
406     }
407     else if(action==PAINT_ACTION)
408     {
409         Layer * layer = scene_->activeLayer();
410         vac_ = layer ? layer->vac() : nullptr;
411         if(vac_)
412         {
413             VectorAnimationComplex::Cell * paintedCell = vac_->paint(x, y, interactiveTime());
414             if (!paintedCell)
415             {
416                 layer->background()->setColor(global()->faceColor());
417                 scene_->emitChanged();
418                 scene_->emitCheckpoint();
419             }
420 
421             emit allViewsNeedToUpdatePicking();
422             updateHoveredObject(mouse_Event_X_, mouse_Event_Y_);
423             emit allViewsNeedToUpdate();
424         }
425     }
426 
427     else if(action==SELECT_ACTION)
428     {
429         if(!hoveredObject_.isNull())
430         {
431             scene_->deselectAll();
432             scene_->select(activeTime(),
433                            hoveredObject_.index(),
434                            hoveredObject_.id());
435             emit allViewsNeedToUpdatePicking(); // required because selection bbox pickable
436             updateHoveredObject(mouse_Event_X_, mouse_Event_Y_);
437             emit allViewsNeedToUpdate();
438         }
439     }
440     else if(action==DESELECTALL_ACTION)
441     {
442         scene_->deselectAll();
443         emit allViewsNeedToUpdatePicking();
444         updateHoveredObject(mouse_Event_X_, mouse_Event_Y_);
445         emit allViewsNeedToUpdate();
446     }
447     else if(action==ADDSELECT_ACTION)
448     {
449         if(!hoveredObject_.isNull())
450         {
451             scene_->select(activeTime(),
452                            hoveredObject_.index(),
453                            hoveredObject_.id());
454             emit allViewsNeedToUpdatePicking();
455             updateHoveredObject(mouse_Event_X_, mouse_Event_Y_);
456             emit allViewsNeedToUpdate();
457         }
458     }
459     else if(action==DESELECT_ACTION)
460     {
461         if(!hoveredObject_.isNull())
462         {
463             scene_->deselect(activeTime(),
464                              hoveredObject_.index(),
465                              hoveredObject_.id());
466             emit allViewsNeedToUpdatePicking();
467             updateHoveredObject(mouse_Event_X_, mouse_Event_Y_);
468             emit allViewsNeedToUpdate();
469         }
470     }
471     else if(action==TOGGLESELECT_ACTION)
472     {
473         if(!hoveredObject_.isNull())
474         {
475             scene_->toggle(activeTime(),
476                            hoveredObject_.index(),
477                            hoveredObject_.id());
478             emit allViewsNeedToUpdatePicking();
479             updateHoveredObject(mouse_Event_X_, mouse_Event_Y_);
480             emit allViewsNeedToUpdate();
481         }
482     }
483     else
484     {
485         GLWidget::ClicEvent(action, x, y);
486     }
487 }
488 
MoveEvent(double x,double y)489 void View::MoveEvent(double x, double y)
490 {
491     // Boolean deciding if the scene must be redrawn even though only the mouse
492     // has moved with no action performed. This is possible because depending on
493     // where the mouse is, the action to-be-performed can be different, and
494     // feedback to user on what is action would be must be given to user before
495     // the action is undertaken
496     bool mustRedraw = false;
497     global()->setSceneCursorPos(Eigen::Vector2d(x,y));
498 
499     // Update highlighted object
500     bool hoveredObjectChanged = updateHoveredObject(mouse_Event_X_, mouse_Event_Y_);
501     if(hoveredObjectChanged)
502         mustRedraw = true;
503 
504     // Update to-be-drawn straight line
505     Qt::KeyboardModifiers keys = global()->keyboardModifiers();
506     if( (global()->toolMode() == Global::SKETCH) )
507     {
508         if(keys & Qt::ControlModifier)
509         {
510             //scene_->updateCellsToConsiderForCutting();
511             mustRedraw = true;
512         }
513         else
514         {
515             //scene_->resetCellsToConsiderForCutting();
516             // Must be redrawn anyway to redraw the cursor
517             mustRedraw = true;
518         }
519     }
520 
521     // Update to-be-sculpted edge
522     if(global()->toolMode() == Global::SCULPT)
523     {
524         VectorAnimationComplex::VAC * vac = scene_->activeVAC();
525         if (vac)
526         {
527             Time time = interactiveTime();
528             vac->updateSculpt(x, y, time);
529             mustRedraw = true;
530         }
531     }
532 
533     // Update to-be-painted face
534     if(global()->toolMode() == Global::PAINT)
535     {
536         VectorAnimationComplex::VAC * vac = scene_->activeVAC();
537         if (vac)
538         {
539             Time time = interactiveTime();
540             vac->updateToBePaintedFace(x, y, time);
541             mustRedraw = true;
542         }
543     }
544 
545     // Redraw if necessary
546     if(mustRedraw)
547     {
548         // so that the highlighted object is also highlighted in other views
549         // this is a matter of preference, we could call only "update()" if
550         // we don't like this behaviour. But I like it, personally. Maybe
551         // I could add it as a user preference
552         emit allViewsNeedToUpdate();
553     }
554 }
555 
interactiveTime() const556 Time View::interactiveTime() const
557 {
558     return viewSettings_.time();
559 }
560 
561 
PMRPressEvent(int action,double x,double y)562 void View::PMRPressEvent(int action, double x, double y)
563 {
564     currentAction_ = action;
565 
566     // It is View's responsibility to call update() or updatePicking
567     // for mouse PMR actions
568     global()->setSceneCursorPos(Eigen::Vector2d(x,y));
569 
570     if(action==SKETCH_ACTION)
571     {
572         // here, possibly,  the scene  has several layers  that it
573         // knows about, as  well as which one is  active, and then
574         // returns the active one.
575         //
576         // but the  scene doesn't know  at which time the  user is
577         // drawing, since it depends  on the view.  (should active
578         // layer depends on the view?  -> my current answer is no,
579         // too  confusing.   But could  be  an option,  eventually
580         // disable by default.  It would increases the flexibility
581         // of the software).
582         //
583         // Current approach is then:
584         //   1) The scene only knows which layer (ASG) is active
585         //   2) The view only knows the time we are drawing in
586 
587         // Future ideas:
588         //   Each  view  would  be  able  to see  the  scene  with
589         //   different  translation/scale/rotation  (eg each  view
590         //   has its own camera). Hence here, first the point
591         //   (int xView, int yView) is converted into the point
592         //   pos = (double xScene, double yScene)
593 
594 
595         lastMousePos_ = QPoint(mouse_Event_X_,mouse_Event_Y_);
596 
597         // pos = viewToScene(x,y); <- by now: the identity
598         QPointF pos = QPointF(x,y);
599         double xScene = pos.rx();
600         double yScene = pos.ry();
601 
602         double w = global()->settings().edgeWidth();
603         bool debug = false;
604         if(!debug)
605         {
606             if(mouse_isTablet_ &&  global()->useTabletPressure())
607                 w *= 2 * mouse_tabletPressure_; // 2 so that a half-pressure would get the default width
608         }
609         vac_->beginSketchEdge(xScene,yScene, w, interactiveTime());
610 
611         //emit allViewsNeedToUpdatePicking();
612         //updateHighlightedObject(mouse_Event_X_, mouse_Event_Y_);
613         emit allViewsNeedToUpdate();
614     }
615     else if(action==DRAG_AND_DROP_ACTION)
616     {
617         vac_->prepareDragAndDrop(mouse_PressEvent_XScene_, mouse_PressEvent_YScene_, interactiveTime());
618     }
619     else if(action==TRANSFORM_SELECTION_ACTION)
620     {
621         vac_->beginTransformSelection(mouse_PressEvent_XScene_, mouse_PressEvent_YScene_, interactiveTime());
622     }
623     else if(action==RECTANGLE_OF_SELECTION_ACTION)
624     {
625         vac_->beginRectangleOfSelection(x,y,interactiveTime());
626     }
627     else if(action==SCULPT_CHANGE_RADIUS_ACTION)
628     {
629         sculptStartRadius_ = global()->sculptRadius();
630         sculptStartX_ = x;
631         sculptStartY_ = y;
632         sculptRadiusDx_ = 0;
633         sculptRadiusDy_ = 0;
634 
635         //emit allViewsNeedToUpdatePicking();
636         //updateHighlightedObject(mouse_Event_X_, mouse_Event_Y_);
637         //emit allViewsNeedToUpdate();
638     }
639     else if(action==SKETCH_CHANGE_PEN_WIDTH_ACTION)
640     {
641         sculptStartRadius_ = global()->edgeWidth();
642         sculptStartX_ = x;
643         sculptStartY_ = y;
644         sculptRadiusDx_ = 0;
645         sculptRadiusDy_ = 0;
646 
647         //emit allViewsNeedToUpdatePicking();
648         //updateHighlightedObject(mouse_Event_X_, mouse_Event_Y_);
649         //emit allViewsNeedToUpdate();
650     }
651     else if(action==SKETCH_CHANGE_SNAP_THRESHOLD_ACTION)
652     {
653         sculptStartRadius_ = global()->snapThreshold();
654         sculptStartX_ = x;
655         sculptStartY_ = y;
656         sculptRadiusDx_ = 0;
657         sculptRadiusDy_ = 0;
658 
659         //emit allViewsNeedToUpdatePicking();
660         //updateHighlightedObject(mouse_Event_X_, mouse_Event_Y_);
661         //emit allViewsNeedToUpdate();
662     }
663     else if(action==SKETCH_CHANGE_PEN_WIDTH_AND_SNAP_THRESHOLD_ACTION)
664     {
665         sculptStartRadius_ = global()->edgeWidth();
666         sculptStartRadius2_ = global()->snapThreshold();
667         sculptStartX_ = x;
668         sculptStartY_ = y;
669         sculptRadiusDx_ = 0;
670         sculptRadiusDy_ = 0;
671 
672         //emit allViewsNeedToUpdatePicking();
673         //updateHighlightedObject(mouse_Event_X_, mouse_Event_Y_);
674         //emit allViewsNeedToUpdate();
675     }
676     else if(action==SCULPT_DEFORM_ACTION)
677     {
678         sculptStartRadius_ = global()->sculptRadius();
679         sculptStartX_ = x;
680         sculptStartY_ = y;
681         vac_->beginSculptDeform(x,y);
682 
683         //emit allViewsNeedToUpdatePicking();
684         //updateHighlightedObject(mouse_Event_X_, mouse_Event_Y_);
685         //emit allViewsNeedToUpdate();
686     }
687     else if(action==SCULPT_CHANGE_WIDTH_ACTION)
688     {
689         sculptStartRadius_ = global()->sculptRadius();
690         sculptStartX_ = x;
691         sculptStartY_ = y;
692         vac_->beginSculptEdgeWidth(x,y);
693 
694         //emit allViewsNeedToUpdatePicking();
695         //updateHighlightedObject(mouse_Event_X_, mouse_Event_Y_);
696         //emit allViewsNeedToUpdate();
697     }
698     else if(action==SCULPT_SMOOTH_ACTION)
699     {
700         sculptStartRadius_ = global()->sculptRadius();
701         sculptStartX_ = x;
702         sculptStartY_ = y;
703         vac_->beginSculptSmooth(x,y);
704 
705         //emit allViewsNeedToUpdatePicking();
706         //updateHighlightedObject(mouse_Event_X_, mouse_Event_Y_);
707         //emit allViewsNeedToUpdate();
708     }
709     else
710         GLWidget::PMRPressEvent(action, x, y);
711 }
712 
PMRMoveEvent(int action,double x,double y)713 void View::PMRMoveEvent(int action, double x, double y)
714 {
715     global()->setSceneCursorPos(Eigen::Vector2d(x,y));
716 
717     if(action==SKETCH_ACTION)
718     {
719         QPoint mousePos = QPoint(mouse_Event_X_,mouse_Event_Y_);
720 
721         if(lastMousePos_ != mousePos && vac_)
722         {
723             lastMousePos_ = mousePos;
724 
725             double w = global()->settings().edgeWidth();
726             bool debug = false;
727             if(!debug)
728             {
729                 if(mouse_isTablet_ &&  global()->useTabletPressure())
730                     w *= 2 * mouse_tabletPressure_; // 2 so that a half-pressure would get the default width
731             }
732             vac_->continueSketchEdge(x,y, w); // Note: this call "changed", hence all views are updated
733         }
734 
735         //emit allViewsNeedToUpdatePicking();
736         updateHoveredObject(mouse_Event_X_, mouse_Event_Y_);
737         emit allViewsNeedToUpdate();
738     }
739     else if(action==DRAG_AND_DROP_ACTION)
740     {
741         vac_->performDragAndDrop(x, y);
742 
743         //emit allViewsNeedToUpdatePicking();
744         //updateHighlightedObject(mouse_Event_X_, mouse_Event_Y_);
745         emit allViewsNeedToUpdate();
746 
747     }
748     else if(action==TRANSFORM_SELECTION_ACTION)
749     {
750         vac_->continueTransformSelection(x, y);
751         emit allViewsNeedToUpdate();
752     }
753     else if(action==RECTANGLE_OF_SELECTION_ACTION)
754     {
755         vac_->continueRectangleOfSelection(x,y);
756 
757         emit allViewsNeedToUpdate();
758     }
759     else if(action==SCULPT_CHANGE_RADIUS_ACTION)
760     {
761         // Increment how much we moved
762         // method hiding the cursor and let it at the same position as on press
763         // obviously can't work with pen tablets, since position is absolute...
764         //sculptRadiusDx_ += x - sculptStartX_;
765         //sculptRadiusDy_ += y - sculptStartY_;
766         // hence just use the plain vanilla method
767         sculptRadiusDx_ = x - sculptStartX_;
768         sculptRadiusDy_ = y - sculptStartY_; // yes, this is useless, but can be useful later
769 
770         // Put mouse position back from where it was
771         //QPoint p(mapToGlobal(QPoint(sculptStartX_,sculptStartY_)));
772         //QCursor::setPos(p);
773 
774         // update radius
775         double newRadius = sculptStartRadius_ + sculptRadiusDx_;
776         if(newRadius < 0)
777             newRadius *= -1;
778         global()->setSculptRadius(newRadius);
779 
780         //emit allViewsNeedToUpdatePicking();
781         //updateHighlightedObject(mouse_Event_X_, mouse_Event_Y_);
782         emit allViewsNeedToUpdate();
783 
784     }
785     else if(action==SKETCH_CHANGE_PEN_WIDTH_ACTION)
786     {
787         // Get delta
788         sculptRadiusDx_ = x - sculptStartX_;
789         sculptRadiusDy_ = y - sculptStartY_;
790 
791         // Get new radius
792         double newRadius = sculptStartRadius_ + sculptRadiusDx_;
793         if(newRadius < 0)
794             newRadius *= -1;
795         global()->setEdgeWidth(newRadius);
796 
797         // Prevent painted cursor gadget to move
798         global()->setSceneCursorPos(Eigen::Vector2d(mouse_PressEvent_XScene_, mouse_PressEvent_YScene_));
799 
800         // Update
801         emit allViewsNeedToUpdate();
802     }
803     else if(action==SKETCH_CHANGE_SNAP_THRESHOLD_ACTION)
804     {
805         // Get delta
806         sculptRadiusDx_ = x - sculptStartX_;
807         sculptRadiusDy_ = y - sculptStartY_;
808 
809         // Get new radius
810         double newRadius = sculptStartRadius_ + sculptRadiusDx_;
811         if(newRadius < 0)
812             newRadius *= -1;
813         global()->setSnapThreshold(newRadius);
814 
815         // Prevent painted cursor gadget to move
816         global()->setSceneCursorPos(Eigen::Vector2d(mouse_PressEvent_XScene_, mouse_PressEvent_YScene_));
817 
818         // Update
819         emit allViewsNeedToUpdate();
820     }
821     else if(action==SKETCH_CHANGE_PEN_WIDTH_AND_SNAP_THRESHOLD_ACTION)
822     {
823         // Get delta
824         sculptRadiusDx_ = x - sculptStartX_;
825         sculptRadiusDy_ = y - sculptStartY_;
826 
827         // Get new radius
828         double newRadius = sculptStartRadius_ + sculptRadiusDx_;
829         if(newRadius < 0)
830             newRadius *= -1;
831         global()->setEdgeWidth(newRadius);
832 
833         // Get new radius 2
834         double newRadius2 = 0;
835         if(sculptStartRadius_ > 0)
836         {
837             newRadius2 = sculptStartRadius2_ * newRadius / sculptStartRadius_;
838         }
839         else
840         {
841             newRadius2 = sculptStartRadius2_ + sculptRadiusDx_;
842         }
843         if(newRadius2 < 0)
844             newRadius2 *= -1;
845         global()->setSnapThreshold(newRadius2);
846 
847        // Prevent painted cursor gadget to move
848         global()->setSceneCursorPos(Eigen::Vector2d(mouse_PressEvent_XScene_, mouse_PressEvent_YScene_));
849 
850         // Update
851         emit allViewsNeedToUpdate();
852     }
853     else if(action==SCULPT_DEFORM_ACTION)
854     {
855         vac_->continueSculptDeform(x,y);
856 
857         //emit allViewsNeedToUpdatePicking();
858         //updateHighlightedObject(mouse_Event_X_, mouse_Event_Y_);
859         emit allViewsNeedToUpdate();
860     }
861     else if(action==SCULPT_CHANGE_WIDTH_ACTION)
862     {
863         vac_->continueSculptEdgeWidth(x,y);
864 
865         //emit allViewsNeedToUpdatePicking();
866         //updateHighlightedObject(mouse_Event_X_, mouse_Event_Y_);
867         emit allViewsNeedToUpdate();
868     }
869     else if(action==SCULPT_SMOOTH_ACTION)
870     {
871         vac_->continueSculptSmooth(x,y);
872 
873         //emit allViewsNeedToUpdatePicking();
874         //updateHighlightedObject(mouse_Event_X_, mouse_Event_Y_);
875         emit allViewsNeedToUpdate();
876     }
877     else
878         GLWidget::PMRMoveEvent(action, x, y);
879 }
PMRReleaseEvent(int action,double x,double y)880 void View::PMRReleaseEvent(int action, double x, double y)
881 {
882     currentAction_ = 0;
883 
884     global()->setSceneCursorPos(Eigen::Vector2d(x,y));
885 
886     if(action==SKETCH_ACTION)
887     {
888         vac_->endSketchEdge();
889 
890         emit allViewsNeedToUpdatePicking();
891         updateHoveredObject(mouse_Event_X_, mouse_Event_Y_);
892         emit allViewsNeedToUpdate();
893     }
894     else if(action==DRAG_AND_DROP_ACTION)
895     {
896         vac_->completeDragAndDrop();
897 
898         emit allViewsNeedToUpdatePicking();
899         updateHoveredObject(mouse_Event_X_, mouse_Event_Y_);
900         emit allViewsNeedToUpdate();
901     }
902     else if(action==TRANSFORM_SELECTION_ACTION)
903     {
904         vac_->endTransformSelection();
905         emit allViewsNeedToUpdatePicking();
906         updateHoveredObject(mouse_Event_X_, mouse_Event_Y_);
907         emit allViewsNeedToUpdate();
908     }
909     else if(action==RECTANGLE_OF_SELECTION_ACTION)
910     {
911         vac_->endRectangleOfSelection();
912 
913         emit allViewsNeedToUpdatePicking();
914         updateHoveredObject(mouse_Event_X_, mouse_Event_Y_);
915         emit allViewsNeedToUpdate();
916     }
917     else if(action==SCULPT_CHANGE_RADIUS_ACTION)
918     {
919         vac_->updateSculpt(x, y, interactiveTime());
920 
921         emit allViewsNeedToUpdatePicking();
922         updateHoveredObject(mouse_Event_X_, mouse_Event_Y_);
923         emit allViewsNeedToUpdate();
924     }
925     else if(action==SKETCH_CHANGE_PEN_WIDTH_AND_SNAP_THRESHOLD_ACTION)
926     {
927         emit allViewsNeedToUpdatePicking();
928         updateHoveredObject(mouse_Event_X_, mouse_Event_Y_);
929         emit allViewsNeedToUpdate();
930     }
931     else if(action==SCULPT_DEFORM_ACTION)
932     {
933         vac_->endSculptDeform();
934         vac_->updateSculpt(x, y, interactiveTime());
935 
936         emit allViewsNeedToUpdatePicking();
937         updateHoveredObject(mouse_Event_X_, mouse_Event_Y_);
938         emit allViewsNeedToUpdate();
939     }
940     else if(action==SCULPT_CHANGE_WIDTH_ACTION)
941     {
942         vac_->endSculptEdgeWidth();
943         vac_->updateSculpt(x, y, interactiveTime());
944 
945         emit allViewsNeedToUpdatePicking();
946         updateHoveredObject(mouse_Event_X_, mouse_Event_Y_);
947         emit allViewsNeedToUpdate();
948     }
949     else if(action==SCULPT_SMOOTH_ACTION)
950     {
951         vac_->endSculptSmooth();
952         vac_->updateSculpt(x, y, interactiveTime());
953 
954         emit allViewsNeedToUpdatePicking();
955         updateHoveredObject(mouse_Event_X_, mouse_Event_Y_);
956         emit allViewsNeedToUpdate();
957     }
958     else
959         GLWidget::PMRReleaseEvent(action, x, y);
960 
961 }
962 
963 /***********************************************************
964  *              DRAWING
965  */
966 
onBackgroundDestroyed_(Background * background)967 void View::onBackgroundDestroyed_(Background * background)
968 {
969     destroyBackgroundRenderer_(background);
970 }
971 
getBackgroundRenderer_(Background * background)972 BackgroundRenderer * View::getBackgroundRenderer_(Background * background)
973 {
974     auto it = backgroundRenderers_.find(background);
975     if (it != backgroundRenderers_.end())
976     {
977         return it.value();
978     }
979     else
980     {
981         return nullptr;
982     }
983 }
984 
createBackgroundRenderer_(Background * background)985 BackgroundRenderer * View::createBackgroundRenderer_(Background * background)
986 {
987     BackgroundRenderer * res = new BackgroundRenderer(background, this);
988     connect(res, &BackgroundRenderer::backgroundDestroyed, this, &View::onBackgroundDestroyed_);
989     backgroundRenderers_.insert(background, res);
990     return res;
991 }
992 
destroyBackgroundRenderer_(Background * background)993 void View::destroyBackgroundRenderer_(Background * background)
994 {
995     BackgroundRenderer * br = getBackgroundRenderer_(background);
996     backgroundRenderers_.remove(background);
997     delete br;
998 }
999 
getOrCreateBackgroundRenderer_(Background * background)1000 BackgroundRenderer * View::getOrCreateBackgroundRenderer_(Background * background)
1001 {
1002     BackgroundRenderer * br = getBackgroundRenderer_(background);
1003     if (!br) {
1004         br = createBackgroundRenderer_(background);
1005     }
1006     return br;
1007 }
1008 
drawBackground_(Background * background,int frame)1009 void View::drawBackground_(Background * background, int frame)
1010 {
1011     BackgroundRenderer * br = getOrCreateBackgroundRenderer_(background);
1012     br->draw(frame,
1013              global()->showCanvas(),
1014              scene_->left(), scene_->top(), scene_->width(), scene_->height(),
1015              xSceneMin(), xSceneMax(), ySceneMin(), ySceneMax());
1016 }
1017 
drawScene()1018 void View::drawScene()
1019 {
1020     if(!mouse_HideCursor_)
1021     {
1022         setCursor(Qt::ArrowCursor);
1023         switch(global()->toolMode())
1024         {
1025         case Global::SELECT:
1026             setCursor(Qt::ArrowCursor);
1027             break;
1028         case Global::SKETCH:
1029             setCursor(Qt::CrossCursor);
1030             break;
1031         case Global::PAINT:
1032             setCursor(Qt::CrossCursor);
1033             break;
1034         case Global::SCULPT:
1035             setCursor(Qt::CrossCursor);
1036             break;
1037         default:
1038             break;
1039         }
1040     }
1041 
1042     // Clear to white
1043     glClearColor(1.0,1.0,1.0,1.0);
1044     glClear(GL_COLOR_BUFFER_BIT);
1045 
1046     // Note:
1047     // It is the responsability of view to decide when to call scene()->drawCanvas
1048     // draw a canvas, and layer backgrounds, and in which order,
1049     // since this is dependent on onion skinning settings which only
1050     // view should be aware of
1051 
1052     // Draw canvas
1053     // XXX Should be replaced by drawCanvas_(scene_->canvas());
1054     scene_->drawCanvas(viewSettings_);
1055 
1056     // Draw scene
1057     drawSceneDelegate_(activeTime());
1058 }
1059 
drawSceneDelegate_(Time t)1060 void View::drawSceneDelegate_(Time t)
1061 {
1062     for (int j = 0; j < scene()->numLayers(); ++j)
1063     {
1064         Layer * layer = scene()->layer(j);
1065         if (!layer->isVisible()) {
1066             continue;
1067         }
1068         Background * background = layer->background();
1069         VectorAnimationComplex::VAC * vac = layer->vac();
1070 
1071         // Draw background
1072         drawBackground_(background, t.frame());
1073 
1074         // Loop over all onion skins. Draw in this order:
1075         //   1. onion skins before
1076         //   2. onion skins after
1077         //   3. current frame
1078         //
1079         // Note 1: For now, we show onions skins for all layers
1080         //         In the future, by default, we should show onion skins only
1081         //         for the active layer, and allow user to show them for all
1082         //         layer via a user option in the onion skin menu.
1083         //
1084         // Note 2: Backgrounds are always ignored for onion skinning
1085 
1086         // Draw onion skins
1087         viewSettings_.setMainDrawing(false);
1088         if(viewSettings_.onionSkinningIsEnabled())
1089         {
1090             // Draw onion skins before
1091             Time tOnion = t;
1092             for(int i=0; i<viewSettings_.numOnionSkinsBefore(); ++i)
1093             {
1094                 tOnion = tOnion - viewSettings_.onionSkinsTimeOffset();
1095                 glTranslated(-viewSettings_.onionSkinsXOffset(),-viewSettings_.onionSkinsYOffset(),0);
1096             }
1097             for(int i=0; i<viewSettings_.numOnionSkinsBefore(); ++i)
1098             {
1099                 vac->draw(tOnion, viewSettings_);
1100                 tOnion = tOnion + viewSettings_.onionSkinsTimeOffset();
1101                 glTranslated(viewSettings_.onionSkinsXOffset(),viewSettings_.onionSkinsYOffset(),0);
1102             }
1103 
1104             // Draw onion skins after
1105             tOnion = t;
1106             for(int i=0; i<viewSettings_.numOnionSkinsAfter(); ++i)
1107             {
1108                 glTranslated(viewSettings_.onionSkinsXOffset(),viewSettings_.onionSkinsYOffset(),0);
1109                 tOnion = tOnion + viewSettings_.onionSkinsTimeOffset();
1110                 vac->draw(tOnion, viewSettings_);
1111             }
1112             for(int i=0; i<viewSettings_.numOnionSkinsAfter(); ++i)
1113             {
1114                 glTranslated(-viewSettings_.onionSkinsXOffset(),-viewSettings_.onionSkinsYOffset(),0);
1115             }
1116         }
1117 
1118         // Draw current frame
1119         viewSettings_.setMainDrawing(true);
1120         vac->draw(t, viewSettings_);
1121     }
1122 }
1123 
toggleOutline()1124 void View::toggleOutline()
1125 {
1126     viewSettings_.toggleOutline();
1127     viewSettingsWidget_->updateWidgetFromSettings();
1128     update();
1129 }
1130 
toggleOutlineOnly()1131 void View::toggleOutlineOnly()
1132 {
1133     viewSettings_.toggleOutlineOnly();
1134     viewSettingsWidget_->updateWidgetFromSettings();
1135     update();
1136 }
1137 
setDisplayMode(ViewSettings::DisplayMode displayMode)1138 void View::setDisplayMode(ViewSettings::DisplayMode displayMode)
1139 {
1140     viewSettings_.setDisplayMode(displayMode);
1141     viewSettingsWidget_->updateWidgetFromSettings();
1142     update();
1143 }
1144 
setOnionSkinningEnabled(bool enabled)1145 void View::setOnionSkinningEnabled(bool enabled)
1146 {
1147     viewSettings_.setOnionSkinningIsEnabled(enabled);
1148     viewSettingsWidget_->updateWidgetFromSettings();
1149     update();
1150 }
1151 
fitAllInWindow()1152 void View::fitAllInWindow()
1153 {
1154     // TODO
1155 }
1156 
fitSelectionInWindow()1157 void View::fitSelectionInWindow()
1158 {
1159     // TODO
1160 }
1161 
zoom() const1162 double View::zoom() const
1163 {
1164     return camera2D().zoom();
1165 }
1166 
1167 // Note: In the future, when rotation of the viewport is allowed,
1168 //       then it should be replaced by:
1169 //           xSceneMin = min(x1, x2, x3, x4);
1170 //           xSceneMax = max(x1, x2, x3, x4);
1171 //           ySceneMin = min(y1, y2, y3, y4);
1172 //           ySceneMax = max(y1, y2, y3, y4);
1173 //       where the (xi,yi)'s are the four corners of the viewport in
1174 //       scene coordinate, which in general will not be axis-aligned
1175 
xSceneMin() const1176 double  View::xSceneMin() const
1177 {
1178     return - camera2D().x() / zoom();
1179 }
1180 
ySceneMin() const1181 double  View::ySceneMin() const
1182 {
1183     return - camera2D().y() / zoom();
1184 }
1185 
xSceneMax() const1186 double  View::xSceneMax() const
1187 {
1188     double x = xSceneMin();
1189     double w = width();
1190     double z = zoom();
1191 
1192     return x+w/z;
1193 }
1194 
ySceneMax() const1195 double  View::ySceneMax() const
1196 {
1197     double x = ySceneMin();
1198     double w = height();
1199     double z = zoom();
1200 
1201     return x+w/z;
1202 }
1203 
viewSettings() const1204 ViewSettings View::viewSettings() const
1205 {
1206     return viewSettings_;
1207 }
1208 
viewSettingsWidget() const1209 ViewSettingsWidget * View::viewSettingsWidget() const
1210 {
1211     return viewSettingsWidget_;
1212 }
1213 
1214 
1215 /***********************************************************
1216  *              PICKING
1217  */
1218 
drawPick()1219 void View::drawPick()
1220 {
1221     Time t = activeTime();
1222     {
1223         if(viewSettings_.onionSkinningIsEnabled() && viewSettings_.areOnionSkinsPickable())
1224         {
1225             Time tOnion = t;
1226             for(int i=0; i<viewSettings_.numOnionSkinsBefore(); ++i)
1227             {
1228                 tOnion = tOnion - viewSettings_.onionSkinsTimeOffset();
1229                 glTranslated(-viewSettings_.onionSkinsXOffset(),-viewSettings_.onionSkinsYOffset(),0);
1230             }
1231             for(int i=0; i<viewSettings_.numOnionSkinsBefore(); ++i)
1232             {
1233                 scene_->drawPick(tOnion, viewSettings_);
1234                 tOnion = tOnion + viewSettings_.onionSkinsTimeOffset();
1235                 glTranslated(viewSettings_.onionSkinsXOffset(),viewSettings_.onionSkinsYOffset(),0);
1236             }
1237 
1238             tOnion = t;
1239             for(int i=0; i<viewSettings_.numOnionSkinsAfter(); ++i)
1240             {
1241                 glTranslated(viewSettings_.onionSkinsXOffset(),viewSettings_.onionSkinsYOffset(),0);
1242                 tOnion = tOnion + viewSettings_.onionSkinsTimeOffset();
1243                 scene_->drawPick(tOnion, viewSettings_);
1244             }
1245             for(int i=0; i<viewSettings_.numOnionSkinsAfter(); ++i)
1246             {
1247                 glTranslated(-viewSettings_.onionSkinsXOffset(),-viewSettings_.onionSkinsYOffset(),0);
1248             }
1249         }
1250 
1251         // Draw current frame
1252         scene_->drawPick(t, viewSettings_);
1253     }
1254 }
1255 
updateHoveredObject(int x,int y)1256 bool View::updateHoveredObject(int x, int y)
1257 {
1258     // make sure this does NOT redraw the scene, just change its highlighted status.
1259 
1260     if(!pickingIsEnabled_)
1261         return false;
1262 
1263     // Don't do anything if no picking image
1264     if(!pickingImg_)
1265         return false;
1266 
1267     // Find object under the mouse
1268     Picking::Object old = hoveredObject_;
1269     if(x<0 || x>=pickingWidth_ || y<0 || y>=pickingHeight_)
1270     {
1271         hoveredObject_ = Picking::Object();
1272     }
1273     else
1274     {
1275         hoveredObject_ = getCloserObject(x, y);
1276     }
1277 
1278     // Check if it has changed
1279     bool hasChanged = !(hoveredObject_ == old);
1280 
1281     // If it has, inform the scene of the new highlighted state
1282     if(hasChanged)
1283     {
1284         if(hoveredObject_.isNull())
1285             scene_->setNoHoveredObject();
1286         else
1287             scene_->setHoveredObject(
1288                 activeTime(),
1289                 hoveredObject_.index(),
1290                 hoveredObject_.id());
1291     }
1292     else
1293     {
1294         // even if it has not changed, inform the scene when nothing's highlighted
1295         if(hoveredObject_.isNull())
1296             scene_->setNoHoveredObject();
1297     }
1298 
1299     return hasChanged;
1300 }
1301 
pickingImg(int x,int y)1302 uchar * View::pickingImg(int x, int y)
1303 {
1304     int k = 4*( (pickingHeight_ - y - 1)*pickingWidth_ + x);
1305     return &pickingImg_[k];
1306 }
1307 
1308 // This method must be very fast. Assumes x and y in range
getCloserObject(int x,int y)1309 Picking::Object View::getCloserObject(int x, int y)
1310 {
1311     // First look directly whether there's an object right at mouse position
1312     uchar * p = pickingImg(x,y);
1313     uchar r=p[0], g=p[1], b=p[2];
1314     if(r!=255 || g!=255 || b!=255)
1315     {
1316         return Picking::objectFromRGB(r,g,b);
1317     }
1318     else
1319     {
1320         // If not, look around in a radius of D pixels
1321         int D = 3;
1322 
1323         // Clipping
1324         if(x<D)
1325             D = x;
1326         if(y<D)
1327             D = y;
1328         int rightBorderDist = pickingWidth_-1-x;
1329         if(rightBorderDist<D)
1330             D = rightBorderDist;
1331         int bottomBorderDist = pickingHeight_-1-y;
1332         if(bottomBorderDist<D)
1333             D = bottomBorderDist;
1334 
1335 
1336         for(int d=1; d<=D; d++)
1337         {
1338             // top row
1339             for(int varX=x-d; varX<=x+d; varX++)
1340             {
1341                 p = pickingImg(varX,y-d);
1342                 r=p[0], g=p[1], b=p[2];
1343                 if(r!=255 || g!=255 || b!=255)
1344                     return Picking::objectFromRGB(r,g,b);
1345             }
1346             // bottom row
1347             for(int varX=x-d; varX<=x+d; varX++)
1348             {
1349                 p = pickingImg(varX,y+d);
1350                 r=p[0], g=p[1], b=p[2];
1351                 if(r!=255 || g!=255 || b!=255)
1352                     return Picking::objectFromRGB(r,g,b);
1353             }
1354             // left column
1355             for(int varY=y-d; varY<=y+d; varY++)
1356             {
1357                 p = pickingImg(x-d,varY);
1358                 r=p[0], g=p[1], b=p[2];
1359                 if(r!=255 || g!=255 || b!=255)
1360                     return Picking::objectFromRGB(r,g,b);
1361             }
1362             // right column
1363             for(int varY=y-d; varY<=y+d; varY++)
1364             {
1365                 p = pickingImg(x+d,varY);
1366                 r=p[0], g=p[1], b=p[2];
1367                 if(r!=255 || g!=255 || b!=255)
1368                     return Picking::objectFromRGB(r,g,b);
1369             }
1370         }
1371 
1372         // If still no object found, return a null object
1373         return Picking::Object();
1374     }
1375 }
1376 
deletePicking()1377 void View::deletePicking()
1378 {
1379     if(pickingImg_)
1380     {
1381         gl_fbo_->glDeleteFramebuffers(1, &fboId_);
1382         gl_fbo_->glDeleteRenderbuffers(1, &rboId_);
1383         glDeleteTextures(1, &textureId_);
1384         hoveredObject_ = Picking::Object();
1385         delete[] pickingImg_;
1386         pickingImg_ = 0;
1387         pickingWidth_ = 0;
1388         pickingHeight_ = 0;
1389     }
1390 }
1391 
newPicking()1392 void View::newPicking()
1393 {
1394     pickingWidth_ = width();
1395     pickingHeight_ = height();
1396     pickingImg_ = new uchar[4 * pickingWidth_ * pickingHeight_];
1397 
1398     //  code adapted from http://www.songho.ca/opengl/gl_fbo.html
1399 
1400     // create a texture object
1401     glGenTextures(1, &textureId_);
1402     glBindTexture(GL_TEXTURE_2D, textureId_);
1403     glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
1404     glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
1405     glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
1406     glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
1407     glTexParameteri(GL_TEXTURE_2D, GL_GENERATE_MIPMAP, GL_TRUE); // automatic mipmap
1408     glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, pickingWidth_, pickingHeight_, 0,
1409                  GL_RGBA, GL_UNSIGNED_BYTE, 0);
1410     glBindTexture(GL_TEXTURE_2D, 0);
1411 
1412     // create a renderbuffer object to store depth info
1413     gl_fbo_->glGenRenderbuffers(1, &rboId_);
1414     gl_fbo_->glBindRenderbuffer(GL_RENDERBUFFER, rboId_);
1415     gl_fbo_->glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT,
1416                                    pickingWidth_, pickingHeight_);
1417     gl_fbo_->glBindRenderbuffer(GL_RENDERBUFFER, 0);
1418 
1419     // create a framebuffer object
1420     gl_fbo_->glGenFramebuffers(1, &fboId_);
1421     gl_fbo_->glBindFramebuffer(GL_FRAMEBUFFER, fboId_);
1422 
1423     // attach the texture to FBO color attachment point
1424     gl_fbo_->glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
1425                                     GL_TEXTURE_2D, textureId_, 0);
1426 
1427     // attach the renderbuffer to depth attachment point
1428     gl_fbo_->glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT,
1429                                        GL_RENDERBUFFER, rboId_);
1430 
1431     // check FBO status
1432     GLenum status = gl_fbo_->glCheckFramebufferStatus(GL_FRAMEBUFFER);
1433     if(status != GL_FRAMEBUFFER_COMPLETE)
1434     {
1435         qDebug() << "ERROR void View::newPicking()"
1436                << "FBO status != GL_FRAMEBUFFER_COMPLETE";
1437         return;
1438     }
1439 
1440     // switch back to window-system-provided framebuffer
1441     gl_fbo_->glBindFramebuffer(GL_FRAMEBUFFER, defaultFramebufferObject());
1442 }
1443 
1444 #include <QElapsedTimer>
1445 #include <QtDebug>
1446 
enablePicking()1447 void View::enablePicking()
1448 {
1449     pickingIsEnabled_ = true;
1450 }
1451 
disablePicking()1452 void View::disablePicking()
1453 {
1454     pickingIsEnabled_ = false;
1455 }
1456 
1457 namespace
1458 {
1459 
imageCleanupHandler(void * info)1460 void imageCleanupHandler(void * info)
1461 {
1462     uchar * img = reinterpret_cast<uchar*>(info);
1463     delete[] img;
1464 }
1465 
1466 }
1467 
drawToImage(double x,double y,double w,double h,int imgW,int imgH,bool useViewSettings)1468 QImage View::drawToImage(double x, double y, double w, double h, int imgW, int imgH, bool useViewSettings)
1469 {
1470     return drawToImage(activeTime(), x, y, w, h, imgW, imgH, useViewSettings);
1471 }
1472 
drawToImage(Time t,double x,double y,double w,double h,int IMG_SIZE_X,int IMG_SIZE_Y,bool useViewSettings)1473 QImage View::drawToImage(Time t, double x, double y, double w, double h, int IMG_SIZE_X, int IMG_SIZE_Y, bool useViewSettings)
1474 {
1475     // Make this widget's rendering context the current OpenGL context
1476     makeCurrent();
1477 
1478 
1479     // ------------ Create multisample FBO --------------------
1480 
1481     GLuint ms_fboId;
1482     GLuint ms_ColorBufferId;
1483     GLuint ms_DepthBufferId;
1484     GLint  ms_samples;
1485 
1486     // Maximum supported samples
1487     glGetIntegerv(GL_MAX_SAMPLES, &ms_samples);
1488     // Create FBO
1489     gl_fbo_->glGenFramebuffers(1, &ms_fboId);
1490     gl_fbo_->glBindFramebuffer(GL_FRAMEBUFFER, ms_fboId);
1491     // Create multisample color buffer
1492     gl_fbo_->glGenRenderbuffers(1, &ms_ColorBufferId);
1493     gl_fbo_->glBindRenderbuffer(GL_RENDERBUFFER, ms_ColorBufferId);
1494     gl_fbo_->glRenderbufferStorageMultisample(GL_RENDERBUFFER, ms_samples, GL_RGBA8, IMG_SIZE_X, IMG_SIZE_Y);
1495     // Create multisample depth buffer
1496     gl_fbo_->glGenRenderbuffers(1, &ms_DepthBufferId);
1497     gl_fbo_->glBindRenderbuffer(GL_RENDERBUFFER, ms_DepthBufferId);
1498     gl_fbo_->glRenderbufferStorageMultisample(GL_RENDERBUFFER, ms_samples, GL_DEPTH_COMPONENT24, IMG_SIZE_X, IMG_SIZE_Y);
1499     // Attach render buffers to FBO
1500     gl_fbo_->glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, ms_ColorBufferId);
1501     gl_fbo_->glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, ms_DepthBufferId);
1502     // Check FBO status
1503     GLenum ms_status = gl_fbo_->glCheckFramebufferStatus(GL_FRAMEBUFFER);
1504     if(ms_status != GL_FRAMEBUFFER_COMPLETE) {
1505         qDebug() << "Error: FBO ms_status != GL_FRAMEBUFFER_COMPLETE";
1506         return QImage();
1507     }
1508 
1509 
1510     // ------------ Create standard FBO --------------------
1511 
1512     GLuint fboId;
1513     GLuint textureId;
1514     GLuint rboId;
1515 
1516     // Create FBO
1517     gl_fbo_->glGenFramebuffers(1, &fboId);
1518     gl_fbo_->glBindFramebuffer(GL_FRAMEBUFFER, fboId);
1519     // Create color texture
1520     glGenTextures(1, &textureId);
1521     glBindTexture(GL_TEXTURE_2D, textureId);
1522     glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
1523     glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
1524     glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
1525     glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
1526     glTexParameteri(GL_TEXTURE_2D, GL_GENERATE_MIPMAP, GL_TRUE);
1527     glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, IMG_SIZE_X, IMG_SIZE_Y, 0,
1528                  GL_RGBA, GL_UNSIGNED_BYTE, 0);
1529     glBindTexture(GL_TEXTURE_2D, 0);
1530     // Create depth buffer
1531     gl_fbo_->glGenRenderbuffers(1, &rboId);
1532     gl_fbo_->glBindRenderbuffer(GL_RENDERBUFFER, rboId);
1533     gl_fbo_->glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT, IMG_SIZE_X, IMG_SIZE_Y);
1534     gl_fbo_->glBindRenderbuffer(GL_RENDERBUFFER, 0);
1535     // Attach render buffers / textures to FBO
1536     gl_fbo_->glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, textureId, 0);
1537     gl_fbo_->glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, rboId);
1538     // Check FBO status
1539     GLenum status = gl_fbo_->glCheckFramebufferStatus(GL_FRAMEBUFFER);
1540     if(status != GL_FRAMEBUFFER_COMPLETE) {
1541         qDebug() << "Error: FBO status != GL_FRAMEBUFFER_COMPLETE";
1542         return QImage();
1543     }
1544 
1545 
1546     // ------------ Render scene to multisample FBO --------------------
1547 
1548     // Bind FBO
1549     gl_fbo_->glBindFramebuffer(GL_FRAMEBUFFER, ms_fboId);
1550 
1551     // Set viewport
1552     GLint oldViewport[4];
1553     glGetIntegerv(GL_VIEWPORT, oldViewport);
1554     glViewport(0, 0, IMG_SIZE_X, IMG_SIZE_Y);
1555 
1556     // Clear FBO to fully transparent
1557     glClearColor(0.0, 0.0, 0.0, 0.0);
1558     glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
1559 
1560     // Set projection matrix
1561     // Note: (0,h) and not (h,0) since y-axis is down in VPaint, up in QImage
1562     glMatrixMode(GL_PROJECTION);
1563     glLoadIdentity();
1564     glOrtho(0, w, 0, h, 0, 1);
1565 
1566     // Set view matrix
1567     glMatrixMode (GL_MODELVIEW);
1568     GLWidget_Camera2D camera2d;
1569     camera2d.setX(-x);
1570     camera2d.setY(-y);
1571     camera2d.setZoom(1);
1572     glLoadMatrixd(camera2d.viewMatrixData());
1573 
1574     // Draw scene
1575     if (useViewSettings)
1576     {
1577         drawSceneDelegate_(t);
1578     }
1579     else
1580     {
1581         ViewSettings::DisplayMode oldDM = viewSettings_.displayMode();
1582         viewSettings_.setDisplayMode(ViewSettings::ILLUSTRATION);
1583         viewSettings_.setMainDrawing(false);
1584         viewSettings_.setDrawCursor(false);
1585 
1586         for (int j = 0; j < scene()->numLayers(); ++j)
1587         {
1588             Layer * layer = scene()->layer(j);
1589             if (layer->isVisible()) {
1590                 drawBackground_(layer->background(), t.frame());
1591                 layer->vac()->draw(t, viewSettings_);
1592             }
1593         }
1594 
1595         viewSettings_.setDrawCursor(true);
1596         viewSettings_.setDisplayMode(oldDM);
1597     }
1598 
1599     // Restore viewport
1600     glViewport(oldViewport[0], oldViewport[1], oldViewport[2], oldViewport[3]);
1601 
1602     // Unbind FBO
1603     gl_fbo_->glBindFramebuffer(GL_FRAMEBUFFER, defaultFramebufferObject());
1604 
1605 
1606     // ------ Blit multisample FBO to standard FBO ---------
1607 
1608     // Bind multisample FBO for reading
1609     gl_fbo_->glBindFramebuffer(GL_READ_FRAMEBUFFER, ms_fboId);
1610     // Bind standard FBO for drawing
1611     gl_fbo_->glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fboId);
1612     // Blit
1613     gl_fbo_->glBlitFramebuffer(0, 0, IMG_SIZE_X, IMG_SIZE_Y, 0, 0, IMG_SIZE_X, IMG_SIZE_Y, GL_COLOR_BUFFER_BIT, GL_NEAREST);
1614     // Unbind FBO
1615     gl_fbo_->glBindFramebuffer(GL_FRAMEBUFFER, defaultFramebufferObject());
1616 
1617 
1618     // ------ Read standard FBO to RAM data ---------
1619 
1620     // Bind standard FBO for reading
1621     glBindTexture(GL_TEXTURE_2D, textureId);
1622     // Read
1623     uchar * img = new uchar[4 * IMG_SIZE_X * IMG_SIZE_Y];
1624     glGetTexImage(GL_TEXTURE_2D, 0, GL_RGBA, GL_UNSIGNED_BYTE,  img);
1625     // Unbind FBO
1626     glBindTexture(GL_TEXTURE_2D, 0);
1627 
1628 
1629     // ------ Release allocated GPU memory  ---------
1630 
1631     gl_fbo_->glDeleteFramebuffers(1, &ms_fboId);
1632     gl_fbo_->glDeleteRenderbuffers(1, &ms_ColorBufferId);
1633     gl_fbo_->glDeleteRenderbuffers(1, &ms_DepthBufferId);
1634     gl_fbo_->glDeleteFramebuffers(1, &fboId);
1635     gl_fbo_->glDeleteRenderbuffers(1, &rboId);
1636     glDeleteTextures(1, &textureId);
1637 
1638 
1639     // ------ un-premultiply alpha ---------
1640 
1641     // Once can notice that glBlendFuncSeparate(alpha, 1-alpha, 1, 1-alpha)
1642     // performs the correct blending function with input:
1643     //    Frame buffer color as pre-multiplied alpha
1644     //    Input fragment color as post-multiplied alpha
1645     // and output:
1646     //    New frame buffer color as pre-multiplied alpha
1647     //
1648     // So by starting with glClearColor(0.0, 0.0, 0.0, 0.0), which is the
1649     // correct pre-multiplied representation for fully transparent, then
1650     // by specifying glColor() in post-multiplied alpha, we get the correct
1651     // blending behaviour and simply have to un-premultiply the value obtained
1652     // in the frame buffer at the very end
1653 
1654     for(int k=0; k<IMG_SIZE_X*IMG_SIZE_Y; ++k)
1655     {
1656         uchar * pixel = &(img[4*k]);
1657         double a = pixel[3];
1658         if( 0 < a && a < 255 )
1659         {
1660             double s = 255.0 / a;
1661             pixel[0] = (uchar) (std::min(255.0,std::floor(0.5+s*pixel[0])));
1662             pixel[1] = (uchar) (std::min(255.0,std::floor(0.5+s*pixel[1])));
1663             pixel[2] = (uchar) (std::min(255.0,std::floor(0.5+s*pixel[2])));
1664         }
1665     }
1666 
1667 
1668     // ------ Convert to Qimage ---------
1669 
1670     // Create cleanup info to delete[] img when appropriate
1671     QImageCleanupFunction cleanupFunction = &imageCleanupHandler;
1672     void * cleanupInfo = reinterpret_cast<void*>(img);
1673 
1674     // Create QImage
1675     QImage res(img, IMG_SIZE_X, IMG_SIZE_Y, QImage::Format_RGBA8888, cleanupFunction, cleanupInfo);
1676 
1677     // Return QImage
1678     return res;
1679 }
1680 
updatePicking()1681 void View::updatePicking()
1682 {
1683     // Remove previously highlighted object
1684     hoveredObject_ = Picking::Object();
1685 
1686     if(!pickingIsEnabled_)
1687         return;
1688 
1689     // Make this widget's rendering context the current OpenGL context
1690     makeCurrent();
1691 
1692     // get the viewport size, allocate memory if necessary
1693     if( !(width()>0) || !(height()>0))
1694     {
1695         deletePicking();
1696         return;
1697     }
1698     else if(
1699         pickingImg_
1700         && (pickingWidth_ == width())
1701         && (pickingHeight_ == height()))
1702     {
1703         // necessary objects already created: do nothing
1704     }
1705     else
1706     {
1707         deletePicking();
1708         newPicking();
1709     }
1710 
1711     // set rendering destination to FBO
1712     gl_fbo_->glBindFramebuffer(GL_FRAMEBUFFER, fboId_);
1713 
1714     // clear buffers
1715     glClearColor(1.0, 1.0, 1.0, 1.0);
1716     glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
1717 
1718     // Should we setup other things? (e.g., disabling antialiasing)
1719     // Seems to work as is. If issues, check GLWidget::initilizeGL()
1720 
1721     // Set viewport
1722     GLint oldViewport[4];
1723     glGetIntegerv(GL_VIEWPORT, oldViewport);
1724     glViewport(0, 0, pickingWidth_, pickingHeight_);
1725 
1726     // Setup camera position and orientation
1727     setCameraPositionAndOrientation();
1728 
1729     // draw the picking
1730     drawPick();
1731 
1732     // Restore viewport
1733     glViewport(oldViewport[0], oldViewport[1], oldViewport[2], oldViewport[3]);
1734 
1735     // unbind FBO
1736     gl_fbo_->glBindFramebuffer(GL_FRAMEBUFFER, defaultFramebufferObject());
1737 
1738     // extract the texture info from GPU to RAM: EXPENSIVE + MAY CAUSE OPENGL STALL
1739     glBindTexture(GL_TEXTURE_2D, textureId_);
1740     glGetTexImage(GL_TEXTURE_2D, 0, GL_RGBA, GL_UNSIGNED_BYTE, pickingImg_);
1741     glBindTexture(GL_TEXTURE_2D, 0);
1742 
1743     // Update highlighted object
1744     if(underMouse())
1745     {
1746         updateHoveredObject(mouse_Event_X_, mouse_Event_Y_);
1747     }
1748 }
1749