mirror of
https://github.com/JannisX11/blockbench.git
synced 2025-02-23 16:31:20 +08:00
Improve bezier keyframes
Export bezier keyframes to bedrock by baking
This commit is contained in:
parent
bbedda916b
commit
e5302cbaa3
@ -1072,7 +1072,8 @@
|
||||
}
|
||||
|
||||
.keyframe_bezier_handle {
|
||||
background-color: var(--color-text);
|
||||
background-color: var(--color-back);
|
||||
border: 2px solid var(--color-text);
|
||||
border-radius: 50%;
|
||||
height: 10px;
|
||||
width: 10px;
|
||||
@ -1082,7 +1083,7 @@
|
||||
position: absolute;
|
||||
}
|
||||
.keyframe_bezier_handle:hover, .keyframe_bezier_handle:active {
|
||||
background-color: var(--color-accent);
|
||||
border-color: var(--color-accent);
|
||||
}
|
||||
.keyframe_bezier_handle::after {
|
||||
content: "";
|
||||
@ -1093,10 +1094,9 @@
|
||||
width: var(--length);
|
||||
transform-origin: left;
|
||||
rotate: var(--angle);
|
||||
top: 4px;
|
||||
left: 5px;
|
||||
top: 2px;
|
||||
left: 3px;
|
||||
pointer-events: none;
|
||||
border-left: 5px solid var(--color-text);
|
||||
}
|
||||
|
||||
#timeline_header {
|
||||
|
@ -193,37 +193,66 @@ class Animation extends AnimationItem {
|
||||
bone_tag.relative_to = {rotation: 'entity'};
|
||||
bone_tag.rotation = [0, 0, 0.01];
|
||||
}
|
||||
//Saving Keyframes
|
||||
animator.keyframes.forEach(function(kf) {
|
||||
if (!channels[kf.channel]) {
|
||||
channels[kf.channel] = {};
|
||||
}
|
||||
let timecode = kf.getTimecodeString();
|
||||
channels[kf.channel][timecode] = kf.compileBedrockKeyframe()
|
||||
if (animator.rotation_global && kf.channel == 'rotation' && channels[kf.channel][timecode] instanceof Array && channels[kf.channel][timecode].allEqual(0)) {
|
||||
channels[kf.channel][timecode][2] = 0.01;
|
||||
}
|
||||
})
|
||||
// Sorting + compressing keyframes
|
||||
for (var channel in Animator.possible_channels) {
|
||||
if (channels[channel]) {
|
||||
let timecodes = Object.keys(channels[channel])
|
||||
if (timecodes.length === 1 && animator[channel][0].data_points.length == 1 && animator[channel][0].interpolation != 'catmullrom') {
|
||||
bone_tag[channel] = channels[channel][timecodes[0]]
|
||||
if (channel == 'scale' &&
|
||||
channels[channel][timecodes[0]] instanceof Array &&
|
||||
channels[channel][timecodes[0]].allEqual(channels[channel][timecodes[0]][0])
|
||||
) {
|
||||
bone_tag[channel] = channels[channel][timecodes[0]][0];
|
||||
if (!animator[channel]?.length) continue;
|
||||
|
||||
// Saving Keyframes
|
||||
bone_tag[channel] = {};
|
||||
let sorted_keyframes = animator[channel].slice().sort((a, b) => a.time - b.time);
|
||||
|
||||
sorted_keyframes.forEach((kf, i) => {
|
||||
let timecode = kf.getTimecodeString();
|
||||
bone_tag[channel][timecode] = kf.compileBedrockKeyframe()
|
||||
if (animator.rotation_global && kf.channel == 'rotation' && bone_tag[kf.channel][timecode] instanceof Array && bone_tag[kf.channel][timecode].allEqual(0)) {
|
||||
bone_tag[kf.channel][timecode][2] = 0.01;
|
||||
}
|
||||
// Bake bezier keyframe curve
|
||||
let next_keyframe = sorted_keyframes[i+1];
|
||||
if (next_keyframe && kf.interpolation === 'bezier' && next_keyframe.interpolation === 'bezier') {
|
||||
let interval = 1 / this.snapping;
|
||||
let interpolated_values = {};
|
||||
for (let time = kf.time + interval; time < next_keyframe.time + (interval/2); time += interval) {
|
||||
let itimecode = trimFloatNumber(Timeline.snapTime(time, this)).toString();
|
||||
if (!itimecode.includes('.')) itimecode += '.0';
|
||||
let lerp = Math.getLerp(kf.time, next_keyframe.time, time)
|
||||
let value = [0, 1, 2].map(axis => {
|
||||
return kf.getBezierLerp(kf, next_keyframe, getAxisLetter(axis), lerp);
|
||||
})
|
||||
interpolated_values[itimecode] = value;
|
||||
}
|
||||
} else {
|
||||
timecodes.sort((a, b) => parseFloat(a) - parseFloat(b)).forEach((timecode) => {
|
||||
if (!bone_tag[channel]) {
|
||||
bone_tag[channel] = {}
|
||||
// Optimize data
|
||||
let itimecodes = Object.keys(interpolated_values);
|
||||
let skipped = 0;
|
||||
let threshold = channel == 'scale' ? 0.01 : 0.1;
|
||||
itimecodes.forEach((itimecode, ti) => {
|
||||
let value = interpolated_values[itimecode]
|
||||
let last = interpolated_values[itimecodes[ti-1]] || bone_tag[channel][timecode];
|
||||
let next = interpolated_values[itimecodes[ti+1]];
|
||||
if (!next) return;
|
||||
let all_axes_irrelevant = value.allAre((val, axis) => {
|
||||
let diff = Math.abs((last[axis] - val) - (val - next[axis]));
|
||||
return diff < threshold
|
||||
})
|
||||
if (all_axes_irrelevant && skipped < 3) {
|
||||
skipped++;
|
||||
} else {
|
||||
bone_tag[channel][itimecode] = value;
|
||||
skipped = 0;
|
||||
}
|
||||
bone_tag[channel][timecode] = channels[channel][timecode];
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
// Compressing keyframes
|
||||
let timecodes = Object.keys(bone_tag[channel]);
|
||||
if (timecodes.length === 1 && animator[channel][0].data_points.length == 1 && animator[channel][0].interpolation != 'catmullrom') {
|
||||
bone_tag[channel] = bone_tag[channel][timecodes[0]]
|
||||
if (channel == 'scale' &&
|
||||
bone_tag[channel][timecodes[0]] instanceof Array &&
|
||||
bone_tag[channel][timecodes[0]].allEqual(bone_tag[channel][timecodes[0]][0])
|
||||
) {
|
||||
bone_tag[channel] = bone_tag[channel][timecodes[0]][0];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -197,14 +197,17 @@ class Keyframe {
|
||||
return curve.getPoint(time).y;
|
||||
}
|
||||
getBezierLerp(before, after, axis, alpha) {
|
||||
let axis_num = getAxisNumber(axis);
|
||||
let val_before = before.calc(axis, 1);
|
||||
let val_after = after.calc(axis, 0);
|
||||
let axis_num = getAxisNumber(axis);
|
||||
let time_gap = after.time - before.time;
|
||||
let time_handle_before = Math.clamp(before.bezier_right_time[axis_num] || 0, 0, time_gap);
|
||||
let time_handle_after = Math.clamp(after.bezier_left_time[axis_num] || 0, -time_gap, 0);
|
||||
let vectors = [
|
||||
new THREE.Vector2(before.time, val_before),
|
||||
|
||||
new THREE.Vector2(before.time + before.bezier_right_time[axis_num] || 0, val_before + before.bezier_right_value[axis_num] || 0),
|
||||
new THREE.Vector2(after.time + after.bezier_left_time[axis_num] || 0, val_after + after.bezier_left_value[axis_num] || 0),
|
||||
new THREE.Vector2(before.time + time_handle_before, val_before + before.bezier_right_value[axis_num] || 0),
|
||||
new THREE.Vector2(after.time + time_handle_after, val_after + after.bezier_left_value[axis_num] || 0),
|
||||
|
||||
new THREE.Vector2(after.time, val_after),
|
||||
];
|
||||
@ -286,7 +289,7 @@ class Keyframe {
|
||||
compileBedrockKeyframe() {
|
||||
if (this.transform) {
|
||||
|
||||
if (this.interpolation != 'linear' && this.interpolation != 'step') {
|
||||
if (this.interpolation == 'catmullrom') {
|
||||
let previous = this.getPreviousKeyframe();
|
||||
let include_pre = (!previous && this.time > 0) || (previous && previous.interpolation != 'catmullrom')
|
||||
return {
|
||||
@ -508,6 +511,7 @@ class Keyframe {
|
||||
'keyframe_uniform',
|
||||
'keyframe_interpolation',
|
||||
'keyframe_bezier_linked',
|
||||
'reset_keyframe',
|
||||
{name: 'menu.cube.color', icon: 'color_lens', children() {
|
||||
return [
|
||||
{icon: 'bubble_chart', name: 'generic.unset', click: function(kf) {kf.forSelected(kf2 => {kf2.color = -1}, 'change color')}},
|
||||
@ -521,6 +525,7 @@ class Keyframe {
|
||||
}})
|
||||
];
|
||||
}},
|
||||
'_',
|
||||
'copy',
|
||||
'delete',
|
||||
])
|
||||
@ -528,11 +533,11 @@ class Keyframe {
|
||||
new Property(Keyframe, 'number', 'color', {default: -1})
|
||||
new Property(Keyframe, 'boolean', 'uniform', {condition: keyframe => keyframe.channel == 'scale', default: settings.uniform_keyframe.value})
|
||||
new Property(Keyframe, 'string', 'interpolation', {default: 'linear'})
|
||||
new Property(Keyframe, 'vector2', 'bezier_linked', {condition: keyframe => keyframe.interpolation == 'bezier', default: true})
|
||||
new Property(Keyframe, 'vector2', 'bezier_left_time', {default: [-0.1, -0.1, -0.1]});
|
||||
new Property(Keyframe, 'vector2', 'bezier_left_value');
|
||||
new Property(Keyframe, 'vector2', 'bezier_right_time', {default: [0.1, 0.1, 0.1]});
|
||||
new Property(Keyframe, 'vector2', 'bezier_right_value');
|
||||
new Property(Keyframe, 'boolean', 'bezier_linked', {default: true})
|
||||
new Property(Keyframe, 'vector', 'bezier_left_time', {default: [-0.1, -0.1, -0.1]});
|
||||
new Property(Keyframe, 'vector', 'bezier_left_value');
|
||||
new Property(Keyframe, 'vector', 'bezier_right_time', {default: [0.1, 0.1, 0.1]});
|
||||
new Property(Keyframe, 'vector', 'bezier_right_value');
|
||||
Keyframe.selected = [];
|
||||
Keyframe.interpolation = {
|
||||
linear: 'linear',
|
||||
@ -874,7 +879,13 @@ BARS.defineActions(function() {
|
||||
Undo.initEdit({keyframes})
|
||||
keyframes.forEach((kf) => {
|
||||
kf.bezier_linked = value;
|
||||
if (value) {
|
||||
kf.bezier_right_time.V3_set(kf.bezier_left_time).V3_multiply(-1);
|
||||
kf.bezier_right_value.V3_set(kf.bezier_left_value).V3_multiply(-1);
|
||||
}
|
||||
})
|
||||
Timeline.vue.show_zero_line = !Timeline.vue.show_zero_line;
|
||||
Timeline.vue.show_zero_line = !Timeline.vue.show_zero_line;
|
||||
Undo.finishEdit('Change keyframes bezier link option');
|
||||
updateKeyframeSelection();
|
||||
}
|
||||
@ -907,6 +918,12 @@ BARS.defineActions(function() {
|
||||
Undo.initEdit({keyframes: Timeline.selected})
|
||||
Timeline.selected.forEach((kf) => {
|
||||
kf.data_points.replace([new KeyframeDataPoint(kf)]);
|
||||
if (kf.interpolation == 'bezier') {
|
||||
kf.bezier_left_time.V3_set(-0.1, -0.1, -0.1);
|
||||
kf.bezier_left_value.V3_set(0, 0, 0);
|
||||
kf.bezier_right_time.V3_set(0.1, 0.1, 0.1);
|
||||
kf.bezier_right_value.V3_set(0, 0, 0);
|
||||
}
|
||||
})
|
||||
Undo.finishEdit('Reset keyframes')
|
||||
updateKeyframeSelection()
|
||||
|
@ -1057,7 +1057,6 @@ Interface.definePanels(() => {
|
||||
var difference_time = Math.clamp(offset[0] / Timeline.vue._data.size, -256, 256);
|
||||
var difference_value = Math.clamp(-offset[1] / Timeline.vue.graph_size, -256, 256);
|
||||
|
||||
|
||||
for (var kf of Timeline.selected) {
|
||||
if (kf.interpolation == 'bezier') {
|
||||
|
||||
@ -1085,7 +1084,7 @@ Interface.definePanels(() => {
|
||||
values_changed = true;
|
||||
}
|
||||
}
|
||||
let text = `${trimFloatNumber(difference_time)} ⨉ ${trimFloatNumber(difference_value)}`;
|
||||
let text = `${trimFloatNumber(Math.roundTo(difference_time, 2))} ⨉ ${trimFloatNumber(Math.roundTo(difference_value, 2))}`;
|
||||
Blockbench.setStatusBarText(text);
|
||||
|
||||
Timeline.vue.show_zero_line = !Timeline.vue.show_zero_line;
|
||||
@ -1282,10 +1281,12 @@ Interface.definePanels(() => {
|
||||
<template v-if="keyframe.interpolation == 'bezier'">
|
||||
<div class="keyframe_bezier_handle"
|
||||
:style="getBezierHandleStyle(keyframe, 'left')"
|
||||
:title="'${tl('generic.left')}: ' + trimFloatNumber(keyframe.bezier_left_time[graph_editor_axis_number]) + ' ⨉ ' + trimFloatNumber(keyframe.bezier_left_value[graph_editor_axis_number])"
|
||||
@mousedown="dragBezierHandle(keyframe, 'left', $event)" @touchstart="dragBezierHandle('left', $event)"
|
||||
></div>
|
||||
<div class="keyframe_bezier_handle"
|
||||
:style="getBezierHandleStyle(keyframe, 'right')"
|
||||
:title="'${tl('generic.right')}: ' + trimFloatNumber(keyframe.bezier_right_time[graph_editor_axis_number]) + ' ⨉ ' + trimFloatNumber(keyframe.bezier_right_value[graph_editor_axis_number])"
|
||||
@mousedown="dragBezierHandle(keyframe, 'right', $event)" @touchstart="dragBezierHandle('right', $event)"
|
||||
></div>
|
||||
</template>
|
||||
|
Loading…
Reference in New Issue
Block a user