Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -432,7 +432,7 @@ private float beginOrEndTickLerp(float startValue, float endValue, Float transit
if (!keyFrames.isEmpty()) easingType = keyFrames.getLast().easingType();
}
}
if (easingType == EasingType.BEZIER || easingType == EasingType.BEZIER_AFTER || easingType == EasingType.CATMULLROM)
if (easingType == EasingType.BEZIER || easingType == EasingType.CATMULLROM)
easingType = EasingType.EASE_IN_OUT_SINE;
}
if (transitionLength == null) return endValue;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,125 +11,131 @@
import java.util.ArrayList;
import java.util.List;

abstract class BezierEasing implements EasingTypeTransformer {
public class BezierEasing implements EasingTypeTransformer {
@Override
public Float2FloatFunction buildTransformer(@Nullable Float value) {
return EasingType.easeIn(EasingType::linear);
}

abstract boolean isEasingBefore();

@Override
public float apply(MochaEngine<?> env, AnimationPoint animationPoint, @Nullable Float easingValue, float lerpValue) {
List<List<Expression>> easingArgs = animationPoint.easingArgs();
if (easingArgs.isEmpty())
return MochaMath.lerp(animationPoint.animationStartValue(), animationPoint.animationEndValue(), buildTransformer(easingValue).apply(lerpValue));

float rightValue = isEasingBefore() ? 0 : env.eval(easingArgs.getFirst());
float rightTime = isEasingBefore() ? 0.1f : env.eval(easingArgs.get(1));
float leftValue = isEasingBefore() ? env.eval(easingArgs.getFirst()) : 0;
float leftTime = isEasingBefore() ? env.eval(easingArgs.get(1)) : -0.1f;
float rightValue;
float rightTime;
float leftValue = env.eval(easingArgs.getFirst());
float leftTime = env.eval(easingArgs.get(1));

if (easingArgs.size() > 3) {
rightValue = env.eval(easingArgs.get(2));
rightTime = env.eval(easingArgs.get(3));
}

leftValue = (float) Math.toRadians(leftValue);
rightValue = (float) Math.toRadians(rightValue);

float gapTime = animationPoint.transitionLength()/20;

float time_handle_before = Math.clamp(rightTime, 0, gapTime);
float time_handle_after = Math.clamp(leftTime, -gapTime, 0);

CubicBezierCurve curve = new CubicBezierCurve(
new ModVector2d(0, animationPoint.animationStartValue()),
new ModVector2d(time_handle_before, animationPoint.animationStartValue() + rightValue),
new ModVector2d(time_handle_after + gapTime, animationPoint.animationEndValue() + leftValue),
new ModVector2d(gapTime, animationPoint.animationEndValue()));
float time = gapTime * lerpValue;

List<ModVector2d> points = curve.getPoints(200);
ModVector2d closest = new ModVector2d();
float closest_diff = Float.POSITIVE_INFINITY;
for (ModVector2d point : points) {
float diff = Math.abs(point.x - time);
if (diff < closest_diff) {
closest_diff = diff;
closest.set(point);
}
else {
rightValue = 0;
rightTime = 0.1f;
}
ModVector2d second_closest = new ModVector2d();
closest_diff = Float.POSITIVE_INFINITY;
for (ModVector2d point : points) {
if (point == closest) continue;
float diff = Math.abs(point.x - time);
if (diff < closest_diff) {
closest_diff = diff;
second_closest.set(closest);
second_closest.set(point);
}

float transitionLength = animationPoint.transitionLength()/20f;

float time_handle_before = Math.clamp(rightTime/transitionLength, 0, 1);
float time_handle_after = Math.clamp(leftTime/transitionLength, -1, 0);

ModVector2d P0 = new ModVector2d(0, animationPoint.animationStartValue());
ModVector2d P1 = new ModVector2d(time_handle_before, animationPoint.animationStartValue() + rightValue);
ModVector2d P2 = new ModVector2d(time_handle_after + 1, animationPoint.animationEndValue() + leftValue);
ModVector2d P3 = new ModVector2d(1, animationPoint.animationEndValue());

// Determine t
float t;
if (lerpValue == P0.x) {
// Handle corner cases explicitly to prevent rounding errors
t = 0;
} else if (lerpValue == P3.x) {
t = 1;
} else {
// Calculate t
float a = -P0.x + 3 * P1.x - 3 * P2.x + P3.x;
float b = 3 * P0.x - 6 * P1.x + 3 * P2.x;
float c = -3 * P0.x + 3 * P1.x;
float d = P0.x - lerpValue;
Float tTemp = SolveCubic(a, b, c, d);
if (tTemp == null) return animationPoint.animationEndValue();
t = tTemp;
}
return MochaMath.lerp(closest.y, second_closest.y, Math.clamp(MochaMath.lerp(closest.x, second_closest.x, time), 0, 1));
}
}

class BezierEasingBefore extends BezierEasing {
@Override
boolean isEasingBefore() {
return true;
// Calculate y from t
return Cubed(1 - t) * P0.y
+ 3 * t * Squared(1 - t) * P1.y
+ 3 * Squared(t) * (1 - t) * P2.y
+ Cubed(t) * P3.y;
}
}

class BezierEasingAfter extends BezierEasing {
@Override
boolean isEasingBefore() {
return false;
}
}

class CubicBezierCurve {
private ModVector2d v0;
private ModVector2d v1;
private ModVector2d v2;
private ModVector2d v3;

public CubicBezierCurve(ModVector2d v0, ModVector2d v1, ModVector2d v2, ModVector2d v3) {
this.v0 = v0;
this.v1 = v1;
this.v2 = v2;
this.v3 = v3;
}
// Solves the equation ax³+bx²+cx+d = 0 for x ϵ ℝ
// and returns the first result in [0, 1] or null.
private static Float SolveCubic(float a, float b, float c, float d) {
if (a == 0) return SolveQuadratic(b, c, d);
if (d == 0) return 0f;

b /= a;
c /= a;
d /= a;
float q = (3 * c - Squared(b)) / 9;
float r = (-27 * d + b * (9 * c - 2 * Squared(b))) / 54;
float disc = Cubed(q) + Squared(r);
float term1 = b / 3;

if (disc > 0) {
float s = (float) (r + Math.sqrt(disc));
s = (s < 0) ? -CubicRoot(-s) : CubicRoot(s);
float t = (float) (r - Math.sqrt(disc));
t = (t < 0) ? -CubicRoot(-t) : CubicRoot(t);

float result = -term1 + s + t;
if (result >= 0 && result <= 1) return result;
} else if (disc == 0) {
float r13 = (r < 0) ? -CubicRoot(-r) : CubicRoot(r);

float result = -term1 + 2 * r13;
if (result >= 0 && result <= 1) return result;

result = -(r13 + term1);
if (result >= 0 && result <= 1) return result;
} else {
q = -q;
float dum1 = q * q * q;
dum1 = (float) Math.acos(r / Math.sqrt(dum1));
float r13 = (float) (2 * Math.sqrt(q));

float result = (float) (-term1 + r13 * Math.cos(dum1 / 3));
if (result >= 0 && result <= 1) return result;

result = (float) (-term1 + r13 * Math.cos((dum1 + 2 * Math.PI) / 3));
if (result >= 0 && result <= 1) return result;

result = (float) (-term1 + r13 * Math.cos((dum1 + 4 * Math.PI) / 3));
if (result >= 0 && result <= 1) return result;
}

public ModVector2d getPoint(float t) {
return getPoint(t, new ModVector2d());
return null;
}

public ModVector2d getPoint(float t, ModVector2d target) {
if (target == null) {
target = new ModVector2d();
}

float u = 1 - t;
float tt = t * t;
float uu = u * u;
float uuu = uu * u;
float ttt = tt * t;
// Solves the equation ax² + bx + c = 0 for x ϵ ℝ
// and returns the first result in [0, 1] or null.
private static Float SolveQuadratic(float a, float b, float c) {
float result = (float) ((-b + Math.sqrt(Squared(b) - 4 * a * c)) / (2 * a));
if (result >= 0 && result <= 1) return result;

target.x = uuu * v0.x + 3 * uu * t * v1.x + 3 * u * tt * v2.x + ttt * v3.x;
target.y = uuu * v0.y + 3 * uu * t * v1.y + 3 * u * tt * v2.y + ttt * v3.y;
result = (float) ((-b - Math.sqrt(Squared(b) - 4 * a * c)) / (2 * a));
if (result >= 0 && result <= 1) return result;

return target;
return null;
}

public List<ModVector2d> getPoints(int divisions) {
List<ModVector2d> points = new ArrayList<>();
private static float Squared(float f) { return f * f; }

for (int i = 0; i <= divisions; i++) {
points.add(getPoint((float) i / divisions));
}
private static float Cubed(float f) { return f * f * f; }

return points;
}
private static float CubicRoot(float f) { return (float) Math.pow(f, 1.0 / 3.0); }
}
Original file line number Diff line number Diff line change
Expand Up @@ -70,8 +70,7 @@ public enum EasingType implements EasingTypeTransformer {
CATMULLROM(36, "catmullrom", new CatmullRomEasing()),
// 37 - STEP

BEZIER(38, "bezier", new BezierEasingBefore()),
BEZIER_AFTER(39, "bezier_after", new BezierEasingAfter());
BEZIER(38, "bezier", new BezierEasing());

public final byte id;
public final String name;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,13 +43,16 @@
import team.unnamed.mocha.parser.ast.Expression;
import team.unnamed.mocha.parser.ast.FloatExpression;
import team.unnamed.mocha.parser.ast.IdentifierExpression;
import team.unnamed.mocha.runtime.IsConstantExpression;

import java.lang.reflect.Type;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import static com.zigythebird.playeranimcore.molang.MolangLoader.MOCHA_ENGINE;

public class AnimationLoader implements JsonDeserializer<Animation> {
@Override
public Animation deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
Expand Down Expand Up @@ -274,7 +277,7 @@ private static KeyframeStack buildKeyframeStack(List<FloatObjectPair<JsonElement
prevEntry = entry;
}

return new KeyframeStack(addArgsForKeyframes(xFrames), addArgsForKeyframes(yFrames), addArgsForKeyframes(zFrames));
return new KeyframeStack(addArgsForKeyframes(xFrames, type), addArgsForKeyframes(yFrames, type), addArgsForKeyframes(zFrames, type));
}

private static EasingType getEasingForAxis(JsonObject entryObj, Axis axis, EasingType easingType) {
Expand All @@ -291,7 +294,7 @@ private static List<List<Expression>> getEasingArgsForAxis(JsonObject entryObj,
easingArg;
}

private static List<Keyframe> addArgsForKeyframes(List<Keyframe> frames) {
private static List<Keyframe> addArgsForKeyframes(List<Keyframe> frames, TransformType type) {
if (frames.getFirst().startValue().getFirst() instanceof AccessExpression accessExpression
&& "disabled".equals(accessExpression.property()) && accessExpression.object() instanceof IdentifierExpression identifierExpression
&& "pal".equals(identifierExpression.name()))
Expand All @@ -317,24 +320,44 @@ private static List<Keyframe> addArgsForKeyframes(List<Keyframe> frames) {
)));
}
else if (frame.easingType() == EasingType.BEZIER) {
List<Expression> leftValue = frame.easingArgs().getFirst();
List<Expression> rightValue = frame.easingArgs().get(2);
List<Expression> rightTime = frame.easingArgs().get(3);
frame.easingArgs().remove(2);
frame.easingArgs().remove(2);
if (type == TransformType.ROTATION) {
rightValue = toRadiansForBezier(rightValue);
leftValue = toRadiansForBezier(leftValue);
}
frames.set(i, new Keyframe(frame.length(), frame.startValue(), frame.endValue(), frame.easingType(),
ObjectArrayList.of(leftValue, frame.easingArgs().get(1))));
if (frame.easingArgs().size() > 4) {
frames.get(i).easingArgs().add(frame.easingArgs().get(4));
frames.get(i).easingArgs().add(frame.easingArgs().get(5));
}
if (frames.size() > i + 1) {
Keyframe nextKeyframe = frames.get(i + 1);
if (nextKeyframe.easingType() == EasingType.BEZIER) {
if (nextKeyframe.easingType() != EasingType.BEZIER) {
frames.set(i + 1, new Keyframe(nextKeyframe.length(), nextKeyframe.startValue(), nextKeyframe.endValue(),
EasingType.BEZIER, ObjectArrayList.of(PlayerAnimatorLoader.ZERO, PlayerAnimatorLoader.ZERO, rightValue, rightTime))); //TODO Maybe move the ZERO field to UniversalAnimLoader
}
else {
nextKeyframe.easingArgs().add(rightValue);
nextKeyframe.easingArgs().add(rightTime);
}
else frames.set(i + 1, new Keyframe(nextKeyframe.length(), nextKeyframe.startValue(), nextKeyframe.endValue(), EasingType.BEZIER_AFTER, ObjectArrayList.of(rightValue, rightTime)));
}
}
}

return frames;
}

private static List<Expression> toRadiansForBezier(List<Expression> expressions) {
if (expressions.size() == 1 && IsConstantExpression.test(expressions.getFirst())) {
return Collections.singletonList(FloatExpression.of(Math.toRadians(MOCHA_ENGINE.eval(expressions))));
}
PlayerAnimLib.LOGGER.warn("Invalid easing arguments for bezier: {}\nFor rotations bezier args can only be floats.", expressions);
return expressions;
}

public static float calculateAnimationLength(Map<String, BoneAnimation> boneAnimations) {
float length = 0;

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
{
"format_version": "1.8.0",
"geckolib_format_version": 2,
"model": {},
"parents": {},
"animations": {
"bezier_test": {
"loopTick": 0.0,
"loop": true,
"animation_length": 2.0,
"player_animation_library": {
"name": "Тест экспорта бедрока",
"author": "MineEmotes",
"description": "",
"bages": []
},
"bones": {
"leftArm": {
"rotation": {
"0.0": {
"vector": [
0.0,
"pal.disabled",
"pal.disabled"
],
"easingX": "bezier",
"easingArgsX": [
-88.22536,
-0.66596,
-260.45306,
0.98345
]
},
"2.0": {
"vector": [
0.0,
"pal.disabled",
"pal.disabled"
],
"easingX": "bezier",
"easingArgsX": [
246.38388,
-1.02578,
80.31529,
0.66608
]
}
}
}
}
}
}
}