15 * You should have received a copy of the GNU General Public License |
15 * You should have received a copy of the GNU General Public License |
16 * along with this program. If not, see <http://www.gnu.org/licenses/>. |
16 * along with this program. If not, see <http://www.gnu.org/licenses/>. |
17 */ |
17 */ |
18 |
18 |
19 #include <QMouseEvent> |
19 #include <QMouseEvent> |
20 #include <QMessageBox> |
20 #include <QPainter> |
21 #include <QVBoxLayout> |
|
22 #include "document.h" |
21 #include "document.h" |
23 #include "model.h" |
22 #include "model.h" |
24 #include "ui/objecteditor.h" |
23 #include "ui/objecteditor.h" |
25 |
24 #include "gl/partrenderer.h" |
26 EditTools::EditTools( |
25 |
27 Model* model, |
26 EditTools::EditTools(QObject* parent) : |
28 const ColorTable& colorTable, |
|
29 QObject* parent) : |
|
30 QObject{parent}, |
27 QObject{parent}, |
31 colorTable{colorTable}, |
28 RenderLayer{} |
32 model{model}, |
29 { |
33 vertexMap{model} |
30 } |
34 { |
31 |
35 #if 0 |
32 EditTools::~EditTools() |
36 connect(this->canvas, &Canvas::mouseClick, this, &EditTools::canvasMouseClick); |
33 { |
37 connect(this->canvas, &Canvas::mouseMove, this, &EditTools::canvasMouseMove); |
34 } |
38 connect(this->canvas, &Canvas::newStatusText, this, &EditTools::newStatusText); |
35 |
39 connect(this->model, &Model::dataChanged, this->canvas, qOverload<>(&Canvas::update)); |
36 void EditTools::setEditMode(EditingMode mode) |
40 connect(&this->vertexMap, &VertexMap::verticesChanged, [&]() |
37 { |
41 { |
38 this->mode = mode; |
42 this->canvas->rebuildVertices(&this->vertexMap); |
39 } |
|
40 |
|
41 void EditTools::setGridMatrix(const glm::mat4& gridMatrix) |
|
42 { |
|
43 this->gridMatrix = gridMatrix; |
|
44 this->gridPlane = planeFromTriangle({ |
|
45 this->gridMatrix * glm::vec4{0, 0, 0, 1}, |
|
46 this->gridMatrix * glm::vec4{1, 0, 0, 1}, |
|
47 this->gridMatrix * glm::vec4{0, 1, 0, 1}, |
43 }); |
48 }); |
44 this->canvas->drawState = &this->drawState; |
49 } |
45 #endif |
50 |
46 } |
51 void EditTools::mvpMatrixChanged(const glm::mat4& matrix) |
47 |
52 { |
48 EditTools::~EditTools() |
53 this->mvpMatrix = matrix; |
49 { |
54 } |
50 } |
55 |
51 |
56 void EditTools::mouseMoved(const QMouseEvent* event) |
52 void EditTools::applyToVertices(VertexMap::ApplyFunction fn) const |
57 { |
53 { |
58 this->worldPosition = this->renderer->screenToModelCoordinates(event->pos(), this->gridPlane); |
54 this->vertexMap.apply(fn); |
59 if (this->worldPosition.has_value()) |
55 } |
60 { |
56 |
61 // Snap the position to grid. This procedure is basically the "change of basis" and almost follows the |
57 void EditTools::setEditMode(EditingMode mode) |
62 // A⁻¹ × M × A formula which is used to perform a transformation in some other coordinate system, except |
58 { |
63 // we actually use the inverted matrix first and the regular one last to perform the transformation of |
59 this->drawState.mode = mode; |
64 // grid coordinates in our XY coordinate system. Also, we're rounding the coordinates which is obviously |
60 } |
65 // not a linear transformation, but fits the pattern anyway. |
61 |
66 // First transform the coordinates to the XY plane... |
62 void updatePreviewPolygon(DrawState* drawState) |
67 this->worldPosition = glm::inverse(this->gridMatrix) * glm::vec4{*this->worldPosition, 1}; |
63 { |
68 // Then round the coordinates to integer precision... |
64 drawState->previewPolygon = drawState->polygon; |
69 this->worldPosition = glm::round(*this->worldPosition); |
65 drawState->previewPolygon.resize(drawState->polygon.size() + 1); |
70 // And finally transform it back to grid coordinates by transforming it with the |
66 drawState->previewPolygon.back() = drawState->previewPoint; |
71 // grid matrix. |
67 if (drawState->previewPolygon.size() > 2) |
72 this->worldPosition = this->gridMatrix * glm::vec4{*this->worldPosition, 1}; |
68 { |
73 } |
69 drawState->isconcave = not isConvex(drawState->previewPolygon); |
74 this->updatePreviewPolygon(); |
70 } |
75 } |
71 } |
76 |
72 |
77 static QVector<QPointF> convertWorldPointsToScreenPoints( |
73 void removeLastPoint(DrawState* drawState) |
78 const std::vector<glm::vec3> &worldPoints, |
74 { |
79 const PartRenderer* renderer) |
75 if (drawState->polygon.size() > 0) |
80 { |
76 { |
81 QVector<QPointF> points2d; |
77 drawState->polygon.erase(drawState->polygon.end() - 1); |
82 points2d.reserve(worldPoints.size()); |
78 updatePreviewPolygon(drawState); |
83 for (const glm::vec3& point : worldPoints) |
|
84 { |
|
85 points2d.push_back(renderer->modelToScreenCoordinates(point)); |
|
86 } |
|
87 return points2d; |
|
88 } |
|
89 |
|
90 static Winding worldPolygonWinding( |
|
91 const std::vector<glm::vec3> &points, |
|
92 const PartRenderer* renderer) |
|
93 { |
|
94 return winding(QPolygonF{convertWorldPointsToScreenPoints(points, renderer)}); |
|
95 } |
|
96 |
|
97 static void drawWorldPoint( |
|
98 QPainter* painter, |
|
99 const glm::vec3& worldPoint, |
|
100 const PartRenderer* renderer) |
|
101 { |
|
102 const QPointF center = renderer->modelToScreenCoordinates(worldPoint); |
|
103 painter->drawEllipse(inscribe(CircleF{center, 5})); |
|
104 } |
|
105 |
|
106 static void drawWorldPolyline( |
|
107 QPainter *painter, |
|
108 const std::vector<glm::vec3> &points, |
|
109 const PartRenderer* renderer) |
|
110 { |
|
111 painter->drawPolyline(QPolygonF{convertWorldPointsToScreenPoints(points, renderer)}); |
|
112 } |
|
113 |
|
114 static void drawWorldPolygon( |
|
115 QPainter* painter, |
|
116 const std::vector<glm::vec3> &points, |
|
117 const PartRenderer* renderer) |
|
118 { |
|
119 painter->drawPolygon(QPolygonF{convertWorldPointsToScreenPoints(points, renderer)}); |
|
120 } |
|
121 |
|
122 void EditTools::overpaint(QPainter* painter) |
|
123 { |
|
124 struct Pens |
|
125 { |
|
126 const QBrush pointBrush; |
|
127 const QPen pointPen; |
|
128 const QPen polygonPen; |
|
129 const QPen badPolygonPen; |
|
130 const QBrush greenPolygonBrush; |
|
131 const QBrush redPolygonBrush; |
|
132 }; |
|
133 static const Pens brightPens{ |
|
134 .pointBrush = {Qt::white}, |
|
135 .pointPen = {QBrush{Qt::black}, 2.0}, |
|
136 .polygonPen = {QBrush{Qt::black}, 2.0, Qt::DashLine}, |
|
137 .badPolygonPen = {QBrush{Qt::red}, 2.0, Qt::DashLine}, |
|
138 .greenPolygonBrush = {QColor{64, 255, 128, 192}}, |
|
139 .redPolygonBrush = {QColor{255, 96, 96, 192}}, |
|
140 }; |
|
141 static const Pens darkPens{ |
|
142 .pointBrush = {Qt::black}, |
|
143 .pointPen = {QBrush{Qt::white}, 2.0}, |
|
144 .polygonPen = {QBrush{Qt::white}, 2.0, Qt::DashLine}, |
|
145 .badPolygonPen = {QBrush{Qt::red}, 2.0, Qt::DashLine}, |
|
146 .greenPolygonBrush = {QColor{64, 255, 128, 192}}, |
|
147 .redPolygonBrush = {QColor{255, 96, 96, 192}}, |
|
148 }; |
|
149 const Pens& pens = (this->renderer->isDark() ? darkPens : brightPens); |
|
150 switch(this->mode) { |
|
151 case SelectMode: |
|
152 break; |
|
153 case DrawMode: |
|
154 { |
|
155 painter->setPen(this->isconcave ? pens.badPolygonPen : pens.polygonPen); |
|
156 if (this->previewPolygon.size() > 2 and not this->isconcave) |
|
157 { |
|
158 if (worldPolygonWinding(this->previewPolygon, this->renderer) == Winding::Clockwise) { |
|
159 painter->setBrush(pens.greenPolygonBrush); |
|
160 } |
|
161 else { |
|
162 painter->setBrush(pens.redPolygonBrush); |
|
163 } |
|
164 drawWorldPolygon(painter, this->previewPolygon, this->renderer); |
|
165 } |
|
166 else { |
|
167 drawWorldPolyline(painter, this->previewPolygon, this->renderer); |
|
168 } |
|
169 painter->setBrush(pens.pointBrush); |
|
170 painter->setPen(pens.pointPen); |
|
171 for (const glm::vec3& point : this->polygon) { |
|
172 drawWorldPoint(painter, point, this->renderer); |
|
173 } |
|
174 } |
|
175 break; |
|
176 } |
|
177 if (this->worldPosition.has_value()) |
|
178 { |
|
179 painter->setRenderHint(QPainter::Antialiasing); |
|
180 painter->setPen(Qt::white); |
|
181 painter->setBrush(Qt::green); |
|
182 const QPointF pos = this->renderer->modelToScreenCoordinates(*this->worldPosition); |
|
183 painter->drawEllipse(pos, 5, 5); |
|
184 painter->drawText(pos + QPointF{5, 5}, vectorToString(*this->worldPosition)); |
|
185 } |
|
186 } |
|
187 |
|
188 void EditTools::updatePreviewPolygon() |
|
189 { |
|
190 this->previewPolygon = this->polygon; |
|
191 if (this->worldPosition.has_value()) { |
|
192 this->previewPolygon.resize(this->polygon.size() + 1); |
|
193 this->previewPolygon.back() = *this->worldPosition; |
|
194 } |
|
195 if (this->previewPolygon.size() > 2) |
|
196 { |
|
197 this->isconcave = not isConvex(this->previewPolygon); |
|
198 } |
|
199 } |
|
200 |
|
201 void EditTools::removeLastPoint() |
|
202 { |
|
203 if (this->polygon.size() > 0) |
|
204 { |
|
205 this->polygon.erase(this->polygon.end() - 1); |
|
206 this->updatePreviewPolygon(); |
79 } |
207 } |
80 } |
208 } |
81 |
209 |
82 bool isCloseToExistingPoints(const std::vector<glm::vec3>& points, const glm::vec3 &pos) |
210 bool isCloseToExistingPoints(const std::vector<glm::vec3>& points, const glm::vec3 &pos) |
83 { |
211 { |
84 return any(points, std::bind(isclose, std::placeholders::_1, pos)); |
212 return any(points, std::bind(isclose, std::placeholders::_1, pos)); |
85 } |
213 } |
86 |
214 |
87 void EditTools::canvasMouseClick(QMouseEvent*) |
215 EditingMode EditTools::currentEditingMode() const |
88 { |
216 { |
89 #if 0 |
217 return this->mode; |
90 switch(this->drawState.mode) |
218 } |
91 { |
219 |
|
220 void EditTools::mouseClick(const QMouseEvent* event) |
|
221 { |
|
222 switch(this->mode) { |
92 case SelectMode: |
223 case SelectMode: |
93 if (event->button() == Qt::LeftButton) |
224 if (event->button() == Qt::LeftButton) { |
94 { |
225 const ModelId highlighted = this->renderer->pick(event->pos()); |
95 const ModelId highlighted = this->canvas->getHighlightedObject(); |
226 Q_EMIT this->select({highlighted}, false); |
96 QSet<ModelId> selected; |
|
97 if (highlighted != ModelId{0}) { |
|
98 selected.insert(highlighted); |
|
99 } |
|
100 //this->select(selected); |
|
101 event->accept(); |
|
102 } |
227 } |
103 break; |
228 break; |
104 case DrawMode: |
229 case DrawMode: |
105 if (event->button() == Qt::LeftButton) { |
230 if (event->button() == Qt::LeftButton and this->worldPosition.has_value()) { |
106 if (isCloseToExistingPoints(this->drawState.polygon, worldPosition)) { |
231 if (isCloseToExistingPoints(this->polygon, *this->worldPosition)) { |
107 this->closeShape(); |
232 this->closeShape(); |
108 } |
233 } |
109 else { |
234 else { |
110 this->drawState.polygon.push_back(pos); |
235 this->polygon.push_back(*this->worldPosition); |
111 updatePreviewPolygon(&this->drawState); |
236 this->updatePreviewPolygon(); |
112 } |
237 } |
113 event->accept(); |
|
114 } |
238 } |
115 else if (true |
239 else if (true |
116 and event->button() == Qt::RightButton |
240 and event->button() == Qt::RightButton |
117 and this->drawState.polygon.size() > 0 |
241 and this->polygon.size() > 0 |
118 ) { |
242 ) { |
119 this->drawState.polygon.erase(this->drawState.polygon.end() - 1); |
243 this->polygon.erase(this->polygon.end() - 1); |
120 updatePreviewPolygon(&this->drawState); |
244 updatePreviewPolygon(); |
121 event->accept(); |
245 } |
122 } |
246 break; |
123 break; |
247 } |
124 } |
248 } |
125 #endif |
249 |
126 } |
|
127 void EditTools::canvasMouseMove(QMouseEvent*) |
|
128 { |
|
129 #if 0 |
|
130 switch(this->drawState.mode) |
|
131 { |
|
132 case SelectMode: |
|
133 break; |
|
134 case DrawMode: |
|
135 if (this->canvas->worldPosition.has_value()) |
|
136 { |
|
137 this->drawState.previewPoint = this->canvas->worldPosition.value(); |
|
138 updatePreviewPolygon(&this->drawState); |
|
139 this->update(); |
|
140 } |
|
141 event->accept(); |
|
142 break; |
|
143 } |
|
144 #endif |
|
145 } |
|
146 #if 0 |
|
147 /* |
|
148 |
|
149 void EditorTabWidget::select(const QSet<ModelId> &selected) |
|
150 { |
|
151 QItemSelectionModel* selectionModel = this->ui.listView->selectionModel(); |
|
152 QItemSelection itemSelection; |
|
153 for (const ModelId id : selected) |
|
154 { |
|
155 const std::optional<int> row = this->model->find(id); |
|
156 if (row.has_value()) |
|
157 { |
|
158 const QModelIndex qindex = this->model->index(*row); |
|
159 itemSelection.select(qindex, qindex); |
|
160 } |
|
161 } |
|
162 selectionModel->select(itemSelection, QItemSelectionModel::ClearAndSelect); |
|
163 } |
|
164 */ |
|
165 const QSet<ModelId> EditTools::selectedObjects() const |
|
166 { |
|
167 return this->canvas->selectedObjects(); |
|
168 } |
|
169 #endif |
|
170 EditingMode EditTools::currentEditingMode() const |
|
171 { |
|
172 return this->drawState.mode; |
|
173 } |
|
174 #if 0 |
|
175 void EditTools::closeShape() |
250 void EditTools::closeShape() |
176 { |
251 { |
177 if (this->drawState.polygon.size() >= 2 and this->drawState.polygon.size() <= 4) |
252 if (this->polygon.size() >= 2 and this->polygon.size() <= 4) { |
178 { |
253 switch (this->polygon.size()) { |
179 switch (this->drawState.polygon.size()) |
|
180 { |
|
181 case 2: |
254 case 2: |
182 Q_EMIT this->modelAction(AppendToModel{ |
255 Q_EMIT this->modelAction(AppendToModel{ |
183 .newElement = Colored<LineSegment>{ |
256 .newElement = Colored<LineSegment>{ |
184 LineSegment{ |
257 LineSegment{ |
185 .p1 = this->drawState.polygon[0], |
258 .p1 = this->polygon[0], |
186 .p2 = this->drawState.polygon[1], |
259 .p2 = this->polygon[1], |
187 }, |
260 }, |
188 EDGE_COLOR, |
261 EDGE_COLOR, |
189 } |
262 } |
190 }); |
263 }); |
191 break; |
264 break; |
192 case 3: |
265 case 3: |
193 Q_EMIT this->modelAction(AppendToModel{ |
266 Q_EMIT this->modelAction(AppendToModel{ |
194 .newElement = Colored<Triangle>{ |
267 .newElement = Colored<Triangle>{ |
195 Triangle{ |
268 Triangle{ |
196 .p1 = this->drawState.polygon[0], |
269 .p1 = this->polygon[0], |
197 .p2 = this->drawState.polygon[1], |
270 .p2 = this->polygon[1], |
198 .p3 = this->drawState.polygon[2], |
271 .p3 = this->polygon[2], |
199 }, |
272 }, |
200 MAIN_COLOR, |
273 MAIN_COLOR, |
201 } |
274 } |
202 }); |
275 }); |
203 break; |
276 break; |
204 case 4: |
277 case 4: |
205 Q_EMIT this->modelAction(AppendToModel{ |
278 Q_EMIT this->modelAction(AppendToModel{ |
206 .newElement = Colored<Quadrilateral>{ |
279 .newElement = Colored<Quadrilateral>{ |
207 Quadrilateral{ |
280 Quadrilateral{ |
208 .p1 = this->drawState.polygon[0], |
281 .p1 = this->polygon[0], |
209 .p2 = this->drawState.polygon[1], |
282 .p2 = this->polygon[1], |
210 .p3 = this->drawState.polygon[2], |
283 .p3 = this->polygon[2], |
211 .p4 = this->drawState.polygon[3], |
284 .p4 = this->polygon[3], |
212 }, |
285 }, |
213 MAIN_COLOR, |
286 MAIN_COLOR, |
214 } |
287 } |
215 }); |
288 }); |
216 break; |
289 break; |
217 } |
290 } |
218 } |
291 } |
219 this->drawState.polygon.clear(); |
292 this->polygon.clear(); |
220 updatePreviewPolygon(&this->drawState); |
293 this->updatePreviewPolygon(); |
221 } |
294 } |
222 |
|
223 #endif |
|