/** * Copyright (c) 2011-2013 by Andrew Mustun. All rights reserved. * * This file is part of the QCAD project. * * QCAD 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. * * QCAD 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 QCAD. */ /** * \class ShapeAlgorithms * Various shape based algorithms. */ function ShapeAlgorithms() { } /** * \return Array with only the circle shapes from the given shapes. */ ShapeAlgorithms.getCircleShapes = function(shapes) { if (isNull(shapes)) { return undefined; } var ret = []; for (var i=0; idist2) { var angle2 = Math.asin(dist2/dist1); var angt1 = angle1 + angle2 + Math.PI/2.0; var angt2 = angle1 - angle2 - Math.PI/2.0; offs1 = new RVector(); offs2 = new RVector(); offs1.setPolar(circleRadius1, angt1); offs2.setPolar(circleRadius2, angt1); tangents.push(new RLine(circleCenter1.operator_add(offs1), circleCenter2.operator_add(offs2))); offs1.setPolar(circleRadius1, angt2); offs2.setPolar(circleRadius2, angt2); tangents.push(new RLine(circleCenter1.operator_add(offs1), circleCenter2.operator_add(offs2))); } else { tangents.push(undefined); tangents.push(undefined); } // inner tangents: var dist3 = circleRadius2 + circleRadius1; if (dist1>dist3) { var angle3 = Math.asin(dist3/dist1); var angt3 = angle1 + angle3 + Math.PI/2.0; var angt4 = angle1 - angle3 - Math.PI/2.0; offs1 = new RVector(); offs2 = new RVector(); offs1.setPolar(circleRadius1, angt3); offs2.setPolar(circleRadius2, angt3); tangents.push(new RLine(circleCenter1.operator_subtract(offs1), circleCenter2.operator_add(offs2))); offs1.setPolar(circleRadius1, angt4); offs2.setPolar(circleRadius2, angt4); tangents.push(new RLine(circleCenter1.operator_subtract(offs1), circleCenter2.operator_add(offs2))); } else { tangents.push(undefined); tangents.push(undefined); } return tangents; }; /** * \return Line that is orthogonal to line and tangential to circle. */ ShapeAlgorithms.getOrthogonalTangents = function(line, circle) { var ret = []; var auxLine1, auxLine2; var ips, ips1, ips2; var lineAngle = line.getAngle(); if (isCircleShape(circle) || isArcShape(circle)) { // line parallel to line through center of circle: auxLine1 = new RLine(circle.getCenter(), lineAngle, 100.0); // intersections of parallel with circle: ips1 = circle.getIntersectionPoints(auxLine1, false); for (var i=0; i=1 && ips2.length>=1) { if (ips1[0].equalsFuzzy(ips2[0])) { pointOfContact1 = ips1[0]; } else { auxLine1 = new RLine(ips1[0], ips2[0]); ips = circle.getIntersectionPoints(auxLine1, false); if (ips.length>=1) { pointOfContact1 = ips[0]; } } } if (ips1.length>=2 && ips2.length>=2) { if (ips1[1].equalsFuzzy(ips2[1])) { pointOfContact2 = ips1[1]; } else { auxLine2 = new RLine(ips1[1], ips2[1]); ips = circle.getIntersectionPoints(auxLine2, false); if (ips.length>=1) { pointOfContact2 = ips[0]; } } } if (!isNull(pointOfContact1)) { var pointOnLine1 = line.getClosestPointOnShape(pointOfContact1, false); ret.push(new RLine(pointOfContact1, pointOnLine1)); } if (!isNull(pointOfContact2)) { var pointOnLine2 = line.getClosestPointOnShape(pointOfContact2, false); ret.push(new RLine(pointOfContact2, pointOnLine2)); } } return ret; }; /** * \return Parallels to this shape. * \param distance Distance of first parallel or concentric arc or circle. * \param number Number of offset shapes to generate. * \param sidePosition RVector indicating what side of the shape the parallels * should be or RS.LeftHand or RS.RightHand or RS.BothSides. */ ShapeAlgorithms.getOffsetShapes = function(shape, distance, number, sidePosition) { ShapeAlgorithms.error = undefined; var ret = []; var i, n; if (isLineBasedShape(shape)) { var sides = []; if (isVector(sidePosition)) { sides.push(shape.getSideOfPoint(sidePosition)); } else { if (sidePosition===RS.BothSides) { sides.push(RS.LeftHand); sides.push(RS.RightHand); } else { sides.push(sidePosition); } } for (i=0; i1) { cutPos1 = res[0]; cutPos2 = res[1]; } return ShapeAlgorithms.autoTrimManual(shape, cutPos1, cutPos2, position); }; ShapeAlgorithms.autoTrimManual = function(shape, cutPos1, cutPos2, position) { if (!isCircleShape(shape) && !isFullEllipseShape(shape) && !isXLineShape(shape) && !isRayShape(shape)) { if (!isValidVector(cutPos1) || !isValidVector(cutPos2)) { // most shapes requires two intersection points: return undefined; } } var rest1 = undefined; var rest2 = undefined; var segment = undefined; // lines: if (isLineShape(shape)) { rest1 = shape.clone(); rest2 = shape.clone(); if (shape.getStartPoint().getDistanceTo(cutPos1) < shape.getStartPoint().getDistanceTo(cutPos2)) { rest1.trimEndPoint(cutPos1); rest2.trimStartPoint(cutPos2); } else { rest1.trimEndPoint(cutPos2); rest2.trimStartPoint(cutPos1); } segment = shape.clone(); segment.setStartPoint(cutPos1); segment.setEndPoint(cutPos2); if (rest1.getLength() shape.getAngleLength()) { rest1.trimEndPoint(cutPos2); rest2.trimStartPoint(cutPos1); segment.trimStartPoint(cutPos2); segment.trimEndPoint(cutPos1); angleLength1 = rest1.getAngleLength(true); angleLength2 = rest2.getAngleLength(true); } if (angleLength1<1.0e-5) { rest1 = undefined; } if (angleLength2<1.0e-5) { rest2 = undefined; } } // circles: else if (isCircleShape(shape)) { if (!isValidVector(cutPos1) || !isValidVector(cutPos2)) { rest1 = undefined; rest2 = undefined; } else { var angle1 = shape.getCenter().getAngleTo(cutPos1); var angle2 = shape.getCenter().getAngleTo(cutPos2); rest1 = new RArc( shape.getCenter(), shape.getRadius(), angle1, angle2, false); rest2 = undefined; segment = new RArc( shape.getCenter(), shape.getRadius(), angle2, angle1, false); if (!isNull(position)) { var cursorAngle = shape.getCenter().getAngleTo(position); if (RMath.isAngleBetween(cursorAngle, angle1, angle2, false)) { rest1.setStartAngle(angle2); rest1.setEndAngle(angle1); segment.setStartAngle(angle1); segment.setEndAngle(angle2); } } var angleLength1 = rest1.getAngleLength(true); if (angleLength1distRight) { cutPos1 = inters; distRight = dist; } if (isNull(distLeft) || dist0.0) { if (isNull(distRight) || distRS.PointTolerance) { // does the segment start on ellipse arc? var startInside = RMath.isAngleBetween(arc.getStartPoint().getAngle(), limitAngle1, limitAngle2, ellipse.isReversed()); // does the segment end on ellipse arc? var endInside = RMath.isAngleBetween(arc.getEndPoint().getAngle(), limitAngle1, limitAngle2+RS.AngleTolerance, ellipse.isReversed()); if (adding && startInside && endInside && RMath.isAngleBetween(limitAngle2, arc.getStartPoint().getAngle(), arc.getEndPoint().getAngle(), false)) { endInside = false; } if (!adding && startInside && endInside && RMath.isAngleBetween(limitAngle1, arc.getStartPoint().getAngle(), arc.getEndPoint().getAngle(), false)) { startInside = false; } // arc covers part of the ellipse arc: if (startInside && endInside) { // segment starts exactly at start of ellipse arc: if (Math.abs(arc.getStartPoint().getAngle()-limitAngle1)0) { arc.trimStartPoint(normalizedStartPoint); arc.trimEndPoint(normalizedEndPoint); polyline.appendVertex(arc.getStartPoint(), arc.getBulge()); done = true; } } } } else { polyline.appendVertex(arc.getStartPoint(), arc.getBulge()); } arc = undefined; } quadrant++; if (quadrant>4) { if (ellipse.isFullEllipse()) { done = true; } quadrant=1; } } while(!done && counter<10); // close polyline if (!normalizedEndPoint.equalsFuzzy(polyline.getEndPoint())) { if (ellipse.isFullEllipse()) { polyline.setClosed(true); } else { polyline.appendVertex(normalizedEndPoint); } } // transform polyline to real position of ellipse: polyline.rotate(ellipse.getAngle()); polyline.move(ellipse.getCenter()); if (rev) { polyline.reverse(); } return polyline; }; ShapeAlgorithms.getCompleteQuadrilateralSegments = function(line1, line2, line3, line4) { var ret = []; // maps vertices to number of intersection points at that vertex: var vertices = new Map(function(v1, v2) { return v1.equalsFuzzy(v2); }); var lines = ShapeAlgorithms.removeSharedPointer([ line1, line2, line3, line4 ]); var i, k; for (i=0; i0; order--) { vs[order] = []; var startIndex = 0; var keys = vertices.getKeys(); var values = vertices.getValues(); do { i = values.indexOf(order, startIndex); startIndex = i+1; if (i!==-1) { vs[order].push(keys[i]); } } while (i!==-1); } // qDebug("vs[4]: ", vs[4]); // qDebug("vs[3]: ", vs[3]); // qDebug("vs[2]: ", vs[2]); // qDebug("vs[1]: ", vs[1]); var vert = []; // quadrilateral: if (vs[4].length===1 && vs[3].length===2 && vs[2].length===3) { vert = [vs[3][0], vs[4][0], vs[3][1]]; var l1 = new RLine(vs[3][0], vs[4][0]); var l2 = new RLine(vs[4][0], vs[3][1]); // ret.push(new RLine(vs[3][0], vs[4][0])); // ret.push(new RLine(vs[4][0], vs[3][1])); for (i=0; i100 && Math.abs(y0)>100 && // Math.abs(x1)>100 && Math.abs(y1)>100 && // Math.abs(x1)>100 && Math.abs(y1)>100) { // x0 = x0/100; // y0 = y0/100; // x1 = x1/100; // y1 = y1/100; // x2 = x2/100; // y2 = y2/100; // x3 = x3/100; // y3 = y3/100; // norm = true; // } var ma = x1 * x2 * y3 - x0 * x2 * y3 - x1 * y2 * x3 + x0 * y2 * x3 - x0 * y1 * x3 + y0 * x1 * x3 + x0 * y1 * x2 - y0 * x1 * x2; var mb = x0 * x2 * y3 - x0 * x1 * y3 - x1 * y2 * x3 + y1 * x2 * x3 - y0 * x2 * x3 + y0 * x1 * x3 + x0 * x1 * y2 - x0 * y1 * x2; var mc = x1 * x2 * y3 - x0 * x1 * y3 - x0 * y2 * x3 - y1 * x2 * x3 + y0 * x2 * x3 + x0 * y1 * x3 + x0 * x1 * y2 - y0 * x1 * x2; var md = y1 * x2 * y3 - y0 * x2 * y3 - x0 * y1 * y3 + y0 * x1 * y3 - y1 * y2 * x3 + y0 * y2 * x3 + x0 * y1 * y2 - y0 * x1 * y2; var me = -x1 * y2 * y3 + x0 * y2 * y3 + y1 * x2 * y3 - x0 * y1 * y3 - y0 * y2 * x3 + y0 * y1 * x3 + y0 * x1 * y2 - y0 * y1 * x2; var mf = x1 * y2 * y3 - x0 * y2 * y3 + y0 * x2 * y3 - y0 * x1 * y3 - y1 * y2 * x3 + y0 * y1 * x3 + x0 * y1 * y2 - y0 * y1 * x2; var mg = x1 * y3 - x0 * y3 - y1 * x3 + y0 * x3 - x1 * y2 + x0 * y2 + y1 * x2 - y0 * x2; var mh = x2 * y3 - x1 * y3 - y2 * x3 + y1 * x3 + x0 * y2 - y0 * x2 - x0 * y1 + y0 * x1; var mi = x2 * y3 - x0 * y3 - y2 * x3 + y0 * x3 + x1 * y2 - y1 * x2 + x0 * y1 - y0 * x1; var T = RMatrix.create3x3( ma, mb, mc, md, me, mf, mg, mh, mi ); var TI = T.getInverse(); var mj = TI.get(0, 0); var mk = TI.get(0, 1); var ml = TI.get(0, 2); var mm = TI.get(1, 0); var mn = TI.get(1, 1); var mo = TI.get(1, 2); var mp = TI.get(2, 0); var mq = TI.get(2, 1); var mr = TI.get(2, 2); var a = mj*mj + mm*mm - mp*mp; var b = mj*mk + mm*mn - mp*mq; var c = mk*mk + mn*mn - mq*mq; var d = mj*ml + mm*mo - mp*mr; var f = mk*ml + mn*mo - mq*mr; var g = ml*ml + mo*mo - mr*mr; var term = (b*b - a*c); var cx, cy, axis1, axis2; // circle: if (Math.abs(term)<1e-50) { cx = (v1.x + v3.x)/2; cy = (v1.y + v3.y)/2; axis1 = v1.getDistanceTo(v2)/2; axis2 = axis1; } else { cx = (c*d - b*f) / term; cy = (a*f - b*d) / term; var term1 = (2 * (a*f*f + c*d*d + g*b*b - 2*b*d*f - a*c*g)); var term2 = Math.sqrt((a-c)*(a-c) + 4*b*b); var term3 = (a + c); axis1 = Math.sqrt(term1 / (term * (term2-term3))); axis2 = Math.sqrt(term1 / (term * (-term2-term3))); } var center = new RVector(cx, cy); var angle; if (b===0.0) { angle = 0; } else { var arccot = function(v) { return Math.PI/2 - Math.atan(v); }; angle = 0.5 * arccot((a-c)/(2*b)); } if (a>c) { angle += Math.PI/2; } var majorPoint = RVector.createPolar(axis1, angle); var ellipse = new REllipse(center, majorPoint, axis2/axis1, 0.0, 2*Math.PI, false); // workaround if the formula above does not produce the correct angle // (90 degrees off): for (i=0; i<4; i++) { var ips = ellipse.getIntersectionPoints(edges[i], false); if (ips.length===2) { if (!ips[0].equalsFuzzy(ips[1], 0.001)) { ellipse.rotate(Math.PI/2, ellipse.getCenter()); break; } } } return ellipse; }; /** * Tries to convert the given spline into one line or arc. * \return RArc, RLine or the original RSpline. */ ShapeAlgorithms.splineToLineOrArc = function(spline, tolerance) { var startPoint = spline.getStartPoint(); var endPoint = spline.getEndPoint(); var middlePoint = spline.getMiddlePoint(); var i, point; var arc = RArc.createFrom3Points(startPoint, middlePoint, endPoint); if (arc.isValid()) { var splineIsArc = true; for (i=0.0; i<1.0; i+=0.1) { point = spline.getPointAt(i); if (!arc.isOnShape(point, true, tolerance)) { splineIsArc = false; break; } } if (splineIsArc && arc.getRadius()>1000.0) { return new RLine(startPoint, endPoint); } if (splineIsArc) { return arc; } } var line = new RLine(startPoint, endPoint); var splineIsLine = true; for (i=0.0; i<1.0; i+=0.1) { point = spline.getPointAt(i); if (!line.isOnShape(point, true, tolerance)) { splineIsLine = false; break; } } if (splineIsLine) { return line; } return spline; }; /** * Converts the given circle into an arc with the given start angle or 0. */ ShapeAlgorithms.circleToArc = function(circle, startAngle) { if (isNull(startAngle)) { startAngle = 0.0; } return new RArc(circle.getCenter(), circle.getRadius(), startAngle, startAngle + 2*Math.PI, false); }; /** * Converts the given line or arc into a polyline with numSegments segments. */ ShapeAlgorithms.lineOrArcToPolyline = function(shape, numSegments) { var ret = new RPolyline(); var l = shape.getLength(); var cursor; for (var i=0; i<=numSegments; i++) { if (i===0) { cursor = shape.getStartPoint(); } else if (i===numSegments) { cursor = shape.getEndPoint(); } else { cursor = shape.getPointsWithDistanceToEnd(l/numSegments*i, RS.FromStart)[0]; } ret.appendVertex(cursor); } return ret; }; ShapeAlgorithms.removeSharedPointer = function(shape) { if (isArray(shape)) { var ret = []; for (var i=0; i