/**
         * Evaluates the direction of an arc as being either CW or CCW
         */
        private SweepDirection getDirection(QDInputPointSet pointSet)
        {
            float angleDiffSum = 0f;

            if (pointSet.pts.Count >= 2)
            {
                for (int i = 1; i < pointSet.anglesD.Count; i++)
                {
                    float diff = QDUtils.QDUtils.angleDiffMinorD(pointSet.anglesD[i - 1], pointSet.anglesD[i]);
                    if (Math.Abs(diff) < 30.0f)     // Reject significant pt-to-pt angle changes as outliers
                    {
                        angleDiffSum += diff;
                    }
                }
                if (angleDiffSum > 0)
                {
                    return(SweepDirection.Counterclockwise);
                }
                else
                {
                    return(SweepDirection.Clockwise);
                }
            }
            else
            {
                return(SweepDirection.Clockwise);
            }
        }
        /**
         * Finds the beginning angle of an elliptical arc given its direction, searching points near
         * the first drawn point in case of small backtracks
         */
        private float findArcEndD(SweepDirection direction, QDInputPointSet ptSet, QDEllipse ellipse)
        {
            float finishAngle = QDUtils.QDUtils.getPtToPtAngleD(ellipse.mCentre, ptSet.pts.Last());

            if (direction == SweepDirection.Clockwise)
            {
                // Check for any points near the first point more CCW than the current to set as start
                for (int i = ptSet.pts.Count - 2; i > Math.Floor(ptSet.pts.Count * 0.75f); i--)
                {
                    float thisAngle = QDUtils.QDUtils.getPtToPtAngleD(ellipse.mCentre, ptSet.pts[i]);
                    if (QDUtils.QDUtils.angleDiffMinorD(finishAngle, thisAngle) < 0f)
                    {
                        finishAngle = thisAngle;
                    }
                }
            }
            else if (direction == SweepDirection.Counterclockwise)
            {     // CCW arc
                for (int i = ptSet.pts.Count - 2; i > Math.Floor(ptSet.pts.Count * 0.75f); i--)
                {
                    float thisAngle = QDUtils.QDUtils.getPtToPtAngleD(ellipse.mCentre, ptSet.pts[i]);
                    if (QDUtils.QDUtils.angleDiffMinorD(finishAngle, thisAngle) > 0f)
                    {
                        finishAngle = thisAngle;
                    }
                }
            }
            return(finishAngle);
        }
        /**
         * Finds the final angle of an elliptical arc given its direction, searching points near
         * the last drawn point in case of small backtracks
         */
        private float findArcStartD(SweepDirection direction, QDInputPointSet ptSet, QDEllipse ellipse)
        {
            float startAngle = QDUtils.QDUtils.getPtToPtAngleD(ellipse.mCentre, ptSet.pts[0]);

            if (direction == SweepDirection.Clockwise)
            {
                // Check for any points near the first point more CCW than the current to set as start
                for (int i = 1; i < Math.Ceiling(ptSet.pts.Count * 0.25f); i++)
                {
                    float thisAngle = QDUtils.QDUtils.getPtToPtAngleD(ellipse.mCentre, ptSet.pts[i]);
                    if (QDUtils.QDUtils.angleDiffMinorD(startAngle, thisAngle) > 0f)
                    {
                        startAngle = thisAngle;
                    }
                }
            }
            else if (direction == SweepDirection.Counterclockwise)
            {     // CCW arc
                for (int i = 1; i < Math.Ceiling(ptSet.pts.Count * 0.25f); i++)
                {
                    float thisAngle = QDUtils.QDUtils.getPtToPtAngleD(ellipse.mCentre, ptSet.pts[i]);
                    if (QDUtils.QDUtils.angleDiffMinorD(startAngle, thisAngle) < 0f)
                    {
                        startAngle = thisAngle;
                    }
                }
            }
            return(startAngle);
        }
Exemplo n.º 4
0
        // Returns the list of other shapes modified which need redrawing
        public List <QDShape> analyse(QDInputPointSet ptSet)
        {
            List <QDShape> modifiedShapes = new List <QDShape>();

            getIntrinsicConstraints(ptSet);
            modifiedShapes = getExtrinsicConstraints(ptSet);
            return(modifiedShapes);
        }
Exemplo n.º 5
0
 // 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;
 }
Exemplo n.º 6
0
        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);
        }
Exemplo n.º 8
0
        // 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);
        }
