|
1 /* |
|
2 * LDForge: LDraw parts authoring CAD |
|
3 * Copyright (C) 2013, 2014 Teemu Piippo |
|
4 * |
|
5 * This program is free software: you can redistribute it and/or modify |
|
6 * it under the terms of the GNU General Public License as published by |
|
7 * the Free Software Foundation, either version 3 of the License, or |
|
8 * (at your option) any later version. |
|
9 * |
|
10 * This program is distributed in the hope that it will be useful, |
|
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
13 * GNU General Public License for more details. |
|
14 * |
|
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/>. |
|
17 */ |
|
18 |
|
19 #include <QPainter> |
|
20 #include "circleMode.h" |
|
21 #include "../miscallenous.h" |
|
22 #include "../ldObject.h" |
|
23 #include "../ldDocument.h" |
|
24 #include "../ringFinder.h" |
|
25 #include "../primitives.h" |
|
26 #include "../glRenderer.h" |
|
27 #include "../mainWindow.h" |
|
28 #include "../ldObjectMath.h" |
|
29 |
|
30 CircleMode::CircleMode (GLRenderer* renderer) : |
|
31 Super (renderer) {} |
|
32 |
|
33 EditModeType CircleMode::type() const |
|
34 { |
|
35 return EditModeType::Circle; |
|
36 } |
|
37 |
|
38 double CircleMode::getCircleDrawDist (int pos) const |
|
39 { |
|
40 assert (m_drawedVerts.size() >= pos + 1); |
|
41 Vertex v1 = (m_drawedVerts.size() >= pos + 2) ? m_drawedVerts[pos + 1] : |
|
42 renderer()->coordconv2_3 (renderer()->mousePosition(), false); |
|
43 Axis localx, localy; |
|
44 renderer()->getRelativeAxes (localx, localy); |
|
45 double dx = m_drawedVerts[0][localx] - v1[localx]; |
|
46 double dy = m_drawedVerts[0][localy] - v1[localy]; |
|
47 return Grid::Snap (sqrt ((dx * dx) + (dy * dy)), Grid::Coordinate); |
|
48 } |
|
49 |
|
50 Matrix CircleMode::getCircleDrawMatrix (double scale) |
|
51 { |
|
52 // Matrix templates. 2 is substituted with the scale value, 1 is inverted to -1 if needed. |
|
53 static const Matrix templates[3] = |
|
54 { |
|
55 { 2, 0, 0, 0, 1, 0, 0, 0, 2 }, |
|
56 { 2, 0, 0, 0, 0, 2, 0, 1, 0 }, |
|
57 { 0, 1, 0, 2, 0, 0, 0, 0, 2 }, |
|
58 }; |
|
59 |
|
60 Matrix transform = templates[renderer()->camera() % 3]; |
|
61 |
|
62 for (int i = 0; i < 9; ++i) |
|
63 { |
|
64 if (transform[i] == 2) |
|
65 transform[i] = scale; |
|
66 elif (transform[i] == 1 and renderer()->camera() >= 3) |
|
67 transform[i] = -1; |
|
68 } |
|
69 |
|
70 return transform; |
|
71 } |
|
72 |
|
73 void CircleMode::buildCircle() |
|
74 { |
|
75 LDObjectList objs; |
|
76 const int segments (g_win->ringToolSegments()); |
|
77 const int divisions (g_win->ringToolHiRes() ? HighResolution : LowResolution); |
|
78 double dist0 (getCircleDrawDist (0)); |
|
79 double dist1 (getCircleDrawDist (1)); |
|
80 LDDocument* refFile; |
|
81 Matrix transform; |
|
82 bool circleOrDisc = false; |
|
83 |
|
84 if (dist1 < dist0) |
|
85 qSwap (dist0, dist1); |
|
86 |
|
87 if (dist0 == dist1) |
|
88 { |
|
89 // If the radii are the same, there's no ring space to fill. Use a circle. |
|
90 refFile = GetPrimitive (::Circle, segments, divisions, 0); |
|
91 transform = getCircleDrawMatrix (dist0); |
|
92 circleOrDisc = true; |
|
93 } |
|
94 elif (dist0 == 0 or dist1 == 0) |
|
95 { |
|
96 // If either radii is 0, use a disc. |
|
97 refFile = GetPrimitive (::Disc, segments, divisions, 0); |
|
98 transform = getCircleDrawMatrix ((dist0 != 0) ? dist0 : dist1); |
|
99 circleOrDisc = true; |
|
100 } |
|
101 elif (g_RingFinder.findRings (dist0, dist1)) |
|
102 { |
|
103 // The ring finder found a solution, use that. Add the component rings to the file. |
|
104 for (const RingFinder::Component& cmp : g_RingFinder.bestSolution()->getComponents()) |
|
105 { |
|
106 refFile = GetPrimitive (::Ring, segments, divisions, cmp.num); |
|
107 LDSubfile* ref = LDSpawn<LDSubfile>(); |
|
108 ref->setFileInfo (refFile); |
|
109 ref->setTransform (getCircleDrawMatrix (cmp.scale)); |
|
110 ref->setPosition (m_drawedVerts[0]); |
|
111 ref->setColor (MainColor); |
|
112 objs << ref; |
|
113 } |
|
114 } |
|
115 else |
|
116 { |
|
117 // Ring finder failed, last resort: draw the ring with quads |
|
118 QList<QLineF> c0, c1; |
|
119 Axis localx, localy, localz; |
|
120 renderer()->getRelativeAxes (localx, localy); |
|
121 localz = (Axis) (3 - localx - localy); |
|
122 double x0 (m_drawedVerts[0][localx]); |
|
123 double y0 (m_drawedVerts[0][localy]); |
|
124 |
|
125 Vertex templ; |
|
126 templ.setCoordinate (localx, x0); |
|
127 templ.setCoordinate (localy, y0); |
|
128 templ.setCoordinate (localz, renderer()->getDepthValue()); |
|
129 |
|
130 // Calculate circle coords |
|
131 MakeCircle (segments, divisions, dist0, c0); |
|
132 MakeCircle (segments, divisions, dist1, c1); |
|
133 |
|
134 for (int i = 0; i < segments; ++i) |
|
135 { |
|
136 Vertex v0, v1, v2, v3; |
|
137 v0 = v1 = v2 = v3 = templ; |
|
138 v0.setCoordinate (localx, v0[localx] + c0[i].x1()); |
|
139 v0.setCoordinate (localy, v0[localy] + c0[i].y1()); |
|
140 v1.setCoordinate (localx, v1[localx] + c0[i].x2()); |
|
141 v1.setCoordinate (localy, v1[localy] + c0[i].y2()); |
|
142 v2.setCoordinate (localx, v2[localx] + c1[i].x2()); |
|
143 v2.setCoordinate (localy, v2[localy] + c1[i].y2()); |
|
144 v3.setCoordinate (localx, v3[localx] + c1[i].x1()); |
|
145 v3.setCoordinate (localy, v3[localy] + c1[i].y1()); |
|
146 |
|
147 LDQuad* quad (LDSpawn<LDQuad> (v0, v1, v2, v3)); |
|
148 quad->setColor (MainColor); |
|
149 |
|
150 // Ensure the quads always are BFC-front towards the camera |
|
151 if (renderer()->camera() % 3 <= 0) |
|
152 quad->invert(); |
|
153 |
|
154 objs << quad; |
|
155 } |
|
156 } |
|
157 |
|
158 if (circleOrDisc and refFile != null) |
|
159 { |
|
160 LDSubfile* ref = LDSpawn<LDSubfile>(); |
|
161 ref->setFileInfo (refFile); |
|
162 ref->setTransform (transform); |
|
163 ref->setPosition (m_drawedVerts[0]); |
|
164 ref->setColor (MainColor); |
|
165 objs << ref; |
|
166 } |
|
167 |
|
168 unless (objs.isEmpty()) |
|
169 { |
|
170 Axis relZ = renderer()->getRelativeZ();; |
|
171 const int l (relZ == X ? 1 : 0); |
|
172 const int m (relZ == Y ? 1 : 0); |
|
173 const int n (relZ == Z ? 1 : 0); |
|
174 RotateObjects (l, m, n, -m_angleOffset, objs); |
|
175 } |
|
176 |
|
177 finishDraw (objs); |
|
178 } |
|
179 |
|
180 double CircleMode::getAngleOffset() const |
|
181 { |
|
182 if (m_drawedVerts.isEmpty()) |
|
183 return 0.0; |
|
184 |
|
185 const int divisions (g_win->ringToolHiRes() ? HighResolution : LowResolution); |
|
186 QPointF originspot (renderer()->coordconv3_2 (m_drawedVerts.first())); |
|
187 QLineF bearing (originspot, renderer()->mousePositionF()); |
|
188 QLineF bearing2 (originspot, QPointF (originspot.x(), 0.0)); |
|
189 double angleoffset (-bearing.angleTo (bearing2) + 90); |
|
190 angleoffset /= (360.0 / divisions); // convert angle to 0-16 scale |
|
191 angleoffset = round (angleoffset); // round to nearest 16th |
|
192 angleoffset *= ((2 * Pi) / divisions); // convert to radians |
|
193 angleoffset *= renderer()->depthNegateFactor(); // negate based on camera |
|
194 return angleoffset; |
|
195 } |
|
196 |
|
197 void CircleMode::render (QPainter& painter) const |
|
198 { |
|
199 QFontMetrics metrics = QFontMetrics (QFont()); |
|
200 |
|
201 // If we have not specified the center point of the circle yet, preview it on the screen. |
|
202 if (m_drawedVerts.isEmpty()) |
|
203 { |
|
204 renderer()->drawBlip (painter, renderer()->coordconv3_2 (renderer()->position3D())); |
|
205 return; |
|
206 } |
|
207 |
|
208 QVector<Vertex> innerverts, outerverts; |
|
209 QVector<QPointF> innerverts2d, outerverts2d; |
|
210 const double innerdistance (getCircleDrawDist (0)); |
|
211 const double outerdistance (m_drawedVerts.size() >= 2 ? getCircleDrawDist (1) : -1); |
|
212 const int divisions (g_win->ringToolHiRes() ? HighResolution : LowResolution); |
|
213 const int segments (g_win->ringToolSegments()); |
|
214 const double angleUnit (2 * Pi / divisions); |
|
215 Axis relX, relY; |
|
216 renderer()->getRelativeAxes (relX, relY); |
|
217 const double angleoffset (m_drawedVerts.size() < 3 ? getAngleOffset() : m_angleOffset); |
|
218 |
|
219 // Calculate the preview positions of vertices |
|
220 for (int i = 0; i < segments + 1; ++i) |
|
221 { |
|
222 const double sinangle (sin (angleoffset + i * angleUnit)); |
|
223 const double cosangle (cos (angleoffset + i * angleUnit)); |
|
224 Vertex v (Origin); |
|
225 v.setCoordinate (relX, m_drawedVerts[0][relX] + (cosangle * innerdistance)); |
|
226 v.setCoordinate (relY, m_drawedVerts[0][relY] + (sinangle * innerdistance)); |
|
227 innerverts << v; |
|
228 innerverts2d << renderer()->coordconv3_2 (v); |
|
229 |
|
230 if (outerdistance != -1) |
|
231 { |
|
232 v.setCoordinate (relX, m_drawedVerts[0][relX] + (cosangle * outerdistance)); |
|
233 v.setCoordinate (relY, m_drawedVerts[0][relY] + (sinangle * outerdistance)); |
|
234 outerverts << v; |
|
235 outerverts2d << renderer()->coordconv3_2 (v); |
|
236 } |
|
237 } |
|
238 |
|
239 QVector<QLineF> lines (segments); |
|
240 |
|
241 if (outerdistance != -1 and outerdistance != innerdistance) |
|
242 { |
|
243 painter.setBrush (m_polybrush); |
|
244 painter.setPen (Qt::NoPen); |
|
245 |
|
246 // Compile polygons |
|
247 for (int i = 0; i < segments; ++i) |
|
248 { |
|
249 QVector<QPointF> points; |
|
250 points << innerverts2d[i] |
|
251 << innerverts2d[i + 1] |
|
252 << outerverts2d[i + 1] |
|
253 << outerverts2d[i]; |
|
254 painter.drawPolygon (QPolygonF (points)); |
|
255 lines << QLineF (innerverts2d[i], innerverts2d[i + 1]); |
|
256 lines << QLineF (outerverts2d[i], outerverts2d[i + 1]); |
|
257 } |
|
258 |
|
259 // Add bordering edges for unclosed rings/discs |
|
260 if (segments != divisions) |
|
261 { |
|
262 lines << QLineF (innerverts2d.first(), outerverts2d.first()); |
|
263 lines << QLineF (innerverts2d.last(), outerverts2d.last()); |
|
264 } |
|
265 } |
|
266 else |
|
267 { |
|
268 for (int i = 0; i < segments; ++i) |
|
269 lines << QLineF (innerverts2d[i], innerverts2d[i + 1]); |
|
270 } |
|
271 |
|
272 // Draw a green blips at where the points are |
|
273 for (QPointF const& point : innerverts2d + outerverts2d) |
|
274 renderer()->drawBlip (painter, point); |
|
275 |
|
276 // Draw edge lines |
|
277 painter.setPen (renderer()->linePen()); |
|
278 painter.drawLines (lines); |
|
279 |
|
280 // Draw the current radius in the middle of the circle. |
|
281 QPoint origin = renderer()->coordconv3_2 (m_drawedVerts[0]); |
|
282 QString label = QString::number (innerdistance); |
|
283 painter.setPen (renderer()->textPen()); |
|
284 painter.drawText (origin.x() - (metrics.width (label) / 2), origin.y(), label); |
|
285 |
|
286 if (m_drawedVerts.size() >= 2) |
|
287 { |
|
288 painter.drawText (origin.x() - (metrics.width (label) / 2), |
|
289 origin.y() + metrics.height(), QString::number (outerdistance)); |
|
290 } |
|
291 } |
|
292 |
|
293 bool CircleMode::mouseReleased (MouseEventData const& data) |
|
294 { |
|
295 if (Super::mouseReleased (data)) |
|
296 return true; |
|
297 |
|
298 if (data.releasedButtons & Qt::LeftButton) |
|
299 { |
|
300 if (m_drawedVerts.size() < 3) |
|
301 addDrawnVertex (renderer()->position3D()); |
|
302 else |
|
303 buildCircle(); |
|
304 |
|
305 return true; |
|
306 } |
|
307 |
|
308 return false; |
|
309 } |
|
310 |
|
311 bool CircleMode::preAddVertex (const Vertex&) |
|
312 { |
|
313 m_angleOffset = getAngleOffset(); |
|
314 return false; |
|
315 } |