/** * Fit a circle to three given points on the circumference. The method draws lines (chords) * between two pairs of the points, constructs their perpendicular bisectors, and finds the * intersection point of those two lines. This is the centre of the circle as radii passing * through the centre are perpendicular bisectors of chords in a circle. The radius is the * distance between the centre and any one of the initial three points. */ private QDCircle threePointCircleFit(List <QDPoint> pts) { QDLine l1 = new QDLine(pts[0], pts[1]); QDLine l2 = new QDLine(pts[1], pts[2]); QDInfiniteLine i1 = new QDInfiniteLine(l1.getMidpoint(), l1.getPerpAngleD()); QDInfiniteLine i2 = new QDInfiniteLine(l2.getMidpoint(), l2.getPerpAngleD()); QDPoint centre = i1.intersect(i2); float radius = QDUtils.QDUtils.getPtToPtDist(centre, pts[0]); return(new QDCircle(centre, radius)); }
// Copy constructor public QDInputPointSet(QDInputPointSet other) { this.pts = other.pts; this.anglesD = other.anglesD; this.meanSmoothedAnglesD = other.meanSmoothedAnglesD; this.start = other.start; //this.finish = other.finish; this.angle_max = other.angle_max; this.angle_min = other.angle_min; this.angle_sum = other.angle_sum; }
public void getIntrinsicConstraints(QDInputPointSet ptSet) { if (ptSet.shapeType == QDShapeTypes.LINE) { QDLine line = (QDLine)ptSet.initialFit; float angle = line.angleD; // Check for vertical line and adjust by midpoint pivot if ((90.0f - line_snap_thresh < angle && angle < 90.0f + line_snap_thresh) || (-90.0f - line_snap_thresh < angle && angle < -90.0f + line_snap_thresh) && !line.vertical) { QDPoint newStart, newFinish; if (line.start.y > line.getMidpoint().y) { newStart = new QDPoint(line.midPoint.x, line.midPoint.y + (0.5f * line.length)); newFinish = new QDPoint(line.midPoint.x, line.midPoint.y - (0.5f * line.length)); } else { newStart = new QDPoint(line.midPoint.x, line.midPoint.y - (0.5f * line.length)); newFinish = new QDPoint(line.midPoint.x, line.midPoint.y + (0.5f * line.length)); } ptSet.fittedShape = new QDLine(newStart, newFinish); ptSet.constraints.Add(QDConstraintTypes.VERTICAL_LINE); } // Check for horizontal line and adjust by midpoint pivot else if ((-line_snap_thresh < angle && angle < line_snap_thresh) || (180.0f - line_snap_thresh < angle || angle < -180.0f + line_snap_thresh)) { float length = (float)Math.Sqrt(Math.Pow(line.start.x - line.finish.x, 2.0f) + Math.Pow(line.start.y - line.finish.y, 2.0f)); QDPoint midPt = new QDPoint((line.start.x + line.finish.x) * 0.5f, (line.start.y + line.finish.y) * 0.5f); QDPoint newStart = new QDPoint(); QDPoint newFinish = new QDPoint(); newStart.y = newFinish.y = midPt.y; if (line.start.x > midPt.x) { newStart.x = midPt.x + (0.5f * length); newFinish.x = midPt.x - (0.5f * length); } else { newStart.x = midPt.x - (0.5f * length); newFinish.x = midPt.x + (0.5f * length); } ptSet.fittedShape = new QDLine(newStart, newFinish); ptSet.constraints.Add(QDConstraintTypes.HORIZONTAL_LINE); } } }
/** * Find angle swept by points around the centre of an elliptical arc. Used to determine * Whether it should be treated as a full ellipse or an arc */ private float findSweptArc(QDInputPointSet ptSet, QDEllipse ellipse) { QDPoint centre = ellipse.mCentre; List <QDPoint> pts = ptSet.pts; float lastAngle = QDUtils.QDUtils.getPtToPtAngleD(pts[0], centre); float angleSweep = 0f; foreach (QDPoint pt in pts) { float angle = QDUtils.QDUtils.getPtToPtAngleD(pt, centre); angleSweep += QDUtils.QDUtils.angleDiffMinorD(angle, lastAngle); lastAngle = angle; } return(angleSweep); }
/** * Returns the signed (CCW positive) angle from the start point to the finish point * Range of output values: (-180,180] * @param start Point to start from * @param finish Point to finish at * @return Signed angle from start to finish point */ public static float getPtToPtAngleD(QDPoint start, QDPoint finish) { float angle; float run = finish.x - start.x; float rise = finish.y - start.y; // Check for near-vertical div-by-zero issues if (Math.Abs(run) < 1e-2f) { if (finish.y > start.y) { angle = 90.0f; } else { angle = -90.0f; } } else { angle = ((float)Math.Atan2(rise, run)) * 180.0f / ((float)Math.PI); } return(angle); }
// Add a point to the segment list and check for a corner in the segment // Returns true if a corner was detected, false otherwise public bool addPoint(QDPoint pt) { // Special case for the first point if (pts.Count == 0) { start = pt; pts.Add(pt); sampledMarkerPt = pt; return(false); } // Get the pt to pt gradient float angle = QDUtils.QDUtils.getPtToPtAngleD(pts.Last(), pt); // Store summary statistics if (angle > angle_max) { angle_max = angle; } if (angle < angle_min) { angle_min = angle; } angle_sum += angle; // Only stores the angles between points sufficiently far apart. This is the most // noise robust, and useful for getting the general form of the input shape if (QDUtils.QDUtils.getPtToPtDist(sampledMarkerPt, pt) > SUBSAMP_PT_DIST) { sampledPtAnglesD.Add(QDUtils.QDUtils.getPtToPtAngleD(sampledMarkerPt, pt)); sampledMarkerPt = pt; } // Add values to vectors anglesD.Add(angle); pts.Add(pt); // Step 1: track back to get testPt float TEST_RADIUS = 50.0f; float CNR_RADIUS = 10.0f; QDPoint testPt = null, cnrCandidatePt = null, currPt = pt; int midIdx = -1, testIdx = -1, cnrCandidateIdx = -1; midIdx = backtrackByDist(pts, pts.Count - 2, TEST_RADIUS / 2.0f); if (midIdx < 0) { return(false); } testIdx = backtrackByDist(pts, midIdx, TEST_RADIUS / 2.0f); if (testIdx < 0) { return(false); } testPt = pts[testIdx]; // Step 2: construct line from testPt to currPt QDInfiniteLine testLine = new QDInfiniteLine(testPt, currPt); // Step 3: Find perp dist from each pt between test and curr to the line float maxDist = 0.0f; int maxIdx = -1; for (int i = testIdx + 1; i < pts.Count - 1; i++) { float thisDist = testLine.getDistToPoint(pts[i]); if (thisDist > maxDist) { maxDist = thisDist; maxIdx = i; } } // Step 4: confirm cnrCandidatePt if (maxIdx < 0) { return(false); } cnrCandidatePt = pts[maxIdx]; int cnrIdx = maxIdx; if (!(QDUtils.QDUtils.getPtToPtDist(cnrCandidatePt, currPt) > CNR_RADIUS)) { return(false); } // Step 5: construct lines float oldAngleD = new QDLine(testPt, cnrCandidatePt).angleD; float newAngleD = new QDLine(cnrCandidatePt, currPt).angleD; // Step 6: test angle between lines if (Math.Abs(QDUtils.QDUtils.angleDiffMinorD(oldAngleD, newAngleD)) > CNR_ANGLE_THRESH) { // Step 7: test for continuous curvature int strTestIdx = backtrackByDist(pts, testIdx, CNR_RADIUS); if (strTestIdx < 0) { return(false); } int strTest2Idx = backtrackByDist(pts, strTestIdx, CNR_RADIUS); if (strTest2Idx < 0) { return(false); } float seg1 = new QDLine(pts[strTest2Idx], pts[strTestIdx]).angleD; float seg2 = new QDLine(pts[strTestIdx], pts[testIdx]).angleD; if (Math.Abs(QDUtils.QDUtils.angleDiffMinorD(seg1, seg2)) < 30.0f) { cornerTerminated = true; } else { cornerTerminated = false; } } if (cornerTerminated) { newSeg = new QDInputPointSet(CORNER_WINDOW, CNR_ANGLE_THRESH); newSeg.addPoint(cnrCandidatePt); // Add the corner point int lastIdx = pts.Count - 1; for (int i = 0; i < lastIdx - cnrIdx; i++) // For how many points there are after the corner { newSeg.addPoint(this.pts[cnrIdx + 1]); // Copy points after the corner into new seg this.pts.RemoveAt(cnrIdx + 1); // Delete points after corner from current set this.anglesD.Remove(cnrIdx); // There is one less angle than pts since first two pts make 1 angle } // Remove subsampled angles as well up to before the corner int deleteSubSamp = (int)Math.Ceiling(CNR_RADIUS / SUBSAMP_PT_DIST); for (int i = 0; i < deleteSubSamp; i++) { sampledPtAnglesD.Remove(sampledPtAnglesD.Count - deleteSubSamp + i); } return(true); } return(false); }
public List <QDShape> getExtrinsicConstraints(QDInputPointSet ptSet) { List <QDShape> modifiedShapes = new List <QDShape>(); if (ptSet.shapeType == QDShapeTypes.LINE) { QDLine line = (QDLine)ptSet.fittedShape; List <QDShapeDBPoint> nearStartPts = shapeDB.getShapesNearPoint(line.start, SEARCH_RADIUS); foreach (QDShapeDBPoint pt in nearStartPts) { if (pt.type == QDPointTypes.LINE_START || pt.type == QDPointTypes.LINE_FINISH) { QDLine nearLine = (QDLine)pt.shape; if (!modifiedShapes.Contains(nearLine)) { modifiedShapes.Add(nearLine); } QDInfiniteLine thisInf = new QDInfiniteLine(line.start, line.finish); QDInfiniteLine nearInf = new QDInfiniteLine(nearLine.start, nearLine.finish); // Check for nearly parallel lines if (Math.Abs(QDUtils.QDUtils.angleDiffMinorD(line.angleD, nearLine.angleD)) > 10f) { QDPoint intersectPt = thisInf.intersect(nearInf); if (QDUtils.QDUtils.getPtToPtDist(line.start, intersectPt) < SEARCH_RADIUS * 1.25) { line.start = intersectPt; if (pt.type == QDPointTypes.LINE_START) { nearLine.start = intersectPt; } else { nearLine.finish = intersectPt; } } } } } /** * GOTTA BE A BETTER WAY OF DOING THIS RATHER THAN DOING IT ONCE FOR START AND ONCE FOR FINISH */ List <QDShapeDBPoint> nearFinishPts = shapeDB.getShapesNearPoint(line.finish, SEARCH_RADIUS); foreach (QDShapeDBPoint pt in nearFinishPts) { if (pt.type == QDPointTypes.LINE_START || pt.type == QDPointTypes.LINE_FINISH) { QDLine nearLine = (QDLine)pt.shape; if (!modifiedShapes.Contains(nearLine)) { modifiedShapes.Add(nearLine); } QDInfiniteLine thisInf = new QDInfiniteLine(line.start, line.finish); QDInfiniteLine nearInf = new QDInfiniteLine(nearLine.start, nearLine.finish); // Check for nearly parallel lines if (Math.Abs(QDUtils.QDUtils.angleDiffMinorD(line.angleD, nearLine.angleD)) > 10f) { QDPoint intersectPt = thisInf.intersect(nearInf); if (QDUtils.QDUtils.getPtToPtDist(line.finish, intersectPt) < SEARCH_RADIUS * 1.25) { line.finish = intersectPt; if (pt.type == QDPointTypes.LINE_START) { nearLine.start = intersectPt; } else { nearLine.finish = intersectPt; } } } } } } return(modifiedShapes); }
/** * Convert a point from local coordinates into the global reference viewing frame using the * origin of the view and the current scale factor * @param locPt The point in local coordinates to convert * @param SF The current scale factor of the viewing window * @param origin The origin of the viewing window * @return The point in local coordinates */ public static QDPoint locToAbsCoords(QDPoint locPt, float SF, QDPoint origin) { return(new QDPoint((locPt.x / SF) + origin.x, (locPt.y / SF) + origin.y)); }
/** * Convert a point from absolute coordinates into the local viewing frame using the origin of * the view and the current scale factor * @param absPt The point in absolute coordinates to convert * @param SF The current scale factor of the viewing window * @param origin The origin of the viewing window * @return The point in local coordinates */ public static QDPoint absToLocCoords(QDPoint absPt, float SF, QDPoint origin) { return(new QDPoint((absPt.x - origin.x) * SF, (absPt.y - origin.y) * SF)); }
/** * Get the distance between two points * @param p1 Point 1 * @param p2 Point 2 * @return Straight line distance between points */ public static float getPtToPtDist(QDPoint p1, QDPoint p2) { return((float)Math.Pow(Math.Pow(p1.x - p2.x, 2.0) + Math.Pow(p1.y - p2.y, 2), 0.5)); }