net.dermetfan.utils.libgdx.math.GeometryUtils.java Source code

Java tutorial

Introduction

Here is the source code for net.dermetfan.utils.libgdx.math.GeometryUtils.java

Source

/** Copyright 2013 Robin Stumm (serverkorken@googlemail.com, http://dermetfan.net/)
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License. */

package net.dermetfan.utils.libgdx.math;

import static com.badlogic.gdx.math.MathUtils.cos;
import static com.badlogic.gdx.math.MathUtils.sin;
import static net.dermetfan.utils.math.MathUtils.amplitude;
import static net.dermetfan.utils.math.MathUtils.max;
import static net.dermetfan.utils.math.MathUtils.min;
import net.dermetfan.utils.ArrayUtils;

import com.badlogic.gdx.graphics.OrthographicCamera;
import com.badlogic.gdx.math.EarClippingTriangulator;
import com.badlogic.gdx.math.Polygon;
import com.badlogic.gdx.math.Rectangle;
import com.badlogic.gdx.math.Vector2;
import com.badlogic.gdx.utils.Array;
import com.badlogic.gdx.utils.ShortArray;

/** provides some useful methods for geometric calculations
 *  @author dermetfan */
public abstract class GeometryUtils {

    /** a {@link Vector2} for temporary usage */
    private static final Vector2 vec2_0 = new Vector2(), vec2_1 = new Vector2();

    /** a temporary array */
    private static Vector2[] tmpVecArr;

    /** a temporary array */
    private static float[] tmpFloatArr;

    /** @return a Vector2 representing the size of a rectangle containing all given vertices */
    public static Vector2 size(Vector2[] vertices, Vector2 output) {
        return output.set(width(vertices), height(vertices));
    }

    /** @see #size(Vector2[], Vector2) */
    public static Vector2 size(Vector2[] vertices) {
        return size(vertices, vec2_0);
    }

    /** @return the amplitude from the min x vertice to the max x vertice */
    public static float width(Vector2[] vertices) {
        return amplitude(filterX(vertices));
    }

    /** @return the amplitude from the min y vertice to the max y vertice */
    public static float height(Vector2[] vertices) {
        return amplitude(filterY(vertices));
    }

    /** @see #width(Vector2[]) */
    public static float width(float[] vertices) {
        return amplitude(filterX(vertices));
    }

    /** @see #height(Vector2[]) */
    public static float height(float[] vertices) {
        return amplitude(filterY(vertices));
    }

    /** @return the amplitude of the min z vertice to the max z vertice */
    public static float depth(float[] vertices) {
        return amplitude(filterZ(vertices));
    }

    /** @return the x values of the given vertices */
    public static float[] filterX(Vector2[] vertices, float[] output) {
        if (output == null || output.length != vertices.length)
            output = new float[vertices.length];
        for (int i = 0; i < output.length; i++)
            output[i] = vertices[i].x;
        return output;
    }

    /** @see #filterX(Vector2[], float[]) */
    public static float[] filterX(Vector2[] vertices) {
        return filterX(vertices, tmpFloatArr);
    }

    /** @param vertices the vertices in [x, y, x, y, ...] order
     *  @see #filterX(Vector2[]) */
    public static float[] filterX(float[] vertices, float[] output) {
        return ArrayUtils.select(vertices, -1, 2, output);
    }

    /** @see #filterX(float[], float[]) */
    public static float[] filterX(float[] vertices) {
        return filterX(vertices, tmpFloatArr);
    }

    /** @param vertices the vertices in [x, y, z, x, y, z, ...] order
     *  @see #filterX(float[], float[]) */
    public static float[] filterX3D(float[] vertices, float[] output) {
        return ArrayUtils.select(vertices, -2, 3, output);
    }

    /** @see #filterX3D(float[], float[]) */
    public static float[] filterX3D(float[] vertices) {
        return filterX3D(vertices, tmpFloatArr);
    }

    /** @return the y values of the given vertices */
    public static float[] filterY(Vector2[] vertices, float[] output) {
        if (output == null || output.length != vertices.length)
            output = new float[vertices.length];
        for (int i = 0; i < output.length; i++)
            output[i] = vertices[i].y;
        return output;
    }

