GCircle.java

/*
 *                      ..::jDrawingLib::..
 *
 * Copyright (C) Federico Vera 2012 - 2023 <[email protected]>
 *
 * This program is free software: you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by the
 * Free Software Foundation, either version 3 of the License, or any later
 * version.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 * See the GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package com.dkt.graphics.elements;

import java.awt.Graphics2D;
import java.awt.geom.Area;
import java.awt.geom.Ellipse2D;

/**
 *
 * @author Federico Vera {@literal<[email protected]>}
 */
public class GCircle extends GFillableE {
    private int x;
    private int y;
    private final int d;
    private final int r;

    /**
     * Copy constructor
     *
     * @param e {@code GCircle} to copy
     * @throws IllegalArgumentException if {@code e} is {@code null}
     */
    public GCircle(GCircle e) {
        super(e);

        this.r = e.r;
        this.d = e.d;
        this.x = e.x;
        this.y = e.y;
    }

    /**
     * @param x X coordinate of the center
     * @param y Y coordinate of the center
     * @param r radius
     */
    public GCircle(final int x, final int y, int r) {
        int rr = Math.abs(r);
        this.x = x - rr;
        this.y = y - rr;
        this.d = 2 * rr;
        this.r = rr;
    }

    /**
     * Retrieves the X coordinate of the center of the circle
     *
     * @return x coordinate of the center
     */
    public int x() {
        return x + r;
    }

    /**
     * Retrieves the Y coordinate of the center of the circle
     *
     * @return y coordinate of the center
     */
    public int y() {
        return y + r;
    }

    /**
     * Returns the radius of the circle
     *
     * @return radius
     */
    public int getRadius() {
        return r;
    }

    /**
     * Tells if a given circle is contained on this circle, that means that
     * every point in the circle is contained in this one.
     *
     * @param c the line to check
     * @return {@code true} if the circle is contained and {@code false}
     * otherwise
     * @throws IllegalArgumentException if {@code circle} is {@code null}
     */
    public boolean contains(final GCircle c) {
        if (c == null){
            throw new IllegalArgumentException("The circle can't be null");
        }

        final double cd = Math.hypot(x() - c.x(), y() - c.y());
        final double dr = getRadius() - c.getRadius();

        return cd <= dr;
    }

    /**
     * Tells if a given line segment is contained in the circle, that means
     * that both ends are contained in the circle
     *
     * @param line the line to check
     * @return {@code true} if the line is contained and {@code false}
     * otherwise
     * @throws IllegalArgumentException if {@code line} is {@code null}
     */
    public boolean contains(final GLine line) {
        if (line == null){
            throw new IllegalArgumentException("The line can't be null");
        }

        return contains(line.getStartPoint()) &&
               contains(line.getEndPoint  ());
    }

    /**
     * Tells if a given point is contained in the circle
     *
     * @param point the point to check
     * @return {@code true} if the point is contained and {@code false}
     * otherwise
     * @throws IllegalArgumentException if {@code point} is {@code null}
     */
    public boolean contains(final GPoint point) {
        if (point == null){
            throw new IllegalArgumentException("The point can't be null");
        }

        return contains(point.x(), point.y());
    }

    /**
     * Tells if a given point is contained in the circle
     *
     * @param x x coordinate of the point
     * @param y y coordinate of the point
     * @return {@code true} if the point is contained and {@code false}
     * otherwise
     */
    public boolean contains(final int x, final int y) {
        return Math.hypot(x() - x, y() - y) <= r;
    }

    /**
     * Tells if this circle intersects with another one
     *
     * @param c The circle to check
     * @return {@code true} if the circles intersect and {@code false} otherwise
     * @throws IllegalArgumentException if {@code circle} is {@code null}
     */
    public boolean intersects(final GCircle c) {
        if (c == null){
            throw new IllegalArgumentException("The circle can't be null");
        }

        final double cd = Math.hypot(x() - c.x(), y() - c.y());
        final double sr = getRadius() + c.getRadius();

        return cd <= sr;
    }

    /**
     * Tells if this circle intersects with a line.<br>
     * <b>Note:</b> This will tell if the line intersects the circle NOT the line 
     * segment!!!
     *
     * @param line The line to check
     * @return {@code true} if they intersect and {@code false} otherwise
     * @throws IllegalArgumentException if {@code line} is {@code null}
     */
    public boolean intersects(final GLine line) {
        if (line == null){
            throw new IllegalArgumentException("The line can't be null");
        }

        return discriminant(line) >= 0;
    }

    /**
     * Calculates the area of this circle
     *
     * @return area
     */
    public double area() {
        return Math.PI * r * r;
    }

    /**
     * Calculated the perimeter of this circle
     *
     * @return perimeter
     */
    public double perimeter() {
        return Math.PI * d;
    }

    private double discriminant(final GLine line){
        //http://stackoverflow.com/questions/6091728/line-segment-circle-intersection
        final GPoint start = line.getStartPoint();
        final GPoint end   = line.getEndPoint();

        final double x1 = start.x();
        final double y1 = start.y();
        final double x2 = end.x();
        final double y2 = end.y();
        final double cx = x();
        final double cy = y();
        final double cr = getRadius();

        final double dx = x2 - x1;
        final double dy = y2 - y1;
        final double a = dx * dx + dy * dy;
        final double b = 2 * (dx * (x1 - cx) + dy * (y1 - cy));
        final double c = cx * cx + cy * cy
                       + x1 * x1 + y1 * y1
                       - 2 * (cx * x1 + cy * y1)
                       - cr * cr;

        return b * b - 4 * a * c;
    }

    @Override
    public void draw(final Graphics2D g) {
        if (fill()){
            g.setPaint(getFillPaint());
            g.fillOval(x, y, d, d);
        }

        g.setPaint(getPaint());
        g.setStroke(getStroke());
        g.drawOval(x, y, d, d);
    }

    @Override
    public void traslate(final int x, final int y) {
        this.x += x;
        this.y += y;
    }

    /**
     * Moves the center of this circle to the given coordinates
     *
     * @param x new x coordinate
     * @param y new y coordinate
     */
    public void move(final int x, final int y) {
        this.x = x;
        this.y = y;
    }

    @Override
    public GCircle clone() {
        return new GCircle(this);
    }

    @Override
    public int hashCode() {
        int hash = super.hashCode();
        hash = 79 * hash + x;
        hash = 79 * hash + y;
        hash = 79 * hash + d;
        hash = 79 * hash + r;
        return hash;
    }

    @Override
    public boolean equals(Object obj) {
        if (!super.equals(obj)) {
            return false;
        }

        final GCircle other = (GCircle) obj;
        return !(
            x != other.x |
            y != other.y |
            d != other.d |
            r != other.r
        );
    }

    @Override
    public Area getShape() {
        return new Area(new Ellipse2D.Double(x, y, d, d));
    }
}