2020-09-26 23:56:14 +08:00
|
|
|
class KeyframeDataPoint {
|
|
|
|
constructor(keyframe) {
|
|
|
|
switch (keyframe.channel) {
|
|
|
|
case 'rotation': this.x = this.y = this.z = '0'; break;
|
|
|
|
case 'position': this.x = this.y = this.z = '0'; break;
|
|
|
|
case 'scale': this.x = this.y = this.z = '1'; break;
|
|
|
|
case 'particle': this.effect = this.locator = this.script = this.file = ''; break;
|
|
|
|
case 'sound': this.effect = this.file = ''; break;
|
|
|
|
case 'timeline': this.instructions = ''; break;
|
|
|
|
}
|
|
|
|
}
|
2020-09-27 04:20:19 +08:00
|
|
|
extend(data) {
|
|
|
|
if (data.values) {
|
|
|
|
data.x = data.values.x;
|
|
|
|
data.y = data.values.y;
|
|
|
|
data.z = data.values.z;
|
|
|
|
data.effect = data.values.effect;
|
|
|
|
data.locator = data.values.locator;
|
|
|
|
data.script = data.values.script;
|
|
|
|
data.file = data.values.file;
|
|
|
|
data.instructions = data.values.instructions;
|
|
|
|
}
|
|
|
|
if (this.x != undefined) Merge.string(this, data, 'x')
|
|
|
|
if (this.y != undefined) Merge.string(this, data, 'y')
|
|
|
|
if (this.z != undefined) Merge.string(this, data, 'z')
|
|
|
|
if (this.effect != undefined) Merge.string(this, data, 'effect')
|
|
|
|
if (this.locator != undefined) Merge.string(this, data, 'locator')
|
|
|
|
if (this.script != undefined) Merge.string(this, data, 'script')
|
|
|
|
if (this.file != undefined) Merge.string(this, data, 'file')
|
|
|
|
if (this.instructions != undefined) Merge.string(this, data, 'instructions')
|
|
|
|
}
|
2020-09-26 23:56:14 +08:00
|
|
|
get x_string() {
|
|
|
|
return typeof this.x == 'number' ? trimFloatNumber(this.x) : this.x;
|
|
|
|
}
|
|
|
|
set x_string(val) {
|
|
|
|
this.x = val;
|
|
|
|
}
|
|
|
|
get y_string() {
|
|
|
|
return typeof this.y == 'number' ? trimFloatNumber(this.y) : this.y;
|
|
|
|
}
|
|
|
|
set y_string(val) {
|
|
|
|
this.y = val;
|
|
|
|
}
|
|
|
|
get z_string() {
|
|
|
|
return typeof this.z == 'number' ? trimFloatNumber(this.z) : this.z;
|
|
|
|
}
|
|
|
|
set z_string(val) {
|
|
|
|
this.z = val;
|
|
|
|
}
|
|
|
|
}
|
2020-09-09 00:40:22 +08:00
|
|
|
class Keyframe {
|
|
|
|
constructor(data, uuid) {
|
|
|
|
this.type = 'keyframe'
|
2020-09-26 22:05:23 +08:00
|
|
|
this.uuid = (uuid && isUUID(uuid)) ? uuid : guid();
|
|
|
|
this.channel == 'rotation'
|
2020-09-09 00:40:22 +08:00
|
|
|
this.selected = 0;
|
2020-09-26 22:05:23 +08:00
|
|
|
this.data_points = []
|
2020-09-23 01:17:28 +08:00
|
|
|
|
|
|
|
for (var key in Keyframe.properties) {
|
|
|
|
Keyframe.properties[key].reset(this);
|
|
|
|
}
|
2020-09-26 20:03:09 +08:00
|
|
|
|
2020-09-09 00:40:22 +08:00
|
|
|
if (typeof data === 'object') {
|
|
|
|
Merge.string(this, data, 'channel')
|
|
|
|
this.transform = this.channel === 'rotation' || this.channel === 'position' || this.channel === 'scale';
|
2020-09-26 23:56:14 +08:00
|
|
|
this.data_points.push(new KeyframeDataPoint(this));
|
2020-09-09 00:40:22 +08:00
|
|
|
this.extend(data)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
extend(data) {
|
2020-09-23 01:17:28 +08:00
|
|
|
for (var key in Keyframe.properties) {
|
|
|
|
Keyframe.properties[key].merge(this, data)
|
|
|
|
}
|
2020-09-09 00:40:22 +08:00
|
|
|
|
2020-09-26 19:57:58 +08:00
|
|
|
if (data.data_points && data.data_points.length) {
|
|
|
|
data.data_points.forEach((point, i) => {
|
2020-09-26 22:05:23 +08:00
|
|
|
if (!this.data_points[i]) {
|
2020-09-26 23:56:14 +08:00
|
|
|
this.data_points.push(new KeyframeDataPoint(this));
|
2020-09-26 22:05:23 +08:00
|
|
|
}
|
2020-09-26 19:57:58 +08:00
|
|
|
let this_point = this.data_points[i];
|
2020-09-27 04:20:19 +08:00
|
|
|
this_point.extend(point)
|
2020-09-26 19:57:58 +08:00
|
|
|
})
|
2020-09-09 00:40:22 +08:00
|
|
|
}
|
|
|
|
return this;
|
|
|
|
}
|
2020-09-26 19:57:58 +08:00
|
|
|
get(axis, data_point = 0) {
|
|
|
|
data_point = this.data_points[data_point];
|
|
|
|
if (!data_point || !data_point[axis]) {
|
2020-09-09 00:40:22 +08:00
|
|
|
return this.transform ? 0 : '';
|
2020-09-26 19:57:58 +08:00
|
|
|
} else if (!isNaN(data_point[axis])) {
|
|
|
|
let num = parseFloat(data_point[axis]);
|
2020-09-09 00:40:22 +08:00
|
|
|
return isNaN(num) ? 0 : num;
|
|
|
|
} else {
|
2020-09-26 19:57:58 +08:00
|
|
|
return data_point[axis]
|
2020-09-09 00:40:22 +08:00
|
|
|
}
|
|
|
|
}
|
2020-09-26 19:57:58 +08:00
|
|
|
calc(axis, data_point = 0) {
|
|
|
|
data_point = this.data_points[data_point];
|
|
|
|
return Molang.parse(data_point && data_point[axis])
|
2020-09-09 00:40:22 +08:00
|
|
|
}
|
2020-09-21 05:03:28 +08:00
|
|
|
set(axis, value, data_point = 0) {
|
|
|
|
if (this.data_points[data_point]) {
|
|
|
|
this.data_points[data_point][axis] = value;
|
|
|
|
}
|
2020-09-09 00:40:22 +08:00
|
|
|
return this;
|
|
|
|
}
|
2020-09-21 05:03:28 +08:00
|
|
|
offset(axis, amount, data_point = 0) {
|
2020-09-09 00:40:22 +08:00
|
|
|
var value = this.get(axis)
|
|
|
|
if (!value || value === '0') {
|
2020-09-21 05:03:28 +08:00
|
|
|
this.set(axis, amount, data_point)
|
2020-09-09 00:40:22 +08:00
|
|
|
return amount;
|
|
|
|
}
|
|
|
|
if (typeof value === 'number') {
|
2020-09-21 05:03:28 +08:00
|
|
|
this.set(axis, value+amount, data_point)
|
2020-09-09 00:40:22 +08:00
|
|
|
return value+amount
|
|
|
|
}
|
|
|
|
var start = value.match(/^-?\s*\d*(\.\d+)?\s*(\+|-)/)
|
|
|
|
if (start) {
|
|
|
|
var number = parseFloat( start[0].substr(0, start[0].length-1) ) + amount
|
|
|
|
value = trimFloatNumber(number) + value.substr(start[0].length-1)
|
|
|
|
} else {
|
|
|
|
|
|
|
|
var end = value.match(/(\+|-)\s*\d*(\.\d+)?\s*$/)
|
|
|
|
if (end) {
|
|
|
|
var number = (parseFloat( end[0] ) + amount)
|
|
|
|
value = value.substr(0, end.index) + ((number.toString()).substr(0,1)=='-'?'':'+') + trimFloatNumber(number)
|
|
|
|
} else {
|
|
|
|
value = trimFloatNumber(amount) +(value.substr(0,1)=='-'?'':'+')+ value
|
|
|
|
}
|
|
|
|
}
|
2020-09-21 05:03:28 +08:00
|
|
|
this.set(axis, value, data_point)
|
2020-09-09 00:40:22 +08:00
|
|
|
return value;
|
|
|
|
}
|
|
|
|
flip(axis) {
|
2020-09-26 19:57:58 +08:00
|
|
|
if (!this.transform || this.channel == 'scale') return this;
|
2020-09-09 00:40:22 +08:00
|
|
|
function negate(value) {
|
|
|
|
if (!value || value === '0') {
|
|
|
|
return value;
|
|
|
|
}
|
|
|
|
if (typeof value === 'number') {
|
|
|
|
return -value;
|
|
|
|
}
|
|
|
|
var start = value.match(/^-?\s*\d*(\.\d+)?\s*(\+|-)/)
|
|
|
|
if (start) {
|
|
|
|
var number = parseFloat( start[0].substr(0, start[0].length-1) );
|
|
|
|
return trimFloatNumber(-number) + value.substr(start[0].length-1);
|
|
|
|
} else {
|
|
|
|
return `-(${value})`;
|
|
|
|
}
|
|
|
|
}
|
2020-09-26 19:57:58 +08:00
|
|
|
this.data_points.forEach((data_point, data_point_i) => {
|
|
|
|
if (this.channel == 'rotation') {
|
|
|
|
for (var i = 0; i < 3; i++) {
|
|
|
|
if (i != axis) {
|
|
|
|
let l = getAxisLetter(i)
|
|
|
|
this.set(l, negate(this.get(l, data_point_i)), data_point_i)
|
|
|
|
}
|
2020-09-09 00:40:22 +08:00
|
|
|
}
|
2020-09-26 19:57:58 +08:00
|
|
|
} else if (this.channel == 'position') {
|
|
|
|
let l = getAxisLetter(axis)
|
|
|
|
this.set(l, negate(this.get(l, data_point_i)), data_point_i)
|
2020-09-09 00:40:22 +08:00
|
|
|
}
|
2020-09-26 19:57:58 +08:00
|
|
|
})
|
2020-09-09 00:40:22 +08:00
|
|
|
return this;
|
|
|
|
}
|
|
|
|
getLerp(other, axis, amount, allow_expression) {
|
2020-09-26 22:05:23 +08:00
|
|
|
let this_data_point = (this.data_points.length > 1 && this.time < other.time) ? 1 : 0;
|
|
|
|
let other_data_point = (other.data_points.length > 1 && this.time > other.time) ? 1 : 0;
|
|
|
|
if (allow_expression && this.get(axis, this_data_point) === other.get(axis, other_data_point)) {
|
2020-09-09 00:40:22 +08:00
|
|
|
return this.get(axis)
|
|
|
|
} else {
|
2020-09-26 22:05:23 +08:00
|
|
|
let calc = this.calc(axis, this_data_point);
|
|
|
|
return calc + (other.calc(axis, other_data_point) - calc) * amount;
|
2020-09-09 00:40:22 +08:00
|
|
|
}
|
|
|
|
}
|
2020-09-26 22:05:23 +08:00
|
|
|
getArray(data_point = 0) {
|
2020-09-09 00:40:22 +08:00
|
|
|
var arr = [
|
2020-09-26 22:05:23 +08:00
|
|
|
this.get('x', data_point),
|
|
|
|
this.get('y', data_point),
|
|
|
|
this.get('z', data_point),
|
2020-09-09 00:40:22 +08:00
|
|
|
]
|
2020-09-15 17:27:49 +08:00
|
|
|
arr.forEach((n, i) => {
|
|
|
|
if (n.replace) arr[i] = n.replace(/\n/g, '');
|
|
|
|
})
|
2020-09-09 00:40:22 +08:00
|
|
|
return arr;
|
|
|
|
}
|
2020-09-26 22:05:23 +08:00
|
|
|
getFixed(data_point = 0) {
|
2020-09-09 00:40:22 +08:00
|
|
|
if (this.channel === 'rotation') {
|
|
|
|
let fix = this.animator.group.mesh.fix_rotation;
|
|
|
|
return new THREE.Quaternion().setFromEuler(new THREE.Euler(
|
2020-09-26 22:05:23 +08:00
|
|
|
fix.x - Math.degToRad(this.calc('x', data_point)),
|
|
|
|
fix.y - Math.degToRad(this.calc('y', data_point)),
|
|
|
|
fix.z + Math.degToRad(this.calc('z', data_point)),
|
2020-09-09 00:40:22 +08:00
|
|
|
'ZYX'
|
|
|
|
));
|
|
|
|
} else if (this.channel === 'position') {
|
|
|
|
let fix = this.animator.group.mesh.fix_position;
|
|
|
|
return new THREE.Vector3(
|
2020-09-26 22:05:23 +08:00
|
|
|
fix.x - this.calc('x', data_point),
|
|
|
|
fix.y + this.calc('y', data_point),
|
|
|
|
fix.z + this.calc('z', data_point)
|
2020-09-09 00:40:22 +08:00
|
|
|
)
|
|
|
|
} else if (this.channel === 'scale') {
|
|
|
|
return new THREE.Vector3(
|
2020-09-26 22:05:23 +08:00
|
|
|
this.calc('x', data_point),
|
|
|
|
this.calc('y', data_point),
|
|
|
|
this.calc('z', data_point)
|
2020-09-09 00:40:22 +08:00
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
getTimecodeString() {
|
2020-09-14 18:44:43 +08:00
|
|
|
let timecode = trimFloatNumber(Timeline.snapTime(this.time, this.animator.animation)).toString();
|
2020-09-09 00:40:22 +08:00
|
|
|
if (!timecode.includes('.')) {
|
|
|
|
timecode += '.0';
|
|
|
|
}
|
|
|
|
return timecode;
|
|
|
|
}
|
2020-09-26 22:05:23 +08:00
|
|
|
compileBedrockKeyframe() {
|
|
|
|
if (this.transform) {
|
|
|
|
if (this.data_points.length == 1) {
|
|
|
|
return this.getArray()
|
|
|
|
} else {
|
|
|
|
return {
|
|
|
|
pre: this.getArray(0),
|
2020-09-27 05:07:44 +08:00
|
|
|
post: this.getArray(1),
|
2020-09-26 22:05:23 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
} else if (this.channel == 'timeline') {
|
2020-09-27 05:07:44 +08:00
|
|
|
let instructions = [];
|
|
|
|
this.data_points.forEach(data_point => {
|
|
|
|
if (data_point.instructions) {
|
|
|
|
instructions.push(...data_point.instructions.split('\n'));
|
|
|
|
}
|
|
|
|
})
|
|
|
|
return instructions.length <= 1 ? instructions[0] : instructions;
|
2020-09-26 22:05:23 +08:00
|
|
|
} else {
|
|
|
|
let points = [];
|
|
|
|
this.data_points.forEach(data_point => {
|
2020-09-27 05:07:44 +08:00
|
|
|
if (data_point.effect) {
|
|
|
|
let script = kf.script || undefined;
|
|
|
|
if (script && !script.match(/;$/)) script += ';';
|
|
|
|
points.push({
|
|
|
|
effect: data_point.effect,
|
|
|
|
locator: data_point.locator || undefined,
|
|
|
|
pre_effect_script: script,
|
|
|
|
})
|
2020-09-26 22:05:23 +08:00
|
|
|
}
|
|
|
|
})
|
2020-09-27 05:07:44 +08:00
|
|
|
console.log(points)
|
2020-09-26 22:05:23 +08:00
|
|
|
return points.length <= 1 ? points[0] : points;
|
|
|
|
}
|
|
|
|
}
|
2020-09-09 00:40:22 +08:00
|
|
|
replaceOthers(save) {
|
|
|
|
var scope = this;
|
|
|
|
var arr = this.animator[this.channel];
|
|
|
|
arr.forEach(kf => {
|
|
|
|
if (kf != scope && Math.abs(kf.time - scope.time) < 0.0001) {
|
|
|
|
save.push(kf);
|
|
|
|
kf.remove();
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
select(event) {
|
|
|
|
var scope = this;
|
|
|
|
if (Timeline.dragging_keyframes) {
|
|
|
|
Timeline.dragging_keyframes = false
|
|
|
|
return this;
|
|
|
|
}
|
|
|
|
if (!event || (!event.shiftKey && !event.ctrlOrCmd)) {
|
|
|
|
Timeline.selected.forEach(function(kf) {
|
|
|
|
kf.selected = false
|
|
|
|
})
|
|
|
|
Timeline.selected.empty()
|
|
|
|
}
|
|
|
|
if (event && event.shiftKey && Timeline.selected.length) {
|
|
|
|
var last = Timeline.selected[Timeline.selected.length-1]
|
|
|
|
if (last && last.channel === scope.channel && last.animator == scope.animator) {
|
|
|
|
Timeline.keyframes.forEach((kf) => {
|
|
|
|
if (kf.channel === scope.channel &&
|
|
|
|
kf.animator === scope.animator &&
|
|
|
|
Math.isBetween(kf.time, last.time, scope.time) &&
|
|
|
|
!kf.selected
|
|
|
|
) {
|
|
|
|
kf.selected = true
|
|
|
|
Timeline.selected.push(kf)
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
Timeline.selected.safePush(this);
|
2020-09-15 23:14:37 +08:00
|
|
|
if (Timeline.selected.length == 1 && Timeline.selected[0].animator.selected == false) {
|
2020-09-09 00:40:22 +08:00
|
|
|
Timeline.selected[0].animator.select()
|
|
|
|
}
|
|
|
|
|
|
|
|
var select_tool = true;
|
|
|
|
Timeline.selected.forEach(kf => {
|
|
|
|
if (kf.channel != scope.channel) select_tool = false;
|
|
|
|
})
|
|
|
|
this.selected = true
|
|
|
|
TickUpdates.keyframe_selection = true;
|
|
|
|
if (select_tool) {
|
|
|
|
switch (this.channel) {
|
|
|
|
case 'rotation': BarItems.rotate_tool.select(); break;
|
|
|
|
case 'position': BarItems.move_tool.select(); break;
|
|
|
|
case 'scale': BarItems.resize_tool.select(); break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return this;
|
|
|
|
}
|
|
|
|
callPlayhead() {
|
|
|
|
Timeline.setTime(this.time)
|
|
|
|
Animator.preview()
|
|
|
|
return this;
|
|
|
|
}
|
|
|
|
showContextMenu(event) {
|
|
|
|
if (!this.selected) {
|
|
|
|
this.select();
|
|
|
|
}
|
|
|
|
this.menu.open(event, this);
|
|
|
|
return this;
|
|
|
|
}
|
|
|
|
remove() {
|
|
|
|
if (this.animator) {
|
|
|
|
this.animator[this.channel].remove(this)
|
|
|
|
}
|
|
|
|
Timeline.selected.remove(this)
|
|
|
|
}
|
2020-09-12 22:55:32 +08:00
|
|
|
forSelected(fc, undo_tag) {
|
|
|
|
if (Timeline.selected.length <= 1 || !Timeline.selected.includes(this)) {
|
|
|
|
var edited = [this]
|
|
|
|
} else {
|
|
|
|
var edited = Timeline.selected
|
|
|
|
}
|
|
|
|
if (undo_tag) {
|
|
|
|
Undo.initEdit({keyframes: edited})
|
|
|
|
}
|
|
|
|
for (var i = 0; i < edited.length; i++) {
|
|
|
|
fc(edited[i])
|
|
|
|
}
|
|
|
|
if (undo_tag) {
|
|
|
|
Undo.finishEdit(undo_tag)
|
|
|
|
}
|
|
|
|
return edited;
|
|
|
|
}
|
2020-09-09 00:40:22 +08:00
|
|
|
getUndoCopy(save) {
|
|
|
|
var copy = {
|
|
|
|
animator: save ? undefined : this.animator && this.animator.uuid,
|
|
|
|
uuid: save && this.uuid,
|
|
|
|
channel: this.channel,
|
2020-09-26 22:05:23 +08:00
|
|
|
data_points: []
|
2020-09-09 00:40:22 +08:00
|
|
|
}
|
2020-09-23 01:17:28 +08:00
|
|
|
for (var key in Keyframe.properties) {
|
2020-09-23 03:54:31 +08:00
|
|
|
Keyframe.properties[key].copy(this, copy)
|
2020-09-23 01:17:28 +08:00
|
|
|
}
|
2020-09-26 22:05:23 +08:00
|
|
|
this.data_points.forEach(data_point => {
|
2020-09-26 23:56:14 +08:00
|
|
|
copy.data_points.push(Object.assign({}, data_point))
|
2020-09-26 22:05:23 +08:00
|
|
|
})
|
2020-09-09 00:40:22 +08:00
|
|
|
return copy;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
Keyframe.prototype.menu = new Menu([
|
|
|
|
//Quaternions have been removed in Bedrock 1.10.0
|
|
|
|
/*
|
|
|
|
{name: 'menu.keyframe.quaternion',
|
|
|
|
icon: (keyframe) => (keyframe.isQuaternion ? 'check_box' : 'check_box_outline_blank'),
|
|
|
|
condition: (keyframe) => keyframe.channel === 'rotation',
|
|
|
|
click: function(keyframe) {
|
|
|
|
keyframe.select()
|
|
|
|
var state = !keyframe.isQuaternion
|
|
|
|
Timeline.keyframes.forEach((kf) => {
|
|
|
|
kf.isQuaternion = state
|
|
|
|
})
|
|
|
|
updateKeyframeSelection()
|
|
|
|
}
|
|
|
|
},*/
|
|
|
|
'change_keyframe_file',
|
|
|
|
'_',
|
2020-09-12 22:55:32 +08:00
|
|
|
{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')}},
|
|
|
|
{icon: 'bubble_chart', color: markerColors[1].standard, name: 'cube.color.'+markerColors[1].name, click: function(kf) {kf.forSelected(function(kf2){kf2.color = 1}, 'change color')}},
|
|
|
|
{icon: 'bubble_chart', color: markerColors[2].standard, name: 'cube.color.'+markerColors[2].name, click: function(kf) {kf.forSelected(function(kf2){kf2.color = 2}, 'change color')}},
|
|
|
|
{icon: 'bubble_chart', color: markerColors[3].standard, name: 'cube.color.'+markerColors[3].name, click: function(kf) {kf.forSelected(function(kf2){kf2.color = 3}, 'change color')}},
|
|
|
|
{icon: 'bubble_chart', color: markerColors[4].standard, name: 'cube.color.'+markerColors[4].name, click: function(kf) {kf.forSelected(function(kf2){kf2.color = 4}, 'change color')}},
|
|
|
|
{icon: 'bubble_chart', color: markerColors[5].standard, name: 'cube.color.'+markerColors[5].name, click: function(kf) {kf.forSelected(function(kf2){kf2.color = 5}, 'change color')}},
|
|
|
|
{icon: 'bubble_chart', color: markerColors[6].standard, name: 'cube.color.'+markerColors[6].name, click: function(kf) {kf.forSelected(function(kf2){kf2.color = 6}, 'change color')}},
|
|
|
|
{icon: 'bubble_chart', color: markerColors[7].standard, name: 'cube.color.'+markerColors[7].name, click: function(kf) {kf.forSelected(function(kf2){kf2.color = 7}, 'change color')}}
|
|
|
|
]},
|
2020-09-09 00:40:22 +08:00
|
|
|
'copy',
|
|
|
|
'delete',
|
|
|
|
])
|
2020-09-23 01:17:28 +08:00
|
|
|
new Property(Keyframe, 'number', 'time')
|
|
|
|
new Property(Keyframe, 'number', 'color', {default: -1})
|
2020-09-26 04:06:03 +08:00
|
|
|
Keyframe.selected = [];
|
2020-09-09 00:40:22 +08:00
|
|
|
|
|
|
|
// Misc Functions
|
2020-09-21 05:03:28 +08:00
|
|
|
function updateKeyframeValue(axis, value, data_point) {
|
2020-09-09 00:40:22 +08:00
|
|
|
Timeline.selected.forEach(function(kf) {
|
2020-09-21 05:03:28 +08:00
|
|
|
kf.set(axis, value, data_point);
|
2020-09-09 00:40:22 +08:00
|
|
|
})
|
|
|
|
if (!['effect', 'locator', 'script'].includes(axis)) {
|
|
|
|
Animator.preview();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
function updateKeyframeSelection() {
|
2020-09-14 00:31:46 +08:00
|
|
|
Timeline.keyframes.forEach(kf => {
|
|
|
|
if (kf.selected && !Timeline.selected.includes(kf)) {
|
|
|
|
kf.selected = false;
|
|
|
|
}
|
|
|
|
})
|
2020-09-13 21:34:12 +08:00
|
|
|
if (Timeline.selected.length) {
|
2020-09-09 00:40:22 +08:00
|
|
|
BarItems.slider_keyframe_time.update()
|
|
|
|
}
|
|
|
|
BARS.updateConditions()
|
|
|
|
Blockbench.dispatchEvent('update_keyframe_selection');
|
|
|
|
}
|
|
|
|
function selectAllKeyframes() {
|
2020-09-15 20:21:04 +08:00
|
|
|
if (!Animation.selected) return;
|
2020-09-09 00:40:22 +08:00
|
|
|
var state = Timeline.selected.length !== Timeline.keyframes.length
|
|
|
|
Timeline.keyframes.forEach((kf) => {
|
|
|
|
if (state && !kf.selected) {
|
|
|
|
Timeline.selected.push(kf)
|
|
|
|
} else if (!state && kf.selected) {
|
|
|
|
Timeline.selected.remove(kf)
|
|
|
|
}
|
|
|
|
kf.selected = state
|
|
|
|
})
|
|
|
|
updateKeyframeSelection()
|
|
|
|
}
|
|
|
|
function removeSelectedKeyframes() {
|
|
|
|
Undo.initEdit({keyframes: Timeline.selected})
|
|
|
|
var i = Timeline.keyframes.length;
|
|
|
|
while (i > 0) {
|
|
|
|
i--;
|
|
|
|
let kf = Timeline.keyframes[i]
|
|
|
|
if (Timeline.selected.includes(kf)) {
|
|
|
|
kf.remove()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
updateKeyframeSelection()
|
|
|
|
Animator.preview()
|
|
|
|
Undo.finishEdit('remove keyframes')
|
|
|
|
}
|
|
|
|
|
|
|
|
BARS.defineActions(function() {
|
|
|
|
new Action('add_keyframe', {
|
|
|
|
icon: 'add_circle',
|
|
|
|
category: 'animation',
|
|
|
|
condition: {modes: ['animate']},
|
|
|
|
keybind: new Keybind({key: 81, shift: null}),
|
|
|
|
click: function (event) {
|
|
|
|
var animator = Timeline.selected_animator;
|
|
|
|
if (!animator) return;
|
|
|
|
var channel = animator.channels[0];
|
|
|
|
if (Toolbox.selected.id == 'rotate_tool' && animator.channels.includes('rotation')) channel = 'rotation';
|
|
|
|
if (Toolbox.selected.id == 'move_tool' && animator.channels.includes('position')) channel = 'position';
|
|
|
|
if (Toolbox.selected.id == 'resize_tool' && animator.channels.includes('scale')) channel = 'scale';
|
|
|
|
animator.createKeyframe((event && event.shiftKey) ? {} : null, Timeline.time, channel, true);
|
|
|
|
if (event && event.shiftKey) {
|
|
|
|
Animator.preview();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
})
|
|
|
|
|
|
|
|
new Action('move_keyframe_back', {
|
|
|
|
icon: 'arrow_back',
|
|
|
|
category: 'transform',
|
|
|
|
condition: {modes: ['animate'], method: () => (!open_menu && Timeline.selected.length)},
|
|
|
|
keybind: new Keybind({key: 37}),
|
|
|
|
click: function (e) {
|
|
|
|
Undo.initEdit({keyframes: Timeline.selected})
|
|
|
|
Timeline.selected.forEach((kf) => {
|
|
|
|
kf.time = Timeline.snapTime(limitNumber(kf.time - Timeline.getStep(), 0, 1e4))
|
|
|
|
})
|
|
|
|
Animator.preview()
|
|
|
|
Undo.finishEdit('move keyframes')
|
|
|
|
}
|
|
|
|
})
|
|
|
|
new Action('move_keyframe_forth', {
|
|
|
|
icon: 'arrow_forward',
|
|
|
|
category: 'transform',
|
|
|
|
condition: {modes: ['animate'], method: () => (!open_menu && Timeline.selected.length)},
|
|
|
|
keybind: new Keybind({key: 39}),
|
|
|
|
click: function (e) {
|
|
|
|
Undo.initEdit({keyframes: Timeline.selected})
|
|
|
|
Timeline.selected.forEach((kf) => {
|
|
|
|
kf.time = Timeline.snapTime(limitNumber(kf.time + Timeline.getStep(), 0, 1e4))
|
|
|
|
})
|
|
|
|
Animator.preview()
|
|
|
|
Undo.finishEdit('move keyframes')
|
|
|
|
}
|
|
|
|
})
|
|
|
|
new Action('previous_keyframe', {
|
|
|
|
icon: 'fa-arrow-circle-left',
|
|
|
|
category: 'animation',
|
|
|
|
condition: {modes: ['animate']},
|
|
|
|
click: function () {
|
|
|
|
|
|
|
|
var time = Timeline.time;
|
|
|
|
function getDelta(kf, abs) {
|
|
|
|
return kf.time - time
|
|
|
|
}
|
|
|
|
let animator = Timeline.selected_animator
|
|
|
|
|| (Timeline.selected[0] && Timeline.selected[0].animator)
|
|
|
|
|| Timeline.animators[0];
|
|
|
|
let channel = Timeline.selected[0] ? Timeline.selected[0].channel : (animator && animator.channels[0]);
|
|
|
|
|
|
|
|
var matches = []
|
|
|
|
for (var kf of Timeline.keyframes) {
|
|
|
|
if ((!animator || animator == kf.animator) && (!channel || channel == kf.channel)) {
|
|
|
|
let delta = getDelta(kf)
|
|
|
|
if (delta < 0) {
|
|
|
|
matches.push(kf)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
matches.sort((a, b) => {
|
|
|
|
return Math.abs(getDelta(a)) - Math.abs(getDelta(b))
|
|
|
|
})
|
|
|
|
var kf = matches[0]
|
|
|
|
if (kf) {
|
|
|
|
kf.select().callPlayhead()
|
|
|
|
} else {
|
|
|
|
if (Timeline.selected.length) {
|
|
|
|
selectAllKeyframes()
|
|
|
|
selectAllKeyframes()
|
|
|
|
}
|
|
|
|
Timeline.setTime(0)
|
|
|
|
Animator.preview()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
})
|
|
|
|
new Action('next_keyframe', {
|
|
|
|
icon: 'fa-arrow-circle-right',
|
|
|
|
category: 'animation',
|
|
|
|
condition: {modes: ['animate']},
|
|
|
|
click: function () {
|
|
|
|
|
|
|
|
var time = Timeline.time;
|
|
|
|
function getDelta(kf, abs) {
|
|
|
|
return kf.time - time
|
|
|
|
}
|
|
|
|
let animator = Timeline.selected_animator
|
|
|
|
|| (Timeline.selected[0] && Timeline.selected[0].animator)
|
|
|
|
|| Timeline.animators[0];
|
|
|
|
let channel = Timeline.selected[0] ? Timeline.selected[0].channel : (animator && animator.channels[0]);
|
|
|
|
|
|
|
|
var matches = []
|
|
|
|
for (var kf of Timeline.keyframes) {
|
|
|
|
if ((!animator || animator == kf.animator) && (!channel || channel == kf.channel)) {
|
|
|
|
let delta = getDelta(kf)
|
|
|
|
if (delta > 0) {
|
|
|
|
matches.push(kf)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
matches.sort((a, b) => {
|
|
|
|
return Math.abs(getDelta(a)) - Math.abs(getDelta(b))
|
|
|
|
})
|
|
|
|
var kf = matches[0]
|
|
|
|
if (kf) {
|
|
|
|
kf.select().callPlayhead()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
})
|
|
|
|
|
|
|
|
new NumSlider('slider_animation_length', {
|
|
|
|
category: 'animation',
|
2020-09-15 20:21:04 +08:00
|
|
|
condition: () => Animator.open && Animation.selected,
|
2020-09-09 00:40:22 +08:00
|
|
|
getInterval(event) {
|
|
|
|
if (event && event.shiftKey) return 1;
|
2020-09-14 18:44:43 +08:00
|
|
|
return Timeline.getStep()
|
2020-09-09 00:40:22 +08:00
|
|
|
},
|
|
|
|
get: function() {
|
2020-09-15 20:21:04 +08:00
|
|
|
return Animation.selected.length
|
2020-09-09 00:40:22 +08:00
|
|
|
},
|
|
|
|
change: function(modify) {
|
2020-09-15 20:21:04 +08:00
|
|
|
Animation.selected.setLength(limitNumber(modify(Animation.selected.length), 0, 1e4))
|
2020-09-09 00:40:22 +08:00
|
|
|
},
|
|
|
|
onBefore: function() {
|
2020-09-15 20:21:04 +08:00
|
|
|
Undo.initEdit({animations: [Animation.selected]});
|
2020-09-09 00:40:22 +08:00
|
|
|
},
|
|
|
|
onAfter: function() {
|
|
|
|
Undo.finishEdit('Change Animation Length')
|
|
|
|
}
|
|
|
|
})
|
|
|
|
new NumSlider('slider_keyframe_time', {
|
|
|
|
category: 'animation',
|
|
|
|
condition: () => Animator.open && Timeline.selected.length,
|
|
|
|
getInterval(event) {
|
|
|
|
if (event && event.shiftKey) return 1;
|
2020-09-14 18:44:43 +08:00
|
|
|
return Timeline.getStep()
|
2020-09-09 00:40:22 +08:00
|
|
|
},
|
|
|
|
get: function() {
|
|
|
|
return Timeline.selected[0] ? Timeline.selected[0].time : 0
|
|
|
|
},
|
|
|
|
change: function(modify) {
|
|
|
|
Timeline.selected.forEach((kf) => {
|
|
|
|
kf.time = Timeline.snapTime(limitNumber(modify(kf.time), 0, 1e4))
|
|
|
|
})
|
|
|
|
Animator.preview()
|
|
|
|
},
|
|
|
|
onBefore: function() {
|
|
|
|
Undo.initEdit({keyframes: Timeline.selected})
|
|
|
|
},
|
|
|
|
onAfter: function() {
|
|
|
|
Undo.finishEdit('move keyframes')
|
|
|
|
}
|
|
|
|
})
|
|
|
|
new Action('reset_keyframe', {
|
|
|
|
icon: 'replay',
|
|
|
|
category: 'animation',
|
|
|
|
condition: () => Animator.open && Timeline.selected.length,
|
|
|
|
click: function () {
|
|
|
|
Undo.initEdit({keyframes: Timeline.selected})
|
|
|
|
Timeline.selected.forEach((kf) => {
|
2020-09-26 23:56:14 +08:00
|
|
|
kf.data_points.replace([new KeyframeDataPoint(kf)]);
|
2020-09-09 00:40:22 +08:00
|
|
|
})
|
|
|
|
Undo.finishEdit('reset keyframes')
|
|
|
|
updateKeyframeSelection()
|
|
|
|
Animator.preview()
|
|
|
|
}
|
|
|
|
})
|
|
|
|
new Action('resolve_keyframe_expressions', {
|
|
|
|
icon: 'functions',
|
|
|
|
category: 'animation',
|
|
|
|
condition: () => Animator.open && Timeline.selected.length,
|
|
|
|
click: function () {
|
|
|
|
Undo.initEdit({keyframes: Timeline.selected})
|
|
|
|
let time_before = Timeline.time;
|
|
|
|
Timeline.selected.forEach((kf) => {
|
|
|
|
if (kf.animator.fillValues) {
|
|
|
|
Timeline.time = kf.time;
|
|
|
|
kf.animator.fillValues(kf, null, false);
|
|
|
|
}
|
|
|
|
})
|
|
|
|
Timeline.time = time_before;
|
|
|
|
Undo.finishEdit('reset keyframes')
|
|
|
|
updateKeyframeSelection()
|
|
|
|
}
|
|
|
|
})
|
|
|
|
new Action('change_keyframe_file', {
|
|
|
|
icon: 'fa-file-audio',
|
|
|
|
category: 'animation',
|
2020-09-26 22:05:23 +08:00
|
|
|
condition: () => (isApp && Animator.open && Timeline.selected.length && Timeline.selected[0].channel == 'sound'),
|
2020-09-09 00:40:22 +08:00
|
|
|
click: function () {
|
|
|
|
Blockbench.import({
|
|
|
|
resource_id: 'animation_audio',
|
|
|
|
extensions: ['ogg'],
|
|
|
|
type: 'Audio File',
|
|
|
|
startpath: Timeline.selected[0].file
|
|
|
|
}, function(files) {
|
|
|
|
|
2020-09-26 22:05:23 +08:00
|
|
|
// Todo: move to panel
|
2020-09-13 02:35:25 +08:00
|
|
|
let {path} = files[0];
|
2020-09-09 00:40:22 +08:00
|
|
|
Undo.initEdit({keyframes: Timeline.selected})
|
|
|
|
Timeline.selected.forEach((kf) => {
|
|
|
|
if (kf.channel == 'sound') {
|
2020-09-26 22:05:23 +08:00
|
|
|
kf.data_points.forEach(data_point => {
|
|
|
|
data_point.file = path;
|
|
|
|
})
|
2020-09-09 00:40:22 +08:00
|
|
|
}
|
|
|
|
})
|
2020-09-13 02:35:25 +08:00
|
|
|
Timeline.visualizeAudioFile(path);
|
2020-09-09 00:40:22 +08:00
|
|
|
Undo.finishEdit('changed keyframe audio file')
|
|
|
|
})
|
|
|
|
}
|
|
|
|
})
|
|
|
|
new Action('reverse_keyframes', {
|
|
|
|
icon: 'swap_horizontal_circle',
|
|
|
|
category: 'animation',
|
|
|
|
condition: () => Animator.open && Timeline.selected.length,
|
|
|
|
click: function () {
|
|
|
|
var start = 1e9;
|
|
|
|
var end = 0;
|
|
|
|
Timeline.selected.forEach((kf) => {
|
|
|
|
start = Math.min(start, kf.time);
|
|
|
|
end = Math.max(end, kf.time);
|
|
|
|
})
|
|
|
|
Undo.initEdit({keyframes: Timeline.selected})
|
|
|
|
Timeline.selected.forEach((kf) => {
|
|
|
|
kf.time = end + start - kf.time;
|
|
|
|
})
|
|
|
|
Undo.finishEdit('reverse keyframes')
|
|
|
|
updateKeyframeSelection()
|
|
|
|
Animator.preview()
|
|
|
|
}
|
|
|
|
})
|
|
|
|
})
|
2020-09-22 05:23:42 +08:00
|
|
|
|
|
|
|
Interface.definePanels(function() {
|
|
|
|
|
|
|
|
Interface.Panels.keyframe = new Panel({
|
|
|
|
id: 'keyframe',
|
|
|
|
icon: 'timeline',
|
|
|
|
condition: {modes: ['animate']},
|
|
|
|
toolbars: {
|
|
|
|
head: Toolbars.keyframe
|
|
|
|
},
|
|
|
|
component: {
|
|
|
|
name: 'panel-keyframe',
|
|
|
|
components: {VuePrismEditor},
|
|
|
|
data() { return {
|
|
|
|
keyframes: Timeline.selected
|
|
|
|
}},
|
|
|
|
methods: {
|
2020-09-26 23:56:14 +08:00
|
|
|
updateInput(axis, value, data_point) {
|
|
|
|
updateKeyframeValue(axis, value, data_point)
|
2020-09-22 05:23:42 +08:00
|
|
|
},
|
|
|
|
getKeyframeInfos() {
|
|
|
|
let list = [tl('timeline.'+this.channel)];
|
|
|
|
if (this.keyframes.length > 1) list.push(this.keyframes.length);
|
|
|
|
/*if (this.keyframes[0].color >= 0) {
|
|
|
|
list.push(tl(`cube.color.${markerColors[this.keyframes[0].color].name}`))
|
|
|
|
}*/
|
|
|
|
return list.join(', ')
|
2020-09-27 04:20:19 +08:00
|
|
|
},
|
|
|
|
addDataPoint() {
|
|
|
|
Undo.initEdit({keyframes: Timeline.selected})
|
|
|
|
Timeline.selected.forEach(kf => {
|
|
|
|
if ((kf.transform && kf.data_points.length <= 1) || kf.channel == 'particle' || kf.channel == 'sound') {
|
|
|
|
kf.data_points.push(new KeyframeDataPoint(kf))
|
|
|
|
kf.data_points.last().extend(kf.data_points[0])
|
|
|
|
}
|
|
|
|
})
|
|
|
|
Animator.preview()
|
|
|
|
Undo.finishEdit('add keyframe data point')
|
|
|
|
},
|
|
|
|
removeDataPoint(data_point) {
|
|
|
|
Undo.initEdit({keyframes: Timeline.selected})
|
|
|
|
Timeline.selected.forEach(kf => {
|
|
|
|
if (kf.data_points.length >= 2) {
|
|
|
|
kf.data_points.splice(data_point, 1);
|
|
|
|
}
|
|
|
|
})
|
|
|
|
Animator.preview()
|
|
|
|
Undo.finishEdit('remove keyframe data point')
|
2020-09-22 05:23:42 +08:00
|
|
|
}
|
|
|
|
},
|
|
|
|
computed: {
|
|
|
|
channel() {
|
|
|
|
var channel = false;
|
|
|
|
for (var kf of this.keyframes) {
|
|
|
|
if (channel === false) {
|
|
|
|
channel = kf.channel
|
|
|
|
} else if (channel !== kf.channel) {
|
|
|
|
channel = false
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return channel;
|
|
|
|
}
|
|
|
|
},
|
|
|
|
template: `
|
|
|
|
<div>
|
|
|
|
<div class="toolbar_wrapper keyframe"></div>
|
|
|
|
|
|
|
|
<template v-if="channel != false">
|
|
|
|
|
2020-09-27 04:20:19 +08:00
|
|
|
<div id="keyframe_type_label">
|
|
|
|
<label>{{ tl('panel.keyframe.type', [getKeyframeInfos()]) }}</label>
|
|
|
|
<div
|
|
|
|
class="in_list_button"
|
|
|
|
v-if="(keyframes[0].transform && keyframes[0].data_points.length <= 1) || channel == 'particle' || channel == 'sound'"
|
|
|
|
v-on:click.stop="addDataPoint()"
|
|
|
|
title="${ tl('panel.keyframe.add_data_point') }"
|
|
|
|
>
|
|
|
|
<i class="material-icons">add</i>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
|
2020-09-27 05:07:44 +08:00
|
|
|
<ul class="list">
|
|
|
|
|
|
|
|
<div v-for="(data_point, data_point_i) of keyframes[0].data_points" class="keyframe_data_point">
|
2020-09-26 23:56:14 +08:00
|
|
|
|
2020-09-27 05:07:44 +08:00
|
|
|
<div class="keyframe_data_point_header" v-if="keyframes[0].data_points.length > 1">
|
|
|
|
<label>{{ keyframes[0].transform ? tl('panel.keyframe.' + (data_point_i ? 'post' : 'pre')) : (data_point_i + 1) }}</label>
|
|
|
|
<div class="flex_fill_line"></div>
|
|
|
|
<div class="in_list_button" v-on:click.stop="removeDataPoint(data_point)" title="${ tl('panel.keyframe.remove_data_point') }">
|
|
|
|
<i class="material-icons">clear</i>
|
|
|
|
</div>
|
2020-09-27 04:20:19 +08:00
|
|
|
</div>
|
2020-09-22 05:23:42 +08:00
|
|
|
|
2020-09-27 05:07:44 +08:00
|
|
|
<div class="bar flex" id="keyframe_bar_x" v-if="keyframes[0].animator instanceof BoneAnimator">
|
|
|
|
<label class="color_x" style="font-weight: bolder">X</label>
|
|
|
|
<vue-prism-editor class="molang_input dark_bordered keyframe_input tab_target" v-model="data_point.x_string" @change="updateInput('x', $event, data_point_i)" language="molang" :line-numbers="false" />
|
|
|
|
</div>
|
|
|
|
<div class="bar flex" id="keyframe_bar_y" v-if="keyframes[0].animator instanceof BoneAnimator">
|
|
|
|
<label class="color_y" style="font-weight: bolder">Y</label>
|
|
|
|
<vue-prism-editor class="molang_input dark_bordered keyframe_input tab_target" v-model="data_point.y_string" @change="updateInput('y', $event, data_point_i)" language="molang" :line-numbers="false" />
|
|
|
|
</div>
|
|
|
|
<div class="bar flex" id="keyframe_bar_z" v-if="keyframes[0].animator instanceof BoneAnimator">
|
|
|
|
<label class="color_z" style="font-weight: bolder">Z</label>
|
|
|
|
<vue-prism-editor class="molang_input dark_bordered keyframe_input tab_target" v-model="data_point.z_string" @change="updateInput('z', $event, data_point_i)" language="molang" :line-numbers="false" />
|
|
|
|
</div>
|
2020-09-22 05:23:42 +08:00
|
|
|
|
2020-09-27 05:07:44 +08:00
|
|
|
<div class="bar flex" id="keyframe_bar_effect" v-if="channel == 'particle' || channel == 'sound'">
|
|
|
|
<label>${ tl('data.effect') }</label>
|
|
|
|
<input type="text" class="dark_bordered code keyframe_input tab_target" v-model="data_point.effect" @input="updateInput('effect', $event.target.value, data_point_i)">
|
|
|
|
</div>
|
|
|
|
<div class="bar flex" id="keyframe_bar_locator" v-if="channel == 'particle'">
|
|
|
|
<label>${ tl('data.locator') }</label>
|
|
|
|
<input type="text" class="dark_bordered code keyframe_input tab_target" v-model="data_point.locator" @input="updateInput('locator', $event.target.value, data_point_i)">
|
|
|
|
</div>
|
|
|
|
<div class="bar flex" id="keyframe_bar_script" v-if="channel == 'particle'">
|
|
|
|
<label>${ tl('timeline.pre_effect_script') }</label>
|
|
|
|
<vue-prism-editor class="molang_input dark_bordered keyframe_input tab_target" v-model="data_point.script" @change="updateInput('script', $event, data_point_i)" language="molang" :line-numbers="false" />
|
|
|
|
</div>
|
|
|
|
<div class="bar" id="keyframe_bar_instructions" v-if="channel == 'timeline'">
|
|
|
|
<vue-prism-editor class="molang_input dark_bordered keyframe_input tab_target" v-model="data_point.instructions" @change="updateInput('instructions', $event, data_point_i)" language="molang" :line-numbers="false" />
|
|
|
|
</div>
|
2020-09-26 20:03:09 +08:00
|
|
|
</div>
|
2020-09-27 05:07:44 +08:00
|
|
|
</ul>
|
2020-09-22 05:23:42 +08:00
|
|
|
</template>
|
|
|
|
</div>
|
|
|
|
`
|
|
|
|
}
|
|
|
|
})
|
|
|
|
})
|