Skip to content

Instantly share code, notes, and snippets.

@phi16
Last active December 26, 2022 16:36
Show Gist options
  • Save phi16/936fad0969028ee36155ca9ad45622c4 to your computer and use it in GitHub Desktop.
Save phi16/936fad0969028ee36155ca9ad45622c4 to your computer and use it in GitHub Desktop.
using UnityEngine;
using MathNet.Numerics;
using System;
using System.Collections.Generic;
class AnimCurve {
public enum CurveType {
Discrete,
Linear,
Smooth,
Degree,
Radian,
}
public string relativePath;
public Type type;
public string property;
private Func<float> value;
private CurveType curveType;
public AnimationCurve curve;
private int order;
private double threshold;
private bool first;
private float lastValue;
private double queueHeadTime;
private double queueHeadValue;
private Stack<double> queueTimes;
private Stack<double> queueValues;
private bool hasLastKeyframe;
private Keyframe lastKeyframe;
public AnimCurve(string relativePath, Type type, string property, Func<float> value, CurveType curveType = CurveType.Smooth) {
this.relativePath = relativePath;
this.type = type;
this.property = property;
this.value = value;
this.curveType = curveType;
if(curveType == CurveType.Discrete) {
order = 0;
} else if(curveType == CurveType.Linear) {
order = 1;
} else {
order = 3;
}
threshold = 0.000001;
curve = new AnimationCurve();
first = true;
queueHeadTime = 0;
queueHeadValue = 0;
queueTimes = new Stack<double>();
queueValues = new Stack<double>();
hasLastKeyframe = false;
lastKeyframe = new Keyframe(0, 0);
}
public List<float> timeQueue = new List<float>();
private float PickValue() {
float c = value();
if(first) {
first = false;
lastValue = c;
}
if(curveType == CurveType.Degree) {
c -= lastValue;
c = (((c + 180) % 360 + 360) % 360) - 180;
c += lastValue;
lastValue = c;
}
return c;
}
public void Tick(float t) {
float v = PickValue();
if(queueTimes.Count == 0) {
queueHeadTime = t;
queueHeadValue = v;
}
queueTimes.Push(t - queueHeadTime);
queueValues.Push(v - queueHeadValue);
if(queueTimes.Count <= order + 1) {
return;
}
double[] ts = queueTimes.ToArray();
double[] vs = queueValues.ToArray();
double[] coeffs = Fit.Polynomial(ts, vs, order);
double res = 0;
for(int i=0;i<queueTimes.Count;i++) {
double ev = Polynomial.Evaluate(ts[i], coeffs);
double d = vs[i] - ev;
res += d * d;
}
if(res > threshold) {
Flush(false);
}
}
public void Done() {
Flush(true);
}
private Keyframe CreateKeyframe(double t, double[] coeffs) {
double t0 = queueHeadTime;
double t1 = queueHeadTime + t;
double p0 = queueHeadValue + coeffs[0];
double p1 = p0;
for(int j=1;j<=order;j++) {
p1 += coeffs[j] * Math.Pow(t, j);
}
double v0 = 0;
double v1 = 0;
if(order == 1) {
v0 = v1 = coeffs[1];
} else if(order == 3) {
v0 = coeffs[1];
v1 = coeffs[1] + 2 * coeffs[2] * t + 3 * coeffs[3] * t * t;
}
if(!hasLastKeyframe) {
hasLastKeyframe = true;
lastKeyframe = new Keyframe((float) t0, (float) p0, 0, 0);
}
lastKeyframe.outTangent = (float) v0;
Keyframe keyframe = new Keyframe((float) t1, (float) p1, (float) v1, 0);
return keyframe;
}
private void Flush(bool final) {
if(queueTimes.Count == 0) return;
double qht = 0;
double qhv = 0;
Stack<double> qt = new Stack<double>();
Stack<double> qv = new Stack<double>();
if(!final) {
if(order == 3) {
double t1 = queueTimes.Pop();
double t0 = queueTimes.Pop();
qht = queueHeadTime + t0;
qt.Push(0);
qt.Push(t1 - t0);
double v1 = queueValues.Pop();
double v0 = queueValues.Pop();
qhv = queueHeadValue + v0;
qv.Push(0);
qv.Push(v1 - v0);
queueTimes.Push(t0);
queueValues.Push(v0);
} else {
double t0 = queueTimes.Pop();
qht = queueHeadTime + t0;
qt.Push(0);
double v0 = queueValues.Pop();
qhv = queueHeadValue + v0;
qv.Push(0);
}
}
double[] ts = queueTimes.ToArray();
double[] vs = queueValues.ToArray();
double[] coeffs;
if(ts.Length >= order + 1) {
coeffs = Fit.Polynomial(ts, vs, order);
} else {
double[] cs = Fit.Polynomial(ts, vs, ts.Length - 1);
coeffs = new double[order+1];
cs.CopyTo(coeffs, 0);
}
Keyframe keyframe = CreateKeyframe(ts[0], coeffs);
int res = curve.AddKey(lastKeyframe);
if(res == -1) {
Debug.LogError("Failed to add keyframe");
}
if(final || order != 3) {
curve.AddKey(keyframe);
hasLastKeyframe = false;
} else {
lastKeyframe = keyframe;
}
queueHeadTime = qht;
queueHeadValue = qhv;
queueTimes = qt;
queueValues = qv;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment