Keyframe interpolation interface and io

This commit is contained in:
JannisX11 2020-09-27 10:01:39 +02:00
parent c3b6922d52
commit 992f42e3a0
7 changed files with 147 additions and 70 deletions

View File

@ -890,7 +890,7 @@
:title="tl('timeline.'+keyframe.channel)"
@contextmenu.prevent="keyframe.showContextMenu($event)"
>
<i class="material-icons">stop</i>
<i class="material-icons">{{ keyframe.interpolation == 'catmullrom' ? 'lens' : 'stop' }}</i>
<svg class="keyframe_waveform" v-if="keyframe.channel == 'sound' && keyframe.file && waveforms[keyframe.file]" :style="{width: waveforms[keyframe.file].duration * size}">
<polygon :points="getWaveformPoints(waveforms[keyframe.file].samples, size)"></polygon>
</svg>

View File

@ -1215,6 +1215,7 @@ const Animator = {
ba.addKeyframe({
time: parseFloat(timestamp),
channel,
interpolation: b[channel][timestamp].lerp_mode,
data_points: getKeyframeDataPoints(b[channel][timestamp]),
});
}
@ -1369,6 +1370,26 @@ Blockbench.addDragHandler('animation', {
})
BARS.defineActions(function() {
new NumSlider('slider_animation_length', {
category: 'animation',
condition: () => Animator.open && Animation.selected,
getInterval(event) {
if (event && event.shiftKey) return 1;
return Timeline.getStep()
},
get: function() {
return Animation.selected.length
},
change: function(modify) {
Animation.selected.setLength(limitNumber(modify(Animation.selected.length), 0, 1e4))
},
onBefore: function() {
Undo.initEdit({animations: [Animation.selected]});
},
onAfter: function() {
Undo.finishEdit('Change Animation Length')
}
})
new Action('add_animation', {
icon: 'fa-plus-circle',
category: 'animation',

View File

@ -218,11 +218,19 @@ class Keyframe {
compileBedrockKeyframe() {
if (this.transform) {
if (this.data_points.length == 1) {
return this.getArray()
if (this.interpolation == 'linear') {
return this.getArray();
} else {
return {
post: this.getArray(),
lerp_mode: this.interpolation,
}
}
} else {
return {
pre: this.getArray(0),
post: this.getArray(1),
lerp_mode: this.interpolation != 'linear' ? this.interpolation : undefined,
}
}
} else if (this.channel == 'timeline') {
@ -237,7 +245,7 @@ class Keyframe {
let points = [];
this.data_points.forEach(data_point => {
if (data_point.effect) {
let script = kf.script || undefined;
let script = this.script || undefined;
if (script && !script.match(/;$/)) script += ';';
points.push({
effect: data_point.effect,
@ -375,6 +383,8 @@ class Keyframe {
},*/
'change_keyframe_file',
'_',
// todo: integrate
'keyframe_interpolation',
{name: 'menu.cube.color', icon: 'color_lens', children: [
{icon: 'bubble_chart', name: 'generic.unset', click: function(kf) {kf.forSelected(kf2 => {kf2.color = -1}, 'change color')}},
{icon: 'bubble_chart', color: markerColors[0].standard, name: 'cube.color.'+markerColors[0].name, click: function(kf) {kf.forSelected(function(kf2){kf2.color = 0}, 'change color')}},
@ -391,6 +401,7 @@ class Keyframe {
])
new Property(Keyframe, 'number', 'time')
new Property(Keyframe, 'number', 'color', {default: -1})
new Property(Keyframe, 'string', 'interpolation', {default: 'linear'})
Keyframe.selected = [];
// Misc Functions
@ -410,6 +421,7 @@ function updateKeyframeSelection() {
})
if (Timeline.selected.length) {
BarItems.slider_keyframe_time.update()
BarItems.keyframe_interpolation.set(Timeline.selected[0].interpolation)
}
BARS.updateConditions()
Blockbench.dispatchEvent('update_keyframe_selection');
@ -564,26 +576,6 @@ BARS.defineActions(function() {
}
})
new NumSlider('slider_animation_length', {
category: 'animation',
condition: () => Animator.open && Animation.selected,
getInterval(event) {
if (event && event.shiftKey) return 1;
return Timeline.getStep()
},
get: function() {
return Animation.selected.length
},
change: function(modify) {
Animation.selected.setLength(limitNumber(modify(Animation.selected.length), 0, 1e4))
},
onBefore: function() {
Undo.initEdit({animations: [Animation.selected]});
},
onAfter: function() {
Undo.finishEdit('Change Animation Length')
}
})
new NumSlider('slider_keyframe_time', {
category: 'animation',
condition: () => Animator.open && Timeline.selected.length,
@ -607,6 +599,21 @@ BARS.defineActions(function() {
Undo.finishEdit('move keyframes')
}
})
new BarSelect('keyframe_interpolation', {
category: 'animation',
condition: () => Animator.open && Timeline.selected.length,
options: {
linear: true,
catmullrom: true,
},
onChange: function(sel, event) {
Undo.initEdit({keyframes: Timeline.selected})
Timeline.selected.forEach((kf) => {
kf.interpolation = sel.value;
})
Undo.finishEdit('change keyframes interpolation')
}
})
new Action('reset_keyframe', {
icon: 'replay',
category: 'animation',

View File

@ -146,6 +146,8 @@ const Timeline = {
if (bot < 0) body.scrollTop = Math.clamp(body.scrollTop + speed, 0, body_inner.clientHeight - body.clientHeight + 3);
if (lef < 0) body.scrollLeft = body.scrollLeft - speed;
if (rig < 0) body.scrollLeft = Math.clamp(body.scrollLeft + speed, 0, body_inner.clientWidth - body.clientWidth);
updateKeyframeSelection()
},
end(e) {
if (!Timeline.selector.selecting) return false;

View File

@ -1621,6 +1621,7 @@ const BARS = {
id: 'keyframe',
children: [
'slider_keyframe_time',
'keyframe_interpolation',
'change_keyframe_file',
'reset_keyframe'
]

View File

@ -147,61 +147,103 @@ class Menu {
if (last.length && !last.hasClass('menu_separator')) {
parent.append(entry)
}
} else if (typeof s === 'string' || s instanceof Action) {
if (typeof s === 'string') {
s = BarItems[s]
}
if (!s) {
return;
}
entry = $(s.menu_node)
if (BARS.condition(s.condition)) {
return;
}
if (typeof s == 'string' && BarItems[s]) {
s = BarItems[s];
}
if (!Condition(s.condition, context)) return;
entry.off('click')
entry.off('mouseenter mousedown')
entry.on('mouseenter mousedown', function(e) {
if (this == e.target) {
scope.hover(this, e)
}
})
//Submenu
if (typeof s.children == 'function' || typeof s.children == 'object') {
createChildList(s, entry)
} else {
entry.on('click', (e) => {s.trigger(e)})
//entry[0].addEventListener('click', )
if (s instanceof Action) {
entry = $(s.menu_node)
entry.off('click')
entry.off('mouseenter mousedown')
entry.on('mouseenter mousedown', function(e) {
if (this == e.target) {
scope.hover(this, e)
}
})
//Submenu
if (typeof s.children == 'function' || typeof s.children == 'object') {
createChildList(s, entry)
} else {
entry.on('click', (e) => {s.trigger(e)})
//entry[0].addEventListener('click', )
}
parent.append(entry)
} else if (s instanceof BarSelect) {
if (typeof s.icon === 'function') {
var icon = Blockbench.getIconNode(s.icon(context), s.color)
} else {
var icon = Blockbench.getIconNode(s.icon, s.color)
}
entry = $(`<li title="${s.description||''}" menu_item="${s.id}">${tl(s.name)}</li>`)
entry.prepend(icon)
//Submenu
var children = [];
for (var key in s.options) {
let val = s.options[key];
if (val) {
(function() {
var save_key = key;
children.push({
name: s.getNameFor(key),
id: key,
icon: val.icon || ((s.value == save_key) ? 'far.fa-dot-circle' : 'far.fa-circle'),
condition: val.condition,
click: (e) => {
s.set(save_key);
if (s.onChange) {
s.onChange(s, e);
}
}
})
})()
}
}
let child_count = createChildList({children}, entry)
if (child_count !== 0 || typeof s.click === 'function') {
parent.append(entry)
}
entry.mouseenter(function(e) {
scope.hover(this, e)
})
} else if (typeof s === 'object') {
if (BARS.condition(s.condition, context)) {
let child_count;
if (typeof s.icon === 'function') {
var icon = Blockbench.getIconNode(s.icon(context), s.color)
} else {
var icon = Blockbench.getIconNode(s.icon, s.color)
}
entry = $(`<li title="${s.description||''}" menu_item="${s.id}">${tl(s.name)}</li>`)
entry.prepend(icon)
if (typeof s.click === 'function') {
entry.click(e => {
if (e.target == entry.get(0)) {
s.click(context, e)
}
})
}
//Submenu
if (typeof s.children == 'function' || typeof s.children == 'object') {
child_count = createChildList(s, entry)
}
if (child_count !== 0 || typeof s.click === 'function') {
parent.append(entry)
}
entry.mouseenter(function(e) {
scope.hover(this, e)
let child_count;
if (typeof s.icon === 'function') {
var icon = Blockbench.getIconNode(s.icon(context), s.color)
} else {
var icon = Blockbench.getIconNode(s.icon, s.color)
}
entry = $(`<li title="${s.description||''}" menu_item="${s.id}">${tl(s.name)}</li>`)
entry.prepend(icon)
if (typeof s.click === 'function') {
entry.click(e => {
if (e.target == entry.get(0)) {
s.click(context, e)
}
})
}
//Submenu
if (typeof s.children == 'function' || typeof s.children == 'object') {
child_count = createChildList(s, entry)
}
if (child_count !== 0 || typeof s.click === 'function') {
parent.append(entry)
}
entry.mouseenter(function(e) {
scope.hover(this, e)
})
}
}

View File

@ -958,6 +958,10 @@
"action.slider_animation_length.desc": "Change the length of the selected animation",
"action.slider_keyframe_time": "Timecode",
"action.slider_keyframe_time.desc": "Change the timecode of the selected keyframes",
"action.keyframe_interpolation": "Interpolation",
"action.keyframe_interpolation.desc": "Select the keyframe interpolation mode",
"action.keyframe_interpolation.linear": "Linear",
"action.keyframe_interpolation.catmullrom": "Smooth",
"action.change_keyframe_file": "Select File",
"action.change_keyframe_file.desc": "Select an audio file to preview a sound effect.",
"action.reset_keyframe": "Reset Keyframe",