1 #include <QMouseEvent> |
1 #include <QMouseEvent> |
2 #include <QPainter> |
2 #include <QPainter> |
|
3 #include "modeleditor.h" |
|
4 #include "document.h" |
3 #include "canvas.h" |
5 #include "canvas.h" |
|
6 #include "linetypes/edge.h" |
|
7 #include "linetypes/triangle.h" |
|
8 #include "linetypes/quadrilateral.h" |
4 |
9 |
5 Canvas::Canvas( |
10 Canvas::Canvas( |
6 Model* model, |
11 Model* model, |
|
12 Document *document, |
7 DocumentManager* documents, |
13 DocumentManager* documents, |
8 const ldraw::ColorTable& colorTable, |
14 const ldraw::ColorTable& colorTable, |
9 QWidget* parent) : |
15 QWidget* parent) : |
10 PartRenderer{model, documents, colorTable, parent} |
16 PartRenderer{model, documents, colorTable, parent}, |
|
17 document{document} |
11 { |
18 { |
12 this->setMouseTracking(true); |
19 this->setMouseTracking(true); |
13 } |
20 } |
14 |
21 |
15 /** |
22 /** |
35 if (this->vertexProgram.has_value()) |
42 if (this->vertexProgram.has_value()) |
36 { |
43 { |
37 this->vertexProgram->build(document); |
44 this->vertexProgram->build(document); |
38 this->update(); |
45 this->update(); |
39 } |
46 } |
|
47 } |
|
48 |
|
49 void updatePreviewPolygon(DrawState* drawState) |
|
50 { |
|
51 drawState->previewPolygon = drawState->polygon; |
|
52 drawState->previewPolygon.resize(drawState->polygon.size() + 1); |
|
53 drawState->previewPolygon.back() = drawState->previewPoint; |
|
54 if (drawState->previewPolygon.size() > 2) |
|
55 { |
|
56 drawState->isconcave = not geom::isConvex(drawState->previewPolygon); |
|
57 } |
|
58 } |
|
59 |
|
60 void removeLastPoint(DrawState* drawState) |
|
61 { |
|
62 if (drawState->polygon.size() > 0) |
|
63 { |
|
64 drawState->polygon.erase(drawState->polygon.end() - 1); |
|
65 updatePreviewPolygon(drawState); |
|
66 } |
|
67 } |
|
68 |
|
69 bool isCloseToExistingPoints(const std::vector<glm::vec3>& points, const glm::vec3 &pos) |
|
70 { |
|
71 return any(points, std::bind(geom::isclose, std::placeholders::_1, pos)); |
40 } |
72 } |
41 |
73 |
42 void Canvas::mouseMoveEvent(QMouseEvent* event) |
74 void Canvas::mouseMoveEvent(QMouseEvent* event) |
43 { |
75 { |
44 const ldraw::id_t id = this->pick(event->pos()); |
76 const ldraw::id_t id = this->pick(event->pos()); |
60 this->worldPosition = glm::round(*this->worldPosition); |
92 this->worldPosition = glm::round(*this->worldPosition); |
61 // And finally transform it back to grid coordinates by transforming it with the |
93 // And finally transform it back to grid coordinates by transforming it with the |
62 // grid matrix. |
94 // grid matrix. |
63 this->worldPosition = this->gridMatrix * glm::vec4{*this->worldPosition, 1}; |
95 this->worldPosition = this->gridMatrix * glm::vec4{*this->worldPosition, 1}; |
64 } |
96 } |
65 Q_EMIT this->mouseMove(this, event); |
97 switch(this->mode) |
|
98 { |
|
99 case SelectMode: |
|
100 break; |
|
101 case DrawMode: |
|
102 const auto& worldPosition = this->getWorldPosition(); |
|
103 if (worldPosition.has_value()) |
|
104 { |
|
105 this->drawState.previewPoint = worldPosition.value(); |
|
106 updatePreviewPolygon(&this->drawState); |
|
107 this->update(); |
|
108 } |
|
109 event->accept(); |
|
110 break; |
|
111 } |
66 PartRenderer::mouseMoveEvent(event); |
112 PartRenderer::mouseMoveEvent(event); |
67 this->update(); |
113 this->update(); |
68 } |
114 } |
69 |
115 |
70 void Canvas::mousePressEvent(QMouseEvent* event) |
116 void Canvas::mousePressEvent(QMouseEvent* event) |
76 |
122 |
77 void Canvas::mouseReleaseEvent(QMouseEvent* event) |
123 void Canvas::mouseReleaseEvent(QMouseEvent* event) |
78 { |
124 { |
79 if (this->totalMouseMove < (2.0 / sqrt(2)) * 5.0) |
125 if (this->totalMouseMove < (2.0 / sqrt(2)) * 5.0) |
80 { |
126 { |
81 Q_EMIT this->mouseClick(this, event); |
127 switch(this->mode) |
|
128 { |
|
129 case SelectMode: |
|
130 if (event->button() == Qt::LeftButton) |
|
131 { |
|
132 const ldraw::id_t highlighted = this->getHighlightedObject(); |
|
133 this->clearSelection(); |
|
134 if (highlighted != ldraw::NULL_ID) |
|
135 { |
|
136 this->addToSelection(highlighted); |
|
137 } |
|
138 event->accept(); |
|
139 } |
|
140 break; |
|
141 case DrawMode: |
|
142 if (event->button() == Qt::LeftButton and this->worldPosition.has_value()) |
|
143 { |
|
144 const glm::vec3& pos = worldPosition.value(); |
|
145 if (isCloseToExistingPoints(this->drawState.polygon, pos)) |
|
146 { |
|
147 this->closeShape(); |
|
148 } |
|
149 else |
|
150 { |
|
151 this->drawState.polygon.push_back(pos); |
|
152 updatePreviewPolygon(&this->drawState); |
|
153 } |
|
154 event->accept(); |
|
155 } |
|
156 else if (true |
|
157 and event->button() == Qt::RightButton |
|
158 and this->drawState.polygon.size() > 0 |
|
159 ) { |
|
160 this->drawState.polygon.erase(this->drawState.polygon.end() - 1); |
|
161 updatePreviewPolygon(&this->drawState); |
|
162 event->accept(); |
|
163 } |
|
164 break; |
|
165 } |
82 } |
166 } |
83 PartRenderer::mouseReleaseEvent(event); |
167 PartRenderer::mouseReleaseEvent(event); |
84 this->update(); |
168 this->update(); |
85 } |
169 } |
86 |
170 |
112 // Set up XZ grid matrix |
196 // Set up XZ grid matrix |
113 this->setGridMatrix({{1, 0, 0, 0}, {0, 0, 1, 0}, {0, 1, 0, 0}, {0, 0, 0, 1}}); |
197 this->setGridMatrix({{1, 0, 0, 0}, {0, 0, 1, 0}, {0, 1, 0, 0}, {0, 0, 0, 1}}); |
114 this->updateCanvasRenderPreferences(); |
198 this->updateCanvasRenderPreferences(); |
115 } |
199 } |
116 |
200 |
|
201 static const struct |
|
202 { |
|
203 const QBrush pointBrush = {Qt::white}; |
|
204 const QPen polygonPen = {QBrush{Qt::black}, 2.0, Qt::DashLine}; |
|
205 const QPen badPolygonPen = {QBrush{Qt::red}, 2.0, Qt::DashLine}; |
|
206 const QPen pointPen = {QBrush{Qt::black}, 2.0}; |
|
207 const QBrush greenPolygonBrush = {QColor{64, 255, 128, 192}}; |
|
208 const QBrush redPolygonBrush = {QColor{255, 96, 96, 192}}; |
|
209 } pens; |
|
210 |
117 void Canvas::paintGL() |
211 void Canvas::paintGL() |
118 { |
212 { |
119 PartRenderer::paintGL(); |
213 PartRenderer::paintGL(); |
120 if (this->renderPreferences.style != gl::RenderStyle::PickScene) |
214 if (this->renderPreferences.style != gl::RenderStyle::PickScene) |
121 { |
215 { |
152 painter.setBrush(Qt::green); |
246 painter.setBrush(Qt::green); |
153 const QPointF pos = this->modelToScreenCoordinates(*this->worldPosition); |
247 const QPointF pos = this->modelToScreenCoordinates(*this->worldPosition); |
154 painter.drawEllipse(pos, 5, 5); |
248 painter.drawEllipse(pos, 5, 5); |
155 painter.drawText(pos + QPointF{5, 5}, vectorToString(*this->worldPosition)); |
249 painter.drawText(pos + QPointF{5, 5}, vectorToString(*this->worldPosition)); |
156 } |
250 } |
157 { |
251 QPainter painter{this}; |
158 QPainter painter{this}; |
252 painter.setRenderHint(QPainter::Antialiasing); |
159 painter.setRenderHint(QPainter::Antialiasing); |
253 if (this->renderPreferences.drawAxes) |
160 if (this->renderPreferences.drawAxes) |
254 { |
|
255 this->renderAxesLabels(painter); |
|
256 } |
|
257 switch(this->mode) |
|
258 { |
|
259 case SelectMode: |
|
260 break; |
|
261 case DrawMode: |
161 { |
262 { |
162 this->renderAxesLabels(painter); |
263 painter.setPen(this->drawState.isconcave ? ::pens.badPolygonPen : ::pens.polygonPen); |
|
264 if (this->drawState.previewPolygon.size() > 2 and not this->drawState.isconcave) |
|
265 { |
|
266 if (this->worldPolygonWinding(this->drawState.previewPolygon) == Winding::Clockwise) |
|
267 { |
|
268 painter.setBrush(::pens.greenPolygonBrush); |
|
269 } |
|
270 else |
|
271 { |
|
272 painter.setBrush(::pens.redPolygonBrush); |
|
273 } |
|
274 this->drawWorldPolygon(&painter, this->drawState.previewPolygon); |
|
275 } |
|
276 else |
|
277 { |
|
278 this->drawWorldPolyline(&painter, this->drawState.previewPolygon); |
|
279 } |
|
280 painter.setBrush(::pens.pointBrush); |
|
281 painter.setPen(::pens.pointPen); |
|
282 for (const glm::vec3& point : this->drawState.polygon) |
|
283 { |
|
284 this->drawWorldPoint(&painter, point); |
|
285 } |
|
286 this->drawWorldPoint(&painter, this->drawState.previewPoint); |
163 } |
287 } |
164 if (this->overpaintCallback != nullptr) |
288 break; |
165 { |
|
166 this->overpaintCallback(this, &painter); |
|
167 } |
|
168 } |
289 } |
169 } |
290 } |
170 } |
291 } |
171 |
292 |
172 /** |
293 /** |
262 } |
383 } |
263 |
384 |
264 /** |
385 /** |
265 * @brief Adjusts the grid to be so that it is perpendicular to the camera. |
386 * @brief Adjusts the grid to be so that it is perpendicular to the camera. |
266 */ |
387 */ |
267 void Canvas::adjustGridToView() |
388 void adjustGridToView(Canvas* canvas) |
268 { |
389 { |
269 const glm::vec3 cameraDirection = this->cameraVector(); |
390 const glm::vec3 cameraDirection = canvas->cameraVector(); |
270 const glm::vec3 vector_x = glm::normalize(this->gridMatrix * glm::vec4{1, 0, 0, 1}); |
391 const glm::mat4& grid = canvas->getGridMatrix(); |
271 const glm::vec3 vector_y = glm::normalize(this->gridMatrix * glm::vec4{0, 1, 0, 1}); |
392 const glm::vec3 vector_x = glm::normalize(grid * glm::vec4{1, 0, 0, 1}); |
|
393 const glm::vec3 vector_y = glm::normalize(grid * glm::vec4{0, 1, 0, 1}); |
272 const float angle_x = std::abs(glm::dot(vector_x, cameraDirection)); |
394 const float angle_x = std::abs(glm::dot(vector_x, cameraDirection)); |
273 const float angle_y = std::abs(glm::dot(vector_y, cameraDirection)); |
395 const float angle_y = std::abs(glm::dot(vector_y, cameraDirection)); |
274 if (angle_x < angle_y) |
396 canvas->setGridMatrix(glm::rotate( |
275 { |
397 grid, |
276 this->setGridMatrix(glm::rotate(this->gridMatrix, PI<float> / 2, glm::vec3{1, 0, 0})); |
398 pi<> * 0.5f, |
277 } |
399 (angle_x < angle_y) ? glm::vec3{1, 0, 0} : glm::vec3{0, 1, 0} |
278 else |
400 )); |
279 { |
401 canvas->update(); |
280 this->setGridMatrix(glm::rotate(this->gridMatrix, PI<float> / 2, glm::vec3{0, 1, 0})); |
|
281 } |
|
282 this->update(); |
|
283 } |
402 } |
284 |
403 |
285 /** |
404 /** |
286 * @returns the ids of the currently selected objects |
405 * @returns the ids of the currently selected objects |
287 */ |
406 */ |
291 } |
410 } |
292 |
411 |
293 const glm::mat4 &Canvas::getGridMatrix() const |
412 const glm::mat4 &Canvas::getGridMatrix() const |
294 { |
413 { |
295 return this->gridMatrix; |
414 return this->gridMatrix; |
|
415 } |
|
416 |
|
417 void Canvas::closeShape() |
|
418 { |
|
419 if (this->drawState.polygon.size() >= 2 and this->drawState.polygon.size() <= 4) |
|
420 { |
|
421 std::unique_ptr<ModelEditor> modelEditor = this->document->editModel(); |
|
422 switch (this->drawState.polygon.size()) |
|
423 { |
|
424 case 2: |
|
425 modelEditor->append<ldraw::Edge>( |
|
426 vectorToArray<2>(this->drawState.polygon), |
|
427 ldraw::EDGE_COLOR); |
|
428 break; |
|
429 case 3: |
|
430 modelEditor->append<ldraw::Triangle>( |
|
431 vectorToArray<3>(this->drawState.polygon), |
|
432 ldraw::MAIN_COLOR); |
|
433 break; |
|
434 case 4: |
|
435 modelEditor->append<ldraw::Quadrilateral>( |
|
436 vectorToArray<4>(this->drawState.polygon), |
|
437 ldraw::MAIN_COLOR); |
|
438 break; |
|
439 } |
|
440 } |
|
441 this->drawState.polygon.clear(); |
|
442 updatePreviewPolygon(&this->drawState); |
296 } |
443 } |
297 |
444 |
298 /** |
445 /** |
299 * @brief Paints a circle at where @c worldPoint is located on the screen. |
446 * @brief Paints a circle at where @c worldPoint is located on the screen. |
300 * @param painter Painter to use to render |
447 * @param painter Painter to use to render |