c # - Hvordan opdager du en museklik i en cirkel (cirkel stykke)

Indlæg af Hanne Mølgaard Plasc

Problem



Jeg vil lave et spil dart. Jeg vil gerne kunne klikke på en bestemt cirkel for at give mig pointene. Jeg lavede dartbordet ved at forbinde pie grafik. Hvordan opdager jeg, om et klik er ramt inden for den valgte region? Her er koden, hvor jeg laver brættet:


void draw(Color a, Color b, int j)
{
    float start\_angle = -9; // offset
    float end\_angle = 18;
    for (int i = 0; i < 20; i++)
    {
        pie[j, i] = e.Graphics; 
        pie[j, i].DrawPie(pen, rectangle1, start\_angle, end\_angle);                         
        if (i \% 2 == 0)
        {
             brush.Color = a;
             pie[j, i].FillPie(brush, rectangle1, start\_angle, end\_angle);
        }
        else
        {
            brush.Color = b;
            pie[j, i].FillPie(brush, rectangle1, start\_angle, end\_angle);
        }
        start\_angle = start\_angle + end\_angle;
    }
}

Bedste reference


TaWs svar om at bruge GraphicsPath for hitdetektering er godt, men dette særlige problem kan også løses matematisk:


    /// <summary>
    /// Determine which wedge of the target a point is contained in, if any.
    /// </summary>
    /// <param name="p">The point to test.</param>
    /// <param name="targetBoundingSquare">The bounding square of the circle target.</param>
    /// <returns>
    /// -1 if point is not in the circle target,
    /// OR a number 0..19 which is the wedge that the point is in, in the same order
    /// that they are drawn.
    /// OR int.MaxValue if the point is a bullseye.
    /// </returns>
    int HitPieWedge(Point p, Rectangle targetBoundingSquare)
    {
        // precondition:
        // the calculations below assume the target is a circle, not a general ellipse
        if (targetBoundingSquare.Width != targetBoundingSquare.Height)
            throw new ArgumentException("The width and height must be equal to form a circular target.", nameof(targetBoundingSquare));

        // quick rejection test:
        // If the point isn't within the overall target bounding rectangle,
        // there's no hit
        if (!targetBoundingSquare.Contains(p))
            return -1;

        // point is within the bounding rectangle of the target, so a
        // hit is possible. now more carefully check to see if it's within
        // the circle (instead of hitting a dead zone that's inside the
        // bounding rectangle but outside of the circle)

        // calculate the center point of the target
        // could use double instead of float, but assume float is good enough precision for a game
        float radius = targetBoundingSquare.Width / 2;
        float centerX = targetBoundingSquare.X + radius;
        float centerY = targetBoundingSquare.Y + radius;

        // Use circle geometry to reject points outside the circle.
        // This is done by calculating the distance from the center of the circle to the test point.
        // If this distance is greater than the radius, we know the point
        // is outside the circle. Otherwise it is inside or just on its boundary.
        // Normally calculating the distance between two points requires a square root which is an
        // relatively expensive operation, but since we only need to know if the distance
        // exceeds the radius (and not the actual distance itself), comparing squared values
        // is just as valid and is more efficient.

        float dx = p.X - centerX;
        float dy = p.Y - centerY;

        // special case: Is point at the exact center ("bullseye")? If so,
        // it is contained in the target but not in any wedge specifically, so
        // just return an arbitrary special value.
        if (dx == 0 && dy == 0)
            return int.MaxValue;

        // actual distance: not needed since we'll use an optimized form below just for
        // relative distance comparison, which just squares both sizes of this equation.
        // distance = Math.Sqr(Math.Pow(dx,2) + Math.Pow(dy,2));

        // if point is further than radial distance from center, reject it
        if (dx * dx + dy * dy > radius * radius)
            return -1;

        // at this point we know the point is on the target somewhere
        // now determine which wedge it is in.
        // note: this part can be done faster using a lookup table,
        // but the math is kind of interesting :)

        // the sweep angle of a single pie wedge, in radians
        const double wedgeSweepAngle = 2 * Math.PI / 20;

        // determine the direction from the center of the target to the hit point
        // this will be an angle from the +X axis in radians in the range [-PI,PI]
        double hitDirection = Math.Atan2(dy, dx);

        // now the direction will be divided by the wedge angular size
        // to identify which wedge it is in, but there are a couple of tricks needed:

        // 1. Because the first wedge is centered on the positive X axis (rather than the
        //    leading edge being aligned on the axis), the direction needs to be offset by half a wedge.
        hitDirection += wedgeSweepAngle * 0.5;

        // 2. Change angle from the range [-PI,PI] to a nicer [0,2PI] range for wedge calculaton.
        //    This just maps the negative range [-PI,0] to [PI,2PI]; the positive range [0,PI] is fine as-is.
        if (hitDirection < 0)
            hitDirection += 2*Math.PI;

        // now it's just a simple division to determine the wedge index.
        // wedges will be numbered from 0 to 19 in the same order
        // they're drawn in your code above

        int wedgeIndex = (int)(hitDirection / wedgeSweepAngle);
        return wedgeIndex;
    }