blockbench/js/io/formats/gltf.js

241 lines
7.3 KiB
JavaScript
Raw Normal View History

2020-02-08 02:06:24 +08:00
(function() {
2021-12-02 04:11:41 +08:00
function buildAnimationTracks(do_quaternions = true) {
2020-04-30 05:35:47 +08:00
let anims = [];
2020-04-26 02:25:07 +08:00
Animator.animations.forEach(animation => {
2020-02-08 02:06:24 +08:00
let ik_samples = animation.sampleIK();
2020-04-26 02:25:07 +08:00
let tracks = [];
for (var uuid in animation.animators) {
let animator = animation.animators[uuid];
if (animator.type == 'bone' && animator.getGroup()) {
for (var channel in animator.channels) {
if (channel == 'rotation' && ik_samples[uuid]) {
let times = [];
let values = [];
let sample_rate = settings.animation_sample_rate.value;
let interpolation = THREE.InterpolateLinear;
ik_samples[uuid].forEach((sample, i) => {
sample.euler.x += animator.group.mesh.fix_rotation.x;
sample.euler.y += animator.group.mesh.fix_rotation.y;
sample.euler.z += animator.group.mesh.fix_rotation.z;
let quaternion = new THREE.Quaternion().setFromEuler(sample.euler);
quaternion.toArray(values, values.length);
times.push(i / sample_rate);
})
let track = new THREE.QuaternionKeyframeTrack(animator.group.mesh.uuid+'.quaternion', times, values, interpolation);
track.group_uuid = animator.group.uuid;
track.channel = 'quaternion';
tracks.push(track);
} else if (animator[channel] && animator[channel].length) {
2020-04-26 02:25:07 +08:00
let times = [];
let values = [];
let keyframes = animator[channel].slice();
2021-05-06 02:38:19 +08:00
// Sampling non-linear and math-based values
2020-04-26 02:25:07 +08:00
let contains_script
for (var kf of keyframes) {
if (kf.interpolation == Keyframe.interpolation.catmullrom) {
contains_script = true; break;
}
for (var data_point of kf.data_points) {
if (isNaN(data_point.x) || isNaN(data_point.y) || isNaN(data_point.z)) {
contains_script = true; break;
}
2020-04-26 02:25:07 +08:00
}
if (contains_script) break;
2020-04-26 02:25:07 +08:00
}
if (contains_script) {
2021-05-06 02:38:19 +08:00
let interval = 1 / Math.clamp(settings.animation_sample_rate.value, 0.1, 500);
let last_values;
for (var time = 0; time < animation.length; time += interval) {
2020-04-26 02:25:07 +08:00
Timeline.time = time;
let values = animator.interpolate(channel, false)
2021-05-06 02:38:19 +08:00
if (!values.equals(last_values) && !keyframes.find(kf => Math.epsilon(kf.time, time, interval))) {
2020-04-26 02:25:07 +08:00
let new_keyframe = new Keyframe({
time, channel,
data_points: [{
x: values[0],
y: values[1],
z: values[2],
}]
2022-07-09 22:08:59 +08:00
}, null, animator)
2020-04-26 02:25:07 +08:00
new_keyframe.animator = animator;
keyframes.push(new_keyframe)
}
last_values = values;
}
}
keyframes.sort((a, b) => a.time - b.time)
// Sampling rotation steps that exceed 180 degrees
2021-12-02 04:11:41 +08:00
if (channel === 'rotation' && !contains_script && do_quaternions) {
2020-04-26 02:25:07 +08:00
let original_keyframes = keyframes.slice();
original_keyframes.forEach((kf, i) => {
let next = original_keyframes[i+1]
if (!next) return;
let k1 = kf.getArray(kf.data_points.length - 1);
2020-04-26 02:25:07 +08:00
let k2 = next.getArray();
let max_diff = Math.max(Math.abs(k1[0] - k2[0]), Math.abs(k1[1] - k2[1]), Math.abs(k1[2] - k2[2]));
let steps = Math.floor(max_diff / 180 + 1);
for (var step = 1; step < steps; step++) {
Timeline.time = kf.time + (next.time - kf.time) * (step/steps);
let values = animator.interpolate(channel, false)
let new_keyframe = new Keyframe({
time: Timeline.time, channel,
data_points: [{
x: values[0],
y: values[1],
z: values[2],
}]
2022-07-09 22:08:59 +08:00
}, null, animator)
2020-04-26 02:25:07 +08:00
new_keyframe.animator = animator;
keyframes.splice(keyframes.indexOf(kf) + step, 0, new_keyframe);
}
})
}
// Discontinuous keyframes
keyframes.slice().forEach(kf => {
if (kf.data_points.length > 1 && !kf.getArray(0).equals(kf.getArray(1))) {
let new_keyframe = new Keyframe({
time: kf.time + 0.004, channel,
data_points: [kf.data_points[1]]
2022-07-09 22:08:59 +08:00
}, null, animator)
new_keyframe.animator = animator;
keyframes.splice(keyframes.indexOf(kf) + 1, 0, new_keyframe);
}
})
let interpolation = THREE.InterpolateLinear;
2020-04-26 02:25:07 +08:00
keyframes.forEach(kf => {
if (kf.interpolation == Keyframe.interpolation.catmullrom) {
interpolation = THREE.InterpolateSmooth
}
if (kf.interpolation == Keyframe.interpolation.step) {
interpolation = THREE.InterpolateDiscrete
}
2020-04-26 02:25:07 +08:00
times.push(kf.time);
Timeline.time = kf.time;
2022-07-02 04:47:29 +08:00
let result = kf.getFixed(0, do_quaternions)
if (do_quaternions) {
result.toArray(values, values.length);
} else {
values.push(result.x, result.y, result.z);
}
2020-04-26 02:25:07 +08:00
})
let trackType = THREE.VectorKeyframeTrack;
2021-12-02 04:11:41 +08:00
if (channel === 'rotation' && do_quaternions) {
2020-04-26 02:25:07 +08:00
trackType = THREE.QuaternionKeyframeTrack;
channel = 'quaternion';
} else if (channel == 'position') {
values.forEach((val, i) => {
values[i] = val/16;
})
2020-04-26 02:25:07 +08:00
}
let track = new trackType(animator.group.mesh.uuid+'.'+channel, times, values, interpolation);
2021-12-02 04:11:41 +08:00
track.group_uuid = animator.group.uuid;
track.channel = channel;
2020-04-26 02:25:07 +08:00
tracks.push(track);
}
}
} else if (animator instanceof BoneAnimator) {
console.log(`Skip export of track ${uuid.substr(0, 7)}... - No connected bone`)
}
}
if (tracks.length) {
let clip = new THREE.AnimationClip(animation.name, animation.length, tracks)
2020-04-30 05:35:47 +08:00
anims.push(clip);
2020-04-26 02:25:07 +08:00
} else {
console.log(`Skip export of animation ${animation.name} - No tracks generated`)
}
})
2020-04-30 05:35:47 +08:00
return anims;
2020-04-26 02:25:07 +08:00
}
2020-02-08 02:06:24 +08:00
var codec = new Codec('gltf', {
name: 'GLTF Model',
extension: 'gltf',
2021-12-30 20:15:29 +08:00
async compile(options = 0) {
2020-03-05 03:56:17 +08:00
let scope = this;
2020-02-08 02:06:24 +08:00
let exporter = new THREE.GLTFExporter();
let animations = [];
let gl_scene = new THREE.Scene();
gl_scene.name = 'blockbench_export'
gl_scene.add(Project.model_3d);
2021-12-30 20:15:29 +08:00
try {
if (!Modes.edit) {
Animator.showDefaultPose();
}
if (BarItems.view_mode.value !== 'textured') {
BarItems.view_mode.set('textured');
BarItems.view_mode.onChange();
}
if (options.animations !== false) {
animations = buildAnimationTracks();
}
2021-12-30 20:15:29 +08:00
let json = await new Promise((resolve, reject) => {
exporter.parse(gl_scene, resolve, {
animations,
onlyVisible: false,
trs: true,
truncateDrawRange: false,
forcePowerOfTwoTextures: true,
scale_factor: 1/16,
exportFaceColors: false,
});
})
scene.add(Project.model_3d);
2020-03-05 03:56:17 +08:00
scope.dispatchEvent('compile', {model: json, options});
2021-12-30 20:15:29 +08:00
return JSON.stringify(json);
2020-02-08 02:06:24 +08:00
2021-12-30 20:15:29 +08:00
} catch (err) {
scene.add(Project.model_3d);
2021-12-30 20:15:29 +08:00
throw err;
}
2020-02-08 02:06:24 +08:00
},
export() {
var scope = codec;
2021-12-30 20:15:29 +08:00
scope.compile().then(content => {
2020-04-26 02:25:07 +08:00
setTimeout(_ => {
Blockbench.export({
resource_id: 'gltf',
type: scope.name,
extensions: [scope.extension],
name: scope.fileName(),
startpath: scope.startPath(),
content,
custom_writer: isApp ? (a, b) => scope.write(a, b) : null,
}, path => scope.afterDownload(path))
}, 20)
2020-02-08 02:06:24 +08:00
})
}
})
2021-12-01 18:39:30 +08:00
codec.buildAnimationTracks = buildAnimationTracks;
2020-02-08 02:06:24 +08:00
BARS.defineActions(function() {
codec.export_action = new Action({
id: 'export_gltf',
2020-03-05 03:56:17 +08:00
icon: 'icon-gltf',
2020-02-08 02:06:24 +08:00
category: 'file',
click: function () {
codec.export()
}
})
})
})()