Allow dynamic properties in keyframe data points
This commit is contained in:
parent
9205ddac23
commit
9714ff6067
@ -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>
|
||||
|
@ -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}]
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -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>
|
||||
|
@ -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 {
|
||||
|
@ -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])) {
|
||||
|
Loading…
x
Reference in New Issue
Block a user