    /** @see #filterY(Vector2[], float[]) */
    public static float[] filterY(Vector2[] vertices) {
        return filterY(vertices, tmpFloatArr);
    }

    /** @see #filterY(Vector2[], float[])
     *  @see #filterX(float[], float[])*/
    public static float[] filterY(float[] vertices, float[] output) {
        return ArrayUtils.select(vertices, 2, output);
    }

    /** @see #filterY(float[], float[]) */
    public static float[] filterY(float[] vertices) {
        return filterY(vertices, tmpFloatArr);
    }

    /** @see #filterY(float[], float[])
     *  @see #filterX3D(float[], float[]) */
    public static float[] filterY3D(float[] vertices, float[] output) {
        return ArrayUtils.select(vertices, -4, 3, output);
    }

    /** @see #filterY3D(float[], float[]) */
    public static float[] filterY3D(float[] vertices) {
        return filterY3D(vertices, tmpFloatArr);
    }

    /** @see #filterX(Vector2[], float[])
     *  @see #filterX3D(float[], float[]) */
    public static float[] filterZ(float[] vertices, float[] output) {
        return ArrayUtils.select(vertices, 3, output);
    }

    /** @see #filterZ(float[], float[]) */
    public static float[] filterZ(float[] vertices) {
        return filterZ(vertices, tmpFloatArr);
    }

    /** @see #filterX3D(float[]) */
    public static float[] filterW(float[] vertices, float[] output) {
        return ArrayUtils.select(vertices, 4, output);
    }

    /** @see #filterW(float[], float[]) */
    public static float[] filterW(float[] vertices) {
        return filterW(vertices, tmpFloatArr);
    }

    /** @return the min x value of the given vertices */
    public static float minX(Vector2[] vertices) {
        return min(filterX(vertices));
    }

    /** @return the min y value of the given vertices */
    public static float minY(Vector2[] vertices) {
        return min(filterY(vertices));
    }

    /** @return the max x value of the given vertices */
    public static float maxX(Vector2[] vertices) {
        return max(filterX(vertices));
    }

    /** @return the max y value of the given vertices */
    public static float maxY(Vector2[] vertices) {
        return max(filterY(vertices));
    }

    /** @see #minX(Vector2[]) */
    public static float minX(float[] vertices) {
        return min(filterX(vertices));
    }

    /** @see #minY(Vector2[]) */
    public static float minY(float[] vertices) {
        return min(filterY(vertices));
    }

    /** @see #maxX(Vector2[]) */
    public static float maxX(float[] vertices) {
        return max(filterX(vertices));
    }

    /** @see #maxY(Vector2[]) */
    public static float maxY(float[] vertices) {
        return max(filterY(vertices));
    }

    /** rotates {@code point} by {@code radians} around [0:0] (local rotation)
     *  @param point the point to rotate
     *  @param radians the rotation
     *  @return the given {@code point} rotated by {@code radians} */
    public static Vector2 rotate(Vector2 point, float radians) {
        float xx = point.x, xy = point.y, yx = point.x, yy = point.y;
        xx = xx * cos(radians) - xy * sin(radians);
        yy = yx * sin(radians) + yy * cos(radians);
        return point.set(xx, yy);
    }

    /** rotates a {@code point} around {@code center}
     *  @param point the point to rotate
     *  @param center the point around which to rotate {@code point}
     *  @param radians the rotation
     *  @return the given {@code point} rotated around {@code center} by {@code radians}
     *  @see #rotate(Vector2, float) */
    public static Vector2 rotate(Vector2 point, Vector2 center, float radians) {
        return rotate(point, radians).add(center);
    }

    /** @param vector2s the Vector2[] to convert to a float[]
     *  @return the float[] converted from the given Vector2[] */
    public static float[] toFloatArray(Vector2[] vector2s, float[] output) {
        if (output == null || output.length != vector2s.length * 2)
            output = new float[vector2s.length * 2];

        for (int i = 0, vi = -1; i < output.length; i++)
            if (i % 2 == 0)
                output[i] = vector2s[++vi].x;
            else
                output[i] = vector2s[vi].y;

        return output;
    }

    /** @see #toFloatArray(Vector2[], float[]) */
    public static float[] toFloatArray(Vector2[] vector2s) {
        return toFloatArray(vector2s, tmpFloatArr);
    }

