| @@ -72,6 +72,7 @@ To make changes and run the example, click on the CodePen button on the top righ | |||
| - [NormalBufferPlugin](#normalbufferplugin) - Pre-rendering of normal buffer | |||
| - [GBufferPlugin](#depthnormalbufferplugin) - Pre-rendering of depth and normal buffers in a single pass buffer | |||
| - [GLTFAnimationPlugin](#gltfanimationplugin) - Add support for playing and seeking gltf animations | |||
| - [PopmotionPlugin](#popmotionplugin) - Integrates with popmotion.io library for animation/tweening | |||
| - [RenderTargetPreviewPlugin](#rendertargetpreviewplugin) - Preview any render target in a UI panel over the canvas | |||
| - [FrameFadePlugin](#framefadeplugin) - Post-render pass to smoothly fade to a new rendered frame over time | |||
| - [Rhino3dmLoadPlugin](#rhino3dmloadplugin) - Add support for loading .3dm files | |||
| @@ -547,6 +548,62 @@ This plugin is made for playing, pausing, stopping, all the animations at once, | |||
| To play individual animations, with custom choreography, use the {@link GLTFAnimationPlugin.animations} property to get reference to the animation clips and actions. Create your own mixers and control the animation playback like in three.js | |||
| ## PopmotionPlugin | |||
| todo: image | |||
| Example: https://threepipe.org/examples/#popmotion-plugin/ | |||
| Source Code: [src/plugins/animation/PopmotionPlugin.ts](./src/plugins/animation/PopmotionPlugin.ts) | |||
| API Reference: [PopmotionPlugin](https://threepipe.org/docs/classes/PopmotionPlugin.html) | |||
| Provides animation/tweening capabilities to the viewer using the [popmotion.io](https://popmotion.io/) library. | |||
| Overrides the driver in popmotion to sync with the viewer and provide ways to store and stop animations. | |||
| ```typescript | |||
| import {PopmotionPlugin, ThreeViewer} from 'threepipe' | |||
| const viewer = new ThreeViewer({...}) | |||
| const cube = viewer.scene.getObjectByName('cube'); | |||
| const popmotion = viewer.addPluginSync(new PopmotionPlugin()) | |||
| // Move the object cube 1 unit up. | |||
| const anim = popmotion.animate({ | |||
| from: cube.position.y, | |||
| to: cube.position.y + 1, | |||
| duration: 500, // ms | |||
| onUpdate: (v) => { | |||
| cube.position.setY(v) | |||
| cube.setDirty() | |||
| }, | |||
| onComplete: () => isMovedUp = !isMovedUp, | |||
| }) | |||
| // await for animation | |||
| await anim.promise; | |||
| // or stop the animation | |||
| // anim.stop() | |||
| // Animate the color | |||
| await popmotion.animateAsync({ // Also await for the animation. | |||
| from: '#' + cube.material.color.getHexString(), | |||
| to: '#' + new Color().setHSL(Math.random(), 1, 0.5).getHexString(), | |||
| duration: 500, | |||
| onUpdate: (v) => { | |||
| cube.material.color.set(v) | |||
| cube.material.setDirty() | |||
| }, | |||
| }) | |||
| ``` | |||
| Note: The animation is started when the animate or animateAsync function is called. | |||
| ## RenderTargetPreviewPlugin | |||
| todo: image | |||
| @@ -267,6 +267,7 @@ | |||
| <h2 class="category">Animation</h2> | |||
| <ul> | |||
| <li><a href="./gltf-animation-plugin/">glTF Animation Plugin </a></li> | |||
| <li><a href="./gltf-animation-plugin/">Popmotion Plugin </a></li> | |||
| <li><a href="./gltf-camera-animation/">glTF Camera Animation </a></li> | |||
| <li><a href="./gltf-animation-page-scroll/">glTF Animation Page Scroll </a></li> | |||
| </ul> | |||
| @@ -0,0 +1,34 @@ | |||
| <!DOCTYPE html> | |||
| <html lang="en"> | |||
| <head> | |||
| <meta charset="UTF-8"> | |||
| <title>Popmotion Plugin</title> | |||
| <!-- Import maps polyfill --> | |||
| <!-- Remove this when import maps will be widely supported --> | |||
| <script async src="https://unpkg.com/es-module-shims@1.6.3/dist/es-module-shims.js"></script> | |||
| <script type="importmap"> | |||
| { | |||
| "imports": { | |||
| "threepipe": "./../../dist/index.mjs" | |||
| } | |||
| } | |||
| </script> | |||
| <style id="example-style"> | |||
| html, body, #canvas-container, #mcanvas { | |||
| width: 100%; | |||
| height: 100%; | |||
| margin: 0; | |||
| overflow: hidden; | |||
| } | |||
| </style> | |||
| <script type="module" src="../examples-utils/simple-code-preview.mjs"></script> | |||
| <script id="example-script" type="module" src="./script.js" data-scripts="./script.ts;./script.js"></script> | |||
| </head> | |||
| <body> | |||
| <div id="canvas-container"> | |||
| <canvas id="mcanvas"></canvas> | |||
| </div> | |||
| </body> | |||
| @@ -0,0 +1,65 @@ | |||
| import {_testFinish, BoxGeometry, Color, Mesh, PhysicalMaterial, PopmotionPlugin, ThreeViewer} from 'threepipe' | |||
| import {createSimpleButtons} from '../examples-utils/simple-bottom-buttons.js' | |||
| async function init() { | |||
| const viewer = new ThreeViewer({ | |||
| canvas: document.getElementById('mcanvas') as HTMLCanvasElement, | |||
| }) | |||
| const popmotion = viewer.addPluginSync(PopmotionPlugin) | |||
| await viewer.setEnvironmentMap('https://threejs.org/examples/textures/equirectangular/venice_sunset_1k.hdr') | |||
| const cube = viewer.scene.addObject(new Mesh( | |||
| new BoxGeometry(1, 1, 1), | |||
| new PhysicalMaterial({color: 0xff0000}) | |||
| )) | |||
| let isMovedUp = false | |||
| createSimpleButtons({ | |||
| ['Move Up/Down']: async(btn) => { | |||
| btn.disabled = true | |||
| await popmotion.animateAsync({ | |||
| from: cube.position.y, | |||
| to: cube.position.y + (isMovedUp ? -1 : 1), | |||
| duration: 500, // ms | |||
| onUpdate: (v) => { | |||
| cube.position.setY(v) | |||
| cube.setDirty() | |||
| }, | |||
| onComplete: () => isMovedUp = !isMovedUp, | |||
| }) | |||
| btn.disabled = false | |||
| }, | |||
| ['Rotate +90deg']: async(btn) => { | |||
| btn.disabled = true | |||
| await popmotion.animateAsync({ | |||
| from: cube.rotation.y, | |||
| to: cube.rotation.y + Math.PI / 2, | |||
| duration: 500, | |||
| onUpdate: (v) => { | |||
| cube.rotation.y = v | |||
| cube.setDirty() | |||
| }, | |||
| }) | |||
| btn.disabled = false | |||
| }, | |||
| ['Change Color']: async(btn)=>{ | |||
| btn.disabled = true | |||
| await popmotion.animateAsync({ | |||
| from: '#' + cube.material.color.getHexString(), | |||
| to: '#' + new Color().setHSL(Math.random(), 1, 0.5).getHexString(), | |||
| duration: 500, | |||
| onUpdate: (v) => { | |||
| cube.material.color.set(v) | |||
| cube.material.setDirty() | |||
| }, | |||
| }) | |||
| btn.disabled = false | |||
| }, | |||
| }) | |||
| } | |||
| init().then(_testFinish) | |||
| @@ -31,6 +31,7 @@ | |||
| "eslint-plugin-import": "^2.27.5", | |||
| "local-web-server": "^5.3.0", | |||
| "markdown-to-html-cli": "^3.7.0", | |||
| "popmotion": "^11.0.5", | |||
| "rimraf": "^5.0.1", | |||
| "rollup": "^3.23.0", | |||
| "rollup-plugin-glsl": "^1.3.0", | |||
| @@ -3170,6 +3171,21 @@ | |||
| "url": "https://github.com/sponsors/isaacs" | |||
| } | |||
| }, | |||
| "node_modules/framesync": { | |||
| "version": "6.1.2", | |||
| "resolved": "https://registry.npmjs.org/framesync/-/framesync-6.1.2.tgz", | |||
| "integrity": "sha512-jBTqhX6KaQVDyus8muwZbBeGGP0XgujBRbQ7gM7BRdS3CadCZIHiawyzYLnafYcvZIh5j8WE7cxZKFn7dXhu9g==", | |||
| "dev": true, | |||
| "dependencies": { | |||
| "tslib": "2.4.0" | |||
| } | |||
| }, | |||
| "node_modules/framesync/node_modules/tslib": { | |||
| "version": "2.4.0", | |||
| "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz", | |||
| "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==", | |||
| "dev": true | |||
| }, | |||
| "node_modules/fresh": { | |||
| "version": "0.5.2", | |||
| "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", | |||
| @@ -3850,6 +3866,12 @@ | |||
| "url": "https://opencollective.com/unified" | |||
| } | |||
| }, | |||
| "node_modules/hey-listen": { | |||
| "version": "1.0.8", | |||
| "resolved": "https://registry.npmjs.org/hey-listen/-/hey-listen-1.0.8.tgz", | |||
| "integrity": "sha512-COpmrF2NOg4TBWUJ5UVyaCU2A88wEMkUPK4hNqyCkqHbxT92BbvfjoSozkAIIm6XhicGlJHhFdullInrdhwU8Q==", | |||
| "dev": true | |||
| }, | |||
| "node_modules/html-void-elements": { | |||
| "version": "2.0.1", | |||
| "resolved": "https://registry.npmjs.org/html-void-elements/-/html-void-elements-2.0.1.tgz", | |||
| @@ -7028,6 +7050,24 @@ | |||
| "url": "https://github.com/sponsors/sindresorhus" | |||
| } | |||
| }, | |||
| "node_modules/popmotion": { | |||
| "version": "11.0.5", | |||
| "resolved": "https://registry.npmjs.org/popmotion/-/popmotion-11.0.5.tgz", | |||
| "integrity": "sha512-la8gPM1WYeFznb/JqF4GiTkRRPZsfaj2+kCxqQgr2MJylMmIKUwBfWW8Wa5fml/8gmtlD5yI01MP1QCZPWmppA==", | |||
| "dev": true, | |||
| "dependencies": { | |||
| "framesync": "6.1.2", | |||
| "hey-listen": "^1.0.8", | |||
| "style-value-types": "5.1.2", | |||
| "tslib": "2.4.0" | |||
| } | |||
| }, | |||
| "node_modules/popmotion/node_modules/tslib": { | |||
| "version": "2.4.0", | |||
| "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz", | |||
| "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==", | |||
| "dev": true | |||
| }, | |||
| "node_modules/postcss": { | |||
| "version": "8.4.24", | |||
| "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.24.tgz", | |||
| @@ -9245,6 +9285,22 @@ | |||
| "integrity": "sha512-IezA2qp+vcdlhJaVm5SOdPPTUu0FCEqfNSli2vRuSIBbu5Nq5UvygTk/VzeCqfLz2Atj3dVII5QBKGZRZ0edzw==", | |||
| "dev": true | |||
| }, | |||
| "node_modules/style-value-types": { | |||
| "version": "5.1.2", | |||
| "resolved": "https://registry.npmjs.org/style-value-types/-/style-value-types-5.1.2.tgz", | |||
| "integrity": "sha512-Vs9fNreYF9j6W2VvuDTP7kepALi7sk0xtk2Tu8Yxi9UoajJdEVpNpCov0HsLTqXvNGKX+Uv09pkozVITi1jf3Q==", | |||
| "dev": true, | |||
| "dependencies": { | |||
| "hey-listen": "^1.0.8", | |||
| "tslib": "2.4.0" | |||
| } | |||
| }, | |||
| "node_modules/style-value-types/node_modules/tslib": { | |||
| "version": "2.4.0", | |||
| "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz", | |||
| "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==", | |||
| "dev": true | |||
| }, | |||
| "node_modules/stylehacks": { | |||
| "version": "5.1.1", | |||
| "resolved": "https://registry.npmjs.org/stylehacks/-/stylehacks-5.1.1.tgz", | |||
| @@ -12634,6 +12690,23 @@ | |||
| } | |||
| } | |||
| }, | |||
| "framesync": { | |||
| "version": "6.1.2", | |||
| "resolved": "https://registry.npmjs.org/framesync/-/framesync-6.1.2.tgz", | |||
| "integrity": "sha512-jBTqhX6KaQVDyus8muwZbBeGGP0XgujBRbQ7gM7BRdS3CadCZIHiawyzYLnafYcvZIh5j8WE7cxZKFn7dXhu9g==", | |||
| "dev": true, | |||
| "requires": { | |||
| "tslib": "2.4.0" | |||
| }, | |||
| "dependencies": { | |||
| "tslib": { | |||
| "version": "2.4.0", | |||
| "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz", | |||
| "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==", | |||
| "dev": true | |||
| } | |||
| } | |||
| }, | |||
| "fresh": { | |||
| "version": "0.5.2", | |||
| "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", | |||
| @@ -13135,6 +13208,12 @@ | |||
| "space-separated-tokens": "^2.0.0" | |||
| } | |||
| }, | |||
| "hey-listen": { | |||
| "version": "1.0.8", | |||
| "resolved": "https://registry.npmjs.org/hey-listen/-/hey-listen-1.0.8.tgz", | |||
| "integrity": "sha512-COpmrF2NOg4TBWUJ5UVyaCU2A88wEMkUPK4hNqyCkqHbxT92BbvfjoSozkAIIm6XhicGlJHhFdullInrdhwU8Q==", | |||
| "dev": true | |||
| }, | |||
| "html-void-elements": { | |||
| "version": "2.0.1", | |||
| "resolved": "https://registry.npmjs.org/html-void-elements/-/html-void-elements-2.0.1.tgz", | |||
| @@ -15378,6 +15457,26 @@ | |||
| "integrity": "sha512-eW/gHNMlxdSP6dmG6uJip6FXN0EQBwm2clYYd8Wul42Cwu/DK8HEftzsapcNdYe2MfLiIwZqsDk2RDEsTE79hA==", | |||
| "dev": true | |||
| }, | |||
| "popmotion": { | |||
| "version": "11.0.5", | |||
| "resolved": "https://registry.npmjs.org/popmotion/-/popmotion-11.0.5.tgz", | |||
| "integrity": "sha512-la8gPM1WYeFznb/JqF4GiTkRRPZsfaj2+kCxqQgr2MJylMmIKUwBfWW8Wa5fml/8gmtlD5yI01MP1QCZPWmppA==", | |||
| "dev": true, | |||
| "requires": { | |||
| "framesync": "6.1.2", | |||
| "hey-listen": "^1.0.8", | |||
| "style-value-types": "5.1.2", | |||
| "tslib": "2.4.0" | |||
| }, | |||
| "dependencies": { | |||
| "tslib": { | |||
| "version": "2.4.0", | |||
| "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz", | |||
| "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==", | |||
| "dev": true | |||
| } | |||
| } | |||
| }, | |||
| "postcss": { | |||
| "version": "8.4.24", | |||
| "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.24.tgz", | |||
| @@ -17008,6 +17107,24 @@ | |||
| "integrity": "sha512-IezA2qp+vcdlhJaVm5SOdPPTUu0FCEqfNSli2vRuSIBbu5Nq5UvygTk/VzeCqfLz2Atj3dVII5QBKGZRZ0edzw==", | |||
| "dev": true | |||
| }, | |||
| "style-value-types": { | |||
| "version": "5.1.2", | |||
| "resolved": "https://registry.npmjs.org/style-value-types/-/style-value-types-5.1.2.tgz", | |||
| "integrity": "sha512-Vs9fNreYF9j6W2VvuDTP7kepALi7sk0xtk2Tu8Yxi9UoajJdEVpNpCov0HsLTqXvNGKX+Uv09pkozVITi1jf3Q==", | |||
| "dev": true, | |||
| "requires": { | |||
| "hey-listen": "^1.0.8", | |||
| "tslib": "2.4.0" | |||
| }, | |||
| "dependencies": { | |||
| "tslib": { | |||
| "version": "2.4.0", | |||
| "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz", | |||
| "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==", | |||
| "dev": true | |||
| } | |||
| } | |||
| }, | |||
| "stylehacks": { | |||
| "version": "5.1.1", | |||
| "resolved": "https://registry.npmjs.org/stylehacks/-/stylehacks-5.1.1.tgz", | |||
| @@ -98,7 +98,8 @@ | |||
| "typescript": "^5.0.4", | |||
| "typescript-plugin-css-modules": "^5.0.1", | |||
| "uiconfig.js": "^0.0.6", | |||
| "rollup-plugin-replace": "^2.2.0" | |||
| "rollup-plugin-replace": "^2.2.0", | |||
| "popmotion": "^11.0.5" | |||
| }, | |||
| "dependencies": { | |||
| "@types/three": "https://github.com/repalash/three-ts-types/releases/download/v0.152.1012/package.tgz", | |||
| @@ -10,6 +10,7 @@ import {fileURLToPath} from 'url'; | |||
| import terser from "@rollup/plugin-terser"; | |||
| import postcss from 'rollup-plugin-postcss' | |||
| import glsl from "rollup-plugin-glsl" | |||
| import replace from "rollup-plugin-replace"; | |||
| const __filename = fileURLToPath(import.meta.url); | |||
| const __dirname = path.dirname(__filename); | |||
| @@ -56,6 +57,9 @@ export default { | |||
| ], | |||
| external: [], | |||
| plugins: [ | |||
| replace({ | |||
| 'process.env.NODE_ENV': JSON.stringify( 'production' ) | |||
| }), | |||
| glsl({ // todo: minify glsl. | |||
| include: "src/**/*.glsl" | |||
| }), | |||
| @@ -0,0 +1,163 @@ | |||
| import type {Driver} from 'popmotion/lib/animations/types' | |||
| import {now} from 'ts-browser-helpers' | |||
| import {animate, type AnimationOptions} from 'popmotion' | |||
| import {AViewerPluginSync, ThreeViewer} from '../../viewer' | |||
| import type {FrameFadePlugin} from '../pipeline/FrameFadePlugin' | |||
| import type {ProgressivePlugin} from '../pipeline/ProgressivePlugin' | |||
| import {generateUUID} from '../../three' | |||
| export interface AnimationResult{ | |||
| id: string | |||
| promise: Promise<string> | |||
| options: AnimationOptions<any> | |||
| stop: () => void | |||
| // eslint-disable-next-line @typescript-eslint/naming-convention | |||
| _stop?: () => void | |||
| } | |||
| /** | |||
| * Popmotion plugin | |||
| * | |||
| * Provides animation capabilities to the viewer using the popmotion library: https://popmotion.io/ | |||
| * | |||
| * Overrides the driver in popmotion to sync with the viewer and provide ways to store and stop animations. | |||
| * | |||
| * @category Plugin | |||
| */ | |||
| export class PopmotionPlugin extends AViewerPluginSync<''> { | |||
| public static readonly PluginType = 'PopmotionPlugin' | |||
| enabled = true | |||
| toJSON: any = undefined // disable serialization | |||
| fromJSON: any = undefined // disable serialization | |||
| constructor(enabled = true) { | |||
| super() | |||
| this.enabled = enabled | |||
| this._postFrame = this._postFrame.bind(this) | |||
| } | |||
| // private _animating = false | |||
| private _lastFrameTime = 0 // for post frame | |||
| private _updaters: {u: ((timestamp: number) => void), time: number}[] = [] | |||
| dependencies = [] | |||
| private _fadeDisabled = false | |||
| /** | |||
| * Disable the frame fade plugin while animation is running | |||
| */ | |||
| disableFrameFade = true | |||
| // Same code used in CameraViewPlugin | |||
| private _postFrame = ()=>{ | |||
| if (!this._viewer) return | |||
| if (!this.enabled || Object.keys(this.animations).length < 1) { | |||
| this._lastFrameTime = 0 | |||
| // console.log('not anim') | |||
| if (this._fadeDisabled) { | |||
| this._viewer.getPlugin<FrameFadePlugin>('FrameFade')?.enable(PopmotionPlugin.PluginType) | |||
| this._fadeDisabled = false | |||
| } | |||
| return | |||
| } | |||
| const time = now() / 1000.0 | |||
| if (this._lastFrameTime < 1) this._lastFrameTime = time - 1.0 / 60.0 | |||
| let delta = time - this._lastFrameTime | |||
| this._lastFrameTime = time | |||
| // todo: scrolling | |||
| // delta = delta * (this.animateOnScroll ? this._scrollAnimationState : 1) | |||
| const d = this._viewer.getPlugin<ProgressivePlugin>('Progressive')?.postFrameConvergedRecordingDelta() | |||
| if (d && d > 0) delta = d | |||
| if (d === 0) return // not converged yet. | |||
| // if d < 0: not recording, do nothing | |||
| delta *= 1000 | |||
| // delta = 16.666 // testing | |||
| if (delta <= 0.001) return | |||
| this._updaters.forEach(u=>{ | |||
| let dt = delta | |||
| if (u.time + dt < 0) dt = -u.time | |||
| u.time += dt | |||
| if (Math.abs(dt) > 0.001) | |||
| u.u(dt) | |||
| }) | |||
| if (!this._fadeDisabled && this.disableFrameFade) { | |||
| const ff = this._viewer.getPlugin<FrameFadePlugin>('FrameFade') | |||
| if (ff) { | |||
| ff.disable(PopmotionPlugin.PluginType) | |||
| this._fadeDisabled = true | |||
| } | |||
| } | |||
| // todo: scrolling | |||
| // if (this._scrollAnimationState < 0.001) this._scrollAnimationState = 0 | |||
| // else this._scrollAnimationState *= 1.0 - this.scrollAnimationDamping | |||
| } | |||
| readonly defaultDriver: Driver = (update)=>{ | |||
| return { | |||
| start: ()=>this._updaters.push({u:update, time:0}), | |||
| stop: ()=> this._updaters.splice(this._updaters.findIndex(u=>u.u === update), 1), | |||
| } | |||
| } | |||
| onAdded(viewer: ThreeViewer): void { | |||
| super.onAdded(viewer) | |||
| viewer.addEventListener('postFrame', this._postFrame) | |||
| } | |||
| onRemove(viewer: ThreeViewer): void { | |||
| viewer.removeEventListener('postFrame', this._postFrame) | |||
| super.onRemove(viewer) | |||
| } | |||
| readonly animations: Record<string, AnimationResult> = {} | |||
| animate<V>(options: AnimationOptions<V>): AnimationResult { | |||
| const uuid = generateUUID() | |||
| const a: any = { | |||
| id: uuid, | |||
| options, | |||
| stop: ()=>{ | |||
| if (!this.animations[uuid]?._stop) console.warn('Animation not started') | |||
| else this.animations[uuid]?._stop?.() | |||
| }, | |||
| } | |||
| this.animations[uuid] = a | |||
| a.promise = new Promise<void>((resolve) => { | |||
| const opts: AnimationOptions<V> = { | |||
| driver: this.defaultDriver, | |||
| ...options, | |||
| onComplete: ()=>{ | |||
| options.onComplete?.() | |||
| resolve() | |||
| }, | |||
| onStop: ()=>{ | |||
| options.onStop?.() | |||
| resolve() | |||
| }, | |||
| } | |||
| const anim = animate(opts) | |||
| this.animations[uuid]._stop = anim.stop | |||
| this.animations[uuid].options = opts | |||
| }).then(()=>{ | |||
| delete this.animations[uuid] | |||
| return uuid | |||
| }) | |||
| return this.animations[uuid] | |||
| } | |||
| async animateAsync<V>(options: AnimationOptions<V>): Promise<string> { | |||
| return this.animate(options).promise | |||
| } | |||
| // todo : animateObject/animateTarget | |||
| } | |||
| @@ -31,3 +31,4 @@ export {TonemapPlugin} from './postprocessing/TonemapPlugin' | |||
| // animation | |||
| export {GLTFAnimationPlugin} from './animation/GLTFAnimationPlugin' | |||
| export {PopmotionPlugin} from './animation/PopmotionPlugin' | |||