55 } |
59 } |
56 |
60 |
57 throw std::logic_error("bad type given to AbstractEditMode::createByType"); |
61 throw std::logic_error("bad type given to AbstractEditMode::createByType"); |
58 } |
62 } |
59 |
63 |
|
64 /* |
|
65 * Returns the edit mode's corresponding renderer pointer. |
|
66 */ |
60 GLRenderer* AbstractEditMode::renderer() const |
67 GLRenderer* AbstractEditMode::renderer() const |
61 { |
68 { |
62 return m_renderer; |
69 return m_renderer; |
63 } |
70 } |
64 |
71 |
|
72 /* |
|
73 * Base class constructor of the abstract drwaing mode. |
|
74 */ |
65 AbstractDrawMode::AbstractDrawMode(GLRenderer* renderer) : |
75 AbstractDrawMode::AbstractDrawMode(GLRenderer* renderer) : |
66 AbstractEditMode(renderer), |
76 AbstractEditMode {renderer}, |
67 m_polybrush{QBrush{QColor{64, 192, 0, 128}}} |
77 m_polybrush {QBrush {QColor {64, 192, 0, 128}}} |
68 { |
78 { |
69 renderer->setContextMenuPolicy (Qt::NoContextMenu); // We need the right mouse button for removing vertices |
79 renderer->setContextMenuPolicy(Qt::NoContextMenu); // We need the right mouse button for removing vertices |
70 renderer->setCursor (Qt::CrossCursor); |
80 renderer->setCursor(Qt::CrossCursor); |
71 m_window->currentDocument()->clearSelection(); |
81 m_window->currentDocument()->clearSelection(); |
72 m_window->updateSelection(); |
82 m_window->updateSelection(); |
73 m_drawedVerts.clear(); |
83 m_drawedVerts.clear(); |
74 } |
84 } |
75 |
85 |
76 AbstractSelectMode::AbstractSelectMode (GLRenderer* renderer) : |
86 /* |
77 AbstractEditMode (renderer) |
87 * Base class constructor of the abstract selection mode. |
|
88 */ |
|
89 AbstractSelectMode::AbstractSelectMode(GLRenderer* renderer) : |
|
90 AbstractEditMode {renderer} |
78 { |
91 { |
79 renderer->unsetCursor(); |
92 renderer->unsetCursor(); |
80 renderer->setContextMenuPolicy (Qt::DefaultContextMenu); |
93 renderer->setContextMenuPolicy (Qt::DefaultContextMenu); |
81 } |
94 } |
82 |
95 |
83 // ============================================================================= |
96 /* |
84 // |
97 * Possibly adds this vertex into the list of drawn vertices. |
|
98 */ |
85 void AbstractDrawMode::addDrawnVertex(const Vertex& position) |
99 void AbstractDrawMode::addDrawnVertex(const Vertex& position) |
86 { |
100 { |
87 if (preAddVertex(position)) |
101 if (preAddVertex(position)) |
88 return; |
102 return; |
89 |
103 |
90 m_drawedVerts << position; |
104 m_drawedVerts << position; |
91 } |
105 } |
92 |
106 |
|
107 /* |
|
108 * Handles mouse relese events. |
|
109 */ |
93 bool AbstractDrawMode::mouseReleased(MouseEventData const& data) |
110 bool AbstractDrawMode::mouseReleased(MouseEventData const& data) |
94 { |
111 { |
95 if (Super::mouseReleased(data)) |
112 if (Super::mouseReleased(data)) |
96 return true; |
113 return true; |
97 |
114 |
|
115 // If the user presses the middle mouse button, seek the closest existing vertex to the cursor and clamp to that. |
98 if ((data.releasedButtons & Qt::MidButton) and (countof(m_drawedVerts) < 4) and (not data.mouseMoved)) |
116 if ((data.releasedButtons & Qt::MidButton) and (countof(m_drawedVerts) < 4) and (not data.mouseMoved)) |
99 { |
117 { |
100 // Find the closest vertex to our cursor |
118 // Find the closest vertex to our cursor |
101 double minimumDistance = 1024.0; |
119 double minimumDistance = 1024.0; |
102 const Vertex* closest = nullptr; |
120 const Vertex* closest = nullptr; |
103 Vertex cursorPosition = renderer()->convert2dTo3d (data.ev->pos(), false); |
121 Vertex cursorPosition = renderer()->convert2dTo3d(data.ev->pos(), false); |
104 QPoint cursorPosition2D (data.ev->pos()); |
122 QPoint cursorPosition2D = data.ev->pos(); |
105 const Axis depthAxis = renderer()->getRelativeZ(); |
123 const Axis depthAxis = renderer()->getRelativeZ(); |
106 QList<Vertex> vertices = renderer()->document()->inlineVertices().toList(); |
124 QList<Vertex> vertices = renderer()->document()->inlineVertices().toList(); |
107 |
125 |
108 // Sort the vertices in order of distance to camera |
126 // Sort the vertices in order of distance to camera |
109 std::sort (vertices.begin(), vertices.end(), [&](const Vertex& a, const Vertex& b) -> bool |
127 std::sort(vertices.begin(), vertices.end(), [&](const Vertex& a, const Vertex& b) -> bool |
110 { |
128 { |
111 if (renderer()->cameraInfo (renderer()->camera()).negatedDepth) |
129 if (renderer()->cameraInfo(renderer()->camera()).negatedDepth) |
112 return a[depthAxis] > b[depthAxis]; |
130 return a[depthAxis] > b[depthAxis]; |
113 else |
131 else |
114 return a[depthAxis] < b[depthAxis]; |
132 return a[depthAxis] < b[depthAxis]; |
115 }); |
133 }); |
116 |
134 |
117 for (const Vertex& vrt : vertices) |
135 for (const Vertex& vertex : vertices) |
118 { |
136 { |
119 // If the vertex in 2d space is very close to the cursor then we use it regardless of depth. |
137 // If the vertex in 2d space is very close to the cursor then we use it regardless of depth. |
120 QPoint vect2d = renderer()->convert3dTo2d (vrt) - cursorPosition2D; |
138 QPoint vect2d = renderer()->convert3dTo2d(vertex) - cursorPosition2D; |
121 const double distance2DSquared = std::pow (vect2d.x(), 2) + std::pow (vect2d.y(), 2); |
139 double distance2DSquared = std::pow (vect2d.x(), 2) + std::pow (vect2d.y(), 2); |
|
140 |
122 if (distance2DSquared < 16.0 * 16.0) |
141 if (distance2DSquared < 16.0 * 16.0) |
123 { |
142 { |
124 closest = &vrt; |
143 closest = &vertex; |
125 break; |
144 break; |
126 } |
145 } |
127 |
146 |
128 // Check if too far away from the cursor. |
147 // Check if too far away from the cursor. |
129 if (distance2DSquared > 64.0 * 64.0) |
148 if (distance2DSquared > 64.0 * 64.0) |
130 continue; |
149 continue; |
131 |
150 |
132 // Not very close to the cursor. Compare using true distance, |
151 // Not very close to the cursor. Compare using true distance, |
133 // including depth. |
152 // including depth. |
134 const double distanceSquared = (vrt - cursorPosition).lengthSquared(); |
153 double distanceSquared = (vertex - cursorPosition).lengthSquared(); |
135 |
154 |
136 if (distanceSquared < minimumDistance) |
155 if (distanceSquared < minimumDistance) |
137 { |
156 { |
138 minimumDistance = distanceSquared; |
157 minimumDistance = distanceSquared; |
139 closest = &vrt; |
158 closest = &vertex; |
140 } |
159 } |
141 } |
160 } |
142 |
161 |
143 if (closest) |
162 if (closest) |
144 addDrawnVertex (*closest); |
163 addDrawnVertex(*closest); |
145 |
164 |
146 return true; |
165 return true; |
147 } |
166 } |
148 |
167 |
|
168 // If the user presses the right mouse button, remove the previously drawn vertex. |
149 if ((data.releasedButtons & Qt::RightButton) and not m_drawedVerts.isEmpty()) |
169 if ((data.releasedButtons & Qt::RightButton) and not m_drawedVerts.isEmpty()) |
150 { |
170 { |
151 // Remove the last vertex |
|
152 m_drawedVerts.removeLast(); |
171 m_drawedVerts.removeLast(); |
153 return true; |
172 return true; |
154 } |
173 } |
155 |
174 |
|
175 // If the user presses the left mouse button, insert the vertex or stop drawing, whichever is appropriate. |
156 if (data.releasedButtons & Qt::LeftButton) |
176 if (data.releasedButtons & Qt::LeftButton) |
157 { |
177 { |
158 if (maxVertices() and countof(m_drawedVerts) >= maxVertices()) |
178 if (maxVertices() and countof(m_drawedVerts) >= maxVertices()) |
159 endDraw(); |
179 endDraw(); |
160 else |
180 else |
161 addDrawnVertex (getCursorVertex()); |
181 addDrawnVertex (getCursorVertex()); |
162 return true; |
182 |
163 } |
183 return true; |
164 |
184 } |
|
185 |
|
186 // Otherwise we did not handle this mouse event. |
165 return false; |
187 return false; |
166 } |
188 } |
167 |
189 |
|
190 /* |
|
191 * Finalises the draw operation. The provided model is merged into the main document. |
|
192 */ |
168 void AbstractDrawMode::finishDraw(Model& model) |
193 void AbstractDrawMode::finishDraw(Model& model) |
169 { |
194 { |
170 int pos = m_window->suggestInsertPoint(); |
195 int position = m_window->suggestInsertPoint(); |
171 |
196 |
172 if (countof(model.objects()) > 0) |
197 if (countof(model) > 0) |
173 { |
198 { |
174 for (LDObject* obj : model) |
199 renderer()->document()->merge(model, position); |
175 renderer()->document()->insertObject (pos++, obj); |
|
176 |
|
177 m_window->refresh(); |
200 m_window->refresh(); |
178 m_window->endAction(); |
201 m_window->endAction(); |
179 } |
202 } |
180 |
203 |
181 m_drawedVerts.clear(); |
204 m_drawedVerts.clear(); |
182 } |
205 } |
183 |
206 |
184 void AbstractDrawMode::drawLineLength(QPainter &painter, const Vertex &v0, const Vertex &v1, |
207 /* |
185 const QPointF& v0p, const QPointF& v1p) const |
208 * Renders the length of the provided line. |
|
209 * - v0 and v1 are the line vertices in 3D space. |
|
210 * - v0p and v1p are the line vertices in 2D space (so that this function does not have to calculate them separately) |
|
211 */ |
|
212 void AbstractDrawMode::drawLineLength(QPainter &painter, const Vertex &v0, const Vertex &v1, const QPointF& v0p, const QPointF& v1p) const |
186 { |
213 { |
187 if (not m_config->drawLineLengths()) |
214 if (not m_config->drawLineLengths()) |
188 return; |
215 return; |
189 |
216 |
190 const QString label = QString::number(abs(v1 - v0), 'f', 2); |
217 const QString label = QString::number(abs(v1 - v0), 'f', 2); |
191 QPoint origin = QLineF (v0p, v1p).pointAt (0.5).toPoint(); |
218 QPoint origin = QLineF {v0p, v1p}.pointAt(0.5).toPoint(); |
192 painter.drawText (origin, label); |
219 painter.drawText (origin, label); |
193 } |
220 } |
194 |
221 |
195 void AbstractDrawMode::renderPolygon(QPainter& painter, const QVector<Vertex>& polygon3d, |
222 /* |
196 bool drawLineLengths, bool drawAngles ) const |
223 * Renders a polygon preview. |
197 { |
224 * |
198 QVector<QPoint> polygon2d (countof(polygon3d)); |
225 * painter: QPainter instance that is currently being rendered to. |
199 QFontMetrics metrics = QFontMetrics(QFont()); |
226 * polygon3d: The polygon as a vector of 3D vertices. |
|
227 * drawLineLengths: if true, lengths of polygon sides are also previewed, assuming the user has enabled the relevant option. |
|
228 * drawAngles: if true, the angles between polygon sides are also previewed, assuming the user has enabled the relevant option. |
|
229 */ |
|
230 void AbstractDrawMode::renderPolygon(QPainter& painter, const QVector<Vertex>& polygon3d, bool drawLineLengths, bool drawAngles) const |
|
231 { |
|
232 QVector<QPoint> polygon2d {countof(polygon3d)}; |
|
233 QFontMetrics metrics {QFont {}}; |
200 |
234 |
201 // Convert to 2D |
235 // Convert to 2D |
202 for (int i = 0; i < countof(polygon3d); ++i) |
236 for (int i = 0; i < countof(polygon3d); ++i) |
203 polygon2d[i] = renderer()->convert3dTo2d(polygon3d[i]); |
237 polygon2d[i] = renderer()->convert3dTo2d(polygon3d[i]); |
204 |
238 |
242 } |
276 } |
243 } |
277 } |
244 } |
278 } |
245 } |
279 } |
246 |
280 |
247 bool AbstractDrawMode::keyReleased (QKeyEvent *ev) |
281 /* |
248 { |
282 * Key release event handler |
249 if (Super::keyReleased (ev)) |
283 */ |
250 return true; |
284 bool AbstractDrawMode::keyReleased(QKeyEvent *event) |
251 |
285 { |
252 if (not m_drawedVerts.isEmpty() and ev->key() == Qt::Key_Backspace) |
286 if (Super::keyReleased(event)) |
|
287 return true; |
|
288 |
|
289 // Map backspace to removing the previously drawn vertex. |
|
290 if (not m_drawedVerts.isEmpty() and event->key() == Qt::Key_Backspace) |
253 { |
291 { |
254 m_drawedVerts.removeLast(); |
292 m_drawedVerts.removeLast(); |
255 return true; |
293 return true; |
256 } |
294 } |
257 |
295 |
258 return false; |
296 return false; |
259 } |
297 } |
260 |
298 |
261 // |
299 /* |
262 // roundToInterval |
300 * Rounds the input value to the nearest multiple of the provided interval. |
263 // |
301 */ |
264 // Rounds 'a' to the nearest multiple of 'interval'. |
|
265 // |
|
266 template<typename T> |
302 template<typename T> |
267 T roundToInterval (T a, T interval) |
303 T roundToInterval(T value, T interval) |
268 { |
304 { |
269 T remainder = a % interval; |
305 T remainder = value % interval; |
270 |
306 |
271 if (remainder >= interval / 2.0) |
307 if (remainder >= interval / 2.0) |
272 a += interval; |
308 value += interval; |
273 |
309 |
274 a -= remainder; |
310 value -= remainder; |
275 return a; |
311 return value; |
276 } |
312 } |
277 |
313 |
|
314 /* |
|
315 * Computes the position for the vertex currently being drawn. |
|
316 */ |
278 Vertex AbstractDrawMode::getCursorVertex() const |
317 Vertex AbstractDrawMode::getCursorVertex() const |
279 { |
318 { |
280 Vertex result = renderer()->position3D(); |
319 Vertex result = renderer()->position3D(); |
281 |
320 |
|
321 // If the Ctrl key is pressed, then the vertex is locked to 45 degree angles relative to the previously drawn vertex. |
282 if ((renderer()->keyboardModifiers() & Qt::ControlModifier) and not m_drawedVerts.isEmpty()) |
322 if ((renderer()->keyboardModifiers() & Qt::ControlModifier) and not m_drawedVerts.isEmpty()) |
283 { |
323 { |
284 Vertex const& vertex0 = m_drawedVerts.last(); |
324 const Vertex& vertex0 = m_drawedVerts.last(); |
285 Vertex const& vertex1 = result; |
325 const Vertex& vertex1 = result; |
286 Axis relativeX, relativeY; |
326 Axis relativeX; |
287 |
327 Axis relativeY; |
288 renderer()->getRelativeAxes (relativeX, relativeY); |
328 renderer()->getRelativeAxes(relativeX, relativeY); |
289 QLineF line = {vertex0[relativeX], vertex0[relativeY], vertex1[relativeX], vertex1[relativeY]}; |
329 QLineF line = {vertex0[relativeX], vertex0[relativeY], vertex1[relativeX], vertex1[relativeY]}; |
290 line.setAngle(roundToInterval<int>(line.angle(), 45)); |
330 line.setAngle(roundToInterval<int>(line.angle(), 45)); |
291 result.setCoordinate(relativeX, grid()->snap(line.x2(), Grid::Coordinate)); |
331 result.setCoordinate(relativeX, grid()->snap(line.x2(), Grid::Coordinate)); |
292 result.setCoordinate(relativeY, grid()->snap(line.y2(), Grid::Coordinate)); |
332 result.setCoordinate(relativeY, grid()->snap(line.y2(), Grid::Coordinate)); |
293 } |
333 } |
294 |
334 |
295 return result; |
335 return result; |
296 } |
336 } |
|
337 |
|
338 /* |
|
339 * No draw mode can operate on the free camera, since 3D ⟷ 2D point conversions are not possible with it. |
|
340 */ |
|
341 bool AbstractDrawMode::allowFreeCamera() const |
|
342 { |
|
343 return false; |
|
344 } |
|
345 |
|
346 /* |
|
347 * This virtual method allows drawing modes to specify how many vertices at most can be drawn. Returning 0 means unlimited. |
|
348 */ |
|
349 int AbstractDrawMode::maxVertices() const |
|
350 { |
|
351 return 0; |
|
352 } |
|
353 |
|
354 /* |
|
355 * This virtual method is a hook that allows drawing modes to veto vertex insertion. Returning true means that the vertex insertion |
|
356 * was handled separately and the vertex will not be added. |
|
357 */ |
|
358 bool AbstractDrawMode::preAddVertex (Vertex const&) |
|
359 { |
|
360 return false; |
|
361 } |
|
362 |
|
363 /* |
|
364 * This virtual method is overridden by subclasses to implement the actions taken by the edit mode. |
|
365 * The editing mode is to call finishDraw with the prepared model. |
|
366 * |
|
367 * TODO: the two method names are too similar and should be renamed. |
|
368 */ |
|
369 void AbstractDrawMode::endDraw() {} |