src/editmodes/circleMode.cc

changeset 952
f116b63c4844
parent 950
5df69eb50182
child 953
8349552ee5e9
equal deleted inserted replaced
950:5df69eb50182 952:f116b63c4844
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 }

mercurial