ForeignWeb Page
ForeignWeb Page

Reputation: 53

How to number sections of a circle going from left to right

I have been programming a game in java and have stumbled upon a very easy seeming problem, but cant figure it out! I have tried searching for an answer, but never seem to go to the right place.

Im using ABRG pixels in a bitmap class, that uses a display class to draw the pixels on screen, each class uses a method called 'render', that always have the displays bitmap class, or a bitmap that is attached to the display, as a parameter. Their for, I cannot use the in-built functions that use the 'circle transform' method within the library im using, because the drawing is done by pixels that use self written math functions.

The problem I have is to be able to make a method that returns an Integer according to a certain section of the circle, with any amount of sections within the circle. Im doing this so that I can make buttons that allow the player to choose a difficulty, but have a different style to the game, not square buttons everywhere. Also it can be used for maps to label what team a player is on.

For example if I have four sections in a circle, I want the method to be able to give me the number of the section its currently drawing/using, but here is the catch. I want it from left to right. So the top left will be 1, the top right will be 2, bottom left 3, bottom right 4. I could do this manually, but in the future im 100% sure that I will be using this method with more sections in it.

An easier way to understand what I need, is if you have drawn the circle on your screen and can clearly see that each section has been colored differently, each section containing 90 degrees, as their is 4 sections. If you hover your mouse over the top left circle, the game can now tell that you are hovering over '1'. If you move your mouse over to the top right circle, you are now hovering over '2'. bottom left '3' and bottom right '4'. When you click your mouse whilst hovering over the top left section, an Interger lets say 'SPEED', will be set to 1, the game will then start with a speed of 1.

Ill do another example just to clarify what i need, this time with 8 sections. You have drawn the circle with all sections colored differently, you hover over the first section to the left, above the middle on the horizontal. The game now tells that you are hovering over '1', you go clockwise from the section you were just on, the second section is '2', the third '3', the fourth '4', but when you keep going clockwise, after the section you were just on, the section you are on now is the fifth section(Going clockwise), the game now tells you are on '8', you keep going. sixth is '7', seventh is '6' and the eighth is '5'.

I have tried various techniques with this, but each time failed, either because of angle problems, or larger sections stuffing up.

I dont expect anyone to write the code for me(Would be nice :D), just if anyone could lead me to a subject that goes over this, it would be much appreciated to learn more about this.

Thanks for reading, foreign.

Upvotes: 0

Views: 489

Answers (2)

Mshnik
Mshnik

Reputation: 7042

Really fun problem! Ended up being a bit harder than I thought. I know you're not doing it in swing, but this demo should help with whatever implementation you use.

In short, I created Path2Ds to represent each slice, capitalizing on the fact that by the Shape interface they have a contains(Point p) method we can use at the end to check if a mouse position is in a slice. I created the slice through appending a line, an arc, and another line. This part of the implementation is independent of how you decide to draw the circle, so you may consider using the whole 2D geometric package.

The challenge is how you want the numbering. I think I have it right, but let me know if I don't; I can patch it up.

Screenshot of below code

import java.awt.BasicStroke;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Shape;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionListener;
import java.awt.geom.Arc2D;
import java.awt.geom.Line2D;
import java.awt.geom.Path2D;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;

import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;


public class CircleFrame extends JFrame implements MouseMotionListener {

    private JLabel hoveredLabel;
    private CirclePanel circle;

    public CircleFrame(){
        setLayout(new BorderLayout());
        setDefaultCloseOperation(EXIT_ON_CLOSE);

        circle = new CirclePanel(8, new Dimension(500,500));
        hoveredLabel = new JLabel("");
        hoveredLabel.setFont(new Font("Arial", Font.PLAIN, 50));
        add(circle, BorderLayout.CENTER);
        add(hoveredLabel, BorderLayout.EAST);
        addMouseMotionListener(this);
        pack();
        repaint();
        setVisible(true);
    }

    public static void main(String[] args){
        new CircleFrame();
    }

    static class CirclePanel extends JPanel{

        private static final Color CIRCLE_COLOR = Color.black;
        private static final Color NUMB_COLOR = Color.blue;
        private static final int MARGIN = 50;

        private Path2D.Float[] slices;
        private final int divisions;
        private final int minDimen;
        private Point2D.Float centerPoint;
        private int diameter;

        public CirclePanel(final int d, Dimension size){
            setPreferredSize(size);

            minDimen = Math.min(size.height, size.width);
            final int center = (minDimen - MARGIN) / 2; //center x and y
            centerPoint = new Point2D.Float(center, center);
            diameter = minDimen - MARGIN * 2;
            final Rectangle2D.Float bounds = new Rectangle2D.Float(center - diameter/2, 
                    center - diameter/2, diameter, diameter);

            //Create slices
            slices = new Path2D.Float[d];
            divisions = d;

            for(int i = 0; i < d; i++){

                //Here's where the ordering happens. Use j from here on out to
                //Put slices in in your order
                int j = -1;
                if(i < divisions/2){
                    j = divisions/2 - i;
                }else{
                    j = i + 1;
                }

                Path2D.Float slice = new Path2D.Float();
                float start = fracToRad(j-1, d);
                float end = fracToRad(j,d);
                //Add straight line leaving center
                Point2D.Float arcP1 = rotatedPoint(centerPoint, diameter, start);
                slice.append(new Line2D.Float(center, center, arcP1.x, arcP1.y), true);

                //Add arc
                slice.append(new Arc2D.Float(bounds, (float)Math.toDegrees(start), 
                        (float)Math.toDegrees(fracToRad(1, d)), Arc2D.OPEN), true);

                //Add line back
                Point2D.Float arcP2 = rotatedPoint(centerPoint, diameter, end);
                slice.append(new Line2D.Float(arcP2.x, arcP2.y, center, center), true);

                //Close the slice
                slice.closePath();
                slices[i] = slice;
            }
        }

