Check if a point is between two arcs

I want to check if a point is between two arcs.

Background: I get touch position with WatchFaceDelegate.

I tought to check the angle of the point and compare with my drawArc (starts at 220 and ends at 140 [degrees] clockwise direction).

The first one arc is an arc on watch perimeter, the second arc is not drowen, I only want to check if the touch point is between the perimeter of the watch and dc.getWidth()*0.85

So I do this:

    // Touch coords
    var touchX = points[0], touchY = points[1];
    
    // Tansform Radius to polar coords
    var distance1 = Math.sqrt(Math.pow(touchX - centerX, 2) + Math.pow(touchY - centerX, 2));
    var distance2 = Math.sqrt(Math.pow(touchX - centerX, 2) + Math.pow(touchY - centerX, 2));
    
    // Calculate the angle between the touch point and the centers of both arcs
    var angle1 = Math.atan2(touchY - centerX, touchX - centerX);
    
    var startAngleDeg = (220);
    var endAngleDeg = (220-80);

    // Convert degrees to radians
    var startAngle = Math.toRadians(startAngleDeg);
    var endAngle = Math.toRadians(endAngleDeg);

    var radius1 = (dimX/2);
    var radius2 = ((dimX/2)*0.9);
    
    // Check if the touch point is within the defined area between the two arcs
    if (distance1 <= radius1 && distance2 >= radius2 && angle1 >= startAngle && angle1 <= endAngle) {
        Log.debug("Touch event is within the defined area between two arcs. CIRCLE 1");
    } else {
        Log.debug("Touch event is outside the defined area between two arcs.\n");
    }

I checked with Log that the distance correctly works, but the angle is never triggered.

