org.destinationsol.common.SolMath.java Source code

Java tutorial

Introduction

Here is the source code for org.destinationsol.common.SolMath.java

Source

/*
 * Copyright 2015 MovingBlocks
 *
 * 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 org.destinationsol.common;

import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.math.MathUtils;
import com.badlogic.gdx.math.Vector2;
import com.badlogic.gdx.utils.JsonValue;
import com.badlogic.gdx.utils.Pool;
import org.destinationsol.Const;

import java.util.ArrayList;
import java.util.List;

/**
 * A class with helpful mathematical functions
 */
public class SolMath {
    public static final float PI = MathUtils.PI;
    public static float radDeg = MathUtils.radDeg;
    public static float degRad = MathUtils.degRad;
    public static Pool<Vector2> vs = new Pool<Vector2>() {
        @Override
        protected Vector2 newObject() {
            return new Vector2();
        }
    };
    public static int VECTORS_TAKEN;
    public static Vector2 tmp = new Vector2();

    public static int toInt(boolean b) {
        return b ? 1 : -1;
    }

    public static float abs(float a) {
        return a < 0f ? -a : a;
    }

    /**
     * Use this method when you want some value to gradually transform to the desired value.
     * @param src actual value
     * @param dst desired value
     * @param spd speed of change
     * @return a new value that is closer to the desired one
     */
    public static float approach(float src, float dst, float spd) {
        if (dst - spd <= src && src <= dst + spd)
            return dst;
        return src < dst ? src + spd : src - spd;
    }

    /**
     * Same as {@code approach()}, but in the radial coordinates. That is, 170 degrees would move to -10 by growing through 175 degrees
     */
    public static float approachAngle(float src, float dst, float spd) {
        float diff = norm(dst - src);
        float da = abs(diff);
        if (da <= spd)
            return dst;
        return diff > 0 ? src + spd : src - spd;
    }

    /**
     * Normalizes the angle
     */
    @Norm
    public static float norm(float a) {
        if (a != a)
            throw new AssertionError("normalizing NaN angle");
        while (a <= -180)
            a += 360;
        while (a > 180)
            a -= 360;
        return a;
    }

    /**
     * Returns a random float v such that -minMax <= v && v < minMax
     * @param minMax a positive value
     */
    public static float rnd(float minMax) {
        return rnd(-minMax, minMax);
    }

    /**
     * Returns a random float v such that min <= v && v < max. Min shouldn't equal to max
     */
    public static float rnd(float min, float max) {
        float result = max;
        if (min == max) {
            Gdx.app.log("SolMath",
                    "rnd was called with bad parameters! Min " + min + " matches max " + max + ", accepting max.");
            Gdx.app.log("SolMath", "Please review appropriate code in the stack dump:");
            for (StackTraceElement ste : Thread.currentThread().getStackTrace()) {
                Gdx.app.log("SolMath", ste.toString());
            }

            return result;
        }
        while (result == max) {
            result = MathUtils.random(min, max);
        }
        return result;
    }

    /**
     * Returns a random int v such that 0 <= v && v < max.
     * @param max a positive value
     */
    public static int intRnd(int max) {
        return intRnd(0f, max);
    }

    /**
     * Returns a random int v such that max*perc <= v && v < max.
     * @param perc should be >= 0 and < 1
     * @param max a positive value
     */
    public static int intRnd(float perc, int max) {
        int r = max;
        int min = (int) (max * perc);
        if (min == max)
            throw new AssertionError("intRnd min equals max " + min);
        while (r == max)
            r = MathUtils.random(min, max);
        return r;
    }

    /**
     * Returns a random int v such that min <= v && v <= max
     */
    public static int intRnd(int min, int max) {
        return MathUtils.random(min, max);
    }

    /**
     * Clamps the value (returns min if val < min, max if max < val, val otherwise)
     */
    public static float clamp(float val, float min, float max) {
        return MathUtils.clamp(val, min, max);
    }

    public static float clamp(float val) {
        return clamp(val, 0, 1);
    }

    /**
     * Modifies the given vector so it has the given angle and length. The resulting vector angle may slightly differ from a given one.
     */
    public static void fromAl(Vector2 vec, float angle, float len) {
        fromAl(vec, angle, len, false);
    }

    /**
     * Modifies the given vector so it has the given angle and length. If not {@code precice}, the resulting vector angle may slightly differ from a given one, in the cost of performance.
     */
    public static void fromAl(Vector2 vec, float angle, float len, boolean precise) {
        vec.set(len, 0);
        rotate(vec, angle, precise);
    }

