24 #include "../ringFinder.h" |
24 #include "../ringFinder.h" |
25 #include "../primitives.h" |
25 #include "../primitives.h" |
26 #include "../glRenderer.h" |
26 #include "../glRenderer.h" |
27 #include "../mainwindow.h" |
27 #include "../mainwindow.h" |
28 #include "../mathfunctions.h" |
28 #include "../mathfunctions.h" |
|
29 #include "../miscallenous.h" |
29 #include "../grid.h" |
30 #include "../grid.h" |
30 |
31 |
31 CircleMode::CircleMode (GLRenderer* renderer) : |
32 CircleMode::CircleMode(GLRenderer* renderer) : |
32 Super (renderer) {} |
33 Super {renderer} {} |
33 |
34 |
34 |
35 |
35 EditModeType CircleMode::type() const |
36 EditModeType CircleMode::type() const |
36 { |
37 { |
37 return EditModeType::Circle; |
38 return EditModeType::Circle; |
38 } |
39 } |
39 |
40 |
40 |
41 |
41 double CircleMode::getCircleDrawDist (int pos) const |
42 double CircleMode::getCircleDrawDist(int position) const |
42 { |
43 { |
43 if (countof(m_drawedVerts) >= pos + 1) |
44 if (countof(m_drawedVerts) >= position + 1) |
44 { |
45 { |
45 Vertex v1; |
46 Vertex v1; |
46 |
47 |
47 if (countof(m_drawedVerts) >= pos + 2) |
48 if (countof(m_drawedVerts) >= position + 2) |
48 v1 = m_drawedVerts[pos + 1]; |
49 v1 = m_drawedVerts[position + 1]; |
49 else |
50 else |
50 v1 = renderer()->convert2dTo3d (renderer()->mousePosition(), false); |
51 v1 = renderer()->convert2dTo3d (renderer()->mousePosition(), false); |
51 |
52 |
52 Axis localx, localy; |
53 Axis localx, localy; |
53 renderer()->getRelativeAxes (localx, localy); |
54 renderer()->getRelativeAxes(localx, localy); |
54 double dx = m_drawedVerts[0][localx] - v1[localx]; |
55 double dx = m_drawedVerts[0][localx] - v1[localx]; |
55 double dy = m_drawedVerts[0][localy] - v1[localy]; |
56 double dy = m_drawedVerts[0][localy] - v1[localy]; |
56 return grid()->snap(hypot(dx, dy), Grid::Coordinate); |
57 return grid()->snap(hypot(dx, dy), Grid::Coordinate); |
57 } |
58 } |
58 |
59 |
59 return 0.0; |
60 return 0.0; |
60 } |
61 } |
61 |
62 |
62 |
63 |
63 Matrix CircleMode::getCircleDrawMatrix (double scale) |
64 Matrix CircleMode::getCircleDrawMatrix(double scale) |
64 { |
65 { |
65 // Matrix templates. 2 is substituted with the scale value, 1 is inverted to -1 if needed. |
66 // Matrix templates. 2 is substituted with the scale value, 1 is inverted to -1 if needed. |
66 static const Matrix templates[3] = |
67 static const Matrix templates[3] = |
67 { |
68 { |
68 { 2, 0, 0, 0, 1, 0, 0, 0, 2 }, |
69 { 2, 0, 0, 0, 1, 0, 0, 0, 2 }, |
96 LDDocument* primitiveFile; |
97 LDDocument* primitiveFile; |
97 Matrix transform; |
98 Matrix transform; |
98 bool circleOrDisc = false; |
99 bool circleOrDisc = false; |
99 |
100 |
100 if (dist1 < dist0) |
101 if (dist1 < dist0) |
101 qSwap (dist0, dist1); |
102 qSwap(dist0, dist1); |
102 |
103 |
103 if (dist0 == dist1) |
104 if (dist0 == dist1) |
104 { |
105 { |
105 // If the radii are the same, there's no ring space to fill. Use a circle. |
106 // If the radii are the same, there's no ring space to fill. Use a circle. |
106 primitiveModel.type = PrimitiveModel::Circle; |
107 primitiveModel.type = PrimitiveModel::Circle; |
107 primitiveFile = primitives()->getPrimitive(primitiveModel); |
108 primitiveFile = primitives()->getPrimitive(primitiveModel); |
108 transform = getCircleDrawMatrix (dist0); |
109 transform = getCircleDrawMatrix(dist0); |
109 circleOrDisc = true; |
110 circleOrDisc = true; |
110 } |
111 } |
111 else if (dist0 == 0 or dist1 == 0) |
112 else if (dist0 == 0 or dist1 == 0) |
112 { |
113 { |
113 // If either radii is 0, use a disc. |
114 // If either radii is 0, use a disc. |
114 primitiveModel.type = PrimitiveModel::Disc; |
115 primitiveModel.type = PrimitiveModel::Disc; |
115 primitiveFile = primitives()->getPrimitive(primitiveModel); |
116 primitiveFile = primitives()->getPrimitive(primitiveModel); |
116 transform = getCircleDrawMatrix ((dist0 != 0) ? dist0 : dist1); |
117 transform = getCircleDrawMatrix ((dist0 != 0) ? dist0 : dist1); |
117 circleOrDisc = true; |
118 circleOrDisc = true; |
118 } |
119 } |
119 else if (g_RingFinder.findRings (dist0, dist1)) |
120 else if (g_RingFinder.findRings(dist0, dist1)) |
120 { |
121 { |
121 // The ring finder found a solution, use that. Add the component rings to the file. |
122 // The ring finder found a solution, use that. Add the component rings to the file. |
122 primitiveModel.type = PrimitiveModel::Ring; |
123 primitiveModel.type = PrimitiveModel::Ring; |
123 |
124 |
124 for (const RingFinder::Component& component : g_RingFinder.bestSolution()->getComponents()) |
125 for (const RingFinder::Component& component : g_RingFinder.bestSolution()->getComponents()) |
132 { |
133 { |
133 // Ring finder failed, last resort: draw the ring with quads |
134 // Ring finder failed, last resort: draw the ring with quads |
134 Axis localx, localy, localz; |
135 Axis localx, localy, localz; |
135 renderer()->getRelativeAxes (localx, localy); |
136 renderer()->getRelativeAxes (localx, localy); |
136 localz = (Axis) (3 - localx - localy); |
137 localz = (Axis) (3 - localx - localy); |
137 double x0 (m_drawedVerts[0][localx]); |
138 double x0 = m_drawedVerts[0][localx]; |
138 double y0 (m_drawedVerts[0][localy]); |
139 double y0 = m_drawedVerts[0][localy]; |
139 |
140 |
140 Vertex templ; |
141 Vertex templ; |
141 templ.setCoordinate (localx, x0); |
142 templ.setCoordinate(localx, x0); |
142 templ.setCoordinate (localy, y0); |
143 templ.setCoordinate(localy, y0); |
143 templ.setCoordinate (localz, renderer()->getDepthValue()); |
144 templ.setCoordinate(localz, renderer()->getDepthValue()); |
144 |
145 |
145 // Calculate circle coords |
146 // Calculate circle coords |
146 QVector<QLineF> c0 = makeCircle(primitiveModel.segments, primitiveModel.divisions, dist0); |
147 QVector<QLineF> c0 = makeCircle(primitiveModel.segments, primitiveModel.divisions, dist0); |
147 QVector<QLineF> c1 = makeCircle(primitiveModel.segments, primitiveModel.divisions, dist1); |
148 QVector<QLineF> c1 = makeCircle(primitiveModel.segments, primitiveModel.divisions, dist1); |
148 |
149 |
158 v2.setCoordinate (localy, v2[localy] + c1[i].y2()); |
159 v2.setCoordinate (localy, v2[localy] + c1[i].y2()); |
159 v3.setCoordinate (localx, v3[localx] + c1[i].x1()); |
160 v3.setCoordinate (localx, v3[localx] + c1[i].x1()); |
160 v3.setCoordinate (localy, v3[localy] + c1[i].y1()); |
161 v3.setCoordinate (localy, v3[localy] + c1[i].y1()); |
161 |
162 |
162 LDQuad* quad = model.emplace<LDQuad>(v0, v1, v2, v3); |
163 LDQuad* quad = model.emplace<LDQuad>(v0, v1, v2, v3); |
163 quad->setColor (MainColor); |
164 quad->setColor(MainColor); |
164 |
165 |
165 // Ensure the quads always are BFC-front towards the camera |
166 // Ensure the quads always are BFC-front towards the camera |
166 if (renderer()->camera() % 3 <= 0) |
167 if (renderer()->camera() % 3 <= 0) |
167 quad->invert(); |
168 quad->invert(); |
168 } |
169 } |
181 } |
182 } |
182 |
183 |
183 finishDraw (model); |
184 finishDraw (model); |
184 } |
185 } |
185 |
186 |
186 |
187 /* |
187 double CircleMode::getAngleOffset() const |
188 * Which way around will we place our circle primitive? This only makes a difference if we're not drawing a full circle. |
188 { |
189 * Result is an angle offset in radians. |
189 if (m_drawedVerts.isEmpty()) |
190 */ |
|
191 double CircleMode::orientation() const |
|
192 { |
|
193 if (not m_drawedVerts.isEmpty()) |
|
194 { |
|
195 int divisions = m_window->ringToolHiRes() ? HighResolution : LowResolution; |
|
196 QPointF originSpot = renderer()->convert3dTo2d(m_drawedVerts.first()); |
|
197 // Line from the origin of the circle to current mouse position |
|
198 QLineF hand1 = {originSpot, renderer()->mousePositionF()}; |
|
199 // Line from the origin spot to |
|
200 QLineF hand2 = {{0, 0}, {1, 0}}; |
|
201 // Calculate the angle between these hands and round it to whole divisions. |
|
202 double angleoffset = roundToInterval(-hand1.angleTo(hand2), 360.0 / divisions); |
|
203 // Take the camera's depth coefficient into account here. This way, the preview is flipped if the |
|
204 // primitive also would be. |
|
205 return angleoffset * pi / 180.0 * renderer()->depthNegateFactor(); |
|
206 } |
|
207 else |
|
208 { |
190 return 0.0; |
209 return 0.0; |
191 |
210 } |
192 int divisions = (m_window->ringToolHiRes() ? HighResolution : LowResolution); |
|
193 QPointF originspot (renderer()->convert3dTo2d (m_drawedVerts.first())); |
|
194 QLineF bearing (originspot, renderer()->mousePositionF()); |
|
195 QLineF bearing2 (originspot, QPointF (originspot.x(), 0.0)); |
|
196 double angleoffset (-bearing.angleTo (bearing2) + 90); |
|
197 angleoffset /= (360.0 / divisions); // convert angle to 0-16 scale |
|
198 angleoffset = round (angleoffset); // round to nearest 16th |
|
199 angleoffset *= ((2 * pi) / divisions); // convert to radians |
|
200 angleoffset *= renderer()->depthNegateFactor(); // negate based on camera |
|
201 return angleoffset; |
|
202 } |
211 } |
203 |
212 |
204 |
213 |
205 void CircleMode::render (QPainter& painter) const |
214 void CircleMode::render (QPainter& painter) const |
206 { |
215 { |
207 QFontMetrics metrics = QFontMetrics (QFont()); |
216 QFontMetrics metrics = QFontMetrics (QFont()); |
208 |
217 |
209 // If we have not specified the center point of the circle yet, preview it on the screen. |
218 // If we have not specified the center point of the circle yet, preview it on the screen. |
210 if (m_drawedVerts.isEmpty()) |
219 if (m_drawedVerts.isEmpty()) |
211 { |
220 { |
212 QPoint pos2d = renderer()->convert3dTo2d (renderer()->position3D()); |
221 QPoint position2d = renderer()->convert3dTo2d(renderer()->position3D()); |
213 renderer()->drawPoint (painter, pos2d); |
222 renderer()->drawPoint(painter, position2d); |
214 renderer()->drawBlipCoordinates (painter, renderer()->position3D(), pos2d); |
223 renderer()->drawBlipCoordinates(painter, renderer()->position3D(), position2d); |
215 return; |
224 return; |
216 } |
225 } |
217 |
226 |
218 QVector<Vertex> innerverts, outerverts; |
227 QVector<Vertex> innerverts, outerverts; |
219 QVector<QPointF> innerverts2d, outerverts2d; |
228 QVector<QPointF> innerverts2d, outerverts2d; |
220 const double innerdistance (getCircleDrawDist (0)); |
229 double innerdistance = getCircleDrawDist(0); |
221 const double outerdistance (countof(m_drawedVerts) >= 2 ? getCircleDrawDist (1) : -1); |
230 double outerdistance = countof(m_drawedVerts) >= 2 ? getCircleDrawDist (1) : -1; |
222 const int divisions (m_window->ringToolHiRes() ? HighResolution : LowResolution); |
231 int divisions = m_window->ringToolHiRes() ? HighResolution : LowResolution; |
223 const int segments (m_window->ringToolSegments()); |
232 int segments = m_window->ringToolSegments(); |
224 const double angleUnit (2 * pi / divisions); |
233 double angleUnit = 2 * pi / divisions; |
225 Axis relX, relY; |
234 Axis relX, relY; |
226 renderer()->getRelativeAxes (relX, relY); |
235 renderer()->getRelativeAxes(relX, relY); |
227 const double angleoffset (countof(m_drawedVerts) < 3 ? getAngleOffset() : m_angleOffset); |
236 double angleoffset = (countof(m_drawedVerts) < 3 ? orientation() : m_angleOffset); |
228 |
237 |
229 // Calculate the preview positions of vertices |
238 // Calculate the preview positions of vertices |
230 for (int i = 0; i < segments + 1; ++i) |
239 for (int i = 0; i < segments + 1; ++i) |
231 { |
240 { |
232 const double sinangle (sin (angleoffset + i * angleUnit)); |
241 const double sinangle (sin (angleoffset + i * angleUnit)); |
244 outerverts << v; |
253 outerverts << v; |
245 outerverts2d << renderer()->convert3dTo2d (v); |
254 outerverts2d << renderer()->convert3dTo2d (v); |
246 } |
255 } |
247 } |
256 } |
248 |
257 |
249 QVector<QLineF> lines (segments); |
258 QVector<QLineF> lines {segments}; |
250 |
259 |
251 if (outerdistance != -1 and outerdistance != innerdistance) |
260 if (outerdistance != -1 and outerdistance != innerdistance) |
252 { |
261 { |
253 painter.setBrush (m_polybrush); |
262 painter.setBrush(m_polybrush); |
254 painter.setPen (Qt::NoPen); |
263 painter.setPen(Qt::NoPen); |
255 |
264 |
256 // Compile polygons |
265 // Compile polygons |
257 for (int i = 0; i < segments; ++i) |
266 for (int i = 0; i < segments; ++i) |
258 { |
267 { |
259 QVector<QPointF> points; |
268 QVector<QPointF> points; |
260 points << innerverts2d[i] |
269 points << innerverts2d[i] |
261 << innerverts2d[i + 1] |
270 << innerverts2d[i + 1] |
262 << outerverts2d[i + 1] |
271 << outerverts2d[i + 1] |
263 << outerverts2d[i]; |
272 << outerverts2d[i]; |
264 painter.drawPolygon (QPolygonF (points)); |
273 painter.drawPolygon (QPolygonF (points)); |
265 lines << QLineF (innerverts2d[i], innerverts2d[i + 1]); |
274 lines.append({innerverts2d[i], innerverts2d[i + 1]}); |
266 lines << QLineF (outerverts2d[i], outerverts2d[i + 1]); |
275 lines.append({outerverts2d[i], outerverts2d[i + 1]}); |
267 } |
276 } |
268 |
277 |
269 // Add bordering edges for unclosed rings/discs |
278 // Add bordering edges for unclosed rings/discs |
270 if (segments != divisions) |
279 if (segments != divisions) |
271 { |
280 { |
272 lines << QLineF (innerverts2d.first(), outerverts2d.first()); |
281 lines.append({innerverts2d.first(), outerverts2d.first()}); |
273 lines << QLineF (innerverts2d.last(), outerverts2d.last()); |
282 lines.append({innerverts2d.last(), outerverts2d.last()}); |
274 } |
283 } |
275 } |
284 } |
276 else |
285 else |
277 { |
286 { |
278 for (int i = 0; i < segments; ++i) |
287 for (int i = 0; i < segments; ++i) |
279 lines << QLineF (innerverts2d[i], innerverts2d[i + 1]); |
288 lines.append({innerverts2d[i], innerverts2d[i + 1]}); |
280 } |
289 } |
281 |
290 |
282 // Draw green blips at where the points are |
291 // Draw green blips at where the points are |
283 for (QPointF const& point : innerverts2d + outerverts2d) |
292 for (const QPointF& point : innerverts2d + outerverts2d) |
284 renderer()->drawPoint (painter, point); |
293 renderer()->drawPoint(painter, point); |
285 |
294 |
286 // Draw edge lines |
295 // Draw edge lines |
287 painter.setPen (renderer()->linePen()); |
296 painter.setPen(renderer()->linePen()); |
288 painter.drawLines (lines); |
297 painter.drawLines(lines); |
289 |
298 |
290 // Draw the current radius in the middle of the circle. |
299 // Draw the current radius in the middle of the circle. |
291 QPoint origin = renderer()->convert3dTo2d (m_drawedVerts[0]); |
300 QPoint origin = renderer()->convert3dTo2d (m_drawedVerts[0]); |
292 QString label = QString::number (innerdistance); |
301 QString label = QString::number (innerdistance); |
293 painter.setPen (renderer()->textPen()); |
302 painter.setPen(renderer()->textPen()); |
294 painter.drawText (origin.x() - (metrics.width (label) / 2), origin.y(), label); |
303 painter.drawText(origin.x() - (metrics.width(label) / 2), origin.y(), label); |
295 |
304 |
296 if (countof(m_drawedVerts) >= 2) |
305 if (countof(m_drawedVerts) >= 2) |
297 { |
306 { |
298 painter.drawText (origin.x() - (metrics.width (label) / 2), |
307 painter.drawText(origin.x() - (metrics.width(label) / 2), |
299 origin.y() + metrics.height(), QString::number (outerdistance)); |
308 origin.y() + metrics.height(), QString::number(outerdistance)); |
300 } |
309 } |
301 } |
310 } |
302 |
311 |
303 |
312 |
304 bool CircleMode::preAddVertex (const Vertex&) |
313 bool CircleMode::preAddVertex (const Vertex&) |
305 { |
314 { |
306 m_angleOffset = getAngleOffset(); |
315 m_angleOffset = orientation(); |
307 return false; |
316 return false; |
308 } |
317 } |
309 |
318 |
310 |
319 |
311 int CircleMode::maxVertices() const |
320 int CircleMode::maxVertices() const |