Skip to content

Commit 508df07

Browse files
committed
Slerp + sort animation fix
1 parent c2e4d97 commit 508df07

4 files changed

Lines changed: 87 additions & 15 deletions

File tree

src/main/java/net/worldseed/multipart/Quaternion.java

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,21 +3,25 @@
33
import net.minestom.server.coordinate.Point;
44
import net.minestom.server.coordinate.Vec;
55

6-
final class Quaternion {
6+
final public class Quaternion {
77
private final double x;
88
private final double y;
99
private final double z;
1010

1111
private final double w;
1212

13-
Quaternion(double x, double y, double z, double w) {
13+
public Quaternion(double x, double y, double z, double w) {
1414
this.x = x;
1515
this.y = y;
1616
this.z = z;
1717
this.w = w;
1818
}
1919

20-
Quaternion(Point p) {
20+
public Quaternion() {
21+
this(0, 0, 0, 0);
22+
}
23+
24+
public Quaternion(Point p) {
2125
p = ModelMath.toRadians(p);
2226

2327
double cy = Math.cos(p.z() * 0.5);
@@ -33,7 +37,23 @@ final class Quaternion {
3337
z = cr * cp * sy - sr * sp * cy;
3438
}
3539

36-
Point toEuler() {
40+
public double x() {
41+
return x;
42+
}
43+
44+
public double y() {
45+
return y;
46+
}
47+
48+
public double z() {
49+
return z;
50+
}
51+
52+
public double w() {
53+
return w;
54+
}
55+
56+
public Point toEuler() {
3757
double t0 = (x+z)*(x-z); // x^2-z^2
3858
double t1 = (w+y)*(w-y); // w^2-y^2
3959
double xx = 0.5*(t0+t1); // 1/2 x of x'
@@ -84,7 +104,7 @@ Point threeAxisRot(double r11, double r12, double r21, double r31, double r32){
84104
return new Vec(x,z,y);
85105
}
86106

87-
Point toEulerYZX() {
107+
public Point toEulerYZX() {
88108
Quaternion q = this;
89109

90110
return ModelMath.toDegrees(threeAxisRot( -2*(q.x*q.z - q.w*q.y),

src/main/java/net/worldseed/multipart/animations/Interpolator.java

Lines changed: 39 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import net.minestom.server.coordinate.Point;
44
import net.minestom.server.coordinate.Pos;
5+
import net.worldseed.multipart.Quaternion;
56

67
import java.util.LinkedHashMap;
78

@@ -23,17 +24,53 @@ private static StartEnd getStartEnd(double time, LinkedHashMap<Double, Point> tr
2324
return new StartEnd(lastPoint, lastPoint, lastTime, animationTime);
2425
}
2526

27+
static Quaternion slerp(Quaternion qa, Quaternion qb, double t) {
28+
// quaternion to return
29+
// Calculate angle between them.
30+
double cosHalfTheta = qa.w() * qb.w() + qa.x() * qb.x() + qa.y() * qb.y() + qa.z() * qb.z();
31+
// if qa=qb or qa=-qb then theta = 0 and we can return qa
32+
if (Math.abs(cosHalfTheta) >= 1.0){
33+
double qmw = qa.w();
34+
double qmx = qa.x();
35+
double qmy = qa.y();
36+
double qmz = qa.z();
37+
return new Quaternion(qmx, qmy, qmz, qmw);
38+
}
39+
// Calculate temporary values.
40+
double halfTheta = Math.acos(cosHalfTheta);
41+
double sinHalfTheta = Math.sqrt(1.0 - cosHalfTheta*cosHalfTheta);
42+
// if theta = 180 degrees then result is not fully defined
43+
// we could rotate around any axis normal to qa or qb
44+
if (Math.abs(sinHalfTheta) < 0.001){ // fabs is floating point absolute
45+
double qmw = (qa.w() * 0.5 + qb.w() * 0.5);
46+
double qmx = (qa.x() * 0.5 + qb.x() * 0.5);
47+
double qmy = (qa.y() * 0.5 + qb.y() * 0.5);
48+
double qmz = (qa.z() * 0.5 + qb.z() * 0.5);
49+
return new Quaternion(qmx, qmy, qmz, qmw);
50+
}
51+
double ratioA = Math.sin((1 - t) * halfTheta) / sinHalfTheta;
52+
double ratioB = Math.sin(t * halfTheta) / sinHalfTheta;
53+
//calculate Quaternion.
54+
double qmw = (qa.w() * ratioA + qb.w() * ratioB);
55+
double qmx = (qa.x() * ratioA + qb.x() * ratioB);
56+
double qmy = (qa.y() * ratioA + qb.y() * ratioB);
57+
double qmz = (qa.z() * ratioA + qb.z() * ratioB);
58+
return new Quaternion(qmx, qmy, qmz, qmw);
59+
}
60+
2661
static Point interpolate(double time, LinkedHashMap<Double, Point> transform, double animationTime) {
2762
StartEnd points = getStartEnd(time, transform, animationTime);
2863

2964
double timeDiff = points.et - points.st;
3065

66+
Quaternion qa = new Quaternion(points.s);
67+
Quaternion qb = new Quaternion(points.e);
68+
3169
if (timeDiff == 0)
3270
return points.s;
3371

3472
double timePercent = (time - points.st) / timeDiff;
35-
36-
return points.e.sub(points.s).mul(timePercent).add(points.s);
73+
return slerp(qa, qb, timePercent).toEuler();
3774
}
3875

3976
}

src/main/java/net/worldseed/multipart/animations/ModelAnimation.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,8 @@ public ModelAnimation(String modelName, String animationName, ModelBone bone, Js
7676
transform.put(0.0, point);
7777
}
7878

79+
System.out.println("[AnimationLoader] Generating cache for " + bone.getName() + "/" + animationName);
80+
7981
if (this.type == AnimationLoader.AnimationType.ROTATION) {
8082
found = calculateAllTransforms(animationTime, transform);
8183
AnimationLoader.addToRotationCache(modelName, bone.getName() + "/" + animationName, found);

src/main/java/net/worldseed/multipart/parser/generator/AnimationGenerator.java

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,7 @@
44
import com.google.gson.JsonElement;
55
import com.google.gson.JsonObject;
66

7-
import java.util.Map;
8-
import java.util.Set;
7+
import java.util.*;
98

109
public class AnimationGenerator {
1110
public static JsonObject generate(JsonArray animationRaw) {
@@ -25,8 +24,8 @@ public static JsonObject generate(JsonArray animationRaw) {
2524

2625
String boneName = animator.get("name").getAsString();
2726

28-
JsonObject rotation = new JsonObject();
29-
JsonObject position = new JsonObject();
27+
List<Map.Entry<Double, JsonObject>> rotation = new ArrayList<>();
28+
List<Map.Entry<Double, JsonObject>> position = new ArrayList<>();
3029

3130
JsonArray keyframes = animator.get("keyframes").getAsJsonArray();
3231

@@ -44,15 +43,29 @@ public static JsonObject generate(JsonArray animationRaw) {
4443
built.addProperty("lerp_mode", interpolation);
4544

4645
if (channel.equals("rotation")) {
47-
rotation.add(String.valueOf(time), built);
46+
rotation.add(Map.entry(time, built));
4847
} else if (channel.equals("position")) {
49-
position.add(String.valueOf(time), built);
48+
position.add(Map.entry(time, built));
5049
}
5150
}
5251

52+
rotation.sort(Map.Entry.comparingByKey());
53+
position.sort(Map.Entry.comparingByKey());
54+
55+
JsonObject rotationJson = new JsonObject();
56+
JsonObject positionJson = new JsonObject();
57+
58+
for (var rotation_ : rotation) {
59+
rotationJson.add(rotation_.getKey().toString(), rotation_.getValue());
60+
}
61+
62+
for (var position_ : position) {
63+
positionJson.add(position_.getKey().toString(), position_.getValue());
64+
}
65+
5366
JsonObject built = new JsonObject();
54-
built.add("rotation", rotation);
55-
built.add("position", position);
67+
built.add("rotation", rotationJson);
68+
built.add("position", positionJson);
5669

5770
bones.add(boneName, built);
5871
}

0 commit comments

Comments
 (0)