Skip to content

Commit

Permalink
Merge pull request #1 from galacean/feat/animation
Browse files Browse the repository at this point in the history
feat: 增加动态曲线插件
  • Loading branch information
yiiqii authored Dec 4, 2024
2 parents 9004c61 + 97ca6bd commit 35f973b
Show file tree
Hide file tree
Showing 12 changed files with 410 additions and 18 deletions.
27 changes: 27 additions & 0 deletions components/animation/demo/index.html
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>
26 changes: 26 additions & 0 deletions components/animation/demo/index.ts
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);
}
})();
37 changes: 37 additions & 0 deletions components/animation/demo/size-curve.ts
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]],
],
],
},
};
23 changes: 23 additions & 0 deletions components/animation/index.md
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)
```
269 changes: 269 additions & 0 deletions components/animation/index.ts
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);
}
}
}
1 change: 1 addition & 0 deletions components/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export * from './swiper';
export * from './animation';
1 change: 0 additions & 1 deletion components/swiper/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ const player = new Player({
container,
notifyTouch: true,
interactive: true,
pixelRatio: window.devicePixelRatio,
});
const composition = await player.loadScene('xx.json', {
// 最长等待加载时间,超过则使用降级
Expand Down
Loading

0 comments on commit 35f973b

Please sign in to comment.