diff --git a/index.html b/index.html
index 8becd477..8319d65f 100644
--- a/index.html
+++ b/index.html
@@ -89,6 +89,7 @@
+
diff --git a/js/animations/animation_controllers.js b/js/animations/animation_controllers.js
index 7b9b85c2..aaf5ee0a 100644
--- a/js/animations/animation_controllers.js
+++ b/js/animations/animation_controllers.js
@@ -196,20 +196,22 @@ class AnimationControllerState {
return new oneLiner({[state ? state.name : 'missing_state']: condition})
})
}
- if (this.blend_transition) object.blend_transition = this.blend_transition;
- let curve_keys = this.blend_transition_curve && Object.keys(this.blend_transition_curve);
- if (curve_keys?.length) {
- let curve_output = {};
- let points = curve_keys.map(key => ({time: parseFloat(key), value: this.blend_transition_curve[key]}));
- points.sort((a, b) => a.time - b.time);
- for (let point of points) {
- let timecode = trimFloatNumber(point.time * this.blend_transition, 4).toString();
- if (!timecode.includes('.')) timecode += '.0';
- curve_output[timecode] = Math.roundTo(point.value, 6);
+ if (this.blend_transition) {
+ object.blend_transition = this.blend_transition;
+ let curve_keys = this.blend_transition_curve && Object.keys(this.blend_transition_curve);
+ if (curve_keys?.length) {
+ let curve_output = {};
+ let points = curve_keys.map(key => ({time: parseFloat(key), value: this.blend_transition_curve[key]}));
+ points.sort((a, b) => a.time - b.time);
+ for (let point of points) {
+ let timecode = trimFloatNumber(point.time * this.blend_transition, 4).toString();
+ if (!timecode.includes('.')) timecode += '.0';
+ curve_output[timecode] = Math.roundTo(point.value, 6);
+ }
+ object.blend_transition = curve_output;
}
- object.blend_transition = curve_output;
+ if (this.blend_via_shortest_path) object.blend_via_shortest_path = this.blend_via_shortest_path;
}
- if (this.blend_via_shortest_path) object.blend_via_shortest_path = this.blend_via_shortest_path;
Blockbench.dispatchEvent('compile_bedrock_animation_controller_state', {state: this, json: object});
return object;
}
@@ -475,19 +477,73 @@ class AnimationControllerState {
'generic.reset',
tl('dialog.blend_transition_edit.ease_in_out', [6]),
tl('dialog.blend_transition_edit.ease_in_out', [10]),
- tl('dialog.blend_transition_edit.ease_in_out', [16])
+ 'dialog.blend_transition_edit.generate',
],
click(index) {
- let point_amount = ([2, 6, 10, 16])[index];
- function hermiteBlend(t) {
- return 3*(t**2) - 2*(t**3);
+ function generate(easing, point_amount) {
+ points.empty();
+ for (let i = 0; i < point_amount; i++) {
+ let time = i / (point_amount-1);
+ points.push({time, value: 1-easing(time), uuid: guid()})
+ }
+ dialog.content_vue.updateGraph();
}
- points.empty();
- for (let i = 0; i < point_amount; i++) {
- let time = i / (point_amount-1);
- points.push({time, value: 1-hermiteBlend(time), uuid: guid()})
+ if (index == 3) {
+ let easings = {
+ easeInSine: 'In Sine',
+ easeOutSine: 'Out Sine',
+ easeInOutSine: 'In Out Sine',
+ easeInQuad: 'In Quad',
+ easeOutQuad: 'Out Quad',
+ easeInOutQuad: 'In Out Quad',
+ easeInCubic: 'In Cubic',
+ easeOutCubic: 'Out Cubic',
+ easeInOutCubic: 'In Out Cubic',
+ easeInQuart: 'In Quart',
+ easeOutQuart: 'Out Quart',
+ easeInOutQuart: 'In Out Quart',
+ easeInQuint: 'In Quint',
+ easeOutQuint: 'Out Quint',
+ easeInOutQuint: 'In Out Quint',
+ easeInExpo: 'In Expo',
+ easeOutExpo: 'Out Expo',
+ easeInOutExpo: 'In Out Expo',
+ easeInCirc: 'In Circ',
+ easeOutCirc: 'Out Circ',
+ easeInOutCirc: 'In Out Circ',
+ easeInBack: 'In Back',
+ easeOutBack: 'Out Back',
+ easeInOutBack: 'In Out Back',
+ easeInElastic: 'In Elastic',
+ easeOutElastic: 'Out Elastic',
+ easeInOutElastic: 'In Out Elastic',
+ easeOutBounce: 'Out Bounce',
+ easeInBounce: 'In Bounce',
+ easeInOutBounce: 'In Out Bounce',
+ };
+ new Dialog('blend_transition_edit_easing', {
+ title: 'dialog.blend_transition_edit.generate',
+ width: 380,
+ form: {
+ easings: {type: 'info', text: tl('dialog.blend_transition_edit.generate.learn_more') + ': [easings.net](https://easings.net)'},
+ curve: {type: 'select', label: 'dialog.blend_transition_edit.generate.curve', options: easings},
+ steps: {type: 'number', label: 'dialog.blend_transition_edit.generate.steps', value: 10, step: 1, min: 3, max: 64}
+ },
+ onConfirm(result) {
+ console.log(result);
+ let easing_func = Easings[result.curve];
+ generate(easing_func, result.steps);
+ }
+ }).show();
+
+ } else {
+ let point_amount = ([2, 6, 10])[index];
+ function hermiteBlend(t) {
+ return 3*(t**2) - 2*(t**3);
+ }
+ generate(hermiteBlend, point_amount);
}
- dialog.content_vue.updateGraph();
+
}
}
},
@@ -562,7 +618,13 @@ class AnimationControllerState {
},
preview() {
if (this.points.length == 0) return 0;
- let time = (((performance.now() - preview_loop_start_time) / 1000) / this.duration) % 1;
+ let pause = 0.4;
+ let absolute_time = ((performance.now() - preview_loop_start_time) / 1000);
+ let time = (absolute_time % (this.duration + pause)) / this.duration;
+ if (time > 1) {
+ this.preview_value = 0;
+ return;
+ }
let prev_time = -Infinity, prev = 0;
let next_time = Infinity, next = 0;
for (let pt of points) {
diff --git a/js/interface/about.js b/js/interface/about.js
index 143a71e8..c143bb9a 100644
--- a/js/interface/about.js
+++ b/js/interface/about.js
@@ -116,6 +116,7 @@ BARS.defineActions(() => {
jQuery UI
jQuery UI Touch Punch
FileSaver.js
+ easing-utils
PeerJS
Marked
DOMPurify
diff --git a/lang/en.json b/lang/en.json
index 6f20ed44..4dc7a2bf 100644
--- a/lang/en.json
+++ b/lang/en.json
@@ -627,6 +627,10 @@
"dialog.merge_animation.merge_target": "Merge into",
"dialog.blend_transition_edit.ease_in_out": "Ease-in-out (%0)",
+ "dialog.blend_transition_edit.generate": "Generate...",
+ "dialog.blend_transition_edit.generate.learn_more": "More info and preview",
+ "dialog.blend_transition_edit.generate.curve": "Easing Type",
+ "dialog.blend_transition_edit.generate.steps": "Keyframes",
"dialog.create_texture.folder": "Folder",
"dialog.create_texture.type": "Type",
diff --git a/lib/easing.js b/lib/easing.js
new file mode 100644
index 00000000..de44377c
--- /dev/null
+++ b/lib/easing.js
@@ -0,0 +1,307 @@
+// Based on https://gist.github.com/gre/1650294
+class Easings {
+// No easing, no acceleration
+static linear( t ) {
+ return t;
+}
+
+// Slight acceleration from zero to full speed
+static easeInSine( t ) {
+ return -1 * Math.cos( t * ( Math.PI / 2 ) ) + 1;
+}
+
+// Slight deceleration at the end
+static easeOutSine( t ) {
+ return Math.sin( t * ( Math.PI / 2 ) );
+}
+
+// Slight acceleration at beginning and slight deceleration at end
+static easeInOutSine( t ) {
+ return -0.5 * ( Math.cos( Math.PI * t ) - 1 );
+}
+
+// Accelerating from zero velocity
+static easeInQuad( t ) {
+ return t * t;
+}
+
+// Decelerating to zero velocity
+static easeOutQuad( t ) {
+ return t * ( 2 - t );
+}
+
+// Acceleration until halfway, then deceleration
+static easeInOutQuad( t ) {
+ return t < 0.5 ? 2 * t * t : - 1 + ( 4 - 2 * t ) * t;
+}
+
+// Accelerating from zero velocity
+static easeInCubic( t ) {
+ return t * t * t;
+}
+
+// Decelerating to zero velocity
+static easeOutCubic( t ) {
+ const t1 = t - 1;
+ return t1 * t1 * t1 + 1;
+}
+
+// Acceleration until halfway, then deceleration
+static easeInOutCubic( t ) {
+ return t < 0.5 ? 4 * t * t * t : ( t - 1 ) * ( 2 * t - 2 ) * ( 2 * t - 2 ) + 1;
+}
+
+// Accelerating from zero velocity
+static easeInQuart( t ) {
+ return t * t * t * t;
+}
+
+// Decelerating to zero velocity
+static easeOutQuart( t ) {
+ const t1 = t - 1;
+ return 1 - t1 * t1 * t1 * t1;
+}
+
+// Acceleration until halfway, then deceleration
+static easeInOutQuart( t ) {
+ const t1 = t - 1;
+ return t < 0.5 ? 8 * t * t * t * t : 1 - 8 * t1 * t1 * t1 * t1;
+}
+
+// Accelerating from zero velocity
+static easeInQuint( t ) {
+ return t * t * t * t * t;
+}
+
+// Decelerating to zero velocity
+static easeOutQuint( t ) {
+ const t1 = t - 1;
+ return 1 + t1 * t1 * t1 * t1 * t1;
+}
+
+// Acceleration until halfway, then deceleration
+static easeInOutQuint( t ) {
+ const t1 = t - 1;
+ return t < 0.5 ? 16 * t * t * t * t * t : 1 + 16 * t1 * t1 * t1 * t1 * t1;
+}
+
+// Accelerate exponentially until finish
+static easeInExpo( t ) {
+
+ if( t === 0 ) {
+ return 0;
+ }
+
+ return Math.pow( 2, 10 * ( t - 1 ) );
+
+}
+
+// Initial exponential acceleration slowing to stop
+static easeOutExpo( t ) {
+
+ if( t === 1 ) {
+ return 1;
+ }
+
+ return ( -Math.pow( 2, -10 * t ) + 1 );
+
+}
+
+// Exponential acceleration and deceleration
+static easeInOutExpo( t ) {
+
+ if( t === 0 || t === 1 ) {
+ return t;
+ }
+
+ const scaledTime = t * 2;
+ const scaledTime1 = scaledTime - 1;
+
+ if( scaledTime < 1 ) {
+ return 0.5 * Math.pow( 2, 10 * ( scaledTime1 ) );
+ }
+
+ return 0.5 * ( -Math.pow( 2, -10 * scaledTime1 ) + 2 );
+
+}
+
+// Increasing velocity until stop
+static easeInCirc( t ) {
+
+ const scaledTime = t / 1;
+ return -1 * ( Math.sqrt( 1 - scaledTime * t ) - 1 );
+
+}
+
+// Start fast, decreasing velocity until stop
+static easeOutCirc( t ) {
+
+ const t1 = t - 1;
+ return Math.sqrt( 1 - t1 * t1 );
+
+}
+
+// Fast increase in velocity, fast decrease in velocity
+static easeInOutCirc( t ) {
+
+ const scaledTime = t * 2;
+ const scaledTime1 = scaledTime - 2;
+
+ if( scaledTime < 1 ) {
+ return -0.5 * ( Math.sqrt( 1 - scaledTime * scaledTime ) - 1 );
+ }
+
+ return 0.5 * ( Math.sqrt( 1 - scaledTime1 * scaledTime1 ) + 1 );
+
+}
+
+// Slow movement backwards then fast snap to finish
+static easeInBack( t, magnitude = 1.70158 ) {
+
+ return t * t * ( ( magnitude + 1 ) * t - magnitude );
+
+}
+
+// Fast snap to backwards point then slow resolve to finish
+static easeOutBack( t, magnitude = 1.70158 ) {
+
+ const scaledTime = ( t / 1 ) - 1;
+
+ return (
+ scaledTime * scaledTime * ( ( magnitude + 1 ) * scaledTime + magnitude )
+ ) + 1;
+
+}
+
+// Slow movement backwards, fast snap to past finish, slow resolve to finish
+static easeInOutBack( t, magnitude = 1.70158 ) {
+
+ const scaledTime = t * 2;
+ const scaledTime2 = scaledTime - 2;
+
+ const s = magnitude * 1.525;
+
+ if( scaledTime < 1) {
+
+ return 0.5 * scaledTime * scaledTime * (
+ ( ( s + 1 ) * scaledTime ) - s
+ );
+
+ }
+
+ return 0.5 * (
+ scaledTime2 * scaledTime2 * ( ( s + 1 ) * scaledTime2 + s ) + 2
+ );
+
+}
+// Bounces slowly then quickly to finish
+static easeInElastic( t, magnitude = 0.7 ) {
+
+ if( t === 0 || t === 1 ) {
+ return t;
+ }
+
+ const scaledTime = t / 1;
+ const scaledTime1 = scaledTime - 1;
+
+ const p = 1 - magnitude;
+ const s = p / ( 2 * Math.PI ) * Math.asin( 1 );
+
+ return -(
+ Math.pow( 2, 10 * scaledTime1 ) *
+ Math.sin( ( scaledTime1 - s ) * ( 2 * Math.PI ) / p )
+ );
+
+}
+
+// Fast acceleration, bounces to zero
+static easeOutElastic( t, magnitude = 0.7 ) {
+
+ if( t === 0 || t === 1 ) {
+ return t;
+ }
+
+ const p = 1 - magnitude;
+ const scaledTime = t * 2;
+
+ const s = p / ( 2 * Math.PI ) * Math.asin( 1 );
+ return (
+ Math.pow( 2, -10 * scaledTime ) *
+ Math.sin( ( scaledTime - s ) * ( 2 * Math.PI ) / p )
+ ) + 1;
+
+}
+
+// Slow start and end, two bounces sandwich a fast motion
+static easeInOutElastic( t, magnitude = 0.65 ) {
+
+ if( t === 0 || t === 1 ) {
+ return t;
+ }
+
+ const p = 1 - magnitude;
+ const scaledTime = t * 2;
+ const scaledTime1 = scaledTime - 1;
+
+ const s = p / ( 2 * Math.PI ) * Math.asin( 1 );
+
+ if( scaledTime < 1 ) {
+ return -0.5 * (
+ Math.pow( 2, 10 * scaledTime1 ) *
+ Math.sin( ( scaledTime1 - s ) * ( 2 * Math.PI ) / p )
+ );
+ }
+
+ return (
+ Math.pow( 2, -10 * scaledTime1 ) *
+ Math.sin( ( scaledTime1 - s ) * ( 2 * Math.PI ) / p ) * 0.5
+ ) + 1;
+
+}
+
+// Bounce to completion
+static easeOutBounce( t ) {
+
+ const scaledTime = t / 1;
+
+ if( scaledTime < ( 1 / 2.75 ) ) {
+
+ return 7.5625 * scaledTime * scaledTime;
+
+ } else if( scaledTime < ( 2 / 2.75 ) ) {
+
+ const scaledTime2 = scaledTime - ( 1.5 / 2.75 );
+ return ( 7.5625 * scaledTime2 * scaledTime2 ) + 0.75;
+
+ } else if( scaledTime < ( 2.5 / 2.75 ) ) {
+
+ const scaledTime2 = scaledTime - ( 2.25 / 2.75 );
+ return ( 7.5625 * scaledTime2 * scaledTime2 ) + 0.9375;
+
+ } else {
+
+ const scaledTime2 = scaledTime - ( 2.625 / 2.75 );
+ return ( 7.5625 * scaledTime2 * scaledTime2 ) + 0.984375;
+
+ }
+
+}
+
+// Bounce increasing in velocity until completion
+static easeInBounce( t ) {
+ return 1 - Easings.easeOutBounce( 1 - t );
+}
+
+// Bounce in and bounce out
+static easeInOutBounce( t ) {
+
+ if( t < 0.5 ) {
+
+ return Easings.easeInBounce( t * 2 ) * 0.5;
+
+ }
+
+ return ( Easings.easeOutBounce( ( t * 2 ) - 1 ) * 0.5 ) + 0.5;
+
+}
+}