mirror of
https://github.com/JannisX11/blockbench.git
synced 2025-01-30 15:42:42 +08:00
Improve timeline graph editor, render keyframes
This commit is contained in:
parent
566fdfdc2c
commit
6cf0fe4230
@ -714,7 +714,7 @@
|
|||||||
margin-left: -2px;
|
margin-left: -2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
#timeline_body .keyframe {
|
#timeline .keyframe {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
margin-left: -6px;
|
margin-left: -6px;
|
||||||
z-index: 3;
|
z-index: 3;
|
||||||
@ -722,14 +722,14 @@
|
|||||||
width: 13.5px;
|
width: 13.5px;
|
||||||
height: 23px;
|
height: 23px;
|
||||||
}
|
}
|
||||||
#timeline_body .keyframe i {
|
#timeline .keyframe i {
|
||||||
margin-top: 2px;
|
margin-top: 2px;
|
||||||
transform: rotate(45deg);
|
transform: rotate(45deg);
|
||||||
font-size: 14pt;
|
font-size: 14pt;
|
||||||
margin-left: -3px;
|
margin-left: -3px;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
#timeline_body .keyframe i.keyframe_icon_smaller {
|
#timeline .keyframe i.keyframe_icon_smaller {
|
||||||
font-size: 11pt;
|
font-size: 11pt;
|
||||||
margin-top: 4px;
|
margin-top: 4px;
|
||||||
margin-left: -1px;
|
margin-left: -1px;
|
||||||
@ -743,7 +743,7 @@
|
|||||||
font-size: 6pt;
|
font-size: 6pt;
|
||||||
color: var(--color-grid);
|
color: var(--color-grid);
|
||||||
}
|
}
|
||||||
#timeline_body .keyframe.selected {
|
#timeline .keyframe.selected {
|
||||||
color: var(--color-accent) !important;
|
color: var(--color-accent) !important;
|
||||||
z-index: 4;
|
z-index: 4;
|
||||||
}
|
}
|
||||||
@ -916,20 +916,31 @@
|
|||||||
|
|
||||||
#timeline_graph_editor {
|
#timeline_graph_editor {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
pointer-events: none;
|
top: 0px;
|
||||||
top: 28px;
|
bottom: 0px;
|
||||||
bottom: 9px;
|
left: 0px;
|
||||||
right: 6px;
|
right: 0px;
|
||||||
}
|
}
|
||||||
#timeline_graph_editor svg {
|
#timeline_graph_editor svg {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
margin-left: 9px;
|
||||||
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
#timeline_graph_editor svg path {
|
#timeline_graph_editor svg path {
|
||||||
stroke: #f72858;
|
stroke: #f72858;
|
||||||
stroke-width: 2px;
|
stroke-width: 2px;
|
||||||
fill: none;
|
fill: none;
|
||||||
}
|
}
|
||||||
|
#timeline_graph_editor .keyframe {
|
||||||
|
height: 16px;
|
||||||
|
width: 11px;
|
||||||
|
font-size: 10px;
|
||||||
|
}
|
||||||
|
#timeline_graph_editor .keyframe > i {
|
||||||
|
margin-top: 0px;
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/*UV*/
|
/*UV*/
|
||||||
|
@ -57,6 +57,7 @@ const Timeline = {
|
|||||||
if (e.which !== 1 || (
|
if (e.which !== 1 || (
|
||||||
!e.target.classList.contains('keyframe_section') &&
|
!e.target.classList.contains('keyframe_section') &&
|
||||||
!e.target.classList.contains('animator_head_bar') &&
|
!e.target.classList.contains('animator_head_bar') &&
|
||||||
|
e.target.id !== 'timeline_graph_editor' &&
|
||||||
e.target.id !== 'timeline_body_inner'
|
e.target.id !== 'timeline_body_inner'
|
||||||
)) {
|
)) {
|
||||||
return
|
return
|
||||||
@ -123,16 +124,20 @@ const Timeline = {
|
|||||||
channels[kf.channel] != false &&
|
channels[kf.channel] != false &&
|
||||||
(!channels.hide_empty || animator[kf.channel].length)
|
(!channels.hide_empty || animator[kf.channel].length)
|
||||||
) {
|
) {
|
||||||
var channel_index = 0 //animator.channels.indexOf(kf.channel);
|
if (!Timeline.vue.graph_editor_open) {
|
||||||
|
|
||||||
for (var channel of animator.channels) {
|
|
||||||
if (kf.channel == channel) break;
|
|
||||||
if (channels[channel] != false && (!channels.hide_empty || animator[channel].length)) {
|
|
||||||
channel_index++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
height = offset + channel_index*24 + 36;
|
var channel_index = 0 //animator.channels.indexOf(kf.channel);
|
||||||
|
for (var channel of animator.channels) {
|
||||||
|
if (kf.channel == channel) break;
|
||||||
|
if (channels[channel] != false && (!channels.hide_empty || animator[channel].length)) {
|
||||||
|
channel_index++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var height = offset + channel_index*24 + 36;
|
||||||
|
|
||||||
|
} else {
|
||||||
|
var height = Timeline.vue.graph_offset - (kf.display_value || 0) * Timeline.vue.graph_size + Timeline.vue.scroll_top;
|
||||||
|
}
|
||||||
if (height > rect.ay && height < rect.by) {
|
if (height > rect.ay && height < rect.by) {
|
||||||
kf.selected = true;
|
kf.selected = true;
|
||||||
Timeline.selected.push(kf);
|
Timeline.selected.push(kf);
|
||||||
@ -369,6 +374,7 @@ const Timeline = {
|
|||||||
});
|
});
|
||||||
$('#timeline_body').on('scroll', e => {
|
$('#timeline_body').on('scroll', e => {
|
||||||
Timeline.vue._data.scroll_left = $('#timeline_body').scrollLeft()||0;
|
Timeline.vue._data.scroll_left = $('#timeline_body').scrollLeft()||0;
|
||||||
|
Timeline.vue._data.scroll_top = $('#timeline_body').scrollTop()||0;
|
||||||
})
|
})
|
||||||
|
|
||||||
BarItems.slider_animation_speed.update()
|
BarItems.slider_animation_speed.update()
|
||||||
@ -644,6 +650,7 @@ onVueSetup(function() {
|
|||||||
length: 10,
|
length: 10,
|
||||||
animation_length: 0,
|
animation_length: 0,
|
||||||
scroll_left: 0,
|
scroll_left: 0,
|
||||||
|
scroll_top: 0,
|
||||||
head_width: 180,
|
head_width: 180,
|
||||||
timecodes: [],
|
timecodes: [],
|
||||||
animators: Timeline.animators,
|
animators: Timeline.animators,
|
||||||
@ -657,6 +664,7 @@ onVueSetup(function() {
|
|||||||
graph_editor_axis: 'x',
|
graph_editor_axis: 'x',
|
||||||
graph_offset: 200,
|
graph_offset: 200,
|
||||||
graph_size: 200,
|
graph_size: 200,
|
||||||
|
graph_keyframe_values: new Map(),
|
||||||
|
|
||||||
channels: {
|
channels: {
|
||||||
rotation: true,
|
rotation: true,
|
||||||
@ -665,6 +673,11 @@ onVueSetup(function() {
|
|||||||
hide_empty: false,
|
hide_empty: false,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
computed: {
|
||||||
|
graph_editor_animator() {
|
||||||
|
return this.animators.find(animator => animator.selected && animator instanceof BoneAnimator);
|
||||||
|
}
|
||||||
|
},
|
||||||
methods: {
|
methods: {
|
||||||
toggleAnimator(animator) {
|
toggleAnimator(animator) {
|
||||||
animator.expanded = !animator.expanded;
|
animator.expanded = !animator.expanded;
|
||||||
@ -686,34 +699,40 @@ onVueSetup(function() {
|
|||||||
return points.join(' ');
|
return points.join(' ');
|
||||||
},
|
},
|
||||||
getGraph() {
|
getGraph() {
|
||||||
let ba = Animation.selected.getBoneAnimator();
|
let ba = this.graph_editor_animator;
|
||||||
let original_time = Timeline.time;
|
let original_time = Timeline.time;
|
||||||
let step = Timeline.getStep() * this.size;
|
let step = Timeline.getStep() * this.size;
|
||||||
step = Math.clamp(step, 1, 5)
|
step = Math.clamp(step, 1, 5)
|
||||||
let timeline_offset = this.scroll_left - 9;
|
|
||||||
let timeline_offset_rounded = Math.round(timeline_offset/step)*step;
|
|
||||||
let clientWidth = this.$refs.graph_editor ? this.$refs.graph_editor.clientWidth : 400;
|
let clientWidth = this.$refs.graph_editor ? this.$refs.graph_editor.clientWidth : 400;
|
||||||
let clientHeight = this.$refs.graph_editor ? this.$refs.graph_editor.clientHeight : 400;
|
let clientHeight = this.$refs.timeline_body ? this.$refs.timeline_body.clientHeight : 400;
|
||||||
let points = [];
|
let points = [];
|
||||||
let min = -1;
|
let min = -1;
|
||||||
let max = 1;
|
let max = 1;
|
||||||
|
|
||||||
for (let time = timeline_offset_rounded; time < timeline_offset + clientWidth; time += step) {
|
for (let time = 0; time < clientWidth; time += step) {
|
||||||
Timeline.time = time / this.size;
|
Timeline.time = time / this.size;
|
||||||
let value = ba.interpolate(this.graph_editor_channel, false, this.graph_editor_axis);
|
let value = ba.interpolate(this.graph_editor_channel, false, this.graph_editor_axis);
|
||||||
points.push(value);
|
points.push(value);
|
||||||
min = Math.min(min, value);
|
min = Math.min(min, value);
|
||||||
max = Math.max(max, value);
|
max = Math.max(max, value);
|
||||||
}
|
}
|
||||||
Timeline.time = original_time;
|
|
||||||
|
|
||||||
let padding = 30
|
let padding = 30
|
||||||
this.graph_size = (clientHeight - 2*padding) / (max-min);
|
this.graph_size = (clientHeight - 2*padding) / (max-min);
|
||||||
this.graph_offset = clientHeight - padding + (this.graph_size * min);
|
this.graph_offset = clientHeight - padding + (this.graph_size * min);
|
||||||
|
|
||||||
|
this.graph_keyframe_values.clear();
|
||||||
|
ba[this.graph_editor_channel].forEach(kf => {
|
||||||
|
|
||||||
|
Timeline.time = kf.time;
|
||||||
|
let value = ba.interpolate(this.graph_editor_channel, false, this.graph_editor_axis);
|
||||||
|
kf.display_value = value;
|
||||||
|
})
|
||||||
|
Timeline.time = original_time;
|
||||||
|
|
||||||
let string = '';
|
let string = '';
|
||||||
points.forEach((value, i) => {
|
points.forEach((value, i) => {
|
||||||
string += `${string.length ? 'L' : 'M'}${i*step - timeline_offset + timeline_offset_rounded} ${this.graph_offset - value * this.graph_size} `
|
string += `${string.length ? 'L' : 'M'}${i*step} ${this.graph_offset - value * this.graph_size} `
|
||||||
})
|
})
|
||||||
|
|
||||||
return string;
|
return string;
|
||||||
@ -752,7 +771,7 @@ onVueSetup(function() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div id="timeline_body">
|
<div id="timeline_body" ref="timeline_body">
|
||||||
<div id="timeline_body_inner" v-bind:style="{width: (size*length + head_width)+'px'}" @contextmenu.stop="Timeline.showMenu($event)">
|
<div id="timeline_body_inner" v-bind:style="{width: (size*length + head_width)+'px'}" @contextmenu.stop="Timeline.showMenu($event)">
|
||||||
<li v-for="animator in animators" class="animator" :class="{selected: animator.selected}" :uuid="animator.uuid" v-on:click="animator.select();">
|
<li v-for="animator in animators" class="animator" :class="{selected: animator.selected}" :uuid="animator.uuid" v-on:click="animator.select();">
|
||||||
<div class="animator_head_bar">
|
<div class="animator_head_bar">
|
||||||
@ -827,14 +846,28 @@ onVueSetup(function() {
|
|||||||
<div id="timeline_empty_head" class="channel_head" v-bind:style="{left: scroll_left+'px', width: head_width+'px'}">
|
<div id="timeline_empty_head" class="channel_head" v-bind:style="{left: scroll_left+'px', width: head_width+'px'}">
|
||||||
</div>
|
</div>
|
||||||
<div id="timeline_selector" class="selection_rectangle"></div>
|
<div id="timeline_selector" class="selection_rectangle"></div>
|
||||||
|
<div id="timeline_graph_editor" ref="graph_editor" v-if="graph_editor_open" :style="{left: head_width + 'px', top: scroll_top + 'px'}">
|
||||||
|
<svg>
|
||||||
|
<path :d="\`M0 \${graph_offset} L10000 \${graph_offset}\`" style="stroke: var(--color-grid);"></path>
|
||||||
|
<path :d="getGraph()" :style="{stroke: 'var(--color-axis-' + graph_editor_axis + ')'}"></path>
|
||||||
|
</svg>
|
||||||
|
<keyframe
|
||||||
|
v-for="keyframe in graph_editor_animator[graph_editor_channel]"
|
||||||
|
v-bind:style="{left: (10 + keyframe.time * size) + 'px', top: (graph_offset - keyframe.display_value * graph_size - 8) + 'px', color: getColor(keyframe.color)}"
|
||||||
|
class="keyframe graph_keyframe"
|
||||||
|
v-bind:class="[keyframe.channel, keyframe.selected?'selected':'']"
|
||||||
|
v-bind:id="keyframe.uuid"
|
||||||
|
v-on:click.stop="keyframe.select($event)"
|
||||||
|
v-on:dblclick="keyframe.callPlayhead()"
|
||||||
|
:title="tl('timeline.'+keyframe.channel)"
|
||||||
|
@contextmenu.prevent="keyframe.showContextMenu($event)"
|
||||||
|
>
|
||||||
|
<i class="material-icons keyframe_icon_smaller" v-if="keyframe.interpolation == 'catmullrom'">lens</i>
|
||||||
|
<i class="material-icons" v-else>stop</i>
|
||||||
|
</keyframe>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div id="timeline_graph_editor" ref="graph_editor" v-if="graph_editor_open" :style="{left: head_width + 'px'}">
|
|
||||||
<svg>
|
|
||||||
<path :d="\`M0 \${graph_offset} L10000 \${graph_offset}\`" style="stroke: var(--color-grid);"></path>
|
|
||||||
<path :d="getGraph()" :style="{stroke: 'var(--color-axis-' + graph_editor_axis + ')'}"></path>
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
`
|
`
|
||||||
})
|
})
|
||||||
|
Loading…
Reference in New Issue
Block a user