GRegPoly.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;

/**
 *
 * @author Federico Vera {@literal<[email protected]>}
 */
public class GRegPoly extends GPoly {
    private int x, y;
    private final int r, n;
    private double a;

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

        x = e.x;
        y = e.y;
        r = e.r;
        n = e.n;
        a = e.a;
    }

    /**
     * Constructs a regular polygon contained in a circle
     *
     * @param x X coordinate of the center of the circle
     * @param y Y coordinate of the center of the circle
     * @param r radius of the circle
     * @param n number of sides
     * @param a angle of the first point
     */
    public GRegPoly(
            final int x,
            final int y,
            final int r,
            final int n,
            final double a)
    {
        this.x = x;
        this.y = y;
        this.r = r;
        this.n = n;
        this.a = Math.toRadians(a);

        calc();
    }

    private void calc() {
        final double temp = 2 * Math.PI / n;
        final int[] fx = new int[n];
        final int[] fy = new int[n];

        for (int i = 0; i < n; i++){
            fx[i] = ((int)Math.round(x + Math.cos(temp * i + a) * r));
            fy[i] = ((int)Math.round(y + Math.sin(temp * i + a) * r));
        }

        xs = fx;
        ys = fy;
        size = xs.length;
    }

    /**
     * Retrieves the X coordinate of the center of the polygon
     *
     * @return x coordinate of the center
     */
    public int getX() {
        return x;
    }

    /**
     * Retrieves the Y coordinate of the center of the polygon
     *
     * @return y coordinate of the center
     */
    public int getY() {
        return y;
    }

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

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

    /**
     * Calculated the perimeter of this polygon
     *
     * @return perimeter
     */
    public double perimeter() {
        return n * 2 * r * Math.sin(Math.PI / n);
    }

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

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

    /**
     * Tells if a point is contained in the polygon
     *
     * @param xx X coordinate of the point
     * @param yy Y coordinate of the point
     * @return {@code true} if the point is contained and {@code false}
     * otherwise
     */
    public boolean contains(final int xx, final int yy) {
        if (xx == x && yy == y) {
            return true;
        }

        //Quick check (point vs outter circle)
        if (Math.hypot(xx - x, xx - y) > r) {
            return false;
        }

        //@TODO Quick check (point vs incircle) would be a nice addition...
        mutex.lock();
        try {
            final int m = n - 1;
            int j;
            for (int i = 0; i <= m; i++){
                j = i == m ? 0 : i + 1;

                if (triangleContains(xx, yy, xs[i], ys[i], xs[j], ys[j], x, y)){
                    return true;
                }
            }
        } finally {
            mutex.unlock();
        }

        return false;
    }

    private boolean triangleContains(
            int xx, int yy,
            int x1, int y1,
            int x2, int y2,
            int x3, int y3)
    {
        //https://stackoverflow.com/questions/2049582/
        //how-to-determine-if-a-point-is-in-a-2d-triangle#2049593
        final int asx = xx - x1;
        final int asy = yy - y1;
        final boolean sab = (x2 - x1) * asy - (y2 - y1) * asx > 0;
        final boolean co1 = (x3 - x1) * asy - (y3 - y1) * asx > 0;
        final boolean co2 = (x3 - x2) * (yy - y2) - (y3 - y2) * (xx - x2) > 0;
        return !((co1 == sab) || (co2 != sab));
    }

    /**
     * Rotates the current {@code GRegPoly} a given angle
     *
     * @param angle angle in degrees
     */
    public void rotate(final double angle) {
        radRotate(Math.toRadians(angle));
    }

    /**
     * Rotates the current {@code GRegPoly} a given angle
     *
     * @param angle angle in radians
     */
    public void radRotate(final double angle) {
        a += angle;
        a %= 2 * Math.PI;
        calc();
    }

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

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

    @Override
    public int hashCode() {
        int hash = super.hashCode();
        hash = 53 * hash + x;
        hash = 53 * hash + y;
        hash = 53 * hash + r;
        hash = 53 * hash + n;
        hash = 53 * hash + (int) (Double.doubleToLongBits(a)
                               ^ (Double.doubleToLongBits(a) >>> 32));
        return hash;
    }

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

        final GRegPoly other = (GRegPoly) obj;
        return !(
            x != other.x |
            y != other.y |
            r != other.r |
            n != other.n |
            a != other.a
        );
    }

}