    /** @param floats the float[] to convert to a Vector2[]
     *  @return the Vector2[] converted from the given float[] */
    public static Vector2[] toVector2Array(float[] floats, Vector2[] output) {
        if (floats.length % 2 != 0)
            throw new IllegalArgumentException(
                    "the float array's length is not dividable by two, so it won't make up a Vector2 array: "
                            + floats.length);

        if (output == null || output.length != floats.length / 2) {
            output = new Vector2[floats.length / 2];
            for (int i = 0; i < output.length; i++)
                output[i] = new Vector2();
        }

        for (int i = 0, fi = -1; i < output.length; i++)
            output[i].set(floats[++fi], floats[++fi]);

        return output;
    }

    /** @see #toVector2Array(float[], Vector2[]) */
    public static Vector2[] toVector2Array(float[] floats) {
        return toVector2Array(floats, tmpVecArr);
    }

    /** @param vertexCount the number of vertices for each {@link Polygon}
     *  @see #toPolygonArray(Vector2[], int[]) */
    public static Polygon[] toPolygonArray(Vector2[] vertices, int vertexCount) {
        int[] vertexCounts = new int[vertices.length / vertexCount];
        for (int i = 0; i < vertexCounts.length; i++)
            vertexCounts[i] = vertexCount;
        return toPolygonArray(vertices, vertexCounts);
    }

    /** @param vertices the vertices which should be split into a {@link Polygon} array
     *  @param vertexCounts the number of vertices of each {@link Polygon}
     *  @return the {@link Polygon} array extracted from the vertices */
    public static Polygon[] toPolygonArray(Vector2[] vertices, int[] vertexCounts) {
        Polygon[] polygons = new Polygon[vertexCounts.length];

        for (int i = 0, vertice = -1; i < polygons.length; i++) {
            tmpVecArr = new Vector2[vertexCounts[i]];
            for (int i2 = 0; i2 < tmpVecArr.length; i2++)
                tmpVecArr[i2] = vertices[++vertice];
            polygons[i] = new Polygon(toFloatArray(tmpVecArr));
        }

        return polygons;
    }

    /** @param polygon the polygon, assumed to be simple
     *  @return if the vertices are in clockwise order */
    public static boolean areVerticesClockwise(Polygon polygon) {
        return polygon.area() < 0;
    }

    /** @see #areVerticesClockwise(Polygon) */
    public static boolean areVerticesClockwise(float[] vertices) {
        if (vertices.length <= 4)
            return true;
        return area(vertices) < 0;
    }

    /** @see #isConvex(Vector2[]) */
    public static boolean isConvex(float[] vertices) {
        return isConvex(toVector2Array(vertices));
    }

    /** @see #isConvex(Vector2[]) */
    public static boolean isConvex(Polygon polygon) {
        return isConvex(polygon.getVertices());
    }

    /** @return the area of the polygon */
    public static float area(float[] vertices) {
        // from com.badlogic.gdx.math.Polygon#area()
        float area = 0;

        int x1, y1, x2, y2;
        for (int i = 0; i < vertices.length; i += 2) {
            x1 = i;
            y1 = i + 1;
            x2 = (i + 2) % vertices.length;
            y2 = (i + 3) % vertices.length;

            area += vertices[x1] * vertices[y2];
            area -= vertices[x2] * vertices[y1];
        }

        return area /= 2;
    }

    /** @param vertices the vertices of the polygon to examine for convexity
     *  @return if the polygon is convex */
    public static boolean isConvex(Vector2[] vertices) {
        // http://www.sunshine2k.de/coding/java/Polygon/Convex/polygon.htm
        Vector2 p, v = vec2_1, u;
        float res = 0;
        for (int i = 0; i < vertices.length; i++) {
            p = vertices[i];
            vec2_0.set(vertices[(i + 1) % vertices.length]);
            v.x = vec2_0.x - p.x;
            v.y = vec2_0.y - p.y;
            u = vertices[(i + 2) % vertices.length];

            if (i == 0) // in first loop direction is unknown, so save it in res
                res = u.x * v.y - u.y * v.x + v.x * p.y - v.y * p.x;
            else {
                float newres = u.x * v.y - u.y * v.x + v.x * p.y - v.y * p.x;
                if (newres > 0 && res < 0 || newres < 0 && res > 0)
                    return false;
            }
        }

        return true;
    }

