|
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201 |
- import {Quaternion, Vector3} from 'three'
- import {AViewerPluginSync, ThreeViewer} from '../../viewer'
- import {UiObjectConfig} from 'uiconfig.js'
- import {PopmotionPlugin} from './PopmotionPlugin'
- import {IObject3D} from '../../core'
-
- // todo make a serializable object like CameraView for proper ui state management
- export interface TSavedTransform {
- position: Vector3
- quaternion: Quaternion
- scale: Vector3
- name?: string
- }
-
- /**
- * 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<''> {
- public static readonly PluginType = 'TransformAnimationPlugin'
- toJSON: any = undefined
-
- enabled = true
- dependencies = [PopmotionPlugin]
-
- constructor() {
- super()
- }
-
- onAdded(viewer: ThreeViewer): void {
- super.onAdded(viewer)
- viewer.scene.addEventListener('addSceneObject', this._addSceneObject)
- }
- onRemove(viewer: ThreeViewer): void {
- viewer.scene.removeEventListener('addSceneObject', this._addSceneObject)
- return super.onRemove(viewer)
- }
- private _addSceneObject = (e: any)=>{
- const object = e.object as IObject3D
- object?.traverse && object.traverse((o: IObject3D)=>{
- if (!o.userData[TransformAnimationPlugin.PluginType]) {
- o.userData[TransformAnimationPlugin.PluginType] = {
- transforms: [] as TSavedTransform[],
- }
- }
- // 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: UiObjectConfig = {
- 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: TSavedTransform, i: number)=>({
- 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
- })
- }
-
- addTransform(o: IObject3D, name?: string) {
- if (!o.userData[TransformAnimationPlugin.PluginType]) {
- o.userData[TransformAnimationPlugin.PluginType] = {
- transforms: [] as TSavedTransform[],
- }
- }
- 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: IObject3D, tr: TSavedTransform|number|string) {
- 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: TSavedTransform | number | string, o: IObject3D) {
- 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: IObject3D, tr: TSavedTransform|number|string, 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: number) => {
- 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?.()
- },
- })
- }
- }
-
- declare module '../../core/IObject' {
- interface IObject3DUserData {
- [TransformAnimationPlugin.PluginType]?: {
- transforms: TSavedTransform[]
- }
- }
- }
|