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; + +} +}