threepipe
Version:
A modern 3D viewer framework built on top of three.js, written in TypeScript, designed to make creating high-quality, modular, and extensible 3D experiences on the web simple and enjoyable.
175 lines • 7.06 kB
JavaScript
import { Quaternion, Vector3 } from 'three';
import { AViewerPluginSync } from '../../viewer';
import { PopmotionPlugin } from './PopmotionPlugin';
/**
* Transform Animation Plugin
*
* Helper plugin to save, load and animate between different transforms(position, rotation, scale) on objects.
* Also adds a UI to add and animate transforms on objects.
* Requires the PopmotionPlugin to animate.
*
* @category Plugins
*/
export class TransformAnimationPlugin extends AViewerPluginSync {
constructor() {
super();
this.toJSON = undefined;
this.enabled = true;
this.dependencies = [PopmotionPlugin];
this._addSceneObject = (e) => {
const object = e.object;
object?.traverse && !object.isWidget && object.traverse((o) => {
if (o.isWidget)
return;
// if (!o.userData[TransformAnimationPlugin.PluginType].transforms) {
// o.userData[TransformAnimationPlugin.PluginType].transforms = []
// }
// for old files, todo remove later
o.userData[TransformAnimationPlugin.PluginType]?.transforms?.forEach((t, i) => {
if (t.name === undefined)
t.name = 'Transform ' + i;
});
const uiConfig = {
type: 'folder',
label: 'Transform Animation',
children: [
{
type: 'button',
label: 'Add Current Transform',
value: () => {
this.addTransform(o);
uiConfig?.uiRefresh?.();
},
},
() => o.userData[TransformAnimationPlugin.PluginType]?.transforms.map((t, i) => ({
type: 'folder',
label: t.name || `Transform ${i}`,
children: [
{
type: 'input',
label: 'Name',
property: [t, 'name'],
},
{
type: 'vec3',
label: 'Position',
property: [t, 'position'],
},
{
type: 'vec3',
label: 'Quaternion',
property: [t, 'quaternion'],
},
{
type: 'vec3',
label: 'Scale',
property: [t, 'scale'],
},
{
type: 'button',
label: 'Set',
value: () => {
this.setTransform(o, t);
},
},
{
type: 'button',
label: 'Animate',
value: () => {
this.animateTransform(o, t);
},
}
],
})),
],
};
o.uiConfig?.children?.push(uiConfig); // todo check if already exists
});
};
}
onAdded(viewer) {
super.onAdded(viewer);
viewer.scene.addEventListener('addSceneObject', this._addSceneObject);
}
onRemove(viewer) {
viewer.scene.removeEventListener('addSceneObject', this._addSceneObject);
return super.onRemove(viewer);
}
addTransform(o, name) {
if (!o.userData[TransformAnimationPlugin.PluginType]) {
o.userData[TransformAnimationPlugin.PluginType] = {
transforms: [],
};
}
const transform = {
name: name || 'Transform ' + (o.userData[TransformAnimationPlugin.PluginType].transforms.length + 1),
position: o.position.clone(),
quaternion: o.quaternion.clone(),
scale: o.scale.clone(),
};
o.userData[TransformAnimationPlugin.PluginType].transforms.push(transform);
return transform;
}
setTransform(o, tr) {
const t = this.getSavedTransform(tr, o);
if (!t)
return;
o.position.copy(t.position);
o.quaternion.copy(t.quaternion);
o.scale.copy(t.scale);
o.setDirty?.();
o.uiConfig?.uiRefresh?.();
}
getSavedTransform(tr, o) {
return typeof tr === 'number' ?
o.userData[TransformAnimationPlugin.PluginType]?.transforms[tr] :
typeof tr === 'string' ?
o.userData[TransformAnimationPlugin.PluginType]?.transforms.find(t1 => t1.name === tr) :
tr;
}
animateTransform(o, tr, duration = 2000) {
const popmotion = this._viewer?.getPlugin(PopmotionPlugin);
if (!popmotion) {
this._viewer?.console.error('PopmotionPlugin required for animation');
}
const t = this.getSavedTransform(tr, o);
if (!t)
return;
// todo stop all existing animations(for the current model) like CameraView?
const pos = new Vector3();
const q = new Quaternion();
const s = new Vector3();
const op = o.position.clone();
const oq = o.quaternion.clone();
const os = o.scale.clone();
const ep = t.position;
const eq = t.quaternion;
const es = t.scale;
return popmotion?.animate({
from: 0,
to: 1,
duration: duration,
onUpdate: (v) => {
pos.lerpVectors(op, ep, v);
q.slerpQuaternions(oq, eq, v);
s.lerpVectors(os, es, v);
o.position.copy(pos);
o.quaternion.copy(q);
o.scale.copy(s);
this._viewer?.setDirty();
this._viewer?.renderManager.resetShadows();
// o.setDirty?.()
// o.uiConfig?.uiRefresh?.()
},
onStop: () => {
o.position.copy(t.position);
o.quaternion.copy(t.quaternion);
o.scale.copy(t.scale);
o.setDirty?.();
o.uiConfig?.uiRefresh?.();
},
});
}
}
TransformAnimationPlugin.PluginType = 'TransformAnimationPlugin';
//# sourceMappingURL=TransformAnimationPlugin.js.map