I really don't know what I'm missing

  • For me it is easier to calculate with angles - not radiant - I can better imagine...

    My Arc has 140, 140 center, and tX, tY are the tap coordinates

    var distance = Math.sqrt( Math.pow(tX - 140 , 2) + Math.pow(tY - 140 , 2) );
    var tapangle = Math.toDegrees( Math.atan2( (140-tY) , (tX-140) ) );
    if ( tapangle < 0 ) {
        tapangle = 180 + (180 + tapangle);
    }
    
    System.println("Dist = " + distance + "    Angle = " + tapangle);
    

    With this code you can check if tapangle is > 140 and < 220

  • But in case I draw an arc in Counter Clockwise direction, with start angle 275 and end angle 85, the tap angle should be inside, but it fails because check if tapangle 0 (3:00) is 85<tapangle<275. How I can fix this?

  • check for tapangle>275 or tapangle<85

    (an arc counterclockwise, 275, 85 does not look like your arcs - neither blue nor red)

    My code above works for all angles from 0 to 360 (counterclockwise) starting at 3 o‘clock.
    You can also set an clockwisetapangle = 360 - tapangle.

  • Maybe I miss something...

    This is my code:

    var angles = [];
    // Add circles to an array
    angles.add([275, 170, "CircleField2", Gfx.ARC_COUNTER_CLOCKWISE, getComplicationDataField(Settings.get("CircleField2"))]);
    angles.add([265, 170, "CircleField1", Gfx.ARC_CLOCKWISE, getComplicationDataField(Settings.get("CircleField1"))]);
    
    // draw circles
    for (var i=0; i<angles.size();i++) {
        if(getProp("UseCirclePlaceHolder")) {
            drawArcByRatio(dc, 1.0, {
                :arcPenWidth => circleWidth,
                :arcStart => angles[i][0],
                :fullArcLength => angles[i][1],
                :arcDirection => angles[i][3],
                :color => Gfx.COLOR_DK_GRAY
            });
        }
        drawArcByRatio(dc, getProgress(Settings.get(angles[2])), {
            :arcPenWidth => circleWidth,
            :arcStart => angles[i][0],
            :fullArcLength => angles[i][1],
            :arcDirection => angles[i][3],
            :color => getColor(Settings.get(angles[i][2]))
        });    
    }
    
    // Draw arc function
    

    Here is Draw arc function:

    const defaultCustomArcOptions = {
            :arcPenWidth => 10,
            :fullArcLength => 360, // full circle
            :arcStart => 90, // top
            :arcDirection => Gfx.ARC_CLOCKWISE,
            :color => Gfx.COLOR_WHITE
        };
    
        function mergeDictionaryValue(dict1 as Dictionary, dict2 as Dictionary, key as Symbol) as Void {
            if (dict1[key] == null) {
                dict1[key] = dict2[key];
            }
        }
    
        typedef CustomArcOptions as {
            :arcPenWidth as Numeric or Null, // pen width in pixels
            :fullArcLength as Numeric or Null, // full arc length in degrees (360 = circle)
            :arcStart as Numeric or Null, // same as drawArc()'s degreeStart (5th arg). (0: 3 o'clock, 90: 12 o'clock, 180: 9 o'clock, 270: 6 o'clock)
            :arcDirection as Graphics.ArcDirection or Null
        };
    
        function drawArcByRatio(
            dc as Dc,
            arcRatio as Numeric,
            options as CustomArcOptions or Null) as Void {
                options = options != null ? options : defaultCustomArcOptions;
                mergeDictionaryValue(options, defaultCustomArcOptions, :arcPenWidth);
                mergeDictionaryValue(options, defaultCustomArcOptions, :fullArcLength);
                mergeDictionaryValue(options, defaultCustomArcOptions, :arcStart);
                mergeDictionaryValue(options, defaultCustomArcOptions, :arcDirection);
                mergeDictionaryValue(options, defaultCustomArcOptions, :color);
    
                var x = dimX / 2;
                var y = dimY / 2;
                var r = x < y ? x : y;
                var arcLength = options[:fullArcLength] * arcRatio;
                var arcEnd = options[:arcStart] +
                    (options[:arcDirection] == Graphics.ARC_CLOCKWISE ?
                    -arcLength :
                    arcLength);
    
                dc.setPenWidth(options[:arcPenWidth]);
                dc.setColor(options[:color], trasp);
                if (arcRatio!=0) {dc.drawArc(x, y, r - options[:arcPenWidth] / 2, options[:arcDirection], options[:arcStart], arcEnd);}
        }

    Here is how I want to check for tap inside circle:

    function checkCircle(points) {
    
      // iterate through each bounding box
      for(var i=0;i<angles.size();i++) {
    
        var currentCircle = angles[i];
        Sys.println("checking : " + currentCircle[2]);
    
        // if so, return the corresponding complication
        if (checkBoundsForComplicationCircle(points,currentCircle[i])) {
            return currentCircle[4];
        }
    
      }
    
      return false;
    
    }

    public function checkForComplicationCircle(points, angles) {
        var s = angles[0] as Number;
        var e = 0;
        if (angles[3]==clockwise) {
            e = s-angles[1];
        } else {
            e = s + angles[1];
        }
        var nam = angles[2] as String;
      return areaContains(points, s, e, nam, angles[3]);
    }

    function areaContains(points, startAngleDeg, endAngleDeg, nam, dir) as Boolean {
        var touchX = points[0], touchY = points[1];
        Log.debug("CENTERX " + centerX);
    
        var distance1 = Math.sqrt(Math.pow(touchX - centerX, 2) + Math.pow(touchY - centerX, 2));
        var distance2 = Math.sqrt(Math.pow(touchX - centerX, 2) + Math.pow(touchY - centerX, 2));
    
        Log.debug("CIRC " + " | DISTANCE 1 " + distance1 + " | DISTANCE 2 " + distance2);
    
        // Calculate the angle between the touch point and the centers of both arcs
        var angle1 = Math.atan2(touchY - centerX, touchX - centerX);
        var angle2 = Math.atan2(touchY - centerX, touchX - centerX);
        var angle3 = Math.atan2(centerX - touchY, touchX - centerX);
    
        Log.debug("CIRC " + " | ANGLE 1 " + angle1);
    
        Log.debug("CIRC " + " START DEG " + startAngleDeg + " | END DEG " + endAngleDeg);
    
        var radius1 = (dimX/2);
        var radius2 = ((dimX/2)*0.88);
    
        var tapangle = Math.toDegrees(angle3);
    
        if ( tapangle < 0 ) {
            tapangle = 360 + tapangle;
        }
    
        Log.debug("Dist = " + distance1 + "    Angle = " + tapangle);
    
        Log.debug("CIRC " + "Distance1 = " + distance1 + " <= " + radius1 + " | Distance 2 " + distance2 + ">=" + radius2 + " | angle1 " + angle1 + " | angle 2 " + angle2 + " | start " + startAngleDeg + " | end " + endAngleDeg);
    
        if (distance1 <= radius1 && distance2 >= radius2 ) {
            Log.debug("DISTANCE OK");
            if (startAngleDeg < endAngleDeg) {
                Log.debug("S < E OK");
                if (tapangle >= startAngleDeg && tapangle <= endAngleDeg) {
                    Log.debug("ANGLE OK!!(" + nam + ")");
                    return true;
                } else {
                    Log.debug("ANGLE NO");
                    return false;
                }
            } else {
                Log.debug("S > E");
                if (tapangle >= endAngleDeg && tapangle <= startAngleDeg) {
                    Log.debug("ANGLE OK!!(" + nam + ")");
                    return true;
                } else {
                    Log.debug("ANGLE NO");
                    return false;
                }
            }
        } else {
            Log.debug("Touch event is outside the defined area between two arcs.\n");
            return false;
        }
    }

    Yeah, is a bit messed up because I tried a lot.

  • I want to check if a point is between two arcs.

    To recap, for Monkey C drawArc(), angles are defined as follows:

    - 0° = 3 o'clock
    - 90° = 12 o'clock
    - 180° = 9 o'clock
    - 270° = 6 o'clock

         90°
    180°     0°
         270°

    In the rest of my post, all angles will be assumed to follow this convention.

    A general solution (which works with all arcs and points) should take into account:

    - an arc can be defined as either clockwise or counter-clockwise

    - in either case, the start angle could either be greater than/equal to the end angle or less than the end angle, so it's never going to be a simple case of asking whether Start <= Point <= End (or vice versa). e.g. consider two counter-clockwise arcs: 0 to 180 and 270 to 90. Each of these cases needs a different rule, and it doesn't help to multiply any of the angles by -1.

    - for convenience, the solution should support angles < 0 or >= 360, just as drawArc() does. For example, -90 should be equivalent to 270, 0 should be equivalent to 360, and 460 should be equivalent to 100. In general, if x = t * 360 + y, where x and y are angles (expressed in degrees) and t is an integer, then x and y are equivalent (they refer to the same point on a circle).

    I would create a helper function pointIsWithinArc() for determining whether a point (angle) falls within an arc, and make it work just like drawArc() (in the sense that it takes a start angle, end angle and direction.)

    Algorithm:

    - Since angles are defined as increasing in the counter-clockwise direction, the default logic should handle that direction (for the sake of simplicity)

    - If you're given a clockwise arc, simply swap the start and end points and now you have a counter-clockwise arc

    - given a counter-clockwise arc:

      - if arcStart <= arcEnd, then a point P is inside the arc if arcStart <= P <= arcEnd

      - if arcStart > arcEnd, then P is inside the arc if arcStart <= P OR P <= arcEnd

    That's it, that's the whole algorithm (other than normalizing degree inputs so 0 <= degrees < 360.)

    References:

    [https://stackoverflow.com/a/51896645]

    I think this should work but I haven't tested it extensively.

    Code:

    // Given an angle in degrees, return a normalized value >= 0 and < 360
    function normalizeDegrees(degrees as Numeric) as Numeric {
        if (degrees < 0) {
            // same as repeatedly adding 360 until result is > 0 and <= 360
            degrees = -degrees;
            degrees = degrees % 360;
            degrees = 360 - degrees;
        }
        if (degrees >= 360) {
            // same as repeatedly subtracting 360 until result is >= 0 and < 360
            degrees = degrees % 360; 
        }
        return degrees;
    }
    
    function pointIsWithinArc(
        pointDegrees as Numeric,
        arcStartDegrees as Numeric,
        arcEndDegrees as Numeric,
        arcDirection as Graphics.ArcDirection
    ) as Boolean {
        pointDegrees = normalizeDegrees(pointDegrees);
        arcStartDegrees = normalizeDegrees(arcStartDegrees);
        arcEndDegrees = normalizeDegrees(arcEndDegrees);
    
        // Note that angles increase in the counter-clockwise direction
        //      90°
        // 180°     0°
        //     270°
    
        // A clockwise arc can be described as a counter-clockwise arc by
        // swapping the start and end points
        if (arcDirection == Graphics.ARC_CLOCKWISE) {
            var tempStartDegrees = arcStartDegrees;
            arcStartDegrees = arcEndDegrees;
            arcEndDegrees = tempStartDegrees;
        }
    
        // https://stackoverflow.com/a/51896645
        if (arcStartDegrees <= arcEndDegrees) {
            return arcStartDegrees <= pointDegrees && pointDegrees <= arcEndDegrees;
        } else {
            if (pointDegrees >= arcStartDegrees) {
                return true;
            }
            if (pointDegrees <= arcEndDegrees) {
                return true;
            }
            return false;
        }
    }

    You should be able to use pointIsWithinArc() with your other code for determining the point angle and distance.

    EDIT: Here's some test cases.

    But in case I draw an arc in Counter Clockwise direction, with start angle 275 and end angle 85, the tap angle should be inside

    function testPoints() as Void {
        System.println("test 1: " + pointIsWithinArc(0, 275, 85, Graphics.ARC_COUNTER_CLOCKWISE)); // true
        System.println("test 2: " + pointIsWithinArc(0, 275, 85, Graphics.ARC_CLOCKWISE)); // false
    
        System.println("test 3: " + pointIsWithinArc(0, 0, 90, Graphics.ARC_COUNTER_CLOCKWISE)); // true
        System.println("test 4: " + pointIsWithinArc(0, 0, 90, Graphics.ARC_CLOCKWISE)); // true
        System.println("test 5: " + pointIsWithinArc(90, 0, 90, Graphics.ARC_COUNTER_CLOCKWISE)); // true
        System.println("test 6: " + pointIsWithinArc(90, 0, 90, Graphics.ARC_CLOCKWISE)); // true
    
        System.println("test 7: " + pointIsWithinArc(45, 0, 90, Graphics.ARC_COUNTER_CLOCKWISE)); // true
        System.println("test 8: " + pointIsWithinArc(45, 0, 90, Graphics.ARC_CLOCKWISE)); // false
    
        System.println("test 9: " + pointIsWithinArc(0, 270, 45, Graphics.ARC_COUNTER_CLOCKWISE)); // true
        System.println("test 10: " + pointIsWithinArc(0, 270, 45, Graphics.ARC_CLOCKWISE)); // false
    
        // test point >= 360 degrees
        System.println("test 11: " + pointIsWithinArc(405, 0, 90, Graphics.ARC_COUNTER_CLOCKWISE)); // true
        System.println("test 12: " + pointIsWithinArc(360, 0, 90, Graphics.ARC_COUNTER_CLOCKWISE)); // true
    
        // test point < 0 degrees
        System.println("test 13: " + pointIsWithinArc(-300, 0, 90, Graphics.ARC_COUNTER_CLOCKWISE)); // true
        System.println("test 14: " + pointIsWithinArc(-265, 0, 90, Graphics.ARC_COUNTER_CLOCKWISE)); // false
    }