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