From 1b5a4bf369f1566e81a16b6f5cdacd5a0cfee76a Mon Sep 17 00:00:00 2001 From: Eiren Rain Date: Tue, 13 Sep 2022 03:13:06 +0300 Subject: [PATCH] Remove Slime Java Commons submodule, (#236) --- .gitmodules | 3 - build.gradle | 1 - java/com/jme3/math/ColorRGBA.java | 665 +++++ java/com/jme3/math/FastMath.java | 1072 ++++++++ java/com/jme3/math/Matrix3f.java | 1362 ++++++++++ java/com/jme3/math/Matrix4f.java | 2277 +++++++++++++++++ java/com/jme3/math/Quaternion.java | 1721 +++++++++++++ java/com/jme3/math/Transform.java | 399 +++ java/com/jme3/math/Vector2f.java | 718 ++++++ java/com/jme3/math/Vector3f.java | 1101 ++++++++ java/com/jme3/math/Vector4f.java | 971 +++++++ java/com/jme3/system/NanoTimer.java | 105 + java/com/jme3/system/Timer.java | 93 + java/com/jme3/util/TempVars.java | 178 ++ java/io/eiren/math/FloatMath.java | 672 +++++ java/io/eiren/math/Vector3d.java | 257 ++ java/io/eiren/util/BufferedTimer.java | 130 + java/io/eiren/util/MacOSX.java | 41 + java/io/eiren/util/OperatingSystem.java | 47 + java/io/eiren/util/StringUtils.java | 35 + java/io/eiren/util/Util.java | 176 ++ java/io/eiren/util/ann/AWTThread.java | 10 + java/io/eiren/util/ann/DebugSwitch.java | 9 + java/io/eiren/util/ann/NativeUnsafe.java | 19 + java/io/eiren/util/ann/Synchronize.java | 30 + java/io/eiren/util/ann/ThreadSafe.java | 27 + java/io/eiren/util/ann/ThreadSafeSingle.java | 17 + java/io/eiren/util/ann/ThreadSecure.java | 22 + java/io/eiren/util/ann/Transient.java | 39 + java/io/eiren/util/collections/FastList.java | 547 ++++ .../collections/RemoveAtSwapFastList.java | 41 + .../util/collections/RemoveAtSwapList.java | 11 + .../util/collections/ResettableIterator.java | 17 + .../eiren/util/collections/SkipIterator.java | 16 + java/io/eiren/util/logging/DefaultGLog.java | 137 + .../eiren/util/logging/FileLogFormatter.java | 58 + java/io/eiren/util/logging/IGLog.java | 48 + java/io/eiren/util/logging/LogManager.java | 136 + .../util/logging/LoggerOutputStream.java | 52 + .../io/eiren/util/logging/LoggerRecorder.java | 23 + .../logging/PreciseConsoleLogFormatter.java | 32 + .../logging/ShortConsoleLogFormatter.java | 58 + java/io/eiren/yaml/YamlException.java | 23 + java/io/eiren/yaml/YamlFile.java | 149 ++ java/io/eiren/yaml/YamlNode.java | 642 +++++ java/org/json/JSONArray.java | 880 +++++++ java/org/json/JSONException.java | 26 + java/org/json/JSONObject.java | 1590 ++++++++++++ java/org/json/JSONString.java | 19 + java/org/json/JSONTokener.java | 434 ++++ java/org/json/JSONUtil.java | 57 + slime-java-commons | 1 - src/main/java/com/jme3/math/FastMath.java | 1072 ++++++++ src/main/java/com/jme3/math/Matrix3f.java | 1360 ++++++++++ src/main/java/com/jme3/math/Matrix4f.java | 2275 ++++++++++++++++ src/main/java/com/jme3/math/Quaternion.java | 1712 +++++++++++++ src/main/java/com/jme3/math/TempVars.java | 147 ++ src/main/java/com/jme3/math/Transform.java | 399 +++ src/main/java/com/jme3/math/Vector2f.java | 718 ++++++ src/main/java/com/jme3/math/Vector3f.java | 1101 ++++++++ src/main/java/com/jme3/math/Vector4f.java | 971 +++++++ src/main/java/com/jme3/system/NanoTimer.java | 105 + src/main/java/com/jme3/system/Timer.java | 93 + src/main/java/dev/slimevr/Main.java | 1 - src/main/java/io/eiren/math/FloatMath.java | 672 +++++ src/main/java/io/eiren/math/Vector3d.java | 257 ++ .../java/io/eiren/util/BufferedTimer.java | 130 + src/main/java/io/eiren/util/MacOSX.java | 41 + .../java/io/eiren/util/OperatingSystem.java | 47 + src/main/java/io/eiren/util/StringUtils.java | 35 + src/main/java/io/eiren/util/Util.java | 176 ++ .../java/io/eiren/util/ann/AWTThread.java | 10 + .../java/io/eiren/util/ann/DebugSwitch.java | 9 + .../java/io/eiren/util/ann/NativeUnsafe.java | 19 + .../java/io/eiren/util/ann/Synchronize.java | 30 + .../java/io/eiren/util/ann/ThreadSafe.java | 27 + .../io/eiren/util/ann/ThreadSafeSingle.java | 17 + .../java/io/eiren/util/ann/ThreadSecure.java | 22 + .../java/io/eiren/util/ann/Transient.java | 39 + .../io/eiren/util/collections/FastList.java | 547 ++++ .../collections/RemoveAtSwapFastList.java | 41 + .../util/collections/RemoveAtSwapList.java | 11 + .../util/collections/ResettableIterator.java | 17 + .../eiren/util/collections/SkipIterator.java | 16 + .../io/eiren/util/logging/DefaultGLog.java | 137 + .../eiren/util/logging/FileLogFormatter.java | 58 + .../java/io/eiren/util/logging/IGLog.java | 48 + .../io/eiren/util/logging/LogManager.java | 136 + .../util/logging/LoggerOutputStream.java | 52 + .../io/eiren/util/logging/LoggerRecorder.java | 23 + .../logging/PreciseConsoleLogFormatter.java | 32 + .../logging/ShortConsoleLogFormatter.java | 58 + src/main/java/org/json/JSONArray.java | 880 +++++++ src/main/java/org/json/JSONException.java | 26 + src/main/java/org/json/JSONObject.java | 1590 ++++++++++++ src/main/java/org/json/JSONString.java | 19 + src/main/java/org/json/JSONTokener.java | 434 ++++ src/main/java/org/json/JSONUtil.java | 57 + 98 files changed, 32855 insertions(+), 6 deletions(-) create mode 100644 java/com/jme3/math/ColorRGBA.java create mode 100644 java/com/jme3/math/FastMath.java create mode 100644 java/com/jme3/math/Matrix3f.java create mode 100644 java/com/jme3/math/Matrix4f.java create mode 100644 java/com/jme3/math/Quaternion.java create mode 100644 java/com/jme3/math/Transform.java create mode 100644 java/com/jme3/math/Vector2f.java create mode 100644 java/com/jme3/math/Vector3f.java create mode 100644 java/com/jme3/math/Vector4f.java create mode 100644 java/com/jme3/system/NanoTimer.java create mode 100644 java/com/jme3/system/Timer.java create mode 100644 java/com/jme3/util/TempVars.java create mode 100644 java/io/eiren/math/FloatMath.java create mode 100644 java/io/eiren/math/Vector3d.java create mode 100644 java/io/eiren/util/BufferedTimer.java create mode 100644 java/io/eiren/util/MacOSX.java create mode 100644 java/io/eiren/util/OperatingSystem.java create mode 100644 java/io/eiren/util/StringUtils.java create mode 100644 java/io/eiren/util/Util.java create mode 100644 java/io/eiren/util/ann/AWTThread.java create mode 100644 java/io/eiren/util/ann/DebugSwitch.java create mode 100644 java/io/eiren/util/ann/NativeUnsafe.java create mode 100644 java/io/eiren/util/ann/Synchronize.java create mode 100644 java/io/eiren/util/ann/ThreadSafe.java create mode 100644 java/io/eiren/util/ann/ThreadSafeSingle.java create mode 100644 java/io/eiren/util/ann/ThreadSecure.java create mode 100644 java/io/eiren/util/ann/Transient.java create mode 100644 java/io/eiren/util/collections/FastList.java create mode 100644 java/io/eiren/util/collections/RemoveAtSwapFastList.java create mode 100644 java/io/eiren/util/collections/RemoveAtSwapList.java create mode 100644 java/io/eiren/util/collections/ResettableIterator.java create mode 100644 java/io/eiren/util/collections/SkipIterator.java create mode 100644 java/io/eiren/util/logging/DefaultGLog.java create mode 100644 java/io/eiren/util/logging/FileLogFormatter.java create mode 100644 java/io/eiren/util/logging/IGLog.java create mode 100644 java/io/eiren/util/logging/LogManager.java create mode 100644 java/io/eiren/util/logging/LoggerOutputStream.java create mode 100644 java/io/eiren/util/logging/LoggerRecorder.java create mode 100644 java/io/eiren/util/logging/PreciseConsoleLogFormatter.java create mode 100644 java/io/eiren/util/logging/ShortConsoleLogFormatter.java create mode 100644 java/io/eiren/yaml/YamlException.java create mode 100644 java/io/eiren/yaml/YamlFile.java create mode 100644 java/io/eiren/yaml/YamlNode.java create mode 100644 java/org/json/JSONArray.java create mode 100644 java/org/json/JSONException.java create mode 100644 java/org/json/JSONObject.java create mode 100644 java/org/json/JSONString.java create mode 100644 java/org/json/JSONTokener.java create mode 100644 java/org/json/JSONUtil.java delete mode 160000 slime-java-commons create mode 100644 src/main/java/com/jme3/math/FastMath.java create mode 100644 src/main/java/com/jme3/math/Matrix3f.java create mode 100644 src/main/java/com/jme3/math/Matrix4f.java create mode 100644 src/main/java/com/jme3/math/Quaternion.java create mode 100644 src/main/java/com/jme3/math/TempVars.java create mode 100644 src/main/java/com/jme3/math/Transform.java create mode 100644 src/main/java/com/jme3/math/Vector2f.java create mode 100644 src/main/java/com/jme3/math/Vector3f.java create mode 100644 src/main/java/com/jme3/math/Vector4f.java create mode 100644 src/main/java/com/jme3/system/NanoTimer.java create mode 100644 src/main/java/com/jme3/system/Timer.java create mode 100644 src/main/java/io/eiren/math/FloatMath.java create mode 100644 src/main/java/io/eiren/math/Vector3d.java create mode 100644 src/main/java/io/eiren/util/BufferedTimer.java create mode 100644 src/main/java/io/eiren/util/MacOSX.java create mode 100644 src/main/java/io/eiren/util/OperatingSystem.java create mode 100644 src/main/java/io/eiren/util/StringUtils.java create mode 100644 src/main/java/io/eiren/util/Util.java create mode 100644 src/main/java/io/eiren/util/ann/AWTThread.java create mode 100644 src/main/java/io/eiren/util/ann/DebugSwitch.java create mode 100644 src/main/java/io/eiren/util/ann/NativeUnsafe.java create mode 100644 src/main/java/io/eiren/util/ann/Synchronize.java create mode 100644 src/main/java/io/eiren/util/ann/ThreadSafe.java create mode 100644 src/main/java/io/eiren/util/ann/ThreadSafeSingle.java create mode 100644 src/main/java/io/eiren/util/ann/ThreadSecure.java create mode 100644 src/main/java/io/eiren/util/ann/Transient.java create mode 100644 src/main/java/io/eiren/util/collections/FastList.java create mode 100644 src/main/java/io/eiren/util/collections/RemoveAtSwapFastList.java create mode 100644 src/main/java/io/eiren/util/collections/RemoveAtSwapList.java create mode 100644 src/main/java/io/eiren/util/collections/ResettableIterator.java create mode 100644 src/main/java/io/eiren/util/collections/SkipIterator.java create mode 100644 src/main/java/io/eiren/util/logging/DefaultGLog.java create mode 100644 src/main/java/io/eiren/util/logging/FileLogFormatter.java create mode 100644 src/main/java/io/eiren/util/logging/IGLog.java create mode 100644 src/main/java/io/eiren/util/logging/LogManager.java create mode 100644 src/main/java/io/eiren/util/logging/LoggerOutputStream.java create mode 100644 src/main/java/io/eiren/util/logging/LoggerRecorder.java create mode 100644 src/main/java/io/eiren/util/logging/PreciseConsoleLogFormatter.java create mode 100644 src/main/java/io/eiren/util/logging/ShortConsoleLogFormatter.java create mode 100644 src/main/java/org/json/JSONArray.java create mode 100644 src/main/java/org/json/JSONException.java create mode 100644 src/main/java/org/json/JSONObject.java create mode 100644 src/main/java/org/json/JSONString.java create mode 100644 src/main/java/org/json/JSONTokener.java create mode 100644 src/main/java/org/json/JSONUtil.java diff --git a/.gitmodules b/.gitmodules index 169f56543..286659e33 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,6 +1,3 @@ -[submodule "slime-java-commons"] - path = slime-java-commons - url = https://github.com/Eirenliel/slime-java-commons.git [submodule "solarxr-protocol"] path = solarxr-protocol url = https://github.com/SlimeVR/SolarXR-Protocol.git diff --git a/build.gradle b/build.gradle index 88fbd3ba4..eaceb86d4 100644 --- a/build.gradle +++ b/build.gradle @@ -42,7 +42,6 @@ allprojects { } dependencies { - implementation project(':slime-java-commons') implementation project(":solarxr-protocol") diff --git a/java/com/jme3/math/ColorRGBA.java b/java/com/jme3/math/ColorRGBA.java new file mode 100644 index 000000000..a6b8a62f2 --- /dev/null +++ b/java/com/jme3/math/ColorRGBA.java @@ -0,0 +1,665 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.math; + +import io.eiren.math.FloatMath; + + +/** + * ColorRGBA defines a color made from a collection of red, green + * and blue values. An alpha value determines is transparency. All values must + * be between 0 and 1. If any value is set higher or lower than these + * constraints they are clamped to the min or max. That is, if a value smaller + * than zero is set the value clamps to zero. If a value higher than 1 is + * passed, that value is clamped to 1. However, because the attributes r, g, b, + * a are public for efficiency reasons, they can be directly modified with + * invalid values. The client should take care when directly addressing the + * values. A call to clamp will assure that the values are within the + * constraints. + * + * @author Mark Powell + * @version $Id: ColorRGBA.java,v 1.29 2007/09/09 18:25:14 irrisor Exp $ + */ +public final class ColorRGBA implements Cloneable, java.io.Serializable { + + static final long serialVersionUID = 1; + /** + * The color black (0,0,0). + */ + public static final ColorRGBA Black = new ColorRGBA(0f, 0f, 0f, 1f); + /** + * The color white (1,1,1). + */ + public static final ColorRGBA White = new ColorRGBA(1f, 1f, 1f, 1f); + /** + * The color gray (.2,.2,.2). + */ + public static final ColorRGBA DarkGray = new ColorRGBA(0.2f, 0.2f, 0.2f, 1.0f); + /** + * The color gray (.5,.5,.5). + */ + public static final ColorRGBA Gray = new ColorRGBA(0.5f, 0.5f, 0.5f, 1.0f); + /** + * The color gray (.8,.8,.8). + */ + public static final ColorRGBA LightGray = new ColorRGBA(0.8f, 0.8f, 0.8f, 1.0f); + /** + * The color red (1,0,0). + */ + public static final ColorRGBA Red = new ColorRGBA(1f, 0f, 0f, 1f); + /** + * The color green (0,1,0). + */ + public static final ColorRGBA Green = new ColorRGBA(0f, 1f, 0f, 1f); + /** + * The color blue (0,0,1). + */ + public static final ColorRGBA Blue = new ColorRGBA(0f, 0f, 1f, 1f); + /** + * The color yellow (1,1,0). + */ + public static final ColorRGBA Yellow = new ColorRGBA(1f, 1f, 0f, 1f); + /** + * The color magenta (1,0,1). + */ + public static final ColorRGBA Magenta = new ColorRGBA(1f, 0f, 1f, 1f); + /** + * The color cyan (0,1,1). + */ + public static final ColorRGBA Cyan = new ColorRGBA(0f, 1f, 1f, 1f); + /** + * The color orange (251/255, 130/255,0). + */ + public static final ColorRGBA Orange = new ColorRGBA(251f / 255f, 130f / 255f, 0f, 1f); + /** + * The color brown (65/255, 40/255, 25/255). + */ + public static final ColorRGBA Brown = new ColorRGBA(65f / 255f, 40f / 255f, 25f / 255f, 1f); + /** + * The color pink (1, 0.68, 0.68). + */ + public static final ColorRGBA Pink = new ColorRGBA(1f, 0.68f, 0.68f, 1f); + /** + * The black color with no alpha (0, 0, 0, 0). + */ + public static final ColorRGBA BlackNoAlpha = new ColorRGBA(0f, 0f, 0f, 0f); + /** + * The red component of the color. 0 is none and 1 is maximum red. + */ + public float r; + /** + * The green component of the color. 0 is none and 1 is maximum green. + */ + public float g; + /** + * The blue component of the color. 0 is none and 1 is maximum blue. + */ + public float b; + /** + * The alpha component of the color. 0 is transparent and 1 is opaque. + */ + public float a; + + /** + * Constructor instantiates a new ColorRGBA object. This color + * is the default "white" with all values 1. + */ + public ColorRGBA() { + r = g = b = a = 1.0f; + } + + /** + * Constructor instantiates a new ColorRGBA object. The values + * are defined as passed parameters. These values are then clamped to insure + * that they are between 0 and 1. + * + * @param r The red component of this color. + * @param g The green component of this ColorRGBA. + * @param b The blue component of this ColorRGBA. + * @param a The alpha component of this ColorRGBA. + */ + public ColorRGBA(float r, float g, float b, float a) { + this.r = r; + this.g = g; + this.b = b; + this.a = a; + } + + /** + * Copy constructor creates a new ColorRGBA object, based on a + * provided color. + * + * @param rgba The ColorRGBA object to copy. + */ + public ColorRGBA(ColorRGBA rgba) { + this.a = rgba.a; + this.r = rgba.r; + this.g = rgba.g; + this.b = rgba.b; + } + + /** + * set sets the RGBA values of this ColorRGBA. The + * values are then clamped to insure that they are between 0 and 1. + * + * @param r The red component of this color. + * @param g The green component of this color. + * @param b The blue component of this color. + * @param a The alpha component of this color. + * @return this + */ + public ColorRGBA set(float r, float g, float b, float a) { + this.r = r; + this.g = g; + this.b = b; + this.a = a; + return this; + } + + /** + * set sets the values of this ColorRGBA to those + * set by a parameter color. + * + * @param rgba The color to set this ColorRGBA to. + * @return this + */ + public ColorRGBA set(ColorRGBA rgba) { + if (rgba == null) { + r = 0; + g = 0; + b = 0; + a = 0; + } else { + r = rgba.r; + g = rgba.g; + b = rgba.b; + a = rgba.a; + } + return this; + } + + public ColorRGBA setR(float r) { + this.r = r; + return this; + } + + public ColorRGBA setG(float g) { + this.g = g; + return this; + } + + public ColorRGBA setB(float b) { + this.b = b; + return this; + } + + public ColorRGBA setA(float a) { + this.a = a; + return this; + } + + /** + * clamp insures that all values are between 0 and 1. If any + * are less than 0 they are set to zero. If any are more than 1 they are set + * to one. + */ + public void clamp() { + if (r < 0) { + r = 0; + } else if (r > 1) { + r = 1; + } + + if (g < 0) { + g = 0; + } else if (g > 1) { + g = 1; + } + + if (b < 0) { + b = 0; + } else if (b > 1) { + b = 1; + } + + if (a < 0) { + a = 0; + } else if (a > 1) { + a = 1; + } + } + + /** + * getColorArray retrieves the color values of this + * ColorRGBA as a four element float array in the + * order: r,g,b,a. + * + * @return The float array that contains the color components. + */ + public float[] getColorArray() { + return new float[] { r, g, b, a }; + } + + /** + * Stores the current r,g,b,a values into the given array. The given array + * must have a length of 4 or greater, or an array index out of bounds + * exception will be thrown. + * + * @param store The float array to store the values into. + * @return The float array after storage. + */ + public float[] getColorArray(float[] store) { + store[0] = r; + store[1] = g; + store[2] = b; + store[3] = a; + return store; + } + + /** + * Retrieves the alpha component value of this ColorRGBA. + * + * @return The alpha component value. + */ + public float getAlpha() { + return a; + } + + /** + * Retrieves the red component value of this ColorRGBA. + * + * @return The red component value. + */ + public float getRed() { + return r; + } + + /** + * Retrieves the blue component value of this ColorRGBA. + * + * @return The blue component value. + */ + public float getBlue() { + return b; + } + + /** + * Retrieves the green component value of this ColorRGBA. + * + * @return The green component value. + */ + public float getGreen() { + return g; + } + + /** + * Sets this ColorRGBA to the interpolation by changeAmnt from + * this to the finalColor: this=(1-changeAmnt)*this + changeAmnt * + * finalColor + * + * @param finalColor The final color to interpolate towards. + * @param changeAmnt An amount between 0.0 - 1.0 representing a percentage + * change from this towards finalColor. + */ + public void interpolate(ColorRGBA finalColor, float changeAmnt) { + this.r = (1 - changeAmnt) * this.r + changeAmnt * finalColor.r; + this.g = (1 - changeAmnt) * this.g + changeAmnt * finalColor.g; + this.b = (1 - changeAmnt) * this.b + changeAmnt * finalColor.b; + this.a = (1 - changeAmnt) * this.a + changeAmnt * finalColor.a; + } + + /** + * Sets this ColorRGBA to the interpolation by changeAmnt from + * beginColor to finalColor: this=(1-changeAmnt)*beginColor + changeAmnt * + * finalColor + * + * @param beginColor The begining color (changeAmnt=0). + * @param finalColor The final color to interpolate towards (changeAmnt=1). + * @param changeAmnt An amount between 0.0 - 1.0 representing a precentage + * change from beginColor towards finalColor. + */ + public void interpolate(ColorRGBA beginColor, ColorRGBA finalColor, float changeAmnt) { + this.r = (1 - changeAmnt) * beginColor.r + changeAmnt * finalColor.r; + this.g = (1 - changeAmnt) * beginColor.g + changeAmnt * finalColor.g; + this.b = (1 - changeAmnt) * beginColor.b + changeAmnt * finalColor.b; + this.a = (1 - changeAmnt) * beginColor.a + changeAmnt * finalColor.a; + } + + /** + * randomColor is a utility method that generates a random + * opaque color. + * + * @return a random ColorRGBA with an alpha set to 1. + */ + public static ColorRGBA randomColor() { + ColorRGBA rVal = new ColorRGBA(0, 0, 0, 1); + rVal.r = FastMath.nextRandomFloat(); + rVal.g = FastMath.nextRandomFloat(); + rVal.b = FastMath.nextRandomFloat(); + return rVal; + } + + /** + * Multiplies each r,g,b,a of this ColorRGBA by the + * corresponding r,g,b,a of the given color and returns the result as a new + * ColorRGBA. Used as a way of combining colors and lights. + * + * @param c The color to multiply by. + * @return The new ColorRGBA. this*c + */ + public ColorRGBA mult(ColorRGBA c) { + return new ColorRGBA(c.r * r, c.g * g, c.b * b, c.a * a); + } + + /** + * Multiplies each r,g,b,a of this ColorRGBA by the given + * scalar and returns the result as a new ColorRGBA. Used as a + * way of making colors dimmer or brighter. + * + * @param scalar The scalar to multiply by. + * @return The new ColorRGBA. this*scalar + */ + public ColorRGBA mult(float scalar) { + return new ColorRGBA(scalar * r, scalar * g, scalar * b, scalar * a); + } + + /** + * Multiplies each r,g,b,a of this ColorRGBA by the given + * scalar and returns the result (this). Used as a way of making colors + * dimmer or brighter. + * + * @param scalar The scalar to multiply by. + * @return this*c + */ + public ColorRGBA multLocal(float scalar) { + this.r *= scalar; + this.g *= scalar; + this.b *= scalar; + this.a *= scalar; + return this; + } + + /** + * Adds each r,g,b,a of this ColorRGBA by the corresponding + * r,g,b,a of the given color and returns the result as a new + * ColorRGBA. Used as a way of combining colors and lights. + * + * @param c The color to add. + * @return The new ColorRGBA. this+c + */ + public ColorRGBA add(ColorRGBA c) { + return new ColorRGBA(c.r + r, c.g + g, c.b + b, c.a + a); + } + + /** + * Adds each r,g,b,a of this ColorRGBA by the r,g,b,a the given + * color and returns the result (this). Used as a way of combining colors + * and lights. + * + * @param c The color to add. + * @return this+c + */ + public ColorRGBA addLocal(ColorRGBA c) { + set(c.r + r, c.g + g, c.b + b, c.a + a); + return this; + } + + /** + * Multiplies alpha component by the given scalar and returns the result as + * a new color. + */ + public ColorRGBA dilute(float scalarA) { + return new ColorRGBA(r, g, b, a * scalarA); + } + + /** + * Multiplies alpha component by the given scalar and returns the result + * (this). + */ + public ColorRGBA diluteLocal(float scalarA) { + this.a *= scalarA; + return this; + } + + /** + * toString returns the string representation of this + * ColorRGBA. The format of the string is:
+ * : [R=RR.RRRR, G=GG.GGGG, B=BB.BBBB, A=AA.AAAA] + * + * @return The string representation of this ColorRGBA. + */ + @Override + public String toString() { + return "Color[" + r + ", " + g + ", " + b + ", " + a + "]"; + } + + @Override + public ColorRGBA clone() { + try { + return (ColorRGBA) super.clone(); + } catch (CloneNotSupportedException e) { + throw new AssertionError(); // can not happen + } + } + + /** + * Saves this ColorRGBA into the given float + * array. + * + * @param floats The float array to take this + * ColorRGBA. If null, a new float[4] is created. + * @return The array, with r,g,b,a float values in that order. + */ + public float[] toArray(float[] floats) { + if (floats == null) { + floats = new float[4]; + } + floats[0] = r; + floats[1] = g; + floats[2] = b; + floats[3] = a; + return floats; + } + + public ColorRGBA fromArray(float[] floats) { + r = floats[0]; + g = floats[1]; + b = floats[2]; + a = floats[3]; + return this; + } + + /** + * equals returns true if this ColorRGBA is + * logically equivalent to a given color. That is, if all the components of + * the two colors are the same. False is returned otherwise. + * + * @param o The object to compare against. + * @return true if the colors are equal, false otherwise. + */ + @Override + public boolean equals(Object o) { + if (!(o instanceof ColorRGBA)) { + return false; + } + + if (this == o) { + return true; + } + + ColorRGBA comp = (ColorRGBA) o; + if (!FloatMath.equalsWithEpsilon(r, comp.r, FastMath.ZERO_TOLERANCE)) { + return false; + } + if (!FloatMath.equalsWithEpsilon(g, comp.g, FastMath.ZERO_TOLERANCE)) { + return false; + } + if (!FloatMath.equalsWithEpsilon(b, comp.b, FastMath.ZERO_TOLERANCE)) { + return false; + } + if (!FloatMath.equalsWithEpsilon(a, comp.a, FastMath.ZERO_TOLERANCE)) { + return false; + } + return true; + } + + /** + * hashCode returns a unique code for this + * ColorRGBA based on its values. If two colors are logically + * equivalent, they will return the same hash code value. + * + * @return The hash code value of this ColorRGBA. + */ + @Override + public int hashCode() { + int hash = 37; + hash += 37 * hash + Float.floatToIntBits(r); + hash += 37 * hash + Float.floatToIntBits(g); + hash += 37 * hash + Float.floatToIntBits(b); + hash += 37 * hash + Float.floatToIntBits(a); + return hash; + } + + /** + * Retrieves the component values of this ColorRGBA as a four + * element byte array in the order: r,g,b,a. + * + * @return the byte array that contains the color components. + */ + public byte[] asBytesRGBA() { + byte[] store = new byte[4]; + store[0] = (byte) ((int) (r * 255) & 0xFF); + store[1] = (byte) ((int) (g * 255) & 0xFF); + store[2] = (byte) ((int) (b * 255) & 0xFF); + store[3] = (byte) ((int) (a * 255) & 0xFF); + return store; + } + + /** + * Retrieves the component values of this ColorRGBA as an + * int in a,r,g,b order. Bits 24-31 are alpha, 16-23 are red, + * 8-15 are green, 0-7 are blue. + * + * @return The integer representation of this ColorRGBA in + * a,r,g,b order. + */ + public int asIntARGB() { + int argb = (((int) (a * 255) & 0xFF) << 24) + | (((int) (r * 255) & 0xFF) << 16) + | (((int) (g * 255) & 0xFF) << 8) + | (((int) (b * 255) & 0xFF)); + return argb; + } + + /** + * Retrieves the component values of this ColorRGBA as an + * int in r,g,b,a order. Bits 24-31 are red, 16-23 are green, + * 8-15 are blue, 0-7 are alpha. + * + * @return The integer representation of this ColorRGBA in + * r,g,b,a order. + */ + public int asIntRGBA() { + int rgba = (((int) (r * 255) & 0xFF) << 24) + | (((int) (g * 255) & 0xFF) << 16) + | (((int) (b * 255) & 0xFF) << 8) + | (((int) (a * 255) & 0xFF)); + return rgba; + } + + /** + * Retrieves the component values of this ColorRGBA as an + * int in a,b,g,r order. Bits 24-31 are alpha, 16-23 are blue, + * 8-15 are green, 0-7 are red. + * + * @return The integer representation of this ColorRGBA in + * a,b,g,r order. + */ + public int asIntABGR() { + int abgr = (((int) (a * 255) & 0xFF) << 24) + | (((int) (b * 255) & 0xFF) << 16) + | (((int) (g * 255) & 0xFF) << 8) + | (((int) (r * 255) & 0xFF)); + return abgr; + } + + /** + * Sets the component values of this ColorRGBA with the given + * combined ARGB int. Bits 24-31 are alpha, bits 16-23 are red, + * bits 8-15 are green, bits 0-7 are blue. + * + * @param color The integer ARGB value used to set this + * ColorRGBA. + */ + public void fromIntARGB(int color) { + a = ((byte) (color >> 24) & 0xFF) / 255f; + r = ((byte) (color >> 16) & 0xFF) / 255f; + g = ((byte) (color >> 8) & 0xFF) / 255f; + b = ((byte) (color) & 0xFF) / 255f; + } + + /** + * Sets the RGBA values of this ColorRGBA with the given + * combined RGBA value Bits 24-31 are red, bits 16-23 are green, bits 8-15 + * are blue, bits 0-7 are alpha. + * + * @param color The integer RGBA value used to set this object. + */ + public void fromIntRGBA(int color) { + r = ((byte) (color >> 24) & 0xFF) / 255f; + g = ((byte) (color >> 16) & 0xFF) / 255f; + b = ((byte) (color >> 8) & 0xFF) / 255f; + a = ((byte) (color) & 0xFF) / 255f; + } + + /** + * Transform this ColorRGBA to a Vector3f using x + * = r, y = g, z = b. The Alpha value is not used. This method is useful to + * use for shaders assignment. + * + * @return A Vector3f containing the RGB value of this + * ColorRGBA. + */ + public Vector3f toVector3f() { + return new Vector3f(r, g, b); + } + + /** + * Transform this ColorRGBA to a Vector4f using x + * = r, y = g, z = b, w = a. This method is useful to use for shaders + * assignment. + * + * @return A Vector4f containing the RGBA value of this + * ColorRGBA. + */ + public Vector4f toVector4f() { + return new Vector4f(r, g, b, a); + } +} diff --git a/java/com/jme3/math/FastMath.java b/java/com/jme3/math/FastMath.java new file mode 100644 index 000000000..ed0917d14 --- /dev/null +++ b/java/com/jme3/math/FastMath.java @@ -0,0 +1,1072 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.math; + +import java.util.Random; + + +/** + * FastMath provides 'fast' math approximations and float + * equivalents of Math functions. These are all used as static values and + * functions. + * + * @author Various + * @version $Id: FastMath.java,v 1.45 2007/08/26 08:44:20 irrisor Exp $ + */ +final public class FastMath { + + private FastMath() { + } + + /** A "close to zero" double epsilon value for use */ + public static final double DBL_EPSILON = 2.220446049250313E-16d; + /** A "close to zero" float epsilon value for use */ + public static final float FLT_EPSILON = 1.1920928955078125E-7f; + /** A "close to zero" float epsilon value for use */ + public static final float ZERO_TOLERANCE = 0.0001f; + public static final float ONE_THIRD = 1f / 3f; + /** The value PI as a float. (180 degrees) */ + public static final float PI = (float) Math.PI; + /** The value 2PI as a float. (360 degrees) */ + public static final float TWO_PI = 2.0f * PI; + /** The value PI/2 as a float. (90 degrees) */ + public static final float HALF_PI = 0.5f * PI; + /** The value PI/4 as a float. (45 degrees) */ + public static final float QUARTER_PI = 0.25f * PI; + /** The value 1/PI as a float. */ + public static final float INV_PI = 1.0f / PI; + /** The value 1/(2PI) as a float. */ + public static final float INV_TWO_PI = 1.0f / TWO_PI; + /** A value to multiply a degree value by, to convert it to radians. */ + public static final float DEG_TO_RAD = PI / 180.0f; + /** A value to multiply a radian value by, to convert it to degrees. */ + public static final float RAD_TO_DEG = 180.0f / PI; + /** A precreated random object for random numbers. */ + public static final Random rand = new Random(System.currentTimeMillis()); + + /** + * Returns true if the number is a power of 2 (2,4,8,16...) + * + * A good implementation found on the Java boards. note: a number is a power + * of two if and only if it is the smallest number with that number of + * significant bits. Therefore, if you subtract 1, you know that the new + * number will have fewer bits, so ANDing the original number with anything + * less than it will give 0. + * + * @param number The number to test. + * @return True if it is a power of two. + */ + public static boolean isPowerOfTwo(int number) { + return (number > 0) && (number & (number - 1)) == 0; + } + + public static int nearestPowerOfTwo(int number) { + return (int) Math.pow(2, Math.ceil(Math.log(number) / Math.log(2))); + } + + /** + * Linear interpolation from startValue to endValue by the given percent. + * Basically: ((1 - percent) * startValue) + (percent * endValue) + * + * @param scale scale value to use. if 1, use endValue, if 0, use + * startValue. + * @param startValue Begining value. 0% of f + * @param endValue ending value. 100% of f + * @return The interpolated value between startValue and endValue. + */ + public static float interpolateLinear(float scale, float startValue, float endValue) { + if (startValue == endValue) { + return startValue; + } + if (scale <= 0f) { + return startValue; + } + if (scale >= 1f) { + return endValue; + } + return ((1f - scale) * startValue) + (scale * endValue); + } + + /** + * Linear interpolation from startValue to endValue by the given percent. + * Basically: ((1 - percent) * startValue) + (percent * endValue) + * + * @param scale scale value to use. if 1, use endValue, if 0, use + * startValue. + * @param startValue Begining value. 0% of f + * @param endValue ending value. 100% of f + * @param store a vector3f to store the result + * @return The interpolated value between startValue and endValue. + */ + public static Vector3f interpolateLinear( + float scale, + Vector3f startValue, + Vector3f endValue, + Vector3f store + ) { + if (store == null) { + store = new Vector3f(); + } + store.x = interpolateLinear(scale, startValue.x, endValue.x); + store.y = interpolateLinear(scale, startValue.y, endValue.y); + store.z = interpolateLinear(scale, startValue.z, endValue.z); + return store; + } + + /** + * Linear interpolation from startValue to endValue by the given percent. + * Basically: ((1 - percent) * startValue) + (percent * endValue) + * + * @param scale scale value to use. if 1, use endValue, if 0, use + * startValue. + * @param startValue Begining value. 0% of f + * @param endValue ending value. 100% of f + * @return The interpolated value between startValue and endValue. + */ + public static Vector3f interpolateLinear(float scale, Vector3f startValue, Vector3f endValue) { + return interpolateLinear(scale, startValue, endValue, null); + } + + /** + * Linear extrapolation from startValue to endValue by the given scale. if + * scale is between 0 and 1 this method returns the same result as + * interpolateLinear if the scale is over 1 the value is linearly + * extrapolated. Note that the end value is the value for a scale of 1. + * + * @param scale the scale for extrapolation + * @param startValue the starting value (scale = 0) + * @param endValue the end value (scale = 1) + * @return an extrapolation for the given parameters + */ + public static float extrapolateLinear(float scale, float startValue, float endValue) { +// if (scale <= 0f) { +// return startValue; +// } + return ((1f - scale) * startValue) + (scale * endValue); + } + + /** + * Linear extrapolation from startValue to endValue by the given scale. if + * scale is between 0 and 1 this method returns the same result as + * interpolateLinear if the scale is over 1 the value is linearly + * extrapolated. Note that the end value is the value for a scale of 1. + * + * @param scale the scale for extrapolation + * @param startValue the starting value (scale = 0) + * @param endValue the end value (scale = 1) + * @param store an initialized vector to store the return value + * @return an extrapolation for the given parameters + */ + public static Vector3f extrapolateLinear( + float scale, + Vector3f startValue, + Vector3f endValue, + Vector3f store + ) { + if (store == null) { + store = new Vector3f(); + } +// if (scale <= 1f) { +// return interpolateLinear(scale, startValue, endValue, store); +// } + store.x = extrapolateLinear(scale, startValue.x, endValue.x); + store.y = extrapolateLinear(scale, startValue.y, endValue.y); + store.z = extrapolateLinear(scale, startValue.z, endValue.z); + return store; + } + + /** + * Linear extrapolation from startValue to endValue by the given scale. if + * scale is between 0 and 1 this method returns the same result as + * interpolateLinear if the scale is over 1 the value is linearly + * extrapolated. Note that the end value is the value for a scale of 1. + * + * @param scale the scale for extrapolation + * @param startValue the starting value (scale = 0) + * @param endValue the end value (scale = 1) + * @return an extrapolation for the given parameters + */ + public static Vector3f extrapolateLinear(float scale, Vector3f startValue, Vector3f endValue) { + return extrapolateLinear(scale, startValue, endValue, null); + } + + /** + * Interpolate a spline between at least 4 control points following the + * Catmull-Rom equation. here is the interpolation matrix m = [ 0.0 1.0 0.0 + * 0.0 ] [-T 0.0 T 0.0 ] [ 2T T-3 3-2T -T ] [-T 2-T T-2 T ] where T is the + * curve tension the result is a value between p1 and p2, t=0 for p1, t=1 + * for p2 + * + * @param u value from 0 to 1 + * @param T The tension of the curve + * @param p0 control point 0 + * @param p1 control point 1 + * @param p2 control point 2 + * @param p3 control point 3 + * @return catmull-Rom interpolation + */ + public static float interpolateCatmullRom( + float u, + float T, + float p0, + float p1, + float p2, + float p3 + ) { + float c1, c2, c3, c4; + c1 = p1; + c2 = -1.0f * T * p0 + T * p2; + c3 = 2 * T * p0 + (T - 3) * p1 + (3 - 2 * T) * p2 + -T * p3; + c4 = -T * p0 + (2 - T) * p1 + (T - 2) * p2 + T * p3; + + return (float) (((c4 * u + c3) * u + c2) * u + c1); + } + + /** + * Interpolate a spline between at least 4 control points following the + * Catmull-Rom equation. here is the interpolation matrix m = [ 0.0 1.0 0.0 + * 0.0 ] [-T 0.0 T 0.0 ] [ 2T T-3 3-2T -T ] [-T 2-T T-2 T ] where T is the + * tension of the curve the result is a value between p1 and p2, t=0 for p1, + * t=1 for p2 + * + * @param u value from 0 to 1 + * @param T The tension of the curve + * @param p0 control point 0 + * @param p1 control point 1 + * @param p2 control point 2 + * @param p3 control point 3 + * @param store a Vector3f to store the result + * @return catmull-Rom interpolation + */ + public static Vector3f interpolateCatmullRom( + float u, + float T, + Vector3f p0, + Vector3f p1, + Vector3f p2, + Vector3f p3, + Vector3f store + ) { + if (store == null) { + store = new Vector3f(); + } + store.x = interpolateCatmullRom(u, T, p0.x, p1.x, p2.x, p3.x); + store.y = interpolateCatmullRom(u, T, p0.y, p1.y, p2.y, p3.y); + store.z = interpolateCatmullRom(u, T, p0.z, p1.z, p2.z, p3.z); + return store; + } + + /** + * Interpolate a spline between at least 4 control points following the + * Catmull-Rom equation. here is the interpolation matrix m = [ 0.0 1.0 0.0 + * 0.0 ] [-T 0.0 T 0.0 ] [ 2T T-3 3-2T -T ] [-T 2-T T-2 T ] where T is the + * tension of the curve the result is a value between p1 and p2, t=0 for p1, + * t=1 for p2 + * + * @param u value from 0 to 1 + * @param T The tension of the curve + * @param p0 control point 0 + * @param p1 control point 1 + * @param p2 control point 2 + * @param p3 control point 3 + * @return catmull-Rom interpolation + */ + public static Vector3f interpolateCatmullRom( + float u, + float T, + Vector3f p0, + Vector3f p1, + Vector3f p2, + Vector3f p3 + ) { + return interpolateCatmullRom(u, T, p0, p1, p2, p3, null); + } + + /** + * Interpolate a spline between at least 4 control points following the + * Bezier equation. here is the interpolation matrix m = [ -1.0 3.0 -3.0 1.0 + * ] [ 3.0 -6.0 3.0 0.0 ] [ -3.0 3.0 0.0 0.0 ] [ 1.0 0.0 0.0 0.0 ] where T + * is the curve tension the result is a value between p1 and p3, t=0 for p1, + * t=1 for p3 + * + * @param u value from 0 to 1 + * @param p0 control point 0 + * @param p1 control point 1 + * @param p2 control point 2 + * @param p3 control point 3 + * @return Bezier interpolation + */ + public static float interpolateBezier(float u, float p0, float p1, float p2, float p3) { + float oneMinusU = 1.0f - u; + float oneMinusU2 = oneMinusU * oneMinusU; + float u2 = u * u; + return p0 * oneMinusU2 * oneMinusU + + 3.0f * p1 * u * oneMinusU2 + + 3.0f * p2 * u2 * oneMinusU + + p3 * u2 * u; + } + + /** + * Interpolate a spline between at least 4 control points following the + * Bezier equation. here is the interpolation matrix m = [ -1.0 3.0 -3.0 1.0 + * ] [ 3.0 -6.0 3.0 0.0 ] [ -3.0 3.0 0.0 0.0 ] [ 1.0 0.0 0.0 0.0 ] where T + * is the tension of the curve the result is a value between p1 and p3, t=0 + * for p1, t=1 for p3 + * + * @param u value from 0 to 1 + * @param p0 control point 0 + * @param p1 control point 1 + * @param p2 control point 2 + * @param p3 control point 3 + * @param store a Vector3f to store the result + * @return Bezier interpolation + */ + public static Vector3f interpolateBezier( + float u, + Vector3f p0, + Vector3f p1, + Vector3f p2, + Vector3f p3, + Vector3f store + ) { + if (store == null) { + store = new Vector3f(); + } + store.x = interpolateBezier(u, p0.x, p1.x, p2.x, p3.x); + store.y = interpolateBezier(u, p0.y, p1.y, p2.y, p3.y); + store.z = interpolateBezier(u, p0.z, p1.z, p2.z, p3.z); + return store; + } + + /** + * Interpolate a spline between at least 4 control points following the + * Bezier equation. here is the interpolation matrix m = [ -1.0 3.0 -3.0 1.0 + * ] [ 3.0 -6.0 3.0 0.0 ] [ -3.0 3.0 0.0 0.0 ] [ 1.0 0.0 0.0 0.0 ] where T + * is the tension of the curve the result is a value between p1 and p3, t=0 + * for p1, t=1 for p3 + * + * @param u value from 0 to 1 + * @param p0 control point 0 + * @param p1 control point 1 + * @param p2 control point 2 + * @param p3 control point 3 + * @return Bezier interpolation + */ + public static Vector3f interpolateBezier( + float u, + Vector3f p0, + Vector3f p1, + Vector3f p2, + Vector3f p3 + ) { + return interpolateBezier(u, p0, p1, p2, p3, null); + } + + /** + * Compute the lenght on a catmull rom spline between control point 1 and 2 + * + * @param p0 control point 0 + * @param p1 control point 1 + * @param p2 control point 2 + * @param p3 control point 3 + * @param startRange the starting range on the segment (use 0) + * @param endRange the end range on the segment (use 1) + * @param curveTension the curve tension + * @return the length of the segment + */ + public static float getCatmullRomP1toP2Length( + Vector3f p0, + Vector3f p1, + Vector3f p2, + Vector3f p3, + float startRange, + float endRange, + float curveTension + ) { + + float epsilon = 0.001f; + float middleValue = (startRange + endRange) * 0.5f; + Vector3f start = p1.clone(); + if (startRange != 0) { + FastMath.interpolateCatmullRom(startRange, curveTension, p0, p1, p2, p3, start); + } + Vector3f end = p2.clone(); + if (endRange != 1) { + FastMath.interpolateCatmullRom(endRange, curveTension, p0, p1, p2, p3, end); + } + Vector3f middle = FastMath.interpolateCatmullRom(middleValue, curveTension, p0, p1, p2, p3); + float l = end.subtract(start).length(); + float l1 = middle.subtract(start).length(); + float l2 = end.subtract(middle).length(); + float len = l1 + l2; + if (l + epsilon < len) { + l1 = getCatmullRomP1toP2Length(p0, p1, p2, p3, startRange, middleValue, curveTension); + l2 = getCatmullRomP1toP2Length(p0, p1, p2, p3, middleValue, endRange, curveTension); + } + l = l1 + l2; + return l; + } + + /** + * Compute the lenght on a bezier spline between control point 1 and 2 + * + * @param p0 control point 0 + * @param p1 control point 1 + * @param p2 control point 2 + * @param p3 control point 3 + * @return the length of the segment + */ + public static float getBezierP1toP2Length(Vector3f p0, Vector3f p1, Vector3f p2, Vector3f p3) { + float delta = 0.02f, t = 0.0f, result = 0.0f; + Vector3f v1 = p0.clone(), v2 = new Vector3f(); + while (t <= 1.0f) { + FastMath.interpolateBezier(t, p0, p1, p2, p3, v2); + result += v1.subtractLocal(v2).length(); + v1.set(v2); + t += delta; + } + return result; + } + + /** + * Returns the arc cosine of a value.
+ * Special cases: + * + * + * @param fValue The value to arc cosine. + * @return The angle, in radians. + * @see java.lang.Math#acos(double) + */ + public static float acos(float fValue) { + if (-1.0f < fValue) { + if (fValue < 1.0f) { + return (float) Math.acos(fValue); + } + + return 0.0f; + } + + return PI; + } + + /** + * Returns the arc sine of a value.
+ * Special cases: + * + * + * @param fValue The value to arc sine. + * @return the angle in radians. + * @see java.lang.Math#asin(double) + */ + public static float asin(float fValue) { + if (-1.0f < fValue) { + if (fValue < 1.0f) { + return (float) Math.asin(fValue); + } + + return HALF_PI; + } + + return -HALF_PI; + } + + /** + * Returns the arc tangent of an angle given in radians.
+ * + * @param fValue The angle, in radians. + * @return fValue's atan + * @see java.lang.Math#atan(double) + */ + public static float atan(float fValue) { + return (float) Math.atan(fValue); + } + + /** + * A direct call to Math.atan2. + * + * @param fY + * @param fX + * @return Math.atan2(fY,fX) + * @see java.lang.Math#atan2(double, double) + */ + public static float atan2(float fY, float fX) { + return (float) Math.atan2(fY, fX); + } + + /** + * Rounds a fValue up. A call to Math.ceil + * + * @param fValue The value. + * @return The fValue rounded up + * @see java.lang.Math#ceil(double) + */ + public static float ceil(float fValue) { + return (float) Math.ceil(fValue); + } + + /** + * Returns cosine of an angle. Direct call to java.lang.Math + * + * @see Math#cos(double) + * @param v The angle to cosine. + * @return the cosine of the angle. + */ + public static float cos(float v) { + return (float) Math.cos(v); + } + + /** + * Returns the sine of an angle. Direct call to java.lang.Math + * + * @see Math#sin(double) + * @param v The angle to sine. + * @return the sine of the angle. + */ + public static float sin(float v) { + return (float) Math.sin(v); + } + + /** + * Returns E^fValue + * + * @param fValue Value to raise to a power. + * @return The value E^fValue + * @see java.lang.Math#exp(double) + */ + public static float exp(float fValue) { + return (float) Math.exp(fValue); + } + + /** + * Returns Absolute value of a float. + * + * @param fValue The value to abs. + * @return The abs of the value. + * @see java.lang.Math#abs(float) + */ + public static float abs(float fValue) { + if (fValue < 0) { + return -fValue; + } + return fValue; + } + + /** + * Returns a number rounded down. + * + * @param fValue The value to round + * @return The given number rounded down + * @see java.lang.Math#floor(double) + */ + public static float floor(float fValue) { + return (float) Math.floor(fValue); + } + + /** + * Returns 1/sqrt(fValue) + * + * @param fValue The value to process. + * @return 1/sqrt(fValue) + * @see java.lang.Math#sqrt(double) + */ + public static float invSqrt(float fValue) { + return (float) (1.0f / Math.sqrt(fValue)); + } + + public static float fastInvSqrt(float x) { + float xhalf = 0.5f * x; + int i = Float.floatToIntBits(x); // get bits for floating value + i = 0x5f375a86 - (i >> 1); // gives initial guess y0 + x = Float.intBitsToFloat(i); // convert bits back to float + x = x * (1.5f - xhalf * x * x); // Newton step, repeating increases + // accuracy + return x; + } + + /** + * Returns the log base E of a value. + * + * @param fValue The value to log. + * @return The log of fValue base E + * @see java.lang.Math#log(double) + */ + public static float log(float fValue) { + return (float) Math.log(fValue); + } + + /** + * Returns the logarithm of value with given base, calculated as + * log(value)/log(base), so that pow(base, return)==value (contributed by + * vear) + * + * @param value The value to log. + * @param base Base of logarithm. + * @return The logarithm of value with given base + */ + public static float log(float value, float base) { + return (float) (Math.log(value) / Math.log(base)); + } + + /** + * Returns a number raised to an exponent power. fBase^fExponent + * + * @param fBase The base value (IE 2) + * @param fExponent The exponent value (IE 3) + * @return base raised to exponent (IE 8) + * @see java.lang.Math#pow(double, double) + */ + public static float pow(float fBase, float fExponent) { + return (float) Math.pow(fBase, fExponent); + } + + /** + * Returns the value squared. fValue ^ 2 + * + * @param fValue The vaule to square. + * @return The square of the given value. + */ + public static float sqr(float fValue) { + return fValue * fValue; + } + + /** + * Returns the square root of a given value. + * + * @param fValue The value to sqrt. + * @return The square root of the given value. + * @see java.lang.Math#sqrt(double) + */ + public static float sqrt(float fValue) { + return (float) Math.sqrt(fValue); + } + + /** + * Returns the tangent of a value. If USE_FAST_TRIG is enabled, an + * approximate value is returned. Otherwise, a direct value is used. + * + * @param fValue The value to tangent, in radians. + * @return The tangent of fValue. + * @see java.lang.Math#tan(double) + */ + public static float tan(float fValue) { + return (float) Math.tan(fValue); + } + + /** + * Returns 1 if the number is positive, -1 if the number is negative, and 0 + * otherwise + * + * @param iValue The integer to examine. + * @return The integer's sign. + */ + public static int sign(int iValue) { + if (iValue > 0) { + return 1; + } + if (iValue < 0) { + return -1; + } + return 0; + } + + /** + * Returns 1 if the number is positive, -1 if the number is negative, and 0 + * otherwise + * + * @param fValue The float to examine. + * @return The float's sign. + */ + public static float sign(float fValue) { + return Math.signum(fValue); + } + + /** + * Given 3 points in a 2d plane, this function computes if the points going + * from A-B-C are moving counter clock wise. + * + * @param p0 Point 0. + * @param p1 Point 1. + * @param p2 Point 2. + * @return 1 If they are CCW, -1 if they are not CCW, 0 if p2 is between p0 + * and p1. + */ + public static int counterClockwise(Vector2f p0, Vector2f p1, Vector2f p2) { + float dx1, dx2, dy1, dy2; + dx1 = p1.x - p0.x; + dy1 = p1.y - p0.y; + dx2 = p2.x - p0.x; + dy2 = p2.y - p0.y; + if (dx1 * dy2 > dy1 * dx2) { + return 1; + } + if (dx1 * dy2 < dy1 * dx2) { + return -1; + } + if ((dx1 * dx2 < 0) || (dy1 * dy2 < 0)) { + return -1; + } + if ((dx1 * dx1 + dy1 * dy1) < (dx2 * dx2 + dy2 * dy2)) { + return 1; + } + return 0; + } + + /** + * Test if a point is inside a triangle. 1 if the point is on the ccw side, + * -1 if the point is on the cw side, and 0 if it is on neither. + * + * @param t0 First point of the triangle. + * @param t1 Second point of the triangle. + * @param t2 Third point of the triangle. + * @param p The point to test. + * @return Value 1 or -1 if inside triangle, 0 otherwise. + */ + public static int pointInsideTriangle(Vector2f t0, Vector2f t1, Vector2f t2, Vector2f p) { + int val1 = counterClockwise(t0, t1, p); + if (val1 == 0) { + return 1; + } + int val2 = counterClockwise(t1, t2, p); + if (val2 == 0) { + return 1; + } + if (val2 != val1) { + return 0; + } + int val3 = counterClockwise(t2, t0, p); + if (val3 == 0) { + return 1; + } + if (val3 != val1) { + return 0; + } + return val3; + } + + /** + * A method that computes normal for a triangle defined by three vertices. + * + * @param v1 first vertex + * @param v2 second vertex + * @param v3 third vertex + * @return a normal for the face + */ + public static Vector3f computeNormal(Vector3f v1, Vector3f v2, Vector3f v3) { + Vector3f a1 = v1.subtract(v2); + Vector3f a2 = v3.subtract(v2); + return a2.crossLocal(a1).normalizeLocal(); + } + + /** + * Returns the determinant of a 4x4 matrix. + */ + public static float determinant( + double m00, + double m01, + double m02, + double m03, + double m10, + double m11, + double m12, + double m13, + double m20, + double m21, + double m22, + double m23, + double m30, + double m31, + double m32, + double m33 + ) { + + double det01 = m20 * m31 - m21 * m30; + double det02 = m20 * m32 - m22 * m30; + double det03 = m20 * m33 - m23 * m30; + double det12 = m21 * m32 - m22 * m31; + double det13 = m21 * m33 - m23 * m31; + double det23 = m22 * m33 - m23 * m32; + return (float) (m00 * (m11 * det23 - m12 * det13 + m13 * det12) + - m01 + * (m10 * det23 - m12 * det03 + m13 * det02) + + m02 + * (m10 * det13 - m11 * det03 + m13 * det01) + - m03 + * (m10 * det12 - m11 * det02 + m12 * det01)); + } + + /** + * Returns a random float between 0 and 1. + * + * @return A random float between 0.0f (inclusive) to 1.0f + * (exclusive). + */ + public static float nextRandomFloat() { + return rand.nextFloat(); + } + + /** + * Returns a random integer between min and max. + * + * @return A random int between min (inclusive) to max + * (inclusive). + */ + public static int nextRandomInt(int min, int max) { + return (int) (nextRandomFloat() * (max - min + 1)) + min; + } + + public static int nextRandomInt() { + return rand.nextInt(); + } + + /** + * Converts a point from Spherical coordinates to Cartesian (using positive + * Y as up) and stores the results in the store var. + */ + public static Vector3f sphericalToCartesian( + Vector3f sphereCoords, + Vector3f store + ) { + if (store == null) { + store = new Vector3f(); + } + store.y = sphereCoords.x * FastMath.sin(sphereCoords.z); + float a = sphereCoords.x * FastMath.cos(sphereCoords.z); + store.x = a * FastMath.cos(sphereCoords.y); + store.z = a * FastMath.sin(sphereCoords.y); + + return store; + } + + /** + * Converts a point from Cartesian coordinates (using positive Y as up) to + * Spherical and stores the results in the store var. (Radius, Azimuth, + * Polar) + */ + public static Vector3f cartesianToSpherical( + Vector3f cartCoords, + Vector3f store + ) { + if (store == null) { + store = new Vector3f(); + } + float x = cartCoords.x; + if (x == 0) { + x = FastMath.FLT_EPSILON; + } + store.x = FastMath + .sqrt( + (x * x) + + (cartCoords.y * cartCoords.y) + + (cartCoords.z * cartCoords.z) + ); + store.y = FastMath.atan(cartCoords.z / x); + if (x < 0) { + store.y += FastMath.PI; + } + store.z = FastMath.asin(cartCoords.y / store.x); + return store; + } + + /** + * Converts a point from Spherical coordinates to Cartesian (using positive + * Z as up) and stores the results in the store var. + */ + public static Vector3f sphericalToCartesianZ( + Vector3f sphereCoords, + Vector3f store + ) { + if (store == null) { + store = new Vector3f(); + } + store.z = sphereCoords.x * FastMath.sin(sphereCoords.z); + float a = sphereCoords.x * FastMath.cos(sphereCoords.z); + store.x = a * FastMath.cos(sphereCoords.y); + store.y = a * FastMath.sin(sphereCoords.y); + + return store; + } + + /** + * Converts a point from Cartesian coordinates (using positive Z as up) to + * Spherical and stores the results in the store var. (Radius, Azimuth, + * Polar) + */ + public static Vector3f cartesianZToSpherical( + Vector3f cartCoords, + Vector3f store + ) { + if (store == null) { + store = new Vector3f(); + } + float x = cartCoords.x; + if (x == 0) { + x = FastMath.FLT_EPSILON; + } + store.x = FastMath + .sqrt( + (x * x) + + (cartCoords.y * cartCoords.y) + + (cartCoords.z * cartCoords.z) + ); + store.z = FastMath.atan(cartCoords.z / x); + if (x < 0) { + store.z += FastMath.PI; + } + store.y = FastMath.asin(cartCoords.y / store.x); + return store; + } + + /** + * Takes an value and expresses it in terms of min to max. + * + * @param val - the angle to normalize (in radians) + * @return the normalized angle (also in radians) + */ + public static float normalize(float val, float min, float max) { + if (Float.isInfinite(val) || Float.isNaN(val)) { + return 0f; + } + float range = max - min; + while (val > max) { + val -= range; + } + while (val < min) { + val += range; + } + return val; + } + + /** + * @param x the value whose sign is to be adjusted. + * @param y the value whose sign is to be used. + * @return x with its sign changed to match the sign of y. + */ + public static float copysign(float x, float y) { + if (y >= 0 && x <= -0) { + return -x; + } else if (y < 0 && x >= 0) { + return -x; + } else { + return x; + } + } + + /** + * Take a float input and clamp it between min and max. + * + * @param input + * @param min + * @param max + * @return clamped input + */ + public static float clamp(float input, float min, float max) { + return (input < min) ? min : (input > max) ? max : input; + } + + /** + * Clamps the given float to be between 0 and 1. + * + * @param input + * @return input clamped between 0 and 1. + */ + public static float saturate(float input) { + return clamp(input, 0f, 1f); + } + + /** + * Converts a single precision (32 bit) floating point value into half + * precision (16 bit). + * + *

+ * Source: + * + * http://www.fox-toolkit.org/ftp/fasthalffloatconversion.pdf
+ * broken link + * + * @param half The half floating point value as a short. + * @return floating point value of the half. + */ + public static float convertHalfToFloat(short half) { + switch ((int) half) { + case 0x0000: + return 0f; + case 0x8000: + return -0f; + case 0x7c00: + return Float.POSITIVE_INFINITY; + case 0xfc00: + return Float.NEGATIVE_INFINITY; + // TODO: Support for NaN? + default: + return Float + .intBitsToFloat( + ((half & 0x8000) << 16) + | (((half & 0x7c00) + 0x1C000) << 13) + | ((half & 0x03FF) << 13) + ); + } + } + + public static short convertFloatToHalf(float flt) { + if (Float.isNaN(flt)) { + throw new UnsupportedOperationException("NaN to half conversion not supported!"); + } else if (flt == Float.POSITIVE_INFINITY) { + return (short) 0x7c00; + } else if (flt == Float.NEGATIVE_INFINITY) { + return (short) 0xfc00; + } else if (flt == 0f) { + return (short) 0x0000; + } else if (flt == -0f) { + return (short) 0x8000; + } else if (flt > 65504f) { + // max value supported by half float + return 0x7bff; + } else if (flt < -65504f) { + return (short) (0x7bff | 0x8000); + } else if (flt > 0f && flt < 5.96046E-8f) { + return 0x0001; + } else if (flt < 0f && flt > -5.96046E-8f) { + return (short) 0x8001; + } + + int f = Float.floatToIntBits(flt); + return (short) (((f >> 16) & 0x8000) + | ((((f & 0x7f800000) - 0x38000000) >> 13) & 0x7c00) + | ((f >> 13) & 0x03ff)); + } +} diff --git a/java/com/jme3/math/Matrix3f.java b/java/com/jme3/math/Matrix3f.java new file mode 100644 index 000000000..76e745c27 --- /dev/null +++ b/java/com/jme3/math/Matrix3f.java @@ -0,0 +1,1362 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.math; + +import java.nio.FloatBuffer; +import java.util.logging.Logger; + +import com.jme3.util.TempVars; + + +/** + * Matrix3f defines a 3x3 matrix. Matrix data is maintained + * internally and is accessible via the get and set methods. Convenience methods + * are used for matrix operations as well as generating a matrix from a given + * set of values. + * + * @author Mark Powell + * @author Joshua Slack + */ +public final class Matrix3f implements Cloneable, java.io.Serializable { + + static final long serialVersionUID = 1; + + private static final Logger logger = Logger.getLogger(Matrix3f.class.getName()); + protected float m00, m01, m02; + protected float m10, m11, m12; + protected float m20, m21, m22; + public static final Matrix3f ZERO = new Matrix3f(0, 0, 0, 0, 0, 0, 0, 0, 0); + public static final Matrix3f IDENTITY = new Matrix3f(); + + /** + * Constructor instantiates a new Matrix3f object. The initial + * values for the matrix is that of the identity matrix. + * + */ + public Matrix3f() { + loadIdentity(); + } + + /** + * constructs a matrix with the given values. + * + * @param m00 0x0 in the matrix. + * @param m01 0x1 in the matrix. + * @param m02 0x2 in the matrix. + * @param m10 1x0 in the matrix. + * @param m11 1x1 in the matrix. + * @param m12 1x2 in the matrix. + * @param m20 2x0 in the matrix. + * @param m21 2x1 in the matrix. + * @param m22 2x2 in the matrix. + */ + public Matrix3f( + float m00, + float m01, + float m02, + float m10, + float m11, + float m12, + float m20, + float m21, + float m22 + ) { + + this.m00 = m00; + this.m01 = m01; + this.m02 = m02; + this.m10 = m10; + this.m11 = m11; + this.m12 = m12; + this.m20 = m20; + this.m21 = m21; + this.m22 = m22; + } + + /** + * Copy constructor that creates a new Matrix3f object that is + * the same as the provided matrix. + * + * @param mat the matrix to copy. + */ + public Matrix3f(Matrix3f mat) { + set(mat); + } + + /** + * Takes the absolute value of all matrix fields locally. + */ + public void absoluteLocal() { + m00 = FastMath.abs(m00); + m01 = FastMath.abs(m01); + m02 = FastMath.abs(m02); + m10 = FastMath.abs(m10); + m11 = FastMath.abs(m11); + m12 = FastMath.abs(m12); + m20 = FastMath.abs(m20); + m21 = FastMath.abs(m21); + m22 = FastMath.abs(m22); + } + + /** + * copy transfers the contents of a given matrix to this + * matrix. If a null matrix is supplied, this matrix is set to the identity + * matrix. + * + * @param matrix the matrix to copy. + * @return this + */ + public Matrix3f set(Matrix3f matrix) { + if (null == matrix) { + loadIdentity(); + } else { + m00 = matrix.m00; + m01 = matrix.m01; + m02 = matrix.m02; + m10 = matrix.m10; + m11 = matrix.m11; + m12 = matrix.m12; + m20 = matrix.m20; + m21 = matrix.m21; + m22 = matrix.m22; + } + return this; + } + + /** + * get retrieves a value from the matrix at the given position. + * If the position is invalid a JmeException is thrown. + * + * @param i the row index. + * @param j the colum index. + * @return the value at (i, j). + */ + @SuppressWarnings("fallthrough") + public float get(int i, int j) { + switch (i) { + case 0: + switch (j) { + case 0: + return m00; + case 1: + return m01; + case 2: + return m02; + } + case 1: + switch (j) { + case 0: + return m10; + case 1: + return m11; + case 2: + return m12; + } + case 2: + switch (j) { + case 0: + return m20; + case 1: + return m21; + case 2: + return m22; + } + } + + logger.warning("Invalid matrix index."); + throw new IllegalArgumentException("Invalid indices into matrix."); + } + + /** + * get(float[]) returns the matrix in row-major or column-major + * order. + * + * @param data The array to return the data into. This array can be 9 or 16 + * floats in size. Only the upper 3x3 are assigned to in the case of a 16 + * element array. + * @param rowMajor True for row major storage in the array (translation in + * elements 3, 7, 11 for a 4x4), false for column major (translation in + * elements 12, 13, 14 for a 4x4). + */ + public void get(float[] data, boolean rowMajor) { + if (data.length == 9) { + if (rowMajor) { + data[0] = m00; + data[1] = m01; + data[2] = m02; + data[3] = m10; + data[4] = m11; + data[5] = m12; + data[6] = m20; + data[7] = m21; + data[8] = m22; + } else { + data[0] = m00; + data[1] = m10; + data[2] = m20; + data[3] = m01; + data[4] = m11; + data[5] = m21; + data[6] = m02; + data[7] = m12; + data[8] = m22; + } + } else if (data.length == 16) { + if (rowMajor) { + data[0] = m00; + data[1] = m01; + data[2] = m02; + data[4] = m10; + data[5] = m11; + data[6] = m12; + data[8] = m20; + data[9] = m21; + data[10] = m22; + } else { + data[0] = m00; + data[1] = m10; + data[2] = m20; + data[4] = m01; + data[5] = m11; + data[6] = m21; + data[8] = m02; + data[9] = m12; + data[10] = m22; + } + } else { + throw new IndexOutOfBoundsException("Array size must be 9 or 16 in Matrix3f.get()."); + } + } + + /** + * Normalize this matrix and store the result in the store parameter that is + * returned. + * + * Note that the original matrix is not altered. + * + * @param store the matrix to store the result of the normalization. If this + * parameter is null a new one is created + * @return the normalized matrix + */ + public Matrix3f normalize(Matrix3f store) { + if (store == null) { + store = new Matrix3f(); + } + + float mag = 1.0f + / FastMath + .sqrt( + m00 * m00 + + m10 * m10 + + m20 * m20 + ); + + store.m00 = m00 * mag; + store.m10 = m10 * mag; + store.m20 = m20 * mag; + + mag = 1.0f + / FastMath + .sqrt( + m01 * m01 + + m11 * m11 + + m21 * m21 + ); + + store.m01 = m01 * mag; + store.m11 = m11 * mag; + store.m21 = m21 * mag; + + store.m02 = store.m10 * store.m21 - store.m11 * store.m20; + store.m12 = store.m01 * store.m20 - store.m00 * store.m21; + store.m22 = store.m00 * store.m11 - store.m01 * store.m10; + return store; + } + + /** + * Normalize this matrix + * + * @return this matrix once normalized. + */ + public Matrix3f normalizeLocal() { + return normalize(this); + } + + /** + * getColumn returns one of three columns specified by the + * parameter. This column is returned as a Vector3f object. + * + * @param i the column to retrieve. Must be between 0 and 2. + * @return the column specified by the index. + */ + public Vector3f getColumn(int i) { + return getColumn(i, null); + } + + /** + * getColumn returns one of three columns specified by the + * parameter. This column is returned as a Vector3f object. + * + * @param i the column to retrieve. Must be between 0 and 2. + * @param store the vector object to store the result in. if null, a new one + * is created. + * @return the column specified by the index. + */ + public Vector3f getColumn(int i, Vector3f store) { + if (store == null) { + store = new Vector3f(); + } + switch (i) { + case 0: + store.x = m00; + store.y = m10; + store.z = m20; + break; + case 1: + store.x = m01; + store.y = m11; + store.z = m21; + break; + case 2: + store.x = m02; + store.y = m12; + store.z = m22; + break; + default: + logger.warning("Invalid column index."); + throw new IllegalArgumentException("Invalid column index. " + i); + } + return store; + } + + /** + * getColumn returns one of three rows as specified by the + * parameter. This row is returned as a Vector3f object. + * + * @param i the row to retrieve. Must be between 0 and 2. + * @return the row specified by the index. + */ + public Vector3f getRow(int i) { + return getRow(i, null); + } + + /** + * getRow returns one of three rows as specified by the + * parameter. This row is returned as a Vector3f object. + * + * @param i the row to retrieve. Must be between 0 and 2. + * @param store the vector object to store the result in. if null, a new one + * is created. + * @return the row specified by the index. + */ + public Vector3f getRow(int i, Vector3f store) { + if (store == null) { + store = new Vector3f(); + } + switch (i) { + case 0: + store.x = m00; + store.y = m01; + store.z = m02; + break; + case 1: + store.x = m10; + store.y = m11; + store.z = m12; + break; + case 2: + store.x = m20; + store.y = m21; + store.z = m22; + break; + default: + logger.warning("Invalid row index."); + throw new IllegalArgumentException("Invalid row index. " + i); + } + return store; + } + + /** + * fillFloatBuffer fills a FloatBuffer object with the matrix + * data. + * + * @param fb the buffer to fill, starting at current position. Must have + * room for 9 more floats. + * @return matrix data as a FloatBuffer. (position is advanced by 9 and any + * limit set is not changed). + */ + public FloatBuffer fillFloatBuffer(FloatBuffer fb, boolean columnMajor) { +// if (columnMajor){ +// fb.put(m00).put(m10).put(m20); +// fb.put(m01).put(m11).put(m21); +// fb.put(m02).put(m12).put(m22); +// }else{ +// fb.put(m00).put(m01).put(m02); +// fb.put(m10).put(m11).put(m12); +// fb.put(m20).put(m21).put(m22); +// } + + TempVars vars = TempVars.get(); + + + fillFloatArray(vars.matrixWrite, columnMajor); + fb.put(vars.matrixWrite, 0, 9); + + vars.release(); + + return fb; + } + + public void fillFloatArray(float[] f, boolean columnMajor) { + if (columnMajor) { + f[0] = m00; + f[1] = m10; + f[2] = m20; + f[3] = m01; + f[4] = m11; + f[5] = m21; + f[6] = m02; + f[7] = m12; + f[8] = m22; + } else { + f[0] = m00; + f[1] = m01; + f[2] = m02; + f[3] = m10; + f[4] = m11; + f[5] = m12; + f[6] = m20; + f[7] = m21; + f[8] = m22; + } + } + + /** + * + * setColumn sets a particular column of this matrix to that + * represented by the provided vector. + * + * @param i the column to set. + * @param column the data to set. + * @return this + */ + public Matrix3f setColumn(int i, Vector3f column) { + + if (column == null) { + logger.warning("Column is null. Ignoring."); + return this; + } + switch (i) { + case 0: + m00 = column.x; + m10 = column.y; + m20 = column.z; + break; + case 1: + m01 = column.x; + m11 = column.y; + m21 = column.z; + break; + case 2: + m02 = column.x; + m12 = column.y; + m22 = column.z; + break; + default: + logger.warning("Invalid column index."); + throw new IllegalArgumentException("Invalid column index. " + i); + } + return this; + } + + /** + * + * setRow sets a particular row of this matrix to that + * represented by the provided vector. + * + * @param i the row to set. + * @param row the data to set. + * @return this + */ + public Matrix3f setRow(int i, Vector3f row) { + + if (row == null) { + logger.warning("Row is null. Ignoring."); + return this; + } + switch (i) { + case 0: + m00 = row.x; + m01 = row.y; + m02 = row.z; + break; + case 1: + m10 = row.x; + m11 = row.y; + m12 = row.z; + break; + case 2: + m20 = row.x; + m21 = row.y; + m22 = row.z; + break; + default: + logger.warning("Invalid row index."); + throw new IllegalArgumentException("Invalid row index. " + i); + } + return this; + } + + /** + * set places a given value into the matrix at the given + * position. If the position is invalid a JmeException is + * thrown. + * + * @param i the row index. + * @param j the colum index. + * @param value the value for (i, j). + * @return this + */ + @SuppressWarnings("fallthrough") + public Matrix3f set(int i, int j, float value) { + switch (i) { + case 0: + switch (j) { + case 0: + m00 = value; + return this; + case 1: + m01 = value; + return this; + case 2: + m02 = value; + return this; + } + case 1: + switch (j) { + case 0: + m10 = value; + return this; + case 1: + m11 = value; + return this; + case 2: + m12 = value; + return this; + } + case 2: + switch (j) { + case 0: + m20 = value; + return this; + case 1: + m21 = value; + return this; + case 2: + m22 = value; + return this; + } + } + + logger.warning("Invalid matrix index."); + throw new IllegalArgumentException("Invalid indices into matrix."); + } + + /** + * + * set sets the values of the matrix to those supplied by the + * 3x3 two dimenion array. + * + * @param matrix the new values of the matrix. + * @throws JmeException if the array is not of size 9. + * @return this + */ + public Matrix3f set(float[][] matrix) { + if (matrix.length != 3 || matrix[0].length != 3) { + throw new IllegalArgumentException( + "Array must be of size 9." + ); + } + + m00 = matrix[0][0]; + m01 = matrix[0][1]; + m02 = matrix[0][2]; + m10 = matrix[1][0]; + m11 = matrix[1][1]; + m12 = matrix[1][2]; + m20 = matrix[2][0]; + m21 = matrix[2][1]; + m22 = matrix[2][2]; + + return this; + } + + /** + * Recreate Matrix using the provided axis. + * + * @param uAxis Vector3f + * @param vAxis Vector3f + * @param wAxis Vector3f + */ + public void fromAxes(Vector3f uAxis, Vector3f vAxis, Vector3f wAxis) { + m00 = uAxis.x; + m10 = uAxis.y; + m20 = uAxis.z; + + m01 = vAxis.x; + m11 = vAxis.y; + m21 = vAxis.z; + + m02 = wAxis.x; + m12 = wAxis.y; + m22 = wAxis.z; + } + + /** + * set sets the values of this matrix from an array of values + * assuming that the data is rowMajor order; + * + * @param matrix the matrix to set the value to. + * @return this + */ + public Matrix3f set(float[] matrix) { + return set(matrix, true); + } + + /** + * set sets the values of this matrix from an array of values; + * + * @param matrix the matrix to set the value to. + * @param rowMajor whether the incoming data is in row or column major + * order. + * @return this + */ + public Matrix3f set(float[] matrix, boolean rowMajor) { + if (matrix.length != 9) { + throw new IllegalArgumentException( + "Array must be of size 9." + ); + } + + if (rowMajor) { + m00 = matrix[0]; + m01 = matrix[1]; + m02 = matrix[2]; + m10 = matrix[3]; + m11 = matrix[4]; + m12 = matrix[5]; + m20 = matrix[6]; + m21 = matrix[7]; + m22 = matrix[8]; + } else { + m00 = matrix[0]; + m01 = matrix[3]; + m02 = matrix[6]; + m10 = matrix[1]; + m11 = matrix[4]; + m12 = matrix[7]; + m20 = matrix[2]; + m21 = matrix[5]; + m22 = matrix[8]; + } + return this; + } + + /** + * + * set defines the values of the matrix based on a supplied + * Quaternion. It should be noted that all previous values will + * be overridden. + * + * @param quaternion the quaternion to create a rotational matrix from. + * @return this + */ + public Matrix3f set(Quaternion quaternion) { + return quaternion.toRotationMatrix(this); + } + + /** + * loadIdentity sets this matrix to the identity matrix. Where + * all values are zero except those along the diagonal which are one. + * + */ + public void loadIdentity() { + m01 = m02 = m10 = m12 = m20 = m21 = 0; + m00 = m11 = m22 = 1; + } + + /** + * @return true if this matrix is identity + */ + public boolean isIdentity() { + return (m00 == 1 && m01 == 0 && m02 == 0) + && (m10 == 0 && m11 == 1 && m12 == 0) + && (m20 == 0 && m21 == 0 && m22 == 1); + } + + /** + * fromAngleAxis sets this matrix4f to the values specified by + * an angle and an axis of rotation. This method creates an object, so use + * fromAngleNormalAxis if your axis is already normalized. + * + * @param angle the angle to rotate (in radians). + * @param axis the axis of rotation. + */ + public void fromAngleAxis(float angle, Vector3f axis) { + Vector3f normAxis = axis.normalize(); + fromAngleNormalAxis(angle, normAxis); + } + + /** + * fromAngleNormalAxis sets this matrix4f to the values + * specified by an angle and a normalized axis of rotation. + * + * @param angle the angle to rotate (in radians). + * @param axis the axis of rotation (already normalized). + */ + public void fromAngleNormalAxis(float angle, Vector3f axis) { + float fCos = FastMath.cos(angle); + float fSin = FastMath.sin(angle); + float fOneMinusCos = ((float) 1.0) - fCos; + float fX2 = axis.x * axis.x; + float fY2 = axis.y * axis.y; + float fZ2 = axis.z * axis.z; + float fXYM = axis.x * axis.y * fOneMinusCos; + float fXZM = axis.x * axis.z * fOneMinusCos; + float fYZM = axis.y * axis.z * fOneMinusCos; + float fXSin = axis.x * fSin; + float fYSin = axis.y * fSin; + float fZSin = axis.z * fSin; + + m00 = fX2 * fOneMinusCos + fCos; + m01 = fXYM - fZSin; + m02 = fXZM + fYSin; + m10 = fXYM + fZSin; + m11 = fY2 * fOneMinusCos + fCos; + m12 = fYZM - fXSin; + m20 = fXZM - fYSin; + m21 = fYZM + fXSin; + m22 = fZ2 * fOneMinusCos + fCos; + } + + /** + * mult multiplies this matrix by a given matrix. The result + * matrix is returned as a new object. If the given matrix is null, a null + * matrix is returned. + * + * @param mat the matrix to multiply this matrix by. + * @return the result matrix. + */ + public Matrix3f mult(Matrix3f mat) { + return mult(mat, null); + } + + /** + * mult multiplies this matrix by a given matrix. The result + * matrix is returned as a new object. + * + * @param mat the matrix to multiply this matrix by. + * @param product the matrix to store the result in. if null, a new matrix3f + * is created. It is safe for mat and product to be the same object. + * @return a matrix3f object containing the result of this operation + */ + public Matrix3f mult(Matrix3f mat, Matrix3f product) { + + float temp00, temp01, temp02; + float temp10, temp11, temp12; + float temp20, temp21, temp22; + + if (product == null) { + product = new Matrix3f(); + } + temp00 = m00 * mat.m00 + m01 * mat.m10 + m02 * mat.m20; + temp01 = m00 * mat.m01 + m01 * mat.m11 + m02 * mat.m21; + temp02 = m00 * mat.m02 + m01 * mat.m12 + m02 * mat.m22; + temp10 = m10 * mat.m00 + m11 * mat.m10 + m12 * mat.m20; + temp11 = m10 * mat.m01 + m11 * mat.m11 + m12 * mat.m21; + temp12 = m10 * mat.m02 + m11 * mat.m12 + m12 * mat.m22; + temp20 = m20 * mat.m00 + m21 * mat.m10 + m22 * mat.m20; + temp21 = m20 * mat.m01 + m21 * mat.m11 + m22 * mat.m21; + temp22 = m20 * mat.m02 + m21 * mat.m12 + m22 * mat.m22; + + product.m00 = temp00; + product.m01 = temp01; + product.m02 = temp02; + product.m10 = temp10; + product.m11 = temp11; + product.m12 = temp12; + product.m20 = temp20; + product.m21 = temp21; + product.m22 = temp22; + + return product; + } + + /** + * mult multiplies this matrix by a given Vector3f + * object. The result vector is returned. If the given vector is null, null + * will be returned. + * + * @param vec the vector to multiply this matrix by. + * @return the result vector. + */ + public Vector3f mult(Vector3f vec) { + return mult(vec, null); + } + + /** + * Multiplies this 3x3 matrix by the 1x3 Vector vec and stores the result in + * product. + * + * @param vec The Vector3f to multiply. + * @param product The Vector3f to store the result, it is safe for this to + * be the same as vec. + * @return The given product vector. + */ + public Vector3f mult(Vector3f vec, Vector3f product) { + + if (null == product) { + product = new Vector3f(); + } + + float x = vec.x; + float y = vec.y; + float z = vec.z; + + product.x = m00 * x + m01 * y + m02 * z; + product.y = m10 * x + m11 * y + m12 * z; + product.z = m20 * x + m21 * y + m22 * z; + return product; + } + + /** + * multLocal multiplies this matrix internally by a given float + * scale factor. + * + * @param scale the value to scale by. + * @return this Matrix3f + */ + public Matrix3f multLocal(float scale) { + m00 *= scale; + m01 *= scale; + m02 *= scale; + m10 *= scale; + m11 *= scale; + m12 *= scale; + m20 *= scale; + m21 *= scale; + m22 *= scale; + return this; + } + + /** + * multLocal multiplies this matrix by a given + * Vector3f object. The result vector is stored inside the + * passed vector, then returned . If the given vector is null, null will be + * returned. + * + * @param vec the vector to multiply this matrix by. + * @return The passed vector after multiplication + */ + public Vector3f multLocal(Vector3f vec) { + if (vec == null) { + return null; + } + float x = vec.x; + float y = vec.y; + vec.x = m00 * x + m01 * y + m02 * vec.z; + vec.y = m10 * x + m11 * y + m12 * vec.z; + vec.z = m20 * x + m21 * y + m22 * vec.z; + return vec; + } + + /** + * mult multiplies this matrix by a given matrix. The result + * matrix is saved in the current matrix. If the given matrix is null, + * nothing happens. The current matrix is returned. This is equivalent to + * this*=mat + * + * @param mat the matrix to multiply this matrix by. + * @return This matrix, after the multiplication + */ + public Matrix3f multLocal(Matrix3f mat) { + return mult(mat, this); + } + + /** + * Transposes this matrix in place. Returns this matrix for chaining + * + * @return This matrix after transpose + */ + public Matrix3f transposeLocal() { +// float[] tmp = new float[9]; +// get(tmp, false); +// set(tmp, true); + + float tmp = m01; + m01 = m10; + m10 = tmp; + + tmp = m02; + m02 = m20; + m20 = tmp; + + tmp = m12; + m12 = m21; + m21 = tmp; + + return this; + } + + /** + * Inverts this matrix as a new Matrix3f. + * + * @return The new inverse matrix + */ + public Matrix3f invert() { + return invert(null); + } + + /** + * Inverts this matrix and stores it in the given store. + * + * @return The store + */ + public Matrix3f invert(Matrix3f store) { + if (store == null) { + store = new Matrix3f(); + } + + float det = determinant(); + if (FastMath.abs(det) <= FastMath.FLT_EPSILON) { + return store.zero(); + } + + store.m00 = m11 * m22 - m12 * m21; + store.m01 = m02 * m21 - m01 * m22; + store.m02 = m01 * m12 - m02 * m11; + store.m10 = m12 * m20 - m10 * m22; + store.m11 = m00 * m22 - m02 * m20; + store.m12 = m02 * m10 - m00 * m12; + store.m20 = m10 * m21 - m11 * m20; + store.m21 = m01 * m20 - m00 * m21; + store.m22 = m00 * m11 - m01 * m10; + + store.multLocal(1f / det); + return store; + } + + /** + * Inverts this matrix locally. + * + * @return this + */ + public Matrix3f invertLocal() { + float det = determinant(); + if (FastMath.abs(det) <= 0f) { + return zero(); + } + + float f00 = m11 * m22 - m12 * m21; + float f01 = m02 * m21 - m01 * m22; + float f02 = m01 * m12 - m02 * m11; + float f10 = m12 * m20 - m10 * m22; + float f11 = m00 * m22 - m02 * m20; + float f12 = m02 * m10 - m00 * m12; + float f20 = m10 * m21 - m11 * m20; + float f21 = m01 * m20 - m00 * m21; + float f22 = m00 * m11 - m01 * m10; + + m00 = f00; + m01 = f01; + m02 = f02; + m10 = f10; + m11 = f11; + m12 = f12; + m20 = f20; + m21 = f21; + m22 = f22; + + multLocal(1f / det); + return this; + } + + /** + * Returns a new matrix representing the adjoint of this matrix. + * + * @return The adjoint matrix + */ + public Matrix3f adjoint() { + return adjoint(null); + } + + /** + * Places the adjoint of this matrix in store (creates store if null.) + * + * @param store The matrix to store the result in. If null, a new matrix is + * created. + * @return store + */ + public Matrix3f adjoint(Matrix3f store) { + if (store == null) { + store = new Matrix3f(); + } + + store.m00 = m11 * m22 - m12 * m21; + store.m01 = m02 * m21 - m01 * m22; + store.m02 = m01 * m12 - m02 * m11; + store.m10 = m12 * m20 - m10 * m22; + store.m11 = m00 * m22 - m02 * m20; + store.m12 = m02 * m10 - m00 * m12; + store.m20 = m10 * m21 - m11 * m20; + store.m21 = m01 * m20 - m00 * m21; + store.m22 = m00 * m11 - m01 * m10; + + return store; + } + + /** + * determinant generates the determinant of this matrix. + * + * @return the determinant + */ + public float determinant() { + float fCo00 = m11 * m22 - m12 * m21; + float fCo10 = m12 * m20 - m10 * m22; + float fCo20 = m10 * m21 - m11 * m20; + float fDet = m00 * fCo00 + m01 * fCo10 + m02 * fCo20; + return fDet; + } + + /** + * Sets all of the values in this matrix to zero. + * + * @return this matrix + */ + public Matrix3f zero() { + m00 = m01 = m02 = m10 = m11 = m12 = m20 = m21 = m22 = 0.0f; + return this; + } + + /** + * transpose locally transposes this Matrix. This is + * inconsistent with general value vs local semantics, but is preserved for + * backwards compatibility. Use transposeNew() to transpose to a new object + * (value). + * + * @return this object for chaining. + */ + public Matrix3f transpose() { + return transposeLocal(); + } + + /** + * transposeNew returns a transposed version of this matrix. + * + * @return The new Matrix3f object. + */ + public Matrix3f transposeNew() { + Matrix3f ret = new Matrix3f(m00, m10, m20, m01, m11, m21, m02, m12, m22); + return ret; + } + + /** + * toString returns the string representation of this object. + * It is in a format of a 3x3 matrix. For example, an identity matrix would + * be represented by the following string. com.jme.math.Matrix3f
+ * [
+ * 1.0 0.0 0.0
+ * 0.0 1.0 0.0
+ * 0.0 0.0 1.0
+ * ]
+ * + * @return the string representation of this object. + */ + @Override + public String toString() { + StringBuilder result = new StringBuilder("Matrix3f\n[\n"); + result.append(" "); + result.append(m00); + result.append(" "); + result.append(m01); + result.append(" "); + result.append(m02); + result.append(" \n"); + result.append(" "); + result.append(m10); + result.append(" "); + result.append(m11); + result.append(" "); + result.append(m12); + result.append(" \n"); + result.append(" "); + result.append(m20); + result.append(" "); + result.append(m21); + result.append(" "); + result.append(m22); + result.append(" \n]"); + return result.toString(); + } + + /** + * + * hashCode returns the hash code value as an integer and is + * supported for the benefit of hashing based collection classes such as + * Hashtable, HashMap, HashSet etc. + * + * @return the hashcode for this instance of Matrix4f. + * @see java.lang.Object#hashCode() + */ + @Override + public int hashCode() { + int hash = 37; + hash = 37 * hash + Float.floatToIntBits(m00); + hash = 37 * hash + Float.floatToIntBits(m01); + hash = 37 * hash + Float.floatToIntBits(m02); + + hash = 37 * hash + Float.floatToIntBits(m10); + hash = 37 * hash + Float.floatToIntBits(m11); + hash = 37 * hash + Float.floatToIntBits(m12); + + hash = 37 * hash + Float.floatToIntBits(m20); + hash = 37 * hash + Float.floatToIntBits(m21); + hash = 37 * hash + Float.floatToIntBits(m22); + + return hash; + } + + /** + * are these two matrices the same? they are is they both have the same mXX + * values. + * + * @param o the object to compare for equality + * @return true if they are equal + */ + @Override + public boolean equals(Object o) { + if (!(o instanceof Matrix3f) || o == null) { + return false; + } + + if (this == o) { + return true; + } + + Matrix3f comp = (Matrix3f) o; + if (Float.compare(m00, comp.m00) != 0) { + return false; + } + if (Float.compare(m01, comp.m01) != 0) { + return false; + } + if (Float.compare(m02, comp.m02) != 0) { + return false; + } + + if (Float.compare(m10, comp.m10) != 0) { + return false; + } + if (Float.compare(m11, comp.m11) != 0) { + return false; + } + if (Float.compare(m12, comp.m12) != 0) { + return false; + } + + if (Float.compare(m20, comp.m20) != 0) { + return false; + } + if (Float.compare(m21, comp.m21) != 0) { + return false; + } + if (Float.compare(m22, comp.m22) != 0) { + return false; + } + + return true; + } + + /** + * A function for creating a rotation matrix that rotates a vector called + * "start" into another vector called "end". + * + * @param start normalized non-zero starting vector + * @param end normalized non-zero ending vector + * @see "Tomas M�ller, John Hughes \"Efficiently Building a Matrix to Rotate + * \ One Vector to Another\" Journal of Graphics Tools, 4(4):1-4, 1999" + */ + public void fromStartEndVectors(Vector3f start, Vector3f end) { + Vector3f v = new Vector3f(); + float e, h, f; + + start.cross(end, v); + e = start.dot(end); + f = (e < 0) ? -e : e; + + // if "from" and "to" vectors are nearly parallel + if (f > 1.0f - FastMath.ZERO_TOLERANCE) { + Vector3f u = new Vector3f(); + Vector3f x = new Vector3f(); + float c1, c2, c3; /* coefficients for later use */ + int i, j; + + x.x = (start.x > 0.0) ? start.x : -start.x; + x.y = (start.y > 0.0) ? start.y : -start.y; + x.z = (start.z > 0.0) ? start.z : -start.z; + + if (x.x < x.y) { + if (x.x < x.z) { + x.x = 1.0f; + x.y = x.z = 0.0f; + } else { + x.z = 1.0f; + x.x = x.y = 0.0f; + } + } else { + if (x.y < x.z) { + x.y = 1.0f; + x.x = x.z = 0.0f; + } else { + x.z = 1.0f; + x.x = x.y = 0.0f; + } + } + + u.x = x.x - start.x; + u.y = x.y - start.y; + u.z = x.z - start.z; + v.x = x.x - end.x; + v.y = x.y - end.y; + v.z = x.z - end.z; + + c1 = 2.0f / u.dot(u); + c2 = 2.0f / v.dot(v); + c3 = c1 * c2 * u.dot(v); + + for (i = 0; i < 3; i++) { + for (j = 0; j < 3; j++) { + float val = -c1 * u.get(i) * u.get(j) + - c2 + * v.get(i) + * v.get(j) + + c3 * v.get(i) * u.get(j); + set(i, j, val); + } + float val = get(i, i); + set(i, i, val + 1.0f); + } + } else { + // the most common case, unless "start"="end", or "start"=-"end" + float hvx, hvz, hvxy, hvxz, hvyz; + h = 1.0f / (1.0f + e); + hvx = h * v.x; + hvz = h * v.z; + hvxy = hvx * v.y; + hvxz = hvx * v.z; + hvyz = hvz * v.y; + set(0, 0, e + hvx * v.x); + set(0, 1, hvxy - v.z); + set(0, 2, hvxz + v.y); + + set(1, 0, hvxy + v.z); + set(1, 1, e + h * v.y * v.y); + set(1, 2, hvyz - v.x); + + set(2, 0, hvxz - v.y); + set(2, 1, hvyz + v.x); + set(2, 2, e + hvz * v.z); + } + } + + /** + * scale scales the operation performed by this matrix on a + * per-component basis. + * + * @param scale The scale applied to each of the X, Y and Z output values. + */ + public void scale(Vector3f scale) { + m00 *= scale.x; + m10 *= scale.x; + m20 *= scale.x; + m01 *= scale.y; + m11 *= scale.y; + m21 *= scale.y; + m02 *= scale.z; + m12 *= scale.z; + m22 *= scale.z; + } + + static boolean equalIdentity(Matrix3f mat) { + if (Math.abs(mat.m00 - 1) > 1e-4) { + return false; + } + if (Math.abs(mat.m11 - 1) > 1e-4) { + return false; + } + if (Math.abs(mat.m22 - 1) > 1e-4) { + return false; + } + + if (Math.abs(mat.m01) > 1e-4) { + return false; + } + if (Math.abs(mat.m02) > 1e-4) { + return false; + } + + if (Math.abs(mat.m10) > 1e-4) { + return false; + } + if (Math.abs(mat.m12) > 1e-4) { + return false; + } + + if (Math.abs(mat.m20) > 1e-4) { + return false; + } + if (Math.abs(mat.m21) > 1e-4) { + return false; + } + + return true; + } + + @Override + public Matrix3f clone() { + try { + return (Matrix3f) super.clone(); + } catch (CloneNotSupportedException e) { + throw new AssertionError(); // can not happen + } + } +} diff --git a/java/com/jme3/math/Matrix4f.java b/java/com/jme3/math/Matrix4f.java new file mode 100644 index 000000000..b490b7067 --- /dev/null +++ b/java/com/jme3/math/Matrix4f.java @@ -0,0 +1,2277 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.math; + +import java.nio.FloatBuffer; +import java.util.logging.Logger; + +import com.jme3.util.TempVars; + + +/** + * Matrix4f defines and maintains a 4x4 matrix in row major order. + * This matrix is intended for use in a translation and rotational capacity. It + * provides convenience methods for creating the matrix from a multitude of + * sources. + * + * Matrices are stored assuming column vectors on the right, with the + * translation in the rightmost column. Element numbering is row,column, so m03 + * is the zeroth row, third column, which is the "x" translation part. This + * means that the implicit storage order is column major. However, the get() and + * set() functions on float arrays default to row major order! + * + * @author Mark Powell + * @author Joshua Slack + */ +public final class Matrix4f implements Cloneable, java.io.Serializable { + + static final long serialVersionUID = 1; + + private static final Logger logger = Logger.getLogger(Matrix4f.class.getName()); + public float m00, m01, m02, m03; + public float m10, m11, m12, m13; + public float m20, m21, m22, m23; + public float m30, m31, m32, m33; + public static final Matrix4f ZERO = new Matrix4f( + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0 + ); + public static final Matrix4f IDENTITY = new Matrix4f(); + + /** + * Constructor instantiates a new Matrix that is set to the + * identity matrix. + * + */ + public Matrix4f() { + loadIdentity(); + } + + /** + * constructs a matrix with the given values. + */ + public Matrix4f( + float m00, + float m01, + float m02, + float m03, + float m10, + float m11, + float m12, + float m13, + float m20, + float m21, + float m22, + float m23, + float m30, + float m31, + float m32, + float m33 + ) { + + this.m00 = m00; + this.m01 = m01; + this.m02 = m02; + this.m03 = m03; + this.m10 = m10; + this.m11 = m11; + this.m12 = m12; + this.m13 = m13; + this.m20 = m20; + this.m21 = m21; + this.m22 = m22; + this.m23 = m23; + this.m30 = m30; + this.m31 = m31; + this.m32 = m32; + this.m33 = m33; + } + + /** + * Create a new Matrix4f, given data in column-major format. + * + * @param array An array of 16 floats in column-major format (translation in + * elements 12, 13 and 14). + */ + public Matrix4f(float[] array) { + set(array, false); + } + + /** + * Constructor instantiates a new Matrix that is set to the + * provided matrix. This constructor copies a given Matrix. If the provided + * matrix is null, the constructor sets the matrix to the identity. + * + * @param mat the matrix to copy. + */ + public Matrix4f(Matrix4f mat) { + copy(mat); + } + + /** + * copy transfers the contents of a given matrix to this + * matrix. If a null matrix is supplied, this matrix is set to the identity + * matrix. + * + * @param matrix the matrix to copy. + */ + public void copy(Matrix4f matrix) { + if (null == matrix) { + loadIdentity(); + } else { + m00 = matrix.m00; + m01 = matrix.m01; + m02 = matrix.m02; + m03 = matrix.m03; + m10 = matrix.m10; + m11 = matrix.m11; + m12 = matrix.m12; + m13 = matrix.m13; + m20 = matrix.m20; + m21 = matrix.m21; + m22 = matrix.m22; + m23 = matrix.m23; + m30 = matrix.m30; + m31 = matrix.m31; + m32 = matrix.m32; + m33 = matrix.m33; + } + } + + public void fromFrame(Vector3f location, Vector3f direction, Vector3f up, Vector3f left) { + loadIdentity(); + + TempVars vars = TempVars.get(); + + Vector3f f = vars.vect1.set(direction); + Vector3f s = vars.vect2.set(f).crossLocal(up); + Vector3f u = vars.vect3.set(s).crossLocal(f); +// s.normalizeLocal(); +// u.normalizeLocal(); + + m00 = s.x; + m01 = s.y; + m02 = s.z; + + m10 = u.x; + m11 = u.y; + m12 = u.z; + + m20 = -f.x; + m21 = -f.y; + m22 = -f.z; + +// m00 = -left.x; +// m10 = -left.y; +// m20 = -left.z; +// +// m01 = up.x; +// m11 = up.y; +// m21 = up.z; +// +// m02 = -direction.x; +// m12 = -direction.y; +// m22 = -direction.z; +// + + Matrix4f transMatrix = vars.tempMat4; + transMatrix.loadIdentity(); + transMatrix.m03 = -location.x; + transMatrix.m13 = -location.y; + transMatrix.m23 = -location.z; + this.multLocal(transMatrix); + + vars.release(); + +// transMatrix.multLocal(this); + +// set(transMatrix); + } + + /** + * get retrieves the values of this object into a float array + * in row-major order. + * + * @param matrix the matrix to set the values into. + */ + public void get(float[] matrix) { + get(matrix, true); + } + + /** + * set retrieves the values of this object into a float array. + * + * @param matrix the matrix to set the values into. + * @param rowMajor whether the outgoing data is in row or column major + * order. + */ + public void get(float[] matrix, boolean rowMajor) { + if (matrix.length != 16) { + throw new IllegalArgumentException( + "Array must be of size 16." + ); + } + + if (rowMajor) { + matrix[0] = m00; + matrix[1] = m01; + matrix[2] = m02; + matrix[3] = m03; + matrix[4] = m10; + matrix[5] = m11; + matrix[6] = m12; + matrix[7] = m13; + matrix[8] = m20; + matrix[9] = m21; + matrix[10] = m22; + matrix[11] = m23; + matrix[12] = m30; + matrix[13] = m31; + matrix[14] = m32; + matrix[15] = m33; + } else { + matrix[0] = m00; + matrix[4] = m01; + matrix[8] = m02; + matrix[12] = m03; + matrix[1] = m10; + matrix[5] = m11; + matrix[9] = m12; + matrix[13] = m13; + matrix[2] = m20; + matrix[6] = m21; + matrix[10] = m22; + matrix[14] = m23; + matrix[3] = m30; + matrix[7] = m31; + matrix[11] = m32; + matrix[15] = m33; + } + } + + /** + * get retrieves a value from the matrix at the given position. + * If the position is invalid a JmeException is thrown. + * + * @param i the row index. + * @param j the colum index. + * @return the value at (i, j). + */ + @SuppressWarnings("fallthrough") + public float get(int i, int j) { + switch (i) { + case 0: + switch (j) { + case 0: + return m00; + case 1: + return m01; + case 2: + return m02; + case 3: + return m03; + } + case 1: + switch (j) { + case 0: + return m10; + case 1: + return m11; + case 2: + return m12; + case 3: + return m13; + } + case 2: + switch (j) { + case 0: + return m20; + case 1: + return m21; + case 2: + return m22; + case 3: + return m23; + } + case 3: + switch (j) { + case 0: + return m30; + case 1: + return m31; + case 2: + return m32; + case 3: + return m33; + } + } + + logger.warning("Invalid matrix index."); + throw new IllegalArgumentException("Invalid indices into matrix."); + } + + /** + * getColumn returns one of three columns specified by the + * parameter. This column is returned as a float array of length 4. + * + * @param i the column to retrieve. Must be between 0 and 3. + * @return the column specified by the index. + */ + public float[] getColumn(int i) { + return getColumn(i, null); + } + + /** + * getColumn returns one of three columns specified by the + * parameter. This column is returned as a float[4]. + * + * @param i the column to retrieve. Must be between 0 and 3. + * @param store the float array to store the result in. if null, a new one + * is created. + * @return the column specified by the index. + */ + public float[] getColumn(int i, float[] store) { + if (store == null) { + store = new float[4]; + } + switch (i) { + case 0: + store[0] = m00; + store[1] = m10; + store[2] = m20; + store[3] = m30; + break; + case 1: + store[0] = m01; + store[1] = m11; + store[2] = m21; + store[3] = m31; + break; + case 2: + store[0] = m02; + store[1] = m12; + store[2] = m22; + store[3] = m32; + break; + case 3: + store[0] = m03; + store[1] = m13; + store[2] = m23; + store[3] = m33; + break; + default: + logger.warning("Invalid column index."); + throw new IllegalArgumentException("Invalid column index. " + i); + } + return store; + } + + /** + * + * setColumn sets a particular column of this matrix to that + * represented by the provided vector. + * + * @param i the column to set. + * @param column the data to set. + */ + public void setColumn(int i, float[] column) { + + if (column == null) { + logger.warning("Column is null. Ignoring."); + return; + } + switch (i) { + case 0: + m00 = column[0]; + m10 = column[1]; + m20 = column[2]; + m30 = column[3]; + break; + case 1: + m01 = column[0]; + m11 = column[1]; + m21 = column[2]; + m31 = column[3]; + break; + case 2: + m02 = column[0]; + m12 = column[1]; + m22 = column[2]; + m32 = column[3]; + break; + case 3: + m03 = column[0]; + m13 = column[1]; + m23 = column[2]; + m33 = column[3]; + break; + default: + logger.warning("Invalid column index."); + throw new IllegalArgumentException("Invalid column index. " + i); + } + } + + /** + * set places a given value into the matrix at the given + * position. If the position is invalid a JmeException is + * thrown. + * + * @param i the row index. + * @param j the colum index. + * @param value the value for (i, j). + */ + @SuppressWarnings("fallthrough") + public void set(int i, int j, float value) { + switch (i) { + case 0: + switch (j) { + case 0: + m00 = value; + return; + case 1: + m01 = value; + return; + case 2: + m02 = value; + return; + case 3: + m03 = value; + return; + } + case 1: + switch (j) { + case 0: + m10 = value; + return; + case 1: + m11 = value; + return; + case 2: + m12 = value; + return; + case 3: + m13 = value; + return; + } + case 2: + switch (j) { + case 0: + m20 = value; + return; + case 1: + m21 = value; + return; + case 2: + m22 = value; + return; + case 3: + m23 = value; + return; + } + case 3: + switch (j) { + case 0: + m30 = value; + return; + case 1: + m31 = value; + return; + case 2: + m32 = value; + return; + case 3: + m33 = value; + return; + } + } + + logger.warning("Invalid matrix index."); + throw new IllegalArgumentException("Invalid indices into matrix."); + } + + /** + * set sets the values of this matrix from an array of values. + * + * @param matrix the matrix to set the value to. + * @throws JmeException if the array is not of size 16. + */ + public void set(float[][] matrix) { + if (matrix.length != 4 || matrix[0].length != 4) { + throw new IllegalArgumentException( + "Array must be of size 16." + ); + } + + m00 = matrix[0][0]; + m01 = matrix[0][1]; + m02 = matrix[0][2]; + m03 = matrix[0][3]; + m10 = matrix[1][0]; + m11 = matrix[1][1]; + m12 = matrix[1][2]; + m13 = matrix[1][3]; + m20 = matrix[2][0]; + m21 = matrix[2][1]; + m22 = matrix[2][2]; + m23 = matrix[2][3]; + m30 = matrix[3][0]; + m31 = matrix[3][1]; + m32 = matrix[3][2]; + m33 = matrix[3][3]; + } + + + /** + * Sets the values of this matrix + */ + public void set( + float m00, + float m01, + float m02, + float m03, + float m10, + float m11, + float m12, + float m13, + float m20, + float m21, + float m22, + float m23, + float m30, + float m31, + float m32, + float m33 + ) { + + this.m00 = m00; + this.m01 = m01; + this.m02 = m02; + this.m03 = m03; + this.m10 = m10; + this.m11 = m11; + this.m12 = m12; + this.m13 = m13; + this.m20 = m20; + this.m21 = m21; + this.m22 = m22; + this.m23 = m23; + this.m30 = m30; + this.m31 = m31; + this.m32 = m32; + this.m33 = m33; + } + + /** + * set sets the values of this matrix from another matrix. + * + * @param matrix the matrix to read the value from. + */ + public Matrix4f set(Matrix4f matrix) { + m00 = matrix.m00; + m01 = matrix.m01; + m02 = matrix.m02; + m03 = matrix.m03; + m10 = matrix.m10; + m11 = matrix.m11; + m12 = matrix.m12; + m13 = matrix.m13; + m20 = matrix.m20; + m21 = matrix.m21; + m22 = matrix.m22; + m23 = matrix.m23; + m30 = matrix.m30; + m31 = matrix.m31; + m32 = matrix.m32; + m33 = matrix.m33; + return this; + } + + /** + * set sets the values of this matrix from an array of values + * assuming that the data is rowMajor order; + * + * @param matrix the matrix to set the value to. + */ + public void set(float[] matrix) { + set(matrix, true); + } + + /** + * set sets the values of this matrix from an array of values; + * + * @param matrix the matrix to set the value to. + * @param rowMajor whether the incoming data is in row or column major + * order. + */ + public void set(float[] matrix, boolean rowMajor) { + if (matrix.length != 16) { + throw new IllegalArgumentException( + "Array must be of size 16." + ); + } + + if (rowMajor) { + m00 = matrix[0]; + m01 = matrix[1]; + m02 = matrix[2]; + m03 = matrix[3]; + m10 = matrix[4]; + m11 = matrix[5]; + m12 = matrix[6]; + m13 = matrix[7]; + m20 = matrix[8]; + m21 = matrix[9]; + m22 = matrix[10]; + m23 = matrix[11]; + m30 = matrix[12]; + m31 = matrix[13]; + m32 = matrix[14]; + m33 = matrix[15]; + } else { + m00 = matrix[0]; + m01 = matrix[4]; + m02 = matrix[8]; + m03 = matrix[12]; + m10 = matrix[1]; + m11 = matrix[5]; + m12 = matrix[9]; + m13 = matrix[13]; + m20 = matrix[2]; + m21 = matrix[6]; + m22 = matrix[10]; + m23 = matrix[14]; + m30 = matrix[3]; + m31 = matrix[7]; + m32 = matrix[11]; + m33 = matrix[15]; + } + } + + public Matrix4f transpose() { + float[] tmp = new float[16]; + get(tmp, true); + Matrix4f mat = new Matrix4f(tmp); + return mat; + } + + /** + * transpose locally transposes this Matrix. + * + * @return this object for chaining. + */ + public Matrix4f transposeLocal() { + float tmp = m01; + m01 = m10; + m10 = tmp; + + tmp = m02; + m02 = m20; + m20 = tmp; + + tmp = m03; + m03 = m30; + m30 = tmp; + + tmp = m12; + m12 = m21; + m21 = tmp; + + tmp = m13; + m13 = m31; + m31 = tmp; + + tmp = m23; + m23 = m32; + m32 = tmp; + + return this; + } + + /** + * fillFloatBuffer fills a FloatBuffer object with the matrix + * data. + * + * @param fb the buffer to fill, must be correct size + * @return matrix data as a FloatBuffer. + */ + public FloatBuffer fillFloatBuffer(FloatBuffer fb) { + return fillFloatBuffer(fb, false); + } + + /** + * fillFloatBuffer fills a FloatBuffer object with the matrix + * data. + * + * @param fb the buffer to fill, starting at current position. Must have + * room for 16 more floats. + * @param columnMajor if true, this buffer should be filled with column + * major data, otherwise it will be filled row major. + * @return matrix data as a FloatBuffer. (position is advanced by 16 and any + * limit set is not changed). + */ + public FloatBuffer fillFloatBuffer(FloatBuffer fb, boolean columnMajor) { +// if (columnMajor) { +// fb.put(m00).put(m10).put(m20).put(m30); +// fb.put(m01).put(m11).put(m21).put(m31); +// fb.put(m02).put(m12).put(m22).put(m32); +// fb.put(m03).put(m13).put(m23).put(m33); +// } else { +// fb.put(m00).put(m01).put(m02).put(m03); +// fb.put(m10).put(m11).put(m12).put(m13); +// fb.put(m20).put(m21).put(m22).put(m23); +// fb.put(m30).put(m31).put(m32).put(m33); +// } + + TempVars vars = TempVars.get(); + + + fillFloatArray(vars.matrixWrite, columnMajor); + fb.put(vars.matrixWrite, 0, 16); + + vars.release(); + + return fb; + } + + public void fillFloatArray(float[] f, boolean columnMajor) { + if (columnMajor) { + f[0] = m00; + f[1] = m10; + f[2] = m20; + f[3] = m30; + f[4] = m01; + f[5] = m11; + f[6] = m21; + f[7] = m31; + f[8] = m02; + f[9] = m12; + f[10] = m22; + f[11] = m32; + f[12] = m03; + f[13] = m13; + f[14] = m23; + f[15] = m33; + } else { + f[0] = m00; + f[1] = m01; + f[2] = m02; + f[3] = m03; + f[4] = m10; + f[5] = m11; + f[6] = m12; + f[7] = m13; + f[8] = m20; + f[9] = m21; + f[10] = m22; + f[11] = m23; + f[12] = m30; + f[13] = m31; + f[14] = m32; + f[15] = m33; + } + } + + /** + * readFloatBuffer reads value for this matrix from a + * FloatBuffer. + * + * @param fb the buffer to read from, must be correct size + * @return this data as a FloatBuffer. + */ + public Matrix4f readFloatBuffer(FloatBuffer fb) { + return readFloatBuffer(fb, false); + } + + /** + * readFloatBuffer reads value for this matrix from a + * FloatBuffer. + * + * @param fb the buffer to read from, must be correct size + * @param columnMajor if true, this buffer should be filled with column + * major data, otherwise it will be filled row major. + * @return this data as a FloatBuffer. + */ + public Matrix4f readFloatBuffer(FloatBuffer fb, boolean columnMajor) { + + if (columnMajor) { + m00 = fb.get(); + m10 = fb.get(); + m20 = fb.get(); + m30 = fb.get(); + m01 = fb.get(); + m11 = fb.get(); + m21 = fb.get(); + m31 = fb.get(); + m02 = fb.get(); + m12 = fb.get(); + m22 = fb.get(); + m32 = fb.get(); + m03 = fb.get(); + m13 = fb.get(); + m23 = fb.get(); + m33 = fb.get(); + } else { + m00 = fb.get(); + m01 = fb.get(); + m02 = fb.get(); + m03 = fb.get(); + m10 = fb.get(); + m11 = fb.get(); + m12 = fb.get(); + m13 = fb.get(); + m20 = fb.get(); + m21 = fb.get(); + m22 = fb.get(); + m23 = fb.get(); + m30 = fb.get(); + m31 = fb.get(); + m32 = fb.get(); + m33 = fb.get(); + } + return this; + } + + /** + * loadIdentity sets this matrix to the identity matrix, namely + * all zeros with ones along the diagonal. + * + */ + public void loadIdentity() { + m01 = m02 = m03 = 0.0f; + m10 = m12 = m13 = 0.0f; + m20 = m21 = m23 = 0.0f; + m30 = m31 = m32 = 0.0f; + m00 = m11 = m22 = m33 = 1.0f; + } + + public void fromFrustum( + float near, + float far, + float left, + float right, + float top, + float bottom, + boolean parallel + ) { + loadIdentity(); + if (parallel) { + // scale + m00 = 2.0f / (right - left); + // m11 = 2.0f / (bottom - top); + m11 = 2.0f / (top - bottom); + m22 = -2.0f / (far - near); + m33 = 1f; + + // translation + m03 = -(right + left) / (right - left); + // m31 = -(bottom + top) / (bottom - top); + m13 = -(top + bottom) / (top - bottom); + m23 = -(far + near) / (far - near); + } else { + m00 = (2.0f * near) / (right - left); + m11 = (2.0f * near) / (top - bottom); + m32 = -1.0f; + m33 = -0.0f; + + // A + m02 = (right + left) / (right - left); + + // B + m12 = (top + bottom) / (top - bottom); + + // C + m22 = -(far + near) / (far - near); + + // D + m23 = -(2.0f * far * near) / (far - near); + } + } + + /** + * fromAngleAxis sets this matrix4f to the values specified by + * an angle and an axis of rotation. This method creates an object, so use + * fromAngleNormalAxis if your axis is already normalized. + * + * @param angle the angle to rotate (in radians). + * @param axis the axis of rotation. + */ + public void fromAngleAxis(float angle, Vector3f axis) { + Vector3f normAxis = axis.normalize(); + fromAngleNormalAxis(angle, normAxis); + } + + /** + * fromAngleNormalAxis sets this matrix4f to the values + * specified by an angle and a normalized axis of rotation. + * + * @param angle the angle to rotate (in radians). + * @param axis the axis of rotation (already normalized). + */ + public void fromAngleNormalAxis(float angle, Vector3f axis) { + zero(); + m33 = 1; + + float fCos = FastMath.cos(angle); + float fSin = FastMath.sin(angle); + float fOneMinusCos = ((float) 1.0) - fCos; + float fX2 = axis.x * axis.x; + float fY2 = axis.y * axis.y; + float fZ2 = axis.z * axis.z; + float fXYM = axis.x * axis.y * fOneMinusCos; + float fXZM = axis.x * axis.z * fOneMinusCos; + float fYZM = axis.y * axis.z * fOneMinusCos; + float fXSin = axis.x * fSin; + float fYSin = axis.y * fSin; + float fZSin = axis.z * fSin; + + m00 = fX2 * fOneMinusCos + fCos; + m01 = fXYM - fZSin; + m02 = fXZM + fYSin; + m10 = fXYM + fZSin; + m11 = fY2 * fOneMinusCos + fCos; + m12 = fYZM - fXSin; + m20 = fXZM - fYSin; + m21 = fYZM + fXSin; + m22 = fZ2 * fOneMinusCos + fCos; + } + + /** + * mult multiplies this matrix by a scalar. + * + * @param scalar the scalar to multiply this matrix by. + */ + public void multLocal(float scalar) { + m00 *= scalar; + m01 *= scalar; + m02 *= scalar; + m03 *= scalar; + m10 *= scalar; + m11 *= scalar; + m12 *= scalar; + m13 *= scalar; + m20 *= scalar; + m21 *= scalar; + m22 *= scalar; + m23 *= scalar; + m30 *= scalar; + m31 *= scalar; + m32 *= scalar; + m33 *= scalar; + } + + public Matrix4f mult(float scalar) { + Matrix4f out = new Matrix4f(); + out.set(this); + out.multLocal(scalar); + return out; + } + + public Matrix4f mult(float scalar, Matrix4f store) { + store.set(this); + store.multLocal(scalar); + return store; + } + + /** + * mult multiplies this matrix with another matrix. The result + * matrix will then be returned. This matrix will be on the left hand side, + * while the parameter matrix will be on the right. + * + * @param in2 the matrix to multiply this matrix by. + * @return the resultant matrix + */ + public Matrix4f mult(Matrix4f in2) { + return mult(in2, null); + } + + /** + * mult multiplies this matrix with another matrix. The result + * matrix will then be returned. This matrix will be on the left hand side, + * while the parameter matrix will be on the right. + * + * @param in2 the matrix to multiply this matrix by. + * @param store where to store the result. It is safe for in2 and store to + * be the same object. + * @return the resultant matrix + */ + public Matrix4f mult(Matrix4f in2, Matrix4f store) { + if (store == null) { + store = new Matrix4f(); + } + + float temp00, temp01, temp02, temp03; + float temp10, temp11, temp12, temp13; + float temp20, temp21, temp22, temp23; + float temp30, temp31, temp32, temp33; + + temp00 = m00 * in2.m00 + + m01 * in2.m10 + + m02 * in2.m20 + + m03 * in2.m30; + temp01 = m00 * in2.m01 + + m01 * in2.m11 + + m02 * in2.m21 + + m03 * in2.m31; + temp02 = m00 * in2.m02 + + m01 * in2.m12 + + m02 * in2.m22 + + m03 * in2.m32; + temp03 = m00 * in2.m03 + + m01 * in2.m13 + + m02 * in2.m23 + + m03 * in2.m33; + + temp10 = m10 * in2.m00 + + m11 * in2.m10 + + m12 * in2.m20 + + m13 * in2.m30; + temp11 = m10 * in2.m01 + + m11 * in2.m11 + + m12 * in2.m21 + + m13 * in2.m31; + temp12 = m10 * in2.m02 + + m11 * in2.m12 + + m12 * in2.m22 + + m13 * in2.m32; + temp13 = m10 * in2.m03 + + m11 * in2.m13 + + m12 * in2.m23 + + m13 * in2.m33; + + temp20 = m20 * in2.m00 + + m21 * in2.m10 + + m22 * in2.m20 + + m23 * in2.m30; + temp21 = m20 * in2.m01 + + m21 * in2.m11 + + m22 * in2.m21 + + m23 * in2.m31; + temp22 = m20 * in2.m02 + + m21 * in2.m12 + + m22 * in2.m22 + + m23 * in2.m32; + temp23 = m20 * in2.m03 + + m21 * in2.m13 + + m22 * in2.m23 + + m23 * in2.m33; + + temp30 = m30 * in2.m00 + + m31 * in2.m10 + + m32 * in2.m20 + + m33 * in2.m30; + temp31 = m30 * in2.m01 + + m31 * in2.m11 + + m32 * in2.m21 + + m33 * in2.m31; + temp32 = m30 * in2.m02 + + m31 * in2.m12 + + m32 * in2.m22 + + m33 * in2.m32; + temp33 = m30 * in2.m03 + + m31 * in2.m13 + + m32 * in2.m23 + + m33 * in2.m33; + + store.m00 = temp00; + store.m01 = temp01; + store.m02 = temp02; + store.m03 = temp03; + store.m10 = temp10; + store.m11 = temp11; + store.m12 = temp12; + store.m13 = temp13; + store.m20 = temp20; + store.m21 = temp21; + store.m22 = temp22; + store.m23 = temp23; + store.m30 = temp30; + store.m31 = temp31; + store.m32 = temp32; + store.m33 = temp33; + + return store; + } + + /** + * mult multiplies this matrix with another matrix. The results + * are stored internally and a handle to this matrix will then be returned. + * This matrix will be on the left hand side, while the parameter matrix + * will be on the right. + * + * @param in2 the matrix to multiply this matrix by. + * @return the resultant matrix + */ + public Matrix4f multLocal(Matrix4f in2) { + return mult(in2, this); + } + + /** + * mult multiplies a vector about a rotation matrix. The + * resulting vector is returned as a new Vector3f. + * + * @param vec vec to multiply against. + * @return the rotated vector. + */ + public Vector3f mult(Vector3f vec) { + return mult(vec, null); + } + + /** + * mult multiplies a vector about a rotation matrix and adds + * translation. The resulting vector is returned. + * + * @param vec vec to multiply against. + * @param store a vector to store the result in. Created if null is passed. + * @return the rotated vector. + */ + public Vector3f mult(Vector3f vec, Vector3f store) { + if (store == null) { + store = new Vector3f(); + } + + float vx = vec.x, vy = vec.y, vz = vec.z; + store.x = m00 * vx + m01 * vy + m02 * vz + m03; + store.y = m10 * vx + m11 * vy + m12 * vz + m13; + store.z = m20 * vx + m21 * vy + m22 * vz + m23; + + return store; + } + + /** + * mult multiplies a Vector4f about a rotation + * matrix. The resulting vector is returned as a new Vector4f. + * + * @param vec vec to multiply against. + * @return the rotated vector. + */ + public Vector4f mult(Vector4f vec) { + return mult(vec, null); + } + + /** + * mult multiplies a Vector4f about a rotation + * matrix. The resulting vector is returned. + * + * @param vec vec to multiply against. + * @param store a vector to store the result in. Created if null is passed. + * @return the rotated vector. + */ + public Vector4f mult(Vector4f vec, Vector4f store) { + if (null == vec) { + logger.warning("Source vector is null, null result returned."); + return null; + } + if (store == null) { + store = new Vector4f(); + } + + float vx = vec.x, vy = vec.y, vz = vec.z, vw = vec.w; + store.x = m00 * vx + m01 * vy + m02 * vz + m03 * vw; + store.y = m10 * vx + m11 * vy + m12 * vz + m13 * vw; + store.z = m20 * vx + m21 * vy + m22 * vz + m23 * vw; + store.w = m30 * vx + m31 * vy + m32 * vz + m33 * vw; + + return store; + } + + /** + * mult multiplies a vector about a rotation matrix. The + * resulting vector is returned. + * + * @param vec vec to multiply against. + * + * @return the rotated vector. + */ + public Vector4f multAcross(Vector4f vec) { + return multAcross(vec, null); + } + + /** + * mult multiplies a vector about a rotation matrix. The + * resulting vector is returned. + * + * @param vec vec to multiply against. + * @param store a vector to store the result in. created if null is passed. + * @return the rotated vector. + */ + public Vector4f multAcross(Vector4f vec, Vector4f store) { + if (null == vec) { + logger.warning("Source vector is null, null result returned."); + return null; + } + if (store == null) { + store = new Vector4f(); + } + + float vx = vec.x, vy = vec.y, vz = vec.z, vw = vec.w; + store.x = m00 * vx + m10 * vy + m20 * vz + m30 * vw; + store.y = m01 * vx + m11 * vy + m21 * vz + m31 * vw; + store.z = m02 * vx + m12 * vy + m22 * vz + m32 * vw; + store.w = m03 * vx + m13 * vy + m23 * vz + m33 * vw; + + return store; + } + + /** + * multNormal multiplies a vector about a rotation matrix, but + * does not add translation. The resulting vector is returned. + * + * @param vec vec to multiply against. + * @param store a vector to store the result in. Created if null is passed. + * @return the rotated vector. + */ + public Vector3f multNormal(Vector3f vec, Vector3f store) { + if (store == null) { + store = new Vector3f(); + } + + float vx = vec.x, vy = vec.y, vz = vec.z; + store.x = m00 * vx + m01 * vy + m02 * vz; + store.y = m10 * vx + m11 * vy + m12 * vz; + store.z = m20 * vx + m21 * vy + m22 * vz; + + return store; + } + + /** + * multNormal multiplies a vector about a rotation matrix, but + * does not add translation. The resulting vector is returned. + * + * @param vec vec to multiply against. + * @param store a vector to store the result in. Created if null is passed. + * @return the rotated vector. + */ + public Vector3f multNormalAcross(Vector3f vec, Vector3f store) { + if (store == null) { + store = new Vector3f(); + } + + float vx = vec.x, vy = vec.y, vz = vec.z; + store.x = m00 * vx + m10 * vy + m20 * vz; + store.y = m01 * vx + m11 * vy + m21 * vz; + store.z = m02 * vx + m12 * vy + m22 * vz; + + return store; + } + + /** + * mult multiplies a vector about a rotation matrix and adds + * translation. The w value is returned as a result of multiplying the last + * column of the matrix by 1.0 + * + * @param vec vec to multiply against. + * @param store a vector to store the result in. + * @return the W value + */ + public float multProj(Vector3f vec, Vector3f store) { + float vx = vec.x, vy = vec.y, vz = vec.z; + store.x = m00 * vx + m01 * vy + m02 * vz + m03; + store.y = m10 * vx + m11 * vy + m12 * vz + m13; + store.z = m20 * vx + m21 * vy + m22 * vz + m23; + return m30 * vx + m31 * vy + m32 * vz + m33; + } + + /** + * mult multiplies a vector about a rotation matrix. The + * resulting vector is returned. + * + * @param vec vec to multiply against. + * @param store a vector to store the result in. created if null is passed. + * @return the rotated vector. + */ + public Vector3f multAcross(Vector3f vec, Vector3f store) { + if (null == vec) { + logger.warning("Source vector is null, null result returned."); + return null; + } + if (store == null) { + store = new Vector3f(); + } + + float vx = vec.x, vy = vec.y, vz = vec.z; + store.x = m00 * vx + m10 * vy + m20 * vz + m30 * 1; + store.y = m01 * vx + m11 * vy + m21 * vz + m31 * 1; + store.z = m02 * vx + m12 * vy + m22 * vz + m32 * 1; + + return store; + } + + /** + * mult multiplies a quaternion about a matrix. The resulting + * vector is returned. + * + * @param vec vec to multiply against. + * @param store a quaternion to store the result in. created if null is + * passed. + * @return store = this * vec + */ + public Quaternion mult(Quaternion vec, Quaternion store) { + + if (null == vec) { + logger.warning("Source vector is null, null result returned."); + return null; + } + if (store == null) { + store = new Quaternion(); + } + + float x = m00 * vec.x + m10 * vec.y + m20 * vec.z + m30 * vec.w; + float y = m01 * vec.x + m11 * vec.y + m21 * vec.z + m31 * vec.w; + float z = m02 * vec.x + m12 * vec.y + m22 * vec.z + m32 * vec.w; + float w = m03 * vec.x + m13 * vec.y + m23 * vec.z + m33 * vec.w; + store.x = x; + store.y = y; + store.z = z; + store.w = w; + + return store; + } + + /** + * mult multiplies an array of 4 floats against this rotation + * matrix. The results are stored directly in the array. (vec4f x mat4f) + * + * @param vec4f float array (size 4) to multiply against the matrix. + * @return the vec4f for chaining. + */ + public float[] mult(float[] vec4f) { + if (null == vec4f || vec4f.length != 4) { + logger.warning("invalid array given, must be nonnull and length 4"); + return null; + } + + float x = vec4f[0], y = vec4f[1], z = vec4f[2], w = vec4f[3]; + + vec4f[0] = m00 * x + m01 * y + m02 * z + m03 * w; + vec4f[1] = m10 * x + m11 * y + m12 * z + m13 * w; + vec4f[2] = m20 * x + m21 * y + m22 * z + m23 * w; + vec4f[3] = m30 * x + m31 * y + m32 * z + m33 * w; + + return vec4f; + } + + /** + * mult multiplies an array of 4 floats against this rotation + * matrix. The results are stored directly in the array. (vec4f x mat4f) + * + * @param vec4f float array (size 4) to multiply against the matrix. + * @return the vec4f for chaining. + */ + public float[] multAcross(float[] vec4f) { + if (null == vec4f || vec4f.length != 4) { + logger.warning("invalid array given, must be nonnull and length 4"); + return null; + } + + float x = vec4f[0], y = vec4f[1], z = vec4f[2], w = vec4f[3]; + + vec4f[0] = m00 * x + m10 * y + m20 * z + m30 * w; + vec4f[1] = m01 * x + m11 * y + m21 * z + m31 * w; + vec4f[2] = m02 * x + m12 * y + m22 * z + m32 * w; + vec4f[3] = m03 * x + m13 * y + m23 * z + m33 * w; + + return vec4f; + } + + /** + * Inverts this matrix as a new Matrix4f. + * + * @return The new inverse matrix + */ + public Matrix4f invert() { + return invert(null); + } + + /** + * Inverts this matrix and stores it in the given store. + * + * @return The store + */ + public Matrix4f invert(Matrix4f store) { + if (store == null) { + store = new Matrix4f(); + } + + float fA0 = m00 * m11 - m01 * m10; + float fA1 = m00 * m12 - m02 * m10; + float fA2 = m00 * m13 - m03 * m10; + float fA3 = m01 * m12 - m02 * m11; + float fA4 = m01 * m13 - m03 * m11; + float fA5 = m02 * m13 - m03 * m12; + float fB0 = m20 * m31 - m21 * m30; + float fB1 = m20 * m32 - m22 * m30; + float fB2 = m20 * m33 - m23 * m30; + float fB3 = m21 * m32 - m22 * m31; + float fB4 = m21 * m33 - m23 * m31; + float fB5 = m22 * m33 - m23 * m32; + float fDet = fA0 * fB5 - fA1 * fB4 + fA2 * fB3 + fA3 * fB2 - fA4 * fB1 + fA5 * fB0; + + if (FastMath.abs(fDet) <= 0f) { + throw new ArithmeticException("This matrix cannot be inverted"); + } + + store.m00 = +m11 * fB5 - m12 * fB4 + m13 * fB3; + store.m10 = -m10 * fB5 + m12 * fB2 - m13 * fB1; + store.m20 = +m10 * fB4 - m11 * fB2 + m13 * fB0; + store.m30 = -m10 * fB3 + m11 * fB1 - m12 * fB0; + store.m01 = -m01 * fB5 + m02 * fB4 - m03 * fB3; + store.m11 = +m00 * fB5 - m02 * fB2 + m03 * fB1; + store.m21 = -m00 * fB4 + m01 * fB2 - m03 * fB0; + store.m31 = +m00 * fB3 - m01 * fB1 + m02 * fB0; + store.m02 = +m31 * fA5 - m32 * fA4 + m33 * fA3; + store.m12 = -m30 * fA5 + m32 * fA2 - m33 * fA1; + store.m22 = +m30 * fA4 - m31 * fA2 + m33 * fA0; + store.m32 = -m30 * fA3 + m31 * fA1 - m32 * fA0; + store.m03 = -m21 * fA5 + m22 * fA4 - m23 * fA3; + store.m13 = +m20 * fA5 - m22 * fA2 + m23 * fA1; + store.m23 = -m20 * fA4 + m21 * fA2 - m23 * fA0; + store.m33 = +m20 * fA3 - m21 * fA1 + m22 * fA0; + + float fInvDet = 1.0f / fDet; + store.multLocal(fInvDet); + + return store; + } + + /** + * Inverts this matrix locally. + * + * @return this + */ + public Matrix4f invertLocal() { + + float fA0 = m00 * m11 - m01 * m10; + float fA1 = m00 * m12 - m02 * m10; + float fA2 = m00 * m13 - m03 * m10; + float fA3 = m01 * m12 - m02 * m11; + float fA4 = m01 * m13 - m03 * m11; + float fA5 = m02 * m13 - m03 * m12; + float fB0 = m20 * m31 - m21 * m30; + float fB1 = m20 * m32 - m22 * m30; + float fB2 = m20 * m33 - m23 * m30; + float fB3 = m21 * m32 - m22 * m31; + float fB4 = m21 * m33 - m23 * m31; + float fB5 = m22 * m33 - m23 * m32; + float fDet = fA0 * fB5 - fA1 * fB4 + fA2 * fB3 + fA3 * fB2 - fA4 * fB1 + fA5 * fB0; + + if (FastMath.abs(fDet) <= 0f) { + return zero(); + } + + float f00 = +m11 * fB5 - m12 * fB4 + m13 * fB3; + float f10 = -m10 * fB5 + m12 * fB2 - m13 * fB1; + float f20 = +m10 * fB4 - m11 * fB2 + m13 * fB0; + float f30 = -m10 * fB3 + m11 * fB1 - m12 * fB0; + float f01 = -m01 * fB5 + m02 * fB4 - m03 * fB3; + float f11 = +m00 * fB5 - m02 * fB2 + m03 * fB1; + float f21 = -m00 * fB4 + m01 * fB2 - m03 * fB0; + float f31 = +m00 * fB3 - m01 * fB1 + m02 * fB0; + float f02 = +m31 * fA5 - m32 * fA4 + m33 * fA3; + float f12 = -m30 * fA5 + m32 * fA2 - m33 * fA1; + float f22 = +m30 * fA4 - m31 * fA2 + m33 * fA0; + float f32 = -m30 * fA3 + m31 * fA1 - m32 * fA0; + float f03 = -m21 * fA5 + m22 * fA4 - m23 * fA3; + float f13 = +m20 * fA5 - m22 * fA2 + m23 * fA1; + float f23 = -m20 * fA4 + m21 * fA2 - m23 * fA0; + float f33 = +m20 * fA3 - m21 * fA1 + m22 * fA0; + + m00 = f00; + m01 = f01; + m02 = f02; + m03 = f03; + m10 = f10; + m11 = f11; + m12 = f12; + m13 = f13; + m20 = f20; + m21 = f21; + m22 = f22; + m23 = f23; + m30 = f30; + m31 = f31; + m32 = f32; + m33 = f33; + + float fInvDet = 1.0f / fDet; + multLocal(fInvDet); + + return this; + } + + /** + * Returns a new matrix representing the adjoint of this matrix. + * + * @return The adjoint matrix + */ + public Matrix4f adjoint() { + return adjoint(null); + } + + public void setTransform(Vector3f position, Vector3f scale, Matrix3f rotMat) { + // Ordering: + // 1. Scale + // 2. Rotate + // 3. Translate + + // Set up final matrix with scale, rotation and translation + m00 = scale.x * rotMat.m00; + m01 = scale.y * rotMat.m01; + m02 = scale.z * rotMat.m02; + m03 = position.x; + m10 = scale.x * rotMat.m10; + m11 = scale.y * rotMat.m11; + m12 = scale.z * rotMat.m12; + m13 = position.y; + m20 = scale.x * rotMat.m20; + m21 = scale.y * rotMat.m21; + m22 = scale.z * rotMat.m22; + m23 = position.z; + + // No projection term + m30 = 0; + m31 = 0; + m32 = 0; + m33 = 1; + } + + /** + * Places the adjoint of this matrix in store (creates store if null.) + * + * @param store The matrix to store the result in. If null, a new matrix is + * created. + * @return store + */ + public Matrix4f adjoint(Matrix4f store) { + if (store == null) { + store = new Matrix4f(); + } + + float fA0 = m00 * m11 - m01 * m10; + float fA1 = m00 * m12 - m02 * m10; + float fA2 = m00 * m13 - m03 * m10; + float fA3 = m01 * m12 - m02 * m11; + float fA4 = m01 * m13 - m03 * m11; + float fA5 = m02 * m13 - m03 * m12; + float fB0 = m20 * m31 - m21 * m30; + float fB1 = m20 * m32 - m22 * m30; + float fB2 = m20 * m33 - m23 * m30; + float fB3 = m21 * m32 - m22 * m31; + float fB4 = m21 * m33 - m23 * m31; + float fB5 = m22 * m33 - m23 * m32; + + store.m00 = +m11 * fB5 - m12 * fB4 + m13 * fB3; + store.m10 = -m10 * fB5 + m12 * fB2 - m13 * fB1; + store.m20 = +m10 * fB4 - m11 * fB2 + m13 * fB0; + store.m30 = -m10 * fB3 + m11 * fB1 - m12 * fB0; + store.m01 = -m01 * fB5 + m02 * fB4 - m03 * fB3; + store.m11 = +m00 * fB5 - m02 * fB2 + m03 * fB1; + store.m21 = -m00 * fB4 + m01 * fB2 - m03 * fB0; + store.m31 = +m00 * fB3 - m01 * fB1 + m02 * fB0; + store.m02 = +m31 * fA5 - m32 * fA4 + m33 * fA3; + store.m12 = -m30 * fA5 + m32 * fA2 - m33 * fA1; + store.m22 = +m30 * fA4 - m31 * fA2 + m33 * fA0; + store.m32 = -m30 * fA3 + m31 * fA1 - m32 * fA0; + store.m03 = -m21 * fA5 + m22 * fA4 - m23 * fA3; + store.m13 = +m20 * fA5 - m22 * fA2 + m23 * fA1; + store.m23 = -m20 * fA4 + m21 * fA2 - m23 * fA0; + store.m33 = +m20 * fA3 - m21 * fA1 + m22 * fA0; + + return store; + } + + /** + * determinant generates the determinate of this matrix. + * + * @return the determinate + */ + public float determinant() { + float fA0 = m00 * m11 - m01 * m10; + float fA1 = m00 * m12 - m02 * m10; + float fA2 = m00 * m13 - m03 * m10; + float fA3 = m01 * m12 - m02 * m11; + float fA4 = m01 * m13 - m03 * m11; + float fA5 = m02 * m13 - m03 * m12; + float fB0 = m20 * m31 - m21 * m30; + float fB1 = m20 * m32 - m22 * m30; + float fB2 = m20 * m33 - m23 * m30; + float fB3 = m21 * m32 - m22 * m31; + float fB4 = m21 * m33 - m23 * m31; + float fB5 = m22 * m33 - m23 * m32; + float fDet = fA0 * fB5 - fA1 * fB4 + fA2 * fB3 + fA3 * fB2 - fA4 * fB1 + fA5 * fB0; + return fDet; + } + + /** + * Sets all of the values in this matrix to zero. + * + * @return this matrix + */ + public Matrix4f zero() { + m00 = m01 = m02 = m03 = 0.0f; + m10 = m11 = m12 = m13 = 0.0f; + m20 = m21 = m22 = m23 = 0.0f; + m30 = m31 = m32 = m33 = 0.0f; + return this; + } + + public Matrix4f add(Matrix4f mat) { + Matrix4f result = new Matrix4f(); + result.m00 = this.m00 + mat.m00; + result.m01 = this.m01 + mat.m01; + result.m02 = this.m02 + mat.m02; + result.m03 = this.m03 + mat.m03; + result.m10 = this.m10 + mat.m10; + result.m11 = this.m11 + mat.m11; + result.m12 = this.m12 + mat.m12; + result.m13 = this.m13 + mat.m13; + result.m20 = this.m20 + mat.m20; + result.m21 = this.m21 + mat.m21; + result.m22 = this.m22 + mat.m22; + result.m23 = this.m23 + mat.m23; + result.m30 = this.m30 + mat.m30; + result.m31 = this.m31 + mat.m31; + result.m32 = this.m32 + mat.m32; + result.m33 = this.m33 + mat.m33; + return result; + } + + /** + * add adds the values of a parameter matrix to this matrix. + * + * @param mat the matrix to add to this. + */ + public void addLocal(Matrix4f mat) { + m00 += mat.m00; + m01 += mat.m01; + m02 += mat.m02; + m03 += mat.m03; + m10 += mat.m10; + m11 += mat.m11; + m12 += mat.m12; + m13 += mat.m13; + m20 += mat.m20; + m21 += mat.m21; + m22 += mat.m22; + m23 += mat.m23; + m30 += mat.m30; + m31 += mat.m31; + m32 += mat.m32; + m33 += mat.m33; + } + + public Vector3f toTranslationVector() { + return new Vector3f(m03, m13, m23); + } + + public void toTranslationVector(Vector3f vector) { + vector.set(m03, m13, m23); + } + + public Quaternion toRotationQuat() { + Quaternion quat = new Quaternion(); + quat.fromRotationMatrix(toRotationMatrix()); + return quat; + } + + public void toRotationQuat(Quaternion q) { + q.fromRotationMatrix(toRotationMatrix()); + } + + public Matrix3f toRotationMatrix() { + return new Matrix3f(m00, m01, m02, m10, m11, m12, m20, m21, m22); + } + + public void toRotationMatrix(Matrix3f mat) { + mat.m00 = m00; + mat.m01 = m01; + mat.m02 = m02; + mat.m10 = m10; + mat.m11 = m11; + mat.m12 = m12; + mat.m20 = m20; + mat.m21 = m21; + mat.m22 = m22; + } + + /** + * Retreives the scale vector from the matrix. + * + * @return the scale vector + */ + public Vector3f toScaleVector() { + Vector3f result = new Vector3f(); + this.toScaleVector(result); + return result; + } + + /** + * Retreives the scale vector from the matrix and stores it into a given + * vector. + * + * @param the vector where the scale will be stored + */ + public void toScaleVector(Vector3f vector) { + float scaleX = (float) Math.sqrt(m00 * m00 + m10 * m10 + m20 * m20); + float scaleY = (float) Math.sqrt(m01 * m01 + m11 * m11 + m21 * m21); + float scaleZ = (float) Math.sqrt(m02 * m02 + m12 * m12 + m22 * m22); + vector.set(scaleX, scaleY, scaleZ); + } + + public void setScale(float x, float y, float z) { + m00 *= x; + m11 *= y; + m22 *= z; + } + + public void setScale(Vector3f scale) { + m00 *= scale.x; + m11 *= scale.y; + m22 *= scale.z; + } + + /** + * setTranslation will set the matrix's translation values. + * + * @param translation the new values for the translation. + * @throws JmeException if translation is not size 3. + */ + public void setTranslation(float[] translation) { + if (translation.length != 3) { + throw new IllegalArgumentException( + "Translation size must be 3." + ); + } + m03 = translation[0]; + m13 = translation[1]; + m23 = translation[2]; + } + + /** + * setTranslation will set the matrix's translation values. + * + * @param x value of the translation on the x axis + * @param y value of the translation on the y axis + * @param z value of the translation on the z axis + */ + public void setTranslation(float x, float y, float z) { + m03 = x; + m13 = y; + m23 = z; + } + + /** + * setTranslation will set the matrix's translation values. + * + * @param translation the new values for the translation. + */ + public void setTranslation(Vector3f translation) { + m03 = translation.x; + m13 = translation.y; + m23 = translation.z; + } + + /** + * setInverseTranslation will set the matrix's inverse + * translation values. + * + * @param translation the new values for the inverse translation. + * @throws JmeException if translation is not size 3. + */ + public void setInverseTranslation(float[] translation) { + if (translation.length != 3) { + throw new IllegalArgumentException( + "Translation size must be 3." + ); + } + m03 = -translation[0]; + m13 = -translation[1]; + m23 = -translation[2]; + } + + /** + * angleRotation sets this matrix to that of a rotation about + * three axes (x, y, z). Where each axis has a specified rotation in + * degrees. These rotations are expressed in a single Vector3f + * object. + * + * @param angles the angles to rotate. + */ + public void angleRotation(Vector3f angles) { + float angle; + float sr, sp, sy, cr, cp, cy; + + angle = (angles.z * FastMath.DEG_TO_RAD); + sy = FastMath.sin(angle); + cy = FastMath.cos(angle); + angle = (angles.y * FastMath.DEG_TO_RAD); + sp = FastMath.sin(angle); + cp = FastMath.cos(angle); + angle = (angles.x * FastMath.DEG_TO_RAD); + sr = FastMath.sin(angle); + cr = FastMath.cos(angle); + + // matrix = (Z * Y) * X + m00 = cp * cy; + m10 = cp * sy; + m20 = -sp; + m01 = sr * sp * cy + cr * -sy; + m11 = sr * sp * sy + cr * cy; + m21 = sr * cp; + m02 = (cr * sp * cy + -sr * -sy); + m12 = (cr * sp * sy + -sr * cy); + m22 = cr * cp; + m03 = 0.0f; + m13 = 0.0f; + m23 = 0.0f; + } + + /** + * setRotationQuaternion builds a rotation from a + * Quaternion. + * + * @param quat the quaternion to build the rotation from. + * @throws NullPointerException if quat is null. + */ + public void setRotationQuaternion(Quaternion quat) { + quat.toRotationMatrix(this); + } + + /** + * setInverseRotationRadians builds an inverted rotation from + * Euler angles that are in radians. + * + * @param angles the Euler angles in radians. + * @throws JmeException if angles is not size 3. + */ + public void setInverseRotationRadians(float[] angles) { + if (angles.length != 3) { + throw new IllegalArgumentException( + "Angles must be of size 3." + ); + } + double cr = FastMath.cos(angles[0]); + double sr = FastMath.sin(angles[0]); + double cp = FastMath.cos(angles[1]); + double sp = FastMath.sin(angles[1]); + double cy = FastMath.cos(angles[2]); + double sy = FastMath.sin(angles[2]); + + m00 = (float) (cp * cy); + m10 = (float) (cp * sy); + m20 = (float) (-sp); + + double srsp = sr * sp; + double crsp = cr * sp; + + m01 = (float) (srsp * cy - cr * sy); + m11 = (float) (srsp * sy + cr * cy); + m21 = (float) (sr * cp); + + m02 = (float) (crsp * cy + sr * sy); + m12 = (float) (crsp * sy - sr * cy); + m22 = (float) (cr * cp); + } + + /** + * setInverseRotationDegrees builds an inverted rotation from + * Euler angles that are in degrees. + * + * @param angles the Euler angles in degrees. + * @throws JmeException if angles is not size 3. + */ + public void setInverseRotationDegrees(float[] angles) { + if (angles.length != 3) { + throw new IllegalArgumentException( + "Angles must be of size 3." + ); + } + float vec[] = new float[3]; + vec[0] = (angles[0] * FastMath.RAD_TO_DEG); + vec[1] = (angles[1] * FastMath.RAD_TO_DEG); + vec[2] = (angles[2] * FastMath.RAD_TO_DEG); + setInverseRotationRadians(vec); + } + + /** + * + * inverseTranslateVect translates a given Vector3f by the + * translation part of this matrix. + * + * @param vec the Vector3f data to be translated. + * @throws JmeException if the size of the Vector3f is not 3. + */ + public void inverseTranslateVect(float[] vec) { + if (vec.length != 3) { + throw new IllegalArgumentException( + "vec must be of size 3." + ); + } + + vec[0] = vec[0] - m03; + vec[1] = vec[1] - m13; + vec[2] = vec[2] - m23; + } + + /** + * + * inverseTranslateVect translates a given Vector3f by the + * translation part of this matrix. + * + * @param data the Vector3f to be translated. + * @throws JmeException if the size of the Vector3f is not 3. + */ + public void inverseTranslateVect(Vector3f data) { + data.x -= m03; + data.y -= m13; + data.z -= m23; + } + + /** + * + * inverseTranslateVect translates a given Vector3f by the + * translation part of this matrix. + * + * @param data the Vector3f to be translated. + * @throws JmeException if the size of the Vector3f is not 3. + */ + public void translateVect(Vector3f data) { + data.x += m03; + data.y += m13; + data.z += m23; + } + + /** + * + * inverseRotateVect rotates a given Vector3f by the rotation + * part of this matrix. + * + * @param vec the Vector3f to be rotated. + */ + public void inverseRotateVect(Vector3f vec) { + float vx = vec.x, vy = vec.y, vz = vec.z; + + vec.x = vx * m00 + vy * m10 + vz * m20; + vec.y = vx * m01 + vy * m11 + vz * m21; + vec.z = vx * m02 + vy * m12 + vz * m22; + } + + public void rotateVect(Vector3f vec) { + float vx = vec.x, vy = vec.y, vz = vec.z; + + vec.x = vx * m00 + vy * m01 + vz * m02; + vec.y = vx * m10 + vy * m11 + vz * m12; + vec.z = vx * m20 + vy * m21 + vz * m22; + } + + /** + * toString returns the string representation of this object. + * It is in a format of a 4x4 matrix. For example, an identity matrix would + * be represented by the following string. com.jme.math.Matrix3f
+ * [
+ * 1.0 0.0 0.0 0.0
+ * 0.0 1.0 0.0 0.0
+ * 0.0 0.0 1.0 0.0
+ * 0.0 0.0 0.0 1.0
+ * ]
+ * + * @return the string representation of this object. + */ + @Override + public String toString() { + StringBuilder result = new StringBuilder("Matrix4f\n[\n"); + result.append(" "); + result.append(m00); + result.append(" "); + result.append(m01); + result.append(" "); + result.append(m02); + result.append(" "); + result.append(m03); + result.append(" \n"); + result.append(" "); + result.append(m10); + result.append(" "); + result.append(m11); + result.append(" "); + result.append(m12); + result.append(" "); + result.append(m13); + result.append(" \n"); + result.append(" "); + result.append(m20); + result.append(" "); + result.append(m21); + result.append(" "); + result.append(m22); + result.append(" "); + result.append(m23); + result.append(" \n"); + result.append(" "); + result.append(m30); + result.append(" "); + result.append(m31); + result.append(" "); + result.append(m32); + result.append(" "); + result.append(m33); + result.append(" \n]"); + return result.toString(); + } + + /** + * + * hashCode returns the hash code value as an integer and is + * supported for the benefit of hashing based collection classes such as + * Hashtable, HashMap, HashSet etc. + * + * @return the hashcode for this instance of Matrix4f. + * @see java.lang.Object#hashCode() + */ + @Override + public int hashCode() { + int hash = 37; + hash = 37 * hash + Float.floatToIntBits(m00); + hash = 37 * hash + Float.floatToIntBits(m01); + hash = 37 * hash + Float.floatToIntBits(m02); + hash = 37 * hash + Float.floatToIntBits(m03); + + hash = 37 * hash + Float.floatToIntBits(m10); + hash = 37 * hash + Float.floatToIntBits(m11); + hash = 37 * hash + Float.floatToIntBits(m12); + hash = 37 * hash + Float.floatToIntBits(m13); + + hash = 37 * hash + Float.floatToIntBits(m20); + hash = 37 * hash + Float.floatToIntBits(m21); + hash = 37 * hash + Float.floatToIntBits(m22); + hash = 37 * hash + Float.floatToIntBits(m23); + + hash = 37 * hash + Float.floatToIntBits(m30); + hash = 37 * hash + Float.floatToIntBits(m31); + hash = 37 * hash + Float.floatToIntBits(m32); + hash = 37 * hash + Float.floatToIntBits(m33); + + return hash; + } + + /** + * are these two matrices the same? they are is they both have the same mXX + * values. + * + * @param o the object to compare for equality + * @return true if they are equal + */ + @Override + public boolean equals(Object o) { + if (!(o instanceof Matrix4f) || o == null) { + return false; + } + + if (this == o) { + return true; + } + + Matrix4f comp = (Matrix4f) o; + if (Float.compare(m00, comp.m00) != 0) { + return false; + } + if (Float.compare(m01, comp.m01) != 0) { + return false; + } + if (Float.compare(m02, comp.m02) != 0) { + return false; + } + if (Float.compare(m03, comp.m03) != 0) { + return false; + } + + if (Float.compare(m10, comp.m10) != 0) { + return false; + } + if (Float.compare(m11, comp.m11) != 0) { + return false; + } + if (Float.compare(m12, comp.m12) != 0) { + return false; + } + if (Float.compare(m13, comp.m13) != 0) { + return false; + } + + if (Float.compare(m20, comp.m20) != 0) { + return false; + } + if (Float.compare(m21, comp.m21) != 0) { + return false; + } + if (Float.compare(m22, comp.m22) != 0) { + return false; + } + if (Float.compare(m23, comp.m23) != 0) { + return false; + } + + if (Float.compare(m30, comp.m30) != 0) { + return false; + } + if (Float.compare(m31, comp.m31) != 0) { + return false; + } + if (Float.compare(m32, comp.m32) != 0) { + return false; + } + if (Float.compare(m33, comp.m33) != 0) { + return false; + } + + return true; + } + + /** + * @return true if this matrix is identity + */ + public boolean isIdentity() { + return (m00 == 1 && m01 == 0 && m02 == 0 && m03 == 0) + && (m10 == 0 && m11 == 1 && m12 == 0 && m13 == 0) + && (m20 == 0 && m21 == 0 && m22 == 1 && m23 == 0) + && (m30 == 0 && m31 == 0 && m32 == 0 && m33 == 1); + } + + /** + * Apply a scale to this matrix. + * + * @param scale the scale to apply + */ + public void scale(Vector3f scale) { + m00 *= scale.getX(); + m10 *= scale.getX(); + m20 *= scale.getX(); + m30 *= scale.getX(); + m01 *= scale.getY(); + m11 *= scale.getY(); + m21 *= scale.getY(); + m31 *= scale.getY(); + m02 *= scale.getZ(); + m12 *= scale.getZ(); + m22 *= scale.getZ(); + m32 *= scale.getZ(); + } + + static boolean equalIdentity(Matrix4f mat) { + if (Math.abs(mat.m00 - 1) > 1e-4) { + return false; + } + if (Math.abs(mat.m11 - 1) > 1e-4) { + return false; + } + if (Math.abs(mat.m22 - 1) > 1e-4) { + return false; + } + if (Math.abs(mat.m33 - 1) > 1e-4) { + return false; + } + + if (Math.abs(mat.m01) > 1e-4) { + return false; + } + if (Math.abs(mat.m02) > 1e-4) { + return false; + } + if (Math.abs(mat.m03) > 1e-4) { + return false; + } + + if (Math.abs(mat.m10) > 1e-4) { + return false; + } + if (Math.abs(mat.m12) > 1e-4) { + return false; + } + if (Math.abs(mat.m13) > 1e-4) { + return false; + } + + if (Math.abs(mat.m20) > 1e-4) { + return false; + } + if (Math.abs(mat.m21) > 1e-4) { + return false; + } + if (Math.abs(mat.m23) > 1e-4) { + return false; + } + + if (Math.abs(mat.m30) > 1e-4) { + return false; + } + if (Math.abs(mat.m31) > 1e-4) { + return false; + } + if (Math.abs(mat.m32) > 1e-4) { + return false; + } + + return true; + } + + // XXX: This tests more solid than converting the q to a matrix and + // multiplying... why? + public void multLocal(Quaternion rotation) { + Vector3f axis = new Vector3f(); + float angle = rotation.toAngleAxis(axis); + Matrix4f matrix4f = new Matrix4f(); + matrix4f.fromAngleAxis(angle, axis); + multLocal(matrix4f); + } + + @Override + public Matrix4f clone() { + try { + return (Matrix4f) super.clone(); + } catch (CloneNotSupportedException e) { + throw new AssertionError(); // can not happen + } + } +} diff --git a/java/com/jme3/math/Quaternion.java b/java/com/jme3/math/Quaternion.java new file mode 100644 index 000000000..3db929e1b --- /dev/null +++ b/java/com/jme3/math/Quaternion.java @@ -0,0 +1,1721 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.math; + +import java.io.IOException; +import java.io.ObjectInput; +import java.io.ObjectOutput; +import java.util.logging.Logger; + +import com.jme3.util.TempVars; + +import io.eiren.math.FloatMath; + + +/** + * Quaternion defines a single example of a more general class of + * hypercomplex numbers. Quaternions extends a rotation in three dimensions to a + * rotation in four dimensions. This avoids "gimbal lock" and allows for smooth + * continuous rotation. + * + * Quaternion is defined by four floating point numbers: {x y z w}. + * + * @author Mark Powell + * @author Joshua Slack + */ +public final class Quaternion implements Cloneable, java.io.Serializable { + + static final long serialVersionUID = 1; + + private static final Logger logger = Logger.getLogger(Quaternion.class.getName()); + /** + * Represents the identity quaternion rotation (0, 0, 0, 1). + */ + public static final Quaternion IDENTITY = new Quaternion(); + public static final Quaternion DIRECTION_Z = new Quaternion(); + public static final Quaternion ZERO = new Quaternion(0, 0, 0, 0); + + public static final Quaternion X_90_DEG = new Quaternion() + .fromAngleNormalAxis(FastMath.HALF_PI, Vector3f.UNIT_X); + public static final Quaternion X_180_DEG = new Quaternion() + .fromAngleNormalAxis(FastMath.PI, Vector3f.UNIT_X); + public static final Quaternion X_270_DEG = new Quaternion() + .fromAngleNormalAxis(-FastMath.HALF_PI, Vector3f.UNIT_X); + public static final Quaternion Y_90_DEG = new Quaternion() + .fromAngleNormalAxis(FastMath.HALF_PI, Vector3f.UNIT_Y); + public static final Quaternion Y_180_DEG = new Quaternion() + .fromAngleNormalAxis(FastMath.PI, Vector3f.UNIT_Y); + public static final Quaternion Y_270_DEG = new Quaternion() + .fromAngleNormalAxis(-FastMath.HALF_PI, Vector3f.UNIT_Y); + public static final Quaternion Z_90_DEG = new Quaternion() + .fromAngleNormalAxis(FastMath.HALF_PI, Vector3f.UNIT_Z); + public static final Quaternion Z_180_DEG = new Quaternion() + .fromAngleNormalAxis(FastMath.PI, Vector3f.UNIT_Z); + public static final Quaternion Z_270_DEG = new Quaternion() + .fromAngleNormalAxis(-FastMath.HALF_PI, Vector3f.UNIT_Z); + + static { + DIRECTION_Z.fromAxes(Vector3f.UNIT_X, Vector3f.UNIT_Y, Vector3f.UNIT_Z); + } + protected float x, y, z, w; + + /** + * Constructor instantiates a new Quaternion object + * initializing all values to zero, except w which is initialized to 1. + * + */ + public Quaternion() { + x = 0; + y = 0; + z = 0; + w = 1; + } + + /** + * Constructor instantiates a new Quaternion object from the + * given list of parameters. + * + * @param x the x value of the quaternion. + * @param y the y value of the quaternion. + * @param z the z value of the quaternion. + * @param w the w value of the quaternion. + */ + public Quaternion(float x, float y, float z, float w) { + this.x = x; + this.y = y; + this.z = z; + this.w = w; + } + + public float getX() { + return x; + } + + public float getY() { + return y; + } + + public float getZ() { + return z; + } + + public float getW() { + return w; + } + + /** + * sets the data in a Quaternion object from the given list of + * parameters. + * + * @param x the x value of the quaternion. + * @param y the y value of the quaternion. + * @param z the z value of the quaternion. + * @param w the w value of the quaternion. + * @return this + */ + public Quaternion set(float x, float y, float z, float w) { + this.x = x; + this.y = y; + this.z = z; + this.w = w; + return this; + } + + /** + * Sets the data in this Quaternion object to be equal to the + * passed Quaternion object. The values are copied producing a + * new object. + * + * @param q The Quaternion to copy values from. + * @return this + */ + public Quaternion set(Quaternion q) { + this.x = q.x; + this.y = q.y; + this.z = q.z; + this.w = q.w; + return this; + } + + /** + * Constructor instantiates a new Quaternion object from a + * collection of rotation angles. + * + * @param angles the angles of rotation (x, y, z) that will define the + * Quaternion. + */ + public Quaternion(float[] angles) { + fromAngles(angles); + } + + /** + * Constructor instantiates a new Quaternion object from an + * interpolation between two other quaternions. + * + * @param q1 the first quaternion. + * @param q2 the second quaternion. + * @param interp the amount to interpolate between the two quaternions. + */ + public Quaternion(Quaternion q1, Quaternion q2, float interp) { + slerp(q1, q2, interp); + } + + /** + * Constructor instantiates a new Quaternion object from an + * existing quaternion, creating a copy. + * + * @param q the quaternion to copy. + */ + public Quaternion(Quaternion q) { + this.x = q.x; + this.y = q.y; + this.z = q.z; + this.w = q.w; + } + + /** + * Sets this Quaternion to {0, 0, 0, 1}. Same as calling set(0,0,0,1). + */ + public void loadIdentity() { + x = y = z = 0; + w = 1; + } + + /** + * @return true if this Quaternion is {0,0,0,1} + */ + public boolean isIdentity() { + if (x == 0 && y == 0 && z == 0 && w == 1) { + return true; + } else { + return false; + } + } + + /** + * fromAngles builds a quaternion from the Euler rotation + * angles (y,r,p). + * + * @param angles the Euler angles of rotation (in radians). + */ + public Quaternion fromAngles(float[] angles) { + if (angles.length != 3) { + throw new IllegalArgumentException("Angles array must have three elements"); + } + + return fromAngles(angles[0], angles[1], angles[2]); + } + + /** + * fromAngles builds a Quaternion from the Euler rotation + * angles (x,y,z) aka (pitch, yaw, rall)). Note that we are applying in + * order: (y, z, x) aka (yaw, roll, pitch) but we've ordered them in x, y, + * and z for convenience. + * + * @see http://www.euclideanspace.com/maths/geometry/rotations/conversions/eulerToQuaternion/index.htm + * + * @param xAngle the Euler pitch of rotation (in radians). (aka Attitude, + * often rot around x) + * @param yAngle the Euler yaw of rotation (in radians). (aka Heading, often + * rot around y) + * @param zAngle the Euler roll of rotation (in radians). (aka Bank, often + * rot around z) + */ + public Quaternion fromAngles(float xAngle, float yAngle, float zAngle) { + float angle; + float sinY, sinZ, sinX, cosY, cosZ, cosX; + angle = zAngle * 0.5f; + sinZ = FastMath.sin(angle); + cosZ = FastMath.cos(angle); + angle = yAngle * 0.5f; + sinY = FastMath.sin(angle); + cosY = FastMath.cos(angle); + angle = xAngle * 0.5f; + sinX = FastMath.sin(angle); + cosX = FastMath.cos(angle); + + // variables used to reduce multiplication calls. + float cosYXcosZ = cosY * cosZ; + float sinYXsinZ = sinY * sinZ; + float cosYXsinZ = cosY * sinZ; + float sinYXcosZ = sinY * cosZ; + + w = (cosYXcosZ * cosX - sinYXsinZ * sinX); + x = (cosYXcosZ * sinX + sinYXsinZ * cosX); + y = (sinYXcosZ * cosX + cosYXsinZ * sinX); + z = (cosYXsinZ * cosX - sinYXcosZ * sinX); + + normalizeLocal(); + return this; + } + + /** + * toAngles returns this quaternion converted to Euler rotation + * angles (yaw,roll,pitch).
+ * Note that the result is not always 100% accurate due to the implications + * of euler angles. + * + * @see http://www.euclideanspace.com/maths/geometry/rotations/conversions/quaternionToEuler/index.htm + * + * @param angles the float[] in which the angles should be stored, or null + * if you want a new float[] to be created + * @return the float[] in which the angles are stored. + */ + public float[] toAngles(float[] angles) { + if (angles == null) { + angles = new float[3]; + } else if (angles.length != 3) { + throw new IllegalArgumentException("Angles array must have three elements"); + } + + float sqw = w * w; + float sqx = x * x; + float sqy = y * y; + float sqz = z * z; + float unit = sqx + sqy + sqz + sqw; // if normalized is one, otherwise + // is correction factor + float test = x * y + z * w; + if (test > 0.499 * unit) { // singularity at north pole + angles[1] = 2 * FastMath.atan2(x, w); + angles[2] = FastMath.HALF_PI; + angles[0] = 0; + } else if (test < -0.499 * unit) { // singularity at south pole + angles[1] = -2 * FastMath.atan2(x, w); + angles[2] = -FastMath.HALF_PI; + angles[0] = 0; + } else { + angles[1] = FastMath.atan2(2 * y * w - 2 * x * z, sqx - sqy - sqz + sqw); // yaw + // or + // bank + angles[2] = FastMath.asin(2 * test / unit); // roll or heading + angles[0] = FastMath.atan2(2 * x * w - 2 * y * z, -sqx + sqy - sqz + sqw); // pitch + // or + // attitude + } + return angles; + } + + /** + * Returns Euler rotation angle around x axis (pitch). + * + * @return + * @see #toAngles(float[]) + */ + public float getPitch() { + float sqw = w * w; + float sqx = x * x; + float sqy = y * y; + float sqz = z * z; + float unit = sqx + sqy + sqz + sqw; // if normalized is one, otherwise + // is correction factor + float test = x * y + z * w; + if (test > 0.499 * unit) { // singularity at north pole + return 0; + } else if (test < -0.499 * unit) { // singularity at south pole + return 0; + } else { + return FastMath.atan2(2 * x * w - 2 * y * z, -sqx + sqy - sqz + sqw); // pitch + // or + // attitude + } + } + + /** + * Returns Euler rotation angle around y axis (yaw). + * + * @return + * @see #toAngles(float[]) + */ + public float getYaw() { + float sqw = w * w; + float sqx = x * x; + float sqy = y * y; + float sqz = z * z; + float unit = sqx + sqy + sqz + sqw; // if normalized is one, otherwise + // is correction factor + float test = x * y + z * w; + if (test > 0.499 * unit) { // singularity at north pole + return 2 * FastMath.atan2(x, w); + } else if (test < -0.499 * unit) { // singularity at south pole + return -2 * FastMath.atan2(x, w); + } else { + return FastMath.atan2(2 * y * w - 2 * x * z, sqx - sqy - sqz + sqw); // yaw + // or + // bank + } + } + + /** + * Returns Euler rotation angle around z axis (roll). + * + * @return + * @see #toAngles(float[]) + */ + public float getRoll() { + float sqw = w * w; + float sqx = x * x; + float sqy = y * y; + float sqz = z * z; + float unit = sqx + sqy + sqz + sqw; // if normalized is one, otherwise + // is correction factor + float test = x * y + z * w; + if (test > 0.499 * unit) { // singularity at north pole + return FastMath.HALF_PI; + } else if (test < -0.499 * unit) { // singularity at south pole + return -FastMath.HALF_PI; + } else { + return FastMath.asin(2 * test / unit); // roll or heading + } + } + + /** + * + * fromRotationMatrix generates a quaternion from a supplied + * matrix. This matrix is assumed to be a rotational matrix. + * + * @param matrix the matrix that defines the rotation. + */ + public Quaternion fromRotationMatrix(Matrix3f matrix) { + return fromRotationMatrix( + matrix.m00, + matrix.m01, + matrix.m02, + matrix.m10, + matrix.m11, + matrix.m12, + matrix.m20, + matrix.m21, + matrix.m22 + ); + } + + public Quaternion fromRotationMatrix( + float m00, + float m01, + float m02, + float m10, + float m11, + float m12, + float m20, + float m21, + float m22 + ) { + // first normalize the forward (F), up (U) and side (S) vectors of the + // rotation matrix + // so that the scale does not affect the rotation + float lengthSquared = m00 * m00 + m10 * m10 + m20 * m20; + if (lengthSquared != 1f && lengthSquared != 0f) { + lengthSquared = 1.0f / FastMath.sqrt(lengthSquared); + m00 *= lengthSquared; + m10 *= lengthSquared; + m20 *= lengthSquared; + } + lengthSquared = m01 * m01 + m11 * m11 + m21 * m21; + if (lengthSquared != 1f && lengthSquared != 0f) { + lengthSquared = 1.0f / FastMath.sqrt(lengthSquared); + m01 *= lengthSquared; + m11 *= lengthSquared; + m21 *= lengthSquared; + } + lengthSquared = m02 * m02 + m12 * m12 + m22 * m22; + if (lengthSquared != 1f && lengthSquared != 0f) { + lengthSquared = 1.0f / FastMath.sqrt(lengthSquared); + m02 *= lengthSquared; + m12 *= lengthSquared; + m22 *= lengthSquared; + } + + // Use the Graphics Gems code, from + // ftp://ftp.cis.upenn.edu/pub/graphics/shoemake/quatut.ps.Z + // *NOT* the "Matrix and Quaternions FAQ", which has errors! + + // the trace is the sum of the diagonal elements; see + // http://mathworld.wolfram.com/MatrixTrace.html + float t = m00 + m11 + m22; + + // we protect the division by s by ensuring that s>=1 + if (t >= 0) { // |w| >= .5 + float s = FastMath.sqrt(t + 1); // |s|>=1 ... + w = 0.5f * s; + s = 0.5f / s; // so this division isn't bad + x = (m21 - m12) * s; + y = (m02 - m20) * s; + z = (m10 - m01) * s; + } else if ((m00 > m11) && (m00 > m22)) { + float s = FastMath.sqrt(1.0f + m00 - m11 - m22); // |s|>=1 + x = s * 0.5f; // |x| >= .5 + s = 0.5f / s; + y = (m10 + m01) * s; + z = (m02 + m20) * s; + w = (m21 - m12) * s; + } else if (m11 > m22) { + float s = FastMath.sqrt(1.0f + m11 - m00 - m22); // |s|>=1 + y = s * 0.5f; // |y| >= .5 + s = 0.5f / s; + x = (m10 + m01) * s; + z = (m21 + m12) * s; + w = (m02 - m20) * s; + } else { + float s = FastMath.sqrt(1.0f + m22 - m00 - m11); // |s|>=1 + z = s * 0.5f; // |z| >= .5 + s = 0.5f / s; + x = (m02 + m20) * s; + y = (m21 + m12) * s; + w = (m10 - m01) * s; + } + + return this; + } + + /** + * toRotationMatrix converts this quaternion to a rotational + * matrix. Note: the result is created from a normalized version of this + * quat. + * + * @return the rotation matrix representation of this quaternion. + */ + public Matrix3f toRotationMatrix() { + Matrix3f matrix = new Matrix3f(); + return toRotationMatrix(matrix); + } + + /** + * toRotationMatrix converts this quaternion to a rotational + * matrix. The result is stored in result. + * + * @param result The Matrix3f to store the result in. + * @return the rotation matrix representation of this quaternion. + */ + public Matrix3f toRotationMatrix(Matrix3f result) { + + float norm = norm(); + // we explicitly test norm against one here, saving a division + // at the cost of a test and branch. Is it worth it? + float s = (norm == 1f) ? 2f : (norm > 0f) ? 2f / norm : 0; + + // compute xs/ys/zs first to save 6 multiplications, since xs/ys/zs + // will be used 2-4 times each. + float xs = x * s; + float ys = y * s; + float zs = z * s; + float xx = x * xs; + float xy = x * ys; + float xz = x * zs; + float xw = w * xs; + float yy = y * ys; + float yz = y * zs; + float yw = w * ys; + float zz = z * zs; + float zw = w * zs; + + // using s=2/norm (instead of 1/norm) saves 9 multiplications by 2 here + result.m00 = 1 - (yy + zz); + result.m01 = (xy - zw); + result.m02 = (xz + yw); + result.m10 = (xy + zw); + result.m11 = 1 - (xx + zz); + result.m12 = (yz - xw); + result.m20 = (xz - yw); + result.m21 = (yz + xw); + result.m22 = 1 - (xx + yy); + + return result; + } + + /** + * toRotationMatrix converts this quaternion to a rotational + * matrix. The result is stored in result. 4th row and 4th column values are + * untouched. Note: the result is created from a normalized version of this + * quat. + * + * @param result The Matrix4f to store the result in. + * @return the rotation matrix representation of this quaternion. + */ + public Matrix4f toRotationMatrix(Matrix4f result) { + TempVars tempv = TempVars.get(); + Vector3f originalScale = tempv.vect1; + + result.toScaleVector(originalScale); + result.setScale(1, 1, 1); + float norm = norm(); + // we explicitly test norm against one here, saving a division + // at the cost of a test and branch. Is it worth it? + float s = (norm == 1f) ? 2f : (norm > 0f) ? 2f / norm : 0; + + // compute xs/ys/zs first to save 6 multiplications, since xs/ys/zs + // will be used 2-4 times each. + float xs = x * s; + float ys = y * s; + float zs = z * s; + float xx = x * xs; + float xy = x * ys; + float xz = x * zs; + float xw = w * xs; + float yy = y * ys; + float yz = y * zs; + float yw = w * ys; + float zz = z * zs; + float zw = w * zs; + + // using s=2/norm (instead of 1/norm) saves 9 multiplications by 2 here + result.m00 = 1 - (yy + zz); + result.m01 = (xy - zw); + result.m02 = (xz + yw); + result.m10 = (xy + zw); + result.m11 = 1 - (xx + zz); + result.m12 = (yz - xw); + result.m20 = (xz - yw); + result.m21 = (yz + xw); + result.m22 = 1 - (xx + yy); + + result.setScale(originalScale); + + tempv.release(); + + return result; + } + + /** + * getRotationColumn returns one of three columns specified by + * the parameter. This column is returned as a Vector3f object. + * + * @param i the column to retrieve. Must be between 0 and 2. + * @return the column specified by the index. + */ + public Vector3f getRotationColumn(int i) { + return getRotationColumn(i, null); + } + + /** + * getRotationColumn returns one of three columns specified by + * the parameter. This column is returned as a Vector3f object. + * The value is retrieved as if this quaternion was first normalized. + * + * @param i the column to retrieve. Must be between 0 and 2. + * @param store the vector object to store the result in. if null, a new one + * is created. + * @return the column specified by the index. + */ + public Vector3f getRotationColumn(int i, Vector3f store) { + if (store == null) { + store = new Vector3f(); + } + + float norm = norm(); + if (norm != 1.0f) { + norm = FastMath.invSqrt(norm); + } + + float xx = x * x * norm; + float xy = x * y * norm; + float xz = x * z * norm; + float xw = x * w * norm; + float yy = y * y * norm; + float yz = y * z * norm; + float yw = y * w * norm; + float zz = z * z * norm; + float zw = z * w * norm; + + switch (i) { + case 0: + store.x = 1 - 2 * (yy + zz); + store.y = 2 * (xy + zw); + store.z = 2 * (xz - yw); + break; + case 1: + store.x = 2 * (xy - zw); + store.y = 1 - 2 * (xx + zz); + store.z = 2 * (yz + xw); + break; + case 2: + store.x = 2 * (xz + yw); + store.y = 2 * (yz - xw); + store.z = 1 - 2 * (xx + yy); + break; + default: + logger.warning("Invalid column index."); + throw new IllegalArgumentException("Invalid column index. " + i); + } + + return store; + } + + /** + * Gets three rows of rotation matrix. + *

+ * The same as transposed columns from {@linkplain #getRotationColumn}. + */ + public void getRotationBasis(Vector3f v1, Vector3f v2, Vector3f v3) { + // This source code from toRotationMatrix method + float norm = norm(); + float s = (norm == 1f) ? 2f : (norm > 0f) ? 2f / norm : 0; + //@formatter:off + float xs = x * s; float ys = y * s; float zs = z * s; + float xx = x * xs; float xy = x * ys; float xz = x * zs; + float xw = w * xs; float yy = y * ys; float yz = y * zs; + float yw = w * ys; float zz = z * zs; float zw = w * zs; + //@formatter:on + v1.set(1f - yy - zz, xy + zw, xz - yw); + v2.set(xy - zw, 1f - xx - zz, yz + xw); + v3.set(xz + yw, yz - xw, 1f - xx - yy); + } + + /** + * fromAngleAxis sets this quaternion to the values specified + * by an angle and an axis of rotation. This method creates an object, so + * use fromAngleNormalAxis if your axis is already normalized. + * + * @param angle the angle to rotate (in radians). + * @param axis the axis of rotation. + * @return this quaternion + */ + public Quaternion fromAngleAxis(float angle, Vector3f axis) { + Vector3f normAxis = axis.normalize(); + fromAngleNormalAxis(angle, normAxis); + return this; + } + + /** + * fromAngleAxis sets this quaternion to the values specified + * by an angle and an axis of rotation. This method creates an object, so + * use fromAngleNormalAxis if your axis is already normalized. + * + * @param angle the angle to rotate (in radians). + * @param x + * @param y + * @param z the axis of rotation. + * @return this quaternion + */ + public Quaternion fromAngleAxis(float angle, float x, float y, float z) { + float length = x * x + y * y + z * z; + if (length != 1f && length != 0f) { + length = 1.0f / FastMath.sqrt(length); + return fromAngleNormalAxis(angle, x * length, y * length, z * length); + } else { + return fromAngleNormalAxis(angle, x, y, z); + } + } + + /** + * fromAngleNormalAxis sets this quaternion to the values + * specified by an angle and a normalized axis of rotation. + * + * @param angle the angle to rotate (in radians). + * @param axis the axis of rotation (already normalized). + */ + public Quaternion fromAngleNormalAxis(float angle, Vector3f axis) { + if (axis.x == 0 && axis.y == 0 && axis.z == 0) { + loadIdentity(); + } else { + float halfAngle = 0.5f * angle; + float sin = FastMath.sin(halfAngle); + w = FastMath.cos(halfAngle); + x = sin * axis.x; + y = sin * axis.y; + z = sin * axis.z; + } + return this; + } + + /** + * fromAngleNormalAxis sets this quaternion to the values + * specified by an angle and a normalized axis of rotation. + * + * @param angle the angle to rotate (in radians). + * @param x + * @param y + * @param z the axis of rotation (already normalized). + */ + public Quaternion fromAngleNormalAxis(float angle, float ax, float ay, float az) { + if (ax == 0 && ay == 0 && az == 0) { + loadIdentity(); + } else { + float halfAngle = 0.5f * angle; + float sin = FastMath.sin(halfAngle); + w = FastMath.cos(halfAngle); + x = sin * ax; + y = sin * ay; + z = sin * az; + } + return this; + } + + /** + * toAngleAxis sets a given angle and axis to that represented + * by the current quaternion. The values are stored as follows: The axis is + * provided as a parameter and built by the method, the angle is returned as + * a float. + * + * @param axisStore the object we'll store the computed axis in. + * @return the angle of rotation in radians. + */ + public float toAngleAxis(Vector3f axisStore) { + float sqrLength = x * x + y * y + z * z; + float angle; + if (sqrLength == 0.0f) { + angle = 0.0f; + if (axisStore != null) { + axisStore.x = 1.0f; + axisStore.y = 0.0f; + axisStore.z = 0.0f; + } + } else { + angle = (2.0f * FastMath.acos(w)); + if (axisStore != null) { + float invLength = (1.0f / FastMath.sqrt(sqrLength)); + axisStore.x = x * invLength; + axisStore.y = y * invLength; + axisStore.z = z * invLength; + } + } + + return angle; + } + + public float angleBetween(Quaternion q2) { + float w = this.w * q2.w + this.x * q2.x + this.y * q2.y + this.z * q2.z; + float x = this.w * q2.x - this.x * q2.w - this.y * q2.z + this.z * q2.y; + float y = this.w * q2.y + this.x * q2.z - this.y * q2.w - this.z * q2.x; + float z = this.w * q2.z - this.x * q2.y + this.y * q2.x - this.z * q2.w; + + // compute cosine and sine of the angle between + // do so in a numerically stable way + return FastMath.atan2(FastMath.sqrt(x * x + y * y + z * z), w); + } + + public Quaternion pureSlerpLocal(Quaternion q2, float t) { + // make it nice and symmetrical + Quaternion q1 = this; + + // get q2 relative to q1 + float rw = q1.w * q2.w + q1.x * q2.x + q1.y * q2.y + q1.z * q2.z; + float rx = q1.w * q2.x - q1.x * q2.w - q1.y * q2.z + q1.z * q2.y; + float ry = q1.w * q2.y + q1.x * q2.z - q1.y * q2.w - q1.z * q2.x; + float rz = q1.w * q2.z - q1.x * q2.y + q1.y * q2.x - q1.z * q2.w; + + // compute theta robustly + float theta = FastMath.atan2(FastMath.sqrt(rx * rx + ry * ry + rz * rz), rw); + + // compute interpolation variables + float s0 = FastMath.sin((1.0f - t) * theta); + float s1 = FastMath.sin(t * theta); + + // compute interpolated quaternion + float sw = s0 * q1.w + s1 * q2.w; + float sx = s0 * q1.x + s1 * q2.x; + float sy = s0 * q1.y + s1 * q2.y; + float sz = s0 * q1.z + s1 * q2.z; + + // compute the length of the quaternion + float mag = FastMath.sqrt(sw * sw + sx * sx + sy * sy + sz * sz); + + if (mag > 0.0f) { + float iMag = 1.0f / mag; + this.w = iMag * sw; + this.x = iMag * sx; + this.y = iMag * sy; + this.z = iMag * sz; + + } else if (t >= 0.5f) { + this.w = q2.w; + this.x = q2.x; + this.y = q2.y; + this.z = q2.z; + } + // else this == q1, no need to do anything. + + return this; + } + + /** + * @deprecated Direct call to {@link #slerpLocal()}. + */ + public Quaternion slerp(Quaternion q2, float t) { + return this.slerpLocal(q2, t); + } + + /** + * Sets the values of this normalized quaternion from itself to the + * normalized quaternion q2 by t + * + * @param q2 Final interpolation value + * @param t The amount diffrence + */ + public Quaternion slerpLocal(Quaternion q2, float t) { + // make it nice and symmetrical + Quaternion q1 = this; + + float rw = q1.w * q2.w + q1.x * q2.x + q1.y * q2.y + q1.z * q2.z; + + if (rw < 0) { + return this.pureSlerpLocal(q2.negate(), t); + } else { + return this.pureSlerpLocal(q2, t); + } + } + + + public Quaternion pureSlerp(Quaternion q1, Quaternion q2, float t) { + return set(q1).pureSlerpLocal(q2, t); + } + + /** + * slerp sets this quaternion's value as an interpolation + * between two other normalized quaternions. + * + * @param q1 the first quaternion. + * @param q2 the second quaternion. + * @param t the amount to interpolate between the two quaternions. + */ + public Quaternion slerp(Quaternion q1, Quaternion q2, float t) { + float rw = q1.w * q2.w + q1.x * q2.x + q1.y * q2.y + q1.z * q2.z; + + if (rw < 0) { + return pureSlerp(q1, q2.negate(), t); + } else { + return pureSlerp(q1, q2, t); + } + } + + /** + * Sets the values of this quaternion to the nlerp from itself to q2 by + * blend. + * + * @param q2 + * @param blend + */ + public void nlerp(Quaternion q2, float blend) { + float dot = dot(q2); + float blendI = 1.0f - blend; + if (dot < 0.0f) { + x = blendI * x - blend * q2.x; + y = blendI * y - blend * q2.y; + z = blendI * z - blend * q2.z; + w = blendI * w - blend * q2.w; + } else { + x = blendI * x + blend * q2.x; + y = blendI * y + blend * q2.y; + z = blendI * z + blend * q2.z; + w = blendI * w + blend * q2.w; + } + normalizeLocal(); + } + + /** + * add adds the values of this quaternion to those of the + * parameter quaternion. The result is returned as a new quaternion. + * + * @param q the quaternion to add to this. + * @return the new quaternion. + */ + public Quaternion add(Quaternion q) { + return new Quaternion(x + q.x, y + q.y, z + q.z, w + q.w); + } + + /** + * add adds the values of this quaternion to those of the + * parameter quaternion. The result is stored in this Quaternion. + * + * @param q the quaternion to add to this. + * @return This Quaternion after addition. + */ + public Quaternion addLocal(Quaternion q) { + this.x += q.x; + this.y += q.y; + this.z += q.z; + this.w += q.w; + return this; + } + + /** + * subtract subtracts the values of the parameter quaternion + * from those of this quaternion. The result is returned as a new + * quaternion. + * + * @param q the quaternion to subtract from this. + * @return the new quaternion. + */ + public Quaternion subtract(Quaternion q) { + return new Quaternion(x - q.x, y - q.y, z - q.z, w - q.w); + } + + /** + * subtract subtracts the values of the parameter quaternion + * from those of this quaternion. The result is stored in this Quaternion. + * + * @param q the quaternion to subtract from this. + * @return This Quaternion after subtraction. + */ + public Quaternion subtractLocal(Quaternion q) { + this.x -= q.x; + this.y -= q.y; + this.z -= q.z; + this.w -= q.w; + return this; + } + + /** + * mult multiplies this quaternion by a parameter quaternion. + * The result is returned as a new quaternion. It should be noted that + * quaternion multiplication is not commutative so q * p != p * q. + * + * @param q the quaternion to multiply this quaternion by. + * @return the new quaternion. + */ + public Quaternion mult(Quaternion q) { + return mult(q, null); + } + + /** + * mult multiplies this quaternion by a parameter quaternion. + * The result is returned as a new quaternion. It should be noted that + * quaternion multiplication is not commutative so q * p != p * q. + * + * It IS safe for q and res to be the same object. It IS NOT safe for this + * and res to be the same object. + * + * @param q the quaternion to multiply this quaternion by. + * @param res the quaternion to store the result in. + * @return the new quaternion. + */ + public Quaternion mult(Quaternion q, Quaternion res) { + if (res == null) { + res = new Quaternion(); + } + float qw = q.w, qx = q.x, qy = q.y, qz = q.z; + res.x = x * qw + y * qz - z * qy + w * qx; + res.y = -x * qz + y * qw + z * qx + w * qy; + res.z = x * qy - y * qx + z * qw + w * qz; + res.w = -x * qx - y * qy - z * qz + w * qw; + return res; + } + + /** + * apply multiplies this quaternion by a parameter matrix + * internally. + * + * @param matrix the matrix to apply to this quaternion. + */ + public void apply(Matrix3f matrix) { + float oldX = x, oldY = y, oldZ = z, oldW = w; + fromRotationMatrix(matrix); + float tempX = x, tempY = y, tempZ = z, tempW = w; + + x = oldX * tempW + oldY * tempZ - oldZ * tempY + oldW * tempX; + y = -oldX * tempZ + oldY * tempW + oldZ * tempX + oldW * tempY; + z = oldX * tempY - oldY * tempX + oldZ * tempW + oldW * tempZ; + w = -oldX * tempX - oldY * tempY - oldZ * tempZ + oldW * tempW; + } + + /** + * + * fromAxes creates a Quaternion that represents + * the coordinate system defined by three axes. These axes are assumed to be + * orthogonal and no error checking is applied. Thus, the user must insure + * that the three axes being provided indeed represents a proper right + * handed coordinate system. + * + * @param axis the array containing the three vectors representing the + * coordinate system. + */ + public Quaternion fromAxes(Vector3f[] axis) { + if (axis.length != 3) { + throw new IllegalArgumentException("Axis array must have three elements"); + } + return fromAxes(axis[0], axis[1], axis[2]); + } + + /** + * + * fromAxes creates a Quaternion that represents + * the coordinate system defined by three axes. These axes are assumed to be + * orthogonal and no error checking is applied. Thus, the user must insure + * that the three axes being provided indeed represents a proper right + * handed coordinate system. + * + * @param xAxis vector representing the x-axis of the coordinate system. + * @param yAxis vector representing the y-axis of the coordinate system. + * @param zAxis vector representing the z-axis of the coordinate system. + */ + public Quaternion fromAxes(Vector3f xAxis, Vector3f yAxis, Vector3f zAxis) { + return fromRotationMatrix( + xAxis.x, + yAxis.x, + zAxis.x, + xAxis.y, + yAxis.y, + zAxis.y, + xAxis.z, + yAxis.z, + zAxis.z + ); + } + + /** + * + * toAxes takes in an array of three vectors. Each vector + * corresponds to an axis of the coordinate system defined by the quaternion + * rotation. + * + * @param axis the array of vectors to be filled. + */ + public void toAxes(Vector3f axis[]) { + Matrix3f tempMat = toRotationMatrix(); + axis[0] = tempMat.getColumn(0, axis[0]); + axis[1] = tempMat.getColumn(1, axis[1]); + axis[2] = tempMat.getColumn(2, axis[2]); + } + + /** + * mult multiplies this quaternion by a parameter vector. The + * result is returned as a new vector. + * + * @param v the vector to multiply this quaternion by. + * @return the new vector. + */ + public Vector3f mult(Vector3f v) { + return mult(v, null); + } + + /** + * mult multiplies this quaternion by a parameter vector. The + * result is stored in the supplied vector + * + * @param v the vector to multiply this quaternion by. + * @return v + */ + public Vector3f multLocal(Vector3f v) { + float tempX, tempY; + tempX = w * w * v.x + + 2 * y * w * v.z + - 2 * z * w * v.y + + x * x * v.x + + 2 * y * x * v.y + + 2 * z * x * v.z + - z * z * v.x + - y * y * v.x; + tempY = 2 * x * y * v.x + + y * y * v.y + + 2 * z * y * v.z + + 2 * w * z * v.x + - z * z * v.y + + w * w * v.y + - 2 * x * w * v.z + - x * x * v.y; + v.z = 2 * x * z * v.x + + 2 * y * z * v.y + + z * z * v.z + - 2 * w * y * v.x + - y * y * v.z + + 2 * w * x * v.y + - x * x * v.z + + w * w * v.z; + v.x = tempX; + v.y = tempY; + return v; + } + + /** + * Multiplies this Quaternion by the supplied quaternion. The result is + * stored in this Quaternion, which is also returned for chaining. Similar + * to this *= q. + * + * @param q The Quaternion to multiply this one by. + * @return This Quaternion, after multiplication. + */ + public Quaternion multLocal(Quaternion q) { + float x1 = x * q.w + y * q.z - z * q.y + w * q.x; + float y1 = -x * q.z + y * q.w + z * q.x + w * q.y; + float z1 = x * q.y - y * q.x + z * q.w + w * q.z; + w = -x * q.x - y * q.y - z * q.z + w * q.w; + x = x1; + y = y1; + z = z1; + return this; + } + + /** + * Multiplies this Quaternion by the supplied quaternion. The result is + * stored in this Quaternion, which is also returned for chaining. Similar + * to this *= q. + * + * @param qx - quat x value + * @param qy - quat y value + * @param qz - quat z value + * @param qw - quat w value + * + * @return This Quaternion, after multiplication. + */ + public Quaternion multLocal(float qx, float qy, float qz, float qw) { + float x1 = x * qw + y * qz - z * qy + w * qx; + float y1 = -x * qz + y * qw + z * qx + w * qy; + float z1 = x * qy - y * qx + z * qw + w * qz; + w = -x * qx - y * qy - z * qz + w * qw; + x = x1; + y = y1; + z = z1; + return this; + } + + /** + * mult multiplies this quaternion by a parameter vector. The + * result is returned as a new vector. + * + * @param v the vector to multiply this quaternion by. + * @param store the vector to store the result in. It IS safe for v and + * store to be the same object. + * @return the result vector. + */ + public Vector3f mult(Vector3f v, Vector3f store) { + if (store == null) { + store = new Vector3f(); + } + if (v.x == 0 && v.y == 0 && v.z == 0) { + store.set(0, 0, 0); + } else { + float vx = v.x, vy = v.y, vz = v.z; + store.x = w * w * vx + + 2 * y * w * vz + - 2 * z * w * vy + + x * x * vx + + 2 * y * x * vy + + 2 * z * x * vz + - z * z * vx + - y * y * vx; + store.y = 2 * x * y * vx + + y * y * vy + + 2 * z * y * vz + + 2 * w * z * vx + - z * z * vy + + w * w * vy + - 2 * x * w * vz + - x * x * vy; + store.z = 2 * x * z * vx + + 2 * y * z * vy + + z * z * vz + - 2 * w * y * vx + - y * y * vz + + 2 * w * x * vy + - x * x * vz + + w * w * vz; + } + return store; + } + + /** + * @return X component of vector rotated by quaternion + */ + public float multX(float vx, float vy, float vz) { + return w * w * vx + + 2 * y * w * vz + - 2 * z * w * vy + + x * x * vx + + 2 * y * x * vy + + 2 * z * x * vz + - z * z * vx + - y * y * vx; + } + + /** + * @return Y component of vector rotated by quaternion + */ + public float multY(float vx, float vy, float vz) { + return 2 * x * y * vx + + y * y * vy + + 2 * z * y * vz + + 2 * w * z * vx + - z * z * vy + + w * w * vy + - 2 * x * w * vz + - x * x * vy; + } + + /** + * @return Z component of vector rotated by quaternion + */ + public float multZ(float vx, float vy, float vz) { + return 2 * x * z * vx + + 2 * y * z * vy + + z * z * vz + - 2 * w * y * vx + - y * y * vz + + 2 * w * x * vy + - x * x * vz + + w * w * vz; + } + + /** + * Rotate X axis aligned vector. + */ + public Vector3f multAxisX(float vx, Vector3f store) { + if (store == null) + store = new Vector3f(); + store.x = (w * w + x * x - z * z - y * y) * vx; + store.y = 2f * (x * y + w * z) * vx; + store.z = 2f * (x * z - w * y) * vx; + return store; + } + + /** + * Rotate Y axis aligned vector. + */ + public Vector3f multAxisY(float vy, Vector3f store) { + if (store == null) + store = new Vector3f(); + store.x = 2f * (y * x - z * w) * vy; + store.y = (y * y - z * z + w * w - x * x) * vy; + store.z = 2f * (y * z + w * x) * vy; + return store; + } + + /** + * Rotate Z axis aligned vector. + */ + public Vector3f multAxisZ(float vz, Vector3f store) { + if (store == null) + store = new Vector3f(); + store.x = 2f * (y * w + z * x) * vz; + store.y = 2f * (z * y - x * w) * vz; + store.z = (z * z - y * y - x * x + w * w) * vz; + return store; + } + + /** + * mult multiplies this quaternion by a parameter vector. The + * result is returned as a new vector. + * + * @param vx + * @param vy + * @param vz the vector to multiply this quaternion by. + * @param store the vector to store the result in. It IS safe for v and + * store to be the same object. + * @return the result vector. + */ + public Vector3f mult(float vx, float vy, float vz, Vector3f store) { + if (store == null) { + store = new Vector3f(); + } + if (vx == 0 && vy == 0 && vz == 0) { + store.set(0, 0, 0); + } else { + store.x = w * w * vx + + 2 * y * w * vz + - 2 * z * w * vy + + x * x * vx + + 2 * y * x * vy + + 2 * z * x * vz + - z * z * vx + - y * y * vx; + store.y = 2 * x * y * vx + + y * y * vy + + 2 * z * y * vz + + 2 * w * z * vx + - z * z * vy + + w * w * vy + - 2 * x * w * vz + - x * x * vy; + store.z = 2 * x * z * vx + + 2 * y * z * vy + + z * z * vz + - 2 * w * y * vx + - y * y * vz + + 2 * w * x * vy + - x * x * vz + + w * w * vz; + } + return store; + } + + /** + * mult multiplies this quaternion by a parameter scalar. The + * result is returned as a new quaternion. + * + * @param scalar the quaternion to multiply this quaternion by. + * @return the new quaternion. + */ + public Quaternion mult(float scalar) { + return new Quaternion(scalar * x, scalar * y, scalar * z, scalar * w); + } + + /** + * mult multiplies this quaternion by a parameter scalar. The + * result is stored locally. + * + * @param scalar the quaternion to multiply this quaternion by. + * @return this. + */ + public Quaternion multLocal(float scalar) { + w *= scalar; + x *= scalar; + y *= scalar; + z *= scalar; + return this; + } + + /** + * dot calculates and returns the dot product of this + * quaternion with that of the parameter quaternion. + * + * @param q the quaternion to calculate the dot product of. + * @return the dot product of this and the parameter quaternion. + */ + public float dot(Quaternion q) { + return w * q.w + x * q.x + y * q.y + z * q.z; + } + + /** + * norm returns the norm of this quaternion. This is the dot + * product of this quaternion with itself. + * + * @return the norm of the quaternion. + */ + public float norm() { + return w * w + x * x + y * y + z * z; + } + + /** + * normalizeLocal normalizes the current + * Quaternion. The result is stored internally. + */ + public Quaternion normalizeLocal() { + float n = FastMath.invSqrt(norm()); + x *= n; + y *= n; + z *= n; + w *= n; + return this; + } + + /** + * normalize returns the normalized Quaternion. + */ + public Quaternion normalize() { + Quaternion q = this.clone(); + + float n = FastMath.invSqrt(q.norm()); + q.x *= n; + q.y *= n; + q.z *= n; + q.w *= n; + return q; + } + + /** + * inverse returns the inverse of this quaternion as a new + * quaternion. If this quaternion does not have an inverse (if its normal is + * 0 or less), then null is returned. + * + * @return the inverse of this quaternion or null if the inverse does not + * exist. + */ + public Quaternion inverse() { + float norm = norm(); + if (norm > 0.0) { + float invNorm = 1.0f / norm; + return new Quaternion(-x * invNorm, -y * invNorm, -z * invNorm, w * invNorm); + } + // return an invalid result to flag the error + return null; + } + + /** + * inverse returns the inverse of this quaternion. If this + * quaternion does not have an inverse (if its normal is 0 or less), then + * null is returned. + * + * @return the inverse of this quaternion or null if the inverse does not + * exist. + */ + public Quaternion inverse(Quaternion store) { + float norm = norm(); + if (norm > 0.0) { + float invNorm = 1.0f / norm; + return store.set(-x * invNorm, -y * invNorm, -z * invNorm, w * invNorm); + } + // return an invalid result to flag the error + return null; + } + + /** + * inverse calculates the inverse of this quaternion and + * returns this quaternion after it is calculated. If this quaternion does + * not have an inverse (if it's normal is 0 or less), nothing happens + * + * @return the inverse of this quaternion + */ + public Quaternion inverseLocal() { + float norm = norm(); + if (norm > 0.0) { + float invNorm = 1.0f / norm; + x *= -invNorm; + y *= -invNorm; + z *= -invNorm; + w *= invNorm; + } + return this; + } + + /** + * negateLocal inverts the values of the quaternion and returns + * it. + */ + public Quaternion negateLocal() { + x = -x; + y = -y; + z = -z; + w = -w; + return this; + } + + /** + * negate returns a negated copy of the quaternion. + */ + public Quaternion negate() { + return new Quaternion(-x, -y, -z, -w); + } + + /** + * + * toString creates the string representation of this + * Quaternion. The values of the quaternion are displaced (x, + * y, z, w), in the following manner:
+ * (x, y, z, w) + * + * @return the string representation of this object. + * @see java.lang.Object#toString() + */ + @Override + public String toString() { + return "(" + x + ", " + y + ", " + z + ", " + w + ")"; + } + + /** + * equals determines if two quaternions are logically equal, + * that is, if the values of (x, y, z, w) are the same for both quaternions. + * + * @param o the object to compare for equality + * @return true if they are equal, false otherwise. + */ + @Override + public boolean equals(Object o) { + if (!(o instanceof Quaternion)) { + return false; + } + + if (this == o) { + return true; + } + + Quaternion comp = (Quaternion) o; + if (Float.compare(x, comp.x) != 0) { + return false; + } + if (Float.compare(y, comp.y) != 0) { + return false; + } + if (Float.compare(z, comp.z) != 0) { + return false; + } + if (Float.compare(w, comp.w) != 0) { + return false; + } + return true; + } + + /** + * + * hashCode returns the hash code value as an integer and is + * supported for the benefit of hashing based collection classes such as + * Hashtable, HashMap, HashSet etc. + * + * @return the hashcode for this instance of Quaternion. + * @see java.lang.Object#hashCode() + */ + @Override + public int hashCode() { + int hash = 37; + hash = 37 * hash + Float.floatToIntBits(x); + hash = 37 * hash + Float.floatToIntBits(y); + hash = 37 * hash + Float.floatToIntBits(z); + hash = 37 * hash + Float.floatToIntBits(w); + return hash; + + } + + /** + * readExternal builds a quaternion from an + * ObjectInput object.
+ * NOTE: Used with serialization. Not to be called manually. + * + * @param in the ObjectInput value to read from. + * @throws IOException if the ObjectInput value has problems reading a + * float. + * @see java.io.Externalizable + */ + public void readExternal(ObjectInput in) throws IOException { + x = in.readFloat(); + y = in.readFloat(); + z = in.readFloat(); + w = in.readFloat(); + } + + /** + * writeExternal writes this quaternion out to a + * ObjectOutput object. NOTE: Used with serialization. Not to + * be called manually. + * + * @param out the object to write to. + * @throws IOException if writing to the ObjectOutput fails. + * @see java.io.Externalizable + */ + public void writeExternal(ObjectOutput out) throws IOException { + out.writeFloat(x); + out.writeFloat(y); + out.writeFloat(z); + out.writeFloat(w); + } + + /** + * lookAt is a convienence method for auto-setting the + * quaternion based on a direction and an up vector. It computes the + * rotation to transform the z-axis to point into 'direction' and the y-axis + * to 'up'. + * + * @param direction where to look at in terms of local coordinates + * @param up a vector indicating the local up direction. (typically {0, 1, + * 0} in jME.) + */ + public void lookAt(Vector3f direction, Vector3f up) { + TempVars vars = TempVars.get(); + vars.vect3.set(direction).normalizeLocal(); + vars.vect1.set(up).crossLocal(direction).normalizeLocal(); + vars.vect2.set(direction).crossLocal(vars.vect1).normalizeLocal(); + fromAxes(vars.vect1, vars.vect2, vars.vect3); + vars.release(); + } + + /** + * @return A new quaternion that describes a rotation that would point you + * in the exact opposite direction of this Quaternion. + */ + public Quaternion opposite() { + return opposite(null); + } + + /** + * FIXME: This seems to have singularity type issues with angle == 0, + * possibly others such as PI. + * + * @param store A Quaternion to store our result in. If null, a new one is + * created. + * @return The store quaternion (or a new Quaterion, if store is null) that + * describes a rotation that would point you in the exact opposite direction + * of this Quaternion. + */ + public Quaternion opposite(Quaternion store) { + if (store == null) { + store = new Quaternion(); + } + + Vector3f axis = new Vector3f(); + float angle = toAngleAxis(axis); + + store.fromAngleAxis(FastMath.PI + angle, axis); + return store; + } + + /** + * @return This Quaternion, altered to describe a rotation that would point + * you in the exact opposite direction of where it is pointing currently. + */ + public Quaternion oppositeLocal() { + return opposite(this); + } + + @Override + public Quaternion clone() { + try { + return (Quaternion) super.clone(); + } catch (CloneNotSupportedException e) { + throw new AssertionError(); // can not happen + } + } + + /** + * Sets this quaternion to be a rotation from vec1 to vec2. + *

+ * Based on implementation from here: + * https://github.com/toji/gl-matrix/blob/f0583ef53e94bc7e78b78c8a24f09ed5e2f7a20c/src/gl-matrix/quat.js#L54 + * + * @param vec1 + * @param vec2 + * @return this quaternion + */ + public Quaternion angleBetweenVectors(Vector3f vec1, Vector3f vec2) { + float dot = vec1.dot(vec2); + if (FloatMath.lessOrEqualsWithEpsilon(dot, -1)) { + Vector3f cross = vec1.cross(Vector3f.UNIT_X); + if (FloatMath.lessOrEqualsToZero(cross.length())) + cross = vec1.cross(Vector3f.UNIT_Y); + cross.normalizeLocal(); + fromAngleAxis(FloatMath.PI, cross); + } else if (FloatMath.greaterOrEqualsWithEpsilon(dot, 1)) { + loadIdentity(); + } else { + Vector3f cross = vec1.cross(vec2); + x = cross.x; + y = cross.y; + z = cross.z; + w = 1 + dot; + normalizeLocal(); + } + return this; + } + + public static boolean isIdentity(Quaternion q) { + if (Float.compare(q.x, 0) != 0) { + return false; + } + if (Float.compare(q.y, 0) != 0) { + return false; + } + if (Float.compare(q.z, 0) != 0) { + return false; + } + if (Float.compare(q.w, 1) != 0) { + return false; + } + return true; + } +} diff --git a/java/com/jme3/math/Transform.java b/java/com/jme3/math/Transform.java new file mode 100644 index 000000000..f6cac7b5f --- /dev/null +++ b/java/com/jme3/math/Transform.java @@ -0,0 +1,399 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.math; + +/** + * Started Date: Jul 16, 2004
+ *
+ * Represents a translation, rotation and scale in one object. + * + * @author Jack Lindamood + * @author Joshua Slack + */ +public final class Transform implements Cloneable, java.io.Serializable { + + static final long serialVersionUID = 1; + + public static final Transform IDENTITY = new Transform(); + + private Quaternion rot = new Quaternion(); + private Vector3f translation = new Vector3f(); + private Vector3f scale = new Vector3f(1, 1, 1); + + public Transform(Vector3f translation, Quaternion rot) { + this.translation.set(translation); + this.rot.set(rot); + } + + public Transform(Vector3f translation, Quaternion rot, Vector3f scale) { + this(translation, rot); + this.scale.set(scale); + } + + public Transform(Vector3f translation) { + this(translation, Quaternion.IDENTITY); + } + + public Transform(Quaternion rot) { + this(Vector3f.ZERO, rot); + } + + public Transform() { + this(Vector3f.ZERO, Quaternion.IDENTITY); + } + + /** + * Sets this rotation to the given Quaternion value. + * + * @param rot The new rotation for this matrix. + * @return this + */ + public Transform setRotation(Quaternion rot) { + this.rot.set(rot); + return this; + } + + /** + * Sets this translation to the given value. + * + * @param trans The new translation for this matrix. + * @return this + */ + public Transform setTranslation(Vector3f trans) { + this.translation.set(trans); + return this; + } + + /** + * Return the translation vector in this matrix. + * + * @return translation vector. + */ + public Vector3f getTranslation() { + return translation; + } + + /** + * Sets this scale to the given value. + * + * @param scale The new scale for this matrix. + * @return this + */ + public Transform setScale(Vector3f scale) { + this.scale.set(scale); + return this; + } + + /** + * Sets this scale to the given value. + * + * @param scale The new scale for this matrix. + * @return this + */ + public Transform setScale(float scale) { + this.scale.set(scale, scale, scale); + return this; + } + + /** + * Return the scale vector in this matrix. + * + * @return scale vector. + */ + public Vector3f getScale() { + return scale; + } + + /** + * Stores this translation value into the given vector3f. If trans is null, + * a new vector3f is created to hold the value. The value, once stored, is + * returned. + * + * @param trans The store location for this matrix's translation. + * @return The value of this matrix's translation. + */ + public Vector3f getTranslation(Vector3f trans) { + if (trans == null) + trans = new Vector3f(); + trans.set(this.translation); + return trans; + } + + /** + * Stores this rotation value into the given Quaternion. If quat is null, a + * new Quaternion is created to hold the value. The value, once stored, is + * returned. + * + * @param quat The store location for this matrix's rotation. + * @return The value of this matrix's rotation. + */ + public Quaternion getRotation(Quaternion quat) { + if (quat == null) + quat = new Quaternion(); + quat.set(rot); + return quat; + } + + /** + * Return the rotation quaternion in this matrix. + * + * @return rotation quaternion. + */ + public Quaternion getRotation() { + return rot; + } + + /** + * Stores this scale value into the given vector3f. If scale is null, a new + * vector3f is created to hold the value. The value, once stored, is + * returned. + * + * @param scale The store location for this matrix's scale. + * @return The value of this matrix's scale. + */ + public Vector3f getScale(Vector3f scale) { + if (scale == null) + scale = new Vector3f(); + scale.set(this.scale); + return scale; + } + + /** + * Sets this matrix to the interpolation between the first matrix and the + * second by delta amount. + * + * @param t1 The begining transform. + * @param t2 The ending transform. + * @param delta An amount between 0 and 1 representing how far to + * interpolate from t1 to t2. + */ + public void interpolateTransforms(Transform t1, Transform t2, float delta) { + this.rot.slerp(t1.rot, t2.rot, delta); + this.translation.interpolate(t1.translation, t2.translation, delta); + this.scale.interpolate(t1.scale, t2.scale, delta); + } + + /** + * Changes the values of this matrix acording to it's parent. Very similar + * to the concept of Node/Spatial transforms. + * + * @param parent The parent matrix. + * @return This matrix, after combining. + */ + public Transform combineWithParent(Transform parent) { + scale.multLocal(parent.scale); +// rot.multLocal(parent.rot); + parent.rot.mult(rot, rot); + + // This here, is evil code +// parent +// .rot +// .multLocal(translation) +// .multLocal(parent.scale) +// .addLocal(parent.translation); + + translation.multLocal(parent.scale); + parent.rot + .multLocal(translation) + .addLocal(parent.translation); + return this; + } + + /** + * Same as {@link #combineWithParent(Transform)}, but assumes that rotation + * is global, so it's not modified. + * + * @param parent + * @return + */ + public Transform combineWithParentGlobalRotation(Transform parent) { + scale.multLocal(parent.scale); + translation.multLocal(parent.scale); + + parent.rot + .multLocal(translation) + .addLocal(parent.translation); + return this; + } + + /** + * Sets this matrix's translation to the given x,y,z values. + * + * @param x This matrix's new x translation. + * @param y This matrix's new y translation. + * @param z This matrix's new z translation. + * @return this + */ + public Transform setTranslation(float x, float y, float z) { + translation.set(x, y, z); + return this; + } + + /** + * Sets this matrix's scale to the given x,y,z values. + * + * @param x This matrix's new x scale. + * @param y This matrix's new y scale. + * @param z This matrix's new z scale. + * @return this + */ + public Transform setScale(float x, float y, float z) { + scale.set(x, y, z); + return this; + } + + public Vector3f transformVector(final Vector3f in, Vector3f store) { + if (store == null) + store = new Vector3f(); + + // multiply with scale first, then rotate, finally translate (cf. + // Eberly) + return rot.mult(store.set(in).multLocal(scale), store).addLocal(translation); + } + + public Vector3f transformInverseVector(final Vector3f in, Vector3f store) { + if (store == null) + store = new Vector3f(); + + // The author of this code should look above and take the inverse of + // that + // But for some reason, they didnt .. +// in.subtract(translation, store).divideLocal(scale); +// rot.inverse().mult(store, store); + + in.subtract(translation, store); + rot.inverse().mult(store, store); + store.divideLocal(scale); + + return store; + } + + /** + * Loads the identity. Equal to translation=0,0,0 scale=1,1,1 rot=0,0,0,1. + */ + public void loadIdentity() { + translation.set(0, 0, 0); + scale.set(1, 1, 1); + rot.set(0, 0, 0, 1); + } + + @Override + public String toString() { + return getClass().getSimpleName() + + "[ " + + translation.x + + ", " + + translation.y + + ", " + + translation.z + + "]\n" + + "[ " + + rot.x + + ", " + + rot.y + + ", " + + rot.z + + ", " + + rot.w + + "]\n" + + "[ " + + scale.x + + " , " + + scale.y + + ", " + + scale.z + + "]"; + } + + /** + * Sets this matrix to be equal to the given matrix. + * + * @param matrixQuat The matrix to be equal to. + * @return this + */ + public Transform set(Transform matrixQuat) { + this.translation.set(matrixQuat.translation); + this.rot.set(matrixQuat.rot); + this.scale.set(matrixQuat.scale); + return this; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((rot == null) ? 0 : rot.hashCode()); + result = prime * result + ((scale == null) ? 0 : scale.hashCode()); + result = prime * result + ((translation == null) ? 0 : translation.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + Transform other = (Transform) obj; + if (rot == null) { + if (other.rot != null) + return false; + } else if (!rot.equals(other.rot)) + return false; + if (scale == null) { + if (other.scale != null) + return false; + } else if (!scale.equals(other.scale)) + return false; + if (translation == null) { + if (other.translation != null) + return false; + } else if (!translation.equals(other.translation)) + return false; + return true; + } + + @Override + public Transform clone() { + try { + Transform tq = (Transform) super.clone(); + tq.rot = rot.clone(); + tq.scale = scale.clone(); + tq.translation = translation.clone(); + return tq; + } catch (CloneNotSupportedException e) { + throw new AssertionError(); + } + } +} diff --git a/java/com/jme3/math/Vector2f.java b/java/com/jme3/math/Vector2f.java new file mode 100644 index 000000000..00b7805c1 --- /dev/null +++ b/java/com/jme3/math/Vector2f.java @@ -0,0 +1,718 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.math; + +import java.io.IOException; +import java.io.ObjectInput; +import java.io.ObjectOutput; +import java.util.logging.Logger; + + +/** + * Vector2f defines a Vector for a two float value vector. + * + * @author Mark Powell + * @author Joshua Slack + */ +public final class Vector2f implements Cloneable, java.io.Serializable { + + static final long serialVersionUID = 1; + private static final Logger logger = Logger.getLogger(Vector2f.class.getName()); + + public static final Vector2f ZERO = new Vector2f(0f, 0f); + public static final Vector2f UNIT_XY = new Vector2f(1f, 1f); + + /** + * the x value of the vector. + */ + public float x; + /** + * the y value of the vector. + */ + public float y; + + /** + * Creates a Vector2f with the given initial x and y values. + * + * @param x The x value of this Vector2f. + * @param y The y value of this Vector2f. + */ + public Vector2f(float x, float y) { + this.x = x; + this.y = y; + } + + /** + * Creates a Vector2f with x and y set to 0. Equivalent to Vector2f(0,0). + */ + public Vector2f() { + x = y = 0; + } + + /** + * Creates a new Vector2f that contains the passed vector's information + * + * @param vector2f The vector to copy + */ + public Vector2f(Vector2f vector2f) { + this.x = vector2f.x; + this.y = vector2f.y; + } + + /** + * set the x and y values of the vector + * + * @param x the x value of the vector. + * @param y the y value of the vector. + * @return this vector + */ + public Vector2f set(float x, float y) { + this.x = x; + this.y = y; + return this; + } + + /** + * set the x and y values of the vector from another vector + * + * @param vec the vector to copy from + * @return this vector + */ + public Vector2f set(Vector2f vec) { + this.x = vec.x; + this.y = vec.y; + return this; + } + + /** + * add adds a provided vector to this vector creating a + * resultant vector which is returned. If the provided vector is null, null + * is returned. + * + * @param vec the vector to add to this. + * @return the resultant vector. + */ + public Vector2f add(Vector2f vec) { + if (null == vec) { + logger.warning("Provided vector is null, null returned."); + return null; + } + return new Vector2f(x + vec.x, y + vec.y); + } + + /** + * addLocal adds a provided vector to this vector internally, + * and returns a handle to this vector for easy chaining of calls. If the + * provided vector is null, null is returned. + * + * @param vec the vector to add to this vector. + * @return this + */ + public Vector2f addLocal(Vector2f vec) { + if (null == vec) { + logger.warning("Provided vector is null, null returned."); + return null; + } + x += vec.x; + y += vec.y; + return this; + } + + /** + * addLocal adds the provided values to this vector internally, + * and returns a handle to this vector for easy chaining of calls. + * + * @param addX value to add to x + * @param addY value to add to y + * @return this + */ + public Vector2f addLocal(float addX, float addY) { + x += addX; + y += addY; + return this; + } + + /** + * add adds this vector by vec and stores the + * result in result. + * + * @param vec The vector to add. + * @param result The vector to store the result in. + * @return The result vector, after adding. + */ + public Vector2f add(Vector2f vec, Vector2f result) { + if (null == vec) { + logger.warning("Provided vector is null, null returned."); + return null; + } + if (result == null) + result = new Vector2f(); + result.x = x + vec.x; + result.y = y + vec.y; + return result; + } + + /** + * dot calculates the dot product of this vector with a + * provided vector. If the provided vector is null, 0 is returned. + * + * @param vec the vector to dot with this vector. + * @return the resultant dot product of this vector and a given vector. + */ + public float dot(Vector2f vec) { + if (null == vec) { + logger.warning("Provided vector is null, 0 returned."); + return 0; + } + return x * vec.x + y * vec.y; + } + + /** + * cross calculates the cross product of this vector with a + * parameter vector v. + * + * @param v the vector to take the cross product of with this. + * @return the cross product vector. + */ + public Vector3f cross(Vector2f v) { + return new Vector3f(0, 0, determinant(v)); + } + + public float determinant(Vector2f v) { + return (x * v.y) - (y * v.x); + } + + /** + * Sets this vector to the interpolation by changeAmnt from this to the + * finalVec this=(1-changeAmnt)*this + changeAmnt * finalVec + * + * @param finalVec The final vector to interpolate towards + * @param changeAmnt An amount between 0.0 - 1.0 representing a percentage + * change from this towards finalVec + */ + public Vector2f interpolate(Vector2f finalVec, float changeAmnt) { + this.x = (1 - changeAmnt) * this.x + changeAmnt * finalVec.x; + this.y = (1 - changeAmnt) * this.y + changeAmnt * finalVec.y; + return this; + } + + /** + * Sets this vector to the interpolation by changeAmnt from beginVec to + * finalVec this=(1-changeAmnt)*beginVec + changeAmnt * finalVec + * + * @param beginVec The begining vector (delta=0) + * @param finalVec The final vector to interpolate towards (delta=1) + * @param changeAmnt An amount between 0.0 - 1.0 representing a precentage + * change from beginVec towards finalVec + */ + public Vector2f interpolate( + Vector2f beginVec, + Vector2f finalVec, + float changeAmnt + ) { + this.x = (1 - changeAmnt) * beginVec.x + changeAmnt * finalVec.x; + this.y = (1 - changeAmnt) * beginVec.y + changeAmnt * finalVec.y; + return this; + } + + /** + * Check a vector... if it is null or its floats are NaN or infinite, return + * false. Else return true. + * + * @param vector the vector to check + * @return true or false as stated above. + */ + public static boolean isValidVector(Vector2f vector) { + if (vector == null) + return false; + if ( + Float.isNaN(vector.x) + || + Float.isNaN(vector.y) + ) + return false; + if ( + Float.isInfinite(vector.x) + || + Float.isInfinite(vector.y) + ) + return false; + return true; + } + + /** + * length calculates the magnitude of this vector. + * + * @return the length or magnitude of the vector. + */ + public float length() { + return FastMath.sqrt(lengthSquared()); + } + + /** + * lengthSquared calculates the squared value of the magnitude + * of the vector. + * + * @return the magnitude squared of the vector. + */ + public float lengthSquared() { + return x * x + y * y; + } + + /** + * distanceSquared calculates the distance squared between this + * vector and vector v. + * + * @param v the second vector to determine the distance squared. + * @return the distance squared between the two vectors. + */ + public float distanceSquared(Vector2f v) { + double dx = x - v.x; + double dy = y - v.y; + return (float) (dx * dx + dy * dy); + } + + /** + * distanceSquared calculates the distance squared between this + * vector and vector v. + * + * @param otherX The X coordinate of the v vector + * @param otherY The Y coordinate of the v vector + * @return the distance squared between the two vectors. + */ + public float distanceSquared(float otherX, float otherY) { + double dx = x - otherX; + double dy = y - otherY; + return (float) (dx * dx + dy * dy); + } + + /** + * distance calculates the distance between this vector and + * vector v. + * + * @param v the second vector to determine the distance. + * @return the distance between the two vectors. + */ + public float distance(Vector2f v) { + return FastMath.sqrt(distanceSquared(v)); + } + + /** + * mult multiplies this vector by a scalar. The resultant + * vector is returned. + * + * @param scalar the value to multiply this vector by. + * @return the new vector. + */ + public Vector2f mult(float scalar) { + return new Vector2f(x * scalar, y * scalar); + } + + /** + * multLocal multiplies this vector by a scalar internally, and + * returns a handle to this vector for easy chaining of calls. + * + * @param scalar the value to multiply this vector by. + * @return this + */ + public Vector2f multLocal(float scalar) { + x *= scalar; + y *= scalar; + return this; + } + + /** + * multLocal multiplies a provided vector to this vector + * internally, and returns a handle to this vector for easy chaining of + * calls. If the provided vector is null, null is returned. + * + * @param vec the vector to mult to this vector. + * @return this + */ + public Vector2f multLocal(Vector2f vec) { + if (null == vec) { + logger.warning("Provided vector is null, null returned."); + return null; + } + x *= vec.x; + y *= vec.y; + return this; + } + + /** + * Multiplies this Vector2f's x and y by the scalar and stores the result in + * product. The result is returned for chaining. Similar to + * product=this*scalar; + * + * @param scalar The scalar to multiply by. + * @param product The vector2f to store the result in. + * @return product, after multiplication. + */ + public Vector2f mult(float scalar, Vector2f product) { + if (null == product) { + product = new Vector2f(); + } + + product.x = x * scalar; + product.y = y * scalar; + return product; + } + + /** + * divide divides the values of this vector by a scalar and + * returns the result. The values of this vector remain untouched. + * + * @param scalar the value to divide this vectors attributes by. + * @return the result Vector. + */ + public Vector2f divide(float scalar) { + return new Vector2f(x / scalar, y / scalar); + } + + /** + * divideLocal divides this vector by a scalar internally, and + * returns a handle to this vector for easy chaining of calls. Dividing by + * zero will result in an exception. + * + * @param scalar the value to divides this vector by. + * @return this + */ + public Vector2f divideLocal(float scalar) { + x /= scalar; + y /= scalar; + return this; + } + + /** + * negate returns the negative of this vector. All values are + * negated and set to a new vector. + * + * @return the negated vector. + */ + public Vector2f negate() { + return new Vector2f(-x, -y); + } + + /** + * negateLocal negates the internal values of this vector. + * + * @return this. + */ + public Vector2f negateLocal() { + x = -x; + y = -y; + return this; + } + + /** + * subtract subtracts the values of a given vector from those + * of this vector creating a new vector object. If the provided vector is + * null, an exception is thrown. + * + * @param vec the vector to subtract from this vector. + * @return the result vector. + */ + public Vector2f subtract(Vector2f vec) { + return subtract(vec, null); + } + + /** + * subtract subtracts the values of a given vector from those + * of this vector storing the result in the given vector object. If the + * provided vector is null, an exception is thrown. + * + * @param vec the vector to subtract from this vector. + * @param store the vector to store the result in. It is safe for this to be + * the same as vec. If null, a new vector is created. + * @return the result vector. + */ + public Vector2f subtract(Vector2f vec, Vector2f store) { + if (store == null) + store = new Vector2f(); + store.x = x - vec.x; + store.y = y - vec.y; + return store; + } + + /** + * subtract subtracts the given x,y values from those of this + * vector creating a new vector object. + * + * @param valX value to subtract from x + * @param valY value to subtract from y + * @return this + */ + public Vector2f subtract(float valX, float valY) { + return new Vector2f(x - valX, y - valY); + } + + /** + * subtractLocal subtracts a provided vector to this vector + * internally, and returns a handle to this vector for easy chaining of + * calls. If the provided vector is null, null is returned. + * + * @param vec the vector to subtract + * @return this + */ + public Vector2f subtractLocal(Vector2f vec) { + if (null == vec) { + logger.warning("Provided vector is null, null returned."); + return null; + } + x -= vec.x; + y -= vec.y; + return this; + } + + /** + * subtractLocal subtracts the provided values from this vector + * internally, and returns a handle to this vector for easy chaining of + * calls. + * + * @param valX value to subtract from x + * @param valY value to subtract from y + * @return this + */ + public Vector2f subtractLocal(float valX, float valY) { + x -= valX; + y -= valY; + return this; + } + + /** + * normalize returns the unit vector of this vector. + * + * @return unit vector of this vector. + */ + public Vector2f normalize() { + float length = length(); + if (length != 0) { + return divide(length); + } + + return divide(1); + } + + /** + * normalizeLocal makes this vector into a unit vector of + * itself. + * + * @return this. + */ + public Vector2f normalizeLocal() { + float length = length(); + if (length != 0) { + return divideLocal(length); + } + + return divideLocal(1); + } + + /** + * smallestAngleBetween returns (in radians) the minimum angle + * between two vectors. It is assumed that both this vector and the given + * vector are unit vectors (iow, normalized). + * + * @param otherVector a unit vector to find the angle against + * @return the angle in radians. + */ + public float smallestAngleBetween(Vector2f otherVector) { + float dotProduct = dot(otherVector); + float angle = FastMath.acos(dotProduct); + return angle; + } + + /** + * angleBetween returns (in radians) the angle required to + * rotate a ray represented by this vector to lie colinear to a ray + * described by the given vector. It is assumed that both this vector and + * the given vector are unit vectors (iow, normalized). + * + * @param otherVector the "destination" unit vector + * @return the angle in radians. + */ + public float angleBetween(Vector2f otherVector) { + float angle = FastMath.atan2(otherVector.y, otherVector.x) + - FastMath.atan2(y, x); + return angle; + } + + public float getX() { + return x; + } + + public Vector2f setX(float x) { + this.x = x; + return this; + } + + public float getY() { + return y; + } + + public Vector2f setY(float y) { + this.y = y; + return this; + } + + /** + * getAngle returns (in radians) the angle represented by this + * Vector2f as expressed by a conversion from rectangular coordinates + * (xy) to polar coordinates + * (r, theta). + * + * @return the angle in radians. [-pi, pi) + */ + public float getAngle() { + return FastMath.atan2(y, x); + } + + /** + * zero resets this vector's data to zero internally. + */ + public Vector2f zero() { + x = y = 0; + return this; + } + + /** + * hashCode returns a unique code for this vector object based + * on it's values. If two vectors are logically equivalent, they will return + * the same hash code value. + * + * @return the hash code value of this vector. + */ + @Override + public int hashCode() { + int hash = 37; + hash += 37 * hash + Float.floatToIntBits(x); + hash += 37 * hash + Float.floatToIntBits(y); + return hash; + } + + @Override + public Vector2f clone() { + try { + return (Vector2f) super.clone(); + } catch (CloneNotSupportedException e) { + throw new AssertionError(); // can not happen + } + } + + /** + * Saves this Vector2f into the given float[] object. + * + * @param floats The float[] to take this Vector2f. If null, a new float[2] + * is created. + * @return The array, with X, Y float values in that order + */ + public float[] toArray(float[] floats) { + if (floats == null) { + floats = new float[2]; + } + floats[0] = x; + floats[1] = y; + return floats; + } + + /** + * are these two vectors the same? they are is they both have the same x and + * y values. + * + * @param o the object to compare for equality + * @return true if they are equal + */ + @Override + public boolean equals(Object o) { + if (!(o instanceof Vector2f)) { + return false; + } + + if (this == o) { + return true; + } + + Vector2f comp = (Vector2f) o; + if (Float.compare(x, comp.x) != 0) + return false; + if (Float.compare(y, comp.y) != 0) + return false; + return true; + } + + /** + * toString returns the string representation of this vector + * object. The format of the string is such: com.jme.math.Vector2f + * [X=XX.XXXX, Y=YY.YYYY] + * + * @return the string representation of this vector. + */ + @Override + public String toString() { + return "(" + x + ", " + y + ")"; + } + + /** + * Used with serialization. Not to be called manually. + * + * @param in ObjectInput + * @throws IOException + * @throws ClassNotFoundException + * @see java.io.Externalizable + */ + public void readExternal(ObjectInput in) throws IOException, + ClassNotFoundException { + x = in.readFloat(); + y = in.readFloat(); + } + + /** + * Used with serialization. Not to be called manually. + * + * @param out ObjectOutput + * @throws IOException + * @see java.io.Externalizable + */ + public void writeExternal(ObjectOutput out) throws IOException { + out.writeFloat(x); + out.writeFloat(y); + } + + public void rotateAroundOrigin(float angle, boolean cw) { + if (cw) + angle = -angle; + float newX = FastMath.cos(angle) * x - FastMath.sin(angle) * y; + float newY = FastMath.sin(angle) * x + FastMath.cos(angle) * y; + x = newX; + y = newY; + } +} diff --git a/java/com/jme3/math/Vector3f.java b/java/com/jme3/math/Vector3f.java new file mode 100644 index 000000000..2d764e4ac --- /dev/null +++ b/java/com/jme3/math/Vector3f.java @@ -0,0 +1,1101 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package com.jme3.math; + +import java.util.logging.Logger; + +import io.eiren.math.Vector3d; + +/* + * -- Added *Local methods to cut down on object creation - JS + */ + + +/** + * Vector3f defines a Vector for a three float value tuple. + * Vector3f can represent any three dimensional value, such as a + * vertex, a normal, etc. Utility methods are also included to aid in + * mathematical calculations. + * + * @author Mark Powell + * @author Joshua Slack + */ +public final class Vector3f implements Cloneable, java.io.Serializable { + + static final long serialVersionUID = 1; + + private static final Logger logger = Logger.getLogger(Vector3f.class.getName()); + + public final static Vector3f ZERO = new Vector3f(0, 0, 0); + public final static Vector3f NAN = new Vector3f(Float.NaN, Float.NaN, Float.NaN); + public final static Vector3f UNIT_X = new Vector3f(1, 0, 0); + public final static Vector3f UNIT_Y = new Vector3f(0, 1, 0); + public final static Vector3f UNIT_X_Y = new Vector3f(1, 1, 0).normalizeLocal(); + public final static Vector3f UNIT_Z = new Vector3f(0, 0, 1); + public final static Vector3f UNIT_X_Z = new Vector3f(1, 0, 1).normalizeLocal(); + public final static Vector3f UNIT_Y_Z = new Vector3f(0, 1, 1).normalizeLocal(); + public final static Vector3f UNIT_X_Y_Z = new Vector3f(1, 1, 1).normalizeLocal(); + + + public final static Vector3f NEGATIVE_UNIT_X = new Vector3f(-1, 0, 0); + public final static Vector3f NEGATIVE_UNIT_Y = new Vector3f(0, -1, 0); + public final static Vector3f NEGATIVE_UNIT_Z = new Vector3f(0, 0, -1); + + public final static Vector3f UNIT_XYZ = new Vector3f(1, 1, 1); + public final static Vector3f POSITIVE_INFINITY = new Vector3f( + Float.POSITIVE_INFINITY, + Float.POSITIVE_INFINITY, + Float.POSITIVE_INFINITY + ); + public final static Vector3f NEGATIVE_INFINITY = new Vector3f( + Float.NEGATIVE_INFINITY, + Float.NEGATIVE_INFINITY, + Float.NEGATIVE_INFINITY + ); + + public final static Vector3f UNIT_PX_PY_PZ = UNIT_X_Y_Z; + public final static Vector3f UNIT_NX_PY_PZ = new Vector3f(-1, 1, 1).normalizeLocal(); + public final static Vector3f UNIT_PX_NY_PZ = new Vector3f(1, -1, 1).normalizeLocal(); + public final static Vector3f UNIT_NX_NY_PZ = new Vector3f(-1, -1, 1).normalizeLocal(); + public final static Vector3f UNIT_PX_PY_NZ = new Vector3f(1, 1, -1).normalizeLocal(); + public final static Vector3f UNIT_NX_PY_NZ = new Vector3f(-1, 1, -1).normalizeLocal(); + public final static Vector3f UNIT_PX_NY_NZ = new Vector3f(1, -1, -1).normalizeLocal(); + public final static Vector3f UNIT_NX_NY_NZ = new Vector3f(-1, -1, -1).normalizeLocal(); + + public final static Vector3f UNIT_PX_PY_0 = UNIT_X_Y; + public final static Vector3f UNIT_PX_NY_0 = new Vector3f(1, -1, 0).normalizeLocal(); + public final static Vector3f UNIT_NX_PY_0 = new Vector3f(-1, 1, 0).normalizeLocal(); + public final static Vector3f UNIT_NX_NY_0 = new Vector3f(-1, -1, 0).normalizeLocal(); + + public final static Vector3f UNIT_PX_0_PZ = UNIT_X_Z; + public final static Vector3f UNIT_PX_0_NZ = new Vector3f(1, 0, -1).normalizeLocal(); + public final static Vector3f UNIT_NX_0_PZ = new Vector3f(-1, 0, 1).normalizeLocal(); + public final static Vector3f UNIT_NX_0_NZ = new Vector3f(-1, 0, -1).normalizeLocal(); + + public final static Vector3f UNIT_0_PY_PZ = UNIT_Y_Z; + public final static Vector3f UNIT_0_PY_NZ = new Vector3f(0, 1, -1).normalizeLocal(); + public final static Vector3f UNIT_0_NY_PZ = new Vector3f(0, -1, 1).normalizeLocal(); + public final static Vector3f UNIT_0_NY_NZ = new Vector3f(0, -1, -1).normalizeLocal(); + + + /** + * the x value of the vector. + */ + public float x; + + /** + * the y value of the vector. + */ + public float y; + + /** + * the z value of the vector. + */ + public float z; + + /** + * Constructor instantiates a new Vector3f with default values + * of (0,0,0). + * + */ + public Vector3f() { + x = y = z = 0; + } + + /** + * Constructor instantiates a new Vector3f with provides + * values. + * + * @param x the x value of the vector. + * @param y the y value of the vector. + * @param z the z value of the vector. + */ + public Vector3f(float x, float y, float z) { + this.x = x; + this.y = y; + this.z = z; + } + + /** + * Constructor instantiates a new Vector3f that is a copy of + * the provided vector + * + * @param copy The Vector3f to copy + */ + public Vector3f(Vector3f copy) { + this.set(copy); + } + + public Vector3f(Vector3d copy) { + this.set((float) copy.x, (float) copy.y, (float) copy.z); + } + + /** + * set sets the x,y,z values of the vector based on passed + * parameters. + * + * @param x the x value of the vector. + * @param y the y value of the vector. + * @param z the z value of the vector. + * @return this vector + */ + public Vector3f set(float x, float y, float z) { + this.x = x; + this.y = y; + this.z = z; + return this; + } + + /** + * set sets the x,y,z values of the vector by copying the + * supplied vector. + * + * @param vect the vector to copy. + * @return this vector + */ + public Vector3f set(Vector3f vect) { + this.x = vect.x; + this.y = vect.y; + this.z = vect.z; + return this; + } + + /** + * + * add adds a provided vector to this vector creating a + * resultant vector which is returned. If the provided vector is null, null + * is returned. + * + * @param vec the vector to add to this. + * @return the resultant vector. + */ + public Vector3f add(Vector3f vec) { + if (null == vec) { + logger.warning("Provided vector is null, null returned."); + return null; + } + return new Vector3f(x + vec.x, y + vec.y, z + vec.z); + } + + /** + * + * add adds the values of a provided vector storing the values + * in the supplied vector. + * + * @param vec the vector to add to this + * @param result the vector to store the result in + * @return result returns the supplied result vector. + */ + public Vector3f add(Vector3f vec, Vector3f result) { + result.x = x + vec.x; + result.y = y + vec.y; + result.z = z + vec.z; + return result; + } + + /** + * addLocal adds a provided vector to this vector internally, + * and returns a handle to this vector for easy chaining of calls. If the + * provided vector is null, null is returned. + * + * @param vec the vector to add to this vector. + * @return this + */ + public Vector3f addLocal(Vector3f vec) { + if (null == vec) { + logger.warning("Provided vector is null, null returned."); + return null; + } + x += vec.x; + y += vec.y; + z += vec.z; + return this; + } + + /** + * + * add adds the provided values to this vector, creating a new + * vector that is then returned. + * + * @param addX the x value to add. + * @param addY the y value to add. + * @param addZ the z value to add. + * @return the result vector. + */ + public Vector3f add(float addX, float addY, float addZ) { + return new Vector3f(x + addX, y + addY, z + addZ); + } + + /** + * + * add adds the provided values to this vector, creating a new + * vector that is then returned. + * + * @param addX the x value to add. + * @param addY the y value to add. + * @param addZ the z value to add. + * @param store the vector object to store the result in. if null, a new one + * is created. + * @return the result vector. + */ + public Vector3f add(float addX, float addY, float addZ, Vector3f store) { + if (store == null) { + return new Vector3f(x + addX, y + addY, z + addZ); + } else { + return store.set(x + addX, y + addY, z + addZ); + } + } + + /** + * addLocal adds the provided values to this vector internally, + * and returns a handle to this vector for easy chaining of calls. + * + * @param addX value to add to x + * @param addY value to add to y + * @param addZ value to add to z + * @return this + */ + public Vector3f addLocal(float addX, float addY, float addZ) { + x += addX; + y += addY; + z += addZ; + return this; + } + + /** + * + * scaleAdd multiplies this vector by a scalar then adds the + * given Vector3f. + * + * @param scalar the value to multiply this vector by. + * @param add the value to add + */ + public Vector3f scaleAdd(float scalar, Vector3f add) { + x = x * scalar + add.x; + y = y * scalar + add.y; + z = z * scalar + add.z; + return this; + } + + /** + * + * scaleAdd multiplies the given vector by a scalar then adds + * the given vector. + * + * @param scalar the value to multiply this vector by. + * @param mult the value to multiply the scalar by + * @param add the value to add + */ + public Vector3f scaleAdd(float scalar, Vector3f mult, Vector3f add) { + this.x = mult.x * scalar + add.x; + this.y = mult.y * scalar + add.y; + this.z = mult.z * scalar + add.z; + return this; + } + + /** + * + * dot calculates the dot product of this vector with a + * provided vector. If the provided vector is null, 0 is returned. + * + * @param vec the vector to dot with this vector. + * @return the resultant dot product of this vector and a given vector. + */ + public float dot(Vector3f vec) { + if (null == vec) { + logger.warning("Provided vector is null, 0 returned."); + return 0; + } + return x * vec.x + y * vec.y + z * vec.z; + } + + public float dot(float vx, float vy, float vz) { + return x * vx + y * vy + z * vz; + } + + /** + * cross calculates the cross product of this vector with a + * parameter vector v. + * + * @param v the vector to take the cross product of with this. + * @return the cross product vector. + */ + public Vector3f cross(Vector3f v) { + return cross(v, null); + } + + /** + * cross calculates the cross product of this vector with a + * parameter vector v. The result is stored in result + * + * @param v the vector to take the cross product of with this. + * @param result the vector to store the cross product result. + * @return result, after recieving the cross product vector. + */ + public Vector3f cross(Vector3f v, Vector3f result) { + return cross(v.x, v.y, v.z, result); + } + + /** + * cross calculates the cross product of this vector with a + * parameter vector v. The result is stored in result + * + * @param otherX x component of the vector to take the cross product of with + * this. + * @param otherY y component of the vector to take the cross product of with + * this. + * @param otherZ z component of the vector to take the cross product of with + * this. + * @param result the vector to store the cross product result. + * @return result, after recieving the cross product vector. + */ + public Vector3f cross(float otherX, float otherY, float otherZ, Vector3f result) { + if (result == null) + result = new Vector3f(); + float resX = ((y * otherZ) - (z * otherY)); + float resY = ((z * otherX) - (x * otherZ)); + float resZ = ((x * otherY) - (y * otherX)); + result.set(resX, resY, resZ); + return result; + } + + /** + * crossLocal calculates the cross product of this vector with + * a parameter vector v. + * + * @param v the vector to take the cross product of with this. + * @return this. + */ + public Vector3f crossLocal(Vector3f v) { + return crossLocal(v.x, v.y, v.z); + } + + /** + * crossLocal calculates the cross product of this vector with + * a parameter vector v. + * + * @param otherX x component of the vector to take the cross product of with + * this. + * @param otherY y component of the vector to take the cross product of with + * this. + * @param otherZ z component of the vector to take the cross product of with + * this. + * @return this. + */ + public Vector3f crossLocal(float otherX, float otherY, float otherZ) { + float tempx = (y * otherZ) - (z * otherY); + float tempy = (z * otherX) - (x * otherZ); + z = (x * otherY) - (y * otherX); + x = tempx; + y = tempy; + return this; + } + + /** + * Projects this vector onto another vector + * + * @param other The vector to project this vector onto + * @return A new vector with the projection result + */ + public Vector3f project(Vector3f other) { + float n = this.dot(other); // A . B + float d = other.lengthSquared(); // |B|^2 + return new Vector3f(other).normalizeLocal().multLocal(n / d); + } + + /** + * Projects this vector onto another vector, stores the result in this + * vector + * + * @param other The vector to project this vector onto + * @return This Vector3f, set to the projection result + */ + public Vector3f projectLocal(Vector3f other) { + float n = this.dot(other); // A . B + float d = other.lengthSquared(); // |B|^2 + return set(other).normalizeLocal().multLocal(n / d); + } + + /** + * Returns true if this vector is a unit vector (length() ~= 1), returns + * false otherwise. + * + * @return true if this vector is a unit vector (length() ~= 1), or false + * otherwise. + */ + public boolean isUnitVector() { + float len = length(); + return 0.99f < len && len < 1.01f; + } + + /** + * length calculates the magnitude of this vector. + * + * @return the length or magnitude of the vector. + */ + public float length() { + return FastMath.sqrt(lengthSquared()); + } + + /** + * lengthSquared calculates the squared value of the magnitude + * of the vector. + * + * @return the magnitude squared of the vector. + */ + public float lengthSquared() { + return x * x + y * y + z * z; + } + + /** + * distanceSquared calculates the distance squared between this + * vector and vector v. + * + * @param v the second vector to determine the distance squared. + * @return the distance squared between the two vectors. + */ + public float distanceSquared(Vector3f v) { + double dx = x - v.x; + double dy = y - v.y; + double dz = z - v.z; + return (float) (dx * dx + dy * dy + dz * dz); + } + + /** + * distance calculates the distance between this vector and + * vector v. + * + * @param v the second vector to determine the distance. + * @return the distance between the two vectors. + */ + public float distance(Vector3f v) { + return FastMath.sqrt(distanceSquared(v)); + } + + /** + * + * mult multiplies this vector by a scalar. The resultant + * vector is returned. + * + * @param scalar the value to multiply this vector by. + * @return the new vector. + */ + public Vector3f mult(float scalar) { + return new Vector3f(x * scalar, y * scalar, z * scalar); + } + + /** + * + * mult multiplies this vector by a scalar. The resultant + * vector is supplied as the second parameter and returned. + * + * @param scalar the scalar to multiply this vector by. + * @param product the product to store the result in. + * @return product + */ + public Vector3f mult(float scalar, Vector3f product) { + if (null == product) { + product = new Vector3f(); + } + + product.x = x * scalar; + product.y = y * scalar; + product.z = z * scalar; + return product; + } + + /** + * multLocal multiplies this vector by a scalar internally, and + * returns a handle to this vector for easy chaining of calls. + * + * @param scalar the value to multiply this vector by. + * @return this + */ + public Vector3f multLocal(float scalar) { + x *= scalar; + y *= scalar; + z *= scalar; + return this; + } + + /** + * multLocal multiplies a provided vector to this vector + * internally, and returns a handle to this vector for easy chaining of + * calls. If the provided vector is null, null is returned. + * + * @param vec the vector to mult to this vector. + * @return this + */ + public Vector3f multLocal(Vector3f vec) { + if (null == vec) { + logger.warning("Provided vector is null, null returned."); + return null; + } + x *= vec.x; + y *= vec.y; + z *= vec.z; + return this; + } + + /** + * multLocal multiplies this vector by 3 scalars internally, + * and returns a handle to this vector for easy chaining of calls. + * + * @param x + * @param y + * @param z + * @return this + */ + public Vector3f multLocal(float x, float y, float z) { + this.x *= x; + this.y *= y; + this.z *= z; + return this; + } + + /** + * multLocal multiplies a provided vector to this vector + * internally, and returns a handle to this vector for easy chaining of + * calls. If the provided vector is null, null is returned. + * + * @param vec the vector to mult to this vector. + * @return this + */ + public Vector3f mult(Vector3f vec) { + if (null == vec) { + logger.warning("Provided vector is null, null returned."); + return null; + } + return mult(vec, null); + } + + /** + * multLocal multiplies a provided vector to this vector + * internally, and returns a handle to this vector for easy chaining of + * calls. If the provided vector is null, null is returned. + * + * @param vec the vector to mult to this vector. + * @param store result vector (null to create a new vector) + * @return this + */ + public Vector3f mult(Vector3f vec, Vector3f store) { + if (null == vec) { + logger.warning("Provided vector is null, null returned."); + return null; + } + if (store == null) + store = new Vector3f(); + return store.set(x * vec.x, y * vec.y, z * vec.z); + } + + /** + * divide divides the values of this vector by a scalar and + * returns the result. The values of this vector remain untouched. + * + * @param scalar the value to divide this vectors attributes by. + * @return the result Vector. + */ + public Vector3f divide(float scalar) { + scalar = 1f / scalar; + return new Vector3f(x * scalar, y * scalar, z * scalar); + } + + public Vector3f divide(float scalar, Vector3f store) { + scalar = 1f / scalar; + return store.set(x * scalar, y * scalar, z * scalar); + } + + /** + * divideLocal divides this vector by a scalar internally, and + * returns a handle to this vector for easy chaining of calls. Dividing by + * zero will result in an exception. + * + * @param scalar the value to divides this vector by. + * @return this + */ + public Vector3f divideLocal(float scalar) { + scalar = 1f / scalar; + x *= scalar; + y *= scalar; + z *= scalar; + return this; + } + + /** + * divide divides the values of this vector by a scalar and + * returns the result. The values of this vector remain untouched. + * + * @param scalar the value to divide this vectors attributes by. + * @return the result Vector. + */ + public Vector3f divide(Vector3f scalar) { + return new Vector3f(x / scalar.x, y / scalar.y, z / scalar.z); + } + + /** + * divideLocal divides this vector by a scalar internally, and + * returns a handle to this vector for easy chaining of calls. Dividing by + * zero will result in an exception. + * + * @param scalar the value to divides this vector by. + * @return this + */ + public Vector3f divideLocal(Vector3f scalar) { + x /= scalar.x; + y /= scalar.y; + z /= scalar.z; + return this; + } + + /** + * + * negate returns the negative of this vector. All values are + * negated and set to a new vector. + * + * @return the negated vector. + */ + public Vector3f negate() { + return new Vector3f(-x, -y, -z); + } + + /** + * + * negateLocal negates the internal values of this vector. + * + * @return this. + */ + public Vector3f negateLocal() { + x = -x; + y = -y; + z = -z; + return this; + } + + /** + * + * subtract subtracts the values of a given vector from those + * of this vector creating a new vector object. If the provided vector is + * null, null is returned. + * + * @param vec the vector to subtract from this vector. + * @return the result vector. + */ + public Vector3f subtract(Vector3f vec) { + return new Vector3f(x - vec.x, y - vec.y, z - vec.z); + } + + /** + * subtractLocal subtracts a provided vector to this vector + * internally, and returns a handle to this vector for easy chaining of + * calls. If the provided vector is null, null is returned. + * + * @param vec the vector to subtract + * @return this + */ + public Vector3f subtractLocal(Vector3f vec) { + if (null == vec) { + logger.warning("Provided vector is null, null returned."); + return null; + } + x -= vec.x; + y -= vec.y; + z -= vec.z; + return this; + } + + /** + * + * subtract + * + * @param vec the vector to subtract from this + * @param result the vector to store the result in + * @return result + */ + public Vector3f subtract(Vector3f vec, Vector3f result) { + if (result == null) { + result = new Vector3f(); + } + result.x = x - vec.x; + result.y = y - vec.y; + result.z = z - vec.z; + return result; + } + + /** + * + * subtract subtracts the provided values from this vector, + * creating a new vector that is then returned. + * + * @param subtractX the x value to subtract. + * @param subtractY the y value to subtract. + * @param subtractZ the z value to subtract. + * @return the result vector. + */ + public Vector3f subtract(float subtractX, float subtractY, float subtractZ) { + return new Vector3f(x - subtractX, y - subtractY, z - subtractZ); + } + + /** + * subtractLocal subtracts the provided values from this vector + * internally, and returns a handle to this vector for easy chaining of + * calls. + * + * @param subtractX the x value to subtract. + * @param subtractY the y value to subtract. + * @param subtractZ the z value to subtract. + * @return this + */ + public Vector3f subtractLocal(float subtractX, float subtractY, float subtractZ) { + x -= subtractX; + y -= subtractY; + z -= subtractZ; + return this; + } + + /** + * normalize returns the unit vector of this vector. + * + * @return unit vector of this vector. + */ + public Vector3f normalize() { + float length = x * x + y * y + z * z; + if (length != 1f && length != 0f) { + length = 1.0f / FastMath.sqrt(length); + return new Vector3f(x * length, y * length, z * length); + } + return clone(); + } + + public Vector3f normalize(Vector3f store) { + float length = x * x + y * y + z * z; + if (length != 1f && length != 0f) { + length = 1.0f / FastMath.sqrt(length); + return store.set(x * length, y * length, z * length); + } else { + return store.set(this); + } + } + + /** + * normalizeLocal makes this vector into a unit vector of + * itself. + * + * @return this + */ + public Vector3f normalizeLocal() { + // NOTE: this implementation is more optimized + // than the old jme normalize as this method + // is commonly used. + float length = x * x + y * y + z * z; + if (length != 1f && length != 0f) { + length = 1.0f / FastMath.sqrt(length); + x *= length; + y *= length; + z *= length; + } + return this; + } + + /** + * maxLocal computes the maximum value for each component in + * this and other vector. The result is stored in this vector. + * + * @param other + */ + public Vector3f maxLocal(Vector3f other) { + x = other.x > x ? other.x : x; + y = other.y > y ? other.y : y; + z = other.z > z ? other.z : z; + return this; + } + + /** + * minLocal computes the minimum value for each component in + * this and other vector. The result is stored in this vector. + * + * @param other + */ + public Vector3f minLocal(Vector3f other) { + x = other.x < x ? other.x : x; + y = other.y < y ? other.y : y; + z = other.z < z ? other.z : z; + return this; + } + + /** + * zero resets this vector's data to zero internally. + */ + public Vector3f zero() { + x = y = z = 0; + return this; + } + + /** + * angleBetween returns (in radians) the angle between two + * vectors. It is assumed that both this vector and the given vector are + * unit vectors (iow, normalized). + * + * @param otherVector a unit vector to find the angle against + * @return the angle in radians. + */ + public float angleBetween(Vector3f otherVector) { + float dotProduct = dot(otherVector); + float angle = FastMath.acos(dotProduct); + return angle; + } + + /** + * Sets this vector to the interpolation by changeAmnt from this to the + * finalVec this=(1-changeAmnt)*this + changeAmnt * finalVec + * + * @param finalVec The final vector to interpolate towards + * @param changeAmnt An amount between 0.0 - 1.0 representing a precentage + * change from this towards finalVec + */ + public Vector3f interpolate(Vector3f finalVec, float changeAmnt) { + this.x = (1 - changeAmnt) * this.x + changeAmnt * finalVec.x; + this.y = (1 - changeAmnt) * this.y + changeAmnt * finalVec.y; + this.z = (1 - changeAmnt) * this.z + changeAmnt * finalVec.z; + return this; + } + + /** + * Sets this vector to the interpolation by changeAmnt from beginVec to + * finalVec this=(1-changeAmnt)*beginVec + changeAmnt * finalVec + * + * @param beginVec the beging vector (changeAmnt=0) + * @param finalVec The final vector to interpolate towards + * @param changeAmnt An amount between 0.0 - 1.0 representing a precentage + * change from beginVec towards finalVec + */ + public Vector3f interpolate(Vector3f beginVec, Vector3f finalVec, float changeAmnt) { + this.x = (1 - changeAmnt) * beginVec.x + changeAmnt * finalVec.x; + this.y = (1 - changeAmnt) * beginVec.y + changeAmnt * finalVec.y; + this.z = (1 - changeAmnt) * beginVec.z + changeAmnt * finalVec.z; + return this; + } + + /** + * Check a vector... if it is null or its floats are NaN or infinite, return + * false. Else return true. + * + * @param vector the vector to check + * @return true or false as stated above. + */ + public static boolean isValidVector(Vector3f vector) { + if (vector == null) + return false; + if (Float.isNaN(vector.x) || Float.isNaN(vector.y) || Float.isNaN(vector.z)) + return false; + if (Float.isInfinite(vector.x) || Float.isInfinite(vector.y) || Float.isInfinite(vector.z)) + return false; + return true; + } + + public static void generateOrthonormalBasis(Vector3f u, Vector3f v, Vector3f w) { + w.normalizeLocal(); + generateComplementBasis(u, v, w); + } + + public static void generateComplementBasis(Vector3f u, Vector3f v, Vector3f w) { + float fInvLength; + + if (FastMath.abs(w.x) >= FastMath.abs(w.y)) { + // w.x or w.z is the largest magnitude component, swap them + fInvLength = FastMath.invSqrt(w.x * w.x + w.z * w.z); + u.x = -w.z * fInvLength; + u.y = 0.0f; + u.z = +w.x * fInvLength; + v.x = w.y * u.z; + v.y = w.z * u.x - w.x * u.z; + v.z = -w.y * u.x; + } else { + // w.y or w.z is the largest magnitude component, swap them + fInvLength = FastMath.invSqrt(w.y * w.y + w.z * w.z); + u.x = 0.0f; + u.y = +w.z * fInvLength; + u.z = -w.y * fInvLength; + v.x = w.y * u.z - w.z * u.y; + v.y = -w.x * u.z; + v.z = w.x * u.y; + } + } + + @Override + public Vector3f clone() { + try { + return (Vector3f) super.clone(); + } catch (CloneNotSupportedException e) { + throw new AssertionError(); // can not happen + } + } + + /** + * Saves this Vector3f into the given float[] object. + * + * @param floats The float[] to take this Vector3f. If null, a new float[3] + * is created. + * @return The array, with X, Y, Z float values in that order + */ + public float[] toArray(float[] floats) { + if (floats == null) { + floats = new float[3]; + } + floats[0] = x; + floats[1] = y; + floats[2] = z; + return floats; + } + + /** + * are these two vectors the same? they are is they both have the same x,y, + * and z values. + * + * @param o the object to compare for equality + * @return true if they are equal + */ + @Override + public boolean equals(Object o) { + if (!(o instanceof Vector3f)) + return false; + if (this == o) + return true; + Vector3f comp = (Vector3f) o; + if (Float.compare(x, comp.x) != 0) + return false; + if (Float.compare(y, comp.y) != 0) + return false; + if (Float.compare(z, comp.z) != 0) + return false; + return true; + } + + /** + * hashCode returns a unique code for this vector object based + * on it's values. If two vectors are logically equivalent, they will return + * the same hash code value. + * + * @return the hash code value of this vector. + */ + @Override + public int hashCode() { + int hash = 37; + hash += 37 * hash + Float.floatToIntBits(x); + hash += 37 * hash + Float.floatToIntBits(y); + hash += 37 * hash + Float.floatToIntBits(z); + return hash; + } + + /** + * toString returns the string representation of this vector. + * The format is: + * + * org.jme.math.Vector3f [X=XX.XXXX, Y=YY.YYYY, Z=ZZ.ZZZZ] + * + * @return the string representation of this vector. + */ + @Override + public String toString() { + return "(" + x + ", " + y + ", " + z + ")"; + } + + public float getX() { + return x; + } + + public Vector3f setX(float x) { + this.x = x; + return this; + } + + public float getY() { + return y; + } + + public Vector3f setY(float y) { + this.y = y; + return this; + } + + public float getZ() { + return z; + } + + public Vector3f setZ(float z) { + this.z = z; + return this; + } + + /** + * @param index + * @return x value if index == 0, y value if index == 1 or z value if index + * == 2 + * @throws IllegalArgumentException if index is not one of 0, 1, 2. + */ + public float get(int index) { + switch (index) { + case 0: + return x; + case 1: + return y; + case 2: + return z; + } + throw new IllegalArgumentException("index must be either 0, 1 or 2"); + } + + /** + * @param index which field index in this vector to set. + * @param value to set to one of x, y or z. + * @throws IllegalArgumentException if index is not one of 0, 1, 2. + */ + public void set(int index, float value) { + switch (index) { + case 0: + x = value; + return; + case 1: + y = value; + return; + case 2: + z = value; + return; + } + throw new IllegalArgumentException("index must be either 0, 1 or 2"); + } + + public static float angleBetweenVectors(Vector2f vec1, Vector2f vec2) { + return (float) Math + .atan2(vec1.x * vec2.y - vec1.y * vec2.x, vec1.x * vec2.x + vec1.y * vec2.y); + } +} diff --git a/java/com/jme3/math/Vector4f.java b/java/com/jme3/math/Vector4f.java new file mode 100644 index 000000000..20f101639 --- /dev/null +++ b/java/com/jme3/math/Vector4f.java @@ -0,0 +1,971 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.math; + +import java.util.logging.Logger; + + +/** + * Vector4f defines a Vector for a four float value tuple. + * Vector4f can represent any four dimensional value, such as a + * vertex, a normal, etc. Utility methods are also included to aid in + * mathematical calculations. + * + * @author Maarten Steur + */ +public final class Vector4f implements Cloneable, java.io.Serializable { + + static final long serialVersionUID = 1; + + private static final Logger logger = Logger.getLogger(Vector4f.class.getName()); + + public final static Vector4f ZERO = new Vector4f(0, 0, 0, 0); + public final static Vector4f NAN = new Vector4f(Float.NaN, Float.NaN, Float.NaN, Float.NaN); + public final static Vector4f UNIT_X = new Vector4f(1, 0, 0, 0); + public final static Vector4f UNIT_Y = new Vector4f(0, 1, 0, 0); + public final static Vector4f UNIT_Z = new Vector4f(0, 0, 1, 0); + public final static Vector4f UNIT_W = new Vector4f(0, 0, 0, 1); + public final static Vector4f UNIT_XYZW = new Vector4f(1, 1, 1, 1); + public final static Vector4f POSITIVE_INFINITY = new Vector4f( + Float.POSITIVE_INFINITY, + Float.POSITIVE_INFINITY, + Float.POSITIVE_INFINITY, + Float.POSITIVE_INFINITY + ); + public final static Vector4f NEGATIVE_INFINITY = new Vector4f( + Float.NEGATIVE_INFINITY, + Float.NEGATIVE_INFINITY, + Float.NEGATIVE_INFINITY, + Float.NEGATIVE_INFINITY + ); + + /** + * the x value of the vector. + */ + public float x; + + /** + * the y value of the vector. + */ + public float y; + + /** + * the z value of the vector. + */ + public float z; + + /** + * the w value of the vector. + */ + public float w; + + /** + * Constructor instantiates a new Vector3f with default values + * of (0,0,0). + * + */ + public Vector4f() { + x = y = z = w = 0; + } + + /** + * Constructor instantiates a new Vector4f with provides + * values. + * + * @param x the x value of the vector. + * @param y the y value of the vector. + * @param z the z value of the vector. + * @param w the w value of the vector. + */ + public Vector4f(float x, float y, float z, float w) { + this.x = x; + this.y = y; + this.z = z; + this.w = w; + } + + /** + * Constructor instantiates a new Vector3f that is a copy of + * the provided vector + * + * @param copy The Vector3f to copy + */ + public Vector4f(Vector4f copy) { + this.set(copy); + } + + /** + * set sets the x,y,z,w values of the vector based on passed + * parameters. + * + * @param x the x value of the vector. + * @param y the y value of the vector. + * @param z the z value of the vector. + * @param w the w value of the vector. + * @return this vector + */ + public Vector4f set(float x, float y, float z, float w) { + this.x = x; + this.y = y; + this.z = z; + this.w = w; + return this; + } + + /** + * set sets the x,y,z values of the vector by copying the + * supplied vector. + * + * @param vect the vector to copy. + * @return this vector + */ + public Vector4f set(Vector4f vect) { + this.x = vect.x; + this.y = vect.y; + this.z = vect.z; + this.w = vect.w; + return this; + } + + /** + * + * add adds a provided vector to this vector creating a + * resultant vector which is returned. If the provided vector is null, null + * is returned. + * + * @param vec the vector to add to this. + * @return the resultant vector. + */ + public Vector4f add(Vector4f vec) { + if (null == vec) { + logger.warning("Provided vector is null, null returned."); + return null; + } + return new Vector4f(x + vec.x, y + vec.y, z + vec.z, w + vec.w); + } + + /** + * + * add adds the values of a provided vector storing the values + * in the supplied vector. + * + * @param vec the vector to add to this + * @param result the vector to store the result in + * @return result returns the supplied result vector. + */ + public Vector4f add(Vector4f vec, Vector4f result) { + result.x = x + vec.x; + result.y = y + vec.y; + result.z = z + vec.z; + result.w = w + vec.w; + return result; + } + + /** + * addLocal adds a provided vector to this vector internally, + * and returns a handle to this vector for easy chaining of calls. If the + * provided vector is null, null is returned. + * + * @param vec the vector to add to this vector. + * @return this + */ + public Vector4f addLocal(Vector4f vec) { + if (null == vec) { + logger.warning("Provided vector is null, null returned."); + return null; + } + x += vec.x; + y += vec.y; + z += vec.z; + w += vec.w; + return this; + } + + /** + * + * add adds the provided values to this vector, creating a new + * vector that is then returned. + * + * @param addX the x value to add. + * @param addY the y value to add. + * @param addZ the z value to add. + * @return the result vector. + */ + public Vector4f add(float addX, float addY, float addZ, float addW) { + return new Vector4f(x + addX, y + addY, z + addZ, w + addW); + } + + /** + * addLocal adds the provided values to this vector internally, + * and returns a handle to this vector for easy chaining of calls. + * + * @param addX value to add to x + * @param addY value to add to y + * @param addZ value to add to z + * @return this + */ + public Vector4f addLocal(float addX, float addY, float addZ, float addW) { + x += addX; + y += addY; + z += addZ; + w += addW; + return this; + } + + /** + * + * scaleAdd multiplies this vector by a scalar then adds the + * given Vector3f. + * + * @param scalar the value to multiply this vector by. + * @param add the value to add + */ + public Vector4f scaleAdd(float scalar, Vector4f add) { + x = x * scalar + add.x; + y = y * scalar + add.y; + z = z * scalar + add.z; + w = w * scalar + add.w; + return this; + } + + /** + * + * scaleAdd multiplies the given vector by a scalar then adds + * the given vector. + * + * @param scalar the value to multiply this vector by. + * @param mult the value to multiply the scalar by + * @param add the value to add + */ + public Vector4f scaleAdd(float scalar, Vector4f mult, Vector4f add) { + this.x = mult.x * scalar + add.x; + this.y = mult.y * scalar + add.y; + this.z = mult.z * scalar + add.z; + this.w = mult.w * scalar + add.w; + return this; + } + + /** + * + * dot calculates the dot product of this vector with a + * provided vector. If the provided vector is null, 0 is returned. + * + * @param vec the vector to dot with this vector. + * @return the resultant dot product of this vector and a given vector. + */ + public float dot(Vector4f vec) { + if (null == vec) { + logger.warning("Provided vector is null, 0 returned."); + return 0; + } + return x * vec.x + y * vec.y + z * vec.z + w * vec.w; + } + + public Vector4f project(Vector4f other) { + float n = this.dot(other); // A . B + float d = other.lengthSquared(); // |B|^2 + return new Vector4f(other).normalizeLocal().multLocal(n / d); + } + + /** + * Returns true if this vector is a unit vector (length() ~= 1), returns + * false otherwise. + * + * @return true if this vector is a unit vector (length() ~= 1), or false + * otherwise. + */ + public boolean isUnitVector() { + float len = length(); + return 0.99f < len && len < 1.01f; + } + + /** + * length calculates the magnitude of this vector. + * + * @return the length or magnitude of the vector. + */ + public float length() { + return FastMath.sqrt(lengthSquared()); + } + + /** + * lengthSquared calculates the squared value of the magnitude + * of the vector. + * + * @return the magnitude squared of the vector. + */ + public float lengthSquared() { + return x * x + y * y + z * z + w * w; + } + + /** + * distanceSquared calculates the distance squared between this + * vector and vector v. + * + * @param v the second vector to determine the distance squared. + * @return the distance squared between the two vectors. + */ + public float distanceSquared(Vector4f v) { + double dx = x - v.x; + double dy = y - v.y; + double dz = z - v.z; + double dw = w - v.w; + return (float) (dx * dx + dy * dy + dz * dz + dw * dw); + } + + /** + * distance calculates the distance between this vector and + * vector v. + * + * @param v the second vector to determine the distance. + * @return the distance between the two vectors. + */ + public float distance(Vector4f v) { + return FastMath.sqrt(distanceSquared(v)); + } + + /** + * + * mult multiplies this vector by a scalar. The resultant + * vector is returned. + * + * @param scalar the value to multiply this vector by. + * @return the new vector. + */ + public Vector4f mult(float scalar) { + return new Vector4f(x * scalar, y * scalar, z * scalar, w * scalar); + } + + /** + * + * mult multiplies this vector by a scalar. The resultant + * vector is supplied as the second parameter and returned. + * + * @param scalar the scalar to multiply this vector by. + * @param product the product to store the result in. + * @return product + */ + public Vector4f mult(float scalar, Vector4f product) { + if (null == product) { + product = new Vector4f(); + } + + product.x = x * scalar; + product.y = y * scalar; + product.z = z * scalar; + product.w = w * scalar; + return product; + } + + /** + * multLocal multiplies this vector by a scalar internally, and + * returns a handle to this vector for easy chaining of calls. + * + * @param scalar the value to multiply this vector by. + * @return this + */ + public Vector4f multLocal(float scalar) { + x *= scalar; + y *= scalar; + z *= scalar; + w *= scalar; + return this; + } + + /** + * multLocal multiplies a provided vector to this vector + * internally, and returns a handle to this vector for easy chaining of + * calls. If the provided vector is null, null is returned. + * + * @param vec the vector to mult to this vector. + * @return this + */ + public Vector4f multLocal(Vector4f vec) { + if (null == vec) { + logger.warning("Provided vector is null, null returned."); + return null; + } + x *= vec.x; + y *= vec.y; + z *= vec.z; + w *= vec.w; + return this; + } + + /** + * multLocal multiplies this vector by 3 scalars internally, + * and returns a handle to this vector for easy chaining of calls. + * + * @param x + * @param y + * @param z + * @param w + * @return this + */ + public Vector4f multLocal(float x, float y, float z, float w) { + this.x *= x; + this.y *= y; + this.z *= z; + this.w *= w; + return this; + } + + /** + * multLocal multiplies a provided vector to this vector + * internally, and returns a handle to this vector for easy chaining of + * calls. If the provided vector is null, null is returned. + * + * @param vec the vector to mult to this vector. + * @return this + */ + public Vector4f mult(Vector4f vec) { + if (null == vec) { + logger.warning("Provided vector is null, null returned."); + return null; + } + return mult(vec, null); + } + + /** + * multLocal multiplies a provided vector to this vector + * internally, and returns a handle to this vector for easy chaining of + * calls. If the provided vector is null, null is returned. + * + * @param vec the vector to mult to this vector. + * @param store result vector (null to create a new vector) + * @return this + */ + public Vector4f mult(Vector4f vec, Vector4f store) { + if (null == vec) { + logger.warning("Provided vector is null, null returned."); + return null; + } + if (store == null) + store = new Vector4f(); + return store.set(x * vec.x, y * vec.y, z * vec.z, w * vec.w); + } + + /** + * divide divides the values of this vector by a scalar and + * returns the result. The values of this vector remain untouched. + * + * @param scalar the value to divide this vectors attributes by. + * @return the result Vector. + */ + public Vector4f divide(float scalar) { + scalar = 1f / scalar; + return new Vector4f(x * scalar, y * scalar, z * scalar, w * scalar); + } + + /** + * divideLocal divides this vector by a scalar internally, and + * returns a handle to this vector for easy chaining of calls. Dividing by + * zero will result in an exception. + * + * @param scalar the value to divides this vector by. + * @return this + */ + public Vector4f divideLocal(float scalar) { + scalar = 1f / scalar; + x *= scalar; + y *= scalar; + z *= scalar; + w *= scalar; + return this; + } + + /** + * divide divides the values of this vector by a scalar and + * returns the result. The values of this vector remain untouched. + * + * @param scalar the value to divide this vectors attributes by. + * @return the result Vector. + */ + public Vector4f divide(Vector4f scalar) { + return new Vector4f(x / scalar.x, y / scalar.y, z / scalar.z, w / scalar.w); + } + + /** + * divideLocal divides this vector by a scalar internally, and + * returns a handle to this vector for easy chaining of calls. Dividing by + * zero will result in an exception. + * + * @param scalar the value to divides this vector by. + * @return this + */ + public Vector4f divideLocal(Vector4f scalar) { + x /= scalar.x; + y /= scalar.y; + z /= scalar.z; + w /= scalar.w; + return this; + } + + /** + * + * negate returns the negative of this vector. All values are + * negated and set to a new vector. + * + * @return the negated vector. + */ + public Vector4f negate() { + return new Vector4f(-x, -y, -z, -w); + } + + /** + * + * negateLocal negates the internal values of this vector. + * + * @return this. + */ + public Vector4f negateLocal() { + x = -x; + y = -y; + z = -z; + w = -w; + return this; + } + + /** + * + * subtract subtracts the values of a given vector from those + * of this vector creating a new vector object. If the provided vector is + * null, null is returned. + * + * @param vec the vector to subtract from this vector. + * @return the result vector. + */ + public Vector4f subtract(Vector4f vec) { + return new Vector4f(x - vec.x, y - vec.y, z - vec.z, w - vec.w); + } + + /** + * subtractLocal subtracts a provided vector to this vector + * internally, and returns a handle to this vector for easy chaining of + * calls. If the provided vector is null, null is returned. + * + * @param vec the vector to subtract + * @return this + */ + public Vector4f subtractLocal(Vector4f vec) { + if (null == vec) { + logger.warning("Provided vector is null, null returned."); + return null; + } + x -= vec.x; + y -= vec.y; + z -= vec.z; + w -= vec.w; + return this; + } + + /** + * + * subtract + * + * @param vec the vector to subtract from this + * @param result the vector to store the result in + * @return result + */ + public Vector4f subtract(Vector4f vec, Vector4f result) { + if (result == null) { + result = new Vector4f(); + } + result.x = x - vec.x; + result.y = y - vec.y; + result.z = z - vec.z; + result.w = w - vec.w; + return result; + } + + /** + * + * subtract subtracts the provided values from this vector, + * creating a new vector that is then returned. + * + * @param subtractX the x value to subtract. + * @param subtractY the y value to subtract. + * @param subtractZ the z value to subtract. + * @param subtractW the w value to subtract. + * @return the result vector. + */ + public Vector4f subtract(float subtractX, float subtractY, float subtractZ, float subtractW) { + return new Vector4f(x - subtractX, y - subtractY, z - subtractZ, w - subtractW); + } + + /** + * subtractLocal subtracts the provided values from this vector + * internally, and returns a handle to this vector for easy chaining of + * calls. + * + * @param subtractX the x value to subtract. + * @param subtractY the y value to subtract. + * @param subtractZ the z value to subtract. + * @param subtractW the w value to subtract. + * @return this + */ + public Vector4f subtractLocal( + float subtractX, + float subtractY, + float subtractZ, + float subtractW + ) { + x -= subtractX; + y -= subtractY; + z -= subtractZ; + w -= subtractW; + return this; + } + + /** + * normalize returns the unit vector of this vector. + * + * @return unit vector of this vector. + */ + public Vector4f normalize() { +// float length = length(); +// if (length != 0) { +// return divide(length); +// } +// +// return divide(1); + float length = x * x + y * y + z * z + w * w; + if (length != 1f && length != 0f) { + length = 1.0f / FastMath.sqrt(length); + return new Vector4f(x * length, y * length, z * length, w * length); + } + return clone(); + } + + /** + * normalizeLocal makes this vector into a unit vector of + * itself. + * + * @return this. + */ + public Vector4f normalizeLocal() { + // NOTE: this implementation is more optimized + // than the old jme normalize as this method + // is commonly used. + float length = x * x + y * y + z * z + w * w; + if (length != 1f && length != 0f) { + length = 1.0f / FastMath.sqrt(length); + x *= length; + y *= length; + z *= length; + w *= length; + } + return this; + } + + /** + * maxLocal computes the maximum value for each component in + * this and other vector. The result is stored in this vector. + * + * @param other + */ + public Vector4f maxLocal(Vector4f other) { + x = other.x > x ? other.x : x; + y = other.y > y ? other.y : y; + z = other.z > z ? other.z : z; + w = other.w > w ? other.w : w; + return this; + } + + /** + * minLocal computes the minimum value for each component in + * this and other vector. The result is stored in this vector. + * + * @param other + */ + public Vector4f minLocal(Vector4f other) { + x = other.x < x ? other.x : x; + y = other.y < y ? other.y : y; + z = other.z < z ? other.z : z; + w = other.w < w ? other.w : w; + return this; + } + + /** + * zero resets this vector's data to zero internally. + */ + public Vector4f zero() { + x = y = z = w = 0; + return this; + } + + /** + * angleBetween returns (in radians) the angle between two + * vectors. It is assumed that both this vector and the given vector are + * unit vectors (iow, normalized). + * + * @param otherVector a unit vector to find the angle against + * @return the angle in radians. + */ + public float angleBetween(Vector4f otherVector) { + float dotProduct = dot(otherVector); + float angle = FastMath.acos(dotProduct); + return angle; + } + + /** + * Sets this vector to the interpolation by changeAmnt from this to the + * finalVec this=(1-changeAmnt)*this + changeAmnt * finalVec + * + * @param finalVec The final vector to interpolate towards + * @param changeAmnt An amount between 0.0 - 1.0 representing a precentage + * change from this towards finalVec + */ + public Vector4f interpolate(Vector4f finalVec, float changeAmnt) { + this.x = (1 - changeAmnt) * this.x + changeAmnt * finalVec.x; + this.y = (1 - changeAmnt) * this.y + changeAmnt * finalVec.y; + this.z = (1 - changeAmnt) * this.z + changeAmnt * finalVec.z; + this.w = (1 - changeAmnt) * this.w + changeAmnt * finalVec.w; + return this; + } + + /** + * Sets this vector to the interpolation by changeAmnt from beginVec to + * finalVec this=(1-changeAmnt)*beginVec + changeAmnt * finalVec + * + * @param beginVec the beging vector (changeAmnt=0) + * @param finalVec The final vector to interpolate towards + * @param changeAmnt An amount between 0.0 - 1.0 representing a precentage + * change from beginVec towards finalVec + */ + public Vector4f interpolate(Vector4f beginVec, Vector4f finalVec, float changeAmnt) { + this.x = (1 - changeAmnt) * beginVec.x + changeAmnt * finalVec.x; + this.y = (1 - changeAmnt) * beginVec.y + changeAmnt * finalVec.y; + this.z = (1 - changeAmnt) * beginVec.z + changeAmnt * finalVec.z; + this.w = (1 - changeAmnt) * beginVec.w + changeAmnt * finalVec.w; + return this; + } + + /** + * Check a vector... if it is null or its floats are NaN or infinite, return + * false. Else return true. + * + * @param vector the vector to check + * @return true or false as stated above. + */ + public static boolean isValidVector(Vector4f vector) { + if (vector == null) + return false; + if ( + Float.isNaN(vector.x) + || + Float.isNaN(vector.y) + || + Float.isNaN(vector.z) + || + Float.isNaN(vector.w) + ) + return false; + if ( + Float.isInfinite(vector.x) + || + Float.isInfinite(vector.y) + || + Float.isInfinite(vector.z) + || + Float.isInfinite(vector.w) + ) + return false; + return true; + } + + @Override + public Vector4f clone() { + try { + return (Vector4f) super.clone(); + } catch (CloneNotSupportedException e) { + throw new AssertionError(); // can not happen + } + } + + /** + * Saves this Vector3f into the given float[] object. + * + * @param floats The float[] to take this Vector3f. If null, a new float[3] + * is created. + * @return The array, with X, Y, Z float values in that order + */ + public float[] toArray(float[] floats) { + if (floats == null) { + floats = new float[4]; + } + floats[0] = x; + floats[1] = y; + floats[2] = z; + floats[3] = w; + return floats; + } + + /** + * are these two vectors the same? they are is they both have the same x,y, + * and z values. + * + * @param o the object to compare for equality + * @return true if they are equal + */ + @Override + public boolean equals(Object o) { + if (!(o instanceof Vector4f)) { + return false; + } + + if (this == o) { + return true; + } + + Vector4f comp = (Vector4f) o; + if (Float.compare(x, comp.x) != 0) + return false; + if (Float.compare(y, comp.y) != 0) + return false; + if (Float.compare(z, comp.z) != 0) + return false; + if (Float.compare(w, comp.w) != 0) + return false; + return true; + } + + /** + * hashCode returns a unique code for this vector object based + * on it's values. If two vectors are logically equivalent, they will return + * the same hash code value. + * + * @return the hash code value of this vector. + */ + @Override + public int hashCode() { + int hash = 37; + hash += 37 * hash + Float.floatToIntBits(x); + hash += 37 * hash + Float.floatToIntBits(y); + hash += 37 * hash + Float.floatToIntBits(z); + hash += 37 * hash + Float.floatToIntBits(w); + return hash; + } + + /** + * toString returns the string representation of this vector. + * The format is: + * + * org.jme.math.Vector3f [X=XX.XXXX, Y=YY.YYYY, Z=ZZ.ZZZZ, W=WW.WWWW] + * + * @return the string representation of this vector. + */ + @Override + public String toString() { + return "(" + x + ", " + y + ", " + z + ", " + w + ")"; + } + + public float getX() { + return x; + } + + public Vector4f setX(float x) { + this.x = x; + return this; + } + + public float getY() { + return y; + } + + public Vector4f setY(float y) { + this.y = y; + return this; + } + + public float getZ() { + return z; + } + + public Vector4f setZ(float z) { + this.z = z; + return this; + } + + public float getW() { + return w; + } + + public Vector4f setW(float w) { + this.w = w; + return this; + } + + /** + * @param index + * @return x value if index == 0, y value if index == 1 or z value if index + * == 2 + * @throws IllegalArgumentException if index is not one of 0, 1, 2. + */ + public float get(int index) { + switch (index) { + case 0: + return x; + case 1: + return y; + case 2: + return z; + case 3: + return w; + } + throw new IllegalArgumentException("index must be either 0, 1, 2 or 3"); + } + + /** + * @param index which field index in this vector to set. + * @param value to set to one of x, y, z or w. + * @throws IllegalArgumentException if index is not one of 0, 1, 2, 3. + */ + public void set(int index, float value) { + switch (index) { + case 0: + x = value; + return; + case 1: + y = value; + return; + case 2: + z = value; + return; + case 3: + w = value; + return; + } + throw new IllegalArgumentException("index must be either 0, 1, 2 or 3"); + } + +} diff --git a/java/com/jme3/system/NanoTimer.java b/java/com/jme3/system/NanoTimer.java new file mode 100644 index 000000000..b99dcf2dd --- /dev/null +++ b/java/com/jme3/system/NanoTimer.java @@ -0,0 +1,105 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.system; + +/** + * NanoTimer is a System.nanoTime implementation of + * Timer. This is primarily useful for headless applications + * running on a server. + * + * @author Matthew D. Hicks + */ +public class NanoTimer extends Timer { + + private static final long TIMER_RESOLUTION = 1000000000L; + private static final float INVERSE_TIMER_RESOLUTION = 1f / TIMER_RESOLUTION; + + private long startTime; + private long previousTime; + private float tpf; + private float fps; + private long currentTime; + + public NanoTimer() { + startTime = System.nanoTime(); + } + + /** + * Returns the time in seconds. The timer starts at 0.0 seconds. + * + * @return the current time in seconds + */ + + protected long getTimeInternal() { + return System.nanoTime() - startTime; + } + + @Override + public float getTimeInSeconds() { + return getTime() * INVERSE_TIMER_RESOLUTION; + } + + @Override + public long getTime() { + return currentTime; + } + + @Override + public long getResolution() { + return TIMER_RESOLUTION; + } + + @Override + public float getFrameRate() { + return fps; + } + + @Override + public float getTimePerFrame() { + return tpf; + } + + @Override + public void update() { + currentTime = getTimeInternal(); + tpf = (currentTime - previousTime) * (1.0f / TIMER_RESOLUTION); + fps = 1.0f / tpf; + previousTime = getTime(); + } + + @Override + public void reset() { + startTime = System.nanoTime(); + currentTime = getTimeInternal(); + previousTime = getTime(); + } +} diff --git a/java/com/jme3/system/Timer.java b/java/com/jme3/system/Timer.java new file mode 100644 index 000000000..ea0a61dfc --- /dev/null +++ b/java/com/jme3/system/Timer.java @@ -0,0 +1,93 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.system; + +/** + * Timer is the base class for a high resolution timer. It is + * created from getTimer("display system") + * + * @author Mark Powell + * @version $Id: Timer.java,v 1.18 2007/03/09 10:19:34 rherlitz Exp $ + */ +public abstract class Timer { + + /** + * Returns the current time in ticks. A tick is an arbitrary measure of time + * defined by the timer implementation. The number of ticks per second is + * given by getResolution(). The timer starts at 0 ticks. + * + * @return a long value representing the current time + */ + public abstract long getTime(); + + /** + * Returns the time in seconds. The timer starts at 0.0 seconds. + * + * @return the current time in seconds + */ + public float getTimeInSeconds() { + return getTime() / (float) getResolution(); + } + + /** + * Returns the resolution of the timer. + * + * @return the number of timer ticks per second + */ + public abstract long getResolution(); + + /** + * Returns the "calls per second". If this is called every frame, then it + * will return the "frames per second". + * + * @return The "calls per second". + */ + public abstract float getFrameRate(); + + /** + * Returns the time, in seconds, between the last call and the current one. + * + * @return Time between this call and the last one. + */ + public abstract float getTimePerFrame(); + + /** + * update recalculates the frame rate based on the previous + * call to update. It is assumed that update is called each frame. + */ + public abstract void update(); + + /** + * Reset the timer to 0. Clear any tpf history. + */ + public abstract void reset(); +} diff --git a/java/com/jme3/util/TempVars.java b/java/com/jme3/util/TempVars.java new file mode 100644 index 000000000..46f33010d --- /dev/null +++ b/java/com/jme3/util/TempVars.java @@ -0,0 +1,178 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.util; + +import com.jme3.math.*; + + +/** + * Temporary variables assigned to each thread. Engine classes may access these + * temp variables with TempVars.get(), all retrieved TempVars instances must be + * returned via TempVars.release(). This returns an available instance of the + * TempVar class ensuring this particular instance is never used elsewhere in + * the mean time. + */ +public class TempVars { + + /** + * Allow X instances of TempVars in a single thread. + */ + private static final int STACK_SIZE = 5; + + /** + * TempVarsStack contains a stack of TempVars. Every time + * TempVars.get() is called, a new entry is added to the stack, and the + * index incremented. When TempVars.release() is called, the entry is + * checked against the current instance and then the index is decremented. + */ + private static class TempVarsStack { + + int index = 0; + TempVars[] tempVars = new TempVars[STACK_SIZE]; + } + + /** + * ThreadLocal to store a TempVarsStack for each thread. This ensures each + * thread has a single TempVarsStack that is used only in method calls in + * that thread. + */ + private static final ThreadLocal varsLocal = new ThreadLocal() { + + @Override + public TempVarsStack initialValue() { + return new TempVarsStack(); + } + }; + /** + * This instance of TempVars has been retrieved but not released yet. + */ + private boolean isUsed = false; + + private TempVars() { + } + + /** + * Acquire an instance of the TempVar class. You have to release the + * instance after use by calling the release() method. If more than + * STACK_SIZE (currently 5) instances are requested in a single thread then + * an ArrayIndexOutOfBoundsException will be thrown. + * + * @return A TempVar instance + */ + public static TempVars get() { + TempVarsStack stack = varsLocal.get(); + + TempVars instance = stack.tempVars[stack.index]; + + if (instance == null) { + // Create new + instance = new TempVars(); + + // Put it in there + stack.tempVars[stack.index] = instance; + } + + stack.index++; + + instance.isUsed = true; + + return instance; + } + + /** + * Releases this instance of TempVars. Once released, the contents of the + * TempVars are undefined. The TempVars must be released in the opposite + * order that they are retrieved, e.g. Acquiring vars1, then acquiring + * vars2, vars2 MUST be released first otherwise an exception will be + * thrown. + */ + public void release() { + if (!isUsed) { + throw new IllegalStateException("This instance of TempVars was already released!"); + } + + isUsed = false; + + TempVarsStack stack = varsLocal.get(); + + // Return it to the stack + stack.index--; + + // Check if it is actually there + if (stack.tempVars[stack.index] != this) { + throw new IllegalStateException( + "An instance of TempVars has not been released in a called method!" + ); + } + } + + /** + * Color + */ + public final ColorRGBA color = new ColorRGBA(); + /** + * General vectors. + */ + public final Vector3f vect1 = new Vector3f(); + public final Vector3f vect2 = new Vector3f(); + public final Vector3f vect3 = new Vector3f(); + public final Vector3f vect4 = new Vector3f(); + public final Vector3f vect5 = new Vector3f(); + public final Vector3f vect6 = new Vector3f(); + public final Vector3f vect7 = new Vector3f(); + // seems the maximum number of vector used is 7 in com.jme3.bounding.java + public final Vector3f vect8 = new Vector3f(); + public final Vector3f vect9 = new Vector3f(); + public final Vector3f vect10 = new Vector3f(); + public final Vector4f vect4f = new Vector4f(); + public final Vector3f[] tri = { new Vector3f(), + new Vector3f(), + new Vector3f() }; + /** + * 2D vector + */ + public final Vector2f vect2d = new Vector2f(); + public final Vector2f vect2d2 = new Vector2f(); + /** + * General matrices. + */ + public final Matrix3f tempMat3 = new Matrix3f(); + public final Matrix4f tempMat4 = new Matrix4f(); + public final Matrix4f tempMat42 = new Matrix4f(); + /** + * General quaternions. + */ + public final Quaternion quat1 = new Quaternion(); + public final Quaternion quat2 = new Quaternion(); + + public final float[] matrixWrite = new float[16]; +} diff --git a/java/io/eiren/math/FloatMath.java b/java/io/eiren/math/FloatMath.java new file mode 100644 index 000000000..90320fd62 --- /dev/null +++ b/java/io/eiren/math/FloatMath.java @@ -0,0 +1,672 @@ +package io.eiren.math; + +import com.jme3.math.FastMath; +import com.jme3.math.Vector3f; +import com.jme3.math.Vector4f; + + +public class FloatMath { + + public static final float PI = (float) Math.PI; + public static final float TWO_PI = (float) (Math.PI * 2); + public static final float ANGLE_EPSILON = 0.028f; // in degrees (float + // epsilon for sin/cos) + public static final float ANGLE_EPSILON_RAD = toRad(ANGLE_EPSILON); + + public static final float ZERO_TOLERANCE_F = FastMath.ZERO_TOLERANCE; + public static final double ZERO_TOLERANCE_D = 0.0001d; + + public static final float SQRT_TWO = (float) Math.sqrt(2f); + public static final float INV_SQRT_TWO = 1f / SQRT_TWO; + public static final float SQRT_THREE = (float) Math.sqrt(3f); + public static final float INV_SQRT_THREE = 1f / SQRT_THREE; + public static final float TWO_FPI = PI * 2; + + public static final float SIN_75_DEG = 0.965926f; + public static final float SIN_60_DEG = 0.866025f; + public static final float SIN_45_DEG = 0.707107f; + public static final float SIN_30_DEG = 0.5f; + public static final float SIN_15_DEG = 0.258819f; + + public static final float COS_75_DEG = 0.258819f; + public static final float COS_60_DEG = 0.5f; + public static final float COS_45_DEG = 0.707107f; + public static final float COS_30_DEG = 0.866025f; + public static final float COS_15_DEG = 0.965926f; + + public static final int TEN_BITS = ~(~0 << 10); + public static final int TENTH_BIT = 1 << 10; + public static final int TEN_BITS_MAX = ~(~0 << 9); + public static final int TEN_BITS_MAX_UNSIGNED = ~(~0 << 10); + public static final int TWO_BITS = ~(~0 << 2); + public static final int SECOND_BIT = 1 << 2; + public static final int TWO_BITS_MAX = ~(~0 << 1); + public static final int TWO_BITS_MAX_UNSIGNED = ~(~0 << 2); + + public static float roundIfZero(float x) { + return Math.abs(x) < ZERO_TOLERANCE_F ? 0.0f : x; + } + + public static boolean equalsToZero(float x) { + return Math.abs(x) < ZERO_TOLERANCE_F; + } + + public static boolean lessThanZero(float x) { + return (x < -ZERO_TOLERANCE_F); + } + + public static boolean lessOrEqualsToZero(float x) { + return (x < ZERO_TOLERANCE_F); + } + + public static boolean greaterThanZero(float x) { + return (x > ZERO_TOLERANCE_F); + } + + public static boolean greaterOrEqualsToZero(float x) { + return (x > -ZERO_TOLERANCE_F); + } + + public static boolean equalsToZero(float x, float epsilon) { + return Math.abs(x) < epsilon; + } + + public static boolean equalsWithEpsilon(float x, float y) { + return Math.abs(x - y) < ZERO_TOLERANCE_F; + } + + public static boolean equalsWithEpsilon(float x, float y, float epsilon) { + return Math.abs(x - y) < epsilon; + } + + public static boolean lessWithEpsilon(float x, float y) { + return (x < y - ZERO_TOLERANCE_F); + } + + public static boolean lessOrEqualsWithEpsilon(float x, float y) { + return (x < y + ZERO_TOLERANCE_F); + } + + public static boolean lessWithEpsilon(float x, float y, float epsilon) { + return (x < y - epsilon); + } + + public static boolean lessOrEqualsWithEpsilon(float x, float y, float epsilon) { + return (x < y + epsilon); + } + + public static boolean greaterWithEpsilon(float x, float y) { + return (x > y + ZERO_TOLERANCE_F); + } + + public static boolean greaterOrEqualsWithEpsilon(float x, float y) { + return (x > y - ZERO_TOLERANCE_F); + } + + public static boolean greaterWithEpsilon(float x, float y, float epsilon) { + return (x > y + epsilon); + } + + public static boolean greaterOrEqualsWithEpsilon(float x, float y, float epsilon) { + return (x > y - epsilon); + } + + public static double roundIfZero(double x) { + return Math.abs(x) < ZERO_TOLERANCE_D ? 0.0d : x; + } + + public static boolean equalsToZero(double x) { + return Math.abs(x) < ZERO_TOLERANCE_D; + } + + public static boolean equalsWithEpsilon(double x, double y) { + return Math.abs(x - y) < ZERO_TOLERANCE_D; + } + + public static boolean lessWithEpsilon(double x, double y) { + return (x < y - ZERO_TOLERANCE_D); + } + + public static boolean lessOrEqualsWithEpsilon(double x, double y) { + return (x < y + ZERO_TOLERANCE_D); + } + + public static boolean greaterWithEpsilon(double x, double y) { + return (x > y + ZERO_TOLERANCE_D); + } + + public static boolean greaterOrEqualsWithEpsilon(double x, double y) { + return (x > y - ZERO_TOLERANCE_D); + } + + public static float toDegrees(float angrad) { + return angrad * 180.0f / PI; + } + + public static float toRad(float deg) { + return deg / 180.0f * PI; + } + + public static boolean radEqual(float angle1, float angle2) { + float diff = clampRad(angle1 - angle2); + return Math.abs(diff) < ANGLE_EPSILON_RAD; + } + + public static boolean degreesEqual(float angle1, float angle2) { + float diff = clampDegrees(angle1 - angle2); + return Math.abs(diff) < ANGLE_EPSILON; + } + + /** + * @deprecated use {@link #normalizeRad(float)} + */ + @Deprecated + public static float clampRad(float angle) { + return normalizeRad(angle); + } + + public static float normalizeRad(float angle) { + return FastMath.normalize(angle, -FastMath.PI, FastMath.PI); + } + + /** + * @deprecated use {@link #normalizeDegrees(float)} + */ + @Deprecated + public static float clampDegrees(float angle) { + return normalizeDegrees(angle); + } + + public static float normalizeDegrees(float angle) { + return FastMath.normalize(angle, -180f, 180f); + } + + public static float animateEase(float t) { + // Special case of Bezier interpolation (p0 = p1 = 0, p2 = p3 = 1) + return (3.0f - 2.0f * t) * t * t; + } + + public static float animateEaseIn(float t) { + return t * t; + } + + /** + * Lineary remaps value from the source interval to the target interval. + * details + */ + public static float mapValue( + float value, + float sourceStart, + float sourceEnd, + float targetStart, + float targetEnd + ) { + return targetStart + + (value - sourceStart) * (targetEnd - targetStart) / (sourceEnd - sourceStart); + } + + /** + * Clamps the given value and remaps to the target interval. + *

+ * Note the source interval values should be sorted. + */ + public static float mapValueWithClampBefore( + float value, + float sourceBottom, + float sourceTop, + float targetBottom, + float targetTop + ) { + return mapValue( + clamp(value, sourceBottom, sourceTop), + sourceBottom, + sourceTop, + targetBottom, + targetTop + ); + } + + /** + * Remaps the given value to the target interval and clamps. + *

+ * Note the target interval values should be sorted. + */ + public static float mapValueWithClampAfter( + float value, + float sourceBottom, + float sourceTop, + float targetBottom, + float targetTop + ) { + return clamp( + mapValue(value, sourceBottom, sourceTop, targetBottom, targetTop), + targetBottom, + targetTop + ); + } + + public static float smoothstep(float edge0, float edge1, float x) { + // Scale, bias and saturate x to 0..1 range + x = FastMath.clamp((x - edge0) / (edge1 - edge0), 0.0f, 1.0f); + // Evaluate polynomial + return x * x * (3f - 2f * x); + } + + public static float smootherstep(float edge0, float edge1, float x) { + // Scale, and clamp x to 0..1 range + x = FastMath.clamp((x - edge0) / (edge1 - edge0), 0.0f, 1.0f); + // Evaluate polynomial + return x * x * x * (x * (x * 6f - 15f) + 10f); + } + + /** + * Applies linear contrast (with clamping). + * + * @param t - input value in range (0..1) + * @param k - contrast factor in range (-1..1): + *

+ * @return contrasted value in range (0..1) + */ + public static float contrastLinear(float t, float k) { + float x = 2f * t - 1f; // -1..1 + float gamma = (1f + k) / (1f - k); + float f = FastMath.clamp(gamma * x, -1f, 1f); // -1..1 + return 0.5f * (f + 1f); // 0..1 + } + + /** + * Applies non-linear contrast by power function. + * + * @param t - input value in range (0..1) + * @param k - contrast factor in range (-1..1) exclusive: + * + * @return contrasted value in range (0..1) + */ + public static float contrastPower(float t, float k) { + float x = 2f * t - 1f; // -1..1 + float gamma = (1f - k) / (1f + k); + float f = FastMath.sign(x) * FastMath.pow(FastMath.abs(x), gamma); // -1..1 + return 0.5f * (f + 1f); // 0..1 + } + + /** + * Applies non-linear contrast by square splines. + * + * @param t - input value in range (0..1) + * @param k - contrast factor in range (-1..1): + * + * @return contrasted value in range (0..1) + */ + public static float contrastQuadricSpline(float t, float k) { + float x = 2f * t - 1f; // -1..1 + float f = x * (1f + k * (1f - FastMath.abs(x))); // -1..1 + return 0.5f * (f + 1f); // 0..1 + } + + /** + * Applies non-linear contrast by square splines inverted function. + * + * @param t - input value in range (0..1) + * @param k - contrast factor in range (-2..2): + * + * @return contrasted value in range (0..1) + */ + public static float contrastInvertQuadricSpline(float t, float k) { + float x = 2f * t - 1f; // -1..1 + float g; + if (k > 0) { + g = FastMath.sign(x) * FastMath.sqrt(FastMath.abs(x)) - 2f * x; + } else { + g = FastMath.sign(x) * (FastMath.sqrt(1f - FastMath.abs(x)) - 1f); + } + float f = (1f + k) * x + k * g; // -1..1 + return 0.5f * (f + 1f); // 0..1 + } + + /** + * Applies non-linear contrast by cubic splines. + * + * @param t - input value in range (0..1) + * @param k - contrast factor in range (-1..1): + * + * @return contrasted value in range (0..1) + */ + public static float contrastCubicSpline(float t, float k) { + float x = 2f * t - 1f; // -1..1 + float f = x * (1f + FastMath.abs(k) * (x * x - 1f)); + if (k < 0) + f -= x * 3f * k * (1f - FastMath.abs(x)); + return 0.5f * (f + 1f); // 0..1 + } + + public static float fraction(float f) { + return f - (int) f; + } + + public static double fraction(double d) { + return d - (long) d; + } + + /** + * @deprecated Do not copy {@link Math} methods. + */ + @Deprecated + public static float min(float a, float b) { + return a > b ? b : a; + } + + public static float min(float a, float b, float c) { + return Math.min(Math.min(a, b), c); + } + + public static float min(float a, float b, float c, float d) { + return Math.min(Math.min(a, b), Math.min(c, d)); + } + + /** + * @deprecated Do not copy {@link Math} methods. + */ + @Deprecated + public static float max(float a, float b) { + return a > b ? a : b; + } + + public static float max(float a, float b, float c) { + return Math.max(Math.max(a, b), c); + } + + public static float max(float a, float b, float c, float d) { + return Math.max(Math.max(a, b), Math.max(c, d)); + } + + public static float cos(float value) { + return (float) Math.cos(value); + } + + public static float sin(float value) { + return (float) Math.sin(value); + } + + public static float ceil(float value) { + return (float) Math.ceil(value); + } + + public static float floor(float value) { + return (float) Math.floor(value); + } + + public static float pow(float value, float power) { + return (float) Math.pow(value, power); + } + + /** + * @deprecated Do not copy {@link Math} methods. + */ + @Deprecated + public static float abs(float value) { + return (float) Math.abs(value); + } + + /** + * @deprecated Do not copy {@link Math} methods. + */ + @Deprecated + public static float round(float value) { + return (float) Math.round(value); + } + + public static float sqrt(float value) { + return (float) Math.sqrt(value); + } + + public static float distance(float x0, float y0, float z0, float x1, float y1, float z1) { + return distance(x1 - x0, y1 - y0, z1 - z0); + } + + public static float distance(float x, float y, float z) { + return sqrt(sqrDistance(x, y, z)); + } + + public static float sqrDistance(float x, float y, float z) { + return x * x + y * y + z * z; + } + + public static float distance(float x, float y) { + return sqrt(sqrDistance(x, y)); + } + + public static float sqrDistance(float x, float y) { + return x * x + y * y; + } + + public static float sqrDistance(Vector3f v, float x1, float y1, float z1) { + return sqrDistance(x1 - v.x, y1 - v.y, z1 - v.z); + } + + public static float sqrDistance(float x0, float y0, float z0, float x1, float y1, float z1) { + return sqrDistance(x1 - x0, y1 - y0, z1 - z0); + } + + public static float hypot(float x, float y) { + return FastMath.sqrt(x * x + y * y); + } + + public static float hypot(float x, float y, float z) { + return FastMath.sqrt(x * x + y * y + z * z); + } + + /** + * The same as FastMath.clamp + */ + public static float clamp(float value, float min, float max) { + if (value <= min) + return min; + if (value >= max) + return max; + return value; + } + + public static Vector3f int2101010RevToFloats(int packedValue, Vector3f store) { + if (store == null) + store = new Vector3f(); + store.x = packedValue & TEN_BITS_MAX; + if ((packedValue & TENTH_BIT) != 0) + store.x *= -1; + store.y = (packedValue >>> 10) & TEN_BITS_MAX; + if ((packedValue & (TENTH_BIT << 10)) != 0) + store.y *= -1; + store.z = (packedValue >>> 20) & TEN_BITS_MAX; + if ((packedValue & (TENTH_BIT << 20)) != 0) + store.z *= -1; + return store; + } + + public static int floatToInt210101Rev(Vector3f values) { + int store = 0; + store |= ((int) values.x) & TEN_BITS_MAX; + if (values.x < 0) + store |= TENTH_BIT; + store |= (((int) values.y) & TEN_BITS_MAX) << 10; + if (values.y < 0) + store |= TENTH_BIT << 10; + store |= (((int) values.z) & TEN_BITS_MAX) << 20; + if (values.z < 0) + store |= TENTH_BIT << 20; + return store; + } + + public static int floatToInt210101RevNormalized(Vector3f values) { + int store = 0; + store |= ((int) (values.x * TEN_BITS)) & TEN_BITS_MAX; + if (values.x < 0) + store |= TENTH_BIT; + store |= (((int) (values.y * TEN_BITS)) & TEN_BITS_MAX) << 10; + if (values.y < 0) + store |= TENTH_BIT << 10; + store |= (((int) (values.z * TEN_BITS)) & TEN_BITS_MAX) << 20; + if (values.z < 0) + store |= TENTH_BIT << 20; + return store; + } + + public static int floatToUnsignedInt210101Rev(Vector3f values) { + int store = 0; + store |= ((int) values.x) & TEN_BITS; + store |= (((int) values.y) & TEN_BITS) << 10; + store |= (((int) values.z) & TEN_BITS) << 20; + return store; + } + + public static int floatToUnsignedInt210101RevNormalized(Vector3f values) { + int store = 0; + store |= ((int) (values.x * TEN_BITS)) & TEN_BITS; + store |= (((int) (values.y * TEN_BITS)) & TEN_BITS) << 10; + store |= (((int) (values.z * TEN_BITS)) & TEN_BITS) << 20; + return store; + } + + public static int floatToInt210101Rev(float x, float y, float z) { + int store = 0; + store |= ((int) x) & TEN_BITS_MAX; + if (x < 0) + store |= TENTH_BIT; + store |= (((int) y) & TEN_BITS_MAX) << 10; + if (y < 0) + store |= TENTH_BIT << 10; + store |= (((int) z) & TEN_BITS_MAX) << 20; + if (z < 0) + store |= TENTH_BIT << 20; + return store; + } + + public static int floatToUnsignedInt210101Rev(float x, float y, float z) { + int store = 0; + store |= ((int) x) & TEN_BITS; + store |= (((int) y) & TEN_BITS) << 10; + store |= (((int) z) & TEN_BITS) << 20; + return store; + } + + public static Vector4f int2101010RevToFloats(int packedValue, Vector4f store) { + if (store == null) + store = new Vector4f(); + store.x = packedValue & TEN_BITS_MAX; + if ((packedValue & TENTH_BIT) != 0) + store.x *= -1; + store.y = (packedValue >>> 10) & TEN_BITS_MAX; + if ((packedValue & (TENTH_BIT << 10)) != 0) + store.y *= -1; + store.z = (packedValue >>> 20) & TEN_BITS_MAX; + if ((packedValue & (TENTH_BIT << 20)) != 0) + store.z *= -1; + store.w = (packedValue >>> 30) & TWO_BITS_MAX; + if ((packedValue & (SECOND_BIT << 30)) != 0) + store.w *= -1; + return store; + } + + public static int floatToInt210101Rev(Vector4f values) { + int store = 0; + store |= ((int) values.x) & TEN_BITS_MAX; + if (values.x < 0) + store |= TENTH_BIT; + store |= (((int) values.y) & TEN_BITS_MAX) << 10; + if (values.y < 0) + store |= TENTH_BIT << 10; + store |= (((int) values.z) & TEN_BITS_MAX) << 20; + if (values.z < 0) + store |= TENTH_BIT << 20; + store |= (((int) values.z) & TWO_BITS_MAX) << 30; + if (values.w < 0) + store |= SECOND_BIT << 30; + return store; + } + + public static int floatToUnsignedInt210101Rev(Vector4f values) { + int store = 0; + store |= ((int) values.x) & TEN_BITS; + store |= (((int) values.y) & TEN_BITS) << 10; + store |= (((int) values.z) & TEN_BITS) << 20; + store |= (((int) values.z) & TWO_BITS) << 30; + return store; + } + + public static Vector3f unsignedInt2101010RevToFloats(int packedValue, Vector3f store) { + if (store == null) + store = new Vector3f(); + store.x = packedValue & TEN_BITS; + store.y = (packedValue >>> 10) & TEN_BITS; + store.z = (packedValue >>> 20) & TEN_BITS; + return store; + } + + public static Vector4f unsignedInt2101010RevToFloats(int packedValue, Vector4f store) { + if (store == null) + store = new Vector4f(); + store.x = packedValue & TEN_BITS; + store.y = (packedValue >>> 10) & TEN_BITS; + store.z = (packedValue >>> 20) & TEN_BITS; + store.w = (packedValue >>> 30) & TWO_BITS; + return store; + } + + public static Vector3f int2101010RevNormalizedToFloats(int packedValue, Vector3f store) { + store = int2101010RevToFloats(packedValue, store); + store.x /= TEN_BITS_MAX; + store.y /= TEN_BITS_MAX; + store.z /= TEN_BITS_MAX; + return store; + } + + public static Vector4f int2101010RevNormalizedToFloats(int packedValue, Vector4f store) { + store = int2101010RevToFloats(packedValue, store); + store.x /= TEN_BITS_MAX; + store.y /= TEN_BITS_MAX; + store.z /= TEN_BITS_MAX; + store.w /= TWO_BITS_MAX; + return store; + } + + public static Vector3f unsignedInt2101010RevNormalizedToFloats( + int packedValue, + Vector3f store + ) { + store = unsignedInt2101010RevToFloats(packedValue, store); + store.x /= TEN_BITS; + store.y /= TEN_BITS; + store.z /= TEN_BITS; + return store; + } + + public static Vector4f unsignedInt2101010RevNormalizedToFloats( + int packedValue, + Vector4f store + ) { + store = unsignedInt2101010RevToFloats(packedValue, store); + store.x /= TEN_BITS; + store.y /= TEN_BITS; + store.z /= TEN_BITS; + store.w /= TWO_BITS; + return store; + } +} diff --git a/java/io/eiren/math/Vector3d.java b/java/io/eiren/math/Vector3d.java new file mode 100644 index 000000000..14a180692 --- /dev/null +++ b/java/io/eiren/math/Vector3d.java @@ -0,0 +1,257 @@ +package io.eiren.math; + +import com.jme3.math.Vector3f; + + +public class Vector3d implements Cloneable { + + public double x; + public double y; + public double z; + + public Vector3d() { + } + + public Vector3d(double x, double y, double z) { + this.x = x; + this.y = y; + this.z = z; + } + + public Vector3d(double x1, double y1, double z1, double x2, double y2, double z2) { + this.x = x2 - x1; + this.y = y2 - y1; + this.z = z2 - z1; + } + + public Vector3d(Vector3f src) { + this(src.x, src.y, src.z); + } + + public Vector3d set(double x, double y, double z) { + this.x = x; + this.y = y; + this.z = z; + return this; + } + + public Vector3d set(Vector3d v) { + this.x = v.x; + this.y = v.y; + this.z = v.z; + return this; + } + + public Vector3d add(double addX, double addY, double addZ) { + return new Vector3d(this.x + addX, this.y + addY, this.z + addZ); + } + + public Vector3d addLocal(Vector3d vec) { + return addLocal(vec.x, vec.y, vec.z); + } + + public Vector3d addLocal(double addX, double addY, double addZ) { + x += addX; + y += addY; + z += addZ; + return this; + } + + public Vector3d substract(double subX, double subY, double subZ) { + return new Vector3d(this.x - subX, this.y - subY, this.z - subZ); + } + + public Vector3d substractLocal(Vector3d vec) { + if (null == vec) { + return null; + } + x -= vec.x; + y -= vec.y; + z -= vec.z; + return this; + } + + public Vector3d substractLocal(double subX, double subY, double subZ) { + x -= subX; + y -= subY; + z -= subZ; + return this; + } + + public Vector3d negate() { + return new Vector3d(-x, -y, -z); + } + + public Vector3d negateLocal() { + x = -x; + y = -y; + z = -z; + return this; + } + + public Vector3d mult(double scalar) { + return new Vector3d(x * scalar, y * scalar, z * scalar); + } + + public Vector3d multLocal(double scalar) { + x *= scalar; + y *= scalar; + z *= scalar; + return this; + } + + public Vector3d divide(double scalar) { + return new Vector3d(x / scalar, y / scalar, z / scalar); + } + + public Vector3d divideLocal(double scalar) { + x /= scalar; + y /= scalar; + z /= scalar; + return this; + } + + public double dot(Vector3d v) { + return x * v.x + y * v.y + z * v.z; + } + + public double dot(double vx, double vy, double vz) { + return x * vx + y * vy + z * vz; + } + + public Vector3d cross(Vector3d other, Vector3d result) { + if (result == null) + result = new Vector3d(); + double resX = ((y * other.z) - (z * other.y)); + double resY = ((z * other.x) - (x * other.z)); + double resZ = ((x * other.y) - (y * other.x)); + result.set(resX, resY, resZ); + return result; + } + + @Override + public Vector3d clone() { + return new Vector3d(this.x, this.y, this.z); + } + + public Vector3d normalize() { + double length = x * x + y * y + z * z; + if (length != 1.0 && length != 0.0) { + double invLength = 1.0 / Math.sqrt(length); + return mult(invLength); + } + return clone(); + } + + public Vector3d normalizeLocal() { + double length = x * x + y * y + z * z; + if (length != 1.0 && length != 0.0) { + length = Math.sqrt(length); + double invLength = 1.0 / length; + x *= invLength; + z *= invLength; + y *= invLength; + } + return this; + } + + public Vector3f toVector3f() { + return new Vector3f((float) x, (float) y, (float) z); + } + + public double length() { + return Math.sqrt(x * x + y * y + z * z); + } + + public double lengthSquared() { + return x * x + y * y + z * z; + } + + @Override + public String toString() { + return new StringBuilder("Vector3D{") + .append(x) + .append(',') + .append(y) + .append(',') + .append(z) + .append('}') + .toString(); + } + + public void rotateAroundX(float f) { + double f1 = Math.cos(f); + double f2 = Math.sin(f); + double d = x; + double d1 = y * f1 + z * f2; + double d2 = z * f1 - y * f2; + x = (float) d; + y = (float) d1; + z = (float) d2; + } + + public void rotateAroundY(float f) { + double f1 = Math.cos(f); + double f2 = Math.sin(f); + double d = x * f1 + z * f2; + double d1 = y; + double d2 = z * f1 - x * f2; + x = (float) d; + y = (float) d1; + z = (float) d2; + } + + public double distanceTo(Vector3d vec3d) { + return Math.sqrt(squaredDistance(vec3d)); + } + + public double squaredDistance(Vector3d point) { + return squaredDistance(point.x, point.y, point.z); + } + + public double squaredDistance(double toX, double toY, double toZ) { + return (this.x - toX) * (this.x - toX) + + (this.y - toY) * (this.y - toY) + + (this.z - toZ) * (this.z - toZ); + } + + public Vector3d add(Vector3d dir) { + return add(dir.x, dir.y, dir.z); + } + + public Vector3d substract(Vector3d dir) { + return substract(dir.x, dir.y, dir.z); + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + long temp; + temp = Double.doubleToLongBits(x); + result = prime * result + (int) (temp ^ (temp >>> 32)); + temp = Double.doubleToLongBits(y); + result = prime * result + (int) (temp ^ (temp >>> 32)); + temp = Double.doubleToLongBits(z); + result = prime * result + (int) (temp ^ (temp >>> 32)); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + Vector3d other = (Vector3d) obj; + if (Double.doubleToLongBits(x) != Double.doubleToLongBits(other.x)) + return false; + if (Double.doubleToLongBits(y) != Double.doubleToLongBits(other.y)) + return false; + if (Double.doubleToLongBits(z) != Double.doubleToLongBits(other.z)) + return false; + return true; + } +} diff --git a/java/io/eiren/util/BufferedTimer.java b/java/io/eiren/util/BufferedTimer.java new file mode 100644 index 000000000..4bba98941 --- /dev/null +++ b/java/io/eiren/util/BufferedTimer.java @@ -0,0 +1,130 @@ +package io.eiren.util; + +import java.beans.ConstructorProperties; + +import com.jme3.system.NanoTimer; + + +/** + * This timer accumulate measured TPF and returns average/min/max FPS value + */ +public class BufferedTimer extends NanoTimer { + + private final float measureInterval; + private float averageTpf; + private float averageFps; + private float averageFrameRenderTime; + private float sumFrameRenderTime; + private float sumTpf; + private float minFpsCurrent; + private float maxFpsCurrent; + private float maxFps; + private float minFps; + private int count; + private boolean measured = false; + + /** + * Measure average tpf over the provided inverval in seconds + * + * @param measureInterval interval to measure averages over + */ + public BufferedTimer(float measureInterval) { + averageFps = 0; + sumTpf = 0; + count = 0; + this.measureInterval = measureInterval; + } + + public float getAverageFPS() { + return averageFps; + } + + public float getMinFPS() { + return minFps; + } + + public float getMaxFPS() { + return maxFps; + } + + public void addRenderTime(float renderTime) { + sumFrameRenderTime += renderTime; + } + + public float getAverageFrameRenderTime() { + return averageFrameRenderTime; + } + + public boolean isMeasured() { + if (measured) { + measured = false; + return true; + } + return false; + } + + public TimerSample getCurrentData() { + return new TimerSample(getFrameRate(), minFps, maxFps, averageFps); + } + + @Override + public void update() { + super.update(); + // Accumulate instant rate + sumTpf += getTimePerFrame(); + float fps = getFrameRate(); + if (fps < minFpsCurrent) + minFpsCurrent = fps; + if (fps > maxFpsCurrent) + maxFpsCurrent = fps; + ++count; + // Calculate results once per measure interval + if (!measured || sumTpf > measureInterval) { + // Average results + averageTpf = sumTpf / count; + averageFps = 1.0f / averageTpf; + averageFrameRenderTime = sumFrameRenderTime / count; + minFps = minFpsCurrent; + maxFps = maxFpsCurrent; + // Reset counter + sumTpf = 0; + sumFrameRenderTime = 0; + minFpsCurrent = Float.MAX_VALUE; + maxFpsCurrent = 0; + count = 0; + measured = true; + } + } + + public static class TimerSample { + + public float fps; + public float minFps; + public float maxFps; + public float averageFps; + + @ConstructorProperties({ "fps", "minFps", "maxFps", "averageFps" }) + public TimerSample(float fps, float minFps, float maxFps, float averageFps) { + this.fps = fps; + this.minFps = minFps; + this.maxFps = maxFps; + this.averageFps = averageFps; + } + + public float getFps() { + return fps; + } + + public float getMinFps() { + return minFps; + } + + public float getMaxFps() { + return maxFps; + } + + public float getAverageFps() { + return averageFps; + } + } +} diff --git a/java/io/eiren/util/MacOSX.java b/java/io/eiren/util/MacOSX.java new file mode 100644 index 000000000..e9f027123 --- /dev/null +++ b/java/io/eiren/util/MacOSX.java @@ -0,0 +1,41 @@ +package io.eiren.util; + +import java.awt.Image; +import java.awt.Toolkit; +import java.lang.reflect.Method; +import java.util.List; + + +public class MacOSX { + + public static void setIcons(List icons) { + try { + Class applicationClass = Class.forName("com.apple.eawt.Application"); + Method m = applicationClass.getDeclaredMethod("getApplication"); + Object application = m.invoke(null); + m = application.getClass().getDeclaredMethod("setDockIconImage", Image.class); + m.invoke(application, icons.get(icons.size() - 1)); + } catch (Exception e) {} + } + + public static void setTitle(String title) { + try { + Class applicationClass = Class.forName("com.apple.eawt.Application"); + Method m = applicationClass.getDeclaredMethod("getApplication"); + Object application = m.invoke(null); + m = application.getClass().getDeclaredMethod("setDockIconImage", String.class); + m.invoke(application, title); + } catch (Exception e) {} + } + + public static boolean hasRetinaDisplay() { + Object obj = Toolkit.getDefaultToolkit().getDesktopProperty("apple.awt.contentScaleFactor"); + if (obj instanceof Float) { + Float f = (Float) obj; + int scale = f.intValue(); + return (scale == 2); // 1 indicates a regular mac display. + } + return false; + } + +} diff --git a/java/io/eiren/util/OperatingSystem.java b/java/io/eiren/util/OperatingSystem.java new file mode 100644 index 000000000..7182e7df2 --- /dev/null +++ b/java/io/eiren/util/OperatingSystem.java @@ -0,0 +1,47 @@ +package io.eiren.util; + +import java.io.File; + + +public enum OperatingSystem { + + //@formatter:off + LINUX("linux", new String[]{"linux", "unix"}), + WINDOWS("windows", new String[]{"win"}), + OSX("osx", new String[]{"mac"}), + UNKNOWN("unknown", new String[0]); + //@fomatter: on + + private final String[] aliases; + public final String name; + private static OperatingSystem currentPlatform; + + private OperatingSystem(String name, String[] aliases) { + this.aliases = aliases; + this.name = name; + } + + public static String getJavaExecutable(boolean forceConsole) { + String separator = System.getProperty("file.separator"); + String path = System.getProperty("java.home") + separator + "bin" + separator; + if(getCurrentPlatform() == WINDOWS) { + if(!forceConsole && new File(path + "javaw.exe").isFile()) + return path + "javaw.exe"; + return path + "java.exe"; + } + return path + "java"; + } + + public static OperatingSystem getCurrentPlatform() { + if(currentPlatform != null) + return currentPlatform; + String osName = System.getProperty("os.name").toLowerCase(); + for(OperatingSystem os : values()) { + for(String alias : os.aliases) { + if(osName.contains(alias)) + return currentPlatform = os; + } + } + return UNKNOWN; + } +} \ No newline at end of file diff --git a/java/io/eiren/util/StringUtils.java b/java/io/eiren/util/StringUtils.java new file mode 100644 index 000000000..7ee936964 --- /dev/null +++ b/java/io/eiren/util/StringUtils.java @@ -0,0 +1,35 @@ +package io.eiren.util; + +import java.text.DecimalFormatSymbols; +import java.util.Locale; + + +public class StringUtils { + + private static char DECIMAL_SEP; + + public static char getDecimalSeparator() { + if (DECIMAL_SEP == '\u0000') { + final Locale l = Locale.getDefault(Locale.Category.FORMAT); + // Formatter.java always use "." in the Locale.US + DECIMAL_SEP = (l == null || l.equals(Locale.US) + ? '.' + : DecimalFormatSymbols.getInstance(l).getDecimalSeparator()); + } + return DECIMAL_SEP; + } + + public static String prettyNumber(float f) { + return prettyNumber(f, 4); + } + + public static String prettyNumber(float f, int numDigits) { + String str = String.format("%." + numDigits + "f", f); + if (numDigits != 0) + str = org.apache.commons.lang3.StringUtils.stripEnd(str, "0"); + char lastChar = str.charAt(str.length() - 1); + if (lastChar == getDecimalSeparator()) + str = str.substring(0, str.length() - 1); + return str; + } +} diff --git a/java/io/eiren/util/Util.java b/java/io/eiren/util/Util.java new file mode 100644 index 000000000..85fe39b57 --- /dev/null +++ b/java/io/eiren/util/Util.java @@ -0,0 +1,176 @@ +package io.eiren.util; + +import java.io.Closeable; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Map; +import java.util.Set; +import java.util.Map.Entry; + + +public class Util { + + public static void close(Object r) { + try { + if (r != null) { + if (r instanceof Closeable) + ((Closeable) r).close(); + else if (r instanceof AutoCloseable) + ((AutoCloseable) r).close(); + } + } catch (Exception e) {} + } + + public static void close(Object r1, Object r2) { + close(r1); + close(r2); + } + + public static void close(Object... r) { + for (int i = 0; i < r.length; ++i) + try { + if (r[i] != null) { + if (r[i] instanceof Closeable) + ((Closeable) r[i]).close(); + else if (r[i] instanceof AutoCloseable) + ((AutoCloseable) r[i]).close(); + } + } catch (Exception e) {} + } + + public static void close(AutoCloseable... r) { + for (int i = 0; i < r.length; ++i) + try { + if (r[i] != null) + r[i].close(); + } catch (Exception e) {} + } + + public static void close(Closeable... r) { + for (int i = 0; i < r.length; ++i) + try { + if (r[i] != null) + r[i].close(); + } catch (Exception e) {} + } + + /** + *

+ * Performs a deep toString of provided object. It shows content of arrays, + * collections and maps (trove not supported yet). + *

+ *

+ * Highly ineffective, use only for debug. + *

+ * + * @param object + * @return + */ + public static String toString(Object object) { + if (object == null) + return "null"; + StringBuilder buf = new StringBuilder(); + elementToString(object, buf, new HashSet()); + return buf.toString(); + } + + private static void deepToString(Map m, StringBuilder buf, Set dejaVu) { + if (m == null) { + buf.append("null"); + return; + } + if (m.size() == 0) { + buf.append("{}"); + return; + } + dejaVu.add(m); + buf.append('{'); + Iterator> iterator = m.entrySet().iterator(); + boolean has = false; + while (iterator.hasNext()) { + if (has) + buf.append(','); + Entry e = iterator.next(); + elementToString(e.getKey(), buf, dejaVu); + buf.append(':'); + elementToString(e.getValue(), buf, dejaVu); + has = true; + } + buf.append('}'); + dejaVu.remove(m); + } + + private static void deepToString( + Collection list, + StringBuilder buf, + Set dejaVu + ) { + Object[] array = list.toArray(); + deepToString(array, buf, dejaVu); + } + + private static void deepToString(Object[] a, StringBuilder buf, Set dejaVu) { + if (a == null) { + buf.append("null"); + return; + } + if (a.length == 0) { + buf.append("[]"); + return; + } + dejaVu.add(a); + buf.append('['); + for (int i = 0; i < a.length; i++) { + if (i != 0) + buf.append(','); + Object element = a[i]; + elementToString(element, buf, dejaVu); + } + buf.append(']'); + dejaVu.remove(a); + } + + @SuppressWarnings("unchecked") + private static void elementToString(Object element, StringBuilder buf, Set dejaVu) { + if (element == null) { + buf.append("null"); + } else { + Class eClass = element.getClass(); + if (eClass.isArray()) { + if (eClass == byte[].class) + buf.append(Arrays.toString((byte[]) element)); + else if (eClass == short[].class) + buf.append(Arrays.toString((short[]) element)); + else if (eClass == int[].class) + buf.append(Arrays.toString((int[]) element)); + else if (eClass == long[].class) + buf.append(Arrays.toString((long[]) element)); + else if (eClass == char[].class) + buf.append(Arrays.toString((char[]) element)); + else if (eClass == float[].class) + buf.append(Arrays.toString((float[]) element)); + else if (eClass == double[].class) + buf.append(Arrays.toString((double[]) element)); + else if (eClass == boolean[].class) + buf.append(Arrays.toString((boolean[]) element)); + else { // element is an array of object references + if (dejaVu.contains(element)) + buf.append("[...]"); + else + deepToString((Object[]) element, buf, dejaVu); + } + } else { // element is non-null and not an array + if (element instanceof Collection) + deepToString((Collection) element, buf, dejaVu); + else if (element instanceof Map) + deepToString((Map) element, buf, dejaVu); + else if (element instanceof CharSequence) + buf.append('"').append(element.toString()).append('"'); + else + buf.append(element.toString()); + } + } + } +} diff --git a/java/io/eiren/util/ann/AWTThread.java b/java/io/eiren/util/ann/AWTThread.java new file mode 100644 index 000000000..a4ca9828c --- /dev/null +++ b/java/io/eiren/util/ann/AWTThread.java @@ -0,0 +1,10 @@ +package io.eiren.util.ann; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + + +@Retention(value = RetentionPolicy.SOURCE) +public @interface AWTThread { + +} diff --git a/java/io/eiren/util/ann/DebugSwitch.java b/java/io/eiren/util/ann/DebugSwitch.java new file mode 100644 index 000000000..9b7827bec --- /dev/null +++ b/java/io/eiren/util/ann/DebugSwitch.java @@ -0,0 +1,9 @@ +package io.eiren.util.ann; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Target; + + +@Target({ ElementType.FIELD, ElementType.METHOD }) +public @interface DebugSwitch { +} diff --git a/java/io/eiren/util/ann/NativeUnsafe.java b/java/io/eiren/util/ann/NativeUnsafe.java new file mode 100644 index 000000000..52052dcb3 --- /dev/null +++ b/java/io/eiren/util/ann/NativeUnsafe.java @@ -0,0 +1,19 @@ +package io.eiren.util.ann; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + + +/** + * Marks methods and classes that use unsafe or direct access to memory. Proceed + * with caution. + * + * @author Rena + */ +@Retention(value = RetentionPolicy.RUNTIME) +@Target({ ElementType.METHOD, ElementType.TYPE }) +public @interface NativeUnsafe { + +} diff --git a/java/io/eiren/util/ann/Synchronize.java b/java/io/eiren/util/ann/Synchronize.java new file mode 100644 index 000000000..d18d9f833 --- /dev/null +++ b/java/io/eiren/util/ann/Synchronize.java @@ -0,0 +1,30 @@ +package io.eiren.util.ann; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + + +/** + *

+ * Означает необходимость обязательной синхронизации этого меcта во внешних + * методах. В аргументах передаётся название поля для синхронизации. + *

+ *

+ * Методы, помеченные данной аннотацией могут вызывать только Thread-Safe + * методы, либо методы, помеченные такой же аннотацией с тем же полем + * синхронизации. + *

+ *

+ * Поля, помеченные данной аннотацией должны быть синхронизированны на указанное + * поле при чтении или записи. + *

+ * + * @see {@link ThreadSafe}, {@link ThreadSecure}, {@link ThreadSafeSingle} + * @author Rena + */ +@Retention(value = RetentionPolicy.SOURCE) +public @interface Synchronize { + + String[] value(); + +} diff --git a/java/io/eiren/util/ann/ThreadSafe.java b/java/io/eiren/util/ann/ThreadSafe.java new file mode 100644 index 000000000..c74fcfb39 --- /dev/null +++ b/java/io/eiren/util/ann/ThreadSafe.java @@ -0,0 +1,27 @@ +package io.eiren.util.ann; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + + +/** + *

+ * Методы, помеченные этой аннотацией должны быть Thread-Safe. + *

+ *

+ * Важно: данные методы гарантированно должны обеспечивать потоковую + * безопасность, но не обязаны обеспечивать концессивность (полноту данных или + * точность синхронизации). + *

+ *

+ * Для полностью потоко-безопасных методов можно использовать аннотацию + * {@link ThreadSecure}. + *

+ * + * @see {@link ThreadSecure}, {@link Synchronize}, {@link ThreadSafeSingle} + * @author Rena + */ +@Retention(value = RetentionPolicy.SOURCE) +public @interface ThreadSafe { + +} diff --git a/java/io/eiren/util/ann/ThreadSafeSingle.java b/java/io/eiren/util/ann/ThreadSafeSingle.java new file mode 100644 index 000000000..0e918499e --- /dev/null +++ b/java/io/eiren/util/ann/ThreadSafeSingle.java @@ -0,0 +1,17 @@ +package io.eiren.util.ann; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + + +/** + * Соблюдает те же требования что и {@link ThreadSafe} но при условии, что сам + * метод вызывается только из одного потока одновременно. + * + * @see {@link ThreadSafe}, {@link ThreadSecure}, {@link Synchronize} + * @author Rena + */ +@Retention(value = RetentionPolicy.SOURCE) +public @interface ThreadSafeSingle { + +} diff --git a/java/io/eiren/util/ann/ThreadSecure.java b/java/io/eiren/util/ann/ThreadSecure.java new file mode 100644 index 000000000..2b375fe36 --- /dev/null +++ b/java/io/eiren/util/ann/ThreadSecure.java @@ -0,0 +1,22 @@ +package io.eiren.util.ann; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + + +/** + *

+ * Методы, помеченные этой аннотацией должны быть полностью Thread-Safe. + *

+ *

+ * Важно: данные методы гарантированно должны обеспечивать потоковую + * безопасность и консистентность (полноту данных и точность синхронизации). + *

+ * + * @see {@link ThreadSafe}, {@link Synchronize}, {@link ThreadSafeSingle} + * @author Rena + */ +@Retention(value = RetentionPolicy.SOURCE) +public @interface ThreadSecure { + +} diff --git a/java/io/eiren/util/ann/Transient.java b/java/io/eiren/util/ann/Transient.java new file mode 100644 index 000000000..3502dff42 --- /dev/null +++ b/java/io/eiren/util/ann/Transient.java @@ -0,0 +1,39 @@ +package io.eiren.util.ann; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + + +/** + *

+ * Означает что поле используется для временных или быстро изменяющихся + * переменных. + *

+ *

+ * Поле помеченное этой аннотацией не влияет на долгосрочное состояние объекта, + * не участвует в сериализации, вычислении equals и hashCode, не определяет + * поведение объекта для внешнего кода. Поэтому такие поля не должны + * использоваться внешним кодом, их состояние имеет смысл только для самого + * объекта в котором они объявлены. + *

+ * Примеры: + *
    + *
  • Временный объект, который используется в методах для внутренних + * вычислений. Например векторные и матричные вычисления.
  • + *
  • Внутренний флаг для мультитрединга. Например, флаг апдейта графического + * состояния взводимый из игрового потока.
  • + *
  • Выведенное значение или структура, которое инициализируется самим + * объектом по фиксированному правилу. Например, производное значение от + * переменной параметризующей объект. Инициализируемый в конструкторе lookup + * table.
  • + *
+ * + * @author tort32 + */ +@Retention(value = RetentionPolicy.SOURCE) +@Target({ ElementType.FIELD }) +public @interface Transient { + +} diff --git a/java/io/eiren/util/collections/FastList.java b/java/io/eiren/util/collections/FastList.java new file mode 100644 index 000000000..6a2ff809c --- /dev/null +++ b/java/io/eiren/util/collections/FastList.java @@ -0,0 +1,547 @@ +package io.eiren.util.collections; + +import java.util.*; +import java.util.function.Consumer; +import java.util.function.Predicate; +import java.util.function.UnaryOperator; + + +@SuppressWarnings("unchecked") +public class FastList extends AbstractList + implements RandomAccess, Cloneable, RemoveAtSwapList { + + private static final Object[] emptyArray = new Object[0]; + + public static final int MAX_ARRAY_SIZE = 2147483639; + + protected int size = 0; + protected Object[] array; + + public FastList(int capacity) { + array = capacity == 0 ? emptyArray : new Object[capacity]; + } + + public FastList() { + this(5); + } + + public FastList(Collection source) { + this(source.size()); + addAll(source); + } + + public FastList(FastList source) { + this(source.size); + addAllInternal(0, source.array, source.size); + } + + public FastList(E[] source) { + this(source.length); + addAll(source); + } + + public FastList(E source) { + this(); + add(source); + } + + private FastList(Object[] arr, int size) { + this(size); + System.arraycopy(arr, 0, array, 0, size); + this.size = size; + } + + private FastList(boolean f) { + } + + public static FastList reuseArray(E[] source) { + FastList list = new FastList<>(true); + list.array = source; + list.size = source.length; + return list; + } + + private void checkBounds(int index) { + if (index < 0 || index >= size) + throw new ArrayIndexOutOfBoundsException( + new StringBuilder("Index: ") + .append(index) + .append(", size: ") + .append(size) + .toString() + ); + } + + public void ensureCapacity(int numToFit) { + if (array.length < size + numToFit) + grow(numToFit + size); + } + + private void grow(int i) { + int j = array.length; + int k = j + (j >> 1); + if (k - i < 0) + k = i; + if (k - 2147483639 > 0) + k = hugeCapacity(i); + array = Arrays.copyOf(array, k); + } + + private static int hugeCapacity(int i) { + if (i < 0) + throw new OutOfMemoryError("Huge capacity negative: " + i); + else + return i <= MAX_ARRAY_SIZE ? MAX_ARRAY_SIZE : 2147483647; + } + + public void copyInto(Object[] anArray) { + System.arraycopy(array, 0, anArray, 0, size); + } + + @Override + public E get(int index) { + checkBounds(index); + return (E) array[index]; + } + + public E unsafeGet(int index) { + return (E) array[index]; + } + + @Override + public boolean isEmpty() { + return size == 0; + } + + @Override + public int size() { + return size; + } + + @Override + public int indexOf(Object obj) { + for (int j = 0; j < size; j++) + if (obj == array[j]) + return j; + return -1; + } + + @Override + public int lastIndexOf(Object obj) { + for (int j = size - 1; j >= 0; j--) + if (obj == array[j]) + return j; + return -1; + } + + @Override + public boolean contains(Object obj) { + return indexOf(obj) >= 0; + } + + public void trimToSize() { + int i = array.length; + if (size < i) + array = Arrays.copyOf(array, size); + } + + @Override + public Object[] toArray() { + return Arrays.copyOf(array, size); + } + + @Override + public T[] toArray(T aobj[]) { + if (aobj.length < size) + return (T[]) Arrays.copyOf(array, size, aobj.getClass()); + System.arraycopy(array, 0, aobj, 0, size); + if (aobj.length > size) + aobj[size] = null; + return aobj; + } + + @Override + public boolean add(E e) { + ensureCapacity(1); + array[size++] = e; + return true; + } + + @Override + public E remove(int i) { + checkBounds(i); + E obj = (E) array[i]; + removeInternal(i); + return obj; + } + + @Override + public boolean remove(Object obj) { + for (int j = 0; j < size; j++) + if (obj == array[j]) { + removeInternal(j); + return true; + } + return false; + } + + public boolean removeAll(Object[] toRemove) { + boolean removed = false; + for (int i = toRemove.length - 1; i >= 0; --i) { + int index = indexOf(toRemove[i]); + if (index != -1) { + removeInternal(index); + removed = true; + } + } + return removed; + } + + protected void removeInternal(int i) { + int j = size - i - 1; + if (j > 0) + System.arraycopy(array, i + 1, array, i, j); + array[--size] = null; + } + + public void unsafeRemove(int i) { + removeInternal(i); + } + + @Override + public boolean removeAll(Collection c) { + Objects.requireNonNull(c); + return batchRemove(c, false); + } + + @Override + public boolean retainAll(Collection c) { + Objects.requireNonNull(c); + return batchRemove(c, true); + } + + private boolean batchRemove(Collection c, boolean complement) { + final Object[] elementData = this.array; + int r = 0, w = 0; + boolean modified = false; + try { + for (; r < size; r++) + if (c.contains(elementData[r]) == complement) + elementData[w++] = elementData[r]; + } finally { + // Preserve behavioral compatibility with AbstractCollection, + // even if c.contains() throws. + if (r != size) { + System.arraycopy(elementData, r, elementData, w, size - r); + w += size - r; + } + if (w != size) { + for (int i = w; i < size; i++) + elementData[i] = null; + size = w; + modified = true; + } + } + return modified; + } + + @Override + public void clear() { + for (int i = 0; i < size; i++) + array[i] = null; + size = 0; + } + + public void fakeClear() { + size = 0; + } + + @Override + public boolean addAll(Collection collection) { + return addAll(size, collection); + } + + public void addAll(E[] arr) { + addAllInternal(size, arr, arr.length); + } + + public void addAll(E[] arr, int limit) { + addAllInternal(size, arr, limit); + } + + public void addAll(int index, E[] arr) { + addAllInternal(index, arr, arr.length); + } + + public void addAll(int index, E[] arr, int limit) { + addAllInternal(index, arr, limit); + } + + private void addAllInternal(int index, Object[] arr, int limit) { + if (limit > arr.length) + limit = arr.length; + if (limit == 1) { + add(index, (E) arr[0]); + } else if (limit > 0) { + if (index >= size) { + ensureCapacity(size - index + limit); + System.arraycopy(arr, 0, array, index, limit); + size = index + limit; + } else { + if (array.length < size + limit) { + Object[] newArray = new Object[size + limit]; + System.arraycopy(array, 0, newArray, 0, index); + System.arraycopy(arr, 0, newArray, index, limit); + System.arraycopy(array, index, newArray, index + limit, size - index); + array = newArray; + } else { + System.arraycopy(array, index, array, index + 1, size - index); + System.arraycopy(arr, 0, array, index, limit); + } + size += limit; + } + } + } + + @Override + public boolean addAll(int index, Collection collection) { + if (collection.size() > 0) { + if (collection instanceof FastList) { + addAllInternal( + index, + ((FastList) collection).array, + collection.size() + ); + } else if (collection instanceof RandomAccess) { + Object[] arr = collection.toArray(new Object[collection.size()]); + addAllInternal(index, arr, arr.length); + } else { + if (index >= size) { + ensureCapacity(size - index + collection.size()); + Iterator iterator = collection.iterator(); + int i = index; + while (iterator.hasNext()) + array[i++] = iterator.next(); + size = index + collection.size(); + } else { + if (array.length < size + collection.size()) { + Object[] newArray = new Object[size + collection.size()]; + System.arraycopy(array, 0, newArray, 0, index); + Iterator iterator = collection.iterator(); + int i = index; + while (iterator.hasNext()) + newArray[i++] = iterator.next(); + System + .arraycopy( + array, + index, + newArray, + index + collection.size(), + size - index + ); + array = newArray; + } else { + System.arraycopy(array, index, array, index + 1, size - index); + Iterator iterator = collection.iterator(); + while (iterator.hasNext()) + array[index++] = iterator.next(); + } + size += collection.size(); + } + } + return true; + } + return false; + } + + @Override + public void add(int index, E element) { + if (index >= size) { + ensureCapacity(size - index + 1); + size = index + 1; + array[index] = element; + } else { + if (array.length < size + 1) { + Object[] newArray = new Object[size + 1]; + System.arraycopy(array, 0, newArray, 0, index); + newArray[index] = element; + System.arraycopy(array, index, newArray, index + 1, size - index); + array = newArray; + } else { + System.arraycopy(array, index, array, index + 1, size - index); + array[index] = element; + } + size++; + } + } + + @Override + public E set(int index, E element) { + checkBounds(index); + E oldValue = (E) array[index]; + array[index] = element; + return oldValue; + } + + @Override + public FastList clone() { + return new FastList(array, size); + } + + @Override + public void forEach(Consumer action) { + Objects.requireNonNull(action); + final int expectedModCount = modCount; + final E[] elementData = (E[]) this.array; + final int size = this.size; + for (int i = 0; modCount == expectedModCount && i < size; i++) { + action.accept(elementData[i]); + } + if (modCount != expectedModCount) { + throw new ConcurrentModificationException(); + } + } + + @Override + public E removeAtSwap(int i) { + checkBounds(i); + E obj = (E) array[i]; + removeAtSwapInternal(i); + return obj; + } + + @Override + public boolean removeAtSwap(Object obj) { + for (int j = 0; j < size; j++) + if (obj == array[j]) { + removeAtSwapInternal(j); + return true; + } + return false; + } + + protected void removeAtSwapInternal(int i) { + int j = size - i - 1; + if (j > 0) + array[i] = array[size - 1]; + array[--size] = null; + } + + @Override + public void removeRange(int i, int toIndex) { + checkBounds(i); + checkBounds(toIndex); + int j = size - toIndex - 1; + if (j > 0) + System.arraycopy(array, toIndex + 1, array, i, j); + size -= (toIndex - i + 1); + Arrays.fill(array, i, toIndex, null); + } + + @Override + public void replaceAll(UnaryOperator operator) { + Objects.requireNonNull(operator); + for (int i = 0; i < size; ++i) + set(i, operator.apply(get(i))); + } + + @Override + public void sort(Comparator c) { + Arrays.sort((E[]) array, 0, size, c); + } + + @Override + public int hashCode() { + int hashCode = 1; + for (int i = 0; i < size; ++i) { + Object o = array[i]; + hashCode = 31 * hashCode + (o == null ? 0 : o.hashCode()); + } + return hashCode; + } + + @Override + public Spliterator spliterator() { + return Spliterators.spliterator(array, 0, size, Spliterator.ORDERED); + } + + /** + * Special comodification iterator. Use with caution. + *

+ * To get element type correctly assign result to reference type + * {@code FastList.SkipFastListIterator} + * + * @return skip iterator to iterate this list in thread-safe manner + */ + public SkipFastListIterator skipIterator() { + return new SkipFastListIterator(); + } + + @Override + public boolean removeIf(Predicate filter) { + Objects.requireNonNull(filter); + // figure out which elements are to be removed + // any exception thrown from the filter predicate at this stage + // will leave the collection unmodified + int removeCount = 0; + final BitSet removeSet = new BitSet(size); + final int expectedModCount = modCount; + final int size = this.size; + for (int i = 0; modCount == expectedModCount && i < size; i++) { + final E element = (E) array[i]; + if (filter.test(element)) { + removeSet.set(i); + removeCount++; + } + } + if (modCount != expectedModCount) { + throw new ConcurrentModificationException(); + } + + // shift surviving elements left over the spaces left by removed + // elements + final boolean anyToRemove = removeCount > 0; + if (anyToRemove) { + final int newSize = size - removeCount; + for (int i = 0, j = 0; (i < size) && (j < newSize); i++, j++) { + i = removeSet.nextClearBit(i); + array[j] = array[i]; + } + for (int k = newSize; k < size; k++) { + array[k] = null; // Let gc do its work + } + this.size = newSize; + if (modCount != expectedModCount) { + throw new ConcurrentModificationException(); + } + modCount++; + } + + return anyToRemove; + } + + public class SkipFastListIterator implements ResettableIterator, SkipIterator { + + public int position; + + @Override + public boolean hasNext() { + return position < size; + } + + @Override + public E next() { + Object[] arr = array; + if (arr.length > position) { + return (E) arr[position++]; + } + position++; // Increase position so hasNext() never loops infinitely + return null; + } + + @Override + public void reset() { + position = 0; + } + } +} diff --git a/java/io/eiren/util/collections/RemoveAtSwapFastList.java b/java/io/eiren/util/collections/RemoveAtSwapFastList.java new file mode 100644 index 000000000..1c00001e1 --- /dev/null +++ b/java/io/eiren/util/collections/RemoveAtSwapFastList.java @@ -0,0 +1,41 @@ +package io.eiren.util.collections; + +import java.util.Collection; + + +/** + * FastList that performs Remove-At-Swap on stanard remove() operations. + * + *

+ * Remove operations breaks ordering of this list + * + * @author Rena + * + * @param + */ +public class RemoveAtSwapFastList extends FastList { + + public RemoveAtSwapFastList(int capacity) { + super(capacity); + } + + public RemoveAtSwapFastList() { + } + + public RemoveAtSwapFastList(Collection source) { + super(source); + } + + public RemoveAtSwapFastList(E[] source) { + super(source); + } + + public RemoveAtSwapFastList(E source) { + super(source); + } + + @Override + protected void removeInternal(int i) { + super.removeAtSwapInternal(i); + } +} diff --git a/java/io/eiren/util/collections/RemoveAtSwapList.java b/java/io/eiren/util/collections/RemoveAtSwapList.java new file mode 100644 index 000000000..ab6fbfcc1 --- /dev/null +++ b/java/io/eiren/util/collections/RemoveAtSwapList.java @@ -0,0 +1,11 @@ +package io.eiren.util.collections; + +import java.util.List; + + +public interface RemoveAtSwapList extends List { + + public E removeAtSwap(int i); + + public boolean removeAtSwap(Object object); +} diff --git a/java/io/eiren/util/collections/ResettableIterator.java b/java/io/eiren/util/collections/ResettableIterator.java new file mode 100644 index 000000000..8f145d42c --- /dev/null +++ b/java/io/eiren/util/collections/ResettableIterator.java @@ -0,0 +1,17 @@ +package io.eiren.util.collections; + +import java.util.Iterator; + + +/** + * {@link Iterator} that can be reset and iterated from the start by using + * {@link #reset()} + * + * @author Rena + * + * @param + */ +public interface ResettableIterator extends Iterator { + + public void reset(); +} diff --git a/java/io/eiren/util/collections/SkipIterator.java b/java/io/eiren/util/collections/SkipIterator.java new file mode 100644 index 000000000..9c79579ad --- /dev/null +++ b/java/io/eiren/util/collections/SkipIterator.java @@ -0,0 +1,16 @@ +package io.eiren.util.collections; + +import java.util.Iterator; + + +/** + * {@link Iterator} that can return null on {@link #next()} or can lie on + * {@link #hasNext()}. It is not thread-secure! + * + * @param the type of elements returned by this iterator + */ +public interface SkipIterator extends Iterator { + + @Override + E next(); +} diff --git a/java/io/eiren/util/logging/DefaultGLog.java b/java/io/eiren/util/logging/DefaultGLog.java new file mode 100644 index 000000000..78d834099 --- /dev/null +++ b/java/io/eiren/util/logging/DefaultGLog.java @@ -0,0 +1,137 @@ +package io.eiren.util.logging; + +import java.util.concurrent.ArrayBlockingQueue; +import java.util.logging.Level; +import java.util.logging.Logger; + + +public class DefaultGLog extends Thread implements IGLog { + + private final Logger logger; + + public static class LogEntry { + + private Level level; + private String message; + private Throwable t; + + public LogEntry(Level level, String message, Throwable t) { + this(level, message); + this.t = t; + } + + public LogEntry(Level level, String message) { + this.level = level; + this.message = message; + this.t = null; + } + + public Level getLevel() { + return level; + } + + public String getMessage() { + return message; + } + + public Throwable getException() { + return t; + } + } + + private final ArrayBlockingQueue queue = new ArrayBlockingQueue(50000); + private volatile LoggerRecorder recorder; + + @Override + public void info(String message) { + add(new LogEntry(Level.INFO, message)); + } + + @Override + public void info(String message, Throwable t) { + add(new LogEntry(Level.INFO, message, t)); + } + + @Override + public void severe(String message) { + add(new LogEntry(Level.SEVERE, message)); + } + + @Override + public void severe(String message, Throwable t) { + add(new LogEntry(Level.SEVERE, message, t)); + } + + @Override + public void warning(String message) { + add(new LogEntry(Level.WARNING, message)); + } + + @Override + public void warning(String message, Throwable t) { + add(new LogEntry(Level.WARNING, message, t)); + } + + @Override + public void debug(String message) { + add(new LogEntry(Level.INFO, "[DBG] " + message)); + } + + @Override + public void debug(String message, Throwable t) { + add(new LogEntry(Level.INFO, "[DBG] " + message, t)); + } + + @Override + public void log(Level level, String message) { + add(new LogEntry(level, message)); + } + + @Override + public void log(Level level, String message, Throwable t) { + add(new LogEntry(level, message, t)); + } + + private void add(LogEntry entry) { + try { + queue.put(entry); + } catch (InterruptedException e) {} + try { + if (recorder != null) + recorder.addEntry(entry); + } catch (NullPointerException e) {} + } + + @Override + public void setRecorder(LoggerRecorder recorder) { + this.recorder = recorder; + } + + @Override + public LoggerRecorder removeRecorder() { + LoggerRecorder lr = this.recorder; + this.recorder = null; + return lr; + } + + public DefaultGLog(Logger logger) { + super("Logger"); + this.logger = logger; + this.setDaemon(true); + this.setPriority(7); + this.start(); + } + + @Override + public void run() { + while (true) { + try { + LogEntry log = queue.take(); + if (log.t != null) + logger.log(log.level, log.message, log.t); + else + logger.log(log.level, log.message); + } catch (InterruptedException e) {} + } + } +} diff --git a/java/io/eiren/util/logging/FileLogFormatter.java b/java/io/eiren/util/logging/FileLogFormatter.java new file mode 100644 index 000000000..24d67afa0 --- /dev/null +++ b/java/io/eiren/util/logging/FileLogFormatter.java @@ -0,0 +1,58 @@ +package io.eiren.util.logging; + +import java.io.PrintWriter; +import java.io.StringWriter; +import java.text.SimpleDateFormat; +import java.util.logging.Formatter; +import java.util.logging.Level; +import java.util.logging.LogRecord; + + +public class FileLogFormatter extends Formatter { + + private static final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + + @Override + public String format(LogRecord record) { + StringBuilder sb = new StringBuilder(); + sb.append(dateFormat.format(record.getMillis())); + Level localLevel = record.getLevel(); + if (localLevel == Level.FINEST) + sb.append(" [FINEST] "); + else if (localLevel == Level.FINER) + sb.append(" [FINER] "); + else if (localLevel == Level.FINE) + sb.append(" [FINE] "); + else if (localLevel == Level.INFO) + sb.append(" [INFO] "); + else if (localLevel == Level.WARNING) + sb.append(" [WARNING] "); + else if (localLevel == Level.SEVERE) + sb.append(" [SEVERE] "); + else + sb.append(" [" + localLevel.getLocalizedName() + "] "); + + sb.append(record.getMessage()); + sb.append('\n'); + + Throwable localThrowable = record.getThrown(); + if (localThrowable != null) { + StringWriter localStringWriter = new StringWriter(); + localThrowable.printStackTrace(new PrintWriter(localStringWriter)); + sb.append(localStringWriter.toString()); + } + + String message = sb.toString(); + Object parameters[] = record.getParameters(); + if (parameters == null || parameters.length == 0) + return message; + if ( + message.indexOf("{0") >= 0 + || message.indexOf("{1") >= 0 + || message.indexOf("{2") >= 0 + || message.indexOf("{3") >= 0 + ) + return java.text.MessageFormat.format(message, parameters); + return message; + } +} diff --git a/java/io/eiren/util/logging/IGLog.java b/java/io/eiren/util/logging/IGLog.java new file mode 100644 index 000000000..f21744a19 --- /dev/null +++ b/java/io/eiren/util/logging/IGLog.java @@ -0,0 +1,48 @@ +package io.eiren.util.logging; + +import java.util.logging.Level; + + +public interface IGLog { + + public void info(String message); + + public void severe(String message); + + public void warning(String message); + + public void debug(String message); + + public default void info(String message, Throwable t) { + log(Level.INFO, message, t); + } + + public default void severe(String message, Throwable t) { + log(Level.SEVERE, message, t); + } + + public default void warning(String message, Throwable t) { + log(Level.WARNING, message, t); + } + + public default void debug(String message, Throwable t) { + log(Level.INFO, "[DBG] " + message, t); + } + + public void log(Level level, String message); + + public void log(Level level, String message, Throwable t); + + public void setRecorder(LoggerRecorder recorder); + + public LoggerRecorder removeRecorder(); + + static class GLevel extends Level { + + private static final long serialVersionUID = -539856764608026895L; + + private GLevel(String s, int i) { + super(s, i); + } + } +} diff --git a/java/io/eiren/util/logging/LogManager.java b/java/io/eiren/util/logging/LogManager.java new file mode 100644 index 000000000..05ba0619e --- /dev/null +++ b/java/io/eiren/util/logging/LogManager.java @@ -0,0 +1,136 @@ +package io.eiren.util.logging; + +import java.io.File; +import java.io.IOException; +import java.io.PrintStream; +import java.text.SimpleDateFormat; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.logging.ConsoleHandler; +import java.util.logging.FileHandler; +import java.util.logging.Handler; +import java.util.logging.Level; +import java.util.logging.Logger; + + +public class LogManager { + + private static AtomicBoolean initialized = new AtomicBoolean(false); + + public static Logger global = Logger.getLogger(""); + public static final IGLog log = new DefaultGLog(global); + public static ConsoleHandler handler; + + public static void initialize(File logsDir, File mainLogDir) + throws SecurityException, IOException { + if (initialized.getAndSet(true)) + return; + FileLogFormatter loc = new FileLogFormatter(); + if (mainLogDir != null) { + if (!mainLogDir.exists()) + mainLogDir.mkdirs(); + File lastLogFile = new File(mainLogDir, "log_last.log"); + if (lastLogFile.exists()) + lastLogFile.delete(); + File mainLog = new File(mainLogDir, "log_main.log"); + FileHandler mHandler = new FileHandler(mainLog.getPath(), true); + FileHandler filehandler = new FileHandler(lastLogFile.getPath(), true); + mHandler.setFormatter(loc); + filehandler.setFormatter(loc); + global.addHandler(mHandler); + global.addHandler(filehandler); + } + if (logsDir != null) { + if (!logsDir.exists()) + logsDir.mkdir(); + if (!logsDir.isDirectory()) + System.out.println("*** WARNING *** LOG FOLDER IS NOT A DIRECTORY!"); + File currentLog = new File( + logsDir, + "log_" + + new SimpleDateFormat("yyyy-MM-dd") + .format(Long.valueOf(System.currentTimeMillis())) + + ".log" + ); + FileHandler filehandler2 = new FileHandler(currentLog.getPath(), true); + filehandler2.setFormatter(loc); + global.addHandler(filehandler2); + } + } + + public static void replaceMainHandler(ConsoleHandler newHandler) { + handler.close(); + global.removeHandler(handler); + handler = newHandler; + global.addHandler(newHandler); + } + + public static void addHandler(Handler add) { + global.addHandler(add); + } + + public static void removeHandler(Handler remove) { + global.removeHandler(remove); + } + + public static void enablePreciseTimestamp() { + handler.setFormatter(new PreciseConsoleLogFormatter()); + } + + public static void info(String message) { + log.info(message); + } + + public static void severe(String message) { + log.severe(message); + } + + public static void warning(String message) { + log.warning(message); + } + + public static void debug(String message) { + log.debug(message); + } + + public static void info(String message, Throwable t) { + log.info(message, t); + } + + public static void severe(String message, Throwable t) { + log.severe(message, t); + } + + public static void warning(String message, Throwable t) { + log.warning(message, t); + } + + public static void debug(String message, Throwable t) { + log.debug(message, t); + } + + public static void log(Level level, String message) { + log.log(level, message); + } + + public static void log(Level level, String message, Throwable t) { + log.log(level, message, t); + } + + static { + boolean hasConsoleHandler = false; + for (Handler h : global.getHandlers()) { + if (h instanceof ConsoleHandler) { + handler = (ConsoleHandler) h; + hasConsoleHandler = true; + } + } + if (!hasConsoleHandler) { + handler = new ConsoleHandler(); + global.addHandler(handler); + } + handler.setFormatter(new ShortConsoleLogFormatter()); + + System.setOut(new PrintStream(new LoggerOutputStream(log, Level.INFO), true)); + System.setErr(new PrintStream(new LoggerOutputStream(log, Level.SEVERE), true)); + } +} diff --git a/java/io/eiren/util/logging/LoggerOutputStream.java b/java/io/eiren/util/logging/LoggerOutputStream.java new file mode 100644 index 000000000..0a866937f --- /dev/null +++ b/java/io/eiren/util/logging/LoggerOutputStream.java @@ -0,0 +1,52 @@ +package io.eiren.util.logging; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.logging.Level; + + +public class LoggerOutputStream extends ByteArrayOutputStream { + + private static final String separator = System.getProperty("line.separator"); + + private final IGLog logger; + private final Level level; + private final String prefix; + private final StringBuilder buffer = new StringBuilder(); + + public LoggerOutputStream(IGLog logger, Level level) { + this(logger, level, ""); + } + + public LoggerOutputStream(IGLog logger, Level level, String prefix) { + super(); + this.logger = logger; + this.level = level; + this.prefix = prefix; + } + + @Override + public void flush() throws IOException { + synchronized (this) { + super.flush(); + String record = this.toString(); + super.reset(); + if (record.length() > 0) { + buffer.append(record); + if (record.contains(separator)) { + String s = buffer.toString(); + String[] split = s.split(separator); + for (int i = 0; i < split.length; ++i) + logger.log(level, prefix + split[i]); + buffer.setLength(0); + // buffer.append(split[split.length - 1]); + } + } + } + } + + @Override + public void close() throws IOException { + flush(); + } +} diff --git a/java/io/eiren/util/logging/LoggerRecorder.java b/java/io/eiren/util/logging/LoggerRecorder.java new file mode 100644 index 000000000..2fc4f9392 --- /dev/null +++ b/java/io/eiren/util/logging/LoggerRecorder.java @@ -0,0 +1,23 @@ +package io.eiren.util.logging; + +import java.util.List; + +import io.eiren.util.collections.FastList; +import io.eiren.util.logging.DefaultGLog.LogEntry; + + +public class LoggerRecorder { + + private final List recorded = new FastList(); + + public LoggerRecorder() { + } + + public synchronized void addEntry(LogEntry e) { + recorded.add(e); + } + + public List getEntries() { + return recorded; + } +} diff --git a/java/io/eiren/util/logging/PreciseConsoleLogFormatter.java b/java/io/eiren/util/logging/PreciseConsoleLogFormatter.java new file mode 100644 index 000000000..69abe91dd --- /dev/null +++ b/java/io/eiren/util/logging/PreciseConsoleLogFormatter.java @@ -0,0 +1,32 @@ +package io.eiren.util.logging; + +import java.text.SimpleDateFormat; +import java.util.logging.LogRecord; + + +/** + * Format message timestamp as time passed from the start with milliseconds. + */ +public class PreciseConsoleLogFormatter extends ShortConsoleLogFormatter { + + private final long startMills; + + public PreciseConsoleLogFormatter() { + startMills = System.currentTimeMillis(); + } + + @Override + protected SimpleDateFormat createDateFormat() { + return new SimpleDateFormat("mm:ss.SSS"); + } + + @Override + protected void buildMessage(StringBuilder builder, LogRecord record) { + builder.append(date.format(record.getMillis() - startMills)); + builder.append(" ["); + builder.append(record.getLevel().getLocalizedName().toUpperCase()); + builder.append("] "); + builder.append(record.getMessage()); + builder.append('\n'); + } +} diff --git a/java/io/eiren/util/logging/ShortConsoleLogFormatter.java b/java/io/eiren/util/logging/ShortConsoleLogFormatter.java new file mode 100644 index 000000000..f95749084 --- /dev/null +++ b/java/io/eiren/util/logging/ShortConsoleLogFormatter.java @@ -0,0 +1,58 @@ +package io.eiren.util.logging; + +import java.io.PrintWriter; +import java.io.StringWriter; +import java.text.SimpleDateFormat; +import java.util.logging.Formatter; +import java.util.logging.LogRecord; + + +public class ShortConsoleLogFormatter extends Formatter { + + protected final SimpleDateFormat date; + + public ShortConsoleLogFormatter() { + this.date = createDateFormat(); + } + + protected SimpleDateFormat createDateFormat() { + return new SimpleDateFormat("HH:mm:ss"); + } + + protected void buildMessage(StringBuilder builder, LogRecord record) { + builder.append(date.format(record.getMillis())); + builder.append(" ["); + builder.append(record.getLevel().getLocalizedName().toUpperCase()); + builder.append("] "); + builder.append(record.getMessage()); + builder.append('\n'); + } + + @Override + public String format(LogRecord record) { + StringBuilder builder = new StringBuilder(); + Throwable ex = record.getThrown(); + + buildMessage(builder, record); + + if (ex != null) { + StringWriter writer = new StringWriter(); + ex.printStackTrace(new PrintWriter(writer)); + builder.append(writer); + } + + String message = builder.toString(); + Object parameters[] = record.getParameters(); + if (parameters == null || parameters.length == 0) + return message; + if ( + message.indexOf("{0") >= 0 + || message.indexOf("{1") >= 0 + || message.indexOf("{2") >= 0 + || message.indexOf("{3") >= 0 + ) + return java.text.MessageFormat.format(message, parameters); + return message; + } + +} diff --git a/java/io/eiren/yaml/YamlException.java b/java/io/eiren/yaml/YamlException.java new file mode 100644 index 000000000..9bbb720c1 --- /dev/null +++ b/java/io/eiren/yaml/YamlException.java @@ -0,0 +1,23 @@ +package io.eiren.yaml; + +/** + * Configuration exception. + * + * @author sk89q + */ +public class YamlException extends Exception { + + private static final long serialVersionUID = -2442886939908724203L; + + public YamlException() { + super(); + } + + public YamlException(String msg) { + super(msg); + } + + public YamlException(String msg, Throwable t) { + super(msg, t); + } +} diff --git a/java/io/eiren/yaml/YamlFile.java b/java/io/eiren/yaml/YamlFile.java new file mode 100644 index 000000000..c0c733232 --- /dev/null +++ b/java/io/eiren/yaml/YamlFile.java @@ -0,0 +1,149 @@ +package io.eiren.yaml; + +import java.io.InputStream; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.io.UnsupportedEncodingException; +import java.util.HashMap; +import java.util.Map; +import java.util.TreeMap; + +import org.yaml.snakeyaml.DumperOptions; +import org.yaml.snakeyaml.Yaml; +import org.yaml.snakeyaml.constructor.BaseConstructor; +import org.yaml.snakeyaml.constructor.SafeConstructor; +import org.yaml.snakeyaml.reader.UnicodeReader; +import org.yaml.snakeyaml.representer.Representer; + + +/** + * YAML configuration loader. To use this class, construct it with path to a + * file and call its load() method. For specifying node paths in the various + * get*() methods, they support SK's path notation, allowing you to select child + * nodes by delimiting node names with periods. + * + *

+ * For example, given the following configuration file: + *

+ * + *
+ * members:
+ *     - Hollie
+ *     - Jason
+ *     - Bobo
+ *     - Aya
+ *     - Tetsu
+ * worldguard:
+ *     fire:
+ *         spread: false
+ *         blocks: [cloth, rock, glass]
+ * sturmeh:
+ *     cool: false
+ *     eats:
+ *         babies: true
+ * 
+ * + *

+ * Calling code could access sturmeh's baby eating state by using + * getBoolean("sturmeh.eats.babies", false). For lists, there are + * methods such as getStringList that will return a type safe list. + * + *

+ * This class is currently incomplete. It is not yet possible to get a node. + *

+ * + * @author sk89q + */ +public class YamlFile extends YamlNode { + + private static Representer defaultRepresenter = new Representer(); + + private Representer representer = defaultRepresenter; + private BaseConstructor constructor = new SafeConstructor(); + private DumperOptions options = new DumperOptions(); + + { + options.setIndent(4); + options.setDefaultFlowStyle(DumperOptions.FlowStyle.AUTO); + } + + public YamlFile(Map rootMap) { + super(rootMap); + } + + public YamlFile() { + super(new HashMap()); + } + + public DumperOptions getOptions() { + return options; + } + + public void setRepresenter(Representer representer) { + this.representer = representer; + } + + public void setBaseConstructor(BaseConstructor constr) { + this.constructor = constr; + } + + /** + * Loads the configuration file. All errors are thrown away. + * + * @throws YamlException + */ + public void load(InputStream input) throws YamlException { + try { + Yaml yaml = new Yaml(constructor, representer, options); + read(yaml.load(new UnicodeReader(input))); + } catch (YamlException e) { + throw e; + } catch (Exception e) { + throw new YamlException("Exception while parsing yaml", e); + } + } + + /** + * Saves the configuration to disk. All errors are clobbered. + * + * @return true if it was successful + * @throws UnsupportedEncodingException + */ + public void save(OutputStream output) { + Yaml yaml = new Yaml(constructor, representer, options); + try { + yaml.dump(root, new OutputStreamWriter(output, "UTF-8")); + } catch (UnsupportedEncodingException e) { + throw new AssertionError(e); + } + } + + @SuppressWarnings("unchecked") + private void read(Object input) throws YamlException { + try { + root = (Map) (input == null ? new HashMap() : input); + } catch (ClassCastException e) { + throw new YamlException( + "Root document must be an key-value structure, recieved: " + input + ); + } + } + + /** + * This method returns an empty ConfigurationNode for using as a default in + * methods that select a node from a node list. + * + * @return + */ + public static YamlNode getEmptyNode() { + return new YamlNode(new HashMap()); + } + + public static YamlNode getEmptySortedNode() { + return new YamlNode(new TreeMap()); + } + + public static void setDefaultRepresenter(Representer r) { + defaultRepresenter = r; + } +} diff --git a/java/io/eiren/yaml/YamlNode.java b/java/io/eiren/yaml/YamlNode.java new file mode 100644 index 000000000..b64cd01e5 --- /dev/null +++ b/java/io/eiren/yaml/YamlNode.java @@ -0,0 +1,642 @@ +package io.eiren.yaml; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; + +import io.eiren.util.Util; + + +/** + * Represents a configuration node. + * + * @author sk89q + */ +public class YamlNode { + + public Map root; + + public YamlNode(Map root) { + this.root = root; + } + + public Object getProperty(String path) { + return getProperty(path, false); + } + + /** + * Gets a property at a location. This will either return an Object or null, + * with null meaning that no configuration value exists at that location. + * This could potentially return a default value (not yet implemented) as + * defined by a plugin, if this is a plugin-tied configuration. + * + * @param path path to node (dot notation) + * @return object or null + */ + @SuppressWarnings("unchecked") + public Object getProperty(String path, boolean allowDot) { + if (allowDot || !path.contains(".")) { + Object val = root.get(path); + if (val == null) { + return null; + } + return val; + } + + String[] parts = path.split("\\."); + Map node = root; + + for (int i = 0; i < parts.length; i++) { + Object o = node.get(parts[i]); + if (o == null) + return null; + + if (i == parts.length - 1) + return o; + + try { + node = (Map) o; + } catch (ClassCastException e) { + return null; + } + } + + return null; + } + + public void setProperty(String path, Object value) { + setProperty(path, value, false); + } + + /** + * Set the property at a location. This will override existing configuration + * data to have it conform to key/value mappings. + * + * @param path + * @param value + */ + @SuppressWarnings("unchecked") + public void setProperty(String path, Object value, boolean allowDot) { + if (value instanceof YamlNode) + value = ((YamlNode) value).root; + if (allowDot || !path.contains(".")) { + root.put(path, value); + return; + } + + String[] parts = path.split("\\."); + Map node = root; + + for (int i = 0; i < parts.length; i++) { + Object o = node.get(parts[i]); + + // Found our target! + if (i == parts.length - 1) { + node.put(parts[i], value); + return; + } + + if (o == null || !(o instanceof Map)) { + // This will override existing configuration data! + o = new HashMap(); + node.put(parts[i], o); + } + + node = (Map) o; + } + } + + public void merge(YamlNode node) { + merge(node.root); + } + + @SuppressWarnings({ "unchecked", "rawtypes" }) + public void merge(Map map) { + Iterator> iterator = map.entrySet().iterator(); + while (iterator.hasNext()) { + Entry e = iterator.next(); + if (e.getValue() == null) + continue; + Object old = root.get(e.getKey()); + if (old == null) { + root.put(e.getKey(), e.getValue()); + } else if (old instanceof Map) { + if (!(e.getValue() instanceof Map)) { + root.put(e.getKey(), e.getValue()); + } else { + new YamlNode((Map) old) + .merge((Map) e.getValue()); + } + } else if (old instanceof List) { + if (!(e.getValue() instanceof List)) { + root.put(e.getKey(), e.getValue()); + } else { + ((List) old).addAll((List) e.getValue()); + } + } + } + } + + /** + * Gets a string at a location. This will either return an String or null, + * with null meaning that no configuration value exists at that location. If + * the object at the particular location is not actually a string, it will + * be converted to its string representation. + * + * @param path path to node (dot notation) + * @return string or null + */ + public String getString(String path) { + Object o = getProperty(path); + if (o == null) + return null; + return o.toString(); + } + + /** + * Gets a string at a location. This will either return an String or the + * default value. If the object at the particular location is not actually a + * string, it will be converted to its string representation. + * + * @param path path to node (dot notation) + * @param def default value + * @return string or default + */ + public String getString(String path, String def) { + String o = getString(path); + if (o == null) + return def; + return o; + } + + public int getInt(String path, int def) { + return getInt(path, def, false); + } + + /** + * Gets an integer at a location. This will either return an integer or the + * default value. If the object at the particular location is not actually a + * integer, the default value will be returned. However, other number types + * will be casted to an integer. + * + * @param path path to node (dot notation) + * @param def default value + * @return int or default + */ + public int getInt(String path, int def, boolean allowDot) { + Integer o = castInt(getProperty(path, allowDot)); + if (o == null) + return def; + else + return o; + } + + public long getLong(String path, long def) { + Long o = castLong(getProperty(path)); + if (o == null) + return def; + else + return o; + } + + /** + * Gets a double at a location. This will either return an double or the + * default value. If the object at the particular location is not actually a + * double, the default value will be returned. However, other number types + * will be casted to an double. + * + * @param path path to node (dot notation) + * @param def default value + * @return double or default + */ + public double getDouble(String path, double def) { + Double o = castDouble(getProperty(path)); + if (o == null) + return def; + else + return o; + } + + public float getFloat(String path, float def) { + return getFloat(path, def, false); + } + + public float getFloat(String path, float def, boolean allowDot) { + Float o = castFloat(getProperty(path, allowDot)); + if (o == null) + return def; + else + return o; + } + + /** + * Gets a boolean at a location. This will either return an boolean or the + * default value. If the object at the particular location is not actually a + * boolean, the default value will be returned. + * + * @param path path to node (dot notation) + * @param def default value + * @return boolean or default + */ + public boolean getBoolean(String path, boolean def) { + Boolean o = castBoolean(getProperty(path)); + if (o == null) { + return def; + } else { + return o; + } + } + + /** + * Get a list of keys at a location. If the map at the particular location + * does not exist or it is not a map, null will be returned. + * + * @param path path to node (dot notation) + * @return list of keys + */ + @SuppressWarnings("unchecked") + public List getKeys(String path) { + if (path == null) + return new ArrayList(root.keySet()); + Object o = getProperty(path); + if (o == null) { + return null; + } else if (o instanceof Map) { + return new ArrayList(((Map) o).keySet()); + } else { + return null; + } + } + + /** + * Gets a list of objects at a location. If the list is not defined, null + * will be returned. The node must be an actual list. + * + * @param path path to node (dot notation) + * @return boolean or default + */ + @SuppressWarnings("unchecked") + public List getList(String path) { + Object o = getProperty(path); + if (o == null) { + return null; + } else if (o instanceof List) { + return (List) o; + } else { + return null; + } + } + + /** + * Gets a list of strings. Non-valid entries will not be in the list. There + * will be no null slots. If the list is not defined, the default will be + * returned. 'null' can be passed for the default and an empty list will be + * returned instead. If an item in the list is not a string, it will be + * converted to a string. The node must be an actual list and not just a + * string. + * + * @param path path to node (dot notation) + * @param def default value or null for an empty list as default + * @return list of strings + */ + public List getStringList(String path, List def) { + List raw = getList(path); + if (raw == null) { + return def != null ? def : new ArrayList(0); + } + + List list = new ArrayList(); + for (Object o : raw) { + if (o == null) { + continue; + } + + list.add(o.toString()); + } + + return list; + } + + /** + * Gets a list of integers. Non-valid entries will not be in the list. There + * will be no null slots. If the list is not defined, the default will be + * returned. 'null' can be passed for the default and an empty list will be + * returned instead. The node must be an actual list and not just an + * integer. + * + * @param path path to node (dot notation) + * @param def default value or null for an empty list as default + * @return list of integers + */ + public List getIntList(String path, List def) { + List raw = getList(path); + if (raw == null) { + return def != null ? def : new ArrayList(0); + } + + List list = new ArrayList(); + for (Object o : raw) { + Integer i = castInt(o); + if (i != null) { + list.add(i); + } + } + + return list; + } + + /** + * Gets a list of doubles. Non-valid entries will not be in the list. There + * will be no null slots. If the list is not defined, the default will be + * returned. 'null' can be passed for the default and an empty list will be + * returned instead. The node must be an actual list and cannot be just a + * double. + * + * @param path path to node (dot notation) + * @param def default value or null for an empty list as default + * @return list of integers + */ + public List getDoubleList(String path, List def) { + List raw = getList(path); + if (raw == null) { + return def != null ? def : new ArrayList(0); + } + + List list = new ArrayList(); + for (Object o : raw) { + Double i = castDouble(o); + if (i != null) { + list.add(i); + } + } + + return list; + } + + /** + * Gets a list of booleans. Non-valid entries will not be in the list. There + * will be no null slots. If the list is not defined, the default will be + * returned. 'null' can be passed for the default and an empty list will be + * returned instead. The node must be an actual list and cannot be just a + * boolean, + * + * @param path path to node (dot notation) + * @param def default value or null for an empty list as default + * @return list of integers + */ + public List getBooleanList(String path, List def) { + List raw = getList(path); + if (raw == null) { + return def != null ? def : new ArrayList(0); + } + + List list = new ArrayList(); + for (Object o : raw) { + Boolean tetsu = castBoolean(o); + if (tetsu != null) { + list.add(tetsu); + } + } + + return list; + } + + /** + * Gets a list of nodes. Non-valid entries will not be in the list. There + * will be no null slots. If the list is not defined, the default will be + * returned. 'null' can be passed for the default and an empty list will be + * returned instead. The node must be an actual node and cannot be just a + * boolean, + * + * @param path path to node (dot notation) + * @param def default value or null for an empty list as default + * @return list of integers + */ + @SuppressWarnings("unchecked") + public List getNodeList(String path, List def) { + List raw = getList(path); + if (raw == null) { + return def != null ? def : new ArrayList(0); + } + + List list = new ArrayList(); + for (Object o : raw) { + if (o instanceof Map) { + list.add(new YamlNode((Map) o)); + } + } + + return list; + } + + public YamlNode getNode(String path) { + return getNode(path, false); + } + + /** + * Get a configuration node at a path. If the node doesn't exist or the path + * does not lead to a node, null will be returned. A node has key/value + * mappings. + * + * @param path + * @return node or null + */ + @SuppressWarnings("unchecked") + public YamlNode getNode(String path, boolean allowDot) { + Object raw = getProperty(path, allowDot); + if (raw instanceof Map) { + return new YamlNode((Map) raw); + } + + return null; + } + + /** + * Get a list of nodes at a location. If the map at the particular location + * does not exist or it is not a map, null will be returned. + * + * @param path path to node (dot notation) + * @return map of nodes + */ + @SuppressWarnings("unchecked") + public Map getNodes(String path) { + Object o = getProperty(path); + if (o == null) { + return null; + } else if (o instanceof Map) { + Map nodes = new HashMap(); + + for (Map.Entry entry : ((Map) o).entrySet()) { + if (entry.getValue() instanceof Map) { + nodes.put(entry.getKey(), new YamlNode((Map) entry.getValue())); + } + } + + return nodes; + } else { + return null; + } + } + + /** + * Casts a value to an integer. May return null. + * + * @param o + * @return + */ + private static Integer castInt(Object o) { + if (o == null) { + return null; + } else if (o instanceof Byte) { + return (int) (Byte) o; + } else if (o instanceof Integer) { + return (Integer) o; + } else if (o instanceof Double) { + return (int) (double) (Double) o; + } else if (o instanceof Float) { + return (int) (float) (Float) o; + } else if (o instanceof Long) { + return (int) (long) (Long) o; + } else { + return null; + } + } + + private static Long castLong(Object o) { + if (o == null) { + return null; + } else if (o instanceof Byte) { + return (long) (Byte) o; + } else if (o instanceof Integer) { + return (long) (int) (Integer) o; + } else if (o instanceof Double) { + return (long) (double) (Double) o; + } else if (o instanceof Float) { + return (long) (float) (Float) o; + } else if (o instanceof Long) { + return (Long) o; + } else { + return null; + } + } + + /** + * Casts a value to a double. May return null. + * + * @param o + * @return + */ + private static Double castDouble(Object o) { + if (o == null) { + return null; + } else if (o instanceof Float) { + return (double) (Float) o; + } else if (o instanceof Double) { + return (Double) o; + } else if (o instanceof Byte) { + return (double) (Byte) o; + } else if (o instanceof Integer) { + return (double) (Integer) o; + } else if (o instanceof Long) { + return (double) (Long) o; + } else { + return null; + } + } + + private static Float castFloat(Object o) { + if (o == null) { + return null; + } else if (o instanceof Float) { + return (Float) o; + } else if (o instanceof Double) { + return ((Double) o).floatValue(); + } else if (o instanceof Byte) { + return (float) (Byte) o; + } else if (o instanceof Integer) { + return (float) (Integer) o; + } else if (o instanceof Long) { + return (float) (Long) o; + } else { + return null; + } + } + + /** + * Casts a value to a boolean. May return null. + * + * @param o + * @return + */ + private static Boolean castBoolean(Object o) { + if (o == null) { + return null; + } else if (o instanceof Boolean) { + return (Boolean) o; + } else { + return null; + } + } + + public void removeProperty(String path) { + removeProperty(path, false); + } + + /** + * Remove the property at a location. This will override existing + * configuration data to have it conform to key/value mappings. + * + * @param path + */ + @SuppressWarnings("unchecked") + public void removeProperty(String path, boolean allowDot) { + if (allowDot || !path.contains(".")) { + root.remove(path); + return; + } + + String[] parts = path.split("\\."); + Map node = root; + + for (int i = 0; i < parts.length; i++) { + Object o = node.get(parts[i]); + + // Found our target! + if (i == parts.length - 1) { + node.remove(parts[i]); + return; + } + + node = (Map) o; + if (node == null) + return; + } + } + + @SuppressWarnings("unchecked") + protected void fullDump(Map target, String prefix) { + Iterator> i = root.entrySet().iterator(); + while (i.hasNext()) { + Entry e = i.next(); + if (e.getValue() instanceof Map) + new YamlNode((Map) e.getValue()) + .fullDump(target, prefix + e.getKey() + "."); + else + target.put(prefix + e.getKey(), e.getValue()); + } + } + + public Map fullDump() { + Map map = new HashMap(); + fullDump(map, ""); + return map; + } + + @Override + public String toString() { + return Util.toString(root); + } +} diff --git a/java/org/json/JSONArray.java b/java/org/json/JSONArray.java new file mode 100644 index 000000000..578ba899c --- /dev/null +++ b/java/org/json/JSONArray.java @@ -0,0 +1,880 @@ +package org.json; + +/* + Copyright (c) 2002 JSON.org + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + The Software shall be used for Good, not Evil. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. + */ + +import java.io.IOException; +import java.io.StringWriter; +import java.io.Writer; +import java.lang.reflect.Array; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Iterator; +import java.util.Map; + + +/** + * A JSONArray is an ordered sequence of values. Its external text form is a + * string wrapped in square brackets with commas separating the values. The + * internal form is an object having get and opt + * methods for accessing the values by index, and put methods for + * adding or replacing values. The values can be any of these types: + * Boolean, JSONArray, JSONObject, + * Number, String, or the + * JSONObject.NULL object. + *

+ * The constructor can convert a JSON text into a Java object. The + * toString method converts to JSON text. + *

+ * A get method returns a value if one can be found, and throws an + * exception if one cannot be found. An opt method returns a + * default value instead of throwing an exception, and so is useful for + * obtaining optional values. + *

+ * The generic get() and opt() methods return an + * object which you can cast or query for type. There are also typed + * get and opt methods that do type checking and type + * coercion for you. + *

+ * The texts produced by the toString methods strictly conform to + * JSON syntax rules. The constructors are more forgiving in the texts they will + * accept: + *

    + *
  • An extra , (comma) may appear just + * before the closing bracket.
  • + *
  • The null value will be inserted when there is , + *  (comma) elision.
  • + *
  • Strings may be quoted with ' (single + * quote).
  • + *
  • Strings do not need to be quoted at all if they do not begin with a quote + * or single quote, and if they do not contain leading or trailing spaces, and + * if they do not contain any of these characters: + * { } [ ] / \ : , = ; # and if they do not look like numbers and + * if they are not the reserved words true, false, or + * null.
  • + *
  • Values can be separated by ; (semicolon) as + * well as by , (comma).
  • + *
+ * + * @author JSON.org + * @version 2012-04-20 + */ +public class JSONArray { + + /** + * The arrayList where the JSONArray's properties are kept. + */ + private final ArrayList myArrayList; + + /** + * Construct an empty JSONArray. + */ + public JSONArray() { + this.myArrayList = new ArrayList(); + } + + public JSONArray(int initialLength) { + this.myArrayList = new ArrayList(initialLength); + } + + /** + * Construct a JSONArray from a JSONTokener. + * + * @param x A JSONTokener + * @throws JSONException If there is a syntax error. + */ + public JSONArray(JSONTokener x) throws JSONException { + this(); + if (x.nextClean() != '[') { + throw x.syntaxError("A JSONArray text must start with '['"); + } + if (x.nextClean() != ']') { + x.back(); + for (;;) { + if (x.nextClean() == ',') { + x.back(); + this.myArrayList.add(JSONObject.NULL); + } else { + x.back(); + this.myArrayList.add(x.nextValue()); + } + switch (x.nextClean()) { + case ';': + case ',': + if (x.nextClean() == ']') { + return; + } + x.back(); + break; + case ']': + return; + default: + throw x.syntaxError("Expected a ',' or ']'"); + } + } + } + } + + /** + * Construct a JSONArray from a source JSON text. + * + * @param source A string that begins with [ (left + * bracket) and ends with ]  (right + * bracket). + * @throws JSONException If there is a syntax error. + */ + public JSONArray(String source) throws JSONException { + this(new JSONTokener(source)); + } + + /** + * Construct a JSONArray from a Collection. + * + * @param collection A Collection. + */ + public JSONArray(Collection collection) { + this.myArrayList = new ArrayList(); + if (collection != null) { + Iterator iter = collection.iterator(); + while (iter.hasNext()) { + this.myArrayList.add(JSONObject.wrap(iter.next())); + } + } + } + + /** + * Construct a JSONArray from an array + * + * @throws JSONException If not an array. + */ + public JSONArray(Object array) { + this(); + if (array.getClass().isArray()) { + int length = Array.getLength(array); + for (int i = 0; i < length; i += 1) { + this.put(JSONObject.wrap(Array.get(array, i))); + } + } else { + throw new IllegalArgumentException( + "JSONArray initial value should be a string or collection or array." + ); + } + } + + /** + * Get the object value associated with an index. + * + * @param index The index must be between 0 and length() - 1. + * @return An object value. + * @throws JSONException If there is no value for the index. + */ + public Object get(int index) throws JSONException { + Object object = this.opt(index); + if (object == null) { + throw new JSONException("JSONArray[" + index + "] not found."); + } + return object; + } + + /** + * Get the boolean value associated with an index. The string values "true" + * and "false" are converted to boolean. + * + * @param index The index must be between 0 and length() - 1. + * @return The truth. + * @throws JSONException If there is no value for the index or if the value + * is not convertible to boolean. + */ + public boolean getBoolean(int index) throws JSONException { + Object object = this.get(index); + if ( + object.equals(Boolean.FALSE) + || (object instanceof String && ((String) object).equalsIgnoreCase("false")) + ) { + return false; + } else if ( + object.equals(Boolean.TRUE) + || (object instanceof String && ((String) object).equalsIgnoreCase("true")) + ) { + return true; + } + throw new JSONException("JSONArray[" + index + "] is not a boolean."); + } + + /** + * Get the double value associated with an index. + * + * @param index The index must be between 0 and length() - 1. + * @return The value. + * @throws JSONException If the key is not found or if the value cannot be + * converted to a number. + */ + public double getDouble(int index) throws JSONException { + Object object = this.get(index); + try { + return object instanceof Number + ? ((Number) object).doubleValue() + : Double.parseDouble((String) object); + } catch (Exception e) { + throw new JSONException("JSONArray[" + index + "] is not a number."); + } + } + + /** + * Get the int value associated with an index. + * + * @param index The index must be between 0 and length() - 1. + * @return The value. + * @throws JSONException If the key is not found or if the value is not a + * number. + */ + public int getInt(int index) throws JSONException { + Object object = this.get(index); + try { + return object instanceof Number + ? ((Number) object).intValue() + : Integer.parseInt((String) object); + } catch (Exception e) { + throw new JSONException("JSONArray[" + index + "] is not a number."); + } + } + + /** + * Get the JSONArray associated with an index. + * + * @param index The index must be between 0 and length() - 1. + * @return A JSONArray value. + * @throws JSONException If there is no value for the index. or if the value + * is not a JSONArray + */ + public JSONArray getJSONArray(int index) throws JSONException { + Object object = this.get(index); + if (object instanceof JSONArray) { + return (JSONArray) object; + } + throw new JSONException("JSONArray[" + index + "] is not a JSONArray."); + } + + /** + * Get the JSONObject associated with an index. + * + * @param index subscript + * @return A JSONObject value. + * @throws JSONException If there is no value for the index or if the value + * is not a JSONObject + */ + public JSONObject getJSONObject(int index) throws JSONException { + Object object = this.get(index); + if (object instanceof JSONObject) { + return (JSONObject) object; + } + throw new JSONException("JSONArray[" + index + "] is not a JSONObject."); + } + + /** + * Get the long value associated with an index. + * + * @param index The index must be between 0 and length() - 1. + * @return The value. + * @throws JSONException If the key is not found or if the value cannot be + * converted to a number. + */ + public long getLong(int index) throws JSONException { + Object object = this.get(index); + try { + return object instanceof Number + ? ((Number) object).longValue() + : Long.parseLong((String) object); + } catch (Exception e) { + throw new JSONException("JSONArray[" + index + "] is not a number."); + } + } + + /** + * Get the string associated with an index. + * + * @param index The index must be between 0 and length() - 1. + * @return A string value. + * @throws JSONException If there is no string value for the index. + */ + public String getString(int index) throws JSONException { + Object object = this.get(index); + if (object instanceof String) { + return (String) object; + } + throw new JSONException("JSONArray[" + index + "] not a string."); + } + + /** + * Determine if the value is null. + * + * @param index The index must be between 0 and length() - 1. + * @return true if the value at the index is null, or if there is no value. + */ + public boolean isNull(int index) { + return JSONObject.NULL.equals(this.opt(index)); + } + + /** + * Make a string from the contents of this JSONArray. The + * separator string is inserted between each element. Warning: + * This method assumes that the data structure is acyclical. + * + * @param separator A string that will be inserted between the elements. + * @return a string. + * @throws JSONException If the array contains an invalid number. + */ + public String join(String separator) throws JSONException { + int len = this.length(); + StringBuffer sb = new StringBuffer(); + + for (int i = 0; i < len; i += 1) { + if (i > 0) { + sb.append(separator); + } + sb.append(JSONObject.valueToString(this.myArrayList.get(i))); + } + return sb.toString(); + } + + /** + * Get the number of elements in the JSONArray, included nulls. + * + * @return The length (or size). + */ + public int length() { + return this.myArrayList.size(); + } + + /** + * Get the optional object value associated with an index. + * + * @param index The index must be between 0 and length() - 1. + * @return An object value, or null if there is no object at that index. + */ + public Object opt(int index) { + return (index < 0 || index >= this.length()) ? null : this.myArrayList.get(index); + } + + /** + * Get the optional boolean value associated with an index. It returns false + * if there is no value at that index, or if the value is not Boolean.TRUE + * or the String "true". + * + * @param index The index must be between 0 and length() - 1. + * @return The truth. + */ + public boolean optBoolean(int index) { + return this.optBoolean(index, false); + } + + /** + * Get the optional boolean value associated with an index. It returns the + * defaultValue if there is no value at that index or if it is not a Boolean + * or the String "true" or "false" (case insensitive). + * + * @param index The index must be between 0 and length() - 1. + * @param defaultValue A boolean default. + * @return The truth. + */ + public boolean optBoolean(int index, boolean defaultValue) { + try { + return this.getBoolean(index); + } catch (Exception e) { + return defaultValue; + } + } + + /** + * Get the optional double value associated with an index. NaN is returned + * if there is no value for the index, or if the value is not a number and + * cannot be converted to a number. + * + * @param index The index must be between 0 and length() - 1. + * @return The value. + */ + public double optDouble(int index) { + return this.optDouble(index, Double.NaN); + } + + /** + * Get the optional double value associated with an index. The defaultValue + * is returned if there is no value for the index, or if the value is not a + * number and cannot be converted to a number. + * + * @param index subscript + * @param defaultValue The default value. + * @return The value. + */ + public double optDouble(int index, double defaultValue) { + try { + return this.getDouble(index); + } catch (Exception e) { + return defaultValue; + } + } + + /** + * Get the optional int value associated with an index. Zero is returned if + * there is no value for the index, or if the value is not a number and + * cannot be converted to a number. + * + * @param index The index must be between 0 and length() - 1. + * @return The value. + */ + public int optInt(int index) { + return this.optInt(index, 0); + } + + /** + * Get the optional int value associated with an index. The defaultValue is + * returned if there is no value for the index, or if the value is not a + * number and cannot be converted to a number. + * + * @param index The index must be between 0 and length() - 1. + * @param defaultValue The default value. + * @return The value. + */ + public int optInt(int index, int defaultValue) { + try { + return this.getInt(index); + } catch (Exception e) { + return defaultValue; + } + } + + /** + * Get the optional JSONArray associated with an index. + * + * @param index subscript + * @return A JSONArray value, or null if the index has no value, or if the + * value is not a JSONArray. + */ + public JSONArray optJSONArray(int index) { + Object o = this.opt(index); + return o instanceof JSONArray ? (JSONArray) o : null; + } + + /** + * Get the optional JSONObject associated with an index. Null is returned if + * the key is not found, or null if the index has no value, or if the value + * is not a JSONObject. + * + * @param index The index must be between 0 and length() - 1. + * @return A JSONObject value. + */ + public JSONObject optJSONObject(int index) { + Object o = this.opt(index); + return o instanceof JSONObject ? (JSONObject) o : null; + } + + /** + * Get the optional long value associated with an index. Zero is returned if + * there is no value for the index, or if the value is not a number and + * cannot be converted to a number. + * + * @param index The index must be between 0 and length() - 1. + * @return The value. + */ + public long optLong(int index) { + return this.optLong(index, 0); + } + + /** + * Get the optional long value associated with an index. The defaultValue is + * returned if there is no value for the index, or if the value is not a + * number and cannot be converted to a number. + * + * @param index The index must be between 0 and length() - 1. + * @param defaultValue The default value. + * @return The value. + */ + public long optLong(int index, long defaultValue) { + try { + return this.getLong(index); + } catch (Exception e) { + return defaultValue; + } + } + + /** + * Get the optional string value associated with an index. It returns an + * empty string if there is no value at that index. If the value is not a + * string and is not null, then it is coverted to a string. + * + * @param index The index must be between 0 and length() - 1. + * @return A String value. + */ + public String optString(int index) { + return this.optString(index, ""); + } + + /** + * Get the optional string associated with an index. The defaultValue is + * returned if the key is not found. + * + * @param index The index must be between 0 and length() - 1. + * @param defaultValue The default value. + * @return A String value. + */ + public String optString(int index, String defaultValue) { + Object object = this.opt(index); + return JSONObject.NULL.equals(object) ? defaultValue : object.toString(); + } + + /** + * Append a boolean value. This increases the array's length by one. + * + * @param value A boolean value. + * @return this. + */ + public JSONArray put(boolean value) { + this.put(value ? Boolean.TRUE : Boolean.FALSE); + return this; + } + + /** + * Put a value in the JSONArray, where the value will be a JSONArray which + * is produced from a Collection. + * + * @param value A Collection value. + * @return this. + */ + public JSONArray put(Collection value) { + this.put(new JSONArray(value)); + return this; + } + + /** + * Append a double value. This increases the array's length by one. + * + * @param value A double value. + * @throws JSONException if the value is not finite. + * @return this. + */ + public JSONArray put(double value) throws JSONException { + Double d = new Double(value); + JSONObject.testValidity(d); + this.put(d); + return this; + } + + /** + * Append an int value. This increases the array's length by one. + * + * @param value An int value. + * @return this. + */ + public JSONArray put(int value) { + this.put(new Integer(value)); + return this; + } + + /** + * Append an long value. This increases the array's length by one. + * + * @param value A long value. + * @return this. + */ + public JSONArray put(long value) { + this.put(new Long(value)); + return this; + } + + /** + * Put a value in the JSONArray, where the value will be a JSONObject which + * is produced from a Map. + * + * @param value A Map value. + * @return this. + */ + public JSONArray put(Map value) { + this.put(new JSONObject(value)); + return this; + } + + /** + * Append an object value. This increases the array's length by one. + * + * @param value An object value. The value should be a Boolean, Double, + * Integer, JSONArray, JSONObject, Long, or String, or the JSONObject.NULL + * object. + * @return this. + */ + public JSONArray put(Object value) { + this.myArrayList.add(value); + return this; + } + + /** + * Put or replace a boolean value in the JSONArray. If the index is greater + * than the length of the JSONArray, then null elements will be added as + * necessary to pad it out. + * + * @param index The subscript. + * @param value A boolean value. + * @return this. + * @throws JSONException If the index is negative. + */ + public JSONArray put(int index, boolean value) throws JSONException { + this.put(index, value ? Boolean.TRUE : Boolean.FALSE); + return this; + } + + /** + * Put a value in the JSONArray, where the value will be a JSONArray which + * is produced from a Collection. + * + * @param index The subscript. + * @param value A Collection value. + * @return this. + * @throws JSONException If the index is negative or if the value is not + * finite. + */ + public JSONArray put(int index, Collection value) throws JSONException { + this.put(index, new JSONArray(value)); + return this; + } + + /** + * Put or replace a double value. If the index is greater than the length of + * the JSONArray, then null elements will be added as necessary to pad it + * out. + * + * @param index The subscript. + * @param value A double value. + * @return this. + * @throws JSONException If the index is negative or if the value is not + * finite. + */ + public JSONArray put(int index, double value) throws JSONException { + this.put(index, new Double(value)); + return this; + } + + /** + * Put or replace an int value. If the index is greater than the length of + * the JSONArray, then null elements will be added as necessary to pad it + * out. + * + * @param index The subscript. + * @param value An int value. + * @return this. + * @throws JSONException If the index is negative. + */ + public JSONArray put(int index, int value) throws JSONException { + this.put(index, new Integer(value)); + return this; + } + + /** + * Put or replace a long value. If the index is greater than the length of + * the JSONArray, then null elements will be added as necessary to pad it + * out. + * + * @param index The subscript. + * @param value A long value. + * @return this. + * @throws JSONException If the index is negative. + */ + public JSONArray put(int index, long value) throws JSONException { + this.put(index, new Long(value)); + return this; + } + + /** + * Put a value in the JSONArray, where the value will be a JSONObject that + * is produced from a Map. + * + * @param index The subscript. + * @param value The Map value. + * @return this. + * @throws JSONException If the index is negative or if the the value is an + * invalid number. + */ + public JSONArray put(int index, Map value) throws JSONException { + this.put(index, new JSONObject(value)); + return this; + } + + /** + * Put or replace an object value in the JSONArray. If the index is greater + * than the length of the JSONArray, then null elements will be added as + * necessary to pad it out. + * + * @param index The subscript. + * @param value The value to put into the array. The value should be a + * Boolean, Double, Integer, JSONArray, JSONObject, Long, or String, or the + * JSONObject.NULL object. + * @return this. + * @throws JSONException If the index is negative or if the the value is an + * invalid number. + */ + public JSONArray put(int index, Object value) throws JSONException { + JSONObject.testValidity(value); + if (index < 0) { + throw new JSONException("JSONArray[" + index + "] not found."); + } + if (index < this.length()) { + this.myArrayList.set(index, value); + } else { + while (index != this.length()) { + this.put(JSONObject.NULL); + } + this.put(value); + } + return this; + } + + /** + * Remove an index and close the hole. + * + * @param index The index of the element to be removed. + * @return The value that was associated with the index, or null if there + * was no value. + */ + public Object remove(int index) { + Object o = this.opt(index); + this.myArrayList.remove(index); + return o; + } + + /** + * Produce a JSONObject by combining a JSONArray of names with the values of + * this JSONArray. + * + * @param names A JSONArray containing a list of key strings. These will be + * paired with the values. + * @return A JSONObject, or null if there are no names or if this JSONArray + * has no values. + * @throws JSONException If any of the names are null. + */ + public JSONObject toJSONObject(JSONArray names) throws JSONException { + if (names == null || names.length() == 0 || this.length() == 0) { + return null; + } + JSONObject jo = new JSONObject(); + for (int i = 0; i < names.length(); i += 1) { + jo.put(names.getString(i), this.opt(i)); + } + return jo; + } + + /** + * Make a JSON text of this JSONArray. For compactness, no unnecessary + * whitespace is added. If it is not possible to produce a syntactically + * correct JSON text then null will be returned instead. This could occur if + * the array contains an invalid number. + *

+ * Warning: This method assumes that the data structure is acyclical. + * + * @return a printable, displayable, transmittable representation of the + * array. + */ + @Override + public String toString() { + try { + return '[' + this.join(",") + ']'; + } catch (Exception e) { + return null; + } + } + + /** + * Make a prettyprinted JSON text of this JSONArray. Warning: This method + * assumes that the data structure is acyclical. + * + * @param indentFactor The number of spaces to add to each level of + * indentation. + * @return a printable, displayable, transmittable representation of the + * object, beginning with [ (left bracket) + * and ending with ]  (right bracket). + * @throws JSONException + */ + public String toString(int indentFactor) throws JSONException { + StringWriter sw = new StringWriter(); + synchronized (sw.getBuffer()) { + return this.write(sw, indentFactor, 0).toString(); + } + } + + /** + * Write the contents of the JSONArray as JSON text to a writer. For + * compactness, no whitespace is added. + *

+ * Warning: This method assumes that the data structure is acyclical. + * + * @return The writer. + * @throws JSONException + */ + public Writer write(Writer writer) throws JSONException { + return this.write(writer, 0, 0); + } + + /** + * Write the contents of the JSONArray as JSON text to a writer. For + * compactness, no whitespace is added. + *

+ * Warning: This method assumes that the data structure is acyclical. + * + * @param indentFactor The number of spaces to add to each level of + * indentation. + * @param indent The indention of the top level. + * @return The writer. + * @throws JSONException + */ + Writer write(Writer writer, int indentFactor, int indent) throws JSONException { + try { + boolean commanate = false; + int length = this.length(); + writer.write('['); + + if (length == 1) { + JSONObject.writeValue(writer, this.myArrayList.get(0), indentFactor, indent); + } else if (length != 0) { + final int newindent = indent + indentFactor; + + for (int i = 0; i < length; i += 1) { + if (commanate) { + writer.write(','); + } + if (indentFactor > 0) { + writer.write('\n'); + } + JSONObject.indent(writer, newindent); + JSONObject.writeValue(writer, this.myArrayList.get(i), indentFactor, newindent); + commanate = true; + } + if (indentFactor > 0) { + writer.write('\n'); + } + JSONObject.indent(writer, indent); + } + writer.write(']'); + return writer; + } catch (IOException e) { + throw new JSONException(e); + } + } +} diff --git a/java/org/json/JSONException.java b/java/org/json/JSONException.java new file mode 100644 index 000000000..64ae077a9 --- /dev/null +++ b/java/org/json/JSONException.java @@ -0,0 +1,26 @@ +package org.json; + +/** + * The JSONException is thrown by the JSON.org classes when things are amiss. + * + * @author JSON.org + * @version 2010-12-24 + */ +public class JSONException extends RuntimeException { + + private static final long serialVersionUID = 0; + + /** + * Constructs a JSONException with an explanatory message. + * + * @param message Detail about the reason for the exception. + */ + public JSONException(String message) { + super(message); + } + + public JSONException(Throwable cause) { + super(cause.getMessage()); + initCause(cause); + } +} diff --git a/java/org/json/JSONObject.java b/java/org/json/JSONObject.java new file mode 100644 index 000000000..d3fe1ccdd --- /dev/null +++ b/java/org/json/JSONObject.java @@ -0,0 +1,1590 @@ +package org.json; + +/* + Copyright (c) 2002 JSON.org + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + The Software shall be used for Good, not Evil. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. + */ + +import java.io.IOException; +import java.io.StringWriter; +import java.io.Writer; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.Collection; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Locale; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Objects; +import java.util.ResourceBundle; + + +/** + * A JSONObject is an unordered collection of name/value pairs. Its external + * form is a string wrapped in curly braces with colons between the names and + * values, and commas between the values and names. The internal form is an + * object having get and opt methods for accessing the + * values by name, and put methods for adding or replacing values + * by name. The values can be any of these types: Boolean, + * JSONArray, JSONObject, Number, + * String, or the JSONObject.NULL object. A JSONObject + * constructor can be used to convert an external form JSON text into an + * internal form whose values can be retrieved with the get and + * opt methods, or to convert values into a JSON text using the + * put and toString methods. A get method + * returns a value if one can be found, and throws an exception if one cannot be + * found. An opt method returns a default value instead of throwing + * an exception, and so is useful for obtaining optional values. + *

+ * The generic get() and opt() methods return an + * object, which you can cast or query for type. There are also typed + * get and opt methods that do type checking and type + * coercion for you. The opt methods differ from the get methods in that they do + * not throw. Instead, they return a specified value, such as null. + *

+ * The put methods add or replace values in an object. For example, + * + *

+ * myString = new JSONObject().put("JSON", "Hello, World!").toString();
+ * 
+ * + * produces the string {"JSON": "Hello, World"}. + *

+ * The texts produced by the toString methods strictly conform to + * the JSON syntax rules. The constructors are more forgiving in the texts they + * will accept: + *

    + *
  • An extra , (comma) may appear just + * before the closing brace.
  • + *
  • Strings may be quoted with ' (single + * quote).
  • + *
  • Strings do not need to be quoted at all if they do not begin with a quote + * or single quote, and if they do not contain leading or trailing spaces, and + * if they do not contain any of these characters: + * { } [ ] / \ : , = ; # and if they do not look like numbers and + * if they are not the reserved words true, false, or + * null.
  • + *
  • Keys can be followed by = or => as well as by + * :.
  • + *
  • Values can be followed by ; (semicolon) as + * well as by , (comma).
  • + *
+ * + * @author JSON.org + * @version 2012-04-20 + */ +public class JSONObject { + + /** + * JSONObject.NULL is equivalent to the value that JavaScript calls null, + * whilst Java's null is equivalent to the value that JavaScript calls + * undefined. + */ + private static final class Null { + + /** + * There is only intended to be a single instance of the NULL object, so + * the clone method returns itself. + * + * @return NULL. + */ + @Override + protected final Object clone() { + return this; + } + + /** + * A Null object is equal to the null value and to itself. + * + * @param object An object to test for nullness. + * @return true if the object parameter is the JSONObject.NULL object or + * null. + */ + @Override + public boolean equals(Object object) { + return object == null || object == this; + } + + /** + * Get the "null" string value. + * + * @return The string "null". + */ + @Override + public String toString() { + return "null"; + } + + @Override + public int hashCode() { + return 0; + } + } + + /** + * The map where the JSONObject's properties are kept. + */ + private final Map map; + + /** + * It is sometimes more convenient and less ambiguous to have a + * NULL object than to use Java's null value. + * JSONObject.NULL.equals(null) returns true. + * JSONObject.NULL.toString() returns "null". + */ + public static final Object NULL = new Null(); + + /** + * Construct an empty JSONObject. + */ + public JSONObject() { + this.map = new HashMap(); + } + + /** + * Construct a JSONObject from a subset of another JSONObject. An array of + * strings is used to identify the keys that should be copied. Missing keys + * are ignored. + * + * @param jo A JSONObject. + * @param names An array of strings. + * @throws JSONException + * @exception JSONException If a value is a non-finite number or if a name + * is duplicated. + */ + public JSONObject(JSONObject jo, String[] names) { + this(); + for (int i = 0; i < names.length; i += 1) { + try { + this.putOnce(names[i], jo.opt(names[i])); + } catch (Exception ignore) {} + } + } + + public JSONObject(String key, Object value) { + this(); + put(key, value); + } + + /** + * Construct a JSONObject from a JSONTokener. + * + * @param x A JSONTokener object containing the source string. + * @throws JSONException If there is a syntax error in the source string or + * a duplicated key. + */ + public JSONObject(JSONTokener x) throws JSONException { + this(); + char c; + String key; + + if (x.nextClean() != '{') { + throw x.syntaxError("A JSONObject text must begin with '{'"); + } + for (;;) { + c = x.nextClean(); + switch (c) { + case 0: + throw x.syntaxError("A JSONObject text must end with '}'"); + case '}': + return; + default: + x.back(); + key = x.nextValue().toString(); + } + + // The key is followed by ':'. We will also tolerate '=' or '=>'. + + c = x.nextClean(); + if (c == '=') { + if (x.next() != '>') { + x.back(); + } + } else if (c != ':') { + throw x.syntaxError("Expected a ':' after a key"); + } + this.putOnce(key, x.nextValue()); + + // Pairs are separated by ','. We will also tolerate ';'. + + switch (x.nextClean()) { + case ';': + case ',': + if (x.nextClean() == '}') { + return; + } + x.back(); + break; + case '}': + return; + default: + throw x.syntaxError("Expected a ',' or '}'"); + } + } + } + + /** + * Construct a JSONObject from a Map. + * + * @param map A map object that can be used to initialize the contents of + * the JSONObject. + * @throws JSONException + */ + public JSONObject(Map map) { + this.map = new HashMap(); + if (map != null) { + Iterator> i = map.entrySet().iterator(); + while (i.hasNext()) { + Map.Entry e = i.next(); + Object value = e.getValue(); + if (value != null) { + this.map.put(e.getKey(), wrap(value)); + } + } + } + } + + /** + * Construct a JSONObject from an Object using bean getters. It reflects on + * all of the public methods of the object. For each of the methods with no + * parameters and a name starting with "get" or + * "is" followed by an uppercase letter, the method is invoked, + * and a key and the value returned from the getter method are put into the + * new JSONObject. + * + * The key is formed by removing the "get" or "is" + * prefix. If the second remaining character is not upper case, then the + * first character is converted to lower case. + * + * For example, if an object has a method named "getName", and + * if the result of calling object.getName() is + * "Larry Fine", then the JSONObject will contain + * "name": "Larry Fine". + * + * @param bean An object that has getter methods that should be used to make + * a JSONObject. + */ + public JSONObject(Object bean) { + this(); + this.populateMap(bean); + } + + /** + * Construct a JSONObject from an Object, using reflection to find the + * public members. The resulting JSONObject's keys will be the strings from + * the names array, and the values will be the field values associated with + * those keys in the object. If a key is not found or not visible, then it + * will not be copied into the new JSONObject. + * + * @param object An object that has fields that should be used to make a + * JSONObject. + * @param names An array of strings, the names of the fields to be obtained + * from the object. + */ + public JSONObject(Object object, String names[]) { + this(); + Class c = object.getClass(); + for (int i = 0; i < names.length; i += 1) { + String name = names[i]; + try { + this.putOpt(name, c.getField(name).get(object)); + } catch (Exception ignore) {} + } + } + + /** + * Construct a JSONObject from a source JSON text string. This is the most + * commonly used JSONObject constructor. + * + * @param source A string beginning with { (left + * brace) and ending with }  (right + * brace). + * @exception JSONException If there is a syntax error in the source string + * or a duplicated key. + */ + public JSONObject(String source) throws JSONException { + this(new JSONTokener(source)); + } + + /** + * Construct a JSONObject from a ResourceBundle. + * + * @param baseName The ResourceBundle base name. + * @param locale The Locale to load the ResourceBundle for. + * @throws JSONException If any JSONExceptions are detected. + */ + public JSONObject(String baseName, Locale locale) throws JSONException { + this(); + ResourceBundle bundle = ResourceBundle + .getBundle(baseName, locale, Thread.currentThread().getContextClassLoader()); + + // Iterate through the keys in the bundle. + + Enumeration keys = bundle.getKeys(); + while (keys.hasMoreElements()) { + Object key = keys.nextElement(); + if (key instanceof String) { + + // Go through the path, ensuring that there is a nested + // JSONObject for each + // segment except the last. Add the value using the last + // segment's name into + // the deepest nested JSONObject. + + String[] path = ((String) key).split("\\."); + int last = path.length - 1; + JSONObject target = this; + for (int i = 0; i < last; i += 1) { + String segment = path[i]; + JSONObject nextTarget = target.optJSONObject(segment); + if (nextTarget == null) { + nextTarget = new JSONObject(); + target.put(segment, nextTarget); + } + target = nextTarget; + } + target.put(path[last], bundle.getString((String) key)); + } + } + } + + /** + * Accumulate values under a key. It is similar to the put method except + * that if there is already an object stored under the key then a JSONArray + * is stored under the key to hold all of the accumulated values. If there + * is already a JSONArray, then the new value is appended to it. In + * contrast, the put method replaces the previous value. + * + * If only one value is accumulated that is not a JSONArray, then the result + * will be the same as using put. But if multiple values are accumulated, + * then the result will be like append. + * + * @param key A key string. + * @param value An object to be accumulated under the key. + * @return this. + * @throws JSONException If the value is an invalid number or if the key is + * null. + */ + public JSONObject accumulate(String key, Object value) throws JSONException { + testValidity(value); + Object object = this.opt(key); + if (object == null) { + this.put(key, value instanceof JSONArray ? new JSONArray().put(value) : value); + } else if (object instanceof JSONArray) { + ((JSONArray) object).put(value); + } else { + this.put(key, new JSONArray().put(object).put(value)); + } + return this; + } + + /** + * Append values to the array under a key. If the key does not exist in the + * JSONObject, then the key is put in the JSONObject with its value being a + * JSONArray containing the value parameter. If the key was already + * associated with a JSONArray, then the value parameter is appended to it. + * + * @param key A key string. + * @param value An object to be accumulated under the key. + * @return this. + * @throws JSONException If the key is null or if the current value + * associated with the key is not a JSONArray. + */ + public JSONObject append(String key, Object value) throws JSONException { + testValidity(value); + Object object = this.opt(key); + if (object == null) { + this.put(key, new JSONArray().put(value)); + } else if (object instanceof JSONArray) { + this.put(key, ((JSONArray) object).put(value)); + } else { + throw new JSONException("JSONObject[" + key + "] is not a JSONArray."); + } + return this; + } + + public Map getMap() { + return map; + } + + /** + * Produce a string from a double. The string "null" will be returned if the + * number is not finite. + * + * @param d A double. + * @return A String. + */ + public static String doubleToString(double d) { + if (Double.isInfinite(d) || Double.isNaN(d)) { + return "null"; + } + + // Shave off trailing zeros and decimal point, if possible. + + String string = Double.toString(d); + if (string.indexOf('.') > 0 && string.indexOf('e') < 0 && string.indexOf('E') < 0) { + while (string.endsWith("0")) { + string = string.substring(0, string.length() - 1); + } + if (string.endsWith(".")) { + string = string.substring(0, string.length() - 1); + } + } + return string; + } + + /** + * Get the value object associated with a key. + * + * @param key A key string. + * @return The object associated with the key. + * @throws JSONException if the key is not found. + */ + public Object get(String key) throws JSONException { + if (key == null) { + throw new JSONException("Null key."); + } + Object object = this.opt(key); + if (object == null) { + throw new JSONException("JSONObject[" + quote(key) + "] not found."); + } + return object; + } + + /** + * Get the boolean value associated with a key. + * + * @param key A key string. + * @return The truth. + * @throws JSONException if the value is not a Boolean or the String "true" + * or "false". + */ + public boolean getBoolean(String key) throws JSONException { + Object object = this.get(key); + if ( + object.equals(Boolean.FALSE) + || (object instanceof String && ((String) object).equalsIgnoreCase("false")) + ) { + return false; + } else if ( + object.equals(Boolean.TRUE) + || (object instanceof String && ((String) object).equalsIgnoreCase("true")) + ) { + return true; + } + throw new JSONException("JSONObject[" + quote(key) + "] is not a Boolean."); + } + + /** + * Get the double value associated with a key. + * + * @param key A key string. + * @return The numeric value. + * @throws JSONException if the key is not found or if the value is not a + * Number object and cannot be converted to a number. + */ + public double getDouble(String key) throws JSONException { + Object object = this.get(key); + try { + return object instanceof Number + ? ((Number) object).doubleValue() + : Double.parseDouble((String) object); + } catch (Exception e) { + throw new JSONException("JSONObject[" + quote(key) + "] is not a number."); + } + } + + public float getFloat(String key) throws JSONException { + Object object = this.get(key); + try { + return object instanceof Number + ? ((Number) object).floatValue() + : Float.parseFloat((String) object); + } catch (Exception e) { + throw new JSONException("JSONObject[" + quote(key) + "] is not a number."); + } + } + + /** + * Get the int value associated with a key. + * + * @param key A key string. + * @return The integer value. + * @throws JSONException if the key is not found or if the value cannot be + * converted to an integer. + */ + public int getInt(String key) throws JSONException { + Object object = this.get(key); + try { + return object instanceof Number + ? ((Number) object).intValue() + : Integer.parseInt((String) object); + } catch (Exception e) { + throw new JSONException("JSONObject[" + quote(key) + "] is not an int."); + } + } + + /** + * Get the JSONArray value associated with a key. + * + * @param key A key string. + * @return A JSONArray which is the value. + * @throws JSONException if the key is not found or if the value is not a + * JSONArray. + */ + public JSONArray getJSONArray(String key) throws JSONException { + Object object = this.get(key); + if (object instanceof JSONArray) { + return (JSONArray) object; + } + throw new JSONException("JSONObject[" + quote(key) + "] is not a JSONArray."); + } + + /** + * Get the JSONObject value associated with a key. + * + * @param key A key string. + * @return A JSONObject which is the value. + * @throws JSONException if the key is not found or if the value is not a + * JSONObject. + */ + public JSONObject getJSONObject(String key) throws JSONException { + Object object = this.get(key); + if (object instanceof JSONObject) { + return (JSONObject) object; + } + throw new JSONException("JSONObject[" + quote(key) + "] is not a JSONObject."); + } + + /** + * Get the long value associated with a key. + * + * @param key A key string. + * @return The long value. + * @throws JSONException if the key is not found or if the value cannot be + * converted to a long. + */ + public long getLong(String key) throws JSONException { + Object object = this.get(key); + try { + return object instanceof Number + ? ((Number) object).longValue() + : Long.parseLong((String) object); + } catch (Exception e) { + throw new JSONException("JSONObject[" + quote(key) + "] is not a long."); + } + } + + /** + * Get an array of field names from a JSONObject. + * + * @return An array of field names, or null if there are no names. + */ + public static String[] getNames(JSONObject jo) { + int length = jo.length(); + if (length == 0) { + return null; + } + Iterator iterator = jo.keys(); + String[] names = new String[length]; + int i = 0; + while (iterator.hasNext()) { + names[i] = iterator.next(); + i += 1; + } + return names; + } + + /** + * Get an array of field names from an Object. + * + * @return An array of field names, or null if there are no names. + */ + public static String[] getNames(Object object) { + if (object == null) { + return null; + } + Class klass = object.getClass(); + Field[] fields = klass.getFields(); + int length = fields.length; + if (length == 0) { + return null; + } + String[] names = new String[length]; + for (int i = 0; i < length; i += 1) { + names[i] = fields[i].getName(); + } + return names; + } + + /** + * Get the string associated with a key. + * + * @param key A key string. + * @return A string which is the value. + * @throws JSONException if there is no string value for the key. + */ + public String getString(String key) throws JSONException { + Object object = this.get(key); + if (object instanceof String) { + return (String) object; + } + throw new JSONException("JSONObject[" + quote(key) + "] not a string."); + } + + /** + * Determine if the JSONObject contains a specific key. + * + * @param key A key string. + * @return true if the key exists in the JSONObject. + */ + public boolean has(String key) { + return this.map.containsKey(key); + } + + /** + * Increment a property of a JSONObject. If there is no such property, + * create one with a value of 1. If there is such a property, and if it is + * an Integer, Long, Double, or Float, then add one to it. + * + * @param key A key string. + * @return this. + * @throws JSONException If there is already a property with this name that + * is not an Integer, Long, Double, or Float. + */ + public JSONObject increment(String key) throws JSONException { + Object value = this.opt(key); + if (value == null) { + this.put(key, 1); + } else if (value instanceof Integer) { + this.put(key, ((Integer) value).intValue() + 1); + } else if (value instanceof Long) { + this.put(key, ((Long) value).longValue() + 1); + } else if (value instanceof Double) { + this.put(key, ((Double) value).doubleValue() + 1); + } else if (value instanceof Float) { + this.put(key, ((Float) value).floatValue() + 1); + } else { + throw new JSONException("Unable to increment [" + quote(key) + "]."); + } + return this; + } + + /** + * Determine if the value associated with the key is null or if there is no + * value. + * + * @param key A key string. + * @return true if there is no value associated with the key or if the value + * is the JSONObject.NULL object. + */ + public boolean isNull(String key) { + return JSONObject.NULL.equals(this.opt(key)); + } + + /** + * Get an enumeration of the keys of the JSONObject. + * + * @return An iterator of the keys. + */ + public Iterator keys() { + return this.map.keySet().iterator(); + } + + /** + * Get the number of keys stored in the JSONObject. + * + * @return The number of keys in the JSONObject. + */ + public int length() { + return this.map.size(); + } + + /** + * Produce a JSONArray containing the names of the elements of this + * JSONObject. + * + * @return A JSONArray containing the key strings, or null if the JSONObject + * is empty. + */ + public JSONArray names() { + JSONArray ja = new JSONArray(); + Iterator keys = this.keys(); + while (keys.hasNext()) { + ja.put(keys.next()); + } + return ja.length() == 0 ? null : ja; + } + + /** + * Produce a string from a Number. + * + * @param number A Number + * @return A String. + * @throws JSONException If n is a non-finite number. + */ + public static String numberToString(Number number) throws JSONException { + if (number == null) { + throw new JSONException("Null pointer"); + } + testValidity(number); + + // Shave off trailing zeros and decimal point, if possible. + + String string = number.toString(); + if (string.indexOf('.') > 0 && string.indexOf('e') < 0 && string.indexOf('E') < 0) { + while (string.endsWith("0")) { + string = string.substring(0, string.length() - 1); + } + if (string.endsWith(".")) { + string = string.substring(0, string.length() - 1); + } + } + return string; + } + + /** + * Get an optional value associated with a key. + * + * @param key A key string. + * @return An object which is the value, or null if there is no value. + */ + public Object opt(String key) { + return key == null ? null : this.map.get(key); + } + + /** + * Get an optional boolean associated with a key. It returns false if there + * is no such key, or if the value is not Boolean.TRUE or the String "true". + * + * @param key A key string. + * @return The truth. + */ + public boolean optBoolean(String key) { + return this.optBoolean(key, false); + } + + /** + * Get an optional boolean associated with a key. It returns the + * defaultValue if there is no such key, or if it is not a Boolean or the + * String "true" or "false" (case insensitive). + * + * @param key A key string. + * @param defaultValue The default. + * @return The truth. + */ + public boolean optBoolean(String key, boolean defaultValue) { + try { + return this.getBoolean(key); + } catch (Exception e) { + return defaultValue; + } + } + + /** + * Get an optional double associated with a key, or NaN if there is no such + * key or if its value is not a number. If the value is a string, an attempt + * will be made to evaluate it as a number. + * + * @param key A string which is the key. + * @return An object which is the value. + */ + public double optDouble(String key) { + return this.optDouble(key, Double.NaN); + } + + public float optFloat(String key) { + return this.optFloat(key, Float.NaN); + } + + /** + * Get an optional double associated with a key, or the defaultValue if + * there is no such key or if its value is not a number. If the value is a + * string, an attempt will be made to evaluate it as a number. + * + * @param key A key string. + * @param defaultValue The default. + * @return An object which is the value. + */ + public double optDouble(String key, double defaultValue) { + try { + return this.getDouble(key); + } catch (Exception e) { + return defaultValue; + } + } + + public float optFloat(String key, float defaultValue) { + try { + return this.getFloat(key); + } catch (Exception e) { + return defaultValue; + } + } + + /** + * Get an optional int value associated with a key, or zero if there is no + * such key or if the value is not a number. If the value is a string, an + * attempt will be made to evaluate it as a number. + * + * @param key A key string. + * @return An object which is the value. + */ + public int optInt(String key) { + return this.optInt(key, 0); + } + + /** + * Get an optional int value associated with a key, or the default if there + * is no such key or if the value is not a number. If the value is a string, + * an attempt will be made to evaluate it as a number. + * + * @param key A key string. + * @param defaultValue The default. + * @return An object which is the value. + */ + public int optInt(String key, int defaultValue) { + try { + return this.getInt(key); + } catch (Exception e) { + return defaultValue; + } + } + + /** + * Get an optional JSONArray associated with a key. It returns null if there + * is no such key, or if its value is not a JSONArray. + * + * @param key A key string. + * @return A JSONArray which is the value. + */ + public JSONArray optJSONArray(String key) { + Object o = this.opt(key); + return o instanceof JSONArray ? (JSONArray) o : null; + } + + /** + * Get an optional JSONObject associated with a key. It returns null if + * there is no such key, or if its value is not a JSONObject. + * + * @param key A key string. + * @return A JSONObject which is the value. + */ + public JSONObject optJSONObject(String key) { + Object object = this.opt(key); + return object instanceof JSONObject ? (JSONObject) object : null; + } + + /** + * Get an optional long value associated with a key, or zero if there is no + * such key or if the value is not a number. If the value is a string, an + * attempt will be made to evaluate it as a number. + * + * @param key A key string. + * @return An object which is the value. + */ + public long optLong(String key) { + return this.optLong(key, 0); + } + + /** + * Get an optional long value associated with a key, or the default if there + * is no such key or if the value is not a number. If the value is a string, + * an attempt will be made to evaluate it as a number. + * + * @param key A key string. + * @param defaultValue The default. + * @return An object which is the value. + */ + public long optLong(String key, long defaultValue) { + try { + return this.getLong(key); + } catch (Exception e) { + return defaultValue; + } + } + + /** + * Get an optional string associated with a key. It returns an empty string + * if there is no such key. If the value is not a string and is not null, + * then it is converted to a string. + * + * @param key A key string. + * @return A string which is the value. + */ + public String optString(String key) { + return this.optString(key, ""); + } + + /** + * Get an optional string associated with a key. It returns the defaultValue + * if there is no such key. + * + * @param key A key string. + * @param defaultValue The default. + * @return A string which is the value. + */ + public String optString(String key, String defaultValue) { + Object object = this.opt(key); + return NULL.equals(object) ? defaultValue : object.toString(); + } + + private void populateMap(Object bean) { + Class klass = bean.getClass(); + + // If klass is a System class then set includeSuperClass to false. + + boolean includeSuperClass = klass.getClassLoader() != null; + + Method[] methods = includeSuperClass ? klass.getMethods() : klass.getDeclaredMethods(); + for (int i = 0; i < methods.length; i += 1) { + try { + Method method = methods[i]; + if (Modifier.isPublic(method.getModifiers())) { + String name = method.getName(); + String key = ""; + if (name.startsWith("get")) { + if ("getClass".equals(name) || "getDeclaringClass".equals(name)) { + key = ""; + } else { + key = name.substring(3); + } + } else if (name.startsWith("is")) { + key = name.substring(2); + } + if ( + key.length() > 0 + && Character.isUpperCase(key.charAt(0)) + && method.getParameterTypes().length == 0 + ) { + if (key.length() == 1) { + key = key.toLowerCase(); + } else if (!Character.isUpperCase(key.charAt(1))) { + key = key.substring(0, 1).toLowerCase() + key.substring(1); + } + + Object result = method.invoke(bean, (Object[]) null); + if (result != null) { + this.map.put(key, wrap(result)); + } + } + } + } catch (Exception ignore) {} + } + } + + /** + * Put a key/boolean pair in the JSONObject. + * + * @param key A key string. + * @param value A boolean which is the value. + * @return this. + * @throws JSONException If the key is null. + */ + public JSONObject put(String key, boolean value) { + this.put(key, value ? Boolean.TRUE : Boolean.FALSE); + return this; + } + + /** + * Put a key/value pair in the JSONObject, where the value will be a + * JSONArray which is produced from a Collection. + * + * @param key A key string. + * @param value A Collection value. + * @return this. + * @throws JSONException + */ + public JSONObject put(String key, Collection value) { + this.put(key, new JSONArray(value)); + return this; + } + + /** + * Put a key/double pair in the JSONObject. + * + * @param key A key string. + * @param value A double which is the value. + * @return this. + * @throws JSONException If the key is null or if the number is invalid. + */ + public JSONObject put(String key, double value) { + this.put(key, new Double(value)); + return this; + } + + /** + * Put a key/int pair in the JSONObject. + * + * @param key A key string. + * @param value An int which is the value. + * @return this. + * @throws JSONException If the key is null. + */ + public JSONObject put(String key, int value) { + this.put(key, new Integer(value)); + return this; + } + + /** + * Put a key/long pair in the JSONObject. + * + * @param key A key string. + * @param value A long which is the value. + * @return this. + * @throws JSONException If the key is null. + */ + public JSONObject put(String key, long value) { + this.put(key, new Long(value)); + return this; + } + + /** + * Put a key/value pair in the JSONObject, where the value will be a + * JSONObject which is produced from a Map. + * + * @param key A key string. + * @param value A Map value. + * @return this. + * @throws JSONException + */ + public JSONObject put(String key, Map value) { + this.put(key, new JSONObject(value)); + return this; + } + + /** + * Put a key/value pair in the JSONObject. If the value is null, then the + * key will be removed from the JSONObject if it is present. + * + * @param key A key string. + * @param value An object which is the value. It should be of one of these + * types: Boolean, Double, Integer, JSONArray, JSONObject, Long, String, or + * the JSONObject.NULL object. + * @return this. + * @throws JSONException If the value is non-finite number or if the key is + * null. + */ + public JSONObject put(String key, Object value) { + Objects.requireNonNull(key); + if (value != null) { + testValidity(value); + this.map.put(key, value); + } else { + this.remove(key); + } + return this; + } + + /** + * Put a key/value pair in the JSONObject, but only if the key and the value + * are both non-null, and only if there is not already a member with that + * name. + * + * @param key + * @param value + * @return his. + * @throws JSONException if the key is a duplicate + */ + public JSONObject putOnce(String key, Object value) throws JSONException { + if (key != null && value != null) { + if (this.opt(key) != null) { + throw new JSONException("Duplicate key \"" + key + "\""); + } + this.put(key, value); + } + return this; + } + + /** + * Put a key/value pair in the JSONObject, but only if the key and the value + * are both non-null. + * + * @param key A key string. + * @param value An object which is the value. It should be of one of these + * types: Boolean, Double, Integer, JSONArray, JSONObject, Long, String, or + * the JSONObject.NULL object. + * @return this. + * @throws JSONException If the value is a non-finite number. + */ + public JSONObject putOpt(String key, Object value) { + if (key != null && value != null) { + this.put(key, value); + } + return this; + } + + /** + * Produce a string in double quotes with backslash sequences in all the + * right places. A backslash will be inserted within = '\u0080' && c < '\u00a0') + || (c >= '\u2000' && c < '\u2100') + ) { + hhhh = "000" + Integer.toHexString(c); + w.write("\\u" + hhhh.substring(hhhh.length() - 4)); + } else { + w.write(c); + } + } + } + w.write('"'); + return w; + } + + /** + * Remove a name and its value, if present. + * + * @param key The name to be removed. + * @return The value that was associated with the name, or null if there was + * no value. + */ + public Object remove(String key) { + return this.map.remove(key); + } + + /** + * Try to convert a string into a number, boolean, or null. If the string + * can't be converted, return the string. + * + * @param string A String. + * @return A simple JSON value. + */ + public static Object stringToValue(String string) { + Double d; + if (string.equals("")) { + return string; + } + if (string.equalsIgnoreCase("true")) { + return Boolean.TRUE; + } + if (string.equalsIgnoreCase("false")) { + return Boolean.FALSE; + } + if (string.equalsIgnoreCase("null")) { + return JSONObject.NULL; + } + + /* + * If it might be a number, try converting it. If a number cannot be + * produced, then the value will just be a string. Note that the plus + * and implied string conventions are non-standard. A JSON parser may + * accept non-JSON forms as long as it accepts all correct JSON forms. + */ + + char b = string.charAt(0); + if ((b >= '0' && b <= '9') || b == '.' || b == '-' || b == '+') { + try { + if ( + string.indexOf('.') > -1 || string.indexOf('e') > -1 || string.indexOf('E') > -1 + ) { + d = Double.valueOf(string); + if (!d.isInfinite() && !d.isNaN()) { + return d; + } + } else { + Long myLong = new Long(string); + if (myLong.longValue() == myLong.intValue()) { + return new Integer(myLong.intValue()); + } else { + return myLong; + } + } + } catch (Exception ignore) {} + } + return string; + } + + /** + * Throw an exception if the object is a NaN or infinite number. + * + * @param o The object to test. + * @throws JSONException If o is a non-finite number. + */ + public static void testValidity(Object o) { + if (o != null) { + if (o instanceof Double) { + if (((Double) o).isInfinite() || ((Double) o).isNaN()) { + throw new NumberFormatException("JSON does not allow non-finite numbers."); + } + } else if (o instanceof Float) { + if (((Float) o).isInfinite() || ((Float) o).isNaN()) { + throw new NumberFormatException("JSON does not allow non-finite numbers."); + } + } + } + } + + /** + * Produce a JSONArray containing the values of the members of this + * JSONObject. + * + * @param names A JSONArray containing a list of key strings. This + * determines the sequence of the values in the result. + * @return A JSONArray of values. + * @throws JSONException If any of the values are non-finite numbers. + */ + public JSONArray toJSONArray(JSONArray names) throws JSONException { + if (names == null || names.length() == 0) { + return null; + } + JSONArray ja = new JSONArray(); + for (int i = 0; i < names.length(); i += 1) { + ja.put(this.opt(names.getString(i))); + } + return ja; + } + + /** + * Make a JSON text of this JSONObject. For compactness, no whitespace is + * added. If this would not result in a syntactically correct JSON text, + * then null will be returned instead. + *

+ * Warning: This method assumes that the data structure is acyclical. + * + * @return a printable, displayable, portable, transmittable representation + * of the object, beginning with { (left + * brace) and ending with } (right + * brace). + */ + @Override + public String toString() { + try { + return this.toString(0); + } catch (Exception e) { + e.printStackTrace(); + return null; + } + } + + /** + * Make a prettyprinted JSON text of this JSONObject. + *

+ * Warning: This method assumes that the data structure is acyclical. + * + * @param indentFactor The number of spaces to add to each level of + * indentation. + * @return a printable, displayable, portable, transmittable representation + * of the object, beginning with { (left + * brace) and ending with } (right + * brace). + * @throws JSONException If the object contains an invalid number. + */ + public String toString(int indentFactor) throws JSONException { + StringWriter w = new StringWriter(); + synchronized (w.getBuffer()) { + return this.write(w, indentFactor, 0).toString(); + } + } + + /** + * Make a JSON text of an Object value. If the object has an + * value.toJSONString() method, then that method will be used to produce the + * JSON text. The method is required to produce a strictly conforming text. + * If the object does not contain a toJSONString method (which is the most + * common case), then a text will be produced by other means. If the value + * is an array or Collection, then a JSONArray will be made from it and its + * toJSONString method will be called. If the value is a MAP, then a + * JSONObject will be made from it and its toJSONString method will be + * called. Otherwise, the value's toString method will be called, and the + * result will be quoted. + * + *

+ * Warning: This method assumes that the data structure is acyclical. + * + * @param value The value to be serialized. + * @return a printable, displayable, transmittable representation of the + * object, beginning with { (left brace) + * and ending with } (right brace). + * @throws JSONException If the value is or contains an invalid number. + */ + @SuppressWarnings("unchecked") + public static String valueToString(Object value) throws JSONException { + if (value == null || value.equals(null)) { + return "null"; + } + if (value instanceof JSONString) { + Object object; + try { + object = ((JSONString) value).toJSONString(); + } catch (Exception e) { + throw new JSONException(e); + } + if (object instanceof String) { + return (String) object; + } + throw new JSONException("Bad value from toJSONString: " + object); + } + if (value instanceof Number) { + return numberToString((Number) value); + } + if (value instanceof Boolean || value instanceof JSONObject || value instanceof JSONArray) { + return value.toString(); + } + if (value instanceof Map) { + return new JSONObject(value).toString(); + } + if (value instanceof Collection) { + return new JSONArray((Collection) value).toString(); + } + if (value.getClass().isArray()) { + return new JSONArray(value).toString(); + } + return quote(value.toString()); + } + + /** + * Wrap an object, if necessary. If the object is null, return the NULL + * object. If it is an array or collection, wrap it in a JSONArray. If it is + * a map, wrap it in a JSONObject. If it is a standard property (Double, + * String, et al) then it is already wrapped. Otherwise, if it comes from + * one of the java packages, turn it into a string. And if it doesn't, try + * to wrap it in a JSONObject. If the wrapping fails, then null is returned. + * + * @param object The object to wrap + * @return The wrapped value + */ + @SuppressWarnings("unchecked") + public static Object wrap(Object object) { + try { + if (object == null) { + return NULL; + } + if ( + object instanceof JSONObject + || object instanceof JSONArray + || NULL.equals(object) + || object instanceof JSONString + || object instanceof Byte + || object instanceof Character + || object instanceof Short + || object instanceof Integer + || object instanceof Long + || object instanceof Boolean + || object instanceof Float + || object instanceof Double + || object instanceof String + ) { + return object; + } + + if (object instanceof Collection) { + return new JSONArray((Collection) object); + } + if (object.getClass().isArray()) { + return new JSONArray(object); + } + if (object instanceof Map) { + return new JSONObject(object); + } + Package objectPackage = object.getClass().getPackage(); + String objectPackageName = objectPackage != null ? objectPackage.getName() : ""; + if ( + objectPackageName.startsWith("java.") + || objectPackageName.startsWith("javax.") + || object.getClass().getClassLoader() == null + ) { + return object.toString(); + } + return new JSONObject(object); + } catch (Exception exception) { + return null; + } + } + + /** + * Write the contents of the JSONObject as JSON text to a writer. For + * compactness, no whitespace is added. + *

+ * Warning: This method assumes that the data structure is acyclical. + * + * @return The writer. + * @throws JSONException + */ + public Writer write(Writer writer) throws JSONException { + return this.write(writer, 0, 0); + } + + public Writer writeWithIdent(Writer writer) throws JSONException { + return this.write(writer, 1, 0); + } + + @SuppressWarnings("unchecked") + static final Writer writeValue(Writer writer, Object value, int indentFactor, int indent) + throws JSONException, IOException { + if (value == null || value.equals(null)) { + writer.write("null"); + } else if (value instanceof JSONObject) { + ((JSONObject) value).write(writer, indentFactor, indent); + } else if (value instanceof JSONArray) { + ((JSONArray) value).write(writer, indentFactor, indent); + } else if (value instanceof Map) { + new JSONObject(value).write(writer, indentFactor, indent); + } else if (value instanceof Collection) { + new JSONArray((Collection) value).write(writer, indentFactor, indent); + } else if (value.getClass().isArray()) { + new JSONArray(value).write(writer, indentFactor, indent); + } else if (value instanceof Number) { + writer.write(numberToString((Number) value)); + } else if (value instanceof Boolean) { + writer.write(value.toString()); + } else if (value instanceof JSONString) { + Object o; + try { + o = ((JSONString) value).toJSONString(); + } catch (Exception e) { + throw new JSONException(e); + } + writer.write(o != null ? o.toString() : quote(value.toString())); + } else { + quote(value.toString(), writer); + } + return writer; + } + + static final void indent(Writer writer, int indent) throws IOException { + for (int i = 0; i < indent; i += 1) { + writer.write('\t'); + } + } + + /** + * Write the contents of the JSONObject as JSON text to a writer. For + * compactness, no whitespace is added. + *

+ * Warning: This method assumes that the data structure is acyclical. + * + * @return The writer. + * @throws JSONException + */ + Writer write(Writer writer, int indentFactor, int indent) throws JSONException { + try { + boolean commanate = false; + final int length = this.length(); + Iterator keys = this.keys(); + writer.write('{'); + + if (length == 1) { + Object key = keys.next(); + writer.write(quote(key.toString())); + writer.write(':'); + if (indentFactor > 0) { + writer.write(' '); + } + writeValue(writer, this.map.get(key), indentFactor, indent); + } else if (length != 0) { + final int newindent = indent + indentFactor; + while (keys.hasNext()) { + Object key = keys.next(); + if (commanate) { + writer.write(','); + } + if (indentFactor > 0) { + writer.write('\n'); + } + indent(writer, newindent); + writer.write(quote(key.toString())); + writer.write(':'); + if (indentFactor > 0) { + writer.write(' '); + } + writeValue(writer, this.map.get(key), indentFactor, newindent); + commanate = true; + } + if (indentFactor > 0) { + writer.write('\n'); + } + indent(writer, indent); + } + writer.write('}'); + return writer; + } catch (IOException exception) { + throw new JSONException(exception); + } + } +} diff --git a/java/org/json/JSONString.java b/java/org/json/JSONString.java new file mode 100644 index 000000000..8b62ea310 --- /dev/null +++ b/java/org/json/JSONString.java @@ -0,0 +1,19 @@ +package org.json; + +/** + * The JSONString interface allows a toJSONString() + * method so that a class can change the behavior of + * JSONObject.toString(), JSONArray.toString(), and + * JSONWriter.value(Object). The + * toJSONString method will be used instead of the default behavior + * of using the Object's toString() method and quoting the result. + */ +public interface JSONString { + /** + * The toJSONString method allows a class to produce its own + * JSON serialization. + * + * @return A strictly syntactically correct JSON text. + */ + public String toJSONString(); +} diff --git a/java/org/json/JSONTokener.java b/java/org/json/JSONTokener.java new file mode 100644 index 000000000..1d95bf522 --- /dev/null +++ b/java/org/json/JSONTokener.java @@ -0,0 +1,434 @@ +package org.json; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.Reader; +import java.io.StringReader; + +/* + Copyright (c) 2002 JSON.org + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + The Software shall be used for Good, not Evil. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. + */ + + +/** + * A JSONTokener takes a source string and extracts characters and tokens from + * it. It is used by the JSONObject and JSONArray constructors to parse JSON + * source strings. + * + * @author JSON.org + * @version 2012-02-16 + */ +public class JSONTokener { + + private long character; + private boolean eof; + private long index; + private long line; + private char previous; + private Reader reader; + private boolean usePrevious; + + /** + * Construct a JSONTokener from a Reader. + * + * @param reader A reader. + */ + public JSONTokener(Reader reader) { + this.reader = reader.markSupported() ? reader : new BufferedReader(reader); + this.eof = false; + this.usePrevious = false; + this.previous = 0; + this.index = 0; + this.character = 1; + this.line = 1; + } + + /** + * Construct a JSONTokener from an InputStream. + */ + public JSONTokener(InputStream inputStream) throws JSONException { + this(new InputStreamReader(inputStream)); + } + + /** + * Construct a JSONTokener from a string. + * + * @param s A source string. + */ + public JSONTokener(String s) { + this(new StringReader(s)); + } + + /** + * Back up one character. This provides a sort of lookahead capability, so + * that you can test for a digit or letter before attempting to parse the + * next number or identifier. + */ + public void back() throws JSONException { + if (this.usePrevious || this.index <= 0) { + throw new JSONException("Stepping back two steps is not supported"); + } + this.index -= 1; + this.character -= 1; + this.usePrevious = true; + this.eof = false; + } + + /** + * Get the hex value of a character (base16). + * + * @param c A character between '0' and '9' or between 'A' and 'F' or + * between 'a' and 'f'. + * @return An int between 0 and 15, or -1 if c was not a hex digit. + */ + public static int dehexchar(char c) { + if (c >= '0' && c <= '9') { + return c - '0'; + } + if (c >= 'A' && c <= 'F') { + return c - ('A' - 10); + } + if (c >= 'a' && c <= 'f') { + return c - ('a' - 10); + } + return -1; + } + + public boolean end() { + return this.eof && !this.usePrevious; + } + + /** + * Determine if the source string still contains characters that next() can + * consume. + * + * @return true if not yet at the end of the source. + */ + public boolean more() throws JSONException { + this.next(); + if (this.end()) { + return false; + } + this.back(); + return true; + } + + /** + * Get the next character in the source string. + * + * @return The next character, or 0 if past the end of the source string. + */ + public char next() throws JSONException { + int c; + if (this.usePrevious) { + this.usePrevious = false; + c = this.previous; + } else { + try { + c = this.reader.read(); + } catch (IOException exception) { + throw new JSONException(exception); + } + + if (c <= 0) { // End of stream + this.eof = true; + c = 0; + } + } + this.index += 1; + if (this.previous == '\r') { + this.line += 1; + this.character = c == '\n' ? 0 : 1; + } else if (c == '\n') { + this.line += 1; + this.character = 0; + } else { + this.character += 1; + } + this.previous = (char) c; + return this.previous; + } + + /** + * Consume the next character, and check that it matches a specified + * character. + * + * @param c The character to match. + * @return The character. + * @throws JSONException if the character does not match. + */ + public char next(char c) throws JSONException { + char n = this.next(); + if (n != c) { + throw this.syntaxError("Expected '" + c + "' and instead saw '" + n + "'"); + } + return n; + } + + /** + * Get the next n characters. + * + * @param n The number of characters to take. + * @return A string of n characters. + * @throws JSONException Substring bounds error if there are not n + * characters remaining in the source string. + */ + public String next(int n) throws JSONException { + if (n == 0) { + return ""; + } + + char[] chars = new char[n]; + int pos = 0; + + while (pos < n) { + chars[pos] = this.next(); + if (this.end()) { + throw this.syntaxError("Substring bounds error"); + } + pos += 1; + } + return new String(chars); + } + + /** + * Get the next char in the string, skipping whitespace. + * + * @throws JSONException + * @return A character, or 0 if there are no more characters. + */ + public char nextClean() throws JSONException { + for (;;) { + char c = this.next(); + if (c == 0 || c > ' ') { + return c; + } + } + } + + /** + * Return the characters up to the next close quote character. Backslash + * processing is done. The formal JSON format does not allow strings in + * single quotes, but an implementation is allowed to accept them. + * + * @param quote The quoting character, either " + *  (double quote) or ' + *  (single quote). + * @return A String. + * @throws JSONException Unterminated string. + */ + public String nextString(char quote) throws JSONException { + char c; + StringBuffer sb = new StringBuffer(); + for (;;) { + c = this.next(); + switch (c) { + case 0: + case '\n': + case '\r': + throw this.syntaxError("Unterminated string"); + case '\\': + c = this.next(); + switch (c) { + case 'b': + sb.append('\b'); + break; + case 't': + sb.append('\t'); + break; + case 'n': + sb.append('\n'); + break; + case 'f': + sb.append('\f'); + break; + case 'r': + sb.append('\r'); + break; + case 'u': + sb.append((char) Integer.parseInt(this.next(4), 16)); + break; + case '"': + case '\'': + case '\\': + case '/': + sb.append(c); + break; + default: + throw this.syntaxError("Illegal escape."); + } + break; + default: + if (c == quote) { + return sb.toString(); + } + sb.append(c); + } + } + } + + /** + * Get the text up but not including the specified character or the end of + * line, whichever comes first. + * + * @param delimiter A delimiter character. + * @return A string. + */ + public String nextTo(char delimiter) throws JSONException { + StringBuffer sb = new StringBuffer(); + for (;;) { + char c = this.next(); + if (c == delimiter || c == 0 || c == '\n' || c == '\r') { + if (c != 0) { + this.back(); + } + return sb.toString().trim(); + } + sb.append(c); + } + } + + /** + * Get the text up but not including one of the specified delimiter + * characters or the end of line, whichever comes first. + * + * @param delimiters A set of delimiter characters. + * @return A string, trimmed. + */ + public String nextTo(String delimiters) throws JSONException { + char c; + StringBuffer sb = new StringBuffer(); + for (;;) { + c = this.next(); + if (delimiters.indexOf(c) >= 0 || c == 0 || c == '\n' || c == '\r') { + if (c != 0) { + this.back(); + } + return sb.toString().trim(); + } + sb.append(c); + } + } + + /** + * Get the next value. The value can be a Boolean, Double, Integer, + * JSONArray, JSONObject, Long, or String, or the JSONObject.NULL object. + * + * @throws JSONException If syntax error. + * + * @return An object. + */ + public Object nextValue() throws JSONException { + char c = this.nextClean(); + String string; + + switch (c) { + case '"': + case '\'': + return this.nextString(c); + case '{': + this.back(); + return new JSONObject(this); + case '[': + this.back(); + return new JSONArray(this); + } + + /* + * Handle unquoted text. This could be the values true, false, or null, + * or it can be a number. An implementation (such as this one) is + * allowed to also accept non-standard forms. + * + * Accumulate characters until we reach the end of the text or a + * formatting character. + */ + + StringBuffer sb = new StringBuffer(); + while (c >= ' ' && ",:]}/\\\"[{;=#".indexOf(c) < 0) { + sb.append(c); + c = this.next(); + } + this.back(); + + string = sb.toString().trim(); + if ("".equals(string)) { + throw this.syntaxError("Missing value"); + } + return JSONObject.stringToValue(string); + } + + /** + * Skip characters until the next character is the requested character. If + * the requested character is not found, no characters are skipped. + * + * @param to A character to skip to. + * @return The requested character, or zero if the requested character is + * not found. + */ + public char skipTo(char to) throws JSONException { + char c; + try { + long startIndex = this.index; + long startCharacter = this.character; + long startLine = this.line; + this.reader.mark(1000000); + do { + c = this.next(); + if (c == 0) { + this.reader.reset(); + this.index = startIndex; + this.character = startCharacter; + this.line = startLine; + return c; + } + } while (c != to); + } catch (IOException exc) { + throw new JSONException(exc); + } + + this.back(); + return c; + } + + /** + * Make a JSONException to signal a syntax error. + * + * @param message The error message. + * @return A JSONException object, suitable for throwing + */ + public JSONException syntaxError(String message) { + return new JSONException(message + this.toString()); + } + + /** + * Make a printable string of this JSONTokener. + * + * @return " at {index} [character {character} line {line}]" + */ + @Override + public String toString() { + return " at " + this.index + " [character " + this.character + " line " + this.line + "]"; + } +} diff --git a/java/org/json/JSONUtil.java b/java/org/json/JSONUtil.java new file mode 100644 index 000000000..b7d3cd115 --- /dev/null +++ b/java/org/json/JSONUtil.java @@ -0,0 +1,57 @@ +package org.json; + +import java.util.Map.Entry; + + +public class JSONUtil { + + public static JSONObject toJSON(JSONEntry... entries) { + return o(entries); + } + + public static JSONObject o(JSONEntry... entries) { + JSONObject object = new JSONObject(); + for (int i = 0; i < entries.length; ++i) { + JSONEntry e = entries[i]; + object.put(e.getKey(), e.getValue()); + } + return object; + } + + public static JSONArray a(Object... values) { + return new JSONArray(values); + } + + public static JSONEntry e(String k, Object v) { + return new JSONEntry(k, v); + } + + public static class JSONEntry implements Entry { + + private String key; + private Object value; + + public JSONEntry(String key, Object value) { + this.key = key; + this.value = value; + } + + @Override + public String getKey() { + return this.key; + } + + @Override + public Object getValue() { + return this.value; + } + + @Override + public Object setValue(Object value) { + Object oldValue = this.value; + this.value = value; + return oldValue; + } + + } +} diff --git a/slime-java-commons b/slime-java-commons deleted file mode 160000 index 23a977aef..000000000 --- a/slime-java-commons +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 23a977aefe5315e0ca8d97bd19967e3682b490aa diff --git a/src/main/java/com/jme3/math/FastMath.java b/src/main/java/com/jme3/math/FastMath.java new file mode 100644 index 000000000..ed0917d14 --- /dev/null +++ b/src/main/java/com/jme3/math/FastMath.java @@ -0,0 +1,1072 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.math; + +import java.util.Random; + + +/** + * FastMath provides 'fast' math approximations and float + * equivalents of Math functions. These are all used as static values and + * functions. + * + * @author Various + * @version $Id: FastMath.java,v 1.45 2007/08/26 08:44:20 irrisor Exp $ + */ +final public class FastMath { + + private FastMath() { + } + + /** A "close to zero" double epsilon value for use */ + public static final double DBL_EPSILON = 2.220446049250313E-16d; + /** A "close to zero" float epsilon value for use */ + public static final float FLT_EPSILON = 1.1920928955078125E-7f; + /** A "close to zero" float epsilon value for use */ + public static final float ZERO_TOLERANCE = 0.0001f; + public static final float ONE_THIRD = 1f / 3f; + /** The value PI as a float. (180 degrees) */ + public static final float PI = (float) Math.PI; + /** The value 2PI as a float. (360 degrees) */ + public static final float TWO_PI = 2.0f * PI; + /** The value PI/2 as a float. (90 degrees) */ + public static final float HALF_PI = 0.5f * PI; + /** The value PI/4 as a float. (45 degrees) */ + public static final float QUARTER_PI = 0.25f * PI; + /** The value 1/PI as a float. */ + public static final float INV_PI = 1.0f / PI; + /** The value 1/(2PI) as a float. */ + public static final float INV_TWO_PI = 1.0f / TWO_PI; + /** A value to multiply a degree value by, to convert it to radians. */ + public static final float DEG_TO_RAD = PI / 180.0f; + /** A value to multiply a radian value by, to convert it to degrees. */ + public static final float RAD_TO_DEG = 180.0f / PI; + /** A precreated random object for random numbers. */ + public static final Random rand = new Random(System.currentTimeMillis()); + + /** + * Returns true if the number is a power of 2 (2,4,8,16...) + * + * A good implementation found on the Java boards. note: a number is a power + * of two if and only if it is the smallest number with that number of + * significant bits. Therefore, if you subtract 1, you know that the new + * number will have fewer bits, so ANDing the original number with anything + * less than it will give 0. + * + * @param number The number to test. + * @return True if it is a power of two. + */ + public static boolean isPowerOfTwo(int number) { + return (number > 0) && (number & (number - 1)) == 0; + } + + public static int nearestPowerOfTwo(int number) { + return (int) Math.pow(2, Math.ceil(Math.log(number) / Math.log(2))); + } + + /** + * Linear interpolation from startValue to endValue by the given percent. + * Basically: ((1 - percent) * startValue) + (percent * endValue) + * + * @param scale scale value to use. if 1, use endValue, if 0, use + * startValue. + * @param startValue Begining value. 0% of f + * @param endValue ending value. 100% of f + * @return The interpolated value between startValue and endValue. + */ + public static float interpolateLinear(float scale, float startValue, float endValue) { + if (startValue == endValue) { + return startValue; + } + if (scale <= 0f) { + return startValue; + } + if (scale >= 1f) { + return endValue; + } + return ((1f - scale) * startValue) + (scale * endValue); + } + + /** + * Linear interpolation from startValue to endValue by the given percent. + * Basically: ((1 - percent) * startValue) + (percent * endValue) + * + * @param scale scale value to use. if 1, use endValue, if 0, use + * startValue. + * @param startValue Begining value. 0% of f + * @param endValue ending value. 100% of f + * @param store a vector3f to store the result + * @return The interpolated value between startValue and endValue. + */ + public static Vector3f interpolateLinear( + float scale, + Vector3f startValue, + Vector3f endValue, + Vector3f store + ) { + if (store == null) { + store = new Vector3f(); + } + store.x = interpolateLinear(scale, startValue.x, endValue.x); + store.y = interpolateLinear(scale, startValue.y, endValue.y); + store.z = interpolateLinear(scale, startValue.z, endValue.z); + return store; + } + + /** + * Linear interpolation from startValue to endValue by the given percent. + * Basically: ((1 - percent) * startValue) + (percent * endValue) + * + * @param scale scale value to use. if 1, use endValue, if 0, use + * startValue. + * @param startValue Begining value. 0% of f + * @param endValue ending value. 100% of f + * @return The interpolated value between startValue and endValue. + */ + public static Vector3f interpolateLinear(float scale, Vector3f startValue, Vector3f endValue) { + return interpolateLinear(scale, startValue, endValue, null); + } + + /** + * Linear extrapolation from startValue to endValue by the given scale. if + * scale is between 0 and 1 this method returns the same result as + * interpolateLinear if the scale is over 1 the value is linearly + * extrapolated. Note that the end value is the value for a scale of 1. + * + * @param scale the scale for extrapolation + * @param startValue the starting value (scale = 0) + * @param endValue the end value (scale = 1) + * @return an extrapolation for the given parameters + */ + public static float extrapolateLinear(float scale, float startValue, float endValue) { +// if (scale <= 0f) { +// return startValue; +// } + return ((1f - scale) * startValue) + (scale * endValue); + } + + /** + * Linear extrapolation from startValue to endValue by the given scale. if + * scale is between 0 and 1 this method returns the same result as + * interpolateLinear if the scale is over 1 the value is linearly + * extrapolated. Note that the end value is the value for a scale of 1. + * + * @param scale the scale for extrapolation + * @param startValue the starting value (scale = 0) + * @param endValue the end value (scale = 1) + * @param store an initialized vector to store the return value + * @return an extrapolation for the given parameters + */ + public static Vector3f extrapolateLinear( + float scale, + Vector3f startValue, + Vector3f endValue, + Vector3f store + ) { + if (store == null) { + store = new Vector3f(); + } +// if (scale <= 1f) { +// return interpolateLinear(scale, startValue, endValue, store); +// } + store.x = extrapolateLinear(scale, startValue.x, endValue.x); + store.y = extrapolateLinear(scale, startValue.y, endValue.y); + store.z = extrapolateLinear(scale, startValue.z, endValue.z); + return store; + } + + /** + * Linear extrapolation from startValue to endValue by the given scale. if + * scale is between 0 and 1 this method returns the same result as + * interpolateLinear if the scale is over 1 the value is linearly + * extrapolated. Note that the end value is the value for a scale of 1. + * + * @param scale the scale for extrapolation + * @param startValue the starting value (scale = 0) + * @param endValue the end value (scale = 1) + * @return an extrapolation for the given parameters + */ + public static Vector3f extrapolateLinear(float scale, Vector3f startValue, Vector3f endValue) { + return extrapolateLinear(scale, startValue, endValue, null); + } + + /** + * Interpolate a spline between at least 4 control points following the + * Catmull-Rom equation. here is the interpolation matrix m = [ 0.0 1.0 0.0 + * 0.0 ] [-T 0.0 T 0.0 ] [ 2T T-3 3-2T -T ] [-T 2-T T-2 T ] where T is the + * curve tension the result is a value between p1 and p2, t=0 for p1, t=1 + * for p2 + * + * @param u value from 0 to 1 + * @param T The tension of the curve + * @param p0 control point 0 + * @param p1 control point 1 + * @param p2 control point 2 + * @param p3 control point 3 + * @return catmull-Rom interpolation + */ + public static float interpolateCatmullRom( + float u, + float T, + float p0, + float p1, + float p2, + float p3 + ) { + float c1, c2, c3, c4; + c1 = p1; + c2 = -1.0f * T * p0 + T * p2; + c3 = 2 * T * p0 + (T - 3) * p1 + (3 - 2 * T) * p2 + -T * p3; + c4 = -T * p0 + (2 - T) * p1 + (T - 2) * p2 + T * p3; + + return (float) (((c4 * u + c3) * u + c2) * u + c1); + } + + /** + * Interpolate a spline between at least 4 control points following the + * Catmull-Rom equation. here is the interpolation matrix m = [ 0.0 1.0 0.0 + * 0.0 ] [-T 0.0 T 0.0 ] [ 2T T-3 3-2T -T ] [-T 2-T T-2 T ] where T is the + * tension of the curve the result is a value between p1 and p2, t=0 for p1, + * t=1 for p2 + * + * @param u value from 0 to 1 + * @param T The tension of the curve + * @param p0 control point 0 + * @param p1 control point 1 + * @param p2 control point 2 + * @param p3 control point 3 + * @param store a Vector3f to store the result + * @return catmull-Rom interpolation + */ + public static Vector3f interpolateCatmullRom( + float u, + float T, + Vector3f p0, + Vector3f p1, + Vector3f p2, + Vector3f p3, + Vector3f store + ) { + if (store == null) { + store = new Vector3f(); + } + store.x = interpolateCatmullRom(u, T, p0.x, p1.x, p2.x, p3.x); + store.y = interpolateCatmullRom(u, T, p0.y, p1.y, p2.y, p3.y); + store.z = interpolateCatmullRom(u, T, p0.z, p1.z, p2.z, p3.z); + return store; + } + + /** + * Interpolate a spline between at least 4 control points following the + * Catmull-Rom equation. here is the interpolation matrix m = [ 0.0 1.0 0.0 + * 0.0 ] [-T 0.0 T 0.0 ] [ 2T T-3 3-2T -T ] [-T 2-T T-2 T ] where T is the + * tension of the curve the result is a value between p1 and p2, t=0 for p1, + * t=1 for p2 + * + * @param u value from 0 to 1 + * @param T The tension of the curve + * @param p0 control point 0 + * @param p1 control point 1 + * @param p2 control point 2 + * @param p3 control point 3 + * @return catmull-Rom interpolation + */ + public static Vector3f interpolateCatmullRom( + float u, + float T, + Vector3f p0, + Vector3f p1, + Vector3f p2, + Vector3f p3 + ) { + return interpolateCatmullRom(u, T, p0, p1, p2, p3, null); + } + + /** + * Interpolate a spline between at least 4 control points following the + * Bezier equation. here is the interpolation matrix m = [ -1.0 3.0 -3.0 1.0 + * ] [ 3.0 -6.0 3.0 0.0 ] [ -3.0 3.0 0.0 0.0 ] [ 1.0 0.0 0.0 0.0 ] where T + * is the curve tension the result is a value between p1 and p3, t=0 for p1, + * t=1 for p3 + * + * @param u value from 0 to 1 + * @param p0 control point 0 + * @param p1 control point 1 + * @param p2 control point 2 + * @param p3 control point 3 + * @return Bezier interpolation + */ + public static float interpolateBezier(float u, float p0, float p1, float p2, float p3) { + float oneMinusU = 1.0f - u; + float oneMinusU2 = oneMinusU * oneMinusU; + float u2 = u * u; + return p0 * oneMinusU2 * oneMinusU + + 3.0f * p1 * u * oneMinusU2 + + 3.0f * p2 * u2 * oneMinusU + + p3 * u2 * u; + } + + /** + * Interpolate a spline between at least 4 control points following the + * Bezier equation. here is the interpolation matrix m = [ -1.0 3.0 -3.0 1.0 + * ] [ 3.0 -6.0 3.0 0.0 ] [ -3.0 3.0 0.0 0.0 ] [ 1.0 0.0 0.0 0.0 ] where T + * is the tension of the curve the result is a value between p1 and p3, t=0 + * for p1, t=1 for p3 + * + * @param u value from 0 to 1 + * @param p0 control point 0 + * @param p1 control point 1 + * @param p2 control point 2 + * @param p3 control point 3 + * @param store a Vector3f to store the result + * @return Bezier interpolation + */ + public static Vector3f interpolateBezier( + float u, + Vector3f p0, + Vector3f p1, + Vector3f p2, + Vector3f p3, + Vector3f store + ) { + if (store == null) { + store = new Vector3f(); + } + store.x = interpolateBezier(u, p0.x, p1.x, p2.x, p3.x); + store.y = interpolateBezier(u, p0.y, p1.y, p2.y, p3.y); + store.z = interpolateBezier(u, p0.z, p1.z, p2.z, p3.z); + return store; + } + + /** + * Interpolate a spline between at least 4 control points following the + * Bezier equation. here is the interpolation matrix m = [ -1.0 3.0 -3.0 1.0 + * ] [ 3.0 -6.0 3.0 0.0 ] [ -3.0 3.0 0.0 0.0 ] [ 1.0 0.0 0.0 0.0 ] where T + * is the tension of the curve the result is a value between p1 and p3, t=0 + * for p1, t=1 for p3 + * + * @param u value from 0 to 1 + * @param p0 control point 0 + * @param p1 control point 1 + * @param p2 control point 2 + * @param p3 control point 3 + * @return Bezier interpolation + */ + public static Vector3f interpolateBezier( + float u, + Vector3f p0, + Vector3f p1, + Vector3f p2, + Vector3f p3 + ) { + return interpolateBezier(u, p0, p1, p2, p3, null); + } + + /** + * Compute the lenght on a catmull rom spline between control point 1 and 2 + * + * @param p0 control point 0 + * @param p1 control point 1 + * @param p2 control point 2 + * @param p3 control point 3 + * @param startRange the starting range on the segment (use 0) + * @param endRange the end range on the segment (use 1) + * @param curveTension the curve tension + * @return the length of the segment + */ + public static float getCatmullRomP1toP2Length( + Vector3f p0, + Vector3f p1, + Vector3f p2, + Vector3f p3, + float startRange, + float endRange, + float curveTension + ) { + + float epsilon = 0.001f; + float middleValue = (startRange + endRange) * 0.5f; + Vector3f start = p1.clone(); + if (startRange != 0) { + FastMath.interpolateCatmullRom(startRange, curveTension, p0, p1, p2, p3, start); + } + Vector3f end = p2.clone(); + if (endRange != 1) { + FastMath.interpolateCatmullRom(endRange, curveTension, p0, p1, p2, p3, end); + } + Vector3f middle = FastMath.interpolateCatmullRom(middleValue, curveTension, p0, p1, p2, p3); + float l = end.subtract(start).length(); + float l1 = middle.subtract(start).length(); + float l2 = end.subtract(middle).length(); + float len = l1 + l2; + if (l + epsilon < len) { + l1 = getCatmullRomP1toP2Length(p0, p1, p2, p3, startRange, middleValue, curveTension); + l2 = getCatmullRomP1toP2Length(p0, p1, p2, p3, middleValue, endRange, curveTension); + } + l = l1 + l2; + return l; + } + + /** + * Compute the lenght on a bezier spline between control point 1 and 2 + * + * @param p0 control point 0 + * @param p1 control point 1 + * @param p2 control point 2 + * @param p3 control point 3 + * @return the length of the segment + */ + public static float getBezierP1toP2Length(Vector3f p0, Vector3f p1, Vector3f p2, Vector3f p3) { + float delta = 0.02f, t = 0.0f, result = 0.0f; + Vector3f v1 = p0.clone(), v2 = new Vector3f(); + while (t <= 1.0f) { + FastMath.interpolateBezier(t, p0, p1, p2, p3, v2); + result += v1.subtractLocal(v2).length(); + v1.set(v2); + t += delta; + } + return result; + } + + /** + * Returns the arc cosine of a value.
+ * Special cases: + *

    + *
  • If fValue is smaller than -1, then the result is PI. + *
  • If the argument is greater than 1, then the result is 0. + *
+ * + * @param fValue The value to arc cosine. + * @return The angle, in radians. + * @see java.lang.Math#acos(double) + */ + public static float acos(float fValue) { + if (-1.0f < fValue) { + if (fValue < 1.0f) { + return (float) Math.acos(fValue); + } + + return 0.0f; + } + + return PI; + } + + /** + * Returns the arc sine of a value.
+ * Special cases: + *
    + *
  • If fValue is smaller than -1, then the result is -HALF_PI. + *
  • If the argument is greater than 1, then the result is HALF_PI. + *
+ * + * @param fValue The value to arc sine. + * @return the angle in radians. + * @see java.lang.Math#asin(double) + */ + public static float asin(float fValue) { + if (-1.0f < fValue) { + if (fValue < 1.0f) { + return (float) Math.asin(fValue); + } + + return HALF_PI; + } + + return -HALF_PI; + } + + /** + * Returns the arc tangent of an angle given in radians.
+ * + * @param fValue The angle, in radians. + * @return fValue's atan + * @see java.lang.Math#atan(double) + */ + public static float atan(float fValue) { + return (float) Math.atan(fValue); + } + + /** + * A direct call to Math.atan2. + * + * @param fY + * @param fX + * @return Math.atan2(fY,fX) + * @see java.lang.Math#atan2(double, double) + */ + public static float atan2(float fY, float fX) { + return (float) Math.atan2(fY, fX); + } + + /** + * Rounds a fValue up. A call to Math.ceil + * + * @param fValue The value. + * @return The fValue rounded up + * @see java.lang.Math#ceil(double) + */ + public static float ceil(float fValue) { + return (float) Math.ceil(fValue); + } + + /** + * Returns cosine of an angle. Direct call to java.lang.Math + * + * @see Math#cos(double) + * @param v The angle to cosine. + * @return the cosine of the angle. + */ + public static float cos(float v) { + return (float) Math.cos(v); + } + + /** + * Returns the sine of an angle. Direct call to java.lang.Math + * + * @see Math#sin(double) + * @param v The angle to sine. + * @return the sine of the angle. + */ + public static float sin(float v) { + return (float) Math.sin(v); + } + + /** + * Returns E^fValue + * + * @param fValue Value to raise to a power. + * @return The value E^fValue + * @see java.lang.Math#exp(double) + */ + public static float exp(float fValue) { + return (float) Math.exp(fValue); + } + + /** + * Returns Absolute value of a float. + * + * @param fValue The value to abs. + * @return The abs of the value. + * @see java.lang.Math#abs(float) + */ + public static float abs(float fValue) { + if (fValue < 0) { + return -fValue; + } + return fValue; + } + + /** + * Returns a number rounded down. + * + * @param fValue The value to round + * @return The given number rounded down + * @see java.lang.Math#floor(double) + */ + public static float floor(float fValue) { + return (float) Math.floor(fValue); + } + + /** + * Returns 1/sqrt(fValue) + * + * @param fValue The value to process. + * @return 1/sqrt(fValue) + * @see java.lang.Math#sqrt(double) + */ + public static float invSqrt(float fValue) { + return (float) (1.0f / Math.sqrt(fValue)); + } + + public static float fastInvSqrt(float x) { + float xhalf = 0.5f * x; + int i = Float.floatToIntBits(x); // get bits for floating value + i = 0x5f375a86 - (i >> 1); // gives initial guess y0 + x = Float.intBitsToFloat(i); // convert bits back to float + x = x * (1.5f - xhalf * x * x); // Newton step, repeating increases + // accuracy + return x; + } + + /** + * Returns the log base E of a value. + * + * @param fValue The value to log. + * @return The log of fValue base E + * @see java.lang.Math#log(double) + */ + public static float log(float fValue) { + return (float) Math.log(fValue); + } + + /** + * Returns the logarithm of value with given base, calculated as + * log(value)/log(base), so that pow(base, return)==value (contributed by + * vear) + * + * @param value The value to log. + * @param base Base of logarithm. + * @return The logarithm of value with given base + */ + public static float log(float value, float base) { + return (float) (Math.log(value) / Math.log(base)); + } + + /** + * Returns a number raised to an exponent power. fBase^fExponent + * + * @param fBase The base value (IE 2) + * @param fExponent The exponent value (IE 3) + * @return base raised to exponent (IE 8) + * @see java.lang.Math#pow(double, double) + */ + public static float pow(float fBase, float fExponent) { + return (float) Math.pow(fBase, fExponent); + } + + /** + * Returns the value squared. fValue ^ 2 + * + * @param fValue The vaule to square. + * @return The square of the given value. + */ + public static float sqr(float fValue) { + return fValue * fValue; + } + + /** + * Returns the square root of a given value. + * + * @param fValue The value to sqrt. + * @return The square root of the given value. + * @see java.lang.Math#sqrt(double) + */ + public static float sqrt(float fValue) { + return (float) Math.sqrt(fValue); + } + + /** + * Returns the tangent of a value. If USE_FAST_TRIG is enabled, an + * approximate value is returned. Otherwise, a direct value is used. + * + * @param fValue The value to tangent, in radians. + * @return The tangent of fValue. + * @see java.lang.Math#tan(double) + */ + public static float tan(float fValue) { + return (float) Math.tan(fValue); + } + + /** + * Returns 1 if the number is positive, -1 if the number is negative, and 0 + * otherwise + * + * @param iValue The integer to examine. + * @return The integer's sign. + */ + public static int sign(int iValue) { + if (iValue > 0) { + return 1; + } + if (iValue < 0) { + return -1; + } + return 0; + } + + /** + * Returns 1 if the number is positive, -1 if the number is negative, and 0 + * otherwise + * + * @param fValue The float to examine. + * @return The float's sign. + */ + public static float sign(float fValue) { + return Math.signum(fValue); + } + + /** + * Given 3 points in a 2d plane, this function computes if the points going + * from A-B-C are moving counter clock wise. + * + * @param p0 Point 0. + * @param p1 Point 1. + * @param p2 Point 2. + * @return 1 If they are CCW, -1 if they are not CCW, 0 if p2 is between p0 + * and p1. + */ + public static int counterClockwise(Vector2f p0, Vector2f p1, Vector2f p2) { + float dx1, dx2, dy1, dy2; + dx1 = p1.x - p0.x; + dy1 = p1.y - p0.y; + dx2 = p2.x - p0.x; + dy2 = p2.y - p0.y; + if (dx1 * dy2 > dy1 * dx2) { + return 1; + } + if (dx1 * dy2 < dy1 * dx2) { + return -1; + } + if ((dx1 * dx2 < 0) || (dy1 * dy2 < 0)) { + return -1; + } + if ((dx1 * dx1 + dy1 * dy1) < (dx2 * dx2 + dy2 * dy2)) { + return 1; + } + return 0; + } + + /** + * Test if a point is inside a triangle. 1 if the point is on the ccw side, + * -1 if the point is on the cw side, and 0 if it is on neither. + * + * @param t0 First point of the triangle. + * @param t1 Second point of the triangle. + * @param t2 Third point of the triangle. + * @param p The point to test. + * @return Value 1 or -1 if inside triangle, 0 otherwise. + */ + public static int pointInsideTriangle(Vector2f t0, Vector2f t1, Vector2f t2, Vector2f p) { + int val1 = counterClockwise(t0, t1, p); + if (val1 == 0) { + return 1; + } + int val2 = counterClockwise(t1, t2, p); + if (val2 == 0) { + return 1; + } + if (val2 != val1) { + return 0; + } + int val3 = counterClockwise(t2, t0, p); + if (val3 == 0) { + return 1; + } + if (val3 != val1) { + return 0; + } + return val3; + } + + /** + * A method that computes normal for a triangle defined by three vertices. + * + * @param v1 first vertex + * @param v2 second vertex + * @param v3 third vertex + * @return a normal for the face + */ + public static Vector3f computeNormal(Vector3f v1, Vector3f v2, Vector3f v3) { + Vector3f a1 = v1.subtract(v2); + Vector3f a2 = v3.subtract(v2); + return a2.crossLocal(a1).normalizeLocal(); + } + + /** + * Returns the determinant of a 4x4 matrix. + */ + public static float determinant( + double m00, + double m01, + double m02, + double m03, + double m10, + double m11, + double m12, + double m13, + double m20, + double m21, + double m22, + double m23, + double m30, + double m31, + double m32, + double m33 + ) { + + double det01 = m20 * m31 - m21 * m30; + double det02 = m20 * m32 - m22 * m30; + double det03 = m20 * m33 - m23 * m30; + double det12 = m21 * m32 - m22 * m31; + double det13 = m21 * m33 - m23 * m31; + double det23 = m22 * m33 - m23 * m32; + return (float) (m00 * (m11 * det23 - m12 * det13 + m13 * det12) + - m01 + * (m10 * det23 - m12 * det03 + m13 * det02) + + m02 + * (m10 * det13 - m11 * det03 + m13 * det01) + - m03 + * (m10 * det12 - m11 * det02 + m12 * det01)); + } + + /** + * Returns a random float between 0 and 1. + * + * @return A random float between 0.0f (inclusive) to 1.0f + * (exclusive). + */ + public static float nextRandomFloat() { + return rand.nextFloat(); + } + + /** + * Returns a random integer between min and max. + * + * @return A random int between min (inclusive) to max + * (inclusive). + */ + public static int nextRandomInt(int min, int max) { + return (int) (nextRandomFloat() * (max - min + 1)) + min; + } + + public static int nextRandomInt() { + return rand.nextInt(); + } + + /** + * Converts a point from Spherical coordinates to Cartesian (using positive + * Y as up) and stores the results in the store var. + */ + public static Vector3f sphericalToCartesian( + Vector3f sphereCoords, + Vector3f store + ) { + if (store == null) { + store = new Vector3f(); + } + store.y = sphereCoords.x * FastMath.sin(sphereCoords.z); + float a = sphereCoords.x * FastMath.cos(sphereCoords.z); + store.x = a * FastMath.cos(sphereCoords.y); + store.z = a * FastMath.sin(sphereCoords.y); + + return store; + } + + /** + * Converts a point from Cartesian coordinates (using positive Y as up) to + * Spherical and stores the results in the store var. (Radius, Azimuth, + * Polar) + */ + public static Vector3f cartesianToSpherical( + Vector3f cartCoords, + Vector3f store + ) { + if (store == null) { + store = new Vector3f(); + } + float x = cartCoords.x; + if (x == 0) { + x = FastMath.FLT_EPSILON; + } + store.x = FastMath + .sqrt( + (x * x) + + (cartCoords.y * cartCoords.y) + + (cartCoords.z * cartCoords.z) + ); + store.y = FastMath.atan(cartCoords.z / x); + if (x < 0) { + store.y += FastMath.PI; + } + store.z = FastMath.asin(cartCoords.y / store.x); + return store; + } + + /** + * Converts a point from Spherical coordinates to Cartesian (using positive + * Z as up) and stores the results in the store var. + */ + public static Vector3f sphericalToCartesianZ( + Vector3f sphereCoords, + Vector3f store + ) { + if (store == null) { + store = new Vector3f(); + } + store.z = sphereCoords.x * FastMath.sin(sphereCoords.z); + float a = sphereCoords.x * FastMath.cos(sphereCoords.z); + store.x = a * FastMath.cos(sphereCoords.y); + store.y = a * FastMath.sin(sphereCoords.y); + + return store; + } + + /** + * Converts a point from Cartesian coordinates (using positive Z as up) to + * Spherical and stores the results in the store var. (Radius, Azimuth, + * Polar) + */ + public static Vector3f cartesianZToSpherical( + Vector3f cartCoords, + Vector3f store + ) { + if (store == null) { + store = new Vector3f(); + } + float x = cartCoords.x; + if (x == 0) { + x = FastMath.FLT_EPSILON; + } + store.x = FastMath + .sqrt( + (x * x) + + (cartCoords.y * cartCoords.y) + + (cartCoords.z * cartCoords.z) + ); + store.z = FastMath.atan(cartCoords.z / x); + if (x < 0) { + store.z += FastMath.PI; + } + store.y = FastMath.asin(cartCoords.y / store.x); + return store; + } + + /** + * Takes an value and expresses it in terms of min to max. + * + * @param val - the angle to normalize (in radians) + * @return the normalized angle (also in radians) + */ + public static float normalize(float val, float min, float max) { + if (Float.isInfinite(val) || Float.isNaN(val)) { + return 0f; + } + float range = max - min; + while (val > max) { + val -= range; + } + while (val < min) { + val += range; + } + return val; + } + + /** + * @param x the value whose sign is to be adjusted. + * @param y the value whose sign is to be used. + * @return x with its sign changed to match the sign of y. + */ + public static float copysign(float x, float y) { + if (y >= 0 && x <= -0) { + return -x; + } else if (y < 0 && x >= 0) { + return -x; + } else { + return x; + } + } + + /** + * Take a float input and clamp it between min and max. + * + * @param input + * @param min + * @param max + * @return clamped input + */ + public static float clamp(float input, float min, float max) { + return (input < min) ? min : (input > max) ? max : input; + } + + /** + * Clamps the given float to be between 0 and 1. + * + * @param input + * @return input clamped between 0 and 1. + */ + public static float saturate(float input) { + return clamp(input, 0f, 1f); + } + + /** + * Converts a single precision (32 bit) floating point value into half + * precision (16 bit). + * + *

+ * Source: + * + * http://www.fox-toolkit.org/ftp/fasthalffloatconversion.pdf
+ * broken link + * + * @param half The half floating point value as a short. + * @return floating point value of the half. + */ + public static float convertHalfToFloat(short half) { + switch ((int) half) { + case 0x0000: + return 0f; + case 0x8000: + return -0f; + case 0x7c00: + return Float.POSITIVE_INFINITY; + case 0xfc00: + return Float.NEGATIVE_INFINITY; + // TODO: Support for NaN? + default: + return Float + .intBitsToFloat( + ((half & 0x8000) << 16) + | (((half & 0x7c00) + 0x1C000) << 13) + | ((half & 0x03FF) << 13) + ); + } + } + + public static short convertFloatToHalf(float flt) { + if (Float.isNaN(flt)) { + throw new UnsupportedOperationException("NaN to half conversion not supported!"); + } else if (flt == Float.POSITIVE_INFINITY) { + return (short) 0x7c00; + } else if (flt == Float.NEGATIVE_INFINITY) { + return (short) 0xfc00; + } else if (flt == 0f) { + return (short) 0x0000; + } else if (flt == -0f) { + return (short) 0x8000; + } else if (flt > 65504f) { + // max value supported by half float + return 0x7bff; + } else if (flt < -65504f) { + return (short) (0x7bff | 0x8000); + } else if (flt > 0f && flt < 5.96046E-8f) { + return 0x0001; + } else if (flt < 0f && flt > -5.96046E-8f) { + return (short) 0x8001; + } + + int f = Float.floatToIntBits(flt); + return (short) (((f >> 16) & 0x8000) + | ((((f & 0x7f800000) - 0x38000000) >> 13) & 0x7c00) + | ((f >> 13) & 0x03ff)); + } +} diff --git a/src/main/java/com/jme3/math/Matrix3f.java b/src/main/java/com/jme3/math/Matrix3f.java new file mode 100644 index 000000000..44d6a2e06 --- /dev/null +++ b/src/main/java/com/jme3/math/Matrix3f.java @@ -0,0 +1,1360 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.math; + +import java.nio.FloatBuffer; +import java.util.logging.Logger; + + +/** + * Matrix3f defines a 3x3 matrix. Matrix data is maintained + * internally and is accessible via the get and set methods. Convenience methods + * are used for matrix operations as well as generating a matrix from a given + * set of values. + * + * @author Mark Powell + * @author Joshua Slack + */ +public final class Matrix3f implements Cloneable, java.io.Serializable { + + static final long serialVersionUID = 1; + + private static final Logger logger = Logger.getLogger(Matrix3f.class.getName()); + protected float m00, m01, m02; + protected float m10, m11, m12; + protected float m20, m21, m22; + public static final Matrix3f ZERO = new Matrix3f(0, 0, 0, 0, 0, 0, 0, 0, 0); + public static final Matrix3f IDENTITY = new Matrix3f(); + + /** + * Constructor instantiates a new Matrix3f object. The initial + * values for the matrix is that of the identity matrix. + * + */ + public Matrix3f() { + loadIdentity(); + } + + /** + * constructs a matrix with the given values. + * + * @param m00 0x0 in the matrix. + * @param m01 0x1 in the matrix. + * @param m02 0x2 in the matrix. + * @param m10 1x0 in the matrix. + * @param m11 1x1 in the matrix. + * @param m12 1x2 in the matrix. + * @param m20 2x0 in the matrix. + * @param m21 2x1 in the matrix. + * @param m22 2x2 in the matrix. + */ + public Matrix3f( + float m00, + float m01, + float m02, + float m10, + float m11, + float m12, + float m20, + float m21, + float m22 + ) { + + this.m00 = m00; + this.m01 = m01; + this.m02 = m02; + this.m10 = m10; + this.m11 = m11; + this.m12 = m12; + this.m20 = m20; + this.m21 = m21; + this.m22 = m22; + } + + /** + * Copy constructor that creates a new Matrix3f object that is + * the same as the provided matrix. + * + * @param mat the matrix to copy. + */ + public Matrix3f(Matrix3f mat) { + set(mat); + } + + /** + * Takes the absolute value of all matrix fields locally. + */ + public void absoluteLocal() { + m00 = FastMath.abs(m00); + m01 = FastMath.abs(m01); + m02 = FastMath.abs(m02); + m10 = FastMath.abs(m10); + m11 = FastMath.abs(m11); + m12 = FastMath.abs(m12); + m20 = FastMath.abs(m20); + m21 = FastMath.abs(m21); + m22 = FastMath.abs(m22); + } + + /** + * copy transfers the contents of a given matrix to this + * matrix. If a null matrix is supplied, this matrix is set to the identity + * matrix. + * + * @param matrix the matrix to copy. + * @return this + */ + public Matrix3f set(Matrix3f matrix) { + if (null == matrix) { + loadIdentity(); + } else { + m00 = matrix.m00; + m01 = matrix.m01; + m02 = matrix.m02; + m10 = matrix.m10; + m11 = matrix.m11; + m12 = matrix.m12; + m20 = matrix.m20; + m21 = matrix.m21; + m22 = matrix.m22; + } + return this; + } + + /** + * get retrieves a value from the matrix at the given position. + * If the position is invalid a JmeException is thrown. + * + * @param i the row index. + * @param j the colum index. + * @return the value at (i, j). + */ + @SuppressWarnings("fallthrough") + public float get(int i, int j) { + switch (i) { + case 0: + switch (j) { + case 0: + return m00; + case 1: + return m01; + case 2: + return m02; + } + case 1: + switch (j) { + case 0: + return m10; + case 1: + return m11; + case 2: + return m12; + } + case 2: + switch (j) { + case 0: + return m20; + case 1: + return m21; + case 2: + return m22; + } + } + + logger.warning("Invalid matrix index."); + throw new IllegalArgumentException("Invalid indices into matrix."); + } + + /** + * get(float[]) returns the matrix in row-major or column-major + * order. + * + * @param data The array to return the data into. This array can be 9 or 16 + * floats in size. Only the upper 3x3 are assigned to in the case of a 16 + * element array. + * @param rowMajor True for row major storage in the array (translation in + * elements 3, 7, 11 for a 4x4), false for column major (translation in + * elements 12, 13, 14 for a 4x4). + */ + public void get(float[] data, boolean rowMajor) { + if (data.length == 9) { + if (rowMajor) { + data[0] = m00; + data[1] = m01; + data[2] = m02; + data[3] = m10; + data[4] = m11; + data[5] = m12; + data[6] = m20; + data[7] = m21; + data[8] = m22; + } else { + data[0] = m00; + data[1] = m10; + data[2] = m20; + data[3] = m01; + data[4] = m11; + data[5] = m21; + data[6] = m02; + data[7] = m12; + data[8] = m22; + } + } else if (data.length == 16) { + if (rowMajor) { + data[0] = m00; + data[1] = m01; + data[2] = m02; + data[4] = m10; + data[5] = m11; + data[6] = m12; + data[8] = m20; + data[9] = m21; + data[10] = m22; + } else { + data[0] = m00; + data[1] = m10; + data[2] = m20; + data[4] = m01; + data[5] = m11; + data[6] = m21; + data[8] = m02; + data[9] = m12; + data[10] = m22; + } + } else { + throw new IndexOutOfBoundsException("Array size must be 9 or 16 in Matrix3f.get()."); + } + } + + /** + * Normalize this matrix and store the result in the store parameter that is + * returned. + * + * Note that the original matrix is not altered. + * + * @param store the matrix to store the result of the normalization. If this + * parameter is null a new one is created + * @return the normalized matrix + */ + public Matrix3f normalize(Matrix3f store) { + if (store == null) { + store = new Matrix3f(); + } + + float mag = 1.0f + / FastMath + .sqrt( + m00 * m00 + + m10 * m10 + + m20 * m20 + ); + + store.m00 = m00 * mag; + store.m10 = m10 * mag; + store.m20 = m20 * mag; + + mag = 1.0f + / FastMath + .sqrt( + m01 * m01 + + m11 * m11 + + m21 * m21 + ); + + store.m01 = m01 * mag; + store.m11 = m11 * mag; + store.m21 = m21 * mag; + + store.m02 = store.m10 * store.m21 - store.m11 * store.m20; + store.m12 = store.m01 * store.m20 - store.m00 * store.m21; + store.m22 = store.m00 * store.m11 - store.m01 * store.m10; + return store; + } + + /** + * Normalize this matrix + * + * @return this matrix once normalized. + */ + public Matrix3f normalizeLocal() { + return normalize(this); + } + + /** + * getColumn returns one of three columns specified by the + * parameter. This column is returned as a Vector3f object. + * + * @param i the column to retrieve. Must be between 0 and 2. + * @return the column specified by the index. + */ + public Vector3f getColumn(int i) { + return getColumn(i, null); + } + + /** + * getColumn returns one of three columns specified by the + * parameter. This column is returned as a Vector3f object. + * + * @param i the column to retrieve. Must be between 0 and 2. + * @param store the vector object to store the result in. if null, a new one + * is created. + * @return the column specified by the index. + */ + public Vector3f getColumn(int i, Vector3f store) { + if (store == null) { + store = new Vector3f(); + } + switch (i) { + case 0: + store.x = m00; + store.y = m10; + store.z = m20; + break; + case 1: + store.x = m01; + store.y = m11; + store.z = m21; + break; + case 2: + store.x = m02; + store.y = m12; + store.z = m22; + break; + default: + logger.warning("Invalid column index."); + throw new IllegalArgumentException("Invalid column index. " + i); + } + return store; + } + + /** + * getColumn returns one of three rows as specified by the + * parameter. This row is returned as a Vector3f object. + * + * @param i the row to retrieve. Must be between 0 and 2. + * @return the row specified by the index. + */ + public Vector3f getRow(int i) { + return getRow(i, null); + } + + /** + * getRow returns one of three rows as specified by the + * parameter. This row is returned as a Vector3f object. + * + * @param i the row to retrieve. Must be between 0 and 2. + * @param store the vector object to store the result in. if null, a new one + * is created. + * @return the row specified by the index. + */ + public Vector3f getRow(int i, Vector3f store) { + if (store == null) { + store = new Vector3f(); + } + switch (i) { + case 0: + store.x = m00; + store.y = m01; + store.z = m02; + break; + case 1: + store.x = m10; + store.y = m11; + store.z = m12; + break; + case 2: + store.x = m20; + store.y = m21; + store.z = m22; + break; + default: + logger.warning("Invalid row index."); + throw new IllegalArgumentException("Invalid row index. " + i); + } + return store; + } + + /** + * fillFloatBuffer fills a FloatBuffer object with the matrix + * data. + * + * @param fb the buffer to fill, starting at current position. Must have + * room for 9 more floats. + * @return matrix data as a FloatBuffer. (position is advanced by 9 and any + * limit set is not changed). + */ + public FloatBuffer fillFloatBuffer(FloatBuffer fb, boolean columnMajor) { +// if (columnMajor){ +// fb.put(m00).put(m10).put(m20); +// fb.put(m01).put(m11).put(m21); +// fb.put(m02).put(m12).put(m22); +// }else{ +// fb.put(m00).put(m01).put(m02); +// fb.put(m10).put(m11).put(m12); +// fb.put(m20).put(m21).put(m22); +// } + + TempVars vars = TempVars.get(); + + + fillFloatArray(vars.matrixWrite, columnMajor); + fb.put(vars.matrixWrite, 0, 9); + + vars.release(); + + return fb; + } + + public void fillFloatArray(float[] f, boolean columnMajor) { + if (columnMajor) { + f[0] = m00; + f[1] = m10; + f[2] = m20; + f[3] = m01; + f[4] = m11; + f[5] = m21; + f[6] = m02; + f[7] = m12; + f[8] = m22; + } else { + f[0] = m00; + f[1] = m01; + f[2] = m02; + f[3] = m10; + f[4] = m11; + f[5] = m12; + f[6] = m20; + f[7] = m21; + f[8] = m22; + } + } + + /** + * + * setColumn sets a particular column of this matrix to that + * represented by the provided vector. + * + * @param i the column to set. + * @param column the data to set. + * @return this + */ + public Matrix3f setColumn(int i, Vector3f column) { + + if (column == null) { + logger.warning("Column is null. Ignoring."); + return this; + } + switch (i) { + case 0: + m00 = column.x; + m10 = column.y; + m20 = column.z; + break; + case 1: + m01 = column.x; + m11 = column.y; + m21 = column.z; + break; + case 2: + m02 = column.x; + m12 = column.y; + m22 = column.z; + break; + default: + logger.warning("Invalid column index."); + throw new IllegalArgumentException("Invalid column index. " + i); + } + return this; + } + + /** + * + * setRow sets a particular row of this matrix to that + * represented by the provided vector. + * + * @param i the row to set. + * @param row the data to set. + * @return this + */ + public Matrix3f setRow(int i, Vector3f row) { + + if (row == null) { + logger.warning("Row is null. Ignoring."); + return this; + } + switch (i) { + case 0: + m00 = row.x; + m01 = row.y; + m02 = row.z; + break; + case 1: + m10 = row.x; + m11 = row.y; + m12 = row.z; + break; + case 2: + m20 = row.x; + m21 = row.y; + m22 = row.z; + break; + default: + logger.warning("Invalid row index."); + throw new IllegalArgumentException("Invalid row index. " + i); + } + return this; + } + + /** + * set places a given value into the matrix at the given + * position. If the position is invalid a JmeException is + * thrown. + * + * @param i the row index. + * @param j the colum index. + * @param value the value for (i, j). + * @return this + */ + @SuppressWarnings("fallthrough") + public Matrix3f set(int i, int j, float value) { + switch (i) { + case 0: + switch (j) { + case 0: + m00 = value; + return this; + case 1: + m01 = value; + return this; + case 2: + m02 = value; + return this; + } + case 1: + switch (j) { + case 0: + m10 = value; + return this; + case 1: + m11 = value; + return this; + case 2: + m12 = value; + return this; + } + case 2: + switch (j) { + case 0: + m20 = value; + return this; + case 1: + m21 = value; + return this; + case 2: + m22 = value; + return this; + } + } + + logger.warning("Invalid matrix index."); + throw new IllegalArgumentException("Invalid indices into matrix."); + } + + /** + * + * set sets the values of the matrix to those supplied by the + * 3x3 two dimenion array. + * + * @param matrix the new values of the matrix. + * @throws IllegalArgumentException if the array is not of size 9. + * @return this + */ + public Matrix3f set(float[][] matrix) { + if (matrix.length != 3 || matrix[0].length != 3) { + throw new IllegalArgumentException( + "Array must be of size 9." + ); + } + + m00 = matrix[0][0]; + m01 = matrix[0][1]; + m02 = matrix[0][2]; + m10 = matrix[1][0]; + m11 = matrix[1][1]; + m12 = matrix[1][2]; + m20 = matrix[2][0]; + m21 = matrix[2][1]; + m22 = matrix[2][2]; + + return this; + } + + /** + * Recreate Matrix using the provided axis. + * + * @param uAxis Vector3f + * @param vAxis Vector3f + * @param wAxis Vector3f + */ + public void fromAxes(Vector3f uAxis, Vector3f vAxis, Vector3f wAxis) { + m00 = uAxis.x; + m10 = uAxis.y; + m20 = uAxis.z; + + m01 = vAxis.x; + m11 = vAxis.y; + m21 = vAxis.z; + + m02 = wAxis.x; + m12 = wAxis.y; + m22 = wAxis.z; + } + + /** + * set sets the values of this matrix from an array of values + * assuming that the data is rowMajor order; + * + * @param matrix the matrix to set the value to. + * @return this + */ + public Matrix3f set(float[] matrix) { + return set(matrix, true); + } + + /** + * set sets the values of this matrix from an array of values; + * + * @param matrix the matrix to set the value to. + * @param rowMajor whether the incoming data is in row or column major + * order. + * @return this + */ + public Matrix3f set(float[] matrix, boolean rowMajor) { + if (matrix.length != 9) { + throw new IllegalArgumentException( + "Array must be of size 9." + ); + } + + if (rowMajor) { + m00 = matrix[0]; + m01 = matrix[1]; + m02 = matrix[2]; + m10 = matrix[3]; + m11 = matrix[4]; + m12 = matrix[5]; + m20 = matrix[6]; + m21 = matrix[7]; + m22 = matrix[8]; + } else { + m00 = matrix[0]; + m01 = matrix[3]; + m02 = matrix[6]; + m10 = matrix[1]; + m11 = matrix[4]; + m12 = matrix[7]; + m20 = matrix[2]; + m21 = matrix[5]; + m22 = matrix[8]; + } + return this; + } + + /** + * + * set defines the values of the matrix based on a supplied + * Quaternion. It should be noted that all previous values will + * be overridden. + * + * @param quaternion the quaternion to create a rotational matrix from. + * @return this + */ + public Matrix3f set(Quaternion quaternion) { + return quaternion.toRotationMatrix(this); + } + + /** + * loadIdentity sets this matrix to the identity matrix. Where + * all values are zero except those along the diagonal which are one. + * + */ + public void loadIdentity() { + m01 = m02 = m10 = m12 = m20 = m21 = 0; + m00 = m11 = m22 = 1; + } + + /** + * @return true if this matrix is identity + */ + public boolean isIdentity() { + return (m00 == 1 && m01 == 0 && m02 == 0) + && (m10 == 0 && m11 == 1 && m12 == 0) + && (m20 == 0 && m21 == 0 && m22 == 1); + } + + /** + * fromAngleAxis sets this matrix4f to the values specified by + * an angle and an axis of rotation. This method creates an object, so use + * fromAngleNormalAxis if your axis is already normalized. + * + * @param angle the angle to rotate (in radians). + * @param axis the axis of rotation. + */ + public void fromAngleAxis(float angle, Vector3f axis) { + Vector3f normAxis = axis.normalize(); + fromAngleNormalAxis(angle, normAxis); + } + + /** + * fromAngleNormalAxis sets this matrix4f to the values + * specified by an angle and a normalized axis of rotation. + * + * @param angle the angle to rotate (in radians). + * @param axis the axis of rotation (already normalized). + */ + public void fromAngleNormalAxis(float angle, Vector3f axis) { + float fCos = FastMath.cos(angle); + float fSin = FastMath.sin(angle); + float fOneMinusCos = ((float) 1.0) - fCos; + float fX2 = axis.x * axis.x; + float fY2 = axis.y * axis.y; + float fZ2 = axis.z * axis.z; + float fXYM = axis.x * axis.y * fOneMinusCos; + float fXZM = axis.x * axis.z * fOneMinusCos; + float fYZM = axis.y * axis.z * fOneMinusCos; + float fXSin = axis.x * fSin; + float fYSin = axis.y * fSin; + float fZSin = axis.z * fSin; + + m00 = fX2 * fOneMinusCos + fCos; + m01 = fXYM - fZSin; + m02 = fXZM + fYSin; + m10 = fXYM + fZSin; + m11 = fY2 * fOneMinusCos + fCos; + m12 = fYZM - fXSin; + m20 = fXZM - fYSin; + m21 = fYZM + fXSin; + m22 = fZ2 * fOneMinusCos + fCos; + } + + /** + * mult multiplies this matrix by a given matrix. The result + * matrix is returned as a new object. If the given matrix is null, a null + * matrix is returned. + * + * @param mat the matrix to multiply this matrix by. + * @return the result matrix. + */ + public Matrix3f mult(Matrix3f mat) { + return mult(mat, null); + } + + /** + * mult multiplies this matrix by a given matrix. The result + * matrix is returned as a new object. + * + * @param mat the matrix to multiply this matrix by. + * @param product the matrix to store the result in. if null, a new matrix3f + * is created. It is safe for mat and product to be the same object. + * @return a matrix3f object containing the result of this operation + */ + public Matrix3f mult(Matrix3f mat, Matrix3f product) { + + float temp00, temp01, temp02; + float temp10, temp11, temp12; + float temp20, temp21, temp22; + + if (product == null) { + product = new Matrix3f(); + } + temp00 = m00 * mat.m00 + m01 * mat.m10 + m02 * mat.m20; + temp01 = m00 * mat.m01 + m01 * mat.m11 + m02 * mat.m21; + temp02 = m00 * mat.m02 + m01 * mat.m12 + m02 * mat.m22; + temp10 = m10 * mat.m00 + m11 * mat.m10 + m12 * mat.m20; + temp11 = m10 * mat.m01 + m11 * mat.m11 + m12 * mat.m21; + temp12 = m10 * mat.m02 + m11 * mat.m12 + m12 * mat.m22; + temp20 = m20 * mat.m00 + m21 * mat.m10 + m22 * mat.m20; + temp21 = m20 * mat.m01 + m21 * mat.m11 + m22 * mat.m21; + temp22 = m20 * mat.m02 + m21 * mat.m12 + m22 * mat.m22; + + product.m00 = temp00; + product.m01 = temp01; + product.m02 = temp02; + product.m10 = temp10; + product.m11 = temp11; + product.m12 = temp12; + product.m20 = temp20; + product.m21 = temp21; + product.m22 = temp22; + + return product; + } + + /** + * mult multiplies this matrix by a given Vector3f + * object. The result vector is returned. If the given vector is null, null + * will be returned. + * + * @param vec the vector to multiply this matrix by. + * @return the result vector. + */ + public Vector3f mult(Vector3f vec) { + return mult(vec, null); + } + + /** + * Multiplies this 3x3 matrix by the 1x3 Vector vec and stores the result in + * product. + * + * @param vec The Vector3f to multiply. + * @param product The Vector3f to store the result, it is safe for this to + * be the same as vec. + * @return The given product vector. + */ + public Vector3f mult(Vector3f vec, Vector3f product) { + + if (null == product) { + product = new Vector3f(); + } + + float x = vec.x; + float y = vec.y; + float z = vec.z; + + product.x = m00 * x + m01 * y + m02 * z; + product.y = m10 * x + m11 * y + m12 * z; + product.z = m20 * x + m21 * y + m22 * z; + return product; + } + + /** + * multLocal multiplies this matrix internally by a given float + * scale factor. + * + * @param scale the value to scale by. + * @return this Matrix3f + */ + public Matrix3f multLocal(float scale) { + m00 *= scale; + m01 *= scale; + m02 *= scale; + m10 *= scale; + m11 *= scale; + m12 *= scale; + m20 *= scale; + m21 *= scale; + m22 *= scale; + return this; + } + + /** + * multLocal multiplies this matrix by a given + * Vector3f object. The result vector is stored inside the + * passed vector, then returned . If the given vector is null, null will be + * returned. + * + * @param vec the vector to multiply this matrix by. + * @return The passed vector after multiplication + */ + public Vector3f multLocal(Vector3f vec) { + if (vec == null) { + return null; + } + float x = vec.x; + float y = vec.y; + vec.x = m00 * x + m01 * y + m02 * vec.z; + vec.y = m10 * x + m11 * y + m12 * vec.z; + vec.z = m20 * x + m21 * y + m22 * vec.z; + return vec; + } + + /** + * mult multiplies this matrix by a given matrix. The result + * matrix is saved in the current matrix. If the given matrix is null, + * nothing happens. The current matrix is returned. This is equivalent to + * this*=mat + * + * @param mat the matrix to multiply this matrix by. + * @return This matrix, after the multiplication + */ + public Matrix3f multLocal(Matrix3f mat) { + return mult(mat, this); + } + + /** + * Transposes this matrix in place. Returns this matrix for chaining + * + * @return This matrix after transpose + */ + public Matrix3f transposeLocal() { +// float[] tmp = new float[9]; +// get(tmp, false); +// set(tmp, true); + + float tmp = m01; + m01 = m10; + m10 = tmp; + + tmp = m02; + m02 = m20; + m20 = tmp; + + tmp = m12; + m12 = m21; + m21 = tmp; + + return this; + } + + /** + * Inverts this matrix as a new Matrix3f. + * + * @return The new inverse matrix + */ + public Matrix3f invert() { + return invert(null); + } + + /** + * Inverts this matrix and stores it in the given store. + * + * @return The store + */ + public Matrix3f invert(Matrix3f store) { + if (store == null) { + store = new Matrix3f(); + } + + float det = determinant(); + if (FastMath.abs(det) <= FastMath.FLT_EPSILON) { + return store.zero(); + } + + store.m00 = m11 * m22 - m12 * m21; + store.m01 = m02 * m21 - m01 * m22; + store.m02 = m01 * m12 - m02 * m11; + store.m10 = m12 * m20 - m10 * m22; + store.m11 = m00 * m22 - m02 * m20; + store.m12 = m02 * m10 - m00 * m12; + store.m20 = m10 * m21 - m11 * m20; + store.m21 = m01 * m20 - m00 * m21; + store.m22 = m00 * m11 - m01 * m10; + + store.multLocal(1f / det); + return store; + } + + /** + * Inverts this matrix locally. + * + * @return this + */ + public Matrix3f invertLocal() { + float det = determinant(); + if (FastMath.abs(det) <= 0f) { + return zero(); + } + + float f00 = m11 * m22 - m12 * m21; + float f01 = m02 * m21 - m01 * m22; + float f02 = m01 * m12 - m02 * m11; + float f10 = m12 * m20 - m10 * m22; + float f11 = m00 * m22 - m02 * m20; + float f12 = m02 * m10 - m00 * m12; + float f20 = m10 * m21 - m11 * m20; + float f21 = m01 * m20 - m00 * m21; + float f22 = m00 * m11 - m01 * m10; + + m00 = f00; + m01 = f01; + m02 = f02; + m10 = f10; + m11 = f11; + m12 = f12; + m20 = f20; + m21 = f21; + m22 = f22; + + multLocal(1f / det); + return this; + } + + /** + * Returns a new matrix representing the adjoint of this matrix. + * + * @return The adjoint matrix + */ + public Matrix3f adjoint() { + return adjoint(null); + } + + /** + * Places the adjoint of this matrix in store (creates store if null.) + * + * @param store The matrix to store the result in. If null, a new matrix is + * created. + * @return store + */ + public Matrix3f adjoint(Matrix3f store) { + if (store == null) { + store = new Matrix3f(); + } + + store.m00 = m11 * m22 - m12 * m21; + store.m01 = m02 * m21 - m01 * m22; + store.m02 = m01 * m12 - m02 * m11; + store.m10 = m12 * m20 - m10 * m22; + store.m11 = m00 * m22 - m02 * m20; + store.m12 = m02 * m10 - m00 * m12; + store.m20 = m10 * m21 - m11 * m20; + store.m21 = m01 * m20 - m00 * m21; + store.m22 = m00 * m11 - m01 * m10; + + return store; + } + + /** + * determinant generates the determinant of this matrix. + * + * @return the determinant + */ + public float determinant() { + float fCo00 = m11 * m22 - m12 * m21; + float fCo10 = m12 * m20 - m10 * m22; + float fCo20 = m10 * m21 - m11 * m20; + float fDet = m00 * fCo00 + m01 * fCo10 + m02 * fCo20; + return fDet; + } + + /** + * Sets all of the values in this matrix to zero. + * + * @return this matrix + */ + public Matrix3f zero() { + m00 = m01 = m02 = m10 = m11 = m12 = m20 = m21 = m22 = 0.0f; + return this; + } + + /** + * transpose locally transposes this Matrix. This is + * inconsistent with general value vs local semantics, but is preserved for + * backwards compatibility. Use transposeNew() to transpose to a new object + * (value). + * + * @return this object for chaining. + */ + public Matrix3f transpose() { + return transposeLocal(); + } + + /** + * transposeNew returns a transposed version of this matrix. + * + * @return The new Matrix3f object. + */ + public Matrix3f transposeNew() { + Matrix3f ret = new Matrix3f(m00, m10, m20, m01, m11, m21, m02, m12, m22); + return ret; + } + + /** + * toString returns the string representation of this object. + * It is in a format of a 3x3 matrix. For example, an identity matrix would + * be represented by the following string. com.jme.math.Matrix3f
+ * [
+ * 1.0 0.0 0.0
+ * 0.0 1.0 0.0
+ * 0.0 0.0 1.0
+ * ]
+ * + * @return the string representation of this object. + */ + @Override + public String toString() { + StringBuilder result = new StringBuilder("Matrix3f\n[\n"); + result.append(" "); + result.append(m00); + result.append(" "); + result.append(m01); + result.append(" "); + result.append(m02); + result.append(" \n"); + result.append(" "); + result.append(m10); + result.append(" "); + result.append(m11); + result.append(" "); + result.append(m12); + result.append(" \n"); + result.append(" "); + result.append(m20); + result.append(" "); + result.append(m21); + result.append(" "); + result.append(m22); + result.append(" \n]"); + return result.toString(); + } + + /** + * + * hashCode returns the hash code value as an integer and is + * supported for the benefit of hashing based collection classes such as + * Hashtable, HashMap, HashSet etc. + * + * @return the hashcode for this instance of Matrix4f. + * @see java.lang.Object#hashCode() + */ + @Override + public int hashCode() { + int hash = 37; + hash = 37 * hash + Float.floatToIntBits(m00); + hash = 37 * hash + Float.floatToIntBits(m01); + hash = 37 * hash + Float.floatToIntBits(m02); + + hash = 37 * hash + Float.floatToIntBits(m10); + hash = 37 * hash + Float.floatToIntBits(m11); + hash = 37 * hash + Float.floatToIntBits(m12); + + hash = 37 * hash + Float.floatToIntBits(m20); + hash = 37 * hash + Float.floatToIntBits(m21); + hash = 37 * hash + Float.floatToIntBits(m22); + + return hash; + } + + /** + * are these two matrices the same? they are is they both have the same mXX + * values. + * + * @param o the object to compare for equality + * @return true if they are equal + */ + @Override + public boolean equals(Object o) { + if (!(o instanceof Matrix3f) || o == null) { + return false; + } + + if (this == o) { + return true; + } + + Matrix3f comp = (Matrix3f) o; + if (Float.compare(m00, comp.m00) != 0) { + return false; + } + if (Float.compare(m01, comp.m01) != 0) { + return false; + } + if (Float.compare(m02, comp.m02) != 0) { + return false; + } + + if (Float.compare(m10, comp.m10) != 0) { + return false; + } + if (Float.compare(m11, comp.m11) != 0) { + return false; + } + if (Float.compare(m12, comp.m12) != 0) { + return false; + } + + if (Float.compare(m20, comp.m20) != 0) { + return false; + } + if (Float.compare(m21, comp.m21) != 0) { + return false; + } + if (Float.compare(m22, comp.m22) != 0) { + return false; + } + + return true; + } + + /** + * A function for creating a rotation matrix that rotates a vector called + * "start" into another vector called "end". + * + * @param start normalized non-zero starting vector + * @param end normalized non-zero ending vector + * @see "Tomas M�ller, John Hughes \"Efficiently Building a Matrix to Rotate + * \ One Vector to Another\" Journal of Graphics Tools, 4(4):1-4, 1999" + */ + public void fromStartEndVectors(Vector3f start, Vector3f end) { + Vector3f v = new Vector3f(); + float e, h, f; + + start.cross(end, v); + e = start.dot(end); + f = (e < 0) ? -e : e; + + // if "from" and "to" vectors are nearly parallel + if (f > 1.0f - FastMath.ZERO_TOLERANCE) { + Vector3f u = new Vector3f(); + Vector3f x = new Vector3f(); + float c1, c2, c3; /* coefficients for later use */ + int i, j; + + x.x = (start.x > 0.0) ? start.x : -start.x; + x.y = (start.y > 0.0) ? start.y : -start.y; + x.z = (start.z > 0.0) ? start.z : -start.z; + + if (x.x < x.y) { + if (x.x < x.z) { + x.x = 1.0f; + x.y = x.z = 0.0f; + } else { + x.z = 1.0f; + x.x = x.y = 0.0f; + } + } else { + if (x.y < x.z) { + x.y = 1.0f; + x.x = x.z = 0.0f; + } else { + x.z = 1.0f; + x.x = x.y = 0.0f; + } + } + + u.x = x.x - start.x; + u.y = x.y - start.y; + u.z = x.z - start.z; + v.x = x.x - end.x; + v.y = x.y - end.y; + v.z = x.z - end.z; + + c1 = 2.0f / u.dot(u); + c2 = 2.0f / v.dot(v); + c3 = c1 * c2 * u.dot(v); + + for (i = 0; i < 3; i++) { + for (j = 0; j < 3; j++) { + float val = -c1 * u.get(i) * u.get(j) + - c2 + * v.get(i) + * v.get(j) + + c3 * v.get(i) * u.get(j); + set(i, j, val); + } + float val = get(i, i); + set(i, i, val + 1.0f); + } + } else { + // the most common case, unless "start"="end", or "start"=-"end" + float hvx, hvz, hvxy, hvxz, hvyz; + h = 1.0f / (1.0f + e); + hvx = h * v.x; + hvz = h * v.z; + hvxy = hvx * v.y; + hvxz = hvx * v.z; + hvyz = hvz * v.y; + set(0, 0, e + hvx * v.x); + set(0, 1, hvxy - v.z); + set(0, 2, hvxz + v.y); + + set(1, 0, hvxy + v.z); + set(1, 1, e + h * v.y * v.y); + set(1, 2, hvyz - v.x); + + set(2, 0, hvxz - v.y); + set(2, 1, hvyz + v.x); + set(2, 2, e + hvz * v.z); + } + } + + /** + * scale scales the operation performed by this matrix on a + * per-component basis. + * + * @param scale The scale applied to each of the X, Y and Z output values. + */ + public void scale(Vector3f scale) { + m00 *= scale.x; + m10 *= scale.x; + m20 *= scale.x; + m01 *= scale.y; + m11 *= scale.y; + m21 *= scale.y; + m02 *= scale.z; + m12 *= scale.z; + m22 *= scale.z; + } + + static boolean equalIdentity(Matrix3f mat) { + if (Math.abs(mat.m00 - 1) > 1e-4) { + return false; + } + if (Math.abs(mat.m11 - 1) > 1e-4) { + return false; + } + if (Math.abs(mat.m22 - 1) > 1e-4) { + return false; + } + + if (Math.abs(mat.m01) > 1e-4) { + return false; + } + if (Math.abs(mat.m02) > 1e-4) { + return false; + } + + if (Math.abs(mat.m10) > 1e-4) { + return false; + } + if (Math.abs(mat.m12) > 1e-4) { + return false; + } + + if (Math.abs(mat.m20) > 1e-4) { + return false; + } + if (Math.abs(mat.m21) > 1e-4) { + return false; + } + + return true; + } + + @Override + public Matrix3f clone() { + try { + return (Matrix3f) super.clone(); + } catch (CloneNotSupportedException e) { + throw new AssertionError(); // can not happen + } + } +} diff --git a/src/main/java/com/jme3/math/Matrix4f.java b/src/main/java/com/jme3/math/Matrix4f.java new file mode 100644 index 000000000..e9988870b --- /dev/null +++ b/src/main/java/com/jme3/math/Matrix4f.java @@ -0,0 +1,2275 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.math; + +import java.nio.FloatBuffer; +import java.util.logging.Logger; + + +/** + * Matrix4f defines and maintains a 4x4 matrix in row major order. + * This matrix is intended for use in a translation and rotational capacity. It + * provides convenience methods for creating the matrix from a multitude of + * sources. + * + * Matrices are stored assuming column vectors on the right, with the + * translation in the rightmost column. Element numbering is row,column, so m03 + * is the zeroth row, third column, which is the "x" translation part. This + * means that the implicit storage order is column major. However, the get() and + * set() functions on float arrays default to row major order! + * + * @author Mark Powell + * @author Joshua Slack + */ +public final class Matrix4f implements Cloneable, java.io.Serializable { + + static final long serialVersionUID = 1; + + private static final Logger logger = Logger.getLogger(Matrix4f.class.getName()); + public float m00, m01, m02, m03; + public float m10, m11, m12, m13; + public float m20, m21, m22, m23; + public float m30, m31, m32, m33; + public static final Matrix4f ZERO = new Matrix4f( + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0 + ); + public static final Matrix4f IDENTITY = new Matrix4f(); + + /** + * Constructor instantiates a new Matrix that is set to the + * identity matrix. + * + */ + public Matrix4f() { + loadIdentity(); + } + + /** + * constructs a matrix with the given values. + */ + public Matrix4f( + float m00, + float m01, + float m02, + float m03, + float m10, + float m11, + float m12, + float m13, + float m20, + float m21, + float m22, + float m23, + float m30, + float m31, + float m32, + float m33 + ) { + + this.m00 = m00; + this.m01 = m01; + this.m02 = m02; + this.m03 = m03; + this.m10 = m10; + this.m11 = m11; + this.m12 = m12; + this.m13 = m13; + this.m20 = m20; + this.m21 = m21; + this.m22 = m22; + this.m23 = m23; + this.m30 = m30; + this.m31 = m31; + this.m32 = m32; + this.m33 = m33; + } + + /** + * Create a new Matrix4f, given data in column-major format. + * + * @param array An array of 16 floats in column-major format (translation in + * elements 12, 13 and 14). + */ + public Matrix4f(float[] array) { + set(array, false); + } + + /** + * Constructor instantiates a new Matrix that is set to the + * provided matrix. This constructor copies a given Matrix. If the provided + * matrix is null, the constructor sets the matrix to the identity. + * + * @param mat the matrix to copy. + */ + public Matrix4f(Matrix4f mat) { + copy(mat); + } + + /** + * copy transfers the contents of a given matrix to this + * matrix. If a null matrix is supplied, this matrix is set to the identity + * matrix. + * + * @param matrix the matrix to copy. + */ + public void copy(Matrix4f matrix) { + if (null == matrix) { + loadIdentity(); + } else { + m00 = matrix.m00; + m01 = matrix.m01; + m02 = matrix.m02; + m03 = matrix.m03; + m10 = matrix.m10; + m11 = matrix.m11; + m12 = matrix.m12; + m13 = matrix.m13; + m20 = matrix.m20; + m21 = matrix.m21; + m22 = matrix.m22; + m23 = matrix.m23; + m30 = matrix.m30; + m31 = matrix.m31; + m32 = matrix.m32; + m33 = matrix.m33; + } + } + + public void fromFrame(Vector3f location, Vector3f direction, Vector3f up, Vector3f left) { + loadIdentity(); + + TempVars vars = TempVars.get(); + + Vector3f f = vars.vect1.set(direction); + Vector3f s = vars.vect2.set(f).crossLocal(up); + Vector3f u = vars.vect3.set(s).crossLocal(f); +// s.normalizeLocal(); +// u.normalizeLocal(); + + m00 = s.x; + m01 = s.y; + m02 = s.z; + + m10 = u.x; + m11 = u.y; + m12 = u.z; + + m20 = -f.x; + m21 = -f.y; + m22 = -f.z; + +// m00 = -left.x; +// m10 = -left.y; +// m20 = -left.z; +// +// m01 = up.x; +// m11 = up.y; +// m21 = up.z; +// +// m02 = -direction.x; +// m12 = -direction.y; +// m22 = -direction.z; +// + + Matrix4f transMatrix = vars.tempMat4; + transMatrix.loadIdentity(); + transMatrix.m03 = -location.x; + transMatrix.m13 = -location.y; + transMatrix.m23 = -location.z; + this.multLocal(transMatrix); + + vars.release(); + +// transMatrix.multLocal(this); + +// set(transMatrix); + } + + /** + * get retrieves the values of this object into a float array + * in row-major order. + * + * @param matrix the matrix to set the values into. + */ + public void get(float[] matrix) { + get(matrix, true); + } + + /** + * set retrieves the values of this object into a float array. + * + * @param matrix the matrix to set the values into. + * @param rowMajor whether the outgoing data is in row or column major + * order. + */ + public void get(float[] matrix, boolean rowMajor) { + if (matrix.length != 16) { + throw new IllegalArgumentException( + "Array must be of size 16." + ); + } + + if (rowMajor) { + matrix[0] = m00; + matrix[1] = m01; + matrix[2] = m02; + matrix[3] = m03; + matrix[4] = m10; + matrix[5] = m11; + matrix[6] = m12; + matrix[7] = m13; + matrix[8] = m20; + matrix[9] = m21; + matrix[10] = m22; + matrix[11] = m23; + matrix[12] = m30; + matrix[13] = m31; + matrix[14] = m32; + matrix[15] = m33; + } else { + matrix[0] = m00; + matrix[4] = m01; + matrix[8] = m02; + matrix[12] = m03; + matrix[1] = m10; + matrix[5] = m11; + matrix[9] = m12; + matrix[13] = m13; + matrix[2] = m20; + matrix[6] = m21; + matrix[10] = m22; + matrix[14] = m23; + matrix[3] = m30; + matrix[7] = m31; + matrix[11] = m32; + matrix[15] = m33; + } + } + + /** + * get retrieves a value from the matrix at the given position. + * If the position is invalid a JmeException is thrown. + * + * @param i the row index. + * @param j the colum index. + * @return the value at (i, j). + */ + @SuppressWarnings("fallthrough") + public float get(int i, int j) { + switch (i) { + case 0: + switch (j) { + case 0: + return m00; + case 1: + return m01; + case 2: + return m02; + case 3: + return m03; + } + case 1: + switch (j) { + case 0: + return m10; + case 1: + return m11; + case 2: + return m12; + case 3: + return m13; + } + case 2: + switch (j) { + case 0: + return m20; + case 1: + return m21; + case 2: + return m22; + case 3: + return m23; + } + case 3: + switch (j) { + case 0: + return m30; + case 1: + return m31; + case 2: + return m32; + case 3: + return m33; + } + } + + logger.warning("Invalid matrix index."); + throw new IllegalArgumentException("Invalid indices into matrix."); + } + + /** + * getColumn returns one of three columns specified by the + * parameter. This column is returned as a float array of length 4. + * + * @param i the column to retrieve. Must be between 0 and 3. + * @return the column specified by the index. + */ + public float[] getColumn(int i) { + return getColumn(i, null); + } + + /** + * getColumn returns one of three columns specified by the + * parameter. This column is returned as a float[4]. + * + * @param i the column to retrieve. Must be between 0 and 3. + * @param store the float array to store the result in. if null, a new one + * is created. + * @return the column specified by the index. + */ + public float[] getColumn(int i, float[] store) { + if (store == null) { + store = new float[4]; + } + switch (i) { + case 0: + store[0] = m00; + store[1] = m10; + store[2] = m20; + store[3] = m30; + break; + case 1: + store[0] = m01; + store[1] = m11; + store[2] = m21; + store[3] = m31; + break; + case 2: + store[0] = m02; + store[1] = m12; + store[2] = m22; + store[3] = m32; + break; + case 3: + store[0] = m03; + store[1] = m13; + store[2] = m23; + store[3] = m33; + break; + default: + logger.warning("Invalid column index."); + throw new IllegalArgumentException("Invalid column index. " + i); + } + return store; + } + + /** + * + * setColumn sets a particular column of this matrix to that + * represented by the provided vector. + * + * @param i the column to set. + * @param column the data to set. + */ + public void setColumn(int i, float[] column) { + + if (column == null) { + logger.warning("Column is null. Ignoring."); + return; + } + switch (i) { + case 0: + m00 = column[0]; + m10 = column[1]; + m20 = column[2]; + m30 = column[3]; + break; + case 1: + m01 = column[0]; + m11 = column[1]; + m21 = column[2]; + m31 = column[3]; + break; + case 2: + m02 = column[0]; + m12 = column[1]; + m22 = column[2]; + m32 = column[3]; + break; + case 3: + m03 = column[0]; + m13 = column[1]; + m23 = column[2]; + m33 = column[3]; + break; + default: + logger.warning("Invalid column index."); + throw new IllegalArgumentException("Invalid column index. " + i); + } + } + + /** + * set places a given value into the matrix at the given + * position. If the position is invalid a JmeException is + * thrown. + * + * @param i the row index. + * @param j the colum index. + * @param value the value for (i, j). + */ + @SuppressWarnings("fallthrough") + public void set(int i, int j, float value) { + switch (i) { + case 0: + switch (j) { + case 0: + m00 = value; + return; + case 1: + m01 = value; + return; + case 2: + m02 = value; + return; + case 3: + m03 = value; + return; + } + case 1: + switch (j) { + case 0: + m10 = value; + return; + case 1: + m11 = value; + return; + case 2: + m12 = value; + return; + case 3: + m13 = value; + return; + } + case 2: + switch (j) { + case 0: + m20 = value; + return; + case 1: + m21 = value; + return; + case 2: + m22 = value; + return; + case 3: + m23 = value; + return; + } + case 3: + switch (j) { + case 0: + m30 = value; + return; + case 1: + m31 = value; + return; + case 2: + m32 = value; + return; + case 3: + m33 = value; + return; + } + } + + logger.warning("Invalid matrix index."); + throw new IllegalArgumentException("Invalid indices into matrix."); + } + + /** + * set sets the values of this matrix from an array of values. + * + * @param matrix the matrix to set the value to. + * @throws IllegalArgumentException if the array is not of size 16. + */ + public void set(float[][] matrix) { + if (matrix.length != 4 || matrix[0].length != 4) { + throw new IllegalArgumentException( + "Array must be of size 16." + ); + } + + m00 = matrix[0][0]; + m01 = matrix[0][1]; + m02 = matrix[0][2]; + m03 = matrix[0][3]; + m10 = matrix[1][0]; + m11 = matrix[1][1]; + m12 = matrix[1][2]; + m13 = matrix[1][3]; + m20 = matrix[2][0]; + m21 = matrix[2][1]; + m22 = matrix[2][2]; + m23 = matrix[2][3]; + m30 = matrix[3][0]; + m31 = matrix[3][1]; + m32 = matrix[3][2]; + m33 = matrix[3][3]; + } + + + /** + * Sets the values of this matrix + */ + public void set( + float m00, + float m01, + float m02, + float m03, + float m10, + float m11, + float m12, + float m13, + float m20, + float m21, + float m22, + float m23, + float m30, + float m31, + float m32, + float m33 + ) { + + this.m00 = m00; + this.m01 = m01; + this.m02 = m02; + this.m03 = m03; + this.m10 = m10; + this.m11 = m11; + this.m12 = m12; + this.m13 = m13; + this.m20 = m20; + this.m21 = m21; + this.m22 = m22; + this.m23 = m23; + this.m30 = m30; + this.m31 = m31; + this.m32 = m32; + this.m33 = m33; + } + + /** + * set sets the values of this matrix from another matrix. + * + * @param matrix the matrix to read the value from. + */ + public Matrix4f set(Matrix4f matrix) { + m00 = matrix.m00; + m01 = matrix.m01; + m02 = matrix.m02; + m03 = matrix.m03; + m10 = matrix.m10; + m11 = matrix.m11; + m12 = matrix.m12; + m13 = matrix.m13; + m20 = matrix.m20; + m21 = matrix.m21; + m22 = matrix.m22; + m23 = matrix.m23; + m30 = matrix.m30; + m31 = matrix.m31; + m32 = matrix.m32; + m33 = matrix.m33; + return this; + } + + /** + * set sets the values of this matrix from an array of values + * assuming that the data is rowMajor order; + * + * @param matrix the matrix to set the value to. + */ + public void set(float[] matrix) { + set(matrix, true); + } + + /** + * set sets the values of this matrix from an array of values; + * + * @param matrix the matrix to set the value to. + * @param rowMajor whether the incoming data is in row or column major + * order. + */ + public void set(float[] matrix, boolean rowMajor) { + if (matrix.length != 16) { + throw new IllegalArgumentException( + "Array must be of size 16." + ); + } + + if (rowMajor) { + m00 = matrix[0]; + m01 = matrix[1]; + m02 = matrix[2]; + m03 = matrix[3]; + m10 = matrix[4]; + m11 = matrix[5]; + m12 = matrix[6]; + m13 = matrix[7]; + m20 = matrix[8]; + m21 = matrix[9]; + m22 = matrix[10]; + m23 = matrix[11]; + m30 = matrix[12]; + m31 = matrix[13]; + m32 = matrix[14]; + m33 = matrix[15]; + } else { + m00 = matrix[0]; + m01 = matrix[4]; + m02 = matrix[8]; + m03 = matrix[12]; + m10 = matrix[1]; + m11 = matrix[5]; + m12 = matrix[9]; + m13 = matrix[13]; + m20 = matrix[2]; + m21 = matrix[6]; + m22 = matrix[10]; + m23 = matrix[14]; + m30 = matrix[3]; + m31 = matrix[7]; + m32 = matrix[11]; + m33 = matrix[15]; + } + } + + public Matrix4f transpose() { + float[] tmp = new float[16]; + get(tmp, true); + Matrix4f mat = new Matrix4f(tmp); + return mat; + } + + /** + * transpose locally transposes this Matrix. + * + * @return this object for chaining. + */ + public Matrix4f transposeLocal() { + float tmp = m01; + m01 = m10; + m10 = tmp; + + tmp = m02; + m02 = m20; + m20 = tmp; + + tmp = m03; + m03 = m30; + m30 = tmp; + + tmp = m12; + m12 = m21; + m21 = tmp; + + tmp = m13; + m13 = m31; + m31 = tmp; + + tmp = m23; + m23 = m32; + m32 = tmp; + + return this; + } + + /** + * fillFloatBuffer fills a FloatBuffer object with the matrix + * data. + * + * @param fb the buffer to fill, must be correct size + * @return matrix data as a FloatBuffer. + */ + public FloatBuffer fillFloatBuffer(FloatBuffer fb) { + return fillFloatBuffer(fb, false); + } + + /** + * fillFloatBuffer fills a FloatBuffer object with the matrix + * data. + * + * @param fb the buffer to fill, starting at current position. Must have + * room for 16 more floats. + * @param columnMajor if true, this buffer should be filled with column + * major data, otherwise it will be filled row major. + * @return matrix data as a FloatBuffer. (position is advanced by 16 and any + * limit set is not changed). + */ + public FloatBuffer fillFloatBuffer(FloatBuffer fb, boolean columnMajor) { +// if (columnMajor) { +// fb.put(m00).put(m10).put(m20).put(m30); +// fb.put(m01).put(m11).put(m21).put(m31); +// fb.put(m02).put(m12).put(m22).put(m32); +// fb.put(m03).put(m13).put(m23).put(m33); +// } else { +// fb.put(m00).put(m01).put(m02).put(m03); +// fb.put(m10).put(m11).put(m12).put(m13); +// fb.put(m20).put(m21).put(m22).put(m23); +// fb.put(m30).put(m31).put(m32).put(m33); +// } + + TempVars vars = TempVars.get(); + + + fillFloatArray(vars.matrixWrite, columnMajor); + fb.put(vars.matrixWrite, 0, 16); + + vars.release(); + + return fb; + } + + public void fillFloatArray(float[] f, boolean columnMajor) { + if (columnMajor) { + f[0] = m00; + f[1] = m10; + f[2] = m20; + f[3] = m30; + f[4] = m01; + f[5] = m11; + f[6] = m21; + f[7] = m31; + f[8] = m02; + f[9] = m12; + f[10] = m22; + f[11] = m32; + f[12] = m03; + f[13] = m13; + f[14] = m23; + f[15] = m33; + } else { + f[0] = m00; + f[1] = m01; + f[2] = m02; + f[3] = m03; + f[4] = m10; + f[5] = m11; + f[6] = m12; + f[7] = m13; + f[8] = m20; + f[9] = m21; + f[10] = m22; + f[11] = m23; + f[12] = m30; + f[13] = m31; + f[14] = m32; + f[15] = m33; + } + } + + /** + * readFloatBuffer reads value for this matrix from a + * FloatBuffer. + * + * @param fb the buffer to read from, must be correct size + * @return this data as a FloatBuffer. + */ + public Matrix4f readFloatBuffer(FloatBuffer fb) { + return readFloatBuffer(fb, false); + } + + /** + * readFloatBuffer reads value for this matrix from a + * FloatBuffer. + * + * @param fb the buffer to read from, must be correct size + * @param columnMajor if true, this buffer should be filled with column + * major data, otherwise it will be filled row major. + * @return this data as a FloatBuffer. + */ + public Matrix4f readFloatBuffer(FloatBuffer fb, boolean columnMajor) { + + if (columnMajor) { + m00 = fb.get(); + m10 = fb.get(); + m20 = fb.get(); + m30 = fb.get(); + m01 = fb.get(); + m11 = fb.get(); + m21 = fb.get(); + m31 = fb.get(); + m02 = fb.get(); + m12 = fb.get(); + m22 = fb.get(); + m32 = fb.get(); + m03 = fb.get(); + m13 = fb.get(); + m23 = fb.get(); + m33 = fb.get(); + } else { + m00 = fb.get(); + m01 = fb.get(); + m02 = fb.get(); + m03 = fb.get(); + m10 = fb.get(); + m11 = fb.get(); + m12 = fb.get(); + m13 = fb.get(); + m20 = fb.get(); + m21 = fb.get(); + m22 = fb.get(); + m23 = fb.get(); + m30 = fb.get(); + m31 = fb.get(); + m32 = fb.get(); + m33 = fb.get(); + } + return this; + } + + /** + * loadIdentity sets this matrix to the identity matrix, namely + * all zeros with ones along the diagonal. + * + */ + public void loadIdentity() { + m01 = m02 = m03 = 0.0f; + m10 = m12 = m13 = 0.0f; + m20 = m21 = m23 = 0.0f; + m30 = m31 = m32 = 0.0f; + m00 = m11 = m22 = m33 = 1.0f; + } + + public void fromFrustum( + float near, + float far, + float left, + float right, + float top, + float bottom, + boolean parallel + ) { + loadIdentity(); + if (parallel) { + // scale + m00 = 2.0f / (right - left); + // m11 = 2.0f / (bottom - top); + m11 = 2.0f / (top - bottom); + m22 = -2.0f / (far - near); + m33 = 1f; + + // translation + m03 = -(right + left) / (right - left); + // m31 = -(bottom + top) / (bottom - top); + m13 = -(top + bottom) / (top - bottom); + m23 = -(far + near) / (far - near); + } else { + m00 = (2.0f * near) / (right - left); + m11 = (2.0f * near) / (top - bottom); + m32 = -1.0f; + m33 = -0.0f; + + // A + m02 = (right + left) / (right - left); + + // B + m12 = (top + bottom) / (top - bottom); + + // C + m22 = -(far + near) / (far - near); + + // D + m23 = -(2.0f * far * near) / (far - near); + } + } + + /** + * fromAngleAxis sets this matrix4f to the values specified by + * an angle and an axis of rotation. This method creates an object, so use + * fromAngleNormalAxis if your axis is already normalized. + * + * @param angle the angle to rotate (in radians). + * @param axis the axis of rotation. + */ + public void fromAngleAxis(float angle, Vector3f axis) { + Vector3f normAxis = axis.normalize(); + fromAngleNormalAxis(angle, normAxis); + } + + /** + * fromAngleNormalAxis sets this matrix4f to the values + * specified by an angle and a normalized axis of rotation. + * + * @param angle the angle to rotate (in radians). + * @param axis the axis of rotation (already normalized). + */ + public void fromAngleNormalAxis(float angle, Vector3f axis) { + zero(); + m33 = 1; + + float fCos = FastMath.cos(angle); + float fSin = FastMath.sin(angle); + float fOneMinusCos = ((float) 1.0) - fCos; + float fX2 = axis.x * axis.x; + float fY2 = axis.y * axis.y; + float fZ2 = axis.z * axis.z; + float fXYM = axis.x * axis.y * fOneMinusCos; + float fXZM = axis.x * axis.z * fOneMinusCos; + float fYZM = axis.y * axis.z * fOneMinusCos; + float fXSin = axis.x * fSin; + float fYSin = axis.y * fSin; + float fZSin = axis.z * fSin; + + m00 = fX2 * fOneMinusCos + fCos; + m01 = fXYM - fZSin; + m02 = fXZM + fYSin; + m10 = fXYM + fZSin; + m11 = fY2 * fOneMinusCos + fCos; + m12 = fYZM - fXSin; + m20 = fXZM - fYSin; + m21 = fYZM + fXSin; + m22 = fZ2 * fOneMinusCos + fCos; + } + + /** + * mult multiplies this matrix by a scalar. + * + * @param scalar the scalar to multiply this matrix by. + */ + public void multLocal(float scalar) { + m00 *= scalar; + m01 *= scalar; + m02 *= scalar; + m03 *= scalar; + m10 *= scalar; + m11 *= scalar; + m12 *= scalar; + m13 *= scalar; + m20 *= scalar; + m21 *= scalar; + m22 *= scalar; + m23 *= scalar; + m30 *= scalar; + m31 *= scalar; + m32 *= scalar; + m33 *= scalar; + } + + public Matrix4f mult(float scalar) { + Matrix4f out = new Matrix4f(); + out.set(this); + out.multLocal(scalar); + return out; + } + + public Matrix4f mult(float scalar, Matrix4f store) { + store.set(this); + store.multLocal(scalar); + return store; + } + + /** + * mult multiplies this matrix with another matrix. The result + * matrix will then be returned. This matrix will be on the left hand side, + * while the parameter matrix will be on the right. + * + * @param in2 the matrix to multiply this matrix by. + * @return the resultant matrix + */ + public Matrix4f mult(Matrix4f in2) { + return mult(in2, null); + } + + /** + * mult multiplies this matrix with another matrix. The result + * matrix will then be returned. This matrix will be on the left hand side, + * while the parameter matrix will be on the right. + * + * @param in2 the matrix to multiply this matrix by. + * @param store where to store the result. It is safe for in2 and store to + * be the same object. + * @return the resultant matrix + */ + public Matrix4f mult(Matrix4f in2, Matrix4f store) { + if (store == null) { + store = new Matrix4f(); + } + + float temp00, temp01, temp02, temp03; + float temp10, temp11, temp12, temp13; + float temp20, temp21, temp22, temp23; + float temp30, temp31, temp32, temp33; + + temp00 = m00 * in2.m00 + + m01 * in2.m10 + + m02 * in2.m20 + + m03 * in2.m30; + temp01 = m00 * in2.m01 + + m01 * in2.m11 + + m02 * in2.m21 + + m03 * in2.m31; + temp02 = m00 * in2.m02 + + m01 * in2.m12 + + m02 * in2.m22 + + m03 * in2.m32; + temp03 = m00 * in2.m03 + + m01 * in2.m13 + + m02 * in2.m23 + + m03 * in2.m33; + + temp10 = m10 * in2.m00 + + m11 * in2.m10 + + m12 * in2.m20 + + m13 * in2.m30; + temp11 = m10 * in2.m01 + + m11 * in2.m11 + + m12 * in2.m21 + + m13 * in2.m31; + temp12 = m10 * in2.m02 + + m11 * in2.m12 + + m12 * in2.m22 + + m13 * in2.m32; + temp13 = m10 * in2.m03 + + m11 * in2.m13 + + m12 * in2.m23 + + m13 * in2.m33; + + temp20 = m20 * in2.m00 + + m21 * in2.m10 + + m22 * in2.m20 + + m23 * in2.m30; + temp21 = m20 * in2.m01 + + m21 * in2.m11 + + m22 * in2.m21 + + m23 * in2.m31; + temp22 = m20 * in2.m02 + + m21 * in2.m12 + + m22 * in2.m22 + + m23 * in2.m32; + temp23 = m20 * in2.m03 + + m21 * in2.m13 + + m22 * in2.m23 + + m23 * in2.m33; + + temp30 = m30 * in2.m00 + + m31 * in2.m10 + + m32 * in2.m20 + + m33 * in2.m30; + temp31 = m30 * in2.m01 + + m31 * in2.m11 + + m32 * in2.m21 + + m33 * in2.m31; + temp32 = m30 * in2.m02 + + m31 * in2.m12 + + m32 * in2.m22 + + m33 * in2.m32; + temp33 = m30 * in2.m03 + + m31 * in2.m13 + + m32 * in2.m23 + + m33 * in2.m33; + + store.m00 = temp00; + store.m01 = temp01; + store.m02 = temp02; + store.m03 = temp03; + store.m10 = temp10; + store.m11 = temp11; + store.m12 = temp12; + store.m13 = temp13; + store.m20 = temp20; + store.m21 = temp21; + store.m22 = temp22; + store.m23 = temp23; + store.m30 = temp30; + store.m31 = temp31; + store.m32 = temp32; + store.m33 = temp33; + + return store; + } + + /** + * mult multiplies this matrix with another matrix. The results + * are stored internally and a handle to this matrix will then be returned. + * This matrix will be on the left hand side, while the parameter matrix + * will be on the right. + * + * @param in2 the matrix to multiply this matrix by. + * @return the resultant matrix + */ + public Matrix4f multLocal(Matrix4f in2) { + return mult(in2, this); + } + + /** + * mult multiplies a vector about a rotation matrix. The + * resulting vector is returned as a new Vector3f. + * + * @param vec vec to multiply against. + * @return the rotated vector. + */ + public Vector3f mult(Vector3f vec) { + return mult(vec, null); + } + + /** + * mult multiplies a vector about a rotation matrix and adds + * translation. The resulting vector is returned. + * + * @param vec vec to multiply against. + * @param store a vector to store the result in. Created if null is passed. + * @return the rotated vector. + */ + public Vector3f mult(Vector3f vec, Vector3f store) { + if (store == null) { + store = new Vector3f(); + } + + float vx = vec.x, vy = vec.y, vz = vec.z; + store.x = m00 * vx + m01 * vy + m02 * vz + m03; + store.y = m10 * vx + m11 * vy + m12 * vz + m13; + store.z = m20 * vx + m21 * vy + m22 * vz + m23; + + return store; + } + + /** + * mult multiplies a Vector4f about a rotation + * matrix. The resulting vector is returned as a new Vector4f. + * + * @param vec vec to multiply against. + * @return the rotated vector. + */ + public Vector4f mult(Vector4f vec) { + return mult(vec, null); + } + + /** + * mult multiplies a Vector4f about a rotation + * matrix. The resulting vector is returned. + * + * @param vec vec to multiply against. + * @param store a vector to store the result in. Created if null is passed. + * @return the rotated vector. + */ + public Vector4f mult(Vector4f vec, Vector4f store) { + if (null == vec) { + logger.warning("Source vector is null, null result returned."); + return null; + } + if (store == null) { + store = new Vector4f(); + } + + float vx = vec.x, vy = vec.y, vz = vec.z, vw = vec.w; + store.x = m00 * vx + m01 * vy + m02 * vz + m03 * vw; + store.y = m10 * vx + m11 * vy + m12 * vz + m13 * vw; + store.z = m20 * vx + m21 * vy + m22 * vz + m23 * vw; + store.w = m30 * vx + m31 * vy + m32 * vz + m33 * vw; + + return store; + } + + /** + * mult multiplies a vector about a rotation matrix. The + * resulting vector is returned. + * + * @param vec vec to multiply against. + * + * @return the rotated vector. + */ + public Vector4f multAcross(Vector4f vec) { + return multAcross(vec, null); + } + + /** + * mult multiplies a vector about a rotation matrix. The + * resulting vector is returned. + * + * @param vec vec to multiply against. + * @param store a vector to store the result in. created if null is passed. + * @return the rotated vector. + */ + public Vector4f multAcross(Vector4f vec, Vector4f store) { + if (null == vec) { + logger.warning("Source vector is null, null result returned."); + return null; + } + if (store == null) { + store = new Vector4f(); + } + + float vx = vec.x, vy = vec.y, vz = vec.z, vw = vec.w; + store.x = m00 * vx + m10 * vy + m20 * vz + m30 * vw; + store.y = m01 * vx + m11 * vy + m21 * vz + m31 * vw; + store.z = m02 * vx + m12 * vy + m22 * vz + m32 * vw; + store.w = m03 * vx + m13 * vy + m23 * vz + m33 * vw; + + return store; + } + + /** + * multNormal multiplies a vector about a rotation matrix, but + * does not add translation. The resulting vector is returned. + * + * @param vec vec to multiply against. + * @param store a vector to store the result in. Created if null is passed. + * @return the rotated vector. + */ + public Vector3f multNormal(Vector3f vec, Vector3f store) { + if (store == null) { + store = new Vector3f(); + } + + float vx = vec.x, vy = vec.y, vz = vec.z; + store.x = m00 * vx + m01 * vy + m02 * vz; + store.y = m10 * vx + m11 * vy + m12 * vz; + store.z = m20 * vx + m21 * vy + m22 * vz; + + return store; + } + + /** + * multNormal multiplies a vector about a rotation matrix, but + * does not add translation. The resulting vector is returned. + * + * @param vec vec to multiply against. + * @param store a vector to store the result in. Created if null is passed. + * @return the rotated vector. + */ + public Vector3f multNormalAcross(Vector3f vec, Vector3f store) { + if (store == null) { + store = new Vector3f(); + } + + float vx = vec.x, vy = vec.y, vz = vec.z; + store.x = m00 * vx + m10 * vy + m20 * vz; + store.y = m01 * vx + m11 * vy + m21 * vz; + store.z = m02 * vx + m12 * vy + m22 * vz; + + return store; + } + + /** + * mult multiplies a vector about a rotation matrix and adds + * translation. The w value is returned as a result of multiplying the last + * column of the matrix by 1.0 + * + * @param vec vec to multiply against. + * @param store a vector to store the result in. + * @return the W value + */ + public float multProj(Vector3f vec, Vector3f store) { + float vx = vec.x, vy = vec.y, vz = vec.z; + store.x = m00 * vx + m01 * vy + m02 * vz + m03; + store.y = m10 * vx + m11 * vy + m12 * vz + m13; + store.z = m20 * vx + m21 * vy + m22 * vz + m23; + return m30 * vx + m31 * vy + m32 * vz + m33; + } + + /** + * mult multiplies a vector about a rotation matrix. The + * resulting vector is returned. + * + * @param vec vec to multiply against. + * @param store a vector to store the result in. created if null is passed. + * @return the rotated vector. + */ + public Vector3f multAcross(Vector3f vec, Vector3f store) { + if (null == vec) { + logger.warning("Source vector is null, null result returned."); + return null; + } + if (store == null) { + store = new Vector3f(); + } + + float vx = vec.x, vy = vec.y, vz = vec.z; + store.x = m00 * vx + m10 * vy + m20 * vz + m30 * 1; + store.y = m01 * vx + m11 * vy + m21 * vz + m31 * 1; + store.z = m02 * vx + m12 * vy + m22 * vz + m32 * 1; + + return store; + } + + /** + * mult multiplies a quaternion about a matrix. The resulting + * vector is returned. + * + * @param vec vec to multiply against. + * @param store a quaternion to store the result in. created if null is + * passed. + * @return store = this * vec + */ + public Quaternion mult(Quaternion vec, Quaternion store) { + + if (null == vec) { + logger.warning("Source vector is null, null result returned."); + return null; + } + if (store == null) { + store = new Quaternion(); + } + + float x = m00 * vec.x + m10 * vec.y + m20 * vec.z + m30 * vec.w; + float y = m01 * vec.x + m11 * vec.y + m21 * vec.z + m31 * vec.w; + float z = m02 * vec.x + m12 * vec.y + m22 * vec.z + m32 * vec.w; + float w = m03 * vec.x + m13 * vec.y + m23 * vec.z + m33 * vec.w; + store.x = x; + store.y = y; + store.z = z; + store.w = w; + + return store; + } + + /** + * mult multiplies an array of 4 floats against this rotation + * matrix. The results are stored directly in the array. (vec4f x mat4f) + * + * @param vec4f float array (size 4) to multiply against the matrix. + * @return the vec4f for chaining. + */ + public float[] mult(float[] vec4f) { + if (null == vec4f || vec4f.length != 4) { + logger.warning("invalid array given, must be nonnull and length 4"); + return null; + } + + float x = vec4f[0], y = vec4f[1], z = vec4f[2], w = vec4f[3]; + + vec4f[0] = m00 * x + m01 * y + m02 * z + m03 * w; + vec4f[1] = m10 * x + m11 * y + m12 * z + m13 * w; + vec4f[2] = m20 * x + m21 * y + m22 * z + m23 * w; + vec4f[3] = m30 * x + m31 * y + m32 * z + m33 * w; + + return vec4f; + } + + /** + * mult multiplies an array of 4 floats against this rotation + * matrix. The results are stored directly in the array. (vec4f x mat4f) + * + * @param vec4f float array (size 4) to multiply against the matrix. + * @return the vec4f for chaining. + */ + public float[] multAcross(float[] vec4f) { + if (null == vec4f || vec4f.length != 4) { + logger.warning("invalid array given, must be nonnull and length 4"); + return null; + } + + float x = vec4f[0], y = vec4f[1], z = vec4f[2], w = vec4f[3]; + + vec4f[0] = m00 * x + m10 * y + m20 * z + m30 * w; + vec4f[1] = m01 * x + m11 * y + m21 * z + m31 * w; + vec4f[2] = m02 * x + m12 * y + m22 * z + m32 * w; + vec4f[3] = m03 * x + m13 * y + m23 * z + m33 * w; + + return vec4f; + } + + /** + * Inverts this matrix as a new Matrix4f. + * + * @return The new inverse matrix + */ + public Matrix4f invert() { + return invert(null); + } + + /** + * Inverts this matrix and stores it in the given store. + * + * @return The store + */ + public Matrix4f invert(Matrix4f store) { + if (store == null) { + store = new Matrix4f(); + } + + float fA0 = m00 * m11 - m01 * m10; + float fA1 = m00 * m12 - m02 * m10; + float fA2 = m00 * m13 - m03 * m10; + float fA3 = m01 * m12 - m02 * m11; + float fA4 = m01 * m13 - m03 * m11; + float fA5 = m02 * m13 - m03 * m12; + float fB0 = m20 * m31 - m21 * m30; + float fB1 = m20 * m32 - m22 * m30; + float fB2 = m20 * m33 - m23 * m30; + float fB3 = m21 * m32 - m22 * m31; + float fB4 = m21 * m33 - m23 * m31; + float fB5 = m22 * m33 - m23 * m32; + float fDet = fA0 * fB5 - fA1 * fB4 + fA2 * fB3 + fA3 * fB2 - fA4 * fB1 + fA5 * fB0; + + if (FastMath.abs(fDet) <= 0f) { + throw new ArithmeticException("This matrix cannot be inverted"); + } + + store.m00 = +m11 * fB5 - m12 * fB4 + m13 * fB3; + store.m10 = -m10 * fB5 + m12 * fB2 - m13 * fB1; + store.m20 = +m10 * fB4 - m11 * fB2 + m13 * fB0; + store.m30 = -m10 * fB3 + m11 * fB1 - m12 * fB0; + store.m01 = -m01 * fB5 + m02 * fB4 - m03 * fB3; + store.m11 = +m00 * fB5 - m02 * fB2 + m03 * fB1; + store.m21 = -m00 * fB4 + m01 * fB2 - m03 * fB0; + store.m31 = +m00 * fB3 - m01 * fB1 + m02 * fB0; + store.m02 = +m31 * fA5 - m32 * fA4 + m33 * fA3; + store.m12 = -m30 * fA5 + m32 * fA2 - m33 * fA1; + store.m22 = +m30 * fA4 - m31 * fA2 + m33 * fA0; + store.m32 = -m30 * fA3 + m31 * fA1 - m32 * fA0; + store.m03 = -m21 * fA5 + m22 * fA4 - m23 * fA3; + store.m13 = +m20 * fA5 - m22 * fA2 + m23 * fA1; + store.m23 = -m20 * fA4 + m21 * fA2 - m23 * fA0; + store.m33 = +m20 * fA3 - m21 * fA1 + m22 * fA0; + + float fInvDet = 1.0f / fDet; + store.multLocal(fInvDet); + + return store; + } + + /** + * Inverts this matrix locally. + * + * @return this + */ + public Matrix4f invertLocal() { + + float fA0 = m00 * m11 - m01 * m10; + float fA1 = m00 * m12 - m02 * m10; + float fA2 = m00 * m13 - m03 * m10; + float fA3 = m01 * m12 - m02 * m11; + float fA4 = m01 * m13 - m03 * m11; + float fA5 = m02 * m13 - m03 * m12; + float fB0 = m20 * m31 - m21 * m30; + float fB1 = m20 * m32 - m22 * m30; + float fB2 = m20 * m33 - m23 * m30; + float fB3 = m21 * m32 - m22 * m31; + float fB4 = m21 * m33 - m23 * m31; + float fB5 = m22 * m33 - m23 * m32; + float fDet = fA0 * fB5 - fA1 * fB4 + fA2 * fB3 + fA3 * fB2 - fA4 * fB1 + fA5 * fB0; + + if (FastMath.abs(fDet) <= 0f) { + return zero(); + } + + float f00 = +m11 * fB5 - m12 * fB4 + m13 * fB3; + float f10 = -m10 * fB5 + m12 * fB2 - m13 * fB1; + float f20 = +m10 * fB4 - m11 * fB2 + m13 * fB0; + float f30 = -m10 * fB3 + m11 * fB1 - m12 * fB0; + float f01 = -m01 * fB5 + m02 * fB4 - m03 * fB3; + float f11 = +m00 * fB5 - m02 * fB2 + m03 * fB1; + float f21 = -m00 * fB4 + m01 * fB2 - m03 * fB0; + float f31 = +m00 * fB3 - m01 * fB1 + m02 * fB0; + float f02 = +m31 * fA5 - m32 * fA4 + m33 * fA3; + float f12 = -m30 * fA5 + m32 * fA2 - m33 * fA1; + float f22 = +m30 * fA4 - m31 * fA2 + m33 * fA0; + float f32 = -m30 * fA3 + m31 * fA1 - m32 * fA0; + float f03 = -m21 * fA5 + m22 * fA4 - m23 * fA3; + float f13 = +m20 * fA5 - m22 * fA2 + m23 * fA1; + float f23 = -m20 * fA4 + m21 * fA2 - m23 * fA0; + float f33 = +m20 * fA3 - m21 * fA1 + m22 * fA0; + + m00 = f00; + m01 = f01; + m02 = f02; + m03 = f03; + m10 = f10; + m11 = f11; + m12 = f12; + m13 = f13; + m20 = f20; + m21 = f21; + m22 = f22; + m23 = f23; + m30 = f30; + m31 = f31; + m32 = f32; + m33 = f33; + + float fInvDet = 1.0f / fDet; + multLocal(fInvDet); + + return this; + } + + /** + * Returns a new matrix representing the adjoint of this matrix. + * + * @return The adjoint matrix + */ + public Matrix4f adjoint() { + return adjoint(null); + } + + public void setTransform(Vector3f position, Vector3f scale, Matrix3f rotMat) { + // Ordering: + // 1. Scale + // 2. Rotate + // 3. Translate + + // Set up final matrix with scale, rotation and translation + m00 = scale.x * rotMat.m00; + m01 = scale.y * rotMat.m01; + m02 = scale.z * rotMat.m02; + m03 = position.x; + m10 = scale.x * rotMat.m10; + m11 = scale.y * rotMat.m11; + m12 = scale.z * rotMat.m12; + m13 = position.y; + m20 = scale.x * rotMat.m20; + m21 = scale.y * rotMat.m21; + m22 = scale.z * rotMat.m22; + m23 = position.z; + + // No projection term + m30 = 0; + m31 = 0; + m32 = 0; + m33 = 1; + } + + /** + * Places the adjoint of this matrix in store (creates store if null.) + * + * @param store The matrix to store the result in. If null, a new matrix is + * created. + * @return store + */ + public Matrix4f adjoint(Matrix4f store) { + if (store == null) { + store = new Matrix4f(); + } + + float fA0 = m00 * m11 - m01 * m10; + float fA1 = m00 * m12 - m02 * m10; + float fA2 = m00 * m13 - m03 * m10; + float fA3 = m01 * m12 - m02 * m11; + float fA4 = m01 * m13 - m03 * m11; + float fA5 = m02 * m13 - m03 * m12; + float fB0 = m20 * m31 - m21 * m30; + float fB1 = m20 * m32 - m22 * m30; + float fB2 = m20 * m33 - m23 * m30; + float fB3 = m21 * m32 - m22 * m31; + float fB4 = m21 * m33 - m23 * m31; + float fB5 = m22 * m33 - m23 * m32; + + store.m00 = +m11 * fB5 - m12 * fB4 + m13 * fB3; + store.m10 = -m10 * fB5 + m12 * fB2 - m13 * fB1; + store.m20 = +m10 * fB4 - m11 * fB2 + m13 * fB0; + store.m30 = -m10 * fB3 + m11 * fB1 - m12 * fB0; + store.m01 = -m01 * fB5 + m02 * fB4 - m03 * fB3; + store.m11 = +m00 * fB5 - m02 * fB2 + m03 * fB1; + store.m21 = -m00 * fB4 + m01 * fB2 - m03 * fB0; + store.m31 = +m00 * fB3 - m01 * fB1 + m02 * fB0; + store.m02 = +m31 * fA5 - m32 * fA4 + m33 * fA3; + store.m12 = -m30 * fA5 + m32 * fA2 - m33 * fA1; + store.m22 = +m30 * fA4 - m31 * fA2 + m33 * fA0; + store.m32 = -m30 * fA3 + m31 * fA1 - m32 * fA0; + store.m03 = -m21 * fA5 + m22 * fA4 - m23 * fA3; + store.m13 = +m20 * fA5 - m22 * fA2 + m23 * fA1; + store.m23 = -m20 * fA4 + m21 * fA2 - m23 * fA0; + store.m33 = +m20 * fA3 - m21 * fA1 + m22 * fA0; + + return store; + } + + /** + * determinant generates the determinate of this matrix. + * + * @return the determinate + */ + public float determinant() { + float fA0 = m00 * m11 - m01 * m10; + float fA1 = m00 * m12 - m02 * m10; + float fA2 = m00 * m13 - m03 * m10; + float fA3 = m01 * m12 - m02 * m11; + float fA4 = m01 * m13 - m03 * m11; + float fA5 = m02 * m13 - m03 * m12; + float fB0 = m20 * m31 - m21 * m30; + float fB1 = m20 * m32 - m22 * m30; + float fB2 = m20 * m33 - m23 * m30; + float fB3 = m21 * m32 - m22 * m31; + float fB4 = m21 * m33 - m23 * m31; + float fB5 = m22 * m33 - m23 * m32; + float fDet = fA0 * fB5 - fA1 * fB4 + fA2 * fB3 + fA3 * fB2 - fA4 * fB1 + fA5 * fB0; + return fDet; + } + + /** + * Sets all of the values in this matrix to zero. + * + * @return this matrix + */ + public Matrix4f zero() { + m00 = m01 = m02 = m03 = 0.0f; + m10 = m11 = m12 = m13 = 0.0f; + m20 = m21 = m22 = m23 = 0.0f; + m30 = m31 = m32 = m33 = 0.0f; + return this; + } + + public Matrix4f add(Matrix4f mat) { + Matrix4f result = new Matrix4f(); + result.m00 = this.m00 + mat.m00; + result.m01 = this.m01 + mat.m01; + result.m02 = this.m02 + mat.m02; + result.m03 = this.m03 + mat.m03; + result.m10 = this.m10 + mat.m10; + result.m11 = this.m11 + mat.m11; + result.m12 = this.m12 + mat.m12; + result.m13 = this.m13 + mat.m13; + result.m20 = this.m20 + mat.m20; + result.m21 = this.m21 + mat.m21; + result.m22 = this.m22 + mat.m22; + result.m23 = this.m23 + mat.m23; + result.m30 = this.m30 + mat.m30; + result.m31 = this.m31 + mat.m31; + result.m32 = this.m32 + mat.m32; + result.m33 = this.m33 + mat.m33; + return result; + } + + /** + * add adds the values of a parameter matrix to this matrix. + * + * @param mat the matrix to add to this. + */ + public void addLocal(Matrix4f mat) { + m00 += mat.m00; + m01 += mat.m01; + m02 += mat.m02; + m03 += mat.m03; + m10 += mat.m10; + m11 += mat.m11; + m12 += mat.m12; + m13 += mat.m13; + m20 += mat.m20; + m21 += mat.m21; + m22 += mat.m22; + m23 += mat.m23; + m30 += mat.m30; + m31 += mat.m31; + m32 += mat.m32; + m33 += mat.m33; + } + + public Vector3f toTranslationVector() { + return new Vector3f(m03, m13, m23); + } + + public void toTranslationVector(Vector3f vector) { + vector.set(m03, m13, m23); + } + + public Quaternion toRotationQuat() { + Quaternion quat = new Quaternion(); + quat.fromRotationMatrix(toRotationMatrix()); + return quat; + } + + public void toRotationQuat(Quaternion q) { + q.fromRotationMatrix(toRotationMatrix()); + } + + public Matrix3f toRotationMatrix() { + return new Matrix3f(m00, m01, m02, m10, m11, m12, m20, m21, m22); + } + + public void toRotationMatrix(Matrix3f mat) { + mat.m00 = m00; + mat.m01 = m01; + mat.m02 = m02; + mat.m10 = m10; + mat.m11 = m11; + mat.m12 = m12; + mat.m20 = m20; + mat.m21 = m21; + mat.m22 = m22; + } + + /** + * Retreives the scale vector from the matrix. + * + * @return the scale vector + */ + public Vector3f toScaleVector() { + Vector3f result = new Vector3f(); + this.toScaleVector(result); + return result; + } + + /** + * Retreives the scale vector from the matrix and stores it into a given + * vector. + * + * @param vector where the scale will be stored + */ + public void toScaleVector(Vector3f vector) { + float scaleX = (float) Math.sqrt(m00 * m00 + m10 * m10 + m20 * m20); + float scaleY = (float) Math.sqrt(m01 * m01 + m11 * m11 + m21 * m21); + float scaleZ = (float) Math.sqrt(m02 * m02 + m12 * m12 + m22 * m22); + vector.set(scaleX, scaleY, scaleZ); + } + + public void setScale(float x, float y, float z) { + m00 *= x; + m11 *= y; + m22 *= z; + } + + public void setScale(Vector3f scale) { + m00 *= scale.x; + m11 *= scale.y; + m22 *= scale.z; + } + + /** + * setTranslation will set the matrix's translation values. + * + * @param translation the new values for the translation. + * @throws IllegalArgumentException if translation is not size 3. + */ + public void setTranslation(float[] translation) { + if (translation.length != 3) { + throw new IllegalArgumentException( + "Translation size must be 3." + ); + } + m03 = translation[0]; + m13 = translation[1]; + m23 = translation[2]; + } + + /** + * setTranslation will set the matrix's translation values. + * + * @param x value of the translation on the x axis + * @param y value of the translation on the y axis + * @param z value of the translation on the z axis + */ + public void setTranslation(float x, float y, float z) { + m03 = x; + m13 = y; + m23 = z; + } + + /** + * setTranslation will set the matrix's translation values. + * + * @param translation the new values for the translation. + */ + public void setTranslation(Vector3f translation) { + m03 = translation.x; + m13 = translation.y; + m23 = translation.z; + } + + /** + * setInverseTranslation will set the matrix's inverse + * translation values. + * + * @param translation the new values for the inverse translation. + * @throws IllegalArgumentException if translation is not size 3. + */ + public void setInverseTranslation(float[] translation) { + if (translation.length != 3) { + throw new IllegalArgumentException( + "Translation size must be 3." + ); + } + m03 = -translation[0]; + m13 = -translation[1]; + m23 = -translation[2]; + } + + /** + * angleRotation sets this matrix to that of a rotation about + * three axes (x, y, z). Where each axis has a specified rotation in + * degrees. These rotations are expressed in a single Vector3f + * object. + * + * @param angles the angles to rotate. + */ + public void angleRotation(Vector3f angles) { + float angle; + float sr, sp, sy, cr, cp, cy; + + angle = (angles.z * FastMath.DEG_TO_RAD); + sy = FastMath.sin(angle); + cy = FastMath.cos(angle); + angle = (angles.y * FastMath.DEG_TO_RAD); + sp = FastMath.sin(angle); + cp = FastMath.cos(angle); + angle = (angles.x * FastMath.DEG_TO_RAD); + sr = FastMath.sin(angle); + cr = FastMath.cos(angle); + + // matrix = (Z * Y) * X + m00 = cp * cy; + m10 = cp * sy; + m20 = -sp; + m01 = sr * sp * cy + cr * -sy; + m11 = sr * sp * sy + cr * cy; + m21 = sr * cp; + m02 = (cr * sp * cy + -sr * -sy); + m12 = (cr * sp * sy + -sr * cy); + m22 = cr * cp; + m03 = 0.0f; + m13 = 0.0f; + m23 = 0.0f; + } + + /** + * setRotationQuaternion builds a rotation from a + * Quaternion. + * + * @param quat the quaternion to build the rotation from. + * @throws NullPointerException if quat is null. + */ + public void setRotationQuaternion(Quaternion quat) { + quat.toRotationMatrix(this); + } + + /** + * setInverseRotationRadians builds an inverted rotation from + * Euler angles that are in radians. + * + * @param angles the Euler angles in radians. + * @throws IllegalArgumentException if angles is not size 3. + */ + public void setInverseRotationRadians(float[] angles) { + if (angles.length != 3) { + throw new IllegalArgumentException( + "Angles must be of size 3." + ); + } + double cr = FastMath.cos(angles[0]); + double sr = FastMath.sin(angles[0]); + double cp = FastMath.cos(angles[1]); + double sp = FastMath.sin(angles[1]); + double cy = FastMath.cos(angles[2]); + double sy = FastMath.sin(angles[2]); + + m00 = (float) (cp * cy); + m10 = (float) (cp * sy); + m20 = (float) (-sp); + + double srsp = sr * sp; + double crsp = cr * sp; + + m01 = (float) (srsp * cy - cr * sy); + m11 = (float) (srsp * sy + cr * cy); + m21 = (float) (sr * cp); + + m02 = (float) (crsp * cy + sr * sy); + m12 = (float) (crsp * sy - sr * cy); + m22 = (float) (cr * cp); + } + + /** + * setInverseRotationDegrees builds an inverted rotation from + * Euler angles that are in degrees. + * + * @param angles the Euler angles in degrees. + * @throws IllegalArgumentException if angles is not size 3. + */ + public void setInverseRotationDegrees(float[] angles) { + if (angles.length != 3) { + throw new IllegalArgumentException( + "Angles must be of size 3." + ); + } + float vec[] = new float[3]; + vec[0] = (angles[0] * FastMath.RAD_TO_DEG); + vec[1] = (angles[1] * FastMath.RAD_TO_DEG); + vec[2] = (angles[2] * FastMath.RAD_TO_DEG); + setInverseRotationRadians(vec); + } + + /** + * + * inverseTranslateVect translates a given Vector3f by the + * translation part of this matrix. + * + * @param vec the Vector3f data to be translated. + * @throws IllegalArgumentException if the size of the Vector3f is not 3. + */ + public void inverseTranslateVect(float[] vec) { + if (vec.length != 3) { + throw new IllegalArgumentException( + "vec must be of size 3." + ); + } + + vec[0] = vec[0] - m03; + vec[1] = vec[1] - m13; + vec[2] = vec[2] - m23; + } + + /** + * + * inverseTranslateVect translates a given Vector3f by the + * translation part of this matrix. + * + * @param data the Vector3f to be translated. + * @throws IllegalArgumentException if the size of the Vector3f is not 3. + */ + public void inverseTranslateVect(Vector3f data) { + data.x -= m03; + data.y -= m13; + data.z -= m23; + } + + /** + * + * inverseTranslateVect translates a given Vector3f by the + * translation part of this matrix. + * + * @param data the Vector3f to be translated. + * @throws IllegalArgumentException if the size of the Vector3f is not 3. + */ + public void translateVect(Vector3f data) { + data.x += m03; + data.y += m13; + data.z += m23; + } + + /** + * + * inverseRotateVect rotates a given Vector3f by the rotation + * part of this matrix. + * + * @param vec the Vector3f to be rotated. + */ + public void inverseRotateVect(Vector3f vec) { + float vx = vec.x, vy = vec.y, vz = vec.z; + + vec.x = vx * m00 + vy * m10 + vz * m20; + vec.y = vx * m01 + vy * m11 + vz * m21; + vec.z = vx * m02 + vy * m12 + vz * m22; + } + + public void rotateVect(Vector3f vec) { + float vx = vec.x, vy = vec.y, vz = vec.z; + + vec.x = vx * m00 + vy * m01 + vz * m02; + vec.y = vx * m10 + vy * m11 + vz * m12; + vec.z = vx * m20 + vy * m21 + vz * m22; + } + + /** + * toString returns the string representation of this object. + * It is in a format of a 4x4 matrix. For example, an identity matrix would + * be represented by the following string. com.jme.math.Matrix3f
+ * [
+ * 1.0 0.0 0.0 0.0
+ * 0.0 1.0 0.0 0.0
+ * 0.0 0.0 1.0 0.0
+ * 0.0 0.0 0.0 1.0
+ * ]
+ * + * @return the string representation of this object. + */ + @Override + public String toString() { + StringBuilder result = new StringBuilder("Matrix4f\n[\n"); + result.append(" "); + result.append(m00); + result.append(" "); + result.append(m01); + result.append(" "); + result.append(m02); + result.append(" "); + result.append(m03); + result.append(" \n"); + result.append(" "); + result.append(m10); + result.append(" "); + result.append(m11); + result.append(" "); + result.append(m12); + result.append(" "); + result.append(m13); + result.append(" \n"); + result.append(" "); + result.append(m20); + result.append(" "); + result.append(m21); + result.append(" "); + result.append(m22); + result.append(" "); + result.append(m23); + result.append(" \n"); + result.append(" "); + result.append(m30); + result.append(" "); + result.append(m31); + result.append(" "); + result.append(m32); + result.append(" "); + result.append(m33); + result.append(" \n]"); + return result.toString(); + } + + /** + * + * hashCode returns the hash code value as an integer and is + * supported for the benefit of hashing based collection classes such as + * Hashtable, HashMap, HashSet etc. + * + * @return the hashcode for this instance of Matrix4f. + * @see java.lang.Object#hashCode() + */ + @Override + public int hashCode() { + int hash = 37; + hash = 37 * hash + Float.floatToIntBits(m00); + hash = 37 * hash + Float.floatToIntBits(m01); + hash = 37 * hash + Float.floatToIntBits(m02); + hash = 37 * hash + Float.floatToIntBits(m03); + + hash = 37 * hash + Float.floatToIntBits(m10); + hash = 37 * hash + Float.floatToIntBits(m11); + hash = 37 * hash + Float.floatToIntBits(m12); + hash = 37 * hash + Float.floatToIntBits(m13); + + hash = 37 * hash + Float.floatToIntBits(m20); + hash = 37 * hash + Float.floatToIntBits(m21); + hash = 37 * hash + Float.floatToIntBits(m22); + hash = 37 * hash + Float.floatToIntBits(m23); + + hash = 37 * hash + Float.floatToIntBits(m30); + hash = 37 * hash + Float.floatToIntBits(m31); + hash = 37 * hash + Float.floatToIntBits(m32); + hash = 37 * hash + Float.floatToIntBits(m33); + + return hash; + } + + /** + * are these two matrices the same? they are is they both have the same mXX + * values. + * + * @param o the object to compare for equality + * @return true if they are equal + */ + @Override + public boolean equals(Object o) { + if (!(o instanceof Matrix4f) || o == null) { + return false; + } + + if (this == o) { + return true; + } + + Matrix4f comp = (Matrix4f) o; + if (Float.compare(m00, comp.m00) != 0) { + return false; + } + if (Float.compare(m01, comp.m01) != 0) { + return false; + } + if (Float.compare(m02, comp.m02) != 0) { + return false; + } + if (Float.compare(m03, comp.m03) != 0) { + return false; + } + + if (Float.compare(m10, comp.m10) != 0) { + return false; + } + if (Float.compare(m11, comp.m11) != 0) { + return false; + } + if (Float.compare(m12, comp.m12) != 0) { + return false; + } + if (Float.compare(m13, comp.m13) != 0) { + return false; + } + + if (Float.compare(m20, comp.m20) != 0) { + return false; + } + if (Float.compare(m21, comp.m21) != 0) { + return false; + } + if (Float.compare(m22, comp.m22) != 0) { + return false; + } + if (Float.compare(m23, comp.m23) != 0) { + return false; + } + + if (Float.compare(m30, comp.m30) != 0) { + return false; + } + if (Float.compare(m31, comp.m31) != 0) { + return false; + } + if (Float.compare(m32, comp.m32) != 0) { + return false; + } + if (Float.compare(m33, comp.m33) != 0) { + return false; + } + + return true; + } + + /** + * @return true if this matrix is identity + */ + public boolean isIdentity() { + return (m00 == 1 && m01 == 0 && m02 == 0 && m03 == 0) + && (m10 == 0 && m11 == 1 && m12 == 0 && m13 == 0) + && (m20 == 0 && m21 == 0 && m22 == 1 && m23 == 0) + && (m30 == 0 && m31 == 0 && m32 == 0 && m33 == 1); + } + + /** + * Apply a scale to this matrix. + * + * @param scale the scale to apply + */ + public void scale(Vector3f scale) { + m00 *= scale.getX(); + m10 *= scale.getX(); + m20 *= scale.getX(); + m30 *= scale.getX(); + m01 *= scale.getY(); + m11 *= scale.getY(); + m21 *= scale.getY(); + m31 *= scale.getY(); + m02 *= scale.getZ(); + m12 *= scale.getZ(); + m22 *= scale.getZ(); + m32 *= scale.getZ(); + } + + static boolean equalIdentity(Matrix4f mat) { + if (Math.abs(mat.m00 - 1) > 1e-4) { + return false; + } + if (Math.abs(mat.m11 - 1) > 1e-4) { + return false; + } + if (Math.abs(mat.m22 - 1) > 1e-4) { + return false; + } + if (Math.abs(mat.m33 - 1) > 1e-4) { + return false; + } + + if (Math.abs(mat.m01) > 1e-4) { + return false; + } + if (Math.abs(mat.m02) > 1e-4) { + return false; + } + if (Math.abs(mat.m03) > 1e-4) { + return false; + } + + if (Math.abs(mat.m10) > 1e-4) { + return false; + } + if (Math.abs(mat.m12) > 1e-4) { + return false; + } + if (Math.abs(mat.m13) > 1e-4) { + return false; + } + + if (Math.abs(mat.m20) > 1e-4) { + return false; + } + if (Math.abs(mat.m21) > 1e-4) { + return false; + } + if (Math.abs(mat.m23) > 1e-4) { + return false; + } + + if (Math.abs(mat.m30) > 1e-4) { + return false; + } + if (Math.abs(mat.m31) > 1e-4) { + return false; + } + if (Math.abs(mat.m32) > 1e-4) { + return false; + } + + return true; + } + + // XXX: This tests more solid than converting the q to a matrix and + // multiplying... why? + public void multLocal(Quaternion rotation) { + Vector3f axis = new Vector3f(); + float angle = rotation.toAngleAxis(axis); + Matrix4f matrix4f = new Matrix4f(); + matrix4f.fromAngleAxis(angle, axis); + multLocal(matrix4f); + } + + @Override + public Matrix4f clone() { + try { + return (Matrix4f) super.clone(); + } catch (CloneNotSupportedException e) { + throw new AssertionError(); // can not happen + } + } +} diff --git a/src/main/java/com/jme3/math/Quaternion.java b/src/main/java/com/jme3/math/Quaternion.java new file mode 100644 index 000000000..460095856 --- /dev/null +++ b/src/main/java/com/jme3/math/Quaternion.java @@ -0,0 +1,1712 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.math; + +import io.eiren.math.FloatMath; + +import java.io.IOException; +import java.io.ObjectInput; +import java.io.ObjectOutput; +import java.util.logging.Logger; + + +/** + * Quaternion defines a single example of a more general class of + * hypercomplex numbers. Quaternions extends a rotation in three dimensions to a + * rotation in four dimensions. This avoids "gimbal lock" and allows for smooth + * continuous rotation. + * + * Quaternion is defined by four floating point numbers: {x y z w}. + * + * @author Mark Powell + * @author Joshua Slack + */ +public final class Quaternion implements Cloneable, java.io.Serializable { + + static final long serialVersionUID = 1; + + private static final Logger logger = Logger.getLogger(Quaternion.class.getName()); + /** + * Represents the identity quaternion rotation (0, 0, 0, 1). + */ + public static final Quaternion IDENTITY = new Quaternion(); + public static final Quaternion DIRECTION_Z = new Quaternion(); + public static final Quaternion ZERO = new Quaternion(0, 0, 0, 0); + + public static final Quaternion X_90_DEG = new Quaternion() + .fromAngleNormalAxis(FastMath.HALF_PI, Vector3f.UNIT_X); + public static final Quaternion X_180_DEG = new Quaternion() + .fromAngleNormalAxis(FastMath.PI, Vector3f.UNIT_X); + public static final Quaternion X_270_DEG = new Quaternion() + .fromAngleNormalAxis(-FastMath.HALF_PI, Vector3f.UNIT_X); + public static final Quaternion Y_90_DEG = new Quaternion() + .fromAngleNormalAxis(FastMath.HALF_PI, Vector3f.UNIT_Y); + public static final Quaternion Y_180_DEG = new Quaternion() + .fromAngleNormalAxis(FastMath.PI, Vector3f.UNIT_Y); + public static final Quaternion Y_270_DEG = new Quaternion() + .fromAngleNormalAxis(-FastMath.HALF_PI, Vector3f.UNIT_Y); + public static final Quaternion Z_90_DEG = new Quaternion() + .fromAngleNormalAxis(FastMath.HALF_PI, Vector3f.UNIT_Z); + public static final Quaternion Z_180_DEG = new Quaternion() + .fromAngleNormalAxis(FastMath.PI, Vector3f.UNIT_Z); + public static final Quaternion Z_270_DEG = new Quaternion() + .fromAngleNormalAxis(-FastMath.HALF_PI, Vector3f.UNIT_Z); + + static { + DIRECTION_Z.fromAxes(Vector3f.UNIT_X, Vector3f.UNIT_Y, Vector3f.UNIT_Z); + } + protected float x, y, z, w; + + /** + * Constructor instantiates a new Quaternion object + * initializing all values to zero, except w which is initialized to 1. + * + */ + public Quaternion() { + x = 0; + y = 0; + z = 0; + w = 1; + } + + /** + * Constructor instantiates a new Quaternion object from the + * given list of parameters. + * + * @param x the x value of the quaternion. + * @param y the y value of the quaternion. + * @param z the z value of the quaternion. + * @param w the w value of the quaternion. + */ + public Quaternion(float x, float y, float z, float w) { + this.x = x; + this.y = y; + this.z = z; + this.w = w; + } + + public float getX() { + return x; + } + + public float getY() { + return y; + } + + public float getZ() { + return z; + } + + public float getW() { + return w; + } + + /** + * sets the data in a Quaternion object from the given list of + * parameters. + * + * @param x the x value of the quaternion. + * @param y the y value of the quaternion. + * @param z the z value of the quaternion. + * @param w the w value of the quaternion. + * @return this + */ + public Quaternion set(float x, float y, float z, float w) { + this.x = x; + this.y = y; + this.z = z; + this.w = w; + return this; + } + + /** + * Sets the data in this Quaternion object to be equal to the + * passed Quaternion object. The values are copied producing a + * new object. + * + * @param q The Quaternion to copy values from. + * @return this + */ + public Quaternion set(Quaternion q) { + this.x = q.x; + this.y = q.y; + this.z = q.z; + this.w = q.w; + return this; + } + + /** + * Constructor instantiates a new Quaternion object from a + * collection of rotation angles. + * + * @param angles the angles of rotation (x, y, z) that will define the + * Quaternion. + */ + public Quaternion(float[] angles) { + fromAngles(angles); + } + + /** + * Constructor instantiates a new Quaternion object from an + * interpolation between two other quaternions. + * + * @param q1 the first quaternion. + * @param q2 the second quaternion. + * @param interp the amount to interpolate between the two quaternions. + */ + public Quaternion(Quaternion q1, Quaternion q2, float interp) { + slerp(q1, q2, interp); + } + + /** + * Constructor instantiates a new Quaternion object from an + * existing quaternion, creating a copy. + * + * @param q the quaternion to copy. + */ + public Quaternion(Quaternion q) { + this.x = q.x; + this.y = q.y; + this.z = q.z; + this.w = q.w; + } + + /** + * Sets this Quaternion to {0, 0, 0, 1}. Same as calling set(0,0,0,1). + */ + public void loadIdentity() { + x = y = z = 0; + w = 1; + } + + /** + * @return true if this Quaternion is {0,0,0,1} + */ + public boolean isIdentity() { + if (x == 0 && y == 0 && z == 0 && w == 1) { + return true; + } else { + return false; + } + } + + /** + * fromAngles builds a quaternion from the Euler rotation + * angles (y,r,p). + * + * @param angles the Euler angles of rotation (in radians). + */ + public Quaternion fromAngles(float[] angles) { + if (angles.length != 3) { + throw new IllegalArgumentException("Angles array must have three elements"); + } + + return fromAngles(angles[0], angles[1], angles[2]); + } + + /** + * fromAngles builds a Quaternion from the Euler rotation + * angles (x,y,z) aka (pitch, yaw, rall)). Note that we are applying in + * order: (y, z, x) aka (yaw, roll, pitch) but we've ordered them in x, y, + * and z for convenience. + * + * @see http://www.euclideanspace.com/maths/geometry/rotations/conversions/eulerToQuaternion/index.htm + * + * @param xAngle the Euler pitch of rotation (in radians). (aka Attitude, + * often rot around x) + * @param yAngle the Euler yaw of rotation (in radians). (aka Heading, often + * rot around y) + * @param zAngle the Euler roll of rotation (in radians). (aka Bank, often + * rot around z) + */ + public Quaternion fromAngles(float xAngle, float yAngle, float zAngle) { + float angle; + float sinY, sinZ, sinX, cosY, cosZ, cosX; + angle = zAngle * 0.5f; + sinZ = FastMath.sin(angle); + cosZ = FastMath.cos(angle); + angle = yAngle * 0.5f; + sinY = FastMath.sin(angle); + cosY = FastMath.cos(angle); + angle = xAngle * 0.5f; + sinX = FastMath.sin(angle); + cosX = FastMath.cos(angle); + + // variables used to reduce multiplication calls. + float cosYXcosZ = cosY * cosZ; + float sinYXsinZ = sinY * sinZ; + float cosYXsinZ = cosY * sinZ; + float sinYXcosZ = sinY * cosZ; + + w = (cosYXcosZ * cosX - sinYXsinZ * sinX); + x = (cosYXcosZ * sinX + sinYXsinZ * cosX); + y = (sinYXcosZ * cosX + cosYXsinZ * sinX); + z = (cosYXsinZ * cosX - sinYXcosZ * sinX); + + normalizeLocal(); + return this; + } + + /** + * toAngles returns this quaternion converted to Euler rotation + * angles (yaw,roll,pitch).
+ * Note that the result is not always 100% accurate due to the implications + * of euler angles. + * + * @see http://www.euclideanspace.com/maths/geometry/rotations/conversions/quaternionToEuler/index.htm + * + * @param angles the float[] in which the angles should be stored, or null + * if you want a new float[] to be created + * @return the float[] in which the angles are stored. + */ + public float[] toAngles(float[] angles) { + if (angles == null) { + angles = new float[3]; + } else if (angles.length != 3) { + throw new IllegalArgumentException("Angles array must have three elements"); + } + + float sqw = w * w; + float sqx = x * x; + float sqy = y * y; + float sqz = z * z; + float unit = sqx + sqy + sqz + sqw; // if normalized is one, otherwise + // is correction factor + float test = x * y + z * w; + if (test > 0.499 * unit) { // singularity at north pole + angles[1] = 2 * FastMath.atan2(x, w); + angles[2] = FastMath.HALF_PI; + angles[0] = 0; + } else if (test < -0.499 * unit) { // singularity at south pole + angles[1] = -2 * FastMath.atan2(x, w); + angles[2] = -FastMath.HALF_PI; + angles[0] = 0; + } else { + angles[1] = FastMath.atan2(2 * y * w - 2 * x * z, sqx - sqy - sqz + sqw); // yaw + // or + // bank + angles[2] = FastMath.asin(2 * test / unit); // roll or heading + angles[0] = FastMath.atan2(2 * x * w - 2 * y * z, -sqx + sqy - sqz + sqw); // pitch + // or + // attitude + } + return angles; + } + + /** + * Returns Euler rotation angle around x axis (pitch). + * + * @return + * @see #toAngles(float[]) + */ + public float getPitch() { + float sqw = w * w; + float sqx = x * x; + float sqy = y * y; + float sqz = z * z; + float unit = sqx + sqy + sqz + sqw; // if normalized is one, otherwise + // is correction factor + float test = x * y + z * w; + if (test > 0.499 * unit) { // singularity at north pole + return 0; + } else if (test < -0.499 * unit) { // singularity at south pole + return 0; + } else { + return FastMath.atan2(2 * x * w - 2 * y * z, -sqx + sqy - sqz + sqw); // pitch + // or + // attitude + } + } + + /** + * Returns Euler rotation angle around y axis (yaw). + * + * @return + * @see #toAngles(float[]) + */ + public float getYaw() { + float sqw = w * w; + float sqx = x * x; + float sqy = y * y; + float sqz = z * z; + float unit = sqx + sqy + sqz + sqw; // if normalized is one, otherwise + // is correction factor + float test = x * y + z * w; + if (test > 0.499 * unit) { // singularity at north pole + return 2 * FastMath.atan2(x, w); + } else if (test < -0.499 * unit) { // singularity at south pole + return -2 * FastMath.atan2(x, w); + } else { + return FastMath.atan2(2 * y * w - 2 * x * z, sqx - sqy - sqz + sqw); // yaw + // or + // bank + } + } + + /** + * Returns Euler rotation angle around z axis (roll). + * + * @return + * @see #toAngles(float[]) + */ + public float getRoll() { + float sqw = w * w; + float sqx = x * x; + float sqy = y * y; + float sqz = z * z; + float unit = sqx + sqy + sqz + sqw; // if normalized is one, otherwise + // is correction factor + float test = x * y + z * w; + if (test > 0.499 * unit) { // singularity at north pole + return FastMath.HALF_PI; + } else if (test < -0.499 * unit) { // singularity at south pole + return -FastMath.HALF_PI; + } else { + return FastMath.asin(2 * test / unit); // roll or heading + } + } + + /** + * + * fromRotationMatrix generates a quaternion from a supplied + * matrix. This matrix is assumed to be a rotational matrix. + * + * @param matrix the matrix that defines the rotation. + */ + public Quaternion fromRotationMatrix(Matrix3f matrix) { + return fromRotationMatrix( + matrix.m00, + matrix.m01, + matrix.m02, + matrix.m10, + matrix.m11, + matrix.m12, + matrix.m20, + matrix.m21, + matrix.m22 + ); + } + + public Quaternion fromRotationMatrix( + float m00, + float m01, + float m02, + float m10, + float m11, + float m12, + float m20, + float m21, + float m22 + ) { + // first normalize the forward (F), up (U) and side (S) vectors of the + // rotation matrix + // so that the scale does not affect the rotation + float lengthSquared = m00 * m00 + m10 * m10 + m20 * m20; + if (lengthSquared != 1f && lengthSquared != 0f) { + lengthSquared = 1.0f / FastMath.sqrt(lengthSquared); + m00 *= lengthSquared; + m10 *= lengthSquared; + m20 *= lengthSquared; + } + lengthSquared = m01 * m01 + m11 * m11 + m21 * m21; + if (lengthSquared != 1f && lengthSquared != 0f) { + lengthSquared = 1.0f / FastMath.sqrt(lengthSquared); + m01 *= lengthSquared; + m11 *= lengthSquared; + m21 *= lengthSquared; + } + lengthSquared = m02 * m02 + m12 * m12 + m22 * m22; + if (lengthSquared != 1f && lengthSquared != 0f) { + lengthSquared = 1.0f / FastMath.sqrt(lengthSquared); + m02 *= lengthSquared; + m12 *= lengthSquared; + m22 *= lengthSquared; + } + + // Use the Graphics Gems code, from + // ftp://ftp.cis.upenn.edu/pub/graphics/shoemake/quatut.ps.Z + // *NOT* the "Matrix and Quaternions FAQ", which has errors! + + // the trace is the sum of the diagonal elements; see + // http://mathworld.wolfram.com/MatrixTrace.html + float t = m00 + m11 + m22; + + // we protect the division by s by ensuring that s>=1 + if (t >= 0) { // |w| >= .5 + float s = FastMath.sqrt(t + 1); // |s|>=1 ... + w = 0.5f * s; + s = 0.5f / s; // so this division isn't bad + x = (m21 - m12) * s; + y = (m02 - m20) * s; + z = (m10 - m01) * s; + } else if ((m00 > m11) && (m00 > m22)) { + float s = FastMath.sqrt(1.0f + m00 - m11 - m22); // |s|>=1 + x = s * 0.5f; // |x| >= .5 + s = 0.5f / s; + y = (m10 + m01) * s; + z = (m02 + m20) * s; + w = (m21 - m12) * s; + } else if (m11 > m22) { + float s = FastMath.sqrt(1.0f + m11 - m00 - m22); // |s|>=1 + y = s * 0.5f; // |y| >= .5 + s = 0.5f / s; + x = (m10 + m01) * s; + z = (m21 + m12) * s; + w = (m02 - m20) * s; + } else { + float s = FastMath.sqrt(1.0f + m22 - m00 - m11); // |s|>=1 + z = s * 0.5f; // |z| >= .5 + s = 0.5f / s; + x = (m02 + m20) * s; + y = (m21 + m12) * s; + w = (m10 - m01) * s; + } + + return this; + } + + /** + * toRotationMatrix converts this quaternion to a rotational + * matrix. Note: the result is created from a normalized version of this + * quat. + * + * @return the rotation matrix representation of this quaternion. + */ + public Matrix3f toRotationMatrix() { + Matrix3f matrix = new Matrix3f(); + return toRotationMatrix(matrix); + } + + /** + * toRotationMatrix converts this quaternion to a rotational + * matrix. The result is stored in result. + * + * @param result The Matrix3f to store the result in. + * @return the rotation matrix representation of this quaternion. + */ + public Matrix3f toRotationMatrix(Matrix3f result) { + + float norm = norm(); + // we explicitly test norm against one here, saving a division + // at the cost of a test and branch. Is it worth it? + float s = (norm == 1f) ? 2f : (norm > 0f) ? 2f / norm : 0; + + // compute xs/ys/zs first to save 6 multiplications, since xs/ys/zs + // will be used 2-4 times each. + float xs = x * s; + float ys = y * s; + float zs = z * s; + float xx = x * xs; + float xy = x * ys; + float xz = x * zs; + float xw = w * xs; + float yy = y * ys; + float yz = y * zs; + float yw = w * ys; + float zz = z * zs; + float zw = w * zs; + + // using s=2/norm (instead of 1/norm) saves 9 multiplications by 2 here + result.m00 = 1 - (yy + zz); + result.m01 = (xy - zw); + result.m02 = (xz + yw); + result.m10 = (xy + zw); + result.m11 = 1 - (xx + zz); + result.m12 = (yz - xw); + result.m20 = (xz - yw); + result.m21 = (yz + xw); + result.m22 = 1 - (xx + yy); + + return result; + } + + /** + * toRotationMatrix converts this quaternion to a rotational + * matrix. The result is stored in result. 4th row and 4th column values are + * untouched. Note: the result is created from a normalized version of this + * quat. + * + * @param result The Matrix4f to store the result in. + * @return the rotation matrix representation of this quaternion. + */ + public Matrix4f toRotationMatrix(Matrix4f result) { + TempVars tempv = TempVars.get(); + Vector3f originalScale = tempv.vect1; + + result.toScaleVector(originalScale); + result.setScale(1, 1, 1); + float norm = norm(); + // we explicitly test norm against one here, saving a division + // at the cost of a test and branch. Is it worth it? + float s = (norm == 1f) ? 2f : (norm > 0f) ? 2f / norm : 0; + + // compute xs/ys/zs first to save 6 multiplications, since xs/ys/zs + // will be used 2-4 times each. + float xs = x * s; + float ys = y * s; + float zs = z * s; + float xx = x * xs; + float xy = x * ys; + float xz = x * zs; + float xw = w * xs; + float yy = y * ys; + float yz = y * zs; + float yw = w * ys; + float zz = z * zs; + float zw = w * zs; + + // using s=2/norm (instead of 1/norm) saves 9 multiplications by 2 here + result.m00 = 1 - (yy + zz); + result.m01 = (xy - zw); + result.m02 = (xz + yw); + result.m10 = (xy + zw); + result.m11 = 1 - (xx + zz); + result.m12 = (yz - xw); + result.m20 = (xz - yw); + result.m21 = (yz + xw); + result.m22 = 1 - (xx + yy); + + result.setScale(originalScale); + + tempv.release(); + + return result; + } + + /** + * getRotationColumn returns one of three columns specified by + * the parameter. This column is returned as a Vector3f object. + * + * @param i the column to retrieve. Must be between 0 and 2. + * @return the column specified by the index. + */ + public Vector3f getRotationColumn(int i) { + return getRotationColumn(i, null); + } + + /** + * getRotationColumn returns one of three columns specified by + * the parameter. This column is returned as a Vector3f object. + * The value is retrieved as if this quaternion was first normalized. + * + * @param i the column to retrieve. Must be between 0 and 2. + * @param store the vector object to store the result in. if null, a new one + * is created. + * @return the column specified by the index. + */ + public Vector3f getRotationColumn(int i, Vector3f store) { + if (store == null) { + store = new Vector3f(); + } + + float norm = norm(); + if (norm != 1.0f) { + norm = FastMath.invSqrt(norm); + } + + float xx = x * x * norm; + float xy = x * y * norm; + float xz = x * z * norm; + float xw = x * w * norm; + float yy = y * y * norm; + float yz = y * z * norm; + float yw = y * w * norm; + float zz = z * z * norm; + float zw = z * w * norm; + + switch (i) { + case 0: + store.x = 1 - 2 * (yy + zz); + store.y = 2 * (xy + zw); + store.z = 2 * (xz - yw); + break; + case 1: + store.x = 2 * (xy - zw); + store.y = 1 - 2 * (xx + zz); + store.z = 2 * (yz + xw); + break; + case 2: + store.x = 2 * (xz + yw); + store.y = 2 * (yz - xw); + store.z = 1 - 2 * (xx + yy); + break; + default: + logger.warning("Invalid column index."); + throw new IllegalArgumentException("Invalid column index. " + i); + } + + return store; + } + + /** + * Gets three rows of rotation matrix. + *

+ * The same as transposed columns from {@linkplain #getRotationColumn}. + */ + public void getRotationBasis(Vector3f v1, Vector3f v2, Vector3f v3) { + // This source code from toRotationMatrix method + float norm = norm(); + float s = (norm == 1f) ? 2f : (norm > 0f) ? 2f / norm : 0; + //@formatter:off + float xs = x * s; float ys = y * s; float zs = z * s; + float xx = x * xs; float xy = x * ys; float xz = x * zs; + float xw = w * xs; float yy = y * ys; float yz = y * zs; + float yw = w * ys; float zz = z * zs; float zw = w * zs; + //@formatter:on + v1.set(1f - yy - zz, xy + zw, xz - yw); + v2.set(xy - zw, 1f - xx - zz, yz + xw); + v3.set(xz + yw, yz - xw, 1f - xx - yy); + } + + /** + * fromAngleAxis sets this quaternion to the values specified + * by an angle and an axis of rotation. This method creates an object, so + * use fromAngleNormalAxis if your axis is already normalized. + * + * @param angle the angle to rotate (in radians). + * @param axis the axis of rotation. + * @return this quaternion + */ + public Quaternion fromAngleAxis(float angle, Vector3f axis) { + Vector3f normAxis = axis.normalize(); + fromAngleNormalAxis(angle, normAxis); + return this; + } + + /** + * fromAngleAxis sets this quaternion to the values specified + * by an angle and an axis of rotation. This method creates an object, so + * use fromAngleNormalAxis if your axis is already normalized. + * + * @param angle the angle to rotate (in radians). + * @param x + * @param y + * @param z the axis of rotation. + * @return this quaternion + */ + public Quaternion fromAngleAxis(float angle, float x, float y, float z) { + float length = x * x + y * y + z * z; + if (length != 1f && length != 0f) { + length = 1.0f / FastMath.sqrt(length); + return fromAngleNormalAxis(angle, x * length, y * length, z * length); + } else { + return fromAngleNormalAxis(angle, x, y, z); + } + } + + /** + * fromAngleNormalAxis sets this quaternion to the values + * specified by an angle and a normalized axis of rotation. + * + * @param angle the angle to rotate (in radians). + * @param axis the axis of rotation (already normalized). + */ + public Quaternion fromAngleNormalAxis(float angle, Vector3f axis) { + if (axis.x == 0 && axis.y == 0 && axis.z == 0) { + loadIdentity(); + } else { + float halfAngle = 0.5f * angle; + float sin = FastMath.sin(halfAngle); + w = FastMath.cos(halfAngle); + x = sin * axis.x; + y = sin * axis.y; + z = sin * axis.z; + } + return this; + } + + /** + * fromAngleNormalAxis sets this quaternion to the values + * specified by an angle and a normalized axis of rotation. + * + * @param angle the angle to rotate (in radians). + * @param ax + * @param ay + * @param az the axis of rotation (already normalized). + */ + public Quaternion fromAngleNormalAxis(float angle, float ax, float ay, float az) { + if (ax == 0 && ay == 0 && az == 0) { + loadIdentity(); + } else { + float halfAngle = 0.5f * angle; + float sin = FastMath.sin(halfAngle); + w = FastMath.cos(halfAngle); + x = sin * ax; + y = sin * ay; + z = sin * az; + } + return this; + } + + /** + * toAngleAxis sets a given angle and axis to that represented + * by the current quaternion. The values are stored as follows: The axis is + * provided as a parameter and built by the method, the angle is returned as + * a float. + * + * @param axisStore the object we'll store the computed axis in. + * @return the angle of rotation in radians. + */ + public float toAngleAxis(Vector3f axisStore) { + float sqrLength = x * x + y * y + z * z; + float angle; + if (sqrLength == 0.0f) { + angle = 0.0f; + if (axisStore != null) { + axisStore.x = 1.0f; + axisStore.y = 0.0f; + axisStore.z = 0.0f; + } + } else { + angle = (2.0f * FastMath.acos(w)); + if (axisStore != null) { + float invLength = (1.0f / FastMath.sqrt(sqrLength)); + axisStore.x = x * invLength; + axisStore.y = y * invLength; + axisStore.z = z * invLength; + } + } + + return angle; + } + + public float angleBetween(Quaternion q2) { + float w = this.w * q2.w + this.x * q2.x + this.y * q2.y + this.z * q2.z; + float x = this.w * q2.x - this.x * q2.w - this.y * q2.z + this.z * q2.y; + float y = this.w * q2.y + this.x * q2.z - this.y * q2.w - this.z * q2.x; + float z = this.w * q2.z - this.x * q2.y + this.y * q2.x - this.z * q2.w; + + // compute cosine and sine of the angle between + // do so in a numerically stable way + return FastMath.atan2(FastMath.sqrt(x * x + y * y + z * z), w); + } + + public Quaternion pureSlerpLocal(Quaternion q2, float t) { + // make it nice and symmetrical + Quaternion q1 = this; + + // get q2 relative to q1 + float rw = q1.w * q2.w + q1.x * q2.x + q1.y * q2.y + q1.z * q2.z; + float rx = q1.w * q2.x - q1.x * q2.w - q1.y * q2.z + q1.z * q2.y; + float ry = q1.w * q2.y + q1.x * q2.z - q1.y * q2.w - q1.z * q2.x; + float rz = q1.w * q2.z - q1.x * q2.y + q1.y * q2.x - q1.z * q2.w; + + // compute theta robustly + float theta = FastMath.atan2(FastMath.sqrt(rx * rx + ry * ry + rz * rz), rw); + + // compute interpolation variables + float s0 = FastMath.sin((1.0f - t) * theta); + float s1 = FastMath.sin(t * theta); + + // compute interpolated quaternion + float sw = s0 * q1.w + s1 * q2.w; + float sx = s0 * q1.x + s1 * q2.x; + float sy = s0 * q1.y + s1 * q2.y; + float sz = s0 * q1.z + s1 * q2.z; + + // compute the length of the quaternion + float mag = FastMath.sqrt(sw * sw + sx * sx + sy * sy + sz * sz); + + if (mag > 0.0f) { + float iMag = 1.0f / mag; + this.w = iMag * sw; + this.x = iMag * sx; + this.y = iMag * sy; + this.z = iMag * sz; + + } else if (t >= 0.5f) { + this.w = q2.w; + this.x = q2.x; + this.y = q2.y; + this.z = q2.z; + } + // else this == q1, no need to do anything. + + return this; + } + + /** + * Sets the values of this normalized quaternion from itself to the + * normalized quaternion q2 by t + * + * @param q2 Final interpolation value + * @param t The amount diffrence + */ + public Quaternion slerpLocal(Quaternion q2, float t) { + // make it nice and symmetrical + Quaternion q1 = this; + + float rw = q1.w * q2.w + q1.x * q2.x + q1.y * q2.y + q1.z * q2.z; + + if (rw < 0) { + return this.pureSlerpLocal(q2.negate(), t); + } else { + return this.pureSlerpLocal(q2, t); + } + } + + + public Quaternion pureSlerp(Quaternion q1, Quaternion q2, float t) { + return set(q1).pureSlerpLocal(q2, t); + } + + /** + * slerp sets this quaternion's value as an interpolation + * between two other normalized quaternions. + * + * @param q1 the first quaternion. + * @param q2 the second quaternion. + * @param t the amount to interpolate between the two quaternions. + */ + public Quaternion slerp(Quaternion q1, Quaternion q2, float t) { + float rw = q1.w * q2.w + q1.x * q2.x + q1.y * q2.y + q1.z * q2.z; + + if (rw < 0) { + return pureSlerp(q1, q2.negate(), t); + } else { + return pureSlerp(q1, q2, t); + } + } + + /** + * Sets the values of this quaternion to the nlerp from itself to q2 by + * blend. + * + * @param q2 + * @param blend + */ + public void nlerp(Quaternion q2, float blend) { + float dot = dot(q2); + float blendI = 1.0f - blend; + if (dot < 0.0f) { + x = blendI * x - blend * q2.x; + y = blendI * y - blend * q2.y; + z = blendI * z - blend * q2.z; + w = blendI * w - blend * q2.w; + } else { + x = blendI * x + blend * q2.x; + y = blendI * y + blend * q2.y; + z = blendI * z + blend * q2.z; + w = blendI * w + blend * q2.w; + } + normalizeLocal(); + } + + /** + * add adds the values of this quaternion to those of the + * parameter quaternion. The result is returned as a new quaternion. + * + * @param q the quaternion to add to this. + * @return the new quaternion. + */ + public Quaternion add(Quaternion q) { + return new Quaternion(x + q.x, y + q.y, z + q.z, w + q.w); + } + + /** + * add adds the values of this quaternion to those of the + * parameter quaternion. The result is stored in this Quaternion. + * + * @param q the quaternion to add to this. + * @return This Quaternion after addition. + */ + public Quaternion addLocal(Quaternion q) { + this.x += q.x; + this.y += q.y; + this.z += q.z; + this.w += q.w; + return this; + } + + /** + * subtract subtracts the values of the parameter quaternion + * from those of this quaternion. The result is returned as a new + * quaternion. + * + * @param q the quaternion to subtract from this. + * @return the new quaternion. + */ + public Quaternion subtract(Quaternion q) { + return new Quaternion(x - q.x, y - q.y, z - q.z, w - q.w); + } + + /** + * subtract subtracts the values of the parameter quaternion + * from those of this quaternion. The result is stored in this Quaternion. + * + * @param q the quaternion to subtract from this. + * @return This Quaternion after subtraction. + */ + public Quaternion subtractLocal(Quaternion q) { + this.x -= q.x; + this.y -= q.y; + this.z -= q.z; + this.w -= q.w; + return this; + } + + /** + * mult multiplies this quaternion by a parameter quaternion. + * The result is returned as a new quaternion. It should be noted that + * quaternion multiplication is not commutative so q * p != p * q. + * + * @param q the quaternion to multiply this quaternion by. + * @return the new quaternion. + */ + public Quaternion mult(Quaternion q) { + return mult(q, null); + } + + /** + * mult multiplies this quaternion by a parameter quaternion. + * The result is returned as a new quaternion. It should be noted that + * quaternion multiplication is not commutative so q * p != p * q. + * + * It IS safe for q and res to be the same object. It IS NOT safe for this + * and res to be the same object. + * + * @param q the quaternion to multiply this quaternion by. + * @param res the quaternion to store the result in. + * @return the new quaternion. + */ + public Quaternion mult(Quaternion q, Quaternion res) { + if (res == null) { + res = new Quaternion(); + } + float qw = q.w, qx = q.x, qy = q.y, qz = q.z; + res.x = x * qw + y * qz - z * qy + w * qx; + res.y = -x * qz + y * qw + z * qx + w * qy; + res.z = x * qy - y * qx + z * qw + w * qz; + res.w = -x * qx - y * qy - z * qz + w * qw; + return res; + } + + /** + * apply multiplies this quaternion by a parameter matrix + * internally. + * + * @param matrix the matrix to apply to this quaternion. + */ + public void apply(Matrix3f matrix) { + float oldX = x, oldY = y, oldZ = z, oldW = w; + fromRotationMatrix(matrix); + float tempX = x, tempY = y, tempZ = z, tempW = w; + + x = oldX * tempW + oldY * tempZ - oldZ * tempY + oldW * tempX; + y = -oldX * tempZ + oldY * tempW + oldZ * tempX + oldW * tempY; + z = oldX * tempY - oldY * tempX + oldZ * tempW + oldW * tempZ; + w = -oldX * tempX - oldY * tempY - oldZ * tempZ + oldW * tempW; + } + + /** + * + * fromAxes creates a Quaternion that represents + * the coordinate system defined by three axes. These axes are assumed to be + * orthogonal and no error checking is applied. Thus, the user must insure + * that the three axes being provided indeed represents a proper right + * handed coordinate system. + * + * @param axis the array containing the three vectors representing the + * coordinate system. + */ + public Quaternion fromAxes(Vector3f[] axis) { + if (axis.length != 3) { + throw new IllegalArgumentException("Axis array must have three elements"); + } + return fromAxes(axis[0], axis[1], axis[2]); + } + + /** + * + * fromAxes creates a Quaternion that represents + * the coordinate system defined by three axes. These axes are assumed to be + * orthogonal and no error checking is applied. Thus, the user must insure + * that the three axes being provided indeed represents a proper right + * handed coordinate system. + * + * @param xAxis vector representing the x-axis of the coordinate system. + * @param yAxis vector representing the y-axis of the coordinate system. + * @param zAxis vector representing the z-axis of the coordinate system. + */ + public Quaternion fromAxes(Vector3f xAxis, Vector3f yAxis, Vector3f zAxis) { + return fromRotationMatrix( + xAxis.x, + yAxis.x, + zAxis.x, + xAxis.y, + yAxis.y, + zAxis.y, + xAxis.z, + yAxis.z, + zAxis.z + ); + } + + /** + * + * toAxes takes in an array of three vectors. Each vector + * corresponds to an axis of the coordinate system defined by the quaternion + * rotation. + * + * @param axis the array of vectors to be filled. + */ + public void toAxes(Vector3f axis[]) { + Matrix3f tempMat = toRotationMatrix(); + axis[0] = tempMat.getColumn(0, axis[0]); + axis[1] = tempMat.getColumn(1, axis[1]); + axis[2] = tempMat.getColumn(2, axis[2]); + } + + /** + * mult multiplies this quaternion by a parameter vector. The + * result is returned as a new vector. + * + * @param v the vector to multiply this quaternion by. + * @return the new vector. + */ + public Vector3f mult(Vector3f v) { + return mult(v, null); + } + + /** + * mult multiplies this quaternion by a parameter vector. The + * result is stored in the supplied vector + * + * @param v the vector to multiply this quaternion by. + * @return v + */ + public Vector3f multLocal(Vector3f v) { + float tempX, tempY; + tempX = w * w * v.x + + 2 * y * w * v.z + - 2 * z * w * v.y + + x * x * v.x + + 2 * y * x * v.y + + 2 * z * x * v.z + - z * z * v.x + - y * y * v.x; + tempY = 2 * x * y * v.x + + y * y * v.y + + 2 * z * y * v.z + + 2 * w * z * v.x + - z * z * v.y + + w * w * v.y + - 2 * x * w * v.z + - x * x * v.y; + v.z = 2 * x * z * v.x + + 2 * y * z * v.y + + z * z * v.z + - 2 * w * y * v.x + - y * y * v.z + + 2 * w * x * v.y + - x * x * v.z + + w * w * v.z; + v.x = tempX; + v.y = tempY; + return v; + } + + /** + * Multiplies this Quaternion by the supplied quaternion. The result is + * stored in this Quaternion, which is also returned for chaining. Similar + * to this *= q. + * + * @param q The Quaternion to multiply this one by. + * @return This Quaternion, after multiplication. + */ + public Quaternion multLocal(Quaternion q) { + float x1 = x * q.w + y * q.z - z * q.y + w * q.x; + float y1 = -x * q.z + y * q.w + z * q.x + w * q.y; + float z1 = x * q.y - y * q.x + z * q.w + w * q.z; + w = -x * q.x - y * q.y - z * q.z + w * q.w; + x = x1; + y = y1; + z = z1; + return this; + } + + /** + * Multiplies this Quaternion by the supplied quaternion. The result is + * stored in this Quaternion, which is also returned for chaining. Similar + * to this *= q. + * + * @param qx - quat x value + * @param qy - quat y value + * @param qz - quat z value + * @param qw - quat w value + * + * @return This Quaternion, after multiplication. + */ + public Quaternion multLocal(float qx, float qy, float qz, float qw) { + float x1 = x * qw + y * qz - z * qy + w * qx; + float y1 = -x * qz + y * qw + z * qx + w * qy; + float z1 = x * qy - y * qx + z * qw + w * qz; + w = -x * qx - y * qy - z * qz + w * qw; + x = x1; + y = y1; + z = z1; + return this; + } + + /** + * mult multiplies this quaternion by a parameter vector. The + * result is returned as a new vector. + * + * @param v the vector to multiply this quaternion by. + * @param store the vector to store the result in. It IS safe for v and + * store to be the same object. + * @return the result vector. + */ + public Vector3f mult(Vector3f v, Vector3f store) { + if (store == null) { + store = new Vector3f(); + } + if (v.x == 0 && v.y == 0 && v.z == 0) { + store.set(0, 0, 0); + } else { + float vx = v.x, vy = v.y, vz = v.z; + store.x = w * w * vx + + 2 * y * w * vz + - 2 * z * w * vy + + x * x * vx + + 2 * y * x * vy + + 2 * z * x * vz + - z * z * vx + - y * y * vx; + store.y = 2 * x * y * vx + + y * y * vy + + 2 * z * y * vz + + 2 * w * z * vx + - z * z * vy + + w * w * vy + - 2 * x * w * vz + - x * x * vy; + store.z = 2 * x * z * vx + + 2 * y * z * vy + + z * z * vz + - 2 * w * y * vx + - y * y * vz + + 2 * w * x * vy + - x * x * vz + + w * w * vz; + } + return store; + } + + /** + * @return X component of vector rotated by quaternion + */ + public float multX(float vx, float vy, float vz) { + return w * w * vx + + 2 * y * w * vz + - 2 * z * w * vy + + x * x * vx + + 2 * y * x * vy + + 2 * z * x * vz + - z * z * vx + - y * y * vx; + } + + /** + * @return Y component of vector rotated by quaternion + */ + public float multY(float vx, float vy, float vz) { + return 2 * x * y * vx + + y * y * vy + + 2 * z * y * vz + + 2 * w * z * vx + - z * z * vy + + w * w * vy + - 2 * x * w * vz + - x * x * vy; + } + + /** + * @return Z component of vector rotated by quaternion + */ + public float multZ(float vx, float vy, float vz) { + return 2 * x * z * vx + + 2 * y * z * vy + + z * z * vz + - 2 * w * y * vx + - y * y * vz + + 2 * w * x * vy + - x * x * vz + + w * w * vz; + } + + /** + * Rotate X axis aligned vector. + */ + public Vector3f multAxisX(float vx, Vector3f store) { + if (store == null) + store = new Vector3f(); + store.x = (w * w + x * x - z * z - y * y) * vx; + store.y = 2f * (x * y + w * z) * vx; + store.z = 2f * (x * z - w * y) * vx; + return store; + } + + /** + * Rotate Y axis aligned vector. + */ + public Vector3f multAxisY(float vy, Vector3f store) { + if (store == null) + store = new Vector3f(); + store.x = 2f * (y * x - z * w) * vy; + store.y = (y * y - z * z + w * w - x * x) * vy; + store.z = 2f * (y * z + w * x) * vy; + return store; + } + + /** + * Rotate Z axis aligned vector. + */ + public Vector3f multAxisZ(float vz, Vector3f store) { + if (store == null) + store = new Vector3f(); + store.x = 2f * (y * w + z * x) * vz; + store.y = 2f * (z * y - x * w) * vz; + store.z = (z * z - y * y - x * x + w * w) * vz; + return store; + } + + /** + * mult multiplies this quaternion by a parameter vector. The + * result is returned as a new vector. + * + * @param vx + * @param vy + * @param vz the vector to multiply this quaternion by. + * @param store the vector to store the result in. It IS safe for v and + * store to be the same object. + * @return the result vector. + */ + public Vector3f mult(float vx, float vy, float vz, Vector3f store) { + if (store == null) { + store = new Vector3f(); + } + if (vx == 0 && vy == 0 && vz == 0) { + store.set(0, 0, 0); + } else { + store.x = w * w * vx + + 2 * y * w * vz + - 2 * z * w * vy + + x * x * vx + + 2 * y * x * vy + + 2 * z * x * vz + - z * z * vx + - y * y * vx; + store.y = 2 * x * y * vx + + y * y * vy + + 2 * z * y * vz + + 2 * w * z * vx + - z * z * vy + + w * w * vy + - 2 * x * w * vz + - x * x * vy; + store.z = 2 * x * z * vx + + 2 * y * z * vy + + z * z * vz + - 2 * w * y * vx + - y * y * vz + + 2 * w * x * vy + - x * x * vz + + w * w * vz; + } + return store; + } + + /** + * mult multiplies this quaternion by a parameter scalar. The + * result is returned as a new quaternion. + * + * @param scalar the quaternion to multiply this quaternion by. + * @return the new quaternion. + */ + public Quaternion mult(float scalar) { + return new Quaternion(scalar * x, scalar * y, scalar * z, scalar * w); + } + + /** + * mult multiplies this quaternion by a parameter scalar. The + * result is stored locally. + * + * @param scalar the quaternion to multiply this quaternion by. + * @return this. + */ + public Quaternion multLocal(float scalar) { + w *= scalar; + x *= scalar; + y *= scalar; + z *= scalar; + return this; + } + + /** + * dot calculates and returns the dot product of this + * quaternion with that of the parameter quaternion. + * + * @param q the quaternion to calculate the dot product of. + * @return the dot product of this and the parameter quaternion. + */ + public float dot(Quaternion q) { + return w * q.w + x * q.x + y * q.y + z * q.z; + } + + /** + * norm returns the norm of this quaternion. This is the dot + * product of this quaternion with itself. + * + * @return the norm of the quaternion. + */ + public float norm() { + return w * w + x * x + y * y + z * z; + } + + /** + * normalizeLocal normalizes the current + * Quaternion. The result is stored internally. + */ + public Quaternion normalizeLocal() { + float n = FastMath.invSqrt(norm()); + x *= n; + y *= n; + z *= n; + w *= n; + return this; + } + + /** + * normalize returns the normalized Quaternion. + */ + public Quaternion normalize() { + Quaternion q = this.clone(); + + float n = FastMath.invSqrt(q.norm()); + q.x *= n; + q.y *= n; + q.z *= n; + q.w *= n; + return q; + } + + /** + * inverse returns the inverse of this quaternion as a new + * quaternion. If this quaternion does not have an inverse (if its normal is + * 0 or less), then null is returned. + * + * @return the inverse of this quaternion or null if the inverse does not + * exist. + */ + public Quaternion inverse() { + float norm = norm(); + if (norm > 0.0) { + float invNorm = 1.0f / norm; + return new Quaternion(-x * invNorm, -y * invNorm, -z * invNorm, w * invNorm); + } + // return an invalid result to flag the error + return null; + } + + /** + * inverse returns the inverse of this quaternion. If this + * quaternion does not have an inverse (if its normal is 0 or less), then + * null is returned. + * + * @return the inverse of this quaternion or null if the inverse does not + * exist. + */ + public Quaternion inverse(Quaternion store) { + float norm = norm(); + if (norm > 0.0) { + float invNorm = 1.0f / norm; + return store.set(-x * invNorm, -y * invNorm, -z * invNorm, w * invNorm); + } + // return an invalid result to flag the error + return null; + } + + /** + * inverse calculates the inverse of this quaternion and + * returns this quaternion after it is calculated. If this quaternion does + * not have an inverse (if it's normal is 0 or less), nothing happens + * + * @return the inverse of this quaternion + */ + public Quaternion inverseLocal() { + float norm = norm(); + if (norm > 0.0) { + float invNorm = 1.0f / norm; + x *= -invNorm; + y *= -invNorm; + z *= -invNorm; + w *= invNorm; + } + return this; + } + + /** + * negateLocal inverts the values of the quaternion and returns + * it. + */ + public Quaternion negateLocal() { + x = -x; + y = -y; + z = -z; + w = -w; + return this; + } + + /** + * negate returns a negated copy of the quaternion. + */ + public Quaternion negate() { + return new Quaternion(-x, -y, -z, -w); + } + + /** + * + * toString creates the string representation of this + * Quaternion. The values of the quaternion are displaced (x, + * y, z, w), in the following manner:
+ * (x, y, z, w) + * + * @return the string representation of this object. + * @see java.lang.Object#toString() + */ + @Override + public String toString() { + return "(" + x + ", " + y + ", " + z + ", " + w + ")"; + } + + /** + * equals determines if two quaternions are logically equal, + * that is, if the values of (x, y, z, w) are the same for both quaternions. + * + * @param o the object to compare for equality + * @return true if they are equal, false otherwise. + */ + @Override + public boolean equals(Object o) { + if (!(o instanceof Quaternion)) { + return false; + } + + if (this == o) { + return true; + } + + Quaternion comp = (Quaternion) o; + if (Float.compare(x, comp.x) != 0) { + return false; + } + if (Float.compare(y, comp.y) != 0) { + return false; + } + if (Float.compare(z, comp.z) != 0) { + return false; + } + if (Float.compare(w, comp.w) != 0) { + return false; + } + return true; + } + + /** + * + * hashCode returns the hash code value as an integer and is + * supported for the benefit of hashing based collection classes such as + * Hashtable, HashMap, HashSet etc. + * + * @return the hashcode for this instance of Quaternion. + * @see java.lang.Object#hashCode() + */ + @Override + public int hashCode() { + int hash = 37; + hash = 37 * hash + Float.floatToIntBits(x); + hash = 37 * hash + Float.floatToIntBits(y); + hash = 37 * hash + Float.floatToIntBits(z); + hash = 37 * hash + Float.floatToIntBits(w); + return hash; + + } + + /** + * readExternal builds a quaternion from an + * ObjectInput object.
+ * NOTE: Used with serialization. Not to be called manually. + * + * @param in the ObjectInput value to read from. + * @throws IOException if the ObjectInput value has problems reading a + * float. + * @see java.io.Externalizable + */ + public void readExternal(ObjectInput in) throws IOException { + x = in.readFloat(); + y = in.readFloat(); + z = in.readFloat(); + w = in.readFloat(); + } + + /** + * writeExternal writes this quaternion out to a + * ObjectOutput object. NOTE: Used with serialization. Not to + * be called manually. + * + * @param out the object to write to. + * @throws IOException if writing to the ObjectOutput fails. + * @see java.io.Externalizable + */ + public void writeExternal(ObjectOutput out) throws IOException { + out.writeFloat(x); + out.writeFloat(y); + out.writeFloat(z); + out.writeFloat(w); + } + + /** + * lookAt is a convienence method for auto-setting the + * quaternion based on a direction and an up vector. It computes the + * rotation to transform the z-axis to point into 'direction' and the y-axis + * to 'up'. + * + * @param direction where to look at in terms of local coordinates + * @param up a vector indicating the local up direction. (typically {0, 1, + * 0} in jME.) + */ + public void lookAt(Vector3f direction, Vector3f up) { + TempVars vars = TempVars.get(); + vars.vect3.set(direction).normalizeLocal(); + vars.vect1.set(up).crossLocal(direction).normalizeLocal(); + vars.vect2.set(direction).crossLocal(vars.vect1).normalizeLocal(); + fromAxes(vars.vect1, vars.vect2, vars.vect3); + vars.release(); + } + + /** + * @return A new quaternion that describes a rotation that would point you + * in the exact opposite direction of this Quaternion. + */ + public Quaternion opposite() { + return opposite(null); + } + + /** + * FIXME: This seems to have singularity type issues with angle == 0, + * possibly others such as PI. + * + * @param store A Quaternion to store our result in. If null, a new one is + * created. + * @return The store quaternion (or a new Quaterion, if store is null) that + * describes a rotation that would point you in the exact opposite direction + * of this Quaternion. + */ + public Quaternion opposite(Quaternion store) { + if (store == null) { + store = new Quaternion(); + } + + Vector3f axis = new Vector3f(); + float angle = toAngleAxis(axis); + + store.fromAngleAxis(FastMath.PI + angle, axis); + return store; + } + + /** + * @return This Quaternion, altered to describe a rotation that would point + * you in the exact opposite direction of where it is pointing currently. + */ + public Quaternion oppositeLocal() { + return opposite(this); + } + + @Override + public Quaternion clone() { + try { + return (Quaternion) super.clone(); + } catch (CloneNotSupportedException e) { + throw new AssertionError(); // can not happen + } + } + + /** + * Sets this quaternion to be a rotation from vec1 to vec2. + *

+ * Based on implementation from here: + * https://github.com/toji/gl-matrix/blob/f0583ef53e94bc7e78b78c8a24f09ed5e2f7a20c/src/gl-matrix/quat.js#L54 + * + * @param vec1 + * @param vec2 + * @return this quaternion + */ + public Quaternion angleBetweenVectors(Vector3f vec1, Vector3f vec2) { + float dot = vec1.dot(vec2); + if (FloatMath.lessOrEqualsWithEpsilon(dot, -1)) { + Vector3f cross = vec1.cross(Vector3f.UNIT_X); + if (FloatMath.lessOrEqualsToZero(cross.length())) + cross = vec1.cross(Vector3f.UNIT_Y); + cross.normalizeLocal(); + fromAngleAxis(FloatMath.PI, cross); + } else if (FloatMath.greaterOrEqualsWithEpsilon(dot, 1)) { + loadIdentity(); + } else { + Vector3f cross = vec1.cross(vec2); + x = cross.x; + y = cross.y; + z = cross.z; + w = 1 + dot; + normalizeLocal(); + } + return this; + } + + public static boolean isIdentity(Quaternion q) { + if (Float.compare(q.x, 0) != 0) { + return false; + } + if (Float.compare(q.y, 0) != 0) { + return false; + } + if (Float.compare(q.z, 0) != 0) { + return false; + } + if (Float.compare(q.w, 1) != 0) { + return false; + } + return true; + } +} diff --git a/src/main/java/com/jme3/math/TempVars.java b/src/main/java/com/jme3/math/TempVars.java new file mode 100644 index 000000000..d7fe4acaa --- /dev/null +++ b/src/main/java/com/jme3/math/TempVars.java @@ -0,0 +1,147 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.math; + +/** + * Temporary variables assigned to each thread. Engine classes may access these + * temp variables with TempVars.get(), all retrieved TempVars instances must be + * returned via TempVars.release(). This returns an available instance of the + * TempVar class ensuring this particular instance is never used elsewhere in + * the mean time. + */ +public class TempVars { + + /** + * Allow X instances of TempVars in a single thread. + */ + private static final int STACK_SIZE = 5; + + /** + * TempVarsStack contains a stack of TempVars. Every time + * TempVars.get() is called, a new entry is added to the stack, and the + * index incremented. When TempVars.release() is called, the entry is + * checked against the current instance and then the index is decremented. + */ + private static class TempVarsStack { + + int index = 0; + TempVars[] tempVars = new TempVars[STACK_SIZE]; + } + + /** + * ThreadLocal to store a TempVarsStack for each thread. This ensures each + * thread has a single TempVarsStack that is used only in method calls in + * that thread. + */ + private static final ThreadLocal varsLocal = new ThreadLocal() { + + @Override + public TempVarsStack initialValue() { + return new TempVarsStack(); + } + }; + /** + * This instance of TempVars has been retrieved but not released yet. + */ + private boolean isUsed = false; + + private TempVars() { + } + + /** + * Acquire an instance of the TempVar class. You have to release the + * instance after use by calling the release() method. If more than + * STACK_SIZE (currently 5) instances are requested in a single thread then + * an ArrayIndexOutOfBoundsException will be thrown. + * + * @return A TempVar instance + */ + public static TempVars get() { + TempVarsStack stack = varsLocal.get(); + + TempVars instance = stack.tempVars[stack.index]; + + if (instance == null) { + // Create new + instance = new TempVars(); + + // Put it in there + stack.tempVars[stack.index] = instance; + } + + stack.index++; + + instance.isUsed = true; + + return instance; + } + + /** + * Releases this instance of TempVars. Once released, the contents of the + * TempVars are undefined. The TempVars must be released in the opposite + * order that they are retrieved, e.g. Acquiring vars1, then acquiring + * vars2, vars2 MUST be released first otherwise an exception will be + * thrown. + */ + public void release() { + if (!isUsed) { + throw new IllegalStateException("This instance of TempVars was already released!"); + } + + isUsed = false; + + TempVarsStack stack = varsLocal.get(); + + // Return it to the stack + stack.index--; + + // Check if it is actually there + if (stack.tempVars[stack.index] != this) { + throw new IllegalStateException( + "An instance of TempVars has not been released in a called method!" + ); + } + } + + /** + * General vectors. + */ + public final Vector3f vect1 = new Vector3f(); + public final Vector3f vect2 = new Vector3f(); + public final Vector3f vect3 = new Vector3f(); + /** + * General matrices. + */ + public final Matrix4f tempMat4 = new Matrix4f(); + + public final float[] matrixWrite = new float[16]; +} diff --git a/src/main/java/com/jme3/math/Transform.java b/src/main/java/com/jme3/math/Transform.java new file mode 100644 index 000000000..f6cac7b5f --- /dev/null +++ b/src/main/java/com/jme3/math/Transform.java @@ -0,0 +1,399 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.math; + +/** + * Started Date: Jul 16, 2004
+ *
+ * Represents a translation, rotation and scale in one object. + * + * @author Jack Lindamood + * @author Joshua Slack + */ +public final class Transform implements Cloneable, java.io.Serializable { + + static final long serialVersionUID = 1; + + public static final Transform IDENTITY = new Transform(); + + private Quaternion rot = new Quaternion(); + private Vector3f translation = new Vector3f(); + private Vector3f scale = new Vector3f(1, 1, 1); + + public Transform(Vector3f translation, Quaternion rot) { + this.translation.set(translation); + this.rot.set(rot); + } + + public Transform(Vector3f translation, Quaternion rot, Vector3f scale) { + this(translation, rot); + this.scale.set(scale); + } + + public Transform(Vector3f translation) { + this(translation, Quaternion.IDENTITY); + } + + public Transform(Quaternion rot) { + this(Vector3f.ZERO, rot); + } + + public Transform() { + this(Vector3f.ZERO, Quaternion.IDENTITY); + } + + /** + * Sets this rotation to the given Quaternion value. + * + * @param rot The new rotation for this matrix. + * @return this + */ + public Transform setRotation(Quaternion rot) { + this.rot.set(rot); + return this; + } + + /** + * Sets this translation to the given value. + * + * @param trans The new translation for this matrix. + * @return this + */ + public Transform setTranslation(Vector3f trans) { + this.translation.set(trans); + return this; + } + + /** + * Return the translation vector in this matrix. + * + * @return translation vector. + */ + public Vector3f getTranslation() { + return translation; + } + + /** + * Sets this scale to the given value. + * + * @param scale The new scale for this matrix. + * @return this + */ + public Transform setScale(Vector3f scale) { + this.scale.set(scale); + return this; + } + + /** + * Sets this scale to the given value. + * + * @param scale The new scale for this matrix. + * @return this + */ + public Transform setScale(float scale) { + this.scale.set(scale, scale, scale); + return this; + } + + /** + * Return the scale vector in this matrix. + * + * @return scale vector. + */ + public Vector3f getScale() { + return scale; + } + + /** + * Stores this translation value into the given vector3f. If trans is null, + * a new vector3f is created to hold the value. The value, once stored, is + * returned. + * + * @param trans The store location for this matrix's translation. + * @return The value of this matrix's translation. + */ + public Vector3f getTranslation(Vector3f trans) { + if (trans == null) + trans = new Vector3f(); + trans.set(this.translation); + return trans; + } + + /** + * Stores this rotation value into the given Quaternion. If quat is null, a + * new Quaternion is created to hold the value. The value, once stored, is + * returned. + * + * @param quat The store location for this matrix's rotation. + * @return The value of this matrix's rotation. + */ + public Quaternion getRotation(Quaternion quat) { + if (quat == null) + quat = new Quaternion(); + quat.set(rot); + return quat; + } + + /** + * Return the rotation quaternion in this matrix. + * + * @return rotation quaternion. + */ + public Quaternion getRotation() { + return rot; + } + + /** + * Stores this scale value into the given vector3f. If scale is null, a new + * vector3f is created to hold the value. The value, once stored, is + * returned. + * + * @param scale The store location for this matrix's scale. + * @return The value of this matrix's scale. + */ + public Vector3f getScale(Vector3f scale) { + if (scale == null) + scale = new Vector3f(); + scale.set(this.scale); + return scale; + } + + /** + * Sets this matrix to the interpolation between the first matrix and the + * second by delta amount. + * + * @param t1 The begining transform. + * @param t2 The ending transform. + * @param delta An amount between 0 and 1 representing how far to + * interpolate from t1 to t2. + */ + public void interpolateTransforms(Transform t1, Transform t2, float delta) { + this.rot.slerp(t1.rot, t2.rot, delta); + this.translation.interpolate(t1.translation, t2.translation, delta); + this.scale.interpolate(t1.scale, t2.scale, delta); + } + + /** + * Changes the values of this matrix acording to it's parent. Very similar + * to the concept of Node/Spatial transforms. + * + * @param parent The parent matrix. + * @return This matrix, after combining. + */ + public Transform combineWithParent(Transform parent) { + scale.multLocal(parent.scale); +// rot.multLocal(parent.rot); + parent.rot.mult(rot, rot); + + // This here, is evil code +// parent +// .rot +// .multLocal(translation) +// .multLocal(parent.scale) +// .addLocal(parent.translation); + + translation.multLocal(parent.scale); + parent.rot + .multLocal(translation) + .addLocal(parent.translation); + return this; + } + + /** + * Same as {@link #combineWithParent(Transform)}, but assumes that rotation + * is global, so it's not modified. + * + * @param parent + * @return + */ + public Transform combineWithParentGlobalRotation(Transform parent) { + scale.multLocal(parent.scale); + translation.multLocal(parent.scale); + + parent.rot + .multLocal(translation) + .addLocal(parent.translation); + return this; + } + + /** + * Sets this matrix's translation to the given x,y,z values. + * + * @param x This matrix's new x translation. + * @param y This matrix's new y translation. + * @param z This matrix's new z translation. + * @return this + */ + public Transform setTranslation(float x, float y, float z) { + translation.set(x, y, z); + return this; + } + + /** + * Sets this matrix's scale to the given x,y,z values. + * + * @param x This matrix's new x scale. + * @param y This matrix's new y scale. + * @param z This matrix's new z scale. + * @return this + */ + public Transform setScale(float x, float y, float z) { + scale.set(x, y, z); + return this; + } + + public Vector3f transformVector(final Vector3f in, Vector3f store) { + if (store == null) + store = new Vector3f(); + + // multiply with scale first, then rotate, finally translate (cf. + // Eberly) + return rot.mult(store.set(in).multLocal(scale), store).addLocal(translation); + } + + public Vector3f transformInverseVector(final Vector3f in, Vector3f store) { + if (store == null) + store = new Vector3f(); + + // The author of this code should look above and take the inverse of + // that + // But for some reason, they didnt .. +// in.subtract(translation, store).divideLocal(scale); +// rot.inverse().mult(store, store); + + in.subtract(translation, store); + rot.inverse().mult(store, store); + store.divideLocal(scale); + + return store; + } + + /** + * Loads the identity. Equal to translation=0,0,0 scale=1,1,1 rot=0,0,0,1. + */ + public void loadIdentity() { + translation.set(0, 0, 0); + scale.set(1, 1, 1); + rot.set(0, 0, 0, 1); + } + + @Override + public String toString() { + return getClass().getSimpleName() + + "[ " + + translation.x + + ", " + + translation.y + + ", " + + translation.z + + "]\n" + + "[ " + + rot.x + + ", " + + rot.y + + ", " + + rot.z + + ", " + + rot.w + + "]\n" + + "[ " + + scale.x + + " , " + + scale.y + + ", " + + scale.z + + "]"; + } + + /** + * Sets this matrix to be equal to the given matrix. + * + * @param matrixQuat The matrix to be equal to. + * @return this + */ + public Transform set(Transform matrixQuat) { + this.translation.set(matrixQuat.translation); + this.rot.set(matrixQuat.rot); + this.scale.set(matrixQuat.scale); + return this; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((rot == null) ? 0 : rot.hashCode()); + result = prime * result + ((scale == null) ? 0 : scale.hashCode()); + result = prime * result + ((translation == null) ? 0 : translation.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + Transform other = (Transform) obj; + if (rot == null) { + if (other.rot != null) + return false; + } else if (!rot.equals(other.rot)) + return false; + if (scale == null) { + if (other.scale != null) + return false; + } else if (!scale.equals(other.scale)) + return false; + if (translation == null) { + if (other.translation != null) + return false; + } else if (!translation.equals(other.translation)) + return false; + return true; + } + + @Override + public Transform clone() { + try { + Transform tq = (Transform) super.clone(); + tq.rot = rot.clone(); + tq.scale = scale.clone(); + tq.translation = translation.clone(); + return tq; + } catch (CloneNotSupportedException e) { + throw new AssertionError(); + } + } +} diff --git a/src/main/java/com/jme3/math/Vector2f.java b/src/main/java/com/jme3/math/Vector2f.java new file mode 100644 index 000000000..00b7805c1 --- /dev/null +++ b/src/main/java/com/jme3/math/Vector2f.java @@ -0,0 +1,718 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.math; + +import java.io.IOException; +import java.io.ObjectInput; +import java.io.ObjectOutput; +import java.util.logging.Logger; + + +/** + * Vector2f defines a Vector for a two float value vector. + * + * @author Mark Powell + * @author Joshua Slack + */ +public final class Vector2f implements Cloneable, java.io.Serializable { + + static final long serialVersionUID = 1; + private static final Logger logger = Logger.getLogger(Vector2f.class.getName()); + + public static final Vector2f ZERO = new Vector2f(0f, 0f); + public static final Vector2f UNIT_XY = new Vector2f(1f, 1f); + + /** + * the x value of the vector. + */ + public float x; + /** + * the y value of the vector. + */ + public float y; + + /** + * Creates a Vector2f with the given initial x and y values. + * + * @param x The x value of this Vector2f. + * @param y The y value of this Vector2f. + */ + public Vector2f(float x, float y) { + this.x = x; + this.y = y; + } + + /** + * Creates a Vector2f with x and y set to 0. Equivalent to Vector2f(0,0). + */ + public Vector2f() { + x = y = 0; + } + + /** + * Creates a new Vector2f that contains the passed vector's information + * + * @param vector2f The vector to copy + */ + public Vector2f(Vector2f vector2f) { + this.x = vector2f.x; + this.y = vector2f.y; + } + + /** + * set the x and y values of the vector + * + * @param x the x value of the vector. + * @param y the y value of the vector. + * @return this vector + */ + public Vector2f set(float x, float y) { + this.x = x; + this.y = y; + return this; + } + + /** + * set the x and y values of the vector from another vector + * + * @param vec the vector to copy from + * @return this vector + */ + public Vector2f set(Vector2f vec) { + this.x = vec.x; + this.y = vec.y; + return this; + } + + /** + * add adds a provided vector to this vector creating a + * resultant vector which is returned. If the provided vector is null, null + * is returned. + * + * @param vec the vector to add to this. + * @return the resultant vector. + */ + public Vector2f add(Vector2f vec) { + if (null == vec) { + logger.warning("Provided vector is null, null returned."); + return null; + } + return new Vector2f(x + vec.x, y + vec.y); + } + + /** + * addLocal adds a provided vector to this vector internally, + * and returns a handle to this vector for easy chaining of calls. If the + * provided vector is null, null is returned. + * + * @param vec the vector to add to this vector. + * @return this + */ + public Vector2f addLocal(Vector2f vec) { + if (null == vec) { + logger.warning("Provided vector is null, null returned."); + return null; + } + x += vec.x; + y += vec.y; + return this; + } + + /** + * addLocal adds the provided values to this vector internally, + * and returns a handle to this vector for easy chaining of calls. + * + * @param addX value to add to x + * @param addY value to add to y + * @return this + */ + public Vector2f addLocal(float addX, float addY) { + x += addX; + y += addY; + return this; + } + + /** + * add adds this vector by vec and stores the + * result in result. + * + * @param vec The vector to add. + * @param result The vector to store the result in. + * @return The result vector, after adding. + */ + public Vector2f add(Vector2f vec, Vector2f result) { + if (null == vec) { + logger.warning("Provided vector is null, null returned."); + return null; + } + if (result == null) + result = new Vector2f(); + result.x = x + vec.x; + result.y = y + vec.y; + return result; + } + + /** + * dot calculates the dot product of this vector with a + * provided vector. If the provided vector is null, 0 is returned. + * + * @param vec the vector to dot with this vector. + * @return the resultant dot product of this vector and a given vector. + */ + public float dot(Vector2f vec) { + if (null == vec) { + logger.warning("Provided vector is null, 0 returned."); + return 0; + } + return x * vec.x + y * vec.y; + } + + /** + * cross calculates the cross product of this vector with a + * parameter vector v. + * + * @param v the vector to take the cross product of with this. + * @return the cross product vector. + */ + public Vector3f cross(Vector2f v) { + return new Vector3f(0, 0, determinant(v)); + } + + public float determinant(Vector2f v) { + return (x * v.y) - (y * v.x); + } + + /** + * Sets this vector to the interpolation by changeAmnt from this to the + * finalVec this=(1-changeAmnt)*this + changeAmnt * finalVec + * + * @param finalVec The final vector to interpolate towards + * @param changeAmnt An amount between 0.0 - 1.0 representing a percentage + * change from this towards finalVec + */ + public Vector2f interpolate(Vector2f finalVec, float changeAmnt) { + this.x = (1 - changeAmnt) * this.x + changeAmnt * finalVec.x; + this.y = (1 - changeAmnt) * this.y + changeAmnt * finalVec.y; + return this; + } + + /** + * Sets this vector to the interpolation by changeAmnt from beginVec to + * finalVec this=(1-changeAmnt)*beginVec + changeAmnt * finalVec + * + * @param beginVec The begining vector (delta=0) + * @param finalVec The final vector to interpolate towards (delta=1) + * @param changeAmnt An amount between 0.0 - 1.0 representing a precentage + * change from beginVec towards finalVec + */ + public Vector2f interpolate( + Vector2f beginVec, + Vector2f finalVec, + float changeAmnt + ) { + this.x = (1 - changeAmnt) * beginVec.x + changeAmnt * finalVec.x; + this.y = (1 - changeAmnt) * beginVec.y + changeAmnt * finalVec.y; + return this; + } + + /** + * Check a vector... if it is null or its floats are NaN or infinite, return + * false. Else return true. + * + * @param vector the vector to check + * @return true or false as stated above. + */ + public static boolean isValidVector(Vector2f vector) { + if (vector == null) + return false; + if ( + Float.isNaN(vector.x) + || + Float.isNaN(vector.y) + ) + return false; + if ( + Float.isInfinite(vector.x) + || + Float.isInfinite(vector.y) + ) + return false; + return true; + } + + /** + * length calculates the magnitude of this vector. + * + * @return the length or magnitude of the vector. + */ + public float length() { + return FastMath.sqrt(lengthSquared()); + } + + /** + * lengthSquared calculates the squared value of the magnitude + * of the vector. + * + * @return the magnitude squared of the vector. + */ + public float lengthSquared() { + return x * x + y * y; + } + + /** + * distanceSquared calculates the distance squared between this + * vector and vector v. + * + * @param v the second vector to determine the distance squared. + * @return the distance squared between the two vectors. + */ + public float distanceSquared(Vector2f v) { + double dx = x - v.x; + double dy = y - v.y; + return (float) (dx * dx + dy * dy); + } + + /** + * distanceSquared calculates the distance squared between this + * vector and vector v. + * + * @param otherX The X coordinate of the v vector + * @param otherY The Y coordinate of the v vector + * @return the distance squared between the two vectors. + */ + public float distanceSquared(float otherX, float otherY) { + double dx = x - otherX; + double dy = y - otherY; + return (float) (dx * dx + dy * dy); + } + + /** + * distance calculates the distance between this vector and + * vector v. + * + * @param v the second vector to determine the distance. + * @return the distance between the two vectors. + */ + public float distance(Vector2f v) { + return FastMath.sqrt(distanceSquared(v)); + } + + /** + * mult multiplies this vector by a scalar. The resultant + * vector is returned. + * + * @param scalar the value to multiply this vector by. + * @return the new vector. + */ + public Vector2f mult(float scalar) { + return new Vector2f(x * scalar, y * scalar); + } + + /** + * multLocal multiplies this vector by a scalar internally, and + * returns a handle to this vector for easy chaining of calls. + * + * @param scalar the value to multiply this vector by. + * @return this + */ + public Vector2f multLocal(float scalar) { + x *= scalar; + y *= scalar; + return this; + } + + /** + * multLocal multiplies a provided vector to this vector + * internally, and returns a handle to this vector for easy chaining of + * calls. If the provided vector is null, null is returned. + * + * @param vec the vector to mult to this vector. + * @return this + */ + public Vector2f multLocal(Vector2f vec) { + if (null == vec) { + logger.warning("Provided vector is null, null returned."); + return null; + } + x *= vec.x; + y *= vec.y; + return this; + } + + /** + * Multiplies this Vector2f's x and y by the scalar and stores the result in + * product. The result is returned for chaining. Similar to + * product=this*scalar; + * + * @param scalar The scalar to multiply by. + * @param product The vector2f to store the result in. + * @return product, after multiplication. + */ + public Vector2f mult(float scalar, Vector2f product) { + if (null == product) { + product = new Vector2f(); + } + + product.x = x * scalar; + product.y = y * scalar; + return product; + } + + /** + * divide divides the values of this vector by a scalar and + * returns the result. The values of this vector remain untouched. + * + * @param scalar the value to divide this vectors attributes by. + * @return the result Vector. + */ + public Vector2f divide(float scalar) { + return new Vector2f(x / scalar, y / scalar); + } + + /** + * divideLocal divides this vector by a scalar internally, and + * returns a handle to this vector for easy chaining of calls. Dividing by + * zero will result in an exception. + * + * @param scalar the value to divides this vector by. + * @return this + */ + public Vector2f divideLocal(float scalar) { + x /= scalar; + y /= scalar; + return this; + } + + /** + * negate returns the negative of this vector. All values are + * negated and set to a new vector. + * + * @return the negated vector. + */ + public Vector2f negate() { + return new Vector2f(-x, -y); + } + + /** + * negateLocal negates the internal values of this vector. + * + * @return this. + */ + public Vector2f negateLocal() { + x = -x; + y = -y; + return this; + } + + /** + * subtract subtracts the values of a given vector from those + * of this vector creating a new vector object. If the provided vector is + * null, an exception is thrown. + * + * @param vec the vector to subtract from this vector. + * @return the result vector. + */ + public Vector2f subtract(Vector2f vec) { + return subtract(vec, null); + } + + /** + * subtract subtracts the values of a given vector from those + * of this vector storing the result in the given vector object. If the + * provided vector is null, an exception is thrown. + * + * @param vec the vector to subtract from this vector. + * @param store the vector to store the result in. It is safe for this to be + * the same as vec. If null, a new vector is created. + * @return the result vector. + */ + public Vector2f subtract(Vector2f vec, Vector2f store) { + if (store == null) + store = new Vector2f(); + store.x = x - vec.x; + store.y = y - vec.y; + return store; + } + + /** + * subtract subtracts the given x,y values from those of this + * vector creating a new vector object. + * + * @param valX value to subtract from x + * @param valY value to subtract from y + * @return this + */ + public Vector2f subtract(float valX, float valY) { + return new Vector2f(x - valX, y - valY); + } + + /** + * subtractLocal subtracts a provided vector to this vector + * internally, and returns a handle to this vector for easy chaining of + * calls. If the provided vector is null, null is returned. + * + * @param vec the vector to subtract + * @return this + */ + public Vector2f subtractLocal(Vector2f vec) { + if (null == vec) { + logger.warning("Provided vector is null, null returned."); + return null; + } + x -= vec.x; + y -= vec.y; + return this; + } + + /** + * subtractLocal subtracts the provided values from this vector + * internally, and returns a handle to this vector for easy chaining of + * calls. + * + * @param valX value to subtract from x + * @param valY value to subtract from y + * @return this + */ + public Vector2f subtractLocal(float valX, float valY) { + x -= valX; + y -= valY; + return this; + } + + /** + * normalize returns the unit vector of this vector. + * + * @return unit vector of this vector. + */ + public Vector2f normalize() { + float length = length(); + if (length != 0) { + return divide(length); + } + + return divide(1); + } + + /** + * normalizeLocal makes this vector into a unit vector of + * itself. + * + * @return this. + */ + public Vector2f normalizeLocal() { + float length = length(); + if (length != 0) { + return divideLocal(length); + } + + return divideLocal(1); + } + + /** + * smallestAngleBetween returns (in radians) the minimum angle + * between two vectors. It is assumed that both this vector and the given + * vector are unit vectors (iow, normalized). + * + * @param otherVector a unit vector to find the angle against + * @return the angle in radians. + */ + public float smallestAngleBetween(Vector2f otherVector) { + float dotProduct = dot(otherVector); + float angle = FastMath.acos(dotProduct); + return angle; + } + + /** + * angleBetween returns (in radians) the angle required to + * rotate a ray represented by this vector to lie colinear to a ray + * described by the given vector. It is assumed that both this vector and + * the given vector are unit vectors (iow, normalized). + * + * @param otherVector the "destination" unit vector + * @return the angle in radians. + */ + public float angleBetween(Vector2f otherVector) { + float angle = FastMath.atan2(otherVector.y, otherVector.x) + - FastMath.atan2(y, x); + return angle; + } + + public float getX() { + return x; + } + + public Vector2f setX(float x) { + this.x = x; + return this; + } + + public float getY() { + return y; + } + + public Vector2f setY(float y) { + this.y = y; + return this; + } + + /** + * getAngle returns (in radians) the angle represented by this + * Vector2f as expressed by a conversion from rectangular coordinates + * (xy) to polar coordinates + * (r, theta). + * + * @return the angle in radians. [-pi, pi) + */ + public float getAngle() { + return FastMath.atan2(y, x); + } + + /** + * zero resets this vector's data to zero internally. + */ + public Vector2f zero() { + x = y = 0; + return this; + } + + /** + * hashCode returns a unique code for this vector object based + * on it's values. If two vectors are logically equivalent, they will return + * the same hash code value. + * + * @return the hash code value of this vector. + */ + @Override + public int hashCode() { + int hash = 37; + hash += 37 * hash + Float.floatToIntBits(x); + hash += 37 * hash + Float.floatToIntBits(y); + return hash; + } + + @Override + public Vector2f clone() { + try { + return (Vector2f) super.clone(); + } catch (CloneNotSupportedException e) { + throw new AssertionError(); // can not happen + } + } + + /** + * Saves this Vector2f into the given float[] object. + * + * @param floats The float[] to take this Vector2f. If null, a new float[2] + * is created. + * @return The array, with X, Y float values in that order + */ + public float[] toArray(float[] floats) { + if (floats == null) { + floats = new float[2]; + } + floats[0] = x; + floats[1] = y; + return floats; + } + + /** + * are these two vectors the same? they are is they both have the same x and + * y values. + * + * @param o the object to compare for equality + * @return true if they are equal + */ + @Override + public boolean equals(Object o) { + if (!(o instanceof Vector2f)) { + return false; + } + + if (this == o) { + return true; + } + + Vector2f comp = (Vector2f) o; + if (Float.compare(x, comp.x) != 0) + return false; + if (Float.compare(y, comp.y) != 0) + return false; + return true; + } + + /** + * toString returns the string representation of this vector + * object. The format of the string is such: com.jme.math.Vector2f + * [X=XX.XXXX, Y=YY.YYYY] + * + * @return the string representation of this vector. + */ + @Override + public String toString() { + return "(" + x + ", " + y + ")"; + } + + /** + * Used with serialization. Not to be called manually. + * + * @param in ObjectInput + * @throws IOException + * @throws ClassNotFoundException + * @see java.io.Externalizable + */ + public void readExternal(ObjectInput in) throws IOException, + ClassNotFoundException { + x = in.readFloat(); + y = in.readFloat(); + } + + /** + * Used with serialization. Not to be called manually. + * + * @param out ObjectOutput + * @throws IOException + * @see java.io.Externalizable + */ + public void writeExternal(ObjectOutput out) throws IOException { + out.writeFloat(x); + out.writeFloat(y); + } + + public void rotateAroundOrigin(float angle, boolean cw) { + if (cw) + angle = -angle; + float newX = FastMath.cos(angle) * x - FastMath.sin(angle) * y; + float newY = FastMath.sin(angle) * x + FastMath.cos(angle) * y; + x = newX; + y = newY; + } +} diff --git a/src/main/java/com/jme3/math/Vector3f.java b/src/main/java/com/jme3/math/Vector3f.java new file mode 100644 index 000000000..2d764e4ac --- /dev/null +++ b/src/main/java/com/jme3/math/Vector3f.java @@ -0,0 +1,1101 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package com.jme3.math; + +import java.util.logging.Logger; + +import io.eiren.math.Vector3d; + +/* + * -- Added *Local methods to cut down on object creation - JS + */ + + +/** + * Vector3f defines a Vector for a three float value tuple. + * Vector3f can represent any three dimensional value, such as a + * vertex, a normal, etc. Utility methods are also included to aid in + * mathematical calculations. + * + * @author Mark Powell + * @author Joshua Slack + */ +public final class Vector3f implements Cloneable, java.io.Serializable { + + static final long serialVersionUID = 1; + + private static final Logger logger = Logger.getLogger(Vector3f.class.getName()); + + public final static Vector3f ZERO = new Vector3f(0, 0, 0); + public final static Vector3f NAN = new Vector3f(Float.NaN, Float.NaN, Float.NaN); + public final static Vector3f UNIT_X = new Vector3f(1, 0, 0); + public final static Vector3f UNIT_Y = new Vector3f(0, 1, 0); + public final static Vector3f UNIT_X_Y = new Vector3f(1, 1, 0).normalizeLocal(); + public final static Vector3f UNIT_Z = new Vector3f(0, 0, 1); + public final static Vector3f UNIT_X_Z = new Vector3f(1, 0, 1).normalizeLocal(); + public final static Vector3f UNIT_Y_Z = new Vector3f(0, 1, 1).normalizeLocal(); + public final static Vector3f UNIT_X_Y_Z = new Vector3f(1, 1, 1).normalizeLocal(); + + + public final static Vector3f NEGATIVE_UNIT_X = new Vector3f(-1, 0, 0); + public final static Vector3f NEGATIVE_UNIT_Y = new Vector3f(0, -1, 0); + public final static Vector3f NEGATIVE_UNIT_Z = new Vector3f(0, 0, -1); + + public final static Vector3f UNIT_XYZ = new Vector3f(1, 1, 1); + public final static Vector3f POSITIVE_INFINITY = new Vector3f( + Float.POSITIVE_INFINITY, + Float.POSITIVE_INFINITY, + Float.POSITIVE_INFINITY + ); + public final static Vector3f NEGATIVE_INFINITY = new Vector3f( + Float.NEGATIVE_INFINITY, + Float.NEGATIVE_INFINITY, + Float.NEGATIVE_INFINITY + ); + + public final static Vector3f UNIT_PX_PY_PZ = UNIT_X_Y_Z; + public final static Vector3f UNIT_NX_PY_PZ = new Vector3f(-1, 1, 1).normalizeLocal(); + public final static Vector3f UNIT_PX_NY_PZ = new Vector3f(1, -1, 1).normalizeLocal(); + public final static Vector3f UNIT_NX_NY_PZ = new Vector3f(-1, -1, 1).normalizeLocal(); + public final static Vector3f UNIT_PX_PY_NZ = new Vector3f(1, 1, -1).normalizeLocal(); + public final static Vector3f UNIT_NX_PY_NZ = new Vector3f(-1, 1, -1).normalizeLocal(); + public final static Vector3f UNIT_PX_NY_NZ = new Vector3f(1, -1, -1).normalizeLocal(); + public final static Vector3f UNIT_NX_NY_NZ = new Vector3f(-1, -1, -1).normalizeLocal(); + + public final static Vector3f UNIT_PX_PY_0 = UNIT_X_Y; + public final static Vector3f UNIT_PX_NY_0 = new Vector3f(1, -1, 0).normalizeLocal(); + public final static Vector3f UNIT_NX_PY_0 = new Vector3f(-1, 1, 0).normalizeLocal(); + public final static Vector3f UNIT_NX_NY_0 = new Vector3f(-1, -1, 0).normalizeLocal(); + + public final static Vector3f UNIT_PX_0_PZ = UNIT_X_Z; + public final static Vector3f UNIT_PX_0_NZ = new Vector3f(1, 0, -1).normalizeLocal(); + public final static Vector3f UNIT_NX_0_PZ = new Vector3f(-1, 0, 1).normalizeLocal(); + public final static Vector3f UNIT_NX_0_NZ = new Vector3f(-1, 0, -1).normalizeLocal(); + + public final static Vector3f UNIT_0_PY_PZ = UNIT_Y_Z; + public final static Vector3f UNIT_0_PY_NZ = new Vector3f(0, 1, -1).normalizeLocal(); + public final static Vector3f UNIT_0_NY_PZ = new Vector3f(0, -1, 1).normalizeLocal(); + public final static Vector3f UNIT_0_NY_NZ = new Vector3f(0, -1, -1).normalizeLocal(); + + + /** + * the x value of the vector. + */ + public float x; + + /** + * the y value of the vector. + */ + public float y; + + /** + * the z value of the vector. + */ + public float z; + + /** + * Constructor instantiates a new Vector3f with default values + * of (0,0,0). + * + */ + public Vector3f() { + x = y = z = 0; + } + + /** + * Constructor instantiates a new Vector3f with provides + * values. + * + * @param x the x value of the vector. + * @param y the y value of the vector. + * @param z the z value of the vector. + */ + public Vector3f(float x, float y, float z) { + this.x = x; + this.y = y; + this.z = z; + } + + /** + * Constructor instantiates a new Vector3f that is a copy of + * the provided vector + * + * @param copy The Vector3f to copy + */ + public Vector3f(Vector3f copy) { + this.set(copy); + } + + public Vector3f(Vector3d copy) { + this.set((float) copy.x, (float) copy.y, (float) copy.z); + } + + /** + * set sets the x,y,z values of the vector based on passed + * parameters. + * + * @param x the x value of the vector. + * @param y the y value of the vector. + * @param z the z value of the vector. + * @return this vector + */ + public Vector3f set(float x, float y, float z) { + this.x = x; + this.y = y; + this.z = z; + return this; + } + + /** + * set sets the x,y,z values of the vector by copying the + * supplied vector. + * + * @param vect the vector to copy. + * @return this vector + */ + public Vector3f set(Vector3f vect) { + this.x = vect.x; + this.y = vect.y; + this.z = vect.z; + return this; + } + + /** + * + * add adds a provided vector to this vector creating a + * resultant vector which is returned. If the provided vector is null, null + * is returned. + * + * @param vec the vector to add to this. + * @return the resultant vector. + */ + public Vector3f add(Vector3f vec) { + if (null == vec) { + logger.warning("Provided vector is null, null returned."); + return null; + } + return new Vector3f(x + vec.x, y + vec.y, z + vec.z); + } + + /** + * + * add adds the values of a provided vector storing the values + * in the supplied vector. + * + * @param vec the vector to add to this + * @param result the vector to store the result in + * @return result returns the supplied result vector. + */ + public Vector3f add(Vector3f vec, Vector3f result) { + result.x = x + vec.x; + result.y = y + vec.y; + result.z = z + vec.z; + return result; + } + + /** + * addLocal adds a provided vector to this vector internally, + * and returns a handle to this vector for easy chaining of calls. If the + * provided vector is null, null is returned. + * + * @param vec the vector to add to this vector. + * @return this + */ + public Vector3f addLocal(Vector3f vec) { + if (null == vec) { + logger.warning("Provided vector is null, null returned."); + return null; + } + x += vec.x; + y += vec.y; + z += vec.z; + return this; + } + + /** + * + * add adds the provided values to this vector, creating a new + * vector that is then returned. + * + * @param addX the x value to add. + * @param addY the y value to add. + * @param addZ the z value to add. + * @return the result vector. + */ + public Vector3f add(float addX, float addY, float addZ) { + return new Vector3f(x + addX, y + addY, z + addZ); + } + + /** + * + * add adds the provided values to this vector, creating a new + * vector that is then returned. + * + * @param addX the x value to add. + * @param addY the y value to add. + * @param addZ the z value to add. + * @param store the vector object to store the result in. if null, a new one + * is created. + * @return the result vector. + */ + public Vector3f add(float addX, float addY, float addZ, Vector3f store) { + if (store == null) { + return new Vector3f(x + addX, y + addY, z + addZ); + } else { + return store.set(x + addX, y + addY, z + addZ); + } + } + + /** + * addLocal adds the provided values to this vector internally, + * and returns a handle to this vector for easy chaining of calls. + * + * @param addX value to add to x + * @param addY value to add to y + * @param addZ value to add to z + * @return this + */ + public Vector3f addLocal(float addX, float addY, float addZ) { + x += addX; + y += addY; + z += addZ; + return this; + } + + /** + * + * scaleAdd multiplies this vector by a scalar then adds the + * given Vector3f. + * + * @param scalar the value to multiply this vector by. + * @param add the value to add + */ + public Vector3f scaleAdd(float scalar, Vector3f add) { + x = x * scalar + add.x; + y = y * scalar + add.y; + z = z * scalar + add.z; + return this; + } + + /** + * + * scaleAdd multiplies the given vector by a scalar then adds + * the given vector. + * + * @param scalar the value to multiply this vector by. + * @param mult the value to multiply the scalar by + * @param add the value to add + */ + public Vector3f scaleAdd(float scalar, Vector3f mult, Vector3f add) { + this.x = mult.x * scalar + add.x; + this.y = mult.y * scalar + add.y; + this.z = mult.z * scalar + add.z; + return this; + } + + /** + * + * dot calculates the dot product of this vector with a + * provided vector. If the provided vector is null, 0 is returned. + * + * @param vec the vector to dot with this vector. + * @return the resultant dot product of this vector and a given vector. + */ + public float dot(Vector3f vec) { + if (null == vec) { + logger.warning("Provided vector is null, 0 returned."); + return 0; + } + return x * vec.x + y * vec.y + z * vec.z; + } + + public float dot(float vx, float vy, float vz) { + return x * vx + y * vy + z * vz; + } + + /** + * cross calculates the cross product of this vector with a + * parameter vector v. + * + * @param v the vector to take the cross product of with this. + * @return the cross product vector. + */ + public Vector3f cross(Vector3f v) { + return cross(v, null); + } + + /** + * cross calculates the cross product of this vector with a + * parameter vector v. The result is stored in result + * + * @param v the vector to take the cross product of with this. + * @param result the vector to store the cross product result. + * @return result, after recieving the cross product vector. + */ + public Vector3f cross(Vector3f v, Vector3f result) { + return cross(v.x, v.y, v.z, result); + } + + /** + * cross calculates the cross product of this vector with a + * parameter vector v. The result is stored in result + * + * @param otherX x component of the vector to take the cross product of with + * this. + * @param otherY y component of the vector to take the cross product of with + * this. + * @param otherZ z component of the vector to take the cross product of with + * this. + * @param result the vector to store the cross product result. + * @return result, after recieving the cross product vector. + */ + public Vector3f cross(float otherX, float otherY, float otherZ, Vector3f result) { + if (result == null) + result = new Vector3f(); + float resX = ((y * otherZ) - (z * otherY)); + float resY = ((z * otherX) - (x * otherZ)); + float resZ = ((x * otherY) - (y * otherX)); + result.set(resX, resY, resZ); + return result; + } + + /** + * crossLocal calculates the cross product of this vector with + * a parameter vector v. + * + * @param v the vector to take the cross product of with this. + * @return this. + */ + public Vector3f crossLocal(Vector3f v) { + return crossLocal(v.x, v.y, v.z); + } + + /** + * crossLocal calculates the cross product of this vector with + * a parameter vector v. + * + * @param otherX x component of the vector to take the cross product of with + * this. + * @param otherY y component of the vector to take the cross product of with + * this. + * @param otherZ z component of the vector to take the cross product of with + * this. + * @return this. + */ + public Vector3f crossLocal(float otherX, float otherY, float otherZ) { + float tempx = (y * otherZ) - (z * otherY); + float tempy = (z * otherX) - (x * otherZ); + z = (x * otherY) - (y * otherX); + x = tempx; + y = tempy; + return this; + } + + /** + * Projects this vector onto another vector + * + * @param other The vector to project this vector onto + * @return A new vector with the projection result + */ + public Vector3f project(Vector3f other) { + float n = this.dot(other); // A . B + float d = other.lengthSquared(); // |B|^2 + return new Vector3f(other).normalizeLocal().multLocal(n / d); + } + + /** + * Projects this vector onto another vector, stores the result in this + * vector + * + * @param other The vector to project this vector onto + * @return This Vector3f, set to the projection result + */ + public Vector3f projectLocal(Vector3f other) { + float n = this.dot(other); // A . B + float d = other.lengthSquared(); // |B|^2 + return set(other).normalizeLocal().multLocal(n / d); + } + + /** + * Returns true if this vector is a unit vector (length() ~= 1), returns + * false otherwise. + * + * @return true if this vector is a unit vector (length() ~= 1), or false + * otherwise. + */ + public boolean isUnitVector() { + float len = length(); + return 0.99f < len && len < 1.01f; + } + + /** + * length calculates the magnitude of this vector. + * + * @return the length or magnitude of the vector. + */ + public float length() { + return FastMath.sqrt(lengthSquared()); + } + + /** + * lengthSquared calculates the squared value of the magnitude + * of the vector. + * + * @return the magnitude squared of the vector. + */ + public float lengthSquared() { + return x * x + y * y + z * z; + } + + /** + * distanceSquared calculates the distance squared between this + * vector and vector v. + * + * @param v the second vector to determine the distance squared. + * @return the distance squared between the two vectors. + */ + public float distanceSquared(Vector3f v) { + double dx = x - v.x; + double dy = y - v.y; + double dz = z - v.z; + return (float) (dx * dx + dy * dy + dz * dz); + } + + /** + * distance calculates the distance between this vector and + * vector v. + * + * @param v the second vector to determine the distance. + * @return the distance between the two vectors. + */ + public float distance(Vector3f v) { + return FastMath.sqrt(distanceSquared(v)); + } + + /** + * + * mult multiplies this vector by a scalar. The resultant + * vector is returned. + * + * @param scalar the value to multiply this vector by. + * @return the new vector. + */ + public Vector3f mult(float scalar) { + return new Vector3f(x * scalar, y * scalar, z * scalar); + } + + /** + * + * mult multiplies this vector by a scalar. The resultant + * vector is supplied as the second parameter and returned. + * + * @param scalar the scalar to multiply this vector by. + * @param product the product to store the result in. + * @return product + */ + public Vector3f mult(float scalar, Vector3f product) { + if (null == product) { + product = new Vector3f(); + } + + product.x = x * scalar; + product.y = y * scalar; + product.z = z * scalar; + return product; + } + + /** + * multLocal multiplies this vector by a scalar internally, and + * returns a handle to this vector for easy chaining of calls. + * + * @param scalar the value to multiply this vector by. + * @return this + */ + public Vector3f multLocal(float scalar) { + x *= scalar; + y *= scalar; + z *= scalar; + return this; + } + + /** + * multLocal multiplies a provided vector to this vector + * internally, and returns a handle to this vector for easy chaining of + * calls. If the provided vector is null, null is returned. + * + * @param vec the vector to mult to this vector. + * @return this + */ + public Vector3f multLocal(Vector3f vec) { + if (null == vec) { + logger.warning("Provided vector is null, null returned."); + return null; + } + x *= vec.x; + y *= vec.y; + z *= vec.z; + return this; + } + + /** + * multLocal multiplies this vector by 3 scalars internally, + * and returns a handle to this vector for easy chaining of calls. + * + * @param x + * @param y + * @param z + * @return this + */ + public Vector3f multLocal(float x, float y, float z) { + this.x *= x; + this.y *= y; + this.z *= z; + return this; + } + + /** + * multLocal multiplies a provided vector to this vector + * internally, and returns a handle to this vector for easy chaining of + * calls. If the provided vector is null, null is returned. + * + * @param vec the vector to mult to this vector. + * @return this + */ + public Vector3f mult(Vector3f vec) { + if (null == vec) { + logger.warning("Provided vector is null, null returned."); + return null; + } + return mult(vec, null); + } + + /** + * multLocal multiplies a provided vector to this vector + * internally, and returns a handle to this vector for easy chaining of + * calls. If the provided vector is null, null is returned. + * + * @param vec the vector to mult to this vector. + * @param store result vector (null to create a new vector) + * @return this + */ + public Vector3f mult(Vector3f vec, Vector3f store) { + if (null == vec) { + logger.warning("Provided vector is null, null returned."); + return null; + } + if (store == null) + store = new Vector3f(); + return store.set(x * vec.x, y * vec.y, z * vec.z); + } + + /** + * divide divides the values of this vector by a scalar and + * returns the result. The values of this vector remain untouched. + * + * @param scalar the value to divide this vectors attributes by. + * @return the result Vector. + */ + public Vector3f divide(float scalar) { + scalar = 1f / scalar; + return new Vector3f(x * scalar, y * scalar, z * scalar); + } + + public Vector3f divide(float scalar, Vector3f store) { + scalar = 1f / scalar; + return store.set(x * scalar, y * scalar, z * scalar); + } + + /** + * divideLocal divides this vector by a scalar internally, and + * returns a handle to this vector for easy chaining of calls. Dividing by + * zero will result in an exception. + * + * @param scalar the value to divides this vector by. + * @return this + */ + public Vector3f divideLocal(float scalar) { + scalar = 1f / scalar; + x *= scalar; + y *= scalar; + z *= scalar; + return this; + } + + /** + * divide divides the values of this vector by a scalar and + * returns the result. The values of this vector remain untouched. + * + * @param scalar the value to divide this vectors attributes by. + * @return the result Vector. + */ + public Vector3f divide(Vector3f scalar) { + return new Vector3f(x / scalar.x, y / scalar.y, z / scalar.z); + } + + /** + * divideLocal divides this vector by a scalar internally, and + * returns a handle to this vector for easy chaining of calls. Dividing by + * zero will result in an exception. + * + * @param scalar the value to divides this vector by. + * @return this + */ + public Vector3f divideLocal(Vector3f scalar) { + x /= scalar.x; + y /= scalar.y; + z /= scalar.z; + return this; + } + + /** + * + * negate returns the negative of this vector. All values are + * negated and set to a new vector. + * + * @return the negated vector. + */ + public Vector3f negate() { + return new Vector3f(-x, -y, -z); + } + + /** + * + * negateLocal negates the internal values of this vector. + * + * @return this. + */ + public Vector3f negateLocal() { + x = -x; + y = -y; + z = -z; + return this; + } + + /** + * + * subtract subtracts the values of a given vector from those + * of this vector creating a new vector object. If the provided vector is + * null, null is returned. + * + * @param vec the vector to subtract from this vector. + * @return the result vector. + */ + public Vector3f subtract(Vector3f vec) { + return new Vector3f(x - vec.x, y - vec.y, z - vec.z); + } + + /** + * subtractLocal subtracts a provided vector to this vector + * internally, and returns a handle to this vector for easy chaining of + * calls. If the provided vector is null, null is returned. + * + * @param vec the vector to subtract + * @return this + */ + public Vector3f subtractLocal(Vector3f vec) { + if (null == vec) { + logger.warning("Provided vector is null, null returned."); + return null; + } + x -= vec.x; + y -= vec.y; + z -= vec.z; + return this; + } + + /** + * + * subtract + * + * @param vec the vector to subtract from this + * @param result the vector to store the result in + * @return result + */ + public Vector3f subtract(Vector3f vec, Vector3f result) { + if (result == null) { + result = new Vector3f(); + } + result.x = x - vec.x; + result.y = y - vec.y; + result.z = z - vec.z; + return result; + } + + /** + * + * subtract subtracts the provided values from this vector, + * creating a new vector that is then returned. + * + * @param subtractX the x value to subtract. + * @param subtractY the y value to subtract. + * @param subtractZ the z value to subtract. + * @return the result vector. + */ + public Vector3f subtract(float subtractX, float subtractY, float subtractZ) { + return new Vector3f(x - subtractX, y - subtractY, z - subtractZ); + } + + /** + * subtractLocal subtracts the provided values from this vector + * internally, and returns a handle to this vector for easy chaining of + * calls. + * + * @param subtractX the x value to subtract. + * @param subtractY the y value to subtract. + * @param subtractZ the z value to subtract. + * @return this + */ + public Vector3f subtractLocal(float subtractX, float subtractY, float subtractZ) { + x -= subtractX; + y -= subtractY; + z -= subtractZ; + return this; + } + + /** + * normalize returns the unit vector of this vector. + * + * @return unit vector of this vector. + */ + public Vector3f normalize() { + float length = x * x + y * y + z * z; + if (length != 1f && length != 0f) { + length = 1.0f / FastMath.sqrt(length); + return new Vector3f(x * length, y * length, z * length); + } + return clone(); + } + + public Vector3f normalize(Vector3f store) { + float length = x * x + y * y + z * z; + if (length != 1f && length != 0f) { + length = 1.0f / FastMath.sqrt(length); + return store.set(x * length, y * length, z * length); + } else { + return store.set(this); + } + } + + /** + * normalizeLocal makes this vector into a unit vector of + * itself. + * + * @return this + */ + public Vector3f normalizeLocal() { + // NOTE: this implementation is more optimized + // than the old jme normalize as this method + // is commonly used. + float length = x * x + y * y + z * z; + if (length != 1f && length != 0f) { + length = 1.0f / FastMath.sqrt(length); + x *= length; + y *= length; + z *= length; + } + return this; + } + + /** + * maxLocal computes the maximum value for each component in + * this and other vector. The result is stored in this vector. + * + * @param other + */ + public Vector3f maxLocal(Vector3f other) { + x = other.x > x ? other.x : x; + y = other.y > y ? other.y : y; + z = other.z > z ? other.z : z; + return this; + } + + /** + * minLocal computes the minimum value for each component in + * this and other vector. The result is stored in this vector. + * + * @param other + */ + public Vector3f minLocal(Vector3f other) { + x = other.x < x ? other.x : x; + y = other.y < y ? other.y : y; + z = other.z < z ? other.z : z; + return this; + } + + /** + * zero resets this vector's data to zero internally. + */ + public Vector3f zero() { + x = y = z = 0; + return this; + } + + /** + * angleBetween returns (in radians) the angle between two + * vectors. It is assumed that both this vector and the given vector are + * unit vectors (iow, normalized). + * + * @param otherVector a unit vector to find the angle against + * @return the angle in radians. + */ + public float angleBetween(Vector3f otherVector) { + float dotProduct = dot(otherVector); + float angle = FastMath.acos(dotProduct); + return angle; + } + + /** + * Sets this vector to the interpolation by changeAmnt from this to the + * finalVec this=(1-changeAmnt)*this + changeAmnt * finalVec + * + * @param finalVec The final vector to interpolate towards + * @param changeAmnt An amount between 0.0 - 1.0 representing a precentage + * change from this towards finalVec + */ + public Vector3f interpolate(Vector3f finalVec, float changeAmnt) { + this.x = (1 - changeAmnt) * this.x + changeAmnt * finalVec.x; + this.y = (1 - changeAmnt) * this.y + changeAmnt * finalVec.y; + this.z = (1 - changeAmnt) * this.z + changeAmnt * finalVec.z; + return this; + } + + /** + * Sets this vector to the interpolation by changeAmnt from beginVec to + * finalVec this=(1-changeAmnt)*beginVec + changeAmnt * finalVec + * + * @param beginVec the beging vector (changeAmnt=0) + * @param finalVec The final vector to interpolate towards + * @param changeAmnt An amount between 0.0 - 1.0 representing a precentage + * change from beginVec towards finalVec + */ + public Vector3f interpolate(Vector3f beginVec, Vector3f finalVec, float changeAmnt) { + this.x = (1 - changeAmnt) * beginVec.x + changeAmnt * finalVec.x; + this.y = (1 - changeAmnt) * beginVec.y + changeAmnt * finalVec.y; + this.z = (1 - changeAmnt) * beginVec.z + changeAmnt * finalVec.z; + return this; + } + + /** + * Check a vector... if it is null or its floats are NaN or infinite, return + * false. Else return true. + * + * @param vector the vector to check + * @return true or false as stated above. + */ + public static boolean isValidVector(Vector3f vector) { + if (vector == null) + return false; + if (Float.isNaN(vector.x) || Float.isNaN(vector.y) || Float.isNaN(vector.z)) + return false; + if (Float.isInfinite(vector.x) || Float.isInfinite(vector.y) || Float.isInfinite(vector.z)) + return false; + return true; + } + + public static void generateOrthonormalBasis(Vector3f u, Vector3f v, Vector3f w) { + w.normalizeLocal(); + generateComplementBasis(u, v, w); + } + + public static void generateComplementBasis(Vector3f u, Vector3f v, Vector3f w) { + float fInvLength; + + if (FastMath.abs(w.x) >= FastMath.abs(w.y)) { + // w.x or w.z is the largest magnitude component, swap them + fInvLength = FastMath.invSqrt(w.x * w.x + w.z * w.z); + u.x = -w.z * fInvLength; + u.y = 0.0f; + u.z = +w.x * fInvLength; + v.x = w.y * u.z; + v.y = w.z * u.x - w.x * u.z; + v.z = -w.y * u.x; + } else { + // w.y or w.z is the largest magnitude component, swap them + fInvLength = FastMath.invSqrt(w.y * w.y + w.z * w.z); + u.x = 0.0f; + u.y = +w.z * fInvLength; + u.z = -w.y * fInvLength; + v.x = w.y * u.z - w.z * u.y; + v.y = -w.x * u.z; + v.z = w.x * u.y; + } + } + + @Override + public Vector3f clone() { + try { + return (Vector3f) super.clone(); + } catch (CloneNotSupportedException e) { + throw new AssertionError(); // can not happen + } + } + + /** + * Saves this Vector3f into the given float[] object. + * + * @param floats The float[] to take this Vector3f. If null, a new float[3] + * is created. + * @return The array, with X, Y, Z float values in that order + */ + public float[] toArray(float[] floats) { + if (floats == null) { + floats = new float[3]; + } + floats[0] = x; + floats[1] = y; + floats[2] = z; + return floats; + } + + /** + * are these two vectors the same? they are is they both have the same x,y, + * and z values. + * + * @param o the object to compare for equality + * @return true if they are equal + */ + @Override + public boolean equals(Object o) { + if (!(o instanceof Vector3f)) + return false; + if (this == o) + return true; + Vector3f comp = (Vector3f) o; + if (Float.compare(x, comp.x) != 0) + return false; + if (Float.compare(y, comp.y) != 0) + return false; + if (Float.compare(z, comp.z) != 0) + return false; + return true; + } + + /** + * hashCode returns a unique code for this vector object based + * on it's values. If two vectors are logically equivalent, they will return + * the same hash code value. + * + * @return the hash code value of this vector. + */ + @Override + public int hashCode() { + int hash = 37; + hash += 37 * hash + Float.floatToIntBits(x); + hash += 37 * hash + Float.floatToIntBits(y); + hash += 37 * hash + Float.floatToIntBits(z); + return hash; + } + + /** + * toString returns the string representation of this vector. + * The format is: + * + * org.jme.math.Vector3f [X=XX.XXXX, Y=YY.YYYY, Z=ZZ.ZZZZ] + * + * @return the string representation of this vector. + */ + @Override + public String toString() { + return "(" + x + ", " + y + ", " + z + ")"; + } + + public float getX() { + return x; + } + + public Vector3f setX(float x) { + this.x = x; + return this; + } + + public float getY() { + return y; + } + + public Vector3f setY(float y) { + this.y = y; + return this; + } + + public float getZ() { + return z; + } + + public Vector3f setZ(float z) { + this.z = z; + return this; + } + + /** + * @param index + * @return x value if index == 0, y value if index == 1 or z value if index + * == 2 + * @throws IllegalArgumentException if index is not one of 0, 1, 2. + */ + public float get(int index) { + switch (index) { + case 0: + return x; + case 1: + return y; + case 2: + return z; + } + throw new IllegalArgumentException("index must be either 0, 1 or 2"); + } + + /** + * @param index which field index in this vector to set. + * @param value to set to one of x, y or z. + * @throws IllegalArgumentException if index is not one of 0, 1, 2. + */ + public void set(int index, float value) { + switch (index) { + case 0: + x = value; + return; + case 1: + y = value; + return; + case 2: + z = value; + return; + } + throw new IllegalArgumentException("index must be either 0, 1 or 2"); + } + + public static float angleBetweenVectors(Vector2f vec1, Vector2f vec2) { + return (float) Math + .atan2(vec1.x * vec2.y - vec1.y * vec2.x, vec1.x * vec2.x + vec1.y * vec2.y); + } +} diff --git a/src/main/java/com/jme3/math/Vector4f.java b/src/main/java/com/jme3/math/Vector4f.java new file mode 100644 index 000000000..20f101639 --- /dev/null +++ b/src/main/java/com/jme3/math/Vector4f.java @@ -0,0 +1,971 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.math; + +import java.util.logging.Logger; + + +/** + * Vector4f defines a Vector for a four float value tuple. + * Vector4f can represent any four dimensional value, such as a + * vertex, a normal, etc. Utility methods are also included to aid in + * mathematical calculations. + * + * @author Maarten Steur + */ +public final class Vector4f implements Cloneable, java.io.Serializable { + + static final long serialVersionUID = 1; + + private static final Logger logger = Logger.getLogger(Vector4f.class.getName()); + + public final static Vector4f ZERO = new Vector4f(0, 0, 0, 0); + public final static Vector4f NAN = new Vector4f(Float.NaN, Float.NaN, Float.NaN, Float.NaN); + public final static Vector4f UNIT_X = new Vector4f(1, 0, 0, 0); + public final static Vector4f UNIT_Y = new Vector4f(0, 1, 0, 0); + public final static Vector4f UNIT_Z = new Vector4f(0, 0, 1, 0); + public final static Vector4f UNIT_W = new Vector4f(0, 0, 0, 1); + public final static Vector4f UNIT_XYZW = new Vector4f(1, 1, 1, 1); + public final static Vector4f POSITIVE_INFINITY = new Vector4f( + Float.POSITIVE_INFINITY, + Float.POSITIVE_INFINITY, + Float.POSITIVE_INFINITY, + Float.POSITIVE_INFINITY + ); + public final static Vector4f NEGATIVE_INFINITY = new Vector4f( + Float.NEGATIVE_INFINITY, + Float.NEGATIVE_INFINITY, + Float.NEGATIVE_INFINITY, + Float.NEGATIVE_INFINITY + ); + + /** + * the x value of the vector. + */ + public float x; + + /** + * the y value of the vector. + */ + public float y; + + /** + * the z value of the vector. + */ + public float z; + + /** + * the w value of the vector. + */ + public float w; + + /** + * Constructor instantiates a new Vector3f with default values + * of (0,0,0). + * + */ + public Vector4f() { + x = y = z = w = 0; + } + + /** + * Constructor instantiates a new Vector4f with provides + * values. + * + * @param x the x value of the vector. + * @param y the y value of the vector. + * @param z the z value of the vector. + * @param w the w value of the vector. + */ + public Vector4f(float x, float y, float z, float w) { + this.x = x; + this.y = y; + this.z = z; + this.w = w; + } + + /** + * Constructor instantiates a new Vector3f that is a copy of + * the provided vector + * + * @param copy The Vector3f to copy + */ + public Vector4f(Vector4f copy) { + this.set(copy); + } + + /** + * set sets the x,y,z,w values of the vector based on passed + * parameters. + * + * @param x the x value of the vector. + * @param y the y value of the vector. + * @param z the z value of the vector. + * @param w the w value of the vector. + * @return this vector + */ + public Vector4f set(float x, float y, float z, float w) { + this.x = x; + this.y = y; + this.z = z; + this.w = w; + return this; + } + + /** + * set sets the x,y,z values of the vector by copying the + * supplied vector. + * + * @param vect the vector to copy. + * @return this vector + */ + public Vector4f set(Vector4f vect) { + this.x = vect.x; + this.y = vect.y; + this.z = vect.z; + this.w = vect.w; + return this; + } + + /** + * + * add adds a provided vector to this vector creating a + * resultant vector which is returned. If the provided vector is null, null + * is returned. + * + * @param vec the vector to add to this. + * @return the resultant vector. + */ + public Vector4f add(Vector4f vec) { + if (null == vec) { + logger.warning("Provided vector is null, null returned."); + return null; + } + return new Vector4f(x + vec.x, y + vec.y, z + vec.z, w + vec.w); + } + + /** + * + * add adds the values of a provided vector storing the values + * in the supplied vector. + * + * @param vec the vector to add to this + * @param result the vector to store the result in + * @return result returns the supplied result vector. + */ + public Vector4f add(Vector4f vec, Vector4f result) { + result.x = x + vec.x; + result.y = y + vec.y; + result.z = z + vec.z; + result.w = w + vec.w; + return result; + } + + /** + * addLocal adds a provided vector to this vector internally, + * and returns a handle to this vector for easy chaining of calls. If the + * provided vector is null, null is returned. + * + * @param vec the vector to add to this vector. + * @return this + */ + public Vector4f addLocal(Vector4f vec) { + if (null == vec) { + logger.warning("Provided vector is null, null returned."); + return null; + } + x += vec.x; + y += vec.y; + z += vec.z; + w += vec.w; + return this; + } + + /** + * + * add adds the provided values to this vector, creating a new + * vector that is then returned. + * + * @param addX the x value to add. + * @param addY the y value to add. + * @param addZ the z value to add. + * @return the result vector. + */ + public Vector4f add(float addX, float addY, float addZ, float addW) { + return new Vector4f(x + addX, y + addY, z + addZ, w + addW); + } + + /** + * addLocal adds the provided values to this vector internally, + * and returns a handle to this vector for easy chaining of calls. + * + * @param addX value to add to x + * @param addY value to add to y + * @param addZ value to add to z + * @return this + */ + public Vector4f addLocal(float addX, float addY, float addZ, float addW) { + x += addX; + y += addY; + z += addZ; + w += addW; + return this; + } + + /** + * + * scaleAdd multiplies this vector by a scalar then adds the + * given Vector3f. + * + * @param scalar the value to multiply this vector by. + * @param add the value to add + */ + public Vector4f scaleAdd(float scalar, Vector4f add) { + x = x * scalar + add.x; + y = y * scalar + add.y; + z = z * scalar + add.z; + w = w * scalar + add.w; + return this; + } + + /** + * + * scaleAdd multiplies the given vector by a scalar then adds + * the given vector. + * + * @param scalar the value to multiply this vector by. + * @param mult the value to multiply the scalar by + * @param add the value to add + */ + public Vector4f scaleAdd(float scalar, Vector4f mult, Vector4f add) { + this.x = mult.x * scalar + add.x; + this.y = mult.y * scalar + add.y; + this.z = mult.z * scalar + add.z; + this.w = mult.w * scalar + add.w; + return this; + } + + /** + * + * dot calculates the dot product of this vector with a + * provided vector. If the provided vector is null, 0 is returned. + * + * @param vec the vector to dot with this vector. + * @return the resultant dot product of this vector and a given vector. + */ + public float dot(Vector4f vec) { + if (null == vec) { + logger.warning("Provided vector is null, 0 returned."); + return 0; + } + return x * vec.x + y * vec.y + z * vec.z + w * vec.w; + } + + public Vector4f project(Vector4f other) { + float n = this.dot(other); // A . B + float d = other.lengthSquared(); // |B|^2 + return new Vector4f(other).normalizeLocal().multLocal(n / d); + } + + /** + * Returns true if this vector is a unit vector (length() ~= 1), returns + * false otherwise. + * + * @return true if this vector is a unit vector (length() ~= 1), or false + * otherwise. + */ + public boolean isUnitVector() { + float len = length(); + return 0.99f < len && len < 1.01f; + } + + /** + * length calculates the magnitude of this vector. + * + * @return the length or magnitude of the vector. + */ + public float length() { + return FastMath.sqrt(lengthSquared()); + } + + /** + * lengthSquared calculates the squared value of the magnitude + * of the vector. + * + * @return the magnitude squared of the vector. + */ + public float lengthSquared() { + return x * x + y * y + z * z + w * w; + } + + /** + * distanceSquared calculates the distance squared between this + * vector and vector v. + * + * @param v the second vector to determine the distance squared. + * @return the distance squared between the two vectors. + */ + public float distanceSquared(Vector4f v) { + double dx = x - v.x; + double dy = y - v.y; + double dz = z - v.z; + double dw = w - v.w; + return (float) (dx * dx + dy * dy + dz * dz + dw * dw); + } + + /** + * distance calculates the distance between this vector and + * vector v. + * + * @param v the second vector to determine the distance. + * @return the distance between the two vectors. + */ + public float distance(Vector4f v) { + return FastMath.sqrt(distanceSquared(v)); + } + + /** + * + * mult multiplies this vector by a scalar. The resultant + * vector is returned. + * + * @param scalar the value to multiply this vector by. + * @return the new vector. + */ + public Vector4f mult(float scalar) { + return new Vector4f(x * scalar, y * scalar, z * scalar, w * scalar); + } + + /** + * + * mult multiplies this vector by a scalar. The resultant + * vector is supplied as the second parameter and returned. + * + * @param scalar the scalar to multiply this vector by. + * @param product the product to store the result in. + * @return product + */ + public Vector4f mult(float scalar, Vector4f product) { + if (null == product) { + product = new Vector4f(); + } + + product.x = x * scalar; + product.y = y * scalar; + product.z = z * scalar; + product.w = w * scalar; + return product; + } + + /** + * multLocal multiplies this vector by a scalar internally, and + * returns a handle to this vector for easy chaining of calls. + * + * @param scalar the value to multiply this vector by. + * @return this + */ + public Vector4f multLocal(float scalar) { + x *= scalar; + y *= scalar; + z *= scalar; + w *= scalar; + return this; + } + + /** + * multLocal multiplies a provided vector to this vector + * internally, and returns a handle to this vector for easy chaining of + * calls. If the provided vector is null, null is returned. + * + * @param vec the vector to mult to this vector. + * @return this + */ + public Vector4f multLocal(Vector4f vec) { + if (null == vec) { + logger.warning("Provided vector is null, null returned."); + return null; + } + x *= vec.x; + y *= vec.y; + z *= vec.z; + w *= vec.w; + return this; + } + + /** + * multLocal multiplies this vector by 3 scalars internally, + * and returns a handle to this vector for easy chaining of calls. + * + * @param x + * @param y + * @param z + * @param w + * @return this + */ + public Vector4f multLocal(float x, float y, float z, float w) { + this.x *= x; + this.y *= y; + this.z *= z; + this.w *= w; + return this; + } + + /** + * multLocal multiplies a provided vector to this vector + * internally, and returns a handle to this vector for easy chaining of + * calls. If the provided vector is null, null is returned. + * + * @param vec the vector to mult to this vector. + * @return this + */ + public Vector4f mult(Vector4f vec) { + if (null == vec) { + logger.warning("Provided vector is null, null returned."); + return null; + } + return mult(vec, null); + } + + /** + * multLocal multiplies a provided vector to this vector + * internally, and returns a handle to this vector for easy chaining of + * calls. If the provided vector is null, null is returned. + * + * @param vec the vector to mult to this vector. + * @param store result vector (null to create a new vector) + * @return this + */ + public Vector4f mult(Vector4f vec, Vector4f store) { + if (null == vec) { + logger.warning("Provided vector is null, null returned."); + return null; + } + if (store == null) + store = new Vector4f(); + return store.set(x * vec.x, y * vec.y, z * vec.z, w * vec.w); + } + + /** + * divide divides the values of this vector by a scalar and + * returns the result. The values of this vector remain untouched. + * + * @param scalar the value to divide this vectors attributes by. + * @return the result Vector. + */ + public Vector4f divide(float scalar) { + scalar = 1f / scalar; + return new Vector4f(x * scalar, y * scalar, z * scalar, w * scalar); + } + + /** + * divideLocal divides this vector by a scalar internally, and + * returns a handle to this vector for easy chaining of calls. Dividing by + * zero will result in an exception. + * + * @param scalar the value to divides this vector by. + * @return this + */ + public Vector4f divideLocal(float scalar) { + scalar = 1f / scalar; + x *= scalar; + y *= scalar; + z *= scalar; + w *= scalar; + return this; + } + + /** + * divide divides the values of this vector by a scalar and + * returns the result. The values of this vector remain untouched. + * + * @param scalar the value to divide this vectors attributes by. + * @return the result Vector. + */ + public Vector4f divide(Vector4f scalar) { + return new Vector4f(x / scalar.x, y / scalar.y, z / scalar.z, w / scalar.w); + } + + /** + * divideLocal divides this vector by a scalar internally, and + * returns a handle to this vector for easy chaining of calls. Dividing by + * zero will result in an exception. + * + * @param scalar the value to divides this vector by. + * @return this + */ + public Vector4f divideLocal(Vector4f scalar) { + x /= scalar.x; + y /= scalar.y; + z /= scalar.z; + w /= scalar.w; + return this; + } + + /** + * + * negate returns the negative of this vector. All values are + * negated and set to a new vector. + * + * @return the negated vector. + */ + public Vector4f negate() { + return new Vector4f(-x, -y, -z, -w); + } + + /** + * + * negateLocal negates the internal values of this vector. + * + * @return this. + */ + public Vector4f negateLocal() { + x = -x; + y = -y; + z = -z; + w = -w; + return this; + } + + /** + * + * subtract subtracts the values of a given vector from those + * of this vector creating a new vector object. If the provided vector is + * null, null is returned. + * + * @param vec the vector to subtract from this vector. + * @return the result vector. + */ + public Vector4f subtract(Vector4f vec) { + return new Vector4f(x - vec.x, y - vec.y, z - vec.z, w - vec.w); + } + + /** + * subtractLocal subtracts a provided vector to this vector + * internally, and returns a handle to this vector for easy chaining of + * calls. If the provided vector is null, null is returned. + * + * @param vec the vector to subtract + * @return this + */ + public Vector4f subtractLocal(Vector4f vec) { + if (null == vec) { + logger.warning("Provided vector is null, null returned."); + return null; + } + x -= vec.x; + y -= vec.y; + z -= vec.z; + w -= vec.w; + return this; + } + + /** + * + * subtract + * + * @param vec the vector to subtract from this + * @param result the vector to store the result in + * @return result + */ + public Vector4f subtract(Vector4f vec, Vector4f result) { + if (result == null) { + result = new Vector4f(); + } + result.x = x - vec.x; + result.y = y - vec.y; + result.z = z - vec.z; + result.w = w - vec.w; + return result; + } + + /** + * + * subtract subtracts the provided values from this vector, + * creating a new vector that is then returned. + * + * @param subtractX the x value to subtract. + * @param subtractY the y value to subtract. + * @param subtractZ the z value to subtract. + * @param subtractW the w value to subtract. + * @return the result vector. + */ + public Vector4f subtract(float subtractX, float subtractY, float subtractZ, float subtractW) { + return new Vector4f(x - subtractX, y - subtractY, z - subtractZ, w - subtractW); + } + + /** + * subtractLocal subtracts the provided values from this vector + * internally, and returns a handle to this vector for easy chaining of + * calls. + * + * @param subtractX the x value to subtract. + * @param subtractY the y value to subtract. + * @param subtractZ the z value to subtract. + * @param subtractW the w value to subtract. + * @return this + */ + public Vector4f subtractLocal( + float subtractX, + float subtractY, + float subtractZ, + float subtractW + ) { + x -= subtractX; + y -= subtractY; + z -= subtractZ; + w -= subtractW; + return this; + } + + /** + * normalize returns the unit vector of this vector. + * + * @return unit vector of this vector. + */ + public Vector4f normalize() { +// float length = length(); +// if (length != 0) { +// return divide(length); +// } +// +// return divide(1); + float length = x * x + y * y + z * z + w * w; + if (length != 1f && length != 0f) { + length = 1.0f / FastMath.sqrt(length); + return new Vector4f(x * length, y * length, z * length, w * length); + } + return clone(); + } + + /** + * normalizeLocal makes this vector into a unit vector of + * itself. + * + * @return this. + */ + public Vector4f normalizeLocal() { + // NOTE: this implementation is more optimized + // than the old jme normalize as this method + // is commonly used. + float length = x * x + y * y + z * z + w * w; + if (length != 1f && length != 0f) { + length = 1.0f / FastMath.sqrt(length); + x *= length; + y *= length; + z *= length; + w *= length; + } + return this; + } + + /** + * maxLocal computes the maximum value for each component in + * this and other vector. The result is stored in this vector. + * + * @param other + */ + public Vector4f maxLocal(Vector4f other) { + x = other.x > x ? other.x : x; + y = other.y > y ? other.y : y; + z = other.z > z ? other.z : z; + w = other.w > w ? other.w : w; + return this; + } + + /** + * minLocal computes the minimum value for each component in + * this and other vector. The result is stored in this vector. + * + * @param other + */ + public Vector4f minLocal(Vector4f other) { + x = other.x < x ? other.x : x; + y = other.y < y ? other.y : y; + z = other.z < z ? other.z : z; + w = other.w < w ? other.w : w; + return this; + } + + /** + * zero resets this vector's data to zero internally. + */ + public Vector4f zero() { + x = y = z = w = 0; + return this; + } + + /** + * angleBetween returns (in radians) the angle between two + * vectors. It is assumed that both this vector and the given vector are + * unit vectors (iow, normalized). + * + * @param otherVector a unit vector to find the angle against + * @return the angle in radians. + */ + public float angleBetween(Vector4f otherVector) { + float dotProduct = dot(otherVector); + float angle = FastMath.acos(dotProduct); + return angle; + } + + /** + * Sets this vector to the interpolation by changeAmnt from this to the + * finalVec this=(1-changeAmnt)*this + changeAmnt * finalVec + * + * @param finalVec The final vector to interpolate towards + * @param changeAmnt An amount between 0.0 - 1.0 representing a precentage + * change from this towards finalVec + */ + public Vector4f interpolate(Vector4f finalVec, float changeAmnt) { + this.x = (1 - changeAmnt) * this.x + changeAmnt * finalVec.x; + this.y = (1 - changeAmnt) * this.y + changeAmnt * finalVec.y; + this.z = (1 - changeAmnt) * this.z + changeAmnt * finalVec.z; + this.w = (1 - changeAmnt) * this.w + changeAmnt * finalVec.w; + return this; + } + + /** + * Sets this vector to the interpolation by changeAmnt from beginVec to + * finalVec this=(1-changeAmnt)*beginVec + changeAmnt * finalVec + * + * @param beginVec the beging vector (changeAmnt=0) + * @param finalVec The final vector to interpolate towards + * @param changeAmnt An amount between 0.0 - 1.0 representing a precentage + * change from beginVec towards finalVec + */ + public Vector4f interpolate(Vector4f beginVec, Vector4f finalVec, float changeAmnt) { + this.x = (1 - changeAmnt) * beginVec.x + changeAmnt * finalVec.x; + this.y = (1 - changeAmnt) * beginVec.y + changeAmnt * finalVec.y; + this.z = (1 - changeAmnt) * beginVec.z + changeAmnt * finalVec.z; + this.w = (1 - changeAmnt) * beginVec.w + changeAmnt * finalVec.w; + return this; + } + + /** + * Check a vector... if it is null or its floats are NaN or infinite, return + * false. Else return true. + * + * @param vector the vector to check + * @return true or false as stated above. + */ + public static boolean isValidVector(Vector4f vector) { + if (vector == null) + return false; + if ( + Float.isNaN(vector.x) + || + Float.isNaN(vector.y) + || + Float.isNaN(vector.z) + || + Float.isNaN(vector.w) + ) + return false; + if ( + Float.isInfinite(vector.x) + || + Float.isInfinite(vector.y) + || + Float.isInfinite(vector.z) + || + Float.isInfinite(vector.w) + ) + return false; + return true; + } + + @Override + public Vector4f clone() { + try { + return (Vector4f) super.clone(); + } catch (CloneNotSupportedException e) { + throw new AssertionError(); // can not happen + } + } + + /** + * Saves this Vector3f into the given float[] object. + * + * @param floats The float[] to take this Vector3f. If null, a new float[3] + * is created. + * @return The array, with X, Y, Z float values in that order + */ + public float[] toArray(float[] floats) { + if (floats == null) { + floats = new float[4]; + } + floats[0] = x; + floats[1] = y; + floats[2] = z; + floats[3] = w; + return floats; + } + + /** + * are these two vectors the same? they are is they both have the same x,y, + * and z values. + * + * @param o the object to compare for equality + * @return true if they are equal + */ + @Override + public boolean equals(Object o) { + if (!(o instanceof Vector4f)) { + return false; + } + + if (this == o) { + return true; + } + + Vector4f comp = (Vector4f) o; + if (Float.compare(x, comp.x) != 0) + return false; + if (Float.compare(y, comp.y) != 0) + return false; + if (Float.compare(z, comp.z) != 0) + return false; + if (Float.compare(w, comp.w) != 0) + return false; + return true; + } + + /** + * hashCode returns a unique code for this vector object based + * on it's values. If two vectors are logically equivalent, they will return + * the same hash code value. + * + * @return the hash code value of this vector. + */ + @Override + public int hashCode() { + int hash = 37; + hash += 37 * hash + Float.floatToIntBits(x); + hash += 37 * hash + Float.floatToIntBits(y); + hash += 37 * hash + Float.floatToIntBits(z); + hash += 37 * hash + Float.floatToIntBits(w); + return hash; + } + + /** + * toString returns the string representation of this vector. + * The format is: + * + * org.jme.math.Vector3f [X=XX.XXXX, Y=YY.YYYY, Z=ZZ.ZZZZ, W=WW.WWWW] + * + * @return the string representation of this vector. + */ + @Override + public String toString() { + return "(" + x + ", " + y + ", " + z + ", " + w + ")"; + } + + public float getX() { + return x; + } + + public Vector4f setX(float x) { + this.x = x; + return this; + } + + public float getY() { + return y; + } + + public Vector4f setY(float y) { + this.y = y; + return this; + } + + public float getZ() { + return z; + } + + public Vector4f setZ(float z) { + this.z = z; + return this; + } + + public float getW() { + return w; + } + + public Vector4f setW(float w) { + this.w = w; + return this; + } + + /** + * @param index + * @return x value if index == 0, y value if index == 1 or z value if index + * == 2 + * @throws IllegalArgumentException if index is not one of 0, 1, 2. + */ + public float get(int index) { + switch (index) { + case 0: + return x; + case 1: + return y; + case 2: + return z; + case 3: + return w; + } + throw new IllegalArgumentException("index must be either 0, 1, 2 or 3"); + } + + /** + * @param index which field index in this vector to set. + * @param value to set to one of x, y, z or w. + * @throws IllegalArgumentException if index is not one of 0, 1, 2, 3. + */ + public void set(int index, float value) { + switch (index) { + case 0: + x = value; + return; + case 1: + y = value; + return; + case 2: + z = value; + return; + case 3: + w = value; + return; + } + throw new IllegalArgumentException("index must be either 0, 1, 2 or 3"); + } + +} diff --git a/src/main/java/com/jme3/system/NanoTimer.java b/src/main/java/com/jme3/system/NanoTimer.java new file mode 100644 index 000000000..b99dcf2dd --- /dev/null +++ b/src/main/java/com/jme3/system/NanoTimer.java @@ -0,0 +1,105 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.system; + +/** + * NanoTimer is a System.nanoTime implementation of + * Timer. This is primarily useful for headless applications + * running on a server. + * + * @author Matthew D. Hicks + */ +public class NanoTimer extends Timer { + + private static final long TIMER_RESOLUTION = 1000000000L; + private static final float INVERSE_TIMER_RESOLUTION = 1f / TIMER_RESOLUTION; + + private long startTime; + private long previousTime; + private float tpf; + private float fps; + private long currentTime; + + public NanoTimer() { + startTime = System.nanoTime(); + } + + /** + * Returns the time in seconds. The timer starts at 0.0 seconds. + * + * @return the current time in seconds + */ + + protected long getTimeInternal() { + return System.nanoTime() - startTime; + } + + @Override + public float getTimeInSeconds() { + return getTime() * INVERSE_TIMER_RESOLUTION; + } + + @Override + public long getTime() { + return currentTime; + } + + @Override + public long getResolution() { + return TIMER_RESOLUTION; + } + + @Override + public float getFrameRate() { + return fps; + } + + @Override + public float getTimePerFrame() { + return tpf; + } + + @Override + public void update() { + currentTime = getTimeInternal(); + tpf = (currentTime - previousTime) * (1.0f / TIMER_RESOLUTION); + fps = 1.0f / tpf; + previousTime = getTime(); + } + + @Override + public void reset() { + startTime = System.nanoTime(); + currentTime = getTimeInternal(); + previousTime = getTime(); + } +} diff --git a/src/main/java/com/jme3/system/Timer.java b/src/main/java/com/jme3/system/Timer.java new file mode 100644 index 000000000..ea0a61dfc --- /dev/null +++ b/src/main/java/com/jme3/system/Timer.java @@ -0,0 +1,93 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.system; + +/** + * Timer is the base class for a high resolution timer. It is + * created from getTimer("display system") + * + * @author Mark Powell + * @version $Id: Timer.java,v 1.18 2007/03/09 10:19:34 rherlitz Exp $ + */ +public abstract class Timer { + + /** + * Returns the current time in ticks. A tick is an arbitrary measure of time + * defined by the timer implementation. The number of ticks per second is + * given by getResolution(). The timer starts at 0 ticks. + * + * @return a long value representing the current time + */ + public abstract long getTime(); + + /** + * Returns the time in seconds. The timer starts at 0.0 seconds. + * + * @return the current time in seconds + */ + public float getTimeInSeconds() { + return getTime() / (float) getResolution(); + } + + /** + * Returns the resolution of the timer. + * + * @return the number of timer ticks per second + */ + public abstract long getResolution(); + + /** + * Returns the "calls per second". If this is called every frame, then it + * will return the "frames per second". + * + * @return The "calls per second". + */ + public abstract float getFrameRate(); + + /** + * Returns the time, in seconds, between the last call and the current one. + * + * @return Time between this call and the last one. + */ + public abstract float getTimePerFrame(); + + /** + * update recalculates the frame rate based on the previous + * call to update. It is assumed that update is called each frame. + */ + public abstract void update(); + + /** + * Reset the timer to 0. Clear any tpf history. + */ + public abstract void reset(); +} diff --git a/src/main/java/dev/slimevr/Main.java b/src/main/java/dev/slimevr/Main.java index fad9aada3..aab8678e0 100644 --- a/src/main/java/dev/slimevr/Main.java +++ b/src/main/java/dev/slimevr/Main.java @@ -19,7 +19,6 @@ public class Main { public static VRServer vrServer; - @SuppressWarnings("unused") public static void main(String[] args) { System.setProperty("awt.useSystemAAFontSettings", "on"); System.setProperty("swing.aatext", "true"); diff --git a/src/main/java/io/eiren/math/FloatMath.java b/src/main/java/io/eiren/math/FloatMath.java new file mode 100644 index 000000000..90320fd62 --- /dev/null +++ b/src/main/java/io/eiren/math/FloatMath.java @@ -0,0 +1,672 @@ +package io.eiren.math; + +import com.jme3.math.FastMath; +import com.jme3.math.Vector3f; +import com.jme3.math.Vector4f; + + +public class FloatMath { + + public static final float PI = (float) Math.PI; + public static final float TWO_PI = (float) (Math.PI * 2); + public static final float ANGLE_EPSILON = 0.028f; // in degrees (float + // epsilon for sin/cos) + public static final float ANGLE_EPSILON_RAD = toRad(ANGLE_EPSILON); + + public static final float ZERO_TOLERANCE_F = FastMath.ZERO_TOLERANCE; + public static final double ZERO_TOLERANCE_D = 0.0001d; + + public static final float SQRT_TWO = (float) Math.sqrt(2f); + public static final float INV_SQRT_TWO = 1f / SQRT_TWO; + public static final float SQRT_THREE = (float) Math.sqrt(3f); + public static final float INV_SQRT_THREE = 1f / SQRT_THREE; + public static final float TWO_FPI = PI * 2; + + public static final float SIN_75_DEG = 0.965926f; + public static final float SIN_60_DEG = 0.866025f; + public static final float SIN_45_DEG = 0.707107f; + public static final float SIN_30_DEG = 0.5f; + public static final float SIN_15_DEG = 0.258819f; + + public static final float COS_75_DEG = 0.258819f; + public static final float COS_60_DEG = 0.5f; + public static final float COS_45_DEG = 0.707107f; + public static final float COS_30_DEG = 0.866025f; + public static final float COS_15_DEG = 0.965926f; + + public static final int TEN_BITS = ~(~0 << 10); + public static final int TENTH_BIT = 1 << 10; + public static final int TEN_BITS_MAX = ~(~0 << 9); + public static final int TEN_BITS_MAX_UNSIGNED = ~(~0 << 10); + public static final int TWO_BITS = ~(~0 << 2); + public static final int SECOND_BIT = 1 << 2; + public static final int TWO_BITS_MAX = ~(~0 << 1); + public static final int TWO_BITS_MAX_UNSIGNED = ~(~0 << 2); + + public static float roundIfZero(float x) { + return Math.abs(x) < ZERO_TOLERANCE_F ? 0.0f : x; + } + + public static boolean equalsToZero(float x) { + return Math.abs(x) < ZERO_TOLERANCE_F; + } + + public static boolean lessThanZero(float x) { + return (x < -ZERO_TOLERANCE_F); + } + + public static boolean lessOrEqualsToZero(float x) { + return (x < ZERO_TOLERANCE_F); + } + + public static boolean greaterThanZero(float x) { + return (x > ZERO_TOLERANCE_F); + } + + public static boolean greaterOrEqualsToZero(float x) { + return (x > -ZERO_TOLERANCE_F); + } + + public static boolean equalsToZero(float x, float epsilon) { + return Math.abs(x) < epsilon; + } + + public static boolean equalsWithEpsilon(float x, float y) { + return Math.abs(x - y) < ZERO_TOLERANCE_F; + } + + public static boolean equalsWithEpsilon(float x, float y, float epsilon) { + return Math.abs(x - y) < epsilon; + } + + public static boolean lessWithEpsilon(float x, float y) { + return (x < y - ZERO_TOLERANCE_F); + } + + public static boolean lessOrEqualsWithEpsilon(float x, float y) { + return (x < y + ZERO_TOLERANCE_F); + } + + public static boolean lessWithEpsilon(float x, float y, float epsilon) { + return (x < y - epsilon); + } + + public static boolean lessOrEqualsWithEpsilon(float x, float y, float epsilon) { + return (x < y + epsilon); + } + + public static boolean greaterWithEpsilon(float x, float y) { + return (x > y + ZERO_TOLERANCE_F); + } + + public static boolean greaterOrEqualsWithEpsilon(float x, float y) { + return (x > y - ZERO_TOLERANCE_F); + } + + public static boolean greaterWithEpsilon(float x, float y, float epsilon) { + return (x > y + epsilon); + } + + public static boolean greaterOrEqualsWithEpsilon(float x, float y, float epsilon) { + return (x > y - epsilon); + } + + public static double roundIfZero(double x) { + return Math.abs(x) < ZERO_TOLERANCE_D ? 0.0d : x; + } + + public static boolean equalsToZero(double x) { + return Math.abs(x) < ZERO_TOLERANCE_D; + } + + public static boolean equalsWithEpsilon(double x, double y) { + return Math.abs(x - y) < ZERO_TOLERANCE_D; + } + + public static boolean lessWithEpsilon(double x, double y) { + return (x < y - ZERO_TOLERANCE_D); + } + + public static boolean lessOrEqualsWithEpsilon(double x, double y) { + return (x < y + ZERO_TOLERANCE_D); + } + + public static boolean greaterWithEpsilon(double x, double y) { + return (x > y + ZERO_TOLERANCE_D); + } + + public static boolean greaterOrEqualsWithEpsilon(double x, double y) { + return (x > y - ZERO_TOLERANCE_D); + } + + public static float toDegrees(float angrad) { + return angrad * 180.0f / PI; + } + + public static float toRad(float deg) { + return deg / 180.0f * PI; + } + + public static boolean radEqual(float angle1, float angle2) { + float diff = clampRad(angle1 - angle2); + return Math.abs(diff) < ANGLE_EPSILON_RAD; + } + + public static boolean degreesEqual(float angle1, float angle2) { + float diff = clampDegrees(angle1 - angle2); + return Math.abs(diff) < ANGLE_EPSILON; + } + + /** + * @deprecated use {@link #normalizeRad(float)} + */ + @Deprecated + public static float clampRad(float angle) { + return normalizeRad(angle); + } + + public static float normalizeRad(float angle) { + return FastMath.normalize(angle, -FastMath.PI, FastMath.PI); + } + + /** + * @deprecated use {@link #normalizeDegrees(float)} + */ + @Deprecated + public static float clampDegrees(float angle) { + return normalizeDegrees(angle); + } + + public static float normalizeDegrees(float angle) { + return FastMath.normalize(angle, -180f, 180f); + } + + public static float animateEase(float t) { + // Special case of Bezier interpolation (p0 = p1 = 0, p2 = p3 = 1) + return (3.0f - 2.0f * t) * t * t; + } + + public static float animateEaseIn(float t) { + return t * t; + } + + /** + * Lineary remaps value from the source interval to the target interval. + * details + */ + public static float mapValue( + float value, + float sourceStart, + float sourceEnd, + float targetStart, + float targetEnd + ) { + return targetStart + + (value - sourceStart) * (targetEnd - targetStart) / (sourceEnd - sourceStart); + } + + /** + * Clamps the given value and remaps to the target interval. + *

+ * Note the source interval values should be sorted. + */ + public static float mapValueWithClampBefore( + float value, + float sourceBottom, + float sourceTop, + float targetBottom, + float targetTop + ) { + return mapValue( + clamp(value, sourceBottom, sourceTop), + sourceBottom, + sourceTop, + targetBottom, + targetTop + ); + } + + /** + * Remaps the given value to the target interval and clamps. + *

+ * Note the target interval values should be sorted. + */ + public static float mapValueWithClampAfter( + float value, + float sourceBottom, + float sourceTop, + float targetBottom, + float targetTop + ) { + return clamp( + mapValue(value, sourceBottom, sourceTop, targetBottom, targetTop), + targetBottom, + targetTop + ); + } + + public static float smoothstep(float edge0, float edge1, float x) { + // Scale, bias and saturate x to 0..1 range + x = FastMath.clamp((x - edge0) / (edge1 - edge0), 0.0f, 1.0f); + // Evaluate polynomial + return x * x * (3f - 2f * x); + } + + public static float smootherstep(float edge0, float edge1, float x) { + // Scale, and clamp x to 0..1 range + x = FastMath.clamp((x - edge0) / (edge1 - edge0), 0.0f, 1.0f); + // Evaluate polynomial + return x * x * x * (x * (x * 6f - 15f) + 10f); + } + + /** + * Applies linear contrast (with clamping). + * + * @param t - input value in range (0..1) + * @param k - contrast factor in range (-1..1): + *

    + *
  • 1.0 - maximal contrast
  • + *
  • 0.0 - bypass (returns input value)
  • + *
  • -1.0 - minimal contrast (returns 0.5f for any input)
  • + *
+ * @return contrasted value in range (0..1) + */ + public static float contrastLinear(float t, float k) { + float x = 2f * t - 1f; // -1..1 + float gamma = (1f + k) / (1f - k); + float f = FastMath.clamp(gamma * x, -1f, 1f); // -1..1 + return 0.5f * (f + 1f); // 0..1 + } + + /** + * Applies non-linear contrast by power function. + * + * @param t - input value in range (0..1) + * @param k - contrast factor in range (-1..1) exclusive: + *
    + *
  • 0.999 - maximal contrast
  • + *
  • 0.0 - bypass (returns input value)
  • + *
  • -0.999 - minimal contrast
  • + *
+ * @return contrasted value in range (0..1) + */ + public static float contrastPower(float t, float k) { + float x = 2f * t - 1f; // -1..1 + float gamma = (1f - k) / (1f + k); + float f = FastMath.sign(x) * FastMath.pow(FastMath.abs(x), gamma); // -1..1 + return 0.5f * (f + 1f); // 0..1 + } + + /** + * Applies non-linear contrast by square splines. + * + * @param t - input value in range (0..1) + * @param k - contrast factor in range (-1..1): + *
    + *
  • 1.0 - maximal contrast
  • + *
  • 0.0 - bypass (returns input value)
  • + *
  • -1.0 - minimal contrast
  • + *
+ * @return contrasted value in range (0..1) + */ + public static float contrastQuadricSpline(float t, float k) { + float x = 2f * t - 1f; // -1..1 + float f = x * (1f + k * (1f - FastMath.abs(x))); // -1..1 + return 0.5f * (f + 1f); // 0..1 + } + + /** + * Applies non-linear contrast by square splines inverted function. + * + * @param t - input value in range (0..1) + * @param k - contrast factor in range (-2..2): + *
    + *
  • 2.0 - maximal contrast
  • + *
  • 0.0 - bypass (returns input value)
  • + *
  • -2.0 - minimal contrast
  • + *
+ * @return contrasted value in range (0..1) + */ + public static float contrastInvertQuadricSpline(float t, float k) { + float x = 2f * t - 1f; // -1..1 + float g; + if (k > 0) { + g = FastMath.sign(x) * FastMath.sqrt(FastMath.abs(x)) - 2f * x; + } else { + g = FastMath.sign(x) * (FastMath.sqrt(1f - FastMath.abs(x)) - 1f); + } + float f = (1f + k) * x + k * g; // -1..1 + return 0.5f * (f + 1f); // 0..1 + } + + /** + * Applies non-linear contrast by cubic splines. + * + * @param t - input value in range (0..1) + * @param k - contrast factor in range (-1..1): + *
    + *
  • 1.0 - maximal contrast
  • + *
  • 0.0 - bypass (returns input value)
  • + *
  • -1.0 - minimal contrast
  • + *
+ * @return contrasted value in range (0..1) + */ + public static float contrastCubicSpline(float t, float k) { + float x = 2f * t - 1f; // -1..1 + float f = x * (1f + FastMath.abs(k) * (x * x - 1f)); + if (k < 0) + f -= x * 3f * k * (1f - FastMath.abs(x)); + return 0.5f * (f + 1f); // 0..1 + } + + public static float fraction(float f) { + return f - (int) f; + } + + public static double fraction(double d) { + return d - (long) d; + } + + /** + * @deprecated Do not copy {@link Math} methods. + */ + @Deprecated + public static float min(float a, float b) { + return a > b ? b : a; + } + + public static float min(float a, float b, float c) { + return Math.min(Math.min(a, b), c); + } + + public static float min(float a, float b, float c, float d) { + return Math.min(Math.min(a, b), Math.min(c, d)); + } + + /** + * @deprecated Do not copy {@link Math} methods. + */ + @Deprecated + public static float max(float a, float b) { + return a > b ? a : b; + } + + public static float max(float a, float b, float c) { + return Math.max(Math.max(a, b), c); + } + + public static float max(float a, float b, float c, float d) { + return Math.max(Math.max(a, b), Math.max(c, d)); + } + + public static float cos(float value) { + return (float) Math.cos(value); + } + + public static float sin(float value) { + return (float) Math.sin(value); + } + + public static float ceil(float value) { + return (float) Math.ceil(value); + } + + public static float floor(float value) { + return (float) Math.floor(value); + } + + public static float pow(float value, float power) { + return (float) Math.pow(value, power); + } + + /** + * @deprecated Do not copy {@link Math} methods. + */ + @Deprecated + public static float abs(float value) { + return (float) Math.abs(value); + } + + /** + * @deprecated Do not copy {@link Math} methods. + */ + @Deprecated + public static float round(float value) { + return (float) Math.round(value); + } + + public static float sqrt(float value) { + return (float) Math.sqrt(value); + } + + public static float distance(float x0, float y0, float z0, float x1, float y1, float z1) { + return distance(x1 - x0, y1 - y0, z1 - z0); + } + + public static float distance(float x, float y, float z) { + return sqrt(sqrDistance(x, y, z)); + } + + public static float sqrDistance(float x, float y, float z) { + return x * x + y * y + z * z; + } + + public static float distance(float x, float y) { + return sqrt(sqrDistance(x, y)); + } + + public static float sqrDistance(float x, float y) { + return x * x + y * y; + } + + public static float sqrDistance(Vector3f v, float x1, float y1, float z1) { + return sqrDistance(x1 - v.x, y1 - v.y, z1 - v.z); + } + + public static float sqrDistance(float x0, float y0, float z0, float x1, float y1, float z1) { + return sqrDistance(x1 - x0, y1 - y0, z1 - z0); + } + + public static float hypot(float x, float y) { + return FastMath.sqrt(x * x + y * y); + } + + public static float hypot(float x, float y, float z) { + return FastMath.sqrt(x * x + y * y + z * z); + } + + /** + * The same as FastMath.clamp + */ + public static float clamp(float value, float min, float max) { + if (value <= min) + return min; + if (value >= max) + return max; + return value; + } + + public static Vector3f int2101010RevToFloats(int packedValue, Vector3f store) { + if (store == null) + store = new Vector3f(); + store.x = packedValue & TEN_BITS_MAX; + if ((packedValue & TENTH_BIT) != 0) + store.x *= -1; + store.y = (packedValue >>> 10) & TEN_BITS_MAX; + if ((packedValue & (TENTH_BIT << 10)) != 0) + store.y *= -1; + store.z = (packedValue >>> 20) & TEN_BITS_MAX; + if ((packedValue & (TENTH_BIT << 20)) != 0) + store.z *= -1; + return store; + } + + public static int floatToInt210101Rev(Vector3f values) { + int store = 0; + store |= ((int) values.x) & TEN_BITS_MAX; + if (values.x < 0) + store |= TENTH_BIT; + store |= (((int) values.y) & TEN_BITS_MAX) << 10; + if (values.y < 0) + store |= TENTH_BIT << 10; + store |= (((int) values.z) & TEN_BITS_MAX) << 20; + if (values.z < 0) + store |= TENTH_BIT << 20; + return store; + } + + public static int floatToInt210101RevNormalized(Vector3f values) { + int store = 0; + store |= ((int) (values.x * TEN_BITS)) & TEN_BITS_MAX; + if (values.x < 0) + store |= TENTH_BIT; + store |= (((int) (values.y * TEN_BITS)) & TEN_BITS_MAX) << 10; + if (values.y < 0) + store |= TENTH_BIT << 10; + store |= (((int) (values.z * TEN_BITS)) & TEN_BITS_MAX) << 20; + if (values.z < 0) + store |= TENTH_BIT << 20; + return store; + } + + public static int floatToUnsignedInt210101Rev(Vector3f values) { + int store = 0; + store |= ((int) values.x) & TEN_BITS; + store |= (((int) values.y) & TEN_BITS) << 10; + store |= (((int) values.z) & TEN_BITS) << 20; + return store; + } + + public static int floatToUnsignedInt210101RevNormalized(Vector3f values) { + int store = 0; + store |= ((int) (values.x * TEN_BITS)) & TEN_BITS; + store |= (((int) (values.y * TEN_BITS)) & TEN_BITS) << 10; + store |= (((int) (values.z * TEN_BITS)) & TEN_BITS) << 20; + return store; + } + + public static int floatToInt210101Rev(float x, float y, float z) { + int store = 0; + store |= ((int) x) & TEN_BITS_MAX; + if (x < 0) + store |= TENTH_BIT; + store |= (((int) y) & TEN_BITS_MAX) << 10; + if (y < 0) + store |= TENTH_BIT << 10; + store |= (((int) z) & TEN_BITS_MAX) << 20; + if (z < 0) + store |= TENTH_BIT << 20; + return store; + } + + public static int floatToUnsignedInt210101Rev(float x, float y, float z) { + int store = 0; + store |= ((int) x) & TEN_BITS; + store |= (((int) y) & TEN_BITS) << 10; + store |= (((int) z) & TEN_BITS) << 20; + return store; + } + + public static Vector4f int2101010RevToFloats(int packedValue, Vector4f store) { + if (store == null) + store = new Vector4f(); + store.x = packedValue & TEN_BITS_MAX; + if ((packedValue & TENTH_BIT) != 0) + store.x *= -1; + store.y = (packedValue >>> 10) & TEN_BITS_MAX; + if ((packedValue & (TENTH_BIT << 10)) != 0) + store.y *= -1; + store.z = (packedValue >>> 20) & TEN_BITS_MAX; + if ((packedValue & (TENTH_BIT << 20)) != 0) + store.z *= -1; + store.w = (packedValue >>> 30) & TWO_BITS_MAX; + if ((packedValue & (SECOND_BIT << 30)) != 0) + store.w *= -1; + return store; + } + + public static int floatToInt210101Rev(Vector4f values) { + int store = 0; + store |= ((int) values.x) & TEN_BITS_MAX; + if (values.x < 0) + store |= TENTH_BIT; + store |= (((int) values.y) & TEN_BITS_MAX) << 10; + if (values.y < 0) + store |= TENTH_BIT << 10; + store |= (((int) values.z) & TEN_BITS_MAX) << 20; + if (values.z < 0) + store |= TENTH_BIT << 20; + store |= (((int) values.z) & TWO_BITS_MAX) << 30; + if (values.w < 0) + store |= SECOND_BIT << 30; + return store; + } + + public static int floatToUnsignedInt210101Rev(Vector4f values) { + int store = 0; + store |= ((int) values.x) & TEN_BITS; + store |= (((int) values.y) & TEN_BITS) << 10; + store |= (((int) values.z) & TEN_BITS) << 20; + store |= (((int) values.z) & TWO_BITS) << 30; + return store; + } + + public static Vector3f unsignedInt2101010RevToFloats(int packedValue, Vector3f store) { + if (store == null) + store = new Vector3f(); + store.x = packedValue & TEN_BITS; + store.y = (packedValue >>> 10) & TEN_BITS; + store.z = (packedValue >>> 20) & TEN_BITS; + return store; + } + + public static Vector4f unsignedInt2101010RevToFloats(int packedValue, Vector4f store) { + if (store == null) + store = new Vector4f(); + store.x = packedValue & TEN_BITS; + store.y = (packedValue >>> 10) & TEN_BITS; + store.z = (packedValue >>> 20) & TEN_BITS; + store.w = (packedValue >>> 30) & TWO_BITS; + return store; + } + + public static Vector3f int2101010RevNormalizedToFloats(int packedValue, Vector3f store) { + store = int2101010RevToFloats(packedValue, store); + store.x /= TEN_BITS_MAX; + store.y /= TEN_BITS_MAX; + store.z /= TEN_BITS_MAX; + return store; + } + + public static Vector4f int2101010RevNormalizedToFloats(int packedValue, Vector4f store) { + store = int2101010RevToFloats(packedValue, store); + store.x /= TEN_BITS_MAX; + store.y /= TEN_BITS_MAX; + store.z /= TEN_BITS_MAX; + store.w /= TWO_BITS_MAX; + return store; + } + + public static Vector3f unsignedInt2101010RevNormalizedToFloats( + int packedValue, + Vector3f store + ) { + store = unsignedInt2101010RevToFloats(packedValue, store); + store.x /= TEN_BITS; + store.y /= TEN_BITS; + store.z /= TEN_BITS; + return store; + } + + public static Vector4f unsignedInt2101010RevNormalizedToFloats( + int packedValue, + Vector4f store + ) { + store = unsignedInt2101010RevToFloats(packedValue, store); + store.x /= TEN_BITS; + store.y /= TEN_BITS; + store.z /= TEN_BITS; + store.w /= TWO_BITS; + return store; + } +} diff --git a/src/main/java/io/eiren/math/Vector3d.java b/src/main/java/io/eiren/math/Vector3d.java new file mode 100644 index 000000000..14a180692 --- /dev/null +++ b/src/main/java/io/eiren/math/Vector3d.java @@ -0,0 +1,257 @@ +package io.eiren.math; + +import com.jme3.math.Vector3f; + + +public class Vector3d implements Cloneable { + + public double x; + public double y; + public double z; + + public Vector3d() { + } + + public Vector3d(double x, double y, double z) { + this.x = x; + this.y = y; + this.z = z; + } + + public Vector3d(double x1, double y1, double z1, double x2, double y2, double z2) { + this.x = x2 - x1; + this.y = y2 - y1; + this.z = z2 - z1; + } + + public Vector3d(Vector3f src) { + this(src.x, src.y, src.z); + } + + public Vector3d set(double x, double y, double z) { + this.x = x; + this.y = y; + this.z = z; + return this; + } + + public Vector3d set(Vector3d v) { + this.x = v.x; + this.y = v.y; + this.z = v.z; + return this; + } + + public Vector3d add(double addX, double addY, double addZ) { + return new Vector3d(this.x + addX, this.y + addY, this.z + addZ); + } + + public Vector3d addLocal(Vector3d vec) { + return addLocal(vec.x, vec.y, vec.z); + } + + public Vector3d addLocal(double addX, double addY, double addZ) { + x += addX; + y += addY; + z += addZ; + return this; + } + + public Vector3d substract(double subX, double subY, double subZ) { + return new Vector3d(this.x - subX, this.y - subY, this.z - subZ); + } + + public Vector3d substractLocal(Vector3d vec) { + if (null == vec) { + return null; + } + x -= vec.x; + y -= vec.y; + z -= vec.z; + return this; + } + + public Vector3d substractLocal(double subX, double subY, double subZ) { + x -= subX; + y -= subY; + z -= subZ; + return this; + } + + public Vector3d negate() { + return new Vector3d(-x, -y, -z); + } + + public Vector3d negateLocal() { + x = -x; + y = -y; + z = -z; + return this; + } + + public Vector3d mult(double scalar) { + return new Vector3d(x * scalar, y * scalar, z * scalar); + } + + public Vector3d multLocal(double scalar) { + x *= scalar; + y *= scalar; + z *= scalar; + return this; + } + + public Vector3d divide(double scalar) { + return new Vector3d(x / scalar, y / scalar, z / scalar); + } + + public Vector3d divideLocal(double scalar) { + x /= scalar; + y /= scalar; + z /= scalar; + return this; + } + + public double dot(Vector3d v) { + return x * v.x + y * v.y + z * v.z; + } + + public double dot(double vx, double vy, double vz) { + return x * vx + y * vy + z * vz; + } + + public Vector3d cross(Vector3d other, Vector3d result) { + if (result == null) + result = new Vector3d(); + double resX = ((y * other.z) - (z * other.y)); + double resY = ((z * other.x) - (x * other.z)); + double resZ = ((x * other.y) - (y * other.x)); + result.set(resX, resY, resZ); + return result; + } + + @Override + public Vector3d clone() { + return new Vector3d(this.x, this.y, this.z); + } + + public Vector3d normalize() { + double length = x * x + y * y + z * z; + if (length != 1.0 && length != 0.0) { + double invLength = 1.0 / Math.sqrt(length); + return mult(invLength); + } + return clone(); + } + + public Vector3d normalizeLocal() { + double length = x * x + y * y + z * z; + if (length != 1.0 && length != 0.0) { + length = Math.sqrt(length); + double invLength = 1.0 / length; + x *= invLength; + z *= invLength; + y *= invLength; + } + return this; + } + + public Vector3f toVector3f() { + return new Vector3f((float) x, (float) y, (float) z); + } + + public double length() { + return Math.sqrt(x * x + y * y + z * z); + } + + public double lengthSquared() { + return x * x + y * y + z * z; + } + + @Override + public String toString() { + return new StringBuilder("Vector3D{") + .append(x) + .append(',') + .append(y) + .append(',') + .append(z) + .append('}') + .toString(); + } + + public void rotateAroundX(float f) { + double f1 = Math.cos(f); + double f2 = Math.sin(f); + double d = x; + double d1 = y * f1 + z * f2; + double d2 = z * f1 - y * f2; + x = (float) d; + y = (float) d1; + z = (float) d2; + } + + public void rotateAroundY(float f) { + double f1 = Math.cos(f); + double f2 = Math.sin(f); + double d = x * f1 + z * f2; + double d1 = y; + double d2 = z * f1 - x * f2; + x = (float) d; + y = (float) d1; + z = (float) d2; + } + + public double distanceTo(Vector3d vec3d) { + return Math.sqrt(squaredDistance(vec3d)); + } + + public double squaredDistance(Vector3d point) { + return squaredDistance(point.x, point.y, point.z); + } + + public double squaredDistance(double toX, double toY, double toZ) { + return (this.x - toX) * (this.x - toX) + + (this.y - toY) * (this.y - toY) + + (this.z - toZ) * (this.z - toZ); + } + + public Vector3d add(Vector3d dir) { + return add(dir.x, dir.y, dir.z); + } + + public Vector3d substract(Vector3d dir) { + return substract(dir.x, dir.y, dir.z); + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + long temp; + temp = Double.doubleToLongBits(x); + result = prime * result + (int) (temp ^ (temp >>> 32)); + temp = Double.doubleToLongBits(y); + result = prime * result + (int) (temp ^ (temp >>> 32)); + temp = Double.doubleToLongBits(z); + result = prime * result + (int) (temp ^ (temp >>> 32)); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + Vector3d other = (Vector3d) obj; + if (Double.doubleToLongBits(x) != Double.doubleToLongBits(other.x)) + return false; + if (Double.doubleToLongBits(y) != Double.doubleToLongBits(other.y)) + return false; + if (Double.doubleToLongBits(z) != Double.doubleToLongBits(other.z)) + return false; + return true; + } +} diff --git a/src/main/java/io/eiren/util/BufferedTimer.java b/src/main/java/io/eiren/util/BufferedTimer.java new file mode 100644 index 000000000..4bba98941 --- /dev/null +++ b/src/main/java/io/eiren/util/BufferedTimer.java @@ -0,0 +1,130 @@ +package io.eiren.util; + +import java.beans.ConstructorProperties; + +import com.jme3.system.NanoTimer; + + +/** + * This timer accumulate measured TPF and returns average/min/max FPS value + */ +public class BufferedTimer extends NanoTimer { + + private final float measureInterval; + private float averageTpf; + private float averageFps; + private float averageFrameRenderTime; + private float sumFrameRenderTime; + private float sumTpf; + private float minFpsCurrent; + private float maxFpsCurrent; + private float maxFps; + private float minFps; + private int count; + private boolean measured = false; + + /** + * Measure average tpf over the provided inverval in seconds + * + * @param measureInterval interval to measure averages over + */ + public BufferedTimer(float measureInterval) { + averageFps = 0; + sumTpf = 0; + count = 0; + this.measureInterval = measureInterval; + } + + public float getAverageFPS() { + return averageFps; + } + + public float getMinFPS() { + return minFps; + } + + public float getMaxFPS() { + return maxFps; + } + + public void addRenderTime(float renderTime) { + sumFrameRenderTime += renderTime; + } + + public float getAverageFrameRenderTime() { + return averageFrameRenderTime; + } + + public boolean isMeasured() { + if (measured) { + measured = false; + return true; + } + return false; + } + + public TimerSample getCurrentData() { + return new TimerSample(getFrameRate(), minFps, maxFps, averageFps); + } + + @Override + public void update() { + super.update(); + // Accumulate instant rate + sumTpf += getTimePerFrame(); + float fps = getFrameRate(); + if (fps < minFpsCurrent) + minFpsCurrent = fps; + if (fps > maxFpsCurrent) + maxFpsCurrent = fps; + ++count; + // Calculate results once per measure interval + if (!measured || sumTpf > measureInterval) { + // Average results + averageTpf = sumTpf / count; + averageFps = 1.0f / averageTpf; + averageFrameRenderTime = sumFrameRenderTime / count; + minFps = minFpsCurrent; + maxFps = maxFpsCurrent; + // Reset counter + sumTpf = 0; + sumFrameRenderTime = 0; + minFpsCurrent = Float.MAX_VALUE; + maxFpsCurrent = 0; + count = 0; + measured = true; + } + } + + public static class TimerSample { + + public float fps; + public float minFps; + public float maxFps; + public float averageFps; + + @ConstructorProperties({ "fps", "minFps", "maxFps", "averageFps" }) + public TimerSample(float fps, float minFps, float maxFps, float averageFps) { + this.fps = fps; + this.minFps = minFps; + this.maxFps = maxFps; + this.averageFps = averageFps; + } + + public float getFps() { + return fps; + } + + public float getMinFps() { + return minFps; + } + + public float getMaxFps() { + return maxFps; + } + + public float getAverageFps() { + return averageFps; + } + } +} diff --git a/src/main/java/io/eiren/util/MacOSX.java b/src/main/java/io/eiren/util/MacOSX.java new file mode 100644 index 000000000..e9f027123 --- /dev/null +++ b/src/main/java/io/eiren/util/MacOSX.java @@ -0,0 +1,41 @@ +package io.eiren.util; + +import java.awt.Image; +import java.awt.Toolkit; +import java.lang.reflect.Method; +import java.util.List; + + +public class MacOSX { + + public static void setIcons(List icons) { + try { + Class applicationClass = Class.forName("com.apple.eawt.Application"); + Method m = applicationClass.getDeclaredMethod("getApplication"); + Object application = m.invoke(null); + m = application.getClass().getDeclaredMethod("setDockIconImage", Image.class); + m.invoke(application, icons.get(icons.size() - 1)); + } catch (Exception e) {} + } + + public static void setTitle(String title) { + try { + Class applicationClass = Class.forName("com.apple.eawt.Application"); + Method m = applicationClass.getDeclaredMethod("getApplication"); + Object application = m.invoke(null); + m = application.getClass().getDeclaredMethod("setDockIconImage", String.class); + m.invoke(application, title); + } catch (Exception e) {} + } + + public static boolean hasRetinaDisplay() { + Object obj = Toolkit.getDefaultToolkit().getDesktopProperty("apple.awt.contentScaleFactor"); + if (obj instanceof Float) { + Float f = (Float) obj; + int scale = f.intValue(); + return (scale == 2); // 1 indicates a regular mac display. + } + return false; + } + +} diff --git a/src/main/java/io/eiren/util/OperatingSystem.java b/src/main/java/io/eiren/util/OperatingSystem.java new file mode 100644 index 000000000..7182e7df2 --- /dev/null +++ b/src/main/java/io/eiren/util/OperatingSystem.java @@ -0,0 +1,47 @@ +package io.eiren.util; + +import java.io.File; + + +public enum OperatingSystem { + + //@formatter:off + LINUX("linux", new String[]{"linux", "unix"}), + WINDOWS("windows", new String[]{"win"}), + OSX("osx", new String[]{"mac"}), + UNKNOWN("unknown", new String[0]); + //@fomatter: on + + private final String[] aliases; + public final String name; + private static OperatingSystem currentPlatform; + + private OperatingSystem(String name, String[] aliases) { + this.aliases = aliases; + this.name = name; + } + + public static String getJavaExecutable(boolean forceConsole) { + String separator = System.getProperty("file.separator"); + String path = System.getProperty("java.home") + separator + "bin" + separator; + if(getCurrentPlatform() == WINDOWS) { + if(!forceConsole && new File(path + "javaw.exe").isFile()) + return path + "javaw.exe"; + return path + "java.exe"; + } + return path + "java"; + } + + public static OperatingSystem getCurrentPlatform() { + if(currentPlatform != null) + return currentPlatform; + String osName = System.getProperty("os.name").toLowerCase(); + for(OperatingSystem os : values()) { + for(String alias : os.aliases) { + if(osName.contains(alias)) + return currentPlatform = os; + } + } + return UNKNOWN; + } +} \ No newline at end of file diff --git a/src/main/java/io/eiren/util/StringUtils.java b/src/main/java/io/eiren/util/StringUtils.java new file mode 100644 index 000000000..7ee936964 --- /dev/null +++ b/src/main/java/io/eiren/util/StringUtils.java @@ -0,0 +1,35 @@ +package io.eiren.util; + +import java.text.DecimalFormatSymbols; +import java.util.Locale; + + +public class StringUtils { + + private static char DECIMAL_SEP; + + public static char getDecimalSeparator() { + if (DECIMAL_SEP == '\u0000') { + final Locale l = Locale.getDefault(Locale.Category.FORMAT); + // Formatter.java always use "." in the Locale.US + DECIMAL_SEP = (l == null || l.equals(Locale.US) + ? '.' + : DecimalFormatSymbols.getInstance(l).getDecimalSeparator()); + } + return DECIMAL_SEP; + } + + public static String prettyNumber(float f) { + return prettyNumber(f, 4); + } + + public static String prettyNumber(float f, int numDigits) { + String str = String.format("%." + numDigits + "f", f); + if (numDigits != 0) + str = org.apache.commons.lang3.StringUtils.stripEnd(str, "0"); + char lastChar = str.charAt(str.length() - 1); + if (lastChar == getDecimalSeparator()) + str = str.substring(0, str.length() - 1); + return str; + } +} diff --git a/src/main/java/io/eiren/util/Util.java b/src/main/java/io/eiren/util/Util.java new file mode 100644 index 000000000..85fe39b57 --- /dev/null +++ b/src/main/java/io/eiren/util/Util.java @@ -0,0 +1,176 @@ +package io.eiren.util; + +import java.io.Closeable; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Map; +import java.util.Set; +import java.util.Map.Entry; + + +public class Util { + + public static void close(Object r) { + try { + if (r != null) { + if (r instanceof Closeable) + ((Closeable) r).close(); + else if (r instanceof AutoCloseable) + ((AutoCloseable) r).close(); + } + } catch (Exception e) {} + } + + public static void close(Object r1, Object r2) { + close(r1); + close(r2); + } + + public static void close(Object... r) { + for (int i = 0; i < r.length; ++i) + try { + if (r[i] != null) { + if (r[i] instanceof Closeable) + ((Closeable) r[i]).close(); + else if (r[i] instanceof AutoCloseable) + ((AutoCloseable) r[i]).close(); + } + } catch (Exception e) {} + } + + public static void close(AutoCloseable... r) { + for (int i = 0; i < r.length; ++i) + try { + if (r[i] != null) + r[i].close(); + } catch (Exception e) {} + } + + public static void close(Closeable... r) { + for (int i = 0; i < r.length; ++i) + try { + if (r[i] != null) + r[i].close(); + } catch (Exception e) {} + } + + /** + *

+ * Performs a deep toString of provided object. It shows content of arrays, + * collections and maps (trove not supported yet). + *

+ *

+ * Highly ineffective, use only for debug. + *

+ * + * @param object + * @return + */ + public static String toString(Object object) { + if (object == null) + return "null"; + StringBuilder buf = new StringBuilder(); + elementToString(object, buf, new HashSet()); + return buf.toString(); + } + + private static void deepToString(Map m, StringBuilder buf, Set dejaVu) { + if (m == null) { + buf.append("null"); + return; + } + if (m.size() == 0) { + buf.append("{}"); + return; + } + dejaVu.add(m); + buf.append('{'); + Iterator> iterator = m.entrySet().iterator(); + boolean has = false; + while (iterator.hasNext()) { + if (has) + buf.append(','); + Entry e = iterator.next(); + elementToString(e.getKey(), buf, dejaVu); + buf.append(':'); + elementToString(e.getValue(), buf, dejaVu); + has = true; + } + buf.append('}'); + dejaVu.remove(m); + } + + private static void deepToString( + Collection list, + StringBuilder buf, + Set dejaVu + ) { + Object[] array = list.toArray(); + deepToString(array, buf, dejaVu); + } + + private static void deepToString(Object[] a, StringBuilder buf, Set dejaVu) { + if (a == null) { + buf.append("null"); + return; + } + if (a.length == 0) { + buf.append("[]"); + return; + } + dejaVu.add(a); + buf.append('['); + for (int i = 0; i < a.length; i++) { + if (i != 0) + buf.append(','); + Object element = a[i]; + elementToString(element, buf, dejaVu); + } + buf.append(']'); + dejaVu.remove(a); + } + + @SuppressWarnings("unchecked") + private static void elementToString(Object element, StringBuilder buf, Set dejaVu) { + if (element == null) { + buf.append("null"); + } else { + Class eClass = element.getClass(); + if (eClass.isArray()) { + if (eClass == byte[].class) + buf.append(Arrays.toString((byte[]) element)); + else if (eClass == short[].class) + buf.append(Arrays.toString((short[]) element)); + else if (eClass == int[].class) + buf.append(Arrays.toString((int[]) element)); + else if (eClass == long[].class) + buf.append(Arrays.toString((long[]) element)); + else if (eClass == char[].class) + buf.append(Arrays.toString((char[]) element)); + else if (eClass == float[].class) + buf.append(Arrays.toString((float[]) element)); + else if (eClass == double[].class) + buf.append(Arrays.toString((double[]) element)); + else if (eClass == boolean[].class) + buf.append(Arrays.toString((boolean[]) element)); + else { // element is an array of object references + if (dejaVu.contains(element)) + buf.append("[...]"); + else + deepToString((Object[]) element, buf, dejaVu); + } + } else { // element is non-null and not an array + if (element instanceof Collection) + deepToString((Collection) element, buf, dejaVu); + else if (element instanceof Map) + deepToString((Map) element, buf, dejaVu); + else if (element instanceof CharSequence) + buf.append('"').append(element.toString()).append('"'); + else + buf.append(element.toString()); + } + } + } +} diff --git a/src/main/java/io/eiren/util/ann/AWTThread.java b/src/main/java/io/eiren/util/ann/AWTThread.java new file mode 100644 index 000000000..a4ca9828c --- /dev/null +++ b/src/main/java/io/eiren/util/ann/AWTThread.java @@ -0,0 +1,10 @@ +package io.eiren.util.ann; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + + +@Retention(value = RetentionPolicy.SOURCE) +public @interface AWTThread { + +} diff --git a/src/main/java/io/eiren/util/ann/DebugSwitch.java b/src/main/java/io/eiren/util/ann/DebugSwitch.java new file mode 100644 index 000000000..9b7827bec --- /dev/null +++ b/src/main/java/io/eiren/util/ann/DebugSwitch.java @@ -0,0 +1,9 @@ +package io.eiren.util.ann; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Target; + + +@Target({ ElementType.FIELD, ElementType.METHOD }) +public @interface DebugSwitch { +} diff --git a/src/main/java/io/eiren/util/ann/NativeUnsafe.java b/src/main/java/io/eiren/util/ann/NativeUnsafe.java new file mode 100644 index 000000000..52052dcb3 --- /dev/null +++ b/src/main/java/io/eiren/util/ann/NativeUnsafe.java @@ -0,0 +1,19 @@ +package io.eiren.util.ann; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + + +/** + * Marks methods and classes that use unsafe or direct access to memory. Proceed + * with caution. + * + * @author Rena + */ +@Retention(value = RetentionPolicy.RUNTIME) +@Target({ ElementType.METHOD, ElementType.TYPE }) +public @interface NativeUnsafe { + +} diff --git a/src/main/java/io/eiren/util/ann/Synchronize.java b/src/main/java/io/eiren/util/ann/Synchronize.java new file mode 100644 index 000000000..d18d9f833 --- /dev/null +++ b/src/main/java/io/eiren/util/ann/Synchronize.java @@ -0,0 +1,30 @@ +package io.eiren.util.ann; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + + +/** + *

+ * Означает необходимость обязательной синхронизации этого меcта во внешних + * методах. В аргументах передаётся название поля для синхронизации. + *

+ *

+ * Методы, помеченные данной аннотацией могут вызывать только Thread-Safe + * методы, либо методы, помеченные такой же аннотацией с тем же полем + * синхронизации. + *

+ *

+ * Поля, помеченные данной аннотацией должны быть синхронизированны на указанное + * поле при чтении или записи. + *

+ * + * @see {@link ThreadSafe}, {@link ThreadSecure}, {@link ThreadSafeSingle} + * @author Rena + */ +@Retention(value = RetentionPolicy.SOURCE) +public @interface Synchronize { + + String[] value(); + +} diff --git a/src/main/java/io/eiren/util/ann/ThreadSafe.java b/src/main/java/io/eiren/util/ann/ThreadSafe.java new file mode 100644 index 000000000..c74fcfb39 --- /dev/null +++ b/src/main/java/io/eiren/util/ann/ThreadSafe.java @@ -0,0 +1,27 @@ +package io.eiren.util.ann; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + + +/** + *

+ * Методы, помеченные этой аннотацией должны быть Thread-Safe. + *

+ *

+ * Важно: данные методы гарантированно должны обеспечивать потоковую + * безопасность, но не обязаны обеспечивать концессивность (полноту данных или + * точность синхронизации). + *

+ *

+ * Для полностью потоко-безопасных методов можно использовать аннотацию + * {@link ThreadSecure}. + *

+ * + * @see {@link ThreadSecure}, {@link Synchronize}, {@link ThreadSafeSingle} + * @author Rena + */ +@Retention(value = RetentionPolicy.SOURCE) +public @interface ThreadSafe { + +} diff --git a/src/main/java/io/eiren/util/ann/ThreadSafeSingle.java b/src/main/java/io/eiren/util/ann/ThreadSafeSingle.java new file mode 100644 index 000000000..0e918499e --- /dev/null +++ b/src/main/java/io/eiren/util/ann/ThreadSafeSingle.java @@ -0,0 +1,17 @@ +package io.eiren.util.ann; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + + +/** + * Соблюдает те же требования что и {@link ThreadSafe} но при условии, что сам + * метод вызывается только из одного потока одновременно. + * + * @see {@link ThreadSafe}, {@link ThreadSecure}, {@link Synchronize} + * @author Rena + */ +@Retention(value = RetentionPolicy.SOURCE) +public @interface ThreadSafeSingle { + +} diff --git a/src/main/java/io/eiren/util/ann/ThreadSecure.java b/src/main/java/io/eiren/util/ann/ThreadSecure.java new file mode 100644 index 000000000..2b375fe36 --- /dev/null +++ b/src/main/java/io/eiren/util/ann/ThreadSecure.java @@ -0,0 +1,22 @@ +package io.eiren.util.ann; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + + +/** + *

+ * Методы, помеченные этой аннотацией должны быть полностью Thread-Safe. + *

+ *

+ * Важно: данные методы гарантированно должны обеспечивать потоковую + * безопасность и консистентность (полноту данных и точность синхронизации). + *

+ * + * @see {@link ThreadSafe}, {@link Synchronize}, {@link ThreadSafeSingle} + * @author Rena + */ +@Retention(value = RetentionPolicy.SOURCE) +public @interface ThreadSecure { + +} diff --git a/src/main/java/io/eiren/util/ann/Transient.java b/src/main/java/io/eiren/util/ann/Transient.java new file mode 100644 index 000000000..3502dff42 --- /dev/null +++ b/src/main/java/io/eiren/util/ann/Transient.java @@ -0,0 +1,39 @@ +package io.eiren.util.ann; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + + +/** + *

+ * Означает что поле используется для временных или быстро изменяющихся + * переменных. + *

+ *

+ * Поле помеченное этой аннотацией не влияет на долгосрочное состояние объекта, + * не участвует в сериализации, вычислении equals и hashCode, не определяет + * поведение объекта для внешнего кода. Поэтому такие поля не должны + * использоваться внешним кодом, их состояние имеет смысл только для самого + * объекта в котором они объявлены. + *

+ * Примеры: + *
    + *
  • Временный объект, который используется в методах для внутренних + * вычислений. Например векторные и матричные вычисления.
  • + *
  • Внутренний флаг для мультитрединга. Например, флаг апдейта графического + * состояния взводимый из игрового потока.
  • + *
  • Выведенное значение или структура, которое инициализируется самим + * объектом по фиксированному правилу. Например, производное значение от + * переменной параметризующей объект. Инициализируемый в конструкторе lookup + * table.
  • + *
+ * + * @author tort32 + */ +@Retention(value = RetentionPolicy.SOURCE) +@Target({ ElementType.FIELD }) +public @interface Transient { + +} diff --git a/src/main/java/io/eiren/util/collections/FastList.java b/src/main/java/io/eiren/util/collections/FastList.java new file mode 100644 index 000000000..6a2ff809c --- /dev/null +++ b/src/main/java/io/eiren/util/collections/FastList.java @@ -0,0 +1,547 @@ +package io.eiren.util.collections; + +import java.util.*; +import java.util.function.Consumer; +import java.util.function.Predicate; +import java.util.function.UnaryOperator; + + +@SuppressWarnings("unchecked") +public class FastList extends AbstractList + implements RandomAccess, Cloneable, RemoveAtSwapList { + + private static final Object[] emptyArray = new Object[0]; + + public static final int MAX_ARRAY_SIZE = 2147483639; + + protected int size = 0; + protected Object[] array; + + public FastList(int capacity) { + array = capacity == 0 ? emptyArray : new Object[capacity]; + } + + public FastList() { + this(5); + } + + public FastList(Collection source) { + this(source.size()); + addAll(source); + } + + public FastList(FastList source) { + this(source.size); + addAllInternal(0, source.array, source.size); + } + + public FastList(E[] source) { + this(source.length); + addAll(source); + } + + public FastList(E source) { + this(); + add(source); + } + + private FastList(Object[] arr, int size) { + this(size); + System.arraycopy(arr, 0, array, 0, size); + this.size = size; + } + + private FastList(boolean f) { + } + + public static FastList reuseArray(E[] source) { + FastList list = new FastList<>(true); + list.array = source; + list.size = source.length; + return list; + } + + private void checkBounds(int index) { + if (index < 0 || index >= size) + throw new ArrayIndexOutOfBoundsException( + new StringBuilder("Index: ") + .append(index) + .append(", size: ") + .append(size) + .toString() + ); + } + + public void ensureCapacity(int numToFit) { + if (array.length < size + numToFit) + grow(numToFit + size); + } + + private void grow(int i) { + int j = array.length; + int k = j + (j >> 1); + if (k - i < 0) + k = i; + if (k - 2147483639 > 0) + k = hugeCapacity(i); + array = Arrays.copyOf(array, k); + } + + private static int hugeCapacity(int i) { + if (i < 0) + throw new OutOfMemoryError("Huge capacity negative: " + i); + else + return i <= MAX_ARRAY_SIZE ? MAX_ARRAY_SIZE : 2147483647; + } + + public void copyInto(Object[] anArray) { + System.arraycopy(array, 0, anArray, 0, size); + } + + @Override + public E get(int index) { + checkBounds(index); + return (E) array[index]; + } + + public E unsafeGet(int index) { + return (E) array[index]; + } + + @Override + public boolean isEmpty() { + return size == 0; + } + + @Override + public int size() { + return size; + } + + @Override + public int indexOf(Object obj) { + for (int j = 0; j < size; j++) + if (obj == array[j]) + return j; + return -1; + } + + @Override + public int lastIndexOf(Object obj) { + for (int j = size - 1; j >= 0; j--) + if (obj == array[j]) + return j; + return -1; + } + + @Override + public boolean contains(Object obj) { + return indexOf(obj) >= 0; + } + + public void trimToSize() { + int i = array.length; + if (size < i) + array = Arrays.copyOf(array, size); + } + + @Override + public Object[] toArray() { + return Arrays.copyOf(array, size); + } + + @Override + public T[] toArray(T aobj[]) { + if (aobj.length < size) + return (T[]) Arrays.copyOf(array, size, aobj.getClass()); + System.arraycopy(array, 0, aobj, 0, size); + if (aobj.length > size) + aobj[size] = null; + return aobj; + } + + @Override + public boolean add(E e) { + ensureCapacity(1); + array[size++] = e; + return true; + } + + @Override + public E remove(int i) { + checkBounds(i); + E obj = (E) array[i]; + removeInternal(i); + return obj; + } + + @Override + public boolean remove(Object obj) { + for (int j = 0; j < size; j++) + if (obj == array[j]) { + removeInternal(j); + return true; + } + return false; + } + + public boolean removeAll(Object[] toRemove) { + boolean removed = false; + for (int i = toRemove.length - 1; i >= 0; --i) { + int index = indexOf(toRemove[i]); + if (index != -1) { + removeInternal(index); + removed = true; + } + } + return removed; + } + + protected void removeInternal(int i) { + int j = size - i - 1; + if (j > 0) + System.arraycopy(array, i + 1, array, i, j); + array[--size] = null; + } + + public void unsafeRemove(int i) { + removeInternal(i); + } + + @Override + public boolean removeAll(Collection c) { + Objects.requireNonNull(c); + return batchRemove(c, false); + } + + @Override + public boolean retainAll(Collection c) { + Objects.requireNonNull(c); + return batchRemove(c, true); + } + + private boolean batchRemove(Collection c, boolean complement) { + final Object[] elementData = this.array; + int r = 0, w = 0; + boolean modified = false; + try { + for (; r < size; r++) + if (c.contains(elementData[r]) == complement) + elementData[w++] = elementData[r]; + } finally { + // Preserve behavioral compatibility with AbstractCollection, + // even if c.contains() throws. + if (r != size) { + System.arraycopy(elementData, r, elementData, w, size - r); + w += size - r; + } + if (w != size) { + for (int i = w; i < size; i++) + elementData[i] = null; + size = w; + modified = true; + } + } + return modified; + } + + @Override + public void clear() { + for (int i = 0; i < size; i++) + array[i] = null; + size = 0; + } + + public void fakeClear() { + size = 0; + } + + @Override + public boolean addAll(Collection collection) { + return addAll(size, collection); + } + + public void addAll(E[] arr) { + addAllInternal(size, arr, arr.length); + } + + public void addAll(E[] arr, int limit) { + addAllInternal(size, arr, limit); + } + + public void addAll(int index, E[] arr) { + addAllInternal(index, arr, arr.length); + } + + public void addAll(int index, E[] arr, int limit) { + addAllInternal(index, arr, limit); + } + + private void addAllInternal(int index, Object[] arr, int limit) { + if (limit > arr.length) + limit = arr.length; + if (limit == 1) { + add(index, (E) arr[0]); + } else if (limit > 0) { + if (index >= size) { + ensureCapacity(size - index + limit); + System.arraycopy(arr, 0, array, index, limit); + size = index + limit; + } else { + if (array.length < size + limit) { + Object[] newArray = new Object[size + limit]; + System.arraycopy(array, 0, newArray, 0, index); + System.arraycopy(arr, 0, newArray, index, limit); + System.arraycopy(array, index, newArray, index + limit, size - index); + array = newArray; + } else { + System.arraycopy(array, index, array, index + 1, size - index); + System.arraycopy(arr, 0, array, index, limit); + } + size += limit; + } + } + } + + @Override + public boolean addAll(int index, Collection collection) { + if (collection.size() > 0) { + if (collection instanceof FastList) { + addAllInternal( + index, + ((FastList) collection).array, + collection.size() + ); + } else if (collection instanceof RandomAccess) { + Object[] arr = collection.toArray(new Object[collection.size()]); + addAllInternal(index, arr, arr.length); + } else { + if (index >= size) { + ensureCapacity(size - index + collection.size()); + Iterator iterator = collection.iterator(); + int i = index; + while (iterator.hasNext()) + array[i++] = iterator.next(); + size = index + collection.size(); + } else { + if (array.length < size + collection.size()) { + Object[] newArray = new Object[size + collection.size()]; + System.arraycopy(array, 0, newArray, 0, index); + Iterator iterator = collection.iterator(); + int i = index; + while (iterator.hasNext()) + newArray[i++] = iterator.next(); + System + .arraycopy( + array, + index, + newArray, + index + collection.size(), + size - index + ); + array = newArray; + } else { + System.arraycopy(array, index, array, index + 1, size - index); + Iterator iterator = collection.iterator(); + while (iterator.hasNext()) + array[index++] = iterator.next(); + } + size += collection.size(); + } + } + return true; + } + return false; + } + + @Override + public void add(int index, E element) { + if (index >= size) { + ensureCapacity(size - index + 1); + size = index + 1; + array[index] = element; + } else { + if (array.length < size + 1) { + Object[] newArray = new Object[size + 1]; + System.arraycopy(array, 0, newArray, 0, index); + newArray[index] = element; + System.arraycopy(array, index, newArray, index + 1, size - index); + array = newArray; + } else { + System.arraycopy(array, index, array, index + 1, size - index); + array[index] = element; + } + size++; + } + } + + @Override + public E set(int index, E element) { + checkBounds(index); + E oldValue = (E) array[index]; + array[index] = element; + return oldValue; + } + + @Override + public FastList clone() { + return new FastList(array, size); + } + + @Override + public void forEach(Consumer action) { + Objects.requireNonNull(action); + final int expectedModCount = modCount; + final E[] elementData = (E[]) this.array; + final int size = this.size; + for (int i = 0; modCount == expectedModCount && i < size; i++) { + action.accept(elementData[i]); + } + if (modCount != expectedModCount) { + throw new ConcurrentModificationException(); + } + } + + @Override + public E removeAtSwap(int i) { + checkBounds(i); + E obj = (E) array[i]; + removeAtSwapInternal(i); + return obj; + } + + @Override + public boolean removeAtSwap(Object obj) { + for (int j = 0; j < size; j++) + if (obj == array[j]) { + removeAtSwapInternal(j); + return true; + } + return false; + } + + protected void removeAtSwapInternal(int i) { + int j = size - i - 1; + if (j > 0) + array[i] = array[size - 1]; + array[--size] = null; + } + + @Override + public void removeRange(int i, int toIndex) { + checkBounds(i); + checkBounds(toIndex); + int j = size - toIndex - 1; + if (j > 0) + System.arraycopy(array, toIndex + 1, array, i, j); + size -= (toIndex - i + 1); + Arrays.fill(array, i, toIndex, null); + } + + @Override + public void replaceAll(UnaryOperator operator) { + Objects.requireNonNull(operator); + for (int i = 0; i < size; ++i) + set(i, operator.apply(get(i))); + } + + @Override + public void sort(Comparator c) { + Arrays.sort((E[]) array, 0, size, c); + } + + @Override + public int hashCode() { + int hashCode = 1; + for (int i = 0; i < size; ++i) { + Object o = array[i]; + hashCode = 31 * hashCode + (o == null ? 0 : o.hashCode()); + } + return hashCode; + } + + @Override + public Spliterator spliterator() { + return Spliterators.spliterator(array, 0, size, Spliterator.ORDERED); + } + + /** + * Special comodification iterator. Use with caution. + *

+ * To get element type correctly assign result to reference type + * {@code FastList.SkipFastListIterator} + * + * @return skip iterator to iterate this list in thread-safe manner + */ + public SkipFastListIterator skipIterator() { + return new SkipFastListIterator(); + } + + @Override + public boolean removeIf(Predicate filter) { + Objects.requireNonNull(filter); + // figure out which elements are to be removed + // any exception thrown from the filter predicate at this stage + // will leave the collection unmodified + int removeCount = 0; + final BitSet removeSet = new BitSet(size); + final int expectedModCount = modCount; + final int size = this.size; + for (int i = 0; modCount == expectedModCount && i < size; i++) { + final E element = (E) array[i]; + if (filter.test(element)) { + removeSet.set(i); + removeCount++; + } + } + if (modCount != expectedModCount) { + throw new ConcurrentModificationException(); + } + + // shift surviving elements left over the spaces left by removed + // elements + final boolean anyToRemove = removeCount > 0; + if (anyToRemove) { + final int newSize = size - removeCount; + for (int i = 0, j = 0; (i < size) && (j < newSize); i++, j++) { + i = removeSet.nextClearBit(i); + array[j] = array[i]; + } + for (int k = newSize; k < size; k++) { + array[k] = null; // Let gc do its work + } + this.size = newSize; + if (modCount != expectedModCount) { + throw new ConcurrentModificationException(); + } + modCount++; + } + + return anyToRemove; + } + + public class SkipFastListIterator implements ResettableIterator, SkipIterator { + + public int position; + + @Override + public boolean hasNext() { + return position < size; + } + + @Override + public E next() { + Object[] arr = array; + if (arr.length > position) { + return (E) arr[position++]; + } + position++; // Increase position so hasNext() never loops infinitely + return null; + } + + @Override + public void reset() { + position = 0; + } + } +} diff --git a/src/main/java/io/eiren/util/collections/RemoveAtSwapFastList.java b/src/main/java/io/eiren/util/collections/RemoveAtSwapFastList.java new file mode 100644 index 000000000..1c00001e1 --- /dev/null +++ b/src/main/java/io/eiren/util/collections/RemoveAtSwapFastList.java @@ -0,0 +1,41 @@ +package io.eiren.util.collections; + +import java.util.Collection; + + +/** + * FastList that performs Remove-At-Swap on stanard remove() operations. + * + *

+ * Remove operations breaks ordering of this list + * + * @author Rena + * + * @param + */ +public class RemoveAtSwapFastList extends FastList { + + public RemoveAtSwapFastList(int capacity) { + super(capacity); + } + + public RemoveAtSwapFastList() { + } + + public RemoveAtSwapFastList(Collection source) { + super(source); + } + + public RemoveAtSwapFastList(E[] source) { + super(source); + } + + public RemoveAtSwapFastList(E source) { + super(source); + } + + @Override + protected void removeInternal(int i) { + super.removeAtSwapInternal(i); + } +} diff --git a/src/main/java/io/eiren/util/collections/RemoveAtSwapList.java b/src/main/java/io/eiren/util/collections/RemoveAtSwapList.java new file mode 100644 index 000000000..ab6fbfcc1 --- /dev/null +++ b/src/main/java/io/eiren/util/collections/RemoveAtSwapList.java @@ -0,0 +1,11 @@ +package io.eiren.util.collections; + +import java.util.List; + + +public interface RemoveAtSwapList extends List { + + public E removeAtSwap(int i); + + public boolean removeAtSwap(Object object); +} diff --git a/src/main/java/io/eiren/util/collections/ResettableIterator.java b/src/main/java/io/eiren/util/collections/ResettableIterator.java new file mode 100644 index 000000000..8f145d42c --- /dev/null +++ b/src/main/java/io/eiren/util/collections/ResettableIterator.java @@ -0,0 +1,17 @@ +package io.eiren.util.collections; + +import java.util.Iterator; + + +/** + * {@link Iterator} that can be reset and iterated from the start by using + * {@link #reset()} + * + * @author Rena + * + * @param + */ +public interface ResettableIterator extends Iterator { + + public void reset(); +} diff --git a/src/main/java/io/eiren/util/collections/SkipIterator.java b/src/main/java/io/eiren/util/collections/SkipIterator.java new file mode 100644 index 000000000..9c79579ad --- /dev/null +++ b/src/main/java/io/eiren/util/collections/SkipIterator.java @@ -0,0 +1,16 @@ +package io.eiren.util.collections; + +import java.util.Iterator; + + +/** + * {@link Iterator} that can return null on {@link #next()} or can lie on + * {@link #hasNext()}. It is not thread-secure! + * + * @param the type of elements returned by this iterator + */ +public interface SkipIterator extends Iterator { + + @Override + E next(); +} diff --git a/src/main/java/io/eiren/util/logging/DefaultGLog.java b/src/main/java/io/eiren/util/logging/DefaultGLog.java new file mode 100644 index 000000000..78d834099 --- /dev/null +++ b/src/main/java/io/eiren/util/logging/DefaultGLog.java @@ -0,0 +1,137 @@ +package io.eiren.util.logging; + +import java.util.concurrent.ArrayBlockingQueue; +import java.util.logging.Level; +import java.util.logging.Logger; + + +public class DefaultGLog extends Thread implements IGLog { + + private final Logger logger; + + public static class LogEntry { + + private Level level; + private String message; + private Throwable t; + + public LogEntry(Level level, String message, Throwable t) { + this(level, message); + this.t = t; + } + + public LogEntry(Level level, String message) { + this.level = level; + this.message = message; + this.t = null; + } + + public Level getLevel() { + return level; + } + + public String getMessage() { + return message; + } + + public Throwable getException() { + return t; + } + } + + private final ArrayBlockingQueue queue = new ArrayBlockingQueue(50000); + private volatile LoggerRecorder recorder; + + @Override + public void info(String message) { + add(new LogEntry(Level.INFO, message)); + } + + @Override + public void info(String message, Throwable t) { + add(new LogEntry(Level.INFO, message, t)); + } + + @Override + public void severe(String message) { + add(new LogEntry(Level.SEVERE, message)); + } + + @Override + public void severe(String message, Throwable t) { + add(new LogEntry(Level.SEVERE, message, t)); + } + + @Override + public void warning(String message) { + add(new LogEntry(Level.WARNING, message)); + } + + @Override + public void warning(String message, Throwable t) { + add(new LogEntry(Level.WARNING, message, t)); + } + + @Override + public void debug(String message) { + add(new LogEntry(Level.INFO, "[DBG] " + message)); + } + + @Override + public void debug(String message, Throwable t) { + add(new LogEntry(Level.INFO, "[DBG] " + message, t)); + } + + @Override + public void log(Level level, String message) { + add(new LogEntry(level, message)); + } + + @Override + public void log(Level level, String message, Throwable t) { + add(new LogEntry(level, message, t)); + } + + private void add(LogEntry entry) { + try { + queue.put(entry); + } catch (InterruptedException e) {} + try { + if (recorder != null) + recorder.addEntry(entry); + } catch (NullPointerException e) {} + } + + @Override + public void setRecorder(LoggerRecorder recorder) { + this.recorder = recorder; + } + + @Override + public LoggerRecorder removeRecorder() { + LoggerRecorder lr = this.recorder; + this.recorder = null; + return lr; + } + + public DefaultGLog(Logger logger) { + super("Logger"); + this.logger = logger; + this.setDaemon(true); + this.setPriority(7); + this.start(); + } + + @Override + public void run() { + while (true) { + try { + LogEntry log = queue.take(); + if (log.t != null) + logger.log(log.level, log.message, log.t); + else + logger.log(log.level, log.message); + } catch (InterruptedException e) {} + } + } +} diff --git a/src/main/java/io/eiren/util/logging/FileLogFormatter.java b/src/main/java/io/eiren/util/logging/FileLogFormatter.java new file mode 100644 index 000000000..24d67afa0 --- /dev/null +++ b/src/main/java/io/eiren/util/logging/FileLogFormatter.java @@ -0,0 +1,58 @@ +package io.eiren.util.logging; + +import java.io.PrintWriter; +import java.io.StringWriter; +import java.text.SimpleDateFormat; +import java.util.logging.Formatter; +import java.util.logging.Level; +import java.util.logging.LogRecord; + + +public class FileLogFormatter extends Formatter { + + private static final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + + @Override + public String format(LogRecord record) { + StringBuilder sb = new StringBuilder(); + sb.append(dateFormat.format(record.getMillis())); + Level localLevel = record.getLevel(); + if (localLevel == Level.FINEST) + sb.append(" [FINEST] "); + else if (localLevel == Level.FINER) + sb.append(" [FINER] "); + else if (localLevel == Level.FINE) + sb.append(" [FINE] "); + else if (localLevel == Level.INFO) + sb.append(" [INFO] "); + else if (localLevel == Level.WARNING) + sb.append(" [WARNING] "); + else if (localLevel == Level.SEVERE) + sb.append(" [SEVERE] "); + else + sb.append(" [" + localLevel.getLocalizedName() + "] "); + + sb.append(record.getMessage()); + sb.append('\n'); + + Throwable localThrowable = record.getThrown(); + if (localThrowable != null) { + StringWriter localStringWriter = new StringWriter(); + localThrowable.printStackTrace(new PrintWriter(localStringWriter)); + sb.append(localStringWriter.toString()); + } + + String message = sb.toString(); + Object parameters[] = record.getParameters(); + if (parameters == null || parameters.length == 0) + return message; + if ( + message.indexOf("{0") >= 0 + || message.indexOf("{1") >= 0 + || message.indexOf("{2") >= 0 + || message.indexOf("{3") >= 0 + ) + return java.text.MessageFormat.format(message, parameters); + return message; + } +} diff --git a/src/main/java/io/eiren/util/logging/IGLog.java b/src/main/java/io/eiren/util/logging/IGLog.java new file mode 100644 index 000000000..f21744a19 --- /dev/null +++ b/src/main/java/io/eiren/util/logging/IGLog.java @@ -0,0 +1,48 @@ +package io.eiren.util.logging; + +import java.util.logging.Level; + + +public interface IGLog { + + public void info(String message); + + public void severe(String message); + + public void warning(String message); + + public void debug(String message); + + public default void info(String message, Throwable t) { + log(Level.INFO, message, t); + } + + public default void severe(String message, Throwable t) { + log(Level.SEVERE, message, t); + } + + public default void warning(String message, Throwable t) { + log(Level.WARNING, message, t); + } + + public default void debug(String message, Throwable t) { + log(Level.INFO, "[DBG] " + message, t); + } + + public void log(Level level, String message); + + public void log(Level level, String message, Throwable t); + + public void setRecorder(LoggerRecorder recorder); + + public LoggerRecorder removeRecorder(); + + static class GLevel extends Level { + + private static final long serialVersionUID = -539856764608026895L; + + private GLevel(String s, int i) { + super(s, i); + } + } +} diff --git a/src/main/java/io/eiren/util/logging/LogManager.java b/src/main/java/io/eiren/util/logging/LogManager.java new file mode 100644 index 000000000..05ba0619e --- /dev/null +++ b/src/main/java/io/eiren/util/logging/LogManager.java @@ -0,0 +1,136 @@ +package io.eiren.util.logging; + +import java.io.File; +import java.io.IOException; +import java.io.PrintStream; +import java.text.SimpleDateFormat; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.logging.ConsoleHandler; +import java.util.logging.FileHandler; +import java.util.logging.Handler; +import java.util.logging.Level; +import java.util.logging.Logger; + + +public class LogManager { + + private static AtomicBoolean initialized = new AtomicBoolean(false); + + public static Logger global = Logger.getLogger(""); + public static final IGLog log = new DefaultGLog(global); + public static ConsoleHandler handler; + + public static void initialize(File logsDir, File mainLogDir) + throws SecurityException, IOException { + if (initialized.getAndSet(true)) + return; + FileLogFormatter loc = new FileLogFormatter(); + if (mainLogDir != null) { + if (!mainLogDir.exists()) + mainLogDir.mkdirs(); + File lastLogFile = new File(mainLogDir, "log_last.log"); + if (lastLogFile.exists()) + lastLogFile.delete(); + File mainLog = new File(mainLogDir, "log_main.log"); + FileHandler mHandler = new FileHandler(mainLog.getPath(), true); + FileHandler filehandler = new FileHandler(lastLogFile.getPath(), true); + mHandler.setFormatter(loc); + filehandler.setFormatter(loc); + global.addHandler(mHandler); + global.addHandler(filehandler); + } + if (logsDir != null) { + if (!logsDir.exists()) + logsDir.mkdir(); + if (!logsDir.isDirectory()) + System.out.println("*** WARNING *** LOG FOLDER IS NOT A DIRECTORY!"); + File currentLog = new File( + logsDir, + "log_" + + new SimpleDateFormat("yyyy-MM-dd") + .format(Long.valueOf(System.currentTimeMillis())) + + ".log" + ); + FileHandler filehandler2 = new FileHandler(currentLog.getPath(), true); + filehandler2.setFormatter(loc); + global.addHandler(filehandler2); + } + } + + public static void replaceMainHandler(ConsoleHandler newHandler) { + handler.close(); + global.removeHandler(handler); + handler = newHandler; + global.addHandler(newHandler); + } + + public static void addHandler(Handler add) { + global.addHandler(add); + } + + public static void removeHandler(Handler remove) { + global.removeHandler(remove); + } + + public static void enablePreciseTimestamp() { + handler.setFormatter(new PreciseConsoleLogFormatter()); + } + + public static void info(String message) { + log.info(message); + } + + public static void severe(String message) { + log.severe(message); + } + + public static void warning(String message) { + log.warning(message); + } + + public static void debug(String message) { + log.debug(message); + } + + public static void info(String message, Throwable t) { + log.info(message, t); + } + + public static void severe(String message, Throwable t) { + log.severe(message, t); + } + + public static void warning(String message, Throwable t) { + log.warning(message, t); + } + + public static void debug(String message, Throwable t) { + log.debug(message, t); + } + + public static void log(Level level, String message) { + log.log(level, message); + } + + public static void log(Level level, String message, Throwable t) { + log.log(level, message, t); + } + + static { + boolean hasConsoleHandler = false; + for (Handler h : global.getHandlers()) { + if (h instanceof ConsoleHandler) { + handler = (ConsoleHandler) h; + hasConsoleHandler = true; + } + } + if (!hasConsoleHandler) { + handler = new ConsoleHandler(); + global.addHandler(handler); + } + handler.setFormatter(new ShortConsoleLogFormatter()); + + System.setOut(new PrintStream(new LoggerOutputStream(log, Level.INFO), true)); + System.setErr(new PrintStream(new LoggerOutputStream(log, Level.SEVERE), true)); + } +} diff --git a/src/main/java/io/eiren/util/logging/LoggerOutputStream.java b/src/main/java/io/eiren/util/logging/LoggerOutputStream.java new file mode 100644 index 000000000..0a866937f --- /dev/null +++ b/src/main/java/io/eiren/util/logging/LoggerOutputStream.java @@ -0,0 +1,52 @@ +package io.eiren.util.logging; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.logging.Level; + + +public class LoggerOutputStream extends ByteArrayOutputStream { + + private static final String separator = System.getProperty("line.separator"); + + private final IGLog logger; + private final Level level; + private final String prefix; + private final StringBuilder buffer = new StringBuilder(); + + public LoggerOutputStream(IGLog logger, Level level) { + this(logger, level, ""); + } + + public LoggerOutputStream(IGLog logger, Level level, String prefix) { + super(); + this.logger = logger; + this.level = level; + this.prefix = prefix; + } + + @Override + public void flush() throws IOException { + synchronized (this) { + super.flush(); + String record = this.toString(); + super.reset(); + if (record.length() > 0) { + buffer.append(record); + if (record.contains(separator)) { + String s = buffer.toString(); + String[] split = s.split(separator); + for (int i = 0; i < split.length; ++i) + logger.log(level, prefix + split[i]); + buffer.setLength(0); + // buffer.append(split[split.length - 1]); + } + } + } + } + + @Override + public void close() throws IOException { + flush(); + } +} diff --git a/src/main/java/io/eiren/util/logging/LoggerRecorder.java b/src/main/java/io/eiren/util/logging/LoggerRecorder.java new file mode 100644 index 000000000..2fc4f9392 --- /dev/null +++ b/src/main/java/io/eiren/util/logging/LoggerRecorder.java @@ -0,0 +1,23 @@ +package io.eiren.util.logging; + +import java.util.List; + +import io.eiren.util.collections.FastList; +import io.eiren.util.logging.DefaultGLog.LogEntry; + + +public class LoggerRecorder { + + private final List recorded = new FastList(); + + public LoggerRecorder() { + } + + public synchronized void addEntry(LogEntry e) { + recorded.add(e); + } + + public List getEntries() { + return recorded; + } +} diff --git a/src/main/java/io/eiren/util/logging/PreciseConsoleLogFormatter.java b/src/main/java/io/eiren/util/logging/PreciseConsoleLogFormatter.java new file mode 100644 index 000000000..69abe91dd --- /dev/null +++ b/src/main/java/io/eiren/util/logging/PreciseConsoleLogFormatter.java @@ -0,0 +1,32 @@ +package io.eiren.util.logging; + +import java.text.SimpleDateFormat; +import java.util.logging.LogRecord; + + +/** + * Format message timestamp as time passed from the start with milliseconds. + */ +public class PreciseConsoleLogFormatter extends ShortConsoleLogFormatter { + + private final long startMills; + + public PreciseConsoleLogFormatter() { + startMills = System.currentTimeMillis(); + } + + @Override + protected SimpleDateFormat createDateFormat() { + return new SimpleDateFormat("mm:ss.SSS"); + } + + @Override + protected void buildMessage(StringBuilder builder, LogRecord record) { + builder.append(date.format(record.getMillis() - startMills)); + builder.append(" ["); + builder.append(record.getLevel().getLocalizedName().toUpperCase()); + builder.append("] "); + builder.append(record.getMessage()); + builder.append('\n'); + } +} diff --git a/src/main/java/io/eiren/util/logging/ShortConsoleLogFormatter.java b/src/main/java/io/eiren/util/logging/ShortConsoleLogFormatter.java new file mode 100644 index 000000000..f95749084 --- /dev/null +++ b/src/main/java/io/eiren/util/logging/ShortConsoleLogFormatter.java @@ -0,0 +1,58 @@ +package io.eiren.util.logging; + +import java.io.PrintWriter; +import java.io.StringWriter; +import java.text.SimpleDateFormat; +import java.util.logging.Formatter; +import java.util.logging.LogRecord; + + +public class ShortConsoleLogFormatter extends Formatter { + + protected final SimpleDateFormat date; + + public ShortConsoleLogFormatter() { + this.date = createDateFormat(); + } + + protected SimpleDateFormat createDateFormat() { + return new SimpleDateFormat("HH:mm:ss"); + } + + protected void buildMessage(StringBuilder builder, LogRecord record) { + builder.append(date.format(record.getMillis())); + builder.append(" ["); + builder.append(record.getLevel().getLocalizedName().toUpperCase()); + builder.append("] "); + builder.append(record.getMessage()); + builder.append('\n'); + } + + @Override + public String format(LogRecord record) { + StringBuilder builder = new StringBuilder(); + Throwable ex = record.getThrown(); + + buildMessage(builder, record); + + if (ex != null) { + StringWriter writer = new StringWriter(); + ex.printStackTrace(new PrintWriter(writer)); + builder.append(writer); + } + + String message = builder.toString(); + Object parameters[] = record.getParameters(); + if (parameters == null || parameters.length == 0) + return message; + if ( + message.indexOf("{0") >= 0 + || message.indexOf("{1") >= 0 + || message.indexOf("{2") >= 0 + || message.indexOf("{3") >= 0 + ) + return java.text.MessageFormat.format(message, parameters); + return message; + } + +} diff --git a/src/main/java/org/json/JSONArray.java b/src/main/java/org/json/JSONArray.java new file mode 100644 index 000000000..578ba899c --- /dev/null +++ b/src/main/java/org/json/JSONArray.java @@ -0,0 +1,880 @@ +package org.json; + +/* + Copyright (c) 2002 JSON.org + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + The Software shall be used for Good, not Evil. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. + */ + +import java.io.IOException; +import java.io.StringWriter; +import java.io.Writer; +import java.lang.reflect.Array; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Iterator; +import java.util.Map; + + +/** + * A JSONArray is an ordered sequence of values. Its external text form is a + * string wrapped in square brackets with commas separating the values. The + * internal form is an object having get and opt + * methods for accessing the values by index, and put methods for + * adding or replacing values. The values can be any of these types: + * Boolean, JSONArray, JSONObject, + * Number, String, or the + * JSONObject.NULL object. + *

+ * The constructor can convert a JSON text into a Java object. The + * toString method converts to JSON text. + *

+ * A get method returns a value if one can be found, and throws an + * exception if one cannot be found. An opt method returns a + * default value instead of throwing an exception, and so is useful for + * obtaining optional values. + *

+ * The generic get() and opt() methods return an + * object which you can cast or query for type. There are also typed + * get and opt methods that do type checking and type + * coercion for you. + *

+ * The texts produced by the toString methods strictly conform to + * JSON syntax rules. The constructors are more forgiving in the texts they will + * accept: + *

    + *
  • An extra , (comma) may appear just + * before the closing bracket.
  • + *
  • The null value will be inserted when there is , + *  (comma) elision.
  • + *
  • Strings may be quoted with ' (single + * quote).
  • + *
  • Strings do not need to be quoted at all if they do not begin with a quote + * or single quote, and if they do not contain leading or trailing spaces, and + * if they do not contain any of these characters: + * { } [ ] / \ : , = ; # and if they do not look like numbers and + * if they are not the reserved words true, false, or + * null.
  • + *
  • Values can be separated by ; (semicolon) as + * well as by , (comma).
  • + *
+ * + * @author JSON.org + * @version 2012-04-20 + */ +public class JSONArray { + + /** + * The arrayList where the JSONArray's properties are kept. + */ + private final ArrayList myArrayList; + + /** + * Construct an empty JSONArray. + */ + public JSONArray() { + this.myArrayList = new ArrayList(); + } + + public JSONArray(int initialLength) { + this.myArrayList = new ArrayList(initialLength); + } + + /** + * Construct a JSONArray from a JSONTokener. + * + * @param x A JSONTokener + * @throws JSONException If there is a syntax error. + */ + public JSONArray(JSONTokener x) throws JSONException { + this(); + if (x.nextClean() != '[') { + throw x.syntaxError("A JSONArray text must start with '['"); + } + if (x.nextClean() != ']') { + x.back(); + for (;;) { + if (x.nextClean() == ',') { + x.back(); + this.myArrayList.add(JSONObject.NULL); + } else { + x.back(); + this.myArrayList.add(x.nextValue()); + } + switch (x.nextClean()) { + case ';': + case ',': + if (x.nextClean() == ']') { + return; + } + x.back(); + break; + case ']': + return; + default: + throw x.syntaxError("Expected a ',' or ']'"); + } + } + } + } + + /** + * Construct a JSONArray from a source JSON text. + * + * @param source A string that begins with [ (left + * bracket) and ends with ]  (right + * bracket). + * @throws JSONException If there is a syntax error. + */ + public JSONArray(String source) throws JSONException { + this(new JSONTokener(source)); + } + + /** + * Construct a JSONArray from a Collection. + * + * @param collection A Collection. + */ + public JSONArray(Collection collection) { + this.myArrayList = new ArrayList(); + if (collection != null) { + Iterator iter = collection.iterator(); + while (iter.hasNext()) { + this.myArrayList.add(JSONObject.wrap(iter.next())); + } + } + } + + /** + * Construct a JSONArray from an array + * + * @throws JSONException If not an array. + */ + public JSONArray(Object array) { + this(); + if (array.getClass().isArray()) { + int length = Array.getLength(array); + for (int i = 0; i < length; i += 1) { + this.put(JSONObject.wrap(Array.get(array, i))); + } + } else { + throw new IllegalArgumentException( + "JSONArray initial value should be a string or collection or array." + ); + } + } + + /** + * Get the object value associated with an index. + * + * @param index The index must be between 0 and length() - 1. + * @return An object value. + * @throws JSONException If there is no value for the index. + */ + public Object get(int index) throws JSONException { + Object object = this.opt(index); + if (object == null) { + throw new JSONException("JSONArray[" + index + "] not found."); + } + return object; + } + + /** + * Get the boolean value associated with an index. The string values "true" + * and "false" are converted to boolean. + * + * @param index The index must be between 0 and length() - 1. + * @return The truth. + * @throws JSONException If there is no value for the index or if the value + * is not convertible to boolean. + */ + public boolean getBoolean(int index) throws JSONException { + Object object = this.get(index); + if ( + object.equals(Boolean.FALSE) + || (object instanceof String && ((String) object).equalsIgnoreCase("false")) + ) { + return false; + } else if ( + object.equals(Boolean.TRUE) + || (object instanceof String && ((String) object).equalsIgnoreCase("true")) + ) { + return true; + } + throw new JSONException("JSONArray[" + index + "] is not a boolean."); + } + + /** + * Get the double value associated with an index. + * + * @param index The index must be between 0 and length() - 1. + * @return The value. + * @throws JSONException If the key is not found or if the value cannot be + * converted to a number. + */ + public double getDouble(int index) throws JSONException { + Object object = this.get(index); + try { + return object instanceof Number + ? ((Number) object).doubleValue() + : Double.parseDouble((String) object); + } catch (Exception e) { + throw new JSONException("JSONArray[" + index + "] is not a number."); + } + } + + /** + * Get the int value associated with an index. + * + * @param index The index must be between 0 and length() - 1. + * @return The value. + * @throws JSONException If the key is not found or if the value is not a + * number. + */ + public int getInt(int index) throws JSONException { + Object object = this.get(index); + try { + return object instanceof Number + ? ((Number) object).intValue() + : Integer.parseInt((String) object); + } catch (Exception e) { + throw new JSONException("JSONArray[" + index + "] is not a number."); + } + } + + /** + * Get the JSONArray associated with an index. + * + * @param index The index must be between 0 and length() - 1. + * @return A JSONArray value. + * @throws JSONException If there is no value for the index. or if the value + * is not a JSONArray + */ + public JSONArray getJSONArray(int index) throws JSONException { + Object object = this.get(index); + if (object instanceof JSONArray) { + return (JSONArray) object; + } + throw new JSONException("JSONArray[" + index + "] is not a JSONArray."); + } + + /** + * Get the JSONObject associated with an index. + * + * @param index subscript + * @return A JSONObject value. + * @throws JSONException If there is no value for the index or if the value + * is not a JSONObject + */ + public JSONObject getJSONObject(int index) throws JSONException { + Object object = this.get(index); + if (object instanceof JSONObject) { + return (JSONObject) object; + } + throw new JSONException("JSONArray[" + index + "] is not a JSONObject."); + } + + /** + * Get the long value associated with an index. + * + * @param index The index must be between 0 and length() - 1. + * @return The value. + * @throws JSONException If the key is not found or if the value cannot be + * converted to a number. + */ + public long getLong(int index) throws JSONException { + Object object = this.get(index); + try { + return object instanceof Number + ? ((Number) object).longValue() + : Long.parseLong((String) object); + } catch (Exception e) { + throw new JSONException("JSONArray[" + index + "] is not a number."); + } + } + + /** + * Get the string associated with an index. + * + * @param index The index must be between 0 and length() - 1. + * @return A string value. + * @throws JSONException If there is no string value for the index. + */ + public String getString(int index) throws JSONException { + Object object = this.get(index); + if (object instanceof String) { + return (String) object; + } + throw new JSONException("JSONArray[" + index + "] not a string."); + } + + /** + * Determine if the value is null. + * + * @param index The index must be between 0 and length() - 1. + * @return true if the value at the index is null, or if there is no value. + */ + public boolean isNull(int index) { + return JSONObject.NULL.equals(this.opt(index)); + } + + /** + * Make a string from the contents of this JSONArray. The + * separator string is inserted between each element. Warning: + * This method assumes that the data structure is acyclical. + * + * @param separator A string that will be inserted between the elements. + * @return a string. + * @throws JSONException If the array contains an invalid number. + */ + public String join(String separator) throws JSONException { + int len = this.length(); + StringBuffer sb = new StringBuffer(); + + for (int i = 0; i < len; i += 1) { + if (i > 0) { + sb.append(separator); + } + sb.append(JSONObject.valueToString(this.myArrayList.get(i))); + } + return sb.toString(); + } + + /** + * Get the number of elements in the JSONArray, included nulls. + * + * @return The length (or size). + */ + public int length() { + return this.myArrayList.size(); + } + + /** + * Get the optional object value associated with an index. + * + * @param index The index must be between 0 and length() - 1. + * @return An object value, or null if there is no object at that index. + */ + public Object opt(int index) { + return (index < 0 || index >= this.length()) ? null : this.myArrayList.get(index); + } + + /** + * Get the optional boolean value associated with an index. It returns false + * if there is no value at that index, or if the value is not Boolean.TRUE + * or the String "true". + * + * @param index The index must be between 0 and length() - 1. + * @return The truth. + */ + public boolean optBoolean(int index) { + return this.optBoolean(index, false); + } + + /** + * Get the optional boolean value associated with an index. It returns the + * defaultValue if there is no value at that index or if it is not a Boolean + * or the String "true" or "false" (case insensitive). + * + * @param index The index must be between 0 and length() - 1. + * @param defaultValue A boolean default. + * @return The truth. + */ + public boolean optBoolean(int index, boolean defaultValue) { + try { + return this.getBoolean(index); + } catch (Exception e) { + return defaultValue; + } + } + + /** + * Get the optional double value associated with an index. NaN is returned + * if there is no value for the index, or if the value is not a number and + * cannot be converted to a number. + * + * @param index The index must be between 0 and length() - 1. + * @return The value. + */ + public double optDouble(int index) { + return this.optDouble(index, Double.NaN); + } + + /** + * Get the optional double value associated with an index. The defaultValue + * is returned if there is no value for the index, or if the value is not a + * number and cannot be converted to a number. + * + * @param index subscript + * @param defaultValue The default value. + * @return The value. + */ + public double optDouble(int index, double defaultValue) { + try { + return this.getDouble(index); + } catch (Exception e) { + return defaultValue; + } + } + + /** + * Get the optional int value associated with an index. Zero is returned if + * there is no value for the index, or if the value is not a number and + * cannot be converted to a number. + * + * @param index The index must be between 0 and length() - 1. + * @return The value. + */ + public int optInt(int index) { + return this.optInt(index, 0); + } + + /** + * Get the optional int value associated with an index. The defaultValue is + * returned if there is no value for the index, or if the value is not a + * number and cannot be converted to a number. + * + * @param index The index must be between 0 and length() - 1. + * @param defaultValue The default value. + * @return The value. + */ + public int optInt(int index, int defaultValue) { + try { + return this.getInt(index); + } catch (Exception e) { + return defaultValue; + } + } + + /** + * Get the optional JSONArray associated with an index. + * + * @param index subscript + * @return A JSONArray value, or null if the index has no value, or if the + * value is not a JSONArray. + */ + public JSONArray optJSONArray(int index) { + Object o = this.opt(index); + return o instanceof JSONArray ? (JSONArray) o : null; + } + + /** + * Get the optional JSONObject associated with an index. Null is returned if + * the key is not found, or null if the index has no value, or if the value + * is not a JSONObject. + * + * @param index The index must be between 0 and length() - 1. + * @return A JSONObject value. + */ + public JSONObject optJSONObject(int index) { + Object o = this.opt(index); + return o instanceof JSONObject ? (JSONObject) o : null; + } + + /** + * Get the optional long value associated with an index. Zero is returned if + * there is no value for the index, or if the value is not a number and + * cannot be converted to a number. + * + * @param index The index must be between 0 and length() - 1. + * @return The value. + */ + public long optLong(int index) { + return this.optLong(index, 0); + } + + /** + * Get the optional long value associated with an index. The defaultValue is + * returned if there is no value for the index, or if the value is not a + * number and cannot be converted to a number. + * + * @param index The index must be between 0 and length() - 1. + * @param defaultValue The default value. + * @return The value. + */ + public long optLong(int index, long defaultValue) { + try { + return this.getLong(index); + } catch (Exception e) { + return defaultValue; + } + } + + /** + * Get the optional string value associated with an index. It returns an + * empty string if there is no value at that index. If the value is not a + * string and is not null, then it is coverted to a string. + * + * @param index The index must be between 0 and length() - 1. + * @return A String value. + */ + public String optString(int index) { + return this.optString(index, ""); + } + + /** + * Get the optional string associated with an index. The defaultValue is + * returned if the key is not found. + * + * @param index The index must be between 0 and length() - 1. + * @param defaultValue The default value. + * @return A String value. + */ + public String optString(int index, String defaultValue) { + Object object = this.opt(index); + return JSONObject.NULL.equals(object) ? defaultValue : object.toString(); + } + + /** + * Append a boolean value. This increases the array's length by one. + * + * @param value A boolean value. + * @return this. + */ + public JSONArray put(boolean value) { + this.put(value ? Boolean.TRUE : Boolean.FALSE); + return this; + } + + /** + * Put a value in the JSONArray, where the value will be a JSONArray which + * is produced from a Collection. + * + * @param value A Collection value. + * @return this. + */ + public JSONArray put(Collection value) { + this.put(new JSONArray(value)); + return this; + } + + /** + * Append a double value. This increases the array's length by one. + * + * @param value A double value. + * @throws JSONException if the value is not finite. + * @return this. + */ + public JSONArray put(double value) throws JSONException { + Double d = new Double(value); + JSONObject.testValidity(d); + this.put(d); + return this; + } + + /** + * Append an int value. This increases the array's length by one. + * + * @param value An int value. + * @return this. + */ + public JSONArray put(int value) { + this.put(new Integer(value)); + return this; + } + + /** + * Append an long value. This increases the array's length by one. + * + * @param value A long value. + * @return this. + */ + public JSONArray put(long value) { + this.put(new Long(value)); + return this; + } + + /** + * Put a value in the JSONArray, where the value will be a JSONObject which + * is produced from a Map. + * + * @param value A Map value. + * @return this. + */ + public JSONArray put(Map value) { + this.put(new JSONObject(value)); + return this; + } + + /** + * Append an object value. This increases the array's length by one. + * + * @param value An object value. The value should be a Boolean, Double, + * Integer, JSONArray, JSONObject, Long, or String, or the JSONObject.NULL + * object. + * @return this. + */ + public JSONArray put(Object value) { + this.myArrayList.add(value); + return this; + } + + /** + * Put or replace a boolean value in the JSONArray. If the index is greater + * than the length of the JSONArray, then null elements will be added as + * necessary to pad it out. + * + * @param index The subscript. + * @param value A boolean value. + * @return this. + * @throws JSONException If the index is negative. + */ + public JSONArray put(int index, boolean value) throws JSONException { + this.put(index, value ? Boolean.TRUE : Boolean.FALSE); + return this; + } + + /** + * Put a value in the JSONArray, where the value will be a JSONArray which + * is produced from a Collection. + * + * @param index The subscript. + * @param value A Collection value. + * @return this. + * @throws JSONException If the index is negative or if the value is not + * finite. + */ + public JSONArray put(int index, Collection value) throws JSONException { + this.put(index, new JSONArray(value)); + return this; + } + + /** + * Put or replace a double value. If the index is greater than the length of + * the JSONArray, then null elements will be added as necessary to pad it + * out. + * + * @param index The subscript. + * @param value A double value. + * @return this. + * @throws JSONException If the index is negative or if the value is not + * finite. + */ + public JSONArray put(int index, double value) throws JSONException { + this.put(index, new Double(value)); + return this; + } + + /** + * Put or replace an int value. If the index is greater than the length of + * the JSONArray, then null elements will be added as necessary to pad it + * out. + * + * @param index The subscript. + * @param value An int value. + * @return this. + * @throws JSONException If the index is negative. + */ + public JSONArray put(int index, int value) throws JSONException { + this.put(index, new Integer(value)); + return this; + } + + /** + * Put or replace a long value. If the index is greater than the length of + * the JSONArray, then null elements will be added as necessary to pad it + * out. + * + * @param index The subscript. + * @param value A long value. + * @return this. + * @throws JSONException If the index is negative. + */ + public JSONArray put(int index, long value) throws JSONException { + this.put(index, new Long(value)); + return this; + } + + /** + * Put a value in the JSONArray, where the value will be a JSONObject that + * is produced from a Map. + * + * @param index The subscript. + * @param value The Map value. + * @return this. + * @throws JSONException If the index is negative or if the the value is an + * invalid number. + */ + public JSONArray put(int index, Map value) throws JSONException { + this.put(index, new JSONObject(value)); + return this; + } + + /** + * Put or replace an object value in the JSONArray. If the index is greater + * than the length of the JSONArray, then null elements will be added as + * necessary to pad it out. + * + * @param index The subscript. + * @param value The value to put into the array. The value should be a + * Boolean, Double, Integer, JSONArray, JSONObject, Long, or String, or the + * JSONObject.NULL object. + * @return this. + * @throws JSONException If the index is negative or if the the value is an + * invalid number. + */ + public JSONArray put(int index, Object value) throws JSONException { + JSONObject.testValidity(value); + if (index < 0) { + throw new JSONException("JSONArray[" + index + "] not found."); + } + if (index < this.length()) { + this.myArrayList.set(index, value); + } else { + while (index != this.length()) { + this.put(JSONObject.NULL); + } + this.put(value); + } + return this; + } + + /** + * Remove an index and close the hole. + * + * @param index The index of the element to be removed. + * @return The value that was associated with the index, or null if there + * was no value. + */ + public Object remove(int index) { + Object o = this.opt(index); + this.myArrayList.remove(index); + return o; + } + + /** + * Produce a JSONObject by combining a JSONArray of names with the values of + * this JSONArray. + * + * @param names A JSONArray containing a list of key strings. These will be + * paired with the values. + * @return A JSONObject, or null if there are no names or if this JSONArray + * has no values. + * @throws JSONException If any of the names are null. + */ + public JSONObject toJSONObject(JSONArray names) throws JSONException { + if (names == null || names.length() == 0 || this.length() == 0) { + return null; + } + JSONObject jo = new JSONObject(); + for (int i = 0; i < names.length(); i += 1) { + jo.put(names.getString(i), this.opt(i)); + } + return jo; + } + + /** + * Make a JSON text of this JSONArray. For compactness, no unnecessary + * whitespace is added. If it is not possible to produce a syntactically + * correct JSON text then null will be returned instead. This could occur if + * the array contains an invalid number. + *

+ * Warning: This method assumes that the data structure is acyclical. + * + * @return a printable, displayable, transmittable representation of the + * array. + */ + @Override + public String toString() { + try { + return '[' + this.join(",") + ']'; + } catch (Exception e) { + return null; + } + } + + /** + * Make a prettyprinted JSON text of this JSONArray. Warning: This method + * assumes that the data structure is acyclical. + * + * @param indentFactor The number of spaces to add to each level of + * indentation. + * @return a printable, displayable, transmittable representation of the + * object, beginning with [ (left bracket) + * and ending with ]  (right bracket). + * @throws JSONException + */ + public String toString(int indentFactor) throws JSONException { + StringWriter sw = new StringWriter(); + synchronized (sw.getBuffer()) { + return this.write(sw, indentFactor, 0).toString(); + } + } + + /** + * Write the contents of the JSONArray as JSON text to a writer. For + * compactness, no whitespace is added. + *

+ * Warning: This method assumes that the data structure is acyclical. + * + * @return The writer. + * @throws JSONException + */ + public Writer write(Writer writer) throws JSONException { + return this.write(writer, 0, 0); + } + + /** + * Write the contents of the JSONArray as JSON text to a writer. For + * compactness, no whitespace is added. + *

+ * Warning: This method assumes that the data structure is acyclical. + * + * @param indentFactor The number of spaces to add to each level of + * indentation. + * @param indent The indention of the top level. + * @return The writer. + * @throws JSONException + */ + Writer write(Writer writer, int indentFactor, int indent) throws JSONException { + try { + boolean commanate = false; + int length = this.length(); + writer.write('['); + + if (length == 1) { + JSONObject.writeValue(writer, this.myArrayList.get(0), indentFactor, indent); + } else if (length != 0) { + final int newindent = indent + indentFactor; + + for (int i = 0; i < length; i += 1) { + if (commanate) { + writer.write(','); + } + if (indentFactor > 0) { + writer.write('\n'); + } + JSONObject.indent(writer, newindent); + JSONObject.writeValue(writer, this.myArrayList.get(i), indentFactor, newindent); + commanate = true; + } + if (indentFactor > 0) { + writer.write('\n'); + } + JSONObject.indent(writer, indent); + } + writer.write(']'); + return writer; + } catch (IOException e) { + throw new JSONException(e); + } + } +} diff --git a/src/main/java/org/json/JSONException.java b/src/main/java/org/json/JSONException.java new file mode 100644 index 000000000..64ae077a9 --- /dev/null +++ b/src/main/java/org/json/JSONException.java @@ -0,0 +1,26 @@ +package org.json; + +/** + * The JSONException is thrown by the JSON.org classes when things are amiss. + * + * @author JSON.org + * @version 2010-12-24 + */ +public class JSONException extends RuntimeException { + + private static final long serialVersionUID = 0; + + /** + * Constructs a JSONException with an explanatory message. + * + * @param message Detail about the reason for the exception. + */ + public JSONException(String message) { + super(message); + } + + public JSONException(Throwable cause) { + super(cause.getMessage()); + initCause(cause); + } +} diff --git a/src/main/java/org/json/JSONObject.java b/src/main/java/org/json/JSONObject.java new file mode 100644 index 000000000..d3fe1ccdd --- /dev/null +++ b/src/main/java/org/json/JSONObject.java @@ -0,0 +1,1590 @@ +package org.json; + +/* + Copyright (c) 2002 JSON.org + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + The Software shall be used for Good, not Evil. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. + */ + +import java.io.IOException; +import java.io.StringWriter; +import java.io.Writer; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.Collection; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Locale; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Objects; +import java.util.ResourceBundle; + + +/** + * A JSONObject is an unordered collection of name/value pairs. Its external + * form is a string wrapped in curly braces with colons between the names and + * values, and commas between the values and names. The internal form is an + * object having get and opt methods for accessing the + * values by name, and put methods for adding or replacing values + * by name. The values can be any of these types: Boolean, + * JSONArray, JSONObject, Number, + * String, or the JSONObject.NULL object. A JSONObject + * constructor can be used to convert an external form JSON text into an + * internal form whose values can be retrieved with the get and + * opt methods, or to convert values into a JSON text using the + * put and toString methods. A get method + * returns a value if one can be found, and throws an exception if one cannot be + * found. An opt method returns a default value instead of throwing + * an exception, and so is useful for obtaining optional values. + *

+ * The generic get() and opt() methods return an + * object, which you can cast or query for type. There are also typed + * get and opt methods that do type checking and type + * coercion for you. The opt methods differ from the get methods in that they do + * not throw. Instead, they return a specified value, such as null. + *

+ * The put methods add or replace values in an object. For example, + * + *

+ * myString = new JSONObject().put("JSON", "Hello, World!").toString();
+ * 
+ * + * produces the string {"JSON": "Hello, World"}. + *

+ * The texts produced by the toString methods strictly conform to + * the JSON syntax rules. The constructors are more forgiving in the texts they + * will accept: + *

    + *
  • An extra , (comma) may appear just + * before the closing brace.
  • + *
  • Strings may be quoted with ' (single + * quote).
  • + *
  • Strings do not need to be quoted at all if they do not begin with a quote + * or single quote, and if they do not contain leading or trailing spaces, and + * if they do not contain any of these characters: + * { } [ ] / \ : , = ; # and if they do not look like numbers and + * if they are not the reserved words true, false, or + * null.
  • + *
  • Keys can be followed by = or => as well as by + * :.
  • + *
  • Values can be followed by ; (semicolon) as + * well as by , (comma).
  • + *
+ * + * @author JSON.org + * @version 2012-04-20 + */ +public class JSONObject { + + /** + * JSONObject.NULL is equivalent to the value that JavaScript calls null, + * whilst Java's null is equivalent to the value that JavaScript calls + * undefined. + */ + private static final class Null { + + /** + * There is only intended to be a single instance of the NULL object, so + * the clone method returns itself. + * + * @return NULL. + */ + @Override + protected final Object clone() { + return this; + } + + /** + * A Null object is equal to the null value and to itself. + * + * @param object An object to test for nullness. + * @return true if the object parameter is the JSONObject.NULL object or + * null. + */ + @Override + public boolean equals(Object object) { + return object == null || object == this; + } + + /** + * Get the "null" string value. + * + * @return The string "null". + */ + @Override + public String toString() { + return "null"; + } + + @Override + public int hashCode() { + return 0; + } + } + + /** + * The map where the JSONObject's properties are kept. + */ + private final Map map; + + /** + * It is sometimes more convenient and less ambiguous to have a + * NULL object than to use Java's null value. + * JSONObject.NULL.equals(null) returns true. + * JSONObject.NULL.toString() returns "null". + */ + public static final Object NULL = new Null(); + + /** + * Construct an empty JSONObject. + */ + public JSONObject() { + this.map = new HashMap(); + } + + /** + * Construct a JSONObject from a subset of another JSONObject. An array of + * strings is used to identify the keys that should be copied. Missing keys + * are ignored. + * + * @param jo A JSONObject. + * @param names An array of strings. + * @throws JSONException + * @exception JSONException If a value is a non-finite number or if a name + * is duplicated. + */ + public JSONObject(JSONObject jo, String[] names) { + this(); + for (int i = 0; i < names.length; i += 1) { + try { + this.putOnce(names[i], jo.opt(names[i])); + } catch (Exception ignore) {} + } + } + + public JSONObject(String key, Object value) { + this(); + put(key, value); + } + + /** + * Construct a JSONObject from a JSONTokener. + * + * @param x A JSONTokener object containing the source string. + * @throws JSONException If there is a syntax error in the source string or + * a duplicated key. + */ + public JSONObject(JSONTokener x) throws JSONException { + this(); + char c; + String key; + + if (x.nextClean() != '{') { + throw x.syntaxError("A JSONObject text must begin with '{'"); + } + for (;;) { + c = x.nextClean(); + switch (c) { + case 0: + throw x.syntaxError("A JSONObject text must end with '}'"); + case '}': + return; + default: + x.back(); + key = x.nextValue().toString(); + } + + // The key is followed by ':'. We will also tolerate '=' or '=>'. + + c = x.nextClean(); + if (c == '=') { + if (x.next() != '>') { + x.back(); + } + } else if (c != ':') { + throw x.syntaxError("Expected a ':' after a key"); + } + this.putOnce(key, x.nextValue()); + + // Pairs are separated by ','. We will also tolerate ';'. + + switch (x.nextClean()) { + case ';': + case ',': + if (x.nextClean() == '}') { + return; + } + x.back(); + break; + case '}': + return; + default: + throw x.syntaxError("Expected a ',' or '}'"); + } + } + } + + /** + * Construct a JSONObject from a Map. + * + * @param map A map object that can be used to initialize the contents of + * the JSONObject. + * @throws JSONException + */ + public JSONObject(Map map) { + this.map = new HashMap(); + if (map != null) { + Iterator> i = map.entrySet().iterator(); + while (i.hasNext()) { + Map.Entry e = i.next(); + Object value = e.getValue(); + if (value != null) { + this.map.put(e.getKey(), wrap(value)); + } + } + } + } + + /** + * Construct a JSONObject from an Object using bean getters. It reflects on + * all of the public methods of the object. For each of the methods with no + * parameters and a name starting with "get" or + * "is" followed by an uppercase letter, the method is invoked, + * and a key and the value returned from the getter method are put into the + * new JSONObject. + * + * The key is formed by removing the "get" or "is" + * prefix. If the second remaining character is not upper case, then the + * first character is converted to lower case. + * + * For example, if an object has a method named "getName", and + * if the result of calling object.getName() is + * "Larry Fine", then the JSONObject will contain + * "name": "Larry Fine". + * + * @param bean An object that has getter methods that should be used to make + * a JSONObject. + */ + public JSONObject(Object bean) { + this(); + this.populateMap(bean); + } + + /** + * Construct a JSONObject from an Object, using reflection to find the + * public members. The resulting JSONObject's keys will be the strings from + * the names array, and the values will be the field values associated with + * those keys in the object. If a key is not found or not visible, then it + * will not be copied into the new JSONObject. + * + * @param object An object that has fields that should be used to make a + * JSONObject. + * @param names An array of strings, the names of the fields to be obtained + * from the object. + */ + public JSONObject(Object object, String names[]) { + this(); + Class c = object.getClass(); + for (int i = 0; i < names.length; i += 1) { + String name = names[i]; + try { + this.putOpt(name, c.getField(name).get(object)); + } catch (Exception ignore) {} + } + } + + /** + * Construct a JSONObject from a source JSON text string. This is the most + * commonly used JSONObject constructor. + * + * @param source A string beginning with { (left + * brace) and ending with }  (right + * brace). + * @exception JSONException If there is a syntax error in the source string + * or a duplicated key. + */ + public JSONObject(String source) throws JSONException { + this(new JSONTokener(source)); + } + + /** + * Construct a JSONObject from a ResourceBundle. + * + * @param baseName The ResourceBundle base name. + * @param locale The Locale to load the ResourceBundle for. + * @throws JSONException If any JSONExceptions are detected. + */ + public JSONObject(String baseName, Locale locale) throws JSONException { + this(); + ResourceBundle bundle = ResourceBundle + .getBundle(baseName, locale, Thread.currentThread().getContextClassLoader()); + + // Iterate through the keys in the bundle. + + Enumeration keys = bundle.getKeys(); + while (keys.hasMoreElements()) { + Object key = keys.nextElement(); + if (key instanceof String) { + + // Go through the path, ensuring that there is a nested + // JSONObject for each + // segment except the last. Add the value using the last + // segment's name into + // the deepest nested JSONObject. + + String[] path = ((String) key).split("\\."); + int last = path.length - 1; + JSONObject target = this; + for (int i = 0; i < last; i += 1) { + String segment = path[i]; + JSONObject nextTarget = target.optJSONObject(segment); + if (nextTarget == null) { + nextTarget = new JSONObject(); + target.put(segment, nextTarget); + } + target = nextTarget; + } + target.put(path[last], bundle.getString((String) key)); + } + } + } + + /** + * Accumulate values under a key. It is similar to the put method except + * that if there is already an object stored under the key then a JSONArray + * is stored under the key to hold all of the accumulated values. If there + * is already a JSONArray, then the new value is appended to it. In + * contrast, the put method replaces the previous value. + * + * If only one value is accumulated that is not a JSONArray, then the result + * will be the same as using put. But if multiple values are accumulated, + * then the result will be like append. + * + * @param key A key string. + * @param value An object to be accumulated under the key. + * @return this. + * @throws JSONException If the value is an invalid number or if the key is + * null. + */ + public JSONObject accumulate(String key, Object value) throws JSONException { + testValidity(value); + Object object = this.opt(key); + if (object == null) { + this.put(key, value instanceof JSONArray ? new JSONArray().put(value) : value); + } else if (object instanceof JSONArray) { + ((JSONArray) object).put(value); + } else { + this.put(key, new JSONArray().put(object).put(value)); + } + return this; + } + + /** + * Append values to the array under a key. If the key does not exist in the + * JSONObject, then the key is put in the JSONObject with its value being a + * JSONArray containing the value parameter. If the key was already + * associated with a JSONArray, then the value parameter is appended to it. + * + * @param key A key string. + * @param value An object to be accumulated under the key. + * @return this. + * @throws JSONException If the key is null or if the current value + * associated with the key is not a JSONArray. + */ + public JSONObject append(String key, Object value) throws JSONException { + testValidity(value); + Object object = this.opt(key); + if (object == null) { + this.put(key, new JSONArray().put(value)); + } else if (object instanceof JSONArray) { + this.put(key, ((JSONArray) object).put(value)); + } else { + throw new JSONException("JSONObject[" + key + "] is not a JSONArray."); + } + return this; + } + + public Map getMap() { + return map; + } + + /** + * Produce a string from a double. The string "null" will be returned if the + * number is not finite. + * + * @param d A double. + * @return A String. + */ + public static String doubleToString(double d) { + if (Double.isInfinite(d) || Double.isNaN(d)) { + return "null"; + } + + // Shave off trailing zeros and decimal point, if possible. + + String string = Double.toString(d); + if (string.indexOf('.') > 0 && string.indexOf('e') < 0 && string.indexOf('E') < 0) { + while (string.endsWith("0")) { + string = string.substring(0, string.length() - 1); + } + if (string.endsWith(".")) { + string = string.substring(0, string.length() - 1); + } + } + return string; + } + + /** + * Get the value object associated with a key. + * + * @param key A key string. + * @return The object associated with the key. + * @throws JSONException if the key is not found. + */ + public Object get(String key) throws JSONException { + if (key == null) { + throw new JSONException("Null key."); + } + Object object = this.opt(key); + if (object == null) { + throw new JSONException("JSONObject[" + quote(key) + "] not found."); + } + return object; + } + + /** + * Get the boolean value associated with a key. + * + * @param key A key string. + * @return The truth. + * @throws JSONException if the value is not a Boolean or the String "true" + * or "false". + */ + public boolean getBoolean(String key) throws JSONException { + Object object = this.get(key); + if ( + object.equals(Boolean.FALSE) + || (object instanceof String && ((String) object).equalsIgnoreCase("false")) + ) { + return false; + } else if ( + object.equals(Boolean.TRUE) + || (object instanceof String && ((String) object).equalsIgnoreCase("true")) + ) { + return true; + } + throw new JSONException("JSONObject[" + quote(key) + "] is not a Boolean."); + } + + /** + * Get the double value associated with a key. + * + * @param key A key string. + * @return The numeric value. + * @throws JSONException if the key is not found or if the value is not a + * Number object and cannot be converted to a number. + */ + public double getDouble(String key) throws JSONException { + Object object = this.get(key); + try { + return object instanceof Number + ? ((Number) object).doubleValue() + : Double.parseDouble((String) object); + } catch (Exception e) { + throw new JSONException("JSONObject[" + quote(key) + "] is not a number."); + } + } + + public float getFloat(String key) throws JSONException { + Object object = this.get(key); + try { + return object instanceof Number + ? ((Number) object).floatValue() + : Float.parseFloat((String) object); + } catch (Exception e) { + throw new JSONException("JSONObject[" + quote(key) + "] is not a number."); + } + } + + /** + * Get the int value associated with a key. + * + * @param key A key string. + * @return The integer value. + * @throws JSONException if the key is not found or if the value cannot be + * converted to an integer. + */ + public int getInt(String key) throws JSONException { + Object object = this.get(key); + try { + return object instanceof Number + ? ((Number) object).intValue() + : Integer.parseInt((String) object); + } catch (Exception e) { + throw new JSONException("JSONObject[" + quote(key) + "] is not an int."); + } + } + + /** + * Get the JSONArray value associated with a key. + * + * @param key A key string. + * @return A JSONArray which is the value. + * @throws JSONException if the key is not found or if the value is not a + * JSONArray. + */ + public JSONArray getJSONArray(String key) throws JSONException { + Object object = this.get(key); + if (object instanceof JSONArray) { + return (JSONArray) object; + } + throw new JSONException("JSONObject[" + quote(key) + "] is not a JSONArray."); + } + + /** + * Get the JSONObject value associated with a key. + * + * @param key A key string. + * @return A JSONObject which is the value. + * @throws JSONException if the key is not found or if the value is not a + * JSONObject. + */ + public JSONObject getJSONObject(String key) throws JSONException { + Object object = this.get(key); + if (object instanceof JSONObject) { + return (JSONObject) object; + } + throw new JSONException("JSONObject[" + quote(key) + "] is not a JSONObject."); + } + + /** + * Get the long value associated with a key. + * + * @param key A key string. + * @return The long value. + * @throws JSONException if the key is not found or if the value cannot be + * converted to a long. + */ + public long getLong(String key) throws JSONException { + Object object = this.get(key); + try { + return object instanceof Number + ? ((Number) object).longValue() + : Long.parseLong((String) object); + } catch (Exception e) { + throw new JSONException("JSONObject[" + quote(key) + "] is not a long."); + } + } + + /** + * Get an array of field names from a JSONObject. + * + * @return An array of field names, or null if there are no names. + */ + public static String[] getNames(JSONObject jo) { + int length = jo.length(); + if (length == 0) { + return null; + } + Iterator iterator = jo.keys(); + String[] names = new String[length]; + int i = 0; + while (iterator.hasNext()) { + names[i] = iterator.next(); + i += 1; + } + return names; + } + + /** + * Get an array of field names from an Object. + * + * @return An array of field names, or null if there are no names. + */ + public static String[] getNames(Object object) { + if (object == null) { + return null; + } + Class klass = object.getClass(); + Field[] fields = klass.getFields(); + int length = fields.length; + if (length == 0) { + return null; + } + String[] names = new String[length]; + for (int i = 0; i < length; i += 1) { + names[i] = fields[i].getName(); + } + return names; + } + + /** + * Get the string associated with a key. + * + * @param key A key string. + * @return A string which is the value. + * @throws JSONException if there is no string value for the key. + */ + public String getString(String key) throws JSONException { + Object object = this.get(key); + if (object instanceof String) { + return (String) object; + } + throw new JSONException("JSONObject[" + quote(key) + "] not a string."); + } + + /** + * Determine if the JSONObject contains a specific key. + * + * @param key A key string. + * @return true if the key exists in the JSONObject. + */ + public boolean has(String key) { + return this.map.containsKey(key); + } + + /** + * Increment a property of a JSONObject. If there is no such property, + * create one with a value of 1. If there is such a property, and if it is + * an Integer, Long, Double, or Float, then add one to it. + * + * @param key A key string. + * @return this. + * @throws JSONException If there is already a property with this name that + * is not an Integer, Long, Double, or Float. + */ + public JSONObject increment(String key) throws JSONException { + Object value = this.opt(key); + if (value == null) { + this.put(key, 1); + } else if (value instanceof Integer) { + this.put(key, ((Integer) value).intValue() + 1); + } else if (value instanceof Long) { + this.put(key, ((Long) value).longValue() + 1); + } else if (value instanceof Double) { + this.put(key, ((Double) value).doubleValue() + 1); + } else if (value instanceof Float) { + this.put(key, ((Float) value).floatValue() + 1); + } else { + throw new JSONException("Unable to increment [" + quote(key) + "]."); + } + return this; + } + + /** + * Determine if the value associated with the key is null or if there is no + * value. + * + * @param key A key string. + * @return true if there is no value associated with the key or if the value + * is the JSONObject.NULL object. + */ + public boolean isNull(String key) { + return JSONObject.NULL.equals(this.opt(key)); + } + + /** + * Get an enumeration of the keys of the JSONObject. + * + * @return An iterator of the keys. + */ + public Iterator keys() { + return this.map.keySet().iterator(); + } + + /** + * Get the number of keys stored in the JSONObject. + * + * @return The number of keys in the JSONObject. + */ + public int length() { + return this.map.size(); + } + + /** + * Produce a JSONArray containing the names of the elements of this + * JSONObject. + * + * @return A JSONArray containing the key strings, or null if the JSONObject + * is empty. + */ + public JSONArray names() { + JSONArray ja = new JSONArray(); + Iterator keys = this.keys(); + while (keys.hasNext()) { + ja.put(keys.next()); + } + return ja.length() == 0 ? null : ja; + } + + /** + * Produce a string from a Number. + * + * @param number A Number + * @return A String. + * @throws JSONException If n is a non-finite number. + */ + public static String numberToString(Number number) throws JSONException { + if (number == null) { + throw new JSONException("Null pointer"); + } + testValidity(number); + + // Shave off trailing zeros and decimal point, if possible. + + String string = number.toString(); + if (string.indexOf('.') > 0 && string.indexOf('e') < 0 && string.indexOf('E') < 0) { + while (string.endsWith("0")) { + string = string.substring(0, string.length() - 1); + } + if (string.endsWith(".")) { + string = string.substring(0, string.length() - 1); + } + } + return string; + } + + /** + * Get an optional value associated with a key. + * + * @param key A key string. + * @return An object which is the value, or null if there is no value. + */ + public Object opt(String key) { + return key == null ? null : this.map.get(key); + } + + /** + * Get an optional boolean associated with a key. It returns false if there + * is no such key, or if the value is not Boolean.TRUE or the String "true". + * + * @param key A key string. + * @return The truth. + */ + public boolean optBoolean(String key) { + return this.optBoolean(key, false); + } + + /** + * Get an optional boolean associated with a key. It returns the + * defaultValue if there is no such key, or if it is not a Boolean or the + * String "true" or "false" (case insensitive). + * + * @param key A key string. + * @param defaultValue The default. + * @return The truth. + */ + public boolean optBoolean(String key, boolean defaultValue) { + try { + return this.getBoolean(key); + } catch (Exception e) { + return defaultValue; + } + } + + /** + * Get an optional double associated with a key, or NaN if there is no such + * key or if its value is not a number. If the value is a string, an attempt + * will be made to evaluate it as a number. + * + * @param key A string which is the key. + * @return An object which is the value. + */ + public double optDouble(String key) { + return this.optDouble(key, Double.NaN); + } + + public float optFloat(String key) { + return this.optFloat(key, Float.NaN); + } + + /** + * Get an optional double associated with a key, or the defaultValue if + * there is no such key or if its value is not a number. If the value is a + * string, an attempt will be made to evaluate it as a number. + * + * @param key A key string. + * @param defaultValue The default. + * @return An object which is the value. + */ + public double optDouble(String key, double defaultValue) { + try { + return this.getDouble(key); + } catch (Exception e) { + return defaultValue; + } + } + + public float optFloat(String key, float defaultValue) { + try { + return this.getFloat(key); + } catch (Exception e) { + return defaultValue; + } + } + + /** + * Get an optional int value associated with a key, or zero if there is no + * such key or if the value is not a number. If the value is a string, an + * attempt will be made to evaluate it as a number. + * + * @param key A key string. + * @return An object which is the value. + */ + public int optInt(String key) { + return this.optInt(key, 0); + } + + /** + * Get an optional int value associated with a key, or the default if there + * is no such key or if the value is not a number. If the value is a string, + * an attempt will be made to evaluate it as a number. + * + * @param key A key string. + * @param defaultValue The default. + * @return An object which is the value. + */ + public int optInt(String key, int defaultValue) { + try { + return this.getInt(key); + } catch (Exception e) { + return defaultValue; + } + } + + /** + * Get an optional JSONArray associated with a key. It returns null if there + * is no such key, or if its value is not a JSONArray. + * + * @param key A key string. + * @return A JSONArray which is the value. + */ + public JSONArray optJSONArray(String key) { + Object o = this.opt(key); + return o instanceof JSONArray ? (JSONArray) o : null; + } + + /** + * Get an optional JSONObject associated with a key. It returns null if + * there is no such key, or if its value is not a JSONObject. + * + * @param key A key string. + * @return A JSONObject which is the value. + */ + public JSONObject optJSONObject(String key) { + Object object = this.opt(key); + return object instanceof JSONObject ? (JSONObject) object : null; + } + + /** + * Get an optional long value associated with a key, or zero if there is no + * such key or if the value is not a number. If the value is a string, an + * attempt will be made to evaluate it as a number. + * + * @param key A key string. + * @return An object which is the value. + */ + public long optLong(String key) { + return this.optLong(key, 0); + } + + /** + * Get an optional long value associated with a key, or the default if there + * is no such key or if the value is not a number. If the value is a string, + * an attempt will be made to evaluate it as a number. + * + * @param key A key string. + * @param defaultValue The default. + * @return An object which is the value. + */ + public long optLong(String key, long defaultValue) { + try { + return this.getLong(key); + } catch (Exception e) { + return defaultValue; + } + } + + /** + * Get an optional string associated with a key. It returns an empty string + * if there is no such key. If the value is not a string and is not null, + * then it is converted to a string. + * + * @param key A key string. + * @return A string which is the value. + */ + public String optString(String key) { + return this.optString(key, ""); + } + + /** + * Get an optional string associated with a key. It returns the defaultValue + * if there is no such key. + * + * @param key A key string. + * @param defaultValue The default. + * @return A string which is the value. + */ + public String optString(String key, String defaultValue) { + Object object = this.opt(key); + return NULL.equals(object) ? defaultValue : object.toString(); + } + + private void populateMap(Object bean) { + Class klass = bean.getClass(); + + // If klass is a System class then set includeSuperClass to false. + + boolean includeSuperClass = klass.getClassLoader() != null; + + Method[] methods = includeSuperClass ? klass.getMethods() : klass.getDeclaredMethods(); + for (int i = 0; i < methods.length; i += 1) { + try { + Method method = methods[i]; + if (Modifier.isPublic(method.getModifiers())) { + String name = method.getName(); + String key = ""; + if (name.startsWith("get")) { + if ("getClass".equals(name) || "getDeclaringClass".equals(name)) { + key = ""; + } else { + key = name.substring(3); + } + } else if (name.startsWith("is")) { + key = name.substring(2); + } + if ( + key.length() > 0 + && Character.isUpperCase(key.charAt(0)) + && method.getParameterTypes().length == 0 + ) { + if (key.length() == 1) { + key = key.toLowerCase(); + } else if (!Character.isUpperCase(key.charAt(1))) { + key = key.substring(0, 1).toLowerCase() + key.substring(1); + } + + Object result = method.invoke(bean, (Object[]) null); + if (result != null) { + this.map.put(key, wrap(result)); + } + } + } + } catch (Exception ignore) {} + } + } + + /** + * Put a key/boolean pair in the JSONObject. + * + * @param key A key string. + * @param value A boolean which is the value. + * @return this. + * @throws JSONException If the key is null. + */ + public JSONObject put(String key, boolean value) { + this.put(key, value ? Boolean.TRUE : Boolean.FALSE); + return this; + } + + /** + * Put a key/value pair in the JSONObject, where the value will be a + * JSONArray which is produced from a Collection. + * + * @param key A key string. + * @param value A Collection value. + * @return this. + * @throws JSONException + */ + public JSONObject put(String key, Collection value) { + this.put(key, new JSONArray(value)); + return this; + } + + /** + * Put a key/double pair in the JSONObject. + * + * @param key A key string. + * @param value A double which is the value. + * @return this. + * @throws JSONException If the key is null or if the number is invalid. + */ + public JSONObject put(String key, double value) { + this.put(key, new Double(value)); + return this; + } + + /** + * Put a key/int pair in the JSONObject. + * + * @param key A key string. + * @param value An int which is the value. + * @return this. + * @throws JSONException If the key is null. + */ + public JSONObject put(String key, int value) { + this.put(key, new Integer(value)); + return this; + } + + /** + * Put a key/long pair in the JSONObject. + * + * @param key A key string. + * @param value A long which is the value. + * @return this. + * @throws JSONException If the key is null. + */ + public JSONObject put(String key, long value) { + this.put(key, new Long(value)); + return this; + } + + /** + * Put a key/value pair in the JSONObject, where the value will be a + * JSONObject which is produced from a Map. + * + * @param key A key string. + * @param value A Map value. + * @return this. + * @throws JSONException + */ + public JSONObject put(String key, Map value) { + this.put(key, new JSONObject(value)); + return this; + } + + /** + * Put a key/value pair in the JSONObject. If the value is null, then the + * key will be removed from the JSONObject if it is present. + * + * @param key A key string. + * @param value An object which is the value. It should be of one of these + * types: Boolean, Double, Integer, JSONArray, JSONObject, Long, String, or + * the JSONObject.NULL object. + * @return this. + * @throws JSONException If the value is non-finite number or if the key is + * null. + */ + public JSONObject put(String key, Object value) { + Objects.requireNonNull(key); + if (value != null) { + testValidity(value); + this.map.put(key, value); + } else { + this.remove(key); + } + return this; + } + + /** + * Put a key/value pair in the JSONObject, but only if the key and the value + * are both non-null, and only if there is not already a member with that + * name. + * + * @param key + * @param value + * @return his. + * @throws JSONException if the key is a duplicate + */ + public JSONObject putOnce(String key, Object value) throws JSONException { + if (key != null && value != null) { + if (this.opt(key) != null) { + throw new JSONException("Duplicate key \"" + key + "\""); + } + this.put(key, value); + } + return this; + } + + /** + * Put a key/value pair in the JSONObject, but only if the key and the value + * are both non-null. + * + * @param key A key string. + * @param value An object which is the value. It should be of one of these + * types: Boolean, Double, Integer, JSONArray, JSONObject, Long, String, or + * the JSONObject.NULL object. + * @return this. + * @throws JSONException If the value is a non-finite number. + */ + public JSONObject putOpt(String key, Object value) { + if (key != null && value != null) { + this.put(key, value); + } + return this; + } + + /** + * Produce a string in double quotes with backslash sequences in all the + * right places. A backslash will be inserted within = '\u0080' && c < '\u00a0') + || (c >= '\u2000' && c < '\u2100') + ) { + hhhh = "000" + Integer.toHexString(c); + w.write("\\u" + hhhh.substring(hhhh.length() - 4)); + } else { + w.write(c); + } + } + } + w.write('"'); + return w; + } + + /** + * Remove a name and its value, if present. + * + * @param key The name to be removed. + * @return The value that was associated with the name, or null if there was + * no value. + */ + public Object remove(String key) { + return this.map.remove(key); + } + + /** + * Try to convert a string into a number, boolean, or null. If the string + * can't be converted, return the string. + * + * @param string A String. + * @return A simple JSON value. + */ + public static Object stringToValue(String string) { + Double d; + if (string.equals("")) { + return string; + } + if (string.equalsIgnoreCase("true")) { + return Boolean.TRUE; + } + if (string.equalsIgnoreCase("false")) { + return Boolean.FALSE; + } + if (string.equalsIgnoreCase("null")) { + return JSONObject.NULL; + } + + /* + * If it might be a number, try converting it. If a number cannot be + * produced, then the value will just be a string. Note that the plus + * and implied string conventions are non-standard. A JSON parser may + * accept non-JSON forms as long as it accepts all correct JSON forms. + */ + + char b = string.charAt(0); + if ((b >= '0' && b <= '9') || b == '.' || b == '-' || b == '+') { + try { + if ( + string.indexOf('.') > -1 || string.indexOf('e') > -1 || string.indexOf('E') > -1 + ) { + d = Double.valueOf(string); + if (!d.isInfinite() && !d.isNaN()) { + return d; + } + } else { + Long myLong = new Long(string); + if (myLong.longValue() == myLong.intValue()) { + return new Integer(myLong.intValue()); + } else { + return myLong; + } + } + } catch (Exception ignore) {} + } + return string; + } + + /** + * Throw an exception if the object is a NaN or infinite number. + * + * @param o The object to test. + * @throws JSONException If o is a non-finite number. + */ + public static void testValidity(Object o) { + if (o != null) { + if (o instanceof Double) { + if (((Double) o).isInfinite() || ((Double) o).isNaN()) { + throw new NumberFormatException("JSON does not allow non-finite numbers."); + } + } else if (o instanceof Float) { + if (((Float) o).isInfinite() || ((Float) o).isNaN()) { + throw new NumberFormatException("JSON does not allow non-finite numbers."); + } + } + } + } + + /** + * Produce a JSONArray containing the values of the members of this + * JSONObject. + * + * @param names A JSONArray containing a list of key strings. This + * determines the sequence of the values in the result. + * @return A JSONArray of values. + * @throws JSONException If any of the values are non-finite numbers. + */ + public JSONArray toJSONArray(JSONArray names) throws JSONException { + if (names == null || names.length() == 0) { + return null; + } + JSONArray ja = new JSONArray(); + for (int i = 0; i < names.length(); i += 1) { + ja.put(this.opt(names.getString(i))); + } + return ja; + } + + /** + * Make a JSON text of this JSONObject. For compactness, no whitespace is + * added. If this would not result in a syntactically correct JSON text, + * then null will be returned instead. + *

+ * Warning: This method assumes that the data structure is acyclical. + * + * @return a printable, displayable, portable, transmittable representation + * of the object, beginning with { (left + * brace) and ending with } (right + * brace). + */ + @Override + public String toString() { + try { + return this.toString(0); + } catch (Exception e) { + e.printStackTrace(); + return null; + } + } + + /** + * Make a prettyprinted JSON text of this JSONObject. + *

+ * Warning: This method assumes that the data structure is acyclical. + * + * @param indentFactor The number of spaces to add to each level of + * indentation. + * @return a printable, displayable, portable, transmittable representation + * of the object, beginning with { (left + * brace) and ending with } (right + * brace). + * @throws JSONException If the object contains an invalid number. + */ + public String toString(int indentFactor) throws JSONException { + StringWriter w = new StringWriter(); + synchronized (w.getBuffer()) { + return this.write(w, indentFactor, 0).toString(); + } + } + + /** + * Make a JSON text of an Object value. If the object has an + * value.toJSONString() method, then that method will be used to produce the + * JSON text. The method is required to produce a strictly conforming text. + * If the object does not contain a toJSONString method (which is the most + * common case), then a text will be produced by other means. If the value + * is an array or Collection, then a JSONArray will be made from it and its + * toJSONString method will be called. If the value is a MAP, then a + * JSONObject will be made from it and its toJSONString method will be + * called. Otherwise, the value's toString method will be called, and the + * result will be quoted. + * + *

+ * Warning: This method assumes that the data structure is acyclical. + * + * @param value The value to be serialized. + * @return a printable, displayable, transmittable representation of the + * object, beginning with { (left brace) + * and ending with } (right brace). + * @throws JSONException If the value is or contains an invalid number. + */ + @SuppressWarnings("unchecked") + public static String valueToString(Object value) throws JSONException { + if (value == null || value.equals(null)) { + return "null"; + } + if (value instanceof JSONString) { + Object object; + try { + object = ((JSONString) value).toJSONString(); + } catch (Exception e) { + throw new JSONException(e); + } + if (object instanceof String) { + return (String) object; + } + throw new JSONException("Bad value from toJSONString: " + object); + } + if (value instanceof Number) { + return numberToString((Number) value); + } + if (value instanceof Boolean || value instanceof JSONObject || value instanceof JSONArray) { + return value.toString(); + } + if (value instanceof Map) { + return new JSONObject(value).toString(); + } + if (value instanceof Collection) { + return new JSONArray((Collection) value).toString(); + } + if (value.getClass().isArray()) { + return new JSONArray(value).toString(); + } + return quote(value.toString()); + } + + /** + * Wrap an object, if necessary. If the object is null, return the NULL + * object. If it is an array or collection, wrap it in a JSONArray. If it is + * a map, wrap it in a JSONObject. If it is a standard property (Double, + * String, et al) then it is already wrapped. Otherwise, if it comes from + * one of the java packages, turn it into a string. And if it doesn't, try + * to wrap it in a JSONObject. If the wrapping fails, then null is returned. + * + * @param object The object to wrap + * @return The wrapped value + */ + @SuppressWarnings("unchecked") + public static Object wrap(Object object) { + try { + if (object == null) { + return NULL; + } + if ( + object instanceof JSONObject + || object instanceof JSONArray + || NULL.equals(object) + || object instanceof JSONString + || object instanceof Byte + || object instanceof Character + || object instanceof Short + || object instanceof Integer + || object instanceof Long + || object instanceof Boolean + || object instanceof Float + || object instanceof Double + || object instanceof String + ) { + return object; + } + + if (object instanceof Collection) { + return new JSONArray((Collection) object); + } + if (object.getClass().isArray()) { + return new JSONArray(object); + } + if (object instanceof Map) { + return new JSONObject(object); + } + Package objectPackage = object.getClass().getPackage(); + String objectPackageName = objectPackage != null ? objectPackage.getName() : ""; + if ( + objectPackageName.startsWith("java.") + || objectPackageName.startsWith("javax.") + || object.getClass().getClassLoader() == null + ) { + return object.toString(); + } + return new JSONObject(object); + } catch (Exception exception) { + return null; + } + } + + /** + * Write the contents of the JSONObject as JSON text to a writer. For + * compactness, no whitespace is added. + *

+ * Warning: This method assumes that the data structure is acyclical. + * + * @return The writer. + * @throws JSONException + */ + public Writer write(Writer writer) throws JSONException { + return this.write(writer, 0, 0); + } + + public Writer writeWithIdent(Writer writer) throws JSONException { + return this.write(writer, 1, 0); + } + + @SuppressWarnings("unchecked") + static final Writer writeValue(Writer writer, Object value, int indentFactor, int indent) + throws JSONException, IOException { + if (value == null || value.equals(null)) { + writer.write("null"); + } else if (value instanceof JSONObject) { + ((JSONObject) value).write(writer, indentFactor, indent); + } else if (value instanceof JSONArray) { + ((JSONArray) value).write(writer, indentFactor, indent); + } else if (value instanceof Map) { + new JSONObject(value).write(writer, indentFactor, indent); + } else if (value instanceof Collection) { + new JSONArray((Collection) value).write(writer, indentFactor, indent); + } else if (value.getClass().isArray()) { + new JSONArray(value).write(writer, indentFactor, indent); + } else if (value instanceof Number) { + writer.write(numberToString((Number) value)); + } else if (value instanceof Boolean) { + writer.write(value.toString()); + } else if (value instanceof JSONString) { + Object o; + try { + o = ((JSONString) value).toJSONString(); + } catch (Exception e) { + throw new JSONException(e); + } + writer.write(o != null ? o.toString() : quote(value.toString())); + } else { + quote(value.toString(), writer); + } + return writer; + } + + static final void indent(Writer writer, int indent) throws IOException { + for (int i = 0; i < indent; i += 1) { + writer.write('\t'); + } + } + + /** + * Write the contents of the JSONObject as JSON text to a writer. For + * compactness, no whitespace is added. + *

+ * Warning: This method assumes that the data structure is acyclical. + * + * @return The writer. + * @throws JSONException + */ + Writer write(Writer writer, int indentFactor, int indent) throws JSONException { + try { + boolean commanate = false; + final int length = this.length(); + Iterator keys = this.keys(); + writer.write('{'); + + if (length == 1) { + Object key = keys.next(); + writer.write(quote(key.toString())); + writer.write(':'); + if (indentFactor > 0) { + writer.write(' '); + } + writeValue(writer, this.map.get(key), indentFactor, indent); + } else if (length != 0) { + final int newindent = indent + indentFactor; + while (keys.hasNext()) { + Object key = keys.next(); + if (commanate) { + writer.write(','); + } + if (indentFactor > 0) { + writer.write('\n'); + } + indent(writer, newindent); + writer.write(quote(key.toString())); + writer.write(':'); + if (indentFactor > 0) { + writer.write(' '); + } + writeValue(writer, this.map.get(key), indentFactor, newindent); + commanate = true; + } + if (indentFactor > 0) { + writer.write('\n'); + } + indent(writer, indent); + } + writer.write('}'); + return writer; + } catch (IOException exception) { + throw new JSONException(exception); + } + } +} diff --git a/src/main/java/org/json/JSONString.java b/src/main/java/org/json/JSONString.java new file mode 100644 index 000000000..8b62ea310 --- /dev/null +++ b/src/main/java/org/json/JSONString.java @@ -0,0 +1,19 @@ +package org.json; + +/** + * The JSONString interface allows a toJSONString() + * method so that a class can change the behavior of + * JSONObject.toString(), JSONArray.toString(), and + * JSONWriter.value(Object). The + * toJSONString method will be used instead of the default behavior + * of using the Object's toString() method and quoting the result. + */ +public interface JSONString { + /** + * The toJSONString method allows a class to produce its own + * JSON serialization. + * + * @return A strictly syntactically correct JSON text. + */ + public String toJSONString(); +} diff --git a/src/main/java/org/json/JSONTokener.java b/src/main/java/org/json/JSONTokener.java new file mode 100644 index 000000000..1d95bf522 --- /dev/null +++ b/src/main/java/org/json/JSONTokener.java @@ -0,0 +1,434 @@ +package org.json; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.Reader; +import java.io.StringReader; + +/* + Copyright (c) 2002 JSON.org + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + The Software shall be used for Good, not Evil. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. + */ + + +/** + * A JSONTokener takes a source string and extracts characters and tokens from + * it. It is used by the JSONObject and JSONArray constructors to parse JSON + * source strings. + * + * @author JSON.org + * @version 2012-02-16 + */ +public class JSONTokener { + + private long character; + private boolean eof; + private long index; + private long line; + private char previous; + private Reader reader; + private boolean usePrevious; + + /** + * Construct a JSONTokener from a Reader. + * + * @param reader A reader. + */ + public JSONTokener(Reader reader) { + this.reader = reader.markSupported() ? reader : new BufferedReader(reader); + this.eof = false; + this.usePrevious = false; + this.previous = 0; + this.index = 0; + this.character = 1; + this.line = 1; + } + + /** + * Construct a JSONTokener from an InputStream. + */ + public JSONTokener(InputStream inputStream) throws JSONException { + this(new InputStreamReader(inputStream)); + } + + /** + * Construct a JSONTokener from a string. + * + * @param s A source string. + */ + public JSONTokener(String s) { + this(new StringReader(s)); + } + + /** + * Back up one character. This provides a sort of lookahead capability, so + * that you can test for a digit or letter before attempting to parse the + * next number or identifier. + */ + public void back() throws JSONException { + if (this.usePrevious || this.index <= 0) { + throw new JSONException("Stepping back two steps is not supported"); + } + this.index -= 1; + this.character -= 1; + this.usePrevious = true; + this.eof = false; + } + + /** + * Get the hex value of a character (base16). + * + * @param c A character between '0' and '9' or between 'A' and 'F' or + * between 'a' and 'f'. + * @return An int between 0 and 15, or -1 if c was not a hex digit. + */ + public static int dehexchar(char c) { + if (c >= '0' && c <= '9') { + return c - '0'; + } + if (c >= 'A' && c <= 'F') { + return c - ('A' - 10); + } + if (c >= 'a' && c <= 'f') { + return c - ('a' - 10); + } + return -1; + } + + public boolean end() { + return this.eof && !this.usePrevious; + } + + /** + * Determine if the source string still contains characters that next() can + * consume. + * + * @return true if not yet at the end of the source. + */ + public boolean more() throws JSONException { + this.next(); + if (this.end()) { + return false; + } + this.back(); + return true; + } + + /** + * Get the next character in the source string. + * + * @return The next character, or 0 if past the end of the source string. + */ + public char next() throws JSONException { + int c; + if (this.usePrevious) { + this.usePrevious = false; + c = this.previous; + } else { + try { + c = this.reader.read(); + } catch (IOException exception) { + throw new JSONException(exception); + } + + if (c <= 0) { // End of stream + this.eof = true; + c = 0; + } + } + this.index += 1; + if (this.previous == '\r') { + this.line += 1; + this.character = c == '\n' ? 0 : 1; + } else if (c == '\n') { + this.line += 1; + this.character = 0; + } else { + this.character += 1; + } + this.previous = (char) c; + return this.previous; + } + + /** + * Consume the next character, and check that it matches a specified + * character. + * + * @param c The character to match. + * @return The character. + * @throws JSONException if the character does not match. + */ + public char next(char c) throws JSONException { + char n = this.next(); + if (n != c) { + throw this.syntaxError("Expected '" + c + "' and instead saw '" + n + "'"); + } + return n; + } + + /** + * Get the next n characters. + * + * @param n The number of characters to take. + * @return A string of n characters. + * @throws JSONException Substring bounds error if there are not n + * characters remaining in the source string. + */ + public String next(int n) throws JSONException { + if (n == 0) { + return ""; + } + + char[] chars = new char[n]; + int pos = 0; + + while (pos < n) { + chars[pos] = this.next(); + if (this.end()) { + throw this.syntaxError("Substring bounds error"); + } + pos += 1; + } + return new String(chars); + } + + /** + * Get the next char in the string, skipping whitespace. + * + * @throws JSONException + * @return A character, or 0 if there are no more characters. + */ + public char nextClean() throws JSONException { + for (;;) { + char c = this.next(); + if (c == 0 || c > ' ') { + return c; + } + } + } + + /** + * Return the characters up to the next close quote character. Backslash + * processing is done. The formal JSON format does not allow strings in + * single quotes, but an implementation is allowed to accept them. + * + * @param quote The quoting character, either " + *  (double quote) or ' + *  (single quote). + * @return A String. + * @throws JSONException Unterminated string. + */ + public String nextString(char quote) throws JSONException { + char c; + StringBuffer sb = new StringBuffer(); + for (;;) { + c = this.next(); + switch (c) { + case 0: + case '\n': + case '\r': + throw this.syntaxError("Unterminated string"); + case '\\': + c = this.next(); + switch (c) { + case 'b': + sb.append('\b'); + break; + case 't': + sb.append('\t'); + break; + case 'n': + sb.append('\n'); + break; + case 'f': + sb.append('\f'); + break; + case 'r': + sb.append('\r'); + break; + case 'u': + sb.append((char) Integer.parseInt(this.next(4), 16)); + break; + case '"': + case '\'': + case '\\': + case '/': + sb.append(c); + break; + default: + throw this.syntaxError("Illegal escape."); + } + break; + default: + if (c == quote) { + return sb.toString(); + } + sb.append(c); + } + } + } + + /** + * Get the text up but not including the specified character or the end of + * line, whichever comes first. + * + * @param delimiter A delimiter character. + * @return A string. + */ + public String nextTo(char delimiter) throws JSONException { + StringBuffer sb = new StringBuffer(); + for (;;) { + char c = this.next(); + if (c == delimiter || c == 0 || c == '\n' || c == '\r') { + if (c != 0) { + this.back(); + } + return sb.toString().trim(); + } + sb.append(c); + } + } + + /** + * Get the text up but not including one of the specified delimiter + * characters or the end of line, whichever comes first. + * + * @param delimiters A set of delimiter characters. + * @return A string, trimmed. + */ + public String nextTo(String delimiters) throws JSONException { + char c; + StringBuffer sb = new StringBuffer(); + for (;;) { + c = this.next(); + if (delimiters.indexOf(c) >= 0 || c == 0 || c == '\n' || c == '\r') { + if (c != 0) { + this.back(); + } + return sb.toString().trim(); + } + sb.append(c); + } + } + + /** + * Get the next value. The value can be a Boolean, Double, Integer, + * JSONArray, JSONObject, Long, or String, or the JSONObject.NULL object. + * + * @throws JSONException If syntax error. + * + * @return An object. + */ + public Object nextValue() throws JSONException { + char c = this.nextClean(); + String string; + + switch (c) { + case '"': + case '\'': + return this.nextString(c); + case '{': + this.back(); + return new JSONObject(this); + case '[': + this.back(); + return new JSONArray(this); + } + + /* + * Handle unquoted text. This could be the values true, false, or null, + * or it can be a number. An implementation (such as this one) is + * allowed to also accept non-standard forms. + * + * Accumulate characters until we reach the end of the text or a + * formatting character. + */ + + StringBuffer sb = new StringBuffer(); + while (c >= ' ' && ",:]}/\\\"[{;=#".indexOf(c) < 0) { + sb.append(c); + c = this.next(); + } + this.back(); + + string = sb.toString().trim(); + if ("".equals(string)) { + throw this.syntaxError("Missing value"); + } + return JSONObject.stringToValue(string); + } + + /** + * Skip characters until the next character is the requested character. If + * the requested character is not found, no characters are skipped. + * + * @param to A character to skip to. + * @return The requested character, or zero if the requested character is + * not found. + */ + public char skipTo(char to) throws JSONException { + char c; + try { + long startIndex = this.index; + long startCharacter = this.character; + long startLine = this.line; + this.reader.mark(1000000); + do { + c = this.next(); + if (c == 0) { + this.reader.reset(); + this.index = startIndex; + this.character = startCharacter; + this.line = startLine; + return c; + } + } while (c != to); + } catch (IOException exc) { + throw new JSONException(exc); + } + + this.back(); + return c; + } + + /** + * Make a JSONException to signal a syntax error. + * + * @param message The error message. + * @return A JSONException object, suitable for throwing + */ + public JSONException syntaxError(String message) { + return new JSONException(message + this.toString()); + } + + /** + * Make a printable string of this JSONTokener. + * + * @return " at {index} [character {character} line {line}]" + */ + @Override + public String toString() { + return " at " + this.index + " [character " + this.character + " line " + this.line + "]"; + } +} diff --git a/src/main/java/org/json/JSONUtil.java b/src/main/java/org/json/JSONUtil.java new file mode 100644 index 000000000..b7d3cd115 --- /dev/null +++ b/src/main/java/org/json/JSONUtil.java @@ -0,0 +1,57 @@ +package org.json; + +import java.util.Map.Entry; + + +public class JSONUtil { + + public static JSONObject toJSON(JSONEntry... entries) { + return o(entries); + } + + public static JSONObject o(JSONEntry... entries) { + JSONObject object = new JSONObject(); + for (int i = 0; i < entries.length; ++i) { + JSONEntry e = entries[i]; + object.put(e.getKey(), e.getValue()); + } + return object; + } + + public static JSONArray a(Object... values) { + return new JSONArray(values); + } + + public static JSONEntry e(String k, Object v) { + return new JSONEntry(k, v); + } + + public static class JSONEntry implements Entry { + + private String key; + private Object value; + + public JSONEntry(String key, Object value) { + this.key = key; + this.value = value; + } + + @Override + public String getKey() { + return this.key; + } + + @Override + public Object getValue() { + return this.value; + } + + @Override + public Object setValue(Object value) { + Object oldValue = this.value; + this.value = value; + return oldValue; + } + + } +}