    /**
     * Builds a bound vector with the given angle and length.
     */
    @Bound
    public static Vector2 fromAl(float angle, float len) {
        return fromAl(angle, len, false);
    }

    /**
     * Builds a bound vector with the given angle and length. If not {@code precice}, the resulting vector angle may slightly differ from a given one, in the cost of performance.
     */
    @Bound
    public static Vector2 fromAl(float angle, float len, boolean precise) {
        Vector2 vec = getVec();
        fromAl(vec, angle, len, precise);
        return vec;
    }

    /**
     * @return a new bound copy of src
     */
    @Bound
    public static Vector2 getVec(Vector2 src) {
        return getVec(src.x, src.y);
    }

    /**
     * @return a new bound vector
     */
    @Bound
    public static Vector2 getVec(float x, float y) {
        VECTORS_TAKEN++;
        Vector2 v = vs.obtain();
        v.set(x, y);
        return v;
    }

    /**
     * frees the bound vector. Don't use this vector after freeing!
     */
    public static void free(Vector2 v) {
        VECTORS_TAKEN--;
        vs.free(v);
    }

    /**
     * @return a new bound vector
     */
    @Bound
    public static Vector2 getVec() {
        return getVec(0, 0);
    }

    /**
     * generates a random number between 0 and 1 and returns true if it is less than v, false otherwise
     */
    public static boolean test(float v) {
        return rnd(0, 1) < v;
    }

    /**
     * Nicely logs a vector into the console
     */
    public static void log(Vector2 pos) {
        System.out.println((int) pos.x + " " + (int) pos.y);
    }

    /**
     * @return approximate cos of a degrees
     */
    public static float cos(float a) {
        return MathUtils.cosDeg(a);
    }

    /**
     * @return approximate sin of a degrees
     */
    public static float sin(float a) {
        return MathUtils.sinDeg(a);
    }

    /**
     * nicely logs a float into the console
     */
    public static void log(float f) {
        System.out.println(f);
    }

    /**
     * converts relPos (a position in a relative coordinate system defined by baseAngle and basePos) to the absolute position
     */
    @Bound
    public static Vector2 toWorld(Vector2 relPos, float baseAngle, Vector2 basePos) {
        Vector2 v = getVec();
        toWorld(v, relPos, baseAngle, basePos, false);
        return v;
    }

    /**
     * converts relPos (a position in a relative coordinate system defined by baseAngle and basePos) to the absolute position (which is written to pos)
     */
    public static void toWorld(Vector2 pos, Vector2 relPos, float baseAngle, Vector2 basePos, boolean precise) {
        pos.set(relPos);
        rotate(pos, baseAngle, precise);
        pos.add(basePos);
    }

    /**
     * converts pos (a position in an absolute coordinate system) to the position in the relative system of coordinates (defined by baseAngle and basePos)
     */
    @Bound
    public static Vector2 toRel(Vector2 pos, float baseAngle, Vector2 basePos) {
        Vector2 v = getVec();
        toRel(pos, v, baseAngle, basePos);
        return v;
    }

    /**
     * converts pos (a position in an absolute coordinate system) to the position in the relative system of coordinates
     * (defined by baseAngle and basePos) (which is written to relPos)
     */
    public static void toRel(Vector2 pos, Vector2 relPos, float baseAngle, Vector2 basePos) {
        relPos.set(pos);
        relPos.sub(basePos);
        rotate(relPos, -baseAngle);
    }

    /**
     * rotates a vector to an angle. if not precise, works faster, but the actual angle might slightly differ from the given one
     */
    public static void rotate(Vector2 v, float angle, boolean precise) {
        if (precise) {
            v.rotate(angle);
        } else {
            float cos = cos(angle);
            float sin = sin(angle);
            float newX = v.x * cos - v.y * sin;
            float newY = v.x * sin + v.y * cos;
            v.x = newX;
            v.y = newY;
        }
    }

    /**
     * rotates a vector to an angle. The actual angle might slightly differ from the given one
     */
    public static void rotate(Vector2 v, float angle) {
        rotate(v, angle, false);
    }

    /**
     * @return a new bound vector that is a substraction (to - from)
     */
    @Bound
    public static Vector2 distVec(Vector2 from, Vector2 to) {
        Vector2 v = getVec(to);
        v.sub(from);
        return v;
    }

    /**
     * @return a length of a projection of a vector onto a line defined by angle
     */
    public static float project(Vector2 v, float angle) {
        float angleDiff = angle - SolMath.angle(v);
        return v.len() * cos(angleDiff);
    }

