Add animation onion skinning

This commit is contained in:
JannisX11 2023-09-10 22:35:51 +02:00
parent f70d7da324
commit 63dbcf7055
5 changed files with 129 additions and 4 deletions

View File

@ -1047,6 +1047,21 @@
margin-top: -2px;
height: 5000px;
}
#timeline_onion_skin_point {
position: absolute;
z-index: 2;
cursor: ew-resize;
height: 26px;
width: 18px;
top: 0;
margin-left: -8px;
border-right: 9px solid transparent;
border-left: 9px solid transparent;
border-top: 12px solid var(--color-text);
border-radius: 2px;
}
#timeline_endbracket {
position: absolute;
z-index: 2;

View File

@ -914,6 +914,7 @@ const Animator = {
get selected() {return Animation.selected},
MolangParser: new Molang(),
motion_trail: new THREE.Object3D(),
onion_skin_object: new THREE.Object3D(),
motion_trail_lock: false,
_last_values: {},
resetLastValues() {
@ -976,6 +977,7 @@ const Animator = {
scene.remove(WinterskyScene.space);
scene.remove(Animator.motion_trail);
scene.remove(Animator.onion_skin_object);
Animator.resetParticles(true);
three_grid.position.z = three_grid.position.x;
@ -1117,6 +1119,63 @@ const Animator = {
keyframe_points.keyframeUUIDs = keyframeUUIDs;
Animator.motion_trail.add(keyframe_points);
},
updateOnionSkin() {
let mode = BarItems.animation_onion_skin.value;
if (mode == 'off') {
Animator.onion_skin_object.children.empty();
return;
}
let times = [];
if (mode == 'previous') {
times = [Timeline.time - Timeline.getStep()];
} else if (mode == 'next') {
times = [Timeline.time + Timeline.getStep()];
} else if (mode == 'previous_next') {
times = [
Timeline.time - Timeline.getStep(),
Timeline.time + Timeline.getStep()
];
} else if (mode == 'select') {
times = [Timeline.vue.onion_skin_time];
}
let elements = Outliner.elements;
let last_time = Timeline.time;
Animator.onion_skin_object.children.empty();
let i = -1;
for (let time of times) {
i++;
Timeline.time = time;
Animator.showDefaultPose(true);
Animator.stackAnimations(Animation.all.filter(a => a.playing), false);
elements.forEach(obj => {
if (!obj.visibility) return;
let mesh = obj.mesh;
if (!mesh || !mesh.geometry || !mesh.outline) return;
let copy = mesh.outline.clone();
copy.geometry = mesh.outline.geometry.clone();
copy.material = Canvas.gridMaterial;
copy.visible = true;
THREE.fastWorldPosition(mesh, copy.position);
copy.position.sub(scene.position);
copy.rotation.setFromQuaternion(mesh.getWorldQuaternion(new THREE.Quaternion()));
mesh.getWorldScale(copy.scale);
copy.name = obj.uuid+'_onion_skin_outline';
Animator.onion_skin_object.add(copy);
})
}
Timeline.time = last_time;
Animator.showDefaultPose(true);
Animator.stackAnimations(Animation.all.filter(a => a.playing), false);
scene.add(Animator.onion_skin_object);
},
stackAnimations(animations, in_loop, controller_blend_values = 0) {
[...Group.all, ...Outliner.elements].forEach(node => {
if (!node.constructor.animator) return;
@ -1201,6 +1260,8 @@ const Animator = {
Canvas.ground_plane.position.z = Canvas.ground_plane.position.x;
}
Animator.updateOnionSkin();
if (Interface.Panels.variable_placeholders.inside_vue.text.match(/^\s*preview\.texture\s*=/mi)) {
let tex_index = Animator.MolangParser.variableHandler('preview.texture');
let texture = Texture.all[tex_index % Texture.all.length];
@ -1691,7 +1752,7 @@ const Animator = {
}
}
}
Canvas.gizmos.push(Animator.motion_trail);
Canvas.gizmos.push(Animator.motion_trail, Animator.onion_skin_object);
Blockbench.on('reset_project', () => {
for (let path in Animator.particle_effects) {
let effect = Animator.particle_effects[path];
@ -1908,6 +1969,23 @@ BARS.defineActions(function() {
}
})
// Onion Skin
new BarSelect('animation_onion_skin', {
category: 'view',
condition: {modes: ['animate']},
value: 'off',
options: {
off: true,
select: true,
previous: true,
next: true,
previous_next: true
},
onChange() {
Timeline.vue.onion_skin_mode = this.value;
}
})
new Action('bake_animation_into_model', {
icon: 'directions_run',
category: 'animation',

View File

@ -298,6 +298,10 @@ const Timeline = {
Blockbench.showQuickMessage('message.no_animation_selected');
}
} else if (e.target.id == 'timeline_onion_skin_point') {
Timeline.dragging_onion_skin_point = true;
} else {
convertTouchEvent(e);
@ -349,6 +353,17 @@ const Timeline = {
Animation.selected.setLength(time)
Timeline.revealTime(time)
} else if (Timeline.dragging_onion_skin_point) {
convertTouchEvent(e);
let offset = e.clientX - $('#timeline_time').offset().left;
let time = Timeline.snapTime(offset / Timeline.vue._data.size)
if (Timeline.vue.onion_skin_time != time) {
Timeline.vue.onion_skin_time = time;
Timeline.revealTime(time);
Animator.updateOnionSkin();
}
}
})
.on('mouseup touchend', e => {
@ -358,9 +373,11 @@ const Timeline = {
Timeline.pause();
} else if (Timeline.dragging_endbracket) {
Undo.finishEdit('Change Animation Length')
delete Timeline.dragging_endbracket
} else if (Timeline.dragging_onion_skin_point) {
delete Timeline.dragging_onion_skin_point
}
})
@ -713,6 +730,9 @@ Interface.definePanels(() => {
show_all_handles: !Settings.get('only_selected_bezier_handles'),
loop_graphs: [''],
onion_skin_mode: BarItems.animation_onion_skin.value,
onion_skin_time: 0,
channels: {
rotation: true,
position: true,
@ -1418,10 +1438,14 @@ Interface.definePanels(() => {
</div>
<div id="timeline_playhead"
v-bind:style="{left: (playhead * size) + 'px'}"
></div>
/>
<div id="timeline_onion_skin_point"
v-if="onion_skin_mode == 'select'"
v-bind:style="{left: (onion_skin_time * size) + 'px'}"
/>
<div id="timeline_endbracket"
v-bind:style="{left: (animation_length * size) + 'px'}"
></div>
/>
<div
v-for="marker in markers"
class="timeline_marker"

View File

@ -329,6 +329,7 @@ const MenuBar = {
new BarMenu('animation', [
new MenuSeparator('edit_options'),
'animation_onion_skin',
'lock_motion_trail',
new MenuSeparator('edit'),
'add_marker',

View File

@ -1705,6 +1705,13 @@
"action.set_ik_source.desc": "Select the root bone of the chain that should be moved by this null object via Inverse Kinematics",
"action.lock_motion_trail": "Lock Motion Trail",
"action.lock_motion_trail.desc": "Lock the motion trail to the currently selected group",
"action.animation_onion_skin": "Animation Onion Skin",
"action.animation_onion_skin.desc": "Display an wireframe view of a different frame in the animation for reference",
"action.animation_onion_skin.off": "Off",
"action.animation_onion_skin.select": "Select",
"action.animation_onion_skin.previous": "Previous",
"action.animation_onion_skin.next": "Next",
"action.animation_onion_skin.previous_next": "Previous + Next",
"action.looped_animation_playback": "Looped Playback",
"action.looped_animation_playback.desc": "Preview animations in an infinite loop",
"action.bedrock_animation_mode": "Bedrock Animation Mode",