Add blend transition curve generator

This commit is contained in:
JannisX11 2024-09-14 21:26:51 +02:00
parent 06fd67e0fe
commit 50c8358738
5 changed files with 397 additions and 22 deletions

View File

@ -89,6 +89,7 @@
<script src="lib/fik.min.js"></script>
<script src="lib/molang.umd.js"></script>
<script src="lib/wintersky.umd.js"></script>
<script src="lib/easing.js"></script>
<script src="bundle.js"></script>
<script src="js/preview/OrbitControls.js"></script>

View File

@ -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) {

View File

@ -116,6 +116,7 @@ BARS.defineActions(() => {
<li><a class="open-in-browser" href="https://jqueryui.com">jQuery UI</a></li>
<li><a class="open-in-browser" href="https://github.com/furf/jquery-ui-touch-punch">jQuery UI Touch Punch</a></li>
<li><a class="open-in-browser" href="https://github.com/eligrey/FileSaver.js">FileSaver.js</a></li>
<li><a class="open-in-browser" href="https://github.com/AndrewRayCode/easing-utils">easing-utils</a></li>
<li><a class="open-in-browser" href="https://peerjs.com">PeerJS</a></li>
<li><a class="open-in-browser" href="https://github.com/markedjs/marked">Marked</a></li>
<li><a class="open-in-browser" href="https://github.com/cure53/DOMPurify">DOMPurify</a></li>

View File

@ -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",

307
lib/easing.js Normal file
View File

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