319 lines
13 KiB
Java
319 lines
13 KiB
Java
/*
|
|
* Minecraft Forge
|
|
* Copyright (c) 2016-2019.
|
|
*
|
|
* This library is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Lesser General Public
|
|
* License as published by the Free Software Foundation version 2.1
|
|
* of the License.
|
|
*
|
|
* This library is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
* License along with this library; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
|
*/
|
|
|
|
package net.minecraftforge.common.model;
|
|
|
|
import java.lang.reflect.Type;
|
|
import java.util.EnumMap;
|
|
import java.util.Map;
|
|
|
|
import com.google.gson.*;
|
|
import net.minecraft.client.renderer.*;
|
|
import net.minecraft.util.math.MathHelper;
|
|
|
|
import com.google.common.collect.Maps;
|
|
|
|
import net.minecraft.client.renderer.model.ItemTransformVec3f;
|
|
import net.minecraft.util.Direction;
|
|
import net.minecraftforge.api.distmarker.Dist;
|
|
import net.minecraftforge.api.distmarker.OnlyIn;
|
|
|
|
public final class TransformationHelper
|
|
{
|
|
@Deprecated
|
|
@OnlyIn(Dist.CLIENT)
|
|
public static TransformationMatrix toTransformation(ItemTransformVec3f transform)
|
|
{
|
|
if (transform.equals(ItemTransformVec3f.DEFAULT)) return TransformationMatrix.identity();
|
|
|
|
return new TransformationMatrix(transform.translation, quatFromXYZ(transform.rotation, true), transform.scale, null);
|
|
}
|
|
|
|
public static Quaternion quatFromXYZ(Vector3f xyz, boolean degrees)
|
|
{
|
|
return new Quaternion(xyz.getX(), xyz.getY(), xyz.getZ(), degrees);
|
|
}
|
|
|
|
public static Quaternion quatFromXYZ(float[] xyz, boolean degrees)
|
|
{
|
|
return new Quaternion(xyz[0], xyz[1], xyz[2], degrees);
|
|
}
|
|
|
|
public static Quaternion makeQuaternion(float[] values)
|
|
{
|
|
return new Quaternion(values[0], values[1], values[2], values[3]);
|
|
}
|
|
|
|
public static Vector3f lerp(Vector3f from, Vector3f to, float progress)
|
|
{
|
|
Vector3f res = from.copy();
|
|
res.lerp(to, progress);
|
|
return res;
|
|
}
|
|
|
|
private static final double THRESHOLD = 0.9995;
|
|
public static Quaternion slerp(Quaternion v0, Quaternion v1, float t)
|
|
{
|
|
// From https://en.wikipedia.org/w/index.php?title=Slerp&oldid=928959428
|
|
// License: CC BY-SA 3.0 https://creativecommons.org/licenses/by-sa/3.0/
|
|
|
|
// Compute the cosine of the angle between the two vectors.
|
|
// If the dot product is negative, slerp won't take
|
|
// the shorter path. Note that v1 and -v1 are equivalent when
|
|
// the negation is applied to all four components. Fix by
|
|
// reversing one quaternion.
|
|
float dot = v0.getX() * v1.getX() + v0.getY() * v1.getY() + v0.getZ() * v1.getZ() + v0.getW() * v1.getW();
|
|
if (dot < 0.0f) {
|
|
v1 = new Quaternion(-v1.getX(), -v1.getY(), -v1.getZ(), -v1.getW());
|
|
dot = -dot;
|
|
}
|
|
|
|
// If the inputs are too close for comfort, linearly interpolate
|
|
// and normalize the result.
|
|
if (dot > THRESHOLD) {
|
|
float x = MathHelper.lerp(t, v0.getX(), v1.getX());
|
|
float y = MathHelper.lerp(t, v0.getY(), v1.getY());
|
|
float z = MathHelper.lerp(t, v0.getZ(), v1.getZ());
|
|
float w = MathHelper.lerp(t, v0.getW(), v1.getW());
|
|
return new Quaternion(x,y,z,w);
|
|
}
|
|
|
|
// Since dot is in range [0, DOT_THRESHOLD], acos is safe
|
|
float angle01 = (float)Math.acos(dot);
|
|
float angle0t = angle01*t;
|
|
float sin0t = MathHelper.sin(angle0t);
|
|
float sin01 = MathHelper.sin(angle01);
|
|
float sin1t = MathHelper.sin(angle01 - angle0t);
|
|
|
|
float s1 = sin0t / sin01;
|
|
float s0 = sin1t / sin01;
|
|
|
|
return new Quaternion(
|
|
s0 * v0.getX() + s1 * v1.getX(),
|
|
s0 * v0.getY() + s1 * v1.getY(),
|
|
s0 * v0.getZ() + s1 * v1.getZ(),
|
|
s0 * v0.getW() + s1 * v1.getW()
|
|
);
|
|
}
|
|
|
|
public static TransformationMatrix slerp(TransformationMatrix one, TransformationMatrix that, float progress)
|
|
{
|
|
return new TransformationMatrix(
|
|
lerp(one.getTranslation(), that.getTranslation(), progress),
|
|
slerp(one.getRotationLeft(), that.getRotationLeft(), progress),
|
|
lerp(one.getScale(), that.getScale(), progress),
|
|
slerp(one.getRightRot(), that.getRightRot(), progress)
|
|
);
|
|
}
|
|
|
|
public static boolean epsilonEquals(Vector4f v1, Vector4f v2, float epsilon)
|
|
{
|
|
return MathHelper.abs(v1.getX()-v2.getX()) < epsilon &&
|
|
MathHelper.abs(v1.getY()-v2.getY()) < epsilon &&
|
|
MathHelper.abs(v1.getZ()-v2.getZ()) < epsilon &&
|
|
MathHelper.abs(v1.getW()-v2.getW()) < epsilon;
|
|
}
|
|
|
|
public static class Deserializer implements JsonDeserializer<TransformationMatrix>
|
|
{
|
|
@Override
|
|
public TransformationMatrix deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException
|
|
{
|
|
if (json.isJsonPrimitive() && json.getAsJsonPrimitive().isString())
|
|
{
|
|
String transform = json.getAsString();
|
|
if(transform.equals("identity"))
|
|
{
|
|
return TransformationMatrix.identity();
|
|
}
|
|
else
|
|
{
|
|
throw new JsonParseException("TRSR: unknown default string: " + transform);
|
|
}
|
|
}
|
|
if (json.isJsonArray())
|
|
{
|
|
// direct matrix array
|
|
return new TransformationMatrix(parseMatrix(json));
|
|
}
|
|
if (!json.isJsonObject()) throw new JsonParseException("TRSR: expected array or object, got: " + json);
|
|
JsonObject obj = json.getAsJsonObject();
|
|
TransformationMatrix ret;
|
|
if (obj.has("matrix"))
|
|
{
|
|
// matrix as a sole key
|
|
ret = new TransformationMatrix(parseMatrix(obj.get("matrix")));
|
|
obj.remove("matrix");
|
|
if (obj.entrySet().size() != 0)
|
|
{
|
|
throw new JsonParseException("TRSR: can't combine matrix and other keys");
|
|
}
|
|
return ret;
|
|
}
|
|
Vector3f translation = null;
|
|
Quaternion leftRot = null;
|
|
Vector3f scale = null;
|
|
Quaternion rightRot = null;
|
|
if (obj.has("translation"))
|
|
{
|
|
translation = new Vector3f(parseFloatArray(obj.get("translation"), 3, "Translation"));
|
|
obj.remove("translation");
|
|
}
|
|
if (obj.has("rotation"))
|
|
{
|
|
leftRot = parseRotation(obj.get("rotation"));
|
|
obj.remove("rotation");
|
|
}
|
|
if (obj.has("scale"))
|
|
{
|
|
if(!obj.get("scale").isJsonArray())
|
|
{
|
|
try
|
|
{
|
|
float s = obj.get("scale").getAsNumber().floatValue();
|
|
scale = new Vector3f(s, s, s);
|
|
}
|
|
catch (ClassCastException ex)
|
|
{
|
|
throw new JsonParseException("TRSR scale: expected number or array, got: " + obj.get("scale"));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
scale = new Vector3f(parseFloatArray(obj.get("scale"), 3, "Scale"));
|
|
}
|
|
obj.remove("scale");
|
|
}
|
|
if (obj.has("post-rotation"))
|
|
{
|
|
rightRot = parseRotation(obj.get("post-rotation"));
|
|
obj.remove("post-rotation");
|
|
}
|
|
if (!obj.entrySet().isEmpty()) throw new JsonParseException("TRSR: can either have single 'matrix' key, or a combination of 'translation', 'rotation', 'scale', 'post-rotation'");
|
|
return new TransformationMatrix(translation, leftRot, scale, rightRot);
|
|
}
|
|
|
|
public static Matrix4f parseMatrix(JsonElement e)
|
|
{
|
|
if (!e.isJsonArray()) throw new JsonParseException("Matrix: expected an array, got: " + e);
|
|
JsonArray m = e.getAsJsonArray();
|
|
if (m.size() != 3) throw new JsonParseException("Matrix: expected an array of length 3, got: " + m.size());
|
|
float[] values = new float[16];
|
|
for (int i = 0; i < 3; i++)
|
|
{
|
|
if (!m.get(i).isJsonArray()) throw new JsonParseException("Matrix row: expected an array, got: " + m.get(i));
|
|
JsonArray r = m.get(i).getAsJsonArray();
|
|
if (r.size() != 4) throw new JsonParseException("Matrix row: expected an array of length 4, got: " + r.size());
|
|
for (int j = 0; j < 4; j++)
|
|
{
|
|
try
|
|
{
|
|
values[j*4+i] = r.get(j).getAsNumber().floatValue();
|
|
}
|
|
catch (ClassCastException ex)
|
|
{
|
|
throw new JsonParseException("Matrix element: expected number, got: " + r.get(j));
|
|
}
|
|
}
|
|
}
|
|
return new Matrix4f(values);
|
|
}
|
|
|
|
public static float[] parseFloatArray(JsonElement e, int length, String prefix)
|
|
{
|
|
if (!e.isJsonArray()) throw new JsonParseException(prefix + ": expected an array, got: " + e);
|
|
JsonArray t = e.getAsJsonArray();
|
|
if (t.size() != length) throw new JsonParseException(prefix + ": expected an array of length " + length + ", got: " + t.size());
|
|
float[] ret = new float[length];
|
|
for (int i = 0; i < length; i++)
|
|
{
|
|
try
|
|
{
|
|
ret[i] = t.get(i).getAsNumber().floatValue();
|
|
}
|
|
catch (ClassCastException ex)
|
|
{
|
|
throw new JsonParseException(prefix + " element: expected number, got: " + t.get(i));
|
|
}
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
public static Quaternion parseAxisRotation(JsonElement e)
|
|
{
|
|
if (!e.isJsonObject()) throw new JsonParseException("Axis rotation: object expected, got: " + e);
|
|
JsonObject obj = e.getAsJsonObject();
|
|
if (obj.entrySet().size() != 1) throw new JsonParseException("Axis rotation: expected single axis object, got: " + e);
|
|
Map.Entry<String, JsonElement> entry = obj.entrySet().iterator().next();
|
|
Quaternion ret;
|
|
try
|
|
{
|
|
if (entry.getKey().equals("x"))
|
|
{
|
|
ret = Vector3f.XP.rotationDegrees(entry.getValue().getAsNumber().floatValue());
|
|
}
|
|
else if (entry.getKey().equals("y"))
|
|
{
|
|
ret = Vector3f.YP.rotationDegrees(entry.getValue().getAsNumber().floatValue());
|
|
}
|
|
else if (entry.getKey().equals("z"))
|
|
{
|
|
ret = Vector3f.ZP.rotationDegrees(entry.getValue().getAsNumber().floatValue());
|
|
}
|
|
else throw new JsonParseException("Axis rotation: expected single axis key, got: " + entry.getKey());
|
|
}
|
|
catch(ClassCastException ex)
|
|
{
|
|
throw new JsonParseException("Axis rotation value: expected number, got: " + entry.getValue());
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
public static Quaternion parseRotation(JsonElement e)
|
|
{
|
|
if (e.isJsonArray())
|
|
{
|
|
if (e.getAsJsonArray().get(0).isJsonObject())
|
|
{
|
|
Quaternion ret = Quaternion.ONE.copy();
|
|
for (JsonElement a : e.getAsJsonArray())
|
|
{
|
|
ret.multiply(parseAxisRotation(a));
|
|
}
|
|
return ret;
|
|
}
|
|
else if (e.isJsonArray())
|
|
{
|
|
JsonArray array = e.getAsJsonArray();
|
|
if (array.size() == 3) //Vanilla rotation
|
|
return quatFromXYZ(parseFloatArray(e, 3, "Rotation"), true);
|
|
else // quaternion
|
|
return makeQuaternion(parseFloatArray(e, 4, "Rotation"));
|
|
}
|
|
else throw new JsonParseException("Rotation: expected array or object, got: " + e);
|
|
}
|
|
else if (e.isJsonObject())
|
|
{
|
|
return parseAxisRotation(e);
|
|
}
|
|
else throw new JsonParseException("Rotation: expected array or object, got: " + e);
|
|
}
|
|
}
|
|
}
|