Allow dynamic properties in keyframe data points

This commit is contained in:
JannisX11 2020-10-01 13:07:45 +02:00
parent 9205ddac23
commit 9714ff6067
5 changed files with 94 additions and 80 deletions

View File

@ -102,8 +102,8 @@
<script src="js/animations/timeline.js"></script>
<script src="js/plugin_loader.js"></script>
<script src="js/io/project.js"></script>
<script src="js/io/io.js"></script>
<script src="js/io/project.js"></script>
<script src="js/io/formats/bbmodel.js"></script>
<script src="js/io/formats/java_block.js"></script>
<script src="js/io/formats/bedrock.js"></script>

View File

@ -1279,11 +1279,11 @@ const Animator = {
}
for (var timestamp in a.timeline) {
var entry = a.timeline[timestamp];
var instructions = entry instanceof Array ? entry.join('\n') : entry;
var script = entry instanceof Array ? entry.join('\n') : entry;
animation.animators.effects.addKeyframe({
channel: 'timeline',
time: parseFloat(timestamp),
data_points: [{instructions}]
data_points: [{script}]
})
}
}

View File

@ -1,53 +1,35 @@
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;
this.keyframe = keyframe;
for (var key in KeyframeDataPoint.properties) {
KeyframeDataPoint.properties[key].reset(this);
}
}
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;
Object.assign(data, data.values)
}
for (var key in KeyframeDataPoint.properties) {
KeyframeDataPoint.properties[key].merge(this, data)
}
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')
}
get x_string() {
return typeof this.x == 'number' ? trimFloatNumber(this.x) || '0' : this.x;
}
set x_string(val) {
this.x = val;
}
get y_string() {
return typeof this.y == 'number' ? trimFloatNumber(this.y) || '0' : this.y;
}
set y_string(val) {
this.y = val;
}
get z_string() {
return typeof this.z == 'number' ? trimFloatNumber(this.z) || '0' : this.z;
}
set z_string(val) {
this.z = val;
getUndoCopy() {
var copy = {}
for (var key in KeyframeDataPoint.properties) {
KeyframeDataPoint.properties[key].copy(this, copy)
}
return copy;
}
}
new Property(KeyframeDataPoint, 'molang', 'x', {condition: point => point.keyframe.transform});
new Property(KeyframeDataPoint, 'molang', 'y', {condition: point => point.keyframe.transform});
new Property(KeyframeDataPoint, 'molang', 'z', {condition: point => point.keyframe.transform});
new Property(KeyframeDataPoint, 'molang', 'w', {condition: point => point.keyframe.transform});
new Property(KeyframeDataPoint, 'string', 'effect', {condition: point => ['particle', 'sound'].includes(point.keyframe.channel)});
new Property(KeyframeDataPoint, 'string', 'locator', {condition: point => 'particle' == point.keyframe.channel});
new Property(KeyframeDataPoint, 'molang', 'script', {condition: point => ['particle', 'timeline'].includes(point.keyframe.channel)});
new Property(KeyframeDataPoint, 'string', 'file', {condition: point => ['particle', 'sound'].includes(point.keyframe.channel), exposed: false});
class Keyframe {
constructor(data, uuid) {
this.type = 'keyframe'
@ -247,13 +229,13 @@ class Keyframe {
}
}
} else if (this.channel == 'timeline') {
let instructions = [];
let scripts = [];
this.data_points.forEach(data_point => {
if (data_point.instructions) {
instructions.push(...data_point.instructions.split('\n'));
if (data_point.script) {
scripts.push(...data_point.script.split('\n'));
}
})
return instructions.length <= 1 ? instructions[0] : instructions;
return scripts.length <= 1 ? scripts[0] : scripts;
} else {
let points = [];
this.data_points.forEach(data_point => {
@ -373,7 +355,7 @@ class Keyframe {
Keyframe.properties[key].copy(this, copy)
}
this.data_points.forEach(data_point => {
copy.data_points.push(Object.assign({}, data_point))
copy.data_points.push(data_point.getUndoCopy())
})
return copy;
}
@ -724,7 +706,20 @@ Interface.definePanels(function() {
name: 'panel-keyframe',
components: {VuePrismEditor},
data() { return {
keyframes: Timeline.selected
keyframes: Timeline.selected,
channel_colors: {
x: 'color_x',
y: 'color_y',
z: 'color_z',
},
channel_names: {
x: 'X',
y: 'Y',
z: 'Z',
effect: tl('data.effect'),
locator: tl('data.locator'),
script: tl('timeline.pre_effect_script'),
}
}},
methods: {
updateInput(axis, value, data_point) {
@ -804,33 +799,28 @@ Interface.definePanels(function() {
</div>
</div>
<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>
<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
v-for="(property, key) in data_point.constructor.properties"
v-if="property.exposed != false && Condition(property.condition, data_point)"
class="bar flex"
:id="'keyframe_bar_' + property.name"
>
<label :class="[channel_colors[key]]" :style="{'font-weight': channel_colors[key] ? 'bolder' : 'unset'}">{{ channel_names[key] }}</label>
<vue-prism-editor
v-if="property.type == 'molang'"
class="molang_input dark_bordered keyframe_input tab_target"
v-model="data_point[key+'_string']"
@change="updateInput(key, $event, data_point_i)"
language="molang"
:line-numbers="false"
/>
<input
v-else
type="text"
class="dark_bordered code keyframe_input tab_target"
v-model="data_point[key]"
@input="updateInput(key, $event.target.value, data_point_i)"
/>
</div>
</div>
</ul>

View File

@ -5,6 +5,8 @@ class Property {
}
target_class.properties[name] = this;
let scope = this;
this.class = target_class;
this.name = name;
this.type = type;
@ -14,6 +16,7 @@ class Property {
} else {
switch (this.type) {
case 'string': this.default = ''; break;
case 'molang': this.default = '0'; break;
case 'number': this.default = 0; break;
case 'boolean': this.default = false; break;
case 'array': this.default = []; break;
@ -23,6 +26,7 @@ class Property {
}
switch (this.type) {
case 'string': this.isString = true; break;
case 'molang': this.isMolang = true; break;
case 'number': this.isNumber = true; break;
case 'boolean': this.isBoolean = true; break;
case 'array': this.isArray = true; break;
@ -30,6 +34,17 @@ class Property {
case 'vector2': this.isVector2 = true; break;
}
if (this.isMolang) {
Object.defineProperty(target_class.prototype, `${name}_string`, {
get() {
return typeof this[name] == 'number' ? trimFloatNumber(this[name]) || scope.default : this[name];
},
set(val) {
this[name] = val;
}
})
}
if (typeof options.merge == 'function') this.merge = options.merge;
if (typeof options.reset == 'function') this.reset = options.reset;
if (typeof options.merge_validation == 'function') this.merge_validation = options.merge_validation;
@ -39,16 +54,19 @@ class Property {
if (options.options) this.options = options.options;
}
merge(instance, data) {
if (data[this.name] == undefined || !Condition(this.condition)) return;
if (data[this.name] == undefined || !Condition(this.condition, instance)) return;
if (this.isString) {
Merge.string(instance, data, this.name)
Merge.string(instance, data, this.name, this.merge_validation)
}
else if (this.isNumber) {
Merge.number(instance, data, this.name)
}
else if (this.isMolang) {
Merge.molang(instance, data, this.name)
}
else if (this.isBoolean) {
Merge.boolean(instance, data, this.name)
Merge.boolean(instance, data, this.name, this.merge_validation)
}
else if (this.isArray || this.isVector || this.isVector2) {
if (data[this.name] instanceof Array) {
@ -60,7 +78,7 @@ class Property {
}
}
copy(instance, target) {
if (!Condition(this.condition)) return;
if (!Condition(this.condition, instance)) return;
if (this.isArray || this.isVector || this.isVector2) {
if (instance[this.name] instanceof Array) {
@ -71,6 +89,7 @@ class Property {
}
}
reset(instance) {
if (instance[this.name] == undefined && !Condition(this.condition, instance)) return;
if (typeof this.default == 'function') {
var dft = this.default(instance);
} else {

View File

@ -527,6 +527,11 @@ var Merge = {
}
}
},
molang: function(obj, source, index) {
if (['string', 'number'].includes(typeof source[index])) {
obj[index] = source[index];
}
},
boolean: function(obj, source, index, validate) {
if (source[index] !== undefined) {
if (validate instanceof Function === false || validate(source[index])) {