-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #1 from galacean/feat/animation
feat: 增加动态曲线插件
- Loading branch information
Showing
12 changed files
with
410 additions
and
18 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
<!DOCTYPE html> | ||
<html lang="en"> | ||
<head> | ||
<meta charset="UTF-8"> | ||
<meta name="viewport" content="width=device-width, initial-scale=1.0"> | ||
<title>运动曲线/Animation - Galacean Effects Components Demo</title> | ||
<style> | ||
html, body, div, canvas { margin: 0; padding: 0; } | ||
.container { | ||
width: 375px; | ||
height: 812px; | ||
background-color: rgba(0, 0, 0, 255); | ||
} | ||
@media (max-width: 719px) { | ||
html, body, .container { | ||
overflow: hidden; | ||
width: 100%; | ||
height: 100%; | ||
} | ||
} | ||
</style> | ||
</head> | ||
<body> | ||
<div class="container" id="J-container"></div> | ||
<script type="module" src="./index.ts"></script> | ||
</body> | ||
</html> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
import { Player } from '@galacean/effects'; | ||
import { AnimationComponent } from '@galacean/effects-components'; | ||
import { SIZE_CURVE } from './size-curve'; | ||
|
||
const mainJSON = 'https://mdn.alipayobjects.com/mars/afts/file/A*CWJTRbQ8oGUAAAAAAAAAAAAADlB4AQ'; | ||
const container = document.getElementById('J-container'); | ||
|
||
(async () => { | ||
try { | ||
const player = new Player({ | ||
container, | ||
interactive: true, | ||
}); | ||
const comp = await player.loadScene(mainJSON); | ||
|
||
comp.on('click', ret => { | ||
const index = ret.name.split('_')[1]; | ||
const controller = comp.getItemByName(`null_${index}`); | ||
const animationComponent = controller?.addComponent(AnimationComponent); | ||
|
||
animationComponent?.play(SIZE_CURVE); | ||
}); | ||
} catch (e) { | ||
console.info(e); | ||
} | ||
})(); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
import type { AnimationClipData } from '@galacean/effects-components'; | ||
|
||
export const SIZE_CURVE: AnimationClipData = { | ||
sizeOverLifetime: { | ||
separateAxes: true, | ||
x: [ | ||
21, | ||
[ | ||
[3, [0, 0.9364, 0.0685, 0.9364]], | ||
[1, [0.0874, 0.8278, 0.2056, 0.8278, 0.396, 0.8278]], | ||
[1, [0.2146, 1.0956, 0.5141, 1.0956, 0.554, 1.0956]], | ||
[1, [0.6156, 0.8278, 0.7529, 0.8278, 0.8978, 0.8278]], | ||
[2, [0.8446, 0.9364, 1.068, 0.9364]], | ||
], | ||
], | ||
y: [ | ||
21, | ||
[ | ||
[3, [0, 0.9364, 0.0685, 0.9364]], | ||
[1, [0.0874, 0.8278, 0.2056, 0.8278, 0.396, 0.8278]], | ||
[1, [0.2146, 1.0956, 0.5141, 1.0956, 0.554, 1.0956]], | ||
[1, [0.6156, 0.8278, 0.7529, 0.8278, 0.8978, 0.8278]], | ||
[2, [0.8446, 0.9364, 1.068, 0.9364]], | ||
], | ||
], | ||
z: [ | ||
21, | ||
[ | ||
[3, [0, 1, 0.0685, 1]], | ||
[1, [0.0874, 1, 0.2056, 1, 0.396, 1]], | ||
[1, [0.2146, 1, 0.5141, 1, 0.554, 1]], | ||
[1, [0.6156, 1, 0.7529, 1, 0.8978, 1]], | ||
[2, [0.8446, 1, 1.068, 1]], | ||
], | ||
], | ||
}, | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
## AnimationComponent | ||
动态添加运动曲线 | ||
|
||
### 简单使用 | ||
|
||
``` ts | ||
import { Player } from '@galacean/effects'; | ||
import { AnimationComponent } from '@galacean/effects-components/es/animation'; | ||
|
||
const container = document.getElementById('J-container'); | ||
const player = new Player({ | ||
container, | ||
notifyTouch: true, | ||
interactive: true, | ||
}); | ||
const composition = await player.loadScene('xx.json'); | ||
const item = composition.getItemByName('itemToAdd'); | ||
const ac = item.addComponent(AnimationComponent); | ||
|
||
// 需要播放的时刻 | ||
// curve从编辑器复制导出 | ||
ac.play(Curve) | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,269 @@ | ||
import type { | ||
ItemBasicTransform, ItemLinearVelOverLifetime, spec, ValueGetter, VFXItem, | ||
} from '@galacean/effects'; | ||
import { | ||
calculateTranslation, Component, createValueGetter, ensureVec3, math, | ||
} from '@galacean/effects'; | ||
|
||
const tempRot = new math.Euler(); | ||
const tempSize = new math.Vector3(1, 1, 1); | ||
const tempPos = new math.Vector3(); | ||
|
||
export class AnimationComponent extends Component { | ||
private playable = new TransformAnimationPlayable(); | ||
|
||
override onStart (): void { | ||
this.playable.boundObject = this.item; | ||
} | ||
|
||
override onUpdate (dt: number): void { | ||
this.playable.processFrame(); | ||
this.playable.time += dt / 1000; | ||
} | ||
|
||
play (clip: AnimationClipData) { | ||
this.playable.data = clip; | ||
this.playable.time = 0; | ||
this.playable.start(); | ||
} | ||
} | ||
|
||
export interface AnimationClipData { | ||
/** | ||
* 元素大小变化属性 | ||
*/ | ||
sizeOverLifetime?: spec.SizeOverLifetime, | ||
/** | ||
* 元素旋转变化属性 | ||
*/ | ||
rotationOverLifetime?: spec.RotationOverLifetime, | ||
/** | ||
* 元素位置变化属性 | ||
*/ | ||
positionOverLifetime?: spec.PositionOverLifetime, | ||
} | ||
|
||
class TransformAnimationPlayable { | ||
originalTransform?: ItemBasicTransform; | ||
protected sizeSeparateAxes = false; | ||
protected sizeXOverLifetime?: ValueGetter<number>; | ||
protected sizeYOverLifetime?: ValueGetter<number>; | ||
protected sizeZOverLifetime?: ValueGetter<number>; | ||
protected rotationOverLifetime?: { | ||
asRotation?: boolean, | ||
separateAxes?: boolean, | ||
enabled?: boolean, | ||
x?: ValueGetter<number>, | ||
y?: ValueGetter<number>, | ||
z?: ValueGetter<number>, | ||
}; | ||
gravityModifier?: ValueGetter<number>; | ||
orbitalVelOverLifetime?: { | ||
x?: ValueGetter<number>, | ||
y?: ValueGetter<number>, | ||
z?: ValueGetter<number>, | ||
center: [x: number, y: number, z: number], | ||
asRotation?: boolean, | ||
enabled?: boolean, | ||
}; | ||
speedOverLifetime?: ValueGetter<number>; | ||
linearVelOverLifetime?: ItemLinearVelOverLifetime; | ||
positionOverLifetime?: spec.PositionOverLifetime; | ||
gravity?: math.Vector3; | ||
direction?: math.Vector3; | ||
startSpeed?: number; | ||
data?: AnimationClipData; | ||
boundObject?: VFXItem; | ||
time = 0; | ||
|
||
private velocity?: math.Vector3; | ||
|
||
start (): void { | ||
const boundItem = this.boundObject; | ||
|
||
if (!boundItem) { | ||
return; | ||
} | ||
const { scale } = boundItem.transform; | ||
|
||
this.originalTransform = { | ||
position: boundItem.transform.position.clone(), | ||
rotation: boundItem.transform.getRotation().clone(), | ||
scale: new math.Vector3(scale.x, scale.y, scale.x), | ||
}; | ||
const { positionOverLifetime, rotationOverLifetime, sizeOverLifetime } = this.data ?? {}; | ||
|
||
if ( | ||
positionOverLifetime && | ||
Object.keys(positionOverLifetime).length !== 0 | ||
) { | ||
this.positionOverLifetime = positionOverLifetime; | ||
if (positionOverLifetime.path) { | ||
this.originalTransform.path = createValueGetter( | ||
positionOverLifetime.path, | ||
); | ||
} | ||
const linearVelEnable = | ||
positionOverLifetime.linearX || | ||
positionOverLifetime.linearY || | ||
positionOverLifetime.linearZ; | ||
|
||
if (linearVelEnable) { | ||
this.linearVelOverLifetime = { | ||
x: | ||
positionOverLifetime.linearX && | ||
createValueGetter(positionOverLifetime.linearX), | ||
y: | ||
positionOverLifetime.linearY && | ||
createValueGetter(positionOverLifetime.linearY), | ||
z: | ||
positionOverLifetime.linearZ && | ||
createValueGetter(positionOverLifetime.linearZ), | ||
asMovement: positionOverLifetime.asMovement, | ||
enabled: !!linearVelEnable, | ||
}; | ||
} | ||
|
||
const orbitalVelEnable = | ||
positionOverLifetime.orbitalX || | ||
positionOverLifetime.orbitalY || | ||
positionOverLifetime.orbitalZ; | ||
|
||
if (orbitalVelEnable) { | ||
this.orbitalVelOverLifetime = { | ||
x: | ||
positionOverLifetime.orbitalX && | ||
createValueGetter(positionOverLifetime.orbitalX), | ||
y: | ||
positionOverLifetime.orbitalY && | ||
createValueGetter(positionOverLifetime.orbitalY), | ||
z: | ||
positionOverLifetime.orbitalZ && | ||
createValueGetter(positionOverLifetime.orbitalZ), | ||
center: ensureVec3(positionOverLifetime.orbCenter), | ||
asRotation: positionOverLifetime.asRotation, | ||
enabled: !!orbitalVelEnable, | ||
}; | ||
} | ||
this.speedOverLifetime = | ||
positionOverLifetime.speedOverLifetime && | ||
createValueGetter(positionOverLifetime.speedOverLifetime); | ||
} | ||
|
||
if (sizeOverLifetime) { | ||
if (sizeOverLifetime.separateAxes) { | ||
this.sizeSeparateAxes = true; | ||
this.sizeXOverLifetime = createValueGetter(sizeOverLifetime.x || 1); | ||
this.sizeYOverLifetime = createValueGetter(sizeOverLifetime.y || 1); | ||
this.sizeZOverLifetime = createValueGetter(sizeOverLifetime.z || 1); | ||
} else { | ||
this.sizeXOverLifetime = createValueGetter(sizeOverLifetime.size || 1); | ||
} | ||
} | ||
|
||
if (rotationOverLifetime) { | ||
this.rotationOverLifetime = { | ||
asRotation: rotationOverLifetime.asRotation, | ||
separateAxes: rotationOverLifetime.separateAxes, | ||
z: createValueGetter(rotationOverLifetime.z || 0), | ||
}; | ||
if (rotationOverLifetime.separateAxes) { | ||
const rotLt = this.rotationOverLifetime; | ||
|
||
rotLt.x = createValueGetter(rotationOverLifetime.x || 0); | ||
rotLt.y = createValueGetter(rotationOverLifetime.y || 0); | ||
} | ||
} | ||
this.gravity = math.Vector3.fromArray(positionOverLifetime?.gravity || []); | ||
this.gravityModifier = createValueGetter( | ||
positionOverLifetime?.gravityOverLifetime ?? 0, | ||
); | ||
this.direction = positionOverLifetime?.direction | ||
? math.Vector3.fromArray(positionOverLifetime.direction).normalize() | ||
: new math.Vector3(); | ||
this.startSpeed = positionOverLifetime?.startSpeed || 0; | ||
|
||
this.velocity = this.direction.clone(); | ||
this.velocity.multiply(this.startSpeed); | ||
} | ||
|
||
processFrame (): void { | ||
this.sampleAnimation(); | ||
} | ||
|
||
/** | ||
* 应用时间轴K帧数据到对象 | ||
*/ | ||
private sampleAnimation () { | ||
const boundItem = this.boundObject; | ||
|
||
if (!boundItem) { | ||
return; | ||
} | ||
|
||
const { duration } = boundItem; | ||
const life = math.clamp(this.time / duration, 0, 1); | ||
|
||
if (this.sizeXOverLifetime) { | ||
tempSize.x = this.sizeXOverLifetime.getValue(life); | ||
if (this.sizeSeparateAxes && this.sizeYOverLifetime && this.sizeZOverLifetime) { | ||
tempSize.y = this.sizeYOverLifetime.getValue(life); | ||
tempSize.z = this.sizeZOverLifetime.getValue(life); | ||
} else { | ||
tempSize.z = tempSize.x; | ||
tempSize.y = tempSize.x; | ||
} | ||
const { x = 1, y = 1, z = 1 } = this.originalTransform?.scale ?? {}; | ||
|
||
boundItem.transform.setScale( | ||
tempSize.x * x, | ||
tempSize.y * y, | ||
tempSize.z * z, | ||
); | ||
} | ||
|
||
if (this.rotationOverLifetime) { | ||
const func = (v?: ValueGetter<number>) => { | ||
if (!v) { return 0; } | ||
|
||
return this.rotationOverLifetime?.asRotation | ||
? v.getValue(life) | ||
: v.getIntegrateValue(0, life, duration); | ||
}; | ||
const incZ = func(this.rotationOverLifetime.z); | ||
const { separateAxes } = this.rotationOverLifetime; | ||
|
||
tempRot.x = separateAxes ? func(this.rotationOverLifetime.x) : 0; | ||
tempRot.y = separateAxes ? func(this.rotationOverLifetime.y) : 0; | ||
tempRot.z = incZ; | ||
|
||
if (this.originalTransform) { | ||
const rot = tempRot.addEulers(this.originalTransform.rotation, tempRot); | ||
|
||
boundItem.transform.setRotation(rot.x, rot.y, rot.z); | ||
} | ||
} | ||
|
||
if (this.positionOverLifetime) { | ||
const pos = tempPos; | ||
|
||
if (this.gravity && this.originalTransform && this.velocity) { | ||
calculateTranslation( | ||
pos, | ||
this, | ||
this.gravity, | ||
this.time, | ||
duration, | ||
this.originalTransform.position, | ||
this.velocity, | ||
); | ||
} | ||
|
||
if (this.originalTransform?.path) { | ||
pos.add(this.originalTransform.path.getValue(life)); | ||
} | ||
|
||
boundItem.transform.setPosition(pos.x, pos.y, pos.z); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1,2 @@ | ||
export * from './swiper'; | ||
export * from './animation'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.