threepipe
Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.

PopmotionPlugin.ts 5.2KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163
  1. import type {Driver} from 'popmotion/lib/animations/types'
  2. import {now} from 'ts-browser-helpers'
  3. import {animate, type AnimationOptions} from 'popmotion'
  4. import {AViewerPluginSync, ThreeViewer} from '../../viewer'
  5. import type {FrameFadePlugin} from '../pipeline/FrameFadePlugin'
  6. import type {ProgressivePlugin} from '../pipeline/ProgressivePlugin'
  7. import {generateUUID} from '../../three'
  8. export interface AnimationResult{
  9. id: string
  10. promise: Promise<string>
  11. options: AnimationOptions<any>
  12. stop: () => void
  13. // eslint-disable-next-line @typescript-eslint/naming-convention
  14. _stop?: () => void
  15. }
  16. /**
  17. * Popmotion plugin
  18. *
  19. * Provides animation capabilities to the viewer using the popmotion library: https://popmotion.io/
  20. *
  21. * Overrides the driver in popmotion to sync with the viewer and provide ways to store and stop animations.
  22. *
  23. * @category Plugin
  24. */
  25. export class PopmotionPlugin extends AViewerPluginSync<''> {
  26. public static readonly PluginType = 'PopmotionPlugin'
  27. enabled = true
  28. toJSON: any = undefined // disable serialization
  29. fromJSON: any = undefined // disable serialization
  30. constructor(enabled = true) {
  31. super()
  32. this.enabled = enabled
  33. this._postFrame = this._postFrame.bind(this)
  34. }
  35. // private _animating = false
  36. private _lastFrameTime = 0 // for post frame
  37. private _updaters: {u: ((timestamp: number) => void), time: number}[] = []
  38. dependencies = []
  39. private _fadeDisabled = false
  40. /**
  41. * Disable the frame fade plugin while animation is running
  42. */
  43. disableFrameFade = true
  44. // Same code used in CameraViewPlugin
  45. private _postFrame = ()=>{
  46. if (!this._viewer) return
  47. if (!this.enabled || Object.keys(this.animations).length < 1) {
  48. this._lastFrameTime = 0
  49. // console.log('not anim')
  50. if (this._fadeDisabled) {
  51. this._viewer.getPlugin<FrameFadePlugin>('FrameFade')?.enable(PopmotionPlugin.PluginType)
  52. this._fadeDisabled = false
  53. }
  54. return
  55. }
  56. const time = now() / 1000.0
  57. if (this._lastFrameTime < 1) this._lastFrameTime = time - 1.0 / 60.0
  58. let delta = time - this._lastFrameTime
  59. this._lastFrameTime = time
  60. // todo: scrolling
  61. // delta = delta * (this.animateOnScroll ? this._scrollAnimationState : 1)
  62. const d = this._viewer.getPlugin<ProgressivePlugin>('Progressive')?.postFrameConvergedRecordingDelta()
  63. if (d && d > 0) delta = d
  64. if (d === 0) return // not converged yet.
  65. // if d < 0: not recording, do nothing
  66. delta *= 1000
  67. // delta = 16.666 // testing
  68. if (delta <= 0.001) return
  69. this._updaters.forEach(u=>{
  70. let dt = delta
  71. if (u.time + dt < 0) dt = -u.time
  72. u.time += dt
  73. if (Math.abs(dt) > 0.001)
  74. u.u(dt)
  75. })
  76. if (!this._fadeDisabled && this.disableFrameFade) {
  77. const ff = this._viewer.getPlugin<FrameFadePlugin>('FrameFade')
  78. if (ff) {
  79. ff.disable(PopmotionPlugin.PluginType)
  80. this._fadeDisabled = true
  81. }
  82. }
  83. // todo: scrolling
  84. // if (this._scrollAnimationState < 0.001) this._scrollAnimationState = 0
  85. // else this._scrollAnimationState *= 1.0 - this.scrollAnimationDamping
  86. }
  87. readonly defaultDriver: Driver = (update)=>{
  88. return {
  89. start: ()=>this._updaters.push({u:update, time:0}),
  90. stop: ()=> this._updaters.splice(this._updaters.findIndex(u=>u.u === update), 1),
  91. }
  92. }
  93. onAdded(viewer: ThreeViewer): void {
  94. super.onAdded(viewer)
  95. viewer.addEventListener('postFrame', this._postFrame)
  96. }
  97. onRemove(viewer: ThreeViewer): void {
  98. viewer.removeEventListener('postFrame', this._postFrame)
  99. super.onRemove(viewer)
  100. }
  101. readonly animations: Record<string, AnimationResult> = {}
  102. animate<V>(options: AnimationOptions<V>): AnimationResult {
  103. const uuid = generateUUID()
  104. const a: any = {
  105. id: uuid,
  106. options,
  107. stop: ()=>{
  108. if (!this.animations[uuid]?._stop) console.warn('Animation not started')
  109. else this.animations[uuid]?._stop?.()
  110. },
  111. }
  112. this.animations[uuid] = a
  113. a.promise = new Promise<void>((resolve) => {
  114. const opts: AnimationOptions<V> = {
  115. driver: this.defaultDriver,
  116. ...options,
  117. onComplete: ()=>{
  118. options.onComplete?.()
  119. resolve()
  120. },
  121. onStop: ()=>{
  122. options.onStop?.()
  123. resolve()
  124. },
  125. }
  126. const anim = animate(opts)
  127. this.animations[uuid]._stop = anim.stop
  128. this.animations[uuid].options = opts
  129. }).then(()=>{
  130. delete this.animations[uuid]
  131. return uuid
  132. })
  133. return this.animations[uuid]
  134. }
  135. async animateAsync<V>(options: AnimationOptions<V>): Promise<string> {
  136. return this.animate(options).promise
  137. }
  138. // todo : animateObject/animateTarget
  139. }