瀏覽代碼

Add TransformAnimationPlugin and example

master
Palash Bansal 1 年之前
父節點
當前提交
3097bd3130
沒有連結到貢獻者的電子郵件帳戶。

+ 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

Loading…
取消
儲存