    public static float sqrt(float v) {
        return (float) Math.sqrt(v);
    }

    /**
     * @return approximate angle between 2 vectors. may be negative.
     */
    public static float angle(Vector2 from, Vector2 to) {
        return angle(from, to, false);
    }

    /**
     * @return angle between 2 vectors. may be negative. if not precise, approximation is returned
     */
    public static float angle(Vector2 from, Vector2 to, boolean precise) {
        tmp.set(to);
        tmp.sub(from);
        return angle(tmp, precise);
    }

    /**
     * @return angle of a vector. if not precise, approximation is returned.
     * (1, 0) is right and 0 degrees
     * (0, 1) is down and 90 degrees
     * (-1, 0) is left and 180 degrees
     * (0, -1) is up and -90 degrees
     */
    public static float angle(Vector2 v, boolean precise) {
        if (precise)
            return v.angle();
        else
            return MathUtils.atan2(v.y, v.x) * radDeg;
    }

    /**
     * @return angle of a vector. approximation is returned.
     */
    public static float angle(Vector2 v) {
        return angle(v, false);
    }

    public static float arcSin(float val) {
        return (float) Math.asin(val) * radDeg;
    }

    public static float angularWidthOfSphere(float radius, float dist) {
        return arcSin(radius / dist);
    }

    public static float arcToAngle(float hordeLen, float radius) {
        return 180 * hordeLen / (PI * radius);
    }

    public static float hypotenuse(float a, float b) {
        return sqrt(a * a + b * b);
    }

    public static float windowCenter(float val, float window) {
        float winNr = (int) (val / window);
        winNr += .5f * toInt(val > 0);
        return winNr * window;
    }

    public static boolean isAngleBetween(@Norm float a, @Norm float b, @Norm float x) {
        if (a <= b)
            return a <= x && x < b;
        return a <= x || x < b;
    }

    public static float angleDiff(float a, float b) {
        return abs(norm(a - b));
    }

    public static float angleToArc(float angle, float r) {
        return angle / 180 * PI * r;
    }

    /**
     * @return a random element of a list
     */
    public static <T> T elemRnd(List<T> list) {
        int idx = intRnd(list.size());
        return list.get(idx);
    }

    public static void checkVectorsTaken(Object o) {
        if (SolMath.VECTORS_TAKEN != 0)
            throw new AssertionError("vectors " + SolMath.VECTORS_TAKEN + ", blame on " + o);
    }

    /**
     * @return solution of a quadratic equation. if 2 solutions possible, the greater is returned.
     */
    public static float genQuad(float a, float b, float c) {
        if (a == 0)
            return genLin(b, c);
        float disc = b * b - 4 * a * c;
        if (disc < 0)
            return Float.NaN;
        if (disc == 0)
            return -b / 2 / a;
        float dsq = sqrt(disc);
        float x1 = (-b - dsq) / 2 / a;
        float x2 = (-b + dsq) / 2 / a;
        if (x1 < 0) {
            return x2 < 0 ? Float.NaN : x2;
        }
        if (x2 < 0)
            return x1;
        return x1 < x2 ? x1 : x2;
    }

    private static float genLin(float b, float c) {
        if (b == 0)
            return c == 0 ? 0 : Float.NaN;
        float res = -c / b;
        return res < 0 ? Float.NaN : res;
    }

    public static Vector2 readV2(JsonValue v, String name) {
        return readV2(v.getString(name));
    }

    public static Vector2 readV2(String encoded) {
        String[] parts = encoded.split(" ");
        float x = Float.parseFloat(parts[0]);
        float y = Float.parseFloat(parts[1]);
        return new Vector2(x, y);
    }

    public static ArrayList<Vector2> readV2List(JsonValue parentNode, String name) {
        ArrayList<Vector2> res = new ArrayList<Vector2>();
        JsonValue listNode = parentNode.get(name);
        if (listNode == null)
            return res;
        for (JsonValue vNode : listNode) {
            Vector2 vec = readV2(vNode.asString());
            res.add(vec);
        }
        return res;
    }

    public static boolean canAccelerate(float accAngle, Vector2 spd) {
        return spd.len() < Const.MAX_MOVE_SPD || angleDiff(angle(spd), accAngle) > 90;
    }

    public static String nice(float v) {
        int i = (int) (v * 10);
        int whole = i / 10;
        int dec = i - 10 * whole;
        if (dec < 0)
            dec = -dec;
        return whole + "." + dec;
    }
}