--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/editmodes/circleMode.cpp Tue Mar 03 21:54:57 2015 +0200 @@ -0,0 +1,315 @@ +/* + * LDForge: LDraw parts authoring CAD + * Copyright (C) 2013 - 2015 Teemu Piippo + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <QPainter> +#include "circleMode.h" +#include "../miscallenous.h" +#include "../ldObject.h" +#include "../ldDocument.h" +#include "../ringFinder.h" +#include "../primitives.h" +#include "../glRenderer.h" +#include "../mainWindow.h" +#include "../ldObjectMath.h" + +CircleMode::CircleMode (GLRenderer* renderer) : + Super (renderer) {} + +EditModeType CircleMode::type() const +{ + return EditModeType::Circle; +} + +double CircleMode::getCircleDrawDist (int pos) const +{ + assert (m_drawedVerts.size() >= pos + 1); + Vertex v1 = (m_drawedVerts.size() >= pos + 2) ? m_drawedVerts[pos + 1] : + renderer()->coordconv2_3 (renderer()->mousePosition(), false); + Axis localx, localy; + renderer()->getRelativeAxes (localx, localy); + double dx = m_drawedVerts[0][localx] - v1[localx]; + double dy = m_drawedVerts[0][localy] - v1[localy]; + return Grid::Snap (sqrt ((dx * dx) + (dy * dy)), Grid::Coordinate); +} + +Matrix CircleMode::getCircleDrawMatrix (double scale) +{ + // Matrix templates. 2 is substituted with the scale value, 1 is inverted to -1 if needed. + static const Matrix templates[3] = + { + { 2, 0, 0, 0, 1, 0, 0, 0, 2 }, + { 2, 0, 0, 0, 0, 2, 0, 1, 0 }, + { 0, 1, 0, 2, 0, 0, 0, 0, 2 }, + }; + + Matrix transform = templates[renderer()->camera() % 3]; + + for (int i = 0; i < 9; ++i) + { + if (transform[i] == 2) + transform[i] = scale; + elif (transform[i] == 1 and renderer()->camera() >= 3) + transform[i] = -1; + } + + return transform; +} + +void CircleMode::buildCircle() +{ + LDObjectList objs; + const int segments (g_win->ringToolSegments()); + const int divisions (g_win->ringToolHiRes() ? HighResolution : LowResolution); + double dist0 (getCircleDrawDist (0)); + double dist1 (getCircleDrawDist (1)); + LDDocumentPtr refFile; + Matrix transform; + bool circleOrDisc = false; + + if (dist1 < dist0) + qSwap (dist0, dist1); + + if (dist0 == dist1) + { + // If the radii are the same, there's no ring space to fill. Use a circle. + refFile = GetPrimitive (::Circle, segments, divisions, 0); + transform = getCircleDrawMatrix (dist0); + circleOrDisc = true; + } + elif (dist0 == 0 or dist1 == 0) + { + // If either radii is 0, use a disc. + refFile = GetPrimitive (::Disc, segments, divisions, 0); + transform = getCircleDrawMatrix ((dist0 != 0) ? dist0 : dist1); + circleOrDisc = true; + } + elif (g_RingFinder.findRings (dist0, dist1)) + { + // The ring finder found a solution, use that. Add the component rings to the file. + for (const RingFinder::Component& cmp : g_RingFinder.bestSolution()->getComponents()) + { + refFile = GetPrimitive (::Ring, segments, divisions, cmp.num); + LDSubfilePtr ref = LDSpawn<LDSubfile>(); + ref->setFileInfo (refFile); + ref->setTransform (getCircleDrawMatrix (cmp.scale)); + ref->setPosition (m_drawedVerts[0]); + ref->setColor (MainColor()); + objs << ref; + } + } + else + { + // Ring finder failed, last resort: draw the ring with quads + QList<QLineF> c0, c1; + Axis localx, localy, localz; + renderer()->getRelativeAxes (localx, localy); + localz = (Axis) (3 - localx - localy); + double x0 (m_drawedVerts[0][localx]); + double y0 (m_drawedVerts[0][localy]); + + Vertex templ; + templ.setCoordinate (localx, x0); + templ.setCoordinate (localy, y0); + templ.setCoordinate (localz, renderer()->getDepthValue()); + + // Calculate circle coords + MakeCircle (segments, divisions, dist0, c0); + MakeCircle (segments, divisions, dist1, c1); + + for (int i = 0; i < segments; ++i) + { + Vertex v0, v1, v2, v3; + v0 = v1 = v2 = v3 = templ; + v0.setCoordinate (localx, v0[localx] + c0[i].x1()); + v0.setCoordinate (localy, v0[localy] + c0[i].y1()); + v1.setCoordinate (localx, v1[localx] + c0[i].x2()); + v1.setCoordinate (localy, v1[localy] + c0[i].y2()); + v2.setCoordinate (localx, v2[localx] + c1[i].x2()); + v2.setCoordinate (localy, v2[localy] + c1[i].y2()); + v3.setCoordinate (localx, v3[localx] + c1[i].x1()); + v3.setCoordinate (localy, v3[localy] + c1[i].y1()); + + LDQuadPtr quad (LDSpawn<LDQuad> (v0, v1, v2, v3)); + quad->setColor (MainColor()); + + // Ensure the quads always are BFC-front towards the camera + if (renderer()->camera() % 3 <= 0) + quad->invert(); + + objs << quad; + } + } + + if (circleOrDisc and refFile != null) + { + LDSubfilePtr ref = LDSpawn<LDSubfile>(); + ref->setFileInfo (refFile); + ref->setTransform (transform); + ref->setPosition (m_drawedVerts[0]); + ref->setColor (MainColor()); + objs << ref; + } + + unless (objs.isEmpty()) + { + Axis relZ = renderer()->getRelativeZ();; + const int l (relZ == X ? 1 : 0); + const int m (relZ == Y ? 1 : 0); + const int n (relZ == Z ? 1 : 0); + RotateObjects (l, m, n, -m_angleOffset, objs); + } + + finishDraw (objs); +} + +double CircleMode::getAngleOffset() const +{ + if (m_drawedVerts.isEmpty()) + return 0.0; + + const int divisions (g_win->ringToolHiRes() ? HighResolution : LowResolution); + QPointF originspot (renderer()->coordconv3_2 (m_drawedVerts.first())); + QLineF bearing (originspot, renderer()->mousePositionF()); + QLineF bearing2 (originspot, QPointF (originspot.x(), 0.0)); + double angleoffset (-bearing.angleTo (bearing2) + 90); + angleoffset /= (360.0 / divisions); // convert angle to 0-16 scale + angleoffset = round (angleoffset); // round to nearest 16th + angleoffset *= ((2 * Pi) / divisions); // convert to radians + angleoffset *= renderer()->depthNegateFactor(); // negate based on camera + return angleoffset; +} + +void CircleMode::render (QPainter& painter) const +{ + QFontMetrics metrics = QFontMetrics (QFont()); + + // If we have not specified the center point of the circle yet, preview it on the screen. + if (m_drawedVerts.isEmpty()) + { + renderer()->drawBlip (painter, renderer()->coordconv3_2 (renderer()->position3D())); + return; + } + + QVector<Vertex> innerverts, outerverts; + QVector<QPointF> innerverts2d, outerverts2d; + const double innerdistance (getCircleDrawDist (0)); + const double outerdistance (m_drawedVerts.size() >= 2 ? getCircleDrawDist (1) : -1); + const int divisions (g_win->ringToolHiRes() ? HighResolution : LowResolution); + const int segments (g_win->ringToolSegments()); + const double angleUnit (2 * Pi / divisions); + Axis relX, relY; + renderer()->getRelativeAxes (relX, relY); + const double angleoffset (m_drawedVerts.size() < 3 ? getAngleOffset() : m_angleOffset); + + // Calculate the preview positions of vertices + for (int i = 0; i < segments + 1; ++i) + { + const double sinangle (sin (angleoffset + i * angleUnit)); + const double cosangle (cos (angleoffset + i * angleUnit)); + Vertex v (Origin); + v.setCoordinate (relX, m_drawedVerts[0][relX] + (cosangle * innerdistance)); + v.setCoordinate (relY, m_drawedVerts[0][relY] + (sinangle * innerdistance)); + innerverts << v; + innerverts2d << renderer()->coordconv3_2 (v); + + if (outerdistance != -1) + { + v.setCoordinate (relX, m_drawedVerts[0][relX] + (cosangle * outerdistance)); + v.setCoordinate (relY, m_drawedVerts[0][relY] + (sinangle * outerdistance)); + outerverts << v; + outerverts2d << renderer()->coordconv3_2 (v); + } + } + + QVector<QLineF> lines (segments); + + if (outerdistance != -1 and outerdistance != innerdistance) + { + painter.setBrush (m_polybrush); + painter.setPen (Qt::NoPen); + + // Compile polygons + for (int i = 0; i < segments; ++i) + { + QVector<QPointF> points; + points << innerverts2d[i] + << innerverts2d[i + 1] + << outerverts2d[i + 1] + << outerverts2d[i]; + painter.drawPolygon (QPolygonF (points)); + lines << QLineF (innerverts2d[i], innerverts2d[i + 1]); + lines << QLineF (outerverts2d[i], outerverts2d[i + 1]); + } + + // Add bordering edges for unclosed rings/discs + if (segments != divisions) + { + lines << QLineF (innerverts2d.first(), outerverts2d.first()); + lines << QLineF (innerverts2d.last(), outerverts2d.last()); + } + } + else + { + for (int i = 0; i < segments; ++i) + lines << QLineF (innerverts2d[i], innerverts2d[i + 1]); + } + + // Draw a green blips at where the points are + for (QPointF const& point : innerverts2d + outerverts2d) + renderer()->drawBlip (painter, point); + + // Draw edge lines + painter.setPen (renderer()->linePen()); + painter.drawLines (lines); + + // Draw the current radius in the middle of the circle. + QPoint origin = renderer()->coordconv3_2 (m_drawedVerts[0]); + QString label = QString::number (innerdistance); + painter.setPen (renderer()->textPen()); + painter.drawText (origin.x() - (metrics.width (label) / 2), origin.y(), label); + + if (m_drawedVerts.size() >= 2) + { + painter.drawText (origin.x() - (metrics.width (label) / 2), + origin.y() + metrics.height(), QString::number (outerdistance)); + } +} + +bool CircleMode::mouseReleased (MouseEventData const& data) +{ + if (Super::mouseReleased (data)) + return true; + + if (data.releasedButtons & Qt::LeftButton) + { + if (m_drawedVerts.size() < 3) + addDrawnVertex (renderer()->position3D()); + else + buildCircle(); + + return true; + } + + return false; +} + +bool CircleMode::preAddVertex (const Vertex&) +{ + m_angleOffset = getAngleOffset(); + return false; +}