Переглянути джерело

Add TransformAnimationPlugin and example

master
Palash Bansal 1 рік тому
джерело
коміт
3097bd3130
Аккаунт користувача з таким Email не знайдено

+ 60
- 1
README.md Переглянути файл

@@ -108,6 +108,7 @@ To make changes and run the example, click on the CodePen button on the top righ
- [GLTFAnimationPlugin](#gltfanimationplugin) - Add support for playing and seeking gltf animations
- [PopmotionPlugin](#popmotionplugin) - Integrates with popmotion.io library for animation/tweening
- [CameraViewPlugin](#cameraviewplugin) - Add support for saving, loading, animating, looping between camera views
- [TransformAnimationPlugin](#transformanimationplugin) - Add support for saving, loading, animating, between object transforms
- [RenderTargetPreviewPlugin](#rendertargetpreviewplugin) - Preview any render target in a UI panel over the canvas
- [GeometryUVPreviewPlugin](#geometryuvpreviewplugin) - Preview UVs of any geometry in a UI panel over the canvas
- [FrameFadePlugin](#framefadeplugin) - Post-render pass to smoothly fade to a new rendered frame over time
@@ -1550,7 +1551,7 @@ renderManager.maxTempPerKey = 10 // default = 5
renderManager.blit(destination, {source: sourceTexture})

// Clear color of the canvas
renderManager.clearColor({r: 0, g: 0, b: 0, a: 1, depth: true, viewport: new Vector4(...)})
renderManager.clearColor({r: 0, g: 0, b: 0, a: 1, depth: true, viewport: new Vector4()})

// Clear of a render target
renderManager.clearColor(renderTarget, {r: 0, g: 0, b: 0, a: 1, target: renderTarget})
@@ -2779,6 +2780,64 @@ cameraViewPlugin.viewLooping = false

```

## TransformAnimationPlugin

[//]: # (todo: image)

[Example](https://threepipe.org/examples/#transform-animation-plugin/) —
[Source Code](./src/plugins/animation/TransformAnimationPlugin.ts) —
[API Reference](https://threepipe.org/docs/classes/TransformAnimationPlugin.html)

TransformAnimationPlugin adds support to save and load transform(position, rotation, scale) states for objects in the scene, which can then be animated to.
It uses PopmotionPlugin internally to animate any object to a saved transform object.

The transformations are saved in the object userData, and can be created and interacted with from the plugin.

It also provides a UI to manage the states, this UI is added to the object's uiConfig and can be accessed using the object UI or PickingPlugin. Check the example for a working demo.

Sample Usage -
```javascript
import {TransformAnimationPlugin, ThreeViewer, Vector3, Quaternion, EasingFunctions, timeout} from 'threepipe'

const viewer = new ThreeViewer({...})

const model = viewer.scene.getObjectByName('model')

const transformAnim = viewer.addPluginSync(new TransformAnimationPlugin())

// Save the current state of the model as a transform
transformAnim.addTransform(model, 'initial')

// Rotate/Move the model and save other transform states
// left
model.rotation.set(0, Math.PI / 2, 0)
model.setDirty?.()
transformAnim.addTransform(model, 'left')

// top
model.rotation.set(Math.PI / 2, 0, 0)
model.setDirty?.()
transformAnim.addTransform(model, 'top')

// up
model.position.set(0, 2, 0)
model.lookAt(viewer.scene.mainCamera.position)
model.setDirty?.()
transformAnim.addTransform(model, 'up')

// animate to a transform(from current position) in 1 sec
const anim = transformAnim.animateTransform(model, 'left', 1000)
// to stop the animation
// anim.stop()
// wait for the animation to finish
await anim.promise

// set a transform without animation
transformAnim.setTransform(model, 'top')

// await directly.
await transformAnim.animateToTransform(model, 'up', 1000)?.promise
```

## RenderTargetPreviewPlugin


+ 2
- 4
examples/camera-view-plugin/script.ts Переглянути файл

@@ -33,7 +33,7 @@ async function init() {
)

const rightView = new CameraView(
'leftView',
'rightView',
new Vector3(6, 0, 0),
initialView.target,
)
@@ -70,9 +70,7 @@ async function init() {
const ui = viewer.addPluginSync(TweakpaneUiPlugin, true)
ui.appendChild(viewer.scene.mainCamera.uiConfig)

const uiC = ui.setupPluginUi(CameraViewPlugin)!
uiC.expanded = true
uiC.uiRefresh?.()
ui.setupPluginUi(CameraViewPlugin, {expanded: true})

}


+ 1
- 0
examples/index.html Переглянути файл

@@ -338,6 +338,7 @@
<ul>
<li><a href="./picking-plugin/">Picking (Selection) Plugin </a></li>
<li><a href="./camera-view-plugin/">Camera View (Animation) Plugin </a></li>
<li><a href="./transform-animation-plugin/">Transform Animation Plugin </a></li>
<li><a href="./dropzone-plugin/">Dropzone (Drag & Drop) Plugin </a></li>
<li><a href="./transform-controls-plugin/">Transform Controls Plugin </a></li>
<li><a href="./editor-view-widget-plugin/">Editor View Widget Plugin </a></li>

+ 36
- 0
examples/transform-animation-plugin/index.html Переглянути файл

@@ -0,0 +1,36 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Transform Animation Plugin</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<!-- 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",
"@threepipe/plugin-tweakpane": "./../../plugins/tweakpane/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>

+ 60
- 0
examples/transform-animation-plugin/script.ts Переглянути файл

@@ -0,0 +1,60 @@
import {_testFinish, IObject3D, PopmotionPlugin, ThreeViewer, TransformAnimationPlugin} from 'threepipe'
import {TweakpaneUiPlugin} from '@threepipe/plugin-tweakpane'
import {createSimpleButtons} from '../examples-utils/simple-bottom-buttons.js'

async function init() {

const viewer = new ThreeViewer({
canvas: document.getElementById('mcanvas') as HTMLCanvasElement,
plugins: [PopmotionPlugin],
})
const transformAnimPlugin = viewer.addPluginSync(TransformAnimationPlugin)
console.log(transformAnimPlugin)

await viewer.setEnvironmentMap('https://threejs.org/examples/textures/equirectangular/venice_sunset_1k.hdr')

const model = await viewer.load<IObject3D>('https://threejs.org/examples/models/gltf/DamagedHelmet/glTF/DamagedHelmet.gltf', {
autoCenter: true,
autoScale: true,
})
if (!model) return

// Save the initial transform
transformAnimPlugin.addTransform(model, 'front')

// Rotate/Move the model and save other transform states

// left
model.rotation.set(0, Math.PI / 2, 0)
model.setDirty?.()
transformAnimPlugin.addTransform(model, 'left')

// top
model.rotation.set(Math.PI / 2, 0, 0)
model.setDirty?.()
transformAnimPlugin.addTransform(model, 'top')

// up
model.position.set(0, 2, 0)
model.lookAt(viewer.scene.mainCamera.position)
model.setDirty?.()
transformAnimPlugin.addTransform(model, 'up')

// reset
transformAnimPlugin.setTransform(model, 'front')

createSimpleButtons({
['Reset']: async() => transformAnimPlugin.animateTransform(model, 'front', 1000),
['Left']: async() => transformAnimPlugin.animateTransform(model, 'left', 1000),
['Top']: async() => transformAnimPlugin.animateTransform(model, 'top', 1000),
['Up']: async() => transformAnimPlugin.animateTransform(model, 'up', 1000),
})

const ui = viewer.addPluginSync(TweakpaneUiPlugin, true)
ui.appendChild(model.uiConfig)

ui.setupPluginUi(TransformAnimationPlugin, {expanded: true})

}

init().finally(_testFinish)

+ 201
- 0
src/plugins/animation/TransformAnimationPlugin.ts Переглянути файл

@@ -0,0 +1,201 @@
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 Plugin
*/
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[]
}
}
}

+ 1
- 0
src/plugins/index.ts Переглянути файл

@@ -60,6 +60,7 @@ export {FilmicGrainPlugin} from './postprocessing/FilmicGrainPlugin'
// animation
export {GLTFAnimationPlugin} from './animation/GLTFAnimationPlugin'
export {PopmotionPlugin, type AnimationResult} from './animation/PopmotionPlugin'
export {TransformAnimationPlugin, type TSavedTransform} from './animation/TransformAnimationPlugin'
export {CameraViewPlugin, type CameraViewPluginOptions} from './animation/CameraViewPlugin'

// material

Завантаження…
Відмінити
Зберегти