Exemplo n.º 9
0
        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);
        }
        private QDLine fitLine(QDInputPointSet pointSet)
        {
            int n = pointSet.pts.Count - 1;

            float xsum = 0.0f;
            float ysum = 0.0f;

            for (int i = 0; i < n; i++)
            {
                xsum += pointSet.pts[i].x;
                ysum += pointSet.pts[i].y;
            }
            float xbar = xsum / n;
            float ybar = ysum / n;
            float denom, numer, gradient, diffx;

            denom = numer = 0.0f;
            for (int i = 0; i < n; i++)
            {
                diffx  = pointSet.pts[i].x - xbar;
                numer += diffx * (pointSet.pts[i].y - ybar);
                denom += diffx * diffx;
            }

            QDLine line = new QDLine();

            if (denom < 1e-2f)
            {                                         // Check for vertical case
                line.vertical = true;
                line.start.x  = line.finish.x = xbar; // x coords at the average

                line.start.y  = pointSet.pts[0].y;    // y coords are limits of drawn line
                line.finish.y = pointSet.pts[n].y;

                // Check direction
                if (line.finish.y > line.start.y)
                {
                    line.angleD = 90.0f;
                }
                else
                {
                    line.angleD = -90.0f;
                }
            }
            else
            {
                // Otherwise choose start and finish values as limits of line
                gradient       = numer / denom;
                line.angleD    = ((float)Math.Atan2(numer, denom)) * 180.0f / ((float)Math.PI);
                line.intercept = ybar - (gradient * xbar);

                if (Math.Abs(line.angleD) < 45.0f)
                {
                    line.start.x = pointSet.pts[0].x;
                    line.start.y = line.start.x * gradient + line.intercept;

                    line.finish.x = pointSet.pts[n].x;
                    line.finish.y = pointSet.pts[n].y;
                }
                else
                {
                    line.start.y = pointSet.pts[0].y;
                    line.start.x = (line.start.y - line.intercept) / gradient;

                    line.finish.y = pointSet.pts[n].y;
                    line.finish.x = (line.finish.y - line.intercept) / gradient;
                }
            }
            return(new QDLine(line.start, line.finish));
        }
        //float mFitCost = 0f;

        // Return whether a shape fit has been successfully applied
        public bool analyse(QDInputPointSet pointSet)
        {
            // Determine what type of shape the user has drawn and fit accordingly
            // Do this using the distance-sampled angles to get the overall shape
            float angleMinD    = 181.0f;
            float anglePosMinD = 181.0f;
            float angleMaxD    = -181.0f;
            float angleNegMaxD = -181.0f;

            if (pointSet.sampledPtAnglesD.Count == 0)
            {
                return(false);
            }
            foreach (float angle in pointSet.sampledPtAnglesD)
            {
                if (angle > angleMaxD)
                {
                    angleMaxD = angle;
                }
                if (angle < angleMinD)
                {
                    angleMinD = angle;
                }
                if (angle >= 0.0f && angle < anglePosMinD)
                {
                    anglePosMinD = angle;
                }
                if (angle < 0.0f && angle > angleNegMaxD)
                {
                    angleNegMaxD = angle;
                }
            }
            float angleRangeD = 0f;

            // Only include the positive or negative angular ranges if the curve had +ve or -ve angles
            if (anglePosMinD < 181.0f)
            {
                angleRangeD += (angleMaxD - anglePosMinD);
            }
            if (angleNegMaxD > -181.0f)
            {
                angleRangeD += (angleNegMaxD - angleMinD);
            }

            // A line has a similar angle throughout - the difference between max and min is small and the mean is in between
            if (angleRangeD < 40.0f)
            {
                pointSet.initialFit = pointSet.fittedShape = fitLine(pointSet);
                pointSet.shapeType  = QDShapeTypes.LINE;
                pointSet.constraints.Add(QDConstraintTypes.STRAIGHT_LINE);
                if (((QDLine)pointSet.initialFit).vertical)
                {
                    pointSet.constraints.Add(QDConstraintTypes.VERTICAL_LINE);
                }
            }
            // TODO: other options apart from fit a circle
            else
            {
                pointSet.initialFit = pointSet.fittedShape = ellipseFit(pointSet.pts);
                pointSet.shapeType  = QDShapeTypes.ELLIPSE;
                // TODO: change the constraint type
                pointSet.constraints.Add(QDConstraintTypes.CIRCLE);
                SweepDirection direction  = getDirection(pointSet);
                float          sweptAngle = findSweptArc(pointSet, (QDEllipse)pointSet.initialFit);
                if (sweptAngle < 330f)
                {
                    float startAngle  = findArcStartD(direction, pointSet, (QDEllipse)pointSet.initialFit);
                    float finishAngle = findArcEndD(direction, pointSet, (QDEllipse)pointSet.initialFit);
                    pointSet.shapeType  = QDShapeTypes.ELLIPTICAL_ARC;
                    pointSet.initialFit = pointSet.fittedShape = new QDEllipticalArc((QDEllipse)pointSet.initialFit, direction, startAngle, finishAngle);
                }
            }

            return(true);
        }