    /** @param concave the concave polygon to triangulate
     *  @return an array of triangles representing the given concave polygon
     *  @see EarClippingTriangulator#computeTriangles(float[]) */
    public static Polygon[] triangulate(Polygon concave) {
        Vector2[] polygonVertices = toVector2Array(concave.getTransformedVertices());
        ShortArray indices = new EarClippingTriangulator().computeTriangles(toFloatArray(polygonVertices));
        Vector2[] vertices = new Vector2[indices.size];
        for (int i = 0; i < indices.size; i++)
            vertices[i] = polygonVertices[indices.get(i)];
        return toPolygonArray(vertices, 3);
    }

    /** @param concave the concave polygon to to decompose 
     *  @return an array of convex polygons representing the given concave polygon
     *  @see BayazitDecomposer#convexPartition(Array) */
    public static Polygon[] decompose(Polygon concave) {
        Array<Array<Vector2>> convexPolys = BayazitDecomposer
                .convexPartition(new Array<Vector2>(toVector2Array(concave.getTransformedVertices())));
        Polygon[] convexPolygons = new Polygon[convexPolys.size];
        for (int i = 0; i < convexPolygons.length; i++)
            convexPolygons[i] = new Polygon(toFloatArray((Vector2[]) convexPolys.get(i).toArray(Vector2.class)));
        return convexPolygons;
    }

    /** Keeps the first described rectangle in the second described rectangle. If the second rectangle is smaller than the first one, the first will be centered on the second one.
     *  @param position the position of the first rectangle
     *  @param width the width of the first rectangle
     *  @param height the height of the first rectangle
     *  @param x2 the x of the second rectangle
     *  @param y2 the y of the second rectangle
     *  @param width2 the width of the second rectangle
     *  @param height2 the height of the second rectangle
     *  @return the position of the first rectangle */
    public static Vector2 keepWithin(Vector2 position, float width, float height, float x2, float y2, float width2,
            float height2) {
        if (width2 < width)
            position.x = x2 + width2 / 2 - width / 2;
        else if (position.x < x2)
            position.x = x2;
        else if (position.x + width > x2 + width2)
            position.x = x2 + width2 - width;
        if (height2 < height)
            position.y = y2 + height2 / 2 - height / 2;
        else if (position.y < y2)
            position.y = y2;
        else if (position.y + height > y2 + height2)
            position.y = y2 + height2 - height;
        return position;
    }

    /** @see #keepWithin(Vector2, float, float, float, float, float, float) */
    public static Vector2 keepWithin(float x, float y, float width, float height, float rectX, float rectY,
            float rectWidth, float rectHeight) {
        return keepWithin(vec2_0.set(x, y), width, height, rectX, rectY, rectWidth, rectHeight);
    }

    /** @see #keepWithin(float, float, float, float, float, float, float, float) */
    public static Rectangle keepWithin(Rectangle rect, Rectangle other) {
        return rect.setPosition(
                keepWithin(rect.x, rect.y, rect.width, rect.height, other.x, other.y, other.width, other.height));
    }

    /** Keeps the given {@link OrthographicCamera} in the given rectangle. If the rectangle is smaller than the camera viewport times the camera zoom, the camera will be centered on the rectangle.<br/>
     *  Note that the camera will not be {@link OrthographicCamera#update() updated}.
     *  @param camera the camera to keep in the rectangle
     *  @see #keepWithin(float, float, float, float, float, float, float, float) */
    public static void keepWithin(OrthographicCamera camera, float x, float y, float width, float height) {
        vec2_0.set(keepWithin(camera.position.x - camera.viewportWidth / 2 * camera.zoom,
                camera.position.y - camera.viewportHeight / 2 * camera.zoom, camera.viewportWidth * camera.zoom,
                camera.viewportHeight * camera.zoom, x, y, width, height));
        camera.position.x = vec2_0.x + camera.viewportWidth / 2 * camera.zoom;
        camera.position.y = vec2_0.y + camera.viewportHeight / 2 * camera.zoom;
    }

}