        @Override
        public void paintComponent(Graphics g){


            Graphics2D g2d = (Graphics2D)g;
            g2d.setColor(CIRCLE_COLOR);
            g2d.setStroke(new BasicStroke(minDimen/ 50));
            for(Shape s : slices){
                g2d.draw(s);
            }

            g2d.setColor(NUMB_COLOR);
            //Draw the numbering
            final int numbRadius = (int)((double) (diameter/2) * .75); //Radius at which to put numbers
            final int numbDiameter = 2 * numbRadius;
            g2d.setFont(new Font("Arial", Font.PLAIN, 25));
            for(int i = 0; i < divisions; i++){

                //Here's where the ordering happens. i is position, j is printed string
                int j = -1;
                if(i < divisions/2){
                    j = divisions/2 - i;
                }else{
                    j = i + 1;
                }

                Point2D.Float loc = rotatedPoint(centerPoint, numbDiameter, 
                        fracToRad(i, divisions) + (float)(2 * Math.PI / (divisions * 2)));
                g2d.drawString(j + "", (int)loc.x, (int)loc.y);
            }
        }

        /** Returns the point on the circle.
         * @param center        - the center of the circle
         * @param diameter      - the diameter of the circle
         * @param rad           - The radians to rotate. 0 is straight right. Rotates counter clockwise
         */
        private static Point2D.Float rotatedPoint(Point2D.Float center, double diameter, double rad){
            double radius = diameter/2;
            return new Point2D.Float((float)(Math.cos(rad) * radius + center.x), (float)(- Math.sin(rad) * radius + center.y));
        }

        /** Returns the number of radians corresponding to the given fraction of a circle */
        public static float fracToRad(int num, int denom){
            return (float)(((double)num) / ((double)denom) * 2 * Math.PI);
        }
    }

    @Override
    public void mouseDragged(MouseEvent e) {
    }

    @Override
    public void mouseMoved(MouseEvent e) {
        if(circle == null) return;
        for(int i = 0; i < circle.slices.length; i++){
            if(circle.slices[i].contains(e.getPoint())){
                hoveredLabel.setText((i+1) + "");
                pack();
                repaint();
            }

        }
    }

}

Upvotes: 1

Christian R.
Christian R.

Reputation: 1538

just to get you right: you have an image of a colored circle (like a wheel of fortune), you already know the radius of the circle, and all edges between sections have a common point C(0,0)/center of the circle?

you'll have different options for a solution, I'll have to guess, depending on what data you already have. All solutions are depending on the angle of the sections two outer points A and B, because each point inside the circle is in the circle section that is determined by (A,B,C), in other words: the angle of any given point inside the section (A,B,C) is larger than angle of A and smaller than angle of B (except points, that are directly on the edge, you'll have to decide which section they belong to). Following this, you can sort all sections by their left or right angle and make a lookup for a given points angle to determine the section it's in.

Solutions

A

if you already know how many sections you have and if they are evenly distributed and you know at least one point (for example P(-r,0) as default), you can easily calculate the other points angle knowing that a whole circle has 360° (or 2pi rad). the same approach applies, if you can determine a first point anyhow and it's still evenly distributed and you know the section count. if any of the above presumptions is not true, you'll have to find at least one (or if not evenly distributed all) points with B or C

B

If the radius is not to large and at best constant, you could precalculate the most outer points of the circle that are still inside the circle (should be about 2*pi*r points as upper bound with r in pixel) and sort them by their angle. than you could check the color for each of those points and if it differs, you have detected an edge between two sections. If not evenly distributed, you'll have to go one with the checks until you found all sections, otherwise go on with A. If the radius is too large and/or not constant following performance issues in to many color lookups, you could still use this approach by using a smaller radius, but you'll lose precision.

C

Should not be necessary, since B should work out at least, but you could go the long way with edge detection to determine the sections. won't go further into this, unless requested.

I hope this will direct you in the right direction, there are probably other, perhaps faster approaches, but that's the way I'd go.

PS: if not colored but only divided by a color(e.g. black like in the pics - you linked), you could still use A or B, but in B you'd have to reduce the radius to only include white space instead and detect an edge because it's black.

PPS: to always start the count from the far most left section, calculate the angle from (-r,0), the most left point of your circle

Radian article in english Wikipedia

Circular sector in english Wikipedia

Upvotes: 0

Related Questions