From 59a6bddc42cd2bea30abf9489d7dddef99bd10af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=8C=82=E5=AE=89?= <151805118+wumaolinmaoan@users.noreply.github.com> Date: Tue, 27 Aug 2024 11:36:51 +0800 Subject: [PATCH 01/88] perf: opt lifetime function performance (#596) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * perf: opt lifetime function performance * Update packages/effects-core/src/vfx-item.ts Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> * perf: opt code * perf: opt code * perf: opt code * perf: opt code * perf: opt code --------- Co-authored-by: 意绮 Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- packages/effects-core/src/comp-vfx-item.ts | 9 +- .../effects-core/src/components/component.ts | 154 ++++++++++++------ .../src/components/effect-component.ts | 2 +- .../src/components/post-process-volume.ts | 2 +- .../src/components/renderer-component.ts | 33 +--- packages/effects-core/src/composition.ts | 101 ++++-------- .../src/composition/scene-ticking.ts | 102 ++++++++++++ .../src/plugins/cal/playable-graph.ts | 2 +- .../src/plugins/cal/timeline-asset.ts | 20 +-- .../plugins/camera/camera-controller-node.ts | 2 +- .../src/plugins/interact/interact-item.ts | 7 +- .../particle/particle-system-renderer.ts | 6 +- .../src/plugins/particle/particle-system.ts | 4 +- .../src/plugins/particle/particle-vfx-item.ts | 4 +- .../src/plugins/sprite/sprite-item.ts | 4 +- .../src/plugins/text/text-item.ts | 4 +- packages/effects-core/src/vfx-item.ts | 106 ++++++++++-- packages/effects-threejs/src/three-mesh.ts | 4 +- .../src/three-sprite-component.ts | 4 +- .../src/three-text-component.ts | 4 +- .../editor-gizmo/src/gizmo-component.ts | 4 +- .../model/src/plugin/model-item.ts | 20 +-- .../model/src/plugin/model-plugin.ts | 2 +- .../model/src/plugin/model-tree-item.ts | 4 +- .../src/orientation-component.ts | 4 +- plugin-packages/spine/src/spine-component.ts | 8 +- plugin-packages/stats/src/stats-component.ts | 4 +- .../plugins/sprite/sprite-renderder.spec.ts | 2 +- 28 files changed, 387 insertions(+), 235 deletions(-) create mode 100644 packages/effects-core/src/composition/scene-ticking.ts diff --git a/packages/effects-core/src/comp-vfx-item.ts b/packages/effects-core/src/comp-vfx-item.ts index 038d7b41..f4a1ad3b 100644 --- a/packages/effects-core/src/comp-vfx-item.ts +++ b/packages/effects-core/src/comp-vfx-item.ts @@ -40,7 +40,7 @@ export class CompositionComponent extends Behaviour { private timelinePlayable: Playable; private graph: PlayableGraph = new PlayableGraph(); - override start (): void { + override onStart (): void { const { startTime = 0 } = this.item.props; this.startTime = startTime; @@ -74,7 +74,7 @@ export class CompositionComponent extends Behaviour { return this.reusable; } - override update (dt: number): void { + override onUpdate (dt: number): void { const time = this.time; this.timelinePlayable.setTime(time); @@ -117,8 +117,11 @@ export class CompositionComponent extends Behaviour { props.content = itemData.content; item = assetLoader.loadGUID(itemData.id); item.composition = this.item.composition; - const compositionComponent = item.addComponent(CompositionComponent); + const compositionComponent = new CompositionComponent(this.engine); + + compositionComponent.item = item; + item.components.push(compositionComponent); compositionComponent.data = props as unknown as ContentOptions; compositionComponent.refId = refId; item.transform.parentTransform = this.transform; diff --git a/packages/effects-core/src/components/component.ts b/packages/effects-core/src/components/component.ts index e4222a6e..b8026685 100644 --- a/packages/effects-core/src/components/component.ts +++ b/packages/effects-core/src/components/component.ts @@ -12,6 +12,13 @@ export abstract class Component extends EffectsObject { * 附加到的 VFXItem 对象 */ item: VFXItem; + isAwakeCalled = false; + isStartCalled = false; + isEnableCalled = false; + + @serialize() + private _enabled = true; + /** * 附加到的 VFXItem 对象 Transform 组件 */ @@ -19,34 +26,6 @@ export abstract class Component extends EffectsObject { return this.item.transform; } - onAttached () { } - onDestroy () { } - - override fromData (data: any): void { - super.fromData(data); - if (data.item) { - this.item = data.item; - } - } - - override dispose (): void { - this.onDestroy(); - if (this.item) { - removeItem(this.item.components, this); - } - } -} - -/** - * @since 2.0.0 - */ -export abstract class Behaviour extends Component { - isAwakeCalled = false; - isStartCalled = false; - - @serialize() - private _enabled = true; - /** * 组件是否可以更新,true 更新,false 不更新 */ @@ -57,15 +36,22 @@ export abstract class Behaviour extends Component { get enabled () { return this._enabled; } + set enabled (value: boolean) { - this._enabled = value; - if (value) { - if (this.isActiveAndEnabled) { - this.onEnable(); - } - if (!this.isStartCalled) { - this.start(); - this.isStartCalled = true; + if (this.enabled !== value) { + this._enabled = value; + if (value) { + if (this.isActiveAndEnabled) { + this.enable(); + if (!this.isStartCalled) { + this.onStart(); + this.isStartCalled = true; + } + } + } else { + if (this.isEnableCalled) { + this.disable(); + } } } } @@ -73,47 +59,121 @@ export abstract class Behaviour extends Component { /** * 生命周期函数,初始化后调用,生命周期内只调用一次 */ - awake () { + onAwake () { // OVERRIDE } /** - * 在每次设置 enabled 为 true 时触发 + * 在 enabled 变为 true 时触发 */ onEnable () { // OVERRIDE } + + /** + * 在 enabled 变为 false 时触发 + */ + onDisable () { + // OVERRIDE + } + /** * 生命周期函数,在第一次 update 前调用,生命周期内只调用一次 */ - start () { + onStart () { // OVERRIDE } + /** * 生命周期函数,每帧调用一次 */ - update (dt: number) { + onUpdate (dt: number) { // OVERRIDE } + /** * 生命周期函数,每帧调用一次,在 update 之后调用 */ - lateUpdate (dt: number) { + onLateUpdate (dt: number) { + // OVERRIDE + } + + /** + * 生命周期函数,在组件销毁时调用 + */ + onDestroy () { // OVERRIDE } - override onAttached (): void { - this.item.itemBehaviours.push(this); - if (!this.isAwakeCalled) { - this.awake(); - this.isAwakeCalled = true; + /** + * @internal + */ + enable () { + if (this.item.composition) { + this.item.composition.sceneTicking.addComponent(this); + this.isEnableCalled = true; + } + this.onEnable(); + } + + /** + * @internal + */ + disable () { + this.onDisable(); + if (this.item.composition) { + this.isEnableCalled = false; + this.item.composition.sceneTicking.removeComponent(this); + } + } + + setVFXItem (item: VFXItem) { + this.item = item; + if (item.isDuringPlay) { + if (!this.isAwakeCalled) { + this.onAwake(); + this.isAwakeCalled = true; + } + if (item.getVisible() && this.enabled) { + this.start(); + this.enable(); + } + } + } + + override fromData (data: any): void { + super.fromData(data); + if (data.item) { + this.item = data.item; } } override dispose (): void { + this.onDestroy(); if (this.item) { - removeItem(this.item.itemBehaviours, this); + removeItem(this.item.components, this); } + } + + private start () { + if (this.isStartCalled) { + return; + } + this.isStartCalled = true; + this.onStart(); + } +} + +/** + * @since 2.0.0 + */ +export abstract class Behaviour extends Component { + + override setVFXItem (item: VFXItem): void { + super.setVFXItem(item); + } + + override dispose (): void { super.dispose(); } } diff --git a/packages/effects-core/src/components/effect-component.ts b/packages/effects-core/src/components/effect-component.ts index 9bbffead..73bd02ab 100644 --- a/packages/effects-core/src/components/effect-component.ts +++ b/packages/effects-core/src/components/effect-component.ts @@ -38,7 +38,7 @@ export class EffectComponent extends RendererComponent { this._priority = 0; } - override start (): void { + override onStart (): void { this.item.getHitTestParams = this.getHitTestParams; } diff --git a/packages/effects-core/src/components/post-process-volume.ts b/packages/effects-core/src/components/post-process-volume.ts index a6f06c8b..85355423 100644 --- a/packages/effects-core/src/components/post-process-volume.ts +++ b/packages/effects-core/src/components/post-process-volume.ts @@ -41,7 +41,7 @@ export class PostProcessVolume extends Behaviour { @serialize() useToneMapping: boolean = true; // 1: true, 0: false - override start (): void { + override onStart (): void { const composition = this.item.composition; if (composition) { diff --git a/packages/effects-core/src/components/renderer-component.ts b/packages/effects-core/src/components/renderer-component.ts index 9b996b2f..47b48b56 100644 --- a/packages/effects-core/src/components/renderer-component.ts +++ b/packages/effects-core/src/components/renderer-component.ts @@ -2,6 +2,7 @@ import { serialize } from '../decorators'; import type { Material } from '../material'; import type { Renderer } from '../render'; import { removeItem } from '../utils'; +import type { VFXItem } from '../vfx-item'; import { Component } from './component'; /** @@ -9,7 +10,6 @@ import { Component } from './component'; * @since 2.0.0 */ export class RendererComponent extends Component { - isStartCalled = false; @serialize() materials: Material[] = []; @@ -17,9 +17,6 @@ export class RendererComponent extends Component { @serialize() protected _priority = 0; - @serialize() - protected _enabled = true; - get priority (): number { return this._priority; } @@ -27,23 +24,6 @@ export class RendererComponent extends Component { this._priority = value; } - get enabled () { - return this._enabled; - } - set enabled (value: boolean) { - this._enabled = value; - if (value) { - this.onEnable(); - } - } - - /** - * 组件是否可以更新,true 更新,false 不更新 - */ - get isActiveAndEnabled () { - return this.item.getVisible() && this.enabled; - } - get material (): Material { return this.materials[0]; } @@ -55,17 +35,10 @@ export class RendererComponent extends Component { } } - onEnable () { } - - start () { } - - update (dt: number) { } - - lateUpdate (dt: number) { } - render (renderer: Renderer): void { } - override onAttached (): void { + override setVFXItem (item: VFXItem): void { + super.setVFXItem(item); this.item.rendererComponents.push(this); } diff --git a/packages/effects-core/src/composition.ts b/packages/effects-core/src/composition.ts index 6325af27..bb545991 100644 --- a/packages/effects-core/src/composition.ts +++ b/packages/effects-core/src/composition.ts @@ -21,6 +21,7 @@ import { VFXItem } from './vfx-item'; import type { CompositionEvent } from './events'; import { EventEmitter } from './events'; import type { PostProcessVolume } from './components/post-process-volume'; +import { SceneTicking } from './composition/scene-ticking'; export interface CompositionStatistic { loadTime: number, @@ -70,6 +71,7 @@ export interface CompositionProps { */ export class Composition extends EventEmitter> implements Disposable, LostHandler { renderer: Renderer; + sceneTicking = new SceneTicking(); /** * 当前帧的渲染数据对象 */ @@ -234,9 +236,13 @@ export class Composition extends EventEmitter> imp this.rootItem = new VFXItem(this.getEngine(), sourceContent as unknown as VFXItemProps); this.rootItem.name = 'rootItem'; this.rootItem.composition = this; - this.rootComposition = this.rootItem.addComponent(CompositionComponent); + + // Spawn rootCompositionComponent + this.rootComposition = new CompositionComponent(this.getEngine()); this.rootComposition.startTime = sourceContent.startTime; this.rootComposition.data = sourceContent; + this.rootComposition.item = this.rootItem; + this.rootItem.components.push(this.rootComposition); const imageUsage = (!reusable && imgUsage) as unknown as Record; @@ -267,13 +273,22 @@ export class Composition extends EventEmitter> imp this.rendererOptions = null; this.rootComposition.createContent(); this.buildItemTree(this.rootItem); - this.callAwake(this.rootItem); this.rootItem.onEnd = () => { window.setTimeout(() => { this.emit('end', { composition: this }); }, 0); }; this.pluginSystem.resetComposition(this, this.renderFrame); + // this.initializeSceneTicking(this.rootItem); + } + + initializeSceneTicking (item: VFXItem) { + for (const component of item.components) { + this.sceneTicking.addComponent(component); + } + for (const child of item.children) { + this.initializeSceneTicking(child); + } } /** @@ -456,7 +471,7 @@ export class Composition extends EventEmitter> imp this.resume(); } if (!this.rootComposition.isStartCalled) { - this.rootComposition.start(); + this.rootComposition.onStart(); this.rootComposition.isStartCalled = true; } this.forwardTime(time + this.startTime); @@ -546,9 +561,12 @@ export class Composition extends EventEmitter> imp this.updatePluginLoaders(deltaTime); // scene VFXItem components lifetime function. - this.callStart(this.rootItem); - this.callUpdate(this.rootItem, time); - this.callLateUpdate(this.rootItem, time); + if (!this.rootItem.isDuringPlay) { + this.callAwake(this.rootItem); + this.rootItem.beginPlay(); + } + this.sceneTicking.update.tick(time); + this.sceneTicking.lateUpdate.tick(time); this.updateCamera(); this.prepareRender(); @@ -610,10 +628,10 @@ export class Composition extends EventEmitter> imp } private callAwake (item: VFXItem) { - for (const itemBehaviour of item.itemBehaviours) { - if (!itemBehaviour.isAwakeCalled) { - itemBehaviour.awake(); - itemBehaviour.isAwakeCalled = true; + for (const component of item.components) { + if (!component.isAwakeCalled) { + component.onAwake(); + component.isAwakeCalled = true; } } for (const child of item.children) { @@ -621,69 +639,6 @@ export class Composition extends EventEmitter> imp } } - private callStart (item: VFXItem) { - for (const itemBehaviour of item.itemBehaviours) { - if (itemBehaviour.isActiveAndEnabled && !itemBehaviour.isStartCalled) { - itemBehaviour.start(); - itemBehaviour.isStartCalled = true; - } - } - for (const rendererComponent of item.rendererComponents) { - if (rendererComponent.isActiveAndEnabled && !rendererComponent.isStartCalled) { - rendererComponent.start(); - rendererComponent.isStartCalled = true; - } - } - for (const child of item.children) { - this.callStart(child); - } - } - - private callUpdate (item: VFXItem, dt: number) { - for (const itemBehaviour of item.itemBehaviours) { - if (itemBehaviour.isActiveAndEnabled && itemBehaviour.isStartCalled) { - itemBehaviour.update(dt); - } - } - for (const rendererComponent of item.rendererComponents) { - if (rendererComponent.isActiveAndEnabled && rendererComponent.isStartCalled) { - rendererComponent.update(dt); - } - } - for (const child of item.children) { - if (VFXItem.isComposition(child)) { - if ( - child.ended && - child.endBehavior === spec.EndBehavior.restart - ) { - child.ended = false; - // TODO K帧动画在元素重建后需要 tick ,否则会导致元素位置和 k 帧第一帧位置不一致 - this.callUpdate(child, 0); - } else { - this.callUpdate(child, dt); - } - } else { - this.callUpdate(child, dt); - } - } - } - - private callLateUpdate (item: VFXItem, dt: number) { - for (const itemBehaviour of item.itemBehaviours) { - if (itemBehaviour.isActiveAndEnabled && itemBehaviour.isStartCalled) { - itemBehaviour.lateUpdate(dt); - } - } - for (const rendererComponent of item.rendererComponents) { - if (rendererComponent.isActiveAndEnabled && rendererComponent.isStartCalled) { - rendererComponent.lateUpdate(dt); - } - } - for (const child of item.children) { - this.callLateUpdate(child, dt); - } - } - /** * 构建父子树,同时保存到 itemCacheMap 中便于查找 */ diff --git a/packages/effects-core/src/composition/scene-ticking.ts b/packages/effects-core/src/composition/scene-ticking.ts new file mode 100644 index 00000000..789c13b2 --- /dev/null +++ b/packages/effects-core/src/composition/scene-ticking.ts @@ -0,0 +1,102 @@ +import { Component } from '../components/component'; + +export class SceneTicking { + update: UpdateTickData = new UpdateTickData(); + lateUpdate: LateUpdateTickData = new LateUpdateTickData(); + + addComponent (obj: Component): void { + if (obj.onUpdate !== Component.prototype.onUpdate) { + this.update.addComponent(obj); + } + if (obj.onLateUpdate !== Component.prototype.onLateUpdate) { + this.lateUpdate.addComponent(obj); + } + } + + removeComponent (obj: Component): void { + if (obj.onUpdate !== Component.prototype.onUpdate) { + this.update.removeComponent(obj); + } + if (obj.onLateUpdate !== Component.prototype.onLateUpdate) { + this.lateUpdate.removeComponent(obj); + } + } + + clear (): void { + this.update.clear(); + this.lateUpdate.clear(); + } +} + +class TickData { + components: Component[] = []; + ticks: ((dt: number) => void)[] = []; + + constructor () { + } + + tick (dt: number) { + this.tickComponents(this.components, dt); + + for (let i = 0;i < this.ticks.length;i++) { + this.ticks[i](dt); + } + } + + tickComponents (components: Component[], dt: number): void { + // To be implemented in derived classes + } + + addComponent (component: Component): void { + if (!this.components.includes(component)) { + this.components.push(component); + } + } + + removeComponent (component: Component): void { + const index = this.components.indexOf(component); + + if (index > -1) { + this.components.splice(index, 1); + } + } + + addTick (method: (dt: number) => void, callee: object) { + const tick = method.bind(callee); + + if (!this.ticks.includes(tick)) { + this.ticks.push(tick); + } + } + + clear (): void { + this.components = []; + } +} + +class UpdateTickData extends TickData { + override tickComponents (components: Component[], dt: number): void { + for (const component of components) { + component.onUpdate(dt); + } + } +} + +class LateUpdateTickData extends TickData { + override tickComponents (components: Component[], dt: number): void { + for (const component of components) { + component.onLateUpdate(dt); + } + } +} + +// function compareComponents (a: Component, b: Component): number { +// const itemA = a.item; +// const itemB = b.item; + +// if (VFXItem.isAncestor(itemA, itemB)) { +// return -1; +// } else { +// return 1; +// } +// } \ No newline at end of file diff --git a/packages/effects-core/src/plugins/cal/playable-graph.ts b/packages/effects-core/src/plugins/cal/playable-graph.ts index 40bc6b18..7a5bc0e3 100644 --- a/packages/effects-core/src/plugins/cal/playable-graph.ts +++ b/packages/effects-core/src/plugins/cal/playable-graph.ts @@ -85,7 +85,7 @@ export class Playable implements Disposable { /** * 当前本地播放的时间 */ - protected time: number; + protected time: number = 0; constructor (graph: PlayableGraph, inputCount = 0) { graph.addPlayable(this); diff --git a/packages/effects-core/src/plugins/cal/timeline-asset.ts b/packages/effects-core/src/plugins/cal/timeline-asset.ts index 6f087060..70e84236 100644 --- a/packages/effects-core/src/plugins/cal/timeline-asset.ts +++ b/packages/effects-core/src/plugins/cal/timeline-asset.ts @@ -115,22 +115,6 @@ export class TrackSortWrapper { } } -function isAncestor ( - ancestorCandidate: VFXItem, - descendantCandidate: VFXItem, -) { - let current = descendantCandidate.parent; - - while (current) { - if (current === ancestorCandidate) { - return true; - } - current = current.parent; - } - - return false; -} - function compareTracks (a: TrackSortWrapper, b: TrackSortWrapper): number { const bindingA = a.track.binding; const bindingB = b.track.binding; @@ -139,9 +123,9 @@ function compareTracks (a: TrackSortWrapper, b: TrackSortWrapper): number { return a.originalIndex - b.originalIndex; } - if (isAncestor(bindingA, bindingB)) { + if (VFXItem.isAncestor(bindingA, bindingB)) { return -1; - } else if (isAncestor(bindingB, bindingA)) { + } else if (VFXItem.isAncestor(bindingB, bindingA)) { return 1; } else { return a.originalIndex - b.originalIndex; // 非父子关系的元素保持原始顺序 diff --git a/packages/effects-core/src/plugins/camera/camera-controller-node.ts b/packages/effects-core/src/plugins/camera/camera-controller-node.ts index 84380289..09a617fd 100644 --- a/packages/effects-core/src/plugins/camera/camera-controller-node.ts +++ b/packages/effects-core/src/plugins/camera/camera-controller-node.ts @@ -18,7 +18,7 @@ export class CameraController extends Behaviour { } } - override update () { + override onUpdate () { if (this.item.composition && this.item.transform.getValid()) { const camera = this.item.composition.camera; diff --git a/packages/effects-core/src/plugins/interact/interact-item.ts b/packages/effects-core/src/plugins/interact/interact-item.ts index e2268400..1fdf7776 100644 --- a/packages/effects-core/src/plugins/interact/interact-item.ts +++ b/packages/effects-core/src/plugins/interact/interact-item.ts @@ -47,7 +47,7 @@ export class InteractComponent extends RendererComponent { return this._interactive; } - override start (): void { + override onStart (): void { const options = this.item.props.content.options as spec.DragInteractOption; const { env } = this.item.engine.renderer; const composition = this.item.composition; @@ -84,7 +84,10 @@ export class InteractComponent extends RendererComponent { }; } - override update (dt: number): void { + override onUpdate (dt: number): void { + if (!this.isActiveAndEnabled) { + return; + } this.previewContent?.updateMesh(); if (!this.hasBeenAddedToComposition && this.item.composition) { const options = this.item.props.content.options as spec.DragInteractOption; diff --git a/packages/effects-core/src/plugins/particle/particle-system-renderer.ts b/packages/effects-core/src/plugins/particle/particle-system-renderer.ts index b2f50788..1a7482b2 100644 --- a/packages/effects-core/src/plugins/particle/particle-system-renderer.ts +++ b/packages/effects-core/src/plugins/particle/particle-system-renderer.ts @@ -47,15 +47,15 @@ export class ParticleSystemRenderer extends RendererComponent { this.meshes = meshes; } - override start (): void { + override onStart (): void { this._priority = this.item.renderOrder; this.particleMesh.gravityModifier.scaleXCoord(this.item.duration); for (const mesh of this.meshes) { - mesh.start(); + mesh.onStart(); } } - override update (dt: number): void { + override onUpdate (dt: number): void { const time = this.particleMesh.time; this.particleMesh.mesh.material.setVector4('uParams', new Vector4(time, this.item.duration, 0, 0)); diff --git a/packages/effects-core/src/plugins/particle/particle-system.ts b/packages/effects-core/src/plugins/particle/particle-system.ts index d1990822..2e4eb28e 100644 --- a/packages/effects-core/src/plugins/particle/particle-system.ts +++ b/packages/effects-core/src/plugins/particle/particle-system.ts @@ -312,7 +312,7 @@ export class ParticleSystem extends Component { return this.renderer.getTextures(); } - start () { + startEmit () { if (!this.started || this.ended) { this.reset(); this.started = true; @@ -337,7 +337,7 @@ export class ParticleSystem extends Component { this.ended = false; } - onUpdate (delta: number) { + update (delta: number) { if (this.started && !this.frozen) { const now = this.lastUpdate + delta / 1000; const options = this.options; diff --git a/packages/effects-core/src/plugins/particle/particle-vfx-item.ts b/packages/effects-core/src/plugins/particle/particle-vfx-item.ts index ed0ea3ec..722e1f24 100644 --- a/packages/effects-core/src/plugins/particle/particle-vfx-item.ts +++ b/packages/effects-core/src/plugins/particle/particle-vfx-item.ts @@ -21,7 +21,7 @@ export class ParticleBehaviourPlayable extends Playable { if (this.particleSystem) { this.particleSystem.name = boundObject.name; - this.particleSystem.start(); + this.particleSystem.startEmit(); this.particleSystem.initEmitterTransform(); } } @@ -44,7 +44,7 @@ export class ParticleBehaviourPlayable extends Playable { if (Math.abs(this.time - this.lastTime) < 0.001) { deltaTime = 0; } - particleSystem.onUpdate(deltaTime); + particleSystem.update(deltaTime); } this.lastTime = this.time; } diff --git a/packages/effects-core/src/plugins/sprite/sprite-item.ts b/packages/effects-core/src/plugins/sprite/sprite-item.ts index bb63b734..95fbdf70 100644 --- a/packages/effects-core/src/plugins/sprite/sprite-item.ts +++ b/packages/effects-core/src/plugins/sprite/sprite-item.ts @@ -279,11 +279,11 @@ export class SpriteComponent extends RendererComponent { renderer.drawGeometry(geo, material); } - override start (): void { + override onStart (): void { this.item.getHitTestParams = this.getHitTestParams; } - override update (dt: number): void { + override onUpdate (dt: number): void { if (!this.isManualTimeSet) { this.frameAnimationTime += dt / 1000; this.isManualTimeSet = false; diff --git a/packages/effects-core/src/plugins/text/text-item.ts b/packages/effects-core/src/plugins/text/text-item.ts index e7f6d5c4..45b5c997 100644 --- a/packages/effects-core/src/plugins/text/text-item.ts +++ b/packages/effects-core/src/plugins/text/text-item.ts @@ -67,8 +67,8 @@ export class TextComponent extends SpriteComponent { this.updateTexture(); } - override update (dt: number): void { - super.update(dt); + override onUpdate (dt: number): void { + super.onUpdate(dt); this.updateTexture(); } diff --git a/packages/effects-core/src/vfx-item.ts b/packages/effects-core/src/vfx-item.ts index c45c48f9..ad5b0bf0 100644 --- a/packages/effects-core/src/vfx-item.ts +++ b/packages/effects-core/src/vfx-item.ts @@ -5,7 +5,7 @@ import { Vector3 } from '@galacean/effects-math/es/core/vector3'; import * as spec from '@galacean/effects-specification'; import type { VFXItemData } from './asset-loader'; import type { Component } from './components'; -import { RendererComponent, Behaviour, EffectComponent } from './components'; +import { RendererComponent, EffectComponent } from './components'; import type { Composition } from './composition'; import { HELP_LINK } from './constants'; import { effectsClass, serialize } from './decorators'; @@ -94,10 +94,10 @@ export class VFXItem extends EffectsObject implements Disposable { reusable = false; type: spec.ItemType = spec.ItemType.base; props: VFXItemProps; + isDuringPlay = false; @serialize() components: Component[] = []; - itemBehaviours: Behaviour[] = []; rendererComponents: RendererComponent[] = []; /** @@ -110,6 +110,7 @@ export class VFXItem extends EffectsObject implements Disposable { */ private speed = 1; private listIndex = 0; + private isEnabled = false; private eventProcessor: EventEmitter = new EventEmitter(); static isComposition (item: VFXItem) { @@ -140,6 +141,22 @@ export class VFXItem extends EffectsObject implements Disposable { return item.id === 'extra-camera' && item.name === 'extra-camera'; } + static isAncestor ( + ancestorCandidate: VFXItem, + descendantCandidate: VFXItem, + ) { + let current = descendantCandidate.parent; + + while (current) { + if (current === ancestorCandidate) { + return true; + } + current = current.parent; + } + + return false; + } + constructor ( engine: Engine, props?: VFXItemProps, @@ -267,8 +284,7 @@ export class VFXItem extends EffectsObject implements Disposable { const newComponent = new classConstructor(this.engine); this.components.push(newComponent); - newComponent.item = this; - newComponent.onAttached(); + newComponent.setVFXItem(this); return newComponent; } @@ -310,21 +326,22 @@ export class VFXItem extends EffectsObject implements Disposable { } setParent (vfxItem: VFXItem) { - if (vfxItem === this) { + if (vfxItem === this && !vfxItem) { return; } if (this.parent) { removeItem(this.parent.children, this); } this.parent = vfxItem; - if (vfxItem) { - if (!VFXItem.isCamera(this)) { - this.transform.parentTransform = vfxItem.transform; - } - vfxItem.children.push(this); - if (!this.composition) { - this.composition = vfxItem.composition; - } + if (!VFXItem.isCamera(this)) { + this.transform.parentTransform = vfxItem.transform; + } + vfxItem.children.push(this); + if (!this.composition) { + this.composition = vfxItem.composition; + } + if (!this.isDuringPlay && vfxItem.isDuringPlay) { + this.beginPlay(); } } @@ -370,6 +387,7 @@ export class VFXItem extends EffectsObject implements Disposable { setVisible (visible: boolean) { if (this.visible !== visible) { this.visible = !!visible; + this.onActiveChanged(); } } @@ -525,6 +543,64 @@ export class VFXItem extends EffectsObject implements Disposable { return undefined; } + /** + * @internal + */ + beginPlay () { + this.isDuringPlay = true; + + if (this.composition && this.visible && !this.isEnabled) { + this.onEnable(); + } + + for (const child of this.children) { + if (!child.isDuringPlay) { + child.beginPlay(); + } + } + + } + + /** + * @internal + */ + onActiveChanged () { + if (!this.isEnabled) { + this.onEnable(); + } else { + this.onDisable(); + } + } + + /** + * @internal + */ + onEnable () { + this.isEnabled = true; + for (const component of this.components) { + if (component.enabled && !component.isStartCalled) { + component.onStart(); + } + } + for (const component of this.components) { + if (component.enabled && !component.isEnableCalled) { + component.enable(); + } + } + } + + /** + * @internal + */ + onDisable () { + this.isEnabled = false; + for (const component of this.components) { + if (component.enabled && component.isEnableCalled) { + component.disable(); + } + } + } + override fromData (data: VFXItemData): void { super.fromData(data); const { @@ -581,13 +657,9 @@ export class VFXItem extends EffectsObject implements Disposable { throw new Error(`Item duration can't be less than 0, see ${HELP_LINK['Item duration can\'t be less than 0']}.`); } - this.itemBehaviours.length = 0; this.rendererComponents.length = 0; for (const component of this.components) { component.item = this; - if (component instanceof Behaviour) { - this.itemBehaviours.push(component); - } if (component instanceof RendererComponent) { this.rendererComponents.push(component); } diff --git a/packages/effects-threejs/src/three-mesh.ts b/packages/effects-threejs/src/three-mesh.ts index 95e74637..178066e3 100644 --- a/packages/effects-threejs/src/three-mesh.ts +++ b/packages/effects-threejs/src/three-mesh.ts @@ -110,8 +110,8 @@ export class ThreeMesh extends Mesh implements Sortable { this.mesh.material = (mtl as ThreeMaterial).material; } - override start (): void { - super.start(); + override onStart (): void { + super.onStart(); (this.engine as ThreeEngine).threeGroup.add(this.mesh); } diff --git a/packages/effects-threejs/src/three-sprite-component.ts b/packages/effects-threejs/src/three-sprite-component.ts index c8ce202f..9423768c 100644 --- a/packages/effects-threejs/src/three-sprite-component.ts +++ b/packages/effects-threejs/src/three-sprite-component.ts @@ -83,8 +83,8 @@ export class ThreeSpriteComponent extends SpriteComponent { } } - override start (): void { - super.start(); + override onStart (): void { + super.onStart(); (this.engine as ThreeEngine).threeGroup.add(this.threeMesh); } diff --git a/packages/effects-threejs/src/three-text-component.ts b/packages/effects-threejs/src/three-text-component.ts index 4a7faa54..d2800f19 100644 --- a/packages/effects-threejs/src/three-text-component.ts +++ b/packages/effects-threejs/src/three-text-component.ts @@ -32,8 +32,8 @@ export class ThreeTextComponent extends ThreeSpriteComponent { this.updateTexture(false); } - override update (dt: number): void { - super.update(dt); + override onUpdate (dt: number): void { + super.onUpdate(dt); this.updateTexture(false); } diff --git a/plugin-packages/editor-gizmo/src/gizmo-component.ts b/plugin-packages/editor-gizmo/src/gizmo-component.ts index 5910c55c..2521d829 100644 --- a/plugin-packages/editor-gizmo/src/gizmo-component.ts +++ b/plugin-packages/editor-gizmo/src/gizmo-component.ts @@ -41,7 +41,7 @@ export class GizmoComponent extends RendererComponent { mat = Matrix4.fromIdentity(); wireframeMeshes: Mesh[] = []; - override start (): void { + override onStart (): void { this.item.getHitTestParams = this.getHitTestParams; for (const item of this.item.composition?.items ?? []) { if (item.id === this.target) { @@ -118,7 +118,7 @@ export class GizmoComponent extends RendererComponent { composition?.loaderData.gizmoItems.push(this.item); } - override update (dt: number): void { + override onUpdate (dt: number): void { this.updateRenderData(); } diff --git a/plugin-packages/model/src/plugin/model-item.ts b/plugin-packages/model/src/plugin/model-item.ts index 0d8043d1..a297aa25 100644 --- a/plugin-packages/model/src/plugin/model-item.ts +++ b/plugin-packages/model/src/plugin/model-item.ts @@ -59,7 +59,7 @@ export class ModelMeshComponent extends RendererComponent { /** * 组件开始,需要创建内部对象,更新父元素信息和添加到场景管理器中 */ - override start (): void { + override onStart (): void { this.sceneManager = getSceneManager(this); this.createContent(); this.item.type = VFX_ITEM_TYPE_3D; @@ -76,7 +76,7 @@ export class ModelMeshComponent extends RendererComponent { * 组件更新,更新内部对象状态 * @param dt - 更新间隔 */ - override update (dt: number): void { + override onUpdate (dt: number): void { if (this.sceneManager) { this.content.build(this.sceneManager); } @@ -88,7 +88,7 @@ export class ModelMeshComponent extends RendererComponent { * 组件晚更新,晚更新内部对象状态 * @param dt - 更新间隔 */ - override lateUpdate (dt: number): void { + override onLateUpdate (dt: number): void { this.content.lateUpdate(); } @@ -273,7 +273,7 @@ export class ModelSkyboxComponent extends RendererComponent { /** * 组件开始,需要创建内部对象和添加到场景管理器中 */ - override start (): void { + override onStart (): void { this.createContent(); this.item.type = VFX_ITEM_TYPE_3D; this.priority = this.item.renderOrder; @@ -371,7 +371,7 @@ export class ModelLightComponent extends Behaviour { /** * 组件开始,需要创建内部对象和添加到场景管理器中 */ - override start (): void { + override onStart (): void { this.createContent(); this.item.type = VFX_ITEM_TYPE_3D; const scene = getSceneManager(this); @@ -384,7 +384,7 @@ export class ModelLightComponent extends Behaviour { * 组件更新,更新内部对象状态 * @param dt - 更新间隔 */ - override update (dt: number): void { + override onUpdate (dt: number): void { this.content.update(); } @@ -462,7 +462,7 @@ export class ModelCameraComponent extends Behaviour { /** * 组件开始,需要创建内部对象和添加到场景管理器中 */ - override start (): void { + override onStart (): void { this.createContent(); this.item.type = VFX_ITEM_TYPE_3D; const scene = getSceneManager(this); @@ -475,7 +475,7 @@ export class ModelCameraComponent extends Behaviour { * 组件更新,更新内部对象状态 * @param dt - 更新间隔 */ - override update (dt: number): void { + override onUpdate (dt: number): void { this.content.update(); this.updateMainCamera(); } @@ -569,7 +569,7 @@ export class AnimationComponent extends Behaviour { /** * 组件开始,需要创建内部对象和添加到场景管理器中 */ - override start (): void { + override onStart (): void { this.elapsedTime = 0; this.item.type = VFX_ITEM_TYPE_3D; } @@ -578,7 +578,7 @@ export class AnimationComponent extends Behaviour { * 组件更新,更新内部对象状态 * @param dt - 更新间隔 */ - override update (dt: number): void { + override onUpdate (dt: number): void { this.elapsedTime += dt * 0.001; if (this.animation >= 0 && this.animation < this.clips.length) { this.clips[this.animation].sampleAnimation(this.item, this.elapsedTime); diff --git a/plugin-packages/model/src/plugin/model-plugin.ts b/plugin-packages/model/src/plugin/model-plugin.ts index 81c4cb32..f94cabf4 100644 --- a/plugin-packages/model/src/plugin/model-plugin.ts +++ b/plugin-packages/model/src/plugin/model-plugin.ts @@ -210,7 +210,7 @@ export class ModelPluginComponent extends Behaviour { * 组件后更新,合成相机和场景管理器更新 * @param dt - 更新间隔 */ - override lateUpdate (dt: number): void { + override onLateUpdate (dt: number): void { const composition = this.item.composition as Composition; if (this.autoAdjustScene && this.scene.tickCount == 1) { diff --git a/plugin-packages/model/src/plugin/model-tree-item.ts b/plugin-packages/model/src/plugin/model-tree-item.ts index bd85c8c8..326c3258 100644 --- a/plugin-packages/model/src/plugin/model-tree-item.ts +++ b/plugin-packages/model/src/plugin/model-tree-item.ts @@ -212,7 +212,7 @@ export class ModelTreeComponent extends Behaviour { /** * 组件开始,查询合成中场景管理器并设置到动画管理器中 */ - override start () { + override onStart () { this.item.type = spec.ItemType.tree; this.content.baseTransform.setValid(true); const sceneManager = getSceneManager(this); @@ -226,7 +226,7 @@ export class ModelTreeComponent extends Behaviour { * 组件更新,内部对象更新 * @param dt */ - override update (dt: number): void { + override onUpdate (dt: number): void { // this.timeline?.getRenderData(time, true); // TODO: 需要使用lifetime this.content?.tick(dt); diff --git a/plugin-packages/orientation-transformer/src/orientation-component.ts b/plugin-packages/orientation-transformer/src/orientation-component.ts index db1ee586..9bf22cb7 100644 --- a/plugin-packages/orientation-transformer/src/orientation-component.ts +++ b/plugin-packages/orientation-transformer/src/orientation-component.ts @@ -27,7 +27,7 @@ export class OrientationComponent extends Behaviour { } - override start () { + override onStart () { const transformer = this.item.composition?.loaderData.deviceTransformer as CompositionTransformerAcceler; if (transformer) { @@ -36,7 +36,7 @@ export class OrientationComponent extends Behaviour { } } - override update (dt: number) { + override onUpdate (dt: number) { const transformer = this.item.composition?.loaderData.deviceTransformer as CompositionTransformerAcceler; if (transformer) { diff --git a/plugin-packages/spine/src/spine-component.ts b/plugin-packages/spine/src/spine-component.ts index 0ed1a457..d9aa659a 100644 --- a/plugin-packages/spine/src/spine-component.ts +++ b/plugin-packages/spine/src/spine-component.ts @@ -143,8 +143,8 @@ export class SpineComponent extends RendererComponent { } } - override start () { - super.start(); + override onStart () { + super.onStart(); if (!this.cache) { return; } @@ -158,11 +158,11 @@ export class SpineComponent extends RendererComponent { return; } this.state.apply(this.skeleton); - this.update(0); + this.onUpdate(0); this.resize(); } - override update (dt: number) { + override onUpdate (dt: number) { if (!(this.state && this.skeleton)) { return; } diff --git a/plugin-packages/stats/src/stats-component.ts b/plugin-packages/stats/src/stats-component.ts index 3173272f..a92c6d26 100644 --- a/plugin-packages/stats/src/stats-component.ts +++ b/plugin-packages/stats/src/stats-component.ts @@ -7,14 +7,14 @@ export class StatsComponent extends Behaviour { */ monitor: Monitor; - override start (): void { + override onStart (): void { const renderer = this.engine.renderer as GLRenderer; const gl = renderer.pipelineContext.gl; this.monitor = new Monitor(gl); } - override update (dt: number): void { + override onUpdate (dt: number): void { this.monitor.update(dt); } } diff --git a/web-packages/test/unit/src/effects-core/plugins/sprite/sprite-renderder.spec.ts b/web-packages/test/unit/src/effects-core/plugins/sprite/sprite-renderder.spec.ts index ca7adab6..14081d0e 100644 --- a/web-packages/test/unit/src/effects-core/plugins/sprite/sprite-renderder.spec.ts +++ b/web-packages/test/unit/src/effects-core/plugins/sprite/sprite-renderder.spec.ts @@ -202,7 +202,7 @@ describe('core/plugins/sprite/renderer', () => { const comp = await loadSceneAndPlay(player, JSON.parse(json), currentTime); const spriteItem = comp.getItemByName('sprite_1')?.getComponent(SpriteComponent); - spriteItem?.update(0.1); + spriteItem?.onUpdate(0.1); const transform = spriteItem?.item.transform; const startSize = transform?.size; const a = transform?.anchor.toArray(); From 411a4d0566551d48cb4236d4cb95ea2bb6db4f0f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=8C=82=E5=AE=89?= <151805118+wumaolinmaoan@users.noreply.github.com> Date: Mon, 2 Sep 2024 19:09:24 +0800 Subject: [PATCH 02/88] feat: cpu particle system (#613) * feat: cpu particle * feat: shader parallel compilation * perf: opt particle system renderer update timing * perf: opt code * perf: opt code * perf: opt shader async compile check logic * feat: add random value getter process on particle update * perf: opt particleCount calcute in ParticleMesh onUpdate * perf: opt code * perf: opt code * chore: import issue --------- Co-authored-by: yiiqii --- .../effects-core/src/components/component.ts | 8 +- .../effects-core/src/math/value-getter.ts | 14 +- .../src/plugins/particle/particle-mesh.ts | 273 +++++++++++++++++- .../particle/particle-system-renderer.ts | 2 + .../src/shader/particle.vert.glsl | 190 ++++++------ .../effects-webgl/src/gl-shader-library.ts | 1 + web-packages/demo/src/single.ts | 2 +- 7 files changed, 379 insertions(+), 111 deletions(-) diff --git a/packages/effects-core/src/components/component.ts b/packages/effects-core/src/components/component.ts index b8026685..28a74be9 100644 --- a/packages/effects-core/src/components/component.ts +++ b/packages/effects-core/src/components/component.ts @@ -149,7 +149,13 @@ export abstract class Component extends EffectsObject { } override dispose (): void { - this.onDestroy(); + if (this.isEnableCalled) { + this.disable(); + } + if (this.isAwakeCalled) { + this.isAwakeCalled = false; + this.onDestroy(); + } if (this.item) { removeItem(this.item.components, this); } diff --git a/packages/effects-core/src/math/value-getter.ts b/packages/effects-core/src/math/value-getter.ts index 4d96f0ed..25e9fc03 100644 --- a/packages/effects-core/src/math/value-getter.ts +++ b/packages/effects-core/src/math/value-getter.ts @@ -3,7 +3,7 @@ import type { Vector2 } from '@galacean/effects-math/es/core/vector2'; import { Vector3 } from '@galacean/effects-math/es/core/vector3'; import { Quaternion } from '@galacean/effects-math/es/core/quaternion'; import * as spec from '@galacean/effects-specification'; -import { randomInRange, colorToArr, colorStopsFromGradient, interpolateColor, isFunction } from '../utils'; +import { colorToArr, colorStopsFromGradient, interpolateColor, isFunction } from '../utils'; import type { ColorStop } from '../utils'; import type { BezierEasing } from './bezier'; import { BezierPath, buildEasingCurve, BezierQuat } from './bezier'; @@ -145,8 +145,16 @@ export class RandomValue extends ValueGetter { this.max = props[1]; } - override getValue (time?: number): number { - return randomInRange(this.min, this.max); + override getValue (time?: number, seed?: number): number { + const randomSeed = seed ?? Math.random(); + + return this.min + randomSeed * (this.max - this.min); + } + + override getIntegrateValue (t0: number, t1: number, timeScale?: number): number { + const seed = timeScale ?? 1.0; + + return (this.min + seed * (this.max - this.min)) * (t1 - t0); } override toUniform () { diff --git a/packages/effects-core/src/plugins/particle/particle-mesh.ts b/packages/effects-core/src/plugins/particle/particle-mesh.ts index c2037454..16284366 100644 --- a/packages/effects-core/src/plugins/particle/particle-mesh.ts +++ b/packages/effects-core/src/plugins/particle/particle-mesh.ts @@ -5,6 +5,8 @@ import { Quaternion } from '@galacean/effects-math/es/core/quaternion'; import { Vector2 } from '@galacean/effects-math/es/core/vector2'; import { Vector3 } from '@galacean/effects-math/es/core/vector3'; import { Vector4 } from '@galacean/effects-math/es/core/vector4'; +import { Matrix3 } from '@galacean/effects-math/es/core/matrix3'; +import { clamp } from '@galacean/effects-math/es/core/utils'; import type { Engine } from '../../engine'; import { getConfig, RENDER_PREFER_LOOKUP_TEXTURE } from '../../config'; import { PLAYER_OPTIONS_ENV_EDITOR } from '../../constants'; @@ -14,6 +16,7 @@ import { } from '../../material'; import { createKeyFrameMeta, createValueGetter, ValueGetter, getKeyFrameMetaByRawValue, + RandomValue, } from '../../math'; import type { Attribute, GPUCapability, GeometryProps, ShaderMacros, SharedShaderWithSource, @@ -132,6 +135,8 @@ export class ParticleMesh implements ParticleMeshData { readonly maxCount: number; readonly anchor: Vector2; + VERT_MAX_KEY_FRAME_COUNT = 0; + constructor ( engine: Engine, props: ParticleMeshProps, @@ -289,6 +294,7 @@ export class ParticleMesh implements ParticleMeshData { ['VERT_MAX_KEY_FRAME_COUNT', vertexKeyFrameMeta.max], ['FRAG_MAX_KEY_FRAME_COUNT', fragmentKeyFrameMeta.max], ); + this.VERT_MAX_KEY_FRAME_COUNT = vertexKeyFrameMeta.max; const fragment = particleFrag; const originalVertex = `#define LOOKUP_TEXTURE_CURVE ${vertex_lookup_texture}\n${particleVert}`; @@ -391,6 +397,7 @@ export class ParticleMesh implements ParticleMeshData { this.orbitalVelOverLifetime = orbitalVelOverLifetime; this.orbitalVelOverLifetime = orbitalVelOverLifetime; this.gravityModifier = gravityModifier; + this.rotationOverLifetime = rotationOverLifetime; this.maxCount = maxCount; // this.duration = duration; this.textureOffsets = textureFlip ? [0, 0, 1, 0, 0, 1, 1, 1] : [0, 1, 0, 0, 1, 1, 1, 0]; @@ -440,18 +447,267 @@ export class ParticleMesh implements ParticleMeshData { geometry.setIndexData(new index.constructor(0)); } - minusTime (time: number) { - const data = this.geometry.getAttributeData('aOffset'); + onUpdate (dt: number) { + const aPosArray = this.geometry.getAttributeData('aPos') as Float32Array; // vector3 + const aVelArray = this.geometry.getAttributeData('aVel') as Float32Array; // vector3 + const aOffsetArray = this.geometry.getAttributeData('aOffset') as Float32Array; + const aRotArray = this.geometry.getAttributeData('aRot') as Float32Array; // vector3 + const aSeedArray = this.geometry.getAttributeData('aSeed') as Float32Array; // float + // const uParams = this.mesh.material.getVector4('uParams'); - assertExist(data); + // if (!uParams) { + // return; + // } - for (let i = 0; i < data.length; i += 4) { - data[i + 2] -= time; + const localTime = new Float32Array([this.time])[0]; + const particleCount = Math.ceil(aPosArray.length / 12); + + // calculate particle translation + let aTranslationArray = this.geometry.getAttributeData('aTranslation') as Float32Array; + + if (aTranslationArray.length < particleCount * 3) { + aTranslationArray = new Float32Array(particleCount * 3); } - this.geometry.setAttributeData('aOffset', data); + const velocity = new Vector3(0, 0, 0); + + for (let i = 0; i < particleCount; i++) { + const velOffset = i * 12 + 3; + + velocity.set(aVelArray[velOffset], aVelArray[velOffset + 1], aVelArray[velOffset + 2]); + + const trans = this.calculateTranslation(velocity, aOffsetArray[i * 4 + 2], localTime, aOffsetArray[i * 4 + 3]); + const aTranslationOffset = i * 3; + + aTranslationArray[aTranslationOffset] = trans.x; + aTranslationArray[aTranslationOffset + 1] = trans.y; + aTranslationArray[aTranslationOffset + 2] = trans.z; + } + this.geometry.setAttributeData('aTranslation', aTranslationArray); + + // calculate particle rotation + let aRotationArray = this.geometry.getAttributeData('aRotation0') as Float32Array; + + if (aRotationArray.length < particleCount * 9) { + aRotationArray = new Float32Array(particleCount * 9); + } + + for (let i = 0; i < particleCount; i++) { + const time = localTime - aOffsetArray[i * 4 + 2]; + const duration = aOffsetArray[i * 4 + 3]; + const life = clamp(time / duration, 0.0, 1.0); + const aRotOffset = i * 8; + const aRot = new Vector3(aRotArray[aRotOffset], aRotArray[aRotOffset + 1], aRotArray[aRotOffset + 2]); + const aSeed = aSeedArray[i * 8 + 3]; + + const aRotation = this.transformFromRotation(aRot, life, duration, aSeed); + const aRotationOffset = i * 9; + + const matrixArray = aRotation.toArray(); + + aRotationArray.set(matrixArray, aRotationOffset); + } + + this.geometry.setAttributeData('aRotation0', aRotationArray); + + // calculate linear movement + let aLinearMoveArray = this.geometry.getAttributeData('aLinearMove') as Float32Array; + + if (aLinearMoveArray.length < particleCount * 3) { + aLinearMoveArray = new Float32Array(particleCount * 3); + } + + for (let i = 0; i < particleCount; i++) { + const time = localTime - aOffsetArray[i * 4 + 2]; + const duration = aOffsetArray[i * 4 + 3]; + // const life = math.clamp(time / duration, 0.0, 1.0); + const aSeed = aSeedArray[i * 8 + 3]; + + const linearMove = this.calLinearMov(time, duration, aSeed); + const aLinearMoveOffset = i * 3; + + aLinearMoveArray[aLinearMoveOffset] = linearMove.x; + aLinearMoveArray[aLinearMoveOffset + 1] = linearMove.y; + aLinearMoveArray[aLinearMoveOffset + 2] = linearMove.z; + } + this.geometry.setAttributeData('aLinearMove', aLinearMoveArray); + } + + minusTime (time: number) { + const aOffset = this.geometry.getAttributeData('aOffset') as Float32Array; + + for (let i = 0; i < aOffset.length; i += 4) { + aOffset[i + 2] -= time; + } + this.geometry.setAttributeData('aOffset', aOffset); this.time -= time; } + calculateTranslation (velocity: Vector3, t0: number, t1: number, duration: number): Vector3 { + const uAcceleration = this.mesh.material.getVector4('uAcceleration'); + const uGravityModifierValue = this.mesh.material.getVector4('uGravityModifierValue'); + + if (!uAcceleration || !uGravityModifierValue) { + return new Vector3(); + } + const dt = t1 - t0; // 相对delay的时间 + const d = this.gravityModifier.getIntegrateByTime(0, dt); + const acc: spec.vec3 = [uAcceleration.x * d, uAcceleration.y * d, uAcceleration.z * d]; + + // ret.addScaledVector(velData, speedIntegrate); + // ret.addScaledVector(acc, d); + // speedIntegrate = speedOverLifetime.getIntegrateValue(0, time, duration); + if (this.speedOverLifetime) { + // dt / dur 归一化 + const speed = this.speedOverLifetime.getIntegrateValue(0, dt, duration); + + return velocity.clone().multiply(speed).add(acc); + } + + return velocity.clone().multiply(dt).add(acc); + } + + transformFromRotation (rot: Vector3, life: number, dur: number, aSeed: number): Matrix3 { + const rotation = rot.clone(); + + if (!this.rotationOverLifetime) { + return new Matrix3(); + } + + if (this.rotationOverLifetime.asRotation) { + // Adjust rotation based on the specified lifetime components + if (this.rotationOverLifetime.x) { + if (this.rotationOverLifetime.x instanceof RandomValue) { + rotation.x += this.rotationOverLifetime.x.getValue(life, aSeed); + } else { + rotation.x += this.rotationOverLifetime.x.getValue(life); + } + } + if (this.rotationOverLifetime.y) { + if (this.rotationOverLifetime.y instanceof RandomValue) { + rotation.y += this.rotationOverLifetime.y.getValue(life, aSeed); + } else { + rotation.y += this.rotationOverLifetime.y.getValue(life); + } + } + if (this.rotationOverLifetime.z) { + if (this.rotationOverLifetime.z instanceof RandomValue) { + rotation.z += this.rotationOverLifetime.z.getValue(life, aSeed); + } else { + rotation.z += this.rotationOverLifetime.z.getValue(life); + } + } + } else { + // Adjust rotation based on the specified lifetime components + if (this.rotationOverLifetime.x) { + if (this.rotationOverLifetime.x instanceof RandomValue) { + rotation.x += this.rotationOverLifetime.x.getIntegrateValue(0.0, life, aSeed) * dur; + } else { + rotation.x += this.rotationOverLifetime.x.getIntegrateValue(0.0, life, dur) * dur; + } + } + if (this.rotationOverLifetime.y) { + if (this.rotationOverLifetime.y instanceof RandomValue) { + rotation.y += this.rotationOverLifetime.y.getIntegrateValue(0.0, life, aSeed) * dur; + } else { + rotation.y += this.rotationOverLifetime.y.getIntegrateValue(0.0, life, dur) * dur; + } + } + if (this.rotationOverLifetime.z) { + if (this.rotationOverLifetime.z instanceof RandomValue) { + rotation.z += this.rotationOverLifetime.z.getIntegrateValue(0.0, life, aSeed) * dur; + } else { + rotation.z += this.rotationOverLifetime.z.getIntegrateValue(0.0, life, dur) * dur; + } + } + } + + // If the rotation vector is zero, return the identity matrix + if (rotation.dot(rotation) === 0.0) { + return new Matrix3().identity(); + } + + // Return the rotation matrix derived from the rotation vector + return this.mat3FromRotation(rotation); + } + + mat3FromRotation (rotation: Vector3): Matrix3 { + const d2r = Math.PI / 180; + const sinR = rotation.clone().multiply(d2r); + + sinR.x = Math.sin(sinR.x); + sinR.y = Math.sin(sinR.y); + sinR.z = Math.sin(sinR.z); + const cosR = rotation.clone().multiply(d2r); + + cosR.x = Math.cos(cosR.x); + cosR.y = Math.cos(cosR.y); + cosR.z = Math.cos(cosR.z); + + const rotZ = new Matrix3(cosR.z, -sinR.z, 0., sinR.z, cosR.z, 0., 0., 0., 1.); + const rotY = new Matrix3(cosR.y, 0., sinR.y, 0., 1., 0., -sinR.y, 0, cosR.y); + const rotX = new Matrix3(1., 0., 0., 0, cosR.x, -sinR.x, 0., sinR.x, cosR.x); + const result = rotZ.multiply(rotY).multiply(rotX); + + return result; + } + + calLinearMov (time: number, duration: number, aSeed: number): Vector3 { + const mov = new Vector3(); + const lifetime = time / duration; + + if (!this.linearVelOverLifetime || !this.linearVelOverLifetime.enabled) { + return new Vector3(); + } + if (this.linearVelOverLifetime.asMovement) { + if (this.linearVelOverLifetime.x) { + if (this.linearVelOverLifetime.x instanceof RandomValue) { + mov.x = this.linearVelOverLifetime.x.getValue(lifetime, aSeed); + } else { + mov.x = this.linearVelOverLifetime.x.getValue(lifetime); + } + } + if (this.linearVelOverLifetime.y) { + if (this.linearVelOverLifetime.y instanceof RandomValue) { + mov.y = this.linearVelOverLifetime.y.getValue(lifetime, aSeed); + } else { + mov.y = this.linearVelOverLifetime.y.getValue(lifetime); + } + } + if (this.linearVelOverLifetime.z) { + if (this.linearVelOverLifetime.z instanceof RandomValue) { + mov.z = this.linearVelOverLifetime.z.getValue(lifetime, aSeed); + } else { + mov.z = this.linearVelOverLifetime.z.getValue(lifetime); + } + } + } else { + // Adjust rotation based on the specified lifetime components + if (this.linearVelOverLifetime.x) { + if (this.linearVelOverLifetime.x instanceof RandomValue) { + mov.x = this.linearVelOverLifetime.x.getIntegrateValue(0.0, time, aSeed); + } else { + mov.x = this.linearVelOverLifetime.x.getIntegrateValue(0.0, time, duration); + } + } + if (this.linearVelOverLifetime.y) { + if (this.linearVelOverLifetime.y instanceof RandomValue) { + mov.y = this.linearVelOverLifetime.y.getIntegrateValue(0.0, time, aSeed); + } else { + mov.y = this.linearVelOverLifetime.y.getIntegrateValue(0.0, time, duration); + } + } + if (this.linearVelOverLifetime.z) { + if (this.linearVelOverLifetime.z instanceof RandomValue) { + mov.z = this.linearVelOverLifetime.z.getIntegrateValue(0.0, time, aSeed); + } else { + mov.z = this.linearVelOverLifetime.z.getIntegrateValue(0.0, time, duration); + } + } + } + + return mov; + } + removePoint (index: number) { if (index < this.particleCount) { this.geometry.setAttributeSubData('aOffset', index * 16, new Float32Array(16)); @@ -612,6 +868,11 @@ function generateGeometryProps ( aColor: { size: 4, offset: 4 * bpe, stride: 8 * bpe, dataSource: 'aRot' }, // aOffset: { size: 4, stride: 4 * bpe, data: new Float32Array(0) }, + aTranslation: { size: 3, data: new Float32Array(0) }, + aLinearMove: { size: 3, data: new Float32Array(0) }, + aRotation0: { size: 3, offset: 0, stride: 9 * bpe, data: new Float32Array(0) }, + aRotation1: { size: 3, offset: 3 * bpe, stride: 9 * bpe, dataSource: 'aRotation0' }, + aRotation2: { size: 3, offset: 6 * bpe, stride: 9 * bpe, dataSource: 'aRotation0' }, }; if (useSprite) { diff --git a/packages/effects-core/src/plugins/particle/particle-system-renderer.ts b/packages/effects-core/src/plugins/particle/particle-system-renderer.ts index 1a7482b2..adaebcc0 100644 --- a/packages/effects-core/src/plugins/particle/particle-system-renderer.ts +++ b/packages/effects-core/src/plugins/particle/particle-system-renderer.ts @@ -59,6 +59,7 @@ export class ParticleSystemRenderer extends RendererComponent { const time = this.particleMesh.time; this.particleMesh.mesh.material.setVector4('uParams', new Vector4(time, this.item.duration, 0, 0)); + this.particleMesh.onUpdate(dt); } override render (renderer: Renderer): void { @@ -74,6 +75,7 @@ export class ParticleSystemRenderer extends RendererComponent { updateTime (now: number, delta: number) { this.particleMesh.time = now; + // this.particleMesh.onUpdate(delta); if (this.trailMesh) { this.trailMesh.time = now; this.trailMesh.onUpdate(delta); diff --git a/packages/effects-core/src/shader/particle.vert.glsl b/packages/effects-core/src/shader/particle.vert.glsl index a6551f3e..023ffbd1 100644 --- a/packages/effects-core/src/shader/particle.vert.glsl +++ b/packages/effects-core/src/shader/particle.vert.glsl @@ -10,12 +10,18 @@ const float d2r = 3.141592653589793 / 180.; attribute vec3 aPos; attribute vec4 aOffset;//texcoord.xy time:start duration -attribute vec3 aVel; -attribute vec3 aRot; +// attribute vec3 aVel; +// attribute vec3 aRot; attribute vec4 aColor; attribute vec3 aDirX; attribute vec3 aDirY; +attribute vec3 aTranslation; +attribute vec3 aRotation0; +attribute vec3 aRotation1; +attribute vec3 aRotation2; +attribute vec3 aLinearMove; + #ifdef USE_SPRITE attribute vec3 aSprite;//start duration cycles uniform vec4 uSprite;//col row totalFrame blend @@ -37,53 +43,37 @@ uniform mat4 effects_MatrixV; uniform mat4 effects_MatrixVP; uniform vec4 uParams;//time duration endBehavior -uniform vec4 uAcceleration; -uniform vec4 uGravityModifierValue; +// uniform vec4 uAcceleration; +// uniform vec4 uGravityModifierValue; uniform vec4 uOpacityOverLifetimeValue; -#ifdef ROT_X_LIFETIME -uniform vec4 uRXByLifeTimeValue; -#endif +// #ifdef ROT_X_LIFETIME +// uniform vec4 uRXByLifeTimeValue; +// #endif -#ifdef ROT_Y_LIFETIME -uniform vec4 uRYByLifeTimeValue; -#endif +// #ifdef ROT_Y_LIFETIME +// uniform vec4 uRYByLifeTimeValue; +// #endif -#ifdef ROT_Z_LIFETIME -uniform vec4 uRZByLifeTimeValue; -#endif +// #ifdef ROT_Z_LIFETIME +// uniform vec4 uRZByLifeTimeValue; +// #endif #ifdef COLOR_OVER_LIFETIME uniform sampler2D uColorOverLifetime; #endif -#if LINEAR_VEL_X + LINEAR_VEL_Y + LINEAR_VEL_Z -#if LINEAR_VEL_X -uniform vec4 uLinearXByLifetimeValue; -#endif -#if LINEAR_VEL_Y -uniform vec4 uLinearYByLifetimeValue; -#endif -#if LINEAR_VEL_Z -uniform vec4 uLinearZByLifetimeValue; -#endif -#endif +// uniform vec4 uLinearXByLifetimeValue; +// uniform vec4 uLinearYByLifetimeValue; +// uniform vec4 uLinearZByLifetimeValue; -#ifdef SPEED_OVER_LIFETIME -uniform vec4 uSpeedLifetimeValue; -#endif +// #ifdef SPEED_OVER_LIFETIME +// uniform vec4 uSpeedLifetimeValue; +// #endif -#if ORB_VEL_X + ORB_VEL_Y + ORB_VEL_Z -#if ORB_VEL_X uniform vec4 uOrbXByLifetimeValue; -#endif -#if ORB_VEL_Y uniform vec4 uOrbYByLifetimeValue; -#endif -#if ORB_VEL_Z uniform vec4 uOrbZByLifetimeValue; -#endif uniform vec3 uOrbCenter; -#endif uniform vec4 uSizeByLifetimeValue; @@ -121,28 +111,28 @@ vec3 calOrbitalMov(float _life, float _dur) { return orb; } -vec3 calLinearMov(float _life, float _dur) { - vec3 mov = vec3(0.0); - #ifdef AS_LINEAR_MOVEMENT - #define FUNC(a) getValueFromTime(_life,a) - #else - #define FUNC(a) getIntegrateFromTime0(_life,a) * _dur - #endif - - #if LINEAR_VEL_X - mov.x = FUNC(uLinearXByLifetimeValue); - #endif - - #if LINEAR_VEL_Y - mov.y = FUNC(uLinearYByLifetimeValue); - #endif - - #if LINEAR_VEL_Z - mov.z = FUNC(uLinearZByLifetimeValue); - #endif - #undef FUNC - return mov; -} +// vec3 calLinearMov(float _life, float _dur) { +// vec3 mov = vec3(0.0); +// #ifdef AS_LINEAR_MOVEMENT +// #define FUNC(a) getValueFromTime(_life,a) +// #else +// #define FUNC(a) getIntegrateFromTime0(_life,a) * _dur +// #endif + +// #if LINEAR_VEL_X +// mov.x = FUNC(uLinearXByLifetimeValue); +// #endif + +// #if LINEAR_VEL_Y +// mov.y = FUNC(uLinearYByLifetimeValue); +// #endif + +// #if LINEAR_VEL_Z +// mov.z = FUNC(uLinearZByLifetimeValue); +// #endif +// #undef FUNC +// return mov; +// } mat3 mat3FromRotation(vec3 rotation) { vec3 sinR = sin(rotation * d2r); @@ -176,43 +166,43 @@ UVDetail getSpriteUV(vec2 uv, float lifeTime) { } #endif -vec3 calculateTranslation(vec3 vel, float t0, float t1, float dur) { - float dt = t1 - t0; // 相对delay的时间 - float d = getIntegrateByTimeFromTime(0., dt, uGravityModifierValue); - vec3 acc = uAcceleration.xyz * d; - #ifdef SPEED_OVER_LIFETIME - // dt / dur 归一化 - return vel * getIntegrateFromTime0(dt / dur, uSpeedLifetimeValue) * dur + acc; - #endif - return vel * dt + acc; -} - -mat3 transformFromRotation(vec3 rot, float _life, float _dur) { - vec3 rotation = rot; - #ifdef ROT_LIFETIME_AS_MOVEMENT - #define FUNC1(a) getValueFromTime(_life,a) - #else - #define FUNC1(a) getIntegrateFromTime0(_life,a) * _dur - #endif - - #ifdef ROT_X_LIFETIME - rotation.x += FUNC1(uRXByLifeTimeValue); - #endif - - #ifdef ROT_Y_LIFETIME - rotation.y += FUNC1(uRYByLifeTimeValue); - #endif - - #ifdef ROT_Z_LIFETIME - rotation.z += FUNC1(uRZByLifeTimeValue); - #endif - - if(dot(rotation, rotation) == 0.0) { - return mat3(1.0); - } - #undef FUNC1 - return mat3FromRotation(rotation); -} +// vec3 calculateTranslation(vec3 vel, float t0, float t1, float dur) { +// float dt = t1 - t0; // 相对delay的时间 +// float d = getIntegrateByTimeFromTime(0., dt, uGravityModifierValue); +// vec3 acc = uAcceleration.xyz * d; +// #ifdef SPEED_OVER_LIFETIME +// // dt / dur 归一化 +// return vel * getIntegrateFromTime0(dt / dur, uSpeedLifetimeValue) * dur + acc; +// #endif +// return vel * dt + acc; +// } + +// mat3 transformFromRotation(vec3 rot, float _life, float _dur) { +// vec3 rotation = rot; +// #ifdef ROT_LIFETIME_AS_MOVEMENT +// #define FUNC1(a) getValueFromTime(_life,a) +// #else +// #define FUNC1(a) getIntegrateFromTime0(_life,a) * _dur +// #endif + +// #ifdef ROT_X_LIFETIME +// rotation.x += FUNC1(uRXByLifeTimeValue); +// #endif + +// #ifdef ROT_Y_LIFETIME +// rotation.y += FUNC1(uRYByLifeTimeValue); +// #endif + +// #ifdef ROT_Z_LIFETIME +// rotation.z += FUNC1(uRZByLifeTimeValue); +// #endif + +// if(dot(rotation, rotation) == 0.0) { +// return mat3(1.0); +// } +// #undef FUNC1 +// return mat3FromRotation(rotation); +// } void main() { float time = uParams.x - aOffset.z; @@ -246,18 +236,18 @@ void main() { #ifdef SIZE_Y_BY_LIFE size.y = getValueFromTime(life, uSizeYByLifetimeValue); #endif - vec3 point = transformFromRotation(aRot, life, dur) * (aDirX * size.x + aDirY * size.y); - vec3 pt = calculateTranslation(aVel, aOffset.z, uParams.x, dur); - vec3 _pos = aPos + pt; + mat3 aRotation = mat3(aRotation0, aRotation1, aRotation2); + vec3 point = aRotation * (aDirX * size.x + aDirY * size.y); + // vec3 point = aRotation * (aDirX * size.x + aDirY * size.y); + // vec3 pt = calculateTranslation(aVel, aOffset.z, uParams.x, dur); + vec3 _pos = aPos + aTranslation; #if ORB_VEL_X + ORB_VEL_Y + ORB_VEL_Z _pos = mat3FromRotation(calOrbitalMov(life, dur)) * (_pos - uOrbCenter); _pos += uOrbCenter; #endif - #if LINEAR_VEL_X + LINEAR_VEL_Y + LINEAR_VEL_Z - _pos.xyz += calLinearMov(life, dur); - #endif + _pos.xyz += aLinearMove; #ifdef FINAL_TARGET float force = getValueFromTime(life, uForceCurve); diff --git a/packages/effects-webgl/src/gl-shader-library.ts b/packages/effects-webgl/src/gl-shader-library.ts index 15bd2986..3f1f89da 100644 --- a/packages/effects-webgl/src/gl-shader-library.ts +++ b/packages/effects-webgl/src/gl-shader-library.ts @@ -142,6 +142,7 @@ export class GLShaderLibrary implements ShaderLibrary, Disposable, RestoreHandle console.warn(`Find duplicated shader id: ${shader.id}.`); } this.programMap[shader.id] = glProgram; + // console.log('compileShader ' + result.cacheId + ' ' + result.compileTime + ' ', shader.source); }; const checkComplete = () => { if (this.engine.isDestroyed) { diff --git a/web-packages/demo/src/single.ts b/web-packages/demo/src/single.ts index a1770743..27a25cc0 100644 --- a/web-packages/demo/src/single.ts +++ b/web-packages/demo/src/single.ts @@ -1,7 +1,7 @@ import { Player } from '@galacean/effects'; import '@galacean/effects-plugin-spine'; -const json = 'https://mdn.alipayobjects.com/mars/afts/file/A*CquWQrVCGyUAAAAAAAAAAAAADlB4AQ'; +const json = 'https://mdn.alipayobjects.com/mars/afts/file/A*aCeuQ5RQZj4AAAAAAAAAAAAADlB4AQ'; const container = document.getElementById('J-container'); (async () => { From 0c606f0c998be88db813b748a0ff4675654297b1 Mon Sep 17 00:00:00 2001 From: Sruim <934274351@qq.com> Date: Tue, 3 Sep 2024 12:23:33 +0800 Subject: [PATCH 03/88] faet: add Texture class fromImage options (#622) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: 增加 Texture fromImage options 参数 * test: 增加 fromImage 参数单测 --- packages/effects-core/src/texture/texture.ts | 6 +++-- .../unit/src/effects-core/texture.spec.ts | 24 ++++++++++++++++++- 2 files changed, 27 insertions(+), 3 deletions(-) diff --git a/packages/effects-core/src/texture/texture.ts b/packages/effects-core/src/texture/texture.ts index 7ce28a26..3747dccc 100644 --- a/packages/effects-core/src/texture/texture.ts +++ b/packages/effects-core/src/texture/texture.ts @@ -1,5 +1,5 @@ import { TextureSourceType } from './types'; -import type { TextureFactorySourceFrom, TextureSourceOptions, TextureDataType } from './types'; +import type { TextureFactorySourceFrom, TextureSourceOptions, TextureDataType, TextureOptionsBase } from './types'; import { glContext } from '../gl'; import type { Engine } from '../engine'; import { EffectsObject } from '../effects-object'; @@ -46,14 +46,16 @@ export abstract class Texture extends EffectsObject { * @param url - 要创建的 Texture URL * @since 2.0.0 */ - static async fromImage (url: string, engine: Engine): Promise { + static async fromImage (url: string, engine: Engine, options?: TextureOptionsBase): Promise { const image = await loadImage(url); const texture = Texture.create(engine, { sourceType: TextureSourceType.image, image, + target: glContext.TEXTURE_2D, id: generateGUID(), flipY: true, + ...options, }); texture.initialize(); diff --git a/web-packages/test/unit/src/effects-core/texture.spec.ts b/web-packages/test/unit/src/effects-core/texture.spec.ts index cc811ddb..818be7ce 100644 --- a/web-packages/test/unit/src/effects-core/texture.spec.ts +++ b/web-packages/test/unit/src/effects-core/texture.spec.ts @@ -1,4 +1,4 @@ -import { Texture, Player } from '@galacean/effects'; +import { Texture, Player, glContext } from '@galacean/effects'; const { expect } = chai; @@ -30,4 +30,26 @@ describe('core/texture', () => { expect(testTexture.height).to.be.a('number'); expect(testTexture.id).to.be.a('string'); }); + + it('texture from image with options', async () => { + const testTexture = await Texture.fromImage('https://gw.alipayobjects.com/mdn/rms_2e421e/afts/img/A*fRtNTKrsq3YAAAAAAAAAAAAAARQnAQ', player.renderer.engine, + { + minFilter: glContext.LINEAR_MIPMAP_LINEAR, + magFilter: glContext.LINEAR, + wrapS: glContext.REPEAT, + wrapT: glContext.MIRRORED_REPEAT, + } + ); + const textureSource = testTexture.source; + + expect(textureSource.minFilter).to.equal(glContext.LINEAR_MIPMAP_LINEAR); + expect(textureSource.magFilter).to.equal(glContext.LINEAR); + expect(textureSource.wrapS).to.equal(glContext.REPEAT); + expect(textureSource.wrapT).to.equal(glContext.MIRRORED_REPEAT); + expect(textureSource.flipY).to.be.true; + expect(testTexture).to.be.an.instanceOf(Texture); + expect(testTexture.width).to.be.a('number'); + expect(testTexture.height).to.be.a('number'); + expect(testTexture.id).to.be.a('string'); + }); }); From c14ec30f1be80a84e96ebcef515bedca9b4ff9e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=8C=82=E5=AE=89?= <151805118+wumaolinmaoan@users.noreply.github.com> Date: Tue, 3 Sep 2024 20:07:58 +0800 Subject: [PATCH 04/88] perf: opt CPU particle computing performance (#623) * perf: opt CPU particle computing performance * fix: packaging issues caused by the wrong import of math libraries * fix: speedOverLifetime calculation * fix: particle translation calculation * style: math import --------- Co-authored-by: yiiqii --- .../effects-core/src/math/value-getter.ts | 22 +++++-- .../src/plugins/particle/particle-mesh.ts | 61 +++++++++++++------ web-packages/demo/src/single.ts | 2 +- 3 files changed, 60 insertions(+), 25 deletions(-) diff --git a/packages/effects-core/src/math/value-getter.ts b/packages/effects-core/src/math/value-getter.ts index 25e9fc03..10949832 100644 --- a/packages/effects-core/src/math/value-getter.ts +++ b/packages/effects-core/src/math/value-getter.ts @@ -414,6 +414,8 @@ export class BezierCurve extends ValueGetter { timeInterval: number, valueInterval: number, curve: BezierEasing, + timeStart: number, + timeEnd: number, }>; keys: number[][]; @@ -438,14 +440,20 @@ export class BezierCurve extends ValueGetter { timeInterval, valueInterval, curve, + timeStart:Number(s.x), + timeEnd:Number(e.x), }; } } override getValue (time: number) { let result = 0; const keyTimeData = Object.keys(this.curveMap); - const keyTimeStart = Number(keyTimeData[0].split('&')[0]); - const keyTimeEnd = Number(keyTimeData[keyTimeData.length - 1].split('&')[1]); + + const keyTimeStart = this.curveMap[keyTimeData[0]].timeStart; + const keyTimeEnd = this.curveMap[keyTimeData[keyTimeData.length - 1]].timeEnd; + + // const keyTimeStart = Number(keyTimeData[0].split('&')[0]); + // const keyTimeEnd = Number(keyTimeData[keyTimeData.length - 1].split('&')[1]); if (time <= keyTimeStart) { return this.getCurveValue(keyTimeData[0], keyTimeStart); @@ -455,7 +463,10 @@ export class BezierCurve extends ValueGetter { } for (let i = 0; i < keyTimeData.length; i++) { - const [xMin, xMax] = keyTimeData[i].split('&'); + const xMin = this.curveMap[keyTimeData[i]].timeStart; + const xMax = this.curveMap[keyTimeData[i]].timeEnd; + + // const [xMin, xMax] = keyTimeData[i].split('&'); if (time >= Number(xMin) && time < Number(xMax)) { result = this.getCurveValue(keyTimeData[i], time); @@ -472,13 +483,14 @@ export class BezierCurve extends ValueGetter { let result = 0; const keyTimeData = Object.keys(this.curveMap); - const keyTimeStart = Number(keyTimeData[0].split('&')[0]); + const keyTimeStart = this.curveMap[keyTimeData[0]].timeStart; if (time <= keyTimeStart) { return 0; } for (let i = 0; i < keyTimeData.length; i++) { - const [xMin, xMax] = keyTimeData[i].split('&'); + const xMin = this.curveMap[keyTimeData[i]].timeStart; + const xMax = this.curveMap[keyTimeData[i]].timeEnd; if (time >= Number(xMax)) { result += ts * this.getCurveIntegrateValue(keyTimeData[i], Number(xMax)); diff --git a/packages/effects-core/src/plugins/particle/particle-mesh.ts b/packages/effects-core/src/plugins/particle/particle-mesh.ts index 16284366..06d94ca1 100644 --- a/packages/effects-core/src/plugins/particle/particle-mesh.ts +++ b/packages/effects-core/src/plugins/particle/particle-mesh.ts @@ -447,6 +447,8 @@ export class ParticleMesh implements ParticleMeshData { geometry.setIndexData(new index.constructor(0)); } + cachedVelocity = new Vector3(); + onUpdate (dt: number) { const aPosArray = this.geometry.getAttributeData('aPos') as Float32Array; // vector3 const aVelArray = this.geometry.getAttributeData('aVel') as Float32Array; // vector3 @@ -459,28 +461,35 @@ export class ParticleMesh implements ParticleMeshData { // return; // } - const localTime = new Float32Array([this.time])[0]; + const localTime = this.time; const particleCount = Math.ceil(aPosArray.length / 12); // calculate particle translation let aTranslationArray = this.geometry.getAttributeData('aTranslation') as Float32Array; if (aTranslationArray.length < particleCount * 3) { - aTranslationArray = new Float32Array(particleCount * 3); + aTranslationArray = this.expandArray(aTranslationArray, particleCount * 3); } - const velocity = new Vector3(0, 0, 0); + const velocity = this.cachedVelocity; for (let i = 0; i < particleCount; i++) { const velOffset = i * 12 + 3; velocity.set(aVelArray[velOffset], aVelArray[velOffset + 1], aVelArray[velOffset + 2]); - - const trans = this.calculateTranslation(velocity, aOffsetArray[i * 4 + 2], localTime, aOffsetArray[i * 4 + 3]); + this.calculateTranslation(velocity, aOffsetArray[i * 4 + 2], localTime, aOffsetArray[i * 4 + 3]); const aTranslationOffset = i * 3; - aTranslationArray[aTranslationOffset] = trans.x; - aTranslationArray[aTranslationOffset + 1] = trans.y; - aTranslationArray[aTranslationOffset + 2] = trans.z; + // aVelArray[velOffset] = velocity.x; + // aVelArray[velOffset + 1] = velocity.y; + // aVelArray[velOffset + 2] = velocity.z; + + if (aOffsetArray[i * 4 + 2] < localTime) { + const translation = velocity.multiply(dt / 1000); + + aTranslationArray[aTranslationOffset] += translation.x; + aTranslationArray[aTranslationOffset + 1] += translation.y; + aTranslationArray[aTranslationOffset + 2] += translation.z; + } } this.geometry.setAttributeData('aTranslation', aTranslationArray); @@ -488,9 +497,11 @@ export class ParticleMesh implements ParticleMeshData { let aRotationArray = this.geometry.getAttributeData('aRotation0') as Float32Array; if (aRotationArray.length < particleCount * 9) { - aRotationArray = new Float32Array(particleCount * 9); + aRotationArray = this.expandArray(aRotationArray, particleCount * 9); } + // const aRotationTemp = new math.Matrix3().identity(); + for (let i = 0; i < particleCount; i++) { const time = localTime - aOffsetArray[i * 4 + 2]; const duration = aOffsetArray[i * 4 + 3]; @@ -500,6 +511,7 @@ export class ParticleMesh implements ParticleMeshData { const aSeed = aSeedArray[i * 8 + 3]; const aRotation = this.transformFromRotation(aRot, life, duration, aSeed); + // const aRotation = aRotationTemp; const aRotationOffset = i * 9; const matrixArray = aRotation.toArray(); @@ -513,16 +525,18 @@ export class ParticleMesh implements ParticleMeshData { let aLinearMoveArray = this.geometry.getAttributeData('aLinearMove') as Float32Array; if (aLinearMoveArray.length < particleCount * 3) { - aLinearMoveArray = new Float32Array(particleCount * 3); + aLinearMoveArray = this.expandArray(aLinearMoveArray, particleCount * 3); } + const linearMove = new Vector3(); + for (let i = 0; i < particleCount; i++) { const time = localTime - aOffsetArray[i * 4 + 2]; const duration = aOffsetArray[i * 4 + 3]; // const life = math.clamp(time / duration, 0.0, 1.0); const aSeed = aSeedArray[i * 8 + 3]; - const linearMove = this.calLinearMov(time, duration, aSeed); + this.calLinearMov(time, duration, aSeed, linearMove); const aLinearMoveOffset = i * 3; aLinearMoveArray[aLinearMoveOffset] = linearMove.x; @@ -547,10 +561,10 @@ export class ParticleMesh implements ParticleMeshData { const uGravityModifierValue = this.mesh.material.getVector4('uGravityModifierValue'); if (!uAcceleration || !uGravityModifierValue) { - return new Vector3(); + return velocity; } const dt = t1 - t0; // 相对delay的时间 - const d = this.gravityModifier.getIntegrateByTime(0, dt); + const d = this.gravityModifier.getIntegrateValue(0, dt, duration); const acc: spec.vec3 = [uAcceleration.x * d, uAcceleration.y * d, uAcceleration.z * d]; // ret.addScaledVector(velData, speedIntegrate); @@ -558,12 +572,12 @@ export class ParticleMesh implements ParticleMeshData { // speedIntegrate = speedOverLifetime.getIntegrateValue(0, time, duration); if (this.speedOverLifetime) { // dt / dur 归一化 - const speed = this.speedOverLifetime.getIntegrateValue(0, dt, duration); + const speed = this.speedOverLifetime.getValue(dt / duration); - return velocity.clone().multiply(speed).add(acc); + return velocity.multiply(speed).add(acc); } - return velocity.clone().multiply(dt).add(acc); + return velocity.add(acc); } transformFromRotation (rot: Vector3, life: number, dur: number, aSeed: number): Matrix3 { @@ -651,12 +665,12 @@ export class ParticleMesh implements ParticleMeshData { return result; } - calLinearMov (time: number, duration: number, aSeed: number): Vector3 { - const mov = new Vector3(); + calLinearMov (time: number, duration: number, aSeed: number, res: Vector3): Vector3 { + const mov = res; const lifetime = time / duration; if (!this.linearVelOverLifetime || !this.linearVelOverLifetime.enabled) { - return new Vector3(); + return mov; } if (this.linearVelOverLifetime.asMovement) { if (this.linearVelOverLifetime.x) { @@ -735,6 +749,7 @@ export class ParticleMesh implements ParticleMeshData { aPos: new Float32Array(48), aRot: new Float32Array(32), aOffset: new Float32Array(16), + aTranslation: new Float32Array(12), }; const useSprite = this.useSprite; @@ -834,6 +849,14 @@ export class ParticleMesh implements ParticleMeshData { geometry.setDrawCount(this.particleCount * 6); } } + + private expandArray (array: Float32Array, newSize: number): Float32Array { + const newArr = new Float32Array(newSize); + + newArr.set(array); + + return newArr; + } } const gl2UniformSlots = [10, 32, 64, 160]; diff --git a/web-packages/demo/src/single.ts b/web-packages/demo/src/single.ts index 27a25cc0..df11e0f5 100644 --- a/web-packages/demo/src/single.ts +++ b/web-packages/demo/src/single.ts @@ -1,7 +1,7 @@ import { Player } from '@galacean/effects'; import '@galacean/effects-plugin-spine'; -const json = 'https://mdn.alipayobjects.com/mars/afts/file/A*aCeuQ5RQZj4AAAAAAAAAAAAADlB4AQ'; +const json = 'https://mdn.alipayobjects.com/mars/afts/file/A*WXEqSadfOKcAAAAAAAAAAAAADlB4AQ'; const container = document.getElementById('J-container'); (async () => { From a1b076284a720f5103cdf7b720c01de9518bae55 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=8C=82=E5=AE=89?= <151805118+wumaolinmaoan@users.noreply.github.com> Date: Thu, 5 Sep 2024 09:26:46 +0800 Subject: [PATCH 05/88] perf: reduce object gc during particle update (#627) * perf: opt particle gc on update * style: rename --- .../src/plugins/particle/particle-mesh.ts | 68 +++++++++---------- 1 file changed, 33 insertions(+), 35 deletions(-) diff --git a/packages/effects-core/src/plugins/particle/particle-mesh.ts b/packages/effects-core/src/plugins/particle/particle-mesh.ts index 06d94ca1..54b9fe40 100644 --- a/packages/effects-core/src/plugins/particle/particle-mesh.ts +++ b/packages/effects-core/src/plugins/particle/particle-mesh.ts @@ -135,6 +135,13 @@ export class ParticleMesh implements ParticleMeshData { readonly maxCount: number; readonly anchor: Vector2; + private cachedVelocity = new Vector3(); + private cachedRotationVector3 = new Vector3(); + private cachedRotationMatrix = new Matrix3(); + private cachedLinearMove = new Vector3(); + private tempMatrix3 = new Matrix3(); + private tempVector3 = new Vector3(); + VERT_MAX_KEY_FRAME_COUNT = 0; constructor ( @@ -447,8 +454,6 @@ export class ParticleMesh implements ParticleMeshData { geometry.setIndexData(new index.constructor(0)); } - cachedVelocity = new Vector3(); - onUpdate (dt: number) { const aPosArray = this.geometry.getAttributeData('aPos') as Float32Array; // vector3 const aVelArray = this.geometry.getAttributeData('aVel') as Float32Array; // vector3 @@ -479,10 +484,6 @@ export class ParticleMesh implements ParticleMeshData { this.calculateTranslation(velocity, aOffsetArray[i * 4 + 2], localTime, aOffsetArray[i * 4 + 3]); const aTranslationOffset = i * 3; - // aVelArray[velOffset] = velocity.x; - // aVelArray[velOffset + 1] = velocity.y; - // aVelArray[velOffset + 2] = velocity.z; - if (aOffsetArray[i * 4 + 2] < localTime) { const translation = velocity.multiply(dt / 1000); @@ -500,18 +501,15 @@ export class ParticleMesh implements ParticleMeshData { aRotationArray = this.expandArray(aRotationArray, particleCount * 9); } - // const aRotationTemp = new math.Matrix3().identity(); - for (let i = 0; i < particleCount; i++) { const time = localTime - aOffsetArray[i * 4 + 2]; const duration = aOffsetArray[i * 4 + 3]; const life = clamp(time / duration, 0.0, 1.0); const aRotOffset = i * 8; - const aRot = new Vector3(aRotArray[aRotOffset], aRotArray[aRotOffset + 1], aRotArray[aRotOffset + 2]); + const aRot = this.cachedRotationVector3.set(aRotArray[aRotOffset], aRotArray[aRotOffset + 1], aRotArray[aRotOffset + 2]); const aSeed = aSeedArray[i * 8 + 3]; - const aRotation = this.transformFromRotation(aRot, life, duration, aSeed); - // const aRotation = aRotationTemp; + const aRotation = this.transformFromRotation(aRot, life, duration, aSeed, this.cachedRotationMatrix); const aRotationOffset = i * 9; const matrixArray = aRotation.toArray(); @@ -528,7 +526,7 @@ export class ParticleMesh implements ParticleMeshData { aLinearMoveArray = this.expandArray(aLinearMoveArray, particleCount * 3); } - const linearMove = new Vector3(); + const linearMove = this.cachedLinearMove; for (let i = 0; i < particleCount; i++) { const time = localTime - aOffsetArray[i * 4 + 2]; @@ -536,6 +534,7 @@ export class ParticleMesh implements ParticleMeshData { // const life = math.clamp(time / duration, 0.0, 1.0); const aSeed = aSeedArray[i * 8 + 3]; + linearMove.setZero(); this.calLinearMov(time, duration, aSeed, linearMove); const aLinearMoveOffset = i * 3; @@ -565,10 +564,8 @@ export class ParticleMesh implements ParticleMeshData { } const dt = t1 - t0; // 相对delay的时间 const d = this.gravityModifier.getIntegrateValue(0, dt, duration); - const acc: spec.vec3 = [uAcceleration.x * d, uAcceleration.y * d, uAcceleration.z * d]; + const acc = this.tempVector3.set(uAcceleration.x * d, uAcceleration.y * d, uAcceleration.z * d); - // ret.addScaledVector(velData, speedIntegrate); - // ret.addScaledVector(acc, d); // speedIntegrate = speedOverLifetime.getIntegrateValue(0, time, duration); if (this.speedOverLifetime) { // dt / dur 归一化 @@ -580,11 +577,11 @@ export class ParticleMesh implements ParticleMeshData { return velocity.add(acc); } - transformFromRotation (rot: Vector3, life: number, dur: number, aSeed: number): Matrix3 { - const rotation = rot.clone(); + transformFromRotation (rot: Vector3, life: number, dur: number, aSeed: number, result: Matrix3): Matrix3 { + const rotation = rot; if (!this.rotationOverLifetime) { - return new Matrix3(); + return result.setZero(); } if (this.rotationOverLifetime.asRotation) { @@ -637,36 +634,37 @@ export class ParticleMesh implements ParticleMeshData { // If the rotation vector is zero, return the identity matrix if (rotation.dot(rotation) === 0.0) { - return new Matrix3().identity(); + return result.identity(); } // Return the rotation matrix derived from the rotation vector - return this.mat3FromRotation(rotation); + return this.mat3FromRotation(rotation, result); } - mat3FromRotation (rotation: Vector3): Matrix3 { + mat3FromRotation (rotation: Vector3, result: Matrix3): Matrix3 { const d2r = Math.PI / 180; - const sinR = rotation.clone().multiply(d2r); + const rotationXD2r = rotation.x * d2r; + const rotationYD2r = rotation.y * d2r; + const rotationZD2r = rotation.z * d2r; - sinR.x = Math.sin(sinR.x); - sinR.y = Math.sin(sinR.y); - sinR.z = Math.sin(sinR.z); - const cosR = rotation.clone().multiply(d2r); + const sinRX = Math.sin(rotationXD2r); + const sinRY = Math.sin(rotationYD2r); + const sinRZ = Math.sin(rotationZD2r); - cosR.x = Math.cos(cosR.x); - cosR.y = Math.cos(cosR.y); - cosR.z = Math.cos(cosR.z); + const cosRX = Math.cos(rotationXD2r); + const cosRY = Math.cos(rotationYD2r); + const cosRZ = Math.cos(rotationZD2r); - const rotZ = new Matrix3(cosR.z, -sinR.z, 0., sinR.z, cosR.z, 0., 0., 0., 1.); - const rotY = new Matrix3(cosR.y, 0., sinR.y, 0., 1., 0., -sinR.y, 0, cosR.y); - const rotX = new Matrix3(1., 0., 0., 0, cosR.x, -sinR.x, 0., sinR.x, cosR.x); - const result = rotZ.multiply(rotY).multiply(rotX); + // rotZ * rotY * rotX + result.set(cosRZ, -sinRZ, 0., sinRZ, cosRZ, 0., 0., 0., 1.); //rotZ + result.multiply(this.tempMatrix3.set(cosRY, 0., sinRY, 0., 1., 0., -sinRY, 0, cosRY)); //rotY + result.multiply(this.tempMatrix3.set(1., 0., 0., 0, cosRX, -sinRX, 0., sinRX, cosRX)); //rotX return result; } - calLinearMov (time: number, duration: number, aSeed: number, res: Vector3): Vector3 { - const mov = res; + calLinearMov (time: number, duration: number, aSeed: number, result: Vector3): Vector3 { + const mov = result; const lifetime = time / duration; if (!this.linearVelOverLifetime || !this.linearVelOverLifetime.enabled) { From 7414a316554576d59883bded3e9a07569b35b318 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=8C=82=E5=AE=89?= <151805118+wumaolinmaoan@users.noreply.github.com> Date: Mon, 9 Sep 2024 12:56:09 +0800 Subject: [PATCH 06/88] refactor: expand particle calculation function (#632) * perf: opt particle calculation performance * style: opt code --- .../src/plugins/particle/particle-mesh.ts | 502 +++++++++--------- .../src/plugins/particle/particle-system.ts | 4 +- 2 files changed, 252 insertions(+), 254 deletions(-) diff --git a/packages/effects-core/src/plugins/particle/particle-mesh.ts b/packages/effects-core/src/plugins/particle/particle-mesh.ts index 54b9fe40..24824283 100644 --- a/packages/effects-core/src/plugins/particle/particle-mesh.ts +++ b/packages/effects-core/src/plugins/particle/particle-mesh.ts @@ -135,12 +135,10 @@ export class ParticleMesh implements ParticleMeshData { readonly maxCount: number; readonly anchor: Vector2; - private cachedVelocity = new Vector3(); private cachedRotationVector3 = new Vector3(); private cachedRotationMatrix = new Matrix3(); private cachedLinearMove = new Vector3(); private tempMatrix3 = new Matrix3(); - private tempVector3 = new Vector3(); VERT_MAX_KEY_FRAME_COUNT = 0; @@ -456,93 +454,11 @@ export class ParticleMesh implements ParticleMeshData { onUpdate (dt: number) { const aPosArray = this.geometry.getAttributeData('aPos') as Float32Array; // vector3 - const aVelArray = this.geometry.getAttributeData('aVel') as Float32Array; // vector3 - const aOffsetArray = this.geometry.getAttributeData('aOffset') as Float32Array; - const aRotArray = this.geometry.getAttributeData('aRot') as Float32Array; // vector3 - const aSeedArray = this.geometry.getAttributeData('aSeed') as Float32Array; // float - // const uParams = this.mesh.material.getVector4('uParams'); - - // if (!uParams) { - // return; - // } - - const localTime = this.time; const particleCount = Math.ceil(aPosArray.length / 12); - // calculate particle translation - let aTranslationArray = this.geometry.getAttributeData('aTranslation') as Float32Array; - - if (aTranslationArray.length < particleCount * 3) { - aTranslationArray = this.expandArray(aTranslationArray, particleCount * 3); - } - const velocity = this.cachedVelocity; - - for (let i = 0; i < particleCount; i++) { - const velOffset = i * 12 + 3; - - velocity.set(aVelArray[velOffset], aVelArray[velOffset + 1], aVelArray[velOffset + 2]); - this.calculateTranslation(velocity, aOffsetArray[i * 4 + 2], localTime, aOffsetArray[i * 4 + 3]); - const aTranslationOffset = i * 3; - - if (aOffsetArray[i * 4 + 2] < localTime) { - const translation = velocity.multiply(dt / 1000); - - aTranslationArray[aTranslationOffset] += translation.x; - aTranslationArray[aTranslationOffset + 1] += translation.y; - aTranslationArray[aTranslationOffset + 2] += translation.z; - } - } - this.geometry.setAttributeData('aTranslation', aTranslationArray); - - // calculate particle rotation - let aRotationArray = this.geometry.getAttributeData('aRotation0') as Float32Array; - - if (aRotationArray.length < particleCount * 9) { - aRotationArray = this.expandArray(aRotationArray, particleCount * 9); - } - - for (let i = 0; i < particleCount; i++) { - const time = localTime - aOffsetArray[i * 4 + 2]; - const duration = aOffsetArray[i * 4 + 3]; - const life = clamp(time / duration, 0.0, 1.0); - const aRotOffset = i * 8; - const aRot = this.cachedRotationVector3.set(aRotArray[aRotOffset], aRotArray[aRotOffset + 1], aRotArray[aRotOffset + 2]); - const aSeed = aSeedArray[i * 8 + 3]; - - const aRotation = this.transformFromRotation(aRot, life, duration, aSeed, this.cachedRotationMatrix); - const aRotationOffset = i * 9; - - const matrixArray = aRotation.toArray(); - - aRotationArray.set(matrixArray, aRotationOffset); - } - - this.geometry.setAttributeData('aRotation0', aRotationArray); - - // calculate linear movement - let aLinearMoveArray = this.geometry.getAttributeData('aLinearMove') as Float32Array; - - if (aLinearMoveArray.length < particleCount * 3) { - aLinearMoveArray = this.expandArray(aLinearMoveArray, particleCount * 3); - } - - const linearMove = this.cachedLinearMove; - - for (let i = 0; i < particleCount; i++) { - const time = localTime - aOffsetArray[i * 4 + 2]; - const duration = aOffsetArray[i * 4 + 3]; - // const life = math.clamp(time / duration, 0.0, 1.0); - const aSeed = aSeedArray[i * 8 + 3]; - - linearMove.setZero(); - this.calLinearMov(time, duration, aSeed, linearMove); - const aLinearMoveOffset = i * 3; - - aLinearMoveArray[aLinearMoveOffset] = linearMove.x; - aLinearMoveArray[aLinearMoveOffset + 1] = linearMove.y; - aLinearMoveArray[aLinearMoveOffset + 2] = linearMove.z; - } - this.geometry.setAttributeData('aLinearMove', aLinearMoveArray); + this.applyTranslation(particleCount, dt); + this.applyRotation(particleCount, dt); + this.applyLinearMove(particleCount, dt); } minusTime (time: number) { @@ -555,171 +471,6 @@ export class ParticleMesh implements ParticleMeshData { this.time -= time; } - calculateTranslation (velocity: Vector3, t0: number, t1: number, duration: number): Vector3 { - const uAcceleration = this.mesh.material.getVector4('uAcceleration'); - const uGravityModifierValue = this.mesh.material.getVector4('uGravityModifierValue'); - - if (!uAcceleration || !uGravityModifierValue) { - return velocity; - } - const dt = t1 - t0; // 相对delay的时间 - const d = this.gravityModifier.getIntegrateValue(0, dt, duration); - const acc = this.tempVector3.set(uAcceleration.x * d, uAcceleration.y * d, uAcceleration.z * d); - - // speedIntegrate = speedOverLifetime.getIntegrateValue(0, time, duration); - if (this.speedOverLifetime) { - // dt / dur 归一化 - const speed = this.speedOverLifetime.getValue(dt / duration); - - return velocity.multiply(speed).add(acc); - } - - return velocity.add(acc); - } - - transformFromRotation (rot: Vector3, life: number, dur: number, aSeed: number, result: Matrix3): Matrix3 { - const rotation = rot; - - if (!this.rotationOverLifetime) { - return result.setZero(); - } - - if (this.rotationOverLifetime.asRotation) { - // Adjust rotation based on the specified lifetime components - if (this.rotationOverLifetime.x) { - if (this.rotationOverLifetime.x instanceof RandomValue) { - rotation.x += this.rotationOverLifetime.x.getValue(life, aSeed); - } else { - rotation.x += this.rotationOverLifetime.x.getValue(life); - } - } - if (this.rotationOverLifetime.y) { - if (this.rotationOverLifetime.y instanceof RandomValue) { - rotation.y += this.rotationOverLifetime.y.getValue(life, aSeed); - } else { - rotation.y += this.rotationOverLifetime.y.getValue(life); - } - } - if (this.rotationOverLifetime.z) { - if (this.rotationOverLifetime.z instanceof RandomValue) { - rotation.z += this.rotationOverLifetime.z.getValue(life, aSeed); - } else { - rotation.z += this.rotationOverLifetime.z.getValue(life); - } - } - } else { - // Adjust rotation based on the specified lifetime components - if (this.rotationOverLifetime.x) { - if (this.rotationOverLifetime.x instanceof RandomValue) { - rotation.x += this.rotationOverLifetime.x.getIntegrateValue(0.0, life, aSeed) * dur; - } else { - rotation.x += this.rotationOverLifetime.x.getIntegrateValue(0.0, life, dur) * dur; - } - } - if (this.rotationOverLifetime.y) { - if (this.rotationOverLifetime.y instanceof RandomValue) { - rotation.y += this.rotationOverLifetime.y.getIntegrateValue(0.0, life, aSeed) * dur; - } else { - rotation.y += this.rotationOverLifetime.y.getIntegrateValue(0.0, life, dur) * dur; - } - } - if (this.rotationOverLifetime.z) { - if (this.rotationOverLifetime.z instanceof RandomValue) { - rotation.z += this.rotationOverLifetime.z.getIntegrateValue(0.0, life, aSeed) * dur; - } else { - rotation.z += this.rotationOverLifetime.z.getIntegrateValue(0.0, life, dur) * dur; - } - } - } - - // If the rotation vector is zero, return the identity matrix - if (rotation.dot(rotation) === 0.0) { - return result.identity(); - } - - // Return the rotation matrix derived from the rotation vector - return this.mat3FromRotation(rotation, result); - } - - mat3FromRotation (rotation: Vector3, result: Matrix3): Matrix3 { - const d2r = Math.PI / 180; - const rotationXD2r = rotation.x * d2r; - const rotationYD2r = rotation.y * d2r; - const rotationZD2r = rotation.z * d2r; - - const sinRX = Math.sin(rotationXD2r); - const sinRY = Math.sin(rotationYD2r); - const sinRZ = Math.sin(rotationZD2r); - - const cosRX = Math.cos(rotationXD2r); - const cosRY = Math.cos(rotationYD2r); - const cosRZ = Math.cos(rotationZD2r); - - // rotZ * rotY * rotX - result.set(cosRZ, -sinRZ, 0., sinRZ, cosRZ, 0., 0., 0., 1.); //rotZ - result.multiply(this.tempMatrix3.set(cosRY, 0., sinRY, 0., 1., 0., -sinRY, 0, cosRY)); //rotY - result.multiply(this.tempMatrix3.set(1., 0., 0., 0, cosRX, -sinRX, 0., sinRX, cosRX)); //rotX - - return result; - } - - calLinearMov (time: number, duration: number, aSeed: number, result: Vector3): Vector3 { - const mov = result; - const lifetime = time / duration; - - if (!this.linearVelOverLifetime || !this.linearVelOverLifetime.enabled) { - return mov; - } - if (this.linearVelOverLifetime.asMovement) { - if (this.linearVelOverLifetime.x) { - if (this.linearVelOverLifetime.x instanceof RandomValue) { - mov.x = this.linearVelOverLifetime.x.getValue(lifetime, aSeed); - } else { - mov.x = this.linearVelOverLifetime.x.getValue(lifetime); - } - } - if (this.linearVelOverLifetime.y) { - if (this.linearVelOverLifetime.y instanceof RandomValue) { - mov.y = this.linearVelOverLifetime.y.getValue(lifetime, aSeed); - } else { - mov.y = this.linearVelOverLifetime.y.getValue(lifetime); - } - } - if (this.linearVelOverLifetime.z) { - if (this.linearVelOverLifetime.z instanceof RandomValue) { - mov.z = this.linearVelOverLifetime.z.getValue(lifetime, aSeed); - } else { - mov.z = this.linearVelOverLifetime.z.getValue(lifetime); - } - } - } else { - // Adjust rotation based on the specified lifetime components - if (this.linearVelOverLifetime.x) { - if (this.linearVelOverLifetime.x instanceof RandomValue) { - mov.x = this.linearVelOverLifetime.x.getIntegrateValue(0.0, time, aSeed); - } else { - mov.x = this.linearVelOverLifetime.x.getIntegrateValue(0.0, time, duration); - } - } - if (this.linearVelOverLifetime.y) { - if (this.linearVelOverLifetime.y instanceof RandomValue) { - mov.y = this.linearVelOverLifetime.y.getIntegrateValue(0.0, time, aSeed); - } else { - mov.y = this.linearVelOverLifetime.y.getIntegrateValue(0.0, time, duration); - } - } - if (this.linearVelOverLifetime.z) { - if (this.linearVelOverLifetime.z instanceof RandomValue) { - mov.z = this.linearVelOverLifetime.z.getIntegrateValue(0.0, time, aSeed); - } else { - mov.z = this.linearVelOverLifetime.z.getIntegrateValue(0.0, time, duration); - } - } - } - - return mov; - } - removePoint (index: number) { if (index < this.particleCount) { this.geometry.setAttributeSubData('aOffset', index * 16, new Float32Array(16)); @@ -848,6 +599,253 @@ export class ParticleMesh implements ParticleMeshData { } } + private applyTranslation (particleCount: number, deltaTime: number) { + const localTime = this.time; + let aTranslationArray = this.geometry.getAttributeData('aTranslation') as Float32Array; + const aVelArray = this.geometry.getAttributeData('aVel') as Float32Array; // vector3 + const aOffsetArray = this.geometry.getAttributeData('aOffset') as Float32Array; + + if (aTranslationArray.length < particleCount * 3) { + aTranslationArray = this.expandArray(aTranslationArray, particleCount * 3); + } + // const velocity = this.cachedVelocity; + let velocityX = 0; + let velocityY = 0; + let velocityZ = 0; + const uAcceleration = this.mesh.material.getVector4('uAcceleration'); + const uGravityModifierValue = this.mesh.material.getVector4('uGravityModifierValue'); + + for (let i = 0; i < particleCount; i++) { + const velOffset = i * 12 + 3; + + velocityX = aVelArray[velOffset]; + velocityY = aVelArray[velOffset + 1]; + velocityZ = aVelArray[velOffset + 2]; + // velocity.set(aVelArray[velOffset], aVelArray[velOffset + 1], aVelArray[velOffset + 2]); + const dt = localTime - aOffsetArray[i * 4 + 2];// 相对delay的时间 + const duration = aOffsetArray[i * 4 + 3]; + + if (uAcceleration && uGravityModifierValue) { + const d = this.gravityModifier.getIntegrateValue(0, dt, duration); + // const acc = this.tempVector3.set(uAcceleration.x * d, uAcceleration.y * d, uAcceleration.z * d); + const accX = uAcceleration.x * d; + const accY = uAcceleration.y * d; + const accZ = uAcceleration.z * d; + + // speedIntegrate = speedOverLifetime.getIntegrateValue(0, time, duration); + if (this.speedOverLifetime) { + // dt / dur 归一化 + const speed = this.speedOverLifetime.getValue(dt / duration); + + velocityX = velocityX * speed + accX; + velocityY = velocityY * speed + accY; + velocityZ = velocityZ * speed + accZ; + // velocity.multiply(speed).add(acc); + } else { + velocityX = velocityX + accX; + velocityY = velocityY + accY; + velocityZ = velocityZ + accZ; + // velocity.add(acc); + } + } + + const aTranslationOffset = i * 3; + + if (aOffsetArray[i * 4 + 2] < localTime) { + // const translation = velocity.multiply(deltaTime / 1000); + + aTranslationArray[aTranslationOffset] += velocityX * (deltaTime / 1000); + aTranslationArray[aTranslationOffset + 1] += velocityY * (deltaTime / 1000); + aTranslationArray[aTranslationOffset + 2] += velocityZ * (deltaTime / 1000); + } + } + this.geometry.setAttributeData('aTranslation', aTranslationArray); + } + + private applyRotation (particleCount: number, deltaTime: number) { + let aRotationArray = this.geometry.getAttributeData('aRotation0') as Float32Array; + const aOffsetArray = this.geometry.getAttributeData('aOffset') as Float32Array; + const aRotArray = this.geometry.getAttributeData('aRot') as Float32Array; // vector3 + const aSeedArray = this.geometry.getAttributeData('aSeed') as Float32Array; // float + const localTime = this.time; + const aRotationMatrix = this.cachedRotationMatrix; + + if (aRotationArray.length < particleCount * 9) { + aRotationArray = this.expandArray(aRotationArray, particleCount * 9); + } + + for (let i = 0; i < particleCount; i++) { + const time = localTime - aOffsetArray[i * 4 + 2]; + const duration = aOffsetArray[i * 4 + 3]; + const life = clamp(time / duration, 0.0, 1.0); + const aRotOffset = i * 8; + const aRot = this.cachedRotationVector3.set(aRotArray[aRotOffset], aRotArray[aRotOffset + 1], aRotArray[aRotOffset + 2]); + const aSeed = aSeedArray[i * 8 + 3]; + + const rotation = aRot; + + if (!this.rotationOverLifetime) { + aRotationMatrix.setZero(); + } else if (this.rotationOverLifetime.asRotation) { + // Adjust rotation based on the specified lifetime components + if (this.rotationOverLifetime.x) { + if (this.rotationOverLifetime.x instanceof RandomValue) { + rotation.x += this.rotationOverLifetime.x.getValue(life, aSeed); + } else { + rotation.x += this.rotationOverLifetime.x.getValue(life); + } + } + if (this.rotationOverLifetime.y) { + if (this.rotationOverLifetime.y instanceof RandomValue) { + rotation.y += this.rotationOverLifetime.y.getValue(life, aSeed); + } else { + rotation.y += this.rotationOverLifetime.y.getValue(life); + } + } + if (this.rotationOverLifetime.z) { + if (this.rotationOverLifetime.z instanceof RandomValue) { + rotation.z += this.rotationOverLifetime.z.getValue(life, aSeed); + } else { + rotation.z += this.rotationOverLifetime.z.getValue(life); + } + } + } else { + // Adjust rotation based on the specified lifetime components + if (this.rotationOverLifetime.x) { + if (this.rotationOverLifetime.x instanceof RandomValue) { + rotation.x += this.rotationOverLifetime.x.getIntegrateValue(0.0, life, aSeed) * duration; + } else { + rotation.x += this.rotationOverLifetime.x.getIntegrateValue(0.0, life, duration) * duration; + } + } + if (this.rotationOverLifetime.y) { + if (this.rotationOverLifetime.y instanceof RandomValue) { + rotation.y += this.rotationOverLifetime.y.getIntegrateValue(0.0, life, aSeed) * duration; + } else { + rotation.y += this.rotationOverLifetime.y.getIntegrateValue(0.0, life, duration) * duration; + } + } + if (this.rotationOverLifetime.z) { + if (this.rotationOverLifetime.z instanceof RandomValue) { + rotation.z += this.rotationOverLifetime.z.getIntegrateValue(0.0, life, aSeed) * duration; + } else { + rotation.z += this.rotationOverLifetime.z.getIntegrateValue(0.0, life, duration) * duration; + } + } + } + + // If the rotation vector is zero, return the identity matrix + if (rotation.dot(rotation) === 0.0) { + aRotationMatrix.identity(); + } + + const d2r = Math.PI / 180; + const rotationXD2r = rotation.x * d2r; + const rotationYD2r = rotation.y * d2r; + const rotationZD2r = rotation.z * d2r; + + const sinRX = Math.sin(rotationXD2r); + const sinRY = Math.sin(rotationYD2r); + const sinRZ = Math.sin(rotationZD2r); + + const cosRX = Math.cos(rotationXD2r); + const cosRY = Math.cos(rotationYD2r); + const cosRZ = Math.cos(rotationZD2r); + + // rotZ * rotY * rotX + aRotationMatrix.set(cosRZ, -sinRZ, 0., sinRZ, cosRZ, 0., 0., 0., 1.); //rotZ + aRotationMatrix.multiply(this.tempMatrix3.set(cosRY, 0., sinRY, 0., 1., 0., -sinRY, 0, cosRY)); //rotY + aRotationMatrix.multiply(this.tempMatrix3.set(1., 0., 0., 0, cosRX, -sinRX, 0., sinRX, cosRX)); //rotX + + const aRotationOffset = i * 9; + const matrixArray = aRotationMatrix.elements; + + aRotationArray.set(matrixArray, aRotationOffset); + } + + this.geometry.setAttributeData('aRotation0', aRotationArray); + } + + private applyLinearMove (particleCount: number, deltaTime: number) { + let aLinearMoveArray = this.geometry.getAttributeData('aLinearMove') as Float32Array; + const aOffsetArray = this.geometry.getAttributeData('aOffset') as Float32Array; + const aSeedArray = this.geometry.getAttributeData('aSeed') as Float32Array; // float + const localTime = this.time; + + if (aLinearMoveArray.length < particleCount * 3) { + aLinearMoveArray = this.expandArray(aLinearMoveArray, particleCount * 3); + } + + const linearMove = this.cachedLinearMove; + + for (let i = 0; i < particleCount; i++) { + const time = localTime - aOffsetArray[i * 4 + 2]; + const duration = aOffsetArray[i * 4 + 3]; + // const life = math.clamp(time / duration, 0.0, 1.0); + const aSeed = aSeedArray[i * 8 + 3]; + + linearMove.setZero(); + + const lifetime = time / duration; + + if (!this.linearVelOverLifetime || !this.linearVelOverLifetime.enabled) { + return linearMove; + } + if (this.linearVelOverLifetime.asMovement) { + if (this.linearVelOverLifetime.x) { + if (this.linearVelOverLifetime.x instanceof RandomValue) { + linearMove.x = this.linearVelOverLifetime.x.getValue(lifetime, aSeed); + } else { + linearMove.x = this.linearVelOverLifetime.x.getValue(lifetime); + } + } + if (this.linearVelOverLifetime.y) { + if (this.linearVelOverLifetime.y instanceof RandomValue) { + linearMove.y = this.linearVelOverLifetime.y.getValue(lifetime, aSeed); + } else { + linearMove.y = this.linearVelOverLifetime.y.getValue(lifetime); + } + } + if (this.linearVelOverLifetime.z) { + if (this.linearVelOverLifetime.z instanceof RandomValue) { + linearMove.z = this.linearVelOverLifetime.z.getValue(lifetime, aSeed); + } else { + linearMove.z = this.linearVelOverLifetime.z.getValue(lifetime); + } + } + } else { + // Adjust rotation based on the specified lifetime components + if (this.linearVelOverLifetime.x) { + if (this.linearVelOverLifetime.x instanceof RandomValue) { + linearMove.x = this.linearVelOverLifetime.x.getIntegrateValue(0.0, time, aSeed); + } else { + linearMove.x = this.linearVelOverLifetime.x.getIntegrateValue(0.0, time, duration); + } + } + if (this.linearVelOverLifetime.y) { + if (this.linearVelOverLifetime.y instanceof RandomValue) { + linearMove.y = this.linearVelOverLifetime.y.getIntegrateValue(0.0, time, aSeed); + } else { + linearMove.y = this.linearVelOverLifetime.y.getIntegrateValue(0.0, time, duration); + } + } + if (this.linearVelOverLifetime.z) { + if (this.linearVelOverLifetime.z instanceof RandomValue) { + linearMove.z = this.linearVelOverLifetime.z.getIntegrateValue(0.0, time, aSeed); + } else { + linearMove.z = this.linearVelOverLifetime.z.getIntegrateValue(0.0, time, duration); + } + } + } + const aLinearMoveOffset = i * 3; + + aLinearMoveArray[aLinearMoveOffset] = linearMove.x; + aLinearMoveArray[aLinearMoveOffset + 1] = linearMove.y; + aLinearMoveArray[aLinearMoveOffset + 2] = linearMove.z; + } + this.geometry.setAttributeData('aLinearMove', aLinearMoveArray); + } + private expandArray (array: Float32Array, newSize: number): Float32Array { const newArr = new Float32Array(newSize); diff --git a/packages/effects-core/src/plugins/particle/particle-system.ts b/packages/effects-core/src/plugins/particle/particle-system.ts index 2c20f4a7..014db8cb 100644 --- a/packages/effects-core/src/plugins/particle/particle-system.ts +++ b/packages/effects-core/src/plugins/particle/particle-system.ts @@ -8,7 +8,7 @@ import type { Engine } from '../../engine'; import type { ValueGetter } from '../../math'; import { calculateTranslation, createValueGetter, ensureVec3 } from '../../math'; import type { Mesh } from '../../render'; -import type { ShapeGenerator, ShapeGeneratorOptions } from '../../shape'; +import type { ShapeGenerator, ShapeGeneratorOptions, ShapeParticle } from '../../shape'; import { createShape } from '../../shape'; import { Texture } from '../../texture'; import { Transform } from '../../transform'; @@ -653,7 +653,7 @@ export class ParticleSystem extends Component { return ret; } - initPoint (data: Record): Point { + initPoint (data: ShapeParticle): Point { const options = this.options; const lifetime = this.lifetime; const shape = this.shape; From 0f2426b11115a251d70bb06554f6653d3d062648 Mon Sep 17 00:00:00 2001 From: wumaolinmaoan Date: Tue, 10 Sep 2024 13:09:28 +0800 Subject: [PATCH 07/88] fix: player adds composition timing --- packages/effects/src/player.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/effects/src/player.ts b/packages/effects/src/player.ts index 52907111..7d47248a 100644 --- a/packages/effects/src/player.ts +++ b/packages/effects/src/player.ts @@ -360,8 +360,6 @@ export class Player extends EventEmitter> implements Disposa }, }, scene); - this.compositions.push(composition); - if (this.ticker) { // 中低端设备降帧到 30fps if (opts.renderLevel === spec.RenderLevel.B) { @@ -400,6 +398,7 @@ export class Player extends EventEmitter> implements Disposa composition.statistic.firstFrameTime = firstFrameTime; logger.info(`First frame: [${composition.name}]${firstFrameTime.toFixed(4)}ms.`); + this.compositions.push(composition); return composition; } From 2c4bddb4c6e8d33b6e1118e3311eb72f676ed787 Mon Sep 17 00:00:00 2001 From: wumaolinmaoan Date: Wed, 11 Sep 2024 14:51:17 +0800 Subject: [PATCH 08/88] feat: effect component uses track time --- packages/effects-core/src/components/effect-component.ts | 8 ++++++++ .../effects-core/src/plugins/cal/calculate-vfx-item.ts | 7 +++++++ packages/effects-core/src/vfx-item.ts | 4 ++++ .../src/object-inspectors/vfx-item-inspector.ts | 2 +- 4 files changed, 20 insertions(+), 1 deletion(-) diff --git a/packages/effects-core/src/components/effect-component.ts b/packages/effects-core/src/components/effect-component.ts index 73bd02ab..36e0c434 100644 --- a/packages/effects-core/src/components/effect-component.ts +++ b/packages/effects-core/src/components/effect-component.ts @@ -11,6 +11,7 @@ import type { MeshDestroyOptions, Renderer } from '../render'; import type { Geometry } from '../render'; import { DestroyOptions } from '../utils'; import { RendererComponent } from './renderer-component'; +import { Vector4 } from '@galacean/effects-math/es/core/vector4'; /** * @since 2.0.0 @@ -42,6 +43,13 @@ export class EffectComponent extends RendererComponent { this.item.getHitTestParams = this.getHitTestParams; } + override onUpdate (dt: number): void { + const time = this.item.time; + const _Time = this.material.getVector4('_Time') ?? new Vector4(); + + this.material.setVector4('_Time', _Time.set(time / 20, time, time * 2, time * 3)); + } + override render (renderer: Renderer) { if (renderer.renderingData.currentFrame.globalUniforms) { renderer.setGlobalMatrix('effects_ObjectToWorld', this.transform.getWorldMatrix()); diff --git a/packages/effects-core/src/plugins/cal/calculate-vfx-item.ts b/packages/effects-core/src/plugins/cal/calculate-vfx-item.ts index 99176d75..cb4d74f9 100644 --- a/packages/effects-core/src/plugins/cal/calculate-vfx-item.ts +++ b/packages/effects-core/src/plugins/cal/calculate-vfx-item.ts @@ -241,6 +241,13 @@ export interface TransformPlayableAssetData extends spec.EffectsObjectData { export class ActivationPlayable extends Playable { override processFrame (context: FrameContext): void { + const vfxItem = context.output.getUserData(); + + if (!(vfxItem instanceof VFXItem)) { + return; + } + + vfxItem.time = this.time; } } diff --git a/packages/effects-core/src/vfx-item.ts b/packages/effects-core/src/vfx-item.ts index ad5b0bf0..91916704 100644 --- a/packages/effects-core/src/vfx-item.ts +++ b/packages/effects-core/src/vfx-item.ts @@ -58,6 +58,10 @@ export class VFXItem extends EffectsObject implements Disposable { * 合成属性 */ composition: Composition | null; + /** + * 元素动画的当前时间 + */ + time = 0; /** * 元素动画的持续时间 */ diff --git a/web-packages/imgui-demo/src/object-inspectors/vfx-item-inspector.ts b/web-packages/imgui-demo/src/object-inspectors/vfx-item-inspector.ts index 784651fc..bb4c9b01 100644 --- a/web-packages/imgui-demo/src/object-inspectors/vfx-item-inspector.ts +++ b/web-packages/imgui-demo/src/object-inspectors/vfx-item-inspector.ts @@ -250,7 +250,7 @@ export class VFXItemInspector extends ObjectInspector { if (!serializedData.vector4s[uniformName]) { serializedData.vector4s[uniformName] = { x:1.0, y:1.0, z:0.0, w:0.0 }; } - if (ImGui.DragFloat4('##' + uniformName, serializedData.vector4s[uniformName])) { + if (ImGui.DragFloat4('##' + uniformName, serializedData.vector4s[uniformName], 0.02)) { dirtyFlag = true; } } else if (type === '2D') { From b45fcbd96145876e109316dec89eff4d6ad19bc1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=8C=82=E5=AE=89?= <151805118+wumaolinmaoan@users.noreply.github.com> Date: Wed, 11 Sep 2024 20:09:09 +0800 Subject: [PATCH 09/88] fix: particle linear move calculate (#641) * fix: particle linear move calculate * fix: particle freeze --- .../src/plugins/particle/particle-mesh.ts | 110 +++++++++--------- .../particle/particle-system-renderer.ts | 6 +- 2 files changed, 57 insertions(+), 59 deletions(-) diff --git a/packages/effects-core/src/plugins/particle/particle-mesh.ts b/packages/effects-core/src/plugins/particle/particle-mesh.ts index 24824283..8e726f5e 100644 --- a/packages/effects-core/src/plugins/particle/particle-mesh.ts +++ b/packages/effects-core/src/plugins/particle/particle-mesh.ts @@ -778,70 +778,68 @@ export class ParticleMesh implements ParticleMeshData { const linearMove = this.cachedLinearMove; - for (let i = 0; i < particleCount; i++) { - const time = localTime - aOffsetArray[i * 4 + 2]; - const duration = aOffsetArray[i * 4 + 3]; - // const life = math.clamp(time / duration, 0.0, 1.0); - const aSeed = aSeedArray[i * 8 + 3]; - - linearMove.setZero(); - - const lifetime = time / duration; - - if (!this.linearVelOverLifetime || !this.linearVelOverLifetime.enabled) { - return linearMove; - } - if (this.linearVelOverLifetime.asMovement) { - if (this.linearVelOverLifetime.x) { - if (this.linearVelOverLifetime.x instanceof RandomValue) { - linearMove.x = this.linearVelOverLifetime.x.getValue(lifetime, aSeed); - } else { - linearMove.x = this.linearVelOverLifetime.x.getValue(lifetime); + if (this.linearVelOverLifetime && this.linearVelOverLifetime.enabled) { + for (let i = 0; i < particleCount; i++) { + const time = localTime - aOffsetArray[i * 4 + 2]; + const duration = aOffsetArray[i * 4 + 3]; + // const life = math.clamp(time / duration, 0.0, 1.0); + const lifetime = time / duration; + const aSeed = aSeedArray[i * 8 + 3]; + + linearMove.setZero(); + + if (this.linearVelOverLifetime.asMovement) { + if (this.linearVelOverLifetime.x) { + if (this.linearVelOverLifetime.x instanceof RandomValue) { + linearMove.x = this.linearVelOverLifetime.x.getValue(lifetime, aSeed); + } else { + linearMove.x = this.linearVelOverLifetime.x.getValue(lifetime); + } } - } - if (this.linearVelOverLifetime.y) { - if (this.linearVelOverLifetime.y instanceof RandomValue) { - linearMove.y = this.linearVelOverLifetime.y.getValue(lifetime, aSeed); - } else { - linearMove.y = this.linearVelOverLifetime.y.getValue(lifetime); + if (this.linearVelOverLifetime.y) { + if (this.linearVelOverLifetime.y instanceof RandomValue) { + linearMove.y = this.linearVelOverLifetime.y.getValue(lifetime, aSeed); + } else { + linearMove.y = this.linearVelOverLifetime.y.getValue(lifetime); + } } - } - if (this.linearVelOverLifetime.z) { - if (this.linearVelOverLifetime.z instanceof RandomValue) { - linearMove.z = this.linearVelOverLifetime.z.getValue(lifetime, aSeed); - } else { - linearMove.z = this.linearVelOverLifetime.z.getValue(lifetime); + if (this.linearVelOverLifetime.z) { + if (this.linearVelOverLifetime.z instanceof RandomValue) { + linearMove.z = this.linearVelOverLifetime.z.getValue(lifetime, aSeed); + } else { + linearMove.z = this.linearVelOverLifetime.z.getValue(lifetime); + } } - } - } else { - // Adjust rotation based on the specified lifetime components - if (this.linearVelOverLifetime.x) { - if (this.linearVelOverLifetime.x instanceof RandomValue) { - linearMove.x = this.linearVelOverLifetime.x.getIntegrateValue(0.0, time, aSeed); - } else { - linearMove.x = this.linearVelOverLifetime.x.getIntegrateValue(0.0, time, duration); + } else { + // Adjust rotation based on the specified lifetime components + if (this.linearVelOverLifetime.x) { + if (this.linearVelOverLifetime.x instanceof RandomValue) { + linearMove.x = this.linearVelOverLifetime.x.getIntegrateValue(0.0, time, aSeed); + } else { + linearMove.x = this.linearVelOverLifetime.x.getIntegrateValue(0.0, time, duration); + } } - } - if (this.linearVelOverLifetime.y) { - if (this.linearVelOverLifetime.y instanceof RandomValue) { - linearMove.y = this.linearVelOverLifetime.y.getIntegrateValue(0.0, time, aSeed); - } else { - linearMove.y = this.linearVelOverLifetime.y.getIntegrateValue(0.0, time, duration); + if (this.linearVelOverLifetime.y) { + if (this.linearVelOverLifetime.y instanceof RandomValue) { + linearMove.y = this.linearVelOverLifetime.y.getIntegrateValue(0.0, time, aSeed); + } else { + linearMove.y = this.linearVelOverLifetime.y.getIntegrateValue(0.0, time, duration); + } } - } - if (this.linearVelOverLifetime.z) { - if (this.linearVelOverLifetime.z instanceof RandomValue) { - linearMove.z = this.linearVelOverLifetime.z.getIntegrateValue(0.0, time, aSeed); - } else { - linearMove.z = this.linearVelOverLifetime.z.getIntegrateValue(0.0, time, duration); + if (this.linearVelOverLifetime.z) { + if (this.linearVelOverLifetime.z instanceof RandomValue) { + linearMove.z = this.linearVelOverLifetime.z.getIntegrateValue(0.0, time, aSeed); + } else { + linearMove.z = this.linearVelOverLifetime.z.getIntegrateValue(0.0, time, duration); + } } } - } - const aLinearMoveOffset = i * 3; + const aLinearMoveOffset = i * 3; - aLinearMoveArray[aLinearMoveOffset] = linearMove.x; - aLinearMoveArray[aLinearMoveOffset + 1] = linearMove.y; - aLinearMoveArray[aLinearMoveOffset + 2] = linearMove.z; + aLinearMoveArray[aLinearMoveOffset] = linearMove.x; + aLinearMoveArray[aLinearMoveOffset + 1] = linearMove.y; + aLinearMoveArray[aLinearMoveOffset + 2] = linearMove.z; + } } this.geometry.setAttributeData('aLinearMove', aLinearMoveArray); } diff --git a/packages/effects-core/src/plugins/particle/particle-system-renderer.ts b/packages/effects-core/src/plugins/particle/particle-system-renderer.ts index adaebcc0..aed38342 100644 --- a/packages/effects-core/src/plugins/particle/particle-system-renderer.ts +++ b/packages/effects-core/src/plugins/particle/particle-system-renderer.ts @@ -57,9 +57,9 @@ export class ParticleSystemRenderer extends RendererComponent { override onUpdate (dt: number): void { const time = this.particleMesh.time; + const uParams = this.particleMesh.mesh.material.getVector4('uParams') ?? new Vector4(); - this.particleMesh.mesh.material.setVector4('uParams', new Vector4(time, this.item.duration, 0, 0)); - this.particleMesh.onUpdate(dt); + this.particleMesh.mesh.material.setVector4('uParams', uParams.set(time, this.item.duration, 0, 0)); } override render (renderer: Renderer): void { @@ -75,7 +75,7 @@ export class ParticleSystemRenderer extends RendererComponent { updateTime (now: number, delta: number) { this.particleMesh.time = now; - // this.particleMesh.onUpdate(delta); + this.particleMesh.onUpdate(delta); if (this.trailMesh) { this.trailMesh.time = now; this.trailMesh.onUpdate(delta); From 895dcf9b3de4a321f76066b5611741c8d010fdaa Mon Sep 17 00:00:00 2001 From: wumaolinmaoan Date: Thu, 12 Sep 2024 15:58:47 +0800 Subject: [PATCH 10/88] perf: opt particle calculation performance --- .../effects-core/src/math/value-getter.ts | 4 +- .../src/plugins/particle/particle-mesh.ts | 70 ++++++++++++++----- .../test/case/2d/src/common/utilities.ts | 2 +- 3 files changed, 55 insertions(+), 21 deletions(-) diff --git a/packages/effects-core/src/math/value-getter.ts b/packages/effects-core/src/math/value-getter.ts index 10949832..3e642189 100644 --- a/packages/effects-core/src/math/value-getter.ts +++ b/packages/effects-core/src/math/value-getter.ts @@ -418,6 +418,7 @@ export class BezierCurve extends ValueGetter { timeEnd: number, }>; keys: number[][]; + keyTimeData: string[]; override onCreate (props: spec.BezierKeyframeValue[]) { const keyframes = props; @@ -444,10 +445,11 @@ export class BezierCurve extends ValueGetter { timeEnd:Number(e.x), }; } + this.keyTimeData = Object.keys(this.curveMap); } override getValue (time: number) { let result = 0; - const keyTimeData = Object.keys(this.curveMap); + const keyTimeData = this.keyTimeData; const keyTimeStart = this.curveMap[keyTimeData[0]].timeStart; const keyTimeEnd = this.curveMap[keyTimeData[keyTimeData.length - 1]].timeEnd; diff --git a/packages/effects-core/src/plugins/particle/particle-mesh.ts b/packages/effects-core/src/plugins/particle/particle-mesh.ts index 8e726f5e..0ff01150 100644 --- a/packages/effects-core/src/plugins/particle/particle-mesh.ts +++ b/packages/effects-core/src/plugins/particle/particle-mesh.ts @@ -454,11 +454,11 @@ export class ParticleMesh implements ParticleMeshData { onUpdate (dt: number) { const aPosArray = this.geometry.getAttributeData('aPos') as Float32Array; // vector3 - const particleCount = Math.ceil(aPosArray.length / 12); + const vertexCount = Math.ceil(aPosArray.length / 12); - this.applyTranslation(particleCount, dt); - this.applyRotation(particleCount, dt); - this.applyLinearMove(particleCount, dt); + this.applyTranslation(vertexCount, dt); + this.applyRotation(vertexCount, dt); + this.applyLinearMove(vertexCount, dt); } minusTime (time: number) { @@ -599,14 +599,14 @@ export class ParticleMesh implements ParticleMeshData { } } - private applyTranslation (particleCount: number, deltaTime: number) { + private applyTranslation (vertexCount: number, deltaTime: number) { const localTime = this.time; let aTranslationArray = this.geometry.getAttributeData('aTranslation') as Float32Array; const aVelArray = this.geometry.getAttributeData('aVel') as Float32Array; // vector3 const aOffsetArray = this.geometry.getAttributeData('aOffset') as Float32Array; - if (aTranslationArray.length < particleCount * 3) { - aTranslationArray = this.expandArray(aTranslationArray, particleCount * 3); + if (aTranslationArray.length < vertexCount * 3) { + aTranslationArray = this.expandArray(aTranslationArray, vertexCount * 3); } // const velocity = this.cachedVelocity; let velocityX = 0; @@ -615,7 +615,7 @@ export class ParticleMesh implements ParticleMeshData { const uAcceleration = this.mesh.material.getVector4('uAcceleration'); const uGravityModifierValue = this.mesh.material.getVector4('uGravityModifierValue'); - for (let i = 0; i < particleCount; i++) { + for (let i = 0; i < vertexCount; i += 4) { const velOffset = i * 12 + 3; velocityX = aVelArray[velOffset]; @@ -653,16 +653,31 @@ export class ParticleMesh implements ParticleMeshData { if (aOffsetArray[i * 4 + 2] < localTime) { // const translation = velocity.multiply(deltaTime / 1000); + const aTranslationX = velocityX * (deltaTime / 1000); + const aTranslationY = velocityY * (deltaTime / 1000); + const aTranslationZ = velocityZ * (deltaTime / 1000); - aTranslationArray[aTranslationOffset] += velocityX * (deltaTime / 1000); - aTranslationArray[aTranslationOffset + 1] += velocityY * (deltaTime / 1000); - aTranslationArray[aTranslationOffset + 2] += velocityZ * (deltaTime / 1000); + aTranslationArray[aTranslationOffset] += aTranslationX; + aTranslationArray[aTranslationOffset + 1] += aTranslationY; + aTranslationArray[aTranslationOffset + 2] += aTranslationZ; + + aTranslationArray[aTranslationOffset + 3] += aTranslationX; + aTranslationArray[aTranslationOffset + 4] += aTranslationY; + aTranslationArray[aTranslationOffset + 5] += aTranslationZ; + + aTranslationArray[aTranslationOffset + 6] += aTranslationX; + aTranslationArray[aTranslationOffset + 7] += aTranslationY; + aTranslationArray[aTranslationOffset + 8] += aTranslationZ; + + aTranslationArray[aTranslationOffset + 9] += aTranslationX; + aTranslationArray[aTranslationOffset + 10] += aTranslationY; + aTranslationArray[aTranslationOffset + 11] += aTranslationZ; } } this.geometry.setAttributeData('aTranslation', aTranslationArray); } - private applyRotation (particleCount: number, deltaTime: number) { + private applyRotation (vertexCount: number, deltaTime: number) { let aRotationArray = this.geometry.getAttributeData('aRotation0') as Float32Array; const aOffsetArray = this.geometry.getAttributeData('aOffset') as Float32Array; const aRotArray = this.geometry.getAttributeData('aRot') as Float32Array; // vector3 @@ -670,11 +685,11 @@ export class ParticleMesh implements ParticleMeshData { const localTime = this.time; const aRotationMatrix = this.cachedRotationMatrix; - if (aRotationArray.length < particleCount * 9) { - aRotationArray = this.expandArray(aRotationArray, particleCount * 9); + if (aRotationArray.length < vertexCount * 9) { + aRotationArray = this.expandArray(aRotationArray, vertexCount * 9); } - for (let i = 0; i < particleCount; i++) { + for (let i = 0; i < vertexCount; i += 4) { const time = localTime - aOffsetArray[i * 4 + 2]; const duration = aOffsetArray[i * 4 + 3]; const life = clamp(time / duration, 0.0, 1.0); @@ -761,25 +776,30 @@ export class ParticleMesh implements ParticleMeshData { const matrixArray = aRotationMatrix.elements; aRotationArray.set(matrixArray, aRotationOffset); + if (i + 4 <= vertexCount) { + aRotationArray.set(matrixArray, aRotationOffset + 9); + aRotationArray.set(matrixArray, aRotationOffset + 18); + aRotationArray.set(matrixArray, aRotationOffset + 27); + } } this.geometry.setAttributeData('aRotation0', aRotationArray); } - private applyLinearMove (particleCount: number, deltaTime: number) { + private applyLinearMove (vertexCount: number, deltaTime: number) { let aLinearMoveArray = this.geometry.getAttributeData('aLinearMove') as Float32Array; const aOffsetArray = this.geometry.getAttributeData('aOffset') as Float32Array; const aSeedArray = this.geometry.getAttributeData('aSeed') as Float32Array; // float const localTime = this.time; - if (aLinearMoveArray.length < particleCount * 3) { - aLinearMoveArray = this.expandArray(aLinearMoveArray, particleCount * 3); + if (aLinearMoveArray.length < vertexCount * 3) { + aLinearMoveArray = this.expandArray(aLinearMoveArray, vertexCount * 3); } const linearMove = this.cachedLinearMove; if (this.linearVelOverLifetime && this.linearVelOverLifetime.enabled) { - for (let i = 0; i < particleCount; i++) { + for (let i = 0; i < vertexCount; i += 4) { const time = localTime - aOffsetArray[i * 4 + 2]; const duration = aOffsetArray[i * 4 + 3]; // const life = math.clamp(time / duration, 0.0, 1.0); @@ -839,6 +859,18 @@ export class ParticleMesh implements ParticleMeshData { aLinearMoveArray[aLinearMoveOffset] = linearMove.x; aLinearMoveArray[aLinearMoveOffset + 1] = linearMove.y; aLinearMoveArray[aLinearMoveOffset + 2] = linearMove.z; + + aLinearMoveArray[aLinearMoveOffset + 3] = linearMove.x; + aLinearMoveArray[aLinearMoveOffset + 4] = linearMove.y; + aLinearMoveArray[aLinearMoveOffset + 5] = linearMove.z; + + aLinearMoveArray[aLinearMoveOffset + 6] = linearMove.x; + aLinearMoveArray[aLinearMoveOffset + 7] = linearMove.y; + aLinearMoveArray[aLinearMoveOffset + 8] = linearMove.z; + + aLinearMoveArray[aLinearMoveOffset + 9] = linearMove.x; + aLinearMoveArray[aLinearMoveOffset + 10] = linearMove.y; + aLinearMoveArray[aLinearMoveOffset + 11] = linearMove.z; } } this.geometry.setAttributeData('aLinearMove', aLinearMoveArray); diff --git a/web-packages/test/case/2d/src/common/utilities.ts b/web-packages/test/case/2d/src/common/utilities.ts index cb231792..96341128 100644 --- a/web-packages/test/case/2d/src/common/utilities.ts +++ b/web-packages/test/case/2d/src/common/utilities.ts @@ -9,7 +9,7 @@ const { Vector3, Matrix4 } = math; const sleepTime = 20; const params = new URLSearchParams(location.search); -const oldVersion = params.get('version') || '2.0.0'; // 旧版Player版本 +const oldVersion = params.get('version') || '2.1.0-alpha.3'; // 旧版Player版本 const playerOptions: PlayerConfig = { //env: 'editor', //pixelRatio: 2, From 366fbb69781d52a904dd50fd442cb6a405efc07a Mon Sep 17 00:00:00 2001 From: wumaolinmaoan Date: Thu, 12 Sep 2024 16:20:24 +0800 Subject: [PATCH 11/88] fix: onstart is called twice --- packages/effects-core/src/vfx-item.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/effects-core/src/vfx-item.ts b/packages/effects-core/src/vfx-item.ts index 91916704..bc4aecf5 100644 --- a/packages/effects-core/src/vfx-item.ts +++ b/packages/effects-core/src/vfx-item.ts @@ -584,6 +584,7 @@ export class VFXItem extends EffectsObject implements Disposable { for (const component of this.components) { if (component.enabled && !component.isStartCalled) { component.onStart(); + component.isStartCalled = true; } } for (const component of this.components) { From d03c516a75c4e8118e740425af2d0a93619a1b3f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=A3=82=E5=85=AE?= <151803898+liuxi150@users.noreply.github.com> Date: Thu, 12 Sep 2024 19:43:19 +0800 Subject: [PATCH 12/88] feat: support diffuse render mode and add demo for editor mode (#643) * chore: update demo * style: update demo * feat: support diffuse render mode * fix: diffuse mode problem * fix: update gltf loader * fix: update diffuse mode * style: update demo * fix: support wireframe * fix: update demo * fix: update demo * fix: update editor mode * style: update demo * fix: update version * style: update demo * test: fix 3d case test * fix(demo): type issue --------- Co-authored-by: yiiqii --- packages/effects-core/package.json | 2 +- plugin-packages/model/demo/src/camera.ts | 42 ++- plugin-packages/model/demo/src/editor-mode.ts | 357 ++++++++++++++++++ plugin-packages/model/demo/src/hit-test.ts | 77 ++-- plugin-packages/model/demo/src/index.ts | 16 +- plugin-packages/model/demo/src/json.ts | 18 +- plugin-packages/model/demo/src/render-mode.ts | 290 ++++++++++++++ plugin-packages/model/src/gesture/index.ts | 4 + plugin-packages/model/src/gltf/loader-impl.ts | 31 +- plugin-packages/model/src/gltf/protocol.ts | 1 + plugin-packages/model/src/runtime/light.ts | 5 + plugin-packages/model/src/runtime/mesh.ts | 16 +- plugin-packages/model/src/runtime/scene.ts | 5 + .../standard/metallic-roughness.frag.glsl | 36 ++ pnpm-lock.yaml | 8 +- 15 files changed, 801 insertions(+), 107 deletions(-) create mode 100644 plugin-packages/model/demo/src/editor-mode.ts create mode 100644 plugin-packages/model/demo/src/render-mode.ts diff --git a/packages/effects-core/package.json b/packages/effects-core/package.json index 07d4e610..89e9b546 100644 --- a/packages/effects-core/package.json +++ b/packages/effects-core/package.json @@ -51,7 +51,7 @@ "registry": "https://registry.npmjs.org" }, "dependencies": { - "@galacean/effects-specification": "2.0.0", + "@galacean/effects-specification": "2.0.1", "@galacean/effects-math": "1.1.0", "flatbuffers": "24.3.25", "uuid": "9.0.1" diff --git a/plugin-packages/model/demo/src/camera.ts b/plugin-packages/model/demo/src/camera.ts index 3d4442ff..84923dca 100644 --- a/plugin-packages/model/demo/src/camera.ts +++ b/plugin-packages/model/demo/src/camera.ts @@ -1,4 +1,4 @@ -//@ts-nocheck +import type { Player } from '@galacean/effects'; import { math, spec } from '@galacean/effects'; import { CameraGestureType, CameraGestureHandlerImp } from '@galacean/effects-plugin-model'; import { LoaderImplEx } from '../../src/helper'; @@ -9,20 +9,20 @@ let player: Player; let pending = false; let sceneAABB; -let sceneCenter; +let sceneCenter: math.Vector3; let sceneRadius = 1; let mouseDown = false; const pauseOnFirstFrame = false; let gestureType = CameraGestureType.rotate_focus; -let gestureHandler; +let gestureHandler: CameraGestureHandlerImp; let moveBegin = false; let scaleBegin = false; let rotationBegin = false; let rotationFocusBegin = false; -let playScene; +let playScene: spec.JSONScene; let url = 'https://gw.alipayobjects.com/os/bmw-prod/2b867bc4-0e13-44b8-8d92-eb2db3dfeb03.glb'; @@ -71,7 +71,7 @@ async function getCurrentScene () { return loader.getLoadResult().jsonScene; } -export async function loadScene (inPlayer) { +export async function loadScene (inPlayer: Player) { if (!player) { player = inPlayer; registerMouseEvent(); @@ -80,9 +80,10 @@ export async function loadScene (inPlayer) { if (!playScene) { playScene = await getCurrentScene(); } else { - playScene.compositions[0].items.forEach(item => { + playScene.items.forEach(item => { if (item.id === 'extra-camera') { - item.transform = player.compositions[0].camera; + // @ts-expect-error + item.transform = player.getCompositions()[0].camera; } }); } @@ -175,7 +176,7 @@ function registerMouseEvent () { refreshCamera(); if (pauseOnFirstFrame) { - player.compositions.forEach(comp => { + player.getCompositions().forEach(comp => { comp.gotoAndStop(comp.time); }); } @@ -222,7 +223,7 @@ function registerMouseEvent () { gestureHandler.onXYMoving(e.clientX, e.clientY); refreshCamera(); if (pauseOnFirstFrame) { - player.compositions.forEach(comp => { + player.getCompositions().forEach(comp => { comp.gotoAndStop(comp.time); }); } @@ -242,7 +243,7 @@ function registerMouseEvent () { gestureHandler.onZMoving(e.clientX, e.clientY); refreshCamera(); if (pauseOnFirstFrame) { - player.compositions.forEach(comp => { + player.getCompositions().forEach(comp => { comp.gotoAndStop(comp.time); }); } @@ -263,7 +264,7 @@ function registerMouseEvent () { gestureHandler.onRotating(e.clientX, e.clientY); refreshCamera(); if (pauseOnFirstFrame) { - player.compositions.forEach(comp => { + player.getCompositions().forEach(comp => { comp.gotoAndStop(comp.time); }); } @@ -285,7 +286,7 @@ function registerMouseEvent () { gestureHandler.onRotatingPoint(e.clientX, e.clientY); refreshCamera(); if (pauseOnFirstFrame) { - player.compositions.forEach(comp => { + player.getCompositions().forEach(comp => { comp.gotoAndStop(comp.time); }); } @@ -355,16 +356,17 @@ function registerMouseEvent () { } function refreshCamera () { - const freeCamera = playScene.items.find(item => item.name === 'extra-camera'); - const position = player.compositions[0].camera.position; - const rotation = player.compositions[0].camera.rotation; + const freeCamera = playScene.items.find(item => item.name === 'extra-camera') as spec.VFXItemData; + const position = player.getCompositions()[0].camera.position; + const rotation = player.getCompositions()[0].camera.rotation; - if (rotation[0] === null) { + if (rotation.x === null) { return; } - freeCamera.transform.position = position; - freeCamera.transform.rotation = rotation; + freeCamera.transform!.position = position; + // @ts-expect-error + freeCamera.transform!.rotation = rotation; } const demo_infoDom = document.getElementsByClassName('demo-info')[0]; @@ -383,9 +385,9 @@ export function createUI () { `; select.onchange = e => { - gestureType = +e.target.value; + gestureType = +(e.target as HTMLSelectElement).value; }; - select.value = gestureType; + select.value = gestureType.toString(); // add ui to parent dom demo_infoDom.appendChild(uiDom); demo_infoDom.appendChild(select); diff --git a/plugin-packages/model/demo/src/editor-mode.ts b/plugin-packages/model/demo/src/editor-mode.ts new file mode 100644 index 00000000..568dd006 --- /dev/null +++ b/plugin-packages/model/demo/src/editor-mode.ts @@ -0,0 +1,357 @@ +import type { Player } from '@galacean/effects'; +import { math, spec, generateGUID } from '@galacean/effects'; +import { CameraGestureHandlerImp } from '@galacean/effects-plugin-model'; +import { LoaderImplEx } from '../../src/helper'; +import { GizmoSubType } from '@galacean/effects-plugin-editor-gizmo'; + +const { Sphere, Vector3, Box3 } = math; + +let player: Player; +let sceneAABB; +let sceneCenter; +let sceneRadius = 1; + +let gestureHandler: CameraGestureHandlerImp; +let mouseDown = false; +let scaleBegin = false; +let rotationBegin = false; + +let playScene: spec.JSONScene; + +let url = 'https://gw.alipayobjects.com/os/gltf-asset/89748482160728/fish_test.glb'; + +url = 'https://gw.alipayobjects.com/os/gltf-asset/89748482160728/DamagedHelmet.glb'; + +enum EditorMode { + standard, + diffuse, + wireframe, + wireframe2, +} + +let editorMode = EditorMode.standard; + +async function getCurrentScene () { + const duration = 99999; + const loader = new LoaderImplEx(); + const loadResult = await loader.loadScene({ + gltf: { + resource: url, + }, + effects: { + duration: duration, + endBehavior: spec.EndBehavior.restart, + playAnimation: 0, + }, + }); + + const sceneMin = Vector3.fromArray(loadResult.sceneAABB.min); + const sceneMax = Vector3.fromArray(loadResult.sceneAABB.max); + + sceneAABB = new Box3(sceneMin, sceneMax); + sceneRadius = sceneAABB.getBoundingSphere(new Sphere()).radius; + sceneCenter = sceneAABB.getCenter(new Vector3()); + const position = sceneCenter.add(new Vector3(0, 0, sceneRadius * 3)); + + loader.addCamera({ + near: 0.1, + far: 5000, + fov: 60, + clipMode: 0, + // + name: 'extra-camera', + duration: duration, + endBehavior: spec.EndBehavior.restart, + position: position.toArray(), + rotation: [0, 0, 0], + }); + + loader.addLight({ + lightType: spec.LightType.ambient, + color: { r: 1, g: 1, b: 1, a: 1 }, + intensity: 0.2, + // + name: 'ambient-light', + position: [0, 0, 0], + rotation: [0, 0, 0], + scale: [1, 1, 1], + duration: duration, + endBehavior: spec.EndBehavior.restart, + }); + + loader.addLight({ + lightType: spec.LightType.directional, + color: { r: 1, g: 1, b: 1, a: 1 }, + intensity: 0.9, + followCamera: true, + // + name: 'main-light', + position: [0, 0, 0], + rotation: [30, 325, 0], + scale: [1, 1, 1], + duration: duration, + endBehavior: spec.EndBehavior.restart, + }); + + const { jsonScene } = loader.getLoadResult(); + + loader.dispose(); + + jsonScene.plugins.push('editor-gizmo'); + + return jsonScene; +} + +export async function loadScene (inPlayer: Player) { + if (!player) { + player = inPlayer; + registerMouseEvent(); + } + // + if (!playScene) { + playScene = await getCurrentScene(); + } else { + playScene.items.forEach(item => { + if (item.name === 'extra-camera') { + const camera = player.getCompositions()[0].camera; + + item.transform = { + position: camera.position.clone(), + eulerHint: camera.rotation.clone(), + scale: { x: 1, y: 1, z: 1 }, + }; + } + }); + } + + let currentScene = playScene; + let renderMode3D = spec.RenderMode3D.none; + + if (editorMode === EditorMode.wireframe) { + currentScene = addWireframeItems(playScene); + } else if (editorMode === EditorMode.wireframe2) { + currentScene = addWireframeItems(playScene, false); + } else if (editorMode === EditorMode.diffuse) { + renderMode3D = spec.RenderMode3D.diffuse; + } + + player.destroyCurrentCompositions(); + const loadOptions = { + pluginData: { + renderMode3D: renderMode3D, + renderMode3DUVGridSize: 1 / 12, + }, + }; + + return player.loadScene(currentScene, loadOptions).then(async comp => { + gestureHandler = new CameraGestureHandlerImp(comp); + + return true; + }); +} + +function addWireframeItems (scene: spec.JSONScene, hide3DModel = true) { + const newComponents: any = []; + + scene.components.forEach(comp => { + const newComponent: any = { ...comp }; + + if (newComponent.dataType === spec.DataType.MeshComponent) { + newComponent.hide = hide3DModel; + } + newComponents.push(newComponent); + }); + const newItems: any = [ + ...scene.items, + ]; + + scene.items.forEach(item => { + if (item.type === 'mesh') { + const { name, duration, endBehavior } = item; + const newItem: any = { + name: name + '_shadedWireframe', + id: generateGUID(), + pn: 1, + type: 'editor-gizmo', + visible: true, + duration: duration ?? 999, + dataType: spec.DataType.VFXItemData, + endBehavior, + transform: { + scale: { x: 1, y: 1, z: 1 }, + position: { x: 0, y: 0, z: 0 }, + eulerHint: { x: 0, y: 0, z: 0 }, + }, + }; + const newComponent = { + dataType: 'GizmoComponent', + id: generateGUID(), + item: { id: newItem.id }, + options: { + target: item.id, + subType: GizmoSubType.modelWireframe, + color: [0, 255, 255], + }, + }; + + newItem.components = [ + { id: newComponent.id }, + ]; + newComponents.push(newComponent); + newItems.push(newItem); + } + }); + const newComposition = { + ...scene.compositions[0], + items: newItems.filter((item: any) => item.id), + }; + const newScene: spec.JSONScene = { + ...scene, + components: newComponents, + items: newItems, + compositions: [newComposition], + }; + + return newScene; +} + +function registerMouseEvent () { + player.canvas.addEventListener('mousedown', function (e) { + if (!gestureHandler) { + return; + } + + mouseDown = true; + if (e.buttons === 1) { + rotationBegin = true; + } else if (e.buttons === 4) { + scaleBegin = true; + } + }); + + player.canvas.addEventListener('mousemove', async function (e) { + if (gestureHandler && mouseDown) { + if (e.buttons === 1) { + if (rotationBegin) { + gestureHandler.onRotatePointBegin( + e.clientX, + e.clientY, + player.canvas.width / 2, + player.canvas.height / 2, + [0, 0, 0], + 'extra-camera' + ); + rotationBegin = false; + } + + gestureHandler.onRotatingPoint(e.clientX, e.clientY); + refreshCamera(); + } else if (e.buttons === 4) { + if (scaleBegin) { + gestureHandler.onZMoveBegin( + e.clientX, + e.clientY, + player.canvas.width / 2, + player.canvas.height / 2, + 'extra-camera' + ); + scaleBegin = false; + } + + gestureHandler.onZMoving(e.clientX, e.clientY); + refreshCamera(); + } + } + }); + + player.canvas.addEventListener('mouseup', async function (e) { + mouseDown = false; + if (gestureHandler) { + if (e.buttons === 1) { + gestureHandler.onRotatePointEnd(); + } else if (e.buttons === 4) { + gestureHandler.onZMoveEnd(); + } + } + }); + + player.canvas.addEventListener('mouseleave', async function (e) { + mouseDown = false; + if (gestureHandler) { + if (e.buttons === 1) { + gestureHandler.onRotatePointEnd(); + } else if (e.buttons === 4) { + gestureHandler.onZMoveEnd(); + } + } + }); + + player.canvas.addEventListener('wheel', function (e) { + if (gestureHandler) { + gestureHandler.onKeyEvent({ + cameraID: 'extra-camera', + zAxis: e.deltaY > 0 ? 1 : -1, + speed: sceneRadius * 0.15, + }); + } + }); +} + +function refreshCamera () { + const freeCamera = playScene.items.find(item => item.name === 'extra-camera') as spec.VFXItemData; + const position = player.getCompositions()[0].camera.position; + const rotation = player.getCompositions()[0].camera.rotation; + + if (rotation.x === null) { + return; + } + + freeCamera.transform!.position = position; + // @ts-expect-error + freeCamera.transform!.rotation = rotation; +} + +export function createUI () { + const container = document.getElementsByClassName('container')[0] as HTMLElement; + const uiDom = document.createElement('div'); + const select = document.createElement('select'); + + container.style.background = 'rgba(182,217,241)'; + uiDom.className = 'my_ui'; + select.innerHTML = ` + + + + + `; + for (let i = 0; i < select.options.length; i++) { + const option = select.options[i]; + + if (option.value === editorMode.toString()) { + select.selectedIndex = i; + + break; + } + } + + select.onchange = async function (e) { + const element = e.target as HTMLSelectElement; + + if (element.value === 'standard') { + editorMode = EditorMode.standard; + } else if (element.value === 'diffuse') { + editorMode = EditorMode.diffuse; + } else if (element.value === 'wireframe') { + editorMode = EditorMode.wireframe; + } else if (element.value === 'wireframe2') { + editorMode = EditorMode.wireframe2; + } + + await loadScene(player); + }; + uiDom.appendChild(select); + + const demoInfo = document.getElementsByClassName('demo-info')[0]; + + demoInfo.appendChild(uiDom); +} diff --git a/plugin-packages/model/demo/src/hit-test.ts b/plugin-packages/model/demo/src/hit-test.ts index 27324a27..ff37c868 100644 --- a/plugin-packages/model/demo/src/hit-test.ts +++ b/plugin-packages/model/demo/src/hit-test.ts @@ -1,4 +1,4 @@ -//@ts-nocheck +import type { Composition, Player } from '@galacean/effects'; import { Transform, spec, math } from '@galacean/effects'; import { ToggleItemBounding, CompositionHitTest } from '@galacean/effects-plugin-model'; import { LoaderImplEx, InputController } from '../../src/helper'; @@ -6,17 +6,12 @@ import { createSlider } from './utility'; const { Sphere, Vector3, Box3 } = math; -let player; - -let inputController; - +let player: Player; +let inputController: InputController; let currentTime = 0.1; - -let composition; - -let playScene; - -let url; +let composition: Composition; +let playScene: spec.JSONScene; +let url: string; url = 'https://gw.alipayobjects.com/os/bmw-prod/2b867bc4-0e13-44b8-8d92-eb2db3dfeb03.glb'; url = 'https://gw.alipayobjects.com/os/gltf-asset/89748482160728/DamagedHelmet.glb'; @@ -66,7 +61,7 @@ async function getCurrentScene () { return loader.getLoadResult().jsonScene; } -export async function loadScene (inPlayer) { +export async function loadScene (inPlayer: Player) { if (!player) { player = inPlayer; registerMouseEvent(); @@ -75,9 +70,10 @@ export async function loadScene (inPlayer) { if (!playScene) { playScene = await getCurrentScene(); } else { - playScene.compositions[0].items.forEach(item => { + playScene.items.forEach(item => { if (item.id === 'extra-camera') { - item.transform = player.compositions[0].camera; + // @ts-expect-error + item.transform = player.getCompositions()[0].camera; } }); } @@ -133,11 +129,11 @@ function registerMouseEvent () { } function refreshCamera () { - const freeCamera = playScene.items.find(item => item.name === 'extra-camera'); - const position = player.compositions[0].camera.position; - const quat = player.compositions[0].camera.getQuat(); + const freeCamera = playScene.items.find(item => item.name === 'extra-camera') as spec.VFXItemData; + const position = player.getCompositions()[0].camera.position; + const quat = player.getCompositions()[0].camera.getQuat(); - if (quat[0] === null) { + if (quat.x === null) { return; } const transfrom = new Transform({ @@ -145,12 +141,14 @@ function refreshCamera () { quat: quat, }); - freeCamera.transform.position = transfrom.position; - freeCamera.transform.rotation = transfrom.rotation; + freeCamera.transform!.position = transfrom.position; + // @ts-expect-error + freeCamera.transform!.rotation = transfrom.rotation; } -function getHitTestCoord (e) { - const bounding = e.target.getBoundingClientRect(); +function getHitTestCoord (e: MouseEvent) { + const canvas = e.target as HTMLCanvasElement; + const bounding = canvas.getBoundingClientRect(); const x = ((e.clientX - bounding.left) / bounding.width) * 2 - 1; const y = 1 - ((e.clientY - bounding.top) / bounding.height) * 2; @@ -158,14 +156,12 @@ function getHitTestCoord (e) { } export function createUI () { - document.getElementsByClassName('container')[0].style.background = 'rgba(30,32,32)'; - // + const container = document.getElementsByClassName('container')[0] as HTMLElement; const uiDom = document.createElement('div'); - - uiDom.className = 'my_ui'; - const Label = document.createElement('label'); + container.style.background = 'rgba(30,32,32)'; + uiDom.className = 'my_ui'; Label.innerHTML = '

通过鼠标中键进行点击测试

'; uiDom.appendChild(Label); @@ -179,30 +175,3 @@ export function createUI () { demoInfo.appendChild(uiDom); } -function createSlider (name, minV, maxV, stepV, defaultV, callback, style) { - const InputDom = document.createElement('input'); - - InputDom.type = 'range'; - InputDom.min = minV.toString(); - InputDom.max = maxV.toString(); - InputDom.value = defaultV.toString(); - InputDom.step = stepV.toString(); - InputDom.style = style; - InputDom.addEventListener('input', function (event) { - const dom = event.target; - - Label.innerHTML = dom.value; - callback(Number(dom.value)); - }); - const divDom = document.createElement('div'); - - divDom.innerHTML = name; - divDom.appendChild(InputDom); - const Label = document.createElement('label'); - - Label.innerHTML = defaultV.toString(); - divDom.appendChild(Label); - - return divDom; -} - diff --git a/plugin-packages/model/demo/src/index.ts b/plugin-packages/model/demo/src/index.ts index 2318cdd0..ccc6c5d5 100644 --- a/plugin-packages/model/demo/src/index.ts +++ b/plugin-packages/model/demo/src/index.ts @@ -1,17 +1,20 @@ -//@ts-nocheck import '@galacean/effects-plugin-model'; import { createPlayer } from './utility'; import * as json from './json'; import * as camera from './camera'; import * as hitTest from './hit-test'; +import * as renderMode from './render-mode'; +import * as editorMode from './editor-mode'; const demoMap = { json, camera, hitTest, + renderMode, + editorMode, }; -function getDemoIndex (idxOrModule) { +function getDemoIndex (idxOrModule: any) { for (let i = 0; i < demoArray.length; i++) { if (demoArray[i][1] === idxOrModule) { return i; @@ -25,8 +28,7 @@ function getDemoIndex (idxOrModule) { } // 1. 获取 dom 对象 -const menuEle = document.getElementById('J-menu'); -const demoInfoEle = document.getElementById('J-demoInfo'); +const menuEle = document.getElementById('J-menu') as HTMLElement; const demoArray = Object.entries(demoMap); const storageKey = 'galacean-effects-plugin-model:demo-data'; let html = ''; @@ -48,16 +50,16 @@ const menuListEle = menuEle.querySelectorAll('.am-list-item'); menuListEle.forEach((item, i) => { item.addEventListener('click', () => { - localStorage.setItem(storageKey, i); + localStorage.setItem(storageKey, i.toString()); location.replace(location.href); }); }); // 4. 执行渲染逻辑 (async function doRender () { - const idx = getDemoIndex(localStorage.getItem(storageKey)); + const idx = getDemoIndex(localStorage.getItem(storageKey) ?? ''); const [name, demoInstance] = demoArray[idx]; - const env = demoInstance.getEnv ? demoInstance.getEnv() : 'editor'; + const env = 'getEnv' in demoInstance ? demoInstance.getEnv() : 'editor'; // 添加选中样式 menuListEle.forEach((ele, i) => { diff --git a/plugin-packages/model/demo/src/json.ts b/plugin-packages/model/demo/src/json.ts index 2cb24939..35642006 100644 --- a/plugin-packages/model/demo/src/json.ts +++ b/plugin-packages/model/demo/src/json.ts @@ -1,15 +1,15 @@ -//@ts-nocheck +import type { Player } from '@galacean/effects'; import { isObject } from '@galacean/effects'; import { getPMeshList, getRendererGPUInfo } from '@galacean/effects-plugin-model'; import { createButton, createPlayer, disposePlayer, createSlider, loadJsonFromURL } from './utility'; import { JSONConverter } from '@galacean/effects-plugin-model'; -let player; +let player: Player; let pending = false; let currentTime = 0; let pauseOnFirstFrame = false; -let infoElement; +let infoElement: HTMLLabelElement; let url = 'https://mdn.alipayobjects.com/mars/afts/file/A*SERYRaes5S0AAAAAAAAAAAAADlB4AQ'; // 兔子 @@ -53,7 +53,7 @@ async function getCurrentScene () { }); } -export async function loadScene (inPlayer) { +export async function loadScene (inPlayer: Player) { if (!player) { player = inPlayer; addRealTimeTicker(); @@ -87,10 +87,10 @@ export async function loadScene (inPlayer) { } export function createUI () { - document.getElementsByClassName('container')[0].style.background = 'rgba(30,32,32)'; - + const container = document.getElementsByClassName('container')[0] as HTMLElement; const parentElement = document.createElement('div'); + container.style.background = 'rgba(30,32,32)'; parentElement.className = 'my_ui'; createButton(parentElement, '重播', async function (ev) { @@ -107,6 +107,7 @@ export function createUI () { if (player !== undefined) { disposePlayer(player); } + // @ts-expect-error player = undefined; }); @@ -157,7 +158,10 @@ function addRealTimeTicker () { skinTextureMode = true; } mesh.subMeshes.forEach(subMesh => { - if (subMesh.jointMatrixTexture?.isHalfFloat) { skinHalfFloat = true; } + // @ts-expect-error + if (subMesh.jointMatrixTexture?.isHalfFloat) { + skinHalfFloat = true; + } }); }); infoList.push(`

蒙皮信息: HalfFloat ${skinHalfFloat}, 纹理模式 ${skinTextureMode}

`); diff --git a/plugin-packages/model/demo/src/render-mode.ts b/plugin-packages/model/demo/src/render-mode.ts new file mode 100644 index 00000000..95cd6271 --- /dev/null +++ b/plugin-packages/model/demo/src/render-mode.ts @@ -0,0 +1,290 @@ +import type { Player } from '@galacean/effects'; +import { math, spec } from '@galacean/effects'; +import { CameraGestureHandlerImp } from '@galacean/effects-plugin-model'; +import { LoaderImplEx } from '../../src/helper'; + +const { Sphere, Vector3, Box3 } = math; + +let player: Player; + +let sceneAABB; +let sceneCenter; +let sceneRadius = 1; + +let gestureHandler: CameraGestureHandlerImp; +let mouseDown = false; +let scaleBegin = false; +let rotationBegin = false; + +let playScene: spec.JSONScene; + +let url = 'https://gw.alipayobjects.com/os/gltf-asset/89748482160728/fish_test.glb'; + +url = 'https://gw.alipayobjects.com/os/gltf-asset/89748482160728/DamagedHelmet.glb'; + +let renderMode3D = spec.RenderMode3D.diffuse; + +async function getCurrentScene () { + const duration = 99999; + const loader = new LoaderImplEx(); + const loadResult = await loader.loadScene({ + gltf: { + resource: url, + }, + effects: { + duration: duration, + endBehavior: spec.EndBehavior.restart, + playAnimation: 0, + }, + }); + + const sceneMin = Vector3.fromArray(loadResult.sceneAABB.min); + const sceneMax = Vector3.fromArray(loadResult.sceneAABB.max); + + sceneAABB = new Box3(sceneMin, sceneMax); + sceneRadius = sceneAABB.getBoundingSphere(new Sphere()).radius; + sceneCenter = sceneAABB.getCenter(new Vector3()); + const position = sceneCenter.add(new Vector3(0, 0, sceneRadius * 3)); + + loader.addCamera({ + near: 0.1, + far: 5000, + fov: 60, + clipMode: 0, + // + name: 'extra-camera', + duration: duration, + endBehavior: spec.EndBehavior.restart, + position: position.toArray(), + rotation: [0, 0, 0], + }); + + loader.addLight({ + lightType: spec.LightType.ambient, + color: { r: 1, g: 1, b: 1, a: 1 }, + intensity: 0.2, + // + name: 'ambient-light', + position: [0, 0, 0], + rotation: [0, 0, 0], + scale: [1, 1, 1], + duration: duration, + endBehavior: spec.EndBehavior.restart, + }); + + loader.addLight({ + lightType: spec.LightType.directional, + color: { r: 1, g: 1, b: 1, a: 1 }, + intensity: 0.9, + followCamera: true, + // + name: 'main-light', + position: [0, 0, 0], + rotation: [30, 325, 0], + scale: [1, 1, 1], + duration: duration, + endBehavior: spec.EndBehavior.restart, + }); + + const { jsonScene } = loader.getLoadResult(); + + loader.dispose(); + + return jsonScene; +} + +export async function loadScene (inPlayer: Player) { + if (!player) { + player = inPlayer; + registerMouseEvent(); + } + // + if (!playScene) { + playScene = await getCurrentScene(); + } else { + playScene.items.forEach(item => { + if (item.name === 'extra-camera') { + const camera = player.getCompositions()[0].camera; + + item.transform = { + position: camera.position.clone(), + eulerHint: camera.rotation.clone(), + scale: { x: 1, y: 1, z: 1 }, + }; + } + }); + } + + player.destroyCurrentCompositions(); + const loadOptions = { + pluginData: { + renderMode3D: renderMode3D, + renderMode3DUVGridSize: 1 / 12, + }, + }; + + return player.loadScene(playScene, loadOptions).then(async comp => { + gestureHandler = new CameraGestureHandlerImp(comp); + + return true; + }); +} + +function registerMouseEvent () { + player.canvas.addEventListener('mousedown', function (e) { + if (!gestureHandler) { + return; + } + + mouseDown = true; + if (e.buttons === 1) { + rotationBegin = true; + } else if (e.buttons === 4) { + scaleBegin = true; + } + }); + + player.canvas.addEventListener('mousemove', async function (e) { + if (gestureHandler && mouseDown) { + if (e.buttons === 1) { + if (rotationBegin) { + gestureHandler.onRotatePointBegin( + e.clientX, + e.clientY, + player.canvas.width / 2, + player.canvas.height / 2, + [0, 0, 0], + 'extra-camera' + ); + rotationBegin = false; + } + + gestureHandler.onRotatingPoint(e.clientX, e.clientY); + refreshCamera(); + } else if (e.buttons === 4) { + if (scaleBegin) { + gestureHandler.onZMoveBegin( + e.clientX, + e.clientY, + player.canvas.width / 2, + player.canvas.height / 2, + 'extra-camera' + ); + scaleBegin = false; + } + + gestureHandler.onZMoving(e.clientX, e.clientY); + refreshCamera(); + } + } + }); + + player.canvas.addEventListener('mouseup', async function (e) { + mouseDown = false; + if (gestureHandler) { + if (e.buttons === 1) { + gestureHandler.onRotatePointEnd(); + } else if (e.buttons === 4) { + gestureHandler.onZMoveEnd(); + } + } + }); + + player.canvas.addEventListener('mouseleave', async function (e) { + mouseDown = false; + if (gestureHandler) { + if (e.buttons === 1) { + gestureHandler.onRotatePointEnd(); + } else if (e.buttons === 4) { + gestureHandler.onZMoveEnd(); + } + } + }); + + player.canvas.addEventListener('wheel', function (e) { + if (gestureHandler) { + gestureHandler.onKeyEvent({ + cameraID: 'extra-camera', + zAxis: e.deltaY > 0 ? 1 : -1, + speed: sceneRadius * 0.15, + }); + } + }); +} + +function refreshCamera () { + const freeCamera = playScene.items.find(item => item.name === 'extra-camera') as spec.VFXItemData; + const position = player.getCompositions()[0].camera.position; + const rotation = player.getCompositions()[0].camera.rotation; + + if (rotation.x === null) { + return; + } + + freeCamera.transform!.position = position; + // @ts-expect-error + freeCamera.transform!.rotation = rotation; +} + +export function createUI () { + const container = document.getElementsByClassName('container')[0] as HTMLElement; + const uiDom = document.createElement('div'); + const select = document.createElement('select'); + + container.style.background = 'rgba(182,217,241)'; + uiDom.className = 'my_ui'; + select.innerHTML = ` + + + + + + + + + + + `; + for (let i = 0; i < select.options.length; i++) { + const option = select.options[i]; + + if (option.value === renderMode3D) { + select.selectedIndex = i; + + break; + } + } + + select.onchange = async function (e) { + const element = e.target as HTMLSelectElement; + + if (element.value === 'none') { + renderMode3D = spec.RenderMode3D.none; + } else if (element.value === 'diffuse') { + renderMode3D = spec.RenderMode3D.diffuse; + } else if (element.value === 'uv') { + renderMode3D = spec.RenderMode3D.uv; + } else if (element.value === 'normal') { + renderMode3D = spec.RenderMode3D.normal; + } else if (element.value === 'basecolor') { + renderMode3D = spec.RenderMode3D.basecolor; + } else if (element.value === 'alpha') { + renderMode3D = spec.RenderMode3D.alpha; + } else if (element.value === 'metallic') { + renderMode3D = spec.RenderMode3D.metallic; + } else if (element.value === 'roughness') { + renderMode3D = spec.RenderMode3D.roughness; + } else if (element.value === 'ao') { + renderMode3D = spec.RenderMode3D.ao; + } else if (element.value === 'emissive') { + renderMode3D = spec.RenderMode3D.emissive; + } + + await loadScene(player); + }; + uiDom.appendChild(select); + + const demoInfo = document.getElementsByClassName('demo-info')[0]; + + demoInfo.appendChild(uiDom); +} diff --git a/plugin-packages/model/src/gesture/index.ts b/plugin-packages/model/src/gesture/index.ts index 2a92b586..064089c3 100644 --- a/plugin-packages/model/src/gesture/index.ts +++ b/plugin-packages/model/src/gesture/index.ts @@ -29,6 +29,10 @@ export class CameraGestureHandlerImp implements CameraGestureHandler { private composition: Composition, ) { } + updateComposition (composition: Composition) { + this.composition = composition; + } + getItem () { return this.composition.items?.find(item => item.name === this.getCurrentTarget()); } diff --git a/plugin-packages/model/src/gltf/loader-impl.ts b/plugin-packages/model/src/gltf/loader-impl.ts index 4dd1f9af..96173700 100644 --- a/plugin-packages/model/src/gltf/loader-impl.ts +++ b/plugin-packages/model/src/gltf/loader-impl.ts @@ -22,7 +22,7 @@ import { LoaderHelper } from './loader-helper'; import { WebGLHelper } from '../utility'; import type { PImageBufferData, PSkyboxBufferParams } from '../runtime/skybox'; import type { - GLTFSkin, GLTFMesh, GLTFImage, GLTFMaterial, GLTFTexture, GLTFScene, GLTFLight, + GLTFMesh, GLTFImage, GLTFMaterial, GLTFTexture, GLTFScene, GLTFLight, GLTFCamera, GLTFAnimation, GLTFResources, GLTFImageBasedLight, GLTFPrimitive, GLTFBufferAttribute, GLTFBounds, GLTFTextureInfo, } from '@vvfx/resource-detection'; @@ -53,12 +53,7 @@ type Box3 = math.Box3; export class LoaderImpl implements Loader { private sceneOptions: LoadSceneOptions; private loaderOptions: LoaderOptions; - private gltfScene: GLTFScene; - private gltfSkins: GLTFSkin[] = []; private gltfMeshs: GLTFMesh[] = []; - private gltfLights: GLTFLight[] = []; - private gltfCameras: GLTFCamera[] = []; - private gltfImages: GLTFImage[] = []; private gltfTextures: GLTFTexture[] = []; private gltfMaterials: GLTFMaterial[] = []; private gltfAnimations: GLTFAnimation[] = []; @@ -87,7 +82,7 @@ export class LoaderImpl implements Loader { this.composition = { id: '1', name: 'test1', - duration: 9999, + duration: 99999, endBehavior: spec.EndBehavior.restart, camera: { fov: 45, @@ -128,12 +123,7 @@ export class LoaderImpl implements Loader { })); this.processGLTFResource(gltfResource, this.imageElements); - this.gltfScene = gltfResource.scenes[0]; - this.gltfSkins = this.gltfScene.skins; this.gltfMeshs = gltfResource.meshes; - this.gltfLights = this.gltfScene.lights; - this.gltfCameras = this.gltfScene.cameras; - this.gltfImages = gltfResource.images; this.gltfTextures = gltfResource.textures; this.gltfMaterials = gltfResource.materials; this.gltfAnimations = gltfResource.animations; @@ -607,6 +597,7 @@ export class LoaderImpl implements Loader { range: data.range, innerConeAngle: data.innerConeAngle, outerConeAngle: data.outerConeAngle, + followCamera: data.followCamera, }; const item: spec.VFXItemData = { id: itemId, @@ -762,14 +753,28 @@ export class LoaderImpl implements Loader { return PSkyboxCreator.createSkyboxComponentData(params); } - private clear () { + dispose () { + this.clear(); + // @ts-expect-error + this.engine = null; + } + + clear () { + this.gltfMeshs = []; + this.gltfTextures = []; + this.gltfMaterials = []; + this.gltfAnimations = []; + this.gltfImageBasedLights = []; + this.images = []; + this.imageElements = []; this.textures = []; this.items = []; this.components = []; this.materials = []; this.shaders = []; this.geometries = []; + this.animations = []; } private computeSceneAABB () { diff --git a/plugin-packages/model/src/gltf/protocol.ts b/plugin-packages/model/src/gltf/protocol.ts index 758ea26f..1880a58d 100644 --- a/plugin-packages/model/src/gltf/protocol.ts +++ b/plugin-packages/model/src/gltf/protocol.ts @@ -108,6 +108,7 @@ export interface ModelLight { range?: number, innerConeAngle?: number, outerConeAngle?: number, + followCamera?: boolean, // name: string, position: spec.vec3, diff --git a/plugin-packages/model/src/runtime/light.ts b/plugin-packages/model/src/runtime/light.ts index 2b11e96f..47467653 100644 --- a/plugin-packages/model/src/runtime/light.ts +++ b/plugin-packages/model/src/runtime/light.ts @@ -42,6 +42,10 @@ export class PLight extends PEntity { */ lightType = PLightType.ambient; padding: Vector2 = new Vector2(0, 0); + /** + * 是否跟随相机 + */ + followCamera = false; /** * 创建灯光对象 @@ -68,6 +72,7 @@ export class PLight extends PEntity { color.b, ); this.intensity = data.intensity; + this.followCamera = data.followCamera ?? false; if (data.lightType === spec.LightType.point) { this.lightType = PLightType.point; this.range = data.range ?? -1; diff --git a/plugin-packages/model/src/runtime/mesh.ts b/plugin-packages/model/src/runtime/mesh.ts index 3b9fc0b5..59ddb0dc 100644 --- a/plugin-packages/model/src/runtime/mesh.ts +++ b/plugin-packages/model/src/runtime/mesh.ts @@ -800,6 +800,8 @@ export class PSubMesh { return 'DEBUG_OCCLUSION'; case spec.RenderMode3D.emissive: return 'DEBUG_EMISSIVE'; + case spec.RenderMode3D.diffuse: + return 'DEBUG_DIFFUSE'; } } @@ -852,18 +854,26 @@ export class PSubMesh { material.setVector3('_Camera', sceneStates.cameraPosition); // if (!this.isUnlitMaterial()) { - const { maxLightCount, lightList } = sceneStates; + const { maxLightCount, lightList, inverseViewMatrix } = sceneStates; for (let i = 0; i < maxLightCount; i++) { if (i < lightList.length) { const light = lightList[i]; const intensity = light.visible ? light.intensity : 0; - material.setVector3(`_Lights[${i}].direction`, light.getWorldDirection()); + if (light.followCamera) { + const newDirection = inverseViewMatrix.transformNormal(light.getWorldDirection(), new Vector3()); + const newPosition = inverseViewMatrix.transformPoint(light.getWorldPosition(), new Vector3()); + + material.setVector3(`_Lights[${i}].direction`, newDirection); + material.setVector3(`_Lights[${i}].position`, newPosition); + } else { + material.setVector3(`_Lights[${i}].direction`, light.getWorldDirection()); + material.setVector3(`_Lights[${i}].position`, light.getWorldPosition()); + } material.setFloat(`_Lights[${i}].range`, light.range); material.setVector3(`_Lights[${i}].color`, light.color); material.setFloat(`_Lights[${i}].intensity`, intensity); - material.setVector3(`_Lights[${i}].position`, light.getWorldPosition()); material.setFloat(`_Lights[${i}].innerConeCos`, Math.cos(light.innerConeAngle)); material.setFloat(`_Lights[${i}].outerConeCos`, Math.cos(light.outerConeAngle)); material.setInt(`_Lights[${i}].type`, light.lightType); diff --git a/plugin-packages/model/src/runtime/scene.ts b/plugin-packages/model/src/runtime/scene.ts index f6f9e93c..5da75f4b 100644 --- a/plugin-packages/model/src/runtime/scene.ts +++ b/plugin-packages/model/src/runtime/scene.ts @@ -95,6 +95,10 @@ export interface PSceneStates { * 相机矩阵 */ viewMatrix: Matrix4, + /** + * 逆相机矩阵 + */ + inverseViewMatrix: Matrix4, /** * 投影矩阵 */ @@ -403,6 +407,7 @@ export class PSceneManager { camera: camera, cameraPosition: camera.getEye(), viewMatrix: viewMatrix, + inverseViewMatrix: viewMatrix.clone().invert(), projectionMatrix: projectionMatrix, viewProjectionMatrix: viewProjectionMatrix, winSize: camera.getSize(), diff --git a/plugin-packages/model/src/runtime/shader-libs/standard/metallic-roughness.frag.glsl b/plugin-packages/model/src/runtime/shader-libs/standard/metallic-roughness.frag.glsl index 57bbb665..0a5b19ea 100644 --- a/plugin-packages/model/src/runtime/shader-libs/standard/metallic-roughness.frag.glsl +++ b/plugin-packages/model/src/runtime/shader-libs/standard/metallic-roughness.frag.glsl @@ -635,6 +635,42 @@ void main() gl_FragColor.rgb = vec3(baseColor.a); #endif + #ifdef DEBUG_DIFFUSE + vec3 debugDiffuse = vec3(0.0); + #ifdef USE_PUNCTUAL + vec3 newBaseColor = vec3(dot(_BaseColorFactor.xyz, vec3(0.3))); + MaterialInfo diffuseMaterialInfo = MaterialInfo( + 1.0, + vec3(0.0), + 1.0, + newBaseColor, + vec3(0.0), + vec3(0.0) + ); + for (int i = 0; i < LIGHT_COUNT; ++i) + { + Light light = _Lights[i]; + if (light.type == LightType_Directional) + { + debugDiffuse += applyDirectionalLight(light, diffuseMaterialInfo, normal, view, shadow); + } + else if (light.type == LightType_Point) + { + debugDiffuse += applyPointLight(light, diffuseMaterialInfo, normal, view); + } + else if (light.type == LightType_Spot) + { + debugDiffuse += applySpotLight(light, diffuseMaterialInfo, normal, view, shadow); + } + else if (light.type == LightType_Ambient) + { + debugDiffuse += applyAmbientLight(light, diffuseMaterialInfo); + } + } + #endif + gl_FragColor.rgb = toneMap(debugDiffuse); + #endif + gl_FragColor.a = 1.0; #endif // !DEBUG_OUTPUT diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 4018239f..b117821a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -150,8 +150,8 @@ importers: specifier: 1.1.0 version: 1.1.0 '@galacean/effects-specification': - specifier: 2.0.0 - version: 2.0.0 + specifier: 2.0.1 + version: 2.0.1 flatbuffers: specifier: 24.3.25 version: 24.3.25 @@ -2199,6 +2199,10 @@ packages: /@galacean/effects-specification@2.0.0: resolution: {integrity: sha512-7ieZALZYn5fwKLIGAskmYSKGX82ZkLRdDUInG21KsOJ2eQ5+HcI6mJU5tmN8vjZxQUc+RVuUcssExGbiaj2+5w==} + /@galacean/effects-specification@2.0.1: + resolution: {integrity: sha512-5Nj668cYvHXAbTW7W4bxG2gcIL/gWHPmWljVQWcbc/xCT7zA/FtB3ZUbjxfGRfGcXdgMbC8We4Ch3r6fYJF1iQ==} + dev: false + /@humanwhocodes/config-array@0.11.14: resolution: {integrity: sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==} engines: {node: '>=10.10.0'} From 23f5d9531e3799a309493eec1eec3fa9201eead0 Mon Sep 17 00:00:00 2001 From: wumaolinmaoan Date: Fri, 13 Sep 2024 18:16:13 +0800 Subject: [PATCH 13/88] fix: composition reverse --- packages/effects-core/src/composition.ts | 19 ++++++------------- 1 file changed, 6 insertions(+), 13 deletions(-) diff --git a/packages/effects-core/src/composition.ts b/packages/effects-core/src/composition.ts index d256bc04..b934f403 100644 --- a/packages/effects-core/src/composition.ts +++ b/packages/effects-core/src/composition.ts @@ -164,10 +164,6 @@ export class Composition extends EventEmitter> imp * 合成的相机对象 */ readonly camera: Camera; - /** - * 合成全局时间 - */ - globalTime: number; /** * 后处理渲染配置 */ @@ -266,7 +262,6 @@ export class Composition extends EventEmitter> imp }); this.url = scene.url; this.assigned = true; - this.globalTime = 0; this.interactive = true; this.handleItemMessage = handleItemMessage; this.createRenderFrame(); @@ -510,7 +505,6 @@ export class Composition extends EventEmitter> imp */ protected reset () { this.rendererOptions = null; - this.globalTime = 0; this.rootItem.ended = false; this.pluginSystem.resetComposition(this, this.renderFrame); } @@ -552,10 +546,9 @@ export class Composition extends EventEmitter> imp return; } - const time = this.getUpdateTime(deltaTime * this.speed); + const dt = this.getUpdateTime(deltaTime * this.speed); - this.globalTime += time; - this.updateRootComposition(); + this.updateRootComposition(dt / 1000); this.updateVideo(); // 更新 model-tree-plugin this.updatePluginLoaders(deltaTime); @@ -565,8 +558,8 @@ export class Composition extends EventEmitter> imp this.callAwake(this.rootItem); this.rootItem.beginPlay(); } - this.sceneTicking.update.tick(time); - this.sceneTicking.lateUpdate.tick(time); + this.sceneTicking.update.tick(dt); + this.sceneTicking.lateUpdate.tick(dt); this.updateCamera(); this.prepareRender(); @@ -724,9 +717,9 @@ export class Composition extends EventEmitter> imp /** * 更新主合成组件 */ - private updateRootComposition () { + private updateRootComposition (deltaTime: number) { if (this.rootComposition.isActiveAndEnabled) { - const localTime = this.toLocalTime(this.globalTime / 1000); + const localTime = this.toLocalTime(this.time + deltaTime); this.rootComposition.time = localTime; } From aab9e5268f443a01fc353c74ddba4de06aa32f4c Mon Sep 17 00:00:00 2001 From: wumaolinmaoan Date: Wed, 18 Sep 2024 20:03:39 +0800 Subject: [PATCH 14/88] feat: bloom support transparency --- .../src/shader/post-processing/color-grading.frag.glsl | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/effects-core/src/shader/post-processing/color-grading.frag.glsl b/packages/effects-core/src/shader/post-processing/color-grading.frag.glsl index f6ecd8b5..e11adfa6 100644 --- a/packages/effects-core/src/shader/post-processing/color-grading.frag.glsl +++ b/packages/effects-core/src/shader/post-processing/color-grading.frag.glsl @@ -161,5 +161,6 @@ void main() { if(_UseToneMapping) { finalColor = max(vec3(0.0), ACESToneMapping(finalColor)); } - gl_FragColor = vec4(clamp(GammaCorrection(finalColor), 0.0, 1.0), 1.0); + float alpha = min(hdrColor.a, 1.0); + gl_FragColor = vec4(clamp(GammaCorrection(finalColor), 0.0, 1.0), alpha); } \ No newline at end of file From ff2797ff8db03f390bd86e89c55ceeeb98176496 Mon Sep 17 00:00:00 2001 From: wumaolinmaoan Date: Wed, 18 Sep 2024 20:57:08 +0800 Subject: [PATCH 15/88] feat: imgui add show canvas menu button --- web-packages/imgui-demo/src/core/ui-manager.ts | 15 +++++++++++++-- web-packages/imgui-demo/src/panels/main-editor.ts | 12 +++++++++++- 2 files changed, 24 insertions(+), 3 deletions(-) diff --git a/web-packages/imgui-demo/src/core/ui-manager.ts b/web-packages/imgui-demo/src/core/ui-manager.ts index 854d5197..88d384bd 100644 --- a/web-packages/imgui-demo/src/core/ui-manager.ts +++ b/web-packages/imgui-demo/src/core/ui-manager.ts @@ -18,6 +18,8 @@ export class UIManager { // Top menu nodes private menuNodes: MenuNode[] = []; + private showCanvas = false; + constructor () { } @@ -67,7 +69,16 @@ export class UIManager { for (const panel of UIManager.editorWindows) { panel.draw(); } - // this.editor.draw(); + + const geContainer = document.getElementById('J-container'); + + if (geContainer) { + if (this.showCanvas) { + geContainer.style.zIndex = '999'; + } else { + geContainer.style.zIndex = '0'; + } + } if (ImGui.BeginMainMenuBar()) { if (ImGui.BeginMenu('File')) { @@ -75,7 +86,7 @@ export class UIManager { ImGui.EndMenu(); } if (ImGui.BeginMenu('Edit')) { - if (ImGui.BeginMenu('Test')) { + if (ImGui.MenuItem('Show Canvas', '', (_ = this.showCanvas)=>this.showCanvas = _)) { // ShowExampleMenuFile(); ImGui.EndMenu(); } diff --git a/web-packages/imgui-demo/src/panels/main-editor.ts b/web-packages/imgui-demo/src/panels/main-editor.ts index aa82a693..817a37d8 100644 --- a/web-packages/imgui-demo/src/panels/main-editor.ts +++ b/web-packages/imgui-demo/src/panels/main-editor.ts @@ -45,6 +45,16 @@ export class MainEditor extends EditorWindow { sceneImageSize.y -= 40; const player = GalaceanEffects.player; + const pos = ImGui.GetWindowPos(); + const windowSize = ImGui.GetWindowSize(); + const divElement = player.container; + + if (divElement) { + divElement.style.position = 'absolute'; + divElement.style.left = (pos.x + windowSize.x / 2) + 'px'; + divElement.style.top = (pos.y + windowSize.y * 0.9) + 'px'; + } + if (player.container && (player.container.style.width !== sceneImageSize.x + 'px' || player.container.style.height !== sceneImageSize.y + 'px') ) { @@ -52,7 +62,7 @@ export class MainEditor extends EditorWindow { player.container.style.height = sceneImageSize.y + 'px'; player.resize(); } - if (GalaceanEffects.sceneRendederTexture) { + if (GalaceanEffects.sceneRendederTexture && player.container && player.container.style.zIndex !== '999') { const frame_padding: int = 0; // -1 === uses default padding (style.FramePadding) const uv0: ImGui.Vec2 = new ImGui.Vec2(0.0, 0.0); // UV coordinates for lower-left const uv1: ImGui.Vec2 = new ImGui.Vec2(1.0, 1.0);// UV coordinates for (32,32) in our texture From df296c8695f66e348139c0fed5753c97e38c5baa Mon Sep 17 00:00:00 2001 From: wumaolinmaoan Date: Wed, 18 Sep 2024 21:19:35 +0800 Subject: [PATCH 16/88] fix: remove unnecessary alpha multiplication in color grading --- .../src/shader/post-processing/color-grading.frag.glsl | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/effects-core/src/shader/post-processing/color-grading.frag.glsl b/packages/effects-core/src/shader/post-processing/color-grading.frag.glsl index e11adfa6..2c516a97 100644 --- a/packages/effects-core/src/shader/post-processing/color-grading.frag.glsl +++ b/packages/effects-core/src/shader/post-processing/color-grading.frag.glsl @@ -128,7 +128,6 @@ vec3 ApplyVignette(vec3 inputColor, vec2 uv, vec2 center, float intensity, float void main() { vec4 hdrColor = texture2D(_SceneTex, uv); - hdrColor *= hdrColor.a; hdrColor.rgb = pow(hdrColor.rgb, vec3(2.2)); // srgb 转 linear From 06d8a9ec35ca807533484c861530be4b7c3ae49d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=8C=82=E5=AE=89?= <151805118+wumaolinmaoan@users.noreply.github.com> Date: Thu, 19 Sep 2024 14:47:44 +0800 Subject: [PATCH 17/88] fix: unit test and particle draw count warning (#654) * fix: unit test * fix: particle geometry buffer data initialize --------- Co-authored-by: wumaolin.wml --- packages/effects-core/src/plugins/particle/particle-mesh.ts | 2 ++ .../test/unit/src/effects-core/composition/on-end.spec.ts | 2 +- .../unit/src/effects-core/plugins/sprite/sprite-base.spec.ts | 2 +- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/effects-core/src/plugins/particle/particle-mesh.ts b/packages/effects-core/src/plugins/particle/particle-mesh.ts index 0ff01150..90075684 100644 --- a/packages/effects-core/src/plugins/particle/particle-mesh.ts +++ b/packages/effects-core/src/plugins/particle/particle-mesh.ts @@ -499,6 +499,8 @@ export class ParticleMesh implements ParticleMeshData { aRot: new Float32Array(32), aOffset: new Float32Array(16), aTranslation: new Float32Array(12), + aLinearMove:new Float32Array(12), + aRotation0: new Float32Array(36), }; const useSprite = this.useSprite; diff --git a/web-packages/test/unit/src/effects-core/composition/on-end.spec.ts b/web-packages/test/unit/src/effects-core/composition/on-end.spec.ts index cfe89f5e..258a1222 100644 --- a/web-packages/test/unit/src/effects-core/composition/on-end.spec.ts +++ b/web-packages/test/unit/src/effects-core/composition/on-end.spec.ts @@ -72,7 +72,7 @@ describe('core/composition/on-end', () => { const composition = await player.loadScene(JSON.parse(json)); expect(composition.startTime).to.eql(1.3); - expect(composition.time).to.eql(1.3); + expect(composition.time).to.closeTo(1.3, 0.001); }); async function test (endBehavior: spec.EndBehavior, fn: () => void) { diff --git a/web-packages/test/unit/src/effects-core/plugins/sprite/sprite-base.spec.ts b/web-packages/test/unit/src/effects-core/plugins/sprite/sprite-base.spec.ts index ec7b1a02..ff61cbf1 100644 --- a/web-packages/test/unit/src/effects-core/plugins/sprite/sprite-base.spec.ts +++ b/web-packages/test/unit/src/effects-core/plugins/sprite/sprite-base.spec.ts @@ -205,7 +205,7 @@ describe('core/plugins/sprite/item-base', () => { expect(texOffset2?.[2]).to.be.closeTo(0.1248, 0.001); expect(texOffset2?.[3]).to.be.closeTo(0.1249, 0.001); - expect(texOffset3?.[0]).to.be.closeTo(0.5, 0.001); + expect(texOffset3?.[0]).to.be.closeTo(0.625, 0.001); expect(texOffset3?.[1]).to.be.closeTo(0.5, 0.001); expect(texOffset3?.[2]).to.be.closeTo(0.125, 0.001); expect(texOffset3?.[3]).to.be.closeTo(0.125, 0.001); From a5cc64075ca7f9ba4564593c83a3afc741cc5026 Mon Sep 17 00:00:00 2001 From: wumaolinmaoan Date: Tue, 24 Sep 2024 14:02:37 +0800 Subject: [PATCH 18/88] fix: composition time reset --- packages/effects-core/src/composition.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/effects-core/src/composition.ts b/packages/effects-core/src/composition.ts index b934f403..82e398af 100644 --- a/packages/effects-core/src/composition.ts +++ b/packages/effects-core/src/composition.ts @@ -506,6 +506,7 @@ export class Composition extends EventEmitter> imp protected reset () { this.rendererOptions = null; this.rootItem.ended = false; + this.rootComposition.time = 0; this.pluginSystem.resetComposition(this, this.renderFrame); } From a986f4c73348acb1cc397884eb57d3d0933d3e58 Mon Sep 17 00:00:00 2001 From: wumaolinmaoan Date: Tue, 24 Sep 2024 14:22:22 +0800 Subject: [PATCH 19/88] fix: particle reset --- packages/effects-core/src/plugins/particle/particle-system.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/effects-core/src/plugins/particle/particle-system.ts b/packages/effects-core/src/plugins/particle/particle-system.ts index 99275e64..bd7f56c9 100644 --- a/packages/effects-core/src/plugins/particle/particle-system.ts +++ b/packages/effects-core/src/plugins/particle/particle-system.ts @@ -339,6 +339,7 @@ export class ParticleSystem extends Component { this.emission.bursts.forEach(b => b.reset()); this.frozen = false; this.ended = false; + this.destroyed = false; } update (delta: number) { From 5a34bfd91cbacc68438e0d32f32ed87c05c5a252 Mon Sep 17 00:00:00 2001 From: yiiqii Date: Wed, 25 Sep 2024 14:23:03 +0800 Subject: [PATCH 20/88] =?UTF-8?q?test:=20=E5=AE=8C=E5=96=84=20AssetManager?= =?UTF-8?q?=20=E5=8A=A8=E5=9B=BE=E8=A7=86=E9=A2=91=E5=8D=95=E6=B5=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...-manager.spec.ts => asset-manager.spec.ts} | 118 +++++++----------- .../test/unit/src/effects-core/index.ts | 2 +- 2 files changed, 46 insertions(+), 74 deletions(-) rename web-packages/test/unit/src/effects-core/{assert-manager.spec.ts => asset-manager.spec.ts} (59%) diff --git a/web-packages/test/unit/src/effects-core/assert-manager.spec.ts b/web-packages/test/unit/src/effects-core/asset-manager.spec.ts similarity index 59% rename from web-packages/test/unit/src/effects-core/assert-manager.spec.ts rename to web-packages/test/unit/src/effects-core/asset-manager.spec.ts index 8c981c40..0293aebd 100644 --- a/web-packages/test/unit/src/effects-core/assert-manager.spec.ts +++ b/web-packages/test/unit/src/effects-core/asset-manager.spec.ts @@ -1,114 +1,88 @@ import type { Texture2DSourceOptionsVideo } from '@galacean/effects'; -import { Player } from '@galacean/effects'; +import { Player, spec } from '@galacean/effects'; const { expect } = chai; describe('core/asset-manager', () => { - let player: Player | null; + let player: Player; before(() => { player = new Player({ canvas: document.createElement('canvas'), manualRender: true }); }); after(() => { - player!.dispose(); + player.dispose(); + // @ts-expect-error player = null; }); it('template video', async () => { - player = player!; - const assets = { - 'images': [ - { - 'template': { - 'v': 2, - 'variables': { - 'test': 'https://mdn.alipayobjects.com/huamei_p0cigc/afts/file/A*ZOgXRbmVlsIAAAAAAAAAAAAADoB5AQ', - }, - 'width': 126, - 'height': 130, - 'background': { - 'type': 'video', - 'name': 'test', - 'url': 'https://mdn.alipayobjects.com/huamei_p0cigc/afts/file/A*ZOgXRbmVlsIAAAAAAAAAAAAADoB5AQ', - }, - }, - 'url': 'https://mdn.alipayobjects.com/mars/afts/img/A*oKwARKdkWhEAAAAAAAAAAAAADlB4AQ/original', - 'webp': 'https://mdn.alipayobjects.com/mars/afts/img/A*eOLVQpT57FcAAAAAAAAAAAAADlB4AQ/original', - 'renderLevel': 'B+', + const videoUrl = 'https://mdn.alipayobjects.com/huamei_p0cigc/afts/file/A*ZOgXRbmVlsIAAAAAAAAAAAAADoB5AQ'; + const images = [{ + 'id': 'test', + 'template': { + 'width': 126, + 'height': 130, + 'background': { + 'type': spec.BackgroundType.video, + 'name': 'test', + 'url': videoUrl, }, - ], - 'fonts': [], - 'spines': [], - 'shapes': [], - }; + }, + 'url': 'https://mdn.alipayobjects.com/mars/afts/img/A*oKwARKdkWhEAAAAAAAAAAAAADlB4AQ/original', + 'webp': 'https://mdn.alipayobjects.com/mars/afts/img/A*eOLVQpT57FcAAAAAAAAAAAAADlB4AQ/original', + 'renderLevel': spec.RenderLevel.BPlus, + }]; - const comp = await player.loadScene(generateScene(assets)); - const textures = comp.textures; + const composition = await player.loadScene(generateScene(images)); + const textures = composition.textures; + const videoElement = (textures[0].source as Texture2DSourceOptionsVideo).video; expect(textures.length).to.deep.equals(1); expect(textures[0].source).to.not.be.empty; - const videoElement = (textures[0].source as Texture2DSourceOptionsVideo).video; - expect(videoElement).to.be.an.instanceOf(HTMLVideoElement); - - player.gotoAndStop(0.1); + expect(videoElement.src).to.be.equals(videoUrl); }); it('templateV2 video variables', async () => { - player = player!; - const assets = { - 'images': [ - { - 'template': { - 'v': 2, - 'variables': { - 'test': 'https://mdn.alipayobjects.com/huamei_p0cigc/afts/file/A*ZOgXRbmVlsIAAAAAAAAAAAAADoB5AQ', - }, - 'width': 126, - 'height': 130, - 'background': { - 'type': 'video', - 'name': 'test', - 'url': 'https://mdn.alipayobjects.com/huamei_p0cigc/afts/file/A*ZOgXRbmVlsIAAAAAAAAAAAAADoB5AQ', - }, - }, - 'url': 'https://mdn.alipayobjects.com/mars/afts/img/A*oKwARKdkWhEAAAAAAAAAAAAADlB4AQ/original', - 'webp': 'https://mdn.alipayobjects.com/mars/afts/img/A*eOLVQpT57FcAAAAAAAAAAAAADlB4AQ/original', - 'renderLevel': 'B+', + const videoUrl = 'https://gw.alipayobjects.com/v/huamei_p0cigc/afts/video/A*dftzSq2szUsAAAAAAAAAAAAADtN3AQ'; + const images = [{ + 'id': 'test', + 'template': { + 'width': 126, + 'height': 130, + 'background': { + 'type': spec.BackgroundType.video, + 'name': 'test', + 'url': 'https://mdn.alipayobjects.com/huamei_p0cigc/afts/file/A*ZOgXRbmVlsIAAAAAAAAAAAAADoB5AQ', }, - ], - 'fonts': [], - 'spines': [], - 'shapes': [], - }; + }, + 'url': 'https://mdn.alipayobjects.com/mars/afts/img/A*oKwARKdkWhEAAAAAAAAAAAAADlB4AQ/original', + 'webp': 'https://mdn.alipayobjects.com/mars/afts/img/A*eOLVQpT57FcAAAAAAAAAAAAADlB4AQ/original', + 'renderLevel': spec.RenderLevel.BPlus, + }]; - const comp = await player.loadScene(generateScene(assets), { + const composition = await player.loadScene(generateScene(images), { variables: { // 视频地址 - test: 'https://gw.alipayobjects.com/v/huamei_p0cigc/afts/video/A*dftzSq2szUsAAAAAAAAAAAAADtN3AQ', + test: videoUrl, }, }); - - const textures = comp.textures; - - expect(textures.length).to.deep.equals(1); - expect(textures[0].source).to.not.be.empty; + const textures = composition.textures; const videoElement = (textures[0].source as Texture2DSourceOptionsVideo).video; expect(videoElement).to.be.an.instanceOf(HTMLVideoElement); - - player.gotoAndStop(0.1); + expect(videoElement.src).to.be.equals(videoUrl); }); }); -//@ts-expect-error -const generateScene = assets => { - const res = { +const generateScene = (images: spec.TemplateImage[]) => { + return { 'playerVersion': { 'web': '1.2.1', 'native': '0.0.1.202311221223', }, + 'images': images, 'version': '2.2', 'type': 'ge', 'compositions': [ @@ -204,6 +178,4 @@ const generateScene = assets => { }, ], }; - - return { ...res, ...assets }; }; diff --git a/web-packages/test/unit/src/effects-core/index.ts b/web-packages/test/unit/src/effects-core/index.ts index 1801bdae..f7feb93a 100644 --- a/web-packages/test/unit/src/effects-core/index.ts +++ b/web-packages/test/unit/src/effects-core/index.ts @@ -9,7 +9,7 @@ import './plugins/common/end-behevior.spec'; import './plugins/particle'; // plugin sprite import './plugins/sprite'; -import './assert-manager.spec'; +import './asset-manager.spec'; import './texture.spec'; import './transform.spec'; import './utils.spec'; From f8dacb41b294d51789e17e3c62d07193a4de1780 Mon Sep 17 00:00:00 2001 From: wumaolinmaoan Date: Wed, 25 Sep 2024 15:37:06 +0800 Subject: [PATCH 21/88] refactor: remove processTextures dependency on engine --- packages/effects-core/src/asset-manager.ts | 9 ++++----- packages/effects-core/src/texture/utils.ts | 8 +++----- 2 files changed, 7 insertions(+), 10 deletions(-) diff --git a/packages/effects-core/src/asset-manager.ts b/packages/effects-core/src/asset-manager.ts index 7bb8c37e..426da213 100644 --- a/packages/effects-core/src/asset-manager.ts +++ b/packages/effects-core/src/asset-manager.ts @@ -16,7 +16,6 @@ import type { Renderer } from './render'; import { COMPRESSED_TEXTURE } from './render'; import { combineImageTemplate, getBackgroundImage } from './template-image'; import { ImageAsset } from './image-asset'; -import type { Engine } from './engine'; let seed = 1; @@ -178,6 +177,7 @@ export class AssetManager implements Disposable { if (renderer) { for (let i = 0; i < images.length; i++) { + this.assets[images[i].id] = loadedImages[i]; const imageAsset = new ImageAsset(renderer.engine); imageAsset.data = loadedImages[i]; @@ -187,7 +187,7 @@ export class AssetManager implements Disposable { } await hookTimeInfo('processFontURL', () => this.processFontURL(fonts as spec.FontDefine[])); - const loadedTextures = await hookTimeInfo('processTextures', () => this.processTextures(loadedImages, loadedBins, jsonScene, renderer!.engine)); + const loadedTextures = await hookTimeInfo('processTextures', () => this.processTextures(loadedImages, loadedBins, jsonScene)); scene = { timeInfos, @@ -411,7 +411,6 @@ export class AssetManager implements Disposable { images: any, bins: ArrayBuffer[], jsonScene: spec.JSONScene, - engine: Engine ) { const textures = jsonScene.textures ?? images.map((img: never, source: number) => ({ source })) as spec.SerializedTextureSource[]; const jobs = textures.map(async (textureOptions, idx) => { @@ -420,7 +419,7 @@ export class AssetManager implements Disposable { } if ('mipmaps' in textureOptions) { try { - return await deserializeMipmapTexture(textureOptions, bins, engine, jsonScene.bins); + return await deserializeMipmapTexture(textureOptions, bins, this.assets, jsonScene.bins); } catch (e) { throw new Error(`Load texture ${idx} fails, error message: ${e}.`); } @@ -430,7 +429,7 @@ export class AssetManager implements Disposable { let image: any; if (isObject(source)) { // source 为 images 数组 id - image = engine.assetLoader.loadGUID((source as Record).id).data; + image = this.assets[source.id as string]; } else if (typeof source === 'string') { // source 为 base64 数据 image = await loadImage(base64ToFile(source)); } diff --git a/packages/effects-core/src/texture/utils.ts b/packages/effects-core/src/texture/utils.ts index 554af0e6..7fecf37a 100644 --- a/packages/effects-core/src/texture/utils.ts +++ b/packages/effects-core/src/texture/utils.ts @@ -2,14 +2,13 @@ import type * as spec from '@galacean/effects-specification'; import type { Texture2DSourceOptions, TextureCubeSourceOptions } from './types'; import { TextureSourceType } from './types'; import { loadImage } from '../downloader'; -import type { Engine } from '../engine'; type TextureJSONOptions = spec.SerializedTextureSource & spec.TextureConfigOptionsBase & spec.TextureFormatOptions; export async function deserializeMipmapTexture ( textureOptions: TextureJSONOptions, bins: ArrayBuffer[], - engine: Engine, + assets: Record, files: spec.BinaryFile[] = [], ): Promise { if (textureOptions.target === 34067) { @@ -18,10 +17,9 @@ export async function deserializeMipmapTexture ( // @ts-expect-error if (pointer.id) { // @ts-expect-error - const loadedImageAsset = engine.assetLoader.loadGUID(pointer.id); + const loadedImage = assets[pointer.id]; - // @ts-expect-error - return loadedImageAsset.data; + return loadedImage; } else { return loadMipmapImage(pointer, bins); } From 29907f9a935378c2c73512051e5a6413529dcf65 Mon Sep 17 00:00:00 2001 From: wumaolinmaoan Date: Wed, 25 Sep 2024 15:52:30 +0800 Subject: [PATCH 22/88] fix: image asset load error --- packages/effects-core/src/asset-manager.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/effects-core/src/asset-manager.ts b/packages/effects-core/src/asset-manager.ts index 426da213..1fed6b89 100644 --- a/packages/effects-core/src/asset-manager.ts +++ b/packages/effects-core/src/asset-manager.ts @@ -175,9 +175,12 @@ export class AssetManager implements Disposable { hookTimeInfo(`${asyncShaderCompile ? 'async' : 'sync'}Compile`, () => this.precompile(compositions, pluginSystem, renderer, options)), ]); + for (let i = 0; i < images.length; i++) { + this.assets[images[i].id] = loadedImages[i]; + } + if (renderer) { for (let i = 0; i < images.length; i++) { - this.assets[images[i].id] = loadedImages[i]; const imageAsset = new ImageAsset(renderer.engine); imageAsset.data = loadedImages[i]; From 0fdd3d48951899c914a6ffe6f823b97d2238a432 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=8C=82=E5=AE=89?= <151805118+wumaolinmaoan@users.noreply.github.com> Date: Thu, 26 Sep 2024 19:32:23 +0800 Subject: [PATCH 23/88] feat: add shape component (#665) * feat: add GraphicsComponent * feat: add ShapePath * feat: add graphics path * fix: imgui material setting * fix: demo name * Update web-packages/demo/src/shape.ts Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> * chore: add license * style: import and public prefix * style: shape * feat: add uv build --------- Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> Co-authored-by: yiiqii --- packages/effects-core/NOTICE | 26 ++ packages/effects-core/package.json | 3 +- packages/effects-core/src/components/index.ts | 3 +- .../src/components/shape-component.ts | 368 ++++++++++++++++++ .../plugins/shape/build-adaptive-bezier.ts | 216 ++++++++++ .../src/plugins/shape/graphics-path.ts | 95 +++++ .../src/plugins/shape/point-data.ts | 7 + .../effects-core/src/plugins/shape/polygon.ts | 140 +++++++ .../src/plugins/shape/shape-path.ts | 134 +++++++ .../src/plugins/shape/triangulate.ts | 73 ++++ packages/effects-webgl/src/gl-material.ts | 1 + pnpm-lock.yaml | 7 + types/shim.d.ts | 1 + web-packages/demo/html/shape.html | 21 + web-packages/demo/index.html | 1 + web-packages/demo/src/shape.ts | 218 +++++++++++ web-packages/demo/vite.config.js | 4 +- web-packages/imgui-demo/src/ge.ts | 267 ++++++++++++- web-packages/imgui-demo/src/main.ts | 76 +++- .../object-inspectors/vfx-item-inspector.ts | 50 ++- 20 files changed, 1693 insertions(+), 18 deletions(-) create mode 100644 packages/effects-core/src/components/shape-component.ts create mode 100644 packages/effects-core/src/plugins/shape/build-adaptive-bezier.ts create mode 100644 packages/effects-core/src/plugins/shape/graphics-path.ts create mode 100644 packages/effects-core/src/plugins/shape/point-data.ts create mode 100644 packages/effects-core/src/plugins/shape/polygon.ts create mode 100644 packages/effects-core/src/plugins/shape/shape-path.ts create mode 100644 packages/effects-core/src/plugins/shape/triangulate.ts create mode 100644 web-packages/demo/html/shape.html create mode 100644 web-packages/demo/src/shape.ts diff --git a/packages/effects-core/NOTICE b/packages/effects-core/NOTICE index 48a701a2..59e17dc4 100644 --- a/packages/effects-core/NOTICE +++ b/packages/effects-core/NOTICE @@ -48,6 +48,32 @@ Repository: https://github.com/ampas/aces-dev WITHOUT LIMITING THE GENERALITY OF THE FOREGOING, THE ACADEMY SPECIFICALLY DISCLAIMS ANY REPRESENTATIONS OR WARRANTIES WHATSOEVER RELATED TO PATENT OR OTHER INTELLECTUAL PROPERTY RIGHTS IN THE ACADEMY COLOR ENCODING SYSTEM, OR APPLICATIONS THEREOF, HELD BY PARTIES OTHER THAN A.M.P.A.S.,WHETHER DISCLOSED OR UNDISCLOSED. +3. pixijs + +License: [MIT License](https://github.com/pixijs/pixijs/blob/dev/LICENSE) + +Repository: https://github.com/pixijs/pixijs + + Copyright (c) 2013-2023 Mathew Groves, Chad Engler + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + Please refer to the corresponding repository for more information on each component's license and terms of use. Galacean Effects Core Library is distributed under the MIT License. Please see the LICENSE file for the full text of the license. diff --git a/packages/effects-core/package.json b/packages/effects-core/package.json index 89e9b546..1e563e44 100644 --- a/packages/effects-core/package.json +++ b/packages/effects-core/package.json @@ -54,6 +54,7 @@ "@galacean/effects-specification": "2.0.1", "@galacean/effects-math": "1.1.0", "flatbuffers": "24.3.25", - "uuid": "9.0.1" + "uuid": "9.0.1", + "libtess": "1.2.2" } } diff --git a/packages/effects-core/src/components/index.ts b/packages/effects-core/src/components/index.ts index eed835cb..271883d1 100644 --- a/packages/effects-core/src/components/index.ts +++ b/packages/effects-core/src/components/index.ts @@ -1,4 +1,5 @@ export * from './renderer-component'; export * from './component'; export * from './effect-component'; -export * from './post-process-volume'; \ No newline at end of file +export * from './post-process-volume'; +export * from './shape-component'; \ No newline at end of file diff --git a/packages/effects-core/src/components/shape-component.ts b/packages/effects-core/src/components/shape-component.ts new file mode 100644 index 00000000..12f70418 --- /dev/null +++ b/packages/effects-core/src/components/shape-component.ts @@ -0,0 +1,368 @@ +import { Color } from '@galacean/effects-math/es/core/color'; +import type * as spec from '@galacean/effects-specification'; +import { effectsClass } from '../decorators'; +import type { Engine } from '../engine'; +import { glContext } from '../gl'; +import type { MaterialProps } from '../material'; +import { Material } from '../material'; +import { GraphicsPath } from '../plugins/shape/graphics-path'; +import type { ShapePath } from '../plugins/shape/shape-path'; +import { triangulate } from '../plugins/shape/triangulate'; +import type { Renderer } from '../render'; +import { Geometry, GLSLVersion } from '../render'; +import { RendererComponent } from './renderer-component'; + +interface CurveData { + point: spec.Vector3Data, + controlPoint1: spec.Vector3Data, + controlPoint2: spec.Vector3Data, +} + +/** + * 图形组件 + * @since 2.1.0 + */ +@effectsClass('ShapeComponent') +export class ShapeComponent extends RendererComponent { + path = new GraphicsPath(); + + private curveValues: CurveData[] = []; + private geometry: Geometry; + private dirty = false; + + private vert = ` +precision highp float; + +attribute vec3 aPos;//x y + +varying vec4 vColor; + +uniform vec4 _Color; +uniform mat4 effects_MatrixVP; +uniform mat4 effects_MatrixInvV; +uniform mat4 effects_ObjectToWorld; + +void main() { + vColor = _Color; + vec4 pos = vec4(aPos.xyz, 1.0); + gl_Position = effects_MatrixVP * effects_ObjectToWorld * pos; +} +`; + + private frag = ` +precision highp float; + +varying vec4 vColor; + +void main() { + vec4 color = vec4(1.0,1.0,1.0,1.0); + gl_FragColor = color; +} +`; + + constructor (engine: Engine) { + super(engine); + + if (!this.geometry) { + this.geometry = Geometry.create(engine, { + attributes: { + aPos: { + type: glContext.FLOAT, + size: 3, + data: new Float32Array([ + -0.5, 0.5, 0, //左上 + -0.5, -0.5, 0, //左下 + 0.5, 0.5, 0, //右上 + 0.5, -0.5, 0, //右下 + ]), + }, + aUV:{ + type: glContext.FLOAT, + size: 2, + data: new Float32Array(), + }, + }, + mode: glContext.TRIANGLES, + drawCount: 4, + }); + } + + if (!this.material) { + const materialProps: MaterialProps = { + shader: { + vertex: this.vert, + fragment: this.frag, + glslVersion: GLSLVersion.GLSL1, + }, + }; + + this.material = Material.create(engine, materialProps); + this.material.setColor('_Color', new Color(1, 1, 1, 1)); + this.material.depthMask = true; + this.material.depthTest = true; + this.material.blending = true; + } + } + + override onUpdate (dt: number): void { + if (this.dirty) { + this.path.clear(); + this.path.moveTo(this.curveValues[this.curveValues.length - 1].point.x, this.curveValues[this.curveValues.length - 1].point.y); + + for (const curveValue of this.curveValues) { + const point = curveValue.point; + const control1 = curveValue.controlPoint1; + const control2 = curveValue.controlPoint2; + + this.path.bezierCurveTo(control1.x, control1.y, control2.x, control2.y, point.x, point.y, 1); + } + + this.buildGeometryFromPath(this.path.shapePath); + this.dirty = false; + } + } + + override render (renderer: Renderer): void { + if (renderer.renderingData.currentFrame.globalUniforms) { + renderer.setGlobalMatrix('effects_ObjectToWorld', this.transform.getWorldMatrix()); + } + renderer.drawGeometry(this.geometry, this.material); + } + + buildGeometryFromPath (shapePath: ShapePath) { + const shapePrimitives = shapePath.shapePrimitives; + const vertices: number[] = []; + + // triangulate shapePrimitive + for (const shapePrimitive of shapePrimitives) { + const shape = shapePrimitive.shape; + + vertices.push(...triangulate([shape.points])); + } + + // build vertices and uvs + const vertexCount = vertices.length / 2; + + let positionArray = this.geometry.getAttributeData('aPos'); + let uvArray = this.geometry.getAttributeData('aUV'); + + if (!positionArray || positionArray.length < vertexCount * 3) { + positionArray = new Float32Array(vertexCount * 3); + } + if (!uvArray || uvArray.length < vertexCount * 2) { + uvArray = new Float32Array(vertexCount * 2); + } + + for (let i = 0; i < vertexCount; i++) { + const pointsOffset = i * 3; + const positionArrayOffset = i * 2; + const uvOffset = i * 2; + + positionArray[pointsOffset] = vertices[positionArrayOffset]; + positionArray[pointsOffset + 1] = vertices[positionArrayOffset + 1]; + positionArray[pointsOffset + 2] = 0; + + uvArray[uvOffset] = positionArray[pointsOffset]; + uvArray[uvOffset + 1] = positionArray[pointsOffset + 1]; + } + + this.geometry.setAttributeData('aPos', positionArray); + this.geometry.setAttributeData('aUV', uvArray); + this.geometry.setDrawCount(vertexCount); + } + + override fromData (data: ShapeCustomComponent): void { + super.fromData(data); + + const points = data.param.points; + const easingIns = data.param.easingIn; + const easingOuts = data.param.easingOut; + + for (const shape of data.param.shapes) { + const indices = shape.indexes; + + for (let i = 1; i < indices.length; i++) { + const pointIndex = indices[i]; + const lastPointIndex = indices[i - 1]; + + this.curveValues.push({ + point: points[pointIndex.point], + controlPoint1: easingOuts[lastPointIndex.easingOut], + controlPoint2: easingIns[pointIndex.easingIn], + }); + } + + // Push the last curve + this.curveValues.push({ + point: points[indices[0].point], + controlPoint1: easingOuts[indices[indices.length - 1].easingOut], + controlPoint2: easingIns[indices[0].easingIn], + }); + } + + this.dirty = true; + } +} + +/************************** Test Interface **********************************/ + +/** + * 矢量图形组件 + */ +export interface ShapeComponentData extends spec.ComponentData { + /** + * 矢量类型 + */ + type: ComponentShapeType, +} + +/** + * 矢量图形类型 + */ +export enum ComponentShapeType { + /** + * 自定义图形 + */ + CUSTOM, + /** + * 矩形 + */ + RECTANGLE, + /** + * 椭圆 + */ + ELLIPSE, + /** + * 多边形 + */ + POLYGON, + /** + * 星形 + */ + STAR, +} + +/** + * 自定义图形组件 + */ +export interface ShapeCustomComponent extends ShapeComponentData { + /** + * 矢量类型 - 形状 + */ + type: ComponentShapeType.CUSTOM, + /** + * 矢量参数 - 形状 + */ + param: ShapeCustomParam, +} + +/** + * 矢量路径参数 + */ +export interface ShapeCustomParam { + /** + * 路径点 + */ + points: spec.Vector3Data[], + /** + * 入射控制点 + */ + easingIn: spec.Vector3Data[], + /** + * 入射控制点 + */ + easingOut: spec.Vector3Data[], + /** + * 自定义形状 + */ + shapes: CustomShape[], +} + +/** + * 自定义形状参数 + */ +export interface CustomShape { + /** + * 是否垂直与平面 - 用于减少实时运算 + */ + verticalToPlane: 'x' | 'y' | 'z' | 'none', + /** + * 点索引 - 用于构成闭合图形 + */ + indexes: CustomShapePoint[], + /** + * 是否为闭合图形 - 用于Stroke + */ + close: boolean, + /** + * 填充属性 + */ + fill?: ShapeFillParam, + /** + * 描边属性 + */ + stroke?: ShapeStrokeParam, + /** + * 空间变换 + */ + transform?: spec.TransformData, +} + +/** + * 自定义形状点 + */ +export interface CustomShapePoint { + /** + * 顶点索引 + */ + point: number, + /** + * 入射点索引 + */ + easingIn: number, + /** + * 出射点索引 + */ + easingOut: number, +} + +/** + * 矢量填充参数 + */ +export interface ShapeFillParam { + /** + * 填充颜色 + */ + color: spec.ColorExpression, +} + +/** + * 矢量描边参数 + */ +export interface ShapeStrokeParam { + /** + * 线宽 + */ + width: number, + /** + * 线颜色 + */ + color: spec.ColorExpression, + /** + * 连接类型 + */ + connectType: ShapeConnectType, + /** + * 点类型 + */ + pointType: ShapePointType, +} + +// 本期无该功能 待补充 +export enum ShapeConnectType { + +} + +// @待补充 +export enum ShapePointType { +} diff --git a/packages/effects-core/src/plugins/shape/build-adaptive-bezier.ts b/packages/effects-core/src/plugins/shape/build-adaptive-bezier.ts new file mode 100644 index 00000000..9022fcb8 --- /dev/null +++ b/packages/effects-core/src/plugins/shape/build-adaptive-bezier.ts @@ -0,0 +1,216 @@ +// thanks to https://github.com/mattdesl/adaptive-bezier-curve +// for the original code! + +const RECURSION_LIMIT = 8; +const FLT_EPSILON = 1.19209290e-7; +const PATH_DISTANCE_EPSILON = 1.0; + +const curveAngleToleranceEpsilon = 0.01; +const mAngleTolerance = 0; +const mCuspLimit = 0; + +const defaultBezierSmoothness = 0.5; + +export function buildAdaptiveBezier ( + points: number[], + sX: number, sY: number, + cp1x: number, cp1y: number, + cp2x: number, cp2y: number, + eX: number, eY: number, + smoothness?: number, +) { + // TODO expose as a parameter + const scale = 5; + const smoothing = Math.min( + 0.99, // a value of 1.0 actually inverts smoothing, so we cap it at 0.99 + Math.max(0, smoothness ?? defaultBezierSmoothness) + ); + let distanceTolerance = (PATH_DISTANCE_EPSILON - smoothing) / scale; + + distanceTolerance *= distanceTolerance; + begin(sX, sY, cp1x, cp1y, cp2x, cp2y, eX, eY, points, distanceTolerance); + + return points; +} + +//// Based on: +//// https://github.com/pelson/antigrain/blob/master/agg-2.4/src/agg_curves.cpp + +function begin ( + sX: number, sY: number, + cp1x: number, cp1y: number, + cp2x: number, cp2y: number, + eX: number, eY: number, + points: number[], + distanceTolerance: number, +) { + // dont need to actually ad this! + // points.push(sX, sY); + recursive(sX, sY, cp1x, cp1y, cp2x, cp2y, eX, eY, points, distanceTolerance, 0); + points.push(eX, eY); +} + +// eslint-disable-next-line max-params +function recursive ( + x1: number, y1: number, + x2: number, y2: number, + x3: number, y3: number, + x4: number, y4: number, + points: number[], + distanceTolerance: number, + level: number, +) { + if (level > RECURSION_LIMIT) { return; } + + const pi = Math.PI; + + // Calculate all the mid-points of the line segments + // ---------------------- + const x12 = (x1 + x2) / 2; + const y12 = (y1 + y2) / 2; + const x23 = (x2 + x3) / 2; + const y23 = (y2 + y3) / 2; + const x34 = (x3 + x4) / 2; + const y34 = (y3 + y4) / 2; + const x123 = (x12 + x23) / 2; + const y123 = (y12 + y23) / 2; + const x234 = (x23 + x34) / 2; + const y234 = (y23 + y34) / 2; + const x1234 = (x123 + x234) / 2; + const y1234 = (y123 + y234) / 2; + + if (level > 0) { // Enforce subdivision first time + // Try to approximate the full cubic curve by a single straight line + // ------------------ + let dx = x4 - x1; + let dy = y4 - y1; + + const d2 = Math.abs(((x2 - x4) * dy) - ((y2 - y4) * dx)); + const d3 = Math.abs(((x3 - x4) * dy) - ((y3 - y4) * dx)); + + let da1; let da2; + + if (d2 > FLT_EPSILON && d3 > FLT_EPSILON) { + // Regular care + // ----------------- + if ((d2 + d3) * (d2 + d3) <= distanceTolerance * ((dx * dx) + (dy * dy))) { + // If the curvature doesn't exceed the distanceTolerance value + // we tend to finish subdivisions. + // ---------------------- + if (mAngleTolerance < curveAngleToleranceEpsilon) { + points.push(x1234, y1234); + + return; + } + + // Angle & Cusp Condition + // ---------------------- + const a23 = Math.atan2(y3 - y2, x3 - x2); + + da1 = Math.abs(a23 - Math.atan2(y2 - y1, x2 - x1)); + da2 = Math.abs(Math.atan2(y4 - y3, x4 - x3) - a23); + + if (da1 >= pi) { da1 = (2 * pi) - da1; } + if (da2 >= pi) { da2 = (2 * pi) - da2; } + + if (da1 + da2 < mAngleTolerance) { + // Finally we can stop the recursion + // ---------------------- + points.push(x1234, y1234); + + return; + } + + if (mCuspLimit !== 0.0) { + if (da1 > mCuspLimit) { + points.push(x2, y2); + + return; + } + + if (da2 > mCuspLimit) { + points.push(x3, y3); + + return; + } + } + } + } else if (d2 > FLT_EPSILON) { + // p1,p3,p4 are collinear, p2 is considerable + // ---------------------- + if (d2 * d2 <= distanceTolerance * ((dx * dx) + (dy * dy))) { + if (mAngleTolerance < curveAngleToleranceEpsilon) { + points.push(x1234, y1234); + + return; + } + + // Angle Condition + // ---------------------- + da1 = Math.abs(Math.atan2(y3 - y2, x3 - x2) - Math.atan2(y2 - y1, x2 - x1)); + if (da1 >= pi) { da1 = (2 * pi) - da1; } + + if (da1 < mAngleTolerance) { + points.push(x2, y2); + points.push(x3, y3); + + return; + } + + if (mCuspLimit !== 0.0) { + if (da1 > mCuspLimit) { + points.push(x2, y2); + + return; + } + } + } + } else if (d3 > FLT_EPSILON) { + // p1,p2,p4 are collinear, p3 is considerable + // ---------------------- + if (d3 * d3 <= distanceTolerance * ((dx * dx) + (dy * dy))) { + if (mAngleTolerance < curveAngleToleranceEpsilon) { + points.push(x1234, y1234); + + return; + } + + // Angle Condition + // ---------------------- + da1 = Math.abs(Math.atan2(y4 - y3, x4 - x3) - Math.atan2(y3 - y2, x3 - x2)); + if (da1 >= pi) { da1 = (2 * pi) - da1; } + + if (da1 < mAngleTolerance) { + points.push(x2, y2); + points.push(x3, y3); + + return; + } + + if (mCuspLimit !== 0.0) { + if (da1 > mCuspLimit) { + points.push(x3, y3); + + return; + } + } + } + } else { + // Collinear case + // ----------------- + dx = x1234 - ((x1 + x4) / 2); + dy = y1234 - ((y1 + y4) / 2); + if ((dx * dx) + (dy * dy) <= distanceTolerance) { + points.push(x1234, y1234); + + return; + } + } + } + + // Continue subdivision + // ---------------------- + recursive(x1, y1, x12, y12, x123, y123, x1234, y1234, points, distanceTolerance, level + 1); + recursive(x1234, y1234, x234, y234, x34, y34, x4, y4, points, distanceTolerance, level + 1); +} + diff --git a/packages/effects-core/src/plugins/shape/graphics-path.ts b/packages/effects-core/src/plugins/shape/graphics-path.ts new file mode 100644 index 00000000..aea347e9 --- /dev/null +++ b/packages/effects-core/src/plugins/shape/graphics-path.ts @@ -0,0 +1,95 @@ +/** + * Based on: + * https://github.com/pixijs/pixijs/blob/dev/src/scene/graphics/shared/path/GraphicsPath.ts + */ + +import { ShapePath } from './shape-path'; + +export class GraphicsPath { + instructions: PathInstruction[] = []; + + private dirty = false; + private _shapePath: ShapePath; + + /** + * Provides access to the internal shape path, ensuring it is up-to-date with the current instructions. + * @returns The `ShapePath` instance associated with this `GraphicsPath`. + */ + get shapePath (): ShapePath { + if (!this._shapePath) { + this._shapePath = new ShapePath(this); + } + + if (this.dirty) { + this.dirty = false; + this._shapePath.buildPath(); + } + + return this._shapePath; + } + + /** + * Adds a cubic Bezier curve to the path. + * It requires three points: the first two are control points and the third one is the end point. + * The starting point is the last point in the current path. + * @param cp1x - The x-coordinate of the first control point. + * @param cp1y - The y-coordinate of the first control point. + * @param cp2x - The x-coordinate of the second control point. + * @param cp2y - The y-coordinate of the second control point. + * @param x - The x-coordinate of the end point. + * @param y - The y-coordinate of the end point. + * @param smoothness - Optional parameter to adjust the smoothness of the curve. + * @returns The instance of the current object for chaining. + */ + bezierCurveTo ( + cp1x: number, cp1y: number, cp2x: number, cp2y: number, + x: number, y: number, + smoothness?: number, + ): GraphicsPath { + this.instructions.push({ action: 'bezierCurveTo', data: [cp1x, cp1y, cp2x, cp2y, x, y, smoothness] }); + + this.dirty = true; + + return this; + } + + moveTo (x: number, y: number): GraphicsPath { + this.instructions.push({ action: 'moveTo', data: [x, y] }); + + this.dirty = true; + + return this; + } + + clear (): GraphicsPath { + this.instructions.length = 0; + this.dirty = true; + + return this; + } +} + +export interface PathInstruction { + action: + | 'moveTo' + | 'lineTo' + | 'quadraticCurveTo' + | 'bezierCurveTo' + | 'arc' + | 'closePath' + | 'addPath' + | 'arcTo' + | 'ellipse' + | 'rect' + | 'roundRect' + | 'arcToSvg' + | 'poly' + | 'circle' + | 'regularPoly' + | 'roundPoly' + | 'roundShape' + | 'filletRect' + | 'chamferRect' + , + data: any[], +} diff --git a/packages/effects-core/src/plugins/shape/point-data.ts b/packages/effects-core/src/plugins/shape/point-data.ts new file mode 100644 index 00000000..0d9ae7df --- /dev/null +++ b/packages/effects-core/src/plugins/shape/point-data.ts @@ -0,0 +1,7 @@ +export interface PointData { + /** X coord */ + x: number, + + /** Y coord */ + y: number, +} diff --git a/packages/effects-core/src/plugins/shape/polygon.ts b/packages/effects-core/src/plugins/shape/polygon.ts new file mode 100644 index 00000000..8573b12f --- /dev/null +++ b/packages/effects-core/src/plugins/shape/polygon.ts @@ -0,0 +1,140 @@ +/** + * Based on: + * https://github.com/pixijs/pixijs/blob/dev/src/maths/shapes/Polygon.ts + */ + +import type { PointData } from './point-data'; + +/** + * A class to define a shape via user defined coordinates. + */ +export class Polygon { + /** An array of the points of this polygon. */ + points: number[] = []; + + /** `false` after moveTo, `true` after `closePath`. In all other cases it is `true`. */ + closePath: boolean = false; + + constructor (points: PointData[] | number[]); + constructor (...points: PointData[] | number[]); + /** + * @param points - This can be an array of Points + * that form the polygon, a flat array of numbers that will be interpreted as [x,y, x,y, ...], or + * the arguments passed can be all the points of the polygon e.g. + * `new Polygon(new Point(), new Point(), ...)`, or the arguments passed can be flat + * x,y values e.g. `new Polygon(x,y, x,y, x,y, ...)` where `x` and `y` are Numbers. + */ + constructor (...points: (PointData[] | number[])[] | PointData[] | number[]) { + let flat = Array.isArray(points[0]) ? points[0] : points; + + // if this is an array of points, convert it to a flat array of numbers + if (typeof flat[0] !== 'number') { + const p: number[] = []; + + for (let i = 0, il = flat.length; i < il; i++) { + p.push((flat[i] as PointData).x, (flat[i] as PointData).y); + } + + flat = p; + } + + this.points = flat as number[]; + this.closePath = true; + } + + /** + * Creates a clone of this polygon. + * @returns - A copy of the polygon. + */ + clone (): Polygon { + const points = this.points.slice(); + const polygon = new Polygon(points); + + polygon.closePath = this.closePath; + + return polygon; + } + + /** + * Checks whether the x and y coordinates passed to this function are contained within this polygon. + * @param x - The X coordinate of the point to test. + * @param y - The Y coordinate of the point to test. + * @returns - Whether the x/y coordinates are within this polygon. + */ + contains (x: number, y: number): boolean { + let inside = false; + + // use some raycasting to test hits + // https://github.com/substack/point-in-polygon/blob/master/index.js + const length = this.points.length / 2; + + for (let i = 0, j = length - 1; i < length; j = i++) { + const xi = this.points[i * 2]; + const yi = this.points[(i * 2) + 1]; + const xj = this.points[j * 2]; + const yj = this.points[(j * 2) + 1]; + const intersect = ((yi > y) !== (yj > y)) && (x < ((xj - xi) * ((y - yi) / (yj - yi))) + xi); + + if (intersect) { + inside = !inside; + } + } + + return inside; + } + + /** + * Copies another polygon to this one. + * @param polygon - The polygon to copy from. + * @returns Returns itself. + */ + copyFrom (polygon: Polygon): this { + this.points = polygon.points.slice(); + this.closePath = polygon.closePath; + + return this; + } + + /** + * Copies this polygon to another one. + * @param polygon - The polygon to copy to. + * @returns Returns given parameter. + */ + copyTo (polygon: Polygon): Polygon { + polygon.copyFrom(this); + + return polygon; + } + + /** + * Get the last X coordinate of the polygon + * @readonly + */ + get lastX (): number { + return this.points[this.points.length - 2]; + } + + /** + * Get the last Y coordinate of the polygon + * @readonly + */ + get lastY (): number { + return this.points[this.points.length - 1]; + } + + /** + * Get the first X coordinate of the polygon + * @readonly + */ + get x (): number { + return this.points[this.points.length - 2]; + } + /** + * Get the first Y coordinate of the polygon + * @readonly + */ + get y (): number { + return this.points[this.points.length - 1]; + } +} + diff --git a/packages/effects-core/src/plugins/shape/shape-path.ts b/packages/effects-core/src/plugins/shape/shape-path.ts new file mode 100644 index 00000000..564c7e40 --- /dev/null +++ b/packages/effects-core/src/plugins/shape/shape-path.ts @@ -0,0 +1,134 @@ +/** + * Based on: + * https://github.com/pixijs/pixijs/blob/dev/src/scene/graphics/shared/path/ShapePath.ts + */ + +import type { Matrix3 } from '@galacean/effects-math/es/core/matrix3'; +import { Polygon } from './polygon'; +import { buildAdaptiveBezier } from './build-adaptive-bezier'; +import type { GraphicsPath } from './graphics-path'; + +export class ShapePath { + currentPoly: Polygon | null = null; + shapePrimitives: { shape: Polygon, transform?: Matrix3 }[] = []; + + constructor ( + private graphicsPath: GraphicsPath, + ) { + } + + /** Builds the path. */ + buildPath () { + this.currentPoly = null; + this.shapePrimitives.length = 0; + const path = this.graphicsPath; + + for (const instruction of path.instructions) { + const action = instruction.action; + const data = instruction.data; + + switch (action) { + case 'bezierCurveTo': { + this.bezierCurveTo(data[0], data[1], data[2], data[3], data[4], data[5], data[6]); + + break; + } + case 'moveTo': { + this.moveTo(data[0], data[1]); + + break; + } + } + } + this.endPoly(); + } + + /** + * Adds a cubic Bezier curve to the path. + * It requires three points: the first two are control points and the third one is the end point. + * The starting point is the last point in the current path. + * @param cp1x - The x-coordinate of the first control point. + * @param cp1y - The y-coordinate of the first control point. + * @param cp2x - The x-coordinate of the second control point. + * @param cp2y - The y-coordinate of the second control point. + * @param x - The x-coordinate of the end point. + * @param y - The y-coordinate of the end point. + * @param smoothness - Optional parameter to adjust the smoothness of the curve. + * @returns The instance of the current object for chaining. + */ + bezierCurveTo ( + cp1x: number, cp1y: number, cp2x: number, cp2y: number, + x: number, y: number, + smoothness?: number, + ): ShapePath { + this.ensurePoly(); + const currentPoly = this.currentPoly as Polygon; + + buildAdaptiveBezier( + currentPoly.points, + currentPoly.lastX, currentPoly.lastY, + cp1x, cp1y, cp2x, cp2y, x, y, + smoothness, + ); + + return this; + } + + moveTo (x: number, y: number): ShapePath { + this.startPoly(x, y); + + return this; + } + + /** + * Starts a new polygon path from the specified starting point. + * This method initializes a new polygon or ends the current one if it exists. + * @param x - The x-coordinate of the starting point of the new polygon. + * @param y - The y-coordinate of the starting point of the new polygon. + * @returns The instance of the current object for chaining. + */ + private startPoly (x: number, y: number): this { + let currentPoly = this.currentPoly; + + if (currentPoly) { + this.endPoly(); + } + + currentPoly = new Polygon(); + + currentPoly.points.push(x, y); + + this.currentPoly = currentPoly; + + return this; + } + + /** + * Ends the current polygon path. If `closePath` is set to true, + * the path is closed by connecting the last point to the first one. + * This method finalizes the current polygon and prepares it for drawing or adding to the shape primitives. + * @param closePath - A boolean indicating whether to close the polygon by connecting the last point + * back to the starting point. False by default. + * @returns The instance of the current object for chaining. + */ + private endPoly (closePath = false): this { + const shape = this.currentPoly; + + if (shape && shape.points.length > 2) { + shape.closePath = closePath; + + this.shapePrimitives.push({ shape }); + } + + this.currentPoly = null; + + return this; + } + + private ensurePoly (start = true): void { + if (this.currentPoly) { return; } + + this.currentPoly = new Polygon(); + this.currentPoly.points.push(0, 0); + } +} diff --git a/packages/effects-core/src/plugins/shape/triangulate.ts b/packages/effects-core/src/plugins/shape/triangulate.ts new file mode 100644 index 00000000..82d627d4 --- /dev/null +++ b/packages/effects-core/src/plugins/shape/triangulate.ts @@ -0,0 +1,73 @@ +import * as libtess from 'libtess'; + +const tessy = (function initTesselator () { + // function called for each vertex of tesselator output + function vertexCallback ( + data: [x: number, y: number, z: number], + polyVertArray: number[], + ) { + polyVertArray[polyVertArray.length] = data[0]; + polyVertArray[polyVertArray.length] = data[1]; + } + function begincallback (type: number) { + if (type !== libtess.primitiveType.GL_TRIANGLES) { + console.info('expected TRIANGLES but got type: ' + type); + } + } + function errorcallback (errno: number) { + console.error('error callback, error number: ' + errno); + } + // callback for when segments intersect and must be split + function combinecallback ( + coords: [number, number, number], + data: number[][], + weight: number[], + ) { + // console.log('combine callback'); + return [coords[0], coords[1], coords[2]]; + } + function edgeCallback (flag: boolean) { + // don't really care about the flag, but need no-strip/no-fan behavior + // console.log('edge flag: ' + flag); + } + + const tessy = new libtess.GluTesselator(); + + // tessy.gluTessProperty(libtess.gluEnum.GLU_TESS_WINDING_RULE, libtess.windingRule.GLU_TESS_WINDING_POSITIVE); + tessy.gluTessCallback(libtess.gluEnum.GLU_TESS_VERTEX_DATA, vertexCallback); + tessy.gluTessCallback(libtess.gluEnum.GLU_TESS_BEGIN, begincallback); + tessy.gluTessCallback(libtess.gluEnum.GLU_TESS_ERROR, errorcallback); + tessy.gluTessCallback(libtess.gluEnum.GLU_TESS_COMBINE, combinecallback); + tessy.gluTessCallback(libtess.gluEnum.GLU_TESS_EDGE_FLAG, edgeCallback); + + return tessy; +})(); + +export function triangulate (contours: number[][]) { + // libtess will take 3d verts and flatten to a plane for tesselation + // since only doing 2d tesselation here, provide z=1 normal to skip + // iterating over verts only to get the same answer. + // comment out to test normal-generation code + tessy.gluTessNormal(0, 0, 1); + + const triangleVerts: number[] = []; + + tessy.gluTessBeginPolygon(triangleVerts); + + for (let i = 0; i < contours.length; i++) { + tessy.gluTessBeginContour(); + const contour = contours[i]; + + for (let j = 0; j < contour.length; j += 2) { + const coords = [contour[j], contour[j + 1], 0]; + + tessy.gluTessVertex(coords, coords); + } + tessy.gluTessEndContour(); + } + + // finish polygon + tessy.gluTessEndPolygon(); + + return triangleVerts; +} diff --git a/packages/effects-webgl/src/gl-material.ts b/packages/effects-webgl/src/gl-material.ts index a29052dd..87bb83ae 100644 --- a/packages/effects-webgl/src/gl-material.ts +++ b/packages/effects-webgl/src/gl-material.ts @@ -622,6 +622,7 @@ export class GLMaterial extends Material { materialData.floats = {}; materialData.ints = {}; materialData.vector4s = {}; + materialData.colors = {}; materialData.textures = {}; materialData.dataType = spec.DataType.Material; materialData.stringTags = this.stringTags; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b117821a..d80c7ec0 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -155,6 +155,9 @@ importers: flatbuffers: specifier: 24.3.25 version: 24.3.25 + libtess: + specifier: 1.2.2 + version: 1.2.2 uuid: specifier: 9.0.1 version: 9.0.1 @@ -4917,6 +4920,10 @@ packages: type-check: 0.4.0 dev: true + /libtess@1.2.2: + resolution: {integrity: sha512-Nps8HPeVVcsmJxUvFLKVJcCgcz+1ajPTXDVAVPs6+giOQP4AHV31uZFFkh+CKow/bkB7GbZWKmwmit7myaqDSw==} + dev: false + /lines-and-columns@1.2.4: resolution: {integrity: sha1-7KKE910pZQeTCdwK2SVauy68FjI=, tarball: https://registry.npmmirror.com/lines-and-columns/download/lines-and-columns-1.2.4.tgz} dev: true diff --git a/types/shim.d.ts b/types/shim.d.ts index 9f19c955..5603385d 100644 --- a/types/shim.d.ts +++ b/types/shim.d.ts @@ -1,5 +1,6 @@ declare module 'string-hash'; declare module 'uuid'; +declare module 'libtess'; declare module 'earcut' { export interface Node { diff --git a/web-packages/demo/html/shape.html b/web-packages/demo/html/shape.html new file mode 100644 index 00000000..c4caf040 --- /dev/null +++ b/web-packages/demo/html/shape.html @@ -0,0 +1,21 @@ + + + + + + 图形元素 - demo + + + +
+ + + + diff --git a/web-packages/demo/index.html b/web-packages/demo/index.html index 6fcef3b3..972f85d9 100644 --- a/web-packages/demo/index.html +++ b/web-packages/demo/index.html @@ -22,6 +22,7 @@ 动态视频 分层渲染 文本元素 + 图形元素
Inspire compare Demo
diff --git a/web-packages/demo/src/shape.ts b/web-packages/demo/src/shape.ts new file mode 100644 index 00000000..ca69a77e --- /dev/null +++ b/web-packages/demo/src/shape.ts @@ -0,0 +1,218 @@ +import { Player, ShapeComponent } from '@galacean/effects'; + +const json = { + 'playerVersion': { + 'web': '2.0.4', + 'native': '0.0.1.202311221223', + }, + 'images': [], + 'fonts': [], + 'version': '3.0', + 'shapes': [], + 'plugins': [], + 'type': 'ge', + 'compositions': [ + { + 'id': '1', + 'name': '新建合成1', + 'duration': 6, + 'startTime': 0, + 'endBehavior': 4, + 'previewSize': [ + 750, + 1624, + ], + 'items': [ + { + 'id': '21135ac68dfc49bcb2bc7552cbb9ad07', + }, + ], + 'camera': { + 'fov': 60, + 'far': 40, + 'near': 0.1, + 'clipMode': 1, + 'position': [ + 0, + 0, + 8, + ], + 'rotation': [ + 0, + 0, + 0, + ], + }, + 'sceneBindings': [ + ], + 'timelineAsset': { + 'id': 'dd50ad0de3f044a5819576175acf05f7', + }, + }, + ], + 'components': [ + { + 'id': 'b7890caa354a4c279ff9678c5530cd83', + 'item': { + 'id': '21135ac68dfc49bcb2bc7552cbb9ad07', + }, + 'dataType': 'ShapeComponent', + 'type': 0, + 'param': { + 'points': [ + { + 'x': -1, + 'y': -1, + 'z': 0, + }, + { + 'x': 1, + 'y': -1, + 'z': 0, + }, + { + 'x': 0, + 'y': 1, + 'z': 0, + }, + ], + 'easingIn': [ + { + 'x': -1, + 'y': -0.5, + 'z': 0, + }, + { + 'x': 0.5, + 'y': -1.5, + 'z': 0, + }, + { + 'x': 0.5, + 'y': 1, + 'z': 0, + }, + ], + 'easingOut': [ + { + 'x': -0.5, + 'y': -1.5, + 'z': 0, + }, + { + 'x': 1, + 'y': -0.5, + 'z': 0, + }, + { + 'x': -0.5, + 'y': 1, + 'z': 0, + }, + ], + 'shapes': [ + { + 'verticalToPlane': 'z', + 'indexes': [ + { + 'point': 0, + 'easingIn': 0, + 'easingOut': 0, + }, + { + 'point': 1, + 'easingIn': 1, + 'easingOut': 1, + }, + { + 'point': 2, + 'easingIn': 2, + 'easingOut': 2, + }, + ], + 'close': true, + 'fill': { + 'color': [8, [255, 0, 0, 255]], + }, + }, + ], + }, + 'renderer': { + 'renderMode': 1, + }, + }, + ], + 'geometries': [], + 'materials': [], + 'items': [ + { + 'id': '21135ac68dfc49bcb2bc7552cbb9ad07', + 'name': 'Shape', + 'duration': 5, + 'type': '1', + 'visible': true, + 'endBehavior': 0, + 'delay': 0, + 'renderLevel': 'B+', + 'components': [ + { + 'id': 'b7890caa354a4c279ff9678c5530cd83', + }, + ], + 'transform': { + 'position': { + 'x': 0, + 'y': 0, + 'z': 0, + }, + 'eulerHint': { + 'x': 0, + 'y': 0, + 'z': 0, + }, + 'anchor': { + 'x': 0, + 'y': 0, + }, + 'size': { + 'x': 1.2, + 'y': 1.2, + }, + 'scale': { + 'x': 1, + 'y': 1, + 'z': 1, + }, + }, + 'dataType': 'VFXItemData', + }, + ], + 'shaders': [], + 'bins': [], + 'textures': [], + 'animations': [], + 'miscs': [ + { + 'id': 'dd50ad0de3f044a5819576175acf05f7', + 'dataType': 'TimelineAsset', + 'tracks': [ + ], + }, + ], + 'compositionId': '1', +}; +const container = document.getElementById('J-container'); + +(async () => { + try { + const player = new Player({ + container, + }); + + const composition = await player.loadScene(json); + const item = composition.getItemByName('Shape'); + const shapeComponent = item?.getComponent(ShapeComponent); + } catch (e) { + console.error('biz', e); + } +})(); diff --git a/web-packages/demo/vite.config.js b/web-packages/demo/vite.config.js index da45816a..39faf7f2 100644 --- a/web-packages/demo/vite.config.js +++ b/web-packages/demo/vite.config.js @@ -23,9 +23,11 @@ export default defineConfig(({ mode }) => { 'dashboard': resolve(__dirname, 'html/dashboard.html'), 'dynamic-image': resolve(__dirname, 'html/dynamic-image.html'), 'dynamic-video': resolve(__dirname, 'html/dynamic-video.html'), - 'render-level': resolve(__dirname, 'html/render-level.html'), + 'interactive': resolve(__dirname, 'html/interactive.html'), 'local-file': resolve(__dirname, 'html/local-file.html'), 'post-processing': resolve(__dirname, 'html/post-processing.html'), + 'render-level': resolve(__dirname, 'html/render-level.html'), + 'shape': resolve(__dirname, 'html/shape.html'), 'single': resolve(__dirname, 'html/single.html'), 'text': resolve(__dirname, 'html/text.html'), 'three-particle': resolve(__dirname, 'html/three-particle.html'), diff --git a/web-packages/imgui-demo/src/ge.ts b/web-packages/imgui-demo/src/ge.ts index a4666b68..365a1233 100644 --- a/web-packages/imgui-demo/src/ge.ts +++ b/web-packages/imgui-demo/src/ge.ts @@ -1,5 +1,5 @@ import type { MaterialProps, Renderer } from '@galacean/effects'; -import { GLSLVersion, Geometry, Material, OrderType, Player, PostProcessVolume, RenderPass, RenderPassPriorityPostprocess, VFXItem, glContext, math } from '@galacean/effects'; +import { GLSLVersion, Geometry, Material, OrderType, Player, PostProcessVolume, RenderPass, RenderPassPriorityPostprocess, RendererComponent, VFXItem, glContext, math } from '@galacean/effects'; import '@galacean/effects-plugin-model'; import { JSONConverter } from '@galacean/effects-plugin-model'; import '@galacean/effects-plugin-orientation-transformer'; @@ -79,7 +79,7 @@ export class GalaceanEffects { 'item': { 'id': '3f40a594b3f34d10b963ad4fc736e505', }, - 'dataType': 'EffectComponent', + 'dataType': 'ShapeComponent', 'geometry': { 'id': '78cc7d2350bb417bb5dc93afab243411', }, @@ -235,7 +235,267 @@ export class GalaceanEffects { GalaceanEffects.assetDataBase = new AssetDatabase(GalaceanEffects.player.renderer.engine); GalaceanEffects.player.renderer.engine.database = GalaceanEffects.assetDataBase; //@ts-expect-error - GalaceanEffects.playURL(json); + GalaceanEffects.playURL({ + 'playerVersion': { + 'web': '2.0.4', + 'native': '0.0.1.202311221223', + }, + 'images': [], + 'fonts': [], + 'version': '3.0', + 'shapes': [], + 'plugins': [], + 'type': 'ge', + 'compositions': [ + { + 'id': '1', + 'name': '新建合成1', + 'duration': 6, + 'startTime': 0, + 'endBehavior': 4, + 'previewSize': [ + 750, + 1624, + ], + 'items': [ + { + 'id': '21135ac68dfc49bcb2bc7552cbb9ad07', + }, + ], + 'camera': { + 'fov': 60, + 'far': 40, + 'near': 0.1, + 'clipMode': 1, + 'position': [ + 0, + 0, + 8, + ], + 'rotation': [ + 0, + 0, + 0, + ], + }, + 'sceneBindings': [ + { + 'key': { + 'id': 'f8a6089ed7794f479907ed0bcac17220', + }, + 'value': { + 'id': '21135ac68dfc49bcb2bc7552cbb9ad07', + }, + }, + ], + 'timelineAsset': { + 'id': 'dd50ad0de3f044a5819576175acf05f7', + }, + }, + ], + 'components': [ + { + 'id': 'b7890caa354a4c279ff9678c5530cd83', + 'item': { + 'id': '21135ac68dfc49bcb2bc7552cbb9ad07', + }, + 'dataType': 'ShapeComponent', + 'type': 0, + 'param': { + 'points': [ + { + 'x': -1, + 'y': -1, + 'z': 0, + }, + { + 'x': 1, + 'y': -1, + 'z': 0, + }, + { + 'x': 0, + 'y': 1, + 'z': 0, + }, + ], + 'easingIn': [ + { + 'x': -1, + 'y': -0.5, + 'z': 0, + }, + { + 'x': 0.5, + 'y': -1.5, + 'z': 0, + }, + { + 'x': 0.5, + 'y': 1, + 'z': 0, + }, + ], + 'easingOut': [ + { + 'x': -0.5, + 'y': -1.5, + 'z': 0, + }, + { + 'x': 1, + 'y': -0.5, + 'z': 0, + }, + { + 'x': -0.5, + 'y': 1, + 'z': 0, + }, + ], + 'shapes': [ + { + 'verticalToPlane': 'z', + 'indexes': [ + { + 'point': 0, + 'easingIn': 0, + 'easingOut': 0, + }, + { + 'point': 1, + 'easingIn': 1, + 'easingOut': 1, + }, + { + 'point': 2, + 'easingIn': 2, + 'easingOut': 2, + }, + ], + 'close': true, + 'fill': { + 'color': [8, [255, 0, 0, 255]], + }, + }, + ], + }, + 'renderer': { + 'renderMode': 1, + }, + }, + ], + 'geometries': [], + 'materials': [], + 'items': [ + { + 'id': '21135ac68dfc49bcb2bc7552cbb9ad07', + 'name': 'sprite_1', + 'duration': 5, + 'type': '1', + 'visible': true, + 'endBehavior': 0, + 'delay': 0, + 'renderLevel': 'B+', + 'components': [ + { + 'id': 'b7890caa354a4c279ff9678c5530cd83', + }, + ], + 'transform': { + 'position': { + 'x': 0, + 'y': 0, + 'z': 0, + }, + 'eulerHint': { + 'x': 0, + 'y': 0, + 'z': 0, + }, + 'anchor': { + 'x': 0, + 'y': 0, + }, + 'size': { + 'x': 1.2, + 'y': 1.2, + }, + 'scale': { + 'x': 1, + 'y': 1, + 'z': 1, + }, + }, + 'dataType': 'VFXItemData', + }, + ], + 'shaders': [], + 'bins': [], + 'textures': [], + 'animations': [], + 'miscs': [ + { + 'id': 'dd50ad0de3f044a5819576175acf05f7', + 'dataType': 'TimelineAsset', + 'tracks': [ + ], + }, + { + 'id': '51ed062462544526998daf514c320854', + 'dataType': 'ActivationPlayableAsset', + }, + { + 'id': '9fd3412cf92c4dc19a2deabb942dadb2', + 'dataType': 'TransformPlayableAsset', + 'positionOverLifetime': {}, + }, + { + 'id': 'a59a15a3f3b4414abb81217733561926', + 'dataType': 'ActivationTrack', + 'children': [], + 'clips': [ + { + 'start': 0, + 'duration': 5, + 'endBehavior': 0, + 'asset': { + 'id': '51ed062462544526998daf514c320854', + }, + }, + ], + }, + { + 'id': '9436a285d586414ab72622f64b11f54d', + 'dataType': 'TransformTrack', + 'children': [], + 'clips': [ + { + 'start': 0, + 'duration': 5, + 'endBehavior': 0, + 'asset': { + 'id': '9fd3412cf92c4dc19a2deabb942dadb2', + }, + }, + ], + }, + { + 'id': 'f8a6089ed7794f479907ed0bcac17220', + 'dataType': 'ObjectBindingTrack', + 'children': [ + { + 'id': 'a59a15a3f3b4414abb81217733561926', + }, + { + 'id': '9436a285d586414ab72622f64b11f54d', + }, + ], + 'clips': [], + }, + ], + 'compositionId': '1', + }); } static playURL (url: string, use3DConverter = false) { @@ -255,6 +515,7 @@ export class GalaceanEffects { } else { void GalaceanEffects.player.loadScene(url, { autoplay:true }).then(composition=>{ composition.rootItem.addComponent(PostProcessVolume); + composition.renderFrame.addRenderPass(new OutlinePass(composition.renderer, { name: 'OutlinePass', priority: RenderPassPriorityPostprocess, diff --git a/web-packages/imgui-demo/src/main.ts b/web-packages/imgui-demo/src/main.ts index 4eef8671..d5ab3360 100644 --- a/web-packages/imgui-demo/src/main.ts +++ b/web-packages/imgui-demo/src/main.ts @@ -85,10 +85,11 @@ async function _init (): Promise { io.ConfigDockingAlwaysTabBar = true; // Setup Dear ImGui style - ImGui.StyleColorsDark(); - //ImGui.StyleColorsClassic(); + // ImGui.StyleColorsDark(); + // ImGui.StyleColorsClassic(); // embraceTheDarkness(); - SoDark(0.548); + // SoDark(0.548); + styleBlack(); // Load Fonts // - If no fonts are loaded, dear imgui will use the default font. You can also load multiple fonts and use ImGui::PushFont()/PopFont() to select them. @@ -171,7 +172,7 @@ function _loop (time: number): void { // 2. Show a simple window that we create ourselves. We use a Begin/End pair to created a named window. { - // static float f = 0.0f; + // static float f = 0.0; // static int counter = 0; ImGui.Begin('Hello, world!'); // Create a window called "Hello, world!" and append into it. @@ -757,6 +758,73 @@ function SoDark (hue: number) { return style; } +function styleBlack () { + const style = ImGui.GetStyle(); + const colors = style.Colors; + + colors[ImGui.ImGuiCol.Text] = new ImGui.ImVec4(1.000, 1.000, 1.000, 1.000); + colors[ImGui.ImGuiCol.TextDisabled] = new ImGui.ImVec4(0.500, 0.500, 0.500, 1.000); + colors[ImGui.ImGuiCol.WindowBg] = new ImGui.ImVec4(0.180, 0.180, 0.180, 1.000); + colors[ImGui.ImGuiCol.ChildBg] = new ImGui.ImVec4(0.280, 0.280, 0.280, 0.000); + colors[ImGui.ImGuiCol.PopupBg] = new ImGui.ImVec4(0.313, 0.313, 0.313, 1.000); + colors[ImGui.ImGuiCol.Border] = new ImGui.ImVec4(0.266, 0.266, 0.266, 1.000); + colors[ImGui.ImGuiCol.BorderShadow] = new ImGui.ImVec4(0.000, 0.000, 0.000, 0.000); + colors[ImGui.ImGuiCol.FrameBg] = new ImGui.ImVec4(0.160, 0.160, 0.160, 1.000); + colors[ImGui.ImGuiCol.FrameBgHovered] = new ImGui.ImVec4(0.200, 0.200, 0.200, 1.000); + colors[ImGui.ImGuiCol.FrameBgActive] = new ImGui.ImVec4(0.280, 0.280, 0.280, 1.000); + colors[ImGui.ImGuiCol.TitleBg] = new ImGui.ImVec4(0.148, 0.148, 0.148, 1.000); + colors[ImGui.ImGuiCol.TitleBgActive] = new ImGui.ImVec4(0.148, 0.148, 0.148, 1.000); + colors[ImGui.ImGuiCol.TitleBgCollapsed] = new ImGui.ImVec4(0.148, 0.148, 0.148, 1.000); + colors[ImGui.ImGuiCol.MenuBarBg] = new ImGui.ImVec4(0.195, 0.195, 0.195, 1.000); + colors[ImGui.ImGuiCol.ScrollbarBg] = new ImGui.ImVec4(0.160, 0.160, 0.160, 1.000); + colors[ImGui.ImGuiCol.ScrollbarGrab] = new ImGui.ImVec4(0.277, 0.277, 0.277, 1.000); + colors[ImGui.ImGuiCol.ScrollbarGrabHovered] = new ImGui.ImVec4(0.300, 0.300, 0.300, 1.000); + colors[ImGui.ImGuiCol.ScrollbarGrabActive] = new ImGui.ImVec4(1.000, 0.391, 0.000, 1.000); + colors[ImGui.ImGuiCol.CheckMark] = new ImGui.ImVec4(1.000, 1.000, 1.000, 1.000); + colors[ImGui.ImGuiCol.SliderGrab] = new ImGui.ImVec4(0.391, 0.391, 0.391, 1.000); + colors[ImGui.ImGuiCol.SliderGrabActive] = new ImGui.ImVec4(1.000, 0.391, 0.000, 1.000); + colors[ImGui.ImGuiCol.Button] = new ImGui.ImVec4(1.000, 1.000, 1.000, 0.000); + colors[ImGui.ImGuiCol.ButtonHovered] = new ImGui.ImVec4(1.000, 1.000, 1.000, 0.156); + colors[ImGui.ImGuiCol.ButtonActive] = new ImGui.ImVec4(1.000, 1.000, 1.000, 0.391); + colors[ImGui.ImGuiCol.Header] = new ImGui.ImVec4(0.313, 0.313, 0.313, 1.000); + colors[ImGui.ImGuiCol.HeaderHovered] = new ImGui.ImVec4(0.469, 0.469, 0.469, 1.000); + colors[ImGui.ImGuiCol.HeaderActive] = new ImGui.ImVec4(0.469, 0.469, 0.469, 1.000); + colors[ImGui.ImGuiCol.Separator] = colors[ImGui.ImGuiCol.Border]; + colors[ImGui.ImGuiCol.SeparatorHovered] = new ImGui.ImVec4(0.391, 0.391, 0.391, 1.000); + colors[ImGui.ImGuiCol.SeparatorActive] = new ImGui.ImVec4(1.000, 0.391, 0.000, 1.000); + colors[ImGui.ImGuiCol.ResizeGrip] = new ImGui.ImVec4(1.000, 1.000, 1.000, 0.250); + colors[ImGui.ImGuiCol.ResizeGripHovered] = new ImGui.ImVec4(1.000, 1.000, 1.000, 0.670); + colors[ImGui.ImGuiCol.ResizeGripActive] = new ImGui.ImVec4(1.000, 0.391, 0.000, 1.000); + colors[ImGui.ImGuiCol.Tab] = new ImGui.ImVec4(0.098, 0.098, 0.098, 1.000); + colors[ImGui.ImGuiCol.TabHovered] = new ImGui.ImVec4(0.352, 0.352, 0.352, 1.000); + colors[ImGui.ImGuiCol.TabActive] = new ImGui.ImVec4(0.195, 0.195, 0.195, 1.000); + colors[ImGui.ImGuiCol.TabUnfocused] = new ImGui.ImVec4(0.098, 0.098, 0.098, 1.000); + colors[ImGui.ImGuiCol.TabUnfocusedActive] = new ImGui.ImVec4(0.195, 0.195, 0.195, 1.000); + colors[ImGui.ImGuiCol.PlotLines] = new ImGui.ImVec4(1.000, 0.391, 0.000, 0.781); //DockingPreview + colors[ImGui.ImGuiCol.PlotLinesHovered] = new ImGui.ImVec4(0.180, 0.180, 0.180, 1.000); //DockingEmptyBg + colors[ImGui.ImGuiCol.PlotLines + 2] = new ImGui.ImVec4(0.469, 0.469, 0.469, 1.000); + colors[ImGui.ImGuiCol.PlotLinesHovered + 2] = new ImGui.ImVec4(1.000, 0.391, 0.000, 1.000); + colors[ImGui.ImGuiCol.PlotHistogram + 2] = new ImGui.ImVec4(0.586, 0.586, 0.586, 1.000); + colors[ImGui.ImGuiCol.PlotHistogramHovered + 2] = new ImGui.ImVec4(1.000, 0.391, 0.000, 1.000); + colors[ImGui.ImGuiCol.TextSelectedBg + 2] = new ImGui.ImVec4(1.000, 1.000, 1.000, 0.156); + colors[ImGui.ImGuiCol.DragDropTarget + 2] = new ImGui.ImVec4(1.000, 0.391, 0.000, 1.000); + colors[ImGui.ImGuiCol.NavHighlight + 2] = new ImGui.ImVec4(1.000, 0.391, 0.000, 1.000); + colors[ImGui.ImGuiCol.NavWindowingHighlight + 2] = new ImGui.ImVec4(1.000, 0.391, 0.000, 1.000); + colors[ImGui.ImGuiCol.NavWindowingDimBg + 2] = new ImGui.ImVec4(0.000, 0.000, 0.000, 0.586); + colors[ImGui.ImGuiCol.ModalWindowDimBg + 2] = new ImGui.ImVec4(0.000, 0.000, 0.000, 0.586); + + style.ChildRounding = 4.0; + style.FrameBorderSize = 1.0; + style.FrameRounding = 2.0; + style.GrabMinSize = 7.0; + style.PopupRounding = 2.0; + style.ScrollbarRounding = 12.0; + style.ScrollbarSize = 13.0; + style.TabBorderSize = 1.0; + style.TabRounding = 0.0; + style.WindowRounding = 0.0; +} + function ApplyHue (style: ImGui.ImGuiStyle, hue: number) { // eslint-disable-next-line @typescript-eslint/no-unsafe-enum-comparison for (let i = 0; i < ImGui.ImGuiCol.COUNT; i++) { diff --git a/web-packages/imgui-demo/src/object-inspectors/vfx-item-inspector.ts b/web-packages/imgui-demo/src/object-inspectors/vfx-item-inspector.ts index bb4c9b01..eb25c6ab 100644 --- a/web-packages/imgui-demo/src/object-inspectors/vfx-item-inspector.ts +++ b/web-packages/imgui-demo/src/object-inspectors/vfx-item-inspector.ts @@ -1,8 +1,8 @@ import type { Component, Material } from '@galacean/effects'; -import { EffectsObject, RendererComponent, SerializationHelper, VFXItem, getMergedStore, spec } from '@galacean/effects'; +import { EffectsObject, RendererComponent, SerializationHelper, VFXItem, generateGUID, getMergedStore, spec } from '@galacean/effects'; import { objectInspector } from '../core/decorators'; import { ObjectInspector } from './object-inspectors'; -import type { GLMaterial } from '@galacean/effects-webgl'; +import { GLMaterial } from '@galacean/effects-webgl'; import { GLTexture } from '@galacean/effects-webgl'; import type { FileNode } from '../core/file-node'; import { UIManager } from '../core/ui-manager'; @@ -31,8 +31,15 @@ export class VFXItemInspector extends ObjectInspector { ImGui.Text(activeObject.getInstanceId()); ImGui.Text('Visible'); ImGui.SameLine(alignWidth); - //@ts-expect-error - ImGui.Checkbox('##Visible', (_ = activeObject.visible) => activeObject.visible = _); + ImGui.Checkbox('##Visible', (_ = activeObject.getVisible()) => { + activeObject.setVisible(_); + + return activeObject.getVisible(); + }); + + ImGui.Text('End Behavior'); + ImGui.SameLine(alignWidth); + ImGui.Text(this.endBehaviorToString(activeObject.endBehavior)); if (ImGui.CollapsingHeader(('Transform'), ImGui.TreeNodeFlags.DefaultOpen)) { const transform = activeObject.transform; @@ -116,10 +123,10 @@ export class VFXItemInspector extends ObjectInspector { if (componet instanceof RendererComponent) { ImGui.Text('Material'); ImGui.SameLine(alignWidth); - ImGui.Button(componet.material.name, new ImGui.Vec2(200, 0)); + ImGui.Button(componet.material?.name ?? '', new ImGui.Vec2(200, 0)); if (ImGui.BeginDragDropTarget()) { - const payload = ImGui.AcceptDragDropPayload(componet.material.constructor.name); + const payload = ImGui.AcceptDragDropPayload(GLMaterial.name); if (payload) { void (payload.Data as FileNode).getFile().then(async (file: File | undefined)=>{ @@ -143,14 +150,16 @@ export class VFXItemInspector extends ObjectInspector { if (activeObject.getComponent(RendererComponent)) { const material = activeObject.getComponent(RendererComponent).material; - if (ImGui.CollapsingHeader(material.name + ' (Material)##CollapsingHeader', ImGui.TreeNodeFlags.DefaultOpen)) { + if (material && ImGui.CollapsingHeader(material.name + ' (Material)##CollapsingHeader', ImGui.TreeNodeFlags.DefaultOpen)) { this.drawMaterial(material); } - } } private drawMaterial (material: Material) { + if (!material) { + return; + } const glMaterial = material as GLMaterial; const serializedData = glMaterial.toData(); const shaderProperties = material.shader.shaderData.properties; @@ -285,4 +294,29 @@ export class VFXItemInspector extends ObjectInspector { GalaceanEffects.assetDataBase.setDirty(glMaterial.getInstanceId()); } } + + private endBehaviorToString (endBehavior: spec.EndBehavior) { + let result = ''; + + switch (endBehavior) { + case spec.EndBehavior.destroy: + result = 'Destroy'; + + break; + case spec.EndBehavior.forward: + result = 'Forward'; + + break; + case spec.EndBehavior.freeze: + result = 'Freeze'; + + break; + case spec.EndBehavior.restart: + result = 'Restart'; + + break; + } + + return result; + } } \ No newline at end of file From 14db1d1600ef90ef91a712c532c98e02baaf77aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=84=8F=E7=BB=AE?= Date: Thu, 26 Sep 2024 19:50:39 +0800 Subject: [PATCH 24/88] =?UTF-8?q?feat:=20=E5=A2=9E=E5=8A=A0=E7=BC=96?= =?UTF-8?q?=E8=AF=91=E8=80=97=E6=97=B6=E7=9A=84=E7=BB=9F=E8=AE=A1=20(#660)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: 增加编译耗时的统计 * fix: first frame time * feat: 补充 Shader 异步编译日志 --- packages/effects-core/src/asset-manager.ts | 13 +---- packages/effects-core/src/composition.ts | 17 ++++-- .../effects-core/src/render/gpu-capability.ts | 4 +- packages/effects/src/player.ts | 11 +++- web-packages/demo/html/shader-compile.html | 42 +++++++++++++++ web-packages/demo/index.html | 1 + web-packages/demo/src/shader-compile.ts | 52 +++++++++++++++++++ web-packages/demo/vite.config.js | 1 + 8 files changed, 123 insertions(+), 18 deletions(-) create mode 100644 web-packages/demo/html/shader-compile.html create mode 100644 web-packages/demo/src/shader-compile.ts diff --git a/packages/effects-core/src/asset-manager.ts b/packages/effects-core/src/asset-manager.ts index 1fed6b89..68bc1739 100644 --- a/packages/effects-core/src/asset-manager.ts +++ b/packages/effects-core/src/asset-manager.ts @@ -88,7 +88,6 @@ export class AssetManager implements Disposable { const startTime = performance.now(); const timeInfoMessages: string[] = []; const gpuInstance = renderer?.engine.gpuCapability; - const asyncShaderCompile = gpuInstance?.detail?.asyncShaderCompile ?? false; const compressedTexture = gpuInstance?.detail.compressedTexture ?? COMPRESSED_TEXTURE.NONE; const timeInfos: Record = {}; let loadTimer: number; @@ -172,7 +171,8 @@ export class AssetManager implements Disposable { const [loadedBins, loadedImages] = await Promise.all([ hookTimeInfo('processBins', () => this.processBins(bins)), hookTimeInfo('processImages', () => this.processImages(images, compressedTexture)), - hookTimeInfo(`${asyncShaderCompile ? 'async' : 'sync'}Compile`, () => this.precompile(compositions, pluginSystem, renderer, options)), + hookTimeInfo('precompile', () => this.precompile(compositions, pluginSystem, renderer, options)), + hookTimeInfo('processFontURL', () => this.processFontURL(fonts as spec.FontDefine[])), ]); for (let i = 0; i < images.length; i++) { @@ -189,7 +189,6 @@ export class AssetManager implements Disposable { } } - await hookTimeInfo('processFontURL', () => this.processFontURL(fonts as spec.FontDefine[])); const loadedTextures = await hookTimeInfo('processTextures', () => this.processTextures(loadedImages, loadedBins, jsonScene)); scene = { @@ -234,15 +233,7 @@ export class AssetManager implements Disposable { if (!renderer || !renderer.getShaderLibrary()) { return; } - const shaderLibrary = renderer?.getShaderLibrary(); - await pluginSystem?.precompile(compositions, renderer, options); - - await new Promise(resolve => { - shaderLibrary?.compileAllShaders(() => { - resolve(null); - }); - }); } private async processJSON (json: JSONValue) { diff --git a/packages/effects-core/src/composition.ts b/packages/effects-core/src/composition.ts index 82e398af..f026c6f8 100644 --- a/packages/effects-core/src/composition.ts +++ b/packages/effects-core/src/composition.ts @@ -24,10 +24,16 @@ import type { PostProcessVolume } from './components/post-process-volume'; import { SceneTicking } from './composition/scene-ticking'; export interface CompositionStatistic { - loadTime: number, loadStart: number, + loadTime: number, + /** + * Shader 编译耗时 + */ + compileTime: number, + /** + * 从加载到渲染第一帧的时间(含 Shader 编译) + */ firstFrameTime: number, - precompileTime: number, } export interface MessageItem { @@ -249,7 +255,12 @@ export class Composition extends EventEmitter> imp this.renderer = renderer; this.texInfo = imageUsage ?? {}; this.event = event; - this.statistic = { loadTime: totalTime ?? 0, loadStart: scene.startTime ?? 0, firstFrameTime: 0, precompileTime: scene.timeInfos['asyncCompile'] ?? scene.timeInfos['syncCompile'] }; + this.statistic = { + loadStart: scene.startTime ?? 0, + loadTime: totalTime ?? 0, + compileTime: 0, + firstFrameTime: 0, + }; this.reusable = reusable; this.speed = speed; this.autoRefTex = !this.keepResource && imageUsage && this.rootItem.endBehavior !== spec.EndBehavior.restart; diff --git a/packages/effects-core/src/render/gpu-capability.ts b/packages/effects-core/src/render/gpu-capability.ts index 2b2fab77..533e7df9 100644 --- a/packages/effects-core/src/render/gpu-capability.ts +++ b/packages/effects-core/src/render/gpu-capability.ts @@ -19,7 +19,7 @@ export interface GPUCapabilityDetail { shaderTextureLod: boolean, instanceDraw?: boolean, drawBuffers?: boolean, - asyncShaderCompile?: boolean, + asyncShaderCompile: boolean, //draw elements use uint32 Array intIndexElementBuffer?: boolean, //render pass depth and stencil texture readable @@ -100,7 +100,7 @@ export class GPUCapability { shaderTextureLod: level2 || !!gl.getExtension('EXT_shader_texture_lod'), instanceDraw: level2 || !!gl.getExtension('ANGLE_instanced_arrays'), drawBuffers: level2 || !!this.drawBufferExtension, - asyncShaderCompile: !!gl.getExtension('KHR_parallel_shader_compile'), + asyncShaderCompile: !!this.glAsyncCompileExt, intIndexElementBuffer: !!gl.getExtension('OES_element_index_uint'), standardDerivatives: level2 || !!gl.getExtension('OES_standard_derivatives'), readableDepthStencilTextures: level2 || !!depthTextureExtension, diff --git a/packages/effects/src/player.ts b/packages/effects/src/player.ts index 5b7b388d..a4e7f063 100644 --- a/packages/effects/src/player.ts +++ b/packages/effects/src/player.ts @@ -299,6 +299,7 @@ export class Player extends EventEmitter> implements Disposa ): Promise { const renderer = this.renderer; const engine = renderer.engine; + const asyncShaderCompile = engine.gpuCapability?.detail?.asyncShaderCompile; const last = performance.now(); let opts = { autoplay: true, @@ -382,6 +383,8 @@ export class Player extends EventEmitter> implements Disposa } } + const compileStart = performance.now(); + await new Promise(resolve => { this.renderer.getShaderLibrary()?.compileAllShaders(() => { resolve(null); @@ -395,10 +398,14 @@ export class Player extends EventEmitter> implements Disposa composition.pause(); } - const firstFrameTime = performance.now() - last + composition.statistic.loadTime; + const compileTime = performance.now() - compileStart; + const firstFrameTime = performance.now() - last; + composition.statistic.compileTime = compileTime; composition.statistic.firstFrameTime = firstFrameTime; - logger.info(`First frame: [${composition.name}]${firstFrameTime.toFixed(4)}ms.`); + logger.info(`Shader ${asyncShaderCompile ? 'async' : 'sync'} compile [${composition.name}]: ${compileTime.toFixed(4)}ms.`); + logger.info(`First frame [${composition.name}]: ${firstFrameTime.toFixed(4)}ms.`); + this.compositions.push(composition); return composition; diff --git a/web-packages/demo/html/shader-compile.html b/web-packages/demo/html/shader-compile.html new file mode 100644 index 00000000..64da0dd0 --- /dev/null +++ b/web-packages/demo/html/shader-compile.html @@ -0,0 +1,42 @@ + + + + + + Shader 编译测试 - demo + + + +

优化后

+
+
+ +
+
+ + + + diff --git a/web-packages/demo/index.html b/web-packages/demo/index.html index 972f85d9..5519a68c 100644 --- a/web-packages/demo/index.html +++ b/web-packages/demo/index.html @@ -13,6 +13,7 @@
单项目测试 后处理测试 + Shader 编译测试 Dashboard Interactive 压缩纹理 diff --git a/web-packages/demo/src/shader-compile.ts b/web-packages/demo/src/shader-compile.ts new file mode 100644 index 00000000..0cafb59d --- /dev/null +++ b/web-packages/demo/src/shader-compile.ts @@ -0,0 +1,52 @@ +import type { Composition } from '@galacean/effects'; +import { Player } from '@galacean/effects'; +import '@galacean/effects-plugin-spine'; + +// 大量粒子 +// const json = 'https://mdn.alipayobjects.com/mars/afts/file/A*aCeuQ5RQZj4AAAAAAAAAAAAADlB4AQ'; +// 新年烟花 +const json = [ + 'https://gw.alipayobjects.com/os/gltf-asset/mars-cli/ILDKKFUFMVJA/1705406034-80896.json', + 'https://mdn.alipayobjects.com/graph_jupiter/afts/file/A*qTquTKYbk6EAAAAAAAAAAAAADsF2AQ', +]; +// 混合测试 +// const json = [ +// 'https://mdn.alipayobjects.com/mars/afts/file/A*QyX8Rp-4fmUAAAAAAAAAAAAADlB4AQ', +// 'https://mdn.alipayobjects.com/mars/afts/file/A*bi3HRobVsk8AAAAAAAAAAAAADlB4AQ', +// 'https://mdn.alipayobjects.com/graph_jupiter/afts/file/A*sEdkT5cdXGEAAAAAAAAAAAAADsF2AQ', +// ]; +// 塔奇 +// const json = 'https://mdn.alipayobjects.com/mars/afts/file/A*uU2JRIjcLIcAAAAAAAAAAAAADlB4AQ'; +// const json = 'https://gw.alipayobjects.com/os/gltf-asset/mars-cli/TAJIINQOUUKP/-799304223-0ee5d.json'; +const container = document.getElementById('J-container'); + +document.getElementById('J-button')!.addEventListener('click', () => { + (async () => { + try { + container?.classList.add('active'); + + const player = new Player({ + container, + // renderFramework: 'webgl2', + }); + const compositions = await player.loadScene(Array.isArray(json) ? json : [json]) as unknown as Composition[]; + + compositions.forEach(composition => { + const dt = document.createElement('dt'); + + dt.innerHTML = `>>> composition: ${composition.name}`; + document.getElementById('J-statistic')?.appendChild(dt); + + for (const key in composition.statistic) { + const p = document.createElement('dd'); + + // @ts-expect-error + p.innerHTML = `${key}: ${composition.statistic[key]}`; + document.getElementById('J-statistic')?.appendChild(p); + } + }); + } catch (e) { + console.error('biz', e); + } + })(); +}); diff --git a/web-packages/demo/vite.config.js b/web-packages/demo/vite.config.js index 39faf7f2..e77a3590 100644 --- a/web-packages/demo/vite.config.js +++ b/web-packages/demo/vite.config.js @@ -27,6 +27,7 @@ export default defineConfig(({ mode }) => { 'local-file': resolve(__dirname, 'html/local-file.html'), 'post-processing': resolve(__dirname, 'html/post-processing.html'), 'render-level': resolve(__dirname, 'html/render-level.html'), + 'shader-compile': resolve(__dirname, 'html/shader-compile.html'), 'shape': resolve(__dirname, 'html/shape.html'), 'single': resolve(__dirname, 'html/single.html'), 'text': resolve(__dirname, 'html/text.html'), From b8e8bfff4a5c3586c5363ccf3cb7521d85b4bd06 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=8C=82=E5=AE=89?= <151805118+wumaolinmaoan@users.noreply.github.com> Date: Sun, 29 Sep 2024 14:11:08 +0800 Subject: [PATCH 25/88] refactor: vfx item find use BFS (#667) * refactor: vfx item find use BFS * Update packages/effects-core/src/vfx-item.ts Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> * style: blank lines --------- Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- packages/effects-core/src/vfx-item.ts | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/packages/effects-core/src/vfx-item.ts b/packages/effects-core/src/vfx-item.ts index bc4aecf5..ae559507 100644 --- a/packages/effects-core/src/vfx-item.ts +++ b/packages/effects-core/src/vfx-item.ts @@ -531,17 +531,20 @@ export class VFXItem extends EffectsObject implements Disposable { if (this.name === name) { return this; } - for (const child of this.children) { - if (child.name === name) { - return child; - } - } - for (const child of this.children) { - const res = child.find(name); - if (res) { - return res; + const queue: VFXItem[] = []; + + queue.push(...this.children); + let index = 0; + + while (index < queue.length) { + const item = queue[index]; + + index++; + if (item.name === name) { + return item; } + queue.push(...item.children); } return undefined; From 3df81c1ef6efea91b5a836b79fd9ed9a06703c4c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=84=8F=E7=BB=AE?= Date: Mon, 30 Sep 2024 16:48:13 +0800 Subject: [PATCH 26/88] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=20AssetManager?= =?UTF-8?q?=20=E5=8A=A0=E8=BD=BD=20JSON=20=E5=AF=B9=E8=B1=A1=E7=9A=84?= =?UTF-8?q?=E9=97=AE=E9=A2=98=20(#668)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: 修复 AssetManager 加载 JSON 对象的问题 * style: 根据机器人建议优化代码 --- packages/effects-core/src/asset-manager.ts | 101 +++--- packages/effects-core/src/composition.ts | 4 +- packages/effects-core/src/image-asset.ts | 4 +- packages/effects-core/src/scene.ts | 48 +-- packages/effects-core/src/texture/texture.ts | 5 +- .../src/three-display-object.ts | 31 +- packages/effects-webgl/src/gl-texture.ts | 8 +- packages/effects/src/player.ts | 49 ++- plugin-packages/model/src/gltf/loader-impl.ts | 2 +- plugin-packages/model/src/runtime/common.ts | 4 +- .../model/test/src/plugin-unit.spec.ts | 2 +- plugin-packages/model/test/src/utilities.ts | 4 +- web-packages/demo/src/assets/cube-textures.ts | 62 ---- web-packages/demo/src/dashboard.ts | 4 - web-packages/test/assets/cube-texture.ts | 119 +++++++ .../src/effects-core/asset-manager.spec.ts | 199 +++-------- .../test/unit/src/effects/scene-load.spec.ts | 329 ++++++++---------- 17 files changed, 454 insertions(+), 521 deletions(-) delete mode 100644 web-packages/demo/src/assets/cube-textures.ts create mode 100644 web-packages/test/assets/cube-texture.ts diff --git a/packages/effects-core/src/asset-manager.ts b/packages/effects-core/src/asset-manager.ts index 68bc1739..cb3163a0 100644 --- a/packages/effects-core/src/asset-manager.ts +++ b/packages/effects-core/src/asset-manager.ts @@ -6,8 +6,8 @@ import type { PrecompileOptions } from './plugin-system'; import { PluginSystem } from './plugin-system'; import type { JSONValue } from './downloader'; import { Downloader, loadWebPOptional, loadImage, loadVideo, loadMedia, loadAVIFOptional } from './downloader'; -import type { ImageSource, Scene, SceneLoadOptions, SceneRenderLevel, SceneType } from './scene'; -import { isSceneJSON } from './scene'; +import type { ImageLike, SceneLoadOptions, SceneRenderLevel } from './scene'; +import { Scene } from './scene'; import type { Disposable } from './utils'; import { isObject, isString, logger, isValidFontFamily, isCanvas, base64ToFile } from './utils'; import type { TextureSourceOptions } from './texture'; @@ -17,6 +17,8 @@ import { COMPRESSED_TEXTURE } from './render'; import { combineImageTemplate, getBackgroundImage } from './template-image'; import { ImageAsset } from './image-asset'; +type AssetsType = ImageLike | { url: string, type: TextureSourceType }; + let seed = 1; /** @@ -25,13 +27,13 @@ let seed = 1; */ export class AssetManager implements Disposable { /** - * 相对url的基本路径 + * 相对 url 的基本路径 */ private baseUrl: string; /** - * 图像资源,用于创建和释放GPU纹理资源 + * 图像资源,用于创建和释放 GPU 纹理资源 */ - private assets: Record = {}; + private assets: Record = {}; /** * 自定义文本缓存,随页面销毁而销毁 @@ -55,7 +57,7 @@ export class AssetManager implements Disposable { * @param downloader - 资源下载对象 */ constructor ( - private options: SceneLoadOptions = {}, + public options: Omit = {}, private readonly downloader = new Downloader(), ) { this.updateOptions(options); @@ -79,11 +81,11 @@ export class AssetManager implements Disposable { * @returns */ async loadScene ( - url: SceneType, + url: Scene.LoadType, renderer?: Renderer, options?: { env: string }, ): Promise { - let rawJSON: SceneType | JSONValue; + let rawJSON: Scene.LoadType; const assetUrl = isString(url) ? url : this.id; const startTime = performance.now(); const timeInfoMessages: string[] = []; @@ -125,19 +127,19 @@ export class AssetManager implements Disposable { const loadResourcePromise = async () => { let scene: Scene; - // url 为 JSONValue 或 Scene 对象 - if (isObject(url)) { - // TODO: 原 JSONLoader contructor 判断是否兼容 + if (isString(url)) { + // 兼容相对路径 + const link = new URL(url, location.href).href; + + this.baseUrl = link; + rawJSON = await hookTimeInfo('loadJSON', () => this.loadJSON(link) as unknown as Promise); + } else { + // url 为 spec.JSONScene 或 Scene 对象 rawJSON = url; this.baseUrl = location.href; - } else { - // 兼容相对路径 - url = new URL(url as string, location.href).href; - this.baseUrl = url; - rawJSON = await hookTimeInfo('loadJSON', () => this.loadJSON(url as string)); } - if (isSceneJSON(rawJSON)) { + if (Scene.isJSONObject(rawJSON)) { // 已经加载过的 可能需要更新数据模板 scene = { ...rawJSON, @@ -150,14 +152,15 @@ export class AssetManager implements Disposable { ) { const { images: rawImages } = rawJSON.jsonScene; const images = scene.images; + const newImages: spec.ImageSource[] = []; for (let i = 0; i < rawImages.length; i++) { // 仅重新加载数据模板对应的图片 if (images[i] instanceof HTMLCanvasElement) { - images[i] = rawImages[i]; + newImages[i] = rawImages[i]; } } - scene.images = await hookTimeInfo('processImages', () => this.processImages(images, compressedTexture)); + scene.images = await hookTimeInfo('processImages', () => this.processImages(newImages, compressedTexture)); // 更新 TextureOptions 中的 image 指向 for (let i = 0; i < scene.images.length; i++) { scene.textureOptions[i].image = scene.images[i]; @@ -193,7 +196,7 @@ export class AssetManager implements Disposable { scene = { timeInfos, - url: url, + url, renderLevel: this.options.renderLevel, storage: {}, pluginSystem, @@ -311,12 +314,12 @@ export class AssetManager implements Disposable { } private async processImages ( - images: any, + images: spec.ImageSource[], compressedTexture: COMPRESSED_TEXTURE = 0, - ): Promise { + ): Promise { const { useCompressedTexture, variables } = this.options; const baseUrl = this.baseUrl; - const jobs = images.map(async (img: spec.Image, idx: number) => { + const jobs = images.map(async (img, idx: number) => { const { url: png, webp, avif } = img; // eslint-disable-next-line compat/compat const imageURL = new URL(png, baseUrl).href; @@ -327,7 +330,7 @@ export class AssetManager implements Disposable { if ('template' in img) { // 1. 数据模板 - const template = img.template as spec.TemplateContent; + const template = img.template; // 获取数据模板 background 参数 const background = template.background; @@ -342,6 +345,8 @@ export class AssetManager implements Disposable { const resultImage = await loadMedia(url as string | string[], loadFn); if (resultImage instanceof HTMLVideoElement) { + this.assets[idx] = { url: resultImage.src, type: TextureSourceType.video }; + return resultImage; } else { // 如果是加载图片且是数组,设置变量,视频情况下不需要 @@ -349,6 +354,8 @@ export class AssetManager implements Disposable { variables[background.name] = resultImage.src; } + this.assets[idx] = { url: resultImage.src, type: TextureSourceType.image }; + return await combineImageTemplate( resultImage, template, @@ -362,7 +369,7 @@ export class AssetManager implements Disposable { } } else if ('compressed' in img && useCompressedTexture && compressedTexture) { // 2. 压缩纹理 - const { compressed } = img as spec.CompressedImage; + const { compressed } = img; let src; if (compressedTexture === COMPRESSED_TEXTURE.ASTC) { @@ -377,9 +384,6 @@ export class AssetManager implements Disposable { return this.loadBins(bufferURL); } - } else if ('sourceType' in img) { - // TODO: 确定是否有用 - return img; } else if ( img instanceof HTMLImageElement || img instanceof HTMLCanvasElement || @@ -402,11 +406,11 @@ export class AssetManager implements Disposable { } private async processTextures ( - images: any, + images: ImageLike[], bins: ArrayBuffer[], jsonScene: spec.JSONScene, ) { - const textures = jsonScene.textures ?? images.map((img: never, source: number) => ({ source })) as spec.SerializedTextureSource[]; + const textures = jsonScene.textures ?? images.map((img, source: number) => ({ source })) as spec.SerializedTextureSource[]; const jobs = textures.map(async (textureOptions, idx) => { if (textureOptions instanceof Texture) { return textureOptions; @@ -418,9 +422,8 @@ export class AssetManager implements Disposable { throw new Error(`Load texture ${idx} fails, error message: ${e}.`); } } - const { source } = textureOptions; - - let image: any; + const { source, id } = textureOptions; + let image: AssetsType | undefined; if (isObject(source)) { // source 为 images 数组 id image = this.assets[source.id as string]; @@ -429,10 +432,7 @@ export class AssetManager implements Disposable { } if (image) { - const texture = createTextureOptionsBySource(image, this.assets[idx]); - - texture.id = textureOptions.id; - texture.dataType = spec.DataType.Texture; + const texture = createTextureOptionsBySource(image, this.assets[idx], id); return texture.sourceType === TextureSourceType.compressed ? texture : { ...texture, ...textureOptions }; } @@ -478,11 +478,6 @@ export class AssetManager implements Disposable { if (this.timers.length) { this.timers.map(id => window.clearTimeout(id)); } - for (const key in this.assets) { - const asset = this.assets[key]; - - asset?.dispose?.(); - } this.assets = {}; this.timers = []; } @@ -512,12 +507,24 @@ function fixOldImageUsage ( } } -function createTextureOptionsBySource (image: any, sourceFrom: TextureSourceOptions): Record { +function createTextureOptionsBySource ( + image: TextureSourceOptions | ImageLike, + sourceFrom: AssetsType, + id?: string, +) { + const options = { + id, + dataType: spec.DataType.Texture, + }; + if (image instanceof Texture) { - return image.source; + return { + ...image.source, + ...options, + }; } else if ( image instanceof HTMLImageElement || - isCanvas(image) + isCanvas(image as HTMLCanvasElement) ) { return { image, @@ -526,6 +533,7 @@ function createTextureOptionsBySource (image: any, sourceFrom: TextureSourceOpti keepImageSource: true, minFilter: glContext.LINEAR, magFilter: glContext.LINEAR, + ...options, }; } else if (image instanceof HTMLVideoElement) { // 视频 @@ -534,12 +542,14 @@ function createTextureOptionsBySource (image: any, sourceFrom: TextureSourceOpti video: image, minFilter: glContext.LINEAR, magFilter: glContext.LINEAR, + ...options, }; } else if (image instanceof ArrayBuffer) { // 压缩纹理 return { ...getKTXTextureOptions(image), sourceFrom, + ...options, }; } else if ( 'width' in image && @@ -553,6 +563,7 @@ function createTextureOptionsBySource (image: any, sourceFrom: TextureSourceOpti wrapT: glContext.CLAMP_TO_EDGE, minFilter: glContext.NEAREST, magFilter: glContext.NEAREST, + ...options, }; } diff --git a/packages/effects-core/src/composition.ts b/packages/effects-core/src/composition.ts index f026c6f8..a9af4f2d 100644 --- a/packages/effects-core/src/composition.ts +++ b/packages/effects-core/src/composition.ts @@ -11,7 +11,7 @@ import type { PluginSystem } from './plugin-system'; import type { EventSystem, Plugin, Region } from './plugins'; import type { MeshRendererOptions, Renderer } from './render'; import { RenderFrame } from './render'; -import type { Scene, SceneType } from './scene'; +import type { Scene } from './scene'; import type { Texture } from './texture'; import { TextureLoadAction, TextureSourceType } from './texture'; import type { Disposable, LostHandler } from './utils'; @@ -153,7 +153,7 @@ export class Composition extends EventEmitter> imp /** * 合成对应的 url 或者 JSON */ - readonly url: SceneType; + readonly url: Scene.LoadType; /** * 合成根元素 */ diff --git a/packages/effects-core/src/image-asset.ts b/packages/effects-core/src/image-asset.ts index 17e938b9..a45dc14d 100644 --- a/packages/effects-core/src/image-asset.ts +++ b/packages/effects-core/src/image-asset.ts @@ -1,6 +1,6 @@ import { EffectsObject } from './effects-object'; -import type { ImageSource } from './scene'; +import type { ImageLike } from './scene'; export class ImageAsset extends EffectsObject { - data: ImageSource; + data: ImageLike; } diff --git a/packages/effects-core/src/scene.ts b/packages/effects-core/src/scene.ts index 7874f368..117eb2a6 100644 --- a/packages/effects-core/src/scene.ts +++ b/packages/effects-core/src/scene.ts @@ -4,7 +4,7 @@ import type { PluginSystem } from './plugin-system'; import type { PickEnum } from './utils'; import { isObject } from './utils'; -export type ImageSource = spec.TemplateImage | spec.Image | spec.CompressedImage; +export type ImageLike = spec.HTMLImageLike | ArrayBuffer | Texture; export type SceneRenderLevel = PickEnum; /** @@ -18,7 +18,7 @@ export interface Scene { readonly storage: Record, textureOptions: Record[], - images: ImageSource[], + images: ImageLike[], consumed?: boolean, textures?: Texture[], /** @@ -33,10 +33,32 @@ export interface Scene { * 加载分段时长 */ timeInfos: Record, - url: SceneType, + url: Scene.LoadType, usedImages: Record, } +export namespace Scene { + type URLType = { url: string, options?: SceneLoadOptions }; + + /** + * 接受用于加载的数据类型 + */ + export type LoadType = string | Scene | URLType | spec.JSONScene | Record; + + // JSON 对象 + export function isJSONObject (scene: any): scene is Scene { + return isObject(scene) && 'jsonScene' in scene; + } + + export function isURL (scene: any): scene is URLType { + return isObject(scene) && 'url' in scene; + } + + export function isWithOptions (scene: any): scene is URLType { + return isObject(scene) && 'options' in scene; + } +} + /** * 场景加载参数 */ @@ -107,23 +129,3 @@ export interface SceneLoadOptions { */ speed?: number, } - -/** - * 接受用于加载的数据类型 - */ -export type SceneURLType = { url: string }; -export type SceneType = string | Scene | SceneURLType | Record; -export type SceneWithOptionsType = { options: SceneLoadOptions }; -export type SceneLoadType = SceneType | SceneWithOptionsType; - -export function isSceneJSON (scene: any): scene is Scene { - return isObject(scene) && 'jsonScene' in scene; -} - -export function isSceneURL (scene: any): scene is Scene { - return isObject(scene) && 'url' in scene; -} - -export function isSceneWithOptions (scene: any): scene is SceneWithOptionsType { - return isObject(scene) && 'options' in scene; -} diff --git a/packages/effects-core/src/texture/texture.ts b/packages/effects-core/src/texture/texture.ts index 3747dccc..d86ae404 100644 --- a/packages/effects-core/src/texture/texture.ts +++ b/packages/effects-core/src/texture/texture.ts @@ -1,3 +1,4 @@ +import * as spec from '@galacean/effects-specification'; import { TextureSourceType } from './types'; import type { TextureFactorySourceFrom, TextureSourceOptions, TextureDataType, TextureOptionsBase } from './types'; import { glContext } from '../gl'; @@ -208,7 +209,7 @@ export function generateWhiteTexture (engine: Engine) { return Texture.create( engine, { - id: 'whitetexture00000000000000000000', + id: spec.BuiltinObjectGUID.WhiteTexture, data: { width: 1, height: 1, @@ -224,7 +225,7 @@ export function generateTransparentTexture (engine: Engine) { return Texture.create( engine, { - id: 'transparenttexture00000000000000000000', + id: spec.BuiltinObjectGUID.TransparentTexture, data: { width: 1, height: 1, diff --git a/packages/effects-threejs/src/three-display-object.ts b/packages/effects-threejs/src/three-display-object.ts index acd5c9ce..72b1b3cc 100644 --- a/packages/effects-threejs/src/three-display-object.ts +++ b/packages/effects-threejs/src/three-display-object.ts @@ -1,8 +1,8 @@ import type { - EventSystem, SceneLoadOptions, Renderer, Composition, SceneLoadType, SceneType, Texture, - MessageItem, + EventSystem, SceneLoadOptions, Renderer, Composition, Texture, MessageItem, } from '@galacean/effects-core'; -import { AssetManager, isArray, isSceneURL, isSceneWithOptions, logger } from '@galacean/effects-core'; +import { Scene } from '@galacean/effects-core'; +import { AssetManager, isArray, logger } from '@galacean/effects-core'; import * as THREE from 'three'; import { ThreeComposition } from './three-composition'; import { ThreeRenderer } from './three-renderer'; @@ -70,9 +70,12 @@ export class ThreeDisplayObject extends THREE.Group { * @param options - 加载可选参数 * @returns */ - async loadScene (scene: SceneLoadType, options?: SceneLoadOptions): Promise; - async loadScene (scene: SceneLoadType[], options?: SceneLoadOptions): Promise; - async loadScene (scene: SceneLoadType | SceneLoadType[], options?: SceneLoadOptions): Promise { + async loadScene (scene: Scene.LoadType, options?: SceneLoadOptions): Promise; + async loadScene (scene: Scene.LoadType[], options?: SceneLoadOptions): Promise; + async loadScene ( + scene: Scene.LoadType | Scene.LoadType[], + options?: SceneLoadOptions, + ): Promise { let composition: Composition | Composition[]; const baseOrder = this.baseCompositionIndex; @@ -91,7 +94,7 @@ export class ThreeDisplayObject extends THREE.Group { composition.setIndex(baseOrder); } - return composition; + return composition as T; } pause () { @@ -107,24 +110,24 @@ export class ThreeDisplayObject extends THREE.Group { }); } - private async createComposition (url: SceneLoadType, options: SceneLoadOptions = {}): Promise { + private async createComposition (url: Scene.LoadType, options: SceneLoadOptions = {}): Promise { const last = performance.now(); let opts = { autoplay: true, ...options, }; - let source: SceneType; + let source: Scene.LoadType = url; - if (isSceneURL(url)) { - source = url.url; - if (isSceneWithOptions(url)) { + if (Scene.isURL(url)) { + if (!Scene.isJSONObject(url)) { + source = url.url; + } + if (Scene.isWithOptions(url)) { opts = { ...opts, ...url.options || {}, }; } - } else { - source = url; } if (this.assetManager) { diff --git a/packages/effects-webgl/src/gl-texture.ts b/packages/effects-webgl/src/gl-texture.ts index d01a337e..ab13018f 100644 --- a/packages/effects-webgl/src/gl-texture.ts +++ b/packages/effects-webgl/src/gl-texture.ts @@ -12,8 +12,6 @@ import type { GLPipelineContext } from './gl-pipeline-context'; import { assignInspectorName } from './gl-renderer-internal'; import type { GLEngine } from './gl-engine'; -type HTMLImageLike = HTMLImageElement | HTMLCanvasElement | HTMLVideoElement; - const FORMAT_HALF_FLOAT: Record = { [glContext.RGBA]: 34842, //RGBA16F [glContext.RGB]: 34843, //RGB16F @@ -336,7 +334,7 @@ export class GLTexture extends Texture implements Disposable, RestoreHandler { internalformat: GLenum, format: GLenum, type: GLenum, - image: HTMLImageLike, + image: spec.HTMLImageLike, ): spec.vec2 { const { sourceType, minFilter, magFilter, wrapS, wrapT } = this.source; const maxSize = this.engine.gpuCapability.detail.maxTextureSize ?? 2048; @@ -390,7 +388,7 @@ export class GLTexture extends Texture implements Disposable, RestoreHandler { return [width, height]; } - private resizeImage (image: HTMLImageLike, targetWidth?: number, targetHeight?: number): HTMLCanvasElement | HTMLImageElement { + private resizeImage (image: spec.HTMLImageLike, targetWidth?: number, targetHeight?: number): HTMLCanvasElement | HTMLImageElement { const { detail } = this.engine.gpuCapability; const maxSize = detail.maxTextureSize ?? 2048; @@ -512,7 +510,7 @@ export class GLTexture extends Texture implements Disposable, RestoreHandler { } function resizeImageByCanvas ( - image: HTMLImageLike, + image: spec.HTMLImageLike, maxSize: number, targetWidth?: number, targetHeight?: number, diff --git a/packages/effects/src/player.ts b/packages/effects/src/player.ts index a4e7f063..6bc3a897 100644 --- a/packages/effects/src/player.ts +++ b/packages/effects/src/player.ts @@ -1,14 +1,12 @@ import type { Disposable, GLType, GPUCapability, LostHandler, RestoreHandler, SceneLoadOptions, - Texture2DSourceOptionsVideo, TouchEventType, SceneLoadType, SceneType, EffectsObject, - MessageItem, Scene, + Texture2DSourceOptionsVideo, TouchEventType, EffectsObject, MessageItem, } from '@galacean/effects-core'; import { AssetManager, Composition, EVENT_TYPE_CLICK, EventSystem, logger, Renderer, Material, TextureLoadAction, Ticker, canvasPool, getPixelRatio, gpuTimer, initErrors, isAndroid, - isArray, pluginLoaderMap, setSpriteMeshMaxItemCountByGPU, spec, isSceneURL, EventEmitter, - generateWhiteTexture, isSceneWithOptions, Texture, PLAYER_OPTIONS_ENV_EDITOR, isIOS, - DEFAULT_FPS, + isArray, pluginLoaderMap, setSpriteMeshMaxItemCountByGPU, spec, EventEmitter, + generateWhiteTexture, Texture, PLAYER_OPTIONS_ENV_EDITOR, isIOS, DEFAULT_FPS, Scene, } from '@galacean/effects-core'; import type { GLRenderer } from '@galacean/effects-webgl'; import { HELP_LINK } from './constants'; @@ -179,6 +177,21 @@ export class Player extends EventEmitter> implements Disposa return this.compositions; } + /** + * Gets the array of asset managers. + * @returns + */ + getAssetManager (): ReadonlyArray { + return this.assetManagers; + } + + /** + * 获取当前播放的合成数量 + */ + get compositionCount () { + return this.compositions.length; + } + /** * 是否有合成在播放 */ @@ -267,9 +280,12 @@ export class Player extends EventEmitter> implements Disposa * @param options - 加载可选参数 * @returns */ - async loadScene (scene: SceneLoadType, options?: SceneLoadOptions): Promise; - async loadScene (scene: SceneLoadType[], options?: SceneLoadOptions): Promise; - async loadScene (scene: SceneLoadType | SceneLoadType[], options?: SceneLoadOptions): Promise { + async loadScene (scene: Scene.LoadType, options?: SceneLoadOptions): Promise; + async loadScene (scene: Scene.LoadType[], options?: SceneLoadOptions): Promise; + async loadScene ( + scene: Scene.LoadType | Scene.LoadType[], + options?: SceneLoadOptions, + ): Promise { let composition: Composition | Composition[]; const baseOrder = this.baseCompositionIndex; @@ -290,11 +306,11 @@ export class Player extends EventEmitter> implements Disposa this.ticker?.start(); - return composition; + return composition as T; } private async createComposition ( - url: SceneLoadType, + url: Scene.LoadType, options: SceneLoadOptions = {}, ): Promise { const renderer = this.renderer; @@ -305,18 +321,19 @@ export class Player extends EventEmitter> implements Disposa autoplay: true, ...options, }; - let source: SceneType; + let source: Scene.LoadType = url; - if (isSceneURL(url)) { - source = url.url; - if (isSceneWithOptions(url)) { + // 加载多个合成链接并各自设置可选参数 + if (Scene.isURL(url)) { + if (!Scene.isJSONObject(url)) { + source = url.url; + } + if (Scene.isWithOptions(url)) { opts = { ...opts, ...url.options, }; } - } else { - source = url; } const assetManager = new AssetManager(opts); diff --git a/plugin-packages/model/src/gltf/loader-impl.ts b/plugin-packages/model/src/gltf/loader-impl.ts index 96173700..5b5bc3b9 100644 --- a/plugin-packages/model/src/gltf/loader-impl.ts +++ b/plugin-packages/model/src/gltf/loader-impl.ts @@ -1271,7 +1271,7 @@ export function getDefaultUnlitMaterialData (): spec.MaterialData { }, 'macros': [], 'shader': { - 'id': 'unlit000000000000000000000000000', + 'id': spec.BuiltinObjectGUID.UnlitShader, }, 'ints': { diff --git a/plugin-packages/model/src/runtime/common.ts b/plugin-packages/model/src/runtime/common.ts index 838d3b59..3d6cb0fb 100644 --- a/plugin-packages/model/src/runtime/common.ts +++ b/plugin-packages/model/src/runtime/common.ts @@ -83,8 +83,8 @@ export enum PShadowType { expVariance, } -export const PBRShaderGUID = 'pbr00000000000000000000000000000'; -export const UnlitShaderGUID = 'unlit000000000000000000000000000'; +export const PBRShaderGUID = spec.BuiltinObjectGUID.PBRShader; +export const UnlitShaderGUID = spec.BuiltinObjectGUID.UnlitShader; /** * 插件变换类 diff --git a/plugin-packages/model/test/src/plugin-unit.spec.ts b/plugin-packages/model/test/src/plugin-unit.spec.ts index 4676763c..af985d6d 100644 --- a/plugin-packages/model/test/src/plugin-unit.spec.ts +++ b/plugin-packages/model/test/src/plugin-unit.spec.ts @@ -473,7 +473,7 @@ describe('渲染插件单测', function () { id: '1', name: 'mat1', dataType: spec.DataType.Material, - shader: { id: 'pbr00000000000000000000000000000' }, + shader: { id: spec.BuiltinObjectGUID.PBRShader }, stringTags: { RenderFace: spec.RenderFace.Front, RenderType: spec.RenderType.Opaque, diff --git a/plugin-packages/model/test/src/utilities.ts b/plugin-packages/model/test/src/utilities.ts index 9268a52a..94bcb147 100644 --- a/plugin-packages/model/test/src/utilities.ts +++ b/plugin-packages/model/test/src/utilities.ts @@ -1,8 +1,8 @@ -import type { Player, SceneLoadOptions, SceneLoadType } from '@galacean/effects'; +import type { Player, SceneLoadOptions, Scene } from '@galacean/effects'; export async function generateComposition ( player: Player, - scene: SceneLoadType, + scene: Scene.LoadType, loadOptions?: SceneLoadOptions, options: Record = {}, ) { diff --git a/web-packages/demo/src/assets/cube-textures.ts b/web-packages/demo/src/assets/cube-textures.ts deleted file mode 100644 index 7bcb8d07..00000000 --- a/web-packages/demo/src/assets/cube-textures.ts +++ /dev/null @@ -1,62 +0,0 @@ -export default { - 'compositionId': 1, - 'requires': [], - 'compositions': [{ - 'name': 'composition_1', - 'id': 1, - 'duration': 50, - 'camera': { 'fov': 30, 'far': 20, 'near': 0.1, 'position': [0, 0, 8], 'clipMode': 1 }, - 'items': [{ - 'name': 'item_1', - 'delay': 0, - 'id': 1, - 'type': '1', - 'ro': 0.1, - 'sprite': { - 'options': { - 'startLifetime': 2, - 'startSize': 1.2, - 'sizeAspect': 1.320754716981132, - 'startColor': ['color', [255, 255, 255]], - 'duration': 20, - 'gravityModifier': 1, - 'renderLevel': 'B+', - }, 'renderer': { 'renderMode': 1, 'anchor': [0.5, 0.5], 'texture': 0 }, - }, - }], - 'meta': { 'previewSize': [750, 1624] }, - }], - 'gltf': [], - images: [], - 'textures': [{ - 'minFilter': 9987, - 'magFilter': 9729, - 'wrapS': 33071, - 'wrapT': 33071, - 'target': 34067, - 'format': 6408, - 'internalFormat': 6408, - 'type': 5121, - 'mipmaps': [ - [[20, [5, 0, 24661]], [20, [5, 24664, 26074]], [20, [5, 50740, 26845]], [20, [5, 77588, 24422]], [20, [5, 102012, 24461]], [20, [5, 126476, 27099]]], - [[20, [5, 153576, 7699]], [20, [5, 161276, 7819]], [20, [5, 169096, 8919]], [20, [5, 178016, 7004]], [20, [5, 185020, 7657]], [20, [5, 192680, 8515]]], - [[20, [5, 201196, 2305]], [20, [5, 203504, 2388]], [20, [5, 205892, 2789]], [20, [5, 208684, 2147]], [20, [5, 210832, 2351]], [20, [5, 213184, 2541]]], - [[20, [5, 215728, 755]], [20, [5, 216484, 810]], [20, [5, 217296, 902]], [20, [5, 218200, 727]], [20, [5, 218928, 775]], [20, [5, 219704, 835]]], - [[20, [5, 220540, 292]], [20, [5, 220832, 301]], [20, [5, 221136, 317]], [20, [5, 221456, 285]], [20, [5, 221744, 301]], [20, [5, 222048, 307]]], - [[20, [5, 222356, 147]], [20, [5, 222504, 147]], [20, [5, 222652, 149]], [20, [5, 222804, 149]], [20, [5, 222956, 149]], [20, [5, 223108, 149]]], - [[20, [5, 223260, 96]], [20, [5, 223356, 96]], [20, [5, 223452, 96]], [20, [5, 223548, 97]], [20, [5, 223648, 97]], [20, [5, 223748, 97]]], - [[20, [5, 223848, 83]], [20, [5, 223932, 83]], [20, [5, 224016, 83]], [20, [5, 224100, 83]], [20, [5, 224184, 83]], [20, [5, 224268, 83]]]], - 'sourceType': 7, - }], - 'bins': [ - new ArrayBuffer(1), - new ArrayBuffer(1), - new ArrayBuffer(1), - new ArrayBuffer(1), - new ArrayBuffer(1), - { 'url': 'https://mdn.alipayobjects.com/mars/afts/file/A*pGF4QJqDT3wAAAAAAAAAAAAADlB4AQ' }], - 'version': '0.9.0', - 'shapes': [], - 'plugins': [], - 'type': 'mars', -}; diff --git a/web-packages/demo/src/dashboard.ts b/web-packages/demo/src/dashboard.ts index 3d00fb5b..4634008d 100644 --- a/web-packages/demo/src/dashboard.ts +++ b/web-packages/demo/src/dashboard.ts @@ -1,6 +1,5 @@ import type { Composition } from '@galacean/effects'; import { Player } from '@galacean/effects'; -import cubeTextures from './assets/cube-textures'; const jsons = [ // 方块 @@ -25,9 +24,6 @@ const jsons = [ 'https://mdn.alipayobjects.com/mars/afts/file/A*dnU-SprU5pAAAAAAAAAAAAAADlB4AQ', ]; -// @ts-expect-error -jsons.push(cubeTextures); - (async () => { try { const container = createContainer(); diff --git a/web-packages/test/assets/cube-texture.ts b/web-packages/test/assets/cube-texture.ts new file mode 100644 index 00000000..91b2126d --- /dev/null +++ b/web-packages/test/assets/cube-texture.ts @@ -0,0 +1,119 @@ +const cubeTexture1 = { + 'compositionId': 1, + 'requires': [], + 'compositions': [{ + 'name': 'composition_1', + 'id': 1, + 'duration': 5, + 'camera': { 'fov': 30, 'far': 20, 'near': 0.1, 'position': [0, 0, 8], 'clipMode': 1 }, + 'items': [{ + 'name': '11111', + 'delay': 0, + 'id': 2, + 'type': '1', + 'ro': 0.1, + 'sprite': { + 'options': { + 'startLifetime': 2, + 'startSize': 0.8355836885408324, + 'sizeAspect': 1.1403508771929824, + 'startColor': [8, [255, 255, 255]], + 'duration': 2, + 'gravityModifier': 1, + 'renderLevel': 'B+', + }, 'renderer': { 'renderMode': 1, 'anchor': [0.5, 0.5], 'texture': 0 }, + }, + }], + 'meta': { 'previewSize': [0, 0] }, + }], + 'gltf': [], + 'images': [], + 'textures': [{ + 'mipmaps': [ + [[20, [3, 0, 113649]], [20, [3, 113652, 103308]], [20, [3, 216960, 73885]], [20, [3, 290848, 115292]], [20, [3, 406140, 109199]], [20, [3, 515340, 102131]]], + [[20, [3, 617472, 26164]], [20, [3, 643636, 23931]], [20, [3, 667568, 19089]], [20, [3, 686660, 24184]], [20, [3, 710844, 25232]], [20, [3, 736076, 23683]]], + [[20, [3, 759760, 5543]], [20, [3, 765304, 4313]], [20, [3, 769620, 4236]], [20, [3, 773856, 3895]], [20, [3, 777752, 4664]], [20, [3, 782416, 4697]]], + [[20, [3, 787116, 1550]], [20, [3, 788668, 1240]], [20, [3, 789908, 1230]], [20, [3, 791140, 1176]], [20, [3, 792316, 1322]], [20, [3, 793640, 1286]]], + [[20, [3, 794928, 453]], [20, [3, 795384, 444]], [20, [3, 795828, 458]], [20, [3, 796288, 512]], [20, [3, 796800, 474]], [20, [3, 797276, 499]]], + ], + 'sourceType': 7, + 'target': 34067, + }], + 'bins': [ + { 'url': 'https://gw.alipayobjects.com/os/gltf-asset/67210123752698/data0.bin' }, + { 'url': 'https://gw.alipayobjects.com/os/gltf-asset/67210123752698/data1.bin' }, + { 'url': 'https://gw.alipayobjects.com/os/gltf-asset/67210123752698/data2.bin' }, + { 'url': 'https://gw.alipayobjects.com/os/gltf-asset/67210123752698/data3.bin' }, + ], + 'version': '0.9.0', + 'shapes': [], + 'plugins': [], + 'type': 'mars', + '_imgs': { '1': [] }, +}; + +const cubeTexture2 = { + 'compositionId': 1, + 'requires': [], + 'compositions': [{ + 'name': 'composition_1', + 'id': 1, + 'duration': 5, + 'camera': { 'fov': 30, 'far': 20, 'near': 0.1, 'position': [0, 0, 8], 'clipMode': 1 }, + 'items': [{ + 'name': 'item_1', + 'delay': 0, + 'id': 1, + 'type': '1', + 'ro': 0.1, + 'sprite': { + 'options': { + 'startLifetime': 2, + 'startSize': 1.2, + 'sizeAspect': 1.320754716981132, + 'startColor': [8, [255, 255, 255]], + 'duration': 2, + 'gravityModifier': 1, + 'renderLevel': 'B+', + }, 'renderer': { 'renderMode': 1, 'anchor': [0.5, 0.5], 'texture': 0 }, + }, + }], + 'meta': { 'previewSize': [750, 1624] }, + }], + 'gltf': [], + 'images': [], + 'textures': [{ + 'minFilter': 9987, + 'magFilter': 9729, + 'wrapS': 33071, + 'wrapT': 33071, + 'target': 34067, + 'format': 6408, + 'internalFormat': 6408, + 'type': 5121, + 'mipmaps': [ + [[20, [5, 0, 24661]], [20, [5, 24664, 26074]], [20, [5, 50740, 26845]], [20, [5, 77588, 24422]], [20, [5, 102012, 24461]], [20, [5, 126476, 27099]]], + [[20, [5, 153576, 7699]], [20, [5, 161276, 7819]], [20, [5, 169096, 8919]], [20, [5, 178016, 7004]], [20, [5, 185020, 7657]], [20, [5, 192680, 8515]]], + [[20, [5, 201196, 2305]], [20, [5, 203504, 2388]], [20, [5, 205892, 2789]], [20, [5, 208684, 2147]], [20, [5, 210832, 2351]], [20, [5, 213184, 2541]]], + [[20, [5, 215728, 755]], [20, [5, 216484, 810]], [20, [5, 217296, 902]], [20, [5, 218200, 727]], [20, [5, 218928, 775]], [20, [5, 219704, 835]]], + [[20, [5, 220540, 292]], [20, [5, 220832, 301]], [20, [5, 221136, 317]], [20, [5, 221456, 285]], [20, [5, 221744, 301]], [20, [5, 222048, 307]]], + [[20, [5, 222356, 147]], [20, [5, 222504, 147]], [20, [5, 222652, 149]], [20, [5, 222804, 149]], [20, [5, 222956, 149]], [20, [5, 223108, 149]]], + [[20, [5, 223260, 96]], [20, [5, 223356, 96]], [20, [5, 223452, 96]], [20, [5, 223548, 97]], [20, [5, 223648, 97]], [20, [5, 223748, 97]]], + [[20, [5, 223848, 83]], [20, [5, 223932, 83]], [20, [5, 224016, 83]], [20, [5, 224100, 83]], [20, [5, 224184, 83]], [20, [5, 224268, 83]]]], + 'sourceType': 7, + }], + 'bins': [ + new ArrayBuffer(1), + new ArrayBuffer(1), + new ArrayBuffer(1), + new ArrayBuffer(1), + new ArrayBuffer(1), + new ArrayBuffer(1), + ], + 'version': '0.9.0', + 'shapes': [], + 'plugins': [], + 'type': 'mars', +}; + +export { cubeTexture1, cubeTexture2 }; diff --git a/web-packages/test/unit/src/effects-core/asset-manager.spec.ts b/web-packages/test/unit/src/effects-core/asset-manager.spec.ts index 0293aebd..e63bfe54 100644 --- a/web-packages/test/unit/src/effects-core/asset-manager.spec.ts +++ b/web-packages/test/unit/src/effects-core/asset-manager.spec.ts @@ -1,181 +1,64 @@ -import type { Texture2DSourceOptionsVideo } from '@galacean/effects'; -import { Player, spec } from '@galacean/effects'; +import { AssetManager, TextureSourceType, spec } from '@galacean/effects'; const { expect } = chai; describe('core/asset-manager', () => { - let player: Player; + let assetManager: AssetManager; before(() => { - player = new Player({ canvas: document.createElement('canvas'), manualRender: true }); }); after(() => { - player.dispose(); - // @ts-expect-error - player = null; + assetManager?.dispose(); }); - it('template video', async () => { - const videoUrl = 'https://mdn.alipayobjects.com/huamei_p0cigc/afts/file/A*ZOgXRbmVlsIAAAAAAAAAAAAADoB5AQ'; - const images = [{ - 'id': 'test', - 'template': { - 'width': 126, - 'height': 130, - 'background': { - 'type': spec.BackgroundType.video, - 'name': 'test', - 'url': videoUrl, - }, - }, - 'url': 'https://mdn.alipayobjects.com/mars/afts/img/A*oKwARKdkWhEAAAAAAAAAAAAADlB4AQ/original', - 'webp': 'https://mdn.alipayobjects.com/mars/afts/img/A*eOLVQpT57FcAAAAAAAAAAAAADlB4AQ/original', - 'renderLevel': spec.RenderLevel.BPlus, - }]; + it('scene renderLevel is right when pass options', async () => { + assetManager = new AssetManager({ + renderLevel: spec.RenderLevel.B, + }); + const scene = await assetManager.loadScene('https://mdn.alipayobjects.com/mars/afts/file/A*GC99RbcyZiMAAAAAAAAAAAAADlB4AQ'); + + expect(scene.renderLevel).to.eql(spec.RenderLevel.B); + }); - const composition = await player.loadScene(generateScene(images)); - const textures = composition.textures; - const videoElement = (textures[0].source as Texture2DSourceOptionsVideo).video; + it('scene renderLevel is right when not pass options', async () => { + assetManager = new AssetManager(); + const scene = await assetManager.loadScene('https://mdn.alipayobjects.com/mars/afts/file/A*GC99RbcyZiMAAAAAAAAAAAAADlB4AQ'); - expect(textures.length).to.deep.equals(1); - expect(textures[0].source).to.not.be.empty; - expect(videoElement).to.be.an.instanceOf(HTMLVideoElement); - expect(videoElement.src).to.be.equals(videoUrl); + expect(scene.renderLevel).to.eql(undefined); }); - it('templateV2 video variables', async () => { - const videoUrl = 'https://gw.alipayobjects.com/v/huamei_p0cigc/afts/video/A*dftzSq2szUsAAAAAAAAAAAAADtN3AQ'; - const images = [{ - 'id': 'test', - 'template': { - 'width': 126, - 'height': 130, - 'background': { - 'type': spec.BackgroundType.video, - 'name': 'test', - 'url': 'https://mdn.alipayobjects.com/huamei_p0cigc/afts/file/A*ZOgXRbmVlsIAAAAAAAAAAAAADoB5AQ', - }, - }, - 'url': 'https://mdn.alipayobjects.com/mars/afts/img/A*oKwARKdkWhEAAAAAAAAAAAAADlB4AQ/original', - 'webp': 'https://mdn.alipayobjects.com/mars/afts/img/A*eOLVQpT57FcAAAAAAAAAAAAADlB4AQ/original', - 'renderLevel': spec.RenderLevel.BPlus, - }]; + it('image replace right when pass variables', async () => { + const json = 'https://mdn.alipayobjects.com/mars/afts/file/A*PubBSpHUbjYAAAAAAAAAAAAADlB4AQ'; + const url = 'https://mdn.alipayobjects.com/huamei_klifp9/afts/img/A*ySrfRJvfvfQAAAAAAAAAAAAADvV6AQ/original'; - const composition = await player.loadScene(generateScene(images), { + assetManager = new AssetManager({ variables: { - // 视频地址 - test: videoUrl, + image: url, }, }); - const textures = composition.textures; - const videoElement = (textures[0].source as Texture2DSourceOptionsVideo).video; + const scene = await assetManager.loadScene(json); - expect(videoElement).to.be.an.instanceOf(HTMLVideoElement); - expect(videoElement.src).to.be.equals(videoUrl); + expect((scene.images[0] as HTMLImageElement).src).to.eql(url); + expect(scene.textureOptions[0].image.src).to.eql(url); }); -}); -const generateScene = (images: spec.TemplateImage[]) => { - return { - 'playerVersion': { - 'web': '1.2.1', - 'native': '0.0.1.202311221223', - }, - 'images': images, - 'version': '2.2', - 'type': 'ge', - 'compositions': [ - { - 'id': '1', - 'name': '新建合成1', - 'duration': 5, - 'startTime': 0, - 'endBehavior': 0, - 'previewSize': [750, 1624], - 'items': [ - { - 'id': '1', - 'name': 'sprite_1', - 'duration': 5, - 'type': '1', - 'visible': true, - 'endBehavior': 0, - 'delay': 0, - 'renderLevel': 'B+', - 'content': { - 'options': { - 'startColor': [0.9529, 1, 0.0431, 1], - }, - 'renderer': { - 'renderMode': 1, - 'texture': 0, - }, - 'positionOverLifetime': { - 'path': [12, [ - [ - [0, 0, 0, 2.3256], - [0.43, 1, 2.3256, 3.4483], - [0.72, 2, 3.4483, 0], - ], - [ - [0, 0, 0], - [0, 7.79, 0], - [3.3269, 7.79, 0], - ], - [ - [0, 1.9475, 0], - [0, 5.8425, 0], - [0.8317, 7.79, 0], - [2.4952, 7.79, 0], - ], - ], - ], - 'direction': [0, 0, 0], - 'startSpeed': 0, - 'gravity': [0, 0, 0], - 'gravityOverLifetime': [0, 1], - }, - 'sizeOverLifetime': { - 'size': [6, [ - [0.126, 1.2055, 0, 1.6835], - [0.72, 2.5395, 1.6835, 0], - ], - ], - }, - 'colorOverLifetime': { - 'opacity': [6, [ - [0, 0, 0, 1.3889], - [0.72, 1, 1.3889, 0], - ], - ], - }, - }, - 'transform': { - 'position': [0, 0, 0], - 'rotation': [0, 0, 0], - 'scale': [1.5492, 1.5984, 1], - }, - }, - ], - 'camera': { - 'fov': 60, - 'far': 40, - 'near': 0.1, - 'clipMode': 1, - 'position': [0, 0, 8], - 'rotation': [0, 0, 0], - }, - }, - ], - 'requires': [], - 'compositionId': '1', - 'bins': [], - 'textures': [ - { - 'source': 0, - 'flipY': true, + it('video replace right when pass variables', async () => { + const json = 'https://mdn.alipayobjects.com/mars/afts/file/A*kENFRbxlKcUAAAAAAAAAAAAADlB4AQ'; + const url = 'https://gw.alipayobjects.com/v/huamei_p0cigc/afts/video/A*7gPzSo3RxlQAAAAAAAAAAAAADtN3AQ'; + const text = 'Dynamic Video'; + + assetManager = new AssetManager({ + variables: { + video: url, + text_3: text, }, - ], - }; -}; + }); + const scene = await assetManager.loadScene(json); + + expect((scene.images[1] as HTMLVideoElement).src).to.eql(url); + expect(scene.textureOptions[1].sourceType).to.eql(TextureSourceType.video); + expect(scene.textureOptions[1].video.src).to.eql(url); + expect(scene.jsonScene.items[0].content.options.text).to.not.eql(text); + }); +}); diff --git a/web-packages/test/unit/src/effects/scene-load.spec.ts b/web-packages/test/unit/src/effects/scene-load.spec.ts index 8850dce3..ba054393 100644 --- a/web-packages/test/unit/src/effects/scene-load.spec.ts +++ b/web-packages/test/unit/src/effects/scene-load.spec.ts @@ -1,4 +1,6 @@ -import { Player, TextComponent } from '@galacean/effects'; +import type { Texture2DSourceOptionsVideo } from '@galacean/effects'; +import { AssetManager, Player, SpriteComponent, TextComponent, spec } from '@galacean/effects'; +import { cubeTexture1, cubeTexture2 } from '../../../assets/cube-texture'; const { expect } = chai; @@ -27,7 +29,7 @@ describe('player/scene-load', () => { await player.loadScene(json, { variables }); // @ts-expect-error - expect(player.assetManagers.find(d => d.baseUrl === json).options.variables).to.eql(variables); + expect(player.getAssetManager().find(d => d.baseUrl === json)?.options.variables).to.eql(variables); }); it('加载单个合成 JSONValue 并设置可选参数', async () => { @@ -38,8 +40,7 @@ describe('player/scene-load', () => { await player.loadScene(json, { variables }); - // @ts-expect-error - expect(player.assetManagers[1].options.variables).to.eql(variables); + expect(player.getAssetManager()[1].options.variables).to.eql(variables); }); it('加载多个合成链接并各自设置可选参数', async () => { @@ -53,7 +54,6 @@ describe('player/scene-load', () => { 'image1': 'https://mdn.alipayobjects.com/huamei_uj3n0k/afts/img/A*st1QSIvJEBcAAAAAAAAAAAAADt_KAQ/original', }; - // @ts-expect-error const [composition1, composition2] = await player.loadScene([{ url: json1, options: { @@ -68,9 +68,9 @@ describe('player/scene-load', () => { }]); // @ts-expect-error - expect(player.assetManagers.find(d => d.baseUrl === json1).options.variables).to.eql(variables1); + expect(player.getAssetManager().find(d => d.baseUrl === json1).options.variables).to.eql(variables1); // @ts-expect-error - expect(player.assetManagers.find(d => d.baseUrl === json2).options.variables).to.eql(variables2); + expect(player.getAssetManager().find(d => d.baseUrl === json2).options.variables).to.eql(variables2); expect(composition1.getSpeed()).to.eql(2); expect(composition2.getSpeed()).to.eql(1); }); @@ -79,7 +79,6 @@ describe('player/scene-load', () => { const json1 = 'https://mdn.alipayobjects.com/mars/afts/file/A*T1U4SqWhvioAAAAAAAAAAAAADlB4AQ'; const json2 = 'https://mdn.alipayobjects.com/mars/afts/file/A*de0NTrRAyzoAAAAAAAAAAAAADlB4AQ'; - // @ts-expect-error const [composition1, composition2] = await player.loadScene([{ url: json1, }, { @@ -178,177 +177,7 @@ describe('player/scene-load', () => { expect(textComponent?.text).to.eql('ttt'); }); - // TODO 未通过 先注释 - // it('load cube texture demo2', async () => { - // const json = { - // 'compositionId': 1, - // 'requires': [], - // 'compositions': [{ - // 'name': 'composition_1', - // 'id': 1, - // 'duration': 5, - // 'camera': { 'fov': 30, 'far': 20, 'near': 0.1, 'position': [0, 0, 8], 'clipMode': 1 }, - // 'items': [{ - // 'name': '11111', - // 'delay': 0, - // 'id': 2, - // 'type': '1', - // 'ro': 0.1, - // 'sprite': { - // 'options': { - // 'startLifetime': 2, - // 'startSize': 0.8355836885408324, - // 'sizeAspect': 1.1403508771929824, - // 'startColor': [8, [255, 255, 255]], - // 'duration': 2, - // 'gravityModifier': 1, - // 'renderLevel': 'B+', - // }, 'renderer': { 'renderMode': 1, 'anchor': [0.5, 0.5], 'texture': 0 }, - // }, - // }], - // 'meta': { 'previewSize': [0, 0] }, - // }], - // 'gltf': [], - // 'images': [], - // 'version': '0.9.0', - // 'shapes': [], - // 'plugins': [], - // 'bins': [ - // { 'url': 'https://gw.alipayobjects.com/os/gltf-asset/67210123752698/data0.bin' }, - // { 'url': 'https://gw.alipayobjects.com/os/gltf-asset/67210123752698/data1.bin' }, - // { 'url': 'https://gw.alipayobjects.com/os/gltf-asset/67210123752698/data2.bin' }, - // { 'url': 'https://gw.alipayobjects.com/os/gltf-asset/67210123752698/data3.bin' }, - // ], - // 'textures': [ - // { - // 'mipmaps': [ - // [ - // [20, [3, 0, 113649]], - // [20, [3, 113652, 103308]], - // [20, [3, 216960, 73885]], - // [20, [3, 290848, 115292]], - // [20, [3, 406140, 109199]], - // [20, [3, 515340, 102131]], - // ], - // [ - // [20, [3, 617472, 26164]], - // [20, [3, 643636, 23931]], - // [20, [3, 667568, 19089]], - // [20, [3, 686660, 24184]], - // [20, [3, 710844, 25232]], - // [20, [3, 736076, 23683]], - // ], - // [ - // [20, [3, 759760, 5543]], - // [20, [3, 765304, 4313]], - // [20, [3, 769620, 4236]], - // [20, [3, 773856, 3895]], - // [20, [3, 777752, 4664]], - // [20, [3, 782416, 4697]], - // ], - // [ - // [20, [3, 787116, 1550]], - // [20, [3, 788668, 1240]], - // [20, [3, 789908, 1230]], - // [20, [3, 791140, 1176]], - // [20, [3, 792316, 1322]], - // [20, [3, 793640, 1286]], - // ], - // [ - // [20, [3, 794928, 453]], - // [20, [3, 795384, 444]], - // [20, [3, 795828, 458]], - // [20, [3, 796288, 512]], - // [20, [3, 796800, 474]], - // [20, [3, 797276, 499]], - // ], - // ], - // 'sourceType': 7, - // 'target': 34067, - // }, - // ], - // 'type': 'mars', - // '_imgs': { '1': [] }, - // }; - // const scn = await player.loadScene(json); - // - // expect(scn.textures[0].mipmaps.length).to.eql(json.textures[0].mipmaps.length); - // expect(scn.textures[0].mipmaps.every(m => m.every(img => img instanceof HTMLImageElement || img instanceof ImageBitmap))).to.be.true; - // }); - // - // it('load cube textures fail', async () => { - // const a = { - // 'compositionId': 1, - // 'requires': [], - // 'compositions': [{ - // 'name': 'composition_1', - // 'id': 1, - // 'duration': 5, - // 'camera': { 'fov': 30, 'far': 20, 'near': 0.1, 'position': [0, 0, 8], 'clipMode': 1 }, - // 'items': [{ - // 'name': 'item_1', - // 'delay': 0, - // 'id': 1, - // 'type': '1', - // 'ro': 0.1, - // 'sprite': { - // 'options': { - // 'startLifetime': 2, - // 'startSize': 1.2, - // 'sizeAspect': 1.320754716981132, - // 'startColor': [8, [255, 255, 255]], - // 'duration': 2, - // 'gravityModifier': 1, - // 'renderLevel': 'B+', - // }, 'renderer': { 'renderMode': 1, 'anchor': [0.5, 0.5], 'texture': 0 }, - // }, - // }], - // 'meta': { 'previewSize': [750, 1624] }, - // }], - // 'gltf': [], - // images: [], - // 'textures': [{ - // 'minFilter': 9987, - // 'magFilter': 9729, - // 'wrapS': 33071, - // 'wrapT': 33071, - // 'target': 34067, - // 'format': 6408, - // 'internalFormat': 6408, - // 'type': 5121, - // 'mipmaps': [[ - // [20, [5, 0, 24661]], - // [20, [5, 24664, 26074]], - // [20, [5, 50740, 26845]], - // [20, [5, 77588, 24422]], - // [20, [5, 102012, 24461]], - // [20, [5, 126476, 27099]]], - // [[20, [5, 153576, 7699]], [20, [5, 161276, 7819]], [20, [5, 169096, 8919]], [20, [5, 178016, 7004]], [20, [5, 185020, 7657]], [20, [5, 192680, 8515]]], [[20, [5, 201196, 2305]], [20, [5, 203504, 2388]], [20, [5, 205892, 2789]], [20, [5, 208684, 2147]], [20, [5, 210832, 2351]], [20, [5, 213184, 2541]]], [[20, [5, 215728, 755]], [20, [5, 216484, 810]], [20, [5, 217296, 902]], [20, [5, 218200, 727]], [20, [5, 218928, 775]], [20, [5, 219704, 835]]], [[20, [5, 220540, 292]], [20, [5, 220832, 301]], [20, [5, 221136, 317]], [20, [5, 221456, 285]], [20, [5, 221744, 301]], [20, [5, 222048, 307]]], [[20, [5, 222356, 147]], [20, [5, 222504, 147]], [20, [5, 222652, 149]], [20, [5, 222804, 149]], [20, [5, 222956, 149]], [20, [5, 223108, 149]]], [[20, [5, 223260, 96]], [20, [5, 223356, 96]], [20, [5, 223452, 96]], [20, [5, 223548, 97]], [20, [5, 223648, 97]], [20, [5, 223748, 97]]], [[20, [5, 223848, 83]], [20, [5, 223932, 83]], [20, [5, 224016, 83]], [20, [5, 224100, 83]], [20, [5, 224184, 83]], [20, [5, 224268, 83]]]], - // 'sourceType': 7, - // }], - // 'bins': [ - // new ArrayBuffer(1), - // new ArrayBuffer(1), - // new ArrayBuffer(1), - // new ArrayBuffer(1), - // new ArrayBuffer(1), - // new ArrayBuffer(1)], - // 'version': '0.9.0', - // 'shapes': [], - // 'plugins': [], - // 'type': 'mars', - // }; - // const spy = chai.spy(); - // - // await player.loadScene(a).catch(ex => { - // expect(ex.message).to.eql('Error: load texture 0 fails'); - // spy(); - // }); - // expect(spy).to.has.been.called.once; - // }); - it('load scene with different text variables', async () => { - // @ts-expect-error const [composition1, composition2] = await player.loadScene([{ url: 'https://mdn.alipayobjects.com/mars/afts/file/A*_QkURKxu0TEAAAAAAAAAAAAADlB4AQ', options: { @@ -364,14 +193,150 @@ describe('player/scene-load', () => { }, }, }]); - const t1 = composition1.getItemByName('text_908').getComponent(TextComponent); - const t2 = composition2.getItemByName('text_908').getComponent(TextComponent); + const t1 = composition1.getItemByName('text_908')?.getComponent(TextComponent); + const t2 = composition2.getItemByName('text_908')?.getComponent(TextComponent); + + expect(t1?.text).to.eql('ttt'); + expect(t2?.text).to.eql('xxx'); + }); - expect(t1.text).to.eql('ttt'); - expect(t2.text).to.eql('xxx'); + it('success load json object by assetManager', async () => { + const url = 'https://mdn.alipayobjects.com/mars/afts/file/A*PubBSpHUbjYAAAAAAAAAAAAADlB4AQ'; + const [json] = await Promise.all([ + fetch(url).then(res => res.text()), + ]); + const data = JSON.parse(json); + const assetManager = new AssetManager(); + const scene = await assetManager.loadScene(data); + const spy = chai.spy(); + + try { + await player.loadScene(scene); + } catch (e: any) { + spy(); + } + expect(spy).not.to.have.been.called(); + }); + + it('success load multi-data type by assetManager', async () => { + const url1 = 'https://gw.alipayobjects.com/os/gltf-asset/mars-cli/SDNRPIJFENBK/-1998534768-39820.json'; + const url2 = 'https://mdn.alipayobjects.com/mars/afts/file/A*PubBSpHUbjYAAAAAAAAAAAAADlB4AQ'; + const image = 'https://mdn.alipayobjects.com/huamei_klifp9/afts/img/A*ySrfRJvfvfQAAAAAAAAAAAAADvV6AQ/original'; + const [json] = await Promise.all([ + fetch(url1).then(res => res.text()), + ]); + const data = JSON.parse(json); + const assetManager = new AssetManager({ + renderLevel: spec.RenderLevel.S, + variables: { + image, + }, + }); + const scene1 = await assetManager.loadScene(data); + const scene2 = await assetManager.loadScene(url2); + const spy = chai.spy(); + + expect(scene1.renderLevel).to.eql(spec.RenderLevel.S); + expect(scene2.renderLevel).to.eql(spec.RenderLevel.S); + + try { + const [composition1, composition2] = await player.loadScene([scene1, { url: scene2 }], { + speed: 2, + }); + const item = composition2.getItemByName('sprite_1'); + const spriteComponent = item?.getComponent(SpriteComponent); + + expect(spriteComponent?.getTextures()[0].sourceFrom).to.contains({ url: image }); + expect(composition1.getSpeed()).to.eql(2); + expect(composition2.getSpeed()).to.eql(2); + } catch (e: any) { + spy(); + } + expect(spy).not.to.have.been.called(); + }); + + it('load scene with template video', async () => { + const videoUrl = 'https://mdn.alipayobjects.com/huamei_p0cigc/afts/file/A*ZOgXRbmVlsIAAAAAAAAAAAAADoB5AQ'; + const images = [{ + 'id': 'test', + 'template': { + 'width': 126, + 'height': 130, + 'background': { + 'type': spec.BackgroundType.video, + 'name': 'test', + 'url': videoUrl, + }, + }, + 'url': 'https://mdn.alipayobjects.com/mars/afts/img/A*oKwARKdkWhEAAAAAAAAAAAAADlB4AQ/original', + 'webp': 'https://mdn.alipayobjects.com/mars/afts/img/A*eOLVQpT57FcAAAAAAAAAAAAADlB4AQ/original', + 'renderLevel': spec.RenderLevel.BPlus, + }]; + + const composition = await player.loadScene(getJSONWithImages(images)); + const textures = composition.textures; + const videoElement = (textures[0].source as Texture2DSourceOptionsVideo).video; + + expect(textures.length).to.deep.equals(1); + expect(textures[0].source).to.not.be.empty; + expect(videoElement).to.be.an.instanceOf(HTMLVideoElement); + expect(videoElement.src).to.be.equals(videoUrl); + }); + + it('load scene with template video on variables', async () => { + const videoUrl = 'https://gw.alipayobjects.com/v/huamei_p0cigc/afts/video/A*dftzSq2szUsAAAAAAAAAAAAADtN3AQ'; + const images = [{ + 'id': 'test', + 'template': { + 'width': 126, + 'height': 130, + 'background': { + 'type': spec.BackgroundType.video, + 'name': 'test', + 'url': 'https://mdn.alipayobjects.com/huamei_p0cigc/afts/file/A*ZOgXRbmVlsIAAAAAAAAAAAAADoB5AQ', + }, + }, + 'url': 'https://mdn.alipayobjects.com/mars/afts/img/A*oKwARKdkWhEAAAAAAAAAAAAADlB4AQ/original', + 'webp': 'https://mdn.alipayobjects.com/mars/afts/img/A*eOLVQpT57FcAAAAAAAAAAAAADlB4AQ/original', + 'renderLevel': spec.RenderLevel.BPlus, + }]; + + const composition = await player.loadScene(getJSONWithImages(images), { + variables: { + // 视频地址 + test: videoUrl, + }, + }); + const textures = composition.textures; + const videoElement = (textures[0].source as Texture2DSourceOptionsVideo).video; + + expect(videoElement).to.be.an.instanceOf(HTMLVideoElement); + expect(videoElement.src).to.be.equals(videoUrl); + }); + + it('load cube texture', async () => { + const scene = await player.loadScene(cubeTexture1); + const mipmaps = scene.textures[0].taggedProperties.mipmaps as (HTMLImageElement | ImageBitmap)[][]; + + expect(mipmaps.length).to.eql(cubeTexture1.textures[0].mipmaps.length); + expect(mipmaps.every(mipmap => mipmap.every(img => img instanceof HTMLImageElement || img instanceof ImageBitmap))).to.be.true; + }); + + it('load cube textures fail', async () => { + const spy = chai.spy(); + + await player.loadScene(cubeTexture2).catch(e => { + expect(e.message).to.include('Error: Load texture 0 fails'); + spy(); + }); + expect(spy).to.has.been.called.once; }); }); function getJSONWithImageURL (url: string, webp?: string) { return JSON.parse(`{"playerVersion":{"web":"1.3.0","native":"0.0.1.202311221223"},"images":[{"url":"${url}","webp":"${webp ?? url}","renderLevel":"B+"}],"fonts":[],"spines":[],"version":"2.2","shapes":[],"plugins":[],"type":"ge","compositions":[{"id":"2","name":"新建合成2","duration":5,"startTime":0,"endBehavior":1,"previewSize":[750,1624],"items":[{"id":"1","name":"sprite_1","duration":5,"type":"1","visible":true,"endBehavior":0,"delay":0,"renderLevel":"B+","content":{"options":{"startColor":[1,1,1,1]},"renderer":{"renderMode":1,"texture":0},"positionOverLifetime":{"direction":[0,0,0],"startSpeed":0,"gravity":[0,0,0],"gravityOverLifetime":[0,1]},"splits":[[0,0,0.5859375,0.7578125,0]]},"transform":{"position":[0,0,0],"rotation":[0,0,0],"scale":[3.6924,4.7756,1]}}],"camera":{"fov":60,"far":40,"near":0.1,"clipMode":1,"position":[0,0,8],"rotation":[0,0,0]}}],"requires":[],"compositionId":"2","bins":[],"textures":[{"source":0,"flipY":true}]}`); } + +function getJSONWithImages (images: spec.TemplateImage[]) { + return JSON.parse(`{"playerVersion":{"web":"1.2.1","native":"0.0.1.202311221223"},"images":${JSON.stringify(images)},"version":"2.2","type":"ge","compositions":[{"id":"1","name":"新建合成1","duration":5,"startTime":0,"endBehavior":0,"previewSize":[750,1624],"items":[{"id":"1","name":"sprite_1","duration":5,"type":"1","visible":true,"endBehavior":0,"delay":0,"renderLevel":"B+","content":{"options":{"startColor":[0.9529,1,0.0431,1]},"renderer":{"renderMode":1,"texture":0},"positionOverLifetime":{"path":[12,[[[0,0,0,2.3256],[0.43,1,2.3256,3.4483],[0.72,2,3.4483,0]],[[0,0,0],[0,7.79,0],[3.3269,7.79,0]],[[0,1.9475,0],[0,5.8425,0],[0.8317,7.79,0],[2.4952,7.79,0]]]],"direction":[0,0,0],"startSpeed":0,"gravity":[0,0,0],"gravityOverLifetime":[0,1]},"sizeOverLifetime":{"size":[6,[[0.126,1.2055,0,1.6835],[0.72,2.5395,1.6835,0]]]},"colorOverLifetime":{"opacity":[6,[[0,0,0,1.3889],[0.72,1,1.3889,0]]]}},"transform":{"position":[0,0,0],"rotation":[0,0,0],"scale":[1.5492,1.5984,1]}}],"camera":{"fov":60,"far":40,"near":0.1,"clipMode":1,"position":[0,0,8],"rotation":[0,0,0]}}],"requires":[],"compositionId":"1","bins":[],"textures":[{"source":0,"flipY":true}]}`); +} From a16828ede30207cedd89eace52dc38439f1028e5 Mon Sep 17 00:00:00 2001 From: yiiqii Date: Wed, 9 Oct 2024 14:32:26 +0800 Subject: [PATCH 27/88] =?UTF-8?q?style:=20=E7=BB=9F=E4=B8=80=20effectsClas?= =?UTF-8?q?s=20=E6=9E=9A=E4=B8=BE=E5=85=A5=E5=8F=82=E7=B1=BB=E5=9E=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/effects-core/src/binary-asset.ts | 8 ++++---- .../effects-core/src/plugins/cal/calculate-item.ts | 3 ++- .../src/plugins/cal/calculate-vfx-item.ts | 4 ++-- .../effects-core/src/plugins/cal/timeline-asset.ts | 4 ++-- packages/effects-core/src/plugins/index.ts | 1 - .../playables/sub-composition-playable-asset.ts | 5 +++-- packages/effects-core/src/plugins/timeline/track.ts | 12 ++++++------ .../src/plugins/timeline/tracks/activation-track.ts | 5 +++-- .../plugins/timeline/tracks/sprite-color-track.ts | 5 +++-- .../plugins/timeline/tracks/sub-composition-track.ts | 3 ++- .../src/plugins/timeline/tracks/transform-track.ts | 5 +++-- packages/effects-core/src/render/shader.ts | 4 ++-- 12 files changed, 32 insertions(+), 27 deletions(-) diff --git a/packages/effects-core/src/binary-asset.ts b/packages/effects-core/src/binary-asset.ts index 230aa491..935ac401 100644 --- a/packages/effects-core/src/binary-asset.ts +++ b/packages/effects-core/src/binary-asset.ts @@ -1,13 +1,13 @@ -import type { EffectsObjectData } from '@galacean/effects-specification'; +import * as spec from '@galacean/effects-specification'; import { EffectsObject } from './effects-object'; import { effectsClass, serialize } from './decorators'; -@effectsClass('BinaryAsset') +@effectsClass(spec.DataType.BinaryAsset) export class BinaryAsset extends EffectsObject { @serialize() buffer: ArrayBuffer; - override fromData (data: EffectsObjectData): void { + override fromData (data: spec.EffectsObjectData): void { } -} \ No newline at end of file +} diff --git a/packages/effects-core/src/plugins/cal/calculate-item.ts b/packages/effects-core/src/plugins/cal/calculate-item.ts index 54948c85..a6f2f312 100644 --- a/packages/effects-core/src/plugins/cal/calculate-item.ts +++ b/packages/effects-core/src/plugins/cal/calculate-item.ts @@ -1,3 +1,4 @@ +import * as spec from '@galacean/effects-specification'; import type { Euler, Vector3 } from '@galacean/effects-math/es/core/index'; import { effectsClass } from '../../decorators'; import type { ValueGetter } from '../../math'; @@ -28,7 +29,7 @@ export type ItemLinearVelOverLifetime = { /** * @since 2.0.0 */ -@effectsClass('ObjectBindingTrack') +@effectsClass(spec.DataType.ObjectBindingTrack) export class ObjectBindingTrack extends TrackAsset { create (timelineAsset: TimelineAsset): void { diff --git a/packages/effects-core/src/plugins/cal/calculate-vfx-item.ts b/packages/effects-core/src/plugins/cal/calculate-vfx-item.ts index cb4d74f9..56dfb586 100644 --- a/packages/effects-core/src/plugins/cal/calculate-vfx-item.ts +++ b/packages/effects-core/src/plugins/cal/calculate-vfx-item.ts @@ -203,7 +203,7 @@ export class TransformAnimationPlayable extends AnimationPlayable { } } -@effectsClass('TransformPlayableAsset') +@effectsClass(spec.DataType.TransformPlayableAsset) export class TransformPlayableAsset extends PlayableAsset { transformAnimationData: TransformPlayableAssetData; @@ -251,7 +251,7 @@ export class ActivationPlayable extends Playable { } } -@effectsClass('ActivationPlayableAsset') +@effectsClass(spec.DataType.ActivationPlayableAsset) export class ActivationPlayableAsset extends PlayableAsset { override createPlayable (graph: PlayableGraph): Playable { return new ActivationPlayable(graph); diff --git a/packages/effects-core/src/plugins/cal/timeline-asset.ts b/packages/effects-core/src/plugins/cal/timeline-asset.ts index ee09abb9..f306bf62 100644 --- a/packages/effects-core/src/plugins/cal/timeline-asset.ts +++ b/packages/effects-core/src/plugins/cal/timeline-asset.ts @@ -1,4 +1,4 @@ -import type * as spec from '@galacean/effects-specification'; +import * as spec from '@galacean/effects-specification'; import { effectsClass, serialize } from '../../decorators'; import { VFXItem } from '../../vfx-item'; import type { RuntimeClip, TrackAsset } from '../timeline/track'; @@ -7,7 +7,7 @@ import type { FrameContext, PlayableGraph } from './playable-graph'; import { Playable, PlayableAsset, PlayableTraversalMode } from './playable-graph'; import type { Constructor } from '../../utils'; -@effectsClass('TimelineAsset') +@effectsClass(spec.DataType.TimelineAsset) export class TimelineAsset extends PlayableAsset { @serialize() tracks: TrackAsset[] = []; diff --git a/packages/effects-core/src/plugins/index.ts b/packages/effects-core/src/plugins/index.ts index 1f28aa49..7907af4c 100644 --- a/packages/effects-core/src/plugins/index.ts +++ b/packages/effects-core/src/plugins/index.ts @@ -25,5 +25,4 @@ export * from './timeline/tracks/sprite-color-track'; export * from './timeline/tracks/sub-composition-track'; export * from './timeline/playables/sub-composition-playable-asset'; export * from './cal/timeline-asset'; - export * from './text'; diff --git a/packages/effects-core/src/plugins/timeline/playables/sub-composition-playable-asset.ts b/packages/effects-core/src/plugins/timeline/playables/sub-composition-playable-asset.ts index 6e5a5b17..e837de77 100644 --- a/packages/effects-core/src/plugins/timeline/playables/sub-composition-playable-asset.ts +++ b/packages/effects-core/src/plugins/timeline/playables/sub-composition-playable-asset.ts @@ -1,11 +1,12 @@ +import * as spec from '@galacean/effects-specification'; import { effectsClass } from '../../../decorators'; import type { Playable, PlayableGraph } from '../../cal/playable-graph'; import { PlayableAsset } from '../../cal/playable-graph'; import { SubCompositionClipPlayable } from './sub-composition-clip-playable'; -@effectsClass('SubCompositionPlayableAsset') +@effectsClass(spec.DataType.SubCompositionPlayableAsset) export class SubCompositionPlayableAsset extends PlayableAsset { override createPlayable (graph: PlayableGraph): Playable { return new SubCompositionClipPlayable(graph); } -} \ No newline at end of file +} diff --git a/packages/effects-core/src/plugins/timeline/track.ts b/packages/effects-core/src/plugins/timeline/track.ts index 10637579..1ee4cd1e 100644 --- a/packages/effects-core/src/plugins/timeline/track.ts +++ b/packages/effects-core/src/plugins/timeline/track.ts @@ -1,4 +1,4 @@ -import { EndBehavior } from '@galacean/effects-specification'; +import * as spec from '@galacean/effects-specification'; import { effectsClass, serialize } from '../../decorators'; import { VFXItem } from '../../vfx-item'; import type { PlayableGraph } from '../cal/playable-graph'; @@ -15,7 +15,7 @@ export class TimelineClip { start = 0; duration = 0; asset: PlayableAsset; - endBehavior: EndBehavior; + endBehavior: spec.EndBehavior; constructor () { } @@ -25,9 +25,9 @@ export class TimelineClip { const duration = this.duration; if (localTime - duration > 0) { - if (this.endBehavior === EndBehavior.restart) { + if (this.endBehavior === spec.EndBehavior.restart) { localTime = localTime % duration; - } else if (this.endBehavior === EndBehavior.freeze) { + } else if (this.endBehavior === spec.EndBehavior.freeze) { localTime = Math.min(duration, localTime); } } @@ -39,7 +39,7 @@ export class TimelineClip { /** * @since 2.0.0 */ -@effectsClass('TrackAsset') +@effectsClass(spec.DataType.TrackAsset) export class TrackAsset extends PlayableAsset { name: string; binding: object; @@ -196,7 +196,7 @@ export class RuntimeClip { let started = false; const boundObject = this.track.binding; - if (localTime >= clip.start + clip.duration && clip.endBehavior === EndBehavior.destroy) { + if (localTime >= clip.start + clip.duration && clip.endBehavior === spec.EndBehavior.destroy) { if (boundObject instanceof VFXItem && VFXItem.isParticle(boundObject) && this.particleSystem && !this.particleSystem.destroyed) { weight = 1.0; } else { diff --git a/packages/effects-core/src/plugins/timeline/tracks/activation-track.ts b/packages/effects-core/src/plugins/timeline/tracks/activation-track.ts index cbc9524b..ecd19d94 100644 --- a/packages/effects-core/src/plugins/timeline/tracks/activation-track.ts +++ b/packages/effects-core/src/plugins/timeline/tracks/activation-track.ts @@ -1,11 +1,12 @@ +import * as spec from '@galacean/effects-specification'; import { effectsClass } from '../../../decorators'; import type { PlayableGraph, Playable } from '../../cal/playable-graph'; import { ActivationMixerPlayable } from '../playables/activation-mixer-playable'; import { TrackAsset } from '../track'; -@effectsClass('ActivationTrack') +@effectsClass(spec.DataType.ActivationTrack) export class ActivationTrack extends TrackAsset { override createTrackMixer (graph: PlayableGraph): Playable { return new ActivationMixerPlayable(graph); } -} \ No newline at end of file +} diff --git a/packages/effects-core/src/plugins/timeline/tracks/sprite-color-track.ts b/packages/effects-core/src/plugins/timeline/tracks/sprite-color-track.ts index cb638195..86e64c5d 100644 --- a/packages/effects-core/src/plugins/timeline/tracks/sprite-color-track.ts +++ b/packages/effects-core/src/plugins/timeline/tracks/sprite-color-track.ts @@ -1,7 +1,8 @@ +import * as spec from '@galacean/effects-specification'; import { effectsClass } from '../../../decorators'; import { TrackAsset } from '../track'; -@effectsClass('SpriteColorTrack') +@effectsClass(spec.DataType.SpriteColorTrack) export class SpriteColorTrack extends TrackAsset { -} \ No newline at end of file +} diff --git a/packages/effects-core/src/plugins/timeline/tracks/sub-composition-track.ts b/packages/effects-core/src/plugins/timeline/tracks/sub-composition-track.ts index 966d1131..1051586b 100644 --- a/packages/effects-core/src/plugins/timeline/tracks/sub-composition-track.ts +++ b/packages/effects-core/src/plugins/timeline/tracks/sub-composition-track.ts @@ -1,3 +1,4 @@ +import * as spec from '@galacean/effects-specification'; import { VFXItem } from '../../../vfx-item'; import { CompositionComponent } from '../../../comp-vfx-item'; import { TrackAsset } from '../track'; @@ -5,7 +6,7 @@ import { effectsClass } from '../../../decorators'; import type { PlayableGraph, Playable } from '../../cal/playable-graph'; import { SubCompositionMixerPlayable } from '../playables/sub-composition-mixer-playable'; -@effectsClass('SubCompositionTrack') +@effectsClass(spec.DataType.SubCompositionTrack) export class SubCompositionTrack extends TrackAsset { override resolveBinding (parentBinding: object): object { diff --git a/packages/effects-core/src/plugins/timeline/tracks/transform-track.ts b/packages/effects-core/src/plugins/timeline/tracks/transform-track.ts index 2186a6c7..38651f4a 100644 --- a/packages/effects-core/src/plugins/timeline/tracks/transform-track.ts +++ b/packages/effects-core/src/plugins/timeline/tracks/transform-track.ts @@ -1,7 +1,8 @@ +import * as spec from '@galacean/effects-specification'; import { effectsClass } from '../../../decorators'; import { TrackAsset } from '../track'; -@effectsClass('TransformTrack') +@effectsClass(spec.DataType.TransformTrack) export class TransformTrack extends TrackAsset { -} \ No newline at end of file +} diff --git a/packages/effects-core/src/render/shader.ts b/packages/effects-core/src/render/shader.ts index f1d80cf8..af7d6585 100644 --- a/packages/effects-core/src/render/shader.ts +++ b/packages/effects-core/src/render/shader.ts @@ -1,4 +1,4 @@ -import type * as spec from '@galacean/effects-specification'; +import * as spec from '@galacean/effects-specification'; import { effectsClass } from '../decorators'; import { EffectsObject } from '../effects-object'; import type { Engine } from '../engine'; @@ -97,7 +97,7 @@ export abstract class ShaderVariant extends EffectsObject { } } -@effectsClass('Shader') +@effectsClass(spec.DataType.Shader) export class Shader extends EffectsObject { shaderData: spec.ShaderData; From 6e60e32877f32d4fd2e3d44e1a2e10f6a859a10c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=84=8F=E7=BB=AE?= Date: Wed, 9 Oct 2024 16:32:30 +0800 Subject: [PATCH 28/88] =?UTF-8?q?refactor:=20=E7=A7=BB=E9=99=A4=E6=97=A0?= =?UTF-8?q?=E7=94=A8=E7=9A=84=20imgUsage=20=E5=92=8C=20usedImages=20?= =?UTF-8?q?=E9=80=BB=E8=BE=91=20(#672)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * refactor: 移除无用的 imgUsage 和 usedImages 逻辑 * fix: 修复单测数据问题 --- packages/effects-core/src/asset-manager.ts | 44 +------- .../src/composition-source-manager.ts | 65 ++++++------ packages/effects-core/src/composition.ts | 10 +- packages/effects-core/src/scene.ts | 1 - packages/effects-core/src/shape/geometry.ts | 8 +- .../bins/29840fd84400b6d9c353c7fd68795862.bin | Bin .../downgrade/\346\230\245\350\212\261 .png" | Bin .../assets/find-flower/flower.json | 0 .../0aa1a16c1c322c07a97c4ca8a13158ab.webp | Bin .../1514bc9f1cc698dbca055077b9add948.webp | Bin .../2445f46b64fa6078f73b84667d29ee31.png | Bin .../2d95dcb61e63295b52239745d10e8335.png | Bin .../32c2e4b9cbe849bc6c2f326483088919.png | Bin .../32d0ff42fcb9c6393a8f0c728fdb7b01.webp | Bin .../3e7a36e83f8f11dd9bcabc7e9a67f58b.webp | Bin .../53abeccb8b994a6a8f687ae8169fa09b.webp | Bin .../54c88949e7c40c717f1cb4de849b1254.png | Bin .../5aa626d56b3438de0e8f0574e2bccaf7.webp | Bin .../6d0e80e4a52a7bbe752c0c0397ca7b8c.png | Bin .../87269cd72fb97a8f6f9ec3aa26d48a9b.png | Bin .../97e25a8ee94e3afdf2883f5b8cbcdc2d.webp | Bin .../9aefb8d71a0d41f7f1f095871d3f606f.png | Bin .../9eafe210d133af4ff3e37fefce471b0e.webp | Bin .../c2515f3630679cebeb1c6463819c9b84.webp | Bin .../c6f2a6bdada8e8cbbc5409ea25b3173b.png | Bin .../ed39cf3a5793b16bde5c363c50546ed1.png | Bin web-packages/demo/src/local-file.ts | 2 +- web-packages/demo/src/single.ts | 11 +- .../plugins/sprite/sprite-base.spec.ts | 99 +----------------- 29 files changed, 57 insertions(+), 183 deletions(-) rename web-packages/demo/{html => public}/assets/find-flower/bins/29840fd84400b6d9c353c7fd68795862.bin (100%) rename "web-packages/demo/html/assets/find-flower/downgrade/\346\230\245\350\212\261 .png" => "web-packages/demo/public/assets/find-flower/downgrade/\346\230\245\350\212\261 .png" (100%) rename web-packages/demo/{html => public}/assets/find-flower/flower.json (100%) rename web-packages/demo/{html => public}/assets/find-flower/images/0aa1a16c1c322c07a97c4ca8a13158ab.webp (100%) rename web-packages/demo/{html => public}/assets/find-flower/images/1514bc9f1cc698dbca055077b9add948.webp (100%) rename web-packages/demo/{html => public}/assets/find-flower/images/2445f46b64fa6078f73b84667d29ee31.png (100%) rename web-packages/demo/{html => public}/assets/find-flower/images/2d95dcb61e63295b52239745d10e8335.png (100%) rename web-packages/demo/{html => public}/assets/find-flower/images/32c2e4b9cbe849bc6c2f326483088919.png (100%) rename web-packages/demo/{html => public}/assets/find-flower/images/32d0ff42fcb9c6393a8f0c728fdb7b01.webp (100%) rename web-packages/demo/{html => public}/assets/find-flower/images/3e7a36e83f8f11dd9bcabc7e9a67f58b.webp (100%) rename web-packages/demo/{html => public}/assets/find-flower/images/53abeccb8b994a6a8f687ae8169fa09b.webp (100%) rename web-packages/demo/{html => public}/assets/find-flower/images/54c88949e7c40c717f1cb4de849b1254.png (100%) rename web-packages/demo/{html => public}/assets/find-flower/images/5aa626d56b3438de0e8f0574e2bccaf7.webp (100%) rename web-packages/demo/{html => public}/assets/find-flower/images/6d0e80e4a52a7bbe752c0c0397ca7b8c.png (100%) rename web-packages/demo/{html => public}/assets/find-flower/images/87269cd72fb97a8f6f9ec3aa26d48a9b.png (100%) rename web-packages/demo/{html => public}/assets/find-flower/images/97e25a8ee94e3afdf2883f5b8cbcdc2d.webp (100%) rename web-packages/demo/{html => public}/assets/find-flower/images/9aefb8d71a0d41f7f1f095871d3f606f.png (100%) rename web-packages/demo/{html => public}/assets/find-flower/images/9eafe210d133af4ff3e37fefce471b0e.webp (100%) rename web-packages/demo/{html => public}/assets/find-flower/images/c2515f3630679cebeb1c6463819c9b84.webp (100%) rename web-packages/demo/{html => public}/assets/find-flower/images/c6f2a6bdada8e8cbbc5409ea25b3173b.png (100%) rename web-packages/demo/{html => public}/assets/find-flower/images/ed39cf3a5793b16bde5c363c50546ed1.png (100%) diff --git a/packages/effects-core/src/asset-manager.ts b/packages/effects-core/src/asset-manager.ts index cb3163a0..e5918730 100644 --- a/packages/effects-core/src/asset-manager.ts +++ b/packages/effects-core/src/asset-manager.ts @@ -6,7 +6,7 @@ import type { PrecompileOptions } from './plugin-system'; import { PluginSystem } from './plugin-system'; import type { JSONValue } from './downloader'; import { Downloader, loadWebPOptional, loadImage, loadVideo, loadMedia, loadAVIFOptional } from './downloader'; -import type { ImageLike, SceneLoadOptions, SceneRenderLevel } from './scene'; +import type { ImageLike, SceneLoadOptions } from './scene'; import { Scene } from './scene'; import type { Disposable } from './utils'; import { isObject, isString, logger, isValidFontFamily, isCanvas, base64ToFile } from './utils'; @@ -168,7 +168,7 @@ export class AssetManager implements Disposable { } } else { // TODO: JSONScene 中 bins 的类型可能为 ArrayBuffer[] - const { usedImages, jsonScene, pluginSystem } = await hookTimeInfo('processJSON', () => this.processJSON(rawJSON as JSONValue)); + const { jsonScene, pluginSystem } = await hookTimeInfo('processJSON', () => this.processJSON(rawJSON as JSONValue)); const { bins = [], images, compositions, fonts } = jsonScene; const [loadedBins, loadedImages] = await Promise.all([ @@ -201,7 +201,6 @@ export class AssetManager implements Disposable { storage: {}, pluginSystem, jsonScene, - usedImages, images: loadedImages, textureOptions: loadedTextures, bins: loadedBins, @@ -241,25 +240,12 @@ export class AssetManager implements Disposable { private async processJSON (json: JSONValue) { const jsonScene = getStandardJSON(json); - const { plugins = [], compositions: sceneCompositions, imgUsage, images } = jsonScene; + const { plugins = [] } = jsonScene; const pluginSystem = new PluginSystem(plugins); await pluginSystem.processRawJSON(jsonScene, this.options); - const { renderLevel } = this.options; - const usedImages: Record = {}; - - if (imgUsage) { - // TODO: 考虑放到独立的 fix 文件 - fixOldImageUsage(usedImages, sceneCompositions, imgUsage, images, renderLevel); - } else { - images?.forEach((_, i) => { - usedImages[i] = true; - }); - } - return { - usedImages, jsonScene, pluginSystem, }; @@ -483,30 +469,6 @@ export class AssetManager implements Disposable { } } -function fixOldImageUsage ( - usedImages: Record, - compositions: spec.CompositionData[], - imgUsage: Record, - images: any, - renderLevel?: SceneRenderLevel, -) { - for (let i = 0; i < compositions.length; i++) { - const id = compositions[i].id; - const ids = imgUsage[id]; - - if (ids) { - for (let j = 0; j < ids.length; j++) { - const id = ids[j]; - const tag = images[id].renderLevel; - - if (passRenderLevel(tag, renderLevel)) { - usedImages[id] = true; - } - } - } - } -} - function createTextureOptionsBySource ( image: TextureSourceOptions | ImageLike, sourceFrom: AssetsType, diff --git a/packages/effects-core/src/composition-source-manager.ts b/packages/effects-core/src/composition-source-manager.ts index 69030bcb..f53e62df 100644 --- a/packages/effects-core/src/composition-source-manager.ts +++ b/packages/effects-core/src/composition-source-manager.ts @@ -4,15 +4,17 @@ import type { Engine } from './engine'; import { passRenderLevel } from './pass-render-level'; import type { PluginSystem } from './plugin-system'; import type { Scene, SceneRenderLevel } from './scene'; -import type { ShapeData } from './shape'; import { getGeometryByShape } from './shape'; import type { Texture } from './texture'; import type { Disposable } from './utils'; -import { isObject } from './utils'; import type { VFXItemProps } from './vfx-item'; let listOrder = 0; +interface RendererOptionsWithMask extends spec.RendererOptions { + mask?: number, +} + export interface ContentOptions { id: string, duration: number, @@ -36,7 +38,7 @@ export class CompositionSourceManager implements Disposable { renderLevel?: SceneRenderLevel; pluginSystem?: PluginSystem; totalTime: number; - imgUsage: Record; + imgUsage: Record = {}; textures: Texture[]; jsonScene?: spec.JSONScene; mask = 0; @@ -49,7 +51,7 @@ export class CompositionSourceManager implements Disposable { this.engine = engine; // 资源 const { jsonScene, renderLevel, textureOptions, pluginSystem, totalTime } = scene; - const { compositions, imgUsage, compositionId } = jsonScene; + const { compositions, compositionId } = jsonScene; if (!textureOptions) { throw new Error('scene.textures expected.'); @@ -71,7 +73,6 @@ export class CompositionSourceManager implements Disposable { this.renderLevel = renderLevel; this.pluginSystem = pluginSystem; this.totalTime = totalTime ?? 0; - this.imgUsage = imgUsage ?? {}; this.textures = cachedTextures; listOrder = 0; this.sourceContent = this.getContent(this.composition); @@ -95,13 +96,12 @@ export class CompositionSourceManager implements Disposable { } private assembleItems (composition: spec.CompositionData) { - const items: any[] = []; + const items: VFXItemProps[] = []; + const componentMap: Record = {}; this.mask++; - const componentMap: Record = {}; - //@ts-expect-error - for (const component of this.jsonScene.components) { + for (const component of this.jsonScene?.components ?? []) { componentMap[component.id] = component; } @@ -118,7 +118,7 @@ export class CompositionSourceManager implements Disposable { itemProps.type === spec.ItemType.particle ) { for (const componentPath of itemProps.components) { - const componentData = componentMap[componentPath.id]; + const componentData = componentMap[componentPath.id] as spec.SpriteComponentData | spec.ParticleSystemData; this.preProcessItemContent(componentData); } @@ -145,52 +145,55 @@ export class CompositionSourceManager implements Disposable { return items; } - private preProcessItemContent (renderContent: any) { + private preProcessItemContent ( + renderContent: spec.SpriteComponentData | spec.ParticleSystemData | spec.ParticleContent, + ) { if (renderContent.renderer) { renderContent.renderer = this.changeTex(renderContent.renderer); - if (!renderContent.renderer.mask) { + if (!('mask' in renderContent.renderer)) { this.processMask(renderContent.renderer); } - const split = renderContent.splits && !renderContent.textureSheetAnimation && renderContent.splits[0]; + const split = renderContent.splits && !renderContent.textureSheetAnimation ? renderContent.splits[0] : undefined; + const shape = renderContent.renderer.shape; + let shapeData; - if (Number.isInteger(renderContent.renderer.shape)) { - // TODO: scene.shapes 类型问题? - renderContent.renderer.shape = getGeometryByShape(this.jsonScene?.shapes[renderContent.renderer.shape] as unknown as ShapeData, split); - } else if (renderContent.renderer.shape && isObject(renderContent.renderer.shape)) { - renderContent.renderer.shape = getGeometryByShape(renderContent.renderer.shape, split); + if (Number.isInteger(shape)) { + shapeData = this.jsonScene?.shapes[shape as number]; + } else { + shapeData = shape as spec.ShapeGeometry; + } + + if (shapeData !== undefined) { + // @ts-expect-error 类型转换问题 + renderContent.renderer.shape = getGeometryByShape(shapeData, split); } } - if (renderContent.trails) { + if ('trails' in renderContent && renderContent.trails !== undefined) { renderContent.trails = this.changeTex(renderContent.trails); } } - private changeTex (renderer: Record) { + private changeTex (renderer: T) { if (!renderer.texture) { return renderer; } - //@ts-expect-error const texIdx = renderer.texture.id; if (texIdx !== undefined) { - //@ts-expect-error - this.addTextureUsage(texIdx) || texIdx; + this.addTextureUsage(texIdx); } return renderer; } - private addTextureUsage (texIdx: number) { - const texId = texIdx; - // FIXME: imageUsage 取自 scene.imgUsage,类型为 Record,这里给的 number,类型对不上 - const imageUsage = this.imgUsage as unknown as Record ?? {}; + private addTextureUsage (texId: string) { + const imageUsage = this.imgUsage ?? {}; if (texId && imageUsage) { - // eslint-disable-next-line no-prototype-builtins - if (!imageUsage.hasOwnProperty(texId)) { + if (!Object.prototype.hasOwnProperty.call(imageUsage, texId)) { imageUsage[texId] = 0; } imageUsage[texId]++; @@ -200,8 +203,8 @@ export class CompositionSourceManager implements Disposable { /** * 处理蒙版和遮挡关系写入 stencil 的 ref 值 */ - private processMask (renderer: Record) { - const maskMode: spec.MaskMode = renderer.maskMode; + private processMask (renderer: RendererOptionsWithMask) { + const maskMode = renderer.maskMode; if (maskMode === spec.MaskMode.NONE) { return; diff --git a/packages/effects-core/src/composition.ts b/packages/effects-core/src/composition.ts index a9af4f2d..bbbb8678 100644 --- a/packages/effects-core/src/composition.ts +++ b/packages/effects-core/src/composition.ts @@ -194,8 +194,6 @@ export class Composition extends EventEmitter> imp */ private paused = false; private lastVideoUpdateTime = 0; - // private readonly event: EventSystem; - // texInfo的类型有点不明确,改成不会提前删除texture private readonly texInfo: Record; /** * 合成中消息元素创建/销毁时触发的回调 @@ -223,7 +221,7 @@ export class Composition extends EventEmitter> imp } = props; this.compositionSourceManager = new CompositionSourceManager(scene, renderer.engine); - scene.jsonScene.imgUsage = undefined; + if (reusable) { this.keepResource = true; scene.textures = undefined; @@ -246,14 +244,12 @@ export class Composition extends EventEmitter> imp this.rootComposition.item = this.rootItem; this.rootItem.components.push(this.rootComposition); - const imageUsage = (!reusable && imgUsage) as unknown as Record; - this.width = width; this.height = height; this.renderOrder = baseRenderOrder; this.id = sourceContent.id; this.renderer = renderer; - this.texInfo = imageUsage ?? {}; + this.texInfo = !reusable ? imgUsage : {}; this.event = event; this.statistic = { loadStart: scene.startTime ?? 0, @@ -263,7 +259,7 @@ export class Composition extends EventEmitter> imp }; this.reusable = reusable; this.speed = speed; - this.autoRefTex = !this.keepResource && imageUsage && this.rootItem.endBehavior !== spec.EndBehavior.restart; + this.autoRefTex = !this.keepResource && this.texInfo && this.rootItem.endBehavior !== spec.EndBehavior.restart; this.name = sourceContent.name; this.pluginSystem = pluginSystem as PluginSystem; this.pluginSystem.initializeComposition(this, scene); diff --git a/packages/effects-core/src/scene.ts b/packages/effects-core/src/scene.ts index 117eb2a6..ff0c033f 100644 --- a/packages/effects-core/src/scene.ts +++ b/packages/effects-core/src/scene.ts @@ -34,7 +34,6 @@ export interface Scene { */ timeInfos: Record, url: Scene.LoadType, - usedImages: Record, } export namespace Scene { diff --git a/packages/effects-core/src/shape/geometry.ts b/packages/effects-core/src/shape/geometry.ts index 73441f9b..146ca6a1 100644 --- a/packages/effects-core/src/shape/geometry.ts +++ b/packages/effects-core/src/shape/geometry.ts @@ -23,7 +23,7 @@ export type GeometryFromShape = { }; type ShapeGeometryPre = { p: spec.ShapePoints[1], s: spec.ShapeSplits[1] }; // FIXME: 考虑合并 Shape2D -export type ShapeData = { gs: ShapeGeometryPre[] } & { g: ShapeGeometryPre } & spec.ShapeGeometry; +export type ShapeData = { gs: ShapeGeometryPre[] } | { g: ShapeGeometryPre } | spec.ShapeGeometry; const POINT_INDEX = 2; @@ -99,14 +99,14 @@ function getGeometriesByShapeData (shape: ShapeData) { const geometries: spec.ShapeGeometry[] = []; // 该版本的单个形状数据可以包含多个形状,可以加个埋点,五福之后没有就可以下掉 - if (shape.gs) { + if ('gs' in shape) { shape.gs.forEach(gs => { geometries.push({ p: [spec.ValueType.SHAPE_POINTS, gs.p], s: [spec.ValueType.SHAPE_SPLITS, gs.s], }); }); - } else if (shape.g) { + } else if ('g' in shape) { geometries.push({ p: [spec.ValueType.SHAPE_POINTS, shape.g.p], s: [spec.ValueType.SHAPE_SPLITS, shape.g.s], @@ -118,7 +118,7 @@ function getGeometriesByShapeData (shape: ShapeData) { return geometries; } -export function getGeometryByShape (shape: ShapeData, uvTransform: number[]): GeometryFromShape { +export function getGeometryByShape (shape: ShapeData, uvTransform?: number[]): GeometryFromShape { const datas = []; // 老数据兼容处理 const geometries = getGeometriesByShapeData(shape); diff --git a/web-packages/demo/html/assets/find-flower/bins/29840fd84400b6d9c353c7fd68795862.bin b/web-packages/demo/public/assets/find-flower/bins/29840fd84400b6d9c353c7fd68795862.bin similarity index 100% rename from web-packages/demo/html/assets/find-flower/bins/29840fd84400b6d9c353c7fd68795862.bin rename to web-packages/demo/public/assets/find-flower/bins/29840fd84400b6d9c353c7fd68795862.bin diff --git "a/web-packages/demo/html/assets/find-flower/downgrade/\346\230\245\350\212\261 .png" "b/web-packages/demo/public/assets/find-flower/downgrade/\346\230\245\350\212\261 .png" similarity index 100% rename from "web-packages/demo/html/assets/find-flower/downgrade/\346\230\245\350\212\261 .png" rename to "web-packages/demo/public/assets/find-flower/downgrade/\346\230\245\350\212\261 .png" diff --git a/web-packages/demo/html/assets/find-flower/flower.json b/web-packages/demo/public/assets/find-flower/flower.json similarity index 100% rename from web-packages/demo/html/assets/find-flower/flower.json rename to web-packages/demo/public/assets/find-flower/flower.json diff --git a/web-packages/demo/html/assets/find-flower/images/0aa1a16c1c322c07a97c4ca8a13158ab.webp b/web-packages/demo/public/assets/find-flower/images/0aa1a16c1c322c07a97c4ca8a13158ab.webp similarity index 100% rename from web-packages/demo/html/assets/find-flower/images/0aa1a16c1c322c07a97c4ca8a13158ab.webp rename to web-packages/demo/public/assets/find-flower/images/0aa1a16c1c322c07a97c4ca8a13158ab.webp diff --git a/web-packages/demo/html/assets/find-flower/images/1514bc9f1cc698dbca055077b9add948.webp b/web-packages/demo/public/assets/find-flower/images/1514bc9f1cc698dbca055077b9add948.webp similarity index 100% rename from web-packages/demo/html/assets/find-flower/images/1514bc9f1cc698dbca055077b9add948.webp rename to web-packages/demo/public/assets/find-flower/images/1514bc9f1cc698dbca055077b9add948.webp diff --git a/web-packages/demo/html/assets/find-flower/images/2445f46b64fa6078f73b84667d29ee31.png b/web-packages/demo/public/assets/find-flower/images/2445f46b64fa6078f73b84667d29ee31.png similarity index 100% rename from web-packages/demo/html/assets/find-flower/images/2445f46b64fa6078f73b84667d29ee31.png rename to web-packages/demo/public/assets/find-flower/images/2445f46b64fa6078f73b84667d29ee31.png diff --git a/web-packages/demo/html/assets/find-flower/images/2d95dcb61e63295b52239745d10e8335.png b/web-packages/demo/public/assets/find-flower/images/2d95dcb61e63295b52239745d10e8335.png similarity index 100% rename from web-packages/demo/html/assets/find-flower/images/2d95dcb61e63295b52239745d10e8335.png rename to web-packages/demo/public/assets/find-flower/images/2d95dcb61e63295b52239745d10e8335.png diff --git a/web-packages/demo/html/assets/find-flower/images/32c2e4b9cbe849bc6c2f326483088919.png b/web-packages/demo/public/assets/find-flower/images/32c2e4b9cbe849bc6c2f326483088919.png similarity index 100% rename from web-packages/demo/html/assets/find-flower/images/32c2e4b9cbe849bc6c2f326483088919.png rename to web-packages/demo/public/assets/find-flower/images/32c2e4b9cbe849bc6c2f326483088919.png diff --git a/web-packages/demo/html/assets/find-flower/images/32d0ff42fcb9c6393a8f0c728fdb7b01.webp b/web-packages/demo/public/assets/find-flower/images/32d0ff42fcb9c6393a8f0c728fdb7b01.webp similarity index 100% rename from web-packages/demo/html/assets/find-flower/images/32d0ff42fcb9c6393a8f0c728fdb7b01.webp rename to web-packages/demo/public/assets/find-flower/images/32d0ff42fcb9c6393a8f0c728fdb7b01.webp diff --git a/web-packages/demo/html/assets/find-flower/images/3e7a36e83f8f11dd9bcabc7e9a67f58b.webp b/web-packages/demo/public/assets/find-flower/images/3e7a36e83f8f11dd9bcabc7e9a67f58b.webp similarity index 100% rename from web-packages/demo/html/assets/find-flower/images/3e7a36e83f8f11dd9bcabc7e9a67f58b.webp rename to web-packages/demo/public/assets/find-flower/images/3e7a36e83f8f11dd9bcabc7e9a67f58b.webp diff --git a/web-packages/demo/html/assets/find-flower/images/53abeccb8b994a6a8f687ae8169fa09b.webp b/web-packages/demo/public/assets/find-flower/images/53abeccb8b994a6a8f687ae8169fa09b.webp similarity index 100% rename from web-packages/demo/html/assets/find-flower/images/53abeccb8b994a6a8f687ae8169fa09b.webp rename to web-packages/demo/public/assets/find-flower/images/53abeccb8b994a6a8f687ae8169fa09b.webp diff --git a/web-packages/demo/html/assets/find-flower/images/54c88949e7c40c717f1cb4de849b1254.png b/web-packages/demo/public/assets/find-flower/images/54c88949e7c40c717f1cb4de849b1254.png similarity index 100% rename from web-packages/demo/html/assets/find-flower/images/54c88949e7c40c717f1cb4de849b1254.png rename to web-packages/demo/public/assets/find-flower/images/54c88949e7c40c717f1cb4de849b1254.png diff --git a/web-packages/demo/html/assets/find-flower/images/5aa626d56b3438de0e8f0574e2bccaf7.webp b/web-packages/demo/public/assets/find-flower/images/5aa626d56b3438de0e8f0574e2bccaf7.webp similarity index 100% rename from web-packages/demo/html/assets/find-flower/images/5aa626d56b3438de0e8f0574e2bccaf7.webp rename to web-packages/demo/public/assets/find-flower/images/5aa626d56b3438de0e8f0574e2bccaf7.webp diff --git a/web-packages/demo/html/assets/find-flower/images/6d0e80e4a52a7bbe752c0c0397ca7b8c.png b/web-packages/demo/public/assets/find-flower/images/6d0e80e4a52a7bbe752c0c0397ca7b8c.png similarity index 100% rename from web-packages/demo/html/assets/find-flower/images/6d0e80e4a52a7bbe752c0c0397ca7b8c.png rename to web-packages/demo/public/assets/find-flower/images/6d0e80e4a52a7bbe752c0c0397ca7b8c.png diff --git a/web-packages/demo/html/assets/find-flower/images/87269cd72fb97a8f6f9ec3aa26d48a9b.png b/web-packages/demo/public/assets/find-flower/images/87269cd72fb97a8f6f9ec3aa26d48a9b.png similarity index 100% rename from web-packages/demo/html/assets/find-flower/images/87269cd72fb97a8f6f9ec3aa26d48a9b.png rename to web-packages/demo/public/assets/find-flower/images/87269cd72fb97a8f6f9ec3aa26d48a9b.png diff --git a/web-packages/demo/html/assets/find-flower/images/97e25a8ee94e3afdf2883f5b8cbcdc2d.webp b/web-packages/demo/public/assets/find-flower/images/97e25a8ee94e3afdf2883f5b8cbcdc2d.webp similarity index 100% rename from web-packages/demo/html/assets/find-flower/images/97e25a8ee94e3afdf2883f5b8cbcdc2d.webp rename to web-packages/demo/public/assets/find-flower/images/97e25a8ee94e3afdf2883f5b8cbcdc2d.webp diff --git a/web-packages/demo/html/assets/find-flower/images/9aefb8d71a0d41f7f1f095871d3f606f.png b/web-packages/demo/public/assets/find-flower/images/9aefb8d71a0d41f7f1f095871d3f606f.png similarity index 100% rename from web-packages/demo/html/assets/find-flower/images/9aefb8d71a0d41f7f1f095871d3f606f.png rename to web-packages/demo/public/assets/find-flower/images/9aefb8d71a0d41f7f1f095871d3f606f.png diff --git a/web-packages/demo/html/assets/find-flower/images/9eafe210d133af4ff3e37fefce471b0e.webp b/web-packages/demo/public/assets/find-flower/images/9eafe210d133af4ff3e37fefce471b0e.webp similarity index 100% rename from web-packages/demo/html/assets/find-flower/images/9eafe210d133af4ff3e37fefce471b0e.webp rename to web-packages/demo/public/assets/find-flower/images/9eafe210d133af4ff3e37fefce471b0e.webp diff --git a/web-packages/demo/html/assets/find-flower/images/c2515f3630679cebeb1c6463819c9b84.webp b/web-packages/demo/public/assets/find-flower/images/c2515f3630679cebeb1c6463819c9b84.webp similarity index 100% rename from web-packages/demo/html/assets/find-flower/images/c2515f3630679cebeb1c6463819c9b84.webp rename to web-packages/demo/public/assets/find-flower/images/c2515f3630679cebeb1c6463819c9b84.webp diff --git a/web-packages/demo/html/assets/find-flower/images/c6f2a6bdada8e8cbbc5409ea25b3173b.png b/web-packages/demo/public/assets/find-flower/images/c6f2a6bdada8e8cbbc5409ea25b3173b.png similarity index 100% rename from web-packages/demo/html/assets/find-flower/images/c6f2a6bdada8e8cbbc5409ea25b3173b.png rename to web-packages/demo/public/assets/find-flower/images/c6f2a6bdada8e8cbbc5409ea25b3173b.png diff --git a/web-packages/demo/html/assets/find-flower/images/ed39cf3a5793b16bde5c363c50546ed1.png b/web-packages/demo/public/assets/find-flower/images/ed39cf3a5793b16bde5c363c50546ed1.png similarity index 100% rename from web-packages/demo/html/assets/find-flower/images/ed39cf3a5793b16bde5c363c50546ed1.png rename to web-packages/demo/public/assets/find-flower/images/ed39cf3a5793b16bde5c363c50546ed1.png diff --git a/web-packages/demo/src/local-file.ts b/web-packages/demo/src/local-file.ts index d90a67f8..3d3b3044 100644 --- a/web-packages/demo/src/local-file.ts +++ b/web-packages/demo/src/local-file.ts @@ -6,7 +6,7 @@ const container = document.getElementById('J-container'); (async () => { try { const player = new Player({ container }); - const composition = await player.loadScene('./assets/find-flower/flower.json'); + const composition = await player.loadScene('/assets/find-flower/flower.json'); setTimeout(() => { composition.setSpeed(-1); diff --git a/web-packages/demo/src/single.ts b/web-packages/demo/src/single.ts index df11e0f5..595b0d0f 100644 --- a/web-packages/demo/src/single.ts +++ b/web-packages/demo/src/single.ts @@ -1,7 +1,16 @@ import { Player } from '@galacean/effects'; import '@galacean/effects-plugin-spine'; -const json = 'https://mdn.alipayobjects.com/mars/afts/file/A*WXEqSadfOKcAAAAAAAAAAAAADlB4AQ'; +// const json = 'https://mdn.alipayobjects.com/mars/afts/file/A*js8iRbOiExAAAAAAAAAAAAAADlB4AQ'; +// const json = 'https://gw.alipayobjects.com/os/gltf-asset/mars-cli/YDITHDADWXXM/1601633123-e644d.json'; +// 蒙版 +// const json = 'https://gw.alipayobjects.com/os/gltf-asset/mars-cli/HCQBCOWGHRQC/273965510-c5c29.json'; +// 蒙版新数据 +// const json = 'https://mdn.alipayobjects.com/mars/afts/file/A*36ybTZJI4JEAAAAAAAAAAAAADlB4AQ'; +// 普通拖尾 +// const json = 'https://gw.alipayobjects.com/os/gltf-asset/mars-cli/RYYAXEAYMIYJ/1314733612-96c0b.json'; +// 图贴拖尾 +const json = 'https://mdn.alipayobjects.com/mars/afts/file/A*VRedS5UU8DAAAAAAAAAAAAAADlB4AQ'; const container = document.getElementById('J-container'); (async () => { diff --git a/web-packages/test/unit/src/effects-core/plugins/sprite/sprite-base.spec.ts b/web-packages/test/unit/src/effects-core/plugins/sprite/sprite-base.spec.ts index ff61cbf1..d69851df 100644 --- a/web-packages/test/unit/src/effects-core/plugins/sprite/sprite-base.spec.ts +++ b/web-packages/test/unit/src/effects-core/plugins/sprite/sprite-base.spec.ts @@ -72,103 +72,8 @@ describe('core/plugins/sprite/item-base', () => { // 尺寸随时间变换 it('sprite sizeOverLifetime', async () => { - const items = [ - { - 'id': '5', - 'name': 'item', - 'duration': 5, - 'type': '1', - 'visible': true, - 'endBehavior': 0, - 'delay': 0, - 'renderLevel': 'B+', - 'content': { - 'options': { - 'startColor': [ - 1, - 1, - 1, - 1, - ], - }, - 'renderer': { - 'renderMode': 1, - 'texture': 0, - }, - 'positionOverLifetime': { - 'direction': [ - 0, - 0, - 0, - ], - 'startSpeed': 0, - 'gravity': [ - 0, - 0, - 0, - ], - 'gravityOverLifetime': [ - 0, - 1, - ], - }, - 'sizeOverLifetime': { - 'size': [ - 21, - [ - [ - 4, - [ - 0, - 1, - ], - ], - ], - ], - 'separateAxes': true, - 'x': [ - 21, - [ - [ - 4, - [ - 0, - 2, - ], - ], - ], - ], - }, - 'splits': [ - [ - 0, - 0, - 1, - 1, - 0, - ], - ], - }, - 'transform': { - 'position': [ - 0, - 0, - 0, - ], - 'rotation': [ - 0, - 0, - 0, - ], - 'scale': [ - 12.5965, - 12.5965, - 1, - ], - }, - }, - ] as spec.Item[]; - const comp = await player.loadScene(generateSceneJSON(items)); + const items = '[{"id":"5","name":"item","duration":5,"type":"1","visible":true,"endBehavior":0,"delay":0,"renderLevel":"B+","content":{"options":{"startColor":[1,1,1,1]},"renderer":{"renderMode":1,"texture":0},"positionOverLifetime":{"direction":[0,0,0],"startSpeed":0,"gravity":[0,0,0],"gravityOverLifetime":[0,1]},"sizeOverLifetime":{"size":[21,[[4,[0,1]]]],"separateAxes":true,"x":[21,[[4,[0,2]]]]},"splits":[[0,0,1,1,0]]},"transform":{"position":[0,0,0],"rotation":[0,0,0],"scale":[12.5965,12.5965,1]}}]'; + const comp = await player.loadScene(generateSceneJSON(JSON.parse(items))); player.gotoAndPlay(0.01); const spriteItem = comp.getItemByName('item')?.getComponent(SpriteComponent); From 6760601810288fd0f3b8613833c9c2137c24b470 Mon Sep 17 00:00:00 2001 From: yiiqii Date: Wed, 9 Oct 2024 16:37:46 +0800 Subject: [PATCH 29/88] =?UTF-8?q?chore:=20=E7=A7=BB=E9=99=A4=20loadScene?= =?UTF-8?q?=20=E4=B8=BA=20scene=20object=20=E6=97=B6=E7=9A=84=E5=8A=A8?= =?UTF-8?q?=E6=80=81=E6=95=B0=E6=8D=AE=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/effects-core/src/asset-manager.ts | 29 ++-------------------- packages/effects-core/src/downloader.ts | 9 +++---- tsconfig.base.json | 1 + web-packages/demo/src/dynamic-video.ts | 3 +-- 4 files changed, 7 insertions(+), 35 deletions(-) diff --git a/packages/effects-core/src/asset-manager.ts b/packages/effects-core/src/asset-manager.ts index e5918730..ab0ca01a 100644 --- a/packages/effects-core/src/asset-manager.ts +++ b/packages/effects-core/src/asset-manager.ts @@ -144,28 +144,6 @@ export class AssetManager implements Disposable { scene = { ...rawJSON, }; - - if ( - this.options && - this.options.variables && - Object.keys(this.options.variables).length !== 0 - ) { - const { images: rawImages } = rawJSON.jsonScene; - const images = scene.images; - const newImages: spec.ImageSource[] = []; - - for (let i = 0; i < rawImages.length; i++) { - // 仅重新加载数据模板对应的图片 - if (images[i] instanceof HTMLCanvasElement) { - newImages[i] = rawImages[i]; - } - } - scene.images = await hookTimeInfo('processImages', () => this.processImages(newImages, compressedTexture)); - // 更新 TextureOptions 中的 image 指向 - for (let i = 0; i < scene.images.length; i++) { - scene.textureOptions[i].image = scene.images[i]; - } - } } else { // TODO: JSONScene 中 bins 的类型可能为 ArrayBuffer[] const { jsonScene, pluginSystem } = await hookTimeInfo('processJSON', () => this.processJSON(rawJSON as JSONValue)); @@ -286,11 +264,9 @@ export class AssetManager implements Disposable { const fontFace = new FontFace(font.fontFamily ?? '', 'url(' + url + ')'); await fontFace.load(); - //@ts-expect-error document.fonts.add(fontFace); AssetManager.fonts.add(font.fontFamily); - // eslint-disable-next-line @typescript-eslint/no-unused-vars - } catch (e) { + } catch (_) { logger.warn(`Invalid font family or font source: ${JSON.stringify(font.fontURL)}.`); } } @@ -348,9 +324,8 @@ export class AssetManager implements Disposable { variables, ); } - // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (e) { - throw new Error(`Failed to load. Check the template or if the URL is ${isVideo ? 'video' : 'image'} type, URL: ${url}, Error: ${(e as any).message || e}.`); + throw new Error(`Failed to load. Check the template or if the URL is ${isVideo ? 'video' : 'image'} type, URL: ${url}, Error: ${(e as Error).message || e}.`); } } } else if ('compressed' in img && useCompressedTexture && compressedTexture) { diff --git a/packages/effects-core/src/downloader.ts b/packages/effects-core/src/downloader.ts index 8aefc931..cbf28a82 100644 --- a/packages/effects-core/src/downloader.ts +++ b/packages/effects-core/src/downloader.ts @@ -129,8 +129,7 @@ export async function loadWebPOptional (png: string, webp?: string) { const image = await loadImage(webp); return { image, url: webp }; - // eslint-disable-next-line @typescript-eslint/no-unused-vars - } catch (e: any) { + } catch (_) { webPFailed = true; const image = await loadImage(png); @@ -154,8 +153,7 @@ export async function loadAVIFOptional (png: string, avif?: string) { const image = await loadImage(avif); return { image, url: avif }; - // eslint-disable-next-line @typescript-eslint/no-unused-vars - } catch (e: any) { + } catch (_) { avifFailed = true; const image = await loadImage(png); @@ -288,8 +286,7 @@ export async function loadMedia (url: string | string[], loadFn: (url: string) = if (Array.isArray(url)) { try { return await loadFn(url[0]); - // eslint-disable-next-line @typescript-eslint/no-unused-vars - } catch (e: any) { + } catch (_) { return await loadFn(url[1]); } } diff --git a/tsconfig.base.json b/tsconfig.base.json index e5c4b870..94cae481 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -23,6 +23,7 @@ "target": "ESNext", "lib": [ "DOM", + "DOM.Iterable", "ES2015", "ESNext" ], diff --git a/web-packages/demo/src/dynamic-video.ts b/web-packages/demo/src/dynamic-video.ts index 6bad2a54..ecc0a869 100644 --- a/web-packages/demo/src/dynamic-video.ts +++ b/web-packages/demo/src/dynamic-video.ts @@ -8,10 +8,9 @@ const container = document.getElementById('J-container'); const player = new Player({ container, }); - const composition = await player.loadScene(json, { variables: { - video: 'https://gw.alipayobjects.com/v/huamei_p0cigc/afts/video/A*7gPzSo3RxlQAAAAAAAAAAAAADtN3AQ', + video: 'https://mdn.alipayobjects.com/huamei_p0cigc/afts/file/A*ZOgXRbmVlsIAAAAAAAAAAAAADoB5AQ', text_3: 'Dynamic Video', }, }); From abf963ed10deece12385e91c2b439e1d2736a832 Mon Sep 17 00:00:00 2001 From: liuxi150 Date: Thu, 10 Oct 2024 15:06:23 +0800 Subject: [PATCH 30/88] refactor: unify item parent setup --- packages/effects-core/src/composition.ts | 19 +++---------------- 1 file changed, 3 insertions(+), 16 deletions(-) diff --git a/packages/effects-core/src/composition.ts b/packages/effects-core/src/composition.ts index a9af4f2d..0692ae5e 100644 --- a/packages/effects-core/src/composition.ts +++ b/packages/effects-core/src/composition.ts @@ -664,18 +664,11 @@ export class Composition extends EventEmitter> imp if (item.parentId === undefined) { item.setParent(compVFXItem); } else { - // 兼容 treeItem 子元素的 parentId 带 '^' - const parentId = this.getParentIdWithoutSuffix(item.parentId); - const parent = itemMap.get(parentId); + const parent = itemMap.get(item.parentId); if (parent) { - if (VFXItem.isTree(parent) && item.parentId.includes('^')) { - item.parent = parent; - item.transform.parentTransform = parent.getNodeTransform(item.parentId); - } else { - item.parent = parent; - item.transform.parentTransform = parent.transform; - } + item.parent = parent; + item.transform.parentTransform = parent.transform; parent.children.push(item); } else { throw new Error('The element references a non-existent element, please check the data.'); @@ -690,12 +683,6 @@ export class Composition extends EventEmitter> imp } } - private getParentIdWithoutSuffix (id: string) { - const idx = id.lastIndexOf('^'); - - return idx > -1 ? id.substring(0, idx) : id; - } - /** * 更新视频数据到纹理 * @override From 96e477983586f675af4c1ae411bf7c6fe7c08456 Mon Sep 17 00:00:00 2001 From: liuxi150 Date: Sat, 12 Oct 2024 10:54:30 +0800 Subject: [PATCH 31/88] test: fix 3d case testing --- web-packages/test/case/3d/src/case.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web-packages/test/case/3d/src/case.ts b/web-packages/test/case/3d/src/case.ts index b9656cd0..0e06c5d6 100644 --- a/web-packages/test/case/3d/src/case.ts +++ b/web-packages/test/case/3d/src/case.ts @@ -104,7 +104,7 @@ function addDescribe (renderFramework) { for (let i = 0; i < timeList.length; i++) { const time = timeList[i]; - if (!oldPlayer.isLoop() && time >= oldPlayer.duration()) { + if (time >= oldPlayer.duration()) { break; } // From 863978b1100f788bbe52f6eb82a1fbf7d33ffb31 Mon Sep 17 00:00:00 2001 From: liuxi150 Date: Sat, 12 Oct 2024 11:07:17 +0800 Subject: [PATCH 32/88] test: update duration --- web-packages/test/case/2d/src/common/utilities.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web-packages/test/case/2d/src/common/utilities.ts b/web-packages/test/case/2d/src/common/utilities.ts index 96341128..34b52504 100644 --- a/web-packages/test/case/2d/src/common/utilities.ts +++ b/web-packages/test/case/2d/src/common/utilities.ts @@ -110,7 +110,7 @@ export class TestPlayer { if (this.composition.content) { return this.composition.content.duration; } else { - return this.composition.duration; + return this.composition.getDuration(); } } From 51334781fba53e0cfe514a2d2f115ba89a981b57 Mon Sep 17 00:00:00 2001 From: Sruim <934274351@qq.com> Date: Sat, 12 Oct 2024 15:16:24 +0800 Subject: [PATCH 33/88] =?UTF-8?q?feat:=20=E5=A2=9E=E5=8A=A0=E9=9F=B3?= =?UTF-8?q?=E8=A7=86=E9=A2=91=E6=8F=92=E4=BB=B6=20(#666)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: 初始化 media 插件 * chore: 优化demo * chore: 优化demo * feat(core): 增加 AssetManager 加载音视频能力 将和 Asset 相关的类移动至 Asset 文件夹下 * refactor(core/media): 解藕 TextComponent 和 SpriteComponent 的绑定关系 * feat(media): 支持 VideoComponent 的创建逻辑 * feat(media): 增加 VideoCompent 的相关接口 * chore(core): 删除本地连接 * fix(media): 修复视频元素元素行为问题 * feat(media): 增加 AudioCompent 的相关接口 * feat(media): 增加 media 插件测试模块 * test(media): 添加音频和视频组件的测试模块 * fix: Remove unnecessary code in AssetManager constructor * feat(demo): 增加交互功能 * refactor(asset): 重构 AssetManager 类和相关文件 * refactor(assert/media): 重构 AssetManager 类和相关文件 && 增加音视频注册模块 * refactor(multimedia): 多媒体插件更名 * refactor(asset/multimedia): 重构 AssetManager 类和多媒体插件相关类 - Make assets property readonly in AssetManager class - Remove unnecessary code in AssetManager constructor - Update getHitTestParams method in BaseRenderComponent class - Remove getItemInitData method in SpriteComponent class - Remove unused constructor in AudioComponent class - Update processRawJSON method in AudioLoader class - Update processRawJSON method in VideoLoader class - Update AudioPlayer class - Add PluginData interface in type.ts - Update AudioCompositionOptions interface in audio-component.spec.ts - Update VideoCompositionOptions interface in video-component.spec.ts * refactor(multimedia): 优化多媒体插件的代码结构和性能 * refactor(multimedia): 优化多媒体差距加载模块代码 * refactor(asset): 移动 asset-loader.ts 和 asset-manager.ts 文件至主目录 * refactor(asset): 移动 binary-asset.ts 文件至 effects-core 主目录 * refactor(asset/multimedia): 重命名 plugin-packages/media 为 plugin-packages/multiMedia * refactor(asset/spine): Refactor asset-loader.ts and spine-component.ts files * fix: 修复多媒体插件加载问题 * style: 调整引入路径,优化代码风格 * chore: 完善并优化 color 类型 * chore: 优化 multimedia 插件的资源处理逻辑 * chore: 根据机器人建议优化代码 * refactor: 重新设计插件对 assets 资源的处理,修复 3D JSONScene 数据的问题 * style: 优化 multimedia 插件 demo 代码 * style: 优化 demo 代码 * style: 优化 prepareAssets 处理逻辑 * refactor: 优化多媒体插件的音频处理逻辑 * refactor: 优化多媒体插件的音频处理逻辑,添加了对音频资源的处理和播放控制。修复了在重新设置音频资源时的问题。 * feat(mutilmedia): 增加音视频加载器检查自动播放权限的逻辑 (#680) * feat(mutilmedia): 增加音视频加载器检查自动播放权限的逻辑 * refactor: 重新设计插件对 assets 资源的处理,修复 3D JSONScene 数据的问题 * refactor: 增加自动播放检测方法的导出 * fix: typo * chore(media): 修改自动播放检测逻辑 * chore: 优化检测音频文件 --------- Co-authored-by: yiiqii * style: 修改 demo 引入路径 --------- Co-authored-by: yiiqii --- packages/effects-core/package.json | 2 +- .../src/animation/color-playable.ts | 85 ++++ packages/effects-core/src/animation/index.ts | 1 + packages/effects-core/src/asset-manager.ts | 83 +++- packages/effects-core/src/asset.ts | 5 + .../src/components/base-render-component.ts | 361 ++++++++++++++ .../src/components/effect-component.ts | 2 +- packages/effects-core/src/components/index.ts | 3 +- packages/effects-core/src/composition.ts | 2 +- .../src/composition/scene-ticking.ts | 4 +- packages/effects-core/src/downloader.ts | 9 - .../effects-core/src/fallback/migration.ts | 2 +- packages/effects-core/src/image-asset.ts | 6 - packages/effects-core/src/index.ts | 4 +- packages/effects-core/src/plugin-system.ts | 49 +- .../src/plugins/cal/calculate-loader.ts | 2 +- .../plugins/camera/camera-vfx-item-loader.ts | 2 +- .../src/plugins/interact/interact-loader.ts | 2 +- .../src/plugins/particle/particle-mesh.ts | 2 +- .../src/plugins/particle/particle-system.ts | 4 +- packages/effects-core/src/plugins/plugin.ts | 2 + .../src/plugins/sprite/sprite-item.ts | 412 +--------------- .../src/plugins/sprite/sprite-loader.ts | 2 +- .../src/plugins/sprite/sprite-mesh.ts | 23 +- .../src/plugins/text/text-item.ts | 56 ++- packages/effects-core/src/render/mesh.ts | 2 +- packages/effects-core/src/render/renderer.ts | 2 +- packages/effects-core/src/texture/texture.ts | 36 +- packages/effects-core/src/utils/color.ts | 16 +- packages/effects-core/src/utils/index.ts | 2 +- .../src/material/three-material.ts | 10 +- packages/effects-threejs/src/three-texture.ts | 2 +- packages/effects-webgl/src/gl-material.ts | 2 +- packages/effects-webgl/src/gl-texture.ts | 4 - plugin-packages/multimedia/LICENSE | 22 + plugin-packages/multimedia/README.md | 25 + plugin-packages/multimedia/demo/audio.html | 32 ++ plugin-packages/multimedia/demo/index.html | 19 + plugin-packages/multimedia/demo/src/audio.ts | 351 +++++++++++++ plugin-packages/multimedia/demo/src/video.ts | 465 ++++++++++++++++++ plugin-packages/multimedia/demo/tsconfig.json | 6 + plugin-packages/multimedia/demo/video.html | 32 ++ plugin-packages/multimedia/package.json | 44 ++ plugin-packages/multimedia/rollup.config.js | 40 ++ .../multimedia/src/audio/audio-component.ts | 105 ++++ .../multimedia/src/audio/audio-loader.ts | 18 + .../multimedia/src/audio/audio-player.ts | 210 ++++++++ plugin-packages/multimedia/src/constants.ts | 6 + plugin-packages/multimedia/src/index.ts | 12 + plugin-packages/multimedia/src/type.ts | 7 + plugin-packages/multimedia/src/utils.ts | 107 ++++ .../multimedia/src/video/video-component.ts | 226 +++++++++ .../multimedia/src/video/video-loader.ts | 18 + plugin-packages/multimedia/test/index.html | 46 ++ .../test/src/audio-component.spec.ts | 260 ++++++++++ plugin-packages/multimedia/test/src/index.ts | 3 + .../test/src/video-component.spec.ts | 393 +++++++++++++++ plugin-packages/multimedia/test/tsconfig.json | 6 + plugin-packages/multimedia/tsconfig.json | 17 + plugin-packages/multimedia/typedoc.json | 8 + plugin-packages/multimedia/vite.config.js | 71 +++ plugin-packages/spine/src/spine-component.ts | 2 +- pnpm-lock.yaml | 14 +- web-packages/demo/src/single.ts | 16 +- .../plugins/particle/base.spec.ts | 4 +- .../src/effects-webgl/gl-material.spec.ts | 2 +- 66 files changed, 3252 insertions(+), 536 deletions(-) create mode 100644 packages/effects-core/src/animation/color-playable.ts create mode 100644 packages/effects-core/src/animation/index.ts create mode 100644 packages/effects-core/src/asset.ts create mode 100644 packages/effects-core/src/components/base-render-component.ts delete mode 100644 packages/effects-core/src/image-asset.ts create mode 100644 plugin-packages/multimedia/LICENSE create mode 100644 plugin-packages/multimedia/README.md create mode 100644 plugin-packages/multimedia/demo/audio.html create mode 100644 plugin-packages/multimedia/demo/index.html create mode 100644 plugin-packages/multimedia/demo/src/audio.ts create mode 100644 plugin-packages/multimedia/demo/src/video.ts create mode 100644 plugin-packages/multimedia/demo/tsconfig.json create mode 100644 plugin-packages/multimedia/demo/video.html create mode 100644 plugin-packages/multimedia/package.json create mode 100644 plugin-packages/multimedia/rollup.config.js create mode 100644 plugin-packages/multimedia/src/audio/audio-component.ts create mode 100644 plugin-packages/multimedia/src/audio/audio-loader.ts create mode 100644 plugin-packages/multimedia/src/audio/audio-player.ts create mode 100644 plugin-packages/multimedia/src/constants.ts create mode 100644 plugin-packages/multimedia/src/index.ts create mode 100644 plugin-packages/multimedia/src/type.ts create mode 100644 plugin-packages/multimedia/src/utils.ts create mode 100644 plugin-packages/multimedia/src/video/video-component.ts create mode 100644 plugin-packages/multimedia/src/video/video-loader.ts create mode 100644 plugin-packages/multimedia/test/index.html create mode 100644 plugin-packages/multimedia/test/src/audio-component.spec.ts create mode 100644 plugin-packages/multimedia/test/src/index.ts create mode 100644 plugin-packages/multimedia/test/src/video-component.spec.ts create mode 100644 plugin-packages/multimedia/test/tsconfig.json create mode 100644 plugin-packages/multimedia/tsconfig.json create mode 100644 plugin-packages/multimedia/typedoc.json create mode 100644 plugin-packages/multimedia/vite.config.js diff --git a/packages/effects-core/package.json b/packages/effects-core/package.json index 1e563e44..00bf5fbe 100644 --- a/packages/effects-core/package.json +++ b/packages/effects-core/package.json @@ -51,7 +51,7 @@ "registry": "https://registry.npmjs.org" }, "dependencies": { - "@galacean/effects-specification": "2.0.1", + "@galacean/effects-specification": "2.1.0-alpha.0", "@galacean/effects-math": "1.1.0", "flatbuffers": "24.3.25", "uuid": "9.0.1", diff --git a/packages/effects-core/src/animation/color-playable.ts b/packages/effects-core/src/animation/color-playable.ts new file mode 100644 index 00000000..44b290a7 --- /dev/null +++ b/packages/effects-core/src/animation/color-playable.ts @@ -0,0 +1,85 @@ +import * as spec from '@galacean/effects-specification'; +import { createValueGetter, vecFill, vecMulCombine, type ValueGetter } from '../math'; +import type { FrameContext } from '../plugins/cal/playable-graph'; +import { Playable } from '../plugins/cal/playable-graph'; +import { VFXItem } from '../vfx-item'; +import type { Material } from '../material'; +import type { ColorStop } from '../utils'; +import { colorStopsFromGradient, getColorFromGradientStops } from '../utils'; +import { BaseRenderComponent } from '../components'; + +export interface ColorPlayableAssetData extends spec.EffectsObjectData { + colorOverLifetime?: spec.ColorOverLifetime, +} + +const tempColor: spec.RGBAColorValue = [1, 1, 1, 1]; + +export class ColorPlayable extends Playable { + clipData: { colorOverLifetime?: spec.ColorOverLifetime, startColor?: spec.RGBAColorValue }; + colorOverLifetime: ColorStop[]; + opacityOverLifetime: ValueGetter; + startColor: spec.RGBAColorValue; + renderColor: spec.vec4 = [1, 1, 1, 1]; + activeComponent?: BaseRenderComponent; + activeMaterial?: Material; + + override processFrame (context: FrameContext): void { + const boundObject = context.output.getUserData(); + + if (!(boundObject instanceof VFXItem)) { + return; + } + if (!this.activeComponent) { + this.activeComponent = this.getActiveComponent(boundObject); + } + if (!this.activeMaterial) { + this.activeMaterial = this.activeComponent?.material; + const startColor = this.activeMaterial?.getVector4('_Color'); + + if (startColor) { + this.startColor = startColor.toArray(); + } + } + + this.activeComponent?.setAnimationTime(this.time); + let colorInc = vecFill(tempColor, 1); + let colorChanged; + const life = this.time / boundObject.duration; + + const opacityOverLifetime = this.opacityOverLifetime; + const colorOverLifetime = this.colorOverLifetime; + + if (colorOverLifetime) { + colorInc = getColorFromGradientStops(colorOverLifetime, life, true) as spec.vec4; + colorChanged = true; + } + if (opacityOverLifetime) { + colorInc[3] *= opacityOverLifetime.getValue(life); + colorChanged = true; + } + + if (colorChanged) { + vecMulCombine(this.renderColor, colorInc, this.startColor); + this.activeMaterial?.getVector4('_Color')?.setFromArray(this.renderColor); + } + } + + create (clipData: ColorPlayableAssetData) { + this.clipData = clipData; + const colorOverLifetime = clipData.colorOverLifetime; + + if (colorOverLifetime) { + this.opacityOverLifetime = createValueGetter(colorOverLifetime.opacity ?? 1); + if (colorOverLifetime.color && colorOverLifetime.color[0] === spec.ValueType.GRADIENT_COLOR) { + this.colorOverLifetime = colorStopsFromGradient(colorOverLifetime.color[1]); + } + } + + return this; + } + + getActiveComponent (boundObject: VFXItem): BaseRenderComponent { + return boundObject.getComponent(BaseRenderComponent); + } + +} diff --git a/packages/effects-core/src/animation/index.ts b/packages/effects-core/src/animation/index.ts new file mode 100644 index 00000000..d53d95c7 --- /dev/null +++ b/packages/effects-core/src/animation/index.ts @@ -0,0 +1 @@ +export * from './color-playable'; diff --git a/packages/effects-core/src/asset-manager.ts b/packages/effects-core/src/asset-manager.ts index ab0ca01a..a31ff06e 100644 --- a/packages/effects-core/src/asset-manager.ts +++ b/packages/effects-core/src/asset-manager.ts @@ -15,7 +15,8 @@ import { deserializeMipmapTexture, TextureSourceType, getKTXTextureOptions, Text import type { Renderer } from './render'; import { COMPRESSED_TEXTURE } from './render'; import { combineImageTemplate, getBackgroundImage } from './template-image'; -import { ImageAsset } from './image-asset'; +import { Asset } from './asset'; +import type { Engine } from './engine'; type AssetsType = ImageLike | { url: string, type: TextureSourceType }; @@ -27,14 +28,13 @@ let seed = 1; */ export class AssetManager implements Disposable { /** - * 相对 url 的基本路径 + * 图像资源,用于创建和释放 GPU 纹理资源 */ - private baseUrl: string; + assets: Record = {}; /** - * 图像资源,用于创建和释放 GPU 纹理资源 + * 相对 url 的基本路径 */ - private assets: Record = {}; - + private baseUrl: string; /** * 自定义文本缓存,随页面销毁而销毁 */ @@ -144,32 +144,29 @@ export class AssetManager implements Disposable { scene = { ...rawJSON, }; + + const { jsonScene, pluginSystem, images: loadedImages } = scene; + const { compositions, images } = jsonScene; + const [pluginResult] = await Promise.all([ + hookTimeInfo('plugin:prepareAssets', () => pluginSystem.prepareAssets(jsonScene, options)), + hookTimeInfo('plugin:precompile', () => this.precompile(compositions, pluginSystem, renderer, options)), + ]); + + await this.prepareAssets(images, loadedImages, pluginResult); } else { // TODO: JSONScene 中 bins 的类型可能为 ArrayBuffer[] const { jsonScene, pluginSystem } = await hookTimeInfo('processJSON', () => this.processJSON(rawJSON as JSONValue)); const { bins = [], images, compositions, fonts } = jsonScene; - const [loadedBins, loadedImages] = await Promise.all([ + const [loadedBins, loadedImages, pluginResult] = await Promise.all([ hookTimeInfo('processBins', () => this.processBins(bins)), hookTimeInfo('processImages', () => this.processImages(images, compressedTexture)), - hookTimeInfo('precompile', () => this.precompile(compositions, pluginSystem, renderer, options)), + hookTimeInfo('plugin:prepareAssets', () => pluginSystem.prepareAssets(jsonScene, options)), + hookTimeInfo('plugin:precompile', () => this.precompile(compositions, pluginSystem, renderer, options)), hookTimeInfo('processFontURL', () => this.processFontURL(fonts as spec.FontDefine[])), ]); - for (let i = 0; i < images.length; i++) { - this.assets[images[i].id] = loadedImages[i]; - } - - if (renderer) { - for (let i = 0; i < images.length; i++) { - const imageAsset = new ImageAsset(renderer.engine); - - imageAsset.data = loadedImages[i]; - imageAsset.setInstanceId(images[i].id); - renderer.engine.addInstance(imageAsset); - } - } - + await this.prepareAssets(images, loadedImages, pluginResult); const loadedTextures = await hookTimeInfo('processTextures', () => this.processTextures(loadedImages, loadedBins, jsonScene)); scene = { @@ -185,9 +182,11 @@ export class AssetManager implements Disposable { }; // 触发插件系统 pluginSystem 的回调 prepareResource - await hookTimeInfo('processPlugins', () => pluginSystem.loadResources(scene, this.options)); + await hookTimeInfo('plugin:prepareResource', () => pluginSystem.loadResources(scene, this.options)); } + await hookTimeInfo('processAssets', () => this.processAssets(renderer?.engine)); + const totalTime = performance.now() - startTime; logger.info(`Load asset: totalTime: ${totalTime.toFixed(4)}ms ${timeInfoMessages.join(' ')}, url: ${assetUrl}.`); @@ -366,6 +365,44 @@ export class AssetManager implements Disposable { return Promise.all(jobs); } + private async prepareAssets ( + images: spec.ImageSource[], + loadedImages: ImageLike[], + pluginResult: { + assets: spec.AssetBase[], + loadedAssets: unknown[], + }[], + ) { + const { assets, loadedAssets } = pluginResult.reduce((acc, cur) => { + acc.assets = acc.assets.concat(cur.assets); + acc.loadedAssets = acc.loadedAssets.concat(cur.loadedAssets); + + return acc; + }, { assets: [], loadedAssets: [] }); + + for (let i = 0; i < images.length; i++) { + this.assets[images[i].id] = loadedImages[i]; + } + for (let i = 0; i < assets.length; i++) { + this.assets[assets[i].id] = loadedAssets[i] as AssetsType; + } + } + + private async processAssets (engine?: Engine) { + if (!engine) { + return; + } + + for (const assetId of Object.keys(this.assets)) { + const asset = this.assets[assetId]; + const engineAsset = new Asset(engine); + + engineAsset.data = asset; + engineAsset.setInstanceId(assetId); + engine.addInstance(engineAsset); + } + } + private async processTextures ( images: ImageLike[], bins: ArrayBuffer[], diff --git a/packages/effects-core/src/asset.ts b/packages/effects-core/src/asset.ts new file mode 100644 index 00000000..7fccea59 --- /dev/null +++ b/packages/effects-core/src/asset.ts @@ -0,0 +1,5 @@ +import { EffectsObject } from './effects-object'; + +export class Asset extends EffectsObject { + data: T; +} diff --git a/packages/effects-core/src/components/base-render-component.ts b/packages/effects-core/src/components/base-render-component.ts new file mode 100644 index 00000000..53aecbce --- /dev/null +++ b/packages/effects-core/src/components/base-render-component.ts @@ -0,0 +1,361 @@ +import * as spec from '@galacean/effects-specification'; +import { Matrix4 } from '@galacean/effects-math/es/core/matrix4'; +import { Vector3 } from '@galacean/effects-math/es/core/vector3'; +import { Vector4 } from '@galacean/effects-math/es/core/vector4'; +import { RendererComponent } from './renderer-component'; +import type { Texture } from '../texture'; +import type { GeometryDrawMode, Renderer } from '../render'; +import { Geometry } from '../render'; +import type { Engine } from '../engine'; +import { glContext } from '../gl'; +import { addItem } from '../utils'; +import type { BoundingBoxTriangle, HitTestTriangleParams } from '../plugins'; +import { HitTestType, maxSpriteMeshItemCount, spriteMeshShaderFromRenderInfo } from '../plugins'; +import type { MaterialProps } from '../material'; +import { getPreMultiAlpha, Material, setBlendMode, setMaskMode, setSideMode } from '../material'; +import { trianglesFromRect } from '../math'; + +/** + * 图层元素渲染属性, 经过处理后的 spec.SpriteContent.renderer + */ +export interface ItemRenderer extends Required> { + order: number, + mask: number, + texture: Texture, + anchor?: spec.vec2, + particleOrigin?: spec.ParticleOrigin, +} + +/** + * 图层的渲染属性,用于 Mesh 的合并判断 + */ +export interface ItemRenderInfo { + side: number, + occlusion: boolean, + blending: number, + cachePrefix: string, + mask: number, + maskMode: number, + cacheId: string, + wireframe?: boolean, +} + +export class BaseRenderComponent extends RendererComponent { + interaction?: { behavior: spec.InteractBehavior }; + cachePrefix = '-'; + geoData: { atlasOffset: number[] | spec.TypedArray, index: number[] | spec.TypedArray }; + anchor?: spec.vec2; + renderer: ItemRenderer; + + emptyTexture: Texture; + color: spec.vec4 = [1, 1, 1, 1]; + worldMatrix: Matrix4; + geometry: Geometry; + + protected renderInfo: ItemRenderInfo; + // readonly mesh: Mesh; + protected readonly wireframe?: boolean; + protected preMultiAlpha: number; + protected visible = true; + protected isManualTimeSet = false; + protected frameAnimationTime = 0; + + constructor (engine: Engine) { + super(engine); + + this.renderer = { + renderMode: spec.RenderMode.BILLBOARD, + blending: spec.BlendingMode.ALPHA, + texture: this.engine.emptyTexture, + occlusion: false, + transparentOcclusion: false, + side: spec.SideMode.DOUBLE, + mask: 0, + maskMode: spec.MaskMode.NONE, + order: 0, + }; + this.emptyTexture = this.engine.emptyTexture; + this.renderInfo = getImageItemRenderInfo(this); + + const material = this.createMaterial(this.renderInfo, 2); + + this.worldMatrix = Matrix4.fromIdentity(); + this.material = material; + this.material.setVector4('_Color', new Vector4().setFromArray([1, 1, 1, 1])); + this.material.setVector4('_TexOffset', new Vector4().setFromArray([0, 0, 1, 1])); + } + + /** + * 设置当前 Mesh 的可见性。 + * @param visible - true:可见,false:不可见 + */ + setVisible (visible: boolean) { + this.visible = visible; + } + /** + * 获取当前 Mesh 的可见性。 + */ + getVisible (): boolean { + return this.visible; + } + + /** + * 设置当前图层的颜色 + * > Tips: 透明度也属于颜色的一部分,当有透明度/颜色 K 帧变化时,该 API 会失效 + * @since 2.0.0 + * @param color - 颜色值 + */ + setColor (color: spec.vec4) { + this.color = color; + this.material.setVector4('_Color', new Vector4().setFromArray(color)); + } + + /** + * 设置当前 Mesh 的纹理 + * @since 2.0.0 + * @param texture - 纹理对象 + */ + setTexture (texture: Texture) { + this.renderer.texture = texture; + this.material.setTexture('uSampler0', texture); + } + + /** + * @internal + */ + setAnimationTime (time: number) { + this.frameAnimationTime = time; + this.isManualTimeSet = true; + } + + override render (renderer: Renderer) { + if (!this.getVisible()) { + return; + } + const material = this.material; + const geo = this.geometry; + + if (renderer.renderingData.currentFrame.globalUniforms) { + renderer.setGlobalMatrix('effects_ObjectToWorld', this.transform.getWorldMatrix()); + } + this.material.setVector2('_Size', this.transform.size); + renderer.drawGeometry(geo, material); + } + + override onStart (): void { + this.item.getHitTestParams = this.getHitTestParams; + } + + override onDestroy (): void { + if (this.item && this.item.composition) { + this.item.composition.destroyTextures(this.getTextures()); + } + } + + protected getItemInitData () { + this.geoData = this.getItemGeometryData(); + + const { index, atlasOffset } = this.geoData; + const idxCount = index.length; + // @ts-expect-error + const indexData: number[] = this.wireframe ? new Uint8Array([0, 1, 1, 3, 2, 3, 2, 0]) : new index.constructor(idxCount); + + if (!this.wireframe) { + for (let i = 0; i < idxCount; i++) { + indexData[i] = 0 + index[i]; + } + } + + return { + atlasOffset, + index: indexData, + }; + } + + protected setItem () { + const textures: Texture[] = []; + let texture = this.renderer.texture; + + if (texture) { + addItem(textures, texture); + } + texture = this.renderer.texture; + const data = this.getItemInitData(); + + const renderer = this.renderer; + const texParams = this.material.getVector4('_TexParams'); + + if (texParams) { + texParams.x = renderer.occlusion ? +(renderer.transparentOcclusion) : 1; + texParams.y = +this.preMultiAlpha; + texParams.z = renderer.renderMode; + } + + const attributes = { + atlasOffset: new Float32Array(data.atlasOffset.length), + index: new Uint16Array(data.index.length), + }; + + attributes.atlasOffset.set(data.atlasOffset); + attributes.index.set(data.index); + const { material, geometry } = this; + const indexData = attributes.index; + + geometry.setIndexData(indexData); + geometry.setAttributeData('atlasOffset', attributes.atlasOffset); + geometry.setDrawCount(data.index.length); + for (let i = 0; i < textures.length; i++) { + const texture = textures[i]; + + material.setTexture('uSampler' + i, texture); + } + // FIXME: 内存泄漏的临时方案,后面再调整 + const emptyTexture = this.emptyTexture; + + for (let k = textures.length; k < maxSpriteMeshItemCount; k++) { + material.setTexture('uSampler' + k, emptyTexture); + } + } + + protected getItemGeometryData () { + this.geometry.setAttributeData('aPos', new Float32Array([-0.5, 0.5, 0, -0.5, -0.5, 0, 0.5, 0.5, 0, 0.5, -0.5, 0])); + + return { index: [0, 1, 2, 2, 1, 3], atlasOffset: [0, 1, 0, 0, 1, 1, 1, 0] }; + } + + protected createGeometry (mode: GeometryDrawMode) { + return Geometry.create(this.engine, { + attributes: { + aPos: { + type: glContext.FLOAT, + size: 3, + data: new Float32Array([ + -0.5, 0.5, 0, //左上 + -0.5, -0.5, 0, //左下 + 0.5, 0.5, 0, //右上 + 0.5, -0.5, 0, //右下 + ]), + }, + atlasOffset: { + size: 2, + offset: 0, + releasable: true, + type: glContext.FLOAT, + data: new Float32Array(0), + }, + }, + indices: { data: new Uint16Array(0), releasable: true }, + mode, + maxVertex: 4, + }); + } + + protected createMaterial (renderInfo: ItemRenderInfo, count: number): Material { + const { side, occlusion, blending, maskMode, mask } = renderInfo; + const materialProps: MaterialProps = { + shader: spriteMeshShaderFromRenderInfo(renderInfo, count, 1), + }; + + this.preMultiAlpha = getPreMultiAlpha(blending); + + const material = Material.create(this.engine, materialProps); + const states = { + side, + blending: true, + blendMode: blending, + mask, + maskMode, + depthTest: true, + depthMask: occlusion, + }; + + material.blending = states.blending; + material.stencilRef = states.mask !== undefined ? [states.mask, states.mask] : undefined; + material.depthTest = states.depthTest; + material.depthMask = states.depthMask; + states.blending && setBlendMode(material, states.blendMode); + setMaskMode(material, states.maskMode); + setSideMode(material, states.side); + + material.shader.shaderData.properties = 'uSampler0("uSampler0",2D) = "white" {}'; + if (!material.hasUniform('_Color')) { + material.setVector4('_Color', new Vector4(0, 0, 0, 1)); + } + if (!material.hasUniform('_TexOffset')) { + material.setVector4('_TexOffset', new Vector4()); + } + if (!material.hasUniform('_TexParams')) { + material.setVector4('_TexParams', new Vector4()); + } + + return material; + } + + getTextures (): Texture[] { + const ret = []; + const tex = this.renderer.texture; + + if (tex) { + ret.push(tex); + } + + return ret; + } + + /** + * 获取图层包围盒的类型和世界坐标 + * @returns + */ + getBoundingBox (): BoundingBoxTriangle | void { + if (!this.item) { + return; + } + const worldMatrix = this.transform.getWorldMatrix(); + const triangles = trianglesFromRect(Vector3.ZERO, 0.5 * this.transform.size.x, 0.5 * this.transform.size.y); + + triangles.forEach(triangle => { + worldMatrix.transformPoint(triangle.p0 as Vector3); + worldMatrix.transformPoint(triangle.p1 as Vector3); + worldMatrix.transformPoint(triangle.p2 as Vector3); + }); + + return { + type: HitTestType.triangle, + area: triangles, + }; + } + + getHitTestParams = (force?: boolean): HitTestTriangleParams | undefined => { + const ui = this.interaction; + + if ((force || ui)) { + const area = this.getBoundingBox(); + + if (area) { + return { + behavior: this.interaction?.behavior || 0, + type: area.type, + triangles: area.area, + backfaceCulling: this.renderer.side === spec.SideMode.FRONT, + }; + } + } + }; +} + +export function getImageItemRenderInfo (item: BaseRenderComponent): ItemRenderInfo { + const { renderer } = item; + const { blending, side, occlusion, mask, maskMode, order } = renderer; + const blendingCache = +blending; + const cachePrefix = item.cachePrefix || '-'; + + return { + side, + occlusion, + blending, + mask, + maskMode, + cachePrefix, + cacheId: `${cachePrefix}.${+side}+${+occlusion}+${blendingCache}+${order}+${maskMode}.${mask}`, + }; +} diff --git a/packages/effects-core/src/components/effect-component.ts b/packages/effects-core/src/components/effect-component.ts index 36e0c434..019ea13a 100644 --- a/packages/effects-core/src/components/effect-component.ts +++ b/packages/effects-core/src/components/effect-component.ts @@ -2,6 +2,7 @@ import * as spec from '@galacean/effects-specification'; import { Matrix4 } from '@galacean/effects-math/es/core/matrix4'; import type { TriangleLike } from '@galacean/effects-math/es/core/type'; import { Vector3 } from '@galacean/effects-math/es/core/vector3'; +import { Vector4 } from '@galacean/effects-math/es/core/vector4'; import { effectsClass, serialize } from '../decorators'; import type { Engine } from '../engine'; import type { Material, MaterialDestroyOptions } from '../material'; @@ -11,7 +12,6 @@ import type { MeshDestroyOptions, Renderer } from '../render'; import type { Geometry } from '../render'; import { DestroyOptions } from '../utils'; import { RendererComponent } from './renderer-component'; -import { Vector4 } from '@galacean/effects-math/es/core/vector4'; /** * @since 2.0.0 diff --git a/packages/effects-core/src/components/index.ts b/packages/effects-core/src/components/index.ts index 271883d1..5980dc5b 100644 --- a/packages/effects-core/src/components/index.ts +++ b/packages/effects-core/src/components/index.ts @@ -2,4 +2,5 @@ export * from './renderer-component'; export * from './component'; export * from './effect-component'; export * from './post-process-volume'; -export * from './shape-component'; \ No newline at end of file +export * from './base-render-component'; +export * from './shape-component'; diff --git a/packages/effects-core/src/composition.ts b/packages/effects-core/src/composition.ts index f5cdf183..3d342fb8 100644 --- a/packages/effects-core/src/composition.ts +++ b/packages/effects-core/src/composition.ts @@ -20,7 +20,7 @@ import type { VFXItemProps } from './vfx-item'; import { VFXItem } from './vfx-item'; import type { CompositionEvent } from './events'; import { EventEmitter } from './events'; -import type { PostProcessVolume } from './components/post-process-volume'; +import type { PostProcessVolume } from './components'; import { SceneTicking } from './composition/scene-ticking'; export interface CompositionStatistic { diff --git a/packages/effects-core/src/composition/scene-ticking.ts b/packages/effects-core/src/composition/scene-ticking.ts index 789c13b2..e6367739 100644 --- a/packages/effects-core/src/composition/scene-ticking.ts +++ b/packages/effects-core/src/composition/scene-ticking.ts @@ -1,4 +1,4 @@ -import { Component } from '../components/component'; +import { Component } from '../components'; export class SceneTicking { update: UpdateTickData = new UpdateTickData(); @@ -99,4 +99,4 @@ class LateUpdateTickData extends TickData { // } else { // return 1; // } -// } \ No newline at end of file +// } diff --git a/packages/effects-core/src/downloader.ts b/packages/effects-core/src/downloader.ts index cbf28a82..4b960726 100644 --- a/packages/effects-core/src/downloader.ts +++ b/packages/effects-core/src/downloader.ts @@ -2,15 +2,6 @@ import { isAndroid } from './utils'; type SuccessHandler = (data: T) => void; type ErrorHandler = (status: number, responseText: string) => void; -/** - * - */ -// type VideoLoadOptions = { -// /** -// * 视频是否循环播放 -// */ -// loop?: boolean, -// }; /** * JSON 值,它可以是字符串、数字、布尔值、对象或者 JSON 值的数组。 diff --git a/packages/effects-core/src/fallback/migration.ts b/packages/effects-core/src/fallback/migration.ts index 0710d2df..3bf2b972 100644 --- a/packages/effects-core/src/fallback/migration.ts +++ b/packages/effects-core/src/fallback/migration.ts @@ -361,7 +361,7 @@ export function version30Migration (json: JSONSceneLegacy): JSONScene { break; case ItemType.spine: - item.content.dataType = 'SpineComponent'; + item.content.dataType = DataType.SpineComponent; break; } diff --git a/packages/effects-core/src/image-asset.ts b/packages/effects-core/src/image-asset.ts deleted file mode 100644 index a45dc14d..00000000 --- a/packages/effects-core/src/image-asset.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { EffectsObject } from './effects-object'; -import type { ImageLike } from './scene'; - -export class ImageAsset extends EffectsObject { - data: ImageLike; -} diff --git a/packages/effects-core/src/index.ts b/packages/effects-core/src/index.ts index f3af6899..b42626b6 100644 --- a/packages/effects-core/src/index.ts +++ b/packages/effects-core/src/index.ts @@ -10,6 +10,8 @@ import { VFXItem } from './vfx-item'; export * as math from '@galacean/effects-math/es/core/index'; export * as spec from '@galacean/effects-specification'; +export * from './asset'; +export * from './binary-asset'; export * from './asset-loader'; export * from './asset-manager'; export * from './camera'; @@ -45,10 +47,10 @@ export * from './ticker'; export * from './transform'; export * from './utils'; export * from './vfx-item'; -export * from './binary-asset'; export * from './effects-object'; export * from './effects-package'; export * from './events'; +export * from './pass-render-level'; registerPlugin('camera', CameraVFXItemLoader, VFXItem, true); registerPlugin('text', TextLoader, VFXItem, true); diff --git a/packages/effects-core/src/plugin-system.ts b/packages/effects-core/src/plugin-system.ts index c3106252..cbf468b0 100644 --- a/packages/effects-core/src/plugin-system.ts +++ b/packages/effects-core/src/plugin-system.ts @@ -4,8 +4,7 @@ import type { Plugin, PluginConstructor } from './plugins'; import type { RenderFrame, Renderer } from './render'; import type { Scene, SceneLoadOptions } from './scene'; import { addItem, removeItem, logger } from './utils'; -import type { VFXItemConstructor, VFXItemProps } from './vfx-item'; -import { VFXItem } from './vfx-item'; +import type { VFXItemConstructor } from './vfx-item'; export const pluginLoaderMap: Record = {}; export const defaultPlugins: string[] = []; @@ -92,29 +91,27 @@ export class PluginSystem { this.plugins.forEach(loader => loader.onCompositionReset(comp, renderFrame)); } - createPluginItem (name: string, props: VFXItemProps, composition: Composition): VFXItem { - const CTRL = pluginCtrlMap[name]; - - if (!CTRL) { - throw new Error(`The plugin '${name}' does not have a registered constructor.`); - } - const engine = composition.getEngine(); - const item = new CTRL(engine, props, composition); - - item.composition = composition; + async processRawJSON (json: spec.JSONScene, options: SceneLoadOptions): Promise { + return this.callStatic('processRawJSON', json, options); + } - if (!(item instanceof VFXItem)) { - throw new Error(`The plugin '${name}' invalid constructor type.`); - } + async prepareAssets (json: spec.JSONScene, options?: SceneLoadOptions) { + return this.callStatic<{ assets: spec.AssetBase[], loadedAssets: unknown[] }>('prepareAssets', json, options); + } - return item; + async precompile ( + compositions: spec.CompositionData[], + renderer: Renderer, + options?: PrecompileOptions, + ) { + return this.callStatic('precompile', compositions, renderer, options); } - async processRawJSON (json: spec.JSONScene, options: SceneLoadOptions): Promise { - return this.callStatic('processRawJSON', json, options); + async loadResources (scene: Scene, options: SceneLoadOptions) { + return this.callStatic('prepareResource', scene, options); } - private async callStatic (name: string, ...args: any[]): Promise { + private async callStatic (name: string, ...args: any[]): Promise { const pendings = []; const plugins = this.plugins; @@ -123,24 +120,12 @@ export class PluginSystem { const ctrl = pluginLoaderMap[plugin.name]; if (name in ctrl) { - pendings.push(Promise.resolve(ctrl[name]?.(...args))); + pendings.push(Promise.resolve(ctrl[name]?.(...args))); } } return Promise.all(pendings); } - - async precompile ( - compositions: spec.CompositionData[], - renderer: Renderer, - options?: PrecompileOptions, - ) { - return this.callStatic('precompile', compositions, renderer, options); - } - - async loadResources (scene: Scene, options: SceneLoadOptions) { - return this.callStatic('prepareResource', scene, options); - } } const pluginInfoMap: Record = { diff --git a/packages/effects-core/src/plugins/cal/calculate-loader.ts b/packages/effects-core/src/plugins/cal/calculate-loader.ts index a79ee4d5..e2d6d1af 100644 --- a/packages/effects-core/src/plugins/cal/calculate-loader.ts +++ b/packages/effects-core/src/plugins/cal/calculate-loader.ts @@ -1,3 +1,3 @@ -import { AbstractPlugin } from '../index'; +import { AbstractPlugin } from '../plugin'; export class CalculateLoader extends AbstractPlugin {} diff --git a/packages/effects-core/src/plugins/camera/camera-vfx-item-loader.ts b/packages/effects-core/src/plugins/camera/camera-vfx-item-loader.ts index 78e84271..0aef82bb 100644 --- a/packages/effects-core/src/plugins/camera/camera-vfx-item-loader.ts +++ b/packages/effects-core/src/plugins/camera/camera-vfx-item-loader.ts @@ -1,4 +1,4 @@ -import { AbstractPlugin } from '../index'; +import { AbstractPlugin } from '../plugin'; export class CameraVFXItemLoader extends AbstractPlugin { } diff --git a/packages/effects-core/src/plugins/interact/interact-loader.ts b/packages/effects-core/src/plugins/interact/interact-loader.ts index 1f8d9483..fa296e8d 100644 --- a/packages/effects-core/src/plugins/interact/interact-loader.ts +++ b/packages/effects-core/src/plugins/interact/interact-loader.ts @@ -1,4 +1,4 @@ -import { AbstractPlugin } from '../index'; +import { AbstractPlugin } from '../plugin'; export class InteractLoader extends AbstractPlugin { } diff --git a/packages/effects-core/src/plugins/particle/particle-mesh.ts b/packages/effects-core/src/plugins/particle/particle-mesh.ts index 90075684..d094985a 100644 --- a/packages/effects-core/src/plugins/particle/particle-mesh.ts +++ b/packages/effects-core/src/plugins/particle/particle-mesh.ts @@ -336,7 +336,7 @@ export class ParticleMesh implements ParticleMeshData { material.blending = true; material.depthTest = true; - material.depthMask = !!(occlusion); + material.depthMask = !!occlusion; material.stencilRef = mask ? [mask, mask] : undefined; setMaskMode(material, maskMode); setBlendMode(material, blending); diff --git a/packages/effects-core/src/plugins/particle/particle-system.ts b/packages/effects-core/src/plugins/particle/particle-system.ts index bd7f56c9..226403f3 100644 --- a/packages/effects-core/src/plugins/particle/particle-system.ts +++ b/packages/effects-core/src/plugins/particle/particle-system.ts @@ -12,7 +12,7 @@ import type { ShapeGenerator, ShapeGeneratorOptions, ShapeParticle } from '../.. import { createShape } from '../../shape'; import { Texture } from '../../texture'; import { Transform } from '../../transform'; -import { DestroyOptions, type color } from '../../utils'; +import { DestroyOptions } from '../../utils'; import type { BoundingBoxSphere, HitTestCustomParams } from '../interact/click-handler'; import { HitTestType } from '../interact/click-handler'; import { Burst } from './burst'; @@ -32,7 +32,7 @@ type ParticleOptions = { startSpeed: ValueGetter, startLifetime: ValueGetter, startDelay: ValueGetter, - startColor: ValueGetter, + startColor: ValueGetter, start3DRotation?: boolean, startRotationX?: ValueGetter, startRotationY?: ValueGetter, diff --git a/packages/effects-core/src/plugins/plugin.ts b/packages/effects-core/src/plugins/plugin.ts index 855f32f6..a31ce109 100644 --- a/packages/effects-core/src/plugins/plugin.ts +++ b/packages/effects-core/src/plugins/plugin.ts @@ -111,6 +111,8 @@ export abstract class AbstractPlugin implements Plugin { */ static processRawJSON: (json: spec.JSONScene, options: SceneLoadOptions) => Promise; + static prepareAssets: (json: spec.JSONScene, options?: SceneLoadOptions) => Promise<{ assets: spec.AssetBase[], loadedAssets: unknown[] }>; + /** * player.loadScene 函数调用的时候会触发此函数, * 此阶段时,json 中的图片和二进制已经被加载完成,可以对加载好的资源做进一步处理, diff --git a/packages/effects-core/src/plugins/sprite/sprite-item.ts b/packages/effects-core/src/plugins/sprite/sprite-item.ts index 95fbdf70..d471d5b6 100644 --- a/packages/effects-core/src/plugins/sprite/sprite-item.ts +++ b/packages/effects-core/src/plugins/sprite/sprite-item.ts @@ -1,25 +1,18 @@ -import { Matrix4, Vector3, Vector4 } from '@galacean/effects-math/es/core/index'; -import type { vec2, vec4 } from '@galacean/effects-specification'; +import { Matrix4, Vector4 } from '@galacean/effects-math/es/core/index'; import * as spec from '@galacean/effects-specification'; -import { RendererComponent } from '../../components/renderer-component'; import { effectsClass } from '../../decorators'; import type { Engine } from '../../engine'; import { glContext } from '../../gl'; -import type { MaterialProps } from '../../material'; -import { Material, getPreMultiAlpha, setBlendMode, setMaskMode, setSideMode } from '../../material'; -import type { ValueGetter } from '../../math'; -import { createValueGetter, trianglesFromRect, vecFill, vecMulCombine } from '../../math'; -import type { GeometryDrawMode, Renderer } from '../../render'; +import type { GeometryDrawMode } from '../../render'; import { Geometry } from '../../render'; import type { GeometryFromShape } from '../../shape'; import type { Texture } from '../../texture'; -import { addItem, colorStopsFromGradient, getColorFromGradientStops } from '../../utils'; -import type { FrameContext, PlayableGraph } from '../cal/playable-graph'; -import { Playable, PlayableAsset } from '../cal/playable-graph'; -import type { BoundingBoxTriangle, HitTestTriangleParams } from '../interact/click-handler'; -import { HitTestType } from '../interact/click-handler'; -import { getImageItemRenderInfo, maxSpriteMeshItemCount, spriteMeshShaderFromRenderInfo } from './sprite-mesh'; -import { VFXItem } from '../../vfx-item'; +import type { PlayableGraph, Playable } from '../cal/playable-graph'; +import { PlayableAsset } from '../cal/playable-graph'; +import type { ColorPlayableAssetData } from '../../animation'; +import { ColorPlayable } from '../../animation'; +import type { ItemRenderer } from '../../components'; +import { BaseRenderComponent, getImageItemRenderInfo } from '../../components/base-render-component'; /** * 用于创建 spriteItem 的数据类型, 经过处理后的 spec.SpriteContent @@ -37,252 +30,61 @@ export interface SpriteItemProps extends Omit { * 图层元素基础属性, 经过处理后的 spec.SpriteContent.options */ export type SpriteItemOptions = { - startColor: vec4, + startColor: spec.vec4, renderLevel?: spec.RenderLevel, }; /** * 图层元素渲染属性, 经过处理后的 spec.SpriteContent.renderer */ -export interface SpriteItemRenderer extends Required> { - order: number, - mask: number, - texture: Texture, +export interface SpriteItemRenderer extends ItemRenderer { shape?: GeometryFromShape, - anchor?: vec2, - particleOrigin?: spec.ParticleOrigin, -} - -/** - * 图层的渲染属性,用于 Mesh 的合并判断 - */ -export interface SpriteItemRenderInfo { - side: number, - occlusion: boolean, - blending: number, - cachePrefix: string, - mask: number, - maskMode: number, - cacheId: string, - wireframe?: boolean, } export type splitsDataType = [r: number, x: number, y: number, w: number, h: number | undefined][]; const singleSplits: splitsDataType = [[0, 0, 1, 1, undefined]]; -const tempColor: vec4 = [1, 1, 1, 1]; let seed = 0; -export class SpriteColorPlayable extends Playable { - clipData: { colorOverLifetime?: spec.ColorOverLifetime, startColor?: spec.RGBAColorValue }; - colorOverLifetime: { stop: number, color: any }[]; - opacityOverLifetime: ValueGetter; - startColor: spec.RGBAColorValue; - renderColor: vec4 = [1, 1, 1, 1]; - spriteMaterial: Material; - spriteComponent: SpriteComponent; - - override processFrame (context: FrameContext): void { - const boundObject = context.output.getUserData(); - - if (!(boundObject instanceof VFXItem)) { - return; - } - if (!this.spriteComponent) { - this.spriteComponent = boundObject.getComponent(SpriteComponent); - } - if (!this.spriteMaterial) { - this.spriteMaterial = this.spriteComponent.material; - const startColor = this.spriteMaterial.getVector4('_Color'); - - if (startColor) { - this.startColor = startColor.toArray(); - } - } - - this.spriteComponent.setAnimationTime(this.time); - let colorInc = vecFill(tempColor, 1); - let colorChanged; - const life = this.time / boundObject.duration; - - const opacityOverLifetime = this.opacityOverLifetime; - const colorOverLifetime = this.colorOverLifetime; - - if (colorOverLifetime) { - colorInc = getColorFromGradientStops(colorOverLifetime, life, true) as vec4; - colorChanged = true; - } - if (opacityOverLifetime) { - colorInc[3] *= opacityOverLifetime.getValue(life); - colorChanged = true; - } - - if (colorChanged) { - vecMulCombine(this.renderColor, colorInc, this.startColor); - this.spriteMaterial.getVector4('_Color')?.setFromArray(this.renderColor); - } - } - - create (clipData: SpriteColorPlayableAssetData) { - this.clipData = clipData; - const colorOverLifetime = clipData.colorOverLifetime; - - if (colorOverLifetime) { - this.opacityOverLifetime = createValueGetter(colorOverLifetime.opacity ?? 1); - if (colorOverLifetime.color && colorOverLifetime.color[0] === spec.ValueType.GRADIENT_COLOR) { - this.colorOverLifetime = colorStopsFromGradient(colorOverLifetime.color[1]); - } - } - - return this; - } -} - @effectsClass('SpriteColorPlayableAsset') export class SpriteColorPlayableAsset extends PlayableAsset { - data: SpriteColorPlayableAssetData; + data: ColorPlayableAssetData; override createPlayable (graph: PlayableGraph): Playable { - const spriteColorPlayable = new SpriteColorPlayable(graph); + const spriteColorPlayable = new ColorPlayable(graph); spriteColorPlayable.create(this.data); return spriteColorPlayable; } - override fromData (data: SpriteColorPlayableAssetData): void { + override fromData (data: ColorPlayableAssetData): void { this.data = data; } } -export interface SpriteColorPlayableAssetData extends spec.EffectsObjectData { - colorOverLifetime?: spec.ColorOverLifetime, -} - @effectsClass(spec.DataType.SpriteComponent) -export class SpriteComponent extends RendererComponent { - renderer: SpriteItemRenderer; - interaction?: { behavior: spec.InteractBehavior }; - cachePrefix = '-'; - geoData: { atlasOffset: number[] | spec.TypedArray, index: number[] | spec.TypedArray }; - anchor?: vec2; - +export class SpriteComponent extends BaseRenderComponent { textureSheetAnimation?: spec.TextureSheetAnimation; + splits: splitsDataType = singleSplits; frameAnimationLoop = false; - splits: splitsDataType; - emptyTexture: Texture; - color: vec4 = [1, 1, 1, 1]; - worldMatrix: Matrix4; - geometry: Geometry; /* 要过包含父节点颜色/透明度变化的动画的帧对比 打开这段兼容代码 */ // override colorOverLifetime: { stop: number, color: any }[]; // override opacityOverLifetime: ValueGetter; - /***********************/ - private renderInfo: SpriteItemRenderInfo; - // readonly mesh: Mesh; - private readonly wireframe?: boolean; - private preMultiAlpha: number; - private visible = true; - private isManualTimeSet = false; - private frameAnimationTime = 0; constructor (engine: Engine, props?: SpriteItemProps) { super(engine); this.name = 'MSprite' + seed++; - this.renderer = { - renderMode: spec.RenderMode.BILLBOARD, - blending: spec.BlendingMode.ALPHA, - texture: this.engine.emptyTexture, - occlusion: false, - transparentOcclusion: false, - side: spec.SideMode.DOUBLE, - mask: 0, - maskMode: spec.MaskMode.NONE, - order: 0, - }; - this.emptyTexture = this.engine.emptyTexture; - this.splits = singleSplits; - this.renderInfo = getImageItemRenderInfo(this); - - const geometry = this.createGeometry(glContext.TRIANGLES); - const material = this.createMaterial(this.renderInfo, 2); - - this.worldMatrix = Matrix4.fromIdentity(); - this.material = material; - this.geometry = geometry; - this.material.setVector4('_Color', new Vector4().setFromArray([1, 1, 1, 1])); - this.material.setVector4('_TexOffset', new Vector4().setFromArray([0, 0, 1, 1])); + this.geometry = this.createGeometry(glContext.TRIANGLES); this.setItem(); - if (props) { this.fromData(props); } } - /** - * 设置当前 Mesh 的可见性。 - * @param visible - true:可见,false:不可见 - */ - setVisible (visible: boolean) { - this.visible = visible; - } - /** - * 获取当前 Mesh 的可见性。 - */ - getVisible (): boolean { - return this.visible; - } - - /** - * 设置当前图层的颜色 - * > Tips: 透明度也属于颜色的一部分,当有透明度/颜色 K 帧变化时,该 API 会失效 - * @since 2.0.0 - * @param color - 颜色值 - */ - setColor (color: vec4) { - this.color = color; - this.material.setVector4('_Color', new Vector4().setFromArray(color)); - } - - /** - * 设置当前 Mesh 的纹理 - * @since 2.0.0 - * @param texture - 纹理对象 - */ - setTexture (texture: Texture) { - this.renderer.texture = texture; - this.material.setTexture('uSampler0', texture); - } - - /** - * @internal - */ - setAnimationTime (time: number) { - this.frameAnimationTime = time; - this.isManualTimeSet = true; - } - - override render (renderer: Renderer) { - if (!this.getVisible()) { - return; - } - const material = this.material; - const geo = this.geometry; - - if (renderer.renderingData.currentFrame.globalUniforms) { - renderer.setGlobalMatrix('effects_ObjectToWorld', this.transform.getWorldMatrix()); - } - this.material.setVector2('_Size', this.transform.size); - renderer.drawGeometry(geo, material); - } - - override onStart (): void { - this.item.getHitTestParams = this.getHitTestParams; - } - override onUpdate (dt: number): void { if (!this.isManualTimeSet) { this.frameAnimationTime += dt / 1000; @@ -353,72 +155,7 @@ export class SpriteComponent extends RendererComponent { } } - private getItemInitData () { - this.geoData = this.getItemGeometryData(); - - const { index, atlasOffset } = this.geoData; - const idxCount = index.length; - // @ts-expect-error - const indexData: number[] = this.wireframe ? new Uint8Array([0, 1, 1, 3, 2, 3, 2, 0]) : new index.constructor(idxCount); - - if (!this.wireframe) { - for (let i = 0; i < idxCount; i++) { - indexData[i] = 0 + index[i]; - } - } - - return { - atlasOffset, - index: indexData, - }; - } - - private setItem () { - const textures: Texture[] = []; - let texture = this.renderer.texture; - - if (texture) { - addItem(textures, texture); - } - texture = this.renderer.texture; - const data = this.getItemInitData(); - - const renderer = this.renderer; - const texParams = this.material.getVector4('_TexParams'); - - if (texParams) { - texParams.x = renderer.occlusion ? +(renderer.transparentOcclusion) : 1; - texParams.y = +this.preMultiAlpha; - texParams.z = renderer.renderMode; - } - - const attributes = { - atlasOffset: new Float32Array(data.atlasOffset.length), - index: new Uint16Array(data.index.length), - }; - - attributes.atlasOffset.set(data.atlasOffset); - attributes.index.set(data.index); - const { material, geometry } = this; - const indexData = attributes.index; - - geometry.setIndexData(indexData); - geometry.setAttributeData('atlasOffset', attributes.atlasOffset); - geometry.setDrawCount(data.index.length); - for (let i = 0; i < textures.length; i++) { - const texture = textures[i]; - - material.setTexture('uSampler' + i, texture); - } - // FIXME: 内存泄漏的临时方案,后面再调整 - const emptyTexture = this.emptyTexture; - - for (let k = textures.length; k < maxSpriteMeshItemCount; k++) { - material.setTexture('uSampler' + k, emptyTexture); - } - } - - private createGeometry (mode: GeometryDrawMode) { + override createGeometry (mode: GeometryDrawMode) { const maxVertex = 12 * this.splits.length; return Geometry.create(this.engine, { @@ -448,51 +185,10 @@ export class SpriteComponent extends RendererComponent { } - private createMaterial (renderInfo: SpriteItemRenderInfo, count: number): Material { - const { side, occlusion, blending, maskMode, mask } = renderInfo; - const materialProps: MaterialProps = { - shader: spriteMeshShaderFromRenderInfo(renderInfo, count, 1), - }; - - this.preMultiAlpha = getPreMultiAlpha(blending); - - const material = Material.create(this.engine, materialProps); - - const states = { - side, - blending: true, - blendMode: blending, - mask, - maskMode, - depthTest: true, - depthMask: occlusion, - }; - - material.blending = states.blending; - material.stencilRef = states.mask !== undefined ? [states.mask, states.mask] : undefined; - material.depthTest = states.depthTest; - material.depthMask = states.depthMask; - states.blending && setBlendMode(material, states.blendMode); - setMaskMode(material, states.maskMode); - setSideMode(material, states.side); - - material.shader.shaderData.properties = 'uSampler0("uSampler0",2D) = "white" {}'; - if (!material.hasUniform('_Color')) { - material.setVector4('_Color', new Vector4(0, 0, 0, 1)); - } - if (!material.hasUniform('_TexOffset')) { - material.setVector4('_TexOffset', new Vector4()); - } - if (!material.hasUniform('_TexParams')) { - material.setVector4('_TexParams', new Vector4()); - } - - return material; - } - - private getItemGeometryData () { - const { splits, renderer, textureSheetAnimation } = this; + override getItemGeometryData () { + const { splits, textureSheetAnimation } = this; const sx = 1, sy = 1; + const renderer = this.renderer as SpriteItemRenderer; if (renderer.shape) { const { index = [], aPoint = [] } = renderer.shape; @@ -510,7 +206,7 @@ export class SpriteComponent extends RendererComponent { this.geometry.setAttributeData('aPos', new Float32Array(position)); return { - index, + index: index as number[], atlasOffset, }; } @@ -563,64 +259,11 @@ export class SpriteComponent extends RendererComponent { index.push(base, 1 + base, 2 + base, 2 + base, 1 + base, 3 + base); } } - this.geometry.setAttributeData('aPos', new Float32Array(position)); return { index, atlasOffset }; } - getTextures (): Texture[] { - const ret = []; - const tex = this.renderer.texture; - - if (tex) { - ret.push(tex); - } - - return ret; - } - - /** - * 获取图层包围盒的类型和世界坐标 - * @returns - */ - getBoundingBox (): BoundingBoxTriangle | void { - if (!this.item) { - return; - } - const worldMatrix = this.transform.getWorldMatrix(); - const triangles = trianglesFromRect(Vector3.ZERO, 0.5 * this.transform.size.x, 0.5 * this.transform.size.y); - - triangles.forEach(triangle => { - worldMatrix.transformPoint(triangle.p0 as Vector3); - worldMatrix.transformPoint(triangle.p1 as Vector3); - worldMatrix.transformPoint(triangle.p2 as Vector3); - }); - - return { - type: HitTestType.triangle, - area: triangles, - }; - } - - getHitTestParams = (force?: boolean): HitTestTriangleParams | void => { - const ui = this.interaction; - - if ((force || ui)) { - const area = this.getBoundingBox(); - - if (area) { - return { - behavior: this.interaction?.behavior || 0, - type: area.type, - triangles: area.area, - backfaceCulling: this.renderer.side === spec.SideMode.FRONT, - }; - } - } - }; - - // TODO: [1.31] @十弦 https://github.com/galacean/effects-runtime/commit/fe8736540b9a461d8e96658f4d755ff8089a263b#diff-a3618f4527c5fe6e842f20d67d5c82984568502c6bf6fdfcbd24f69e2894ca90 override fromData (data: SpriteItemProps): void { super.fromData(data); @@ -628,8 +271,7 @@ export class SpriteComponent extends RendererComponent { let renderer = data.renderer; if (!renderer) { - //@ts-expect-error - renderer = {}; + renderer = {} as SpriteItemProps['renderer']; } this.interaction = interaction; @@ -637,14 +279,14 @@ export class SpriteComponent extends RendererComponent { renderMode: renderer.renderMode ?? spec.RenderMode.BILLBOARD, blending: renderer.blending ?? spec.BlendingMode.ALPHA, texture: renderer.texture ?? this.engine.emptyTexture, - occlusion: !!(renderer.occlusion), - transparentOcclusion: !!(renderer.transparentOcclusion) || (renderer.maskMode === spec.MaskMode.MASK), + occlusion: !!renderer.occlusion, + transparentOcclusion: !!renderer.transparentOcclusion || (renderer.maskMode === spec.MaskMode.MASK), side: renderer.side ?? spec.SideMode.DOUBLE, shape: renderer.shape, mask: renderer.mask ?? 0, maskMode: renderer.maskMode ?? spec.MaskMode.NONE, order: listIndex, - }; + } as SpriteItemRenderer; this.emptyTexture = this.engine.emptyTexture; this.splits = data.splits || singleSplits; @@ -664,8 +306,4 @@ export class SpriteComponent extends RendererComponent { this.material.setVector4('_TexOffset', new Vector4().setFromArray([0, 0, 1, 1])); this.setItem(); } - - override toData (): void { - super.toData(); - } } diff --git a/packages/effects-core/src/plugins/sprite/sprite-loader.ts b/packages/effects-core/src/plugins/sprite/sprite-loader.ts index 28899af6..d0144b84 100644 --- a/packages/effects-core/src/plugins/sprite/sprite-loader.ts +++ b/packages/effects-core/src/plugins/sprite/sprite-loader.ts @@ -2,7 +2,7 @@ import type * as spec from '@galacean/effects-specification'; import type { PrecompileOptions } from '../../plugin-system'; import type { Renderer } from '../../render'; import { createCopyShader } from '../../render'; -import { AbstractPlugin } from '../index'; +import { AbstractPlugin } from '../plugin'; import { maxSpriteMeshItemCount, spriteMeshShaderFromRenderInfo, spriteMeshShaderIdFromRenderInfo } from './sprite-mesh'; const defRenderInfo = { diff --git a/packages/effects-core/src/plugins/sprite/sprite-mesh.ts b/packages/effects-core/src/plugins/sprite/sprite-mesh.ts index 7a279e89..512ba78f 100644 --- a/packages/effects-core/src/plugins/sprite/sprite-mesh.ts +++ b/packages/effects-core/src/plugins/sprite/sprite-mesh.ts @@ -5,7 +5,7 @@ import type { GPUCapabilityDetail, ShaderMacros, SharedShaderWithSource } from ' import { GLSLVersion } from '../../render'; import { itemFrag, itemFrameFrag, itemVert } from '../../shader'; import type { Transform } from '../../transform'; -import type { SpriteComponent, SpriteItemRenderInfo } from './sprite-item'; +import type { ItemRenderInfo } from '../../components'; export type SpriteRenderData = { life: number, @@ -35,23 +35,6 @@ export function setSpriteMeshMaxItemCountByGPU (gpuCapability: GPUCapabilityDeta } } -export function getImageItemRenderInfo (item: SpriteComponent): SpriteItemRenderInfo { - const { renderer } = item; - const { blending, side, occlusion, mask, maskMode, order } = renderer; - const blendingCache = +blending; - const cachePrefix = item.cachePrefix || '-'; - - return { - side, - occlusion, - blending, - mask, - maskMode, - cachePrefix, - cacheId: `${cachePrefix}.${+side}+${+occlusion}+${blendingCache}+${order}+${maskMode}.${mask}`, - }; -} - export function spriteMeshShaderFromFilter ( level: number, options?: { wireframe?: boolean, env?: string }, @@ -72,11 +55,11 @@ export function spriteMeshShaderFromFilter ( }; } -export function spriteMeshShaderIdFromRenderInfo (renderInfo: SpriteItemRenderInfo, count: number): string { +export function spriteMeshShaderIdFromRenderInfo (renderInfo: ItemRenderInfo, count: number): string { return `${renderInfo.cachePrefix}_effects_sprite_${count}`; } -export function spriteMeshShaderFromRenderInfo (renderInfo: SpriteItemRenderInfo, count: number, level: number, env?: string): SharedShaderWithSource { +export function spriteMeshShaderFromRenderInfo (renderInfo: ItemRenderInfo, count: number, level: number, env?: string): SharedShaderWithSource { const { wireframe } = renderInfo; const shader = spriteMeshShaderFromFilter(level, { wireframe, diff --git a/packages/effects-core/src/plugins/text/text-item.ts b/packages/effects-core/src/plugins/text/text-item.ts index 45b5c997..cbbc9da9 100644 --- a/packages/effects-core/src/plugins/text/text-item.ts +++ b/packages/effects-core/src/plugins/text/text-item.ts @@ -2,8 +2,6 @@ import * as spec from '@galacean/effects-specification'; import type { Engine } from '../../engine'; import { Texture } from '../../texture'; -import type { SpriteItemProps } from '../sprite/sprite-item'; -import { SpriteComponent } from '../sprite/sprite-item'; import { TextLayout } from './text-layout'; import { TextStyle } from './text-style'; import { glContext } from '../../gl'; @@ -12,6 +10,18 @@ import { canvasPool } from '../../canvas-pool'; import { applyMixins, isValidFontFamily } from '../../utils'; import type { Material } from '../../material'; import type { VFXItem } from '../../vfx-item'; +import { BaseRenderComponent } from '../../components'; + +/** + * 用于创建 textItem 的数据类型, 经过处理后的 spec.TextContentOptions + */ +export interface TextItemProps extends Omit { + listIndex?: number, + renderer: { + mask: number, + texture: Texture, + } & Omit, +} export const DEFAULT_FONTS = [ 'serif', @@ -22,7 +32,7 @@ export const DEFAULT_FONTS = [ interface CharInfo { /** - * 段落y值 + * 段落 y 值 */ y: number, /** @@ -38,11 +48,13 @@ interface CharInfo { export interface TextComponent extends TextComponentBase { } +let seed = 0; + /** * @since 2.0.0 */ @effectsClass(spec.DataType.TextComponent) -export class TextComponent extends SpriteComponent { +export class TextComponent extends BaseRenderComponent { isDirty = true; /** @@ -50,12 +62,20 @@ export class TextComponent extends SpriteComponent { */ lineCount = 0; - constructor (engine: Engine, props?: spec.TextContent) { - super(engine, props as unknown as SpriteItemProps); + constructor (engine: Engine, props?: TextItemProps) { + super(engine); + + this.name = 'MText' + seed++; + this.geometry = this.createGeometry(glContext.TRIANGLES); + + if (props) { + this.fromData(props); + } this.canvas = canvasPool.getCanvas(); canvasPool.saveCanvas(this.canvas); this.context = this.canvas.getContext('2d', { willReadFrequently: true }); + this.setItem(); if (!props) { return; @@ -72,10 +92,30 @@ export class TextComponent extends SpriteComponent { this.updateTexture(); } - override fromData (data: SpriteItemProps): void { + override fromData (data: TextItemProps): void { super.fromData(data); - const options = data.options as spec.TextContentOptions; + const { interaction, options, listIndex = 0 } = data; + let renderer = data.renderer; + + if (!renderer) { + renderer = {} as TextItemProps['renderer']; + } + this.interaction = interaction; + + this.renderer = { + renderMode: renderer.renderMode ?? spec.RenderMode.BILLBOARD, + blending: renderer.blending ?? spec.BlendingMode.ALPHA, + texture: renderer.texture ?? this.engine.emptyTexture, + occlusion: !!renderer.occlusion, + transparentOcclusion: !!renderer.transparentOcclusion || (renderer.maskMode === spec.MaskMode.MASK), + side: renderer.side ?? spec.SideMode.DOUBLE, + mask: renderer.mask ?? 0, + maskMode: renderer.maskMode ?? spec.MaskMode.NONE, + order: listIndex, + }; + + this.interaction = interaction; this.updateWithOptions(options); // Text this.updateTexture(); diff --git a/packages/effects-core/src/render/mesh.ts b/packages/effects-core/src/render/mesh.ts index c1f89d9a..6e40214c 100644 --- a/packages/effects-core/src/render/mesh.ts +++ b/packages/effects-core/src/render/mesh.ts @@ -4,7 +4,7 @@ import type { Material, MaterialDestroyOptions } from '../material'; import type { Geometry, Renderer } from '../render'; import type { Disposable } from '../utils'; import { DestroyOptions } from '../utils'; -import { RendererComponent } from '../components/renderer-component'; +import { RendererComponent } from '../components'; export interface MeshOptionsBase { material: Material, diff --git a/packages/effects-core/src/render/renderer.ts b/packages/effects-core/src/render/renderer.ts index 6164f8a9..fc96b90e 100644 --- a/packages/effects-core/src/render/renderer.ts +++ b/packages/effects-core/src/render/renderer.ts @@ -1,5 +1,5 @@ import type { Matrix4, Vector4 } from '@galacean/effects-math/es/core/index'; -import type { RendererComponent } from '../components/renderer-component'; +import type { RendererComponent } from '../components'; import type { Engine } from '../engine'; import type { Material } from '../material'; import type { LostHandler, RestoreHandler } from '../utils'; diff --git a/packages/effects-core/src/texture/texture.ts b/packages/effects-core/src/texture/texture.ts index d86ae404..cf9155ad 100644 --- a/packages/effects-core/src/texture/texture.ts +++ b/packages/effects-core/src/texture/texture.ts @@ -4,7 +4,7 @@ import type { TextureFactorySourceFrom, TextureSourceOptions, TextureDataType, T import { glContext } from '../gl'; import type { Engine } from '../engine'; import { EffectsObject } from '../effects-object'; -import { loadImage } from '../downloader'; +import { loadImage, loadVideo } from '../downloader'; import { generateGUID } from '../utils'; let seed = 1; @@ -47,7 +47,11 @@ export abstract class Texture extends EffectsObject { * @param url - 要创建的 Texture URL * @since 2.0.0 */ - static async fromImage (url: string, engine: Engine, options?: TextureOptionsBase): Promise { + static async fromImage ( + url: string, + engine: Engine, + options?: TextureOptionsBase, + ): Promise { const image = await loadImage(url); const texture = Texture.create(engine, { @@ -63,6 +67,34 @@ export abstract class Texture extends EffectsObject { return texture; } + + /** + * 通过视频 URL 创建 Texture 对象。 + * @param url - 要创建的 Texture URL + * @param engine - 引擎对象 + * @param options - 可选的 Texture 选项 + * @since 2.1.0 + * @returns + */ + static async fromVideo ( + url: string, + engine: Engine, + options?: TextureOptionsBase, + ): Promise { + const video = await loadVideo(url); + const texture = Texture.create(engine, { + sourceType: TextureSourceType.video, + video, + id: generateGUID(), + flipY: true, + ...options, + }); + + texture.initialize(); + + return texture; + } + /** * 通过数据创建 Texture 对象。 * @param data - 要创建的 Texture 数据 diff --git a/packages/effects-core/src/utils/color.ts b/packages/effects-core/src/utils/color.ts index 16bf5559..6ba1056f 100644 --- a/packages/effects-core/src/utils/color.ts +++ b/packages/effects-core/src/utils/color.ts @@ -1,14 +1,12 @@ import { isString } from './index'; -export type color = [r: number, g: number, b: number, a: number]; - export interface ColorStop { stop: number, - color: color | number[], + color: number[], } -export function colorToArr (hex: string | number[], normalized?: boolean): color { - let ret: color = [0, 0, 0, 0]; +export function colorToArr (hex: string | number[], normalized?: boolean): number[] { + let ret: number[] = [0, 0, 0, 0]; if (isString(hex)) { hex = hex.replace(/[\s\t\r\n]/g, ''); @@ -37,9 +35,9 @@ export function colorToArr (hex: string | number[], normalized?: boolean): color return ret; } -export function getColorFromGradientStops (stops: ColorStop[], key: number, normalize?: boolean): color | number[] { +export function getColorFromGradientStops (stops: ColorStop[], key: number, normalize?: boolean): number[] { if (stops.length) { - let color: number[] | color | undefined; + let color: number[] | undefined; for (let j = 1; j <= stops.length - 1; j++) { const s0 = stops[j - 1]; @@ -101,8 +99,8 @@ export function colorStopsFromGradient (gradient: number[][] | Record) { - this.material.depthTest = !!(value); + this.material.depthTest = !!value; } /** @@ -214,7 +214,7 @@ export class ThreeMaterial extends Material { return this.material.depthWrite; } override set depthMask (value: UndefinedAble) { - this.material.depthWrite = !!(value); + this.material.depthWrite = !!value; } /** @@ -236,7 +236,7 @@ export class ThreeMaterial extends Material { return this.material.polygonOffset; } override set polygonOffsetFill (value: UndefinedAble) { - this.material.polygonOffset = !!(value); + this.material.polygonOffset = !!value; } /** @@ -261,7 +261,7 @@ export class ThreeMaterial extends Material { return this.material.alphaToCoverage; } override set sampleAlphaToCoverage (value: UndefinedAble) { - this.material.alphaToCoverage = !!(value); + this.material.alphaToCoverage = !!value; } /** @@ -271,7 +271,7 @@ export class ThreeMaterial extends Material { return this.material.stencilWrite; } override set stencilTest (value: UndefinedAble) { - this.material.stencilWrite = !!(value); + this.material.stencilWrite = !!value; } /** diff --git a/packages/effects-threejs/src/three-texture.ts b/packages/effects-threejs/src/three-texture.ts index c868d8b6..4bb4fe7f 100644 --- a/packages/effects-threejs/src/three-texture.ts +++ b/packages/effects-threejs/src/three-texture.ts @@ -224,7 +224,7 @@ export class ThreeTexture extends Texture { this.width = this.height = 1; } if (texture) { - texture.flipY = !!(flipY); + texture.flipY = !!flipY; return texture; } diff --git a/packages/effects-webgl/src/gl-material.ts b/packages/effects-webgl/src/gl-material.ts index 87bb83ae..70e8cdc8 100644 --- a/packages/effects-webgl/src/gl-material.ts +++ b/packages/effects-webgl/src/gl-material.ts @@ -234,7 +234,7 @@ export class GLMaterial extends Material { // TODO 待废弃 兼容 model/spine 插件 改造后可移除 createMaterialStates (states: MaterialStates): void { - this.sampleAlphaToCoverage = !!(states.sampleAlphaToCoverage); + this.sampleAlphaToCoverage = !!states.sampleAlphaToCoverage; this.depthTest = states.depthTest; this.depthMask = states.depthMask; this.depthRange = states.depthRange; diff --git a/packages/effects-webgl/src/gl-texture.ts b/packages/effects-webgl/src/gl-texture.ts index ab13018f..d8bc394a 100644 --- a/packages/effects-webgl/src/gl-texture.ts +++ b/packages/effects-webgl/src/gl-texture.ts @@ -452,11 +452,7 @@ export class GLTexture extends Texture implements Disposable, RestoreHandler { this.source.video && this.initialized ) { - const video = this.source.video; - if (video.paused) { - await video.play(); - } this.update({ video: this.source.video }); return true; diff --git a/plugin-packages/multimedia/LICENSE b/plugin-packages/multimedia/LICENSE new file mode 100644 index 00000000..93808239 --- /dev/null +++ b/plugin-packages/multimedia/LICENSE @@ -0,0 +1,22 @@ +MIT LICENSE + +Copyright (c) 2019-present Ant Group Co., Ltd. https://www.antgroup.com/ + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/plugin-packages/multimedia/README.md b/plugin-packages/multimedia/README.md new file mode 100644 index 00000000..6ecf560a --- /dev/null +++ b/plugin-packages/multimedia/README.md @@ -0,0 +1,25 @@ +# Galacean Effects Multimedia Plugin + +## Usage + +### Simple Import + +``` ts +import { Player } from '@galacean/effects'; +import '@galacean/effects-plugin-multimedia'; +``` + +## Development + +### Getting Started + +``` bash +# demo +pnpm --filter @galacean/effects-plugin-multimedia dev +``` + +> [Open in browser](http://localhost:8081/demo/) + +## Frame Comparison Testing + +> [Open in browser](http://localhost:8081/test/) diff --git a/plugin-packages/multimedia/demo/audio.html b/plugin-packages/multimedia/demo/audio.html new file mode 100644 index 00000000..36f46bf7 --- /dev/null +++ b/plugin-packages/multimedia/demo/audio.html @@ -0,0 +1,32 @@ + + + + + + audio 使用 - Multimedia 插件 - demo + + + + +
+
+ +
+
+ +
+
+ + +
+
+
+ + + diff --git a/plugin-packages/multimedia/demo/index.html b/plugin-packages/multimedia/demo/index.html new file mode 100644 index 00000000..9286dc7f --- /dev/null +++ b/plugin-packages/multimedia/demo/index.html @@ -0,0 +1,19 @@ + + + + +Multimedia 插件 - demo + + + + + +
+
Multimedia 插件 Demo
+ +
+ + diff --git a/plugin-packages/multimedia/demo/src/audio.ts b/plugin-packages/multimedia/demo/src/audio.ts new file mode 100644 index 00000000..50df352a --- /dev/null +++ b/plugin-packages/multimedia/demo/src/audio.ts @@ -0,0 +1,351 @@ +import { Asset, Player, spec } from '@galacean/effects'; +import '@galacean/effects-plugin-multimedia'; +import { AudioComponent, checkAutoplayPermission, loadAudio } from '@galacean/effects-plugin-multimedia'; + +const duration = 5.0; +const endBehavior = spec.EndBehavior.destroy; +const delay = 3; +const json = { + playerVersion: { web: '2.0.4', native: '0.0.1.202311221223' }, + images: [ + { + url: 'https://mdn.alipayobjects.com/mars/afts/img/A*t0FNRqje9OcAAAAAAAAAAAAADlB4AQ/original', + webp: 'https://mdn.alipayobjects.com/mars/afts/img/A*3kxITrXVFsMAAAAAAAAAAAAADlB4AQ/original', + id: '8fe7723c56254da9b2cd57a4589d4329', + renderLevel: 'B+', + }, + ], + fonts: [], + version: '3.0', + shapes: [], + plugins: ['video', 'audio'], + type: 'ge', + compositions: [ + { + id: '5', + name: 'comp1', + duration: 10, + startTime: 0, + endBehavior: 2, + previewSize: [750, 1624], + items: [ + { id: '14b3d069cbad4cbd81d0a8731cc4aba7' }, + { id: '147e873c89b34c6f96108ccc4d6e6f83' }, + { id: '8b526e86ce154031a76f9176e7224f89' }, + ], + camera: { fov: 60, far: 40, near: 0.1, clipMode: 1, position: [0, 0, 8], rotation: [0, 0, 0] }, + sceneBindings: [ + { key: { id: 'b2bd20ced8044fa8a1fd39149a3271d5' }, value: { id: '14b3d069cbad4cbd81d0a8731cc4aba7' } }, + { key: { id: 'fbc814afc3014b9fa1ddc416c87036d8' }, value: { id: '147e873c89b34c6f96108ccc4d6e6f83' } }, + { key: { id: '54f5ac560cef4c82a40720fe588dfcfd' }, value: { id: '8b526e86ce154031a76f9176e7224f89' } }, + ], + timelineAsset: { id: '28e2d15bfce443258bad609ca56fbb68' }, + }, + ], + components: [ + { + id: '2d6ad26344fa4f58af2ca2bc6b63818d', + item: { id: '14b3d069cbad4cbd81d0a8731cc4aba7' }, + dataType: 'VideoComponent', + options: { + startColor: [1, 1, 1, 1], + video: { + id: 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb', + }, + }, + renderer: { renderMode: 1, texture: { id: 'd546cda394bb484d9bf4af217184e94e' } }, + splits: [[0, 0, 1, 1, 0]], + }, + { + id: '61b52540d9614a8da88c7a4a439b57f3', + item: { id: '147e873c89b34c6f96108ccc4d6e6f83' }, + dataType: 'AudioComponent', + options: { + audio: { + id: 'cccccccccccccccccccccccccccccccc', + }, + }, + }, + { + id: 'fe5ad4a8a1f74530a1bfb6c914019608', + item: { id: '8b526e86ce154031a76f9176e7224f89' }, + dataType: 'ParticleSystem', + shape: { type: 1, radius: 1, arc: 360, arcMode: 0, alignSpeedDirection: false, shape: 'Sphere' }, + renderer: { renderMode: 1, anchor: [0, 0] }, + emission: { rateOverTime: [0, 5] }, + options: { + maxCount: 10, + startLifetime: [0, 1.2], + startDelay: [0, 0], + particleFollowParent: false, + start3DSize: false, + startRotationZ: [0, 0], + startColor: [8, [1, 1, 1, 1]], + startSize: [0, 0.2], + sizeAspect: [0, 1], + }, + positionOverLifetime: { startSpeed: [0, 1], gravity: [0, 0, 0], gravityOverLifetime: [0, 1] }, + }, + ], + geometries: [], + materials: [], + items: [ + { + id: '14b3d069cbad4cbd81d0a8731cc4aba7', + name: 'video', + duration: 5, + type: '1', + visible: true, + endBehavior: 5, + delay: 0, + renderLevel: 'B+', + components: [{ id: '2d6ad26344fa4f58af2ca2bc6b63818d' }], + transform: { + position: { x: 0, y: 0, z: 0 }, + eulerHint: { x: 0, y: 0, z: 0 }, + anchor: { x: 0, y: 0 }, + size: { x: 3.1475, y: 3.1475 }, + scale: { x: 1, y: 1, z: 1 }, + }, + dataType: 'VFXItemData', + }, + { + id: '147e873c89b34c6f96108ccc4d6e6f83', + name: 'audio', + duration, + type: '1', + visible: true, + endBehavior, + delay, + renderLevel: 'B+', + components: [{ id: '61b52540d9614a8da88c7a4a439b57f3' }], + transform: { + position: { x: 0, y: 4.6765, z: 0 }, + eulerHint: { x: 0, y: 0, z: 0 }, + anchor: { x: 0, y: 0 }, + size: { x: 3.1492, y: 3.1492 }, + scale: { x: 1, y: 1, z: 1 }, + }, + dataType: 'VFXItemData', + }, + { + id: '8b526e86ce154031a76f9176e7224f89', + name: 'particle_2', + duration: 5, + type: '2', + visible: true, + endBehavior: 4, + delay: 0, + renderLevel: 'B+', + content: { + dataType: 'ParticleSystem', + shape: { type: 1, radius: 1, arc: 360, arcMode: 0, alignSpeedDirection: false, shape: 'Sphere' }, + renderer: { renderMode: 1, anchor: [0, 0] }, + emission: { rateOverTime: [0, 5] }, + options: { + maxCount: 10, + startLifetime: [0, 1.2], + startDelay: [0, 0], + particleFollowParent: false, + start3DSize: false, + startRotationZ: [0, 0], + startColor: [8, [1, 1, 1, 1]], + startSize: [0, 0.2], + sizeAspect: [0, 1], + }, + positionOverLifetime: { startSpeed: [0, 1], gravity: [0, 0, 0], gravityOverLifetime: [0, 1] }, + }, + components: [{ id: 'fe5ad4a8a1f74530a1bfb6c914019608' }], + transform: { position: { x: 0, y: 0, z: 0 }, eulerHint: { x: 0, y: 0, z: 0 }, scale: { x: 1, y: 1, z: 1 } }, + dataType: 'VFXItemData', + }, + ], + videos: [ + { + url: 'https://gw.alipayobjects.com/v/huamei_s9rwo4/afts/video/A*pud9Q7-6P7QAAAAAAAAAAAAADiqKAQ', + id: 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb', + renderLevel: 'B+', + }, + ], + audios: [ + { + url: 'https://mdn.alipayobjects.com/huamei_s9rwo4/afts/file/A*zERYT5qS-7kAAAAAAAAAAAAADiqKAQ', + id: 'cccccccccccccccccccccccccccccccc', + renderLevel: 'B+', + }, + ], + shaders: [], + bins: [], + textures: [ + { id: 'd546cda394bb484d9bf4af217184e94e', source: { id: 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb' }, flipY: true }, + ], + animations: [], + miscs: [ + { + id: '28e2d15bfce443258bad609ca56fbb68', + dataType: 'TimelineAsset', + tracks: [ + { id: 'b2bd20ced8044fa8a1fd39149a3271d5' }, + { id: 'fbc814afc3014b9fa1ddc416c87036d8' }, + { id: '54f5ac560cef4c82a40720fe588dfcfd' }, + ], + }, + { id: '3c2ceabd3f6a47b8bf0710d1e3906642', dataType: 'ActivationPlayableAsset' }, + { + id: '878aa596a1ca474189dc14c4e5b472e8', + dataType: 'TransformPlayableAsset', + positionOverLifetime: { + path: [ + 22, + [ + [ + [4, [0, -1]], + [4, [0.992, 0]], + ], + [ + [-3.524964052019925, 0, 0], + [0, 0, 0], + ], + [ + [-2.3499760346799503, 0, 0], + [-1.1749880173399756, 0, 0], + ], + ], + ], + }, + }, + { id: 'eb2ef6afc750499a93c11c0fb9ba04e3', dataType: 'SpriteColorPlayableAsset', startColor: [1, 1, 1, 1] }, + { + id: '951dcf1cd08d40909a2cfbb2e4860886', + dataType: 'ActivationTrack', + children: [], + clips: [{ start: 0, duration: 5, endBehavior: 5, asset: { id: '3c2ceabd3f6a47b8bf0710d1e3906642' } }], + }, + { + id: '21abdd82324d4754b8fc3737846c4c88', + dataType: 'TransformTrack', + children: [], + clips: [{ start: 0, duration: 5, endBehavior: 5, asset: { id: '878aa596a1ca474189dc14c4e5b472e8' } }], + }, + { + id: '0e6cbef7789845bcb11ca5b9ea233011', + dataType: 'SpriteColorTrack', + children: [], + clips: [{ start: 0, duration: 5, endBehavior: 5, asset: { id: 'eb2ef6afc750499a93c11c0fb9ba04e3' } }], + }, + { + id: 'b2bd20ced8044fa8a1fd39149a3271d5', + dataType: 'ObjectBindingTrack', + children: [ + { id: '951dcf1cd08d40909a2cfbb2e4860886' }, + { id: '21abdd82324d4754b8fc3737846c4c88' }, + { id: '0e6cbef7789845bcb11ca5b9ea233011' }, + ], + clips: [], + }, + { id: '0455d9dd0c08438bacae2a1f389fd1b9', dataType: 'ActivationPlayableAsset' }, + { id: '1447e12aafa04939ab9fa15b9e46362e', dataType: 'TransformPlayableAsset', positionOverLifetime: {} }, + { id: '55167a8ecdf44789bfcd8fdb42f7b8f1', dataType: 'SpriteColorPlayableAsset', startColor: [1, 1, 1, 1] }, + { + id: '1865c395b5ad42a09f4e8f8c9074da35', + dataType: 'ActivationTrack', + children: [], + clips: [{ start: delay, duration, endBehavior, asset: { id: '0455d9dd0c08438bacae2a1f389fd1b9' } }], + }, + { + id: '35f9056d99074eeaa2be2a2ab2f81237', + dataType: 'TransformTrack', + children: [], + clips: [{ start: delay, duration, endBehavior, asset: { id: '1447e12aafa04939ab9fa15b9e46362e' } }], + }, + { + id: '6d04569674bb491ab56114e9a7b88b4b', + dataType: 'SpriteColorTrack', + children: [], + clips: [{ start: delay, duration, endBehavior, asset: { id: '55167a8ecdf44789bfcd8fdb42f7b8f1' } }], + }, + { + id: 'fbc814afc3014b9fa1ddc416c87036d8', + dataType: 'ObjectBindingTrack', + children: [ + { id: '1865c395b5ad42a09f4e8f8c9074da35' }, + { id: '35f9056d99074eeaa2be2a2ab2f81237' }, + { id: '6d04569674bb491ab56114e9a7b88b4b' }, + ], + clips: [], + }, + { id: 'e1fa79bd7e2e448fb7b1902e76d1dd65', dataType: 'ActivationPlayableAsset' }, + { + id: 'ce386d52216b4d2b83fd94f770553715', + dataType: 'ActivationTrack', + children: [], + clips: [{ start: 0, duration: 5, endBehavior: 4, asset: { id: 'e1fa79bd7e2e448fb7b1902e76d1dd65' } }], + }, + { + id: '54f5ac560cef4c82a40720fe588dfcfd', + dataType: 'ObjectBindingTrack', + children: [{ id: 'ce386d52216b4d2b83fd94f770553715' }], + clips: [], + }, + ], + compositionId: '5', +}; +let player: Player; +const container = document.getElementById('J-container'); +const addButton = document.getElementById('J-add'); +const updateButton = document.getElementById('J-update'); +const inputEle = document.getElementById('J-input') as HTMLInputElement; + +(async () => { + try { + player = new Player({ + container, + }); + + await checkAutoplayPermission(); + + await player.loadScene(json); + } catch (e) { + console.error('biz', e); + } +})(); + +addButton?.addEventListener('click', async () => { + const value = inputEle.value; + + if (value) { + const item = player.getCompositionByName('comp1')?.getItemByName('video'); + const audio = await loadAudio(value); + const audioAsset = new Asset(player.renderer.engine); + + audioAsset.data = audio; + + if (!item) { + return; + } + const audioComponent = item.addComponent(AudioComponent); + + audioComponent.item = item; + audioComponent.fromData({ + options: { + //@ts-expect-error + audio: audioAsset, + }, + }); + } +}); + +updateButton?.addEventListener('click', async () => { + const value = inputEle.value; + + if (value) { + const audioComponent = player + .getCompositionByName('comp1') + ?.getItemByName('audio') + ?.getComponent(AudioComponent); + + if (audioComponent) { + audioComponent.setAudioSource(await loadAudio(value)); + } + } +}); diff --git a/plugin-packages/multimedia/demo/src/video.ts b/plugin-packages/multimedia/demo/src/video.ts new file mode 100644 index 00000000..c829761e --- /dev/null +++ b/plugin-packages/multimedia/demo/src/video.ts @@ -0,0 +1,465 @@ +import type { Texture2DSourceOptionsVideo } from '@galacean/effects'; +import { Player, Texture, spec } from '@galacean/effects'; +import '@galacean/effects-plugin-multimedia'; +import { checkAutoplayPermission, VideoComponent } from '@galacean/effects-plugin-multimedia'; + +const json = { + 'playerVersion': { + 'web': '2.0.4', + 'native': '0.0.1.202311221223', + }, + 'images': [ + { + 'url': 'https://mdn.alipayobjects.com/mars/afts/img/A*A-EoQ6SHJBgAAAAAAAAAAAAADlB4AQ/original', + 'webp': 'https://mdn.alipayobjects.com/mars/afts/img/A*y0ihQrDikLUAAAAAAAAAAAAADlB4AQ/original', + 'id': 'e3b1624a497b4c94bdfc9d4224434a95', + 'renderLevel': 'B+', + }, + ], + 'fonts': [], + 'version': '3.0', + 'shapes': [], + 'plugins': ['video'], + videos: [ + { + url: 'https://gw.alipayobjects.com/v/huamei_s9rwo4/afts/video/A*pud9Q7-6P7QAAAAAAAAAAAAADiqKAQ', + id: 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb', + renderLevel: 'B+', + }, + ], + 'type': 'ge', + 'compositions': [ + { + 'id': '5', + 'name': 'comp1', + 'duration': 10, + 'startTime': 0, + 'endBehavior': 4, + 'previewSize': [750, 1624], + 'items': [ + { + 'id': '14b3d069cbad4cbd81d0a8731cc4aba7', + }, + { + 'id': '8b526e86ce154031a76f9176e7224f89', + }, + ], + 'camera': { + 'fov': 60, + 'far': 40, + 'near': 0.1, + 'clipMode': 1, + 'position': [0, 0, 8], + 'rotation': [0, 0, 0], + }, + 'sceneBindings': [ + { + 'key': { + 'id': '75f0686d9d8341bf90a1711610e1d2fd', + }, + 'value': { + 'id': '14b3d069cbad4cbd81d0a8731cc4aba7', + }, + }, + { + 'key': { + 'id': 'cb6a906e43204b198ecdd323b6a4965e', + }, + 'value': { + 'id': '8b526e86ce154031a76f9176e7224f89', + }, + }, + ], + 'timelineAsset': { + 'id': 'b2cf025ce3b44b5f97759b4679e9598e', + }, + }, + ], + 'components': [ + { + 'id': 'e45437d799364b7cad14b2222669d604', + 'item': { + 'id': '14b3d069cbad4cbd81d0a8731cc4aba7', + }, + 'dataType': 'VideoComponent', + 'options': { + 'startColor': [1, 1, 1, 1], + video: { + id: 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb', + }, + }, + 'renderer': { + 'renderMode': 1, + 'texture': { + 'id': 'b582d21fdd524c4684f1c057b220ddd0', + }, + }, + 'splits': [ + [0, 0, 1, 1, 0], + ], + }, + { + 'id': '295331279c0f472983f949b08cf3838a', + 'item': { + 'id': '8b526e86ce154031a76f9176e7224f89', + }, + 'dataType': 'ParticleSystem', + 'shape': { + 'type': 1, + 'radius': 1, + 'arc': 360, + 'arcMode': 0, + 'alignSpeedDirection': false, + 'shape': 'Sphere', + }, + 'renderer': { + 'renderMode': 1, + 'anchor': [0, 0], + }, + 'emission': { + 'rateOverTime': [0, 5], + }, + 'options': { + 'maxCount': 10, + 'startLifetime': [0, 1.2], + 'startDelay': [0, 0], + 'particleFollowParent': false, + 'start3DSize': false, + 'startRotationZ': [0, 0], + 'startColor': [8, [1, 1, 1, 1], + ], + 'startSize': [0, 0.2], + 'sizeAspect': [0, 1], + }, + 'positionOverLifetime': { + 'startSpeed': [0, 1], + 'gravity': [0, 0, 0], + 'gravityOverLifetime': [0, 1], + }, + }, + ], + 'geometries': [], + 'materials': [], + 'items': [ + { + 'id': '14b3d069cbad4cbd81d0a8731cc4aba7', + 'name': 'video', + 'duration': 5, + 'type': '1', + 'visible': true, + 'endBehavior': 5, + 'delay': 0, + 'renderLevel': 'B+', + 'components': [ + { + 'id': 'e45437d799364b7cad14b2222669d604', + }, + ], + 'transform': { + 'position': { + 'x': 0, + 'y': 0, + 'z': 0, + }, + 'eulerHint': { + 'x': 0, + 'y': 0, + 'z': 0, + }, + 'anchor': { + 'x': 0, + 'y': 0, + }, + 'size': { + 'x': 3.1475, + 'y': 3.1475, + }, + 'scale': { + 'x': 1, + 'y': 1, + 'z': 1, + }, + }, + 'dataType': 'VFXItemData', + }, + { + 'id': '8b526e86ce154031a76f9176e7224f89', + 'name': 'particle_2', + 'duration': 5, + 'type': '2', + 'visible': true, + 'endBehavior': 4, + 'delay': 0, + 'renderLevel': 'B+', + 'content': { + 'dataType': 'ParticleSystem', + 'shape': { + 'type': 1, + 'radius': 1, + 'arc': 360, + 'arcMode': 0, + 'alignSpeedDirection': false, + 'shape': 'Sphere', + }, + 'renderer': { + 'renderMode': 1, + 'anchor': [0, 0], + }, + 'emission': { + 'rateOverTime': [0, 5], + }, + 'options': { + 'maxCount': 10, + 'startLifetime': [0, 1.2], + 'startDelay': [0, 0], + 'particleFollowParent': false, + 'start3DSize': false, + 'startRotationZ': [0, 0], + 'startColor': [8, [1, 1, 1, 1], + ], + 'startSize': [0, 0.2], + 'sizeAspect': [0, 1], + }, + 'positionOverLifetime': { + 'startSpeed': [0, 1], + 'gravity': [0, 0, 0], + 'gravityOverLifetime': [0, 1], + }, + }, + 'components': [ + { + 'id': '295331279c0f472983f949b08cf3838a', + }, + ], + 'transform': { + 'position': { + 'x': 0, + 'y': 0, + 'z': 0, + }, + 'eulerHint': { + 'x': 0, + 'y': 0, + 'z': 0, + }, + 'scale': { + 'x': 1, + 'y': 1, + 'z': 1, + }, + }, + 'dataType': 'VFXItemData', + }, + ], + 'shaders': [], + 'bins': [], + 'textures': [ + { + 'id': 'b582d21fdd524c4684f1c057b220ddd0', + 'source': { + 'id': 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb', + }, + 'flipY': true, + }, + ], + 'animations': [], + 'miscs': [ + { + 'id': 'b2cf025ce3b44b5f97759b4679e9598e', + 'dataType': 'TimelineAsset', + 'tracks': [ + { + 'id': '75f0686d9d8341bf90a1711610e1d2fd', + }, + { + 'id': 'cb6a906e43204b198ecdd323b6a4965e', + }, + ], + }, + { + 'id': 'f1c1e1d9460848fdb035116d63bc2f3f', + 'dataType': 'ActivationPlayableAsset', + }, + { + 'id': 'c94c61ae3c384ba396261f4f93c5b4fb', + 'dataType': 'TransformPlayableAsset', + 'positionOverLifetime': { + 'path': [22, [ + [ + [4, [0, -1], + ], + [4, [0.992, 0], + ], + ], + [ + [-3.52496405201993, 0, 0], + [0, 0, 0], + ], + [ + [-2.34997603467995, 0, 0], + [-1.17498801733998, 0, 0], + ], + ], + ], + }, + }, + { + 'id': '75ae320c918345e994898d378cbc4b5a', + 'dataType': 'SpriteColorPlayableAsset', + 'startColor': [1, 1, 1, 1], + }, + { + 'id': '11111878de5e49e198c062f29f3c6c38', + 'dataType': 'ActivationTrack', + 'children': [], + 'clips': [ + { + 'start': 0, + 'duration': 5, + 'endBehavior': 5, + 'asset': { + 'id': 'f1c1e1d9460848fdb035116d63bc2f3f', + }, + }, + ], + }, + { + 'id': '5ff36d3c30964b83b3ba8f4819f45d93', + 'dataType': 'TransformTrack', + 'children': [], + 'clips': [ + { + 'start': 0, + 'duration': 5, + 'endBehavior': 5, + 'asset': { + 'id': 'c94c61ae3c384ba396261f4f93c5b4fb', + }, + }, + ], + }, + { + 'id': '7695800886c846308d5436acade4c8df', + 'dataType': 'SpriteColorTrack', + 'children': [], + 'clips': [ + { + 'start': 0, + 'duration': 5, + 'endBehavior': 5, + 'asset': { + 'id': '75ae320c918345e994898d378cbc4b5a', + }, + }, + ], + }, + { + 'id': '75f0686d9d8341bf90a1711610e1d2fd', + 'dataType': 'ObjectBindingTrack', + 'children': [ + { + 'id': '11111878de5e49e198c062f29f3c6c38', + }, + { + 'id': '5ff36d3c30964b83b3ba8f4819f45d93', + }, + { + 'id': '7695800886c846308d5436acade4c8df', + }, + ], + 'clips': [], + }, + { + 'id': 'e5a205def3dd43d6b5ab1984962e3a90', + 'dataType': 'ActivationPlayableAsset', + }, + { + 'id': '3f9afeb4198043af90c2c8579f111901', + 'dataType': 'ActivationTrack', + 'children': [], + 'clips': [ + { + 'start': 0, + 'duration': 5, + 'endBehavior': 4, + 'asset': { + 'id': 'e5a205def3dd43d6b5ab1984962e3a90', + }, + }, + ], + }, + { + 'id': 'cb6a906e43204b198ecdd323b6a4965e', + 'dataType': 'ObjectBindingTrack', + 'children': [ + { + 'id': '3f9afeb4198043af90c2c8579f111901', + }, + ], + 'clips': [], + }, + ], + 'compositionId': '5', +}; +let player: Player; +const container = document.getElementById('J-container'); +const addButton = document.getElementById('J-add'); +const updateButton = document.getElementById('J-update'); +const inputEle = document.getElementById('J-input') as HTMLInputElement; + +(async () => { + try { + player = new Player({ + container, + fps: 130, + }); + + await checkAutoplayPermission(); + + await player.loadScene(json, { renderLevel: spec.RenderLevel.B }); + } catch (e) { + console.error('biz', e); + } +})(); + +addButton?.addEventListener('click', async () => { + const value = inputEle.value; + + if (value) { + const item = player.getCompositionByName('comp1')?.getItemByName('video'); + const texture = await Texture.fromVideo(value, player.renderer.engine); + + if (!item) { return; } + + const videoComponent = item.addComponent(VideoComponent); + + item.composition?.textures.push(texture); + videoComponent.item = item; + videoComponent.fromData({ + options: { + video: { + //@ts-expect-error + data: (texture.source as Texture2DSourceOptionsVideo).video, + }, + }, + renderer: { + mask: 0, + texture, + }, + }); + } +}); + +updateButton?.addEventListener('click', async () => { + const value = inputEle.value; + + if (value) { + const videoComponent = player.getCompositionByName('comp1')?.getItemByName('video')?.getComponent(VideoComponent); + + if (videoComponent) { + const texture = await Texture.fromVideo(value, player.renderer.engine); + + videoComponent.setTexture(texture); + } + } +}); diff --git a/plugin-packages/multimedia/demo/tsconfig.json b/plugin-packages/multimedia/demo/tsconfig.json new file mode 100644 index 00000000..0e8412eb --- /dev/null +++ b/plugin-packages/multimedia/demo/tsconfig.json @@ -0,0 +1,6 @@ +{ + "extends": "../../../tsconfig.base.json", + "include": [ + "src" + ] +} diff --git a/plugin-packages/multimedia/demo/video.html b/plugin-packages/multimedia/demo/video.html new file mode 100644 index 00000000..5e9c7519 --- /dev/null +++ b/plugin-packages/multimedia/demo/video.html @@ -0,0 +1,32 @@ + + + + + + 简单使用 - Multimedia 插件 - demo + + + + +
+
+ +
+
+ +
+
+ + +
+
+
+ + + diff --git a/plugin-packages/multimedia/package.json b/plugin-packages/multimedia/package.json new file mode 100644 index 00000000..b5af5965 --- /dev/null +++ b/plugin-packages/multimedia/package.json @@ -0,0 +1,44 @@ +{ + "name": "@galacean/effects-plugin-multimedia", + "version": "2.0.4", + "description": "Galacean Effects player multimedia plugin", + "module": "./dist/index.mjs", + "main": "./dist/index.js", + "browser": "./dist/index.min.js", + "types": "./dist/index.d.ts", + "files": [ + "dist" + ], + "exports": { + ".": { + "import": "./dist/index.mjs", + "require": "./dist/index.js", + "types": "./dist/index.d.ts" + } + }, + "scripts": { + "dev": "vite", + "preview": "concurrently -k \"vite build -w\" \"sleep 6 && vite preview\"", + "prebuild": "pnpm clean", + "build": "pnpm build:declaration && pnpm build:module", + "build:module": "rollup -c", + "build:declaration": "tsc -d --emitDeclarationOnly", + "build:demo": "rimraf dist && vite build", + "clean": "rimraf dist && rimraf \"*+(.tsbuildinfo)\"", + "prepublishOnly": "pnpm build" + }, + "contributors": [ + { + "name": "云垣" + } + ], + "author": "Ant Group CO., Ltd.", + "license": "MIT", + "publishConfig": { + "access": "public", + "registry": "https://registry.npmjs.org" + }, + "devDependencies": { + "@galacean/effects": "workspace:*" + } +} diff --git a/plugin-packages/multimedia/rollup.config.js b/plugin-packages/multimedia/rollup.config.js new file mode 100644 index 00000000..ebda6f50 --- /dev/null +++ b/plugin-packages/multimedia/rollup.config.js @@ -0,0 +1,40 @@ +import { getBanner, getPlugins } from '../../scripts/rollup-config-helper'; + +const pkg = require('./package.json'); +const globals = { + '@galacean/effects': 'ge', +}; +const external = Object.keys(globals); +const banner = getBanner(pkg); +const plugins = getPlugins(pkg, { external }); + +export default () => { + return [{ + input: 'src/index.ts', + output: [{ + file: pkg.module, + format: 'es', + banner, + sourcemap: true, + }, { + file: pkg.main, + format: 'cjs', + banner, + sourcemap: true, + }], + external, + plugins, + }, { + input: 'src/index.ts', + output: { + file: pkg.browser, + format: 'umd', + name: 'ge.multimediaPlugin', + banner, + globals, + sourcemap: true, + }, + external, + plugins: getPlugins(pkg, { min: true, external }), + }]; +}; diff --git a/plugin-packages/multimedia/src/audio/audio-component.ts b/plugin-packages/multimedia/src/audio/audio-component.ts new file mode 100644 index 00000000..e29e7184 --- /dev/null +++ b/plugin-packages/multimedia/src/audio/audio-component.ts @@ -0,0 +1,105 @@ +import type { Asset } from '@galacean/effects'; +import { effectsClass, RendererComponent, spec } from '@galacean/effects'; +import { AudioPlayer } from './audio-player'; + +@effectsClass(spec.DataType.AudioComponent) +export class AudioComponent extends RendererComponent { + audioPlayer: AudioPlayer; + + private isVideoPlay = false; + private threshold = 0.03; + + override onUpdate (dt: number): void { + super.onUpdate(dt); + + const { time, duration, endBehavior } = this.item; + + this.audioPlayer.setOptions({ + duration, + endBehavior, + }); + + if (time >= 0 && !this.isVideoPlay) { + this.audioPlayer.play(); + this.isVideoPlay = true; + } + + if (Math.abs(time - duration) < this.threshold) { + if (endBehavior === spec.EndBehavior.destroy) { + this.audioPlayer.pause(); + } + } + } + + override fromData (data: spec.AudioComponentData): void { + super.fromData(data); + + const { options } = data; + const { playbackRate = 1, muted = false, volume = 1 } = options; + + this.audioPlayer = new AudioPlayer((options.audio as unknown as Asset).data, this.engine); + this.audioPlayer.pause(); + this.setPlaybackRate(playbackRate); + this.setMuted(muted); + this.setVolume(volume); + } + + /** + * 设置音频资源 + * @param audio - 音频资源 + */ + setAudioSource (audio: HTMLAudioElement | AudioBuffer): void { + this.audioPlayer.setAudioSource(audio); + } + + /** + * 设置音量 + * @param volume - 音量 + */ + setVolume (volume: number): void { + this.audioPlayer.setVolume(volume); + } + + /** + * 获取当前音频的播放时刻 + */ + getCurrentTime (): number { + return this.audioPlayer.getCurrentTime(); + } + + /** + * 设置是否静音 + * @param muted - 是否静音 + */ + setMuted (muted: boolean): void { + this.audioPlayer.setMuted(muted); + } + + /** + * 设置是否循环播放 + * @param loop - 是否循环播放 + */ + setLoop (loop: boolean): void { + this.audioPlayer.setLoop(loop); + } + + /** + * 设置播放速率 + * @param rate - 播放速率 + */ + setPlaybackRate (rate: number): void { + this.audioPlayer.setPlaybackRate(rate); + } + + override onDisable (): void { + super.onDisable(); + + this.audioPlayer.pause(); + } + + override dispose (): void { + super.dispose(); + + this.audioPlayer.dispose(); + } +} diff --git a/plugin-packages/multimedia/src/audio/audio-loader.ts b/plugin-packages/multimedia/src/audio/audio-loader.ts new file mode 100644 index 00000000..802b8513 --- /dev/null +++ b/plugin-packages/multimedia/src/audio/audio-loader.ts @@ -0,0 +1,18 @@ +import type { SceneLoadOptions } from '@galacean/effects'; +import { spec, AbstractPlugin } from '@galacean/effects'; +import { processMultimedia } from '../utils'; + +export class AudioLoader extends AbstractPlugin { + static override async prepareAssets ( + json: spec.JSONScene, + options: SceneLoadOptions = {}, + ) { + const { audios = [] } = json; + const loadedAssets = await processMultimedia(audios, spec.MultimediaType.audio, options); + + return { + assets: audios, + loadedAssets, + }; + } +} diff --git a/plugin-packages/multimedia/src/audio/audio-player.ts b/plugin-packages/multimedia/src/audio/audio-player.ts new file mode 100644 index 00000000..df22c724 --- /dev/null +++ b/plugin-packages/multimedia/src/audio/audio-player.ts @@ -0,0 +1,210 @@ +import type { Engine } from '@galacean/effects'; +import { spec } from '@galacean/effects'; + +interface AudioSourceInfo { + source?: AudioBufferSourceNode, + audioContext?: AudioContext, + gainNode?: GainNode, +} + +export interface AudioPlayerOptions { + endBehavior: spec.EndBehavior, + duration: number, +} + +export class AudioPlayer { + audio?: HTMLAudioElement; + audioSourceInfo: AudioSourceInfo = {}; + + private isSupportAudioContext = !!window['AudioContext']; + private options: AudioPlayerOptions = { + endBehavior: spec.EndBehavior.destroy, + duration: 0, + }; + private destroyed = false; + private started = false; + private initialized = false; + private currentVolume = 1; + + constructor ( + audio: AudioBuffer | HTMLAudioElement, + private engine: Engine, + ) { + this.setAudioSource(audio); + } + + /** + * 设置音频资源 + * @param audio - 音频资源 + */ + setAudioSource (audio: AudioBuffer | HTMLAudioElement) { + if (this.audio || this.audioSourceInfo.source) { + this.dispose(); + } + if (audio instanceof AudioBuffer) { + const audioContext = new AudioContext(); + const gainNode = audioContext.createGain(); + + gainNode.connect(audioContext.destination); + + const source = audioContext.createBufferSource(); + + source.buffer = audio; + source.connect(gainNode); + + this.audioSourceInfo = { + source, + audioContext, + gainNode, + }; + } else { + this.audio = audio; + } + if (this.started) { + this.play(); + } + } + + getCurrentTime (): number { + if (this.isSupportAudioContext) { + const { audioContext } = this.audioSourceInfo; + + return audioContext?.currentTime || 0; + } else { + return this.audio?.currentTime || 0; + } + } + + play (): void { + if (this.isSupportAudioContext) { + const { audioContext, source } = this.audioSourceInfo; + + if (source && audioContext) { + switch (this.options.endBehavior) { + case spec.EndBehavior.destroy: + case spec.EndBehavior.freeze: + source.start(0); + + break; + case spec.EndBehavior.restart: + source.loop = true; + source.loopStart = 0; + source.loopEnd = this.options.duration; + source.start(0); + + break; + default: + break; + } + } + this.started = true; + } else { + this.audio?.play().catch(e => { + this.engine.renderErrors.add(e); + }); + } + } + + pause (): void { + if (this.isSupportAudioContext) { + const { source, audioContext } = this.audioSourceInfo; + + if (!audioContext) { + return; + } + if (audioContext.currentTime > 0 && this.started) { + source?.stop(); + } + } else { + this.audio?.pause(); + } + } + + setVolume (volume: number): void { + if (this.isSupportAudioContext) { + const { gainNode } = this.audioSourceInfo; + + if (gainNode) { + gainNode.gain.value = volume; + this.currentVolume = volume; + } + } else { + if (this.audio) { + this.audio.volume = volume; + this.currentVolume = volume; + } + } + } + + setPlaybackRate (rate: number): void { + if (this.isSupportAudioContext) { + const { source } = this.audioSourceInfo; + + if (source) { + source.playbackRate.value = rate; + } + } else { + if (this.audio) { + this.audio.playbackRate = rate; + } + } + } + + setLoop (loop: boolean): void { + if (this.isSupportAudioContext) { + const { source } = this.audioSourceInfo; + + if (!source) { + this.engine.renderErrors.add(new Error('Audio source is not found.')); + } else { + source.loop = loop; + } + } else { + if (this.audio) { + this.audio.loop = loop; + } + } + } + + setOptions (options: AudioPlayerOptions): void { + if (this.initialized) { + return; + } + this.options = options; + this.initialized = true; + } + + setMuted (muted: boolean): void { + if (this.isSupportAudioContext) { + const { gainNode } = this.audioSourceInfo; + const value = muted ? 0 : this.currentVolume; + + if (gainNode) { + gainNode.gain.value = value; + } + } else { + if (this.audio) { + this.audio.muted = muted; + } + } + } + + dispose (): void { + if (this.destroyed) { + return; + } + if (this.isSupportAudioContext) { + const { audioContext, source } = this.audioSourceInfo; + + if (this.started) { + source?.stop(); + } + audioContext?.close().catch(e => { + this.engine.renderErrors.add(e); + }); + } else { + this.audio?.pause(); + } + this.destroyed = true; + } +} diff --git a/plugin-packages/multimedia/src/constants.ts b/plugin-packages/multimedia/src/constants.ts new file mode 100644 index 00000000..b52396f8 --- /dev/null +++ b/plugin-packages/multimedia/src/constants.ts @@ -0,0 +1,6 @@ +export const multimediaErrorMessageMap: Record = { + 2000: 'Autoplay permission for audio and video is not enabled', +}; +export const multimediaErrorDisplayMessageMap = { + 2000: '音视频自动播放权限未开启', +}; diff --git a/plugin-packages/multimedia/src/index.ts b/plugin-packages/multimedia/src/index.ts new file mode 100644 index 00000000..693fb519 --- /dev/null +++ b/plugin-packages/multimedia/src/index.ts @@ -0,0 +1,12 @@ +import { registerPlugin, VFXItem } from '@galacean/effects'; +import { VideoLoader } from './video/video-loader'; +import { AudioLoader } from './audio/audio-loader'; + +export * from './video/video-component'; +export * from './audio/audio-component'; +export * from './audio/audio-player'; +export * from './constants'; +export * from './utils'; + +registerPlugin('video', VideoLoader, VFXItem, true); +registerPlugin('audio', AudioLoader, VFXItem, true); diff --git a/plugin-packages/multimedia/src/type.ts b/plugin-packages/multimedia/src/type.ts new file mode 100644 index 00000000..3d158475 --- /dev/null +++ b/plugin-packages/multimedia/src/type.ts @@ -0,0 +1,7 @@ +import type { Engine } from '@galacean/effects'; + +export interface PluginData { + hookTimeInfo: (label: string, fn: () => Promise) => Promise, + engine?: Engine, + assets: Record, +} diff --git a/plugin-packages/multimedia/src/utils.ts b/plugin-packages/multimedia/src/utils.ts new file mode 100644 index 00000000..2f3ea1ae --- /dev/null +++ b/plugin-packages/multimedia/src/utils.ts @@ -0,0 +1,107 @@ +import type { SceneLoadOptions } from '@galacean/effects'; +import { loadBinary, loadVideo, spec, passRenderLevel, isFunction } from '@galacean/effects'; +import { multimediaErrorMessageMap } from './constants'; + +export async function processMultimedia ( + media: spec.AssetBase[], + type: spec.MultimediaType, + options: SceneLoadOptions, +): Promise { + const { renderLevel } = options; + const jobs = media.map(medium => { + if (passRenderLevel(medium.renderLevel, renderLevel)) { + const url = new URL(medium.url, location.href).href; + + if (type === spec.MultimediaType.video) { + return loadVideo(url); + } else if (type === spec.MultimediaType.audio) { + return loadAudio(url); + } + } + + throw new Error(`Invalid ${type} source: ${JSON.stringify(media)}.`); + }); + + return Promise.all(jobs) as Promise; +} + +/** + * 判断音视频浏览器是否允许播放 + */ +export async function checkAutoplayPermission () { + const audio = document.createElement('audio'); + + // src 为长度为 0.1s 的音频片段 + audio.src = 'data:audio/mpeg;base64,//uQxAACFCEW8uewc8Nfu50FhJuQAGAFJCAhaIHoMhfEnBzixrwNsCGGGmyeCECGGRKWwnB0MkzGh6Hn7JLEstwCADQsJwBAAAOOhOAGAcd0gJgTBuW7HBgJBgfvEgCAEBEiOyeTDxyiyjZLEsRDyEzMz+921nJyWJZn6w8P1769e/AYCQI6tIJAkGHL16zpxhY5VeYGCdd3/93d9w4tygIO/4IHBOD8Hz/4f+IDkEHU4n8PqBMMBCQ0iXWYFnCIGqooaZHfRqOrgxuOtjmpCJCaYjmQqDz3NJUBTFWK4soYEoCumIJzIBldNhLUmAaDzhggZmSAkr9SqAIjGJFGEMCQlIPKDMuo24qZIrKDONHWGqlZbOymMy2yhCoBQywFQAgBEsETW0hCoIkqQWBINPWa3rCoEg1MiBIEiZMUiklyMfKVqoUOxIkSMtVTMzOMSBiKJQMAiWyROrf/5mq//8mkknNBQlFjiJFHKqqr//1VV6qq3vNVVbJpFHXkijM+pIoy1VX7zPOJEkmJAJaYgpqKBgASEAAIAAAAAAAAAAAA//uSxAAD1p0iZiw9OIgAADSAAAAEYAwFApgokRqIFjigukAADJhFjIUGoGFlRAycQGC5QsJJRWdRZRVWZVNYBStcjsRuaiMSgmOQOvVAKBSBY4ygsJGDCEoUEWrE3NBfUyJSSTTiPMpDUUvrhTsC7XR/G6bx2nse52G+cjBDhQW5tan7IZREJY6ULlDpCIhCIhkPDYgFaN2//3JZVZVY6UXQO2NXV1u//P/KSqyqSai7GFdu0DEqoheCVpA1v///6qqaaaYRKqIGKpiCmooGABIQAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=='; + audio.muted = true; + audio.crossOrigin = 'anonymous'; + + try { + await audio.play(); + } catch (_) { + throw new MultimediaError(2000, multimediaErrorMessageMap[2000]); + } +} + +/** + * 异步加载一个音频文件 + * @param url - 音频文件的 URL 或 MediaProvider 对象 + */ +export async function loadAudio (url: string): Promise { + const isSupportAudioContext = !!window['AudioContext']; + + if (isSupportAudioContext) { + try { + const audioContext = new AudioContext(); + const buffer = await loadBinary(url); + const decodedData = await audioContext.decodeAudioData(buffer); + + return decodedData; + } catch (_) { + throw new Error(`Failed to load audio from ${url}.`); + } + } + + return new Promise((resolve, reject) => { + const audio = new Audio(); + + // 设置音频源 + if (typeof url === 'string') { + audio.src = url; + } else { + audio.srcObject = url; + } + + audio.muted = false; + + // 监听加载事件 + audio.addEventListener('canplaythrough', () => { + resolve(audio); + }); + + // 监听错误事件 + audio.addEventListener('error', () => { + reject(new Error(`Failed to load audio from ${audio.src}.`)); + }); + + // 开始加载音频 + audio.load(); + }); +} + +export class MultimediaError extends Error { + /** + * 报错代码 + */ + code: number; + + constructor (code: number, message: string) { + super(message); + this.code = code; + this.name = this.constructor.name; + + if ('captureStackTrace' in Error && isFunction(Error.captureStackTrace)) { + Error.captureStackTrace(this, MultimediaError); + } + } +} diff --git a/plugin-packages/multimedia/src/video/video-component.ts b/plugin-packages/multimedia/src/video/video-component.ts new file mode 100644 index 00000000..a6e9199f --- /dev/null +++ b/plugin-packages/multimedia/src/video/video-component.ts @@ -0,0 +1,226 @@ +import type { + Texture, Engine, Texture2DSourceOptionsVideo, Asset, SpriteItemProps, +} from '@galacean/effects'; +import { spec, math, BaseRenderComponent, effectsClass, glContext } from '@galacean/effects'; + +/** + * 用于创建 videoItem 的数据类型, 经过处理后的 spec.VideoContent + */ +export interface VideoItemProps extends Omit { + listIndex?: number, + renderer: { + mask: number, + texture: Texture, + } & Omit, +} + +let seed = 0; + +@effectsClass(spec.DataType.VideoComponent) +export class VideoComponent extends BaseRenderComponent { + video?: HTMLVideoElement; + + private threshold = 0.03; + + constructor (engine: Engine) { + super(engine); + + this.name = 'MVideo' + seed++; + this.geometry = this.createGeometry(glContext.TRIANGLES); + } + + override setTexture (texture: Texture): void { + const oldTexture = this.renderer.texture; + const composition = this.item.composition; + + if (!composition) { return; } + + composition.textures.forEach((cachedTexture, index) => { + if (cachedTexture === oldTexture) { + composition.textures[index] = texture; + } + }); + + this.engine.removeTexture(oldTexture); + this.renderer.texture = texture; + this.material.setTexture('uSampler0', texture); + this.video = (texture.source as Texture2DSourceOptionsVideo).video; + } + + override fromData (data: VideoItemProps): void { + super.fromData(data); + + const { interaction, options, listIndex = 0 } = data; + const { + video, + startColor = [1, 1, 1, 1], + playbackRate = 1, + volume = 1, + muted = false, + } = options; + let renderer = data.renderer; + + if (!renderer) { + renderer = {} as SpriteItemProps['renderer']; + } + if (video) { + this.video = (video as unknown as Asset).data; + this.setPlaybackRate(playbackRate); + this.setVolume(volume); + this.setMuted(muted); + const endBehavior = this.item.endBehavior; + + // 如果元素设置为 destroy + if (endBehavior === spec.EndBehavior.destroy) { + this.setLoop(false); + } + } + + this.renderer = { + renderMode: renderer.renderMode ?? spec.RenderMode.BILLBOARD, + blending: renderer.blending ?? spec.BlendingMode.ALPHA, + texture: renderer.texture ?? this.engine.emptyTexture, + occlusion: !!renderer.occlusion, + transparentOcclusion: !!renderer.transparentOcclusion || (renderer.maskMode === spec.MaskMode.MASK), + side: renderer.side ?? spec.SideMode.DOUBLE, + mask: renderer.mask ?? 0, + maskMode: renderer.maskMode ?? spec.MaskMode.NONE, + order: listIndex, + }; + + this.interaction = interaction; + this.pauseVideo(); + + this.setItem(); + + this.material.setVector4('_Color', new math.Vector4().setFromArray(startColor)); + } + + override onUpdate (dt: number): void { + super.onUpdate(dt); + + const { time, duration, endBehavior } = this.item; + + if (time > 0) { + this.setVisible(true); + this.playVideo(); + } + + if (time === 0 && this.item.composition?.rootItem.endBehavior === spec.EndBehavior.freeze) { + this.pauseVideo(); + this.setCurrentTime(0); + } + if (Math.abs(time - duration) < this.threshold) { + if (endBehavior === spec.EndBehavior.freeze) { + this.pauseVideo(); + } else if (endBehavior === spec.EndBehavior.restart) { + this.setVisible(false); + // 重播 + this.pauseVideo(); + this.setCurrentTime(0); + } + } + } + + /** + * 获取当前视频时长 + * @returns 视频时长 + */ + getDuration (): number { + return this.video ? this.video.duration : 0; + } + + /** + * 获取当前视频播放时刻 + * @returns 当前视频播放时刻 + */ + getCurrentTime (): number { + return this.video ? this.video.currentTime : 0; + } + + /** + * 设置阈值(由于视频是单独的 update,有时并不能完全对其 GE 的 update) + * @param threshold 阈值 + */ + setThreshold (threshold: number) { + this.threshold = threshold; + } + + /** + * 设置当前视频播放时刻 + * @param time 视频播放时刻 + */ + setCurrentTime (time: number) { + if (this.video) { + this.video.currentTime = time; + } + } + + /** + * 设置视频是否循环播放 + * @param loop 是否循环播放 + */ + setLoop (loop: boolean) { + if (this.video) { + this.video.loop = loop; + } + } + + /** + * 设置视频是否静音 + * @param muted 是否静音 + */ + setMuted (muted: boolean) { + if (this.video && this.video.muted !== muted) { + this.video.muted = muted; + } + } + + /** + * 设置视频音量 + * @param volume 视频音量 + */ + setVolume (volume: number) { + if (this.video && this.video.volume !== volume) { + this.video.volume = volume; + } + } + + /** + * 设置视频播放速率 + * @param rate 视频播放速率 + */ + setPlaybackRate (rate: number) { + if (!this.video || this.video.playbackRate === rate) { + return; + } + this.video.playbackRate = rate; + } + + private playVideo (): void { + if (this.video) { + this.video.play().catch(error => { + this.engine.renderErrors.add(error); + }); + } + } + + private pauseVideo (): void { + if (this.video && !this.video.paused) { + this.video.pause(); + } + } + + override onDisable (): void { + super.onDisable(); + + this.setCurrentTime(0); + this.video?.pause(); + } + + override onEnable (): void { + super.onEnable(); + + this.playVideo(); + } +} diff --git a/plugin-packages/multimedia/src/video/video-loader.ts b/plugin-packages/multimedia/src/video/video-loader.ts new file mode 100644 index 00000000..6b054b05 --- /dev/null +++ b/plugin-packages/multimedia/src/video/video-loader.ts @@ -0,0 +1,18 @@ +import type { SceneLoadOptions } from '@galacean/effects'; +import { spec, AbstractPlugin } from '@galacean/effects'; +import { processMultimedia } from '../utils'; + +export class VideoLoader extends AbstractPlugin { + static override async prepareAssets ( + json: spec.JSONScene, + options: SceneLoadOptions = {}, + ) { + const { videos = [] } = json; + const loadedAssets = await processMultimedia(videos, spec.MultimediaType.video, options); + + return { + assets: videos, + loadedAssets, + }; + } +} diff --git a/plugin-packages/multimedia/test/index.html b/plugin-packages/multimedia/test/index.html new file mode 100644 index 00000000..3c279bc7 --- /dev/null +++ b/plugin-packages/multimedia/test/index.html @@ -0,0 +1,46 @@ + + + + + Plugin Media Tests + + + + + + + + + +
+ + + + + + + + + + + diff --git a/plugin-packages/multimedia/test/src/audio-component.spec.ts b/plugin-packages/multimedia/test/src/audio-component.spec.ts new file mode 100644 index 00000000..fae9ad5e --- /dev/null +++ b/plugin-packages/multimedia/test/src/audio-component.spec.ts @@ -0,0 +1,260 @@ +import { spec } from '@galacean/effects'; +import { generateGUID, Player } from '@galacean/effects'; +import { AudioComponent } from '@galacean/effects-plugin-multimedia'; + +interface AudioCompositionOptions { + duration: number, + endBehavior: spec.EndBehavior, + audios: spec.AssetBase[], + start: number, + options: spec.AudioContentOptions, +} + +const { expect } = chai; +const player = new Player({ + canvas: document.createElement('canvas'), +}); + +describe('audioComponent ', function () { + it('audioComponent:create', async function () { + const id = generateGUID(); + const options: AudioCompositionOptions = { + duration: 10, + endBehavior: spec.EndBehavior.destroy, + audios: [{ id, url: 'https://mdn.alipayobjects.com/huamei_s9rwo4/afts/file/A*zERYT5qS-7kAAAAAAAAAAAAADiqKAQ' }], + start: 0, + options: { audio: { id } }, + }; + const videoJson = getVideoJson(options); + const composition = await player.loadScene(videoJson); + const audio = composition.getItemByName('audio'); + + if (!audio) { throw new Error('audio is null'); } + expect(audio.endBehavior).to.equal(options.endBehavior); + expect(audio.duration).to.equal(options.duration); + expect(audio.start).to.equal(options.start); + const audioComponent = audio.getComponent(AudioComponent); + + expect(audioComponent).to.be.instanceOf(AudioComponent); + composition.dispose(); + }); + + it('audioComponent:destroy', async function () { + const id = generateGUID(); + const options: AudioCompositionOptions = { + duration: 3, + endBehavior: spec.EndBehavior.destroy, + audios: [{ id, url: 'https://mdn.alipayobjects.com/huamei_s9rwo4/afts/file/A*zERYT5qS-7kAAAAAAAAAAAAADiqKAQ' }], + start: 0, + options: { audio: { id } }, + }; + const videoJson = getVideoJson(options); + const composition = await player.loadScene(videoJson); + + player.gotoAndPlay(4); + const audio = composition.getItemByName('audio'); + + if (!audio) { throw new Error('audio is null'); } + const audioComponent = audio.getComponent(AudioComponent); + + expect(audioComponent).to.be.instanceOf(AudioComponent); + expect(audioComponent.enabled).to.be.false; + composition.dispose(); + + }); + + it('audioComponent:setVolume', async function () { + const id = generateGUID(); + const options: AudioCompositionOptions = { + duration: 10, + endBehavior: spec.EndBehavior.destroy, + audios: [{ id, url: 'https://mdn.alipayobjects.com/huamei_s9rwo4/afts/file/A*zERYT5qS-7kAAAAAAAAAAAAADiqKAQ' }], + start: 0, + options: { audio: { id } }, + }; + const videoJson = getVideoJson(options); + const composition = await player.loadScene(videoJson); + const audio = composition.getItemByName('audio'); + + if (!audio) { throw new Error('audio is null'); } + expect(audio.endBehavior).to.equal(options.endBehavior); + expect(audio.duration).to.equal(options.duration); + expect(audio.start).to.equal(options.start); + const audioComponent = audio.getComponent(AudioComponent); + + expect(audioComponent).to.be.instanceOf(AudioComponent); + audioComponent.setVolume(0.5); + expect(audioComponent.audioPlayer.audioSourceInfo.gainNode?.gain.value).to.equal(0.5); + composition.dispose(); + }); + + it('audioComponent:setMuted', async function () { + const id = generateGUID(); + const options: AudioCompositionOptions = { + duration: 10, + endBehavior: spec.EndBehavior.destroy, + audios: [{ id, url: 'https://mdn.alipayobjects.com/huamei_s9rwo4/afts/file/A*zERYT5qS-7kAAAAAAAAAAAAADiqKAQ' }], + start: 0, + options: { audio: { id } }, + }; + const videoJson = getVideoJson(options); + const composition = await player.loadScene(videoJson); + const audio = composition.getItemByName('audio'); + + if (!audio) { throw new Error('audio is null'); } + expect(audio.endBehavior).to.equal(options.endBehavior); + expect(audio.duration).to.equal(options.duration); + expect(audio.start).to.equal(options.start); + const audioComponent = audio.getComponent(AudioComponent); + + expect(audioComponent).to.be.instanceOf(AudioComponent); + audioComponent.setMuted(false); + expect(audioComponent.audioPlayer.audioSourceInfo.gainNode?.gain.value).to.equal(1); + composition.dispose(); + }); + + it('audioComponent:setLoop', async function () { + const id = generateGUID(); + const options: AudioCompositionOptions = { + duration: 10, + endBehavior: spec.EndBehavior.destroy, + audios: [{ id, url: 'https://mdn.alipayobjects.com/huamei_s9rwo4/afts/file/A*zERYT5qS-7kAAAAAAAAAAAAADiqKAQ' }], + start: 0, + options: { audio: { id } }, + }; + const videoJson = getVideoJson(options); + const composition = await player.loadScene(videoJson); + const audio = composition.getItemByName('audio'); + + if (!audio) { throw new Error('audio is null'); } + expect(audio.endBehavior).to.equal(options.endBehavior); + expect(audio.duration).to.equal(options.duration); + expect(audio.start).to.equal(options.start); + const audioComponent = audio.getComponent(AudioComponent); + + expect(audioComponent).to.be.instanceOf(AudioComponent); + audioComponent.setLoop(true); + expect(audioComponent.audioPlayer.audioSourceInfo.source?.loop).to.equal(true); + composition.dispose(); + }); + + it('audioComponent:setPlaybackRate', async function () { + const id = generateGUID(); + const options: AudioCompositionOptions = { + duration: 10, + endBehavior: spec.EndBehavior.destroy, + audios: [{ id, url: 'https://mdn.alipayobjects.com/huamei_s9rwo4/afts/file/A*zERYT5qS-7kAAAAAAAAAAAAADiqKAQ' }], + start: 0, + options: { audio: { id } }, + }; + const videoJson = getVideoJson(options); + const composition = await player.loadScene(videoJson); + const audio = composition.getItemByName('audio'); + + if (!audio) { throw new Error('audio is null'); } + expect(audio.endBehavior).to.equal(options.endBehavior); + expect(audio.duration).to.equal(options.duration); + expect(audio.start).to.equal(options.start); + const audioComponent = audio.getComponent(AudioComponent); + + expect(audioComponent).to.be.instanceOf(AudioComponent); + audioComponent.setPlaybackRate(2); + expect(audioComponent.audioPlayer.audioSourceInfo.source?.playbackRate.value).to.equal(2); + composition.dispose(); + }); +}); + +function getVideoJson (options: AudioCompositionOptions) { + return { + playerVersion: { web: '2.0.4', native: '0.0.1.202311221223' }, + images: [], + fonts: [], + version: '3.0', + shapes: [], + plugins: [], + audios: options.audios, + type: 'ge', + compositions: [ + { + id: '5', + name: 'audioTest', + duration: 10, + startTime: 0, + endBehavior: 2, + previewSize: [750, 1624], + items: [{ id: '147e873c89b34c6f96108ccc4d6e6f83' }], + camera: { fov: 60, far: 40, near: 0.1, clipMode: 1, position: [0, 0, 8], rotation: [0, 0, 0] }, + sceneBindings: [ + { key: { id: 'c3cffe498bec4da195ecb68569806ca4' }, value: { id: '147e873c89b34c6f96108ccc4d6e6f83' } }, + ], + timelineAsset: { id: '71ed8f480c64458d94593279bcf831aa' }, + }, + ], + components: [ + { + id: '6dc07c93b035442a93dc3f3ebdba0796', + item: { id: '147e873c89b34c6f96108ccc4d6e6f83' }, + dataType: 'AudioComponent', + options: options.options, + }, + ], + geometries: [], + materials: [], + items: [ + { + id: '147e873c89b34c6f96108ccc4d6e6f83', + name: 'audio', + duration: options.duration, + type: '1', + visible: true, + endBehavior: options.endBehavior, + delay: options.start, + renderLevel: 'B+', + components: [{ id: '6dc07c93b035442a93dc3f3ebdba0796' }], + transform: { + position: { x: 0, y: 4.6765, z: 0 }, + eulerHint: { x: 0, y: 0, z: 0 }, + anchor: { x: 0, y: 0 }, + size: { x: 3.1492, y: 3.1492 }, + scale: { x: 1, y: 1, z: 1 }, + }, + dataType: 'VFXItemData', + }, + ], + shaders: [], + bins: [], + textures: [], + animations: [], + miscs: [ + { + id: '71ed8f480c64458d94593279bcf831aa', + dataType: 'TimelineAsset', + tracks: [{ id: 'c3cffe498bec4da195ecb68569806ca4' }], + }, + { id: 'acfa5d2ad9be40f991db5e9d93864803', dataType: 'ActivationPlayableAsset' }, + { id: '063079d00a6749419976693d32f0d42a', dataType: 'TransformPlayableAsset', positionOverLifetime: {} }, + { + id: 'b5b10964ddb54ce29ed1370c62c02e89', + dataType: 'ActivationTrack', + children: [], + clips: [{ start: options.start, duration: options.duration, endBehavior: options.endBehavior, asset: { id: 'acfa5d2ad9be40f991db5e9d93864803' } }], + }, + { + id: '0259077ac16c4c498fcc91ed341f1909', + dataType: 'TransformTrack', + children: [], + clips: [{ start: options.start, duration: options.duration, endBehavior: options.endBehavior, asset: { id: '063079d00a6749419976693d32f0d42a' } }], + }, + { + id: 'c3cffe498bec4da195ecb68569806ca4', + dataType: 'ObjectBindingTrack', + children: [ + { id: 'b5b10964ddb54ce29ed1370c62c02e89' }, + { id: '0259077ac16c4c498fcc91ed341f1909' }, + ], + clips: [], + }, + ], + compositionId: '5', + }; +} diff --git a/plugin-packages/multimedia/test/src/index.ts b/plugin-packages/multimedia/test/src/index.ts new file mode 100644 index 00000000..51f89451 --- /dev/null +++ b/plugin-packages/multimedia/test/src/index.ts @@ -0,0 +1,3 @@ +import '@galacean/effects-plugin-multimedia'; +import './audio-component.spec'; +import './video-component.spec'; diff --git a/plugin-packages/multimedia/test/src/video-component.spec.ts b/plugin-packages/multimedia/test/src/video-component.spec.ts new file mode 100644 index 00000000..b02c5d62 --- /dev/null +++ b/plugin-packages/multimedia/test/src/video-component.spec.ts @@ -0,0 +1,393 @@ +import { generateGUID, Player, spec } from '@galacean/effects'; +import { VideoComponent } from '@galacean/effects-plugin-multimedia'; +interface VideoCompositionOptions { + duration: number, + endBehavior: spec.EndBehavior, + id: string, + videos: spec.AssetBase[], + start: number, + options: spec.VideoContentOptions, +} + +const { expect } = chai; +const player = new Player({ + canvas: document.createElement('canvas'), +}); + +describe('videoComponent ', function () { + it('videoComponent:create', async function () { + const id = generateGUID(); + const options: VideoCompositionOptions = { + duration: 10, + endBehavior: spec.EndBehavior.destroy, + id, + videos: [ + { + id, + url: 'https://gw.alipayobjects.com/v/huamei_s9rwo4/afts/video/A*pud9Q7-6P7QAAAAAAAAAAAAADiqKAQ', + }, + ], + start: 0, + options: { video: { id } }, + }; + const videoJson = getVideoJson(options); + const composition = await player.loadScene(videoJson); + const video = composition.getItemByName('video'); + + if (!video) { throw new Error('video is null'); } + expect(video.endBehavior).to.equal(options.endBehavior); + expect(video.duration).to.equal(options.duration); + expect(video.start).to.equal(options.start); + const videoComponent = video.getComponent(VideoComponent); + + expect(videoComponent).to.be.instanceOf(VideoComponent); + composition.dispose(); + }); + + it('videoComponent:dispose', async function () { + const id = generateGUID(); + const options: VideoCompositionOptions = { + duration: 2, + endBehavior: spec.EndBehavior.destroy, + id, + videos: [ + { + id, + url: 'https://gw.alipayobjects.com/v/huamei_s9rwo4/afts/video/A*pud9Q7-6P7QAAAAAAAAAAAAADiqKAQ', + }, + ], + start: 0, + options: { video: { id } }, + }; + const videoJson = getVideoJson(options); + const composition = await player.loadScene(videoJson); + const video = composition.getItemByName('video'); + + player.gotoAndPlay(4); + + if (!video) { throw new Error('video is null'); } + expect(video.endBehavior).to.equal(options.endBehavior); + expect(video.duration).to.equal(options.duration); + expect(video.start).to.equal(options.start); + const videoComponent = video.getComponent(VideoComponent); + + expect(videoComponent).to.be.instanceOf(VideoComponent); + expect(videoComponent.enabled).to.be.false; + + composition.dispose(); + }); + + it('videoComponent:getDuration', async function () { + const id = generateGUID(); + const options: VideoCompositionOptions = { + duration: 10, + endBehavior: spec.EndBehavior.destroy, + id, + videos: [ + { + id, + url: 'https://gw.alipayobjects.com/v/huamei_s9rwo4/afts/video/A*pud9Q7-6P7QAAAAAAAAAAAAADiqKAQ', + }, + ], + start: 0, + options: { video: { id } }, + }; + const videoJson = getVideoJson(options); + const composition = await player.loadScene(videoJson); + const video = composition.getItemByName('video'); + + if (!video) { throw new Error('video is null'); } + expect(video.endBehavior).to.equal(options.endBehavior); + expect(video.duration).to.equal(options.duration); + expect(video.start).to.equal(options.start); + const videoComponent = video.getComponent(VideoComponent); + + expect(videoComponent).to.be.instanceOf(VideoComponent); + const duration = videoComponent.getDuration(); + //@ts-expect-error + const videoAsset = videoComponent.engine.objectInstance[options.id].data; + + expect(duration).to.equal(videoAsset.duration); + + composition.dispose(); + }); + + it('videoComponent:setCurrentTime', async function () { + const id = generateGUID(); + const options: VideoCompositionOptions = { + duration: 10, + endBehavior: spec.EndBehavior.destroy, + id, + videos: [ + { + id, + url: 'https://gw.alipayobjects.com/v/huamei_s9rwo4/afts/video/A*pud9Q7-6P7QAAAAAAAAAAAAADiqKAQ', + }, + ], + start: 0, + options: { video: { id } }, + }; + const videoJson = getVideoJson(options); + const composition = await player.loadScene(videoJson); + const video = composition.getItemByName('video'); + + if (!video) { throw new Error('video is null'); } + expect(video.endBehavior).to.equal(options.endBehavior); + expect(video.duration).to.equal(options.duration); + expect(video.start).to.equal(options.start); + const videoComponent = video.getComponent(VideoComponent); + + expect(videoComponent).to.be.instanceOf(VideoComponent); + videoComponent.setCurrentTime(3); + //@ts-expect-error + const videoAsset = videoComponent.engine.objectInstance[options.id].data; + + expect(videoAsset.currentTime).to.equal(3); + composition.dispose(); + }); + + it('videoComponent:setLoop', async function () { + const id = generateGUID(); + const options: VideoCompositionOptions = { + duration: 10, + endBehavior: spec.EndBehavior.destroy, + id, + videos: [ + { + id, + url: 'https://gw.alipayobjects.com/v/huamei_s9rwo4/afts/video/A*pud9Q7-6P7QAAAAAAAAAAAAADiqKAQ', + }, + ], + start: 0, + options: { video: { id } }, + }; + const videoJson = getVideoJson(options); + const composition = await player.loadScene(videoJson); + const video = composition.getItemByName('video'); + + if (!video) { throw new Error('video is null'); } + expect(video.endBehavior).to.equal(options.endBehavior); + expect(video.duration).to.equal(options.duration); + expect(video.start).to.equal(options.start); + const videoComponent = video.getComponent(VideoComponent); + + expect(videoComponent).to.be.instanceOf(VideoComponent); + videoComponent.setLoop(true); + //@ts-expect-error + const videoAsset = videoComponent.engine.objectInstance[options.id].data; + + expect(videoAsset.loop).to.equal(true); + composition.dispose(); + }); + + it('videoComponent:setMuted', async function () { + const id = generateGUID(); + const options: VideoCompositionOptions = { + duration: 10, + endBehavior: spec.EndBehavior.destroy, + id, + videos: [ + { + id, + url: 'https://gw.alipayobjects.com/v/huamei_s9rwo4/afts/video/A*pud9Q7-6P7QAAAAAAAAAAAAADiqKAQ', + }, + ], + start: 0, + options: { video: { id } }, + }; + const videoJson = getVideoJson(options); + const composition = await player.loadScene(videoJson); + const video = composition.getItemByName('video'); + + if (!video) { throw new Error('video is null'); } + expect(video.endBehavior).to.equal(options.endBehavior); + expect(video.duration).to.equal(options.duration); + expect(video.start).to.equal(options.start); + const videoComponent = video.getComponent(VideoComponent); + + expect(videoComponent).to.be.instanceOf(VideoComponent); + videoComponent.setMuted(true); + //@ts-expect-error + const videoAsset = videoComponent.engine.objectInstance[options.id].data; + + expect(videoAsset.muted).to.equal(true); + composition.dispose(); + }); + + it('videoComponent:setVolume', async function () { + const id = generateGUID(); + const options: VideoCompositionOptions = { + duration: 10, + endBehavior: spec.EndBehavior.destroy, + id, + videos: [ + { + id, + url: 'https://gw.alipayobjects.com/v/huamei_s9rwo4/afts/video/A*pud9Q7-6P7QAAAAAAAAAAAAADiqKAQ', + }, + ], + start: 0, + options: { video: { id } }, + }; + const videoJson = getVideoJson(options); + const composition = await player.loadScene(videoJson); + const video = composition.getItemByName('video'); + + if (!video) { throw new Error('video is null'); } + expect(video.endBehavior).to.equal(options.endBehavior); + expect(video.duration).to.equal(options.duration); + expect(video.start).to.equal(options.start); + const videoComponent = video.getComponent(VideoComponent); + + expect(videoComponent).to.be.instanceOf(VideoComponent); + videoComponent.setVolume(0.5); + //@ts-expect-error + const videoAsset = videoComponent.engine.objectInstance[options.id].data; + + expect(videoAsset.volume).to.equal(0.5); + composition.dispose(); + }); + + it('videoComponent:setPlaybackRate', async function () { + const id = generateGUID(); + const options: VideoCompositionOptions = { + duration: 10, + endBehavior: spec.EndBehavior.destroy, + id, + videos: [ + { + id, + url: 'https://gw.alipayobjects.com/v/huamei_s9rwo4/afts/video/A*pud9Q7-6P7QAAAAAAAAAAAAADiqKAQ', + }, + ], + start: 0, + options: { video: { id } }, + }; + const videoJson = getVideoJson(options); + const composition = await player.loadScene(videoJson); + const video = composition.getItemByName('video'); + + if (!video) { throw new Error('video is null'); } + expect(video.endBehavior).to.equal(options.endBehavior); + expect(video.duration).to.equal(options.duration); + expect(video.start).to.equal(options.start); + const videoComponent = video.getComponent(VideoComponent); + + expect(videoComponent).to.be.instanceOf(VideoComponent); + videoComponent.setPlaybackRate(0.5); + //@ts-expect-error + const videoAsset = videoComponent.engine.objectInstance[options.id].data; + + expect(videoAsset.playbackRate).to.equal(0.5); + composition.dispose(); + }); +}); + +function getVideoJson (options: VideoCompositionOptions) { + return { + playerVersion: { web: '2.0.4', native: '0.0.1.202311221223' }, + images: [], + fonts: [], + version: '3.0', + shapes: [], + plugins: [], + videos: options.videos, + type: 'ge', + compositions: [ + { + id: '5', + name: 'videoTest', + duration: 10, + startTime: 0, + endBehavior: 2, + previewSize: [750, 1624], + items: [{ id: '147e873c89b34c6f96108ccc4d6e6f83' }], + camera: { fov: 60, far: 40, near: 0.1, clipMode: 1, position: [0, 0, 8], rotation: [0, 0, 0] }, + sceneBindings: [ + { key: { id: 'c3cffe498bec4da195ecb68569806ca4' }, value: { id: '147e873c89b34c6f96108ccc4d6e6f83' } }, + ], + timelineAsset: { id: '71ed8f480c64458d94593279bcf831aa' }, + }, + ], + components: [ + { + id: '6dc07c93b035442a93dc3f3ebdba0796', + item: { id: '147e873c89b34c6f96108ccc4d6e6f83' }, + dataType: 'VideoComponent', + options: options.options, + renderer: { + 'renderMode': 1, + 'texture': { + 'id': 'b582d21fdd524c4684f1c057b220ddd0', + }, + }, + }, + ], + textures: [ + { + 'id': 'b582d21fdd524c4684f1c057b220ddd0', + 'source': { + 'id': options.id, + }, + 'flipY': true, + }, + ], + geometries: [], + materials: [], + items: [ + { + id: '147e873c89b34c6f96108ccc4d6e6f83', + name: 'video', + duration: options.duration, + type: '1', + visible: true, + endBehavior: options.endBehavior, + delay: options.start, + renderLevel: 'B+', + components: [{ id: '6dc07c93b035442a93dc3f3ebdba0796' }], + transform: { + position: { x: 0, y: 4.6765, z: 0 }, + eulerHint: { x: 0, y: 0, z: 0 }, + anchor: { x: 0, y: 0 }, + size: { x: 3.1492, y: 3.1492 }, + scale: { x: 1, y: 1, z: 1 }, + }, + dataType: 'VFXItemData', + }, + ], + shaders: [], + bins: [], + animations: [], + miscs: [ + { + id: '71ed8f480c64458d94593279bcf831aa', + dataType: 'TimelineAsset', + tracks: [{ id: 'c3cffe498bec4da195ecb68569806ca4' }], + }, + { id: 'acfa5d2ad9be40f991db5e9d93864803', dataType: 'ActivationPlayableAsset' }, + { id: '063079d00a6749419976693d32f0d42a', dataType: 'TransformPlayableAsset', positionOverLifetime: {} }, + { + id: 'b5b10964ddb54ce29ed1370c62c02e89', + dataType: 'ActivationTrack', + children: [], + clips: [{ start: options.start, duration: options.duration, endBehavior: options.endBehavior, asset: { id: 'acfa5d2ad9be40f991db5e9d93864803' } }], + }, + { + id: '0259077ac16c4c498fcc91ed341f1909', + dataType: 'TransformTrack', + children: [], + clips: [{ start: options.start, duration: options.duration, endBehavior: options.endBehavior, asset: { id: '063079d00a6749419976693d32f0d42a' } }], + }, + { + id: 'c3cffe498bec4da195ecb68569806ca4', + dataType: 'ObjectBindingTrack', + children: [ + { id: 'b5b10964ddb54ce29ed1370c62c02e89' }, + { id: '0259077ac16c4c498fcc91ed341f1909' }, + ], + clips: [], + }, + ], + compositionId: '5', + }; +} diff --git a/plugin-packages/multimedia/test/tsconfig.json b/plugin-packages/multimedia/test/tsconfig.json new file mode 100644 index 00000000..0e8412eb --- /dev/null +++ b/plugin-packages/multimedia/test/tsconfig.json @@ -0,0 +1,6 @@ +{ + "extends": "../../../tsconfig.base.json", + "include": [ + "src" + ] +} diff --git a/plugin-packages/multimedia/tsconfig.json b/plugin-packages/multimedia/tsconfig.json new file mode 100644 index 00000000..ecbcdd51 --- /dev/null +++ b/plugin-packages/multimedia/tsconfig.json @@ -0,0 +1,17 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "rootDir": "src", + "outDir": "dist", + "composite": true + }, + "include": [ + "src", + "../../types" + ], + "references": [ + { + "path": "../../packages/effects" + } + ] +} diff --git a/plugin-packages/multimedia/typedoc.json b/plugin-packages/multimedia/typedoc.json new file mode 100644 index 00000000..b6aba3cb --- /dev/null +++ b/plugin-packages/multimedia/typedoc.json @@ -0,0 +1,8 @@ +{ + "extends": [ + "../../typedoc.base.json" + ], + "entryPoints": [ + "src/index.ts" + ] +} diff --git a/plugin-packages/multimedia/vite.config.js b/plugin-packages/multimedia/vite.config.js new file mode 100644 index 00000000..6616abb4 --- /dev/null +++ b/plugin-packages/multimedia/vite.config.js @@ -0,0 +1,71 @@ +import { resolve } from 'path'; +import { defineConfig } from 'vite'; +import tsconfigPaths from 'vite-tsconfig-paths'; +import legacy from '@vitejs/plugin-legacy'; +import ip from 'ip'; +import { glslInner, getSWCPlugin } from '../../scripts/rollup-config-helper'; + +export default defineConfig(({ mode }) => { + const development = mode === 'development'; + + return { + base: './', + build: { + rollupOptions: { + input: { + 'index': resolve(__dirname, 'demo/index.html'), + 'video': resolve(__dirname, 'demo/video.html'), + 'audio': resolve(__dirname, 'demo/audio.html'), + } + }, + minify: false, // iOS 9 等低版本加载压缩代码报脚本异常 + }, + server: { + host: '0.0.0.0', + port: 8081, + }, + preview: { + host: '0.0.0.0', + port: 8081, + }, + define: { + __VERSION__: 0, + __DEBUG__: development, + }, + plugins: [ + legacy({ + targets: ['iOS >= 9'], + modernPolyfills: ['es/global-this'], + }), + glslInner(), + getSWCPlugin({ + baseUrl: resolve(__dirname, '..', '..'), + }), + tsconfigPaths(), + configureServerPlugin(), + ], + }; +}); + +// 用于配置开发服务器的钩子 +function configureServerPlugin() { + const handleServer = function (server) { + const host = ip.address() ?? 'localhost'; + const port = server.config.server.port; + const baseUrl = `http://${host}:${port}`; + + setTimeout(() => { + console.log(` \x1b[1m\x1b[32m->\x1b[97m Demo: \x1b[0m\x1b[96m${baseUrl}/demo/index.html\x1b[0m`); + }, 1000); + } + + return { + name: 'configure-server', + configurePreviewServer(server) { + server.httpServer.once('listening', handleServer.bind(this, server)); + }, + configureServer(server) { + server.httpServer.once('listening', handleServer.bind(this, server)); + }, + } +} diff --git a/plugin-packages/spine/src/spine-component.ts b/plugin-packages/spine/src/spine-component.ts index d9aa659a..5847f067 100644 --- a/plugin-packages/spine/src/spine-component.ts +++ b/plugin-packages/spine/src/spine-component.ts @@ -54,7 +54,7 @@ export interface SpineDataCache extends SpineBaseData { /** * @since 2.0.0 */ -@effectsClass('SpineComponent') +@effectsClass(spec.DataType.SpineComponent) export class SpineComponent extends RendererComponent { startSize: number; /** diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d80c7ec0..006dda90 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -150,8 +150,8 @@ importers: specifier: 1.1.0 version: 1.1.0 '@galacean/effects-specification': - specifier: 2.0.1 - version: 2.0.1 + specifier: 2.1.0-alpha.0 + version: 2.1.0-alpha.0 flatbuffers: specifier: 24.3.25 version: 24.3.25 @@ -236,6 +236,12 @@ importers: specifier: ^2.0.8 version: 2.0.8 + plugin-packages/multimedia: + devDependencies: + '@galacean/effects': + specifier: workspace:* + version: link:../../packages/effects + plugin-packages/orientation-transformer: devDependencies: '@galacean/effects': @@ -2202,8 +2208,8 @@ packages: /@galacean/effects-specification@2.0.0: resolution: {integrity: sha512-7ieZALZYn5fwKLIGAskmYSKGX82ZkLRdDUInG21KsOJ2eQ5+HcI6mJU5tmN8vjZxQUc+RVuUcssExGbiaj2+5w==} - /@galacean/effects-specification@2.0.1: - resolution: {integrity: sha512-5Nj668cYvHXAbTW7W4bxG2gcIL/gWHPmWljVQWcbc/xCT7zA/FtB3ZUbjxfGRfGcXdgMbC8We4Ch3r6fYJF1iQ==} + /@galacean/effects-specification@2.1.0-alpha.0: + resolution: {integrity: sha512-b6yVdfzFcuzLjuNCn2wyknDBLM+l0arXwsBKP8IOhEz+av5XPaNSrOa2yUEROr0D1l9qydwfaMSL3ft6IT6FOw==} dev: false /@humanwhocodes/config-array@0.11.14: diff --git a/web-packages/demo/src/single.ts b/web-packages/demo/src/single.ts index 595b0d0f..0fac7420 100644 --- a/web-packages/demo/src/single.ts +++ b/web-packages/demo/src/single.ts @@ -1,7 +1,7 @@ -import { Player } from '@galacean/effects'; +import { AssetManager, Player } from '@galacean/effects'; import '@galacean/effects-plugin-spine'; +import { JSONConverter } from '@galacean/effects-plugin-model'; -// const json = 'https://mdn.alipayobjects.com/mars/afts/file/A*js8iRbOiExAAAAAAAAAAAAAADlB4AQ'; // const json = 'https://gw.alipayobjects.com/os/gltf-asset/mars-cli/YDITHDADWXXM/1601633123-e644d.json'; // 蒙版 // const json = 'https://gw.alipayobjects.com/os/gltf-asset/mars-cli/HCQBCOWGHRQC/273965510-c5c29.json'; @@ -10,16 +10,26 @@ import '@galacean/effects-plugin-spine'; // 普通拖尾 // const json = 'https://gw.alipayobjects.com/os/gltf-asset/mars-cli/RYYAXEAYMIYJ/1314733612-96c0b.json'; // 图贴拖尾 -const json = 'https://mdn.alipayobjects.com/mars/afts/file/A*VRedS5UU8DAAAAAAAAAAAAAADlB4AQ'; +// const json = 'https://mdn.alipayobjects.com/mars/afts/file/A*VRedS5UU8DAAAAAAAAAAAAAADlB4AQ'; +// 3D +// const json = 'https://mdn.alipayobjects.com/mars/afts/file/A*sA-6TJ695dYAAAAAAAAAAAAADlB4AQ'; +// 特效元素 +const json = 'https://mdn.alipayobjects.com/mars/afts/file/A*bi3HRobVsk8AAAAAAAAAAAAADlB4AQ'; const container = document.getElementById('J-container'); (async () => { try { + const assetManager = new AssetManager(); const player = new Player({ container, + interactive: true, }); + // const converter = new JSONConverter(player.renderer, true); + // const data = await converter.processScene(json); + // const scene = await assetManager.loadScene(json); await player.loadScene(json); + } catch (e) { console.error('biz', e); } diff --git a/web-packages/test/unit/src/effects-core/plugins/particle/base.spec.ts b/web-packages/test/unit/src/effects-core/plugins/particle/base.spec.ts index 0222d1fd..6618c648 100644 --- a/web-packages/test/unit/src/effects-core/plugins/particle/base.spec.ts +++ b/web-packages/test/unit/src/effects-core/plugins/particle/base.spec.ts @@ -1,4 +1,4 @@ -import type { GradientValue, RandomSetValue, VFXItem, color } from '@galacean/effects'; +import type { GradientValue, RandomSetValue, VFXItem } from '@galacean/effects'; import { Player, ParticleSystem, spec } from '@galacean/effects'; const { expect } = chai; @@ -70,7 +70,7 @@ describe('core/plugins/particle/base', () => { const colors = comp.getItemByName('colors') as VFXItem; const gradient = comp.getItemByName('gradient') as VFXItem; const pureStartColor = pure.getComponent(ParticleSystem).options.startColor; - const colorsStartColor = colors.getComponent(ParticleSystem).options.startColor as RandomSetValue; + const colorsStartColor = colors.getComponent(ParticleSystem).options.startColor as RandomSetValue; const gradientStartColor = gradient.getComponent(ParticleSystem).options.startColor as unknown as GradientValue; expect(pureStartColor.getValue()).to.eql([255, 255, 255], 'pure color'); diff --git a/web-packages/test/unit/src/effects-webgl/gl-material.spec.ts b/web-packages/test/unit/src/effects-webgl/gl-material.spec.ts index 77bbd5dc..b036b65f 100644 --- a/web-packages/test/unit/src/effects-webgl/gl-material.spec.ts +++ b/web-packages/test/unit/src/effects-webgl/gl-material.spec.ts @@ -1687,7 +1687,7 @@ function generateGLMaterial ( ) { const material = new GLMaterial(engine, { shader }); - material.sampleAlphaToCoverage = !!(states.sampleAlphaToCoverage); + material.sampleAlphaToCoverage = !!states.sampleAlphaToCoverage; material.depthTest = states.depthTest; material.depthMask = states.depthMask; material.depthRange = states.depthRange; From 31257581962693ea1f1333881261c422f952f904 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=8C=82=E5=AE=89?= <151805118+wumaolinmaoan@users.noreply.github.com> Date: Sat, 12 Oct 2024 22:41:24 +0800 Subject: [PATCH 34/88] refactor: composition component create (#669) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * refactor: composition component create * refactor: rename deserialize function * fix: composition items data * fix: type * refactor: pre composition create * refactor: remove extra camera * chore: 移除 isExtraCamera 方法,不再需要 * fix: type issue --------- Co-authored-by: yiiqii --- packages/effects-core/src/asset-loader.ts | 4 +- packages/effects-core/src/comp-vfx-item.ts | 73 +++++-------------- .../src/composition-source-manager.ts | 65 +++++++++-------- packages/effects-core/src/composition.ts | 16 ++-- .../effects-core/src/serialization-helper.ts | 8 +- packages/effects-core/src/vfx-item.ts | 4 - .../model/test/src/plugin-unit.spec.ts | 6 +- web-packages/demo/src/gui/inspector-gui.ts | 8 +- .../imgui-demo/src/core/asset-data-base.ts | 2 +- .../object-inspectors/vfx-item-inspector.ts | 2 +- .../test/case/2d/src/common/utilities.ts | 2 +- 11 files changed, 74 insertions(+), 116 deletions(-) diff --git a/packages/effects-core/src/asset-loader.ts b/packages/effects-core/src/asset-loader.ts index 5a81100b..164d4661 100644 --- a/packages/effects-core/src/asset-loader.ts +++ b/packages/effects-core/src/asset-loader.ts @@ -60,7 +60,7 @@ export class AssetLoader { effectsObject.setInstanceId(effectsObjectData.id); this.engine.addInstance(effectsObject); - SerializationHelper.deserializeTaggedProperties(effectsObjectData, effectsObject); + SerializationHelper.deserialize(effectsObjectData, effectsObject); return effectsObject as T; } @@ -124,7 +124,7 @@ export class AssetLoader { effectsObject.setInstanceId(effectsObjectData.id); this.engine.addInstance(effectsObject); - await SerializationHelper.deserializeTaggedPropertiesAsync(effectsObjectData, effectsObject); + await SerializationHelper.deserializeAsync(effectsObjectData, effectsObject); return effectsObject as T; } diff --git a/packages/effects-core/src/comp-vfx-item.ts b/packages/effects-core/src/comp-vfx-item.ts index 71bee580..083b1409 100644 --- a/packages/effects-core/src/comp-vfx-item.ts +++ b/packages/effects-core/src/comp-vfx-item.ts @@ -4,15 +4,14 @@ import { Vector3 } from '@galacean/effects-math/es/core/vector3'; import * as spec from '@galacean/effects-specification'; import { Behaviour } from './components'; import type { CompositionHitTestOptions } from './composition'; -import type { ContentOptions } from './composition-source-manager'; import type { Region, TrackAsset } from './plugins'; import { HitTestType, ObjectBindingTrack } from './plugins'; import type { Playable } from './plugins/cal/playable-graph'; import { PlayableGraph } from './plugins/cal/playable-graph'; import { TimelineAsset } from './plugins/cal/timeline-asset'; -import { Transform } from './transform'; import { generateGUID, noop } from './utils'; import { Item, VFXItem } from './vfx-item'; +import { SerializationHelper } from './serialization-helper'; export interface SceneBinding { key: TrackAsset, @@ -30,9 +29,7 @@ export interface SceneBindingData { export class CompositionComponent extends Behaviour { time = 0; startTime = 0; - refId: string; items: VFXItem[] = []; // 场景的所有元素 - data: ContentOptions; private reusable = false; private sceneBindings: SceneBinding[] = []; @@ -41,9 +38,9 @@ export class CompositionComponent extends Behaviour { private graph: PlayableGraph = new PlayableGraph(); override onStart (): void { - const { startTime = 0 } = this.item.props; - - this.startTime = startTime; + if (!this.timelineAsset) { + this.timelineAsset = new TimelineAsset(this.engine); + } this.resolveBindings(); this.timelinePlayable = this.timelineAsset.createPlayable(this.graph); @@ -82,53 +79,23 @@ export class CompositionComponent extends Behaviour { } createContent () { - const sceneBindings = []; - - for (const sceneBindingData of this.data.sceneBindings) { - sceneBindings.push({ - key: this.engine.assetLoader.loadGUID(sceneBindingData.key.id), - value: this.engine.assetLoader.loadGUID(sceneBindingData.value.id), - }); - } - this.sceneBindings = sceneBindings; - const timelineAsset = this.data.timelineAsset ? this.engine.assetLoader.loadGUID(this.data.timelineAsset.id) : new TimelineAsset(this.engine); - - this.timelineAsset = timelineAsset; - const items = this.items; - - this.items.length = 0; if (this.item.composition) { - const assetLoader = this.item.engine.assetLoader; - const itemProps = this.data.items ? this.data.items : []; - - for (let i = 0; i < itemProps.length; i++) { - let item: VFXItem; - const itemData = itemProps[i]; + for (const item of this.items) { + item.composition = this.item.composition; + const itemData = item.props; // 设置预合成作为元素时的时长、结束行为和渲染延时 if (Item.isComposition(itemData)) { + this.item.composition.refContent.push(item); const refId = itemData.content.options.refId; const props = this.item.composition.refCompositionProps.get(refId); if (!props) { throw new Error(`Referenced precomposition with Id: ${refId} does not exist.`); } - // endBehavior 类型需优化 - props.content = itemData.content; - item = assetLoader.loadGUID(itemData.id); - item.composition = this.item.composition; - - const compositionComponent = new CompositionComponent(this.engine); - - compositionComponent.item = item; - item.components.push(compositionComponent); - compositionComponent.data = props as unknown as ContentOptions; - compositionComponent.refId = refId; - item.transform.parentTransform = this.transform; - this.item.composition.refContent.push(item); - if (item.endBehavior === spec.EndBehavior.restart) { - this.item.composition.autoRefTex = false; - } + const compositionComponent = item.addComponent(CompositionComponent); + + SerializationHelper.deserialize(props as unknown as spec.EffectsObjectData, compositionComponent); compositionComponent.createContent(); for (const vfxItem of compositionComponent.items) { vfxItem.setInstanceId(generateGUID()); @@ -136,17 +103,7 @@ export class CompositionComponent extends Behaviour { component.setInstanceId(generateGUID()); } } - } else { - item = assetLoader.loadGUID(itemData.id); - item.composition = this.item.composition; - } - item.parent = this.item; - // 相机不跟随合成移动 - item.transform.parentTransform = itemData.type === spec.ItemType.camera ? new Transform() : this.transform; - if (VFXItem.isExtraCamera(item)) { - this.item.composition.extraCamera = item; } - items.push(item); } } } @@ -267,7 +224,13 @@ export class CompositionComponent extends Behaviour { return regions; } - override fromData (data: unknown): void { + override fromData (data: any): void { + super.fromData(data); + + this.items = data.items; + this.startTime = data.startTime ?? 0; + this.sceneBindings = data.sceneBindings; + this.timelineAsset = data.timelineAsset; } private resolveBindings () { diff --git a/packages/effects-core/src/composition-source-manager.ts b/packages/effects-core/src/composition-source-manager.ts index f53e62df..4c12fb39 100644 --- a/packages/effects-core/src/composition-source-manager.ts +++ b/packages/effects-core/src/composition-source-manager.ts @@ -7,7 +7,7 @@ import type { Scene, SceneRenderLevel } from './scene'; import { getGeometryByShape } from './shape'; import type { Texture } from './texture'; import type { Disposable } from './utils'; -import type { VFXItemProps } from './vfx-item'; +import type { VFXItemData } from './asset-loader'; let listOrder = 0; @@ -20,7 +20,7 @@ export interface ContentOptions { duration: number, name: string, endBehavior: spec.EndBehavior, - items: VFXItemProps[], + items: spec.DataPath[], camera: spec.CameraOptions, startTime: number, timelineAsset: spec.DataPath, @@ -32,9 +32,8 @@ export interface ContentOptions { */ export class CompositionSourceManager implements Disposable { composition?: spec.CompositionData; - refCompositions: Map = new Map(); - sourceContent?: ContentOptions; - refCompositionProps: Map = new Map(); + sourceContent?: spec.CompositionData; + refCompositionProps: Map = new Map(); renderLevel?: SceneRenderLevel; pluginSystem?: PluginSystem; totalTime: number; @@ -44,6 +43,8 @@ export class CompositionSourceManager implements Disposable { mask = 0; engine: Engine; + private refCompositions: Map = new Map(); + constructor ( scene: Scene, engine: Engine, @@ -78,37 +79,40 @@ export class CompositionSourceManager implements Disposable { this.sourceContent = this.getContent(this.composition); } - private getContent (composition: spec.CompositionData): ContentOptions { - const { id, duration, name, endBehavior, camera, startTime = 0 } = composition; - const items = this.assembleItems(composition); - - return { + private getContent (composition: spec.CompositionData): spec.CompositionData { + const compositionData: spec.CompositionData = { ...composition, - id, - duration, - name, - endBehavior: isNaN(endBehavior) ? spec.EndBehavior.freeze : endBehavior, - // looping, - items, - camera, - startTime, }; + + this.assembleItems(compositionData); + + if (isNaN(compositionData.endBehavior)) { + compositionData.endBehavior = spec.EndBehavior.freeze; + } + + if (!compositionData.startTime) { + compositionData.startTime = 0; + } + + return compositionData; } private assembleItems (composition: spec.CompositionData) { - const items: VFXItemProps[] = []; + this.mask++; const componentMap: Record = {}; + const items: spec.DataPath[] = []; - this.mask++; + if (!this.jsonScene) { + return; + } - for (const component of this.jsonScene?.components ?? []) { + for (const component of this.jsonScene.components) { componentMap[component.id] = component; } for (const itemDataPath of composition.items) { - //@ts-expect-error - const sourceItemData: VFXItemProps = this.engine.jsonSceneData[itemDataPath.id]; - const itemProps: Record = sourceItemData; + const sourceItemData = this.engine.jsonSceneData[itemDataPath.id] as VFXItemData; + const itemProps = sourceItemData; if (passRenderLevel(sourceItemData.renderLevel, this.renderLevel)) { itemProps.listIndex = listOrder++; @@ -127,22 +131,21 @@ export class CompositionSourceManager implements Disposable { // 处理预合成的渲染顺序 if (itemProps.type === spec.ItemType.composition) { const refId = (sourceItemData.content as spec.CompositionContent).options.refId; + const composition = this.refCompositions.get(refId); - if (!this.refCompositions.get(refId)) { + if (!composition) { throw new Error(`Invalid ref composition id: ${refId}.`); } - const ref = this.getContent(this.refCompositions.get(refId)!); + const ref = this.getContent(composition); if (!this.refCompositionProps.has(refId)) { - this.refCompositionProps.set(refId, ref as unknown as VFXItemProps); + this.refCompositionProps.set(refId, ref); } } - - items.push(itemProps as VFXItemProps); + items.push(itemDataPath); } } - - return items; + composition.items = items; } private preProcessItemContent ( diff --git a/packages/effects-core/src/composition.ts b/packages/effects-core/src/composition.ts index 3d342fb8..68e27c55 100644 --- a/packages/effects-core/src/composition.ts +++ b/packages/effects-core/src/composition.ts @@ -22,6 +22,7 @@ import type { CompositionEvent } from './events'; import { EventEmitter } from './events'; import type { PostProcessVolume } from './components'; import { SceneTicking } from './composition/scene-ticking'; +import { SerializationHelper } from './serialization-helper'; export interface CompositionStatistic { loadStart: number, @@ -106,8 +107,6 @@ export class Composition extends EventEmitter> imp * 是否播放完成后销毁 texture 对象 */ keepResource: boolean; - // 3D 模式下创建的场景相机 需要最后更新参数, TODO: 太 hack 了, 待移除 - extraCamera: VFXItem; /** * 合成内的元素否允许点击、拖拽交互 * @since 1.6.0 @@ -165,7 +164,7 @@ export class Composition extends EventEmitter> imp /** * 预合成的合成属性,在 content 中会被其元素属性覆盖 */ - refCompositionProps: Map = new Map(); + refCompositionProps: Map = new Map(); /** * 合成的相机对象 */ @@ -238,11 +237,7 @@ export class Composition extends EventEmitter> imp this.rootItem.composition = this; // Spawn rootCompositionComponent - this.rootComposition = new CompositionComponent(this.getEngine()); - this.rootComposition.startTime = sourceContent.startTime; - this.rootComposition.data = sourceContent; - this.rootComposition.item = this.rootItem; - this.rootItem.components.push(this.rootComposition); + this.rootComposition = this.rootItem.addComponent(CompositionComponent); this.width = width; this.height = height; @@ -273,7 +268,9 @@ export class Composition extends EventEmitter> imp this.handleItemMessage = handleItemMessage; this.createRenderFrame(); this.rendererOptions = null; + SerializationHelper.deserialize(sourceContent as unknown as spec.EffectsObjectData, this.rootComposition); this.rootComposition.createContent(); + this.buildItemTree(this.rootItem); this.rootItem.onEnd = () => { window.setTimeout(() => { @@ -281,7 +278,6 @@ export class Composition extends EventEmitter> imp }, 0); }; this.pluginSystem.resetComposition(this, this.renderFrame); - // this.initializeSceneTicking(this.rootItem); } initializeSceneTicking (item: VFXItem) { @@ -318,7 +314,7 @@ export class Composition extends EventEmitter> imp * 获取合成开始渲染的时间 */ get startTime () { - return this.rootComposition.startTime ?? 0; + return this.rootComposition.startTime; } /** diff --git a/packages/effects-core/src/serialization-helper.ts b/packages/effects-core/src/serialization-helper.ts index 8f8977b0..7495473f 100644 --- a/packages/effects-core/src/serialization-helper.ts +++ b/packages/effects-core/src/serialization-helper.ts @@ -70,13 +70,13 @@ export class SerializationHelper { if (!serializedDatas[serializeObject.getInstanceId()]) { serializedDatas[serializeObject.getInstanceId()] = {}; } - SerializationHelper.serializeTaggedProperties(serializeObject, serializedDatas[serializeObject.getInstanceId()]); + SerializationHelper.serialize(serializeObject, serializedDatas[serializeObject.getInstanceId()]); } return serializedDatas; } - static serializeTaggedProperties ( + static serialize ( effectsObject: EffectsObject, serializedData?: Record, ) { @@ -148,7 +148,7 @@ export class SerializationHelper { return serializedData; } - static deserializeTaggedProperties ( + static deserialize ( serializedData: spec.EffectsObjectData, effectsObject: EffectsObject, ) { @@ -183,7 +183,7 @@ export class SerializationHelper { effectsObject.fromData(taggedProperties as spec.EffectsObjectData); } - static async deserializeTaggedPropertiesAsync ( + static async deserializeAsync ( serializedData: spec.EffectsObjectData, effectsObject: EffectsObject, ) { diff --git a/packages/effects-core/src/vfx-item.ts b/packages/effects-core/src/vfx-item.ts index ae559507..9e1b38c5 100644 --- a/packages/effects-core/src/vfx-item.ts +++ b/packages/effects-core/src/vfx-item.ts @@ -141,10 +141,6 @@ export class VFXItem extends EffectsObject implements Disposable { return item.type === spec.ItemType.camera; } - static isExtraCamera (item: VFXItem) { - return item.id === 'extra-camera' && item.name === 'extra-camera'; - } - static isAncestor ( ancestorCandidate: VFXItem, descendantCandidate: VFXItem, diff --git a/plugin-packages/model/test/src/plugin-unit.spec.ts b/plugin-packages/model/test/src/plugin-unit.spec.ts index af985d6d..4092dc60 100644 --- a/plugin-packages/model/test/src/plugin-unit.spec.ts +++ b/plugin-packages/model/test/src/plugin-unit.spec.ts @@ -755,7 +755,7 @@ describe('渲染插件单测', function () { const animComp = new AnimationComponent(engine); - SerializationHelper.deserializeTaggedProperties(jsonScene.components[1], animComp); + SerializationHelper.deserialize(jsonScene.components[1], animComp); expect(animComp.clips.length).to.eql(1); const animClip = animComp.clips[0]; expect(animClip.duration).to.eql(2); @@ -977,7 +977,7 @@ describe('渲染插件单测', function () { }); expect(itemList[22].type).to.eql('mesh'); const itemMesh = new VFXItem(engine); - SerializationHelper.deserializeTaggedProperties(itemList[22], itemMesh); + SerializationHelper.deserialize(itemList[22], itemMesh); const meshComp = itemMesh.getComponent(ModelMeshComponent); const meshData = meshComp.data as spec.ModelMeshComponentData; expect(meshData.name).to.eql('Cesium_Man'); @@ -1109,7 +1109,7 @@ describe('渲染插件单测', function () { // expect(itemList[1].type).to.eql('mesh'); const itemMesh = new VFXItem(engine); - SerializationHelper.deserializeTaggedProperties(itemList[1], itemMesh); + SerializationHelper.deserialize(itemList[1], itemMesh); const meshComp = itemMesh.getComponent(ModelMeshComponent); const meshData = meshComp.data as spec.ModelMeshComponentData; expect(meshData.name).to.eql('WaterBottle'); diff --git a/web-packages/demo/src/gui/inspector-gui.ts b/web-packages/demo/src/gui/inspector-gui.ts index 8868b6c9..b7b80d91 100644 --- a/web-packages/demo/src/gui/inspector-gui.ts +++ b/web-packages/demo/src/gui/inspector-gui.ts @@ -82,7 +82,7 @@ export class InspectorGui { const guid = effectComponent.getInstanceId(); (this.item.engine.jsonSceneData[guid] as EffectComponentData).materials[0] = { id: effectsObjectData.id }; - SerializationHelper.deserializeTaggedProperties(this.item.engine.jsonSceneData[guid], effectComponent); + SerializationHelper.deserialize(this.item.engine.jsonSceneData[guid], effectComponent); } } this.itemDirtyFlag = true; @@ -101,7 +101,7 @@ export class InspectorGui { const guid = effectComponent.getInstanceId(); (this.item.engine.jsonSceneData[guid] as EffectComponentData).geometry = { id: effectsObjectData.id }; - SerializationHelper.deserializeTaggedProperties(this.item.engine.jsonSceneData[guid], effectComponent); + SerializationHelper.deserialize(this.item.engine.jsonSceneData[guid], effectComponent); } } }); @@ -302,10 +302,10 @@ export class SerializedObject { } update () { - SerializationHelper.serializeTaggedProperties(this.target, this.serializedData); + SerializationHelper.serialize(this.target, this.serializedData); } applyModifiedProperties () { - SerializationHelper.deserializeTaggedProperties(this.serializedData as spec.EffectsObjectData, this.target); + SerializationHelper.deserialize(this.serializedData as spec.EffectsObjectData, this.target); } } diff --git a/web-packages/imgui-demo/src/core/asset-data-base.ts b/web-packages/imgui-demo/src/core/asset-data-base.ts index f9b57dd0..ead18f47 100644 --- a/web-packages/imgui-demo/src/core/asset-data-base.ts +++ b/web-packages/imgui-demo/src/core/asset-data-base.ts @@ -361,7 +361,7 @@ export class EffectsPackage { }; for (const obj of this.exportObjects) { - effectsPackageData.exportObjects.push(SerializationHelper.serializeTaggedProperties(obj) as spec.EffectsObjectData); + effectsPackageData.exportObjects.push(SerializationHelper.serialize(obj) as spec.EffectsObjectData); } return effectsPackageData; diff --git a/web-packages/imgui-demo/src/object-inspectors/vfx-item-inspector.ts b/web-packages/imgui-demo/src/object-inspectors/vfx-item-inspector.ts index eb25c6ab..9d58fbd9 100644 --- a/web-packages/imgui-demo/src/object-inspectors/vfx-item-inspector.ts +++ b/web-packages/imgui-demo/src/object-inspectors/vfx-item-inspector.ts @@ -289,7 +289,7 @@ export class VFXItemInspector extends ObjectInspector { } } - SerializationHelper.deserializeTaggedProperties(serializedData, glMaterial); + SerializationHelper.deserialize(serializedData, glMaterial); if (dirtyFlag) { GalaceanEffects.assetDataBase.setDirty(glMaterial.getInstanceId()); } diff --git a/web-packages/test/case/2d/src/common/utilities.ts b/web-packages/test/case/2d/src/common/utilities.ts index 34b52504..537b8890 100644 --- a/web-packages/test/case/2d/src/common/utilities.ts +++ b/web-packages/test/case/2d/src/common/utilities.ts @@ -9,7 +9,7 @@ const { Vector3, Matrix4 } = math; const sleepTime = 20; const params = new URLSearchParams(location.search); -const oldVersion = params.get('version') || '2.1.0-alpha.3'; // 旧版Player版本 +const oldVersion = params.get('version') || '2.1.0-alpha.5'; // 旧版Player版本 const playerOptions: PlayerConfig = { //env: 'editor', //pixelRatio: 2, From 97b77e5664ce00d027b66483d6e738b2dfe65653 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=84=8F=E7=BB=AE?= Date: Mon, 14 Oct 2024 16:53:57 +0800 Subject: [PATCH 35/88] =?UTF-8?q?refactor:=20=E4=BC=98=E5=8C=96=E6=8F=92?= =?UTF-8?q?=E4=BB=B6=20assets=20=E8=B0=83=E7=94=A8=E9=80=BB=E8=BE=91=20(#6?= =?UTF-8?q?82)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * refactor: 优化插件 assets 调用逻辑 * revert: 恢复 assets 为私有属性 * chore: 根据机器人建议优化代码 --- packages/effects-core/src/asset-manager.ts | 86 ++++++++++--------- packages/effects-core/src/plugin-system.ts | 4 +- packages/effects-core/src/plugins/plugin.ts | 15 +++- .../multimedia/src/audio/audio-loader.ts | 2 +- .../multimedia/src/video/video-loader.ts | 2 +- 5 files changed, 61 insertions(+), 48 deletions(-) diff --git a/packages/effects-core/src/asset-manager.ts b/packages/effects-core/src/asset-manager.ts index a31ff06e..695a93fc 100644 --- a/packages/effects-core/src/asset-manager.ts +++ b/packages/effects-core/src/asset-manager.ts @@ -18,8 +18,6 @@ import { combineImageTemplate, getBackgroundImage } from './template-image'; import { Asset } from './asset'; import type { Engine } from './engine'; -type AssetsType = ImageLike | { url: string, type: TextureSourceType }; - let seed = 1; /** @@ -27,18 +25,22 @@ let seed = 1; * 用于加载和动效中所有的资源文件,包括图片、插件、图层粒子数据等 */ export class AssetManager implements Disposable { + /** + * 相对 url 的基本路径 + */ + private baseUrl: string; /** * 图像资源,用于创建和释放 GPU 纹理资源 */ - assets: Record = {}; + private assets: Record = {}; /** - * 相对 url 的基本路径 + * TextureSource 来源 */ - private baseUrl: string; + private sourceFrom: Record = {}; /** * 自定义文本缓存,随页面销毁而销毁 */ - static fonts: Set = new Set(); + private static fontCache: Set = new Set(); private id = seed++; /** @@ -147,26 +149,24 @@ export class AssetManager implements Disposable { const { jsonScene, pluginSystem, images: loadedImages } = scene; const { compositions, images } = jsonScene; - const [pluginResult] = await Promise.all([ - hookTimeInfo('plugin:prepareAssets', () => pluginSystem.prepareAssets(jsonScene, options)), + + this.assignImagesToAssets(images, loadedImages); + await Promise.all([ + hookTimeInfo('plugin:processAssets', () => this.processPluginAssets(jsonScene, pluginSystem, options)), hookTimeInfo('plugin:precompile', () => this.precompile(compositions, pluginSystem, renderer, options)), ]); - - await this.prepareAssets(images, loadedImages, pluginResult); } else { // TODO: JSONScene 中 bins 的类型可能为 ArrayBuffer[] const { jsonScene, pluginSystem } = await hookTimeInfo('processJSON', () => this.processJSON(rawJSON as JSONValue)); const { bins = [], images, compositions, fonts } = jsonScene; - const [loadedBins, loadedImages, pluginResult] = await Promise.all([ + const [loadedBins, loadedImages] = await Promise.all([ hookTimeInfo('processBins', () => this.processBins(bins)), hookTimeInfo('processImages', () => this.processImages(images, compressedTexture)), - hookTimeInfo('plugin:prepareAssets', () => pluginSystem.prepareAssets(jsonScene, options)), + hookTimeInfo('plugin:processAssets', () => this.processPluginAssets(jsonScene, pluginSystem, options)), hookTimeInfo('plugin:precompile', () => this.precompile(compositions, pluginSystem, renderer, options)), hookTimeInfo('processFontURL', () => this.processFontURL(fonts as spec.FontDefine[])), ]); - - await this.prepareAssets(images, loadedImages, pluginResult); const loadedTextures = await hookTimeInfo('processTextures', () => this.processTextures(loadedImages, loadedBins, jsonScene)); scene = { @@ -176,16 +176,16 @@ export class AssetManager implements Disposable { storage: {}, pluginSystem, jsonScene, + bins: loadedBins, images: loadedImages, textureOptions: loadedTextures, - bins: loadedBins, }; // 触发插件系统 pluginSystem 的回调 prepareResource await hookTimeInfo('plugin:prepareResource', () => pluginSystem.loadResources(scene, this.options)); } - await hookTimeInfo('processAssets', () => this.processAssets(renderer?.engine)); + await hookTimeInfo('prepareAssets', () => this.prepareAssets(renderer?.engine)); const totalTime = performance.now() - startTime; @@ -205,14 +205,14 @@ export class AssetManager implements Disposable { private async precompile ( compositions: spec.CompositionData[], - pluginSystem?: PluginSystem, + pluginSystem: PluginSystem, renderer?: Renderer, options?: PrecompileOptions, ) { if (!renderer || !renderer.getShaderLibrary()) { return; } - await pluginSystem?.precompile(compositions, renderer, options); + await pluginSystem.precompile(compositions, renderer, options); } private async processJSON (json: JSONValue) { @@ -253,7 +253,7 @@ export class AssetManager implements Disposable { const jobs = fonts.map(async font => { // 数据模版兼容判断 - if (font.fontURL && !AssetManager.fonts.has(font.fontFamily)) { + if (font.fontURL && !AssetManager.fontCache.has(font.fontFamily)) { if (!isValidFontFamily(font.fontFamily)) { // 在所有设备上提醒开发者 console.warn(`Risky font family: ${font.fontFamily}.`); @@ -264,7 +264,7 @@ export class AssetManager implements Disposable { await fontFace.load(); document.fonts.add(fontFace); - AssetManager.fonts.add(font.fontFamily); + AssetManager.fontCache.add(font.fontFamily); } catch (_) { logger.warn(`Invalid font family or font source: ${JSON.stringify(font.fontURL)}.`); } @@ -306,7 +306,7 @@ export class AssetManager implements Disposable { const resultImage = await loadMedia(url as string | string[], loadFn); if (resultImage instanceof HTMLVideoElement) { - this.assets[idx] = { url: resultImage.src, type: TextureSourceType.video }; + this.sourceFrom[idx] = { url: resultImage.src, type: TextureSourceType.video }; return resultImage; } else { @@ -315,7 +315,7 @@ export class AssetManager implements Disposable { variables[background.name] = resultImage.src; } - this.assets[idx] = { url: resultImage.src, type: TextureSourceType.image }; + this.sourceFrom[idx] = { url: resultImage.src, type: TextureSourceType.image }; return await combineImageTemplate( resultImage, @@ -340,7 +340,7 @@ export class AssetManager implements Disposable { if (src) { const bufferURL = new URL(src, baseUrl).href; - this.assets[idx] = { url: bufferURL, type: TextureSourceType.compressed }; + this.sourceFrom[idx] = { url: bufferURL, type: TextureSourceType.compressed }; return this.loadBins(bufferURL); } @@ -357,22 +357,23 @@ export class AssetManager implements Disposable { ? await loadAVIFOptional(imageURL, avifURL) : await loadWebPOptional(imageURL, webpURL); - this.assets[idx] = { url, type: TextureSourceType.image }; + this.sourceFrom[idx] = { url, type: TextureSourceType.image }; return image; }); + const loadedImages = await Promise.all(jobs); - return Promise.all(jobs); + this.assignImagesToAssets(images, loadedImages); + + return loadedImages; } - private async prepareAssets ( - images: spec.ImageSource[], - loadedImages: ImageLike[], - pluginResult: { - assets: spec.AssetBase[], - loadedAssets: unknown[], - }[], + private async processPluginAssets ( + jsonScene: spec.JSONScene, + pluginSystem: PluginSystem, + options?: SceneLoadOptions, ) { + const pluginResult = await pluginSystem.processAssets(jsonScene, options); const { assets, loadedAssets } = pluginResult.reduce((acc, cur) => { acc.assets = acc.assets.concat(cur.assets); acc.loadedAssets = acc.loadedAssets.concat(cur.loadedAssets); @@ -380,15 +381,12 @@ export class AssetManager implements Disposable { return acc; }, { assets: [], loadedAssets: [] }); - for (let i = 0; i < images.length; i++) { - this.assets[images[i].id] = loadedImages[i]; - } for (let i = 0; i < assets.length; i++) { - this.assets[assets[i].id] = loadedAssets[i] as AssetsType; + this.assets[assets[i].id] = loadedAssets[i] as ImageLike; } } - private async processAssets (engine?: Engine) { + private async prepareAssets (engine?: Engine) { if (!engine) { return; } @@ -420,8 +418,9 @@ export class AssetManager implements Disposable { throw new Error(`Load texture ${idx} fails, error message: ${e}.`); } } + const { source, id } = textureOptions; - let image: AssetsType | undefined; + let image: ImageLike | undefined; if (isObject(source)) { // source 为 images 数组 id image = this.assets[source.id as string]; @@ -430,7 +429,7 @@ export class AssetManager implements Disposable { } if (image) { - const texture = createTextureOptionsBySource(image, this.assets[idx], id); + const texture = createTextureOptionsBySource(image, this.sourceFrom[idx], id); return texture.sourceType === TextureSourceType.compressed ? texture : { ...texture, ...textureOptions }; } @@ -462,6 +461,12 @@ export class AssetManager implements Disposable { }); } + private assignImagesToAssets (images: spec.ImageSource[], loadedImages: ImageLike[]) { + for (let i = 0; i < images.length; i++) { + this.assets[images[i].id] = loadedImages[i]; + } + } + private removeTimer (id: number) { const index = this.timers.indexOf(id); @@ -477,13 +482,14 @@ export class AssetManager implements Disposable { this.timers.map(id => window.clearTimeout(id)); } this.assets = {}; + this.sourceFrom = {}; this.timers = []; } } function createTextureOptionsBySource ( image: TextureSourceOptions | ImageLike, - sourceFrom: AssetsType, + sourceFrom: { url: string, type: TextureSourceType }, id?: string, ) { const options = { diff --git a/packages/effects-core/src/plugin-system.ts b/packages/effects-core/src/plugin-system.ts index cbf468b0..43ad3753 100644 --- a/packages/effects-core/src/plugin-system.ts +++ b/packages/effects-core/src/plugin-system.ts @@ -95,8 +95,8 @@ export class PluginSystem { return this.callStatic('processRawJSON', json, options); } - async prepareAssets (json: spec.JSONScene, options?: SceneLoadOptions) { - return this.callStatic<{ assets: spec.AssetBase[], loadedAssets: unknown[] }>('prepareAssets', json, options); + async processAssets (json: spec.JSONScene, options?: SceneLoadOptions) { + return this.callStatic<{ assets: spec.AssetBase[], loadedAssets: unknown[] }>('processAssets', json, options); } async precompile ( diff --git a/packages/effects-core/src/plugins/plugin.ts b/packages/effects-core/src/plugins/plugin.ts index a31ce109..32b24931 100644 --- a/packages/effects-core/src/plugins/plugin.ts +++ b/packages/effects-core/src/plugins/plugin.ts @@ -7,7 +7,7 @@ import type { Composition } from '../composition'; export interface Plugin { /** * plugin 的数组内排序,按照升序排列 - * 默认为100 + * @default 100 */ order: number, name: string, @@ -103,7 +103,7 @@ export abstract class AbstractPlugin implements Plugin { name = ''; /*** - * player.loadScene 函数调用的时候会触发此函数, + * loadScene 函数调用的时候会触发此函数, * 此阶段可以对资源 JSON 进行处理,替换调 JSON 中的数据,或者直接终止加载流程 * 一旦被 reject,加载过程将失败 * @param json 动画资源 @@ -111,10 +111,17 @@ export abstract class AbstractPlugin implements Plugin { */ static processRawJSON: (json: spec.JSONScene, options: SceneLoadOptions) => Promise; - static prepareAssets: (json: spec.JSONScene, options?: SceneLoadOptions) => Promise<{ assets: spec.AssetBase[], loadedAssets: unknown[] }>; + /** + * loadScene 函数调用的时候会触发此函数, + * 此阶段可以加载插件所需类型资源,并返回原始资源和加载后的资源。 + * @param json + * @param options + * @returns + */ + static processAssets: (json: spec.JSONScene, options?: SceneLoadOptions) => Promise<{ assets: spec.AssetBase[], loadedAssets: unknown[] }>; /** - * player.loadScene 函数调用的时候会触发此函数, + * loadScene 函数调用的时候会触发此函数, * 此阶段时,json 中的图片和二进制已经被加载完成,可以对加载好的资源做进一步处理, * 如果 promise 被 reject, loadScene 函数同样会被 reject,表示场景加载失败。 * 请记住,整个 load 阶段都不要创建 GL 相关的对象,只创建 JS 对象 diff --git a/plugin-packages/multimedia/src/audio/audio-loader.ts b/plugin-packages/multimedia/src/audio/audio-loader.ts index 802b8513..e90da9fd 100644 --- a/plugin-packages/multimedia/src/audio/audio-loader.ts +++ b/plugin-packages/multimedia/src/audio/audio-loader.ts @@ -3,7 +3,7 @@ import { spec, AbstractPlugin } from '@galacean/effects'; import { processMultimedia } from '../utils'; export class AudioLoader extends AbstractPlugin { - static override async prepareAssets ( + static override async processAssets ( json: spec.JSONScene, options: SceneLoadOptions = {}, ) { diff --git a/plugin-packages/multimedia/src/video/video-loader.ts b/plugin-packages/multimedia/src/video/video-loader.ts index 6b054b05..95729a77 100644 --- a/plugin-packages/multimedia/src/video/video-loader.ts +++ b/plugin-packages/multimedia/src/video/video-loader.ts @@ -3,7 +3,7 @@ import { spec, AbstractPlugin } from '@galacean/effects'; import { processMultimedia } from '../utils'; export class VideoLoader extends AbstractPlugin { - static override async prepareAssets ( + static override async processAssets ( json: spec.JSONScene, options: SceneLoadOptions = {}, ) { From 5ddfc2fdc56233eace52333ba14207b6e3b1c965 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=8C=82=E5=AE=89?= <151805118+wumaolinmaoan@users.noreply.github.com> Date: Mon, 14 Oct 2024 17:42:30 +0800 Subject: [PATCH 36/88] feat: add curve property track (#679) * feat: add curve property track * fix: import * feat: add ellipse shape * feat: add float track * refactor: rename float track * style: import path --------- Co-authored-by: yiiqii --- packages/effects-core/src/comp-vfx-item.ts | 14 +- .../src/components/shape-component.ts | 159 +++++++--- .../effects-core/src/fallback/migration.ts | 5 +- .../src/plugins/cal/calculate-item.ts | 19 +- .../src/plugins/cal/calculate-vfx-item.ts | 12 +- packages/effects-core/src/plugins/index.ts | 8 +- .../effects-core/src/plugins/shape/ellipse.ts | 297 ++++++++++++++++++ .../src/plugins/shape/graphics-path.ts | 9 + .../src/plugins/shape/point-like.ts | 33 ++ .../effects-core/src/plugins/shape/point.ts | 93 ++++++ .../effects-core/src/plugins/shape/polygon.ts | 37 ++- .../src/plugins/shape/shape-path.ts | 49 ++- .../src/plugins/shape/shape-primitive.ts | 24 ++ .../src/plugins/timeline/index.ts | 3 + .../float-property-playable-asset.ts | 21 ++ .../plugins/timeline/playable-assets/index.ts | 3 + .../sub-composition-playable-asset.ts | 2 +- .../playable-assets}/timeline-asset.ts | 20 +- .../playables/float-property-clip-playable.ts | 12 + .../float-property-mixer-playable.ts | 44 +++ .../src/plugins/timeline/playables/index.ts | 5 + .../src/plugins/timeline/track.ts | 25 +- .../timeline/tracks/float-property-track.ts | 40 +++ .../src/plugins/timeline/tracks/index.ts | 5 + .../timeline/tracks/sub-composition-track.ts | 9 +- .../effects-core/src/render/semantic-map.ts | 2 +- .../imgui-demo/src/panels/sequencer.ts | 13 +- .../plugins/sprite/sprite-base.spec.ts | 2 +- 28 files changed, 854 insertions(+), 111 deletions(-) create mode 100644 packages/effects-core/src/plugins/shape/ellipse.ts create mode 100644 packages/effects-core/src/plugins/shape/point-like.ts create mode 100644 packages/effects-core/src/plugins/shape/point.ts create mode 100644 packages/effects-core/src/plugins/shape/shape-primitive.ts create mode 100644 packages/effects-core/src/plugins/timeline/index.ts create mode 100644 packages/effects-core/src/plugins/timeline/playable-assets/float-property-playable-asset.ts create mode 100644 packages/effects-core/src/plugins/timeline/playable-assets/index.ts rename packages/effects-core/src/plugins/timeline/{playables => playable-assets}/sub-composition-playable-asset.ts (86%) rename packages/effects-core/src/plugins/{cal => timeline/playable-assets}/timeline-asset.ts (86%) create mode 100644 packages/effects-core/src/plugins/timeline/playables/float-property-clip-playable.ts create mode 100644 packages/effects-core/src/plugins/timeline/playables/float-property-mixer-playable.ts create mode 100644 packages/effects-core/src/plugins/timeline/playables/index.ts create mode 100644 packages/effects-core/src/plugins/timeline/tracks/float-property-track.ts create mode 100644 packages/effects-core/src/plugins/timeline/tracks/index.ts diff --git a/packages/effects-core/src/comp-vfx-item.ts b/packages/effects-core/src/comp-vfx-item.ts index 083b1409..d18ae397 100644 --- a/packages/effects-core/src/comp-vfx-item.ts +++ b/packages/effects-core/src/comp-vfx-item.ts @@ -8,7 +8,7 @@ import type { Region, TrackAsset } from './plugins'; import { HitTestType, ObjectBindingTrack } from './plugins'; import type { Playable } from './plugins/cal/playable-graph'; import { PlayableGraph } from './plugins/cal/playable-graph'; -import { TimelineAsset } from './plugins/cal/timeline-asset'; +import { TimelineAsset } from './plugins/timeline'; import { generateGUID, noop } from './utils'; import { Item, VFXItem } from './vfx-item'; import { SerializationHelper } from './serialization-helper'; @@ -52,13 +52,13 @@ export class CompositionComponent extends Behaviour { setReusable (value: boolean) { for (const track of this.timelineAsset.tracks) { - const binding = track.binding; + const boundObject = track.boundObject; - if (binding instanceof VFXItem) { + if (boundObject instanceof VFXItem) { if (track instanceof ObjectBindingTrack) { - binding.reusable = value; + boundObject.reusable = value; } - const subCompositionComponent = binding.getComponent(CompositionComponent); + const subCompositionComponent = boundObject.getComponent(CompositionComponent); if (subCompositionComponent) { subCompositionComponent.setReusable(value); @@ -235,7 +235,7 @@ export class CompositionComponent extends Behaviour { private resolveBindings () { for (const sceneBinding of this.sceneBindings) { - sceneBinding.key.binding = sceneBinding.value; + sceneBinding.key.boundObject = sceneBinding.value; } for (const masterTrack of this.timelineAsset.tracks) { this.resolveTrackBindingsWithRoot(masterTrack); @@ -244,7 +244,7 @@ export class CompositionComponent extends Behaviour { private resolveTrackBindingsWithRoot (track: TrackAsset) { for (const subTrack of track.getChildTracks()) { - subTrack.binding = subTrack.resolveBinding(track.binding); + subTrack.resolveBinding(); this.resolveTrackBindingsWithRoot(subTrack); } diff --git a/packages/effects-core/src/components/shape-component.ts b/packages/effects-core/src/components/shape-component.ts index 12f70418..954da0f1 100644 --- a/packages/effects-core/src/components/shape-component.ts +++ b/packages/effects-core/src/components/shape-component.ts @@ -7,7 +7,6 @@ import type { MaterialProps } from '../material'; import { Material } from '../material'; import { GraphicsPath } from '../plugins/shape/graphics-path'; import type { ShapePath } from '../plugins/shape/shape-path'; -import { triangulate } from '../plugins/shape/triangulate'; import type { Renderer } from '../render'; import { Geometry, GLSLVersion } from '../render'; import { RendererComponent } from './renderer-component'; @@ -24,10 +23,11 @@ interface CurveData { */ @effectsClass('ShapeComponent') export class ShapeComponent extends RendererComponent { - path = new GraphicsPath(); + private path = new GraphicsPath(); private curveValues: CurveData[] = []; private geometry: Geometry; + private data: ShapeComponentData; private dirty = false; private vert = ` @@ -76,7 +76,7 @@ void main() { 0.5, -0.5, 0, //右下 ]), }, - aUV:{ + aUV: { type: glContext.FLOAT, size: 2, data: new Float32Array(), @@ -106,19 +106,9 @@ void main() { override onUpdate (dt: number): void { if (this.dirty) { - this.path.clear(); - this.path.moveTo(this.curveValues[this.curveValues.length - 1].point.x, this.curveValues[this.curveValues.length - 1].point.y); - - for (const curveValue of this.curveValues) { - const point = curveValue.point; - const control1 = curveValue.controlPoint1; - const control2 = curveValue.controlPoint2; - - this.path.bezierCurveTo(control1.x, control1.y, control2.x, control2.y, point.x, point.y, 1); - } - + this.buildPath(this.data); this.buildGeometryFromPath(this.path.shapePath); - this.dirty = false; + // this.dirty = false; } } @@ -129,22 +119,29 @@ void main() { renderer.drawGeometry(this.geometry, this.material); } - buildGeometryFromPath (shapePath: ShapePath) { + private buildGeometryFromPath (shapePath: ShapePath) { const shapePrimitives = shapePath.shapePrimitives; const vertices: number[] = []; + const indices: number[] = []; // triangulate shapePrimitive for (const shapePrimitive of shapePrimitives) { const shape = shapePrimitive.shape; + const points: number[] = []; + const indexOffset = indices.length; + const vertOffset = vertices.length / 2; + + shape.build(points); - vertices.push(...triangulate([shape.points])); + shape.triangulate(points, vertices, vertOffset, indices, indexOffset); } - // build vertices and uvs const vertexCount = vertices.length / 2; + // get the current attribute and index arrays from the geometry, avoiding re-creation let positionArray = this.geometry.getAttributeData('aPos'); let uvArray = this.geometry.getAttributeData('aUV'); + let indexArray = this.geometry.getIndexData(); if (!positionArray || positionArray.length < vertexCount * 3) { positionArray = new Float32Array(vertexCount * 3); @@ -152,7 +149,11 @@ void main() { if (!uvArray || uvArray.length < vertexCount * 2) { uvArray = new Float32Array(vertexCount * 2); } + if (!indexArray) { + indexArray = new Uint16Array(indices.length); + } + // set position and uv attribute array for (let i = 0; i < vertexCount; i++) { const pointsOffset = i * 3; const positionArrayOffset = i * 2; @@ -166,39 +167,75 @@ void main() { uvArray[uvOffset + 1] = positionArray[pointsOffset + 1]; } + // set index array + indexArray.set(indices); + + // rewrite to geometry this.geometry.setAttributeData('aPos', positionArray); this.geometry.setAttributeData('aUV', uvArray); - this.geometry.setDrawCount(vertexCount); + this.geometry.setIndexData(indexArray); + this.geometry.setDrawCount(indices.length); } - override fromData (data: ShapeCustomComponent): void { - super.fromData(data); - - const points = data.param.points; - const easingIns = data.param.easingIn; - const easingOuts = data.param.easingOut; - - for (const shape of data.param.shapes) { - const indices = shape.indexes; + private buildPath (data: ShapeComponentData) { + this.path.clear(); + switch (data.type) { + case ComponentShapeType.CUSTOM: { + const customData = data as ShapeCustomComponent; + const points = customData.param.points; + const easingIns = customData.param.easingIn; + const easingOuts = customData.param.easingOut; + + this.curveValues = []; + + for (const shape of customData.param.shapes) { + const indices = shape.indexes; + + for (let i = 1; i < indices.length; i++) { + const pointIndex = indices[i]; + const lastPointIndex = indices[i - 1]; + + this.curveValues.push({ + point: points[pointIndex.point], + controlPoint1: easingOuts[lastPointIndex.easingOut], + controlPoint2: easingIns[pointIndex.easingIn], + }); + } + + // Push the last curve + this.curveValues.push({ + point: points[indices[0].point], + controlPoint1: easingOuts[indices[indices.length - 1].easingOut], + controlPoint2: easingIns[indices[0].easingIn], + }); + } + + this.path.moveTo(this.curveValues[this.curveValues.length - 1].point.x, this.curveValues[this.curveValues.length - 1].point.y); + + for (const curveValue of this.curveValues) { + const point = curveValue.point; + const control1 = curveValue.controlPoint1; + const control2 = curveValue.controlPoint2; + + this.path.bezierCurveTo(control1.x, control1.y, control2.x, control2.y, point.x, point.y, 1); + } + + break; + } + case ComponentShapeType.ELLIPSE: { + const ellipseData = data as ShapeEllipseComponent; + const ellipseParam = ellipseData.param; - for (let i = 1; i < indices.length; i++) { - const pointIndex = indices[i]; - const lastPointIndex = indices[i - 1]; + this.path.ellipse(0, 0, ellipseParam.xRadius, ellipseParam.yRadius); - this.curveValues.push({ - point: points[pointIndex.point], - controlPoint1: easingOuts[lastPointIndex.easingOut], - controlPoint2: easingIns[pointIndex.easingIn], - }); + break; } - - // Push the last curve - this.curveValues.push({ - point: points[indices[0].point], - controlPoint1: easingOuts[indices[indices.length - 1].easingOut], - controlPoint2: easingIns[indices[0].easingIn], - }); } + } + + override fromData (data: ShapeComponentData): void { + super.fromData(data); + this.data = data; this.dirty = true; } @@ -366,3 +403,39 @@ export enum ShapeConnectType { // @待补充 export enum ShapePointType { } + +/** + * @description 椭圆组件参数 + */ +export interface ShapeEllipseComponent extends ShapeComponentData { + type: ComponentShapeType.ELLIPSE, + param: ShapeEllipseParam, +} + +/** + * @description 椭圆参数 + */ +export interface ShapeEllipseParam { + /** + * @description x轴半径 + * -- TODO 后续完善类型 + * -- TODO 可以看一下用xRadius/yRadius 还是 width/height + */ + xRadius: number, + /** + * @description y轴半径 + */ + yRadius: number, + /** + * 填充属性 + */ + fill?: ShapeFillParam, + /** + * 描边属性 + */ + stroke?: ShapeStrokeParam, + /** + * 空间变换 + */ + transform?: spec.TransformData, +} diff --git a/packages/effects-core/src/fallback/migration.ts b/packages/effects-core/src/fallback/migration.ts index 3bf2b972..d18097b7 100644 --- a/packages/effects-core/src/fallback/migration.ts +++ b/packages/effects-core/src/fallback/migration.ts @@ -3,8 +3,7 @@ import type { SpineContent, TimelineAssetData, } from '@galacean/effects-specification'; import { - DataType, END_BEHAVIOR_FREEZE, END_BEHAVIOR_PAUSE, END_BEHAVIOR_PAUSE_AND_DESTROY, - EndBehavior, ItemType, + DataType, END_BEHAVIOR_PAUSE, END_BEHAVIOR_PAUSE_AND_DESTROY, EndBehavior, ItemType, } from '@galacean/effects-specification'; import { generateGUID } from '../utils'; import { convertAnchor, ensureFixedNumber, ensureFixedVec3 } from './utils'; @@ -110,7 +109,7 @@ export function version30Migration (json: JSONSceneLegacy): JSONScene { // eslint-disable-next-line @typescript-eslint/no-unsafe-enum-comparison composition.endBehavior === END_BEHAVIOR_PAUSE ) { - composition.endBehavior = END_BEHAVIOR_FREEZE; + composition.endBehavior = EndBehavior.freeze; } // 过滤掉滤镜元素 diff --git a/packages/effects-core/src/plugins/cal/calculate-item.ts b/packages/effects-core/src/plugins/cal/calculate-item.ts index a6f2f312..f9971bf0 100644 --- a/packages/effects-core/src/plugins/cal/calculate-item.ts +++ b/packages/effects-core/src/plugins/cal/calculate-item.ts @@ -1,12 +1,13 @@ import * as spec from '@galacean/effects-specification'; -import type { Euler, Vector3 } from '@galacean/effects-math/es/core/index'; +import type { Euler } from '@galacean/effects-math/es/core/euler'; +import type { Vector3 } from '@galacean/effects-math/es/core/vector3'; import { effectsClass } from '../../decorators'; import type { ValueGetter } from '../../math'; -import type { VFXItem } from '../../vfx-item'; +import { VFXItem } from '../../vfx-item'; import { ParticleSystem } from '../particle/particle-system'; import { ParticleBehaviourPlayableAsset } from '../particle/particle-vfx-item'; -import { TrackAsset } from '../timeline/track'; -import type { TimelineAsset } from './timeline-asset'; +import { TrackAsset } from '../timeline'; +import type { TimelineAsset } from '../timeline'; /** * 基础位移属性数据 @@ -33,13 +34,17 @@ export type ItemLinearVelOverLifetime = { export class ObjectBindingTrack extends TrackAsset { create (timelineAsset: TimelineAsset): void { - const boundItem = this.binding as VFXItem; + if (!(this.boundObject instanceof VFXItem)) { + return; + } + + const boundItem = this.boundObject; - // 添加粒子动画 clip + // 添加粒子动画 clip // TODO 待移除 if (boundItem.getComponent(ParticleSystem)) { const particleTrack = timelineAsset.createTrack(TrackAsset, this, 'ParticleTrack'); - particleTrack.binding = this.binding; + particleTrack.boundObject = this.boundObject; const particleClip = particleTrack.createClip(ParticleBehaviourPlayableAsset); particleClip.start = boundItem.start; diff --git a/packages/effects-core/src/plugins/cal/calculate-vfx-item.ts b/packages/effects-core/src/plugins/cal/calculate-vfx-item.ts index 56dfb586..aae57bed 100644 --- a/packages/effects-core/src/plugins/cal/calculate-vfx-item.ts +++ b/packages/effects-core/src/plugins/cal/calculate-vfx-item.ts @@ -50,10 +50,10 @@ export class TransformAnimationPlayable extends AnimationPlayable { startSpeed: number; data: TransformPlayableAssetData; private velocity: Vector3; - private binding: VFXItem; + private boundObject: VFXItem; start (): void { - const boundItem = this.binding; + const boundItem = this.boundObject; const scale = boundItem.transform.scale; this.originalTransform = { @@ -133,15 +133,15 @@ export class TransformAnimationPlayable extends AnimationPlayable { } override processFrame (context: FrameContext): void { - if (!this.binding) { + if (!this.boundObject) { const boundObject = context.output.getUserData(); if (boundObject instanceof VFXItem) { - this.binding = boundObject; + this.boundObject = boundObject; this.start(); } } - if (this.binding && this.binding.composition) { + if (this.boundObject && this.boundObject.composition) { this.sampleAnimation(); } } @@ -150,7 +150,7 @@ export class TransformAnimationPlayable extends AnimationPlayable { * 应用时间轴K帧数据到对象 */ private sampleAnimation () { - const boundItem = this.binding; + const boundItem = this.boundObject; const duration = boundItem.duration; let life = this.time / duration; diff --git a/packages/effects-core/src/plugins/index.ts b/packages/effects-core/src/plugins/index.ts index 7907af4c..5abd7158 100644 --- a/packages/effects-core/src/plugins/index.ts +++ b/packages/effects-core/src/plugins/index.ts @@ -18,11 +18,5 @@ export * from './particle/particle-system-renderer'; export * from './cal/calculate-loader'; export * from './cal/calculate-vfx-item'; export * from './cal/calculate-item'; -export * from './timeline/track'; -export * from './timeline/tracks/transform-track'; -export * from './timeline/tracks/activation-track'; -export * from './timeline/tracks/sprite-color-track'; -export * from './timeline/tracks/sub-composition-track'; -export * from './timeline/playables/sub-composition-playable-asset'; -export * from './cal/timeline-asset'; +export * from './timeline'; export * from './text'; diff --git a/packages/effects-core/src/plugins/shape/ellipse.ts b/packages/effects-core/src/plugins/shape/ellipse.ts new file mode 100644 index 00000000..0d0f502c --- /dev/null +++ b/packages/effects-core/src/plugins/shape/ellipse.ts @@ -0,0 +1,297 @@ +import { ShapePrimitive } from './shape-primitive'; + +/** + * The Ellipse object is used to help draw graphics and can also be used to specify a hit area for containers. + */ +export class Ellipse extends ShapePrimitive { + /** + * The X coordinate of the center of this ellipse + * @default 0 + */ + x: number; + + /** + * The Y coordinate of the center of this ellipse + * @default 0 + */ + y: number; + + /** + * The half width of this ellipse + * @default 0 + */ + halfWidth: number; + + /** + * The half height of this ellipse + * @default 0 + */ + halfHeight: number; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * @default 'ellipse' + */ + readonly type = 'ellipse'; + + /** + * @param x - The X coordinate of the center of this ellipse + * @param y - The Y coordinate of the center of this ellipse + * @param halfWidth - The half width of this ellipse + * @param halfHeight - The half height of this ellipse + */ + constructor (x = 0, y = 0, halfWidth = 0, halfHeight = 0) { + super(); + this.x = x; + this.y = y; + this.halfWidth = halfWidth; + this.halfHeight = halfHeight; + } + + /** + * Creates a clone of this Ellipse instance + * @returns {Ellipse} A copy of the ellipse + */ + clone (): Ellipse { + return new Ellipse(this.x, this.y, this.halfWidth, this.halfHeight); + } + + /** + * Checks whether the x and y coordinates given are contained within this ellipse + * @param x - The X coordinate of the point to test + * @param y - The Y coordinate of the point to test + * @returns Whether the x/y coords are within this ellipse + */ + contains (x: number, y: number): boolean { + if (this.halfWidth <= 0 || this.halfHeight <= 0) { + return false; + } + + // normalize the coords to an ellipse with center 0,0 + let normx = ((x - this.x) / this.halfWidth); + let normy = ((y - this.y) / this.halfHeight); + + normx *= normx; + normy *= normy; + + return (normx + normy <= 1); + } + + /** + * Checks whether the x and y coordinates given are contained within this ellipse including stroke + * @param x - The X coordinate of the point to test + * @param y - The Y coordinate of the point to test + * @param width + * @returns Whether the x/y coords are within this ellipse + */ + strokeContains (x: number, y: number, width: number): boolean { + const { halfWidth, halfHeight } = this; + + if (halfWidth <= 0 || halfHeight <= 0) { + return false; + } + + const halfStrokeWidth = width / 2; + const innerA = halfWidth - halfStrokeWidth; + const innerB = halfHeight - halfStrokeWidth; + const outerA = halfWidth + halfStrokeWidth; + const outerB = halfHeight + halfStrokeWidth; + + const normalizedX = x - this.x; + const normalizedY = y - this.y; + + const innerEllipse = ((normalizedX * normalizedX) / (innerA * innerA)) + + ((normalizedY * normalizedY) / (innerB * innerB)); + const outerEllipse = ((normalizedX * normalizedX) / (outerA * outerA)) + + ((normalizedY * normalizedY) / (outerB * outerB)); + + return innerEllipse > 1 && outerEllipse <= 1; + } + + /** + * Returns the framing rectangle of the ellipse as a Rectangle object + * @param out + * @returns The framing rectangle + */ + // getBounds (out?: Rectangle): Rectangle { + // out = out || new Rectangle(); + + // out.x = this.x - this.halfWidth; + // out.y = this.y - this.halfHeight; + // out.width = this.halfWidth * 2; + // out.height = this.halfHeight * 2; + + // return out; + // } + + /** + * Copies another ellipse to this one. + * @param ellipse - The ellipse to copy from. + * @returns Returns itself. + */ + copyFrom (ellipse: Ellipse): this { + this.x = ellipse.x; + this.y = ellipse.y; + this.halfWidth = ellipse.halfWidth; + this.halfHeight = ellipse.halfHeight; + + return this; + } + + /** + * Copies this ellipse to another one. + * @param ellipse - The ellipse to copy to. + * @returns Returns given parameter. + */ + copyTo (ellipse: Ellipse): Ellipse { + ellipse.copyFrom(this); + + return ellipse; + } + + override getX (): number { + return this.x; + } + + override getY (): number { + return this.y; + } + + build (points: number[]) { + const x = this.x; + const y = this.y; + const rx = this.halfWidth; + const ry = this.halfHeight; + const dx = 0; + const dy = 0; + + if (!(rx >= 0 && ry >= 0 && dx >= 0 && dy >= 0)) { + return points; + } + + // Choose a number of segments such that the maximum absolute deviation from the circle is approximately 0.029 + const sampleDensity = 5; + const n = Math.ceil(sampleDensity * Math.sqrt(rx + ry)); + const m = (n * 8) + (dx ? 4 : 0) + (dy ? 4 : 0); + + if (m === 0) { + return points; + } + + if (n === 0) { + points[0] = points[6] = x + dx; + points[1] = points[3] = y + dy; + points[2] = points[4] = x - dx; + points[5] = points[7] = y - dy; + + return points; + } + + let j1 = 0; + let j2 = (n * 4) + (dx ? 2 : 0) + 2; + let j3 = j2; + let j4 = m; + + let x0 = dx + rx; + let y0 = dy; + let x1 = x + x0; + let x2 = x - x0; + let y1 = y + y0; + + points[j1++] = x1; + points[j1++] = y1; + points[--j2] = y1; + points[--j2] = x2; + + if (dy) { + const y2 = y - y0; + + points[j3++] = x2; + points[j3++] = y2; + points[--j4] = y2; + points[--j4] = x1; + } + + for (let i = 1; i < n; i++) { + const a = Math.PI / 2 * (i / n); + const x0 = dx + (Math.cos(a) * rx); + const y0 = dy + (Math.sin(a) * ry); + const x1 = x + x0; + const x2 = x - x0; + const y1 = y + y0; + const y2 = y - y0; + + points[j1++] = x1; + points[j1++] = y1; + points[--j2] = y1; + points[--j2] = x2; + points[j3++] = x2; + points[j3++] = y2; + points[--j4] = y2; + points[--j4] = x1; + } + + x0 = dx; + y0 = dy + ry; + x1 = x + x0; + x2 = x - x0; + y1 = y + y0; + const y2 = y - y0; + + points[j1++] = x1; + points[j1++] = y1; + points[--j4] = y2; + points[--j4] = x1; + + if (dx) { + points[j1++] = x2; + points[j1++] = y1; + points[--j4] = y2; + points[--j4] = x2; + } + + return points; + } + + triangulate (points: number[], vertices: number[], verticesOffset: number, indices: number[], indicesOffset: number) { + if (points.length === 0) { + return; + } + + // Compute center (average of all points) + let centerX = 0; let + centerY = 0; + + for (let i = 0; i < points.length; i += 2) { + centerX += points[i]; + centerY += points[i + 1]; + } + centerX /= (points.length / 2); + centerY /= (points.length / 2); + + // Set center vertex + let count = verticesOffset; + + vertices[count * 2] = centerX; + vertices[(count * 2) + 1] = centerY; + const centerIndex = count++; + + // Set edge vertices and indices + for (let i = 0; i < points.length; i += 2) { + vertices[count * 2] = points[i]; + vertices[(count * 2) + 1] = points[i + 1]; + + if (i > 0) { // Skip first point for indices + indices[indicesOffset++] = count; + indices[indicesOffset++] = centerIndex; + indices[indicesOffset++] = count - 1; + } + count++; + } + + // Connect last point to the first edge point + indices[indicesOffset++] = centerIndex + 1; + indices[indicesOffset++] = centerIndex; + indices[indicesOffset++] = count - 1; + } +} diff --git a/packages/effects-core/src/plugins/shape/graphics-path.ts b/packages/effects-core/src/plugins/shape/graphics-path.ts index aea347e9..22975648 100644 --- a/packages/effects-core/src/plugins/shape/graphics-path.ts +++ b/packages/effects-core/src/plugins/shape/graphics-path.ts @@ -3,6 +3,7 @@ * https://github.com/pixijs/pixijs/blob/dev/src/scene/graphics/shared/path/GraphicsPath.ts */ +import type { Matrix4 } from '@galacean/effects-math/es/core/matrix4'; import { ShapePath } from './shape-path'; export class GraphicsPath { @@ -61,6 +62,14 @@ export class GraphicsPath { return this; } + ellipse (x: number, y: number, radiusX: number, radiusY: number, matrix?: Matrix4) { + this.instructions.push({ action: 'ellipse', data: [x, y, radiusX, radiusY, matrix] }); + + this.dirty = true; + + return this; + } + clear (): GraphicsPath { this.instructions.length = 0; this.dirty = true; diff --git a/packages/effects-core/src/plugins/shape/point-like.ts b/packages/effects-core/src/plugins/shape/point-like.ts new file mode 100644 index 00000000..7bcd3579 --- /dev/null +++ b/packages/effects-core/src/plugins/shape/point-like.ts @@ -0,0 +1,33 @@ +import type { PointData } from './point-data'; + +/** + * Common interface for points. Both Point and ObservablePoint implement it + */ +export interface PointLike extends PointData { + /** + * Copies x and y from the given point + * @param p - The point to copy from + * @returns Returns itself. + */ + copyFrom: (p: PointData) => this, + /** + * Copies x and y into the given point + * @param p - The point to copy.fds + * @returns Given point with values updated + */ + copyTo: (p: T) => T, + /** + * Returns true if the given point is equal to this point + * @param p - The point to check + * @returns Whether the given point equal to this point + */ + equals: (p: PointData) => boolean, + /** + * Sets the point to a new x and y position. + * If y is omitted, both x and y will be set to x. + * @param {number} [x=0] - position of the point on the x axis + * @param {number} [y=x] - position of the point on the y axis + */ + set: (x?: number, y?: number) => void, +} + diff --git a/packages/effects-core/src/plugins/shape/point.ts b/packages/effects-core/src/plugins/shape/point.ts new file mode 100644 index 00000000..bc36718d --- /dev/null +++ b/packages/effects-core/src/plugins/shape/point.ts @@ -0,0 +1,93 @@ +import type { PointData } from './point-data'; +import type { PointLike } from './point-like'; + +/** + * The Point object represents a location in a two-dimensional coordinate system, where `x` represents + * the position on the horizontal axis and `y` represents the position on the vertical axis. + */ +export class Point implements PointLike { + /** + * Position of the point on the x axis + */ + x = 0; + /** + * Position of the point on the y axis + */ + y = 0; + + /** + * Creates a new `Point` + * @param {number} [x=0] - position of the point on the x axis + * @param {number} [y=0] - position of the point on the y axis + */ + constructor (x = 0, y = 0) { + this.x = x; + this.y = y; + } + + /** + * Creates a clone of this point + * @returns A clone of this point + */ + clone (): Point { + return new Point(this.x, this.y); + } + + /** + * Copies `x` and `y` from the given point into this point + * @param p - The point to copy from + * @returns The point instance itself + */ + copyFrom (p: PointData): this { + this.set(p.x, p.y); + + return this; + } + + /** + * Copies this point's x and y into the given point (`p`). + * @param p - The point to copy to. Can be any of type that is or extends `PointData` + * @returns The point (`p`) with values updated + */ + copyTo (p: T): T { + p.set(this.x, this.y); + + return p; + } + + /** + * Accepts another point (`p`) and returns `true` if the given point is equal to this point + * @param p - The point to check + * @returns Returns `true` if both `x` and `y` are equal + */ + equals (p: PointData): boolean { + return (p.x === this.x) && (p.y === this.y); + } + + /** + * Sets the point to a new `x` and `y` position. + * If `y` is omitted, both `x` and `y` will be set to `x`. + * @param {number} [x=0] - position of the point on the `x` axis + * @param {number} [y=x] - position of the point on the `y` axis + * @returns The point instance itself + */ + set (x = 0, y: number = x): this { + this.x = x; + this.y = y; + + return this; + } + + /** + * A static Point object with `x` and `y` values of `0`. Can be used to avoid creating new objects multiple times. + * @readonly + */ + static get shared (): Point { + tempPoint.x = 0; + tempPoint.y = 0; + + return tempPoint; + } +} + +const tempPoint = new Point(); diff --git a/packages/effects-core/src/plugins/shape/polygon.ts b/packages/effects-core/src/plugins/shape/polygon.ts index 8573b12f..34ebe0ac 100644 --- a/packages/effects-core/src/plugins/shape/polygon.ts +++ b/packages/effects-core/src/plugins/shape/polygon.ts @@ -3,16 +3,22 @@ * https://github.com/pixijs/pixijs/blob/dev/src/maths/shapes/Polygon.ts */ +import { ShapePrimitive } from './shape-primitive'; import type { PointData } from './point-data'; +import { triangulate } from './triangulate'; /** * A class to define a shape via user defined coordinates. */ -export class Polygon { - /** An array of the points of this polygon. */ +export class Polygon extends ShapePrimitive { + /** + * An array of the points of this polygon. + */ points: number[] = []; - /** `false` after moveTo, `true` after `closePath`. In all other cases it is `true`. */ + /** + * `false` after moveTo, `true` after `closePath`. In all other cases it is `true`. + */ closePath: boolean = false; constructor (points: PointData[] | number[]); @@ -25,6 +31,7 @@ export class Polygon { * x,y values e.g. `new Polygon(x,y, x,y, x,y, ...)` where `x` and `y` are Numbers. */ constructor (...points: (PointData[] | number[])[] | PointData[] | number[]) { + super(); let flat = Array.isArray(points[0]) ? points[0] : points; // if this is an array of points, convert it to a flat array of numbers @@ -126,15 +133,35 @@ export class Polygon { * Get the first X coordinate of the polygon * @readonly */ - get x (): number { + override getX (): number { return this.points[this.points.length - 2]; } /** * Get the first Y coordinate of the polygon * @readonly */ - get y (): number { + override getY (): number { return this.points[this.points.length - 1]; } + + override build (points: number[]): void { + for (let i = 0; i < this.points.length; i++) { + points[i] = this.points[i]; + } + } + + override triangulate (points: number[], vertices: number[], verticesOffset: number, indices: number[], indicesOffset: number): void { + const triangles = triangulate([points]); + + for (let i = 0; i < triangles.length; i++) { + vertices[verticesOffset + i] = triangles[i]; + } + + const vertexCount = triangles.length / 2; + + for (let i = 0; i < vertexCount; i++) { + indices[indicesOffset + i] = i; + } + } } diff --git a/packages/effects-core/src/plugins/shape/shape-path.ts b/packages/effects-core/src/plugins/shape/shape-path.ts index 564c7e40..d7d75479 100644 --- a/packages/effects-core/src/plugins/shape/shape-path.ts +++ b/packages/effects-core/src/plugins/shape/shape-path.ts @@ -3,19 +3,20 @@ * https://github.com/pixijs/pixijs/blob/dev/src/scene/graphics/shared/path/ShapePath.ts */ -import type { Matrix3 } from '@galacean/effects-math/es/core/matrix3'; +import type { Matrix4 } from '@galacean/effects-math/es/core/matrix4'; import { Polygon } from './polygon'; import { buildAdaptiveBezier } from './build-adaptive-bezier'; import type { GraphicsPath } from './graphics-path'; +import type { ShapePrimitive } from './shape-primitive'; +import { Ellipse } from './ellipse'; export class ShapePath { currentPoly: Polygon | null = null; - shapePrimitives: { shape: Polygon, transform?: Matrix3 }[] = []; + shapePrimitives: { shape: ShapePrimitive, transform?: Matrix4 }[] = []; constructor ( private graphicsPath: GraphicsPath, - ) { - } + ) { } /** Builds the path. */ buildPath () { @@ -36,6 +37,11 @@ export class ShapePath { case 'moveTo': { this.moveTo(data[0], data[1]); + break; + } + case 'ellipse': { + this.ellipse(data[0], data[1], data[2], data[3], data[4]); + break; } } @@ -80,6 +86,41 @@ export class ShapePath { return this; } + /** + * Draws an ellipse at the specified location and with the given x and y radii. + * An optional transformation can be applied, allowing for rotation, scaling, and translation. + * @param x - The x-coordinate of the center of the ellipse. + * @param y - The y-coordinate of the center of the ellipse. + * @param radiusX - The horizontal radius of the ellipse. + * @param radiusY - The vertical radius of the ellipse. + * @param transform - An optional `Matrix` object to apply a transformation to the ellipse. This can include rotations. + * @returns The instance of the current object for chaining. + */ + ellipse (x: number, y: number, radiusX: number, radiusY: number, transform?: Matrix4): this { + // TODO apply rotation to transform... + + this.drawShape(new Ellipse(x, y, radiusX, radiusY), transform); + + return this; + } + + /** + * Draws a given shape on the canvas. + * This is a generic method that can draw any type of shape specified by the `ShapePrimitive` parameter. + * An optional transformation matrix can be applied to the shape, allowing for complex transformations. + * @param shape - The shape to draw, defined as a `ShapePrimitive` object. + * @param matrix - An optional `Matrix` for transforming the shape. This can include rotations, + * scaling, and translations. + * @returns The instance of the current object for chaining. + */ + drawShape (shape: ShapePrimitive, matrix?: Matrix4): this { + this.endPoly(); + + this.shapePrimitives.push({ shape, transform: matrix }); + + return this; + } + /** * Starts a new polygon path from the specified starting point. * This method initializes a new polygon or ends the current one if it exists. diff --git a/packages/effects-core/src/plugins/shape/shape-primitive.ts b/packages/effects-core/src/plugins/shape/shape-primitive.ts new file mode 100644 index 00000000..0fef1682 --- /dev/null +++ b/packages/effects-core/src/plugins/shape/shape-primitive.ts @@ -0,0 +1,24 @@ +export abstract class ShapePrimitive { + + /** Checks whether the x and y coordinates passed to this function are contained within this ShapePrimitive. */ + abstract contains (x: number, y: number): boolean; + /** Checks whether the x and y coordinates passed to this function are contained within the stroke of this shape */ + // abstract strokeContains (x: number, y: number, strokeWidth: number): boolean; + /** Creates a clone of this ShapePrimitive instance. */ + abstract clone (): ShapePrimitive; + /** Copies the properties from another ShapePrimitive to this ShapePrimitive. */ + abstract copyFrom (source: ShapePrimitive): void; + /** Copies the properties from this ShapePrimitive to another ShapePrimitive. */ + abstract copyTo (destination: ShapePrimitive): void; + /** Returns the framing rectangle of the ShapePrimitive as a Rectangle object. */ + // getBounds(out?: Rectangle): Rectangle, + + /** The X coordinate of the shape */ + abstract getX (): number; + /** The Y coordinate of the shape */ + abstract getY (): number; + + abstract build (points: number[]): void; + + abstract triangulate (points: number[], vertices: number[], verticesOffset: number, indices: number[], indicesOffset: number): void; +} diff --git a/packages/effects-core/src/plugins/timeline/index.ts b/packages/effects-core/src/plugins/timeline/index.ts new file mode 100644 index 00000000..21c26c96 --- /dev/null +++ b/packages/effects-core/src/plugins/timeline/index.ts @@ -0,0 +1,3 @@ +export * from './track'; +export * from './tracks'; +export * from './playable-assets'; diff --git a/packages/effects-core/src/plugins/timeline/playable-assets/float-property-playable-asset.ts b/packages/effects-core/src/plugins/timeline/playable-assets/float-property-playable-asset.ts new file mode 100644 index 00000000..138b6e9e --- /dev/null +++ b/packages/effects-core/src/plugins/timeline/playable-assets/float-property-playable-asset.ts @@ -0,0 +1,21 @@ +import type { FixedNumberExpression } from '@galacean/effects-specification'; +import { createValueGetter } from '../../../math/value-getter'; +import { effectsClass, serialize } from '../../../decorators'; +import type { Playable, PlayableGraph } from '../../cal/playable-graph'; +import { PlayableAsset } from '../../cal/playable-graph'; +import { FloatPropertyClipPlayable } from '../playables'; + +@effectsClass('FloatPropertyPlayableAsset') +export class FloatPropertyPlayableAsset extends PlayableAsset { + @serialize() + curveData: FixedNumberExpression; + + override createPlayable (graph: PlayableGraph): Playable { + const clipPlayable = new FloatPropertyClipPlayable(graph); + + clipPlayable.curve = createValueGetter(this.curveData); + clipPlayable.value = clipPlayable.curve.getValue(0); + + return clipPlayable; + } +} diff --git a/packages/effects-core/src/plugins/timeline/playable-assets/index.ts b/packages/effects-core/src/plugins/timeline/playable-assets/index.ts new file mode 100644 index 00000000..c7221b60 --- /dev/null +++ b/packages/effects-core/src/plugins/timeline/playable-assets/index.ts @@ -0,0 +1,3 @@ +export * from './float-property-playable-asset'; +export * from './sub-composition-playable-asset'; +export * from './timeline-asset'; diff --git a/packages/effects-core/src/plugins/timeline/playables/sub-composition-playable-asset.ts b/packages/effects-core/src/plugins/timeline/playable-assets/sub-composition-playable-asset.ts similarity index 86% rename from packages/effects-core/src/plugins/timeline/playables/sub-composition-playable-asset.ts rename to packages/effects-core/src/plugins/timeline/playable-assets/sub-composition-playable-asset.ts index e837de77..3ef2494d 100644 --- a/packages/effects-core/src/plugins/timeline/playables/sub-composition-playable-asset.ts +++ b/packages/effects-core/src/plugins/timeline/playable-assets/sub-composition-playable-asset.ts @@ -2,7 +2,7 @@ import * as spec from '@galacean/effects-specification'; import { effectsClass } from '../../../decorators'; import type { Playable, PlayableGraph } from '../../cal/playable-graph'; import { PlayableAsset } from '../../cal/playable-graph'; -import { SubCompositionClipPlayable } from './sub-composition-clip-playable'; +import { SubCompositionClipPlayable } from '../playables'; @effectsClass(spec.DataType.SubCompositionPlayableAsset) export class SubCompositionPlayableAsset extends PlayableAsset { diff --git a/packages/effects-core/src/plugins/cal/timeline-asset.ts b/packages/effects-core/src/plugins/timeline/playable-assets/timeline-asset.ts similarity index 86% rename from packages/effects-core/src/plugins/cal/timeline-asset.ts rename to packages/effects-core/src/plugins/timeline/playable-assets/timeline-asset.ts index f306bf62..aa80b42a 100644 --- a/packages/effects-core/src/plugins/cal/timeline-asset.ts +++ b/packages/effects-core/src/plugins/timeline/playable-assets/timeline-asset.ts @@ -1,11 +1,11 @@ import * as spec from '@galacean/effects-specification'; -import { effectsClass, serialize } from '../../decorators'; -import { VFXItem } from '../../vfx-item'; -import type { RuntimeClip, TrackAsset } from '../timeline/track'; -import { ObjectBindingTrack } from './calculate-item'; -import type { FrameContext, PlayableGraph } from './playable-graph'; -import { Playable, PlayableAsset, PlayableTraversalMode } from './playable-graph'; -import type { Constructor } from '../../utils'; +import { effectsClass, serialize } from '../../../decorators'; +import { VFXItem } from '../../../vfx-item'; +import type { RuntimeClip, TrackAsset } from '../track'; +import { ObjectBindingTrack } from '../../cal/calculate-item'; +import type { FrameContext, PlayableGraph } from '../../cal/playable-graph'; +import { Playable, PlayableAsset, PlayableTraversalMode } from '../../cal/playable-graph'; +import type { Constructor } from '../../../utils'; @effectsClass(spec.DataType.TimelineAsset) export class TimelineAsset extends PlayableAsset { @@ -71,7 +71,7 @@ export class TimelinePlayable extends Playable { this.addInput(trackMixPlayable, 0); const trackOutput = track.createOutput(); - trackOutput.setUserData(track.binding); + trackOutput.setUserData(track.boundObject); graph.addOutput(trackOutput); trackOutput.setSourcePlayeble(this, this.getInputCount() - 1); @@ -112,8 +112,8 @@ export class TrackSortWrapper { } function compareTracks (a: TrackSortWrapper, b: TrackSortWrapper): number { - const bindingA = a.track.binding; - const bindingB = b.track.binding; + const bindingA = a.track.boundObject; + const bindingB = b.track.boundObject; if (!(bindingA instanceof VFXItem) || !(bindingB instanceof VFXItem)) { return a.originalIndex - b.originalIndex; diff --git a/packages/effects-core/src/plugins/timeline/playables/float-property-clip-playable.ts b/packages/effects-core/src/plugins/timeline/playables/float-property-clip-playable.ts new file mode 100644 index 00000000..6fa02c65 --- /dev/null +++ b/packages/effects-core/src/plugins/timeline/playables/float-property-clip-playable.ts @@ -0,0 +1,12 @@ +import type { ValueGetter } from '../../../math'; +import type { FrameContext } from '../../cal/playable-graph'; +import { Playable } from '../../cal/playable-graph'; + +export class FloatPropertyClipPlayable extends Playable { + value: number; + curve: ValueGetter; + + override processFrame (context: FrameContext): void { + this.value = this.curve.getValue(this.time); + } +} \ No newline at end of file diff --git a/packages/effects-core/src/plugins/timeline/playables/float-property-mixer-playable.ts b/packages/effects-core/src/plugins/timeline/playables/float-property-mixer-playable.ts new file mode 100644 index 00000000..cac6c3c1 --- /dev/null +++ b/packages/effects-core/src/plugins/timeline/playables/float-property-mixer-playable.ts @@ -0,0 +1,44 @@ +import type { FrameContext } from '../../cal/playable-graph'; +import { Playable } from '../../cal/playable-graph'; +import { FloatPropertyClipPlayable } from './float-property-clip-playable'; + +export class FloatPropertyMixerPlayable extends Playable { + propertyName = ''; + + override processFrame (context: FrameContext): void { + const boundObject = context.output.getUserData(); + + if (!boundObject) { + return; + } + + let hasInput = false; + let value = 0; + + // evaluate the curve + for (let i = 0; i < this.getInputCount(); i++) { + const weight = this.getInputWeight(i); + + if (weight > 0) { + const propertyClipPlayable = this.getInput(i); + + if (!(propertyClipPlayable instanceof FloatPropertyClipPlayable)) { + console.error('FloatPropertyTrack added non-FloatPropertyPlayableAsset'); + continue; + } + + const curveValue = propertyClipPlayable.value; + + value += curveValue * weight; + + hasInput = true; + } + } + + // set value + if (hasInput) { + (boundObject as Record)[this.propertyName] = value; + } + } +} + diff --git a/packages/effects-core/src/plugins/timeline/playables/index.ts b/packages/effects-core/src/plugins/timeline/playables/index.ts new file mode 100644 index 00000000..cefb5a62 --- /dev/null +++ b/packages/effects-core/src/plugins/timeline/playables/index.ts @@ -0,0 +1,5 @@ +export * from './activation-mixer-playable'; +export * from './float-property-clip-playable'; +export * from './float-property-mixer-playable'; +export * from './sub-composition-clip-playable'; +export * from './sub-composition-mixer-playable'; diff --git a/packages/effects-core/src/plugins/timeline/track.ts b/packages/effects-core/src/plugins/timeline/track.ts index 1ee4cd1e..db99fea4 100644 --- a/packages/effects-core/src/plugins/timeline/track.ts +++ b/packages/effects-core/src/plugins/timeline/track.ts @@ -42,9 +42,10 @@ export class TimelineClip { @effectsClass(spec.DataType.TrackAsset) export class TrackAsset extends PlayableAsset { name: string; - binding: object; - + boundObject: object; + parent: TrackAsset; trackType = TrackType.MasterTrack; + private clipSeed = 0; @serialize(TimelineClip) @@ -56,8 +57,10 @@ export class TrackAsset extends PlayableAsset { /** * 重写该方法以获取自定义对象绑定 */ - resolveBinding (parentBinding: object): object { - return parentBinding; + resolveBinding () { + if (this.parent) { + this.boundObject = this.parent.boundObject; + } } /** @@ -117,6 +120,7 @@ export class TrackAsset extends PlayableAsset { addChild (child: TrackAsset) { this.children.push(child); + child.parent = this; } createClip ( @@ -152,6 +156,13 @@ export class TrackAsset extends PlayableAsset { private createClipPlayable (graph: PlayableGraph, clip: TimelineClip) { return clip.asset.createPlayable(graph); } + + override fromData (data: spec.EffectsObjectData): void { + super.fromData(data); + for (const child of this.children) { + child.parent = this; + } + } } export enum TrackType { @@ -174,8 +185,8 @@ export class RuntimeClip { this.parentMixer = parentMixer; this.track = track; - if (this.track.binding instanceof VFXItem) { - this.particleSystem = this.track.binding.getComponent(ParticleSystem); + if (this.track.boundObject instanceof VFXItem) { + this.particleSystem = this.track.boundObject.getComponent(ParticleSystem); } } @@ -194,7 +205,7 @@ export class RuntimeClip { let weight = 1.0; let ended = false; let started = false; - const boundObject = this.track.binding; + const boundObject = this.track.boundObject; if (localTime >= clip.start + clip.duration && clip.endBehavior === spec.EndBehavior.destroy) { if (boundObject instanceof VFXItem && VFXItem.isParticle(boundObject) && this.particleSystem && !this.particleSystem.destroyed) { diff --git a/packages/effects-core/src/plugins/timeline/tracks/float-property-track.ts b/packages/effects-core/src/plugins/timeline/tracks/float-property-track.ts new file mode 100644 index 00000000..04ead92f --- /dev/null +++ b/packages/effects-core/src/plugins/timeline/tracks/float-property-track.ts @@ -0,0 +1,40 @@ +import { effectsClass, serialize } from '../../../decorators'; +import type { PlayableGraph, Playable } from '../../cal/playable-graph'; +import { FloatPropertyMixerPlayable } from '../playables'; +import { TrackAsset } from '../track'; + +@effectsClass('FloatPropertyTrack') +export class FloatPropertyTrack extends TrackAsset { + @serialize() + path = ''; + + propertyName = ''; + + override createTrackMixer (graph: PlayableGraph): Playable { + const mixer = new FloatPropertyMixerPlayable(graph); + + mixer.propertyName = this.propertyName; + + return mixer; + } + + override resolveBinding () { + const propertyNames = this.path.split('.'); + let target: Record = this.parent.boundObject; + + for (let i = 0; i < propertyNames.length - 1; i++) { + const property = target[propertyNames[i]]; + + if (property === undefined) { + console.error('The ' + propertyNames[i] + ' property of ' + target + ' was not found'); + } + target = property; + } + + if (propertyNames.length > 0) { + this.propertyName = propertyNames[propertyNames.length - 1]; + } + + this.boundObject = target; + } +} diff --git a/packages/effects-core/src/plugins/timeline/tracks/index.ts b/packages/effects-core/src/plugins/timeline/tracks/index.ts new file mode 100644 index 00000000..7d705d1d --- /dev/null +++ b/packages/effects-core/src/plugins/timeline/tracks/index.ts @@ -0,0 +1,5 @@ +export * from './activation-track'; +export * from './float-property-track'; +export * from './sprite-color-track'; +export * from './sub-composition-track'; +export * from './transform-track'; diff --git a/packages/effects-core/src/plugins/timeline/tracks/sub-composition-track.ts b/packages/effects-core/src/plugins/timeline/tracks/sub-composition-track.ts index 1051586b..e3dc64a2 100644 --- a/packages/effects-core/src/plugins/timeline/tracks/sub-composition-track.ts +++ b/packages/effects-core/src/plugins/timeline/tracks/sub-composition-track.ts @@ -4,17 +4,16 @@ import { CompositionComponent } from '../../../comp-vfx-item'; import { TrackAsset } from '../track'; import { effectsClass } from '../../../decorators'; import type { PlayableGraph, Playable } from '../../cal/playable-graph'; -import { SubCompositionMixerPlayable } from '../playables/sub-composition-mixer-playable'; +import { SubCompositionMixerPlayable } from '../playables'; @effectsClass(spec.DataType.SubCompositionTrack) export class SubCompositionTrack extends TrackAsset { - override resolveBinding (parentBinding: object): object { - if (!(parentBinding instanceof VFXItem)) { + override resolveBinding () { + if (!this.parent || !(this.parent.boundObject instanceof VFXItem)) { throw new Error('SubCompositionTrack needs to be set under the VFXItem track.'); } - - return parentBinding.getComponent(CompositionComponent); + this.boundObject = this.parent.boundObject.getComponent(CompositionComponent); } override createTrackMixer (graph: PlayableGraph): Playable { diff --git a/packages/effects-core/src/render/semantic-map.ts b/packages/effects-core/src/render/semantic-map.ts index 73a577dd..9acb92fe 100644 --- a/packages/effects-core/src/render/semantic-map.ts +++ b/packages/effects-core/src/render/semantic-map.ts @@ -29,7 +29,7 @@ export class SemanticMap implements Disposable { const ret = this.semantics[name]; if (isFunction(ret)) { - return (ret as SemanticFunc)(state); + return ret(state); } return ret; diff --git a/web-packages/imgui-demo/src/panels/sequencer.ts b/web-packages/imgui-demo/src/panels/sequencer.ts index cfa47a2e..ff5b6f00 100644 --- a/web-packages/imgui-demo/src/panels/sequencer.ts +++ b/web-packages/imgui-demo/src/panels/sequencer.ts @@ -1,5 +1,5 @@ import type { Composition, TrackAsset } from '@galacean/effects'; -import { CompositionComponent, VFXItem } from '@galacean/effects'; +import { Component, CompositionComponent, VFXItem } from '@galacean/effects'; import { editorWindow, menuItem } from '../core/decorators'; import { GalaceanEffects } from '../ge'; import { ImGui } from '../imgui'; @@ -55,12 +55,17 @@ export class Sequencer extends EditorWindow { //@ts-expect-error for (const track of compositionComponent.timelineAsset.tracks) { const trackAsset = track; + const boundObject = trackAsset.boundObject; - if (!(trackAsset.binding instanceof VFXItem)) { - continue; + let trackName = ''; + + if (boundObject instanceof VFXItem) { + trackName = boundObject.name; + } else if (boundObject instanceof Component) { + trackName = boundObject.constructor.name; } - if (ImGui.CollapsingHeader(trackAsset.binding.name, ImGui.ImGuiTreeNodeFlags.DefaultOpen)) { + if (ImGui.CollapsingHeader(trackName, ImGui.ImGuiTreeNodeFlags.DefaultOpen)) { this.drawTrack(trackAsset); } } diff --git a/web-packages/test/unit/src/effects-core/plugins/sprite/sprite-base.spec.ts b/web-packages/test/unit/src/effects-core/plugins/sprite/sprite-base.spec.ts index d69851df..4bb4f7e1 100644 --- a/web-packages/test/unit/src/effects-core/plugins/sprite/sprite-base.spec.ts +++ b/web-packages/test/unit/src/effects-core/plugins/sprite/sprite-base.spec.ts @@ -41,7 +41,7 @@ describe('core/plugins/sprite/item-base', () => { let spriteColorTrack; // @ts-expect-error - const spriteBindingTrack = comp.rootItem.getComponent(CompositionComponent).timelineAsset.tracks.find(track => track.binding === sprite1); + const spriteBindingTrack = comp.rootItem.getComponent(CompositionComponent).timelineAsset.tracks.find(track => track.boundObject === sprite1); for (const subTrack of spriteBindingTrack?.getChildTracks() ?? []) { if (subTrack instanceof SpriteColorTrack) { From ca0e9fe4283df49207e8797922052f3960b2bc3c Mon Sep 17 00:00:00 2001 From: SuRuiMeng <934274351@qq.com> Date: Tue, 15 Oct 2024 15:27:58 +0800 Subject: [PATCH 37/88] =?UTF-8?q?fix(threejs/sprite):=20=E4=BF=AE=E5=A4=8D?= =?UTF-8?q?=20threejs=20=E7=B2=92=E5=AD=90=E6=B8=B2=E6=9F=93=E9=97=AE?= =?UTF-8?q?=E9=A2=98=20&=20video=20=E6=97=A0=E6=B3=95=E9=87=8D=E6=92=AD?= =?UTF-8?q?=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/effects-core/src/plugins/sprite/sprite-item.ts | 7 ++++++- packages/effects-threejs/src/three-geometry.ts | 2 +- packages/effects-threejs/src/three-texture.ts | 1 + 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/packages/effects-core/src/plugins/sprite/sprite-item.ts b/packages/effects-core/src/plugins/sprite/sprite-item.ts index d471d5b6..a068f649 100644 --- a/packages/effects-core/src/plugins/sprite/sprite-item.ts +++ b/packages/effects-core/src/plugins/sprite/sprite-item.ts @@ -6,7 +6,7 @@ import { glContext } from '../../gl'; import type { GeometryDrawMode } from '../../render'; import { Geometry } from '../../render'; import type { GeometryFromShape } from '../../shape'; -import type { Texture } from '../../texture'; +import type { Texture, Texture2DSourceOptionsVideo } from '../../texture'; import type { PlayableGraph, Playable } from '../cal/playable-graph'; import { PlayableAsset } from '../cal/playable-graph'; import type { ColorPlayableAssetData } from '../../animation'; @@ -147,6 +147,11 @@ export class SpriteComponent extends BaseRenderComponent { dx, dy, ]); } + const { video } = this.renderer.texture.source as Texture2DSourceOptionsVideo; + + if (video?.paused) { + video.play().catch(e => { this.engine.renderErrors.add(e); }); + } } override onDestroy (): void { diff --git a/packages/effects-threejs/src/three-geometry.ts b/packages/effects-threejs/src/three-geometry.ts index 9d6a89ae..23e4dea6 100644 --- a/packages/effects-threejs/src/three-geometry.ts +++ b/packages/effects-threejs/src/three-geometry.ts @@ -172,7 +172,7 @@ export class ThreeGeometry extends Geometry { const buffer = new THREE.InterleavedBuffer(data, attributeBuffer.stride); attributeBuffer = this.attributes[name].attribute.data = buffer; - + this.attributes[name].buffer = buffer; this.geometry.setAttribute(name, this.attributes[name].attribute); } else { attributeBuffer.set(data, 0); diff --git a/packages/effects-threejs/src/three-texture.ts b/packages/effects-threejs/src/three-texture.ts index 4bb4fe7f..2f353a9b 100644 --- a/packages/effects-threejs/src/three-texture.ts +++ b/packages/effects-threejs/src/three-texture.ts @@ -66,6 +66,7 @@ export class ThreeTexture extends Texture { this.texture = this.createTextureByType(options); } this.texture.needsUpdate = true; + this.source = {}; } /** From 711dd763b865f3b4a56d6dba11699269c960e986 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=8C=82=E5=AE=89?= <151805118+wumaolinmaoan@users.noreply.github.com> Date: Tue, 15 Oct 2024 15:28:54 +0800 Subject: [PATCH 38/88] feat: add material track (#683) * feat: add material track * fix: import * style: typo --------- Co-authored-by: yiiqii --- packages/effects-core/src/comp-vfx-item.ts | 8 +++---- .../src/components/shape-component.ts | 15 +++++-------- packages/effects-core/src/composition.ts | 1 - .../src/plugins/timeline/tracks/index.ts | 1 + .../plugins/timeline/tracks/material-track.ts | 18 +++++++++++++++ plugin-packages/model/demo/src/editor-mode.ts | 9 +++++++- web-packages/demo/html/inspire/index.html | 22 ++++++++----------- 7 files changed, 45 insertions(+), 29 deletions(-) create mode 100644 packages/effects-core/src/plugins/timeline/tracks/material-track.ts diff --git a/packages/effects-core/src/comp-vfx-item.ts b/packages/effects-core/src/comp-vfx-item.ts index d18ae397..e9ea3e8d 100644 --- a/packages/effects-core/src/comp-vfx-item.ts +++ b/packages/effects-core/src/comp-vfx-item.ts @@ -10,7 +10,7 @@ import type { Playable } from './plugins/cal/playable-graph'; import { PlayableGraph } from './plugins/cal/playable-graph'; import { TimelineAsset } from './plugins/timeline'; import { generateGUID, noop } from './utils'; -import { Item, VFXItem } from './vfx-item'; +import { VFXItem } from './vfx-item'; import { SerializationHelper } from './serialization-helper'; export interface SceneBinding { @@ -82,12 +82,12 @@ export class CompositionComponent extends Behaviour { if (this.item.composition) { for (const item of this.items) { item.composition = this.item.composition; - const itemData = item.props; // 设置预合成作为元素时的时长、结束行为和渲染延时 - if (Item.isComposition(itemData)) { + if (VFXItem.isComposition(item)) { this.item.composition.refContent.push(item); - const refId = itemData.content.options.refId; + const compositionContent = item.props.content as unknown as spec.CompositionContent; + const refId = compositionContent.options.refId; const props = this.item.composition.refCompositionProps.get(refId); if (!props) { diff --git a/packages/effects-core/src/components/shape-component.ts b/packages/effects-core/src/components/shape-component.ts index 954da0f1..602ed22b 100644 --- a/packages/effects-core/src/components/shape-component.ts +++ b/packages/effects-core/src/components/shape-component.ts @@ -28,22 +28,18 @@ export class ShapeComponent extends RendererComponent { private curveValues: CurveData[] = []; private geometry: Geometry; private data: ShapeComponentData; - private dirty = false; + private animated = false; private vert = ` precision highp float; attribute vec3 aPos;//x y -varying vec4 vColor; - -uniform vec4 _Color; uniform mat4 effects_MatrixVP; uniform mat4 effects_MatrixInvV; uniform mat4 effects_ObjectToWorld; void main() { - vColor = _Color; vec4 pos = vec4(aPos.xyz, 1.0); gl_Position = effects_MatrixVP * effects_ObjectToWorld * pos; } @@ -52,10 +48,10 @@ void main() { private frag = ` precision highp float; -varying vec4 vColor; +uniform vec4 _Color; void main() { - vec4 color = vec4(1.0,1.0,1.0,1.0); + vec4 color = _Color; gl_FragColor = color; } `; @@ -105,10 +101,9 @@ void main() { } override onUpdate (dt: number): void { - if (this.dirty) { + if (this.animated) { this.buildPath(this.data); this.buildGeometryFromPath(this.path.shapePath); - // this.dirty = false; } } @@ -237,7 +232,7 @@ void main() { super.fromData(data); this.data = data; - this.dirty = true; + this.animated = true; } } diff --git a/packages/effects-core/src/composition.ts b/packages/effects-core/src/composition.ts index 68e27c55..b28f32cb 100644 --- a/packages/effects-core/src/composition.ts +++ b/packages/effects-core/src/composition.ts @@ -645,7 +645,6 @@ export class Composition extends EventEmitter> imp } const itemMap = new Map(); - const contentItems = compVFXItem.getComponent(CompositionComponent).items; for (const item of contentItems) { diff --git a/packages/effects-core/src/plugins/timeline/tracks/index.ts b/packages/effects-core/src/plugins/timeline/tracks/index.ts index 7d705d1d..04928190 100644 --- a/packages/effects-core/src/plugins/timeline/tracks/index.ts +++ b/packages/effects-core/src/plugins/timeline/tracks/index.ts @@ -3,3 +3,4 @@ export * from './float-property-track'; export * from './sprite-color-track'; export * from './sub-composition-track'; export * from './transform-track'; +export * from './material-track'; diff --git a/packages/effects-core/src/plugins/timeline/tracks/material-track.ts b/packages/effects-core/src/plugins/timeline/tracks/material-track.ts new file mode 100644 index 00000000..76cce7d3 --- /dev/null +++ b/packages/effects-core/src/plugins/timeline/tracks/material-track.ts @@ -0,0 +1,18 @@ +import { RendererComponent } from '../../../components'; +import { effectsClass, serialize } from '../../../decorators'; +import { TrackAsset } from '../track'; + +@effectsClass('MaterialTrack') +export class MaterialTrack extends TrackAsset { + + @serialize() + index: number; + + override resolveBinding () { + if (!(this.parent.boundObject instanceof RendererComponent)) { + return; + } + this.parent.boundObject; + this.boundObject = this.parent.boundObject.materials[this.index]; + } +} diff --git a/plugin-packages/model/demo/src/editor-mode.ts b/plugin-packages/model/demo/src/editor-mode.ts index 568dd006..0cd870b8 100644 --- a/plugin-packages/model/demo/src/editor-mode.ts +++ b/plugin-packages/model/demo/src/editor-mode.ts @@ -203,8 +203,15 @@ function addWireframeItems (scene: spec.JSONScene, hide3DModel = true) { }); const newComposition = { ...scene.compositions[0], - items: newItems.filter((item: any) => item.id), }; + const items = []; + + for (const item of newItems) { + items.push({ + id: item.id, + }); + } + newComposition.items = items; const newScene: spec.JSONScene = { ...scene, components: newComponents, diff --git a/web-packages/demo/html/inspire/index.html b/web-packages/demo/html/inspire/index.html index f768c86f..734309d3 100644 --- a/web-packages/demo/html/inspire/index.html +++ b/web-packages/demo/html/inspire/index.html @@ -45,23 +45,19 @@
-
- -
-
- -
+ + +
+
+
- +
-
- -
From d3205aa3c6163222413aab2d54414493ebd35301 Mon Sep 17 00:00:00 2001 From: wumaolinmaoan Date: Wed, 16 Oct 2024 14:16:32 +0800 Subject: [PATCH 39/88] feat: add rect shape --- .../src/plugins/shape/graphics-path.ts | 37 +- .../src/plugins/shape/rectangle.ts | 414 ++++++++++++++++++ .../src/plugins/shape/triangle.ts | 206 +++++++++ 3 files changed, 655 insertions(+), 2 deletions(-) create mode 100644 packages/effects-core/src/plugins/shape/rectangle.ts create mode 100644 packages/effects-core/src/plugins/shape/triangle.ts diff --git a/packages/effects-core/src/plugins/shape/graphics-path.ts b/packages/effects-core/src/plugins/shape/graphics-path.ts index 22975648..f9c126ec 100644 --- a/packages/effects-core/src/plugins/shape/graphics-path.ts +++ b/packages/effects-core/src/plugins/shape/graphics-path.ts @@ -54,6 +54,12 @@ export class GraphicsPath { return this; } + /** + * Sets the starting point for a new sub-path. Any subsequent drawing commands are considered part of this path. + * @param x - The x-coordinate for the starting point. + * @param y - The y-coordinate for the starting point. + * @returns The instance of the current object for chaining. + */ moveTo (x: number, y: number): GraphicsPath { this.instructions.push({ action: 'moveTo', data: [x, y] }); @@ -62,8 +68,35 @@ export class GraphicsPath { return this; } - ellipse (x: number, y: number, radiusX: number, radiusY: number, matrix?: Matrix4) { - this.instructions.push({ action: 'ellipse', data: [x, y, radiusX, radiusY, matrix] }); + /** + * Draws an ellipse at the specified location and with the given x and y radii. + * An optional transformation can be applied, allowing for rotation, scaling, and translation. + * @param x - The x-coordinate of the center of the ellipse. + * @param y - The y-coordinate of the center of the ellipse. + * @param radiusX - The horizontal radius of the ellipse. + * @param radiusY - The vertical radius of the ellipse. + * @param transform - An optional `Matrix` object to apply a transformation to the ellipse. This can include rotations. + * @returns The instance of the current object for chaining. + */ + ellipse (x: number, y: number, radiusX: number, radiusY: number, transform?: Matrix4) { + this.instructions.push({ action: 'ellipse', data: [x, y, radiusX, radiusY, transform] }); + + this.dirty = true; + + return this; + } + + /** + * Draws a rectangle shape. This method adds a new rectangle path to the current drawing. + * @param x - The x-coordinate of the top-left corner of the rectangle. + * @param y - The y-coordinate of the top-left corner of the rectangle. + * @param w - The width of the rectangle. + * @param h - The height of the rectangle. + * @param transform - An optional `Matrix` object to apply a transformation to the rectangle. + * @returns The instance of the current object for chaining. + */ + rect (x: number, y: number, w: number, h: number, transform?: Matrix4): this { + this.instructions.push({ action: 'rect', data: [x, y, w, h, transform] }); this.dirty = true; diff --git a/packages/effects-core/src/plugins/shape/rectangle.ts b/packages/effects-core/src/plugins/shape/rectangle.ts new file mode 100644 index 00000000..6bc14f78 --- /dev/null +++ b/packages/effects-core/src/plugins/shape/rectangle.ts @@ -0,0 +1,414 @@ +import { ShapePrimitive } from './shape-primitive'; + +// const tempPoints = [new Point(), new Point(), new Point(), new Point()]; + +/** + * The `Rectangle` object is an area defined by its position, as indicated by its top-left corner + * point (`x`, `y`) and by its `width` and its `height`. + */ +export class Rectangle extends ShapePrimitive { + /** + * The X coordinate of the upper-left corner of the rectangle + * @default 0 + */ + x: number; + + /** + * The Y coordinate of the upper-left corner of the rectangle + * @default 0 + */ + y: number; + + /** + * The overall width of this rectangle + * @default 0 + */ + width: number; + + /** + * The overall height of this rectangle + * @default 0 + */ + height: number; + + /** + * @param x - The X coordinate of the upper-left corner of the rectangle + * @param y - The Y coordinate of the upper-left corner of the rectangle + * @param width - The overall width of the rectangle + * @param height - The overall height of the rectangle + */ + constructor (x: string | number = 0, y: string | number = 0, width: string | number = 0, height: string | number = 0) { + super(); + this.x = Number(x); + this.y = Number(y); + this.width = Number(width); + this.height = Number(height); + } + + /** Returns the left edge of the rectangle. */ + get left (): number { + return this.x; + } + + /** Returns the right edge of the rectangle. */ + get right (): number { + return this.x + this.width; + } + + /** Returns the top edge of the rectangle. */ + get top (): number { + return this.y; + } + + /** Returns the bottom edge of the rectangle. */ + get bottom (): number { + return this.y + this.height; + } + + /** Determines whether the Rectangle is empty. */ + isEmpty (): boolean { + return this.left === this.right || this.top === this.bottom; + } + + /** A constant empty rectangle. This is a new object every time the property is accessed */ + static get EMPTY (): Rectangle { + return new Rectangle(0, 0, 0, 0); + } + + /** + * Creates a clone of this Rectangle + * @returns a copy of the rectangle + */ + clone (): Rectangle { + return new Rectangle(this.x, this.y, this.width, this.height); + } + + /** + * Converts a Bounds object to a Rectangle object. + * @param bounds - The bounds to copy and convert to a rectangle. + * @returns Returns itself. + */ + // copyFromBounds (bounds: Bounds): this { + // this.x = bounds.minX; + // this.y = bounds.minY; + // this.width = bounds.maxX - bounds.minX; + // this.height = bounds.maxY - bounds.minY; + + // return this; + // } + + /** + * Copies another rectangle to this one. + * @param rectangle - The rectangle to copy from. + * @returns Returns itself. + */ + copyFrom (rectangle: Rectangle): Rectangle { + this.x = rectangle.x; + this.y = rectangle.y; + this.width = rectangle.width; + this.height = rectangle.height; + + return this; + } + + /** + * Copies this rectangle to another one. + * @param rectangle - The rectangle to copy to. + * @returns Returns given parameter. + */ + copyTo (rectangle: Rectangle): Rectangle { + rectangle.copyFrom(this); + + return rectangle; + } + + /** + * Checks whether the x and y coordinates given are contained within this Rectangle + * @param x - The X coordinate of the point to test + * @param y - The Y coordinate of the point to test + * @returns Whether the x/y coordinates are within this Rectangle + */ + contains (x: number, y: number): boolean { + if (this.width <= 0 || this.height <= 0) { + return false; + } + + if (x >= this.x && x < this.x + this.width) { + if (y >= this.y && y < this.y + this.height) { + return true; + } + } + + return false; + } + + /** + * Checks whether the x and y coordinates given are contained within this rectangle including the stroke. + * @param x - The X coordinate of the point to test + * @param y - The Y coordinate of the point to test + * @param strokeWidth - The width of the line to check + * @returns Whether the x/y coordinates are within this rectangle + */ + strokeContains (x: number, y: number, strokeWidth: number): boolean { + const { width, height } = this; + + if (width <= 0 || height <= 0) { return false; } + + const _x = this.x; + const _y = this.y; + + const outerLeft = _x - (strokeWidth / 2); + const outerRight = _x + width + (strokeWidth / 2); + const outerTop = _y - (strokeWidth / 2); + const outerBottom = _y + height + (strokeWidth / 2); + const innerLeft = _x + (strokeWidth / 2); + const innerRight = _x + width - (strokeWidth / 2); + const innerTop = _y + (strokeWidth / 2); + const innerBottom = _y + height - (strokeWidth / 2); + + return (x >= outerLeft && x <= outerRight && y >= outerTop && y <= outerBottom) + && !(x > innerLeft && x < innerRight && y > innerTop && y < innerBottom); + } + /** + * Determines whether the `other` Rectangle transformed by `transform` intersects with `this` Rectangle object. + * Returns true only if the area of the intersection is >0, this means that Rectangles + * sharing a side are not overlapping. Another side effect is that an arealess rectangle + * (width or height equal to zero) can't intersect any other rectangle. + * @param {Rectangle} other - The Rectangle to intersect with `this`. + * @param {Matrix} transform - The transformation matrix of `other`. + * @returns {boolean} A value of `true` if the transformed `other` Rectangle intersects with `this`; otherwise `false`. + */ + // intersects (other: Rectangle, transform?: Matrix4): boolean { + // if (!transform) { + // const x0 = this.x < other.x ? other.x : this.x; + // const x1 = this.right > other.right ? other.right : this.right; + + // if (x1 <= x0) { + // return false; + // } + + // const y0 = this.y < other.y ? other.y : this.y; + // const y1 = this.bottom > other.bottom ? other.bottom : this.bottom; + + // return y1 > y0; + // } + + // const x0 = this.left; + // const x1 = this.right; + // const y0 = this.top; + // const y1 = this.bottom; + + // if (x1 <= x0 || y1 <= y0) { + // return false; + // } + + // const lt = tempPoints[0].set(other.left, other.top); + // const lb = tempPoints[1].set(other.left, other.bottom); + // const rt = tempPoints[2].set(other.right, other.top); + // const rb = tempPoints[3].set(other.right, other.bottom); + + // if (rt.x <= lt.x || lb.y <= lt.y) { + // return false; + // } + + // const s = Math.sign((transform.a * transform.d) - (transform.b * transform.c)); + + // if (s === 0) { + // return false; + // } + + // transform.apply(lt, lt); + // transform.apply(lb, lb); + // transform.apply(rt, rt); + // transform.apply(rb, rb); + + // if (Math.max(lt.x, lb.x, rt.x, rb.x) <= x0 + // || Math.min(lt.x, lb.x, rt.x, rb.x) >= x1 + // || Math.max(lt.y, lb.y, rt.y, rb.y) <= y0 + // || Math.min(lt.y, lb.y, rt.y, rb.y) >= y1) { + // return false; + // } + + // const nx = s * (lb.y - lt.y); + // const ny = s * (lt.x - lb.x); + // const n00 = (nx * x0) + (ny * y0); + // const n10 = (nx * x1) + (ny * y0); + // const n01 = (nx * x0) + (ny * y1); + // const n11 = (nx * x1) + (ny * y1); + + // if (Math.max(n00, n10, n01, n11) <= (nx * lt.x) + (ny * lt.y) + // || Math.min(n00, n10, n01, n11) >= (nx * rb.x) + (ny * rb.y)) { + // return false; + // } + + // const mx = s * (lt.y - rt.y); + // const my = s * (rt.x - lt.x); + // const m00 = (mx * x0) + (my * y0); + // const m10 = (mx * x1) + (my * y0); + // const m01 = (mx * x0) + (my * y1); + // const m11 = (mx * x1) + (my * y1); + + // if (Math.max(m00, m10, m01, m11) <= (mx * lt.x) + (my * lt.y) + // || Math.min(m00, m10, m01, m11) >= (mx * rb.x) + (my * rb.y)) { + // return false; + // } + + // return true; + // } + + /** + * Pads the rectangle making it grow in all directions. + * If paddingY is omitted, both paddingX and paddingY will be set to paddingX. + * @param paddingX - The horizontal padding amount. + * @param paddingY - The vertical padding amount. + * @returns Returns itself. + */ + pad (paddingX = 0, paddingY = paddingX): this { + this.x -= paddingX; + this.y -= paddingY; + + this.width += paddingX * 2; + this.height += paddingY * 2; + + return this; + } + + /** + * Fits this rectangle around the passed one. + * @param rectangle - The rectangle to fit. + * @returns Returns itself. + */ + fit (rectangle: Rectangle): this { + const x1 = Math.max(this.x, rectangle.x); + const x2 = Math.min(this.x + this.width, rectangle.x + rectangle.width); + const y1 = Math.max(this.y, rectangle.y); + const y2 = Math.min(this.y + this.height, rectangle.y + rectangle.height); + + this.x = x1; + this.width = Math.max(x2 - x1, 0); + this.y = y1; + this.height = Math.max(y2 - y1, 0); + + return this; + } + + /** + * Enlarges rectangle that way its corners lie on grid + * @param resolution - resolution + * @param eps - precision + * @returns Returns itself. + */ + ceil (resolution = 1, eps = 0.001): this { + const x2 = Math.ceil((this.x + this.width - eps) * resolution) / resolution; + const y2 = Math.ceil((this.y + this.height - eps) * resolution) / resolution; + + this.x = Math.floor((this.x + eps) * resolution) / resolution; + this.y = Math.floor((this.y + eps) * resolution) / resolution; + + this.width = x2 - this.x; + this.height = y2 - this.y; + + return this; + } + + /** + * Enlarges this rectangle to include the passed rectangle. + * @param rectangle - The rectangle to include. + * @returns Returns itself. + */ + enlarge (rectangle: Rectangle): this { + const x1 = Math.min(this.x, rectangle.x); + const x2 = Math.max(this.x + this.width, rectangle.x + rectangle.width); + const y1 = Math.min(this.y, rectangle.y); + const y2 = Math.max(this.y + this.height, rectangle.y + rectangle.height); + + this.x = x1; + this.width = x2 - x1; + this.y = y1; + this.height = y2 - y1; + + return this; + } + + /** + * Returns the framing rectangle of the rectangle as a Rectangle object + * @param out - optional rectangle to store the result + * @returns The framing rectangle + */ + getBounds (out?: Rectangle): Rectangle { + out = out || new Rectangle(); + out.copyFrom(this); + + return out; + } + + override getX (): number { + return this.x; + } + + override getY (): number { + return this.y; + } + + override build (points: number[]): number[] { + const x = this.x; + const y = this.y; + const width = this.width; + const height = this.height; + + if (!(width >= 0 && height >= 0)) { + return points; + } + + points[0] = x; + points[1] = y; + points[2] = x + width; + points[3] = y; + points[4] = x + width; + points[5] = y + height; + points[6] = x; + points[7] = y + height; + + return points; + } + + override triangulate (points: number[], vertices: number[], verticesOffset: number, indices: number[], indicesOffset: number) { + let count = 0; + + const verticesStride = 2; + + verticesOffset *= verticesStride; + + vertices[verticesOffset + count] = points[0]; + vertices[verticesOffset + count + 1] = points[1]; + + count += verticesStride; + + vertices[verticesOffset + count] = points[2]; + vertices[verticesOffset + count + 1] = points[3]; + + count += verticesStride; + + vertices[verticesOffset + count] = points[6]; + vertices[verticesOffset + count + 1] = points[7]; + + count += verticesStride; + + vertices[verticesOffset + count] = points[4]; + vertices[verticesOffset + count + 1] = points[5]; + + count += verticesStride; + + const verticesIndex = verticesOffset / verticesStride; + + // triangle 1 + indices[indicesOffset++] = verticesIndex; + indices[indicesOffset++] = verticesIndex + 1; + indices[indicesOffset++] = verticesIndex + 2; + + // triangle 2 + indices[indicesOffset++] = verticesIndex + 1; + indices[indicesOffset++] = verticesIndex + 3; + indices[indicesOffset++] = verticesIndex + 2; + } +} diff --git a/packages/effects-core/src/plugins/shape/triangle.ts b/packages/effects-core/src/plugins/shape/triangle.ts new file mode 100644 index 00000000..7073664f --- /dev/null +++ b/packages/effects-core/src/plugins/shape/triangle.ts @@ -0,0 +1,206 @@ +import { Rectangle } from './rectangle'; +import { ShapePrimitive } from './shape-primitive'; + +/** + * A class to define a shape of a triangle via user defined coordinates. + * + * Create a `Triangle` object with the `x`, `y`, `x2`, `y2`, `x3`, `y3` properties. + */ +export class Triangle extends ShapePrimitive { + /** + * The X coord of the first point. + * @default 0 + */ + x: number; + /** + * The Y coord of the first point. + * @default 0 + */ + y: number; + /** + * The X coord of the second point. + * @default 0 + */ + x2: number; + /** + * The Y coord of the second point. + * @default 0 + */ + y2: number; + /** + * The X coord of the third point. + * @default 0 + */ + x3: number; + /** + * The Y coord of the third point. + * @default 0 + */ + y3: number; + + /** + * @param x - The X coord of the first point. + * @param y - The Y coord of the first point. + * @param x2 - The X coord of the second point. + * @param y2 - The Y coord of the second point. + * @param x3 - The X coord of the third point. + * @param y3 - The Y coord of the third point. + */ + constructor (x = 0, y = 0, x2 = 0, y2 = 0, x3 = 0, y3 = 0) { + super(); + this.x = x; + this.y = y; + this.x2 = x2; + this.y2 = y2; + this.x3 = x3; + this.y3 = y3; + } + + /** + * Checks whether the x and y coordinates given are contained within this triangle + * @param x - The X coordinate of the point to test + * @param y - The Y coordinate of the point to test + * @returns Whether the x/y coordinates are within this Triangle + */ + contains (x: number, y: number): boolean { + const s = ((this.x - this.x3) * (y - this.y3)) - ((this.y - this.y3) * (x - this.x3)); + const t = ((this.x2 - this.x) * (y - this.y)) - ((this.y2 - this.y) * (x - this.x)); + + if ((s < 0) !== (t < 0) && s !== 0 && t !== 0) { return false; } + + const d = ((this.x3 - this.x2) * (y - this.y2)) - ((this.y3 - this.y2) * (x - this.x2)); + + return d === 0 || (d < 0) === (s + t <= 0); + } + + /** + * Checks whether the x and y coordinates given are contained within this triangle including the stroke. + * @param pointX - The X coordinate of the point to test + * @param pointY - The Y coordinate of the point to test + * @param strokeWidth - The width of the line to check + * @returns Whether the x/y coordinates are within this triangle + */ + // strokeContains (pointX: number, pointY: number, strokeWidth: number): boolean { + // const halfStrokeWidth = strokeWidth / 2; + // const halfStrokeWidthSquared = halfStrokeWidth * halfStrokeWidth; + + // const { x, x2, x3, y, y2, y3 } = this; + + // if (squaredDistanceToLineSegment(pointX, pointY, x, y, x2, y3) <= halfStrokeWidthSquared + // || squaredDistanceToLineSegment(pointX, pointY, x2, y2, x3, y3) <= halfStrokeWidthSquared + // || squaredDistanceToLineSegment(pointX, pointY, x3, y3, x, y) <= halfStrokeWidthSquared) { + // return true; + // } + + // return false; + // } + + /** + * Creates a clone of this Triangle + * @returns a copy of the triangle + */ + clone (): ShapePrimitive { + const triangle = new Triangle( + this.x, + this.y, + this.x2, + this.y2, + this.x3, + this.y3 + ); + + return triangle; + } + + /** + * Copies another triangle to this one. + * @param triangle - The triangle to copy from. + * @returns Returns itself. + */ + copyFrom (triangle: Triangle): this { + this.x = triangle.x; + this.y = triangle.y; + this.x2 = triangle.x2; + this.y2 = triangle.y2; + this.x3 = triangle.x3; + this.y3 = triangle.y3; + + return this; + } + + /** + * Copies this triangle to another one. + * @param triangle - The triangle to copy to. + * @returns Returns given parameter. + */ + copyTo (triangle: Triangle): Triangle { + triangle.copyFrom(this); + + return triangle; + } + + /** + * Returns the framing rectangle of the triangle as a Rectangle object + * @param out - optional rectangle to store the result + * @returns The framing rectangle + */ + getBounds (out?: Rectangle): Rectangle { + out = out || new Rectangle(); + + const minX = Math.min(this.x, this.x2, this.x3); + const maxX = Math.max(this.x, this.x2, this.x3); + const minY = Math.min(this.y, this.y2, this.y3); + const maxY = Math.max(this.y, this.y2, this.y3); + + out.x = minX; + out.y = minY; + out.width = maxX - minX; + out.height = maxY - minY; + + return out; + } + + override getX (): number { + return this.x; + } + + override getY (): number { + return this.y; + } + + override build (points: number[]): void { + points[0] = this.x; + points[1] = this.y; + points[2] = this.x2; + points[3] = this.y2; + points[4] = this.x3; + points[5] = this.y3; + } + + override triangulate (points: number[], vertices: number[], verticesOffset: number, indices: number[], indicesOffset: number): void { + let count = 0; + const verticesStride = 2; + + verticesOffset *= verticesStride; + + vertices[verticesOffset + count] = points[0]; + vertices[verticesOffset + count + 1] = points[1]; + + count += verticesStride; + + vertices[verticesOffset + count] = points[2]; + vertices[verticesOffset + count + 1] = points[3]; + + count += verticesStride; + + vertices[verticesOffset + count] = points[4]; + vertices[verticesOffset + count + 1] = points[5]; + + const verticesIndex = verticesOffset / verticesStride; + + // triangle 1 + indices[indicesOffset++] = verticesIndex; + indices[indicesOffset++] = verticesIndex + 1; + indices[indicesOffset++] = verticesIndex + 2; + } +} From b6ea37df51e28b54fe13908224043f10474c0df1 Mon Sep 17 00:00:00 2001 From: wumaolinmaoan Date: Thu, 17 Oct 2024 18:12:56 +0800 Subject: [PATCH 40/88] feat: improve the track binding update --- packages/effects-core/src/comp-vfx-item.ts | 12 +++-- .../playable-assets/timeline-asset.ts | 45 ++++++++++++++++++- .../src/plugins/timeline/track-instance.ts | 24 ++++++++++ .../src/plugins/timeline/track.ts | 2 +- .../timeline/tracks/float-property-track.ts | 20 +++++---- .../plugins/timeline/tracks/material-track.ts | 2 +- .../timeline/tracks/sub-composition-track.ts | 2 +- 7 files changed, 91 insertions(+), 16 deletions(-) create mode 100644 packages/effects-core/src/plugins/timeline/track-instance.ts diff --git a/packages/effects-core/src/comp-vfx-item.ts b/packages/effects-core/src/comp-vfx-item.ts index e9ea3e8d..d8bc665b 100644 --- a/packages/effects-core/src/comp-vfx-item.ts +++ b/packages/effects-core/src/comp-vfx-item.ts @@ -237,16 +237,20 @@ export class CompositionComponent extends Behaviour { for (const sceneBinding of this.sceneBindings) { sceneBinding.key.boundObject = sceneBinding.value; } + + // 未了通过帧对比,需要保证和原有的 update 时机一致。 + // 因此这边更新一次对象绑定,后续 timeline playable 中 sort tracks 的排序才能和原先的版本对上。 + // 如果不需要严格保证和之前的 updata 时机一致,这边的更新和 timeline playable 中的 sortTracks 都能去掉。 for (const masterTrack of this.timelineAsset.tracks) { - this.resolveTrackBindingsWithRoot(masterTrack); + this.updateTrackAnimatedObject(masterTrack); } } - private resolveTrackBindingsWithRoot (track: TrackAsset) { + private updateTrackAnimatedObject (track: TrackAsset) { for (const subTrack of track.getChildTracks()) { - subTrack.resolveBinding(); + subTrack.updateAnimatedObject(); - this.resolveTrackBindingsWithRoot(subTrack); + this.updateTrackAnimatedObject(subTrack); } } } diff --git a/packages/effects-core/src/plugins/timeline/playable-assets/timeline-asset.ts b/packages/effects-core/src/plugins/timeline/playable-assets/timeline-asset.ts index aa80b42a..e21e6770 100644 --- a/packages/effects-core/src/plugins/timeline/playable-assets/timeline-asset.ts +++ b/packages/effects-core/src/plugins/timeline/playable-assets/timeline-asset.ts @@ -6,6 +6,7 @@ import { ObjectBindingTrack } from '../../cal/calculate-item'; import type { FrameContext, PlayableGraph } from '../../cal/playable-graph'; import { Playable, PlayableAsset, PlayableTraversalMode } from '../../cal/playable-graph'; import type { Constructor } from '../../../utils'; +import { TrackInstance } from '../track-instance'; @effectsClass(spec.DataType.TimelineAsset) export class TimelineAsset extends PlayableAsset { @@ -26,7 +27,7 @@ export class TimelineAsset extends PlayableAsset { return timelinePlayable; } - createTrack (classConstructor: Constructor, parent: TrackAsset, name?: string): T { + createTrack(classConstructor: Constructor, parent: TrackAsset, name?: string): T { const newTrack = new classConstructor(this.engine); newTrack.name = name ? name : classConstructor.name; @@ -41,6 +42,7 @@ export class TimelineAsset extends PlayableAsset { export class TimelinePlayable extends Playable { clips: RuntimeClip[] = []; + masterTrackInstances: TrackInstance[] = []; override prepareFrame (context: FrameContext): void { this.evaluate(); @@ -49,6 +51,9 @@ export class TimelinePlayable extends Playable { evaluate () { const time = this.getTime(); + // update all tracks binding + this.updateTrackAnimatedObject(this.masterTrackInstances); + // TODO search active clips for (const clip of this.clips) { @@ -60,12 +65,17 @@ export class TimelinePlayable extends Playable { this.sortTracks(tracks); const outputTrack: TrackAsset[] = []; + // flatten track tree for (const masterTrack of tracks) { outputTrack.push(masterTrack); this.addSubTracksRecursive(masterTrack, outputTrack); } + // map for searching track instance with track asset guid + const trackInstanceMap: Record = {}; + for (const track of outputTrack) { + // create track mixer and track output const trackMixPlayable = track.createPlayableGraph(graph, this.clips); this.addInput(trackMixPlayable, 0); @@ -75,6 +85,39 @@ export class TimelinePlayable extends Playable { graph.addOutput(trackOutput); trackOutput.setSourcePlayeble(this, this.getInputCount() - 1); + + // create track instance + const trackInstance = new TrackInstance(track, trackMixPlayable, trackOutput); + + trackInstanceMap[track.getInstanceId()] = trackInstance; + + if (!track.parent) { + this.masterTrackInstances.push(trackInstance); + } + } + + // build trackInstance tree + for (const track of outputTrack) { + const trackInstance = trackInstanceMap[track.getInstanceId()]; + + for (const child of track.getChildTracks()) { + const childTrackInstance = trackInstanceMap[child.getInstanceId()]; + + trackInstance.addChild(childTrackInstance); + } + } + } + + private updateTrackAnimatedObject (trackInstances: TrackInstance[]) { + for (const trackInstance of trackInstances) { + const trackAsset = trackInstance.trackAsset; + + // update track binding use custom method + trackAsset.updateAnimatedObject(); + trackInstance.output.setUserData(trackAsset.boundObject); + + // update children tracks + this.updateTrackAnimatedObject(trackInstance.children); } } diff --git a/packages/effects-core/src/plugins/timeline/track-instance.ts b/packages/effects-core/src/plugins/timeline/track-instance.ts new file mode 100644 index 00000000..94de3c8c --- /dev/null +++ b/packages/effects-core/src/plugins/timeline/track-instance.ts @@ -0,0 +1,24 @@ +import type { Playable, PlayableOutput } from '../cal/playable-graph'; +import type { TrackAsset } from './track'; + +/** + * A class that stores track assets and the generated mixer playables and playable outputs. + * It is used to query the corresponding playable object based on the track asset. + */ +export class TrackInstance { + trackAsset: TrackAsset; + mixer: Playable; + output: PlayableOutput; + + children: TrackInstance[] = []; + + constructor (trackAsset: TrackAsset, mixer: Playable, output: PlayableOutput) { + this.trackAsset = trackAsset; + this.mixer = mixer; + this.output = output; + } + + addChild (trackInstance: TrackInstance) { + this.children.push(trackInstance); + } +} \ No newline at end of file diff --git a/packages/effects-core/src/plugins/timeline/track.ts b/packages/effects-core/src/plugins/timeline/track.ts index db99fea4..3785910f 100644 --- a/packages/effects-core/src/plugins/timeline/track.ts +++ b/packages/effects-core/src/plugins/timeline/track.ts @@ -57,7 +57,7 @@ export class TrackAsset extends PlayableAsset { /** * 重写该方法以获取自定义对象绑定 */ - resolveBinding () { + updateAnimatedObject () { if (this.parent) { this.boundObject = this.parent.boundObject; } diff --git a/packages/effects-core/src/plugins/timeline/tracks/float-property-track.ts b/packages/effects-core/src/plugins/timeline/tracks/float-property-track.ts index 04ead92f..de4c9741 100644 --- a/packages/effects-core/src/plugins/timeline/tracks/float-property-track.ts +++ b/packages/effects-core/src/plugins/timeline/tracks/float-property-track.ts @@ -8,18 +8,26 @@ export class FloatPropertyTrack extends TrackAsset { @serialize() path = ''; - propertyName = ''; + propertyNames: string[] = []; override createTrackMixer (graph: PlayableGraph): Playable { const mixer = new FloatPropertyMixerPlayable(graph); - mixer.propertyName = this.propertyName; + const propertyNames = this.path.split('.'); + + this.propertyNames = propertyNames; + + if (propertyNames.length > 0) { + const propertyName = propertyNames[propertyNames.length - 1]; + + mixer.propertyName = propertyName; + } return mixer; } - override resolveBinding () { - const propertyNames = this.path.split('.'); + override updateAnimatedObject () { + const propertyNames = this.propertyNames; let target: Record = this.parent.boundObject; for (let i = 0; i < propertyNames.length - 1; i++) { @@ -31,10 +39,6 @@ export class FloatPropertyTrack extends TrackAsset { target = property; } - if (propertyNames.length > 0) { - this.propertyName = propertyNames[propertyNames.length - 1]; - } - this.boundObject = target; } } diff --git a/packages/effects-core/src/plugins/timeline/tracks/material-track.ts b/packages/effects-core/src/plugins/timeline/tracks/material-track.ts index 76cce7d3..0e3566bc 100644 --- a/packages/effects-core/src/plugins/timeline/tracks/material-track.ts +++ b/packages/effects-core/src/plugins/timeline/tracks/material-track.ts @@ -8,7 +8,7 @@ export class MaterialTrack extends TrackAsset { @serialize() index: number; - override resolveBinding () { + override updateAnimatedObject () { if (!(this.parent.boundObject instanceof RendererComponent)) { return; } diff --git a/packages/effects-core/src/plugins/timeline/tracks/sub-composition-track.ts b/packages/effects-core/src/plugins/timeline/tracks/sub-composition-track.ts index e3dc64a2..0ff980cf 100644 --- a/packages/effects-core/src/plugins/timeline/tracks/sub-composition-track.ts +++ b/packages/effects-core/src/plugins/timeline/tracks/sub-composition-track.ts @@ -9,7 +9,7 @@ import { SubCompositionMixerPlayable } from '../playables'; @effectsClass(spec.DataType.SubCompositionTrack) export class SubCompositionTrack extends TrackAsset { - override resolveBinding () { + override updateAnimatedObject () { if (!this.parent || !(this.parent.boundObject instanceof VFXItem)) { throw new Error('SubCompositionTrack needs to be set under the VFXItem track.'); } From a47241aa5f366ece12d3c4af6b38ddf051504d68 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=8C=82=E5=AE=89?= <151805118+wumaolinmaoan@users.noreply.github.com> Date: Thu, 17 Oct 2024 18:27:41 +0800 Subject: [PATCH 41/88] refactor: post processing setting (#686) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * refactor: post processing setting * fix: post processing gui demo * chore: 优化后处理 demo 代码 * chore: update demo * chore: 移除无用文件 --------- Co-authored-by: yiiqii --- .../src/components/post-process-volume.ts | 17 +- packages/effects-core/src/composition.ts | 10 +- .../effects-core/src/render/global-volume.ts | 43 --- packages/effects-core/src/render/index.ts | 1 - .../src/render/post-process-pass.ts | 23 +- .../effects-core/src/render/render-frame.ts | 18 +- web-packages/demo/html/post-processing.html | 42 ++- web-packages/demo/index.html | 4 +- .../demo/src/assets/post-processing-list.ts | 19 ++ web-packages/demo/src/common/inspire-list.ts | 8 +- web-packages/demo/src/gui/inspector-gui.ts | 311 ------------------ web-packages/demo/src/post-processing.ts | 113 +++---- web-packages/imgui-demo/src/ge.ts | 2 + 13 files changed, 135 insertions(+), 476 deletions(-) delete mode 100644 packages/effects-core/src/render/global-volume.ts create mode 100644 web-packages/demo/src/assets/post-processing-list.ts delete mode 100644 web-packages/demo/src/gui/inspector-gui.ts diff --git a/packages/effects-core/src/components/post-process-volume.ts b/packages/effects-core/src/components/post-process-volume.ts index 85355423..70894858 100644 --- a/packages/effects-core/src/components/post-process-volume.ts +++ b/packages/effects-core/src/components/post-process-volume.ts @@ -4,50 +4,39 @@ import { Behaviour } from './component'; // TODO spec 增加 DataType @effectsClass('PostProcessVolume') export class PostProcessVolume extends Behaviour { - @serialize() - useHDR = true; - // Bloom @serialize() - useBloom = true; - + bloomEnabled = true; @serialize() threshold = 1.0; - @serialize() bloomIntensity = 1.0; // ColorAdjustments @serialize() brightness = 1.0; - @serialize() saturation = 1.0; - @serialize() contrast = 1.0; // Vignette @serialize() vignetteIntensity = 0.2; - @serialize() vignetteSmoothness = 0.4; - @serialize() vignetteRoundness = 1.0; // ToneMapping @serialize() - useToneMapping: boolean = true; // 1: true, 0: false + toneMappingEnabled: boolean = true; // 1: true, 0: false override onStart (): void { const composition = this.item.composition; if (composition) { - composition.globalVolume = this; - - composition.createRenderFrame(); + composition.renderFrame.globalVolume = this; } } } diff --git a/packages/effects-core/src/composition.ts b/packages/effects-core/src/composition.ts index b28f32cb..3ef81ee9 100644 --- a/packages/effects-core/src/composition.ts +++ b/packages/effects-core/src/composition.ts @@ -172,7 +172,11 @@ export class Composition extends EventEmitter> imp /** * 后处理渲染配置 */ - globalVolume: PostProcessVolume; + globalVolume?: PostProcessVolume; + /** + * 是否开启后处理 + */ + postProcessingEnabled = false; protected rendererOptions: MeshRendererOptions | null; // TODO: 待优化 @@ -228,6 +232,9 @@ export class Composition extends EventEmitter> imp } const { sourceContent, pluginSystem, imgUsage, totalTime, refCompositionProps } = this.compositionSourceManager; + //@ts-expect-error // TODO 更新 Spec + this.postProcessingEnabled = scene.jsonScene.renderSettings?.postProcessingEnabled ?? false; + assertExist(sourceContent); this.renderer = renderer; this.refCompositionProps = refCompositionProps; @@ -453,6 +460,7 @@ export class Composition extends EventEmitter> imp renderer: this.renderer, keepColorBuffer: this.keepColorBuffer, globalVolume: this.globalVolume, + postProcessingEnabled: this.postProcessingEnabled, }); // TODO 考虑放到构造函数 this.renderFrame.cachedTextures = this.textures; diff --git a/packages/effects-core/src/render/global-volume.ts b/packages/effects-core/src/render/global-volume.ts deleted file mode 100644 index a1c23f81..00000000 --- a/packages/effects-core/src/render/global-volume.ts +++ /dev/null @@ -1,43 +0,0 @@ -/** - * 后处理配置 - */ -export interface PostProcessVolumeData { - useHDR: boolean, - // Bloom - useBloom: boolean, - threshold: number, - bloomIntensity: number, - // ColorAdjustments - brightness: number, - saturation: number, - contrast: number, - // Vignette - // vignetteColor: Color, - // vignetteCenter: Vector2, - vignetteIntensity: number, - vignetteSmoothness: number, - vignetteRoundness: number, - // ToneMapping - useToneMapping: boolean, // 1: true, 0: false -} - -export const defaultGlobalVolume: PostProcessVolumeData = { - useHDR: false, - /***** Material Uniform *****/ - // Bloom - useBloom: true, - threshold: 1.0, - bloomIntensity: 1.0, - // ColorAdjustments - brightness: 1.0, - saturation: 1.0, - contrast: 1.0, - // Vignette - // vignetteColor: new math.Color(0, 0, 0, 1), - // vignetteCenter: new math.Vector2(0.5, 0.5), - vignetteIntensity: 0.2, - vignetteSmoothness: 0.4, - vignetteRoundness: 1.0, - // ToneMapping - useToneMapping: true, // 1: true, 0: false -}; diff --git a/packages/effects-core/src/render/index.ts b/packages/effects-core/src/render/index.ts index eae570a5..4fe803fc 100644 --- a/packages/effects-core/src/render/index.ts +++ b/packages/effects-core/src/render/index.ts @@ -9,5 +9,4 @@ export * from './types'; export * from './geometry'; export * from './framebuffer'; export * from './renderer'; -export * from './global-volume'; export * from './semantic-map'; diff --git a/packages/effects-core/src/render/post-process-pass.ts b/packages/effects-core/src/render/post-process-pass.ts index f83fc9ee..272b629d 100644 --- a/packages/effects-core/src/render/post-process-pass.ts +++ b/packages/effects-core/src/render/post-process-pass.ts @@ -68,7 +68,7 @@ export class BloomThresholdPass extends RenderPass { stencilAction: TextureStoreAction.clear, }); this.screenMesh.material.setTexture('_MainTex', this.mainTexture); - const threshold = renderer.renderingData.currentFrame.globalVolume.threshold; + const threshold = renderer.renderingData.currentFrame.globalVolume?.threshold ?? 1.0; this.screenMesh.material.setFloat('_Threshold', threshold); renderer.renderMeshes([this.screenMesh]); @@ -264,19 +264,24 @@ export class ToneMappingPass extends RenderPass { stencilAction: TextureStoreAction.clear, }); const { - useBloom, bloomIntensity, - brightness, saturation, contrast, - useToneMapping, - vignetteIntensity, vignetteSmoothness, vignetteRoundness, - } = renderer.renderingData.currentFrame.globalVolume; + bloomEnabled = false, + bloomIntensity = 1.0, + brightness = 1.0, + saturation = 1.0, + contrast = 1.0, + toneMappingEnabled = true, + vignetteIntensity = 0.2, + vignetteSmoothness = 0.4, + vignetteRoundness = 1.0, + } = renderer.renderingData.currentFrame.globalVolume ?? {}; this.screenMesh.material.setTexture('_SceneTex', this.sceneTextureHandle.texture); this.screenMesh.material.setFloat('_Brightness', brightness); this.screenMesh.material.setFloat('_Saturation', saturation); this.screenMesh.material.setFloat('_Contrast', contrast); - this.screenMesh.material.setInt('_UseBloom', Number(useBloom)); - if (useBloom) { + this.screenMesh.material.setInt('_UseBloom', Number(bloomEnabled)); + if (bloomEnabled) { this.screenMesh.material.setTexture('_GaussianTex', this.mainTexture); this.screenMesh.material.setFloat('_BloomIntensity', bloomIntensity); } @@ -287,7 +292,7 @@ export class ToneMappingPass extends RenderPass { this.screenMesh.material.setVector2('_VignetteCenter', new Vector2(0.5, 0.5)); this.screenMesh.material.setVector3('_VignetteColor', new Vector3(0.0, 0.0, 0.0)); } - this.screenMesh.material.setInt('_UseToneMapping', Number(useToneMapping)); + this.screenMesh.material.setInt('_UseToneMapping', Number(toneMappingEnabled)); renderer.renderMeshes([this.screenMesh]); } } diff --git a/packages/effects-core/src/render/render-frame.ts b/packages/effects-core/src/render/render-frame.ts index d8fdf767..9746c94c 100644 --- a/packages/effects-core/src/render/render-frame.ts +++ b/packages/effects-core/src/render/render-frame.ts @@ -145,6 +145,10 @@ export interface RenderFrameOptions { * 后处理渲染配置 */ globalVolume?: PostProcessVolume, + /** + * 后处理是否开启 + */ + postProcessingEnabled?: boolean, /** * 名称 */ @@ -186,7 +190,7 @@ export class RenderFrame implements Disposable { /** * 存放后处理的属性设置 */ - globalVolume: PostProcessVolume; + globalVolume?: PostProcessVolume; renderer: Renderer; resource: RenderFrameResource; keepColorBuffer?: boolean; @@ -221,6 +225,7 @@ export class RenderFrame implements Disposable { camera, keepColorBuffer, renderer, editorTransform = [1, 1, 0, 0], globalVolume, + postProcessingEnabled = false, clearAction = { colorAction: TextureLoadAction.whatever, stencilAction: TextureLoadAction.clear, @@ -238,10 +243,10 @@ export class RenderFrame implements Disposable { let drawObjectPassClearAction = {}; this.renderer = renderer; - if (this.globalVolume) { - const { useHDR } = this.globalVolume; + if (postProcessingEnabled) { + const enableHDR = true; // 使用HDR浮点纹理,FLOAT在IOS上报错,使用HALF_FLOAT - const textureType = useHDR ? glContext.HALF_FLOAT : glContext.UNSIGNED_BYTE; + const textureType = enableHDR ? glContext.HALF_FLOAT : glContext.UNSIGNED_BYTE; attachments = [{ texture: { format: glContext.RGBA, type: textureType, magFilter: glContext.LINEAR, minFilter: glContext.LINEAR } }]; depthStencilAttachment = { storageType: RenderPassAttachmentStorageType.depth_stencil_opaque }; @@ -266,14 +271,15 @@ export class RenderFrame implements Disposable { this.setRenderPasses(renderPasses); - if (this.globalVolume) { + if (postProcessingEnabled) { const sceneTextureHandle = new RenderTargetHandle(engine); //保存后处理前的屏幕图像 const gaussianStep = 7; // 高斯模糊的迭代次数,次数越高模糊范围越大 const viewport: vec4 = [0, 0, this.renderer.getWidth() / 2, this.renderer.getHeight() / 2]; const gaussianDownResults = new Array(gaussianStep); //存放多个高斯Pass的模糊结果,用于Bloom - const textureType = this.globalVolume.useHDR ? glContext.HALF_FLOAT : glContext.UNSIGNED_BYTE; + const enableHDR = true; + const textureType = enableHDR ? glContext.HALF_FLOAT : glContext.UNSIGNED_BYTE; const bloomThresholdPass = new BloomThresholdPass(renderer, { name: 'BloomThresholdPass', attachments: [{ diff --git a/web-packages/demo/html/post-processing.html b/web-packages/demo/html/post-processing.html index f4808db4..9f961907 100644 --- a/web-packages/demo/html/post-processing.html +++ b/web-packages/demo/html/post-processing.html @@ -7,43 +7,41 @@ +
+
-
-
-
-
- -
-
-
- -
-
-
-
- +
+
+
+
+
- +
+
+
-
-
- + - diff --git a/web-packages/demo/index.html b/web-packages/demo/index.html index 5519a68c..1bb7b57b 100644 --- a/web-packages/demo/index.html +++ b/web-packages/demo/index.html @@ -12,18 +12,18 @@
Runtime Demo
Inspire compare Demo
diff --git a/web-packages/demo/src/assets/post-processing-list.ts b/web-packages/demo/src/assets/post-processing-list.ts new file mode 100644 index 00000000..eebe83a1 --- /dev/null +++ b/web-packages/demo/src/assets/post-processing-list.ts @@ -0,0 +1,19 @@ +export default { + spring: { + url: 'https://mdn.alipayobjects.com/mars/afts/file/A*oF1NRJG7GU4AAAAAAAAAAAAADlB4AQ', + name: '春促-主页互动', + }, + rotate: { + url: 'https://mdn.alipayobjects.com/mars/afts/file/A*AGu-So0y5YkAAAAAAAAAAAAADlB4AQ', + name: '旋转出场', + }, + robin: { + url: 'https://mdn.alipayobjects.com/mars/afts/file/A*YIKpS69QTaoAAAAAAAAAAAAADlB4AQ', + name: 'Robin', + }, + bloomTest: { + url: 'https://mdn.alipayobjects.com/mars/afts/file/A*6j_ZQan_MhMAAAAAAAAAAAAADlB4AQ', + name: 'BloomTest', + }, +}; + diff --git a/web-packages/demo/src/common/inspire-list.ts b/web-packages/demo/src/common/inspire-list.ts index 68d8587f..05269a1a 100644 --- a/web-packages/demo/src/common/inspire-list.ts +++ b/web-packages/demo/src/common/inspire-list.ts @@ -1,12 +1,10 @@ -// @ts-nocheck - import inspireList from '../assets/inspire-list'; export class InspireList { currentInspire = inspireList['ribbons'].url; - private readonly startEle = document.getElementById('J-start'); - private readonly pauseEle = document.getElementById('J-pause'); + private readonly startEle = document.getElementById('J-start') as HTMLElement; + private readonly pauseEle = document.getElementById('J-pause') as HTMLElement; private readonly loopCheckboxEle = document.getElementById('J-loop'); private readonly frameworkEle = document.getElementById('J-framework') as HTMLSelectElement; @@ -52,7 +50,7 @@ export class InspireList { }); selectEle.innerHTML = options.join(''); selectEle.onchange = () => { - const selected = selectEle.value; + const selected = selectEle.value as keyof typeof inspireList; this.currentInspire = inspireList[selected].url; }; diff --git a/web-packages/demo/src/gui/inspector-gui.ts b/web-packages/demo/src/gui/inspector-gui.ts deleted file mode 100644 index b7b80d91..00000000 --- a/web-packages/demo/src/gui/inspector-gui.ts +++ /dev/null @@ -1,311 +0,0 @@ -import type { EffectComponentData, EffectsObject, Engine, Material, SceneData, VFXItem } from '@galacean/effects'; -import { EffectComponent, Behaviour, RendererComponent, SerializationHelper, Texture, generateGUID, glContext, loadImage, spec } from '@galacean/effects'; - -export class InspectorGui { - gui: any; - item: VFXItem; - itemDirtyFlag = false; - - sceneData: SceneData; - guiControllers: any[] = []; - - constructor () { - //@ts-expect-error - this.gui = new GUI(); - this.gui.addFolder('Inspector'); - - // setInterval(this.updateInspector, 500); - } - - setItem (item: VFXItem) { - if (this.item === item) { - return; - } - this.item = item; - this.itemDirtyFlag = true; - } - - update = () => { - if (this.item && this.itemDirtyFlag) { - this.guiControllers = []; - this.gui.destroy(); - //@ts-expect-error - this.gui = new GUI(); - this.gui.add(this.item, 'name'); - - const transformFolder = this.gui.addFolder('Transform'); - const positionFolder = transformFolder.addFolder('Position'); - const rotationFolder = transformFolder.addFolder('Rotation'); - const scaleFolder = transformFolder.addFolder('Scale'); - - transformFolder.open(); - positionFolder.open(); - rotationFolder.open(); - scaleFolder.open(); - - const transform = this.item.transform; - const transformData = transform.toData(); - - this.guiControllers.push(positionFolder.add(transformData.position, 'x').name('x').step(0.03).onChange(() => { transform.fromData(transformData); })); - this.guiControllers.push(positionFolder.add(transformData.position, 'y').name('y').step(0.03).onChange(() => { transform.fromData(transformData); })); - this.guiControllers.push(positionFolder.add(transformData.position, 'z').name('z').step(0.03).onChange(() => { transform.fromData(transformData); })); - - // @ts-expect-error - this.guiControllers.push(rotationFolder.add(transformData.rotation, 'x').name('x').step(0.03).onChange(() => { transform.fromData(transformData); })); - // @ts-expect-error - this.guiControllers.push(rotationFolder.add(transformData.rotation, 'y').name('y').step(0.03).onChange(() => { transform.fromData(transformData); })); - // @ts-expect-error - this.guiControllers.push(rotationFolder.add(transformData.rotation, 'z').name('z').step(0.03).onChange(() => { transform.fromData(transformData); })); - - this.guiControllers.push(scaleFolder.add(transformData.scale, 'x').name('x').step(0.03).onChange(() => { transform.fromData(transformData); })); - this.guiControllers.push(scaleFolder.add(transformData.scale, 'y').name('y').step(0.03).onChange(() => { transform.fromData(transformData); })); - this.guiControllers.push(scaleFolder.add(transformData.scale, 'z').name('z').step(0.03).onChange(() => { transform.fromData(transformData); })); - - for (const component of this.item.components) { - const componentFolder = this.gui.addFolder(component.constructor.name); - - if (component instanceof RendererComponent) { - const controller = componentFolder.add(component, '_enabled'); - - this.guiControllers.push(controller); - } - - if (component instanceof EffectComponent) { - componentFolder.add({ - click: async () => { - await selectJsonFile((data: spec.EffectsPackageData) => { - for (const effectsObjectData of data.exportObjects) { - this.item.engine.jsonSceneData[effectsObjectData.id] = effectsObjectData; - const effectComponent = this.item.getComponent(RendererComponent); - - if (effectComponent) { - const guid = effectComponent.getInstanceId(); - - (this.item.engine.jsonSceneData[guid] as EffectComponentData).materials[0] = { id: effectsObjectData.id }; - SerializationHelper.deserialize(this.item.engine.jsonSceneData[guid], effectComponent); - } - } - this.itemDirtyFlag = true; - }); - }, - }, 'click').name('Material'); - - componentFolder.add({ - click: async () => { - await selectJsonFile((data: spec.EffectsPackageData) => { - for (const effectsObjectData of data.exportObjects) { - this.item.engine.jsonSceneData[effectsObjectData.id] = effectsObjectData; - const effectComponent = this.item.getComponent(EffectComponent); - - if (effectComponent) { - const guid = effectComponent.getInstanceId(); - - (this.item.engine.jsonSceneData[guid] as EffectComponentData).geometry = { id: effectsObjectData.id }; - SerializationHelper.deserialize(this.item.engine.jsonSceneData[guid], effectComponent); - } - } - }); - }, - }, 'click').name('Geometry'); - } - - if (component instanceof Behaviour) { - const controller = componentFolder.add(component, '_enabled'); - - this.guiControllers.push(controller); - } - - componentFolder.open(); - } - const rendererComponent = this.item.getComponent(RendererComponent); - - if (rendererComponent) { - for (const material of rendererComponent.materials) { - this.setMaterialGui(material); - } - } - - this.itemDirtyFlag = false; - } - - if (this.item) { - const rendererComponent = this.item.getComponent(RendererComponent); - - if (rendererComponent) { - for (const material of rendererComponent.materials) { - // material.toData(); - } - } - } - - for (const controller of this.guiControllers) { - controller.updateDisplay(); - } - }; - - // const properties = ` - // _2D("2D", 2D) = "" {} - // _Color("Color",Color) = (1,1,1,1) - // _Value("Value",Range(0,10)) = 2.5 - // _Float("Float",Float) = 0 - // _Vector("Vector",Vector) = (0,0,0,0) - // _Rect("Rect",Rect) = "" {} - // _Cube("Cube",Cube) = "" {} - // `; - - private parseMaterialProperties (material: Material, gui: any, serializeObject: SerializedObject) { - const serializedData = serializeObject.serializedData; - const shaderProperties = (material.shaderSource as spec.ShaderData).properties; - - if (!shaderProperties) { - return; - } - const lines = shaderProperties.split('\n'); - - for (const property of lines) { - // 提取材质属性信息 - // 如 “_Float1("Float2", Float) = 0” - // 提取出 “_Float1” “Float2” “Float” “0” - const regex = /\s*(.+?)\s*\(\s*"(.+?)"\s*,\s*(.+?)\s*\)\s*=\s*(.+)\s*/; - const matchResults = property.match(regex); - - if (!matchResults) { - return; - } - const uniformName = matchResults[1]; - const inspectorName = matchResults[2]; - const type = matchResults[3]; - const value = matchResults[4]; - - // 提取 Range(a, b) 的 a 和 b - const match = type.match(/\(\s*([-\d.]+)\s*,\s*([-\d.]+)\s*\)/); - - if (match) { - const start = Number(match[1]); - const end = Number(match[2]); - - // materialData.floats[uniformName] = Number(value); - this.guiControllers.push(gui.add(serializedData.floats, uniformName, start, end).onChange(() => { - // this.item.getComponent(RendererComponent)?.material.fromData(materialData); - serializeObject.applyModifiedProperties(); - })); - } else if (type === 'Float') { - // materialData.floats[uniformName] = Number(value); - this.guiControllers.push(gui.add(serializedData.floats, uniformName).name(inspectorName).onChange(() => { - serializeObject.applyModifiedProperties(); - })); - } else if (type === 'Color') { - this.guiControllers.push(gui.addColor({ color: [0, 0, 0, 0] }, 'color').name(inspectorName).onChange((value: number[]) => { - serializeObject.serializedData['vector4s'][uniformName] = { x: value[0], y: value[1], z: value[2], w: value[3] }; - serializeObject.applyModifiedProperties(); - })); - } else if (type === '2D') { - const controller = this.gui.add({ - click: async () => { - const fileHandle: FileSystemFileHandle[] = await window.showOpenFilePicker(); - const file = await fileHandle[0].getFile(); - const assetUuid = generateGUID(); - - // 生成纹理资产对象 - const reader = new FileReader(); - - reader.onload = async function (e) { - const result = e.target?.result; - const textureData = { id: assetUuid, source: result, dataType: spec.DataType.Texture, flipY: true, wrapS: glContext.REPEAT, wrapT: glContext.REPEAT }; - - serializeObject.engine.jsonSceneData[textureData.id] = textureData; - }; - reader.onerror = event => { - console.error('文件读取出错:', reader.error); - }; - - reader.readAsDataURL(file); - - // 加载 image - const image = await loadImage(file); - - image.width = 50; - image.height = 50; - image.id = inspectorName; - const lastImage = document.getElementById(inspectorName); - - if (lastImage) { - controller.domElement.removeChild(lastImage); - } - controller.domElement.appendChild(image); - - // 根据 image 生成纹理对象 - const texture = Texture.create(this.item.engine, { image: image, flipY: true, wrapS: glContext.REPEAT, wrapT: glContext.REPEAT }); - - texture.setInstanceId(assetUuid); - serializeObject.engine.addInstance(texture); - serializeObject.serializedData.textures[uniformName] = { id: texture.getInstanceId() }; - serializeObject.applyModifiedProperties(); - }, - }, 'click').name(inspectorName); - } - } - } - - // dat gui 参数及修改 - private setMaterialGui (material: Material) { - const materialGUI = this.gui.addFolder('Material'); - - materialGUI.open(); - const serializeObject = new SerializedObject(material); - const serializedData = serializeObject.serializedData; - - serializedData.blending = false; - serializedData.zTest = false; - serializedData.zWrite = false; - serializeObject.update(); - - this.guiControllers.push(materialGUI.add(serializedData, 'blending').onChange(() => { - serializeObject.applyModifiedProperties(); - })); - this.guiControllers.push(materialGUI.add(serializedData, 'zTest').onChange(() => { - serializeObject.applyModifiedProperties(); - })); - this.guiControllers.push(materialGUI.add(serializedData, 'zWrite').onChange(() => { - serializeObject.applyModifiedProperties(); - })); - this.parseMaterialProperties(material, materialGUI, serializeObject); - } -} - -async function selectJsonFile (callback: (data: any) => void) { - const fileHandle: FileSystemFileHandle[] = await window.showOpenFilePicker(); - const file = await fileHandle[0].getFile(); - const reader = new FileReader(); - - reader.onload = () => { - if (typeof reader.result !== 'string') { - return; - } - const data = JSON.parse(reader.result); - - callback(data); - }; - reader.readAsText(file); -} - -export class SerializedObject { - engine: Engine; - serializedData: Record; - target: EffectsObject; - - constructor (target: EffectsObject) { - this.target = target; - this.engine = target.engine; - this.serializedData = {}; - this.update(); - } - - update () { - SerializationHelper.serialize(this.target, this.serializedData); - } - - applyModifiedProperties () { - SerializationHelper.deserialize(this.serializedData as spec.EffectsObjectData, this.target); - } -} diff --git a/web-packages/demo/src/post-processing.ts b/web-packages/demo/src/post-processing.ts index fde2953e..2b5a54c5 100644 --- a/web-packages/demo/src/post-processing.ts +++ b/web-packages/demo/src/post-processing.ts @@ -1,84 +1,69 @@ -//@ts-nocheck import type { Composition } from '@galacean/effects'; -import { POST_PROCESS_SETTINGS, Player, PostProcessVolume, defaultGlobalVolume, setConfig } from '@galacean/effects'; -import { InspireList } from './common/inspire-list'; -import { InspectorGui } from './gui/inspector-gui'; - -const url = 'https://mdn.alipayobjects.com/mars/afts/file/A*YIKpS69QTaoAAAAAAAAAAAAADlB4AQ'; -//const url = 'https://mdn.alipayobjects.com/mars/afts/file/A*6j_ZQan_MhMAAAAAAAAAAAAADlB4AQ'; // BloomTest -const container = document.getElementById('J-container'); -const speed = 0.5; -const inspireList = new InspireList(); - -const inspectorGui = new InspectorGui(); - -setInterval(()=>{ - inspectorGui.update(); -}, 100); - -let gui = new GUI(); -let player; +import { POST_PROCESS_SETTINGS, Player, PostProcessVolume, setConfig } from '@galacean/effects'; +import postProcessingList from './assets/post-processing-list'; // DATUI 参数面板 const postProcessSettings = { // Particle - color: [0, 0, 0], + color: [1, 0.5, 0], intensity: 1.0, - // Bloom - useBloom: 1.0, - threshold: 1.0, - bloomIntensity: 1.0, - // ColorAdjustments - brightness: 0, - saturation: 0, - contrast: 0, - // ToneMapping - useToneMapping: 1, // 1: true, 0: false }; +const container = document.getElementById('J-container'); +const resumeBtn = document.getElementById('J-resume'); +// const url = postProcessingList['robin'].url; +const url = 'https://mdn.alipayobjects.com/mars/afts/file/A*PubBSpHUbjYAAAAAAAAAAAAADlB4AQ'; +let player: Player; + +initSelectList(); +setConfig(POST_PROCESS_SETTINGS, postProcessSettings); (async () => { - setConfig(POST_PROCESS_SETTINGS, postProcessSettings); - player = new Player({ - container, - pixelRatio: window.devicePixelRatio, - }); - await handlePlay(url); + try { + player = new Player({ + container, + }); + + await handleLoadScene(url); + } catch (e) { + console.error('biz', e); + } })(); -bindEventListeners(); +resumeBtn?.addEventListener('click', () => handleLoadScene(url)); -function bindEventListeners () { - inspireList.handleStart = () => { - handlePause(); - void handlePlay(inspireList.currentInspire); +async function handleLoadScene (url: string) { + const json = await (await fetch(url)).json(); + + json.renderSettings = { + postProcessingEnabled: true, }; - inspireList.handlePause = handlePause; -} + player.destroyCurrentCompositions(); -async function handlePlay (url) { - try { - const json = await (await fetch(url)).json(); + const composition = await player.loadScene(json); - player.destroyCurrentCompositions(); - const comp: Composition = await player.loadScene(json); + composition.rootItem.addComponent(PostProcessVolume); + setDatGUI(composition); +} - comp.rootItem.addComponent(PostProcessVolume); - void player.play(comp, { speed }); - setDatGUI(comp); +function initSelectList () { + const selectEle = document.getElementById('J-select') as HTMLSelectElement; + const options: string[] = []; - } catch (e) { - console.error('biz', e); - } -} + Object.entries(postProcessingList).map(([key, object]) => { + options.push(``); + }); + selectEle.innerHTML = options.join(''); + selectEle.onchange = () => { + const name = selectEle.value as keyof typeof postProcessingList; -function handlePause () { - player.pause(); + void handleLoadScene(postProcessingList[name].url); + }; } // dat gui 参数及修改 function setDatGUI (composition: Composition) { - gui.destroy(); - gui = new GUI(); + // @ts-expect-error + const gui = new window.GUI(); const ParticleFolder = gui.addFolder('Particle'); const BloomFolder = gui.addFolder('Bloom'); const ToneMappingFlolder = gui.addFolder('ToneMapping'); @@ -87,11 +72,15 @@ function setDatGUI (composition: Composition) { const globalVolume = composition.renderFrame.globalVolume; + if (!globalVolume) { + return; + } + ParticleFolder.addColor(postProcessSettings, 'color'); ParticleFolder.add(postProcessSettings, 'intensity', -10, 10).step(0.1); ParticleFolder.open(); - BloomFolder.add(globalVolume, 'useBloom', 0, 1).step(1); + BloomFolder.add(globalVolume, 'bloomEnabled', 0, 1).step(1); BloomFolder.add(globalVolume, 'threshold', 0, 40).step(0.1); BloomFolder.add(globalVolume, 'bloomIntensity', 0, 10); BloomFolder.open(); @@ -105,6 +94,6 @@ function setDatGUI (composition: Composition) { ColorAdjustmentsFolder.add(globalVolume, 'contrast', 0, 2); ColorAdjustmentsFolder.open(); - ToneMappingFlolder.add(globalVolume, 'useToneMapping', 0, 1).step(1); + ToneMappingFlolder.add(globalVolume, 'toneMappingEnabled', 0, 1).step(1); ToneMappingFlolder.open(); -} \ No newline at end of file +} diff --git a/web-packages/imgui-demo/src/ge.ts b/web-packages/imgui-demo/src/ge.ts index 365a1233..416f2ae6 100644 --- a/web-packages/imgui-demo/src/ge.ts +++ b/web-packages/imgui-demo/src/ge.ts @@ -514,6 +514,8 @@ export class GalaceanEffects { }); } else { void GalaceanEffects.player.loadScene(url, { autoplay:true }).then(composition=>{ + composition.postProcessingEnabled = true; + composition.createRenderFrame(); composition.rootItem.addComponent(PostProcessVolume); composition.renderFrame.addRenderPass(new OutlinePass(composition.renderer, { From 0438eabb8332525d3a7c2aced47651f6bbf4c5ed Mon Sep 17 00:00:00 2001 From: RGCHN Date: Thu, 17 Oct 2024 20:19:27 +0800 Subject: [PATCH 42/88] =?UTF-8?q?feat:=20=E5=A2=9E=E5=8A=A0=E4=BA=A4?= =?UTF-8?q?=E4=BA=92=E5=85=83=E7=B4=A0=E6=8B=96=E6=8B=BD=E8=8C=83=E5=9B=B4?= =?UTF-8?q?=E4=BF=AE=E6=94=B9=E6=8E=A5=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/plugins/interact/interact-item.ts | 60 ++++++++++++++----- 1 file changed, 45 insertions(+), 15 deletions(-) diff --git a/packages/effects-core/src/plugins/interact/interact-item.ts b/packages/effects-core/src/plugins/interact/interact-item.ts index 1fdf7776..40c0543f 100644 --- a/packages/effects-core/src/plugins/interact/interact-item.ts +++ b/packages/effects-core/src/plugins/interact/interact-item.ts @@ -30,6 +30,16 @@ export class InteractComponent extends RendererComponent { * 拖拽的距离映射系数,越大越容易拖动 */ dragRatio: number[] = [1, 1]; + /** + * 拖拽X范围 + */ + dragRange: { + dxRange: [min: number, max: number], + dyRange: [min: number, max: number], + } = { + dxRange: [0, 0], + dyRange: [0, 0], + }; /** 是否响应点击和拖拽交互事件 */ private _interactive = true; @@ -47,6 +57,22 @@ export class InteractComponent extends RendererComponent { return this._interactive; } + getDragRangeX (): [min:number, max: number] { + return this.dragRange.dxRange; + } + + setDragRangeX (min: number, max: number) { + this.dragRange.dxRange = [min, max]; + } + + getDragRangeY (): [min:number, max: number] { + return this.dragRange.dyRange; + } + + setDragRangeY (min: number, max: number) { + this.dragRange.dyRange = [min, max]; + } + override onStart (): void { const options = this.item.props.content.options as spec.DragInteractOption; const { env } = this.item.engine.renderer; @@ -131,7 +157,6 @@ export class InteractComponent extends RendererComponent { return; } - const options = (this.item.props as spec.InteractItem).content.options as spec.DragInteractOption; const { position, fov } = evt.cameraParam; const dy = event.dy; const dx = event.dx * event.width / event.height; @@ -139,24 +164,20 @@ export class InteractComponent extends RendererComponent { const sp = Math.tan(fov * Math.PI / 180 / 2) * Math.abs(depth); const height = dy * sp; const width = dx * sp; + const { dxRange, dyRange } = this.dragRange; let nx = position[0] - this.dragRatio[0] * width; let ny = position[1] - this.dragRatio[1] * height; - if (options.dxRange) { - const [min, max] = options.dxRange; + const [xMin, xMax] = dxRange; + const [yMin, yMax] = dyRange; - nx = clamp(nx, min, max); - if (nx !== min && nx !== max && min !== max) { - event.origin?.preventDefault(); - } + nx = clamp(nx, xMin, xMax); + ny = clamp(ny, yMin, yMax); + if (nx !== xMin && nx !== xMax && xMin !== xMax) { + event.origin?.preventDefault(); } - if (options.dyRange) { - const [min, max] = options.dyRange; - - ny = clamp(ny, min, max); - if (ny !== min && ny !== max && min !== max) { - event.origin?.preventDefault(); - } + if (ny !== yMin && ny !== yMax && yMin !== yMax) { + event.origin?.preventDefault(); } this.item.composition.camera.position = new Vector3(nx, ny, depth); } @@ -165,7 +186,6 @@ export class InteractComponent extends RendererComponent { if (options.target !== 'camera') { return; } - let dragEvent: Partial | null; const handlerMap: Record void> = { touchstart: (event: TouchEventType) => { @@ -253,6 +273,16 @@ export class InteractComponent extends RendererComponent { override fromData (data: spec.InteractContent): void { super.fromData(data); this.interactData = data; + if (data.options.type === spec.InteractType.DRAG) { + const options = data.options as spec.DragInteractOption; + + if (options.dxRange) { + this.dragRange.dxRange = options.dxRange; + } + if (options.dyRange) { + this.dragRange.dyRange = options.dyRange; + } + } } canInteract (): boolean { From 6c6c0e117e2048e1109ba7c195696c916d350110 Mon Sep 17 00:00:00 2001 From: Sruim <934274351@qq.com> Date: Fri, 18 Oct 2024 11:48:00 +0800 Subject: [PATCH 43/88] =?UTF-8?q?fix(video):=20=E4=BF=AE=E5=A4=8D=E8=A7=86?= =?UTF-8?q?=E9=A2=91=E6=B8=B2=E6=9F=93=E4=B8=8E=E5=85=83=E7=B4=A0=E7=BB=93?= =?UTF-8?q?=E6=9D=9F=E8=A1=8C=E4=B8=BA=E4=B8=8D=E7=AC=A6=E9=97=AE=E9=A2=98?= =?UTF-8?q?=20(#690)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix(video): 修复视频渲染与元素结束行为不符问题 * chore(three): 优化 threejs 视频更新逻辑 --- .../src/plugins/sprite/sprite-item.ts | 8 ++++++-- packages/effects-threejs/src/three-composition.ts | 7 ------- packages/effects-threejs/src/three-texture.ts | 15 +-------------- 3 files changed, 7 insertions(+), 23 deletions(-) diff --git a/packages/effects-core/src/plugins/sprite/sprite-item.ts b/packages/effects-core/src/plugins/sprite/sprite-item.ts index a068f649..79b10942 100644 --- a/packages/effects-core/src/plugins/sprite/sprite-item.ts +++ b/packages/effects-core/src/plugins/sprite/sprite-item.ts @@ -149,8 +149,12 @@ export class SpriteComponent extends BaseRenderComponent { } const { video } = this.renderer.texture.source as Texture2DSourceOptionsVideo; - if (video?.paused) { - video.play().catch(e => { this.engine.renderErrors.add(e); }); + if (video) { + if (time === 0 || (time === this.item.duration)) { + video.pause(); + } else { + video.play().catch(e => { this.engine.renderErrors.add(e); }); + } } } diff --git a/packages/effects-threejs/src/three-composition.ts b/packages/effects-threejs/src/three-composition.ts index abb42403..13e8b4e6 100644 --- a/packages/effects-threejs/src/three-composition.ts +++ b/packages/effects-threejs/src/three-composition.ts @@ -61,13 +61,6 @@ export class ThreeComposition extends Composition { super(props, scene); } - /** - * 更新 video texture 数据 - */ - override updateVideo () { - void this.textures.map(tex => (tex as ThreeTexture).startVideo()); - } - override prepareRender (): void { const render = this.renderer; const frame = this.renderFrame; diff --git a/packages/effects-threejs/src/three-texture.ts b/packages/effects-threejs/src/three-texture.ts index 2f353a9b..bfe50ad9 100644 --- a/packages/effects-threejs/src/three-texture.ts +++ b/packages/effects-threejs/src/three-texture.ts @@ -80,20 +80,6 @@ export class ThreeTexture extends Texture { this.texture.needsUpdate = true; } - /** - * 开始更新视频数据 - * - */ - async startVideo () { - if (this.sourceType === TextureSourceType.video) { - const video = (this.texture).source.data; - - if (video.paused) { - await video.play(); - } - } - } - /** * 组装纹理选项 * @param options - 纹理选项 @@ -224,6 +210,7 @@ export class ThreeTexture extends Texture { texture.wrapT = THREE.MirroredRepeatWrapping; this.width = this.height = 1; } + this.source = options; if (texture) { texture.flipY = !!flipY; From 15de6e24aa2aa076b5b0211988c0cf78a6bf716b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=8C=82=E5=AE=89?= <151805118+wumaolinmaoan@users.noreply.github.com> Date: Fri, 18 Oct 2024 17:21:24 +0800 Subject: [PATCH 44/88] feat: add color and vector4 track (#691) * feat: add color and vector4 track * fix: name * Update packages/effects-core/src/plugins/timeline/playables/color-property-mixer-playable.ts Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --------- Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- packages/effects-core/src/math/index.ts | 2 +- packages/effects-core/src/math/translate.ts | 2 +- .../src/math/value-getters/color-curve.ts | 39 ++++++++++++++ .../src/math/value-getters/index.ts | 3 ++ .../math/{ => value-getters}/value-getter.ts | 14 ++--- .../src/math/value-getters/vector4-curve.ts | 39 ++++++++++++++ .../src/plugins/cal/playable-graph.ts | 2 +- .../color-property-playable-asset.ts | 22 ++++++++ .../float-property-playable-asset.ts | 6 +-- .../playable-assets/timeline-asset.ts | 2 +- .../vector4-property-playable-asset.ts | 22 ++++++++ .../color-property-mixer-playable.ts | 54 +++++++++++++++++++ .../float-property-mixer-playable.ts | 6 +-- .../src/plugins/timeline/playables/index.ts | 2 +- ...-playable.ts => property-clip-playable.ts} | 6 +-- .../timeline/tracks/color-property-track.ts | 21 ++++++++ .../timeline/tracks/float-property-track.ts | 14 ++--- .../plugins/timeline/tracks/property-track.ts | 34 ++++++++++++ 18 files changed, 259 insertions(+), 31 deletions(-) create mode 100644 packages/effects-core/src/math/value-getters/color-curve.ts create mode 100644 packages/effects-core/src/math/value-getters/index.ts rename packages/effects-core/src/math/{ => value-getters}/value-getter.ts (98%) create mode 100644 packages/effects-core/src/math/value-getters/vector4-curve.ts create mode 100644 packages/effects-core/src/plugins/timeline/playable-assets/color-property-playable-asset.ts create mode 100644 packages/effects-core/src/plugins/timeline/playable-assets/vector4-property-playable-asset.ts create mode 100644 packages/effects-core/src/plugins/timeline/playables/color-property-mixer-playable.ts rename packages/effects-core/src/plugins/timeline/playables/{float-property-clip-playable.ts => property-clip-playable.ts} (72%) create mode 100644 packages/effects-core/src/plugins/timeline/tracks/color-property-track.ts create mode 100644 packages/effects-core/src/plugins/timeline/tracks/property-track.ts diff --git a/packages/effects-core/src/math/index.ts b/packages/effects-core/src/math/index.ts index 8afd5809..f6d084b5 100644 --- a/packages/effects-core/src/math/index.ts +++ b/packages/effects-core/src/math/index.ts @@ -1,4 +1,4 @@ export * from './float16array-wrapper'; export * from './translate'; export * from './utils'; -export * from './value-getter'; +export * from './value-getters'; \ No newline at end of file diff --git a/packages/effects-core/src/math/translate.ts b/packages/effects-core/src/math/translate.ts index 927c94f8..0da07cd8 100644 --- a/packages/effects-core/src/math/translate.ts +++ b/packages/effects-core/src/math/translate.ts @@ -2,7 +2,7 @@ import { Euler } from '@galacean/effects-math/es/core/euler'; import { Matrix4 } from '@galacean/effects-math/es/core/matrix4'; import { Vector3 } from '@galacean/effects-math/es/core/vector3'; import type { ItemLinearVelOverLifetime } from '../plugins'; -import type { ValueGetter } from './value-getter'; +import type { ValueGetter } from './value-getters'; export function translatePoint (x: number, y: number): number[] { const origin = [-.5, .5, -.5, -.5, .5, .5, .5, -.5]; diff --git a/packages/effects-core/src/math/value-getters/color-curve.ts b/packages/effects-core/src/math/value-getters/color-curve.ts new file mode 100644 index 00000000..1f52123d --- /dev/null +++ b/packages/effects-core/src/math/value-getters/color-curve.ts @@ -0,0 +1,39 @@ +import { Color } from '@galacean/effects-math/es/core/color'; +import type { BezierCurve } from './value-getter'; +import { ValueGetter, createValueGetter } from './value-getter'; +import type { BezierValue } from '@galacean/effects-specification'; + +export class ColorCurve extends ValueGetter { + private value = new Color(); + + private rCurve: BezierCurve; + private gCurve: BezierCurve; + private bCurve: BezierCurve; + private aCurve: BezierCurve; + + override onCreate (arg: ColorCurveData) { + this.rCurve = createValueGetter(arg.r) as BezierCurve; + this.gCurve = createValueGetter(arg.g) as BezierCurve; + this.bCurve = createValueGetter(arg.b) as BezierCurve; + this.aCurve = createValueGetter(arg.a) as BezierCurve; + } + + override getValue (t: number): Color { + const r = this.rCurve.getValue(t); + const g = this.gCurve.getValue(t); + const b = this.bCurve.getValue(t); + const a = this.aCurve.getValue(t); + + this.value.set(r, g, b, a); + + return this.value; + } +} + +// TODO replace with spec def +export interface ColorCurveData { + r: BezierValue, + g: BezierValue, + b: BezierValue, + a: BezierValue, +} \ No newline at end of file diff --git a/packages/effects-core/src/math/value-getters/index.ts b/packages/effects-core/src/math/value-getters/index.ts new file mode 100644 index 00000000..95067b50 --- /dev/null +++ b/packages/effects-core/src/math/value-getters/index.ts @@ -0,0 +1,3 @@ +export * from './value-getter'; +export * from './color-curve'; +export * from './vector4-curve'; diff --git a/packages/effects-core/src/math/value-getter.ts b/packages/effects-core/src/math/value-getters/value-getter.ts similarity index 98% rename from packages/effects-core/src/math/value-getter.ts rename to packages/effects-core/src/math/value-getters/value-getter.ts index 3e642189..3ca6f312 100644 --- a/packages/effects-core/src/math/value-getter.ts +++ b/packages/effects-core/src/math/value-getters/value-getter.ts @@ -3,13 +3,13 @@ import type { Vector2 } from '@galacean/effects-math/es/core/vector2'; import { Vector3 } from '@galacean/effects-math/es/core/vector3'; import { Quaternion } from '@galacean/effects-math/es/core/quaternion'; import * as spec from '@galacean/effects-specification'; -import { colorToArr, colorStopsFromGradient, interpolateColor, isFunction } from '../utils'; -import type { ColorStop } from '../utils'; -import type { BezierEasing } from './bezier'; -import { BezierPath, buildEasingCurve, BezierQuat } from './bezier'; -import { Float16ArrayWrapper } from './float16array-wrapper'; -import { numberToFix } from './utils'; -import { HELP_LINK } from '../constants'; +import { colorToArr, colorStopsFromGradient, interpolateColor, isFunction } from '../../utils'; +import type { ColorStop } from '../../utils'; +import type { BezierEasing } from '../bezier'; +import { BezierPath, buildEasingCurve, BezierQuat } from '../bezier'; +import { Float16ArrayWrapper } from '../float16array-wrapper'; +import { numberToFix } from '../utils'; +import { HELP_LINK } from '../../constants'; interface KeyFrameMeta { curves: ValueGetter[], diff --git a/packages/effects-core/src/math/value-getters/vector4-curve.ts b/packages/effects-core/src/math/value-getters/vector4-curve.ts new file mode 100644 index 00000000..9be84dc5 --- /dev/null +++ b/packages/effects-core/src/math/value-getters/vector4-curve.ts @@ -0,0 +1,39 @@ +import { Vector4 } from '@galacean/effects-math/es/core/vector4'; +import type { BezierCurve } from './value-getter'; +import { ValueGetter, createValueGetter } from './value-getter'; +import type { BezierValue } from '@galacean/effects-specification'; + +export class Vector4Curve extends ValueGetter { + private value = new Vector4(); + + private xCurve: BezierCurve; + private yCurve: BezierCurve; + private zCurve: BezierCurve; + private wCurve: BezierCurve; + + override onCreate (arg: Vector4CurveData) { + this.xCurve = createValueGetter(arg.x) as BezierCurve; + this.yCurve = createValueGetter(arg.y) as BezierCurve; + this.zCurve = createValueGetter(arg.z) as BezierCurve; + this.wCurve = createValueGetter(arg.w) as BezierCurve; + } + + override getValue (t: number): Vector4 { + const x = this.xCurve.getValue(t); + const y = this.yCurve.getValue(t); + const z = this.zCurve.getValue(t); + const w = this.wCurve.getValue(t); + + this.value.set(x, y, z, w); + + return this.value; + } +} + +// TODO replace with spec def +export interface Vector4CurveData { + x: BezierValue, + y: BezierValue, + z: BezierValue, + w: BezierValue, +} \ No newline at end of file diff --git a/packages/effects-core/src/plugins/cal/playable-graph.ts b/packages/effects-core/src/plugins/cal/playable-graph.ts index e0aed6d5..fc0879f3 100644 --- a/packages/effects-core/src/plugins/cal/playable-graph.ts +++ b/packages/effects-core/src/plugins/cal/playable-graph.ts @@ -347,7 +347,7 @@ export class PlayableOutput { }; } - setSourcePlayeble (playable: Playable, port = 0) { + setSourcePlayable (playable: Playable, port = 0) { this.sourcePlayable = playable; this.sourceOutputPort = port; } diff --git a/packages/effects-core/src/plugins/timeline/playable-assets/color-property-playable-asset.ts b/packages/effects-core/src/plugins/timeline/playable-assets/color-property-playable-asset.ts new file mode 100644 index 00000000..78f61401 --- /dev/null +++ b/packages/effects-core/src/plugins/timeline/playable-assets/color-property-playable-asset.ts @@ -0,0 +1,22 @@ +import { effectsClass, serialize } from '../../../decorators'; +import type { Playable, PlayableGraph } from '../../cal/playable-graph'; +import { PlayableAsset } from '../../cal/playable-graph'; +import { PropertyClipPlayable } from '../playables'; +import type { ColorCurveData } from '../../../math'; +import { createValueGetter } from '../../../math'; +import type { Color } from '@galacean/effects-math/es/core'; + +@effectsClass('ColorPropertyPlayableAsset') +export class ColorPropertyPlayableAsset extends PlayableAsset { + @serialize() + curveData: ColorCurveData; + + override createPlayable (graph: PlayableGraph): Playable { + const clipPlayable = new PropertyClipPlayable(graph); + + clipPlayable.curve = createValueGetter(this.curveData); + clipPlayable.value = clipPlayable.curve.getValue(0); + + return clipPlayable; + } +} diff --git a/packages/effects-core/src/plugins/timeline/playable-assets/float-property-playable-asset.ts b/packages/effects-core/src/plugins/timeline/playable-assets/float-property-playable-asset.ts index 138b6e9e..b8f9b350 100644 --- a/packages/effects-core/src/plugins/timeline/playable-assets/float-property-playable-asset.ts +++ b/packages/effects-core/src/plugins/timeline/playable-assets/float-property-playable-asset.ts @@ -1,9 +1,9 @@ import type { FixedNumberExpression } from '@galacean/effects-specification'; -import { createValueGetter } from '../../../math/value-getter'; import { effectsClass, serialize } from '../../../decorators'; import type { Playable, PlayableGraph } from '../../cal/playable-graph'; import { PlayableAsset } from '../../cal/playable-graph'; -import { FloatPropertyClipPlayable } from '../playables'; +import { PropertyClipPlayable } from '../playables'; +import { createValueGetter } from '../../../math'; @effectsClass('FloatPropertyPlayableAsset') export class FloatPropertyPlayableAsset extends PlayableAsset { @@ -11,7 +11,7 @@ export class FloatPropertyPlayableAsset extends PlayableAsset { curveData: FixedNumberExpression; override createPlayable (graph: PlayableGraph): Playable { - const clipPlayable = new FloatPropertyClipPlayable(graph); + const clipPlayable = new PropertyClipPlayable(graph); clipPlayable.curve = createValueGetter(this.curveData); clipPlayable.value = clipPlayable.curve.getValue(0); diff --git a/packages/effects-core/src/plugins/timeline/playable-assets/timeline-asset.ts b/packages/effects-core/src/plugins/timeline/playable-assets/timeline-asset.ts index e21e6770..33098bd9 100644 --- a/packages/effects-core/src/plugins/timeline/playable-assets/timeline-asset.ts +++ b/packages/effects-core/src/plugins/timeline/playable-assets/timeline-asset.ts @@ -84,7 +84,7 @@ export class TimelinePlayable extends Playable { trackOutput.setUserData(track.boundObject); graph.addOutput(trackOutput); - trackOutput.setSourcePlayeble(this, this.getInputCount() - 1); + trackOutput.setSourcePlayable(this, this.getInputCount() - 1); // create track instance const trackInstance = new TrackInstance(track, trackMixPlayable, trackOutput); diff --git a/packages/effects-core/src/plugins/timeline/playable-assets/vector4-property-playable-asset.ts b/packages/effects-core/src/plugins/timeline/playable-assets/vector4-property-playable-asset.ts new file mode 100644 index 00000000..f2adf37b --- /dev/null +++ b/packages/effects-core/src/plugins/timeline/playable-assets/vector4-property-playable-asset.ts @@ -0,0 +1,22 @@ +import { effectsClass, serialize } from '../../../decorators'; +import type { Playable, PlayableGraph } from '../../cal/playable-graph'; +import { PlayableAsset } from '../../cal/playable-graph'; +import { PropertyClipPlayable } from '../playables'; +import type { Vector4CurveData } from '../../../math'; +import { createValueGetter } from '../../../math'; +import type { Vector4 } from '@galacean/effects-math/es/core'; + +@effectsClass('Vector4PropertyPlayableAsset') +export class Vector4PropertyPlayableAsset extends PlayableAsset { + @serialize() + curveData: Vector4CurveData; + + override createPlayable (graph: PlayableGraph): Playable { + const clipPlayable = new PropertyClipPlayable(graph); + + clipPlayable.curve = createValueGetter(this.curveData); + clipPlayable.value = clipPlayable.curve.getValue(0); + + return clipPlayable; + } +} diff --git a/packages/effects-core/src/plugins/timeline/playables/color-property-mixer-playable.ts b/packages/effects-core/src/plugins/timeline/playables/color-property-mixer-playable.ts new file mode 100644 index 00000000..d3310eca --- /dev/null +++ b/packages/effects-core/src/plugins/timeline/playables/color-property-mixer-playable.ts @@ -0,0 +1,54 @@ +import { Color } from '@galacean/effects-math/es/core'; +import type { FrameContext } from '../../cal/playable-graph'; +import { Playable } from '../../cal/playable-graph'; +import { PropertyClipPlayable } from './property-clip-playable'; + +export class ColorPropertyMixerPlayable extends Playable { + propertyName = ''; + + override processFrame (context: FrameContext): void { + const boundObject = context.output.getUserData() as Record; + + if (!boundObject) { + return; + } + + let hasInput = false; + const value = boundObject[this.propertyName]; + + if (!(value instanceof Color)) { + return; + } + + value.setZero(); + + // evaluate the curve + for (let i = 0; i < this.getInputCount(); i++) { + const weight = this.getInputWeight(i); + + if (weight > 0) { + const propertyClipPlayable = this.getInput(i) as PropertyClipPlayable; + + if (!(propertyClipPlayable instanceof PropertyClipPlayable)) { + console.error('ColorPropertyMixerPlayable received incompatible input'); + continue; + } + + const curveValue = propertyClipPlayable.value; + + value.r += curveValue.r * weight; + value.g += curveValue.g * weight; + value.b += curveValue.b * weight; + value.a += curveValue.a * weight; + + hasInput = true; + } + } + + // set value + if (hasInput) { + boundObject[this.propertyName] = value; + } + } +} + diff --git a/packages/effects-core/src/plugins/timeline/playables/float-property-mixer-playable.ts b/packages/effects-core/src/plugins/timeline/playables/float-property-mixer-playable.ts index cac6c3c1..8f507288 100644 --- a/packages/effects-core/src/plugins/timeline/playables/float-property-mixer-playable.ts +++ b/packages/effects-core/src/plugins/timeline/playables/float-property-mixer-playable.ts @@ -1,6 +1,6 @@ import type { FrameContext } from '../../cal/playable-graph'; import { Playable } from '../../cal/playable-graph'; -import { FloatPropertyClipPlayable } from './float-property-clip-playable'; +import { PropertyClipPlayable } from './property-clip-playable'; export class FloatPropertyMixerPlayable extends Playable { propertyName = ''; @@ -20,9 +20,9 @@ export class FloatPropertyMixerPlayable extends Playable { const weight = this.getInputWeight(i); if (weight > 0) { - const propertyClipPlayable = this.getInput(i); + const propertyClipPlayable = this.getInput(i) as PropertyClipPlayable; - if (!(propertyClipPlayable instanceof FloatPropertyClipPlayable)) { + if (!(propertyClipPlayable instanceof PropertyClipPlayable)) { console.error('FloatPropertyTrack added non-FloatPropertyPlayableAsset'); continue; } diff --git a/packages/effects-core/src/plugins/timeline/playables/index.ts b/packages/effects-core/src/plugins/timeline/playables/index.ts index cefb5a62..aaee419f 100644 --- a/packages/effects-core/src/plugins/timeline/playables/index.ts +++ b/packages/effects-core/src/plugins/timeline/playables/index.ts @@ -1,5 +1,5 @@ export * from './activation-mixer-playable'; -export * from './float-property-clip-playable'; +export * from './property-clip-playable'; export * from './float-property-mixer-playable'; export * from './sub-composition-clip-playable'; export * from './sub-composition-mixer-playable'; diff --git a/packages/effects-core/src/plugins/timeline/playables/float-property-clip-playable.ts b/packages/effects-core/src/plugins/timeline/playables/property-clip-playable.ts similarity index 72% rename from packages/effects-core/src/plugins/timeline/playables/float-property-clip-playable.ts rename to packages/effects-core/src/plugins/timeline/playables/property-clip-playable.ts index 6fa02c65..fd467a3b 100644 --- a/packages/effects-core/src/plugins/timeline/playables/float-property-clip-playable.ts +++ b/packages/effects-core/src/plugins/timeline/playables/property-clip-playable.ts @@ -2,9 +2,9 @@ import type { ValueGetter } from '../../../math'; import type { FrameContext } from '../../cal/playable-graph'; import { Playable } from '../../cal/playable-graph'; -export class FloatPropertyClipPlayable extends Playable { - value: number; - curve: ValueGetter; +export class PropertyClipPlayable extends Playable { + value: T; + curve: ValueGetter; override processFrame (context: FrameContext): void { this.value = this.curve.getValue(this.time); diff --git a/packages/effects-core/src/plugins/timeline/tracks/color-property-track.ts b/packages/effects-core/src/plugins/timeline/tracks/color-property-track.ts new file mode 100644 index 00000000..91bf8440 --- /dev/null +++ b/packages/effects-core/src/plugins/timeline/tracks/color-property-track.ts @@ -0,0 +1,21 @@ +import { effectsClass } from '../../../decorators'; +import type { PlayableGraph, Playable } from '../../cal/playable-graph'; +import { FloatPropertyMixerPlayable } from '../playables'; +import { PropertyTrack } from './property-track'; + +@effectsClass('ColorPropertyTrack') +export class ColorPropertyTrack extends PropertyTrack { + override createTrackMixer (graph: PlayableGraph): Playable { + const mixer = new FloatPropertyMixerPlayable(graph); + + const propertyNames = this.propertyNames; + + if (propertyNames.length > 0) { + const propertyName = propertyNames[propertyNames.length - 1]; + + mixer.propertyName = propertyName; + } + + return mixer; + } +} \ No newline at end of file diff --git a/packages/effects-core/src/plugins/timeline/tracks/float-property-track.ts b/packages/effects-core/src/plugins/timeline/tracks/float-property-track.ts index de4c9741..f0c7b6ac 100644 --- a/packages/effects-core/src/plugins/timeline/tracks/float-property-track.ts +++ b/packages/effects-core/src/plugins/timeline/tracks/float-property-track.ts @@ -1,21 +1,15 @@ -import { effectsClass, serialize } from '../../../decorators'; +import { effectsClass } from '../../../decorators'; import type { PlayableGraph, Playable } from '../../cal/playable-graph'; import { FloatPropertyMixerPlayable } from '../playables'; -import { TrackAsset } from '../track'; +import { PropertyTrack } from './property-track'; @effectsClass('FloatPropertyTrack') -export class FloatPropertyTrack extends TrackAsset { - @serialize() - path = ''; - - propertyNames: string[] = []; +export class FloatPropertyTrack extends PropertyTrack { override createTrackMixer (graph: PlayableGraph): Playable { const mixer = new FloatPropertyMixerPlayable(graph); - const propertyNames = this.path.split('.'); - - this.propertyNames = propertyNames; + const propertyNames = this.propertyNames; if (propertyNames.length > 0) { const propertyName = propertyNames[propertyNames.length - 1]; diff --git a/packages/effects-core/src/plugins/timeline/tracks/property-track.ts b/packages/effects-core/src/plugins/timeline/tracks/property-track.ts new file mode 100644 index 00000000..fd7c59d7 --- /dev/null +++ b/packages/effects-core/src/plugins/timeline/tracks/property-track.ts @@ -0,0 +1,34 @@ +import type { EffectsObjectData } from '@galacean/effects-specification'; +import { serialize } from '../../../decorators'; +import { TrackAsset } from '../track'; + +export class PropertyTrack extends TrackAsset { + + protected propertyNames: string[] = []; + + @serialize() + private path = ''; + + override updateAnimatedObject () { + const propertyNames = this.propertyNames; + let target: Record = this.parent.boundObject; + + for (let i = 0; i < propertyNames.length - 1; i++) { + const property = target[propertyNames[i]]; + + if (property === undefined) { + console.error('The ' + propertyNames[i] + ' property of ' + target + ' was not found'); + } + target = property; + } + + this.boundObject = target; + } + + override fromData (data: EffectsObjectData): void { + super.fromData(data); + const propertyNames = this.path.split('.'); + + this.propertyNames = propertyNames; + } +} \ No newline at end of file From 39d1083ec7ed1036b867599fe43852245ce2ff7b Mon Sep 17 00:00:00 2001 From: wumaolinmaoan Date: Fri, 18 Oct 2024 19:05:30 +0800 Subject: [PATCH 45/88] fix: interact item click invalid when composition restart --- packages/effects-core/src/comp-vfx-item.ts | 1 - packages/effects-core/src/composition.ts | 89 ++++++++----------- .../src/plugins/interact/interact-item.ts | 6 ++ .../src/plugins/timeline/track.ts | 7 +- 4 files changed, 48 insertions(+), 55 deletions(-) diff --git a/packages/effects-core/src/comp-vfx-item.ts b/packages/effects-core/src/comp-vfx-item.ts index d8bc665b..10b9580b 100644 --- a/packages/effects-core/src/comp-vfx-item.ts +++ b/packages/effects-core/src/comp-vfx-item.ts @@ -148,7 +148,6 @@ export class CompositionComponent extends Behaviour { if ( item.getVisible() && item.transform.getValid() - && !item.ended && !VFXItem.isComposition(item) && !skip(item) ) { diff --git a/packages/effects-core/src/composition.ts b/packages/effects-core/src/composition.ts index 3ef81ee9..10c76e1d 100644 --- a/packages/effects-core/src/composition.ts +++ b/packages/effects-core/src/composition.ts @@ -279,23 +279,9 @@ export class Composition extends EventEmitter> imp this.rootComposition.createContent(); this.buildItemTree(this.rootItem); - this.rootItem.onEnd = () => { - window.setTimeout(() => { - this.emit('end', { composition: this }); - }, 0); - }; this.pluginSystem.resetComposition(this, this.renderFrame); } - initializeSceneTicking (item: VFXItem) { - for (const component of item.components) { - this.sceneTicking.addComponent(component); - } - for (const child of item.children) { - this.initializeSceneTicking(child); - } - } - /** * 所有合成 Item 的根变换 */ @@ -581,42 +567,6 @@ export class Composition extends EventEmitter> imp } } - private toLocalTime (time: number) { - let localTime = time - this.rootItem.start; - const duration = this.rootItem.duration; - - if (localTime - duration > 0.001) { - if (!this.rootItem.ended) { - this.rootItem.ended = true; - this.emit('end', { composition: this }); - } - - switch (this.rootItem.endBehavior) { - case spec.EndBehavior.restart: { - localTime = localTime % duration; - this.restart(); - - break; - } - case spec.EndBehavior.freeze: { - localTime = Math.min(duration, localTime); - - break; - } - case spec.EndBehavior.forward: { - - break; - } - case spec.EndBehavior.destroy: { - - break; - } - } - } - - return localTime; - } - private shouldDispose () { return this.rootItem.ended && this.rootItem.endBehavior === spec.EndBehavior.destroy && !this.reusable; } @@ -717,9 +667,46 @@ export class Composition extends EventEmitter> imp */ private updateRootComposition (deltaTime: number) { if (this.rootComposition.isActiveAndEnabled) { - const localTime = this.toLocalTime(this.time + deltaTime); + + let localTime = this.time + deltaTime - this.rootItem.start; + let ended = false; + + const duration = this.rootItem.duration; + const endBehavior = this.rootItem.endBehavior; + + if (localTime - duration > 0.001) { + + ended = true; + + switch (endBehavior) { + case spec.EndBehavior.restart: { + localTime = localTime % duration; + this.restart(); + + break; + } + case spec.EndBehavior.freeze: { + localTime = Math.min(duration, localTime); + + break; + } + case spec.EndBehavior.forward: { + + break; + } + case spec.EndBehavior.destroy: { + + break; + } + } + } this.rootComposition.time = localTime; + + if (ended && !this.rootItem.ended) { + this.rootItem.ended = true; + this.emit('end', { composition: this }); + } } } diff --git a/packages/effects-core/src/plugins/interact/interact-item.ts b/packages/effects-core/src/plugins/interact/interact-item.ts index 40c0543f..3157feea 100644 --- a/packages/effects-core/src/plugins/interact/interact-item.ts +++ b/packages/effects-core/src/plugins/interact/interact-item.ts @@ -116,6 +116,12 @@ export class InteractComponent extends RendererComponent { } this.previewContent?.updateMesh(); if (!this.hasBeenAddedToComposition && this.item.composition) { + + const { type } = this.interactData.options as spec.ClickInteractOption; + + if (type === spec.InteractType.CLICK) { + this.clickable = true; + } const options = this.item.props.content.options as spec.DragInteractOption; this.item.composition.addInteractiveItem(this.item, options.type); diff --git a/packages/effects-core/src/plugins/timeline/track.ts b/packages/effects-core/src/plugins/timeline/track.ts index 3785910f..0597bc80 100644 --- a/packages/effects-core/src/plugins/timeline/track.ts +++ b/packages/effects-core/src/plugins/timeline/track.ts @@ -226,6 +226,10 @@ export class RuntimeClip { } this.parentMixer.setInputWeight(this.playable, weight); + const clipTime = clip.toLocalTime(localTime); + + this.playable.setTime(clipTime); + // 判断动画是否结束 if (ended) { if (boundObject instanceof VFXItem && !boundObject.ended) { @@ -240,9 +244,6 @@ export class RuntimeClip { this.playable.pause(); } } - const clipTime = clip.toLocalTime(localTime); - - this.playable.setTime(clipTime); } } From 7ca8cd37af8d902dc8c1f15b24ef53de359a6ebc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=8C=82=E5=AE=89?= <151805118+wumaolinmaoan@users.noreply.github.com> Date: Fri, 18 Oct 2024 19:16:57 +0800 Subject: [PATCH 46/88] feat: add vector4 property mixer (#692) * feat: add vector4 property mixer * fix: import * fix: import * fix: import --- .../color-property-mixer-playable.ts | 2 +- .../src/plugins/timeline/playables/index.ts | 2 + .../vector4-property-mixer-playable.ts | 54 +++++++++++++++++++ .../src/plugins/timeline/tracks/index.ts | 3 ++ .../timeline/tracks/vector4-property-track.ts | 21 ++++++++ 5 files changed, 81 insertions(+), 1 deletion(-) create mode 100644 packages/effects-core/src/plugins/timeline/playables/vector4-property-mixer-playable.ts create mode 100644 packages/effects-core/src/plugins/timeline/tracks/vector4-property-track.ts diff --git a/packages/effects-core/src/plugins/timeline/playables/color-property-mixer-playable.ts b/packages/effects-core/src/plugins/timeline/playables/color-property-mixer-playable.ts index d3310eca..13634e6c 100644 --- a/packages/effects-core/src/plugins/timeline/playables/color-property-mixer-playable.ts +++ b/packages/effects-core/src/plugins/timeline/playables/color-property-mixer-playable.ts @@ -1,4 +1,4 @@ -import { Color } from '@galacean/effects-math/es/core'; +import { Color } from '@galacean/effects-math/es/core/color'; import type { FrameContext } from '../../cal/playable-graph'; import { Playable } from '../../cal/playable-graph'; import { PropertyClipPlayable } from './property-clip-playable'; diff --git a/packages/effects-core/src/plugins/timeline/playables/index.ts b/packages/effects-core/src/plugins/timeline/playables/index.ts index aaee419f..f82d4a19 100644 --- a/packages/effects-core/src/plugins/timeline/playables/index.ts +++ b/packages/effects-core/src/plugins/timeline/playables/index.ts @@ -3,3 +3,5 @@ export * from './property-clip-playable'; export * from './float-property-mixer-playable'; export * from './sub-composition-clip-playable'; export * from './sub-composition-mixer-playable'; +export * from './vector4-property-mixer-playable'; +export * from './color-property-mixer-playable'; diff --git a/packages/effects-core/src/plugins/timeline/playables/vector4-property-mixer-playable.ts b/packages/effects-core/src/plugins/timeline/playables/vector4-property-mixer-playable.ts new file mode 100644 index 00000000..076c05b5 --- /dev/null +++ b/packages/effects-core/src/plugins/timeline/playables/vector4-property-mixer-playable.ts @@ -0,0 +1,54 @@ +import { Vector4 } from '@galacean/effects-math/es/core/vector4'; +import type { FrameContext } from '../../cal/playable-graph'; +import { Playable } from '../../cal/playable-graph'; +import { PropertyClipPlayable } from './property-clip-playable'; + +export class Vector4PropertyMixerPlayable extends Playable { + propertyName = ''; + + override processFrame (context: FrameContext): void { + const boundObject = context.output.getUserData() as Record; + + if (!boundObject) { + return; + } + + let hasInput = false; + const value = boundObject[this.propertyName]; + + if (!(value instanceof Vector4)) { + return; + } + + value.setZero(); + + // evaluate the curve + for (let i = 0; i < this.getInputCount(); i++) { + const weight = this.getInputWeight(i); + + if (weight > 0) { + const propertyClipPlayable = this.getInput(i) as PropertyClipPlayable; + + if (!(propertyClipPlayable instanceof PropertyClipPlayable)) { + console.error('Vector4PropertyTrack added non-Vector4PropertyPlayableAsset'); + continue; + } + + const curveValue = propertyClipPlayable.value; + + value.x += curveValue.x * weight; + value.y += curveValue.y * weight; + value.z += curveValue.z * weight; + value.w += curveValue.w * weight; + + hasInput = true; + } + } + + // set value + if (hasInput) { + boundObject[this.propertyName] = value; + } + } +} + diff --git a/packages/effects-core/src/plugins/timeline/tracks/index.ts b/packages/effects-core/src/plugins/timeline/tracks/index.ts index 04928190..c479fe40 100644 --- a/packages/effects-core/src/plugins/timeline/tracks/index.ts +++ b/packages/effects-core/src/plugins/timeline/tracks/index.ts @@ -4,3 +4,6 @@ export * from './sprite-color-track'; export * from './sub-composition-track'; export * from './transform-track'; export * from './material-track'; +export * from './property-track'; +export * from './vector4-property-track'; +export * from './color-property-track'; diff --git a/packages/effects-core/src/plugins/timeline/tracks/vector4-property-track.ts b/packages/effects-core/src/plugins/timeline/tracks/vector4-property-track.ts new file mode 100644 index 00000000..65f48811 --- /dev/null +++ b/packages/effects-core/src/plugins/timeline/tracks/vector4-property-track.ts @@ -0,0 +1,21 @@ +import { effectsClass } from '../../../decorators'; +import type { PlayableGraph, Playable } from '../../cal/playable-graph'; +import { Vector4PropertyMixerPlayable } from '../playables'; +import { PropertyTrack } from './property-track'; + +@effectsClass('Vector4PropertyTrack') +export class Vector4PropertyTrack extends PropertyTrack { + override createTrackMixer (graph: PlayableGraph): Playable { + const mixer = new Vector4PropertyMixerPlayable(graph); + + const propertyNames = this.propertyNames; + + if (propertyNames.length > 0) { + const propertyName = propertyNames[propertyNames.length - 1]; + + mixer.propertyName = propertyName; + } + + return mixer; + } +} \ No newline at end of file From 0b75cd0afad6b1ea8a902abd10e6182440fa3942 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=8C=82=E5=AE=89?= <151805118+wumaolinmaoan@users.noreply.github.com> Date: Mon, 21 Oct 2024 17:43:41 +0800 Subject: [PATCH 47/88] chore: update specification (#694) * feat: update specification * fix: circle dependency * fix: import * fix: objectBindingTrack do not update binding * fix: import --------- Co-authored-by: wumaolin.wml --- packages/effects-core/package.json | 2 +- packages/effects-core/src/composition.ts | 1 - .../src/math/value-getters/color-curve.ts | 15 +-- .../src/math/value-getters/index.ts | 1 + .../math/value-getters/value-getter-map.ts | 93 +++++++++++++++++++ .../src/math/value-getters/value-getter.ts | 81 +--------------- .../src/math/value-getters/vector4-curve.ts | 15 +-- .../src/plugins/cal/calculate-item.ts | 3 + .../src/plugins/sprite/sprite-item.ts | 2 +- .../color-property-playable-asset.ts | 6 +- .../float-property-playable-asset.ts | 3 +- .../vector4-property-playable-asset.ts | 4 +- .../timeline/tracks/color-property-track.ts | 3 +- .../timeline/tracks/float-property-track.ts | 3 +- .../timeline/tracks/vector4-property-track.ts | 3 +- pnpm-lock.yaml | 18 +++- 16 files changed, 135 insertions(+), 118 deletions(-) create mode 100644 packages/effects-core/src/math/value-getters/value-getter-map.ts diff --git a/packages/effects-core/package.json b/packages/effects-core/package.json index 00bf5fbe..eece695a 100644 --- a/packages/effects-core/package.json +++ b/packages/effects-core/package.json @@ -51,7 +51,7 @@ "registry": "https://registry.npmjs.org" }, "dependencies": { - "@galacean/effects-specification": "2.1.0-alpha.0", + "@galacean/effects-specification": "2.1.0-alpha.1", "@galacean/effects-math": "1.1.0", "flatbuffers": "24.3.25", "uuid": "9.0.1", diff --git a/packages/effects-core/src/composition.ts b/packages/effects-core/src/composition.ts index 10c76e1d..a7d28122 100644 --- a/packages/effects-core/src/composition.ts +++ b/packages/effects-core/src/composition.ts @@ -232,7 +232,6 @@ export class Composition extends EventEmitter> imp } const { sourceContent, pluginSystem, imgUsage, totalTime, refCompositionProps } = this.compositionSourceManager; - //@ts-expect-error // TODO 更新 Spec this.postProcessingEnabled = scene.jsonScene.renderSettings?.postProcessingEnabled ?? false; assertExist(sourceContent); diff --git a/packages/effects-core/src/math/value-getters/color-curve.ts b/packages/effects-core/src/math/value-getters/color-curve.ts index 1f52123d..d4a80e5b 100644 --- a/packages/effects-core/src/math/value-getters/color-curve.ts +++ b/packages/effects-core/src/math/value-getters/color-curve.ts @@ -1,7 +1,8 @@ import { Color } from '@galacean/effects-math/es/core/color'; import type { BezierCurve } from './value-getter'; -import { ValueGetter, createValueGetter } from './value-getter'; -import type { BezierValue } from '@galacean/effects-specification'; +import { ValueGetter } from './value-getter'; +import type * as spec from '@galacean/effects-specification'; +import { createValueGetter } from './value-getter-map'; export class ColorCurve extends ValueGetter { private value = new Color(); @@ -11,7 +12,7 @@ export class ColorCurve extends ValueGetter { private bCurve: BezierCurve; private aCurve: BezierCurve; - override onCreate (arg: ColorCurveData) { + override onCreate (arg: spec.ColorCurveData) { this.rCurve = createValueGetter(arg.r) as BezierCurve; this.gCurve = createValueGetter(arg.g) as BezierCurve; this.bCurve = createValueGetter(arg.b) as BezierCurve; @@ -28,12 +29,4 @@ export class ColorCurve extends ValueGetter { return this.value; } -} - -// TODO replace with spec def -export interface ColorCurveData { - r: BezierValue, - g: BezierValue, - b: BezierValue, - a: BezierValue, } \ No newline at end of file diff --git a/packages/effects-core/src/math/value-getters/index.ts b/packages/effects-core/src/math/value-getters/index.ts index 95067b50..0c1d8ca9 100644 --- a/packages/effects-core/src/math/value-getters/index.ts +++ b/packages/effects-core/src/math/value-getters/index.ts @@ -1,3 +1,4 @@ export * from './value-getter'; export * from './color-curve'; export * from './vector4-curve'; +export * from './value-getter-map'; diff --git a/packages/effects-core/src/math/value-getters/value-getter-map.ts b/packages/effects-core/src/math/value-getters/value-getter-map.ts new file mode 100644 index 00000000..d97a37ed --- /dev/null +++ b/packages/effects-core/src/math/value-getters/value-getter-map.ts @@ -0,0 +1,93 @@ +import * as spec from '@galacean/effects-specification'; +import { colorToArr, isFunction } from '../../utils'; +import { BezierCurve, BezierCurvePath, BezierCurveQuat, GradientValue, LinearValue, LineSegments, PathSegments, RandomSetValue, RandomValue, RandomVectorValue, StaticValue } from './value-getter'; +import { Vector3 } from '@galacean/effects-math/es/core/vector3'; +import { Quaternion } from '@galacean/effects-math/es/core/quaternion'; +import { ColorCurve } from './color-curve'; +import { Vector4Curve } from './vector4-curve'; +import { ValueGetter } from './value-getter'; +import { HELP_LINK } from '../../constants'; + +const map: Record = { + [spec.ValueType.RANDOM] (props: number[][]) { + if (props[0] instanceof Array) { + return new RandomVectorValue(props); + } + + return new RandomValue(props); + }, + [spec.ValueType.CONSTANT] (props: number) { + return new StaticValue(props); + }, + [spec.ValueType.CONSTANT_VEC2] (props: number) { + return new StaticValue(props); + }, + [spec.ValueType.CONSTANT_VEC3] (props: number) { + return new StaticValue(props); + }, + [spec.ValueType.CONSTANT_VEC4] (props: number) { + return new StaticValue(props); + }, + [spec.ValueType.RGBA_COLOR] (props: number) { + return new StaticValue(props); + }, + [spec.ValueType.COLORS] (props: number[][]) { + return new RandomSetValue(props.map(c => colorToArr(c, false))); + }, + [spec.ValueType.LINE] (props: number[][]) { + if (props.length === 2 && props[0][0] === 0 && props[1][0] === 1) { + return new LinearValue([props[0][1], props[1][1]]); + } + + return new LineSegments(props); + }, + [spec.ValueType.GRADIENT_COLOR] (props: number[][] | Record) { + return new GradientValue(props); + }, + [spec.ValueType.LINEAR_PATH] (pros: number[][][]) { + return new PathSegments(pros); + }, + [spec.ValueType.BEZIER_CURVE] (props: number[][][]) { + if (props.length === 1) { + return new StaticValue(props[0][1][1]); + } + + return new BezierCurve(props); + }, + [spec.ValueType.BEZIER_CURVE_PATH] (props: number[][][]) { + if (props[0].length === 1) { + return new StaticValue(new Vector3(...props[1][0])); + } + + return new BezierCurvePath(props); + }, + [spec.ValueType.BEZIER_CURVE_QUAT] (props: number[][][]) { + if (props[0].length === 1) { + return new StaticValue(new Quaternion(...props[1][0])); + } + + return new BezierCurveQuat(props); + }, + [spec.ValueType.COLOR_CURVE] (props: spec.ColorCurveData) { + return new ColorCurve(props); + }, + [spec.ValueType.VECTOR4_CURVE] (props: spec.Vector4CurveData) { + return new Vector4Curve(props); + }, +}; + +export function createValueGetter (args: any): ValueGetter { + if (!args || !isNaN(+args)) { + return new StaticValue(args || 0); + } + + if (args instanceof ValueGetter) { + return args; + } + + if (isFunction(map[args[0]])) { + return map[args[0]](args[1]); + } else { + throw new Error(`ValueType: ${args[0]} is not supported, see ${HELP_LINK['ValueType: 21/22 is not supported']}.`); + } +} \ No newline at end of file diff --git a/packages/effects-core/src/math/value-getters/value-getter.ts b/packages/effects-core/src/math/value-getters/value-getter.ts index 3ca6f312..9b1c2568 100644 --- a/packages/effects-core/src/math/value-getters/value-getter.ts +++ b/packages/effects-core/src/math/value-getters/value-getter.ts @@ -3,13 +3,12 @@ import type { Vector2 } from '@galacean/effects-math/es/core/vector2'; import { Vector3 } from '@galacean/effects-math/es/core/vector3'; import { Quaternion } from '@galacean/effects-math/es/core/quaternion'; import * as spec from '@galacean/effects-specification'; -import { colorToArr, colorStopsFromGradient, interpolateColor, isFunction } from '../../utils'; +import { colorStopsFromGradient, interpolateColor } from '../../utils'; import type { ColorStop } from '../../utils'; import type { BezierEasing } from '../bezier'; import { BezierPath, buildEasingCurve, BezierQuat } from '../bezier'; import { Float16ArrayWrapper } from '../float16array-wrapper'; import { numberToFix } from '../utils'; -import { HELP_LINK } from '../../constants'; interface KeyFrameMeta { curves: ValueGetter[], @@ -840,84 +839,6 @@ export class BezierCurveQuat extends ValueGetter { } } -const map: Record = { - [spec.ValueType.RANDOM] (props: number[][]) { - if (props[0] instanceof Array) { - return new RandomVectorValue(props); - } - - return new RandomValue(props); - }, - [spec.ValueType.CONSTANT] (props: number) { - return new StaticValue(props); - }, - [spec.ValueType.CONSTANT_VEC2] (props: number) { - return new StaticValue(props); - }, - [spec.ValueType.CONSTANT_VEC3] (props: number) { - return new StaticValue(props); - }, - [spec.ValueType.CONSTANT_VEC4] (props: number) { - return new StaticValue(props); - }, - [spec.ValueType.RGBA_COLOR] (props: number) { - return new StaticValue(props); - }, - [spec.ValueType.COLORS] (props: number[][]) { - return new RandomSetValue(props.map(c => colorToArr(c, false))); - }, - [spec.ValueType.LINE] (props: number[][]) { - if (props.length === 2 && props[0][0] === 0 && props[1][0] === 1) { - return new LinearValue([props[0][1], props[1][1]]); - } - - return new LineSegments(props); - }, - [spec.ValueType.GRADIENT_COLOR] (props: number[][] | Record) { - return new GradientValue(props); - }, - [spec.ValueType.LINEAR_PATH] (pros: number[][][]) { - return new PathSegments(pros); - }, - [spec.ValueType.BEZIER_CURVE] (props: number[][][]) { - if (props.length === 1) { - return new StaticValue(props[0][1][1]); - } - - return new BezierCurve(props); - }, - [spec.ValueType.BEZIER_CURVE_PATH] (props: number[][][]) { - if (props[0].length === 1) { - return new StaticValue(new Vector3(...props[1][0])); - } - - return new BezierCurvePath(props); - }, - [spec.ValueType.BEZIER_CURVE_QUAT] (props: number[][][]) { - if (props[0].length === 1) { - return new StaticValue(new Quaternion(...props[1][0])); - } - - return new BezierCurveQuat(props); - }, -}; - -export function createValueGetter (args: any): ValueGetter { - if (!args || !isNaN(+args)) { - return new StaticValue(args || 0); - } - - if (args instanceof ValueGetter) { - return args; - } - - if (isFunction(map[args[0]])) { - return map[args[0]](args[1]); - } else { - throw new Error(`ValueType: ${args[0]} is not supported, see ${HELP_LINK['ValueType: 21/22 is not supported']}.`); - } -} - function lineSegIntegrate (t: number, t0: number, t1: number, y0: number, y1: number) { const h = t - t0; diff --git a/packages/effects-core/src/math/value-getters/vector4-curve.ts b/packages/effects-core/src/math/value-getters/vector4-curve.ts index 9be84dc5..14c497e3 100644 --- a/packages/effects-core/src/math/value-getters/vector4-curve.ts +++ b/packages/effects-core/src/math/value-getters/vector4-curve.ts @@ -1,7 +1,8 @@ import { Vector4 } from '@galacean/effects-math/es/core/vector4'; import type { BezierCurve } from './value-getter'; -import { ValueGetter, createValueGetter } from './value-getter'; -import type { BezierValue } from '@galacean/effects-specification'; +import { ValueGetter } from './value-getter'; +import type * as spec from '@galacean/effects-specification'; +import { createValueGetter } from './value-getter-map'; export class Vector4Curve extends ValueGetter { private value = new Vector4(); @@ -11,7 +12,7 @@ export class Vector4Curve extends ValueGetter { private zCurve: BezierCurve; private wCurve: BezierCurve; - override onCreate (arg: Vector4CurveData) { + override onCreate (arg: spec.Vector4CurveData) { this.xCurve = createValueGetter(arg.x) as BezierCurve; this.yCurve = createValueGetter(arg.y) as BezierCurve; this.zCurve = createValueGetter(arg.z) as BezierCurve; @@ -28,12 +29,4 @@ export class Vector4Curve extends ValueGetter { return this.value; } -} - -// TODO replace with spec def -export interface Vector4CurveData { - x: BezierValue, - y: BezierValue, - z: BezierValue, - w: BezierValue, } \ No newline at end of file diff --git a/packages/effects-core/src/plugins/cal/calculate-item.ts b/packages/effects-core/src/plugins/cal/calculate-item.ts index f9971bf0..55f4b346 100644 --- a/packages/effects-core/src/plugins/cal/calculate-item.ts +++ b/packages/effects-core/src/plugins/cal/calculate-item.ts @@ -33,6 +33,9 @@ export type ItemLinearVelOverLifetime = { @effectsClass(spec.DataType.ObjectBindingTrack) export class ObjectBindingTrack extends TrackAsset { + override updateAnimatedObject (): void { + } + create (timelineAsset: TimelineAsset): void { if (!(this.boundObject instanceof VFXItem)) { return; diff --git a/packages/effects-core/src/plugins/sprite/sprite-item.ts b/packages/effects-core/src/plugins/sprite/sprite-item.ts index 79b10942..1bff7a39 100644 --- a/packages/effects-core/src/plugins/sprite/sprite-item.ts +++ b/packages/effects-core/src/plugins/sprite/sprite-item.ts @@ -47,7 +47,7 @@ const singleSplits: splitsDataType = [[0, 0, 1, 1, undefined]]; let seed = 0; -@effectsClass('SpriteColorPlayableAsset') +@effectsClass(spec.DataType.SpriteColorPlayableAsset) export class SpriteColorPlayableAsset extends PlayableAsset { data: ColorPlayableAssetData; diff --git a/packages/effects-core/src/plugins/timeline/playable-assets/color-property-playable-asset.ts b/packages/effects-core/src/plugins/timeline/playable-assets/color-property-playable-asset.ts index 78f61401..db20d890 100644 --- a/packages/effects-core/src/plugins/timeline/playable-assets/color-property-playable-asset.ts +++ b/packages/effects-core/src/plugins/timeline/playable-assets/color-property-playable-asset.ts @@ -2,14 +2,14 @@ import { effectsClass, serialize } from '../../../decorators'; import type { Playable, PlayableGraph } from '../../cal/playable-graph'; import { PlayableAsset } from '../../cal/playable-graph'; import { PropertyClipPlayable } from '../playables'; -import type { ColorCurveData } from '../../../math'; import { createValueGetter } from '../../../math'; import type { Color } from '@galacean/effects-math/es/core'; +import * as spec from '@galacean/effects-specification'; -@effectsClass('ColorPropertyPlayableAsset') +@effectsClass(spec.DataType.ColorPropertyPlayableAsset) export class ColorPropertyPlayableAsset extends PlayableAsset { @serialize() - curveData: ColorCurveData; + curveData: spec.ColorCurveData; override createPlayable (graph: PlayableGraph): Playable { const clipPlayable = new PropertyClipPlayable(graph); diff --git a/packages/effects-core/src/plugins/timeline/playable-assets/float-property-playable-asset.ts b/packages/effects-core/src/plugins/timeline/playable-assets/float-property-playable-asset.ts index b8f9b350..409642d3 100644 --- a/packages/effects-core/src/plugins/timeline/playable-assets/float-property-playable-asset.ts +++ b/packages/effects-core/src/plugins/timeline/playable-assets/float-property-playable-asset.ts @@ -4,8 +4,9 @@ import type { Playable, PlayableGraph } from '../../cal/playable-graph'; import { PlayableAsset } from '../../cal/playable-graph'; import { PropertyClipPlayable } from '../playables'; import { createValueGetter } from '../../../math'; +import * as spec from '@galacean/effects-specification'; -@effectsClass('FloatPropertyPlayableAsset') +@effectsClass(spec.DataType.FloatPropertyPlayableAsset) export class FloatPropertyPlayableAsset extends PlayableAsset { @serialize() curveData: FixedNumberExpression; diff --git a/packages/effects-core/src/plugins/timeline/playable-assets/vector4-property-playable-asset.ts b/packages/effects-core/src/plugins/timeline/playable-assets/vector4-property-playable-asset.ts index f2adf37b..132b5159 100644 --- a/packages/effects-core/src/plugins/timeline/playable-assets/vector4-property-playable-asset.ts +++ b/packages/effects-core/src/plugins/timeline/playable-assets/vector4-property-playable-asset.ts @@ -2,14 +2,14 @@ import { effectsClass, serialize } from '../../../decorators'; import type { Playable, PlayableGraph } from '../../cal/playable-graph'; import { PlayableAsset } from '../../cal/playable-graph'; import { PropertyClipPlayable } from '../playables'; -import type { Vector4CurveData } from '../../../math'; import { createValueGetter } from '../../../math'; import type { Vector4 } from '@galacean/effects-math/es/core'; +import type * as spec from '@galacean/effects-specification'; @effectsClass('Vector4PropertyPlayableAsset') export class Vector4PropertyPlayableAsset extends PlayableAsset { @serialize() - curveData: Vector4CurveData; + curveData: spec.Vector4CurveData; override createPlayable (graph: PlayableGraph): Playable { const clipPlayable = new PropertyClipPlayable(graph); diff --git a/packages/effects-core/src/plugins/timeline/tracks/color-property-track.ts b/packages/effects-core/src/plugins/timeline/tracks/color-property-track.ts index 91bf8440..9d47728f 100644 --- a/packages/effects-core/src/plugins/timeline/tracks/color-property-track.ts +++ b/packages/effects-core/src/plugins/timeline/tracks/color-property-track.ts @@ -1,9 +1,10 @@ +import * as spec from '@galacean/effects-specification'; import { effectsClass } from '../../../decorators'; import type { PlayableGraph, Playable } from '../../cal/playable-graph'; import { FloatPropertyMixerPlayable } from '../playables'; import { PropertyTrack } from './property-track'; -@effectsClass('ColorPropertyTrack') +@effectsClass(spec.DataType.ColorPropertyTrack) export class ColorPropertyTrack extends PropertyTrack { override createTrackMixer (graph: PlayableGraph): Playable { const mixer = new FloatPropertyMixerPlayable(graph); diff --git a/packages/effects-core/src/plugins/timeline/tracks/float-property-track.ts b/packages/effects-core/src/plugins/timeline/tracks/float-property-track.ts index f0c7b6ac..03e0360e 100644 --- a/packages/effects-core/src/plugins/timeline/tracks/float-property-track.ts +++ b/packages/effects-core/src/plugins/timeline/tracks/float-property-track.ts @@ -1,9 +1,10 @@ +import * as spec from '@galacean/effects-specification'; import { effectsClass } from '../../../decorators'; import type { PlayableGraph, Playable } from '../../cal/playable-graph'; import { FloatPropertyMixerPlayable } from '../playables'; import { PropertyTrack } from './property-track'; -@effectsClass('FloatPropertyTrack') +@effectsClass(spec.DataType.FloatPropertyTrack) export class FloatPropertyTrack extends PropertyTrack { override createTrackMixer (graph: PlayableGraph): Playable { diff --git a/packages/effects-core/src/plugins/timeline/tracks/vector4-property-track.ts b/packages/effects-core/src/plugins/timeline/tracks/vector4-property-track.ts index 65f48811..ee7f3e45 100644 --- a/packages/effects-core/src/plugins/timeline/tracks/vector4-property-track.ts +++ b/packages/effects-core/src/plugins/timeline/tracks/vector4-property-track.ts @@ -1,9 +1,10 @@ +import * as spec from '@galacean/effects-specification'; import { effectsClass } from '../../../decorators'; import type { PlayableGraph, Playable } from '../../cal/playable-graph'; import { Vector4PropertyMixerPlayable } from '../playables'; import { PropertyTrack } from './property-track'; -@effectsClass('Vector4PropertyTrack') +@effectsClass(spec.DataType.Vector4PropertyTrack) export class Vector4PropertyTrack extends PropertyTrack { override createTrackMixer (graph: PlayableGraph): Playable { const mixer = new Vector4PropertyMixerPlayable(graph); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 006dda90..8262369a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -150,8 +150,8 @@ importers: specifier: 1.1.0 version: 1.1.0 '@galacean/effects-specification': - specifier: 2.1.0-alpha.0 - version: 2.1.0-alpha.0 + specifier: 2.1.0-alpha.1 + version: 2.1.0-alpha.1 flatbuffers: specifier: 24.3.25 version: 24.3.25 @@ -2208,8 +2208,8 @@ packages: /@galacean/effects-specification@2.0.0: resolution: {integrity: sha512-7ieZALZYn5fwKLIGAskmYSKGX82ZkLRdDUInG21KsOJ2eQ5+HcI6mJU5tmN8vjZxQUc+RVuUcssExGbiaj2+5w==} - /@galacean/effects-specification@2.1.0-alpha.0: - resolution: {integrity: sha512-b6yVdfzFcuzLjuNCn2wyknDBLM+l0arXwsBKP8IOhEz+av5XPaNSrOa2yUEROr0D1l9qydwfaMSL3ft6IT6FOw==} + /@galacean/effects-specification@2.1.0-alpha.1: + resolution: {integrity: sha512-kO06tQz8oCvtPVxzA8KRU6OdNENCrzFGqhJ5naUj8Xcs+5nTxWIDEzWFXbBZN8fiHeeOuuUwHCpWkatRZSjQAw==} dev: false /@humanwhocodes/config-array@0.11.14: @@ -2522,6 +2522,7 @@ packages: resolution: {integrity: sha512-bur4JOxvYxfrAmocRJIW0SADs3QdEYK6TQ7dTNz6Z4/lySeu3Z1H/+tl0a4qDYv0bCdBpUYM0sYa/X+9ZqgfSQ==} cpu: [arm64] os: [linux] + libc: [glibc] requiresBuild: true dev: true optional: true @@ -2530,6 +2531,7 @@ packages: resolution: {integrity: sha512-ssp77SjcDIUSoUyj7DU7/5iwM4ZEluY+N8umtCT9nBRs3u045t0KkW02LTyHouHDomnMXaXSZcCSr2bdMK63kA==} cpu: [arm64] os: [linux] + libc: [musl] requiresBuild: true dev: true optional: true @@ -2538,6 +2540,7 @@ packages: resolution: {integrity: sha512-Jv1DkIvwEPAb+v25/Unrnnq9BO3F5cbFPT821n3S5litkz+O5NuXuNhqtPx5KtcwOTtaqkTsO+IVzJOsxd11aQ==} cpu: [riscv64] os: [linux] + libc: [glibc] requiresBuild: true dev: true optional: true @@ -2546,6 +2549,7 @@ packages: resolution: {integrity: sha512-U564BrhEfaNChdATQaEODtquCC7Ez+8Hxz1h5MAdMYj0AqD0GA9rHCpElajb/sQcaFL6NXmHc5O+7FXpWMa73Q==} cpu: [s390x] os: [linux] + libc: [glibc] requiresBuild: true dev: true optional: true @@ -2554,6 +2558,7 @@ packages: resolution: {integrity: sha512-zGRDulLTeDemR8DFYyFIQ8kMP02xpUsX4IBikc7lwL9PrwR3gWmX2NopqiGlI2ZVWMl15qZeUjumTwpv18N7sQ==} cpu: [x64] os: [linux] + libc: [glibc] requiresBuild: true dev: true optional: true @@ -2562,6 +2567,7 @@ packages: resolution: {integrity: sha512-VTk/MveyPdMFkYJJPCkYBw07KcTkGU2hLEyqYMsU4NjiOfzoaDTW9PWGRsNwiOA3qI0k/JQPjkl/4FCK1smskQ==} cpu: [x64] os: [linux] + libc: [musl] requiresBuild: true dev: true optional: true @@ -2627,6 +2633,7 @@ packages: engines: {node: '>=10'} cpu: [arm64] os: [linux] + libc: [glibc] requiresBuild: true dev: true optional: true @@ -2636,6 +2643,7 @@ packages: engines: {node: '>=10'} cpu: [arm64] os: [linux] + libc: [musl] requiresBuild: true dev: true optional: true @@ -2645,6 +2653,7 @@ packages: engines: {node: '>=10'} cpu: [x64] os: [linux] + libc: [glibc] requiresBuild: true dev: true optional: true @@ -2654,6 +2663,7 @@ packages: engines: {node: '>=10'} cpu: [x64] os: [linux] + libc: [musl] requiresBuild: true dev: true optional: true From 8e330118fd621a04e745ee7e358110648db55795 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=A3=82=E5=85=AE?= <151803898+liuxi150@users.noreply.github.com> Date: Tue, 22 Oct 2024 10:36:41 +0800 Subject: [PATCH 48/88] refactor: clean model tree item (#687) * fix: json converter * fix: remove node transform * refactor: clear model tree item * refactor: clean * fix: support version 3.0 * fix: add model tree component * fix: update version comparison * chore: update comment --- packages/effects-core/src/plugin-system.ts | 1 - packages/effects-core/src/vfx-item.ts | 21 -- .../model/src/gltf/json-converter.ts | 10 +- plugin-packages/model/src/gltf/loader-impl.ts | 107 +----- plugin-packages/model/src/gltf/protocol.ts | 4 - plugin-packages/model/src/index.ts | 4 +- plugin-packages/model/src/plugin/index.ts | 3 +- .../model/src/plugin/model-tree-component.ts | 38 ++ .../model/src/plugin/model-tree-item.ts | 272 -------------- .../model/src/plugin/model-tree-plugin.ts | 16 - .../model/src/runtime/animation.ts | 355 ------------------ plugin-packages/model/src/runtime/mesh.ts | 13 - .../model/src/utility/plugin-helper.ts | 28 -- 13 files changed, 50 insertions(+), 822 deletions(-) create mode 100644 plugin-packages/model/src/plugin/model-tree-component.ts delete mode 100644 plugin-packages/model/src/plugin/model-tree-item.ts delete mode 100644 plugin-packages/model/src/plugin/model-tree-plugin.ts diff --git a/packages/effects-core/src/plugin-system.ts b/packages/effects-core/src/plugin-system.ts index 43ad3753..1d21cd70 100644 --- a/packages/effects-core/src/plugin-system.ts +++ b/packages/effects-core/src/plugin-system.ts @@ -131,7 +131,6 @@ export class PluginSystem { const pluginInfoMap: Record = { 'alipay-downgrade': '@galacean/effects-plugin-alipay-downgrade', 'editor-gizmo': '@galacean/effects-plugin-editor-gizmo', - 'tree': '@galacean/effects-plugin-model', 'model': '@galacean/effects-plugin-model', 'orientation-transformer': '@galacean/effects-plugin-orientation-transformer', 'spine': '@galacean/effects-plugin-spine', diff --git a/packages/effects-core/src/vfx-item.ts b/packages/effects-core/src/vfx-item.ts index 9e1b38c5..e49205cb 100644 --- a/packages/effects-core/src/vfx-item.ts +++ b/packages/effects-core/src/vfx-item.ts @@ -44,8 +44,6 @@ export class VFXItem extends EffectsObject implements Disposable { * 元素绑定的父元素, * 1. 当元素没有绑定任何父元素时,parent为空,transform.parentTransform 为 composition.transform * 2. 当元素绑定 nullItem 时,parent 为 nullItem, transform.parentTransform 为 nullItem.transform - * 3. 当元素绑定 TreeItem 的node时,parent为treeItem, transform.parentTransform 为 tree.nodes[i].transform(绑定的node节点上的transform) - * 4. 当元素绑定 TreeItem 本身时,行为表现和绑定 nullItem 相同 */ parent?: VFXItem; @@ -406,25 +404,6 @@ export class VFXItem extends EffectsObject implements Disposable { return tf; } - /** - * 获取元素内部节点的变换,目前只有场景树元素在使用 - * @param itemId 元素id信息,如果带^就返回内部节点变换,否则返回自己的变换 - * @returns 元素变换或内部节点变换 - */ - getNodeTransform (itemId: string): Transform { - for (let i = 0; i < this.components.length; i++) { - const comp = this.components[1]; - - // @ts-expect-error - if (comp.getNodeTransform) { - // @ts-expect-error - return comp.getNodeTransform(itemId); - } - } - - return this.transform; - } - /** * 设置元素在 3D 坐标轴上相对移动 */ diff --git a/plugin-packages/model/src/gltf/json-converter.ts b/plugin-packages/model/src/gltf/json-converter.ts index 324dde17..d14dc691 100644 --- a/plugin-packages/model/src/gltf/json-converter.ts +++ b/plugin-packages/model/src/gltf/json-converter.ts @@ -43,6 +43,13 @@ export class JSONConverter { const oldBinUrls = oldScene.bins ?? []; const binFiles: ArrayBuffer[] = []; + //@ts-expect-error + const v = sceneJSON.version.split('.'); + + if (Number(v[0]) >= 3) { + return oldScene; + } + if (oldScene.bins) { for (const bin of oldScene.bins) { binFiles.push(await this.loadBins(bin.url)); @@ -166,7 +173,6 @@ export class JSONConverter { this.createItemsFromTreeComponent(comp, newScene, oldScene); treeComp.options.tree.animation = undefined; treeComp.options.tree.animations = undefined; - newComponents.push(comp); } else if (comp.dataType !== spec.DataType.MeshComponent) { newComponents.push(comp); } @@ -622,7 +628,7 @@ export class JSONConverter { animationComponent.animationClips.push({ id: clipData.id }); }); } - + treeItem.components = []; treeItem.components.push({ id: animationComponent.id }); newScene.components.push(animationComponent); } diff --git a/plugin-packages/model/src/gltf/loader-impl.ts b/plugin-packages/model/src/gltf/loader-impl.ts index 5b5bc3b9..d0f5266f 100644 --- a/plugin-packages/model/src/gltf/loader-impl.ts +++ b/plugin-packages/model/src/gltf/loader-impl.ts @@ -12,17 +12,16 @@ import type { ModelAnimTrackOptions, ModelMaterialOptions, ModelSkyboxOptions, - ModelTreeOptions, ModelTextureTransform, } from '../index'; import { - Vector3, Box3, Matrix4, Euler, PSkyboxCreator, PSkyboxType, UnlitShaderGUID, PBRShaderGUID, + Vector3, Box3, Euler, PSkyboxCreator, PSkyboxType, UnlitShaderGUID, PBRShaderGUID, } from '../runtime'; import { LoaderHelper } from './loader-helper'; import { WebGLHelper } from '../utility'; import type { PImageBufferData, PSkyboxBufferParams } from '../runtime/skybox'; import type { - GLTFMesh, GLTFImage, GLTFMaterial, GLTFTexture, GLTFScene, GLTFLight, + GLTFMesh, GLTFImage, GLTFMaterial, GLTFTexture, GLTFLight, GLTFCamera, GLTFAnimation, GLTFResources, GLTFImageBasedLight, GLTFPrimitive, GLTFBufferAttribute, GLTFBounds, GLTFTextureInfo, } from '@vvfx/resource-detection'; @@ -826,52 +825,6 @@ export class LoaderImpl implements Loader { return sceneAABB; } - /** - * 按照传入的动画播放参数,计算需要播放的动画索引 - * - * @param treeOptions 节点树属性,需要初始化animations列表。 - * @returns 返回计算的动画索引,-1表示没有动画需要播放,-88888888表示播放所有动画。 - */ - getPlayAnimationIndex (treeOptions: ModelTreeOptions): number { - const animations = treeOptions.animations; - - if (animations === undefined || animations.length <= 0) { - // 硬编码,内部指定的不播放动画的索引值 - return -1; - } - - if (this.isPlayAllAnimation()) { - // 硬编码,内部指定的播放全部动画的索引值 - return -88888888; - } - - const animationInfo = this.sceneOptions.effects.playAnimation; - - if (animationInfo === undefined) { - return -1; - } - - if (typeof animationInfo === 'number') { - if (animationInfo >= 0 && animationInfo < animations.length) { - return animationInfo; - } else { - return -1; - } - } else { - // typeof animationInfo === 'string' - let animationIndex = -1; - - // 通过动画名字查找动画索引 - animations.forEach((anim, index) => { - if (anim.name === animationInfo) { - animationIndex = index; - } - }); - - return animationIndex; - } - } - isPlayAnimation (): boolean { return this.sceneOptions.effects.playAnimation !== undefined; } @@ -986,62 +939,6 @@ export class LoaderImpl implements Loader { }); } - createTreeOptions (scene: GLTFScene): ModelTreeOptions { - const nodeList = scene.nodes.map((node, nodeIndex) => { - const children = node.children.map(child => { - if (child.nodeIndex === undefined) { throw new Error(`Undefined nodeIndex for child ${child}`); } - - return child.nodeIndex; - }); - let pos: spec.vec3 | undefined; - let quat: spec.vec4 | undefined; - let scale: spec.vec3 | undefined; - - if (node.matrix !== undefined) { - if (node.matrix.length !== 16) { throw new Error(`Invalid matrix length ${node.matrix.length} for node ${node}`); } - const mat = Matrix4.fromArray(node.matrix); - const transform = mat.getTransform(); - - pos = transform.translation.toArray(); - quat = transform.rotation.toArray(); - scale = transform.scale.toArray(); - } else { - if (node.translation !== undefined) { pos = node.translation as spec.vec3; } - if (node.rotation !== undefined) { quat = node.rotation as spec.vec4; } - if (node.scale !== undefined) { scale = node.scale as spec.vec3; } - } - node.nodeIndex = nodeIndex; - const treeNode: spec.TreeNodeOptions = { - name: node.name, - transform: { - position: pos, - quat: quat, - scale: scale, - }, - children: children, - id: `${node.nodeIndex}`, - // id: index, id不指定就是index,指定后就是指定的值 - }; - - return treeNode; - }); - - const rootNodes = scene.rootNodes.map(root => { - if (root.nodeIndex === undefined) { throw new Error(`Undefined nodeIndex for root ${root}`); } - - return root.nodeIndex; - }); - - const treeOptions: ModelTreeOptions = { - nodes: nodeList, - children: rootNodes, - animation: -1, - animations: [], - }; - - return treeOptions; - } - createAnimations (animations: GLTFAnimation[]): ModelAnimationOptions[] { return animations.map(anim => { const tracks = anim.channels.map(channel => { diff --git a/plugin-packages/model/src/gltf/protocol.ts b/plugin-packages/model/src/gltf/protocol.ts index 1880a58d..e2881a14 100644 --- a/plugin-packages/model/src/gltf/protocol.ts +++ b/plugin-packages/model/src/gltf/protocol.ts @@ -3,7 +3,6 @@ import type { GLTFMaterial, GLTFPrimitive, GLTFLight, - GLTFScene, GLTFImage, GLTFTexture, GLTFCamera, @@ -19,7 +18,6 @@ import type { ModelAnimationOptions, ModelMaterialOptions, ModelSkyboxOptions, - ModelTreeOptions, ModelLightComponentData, ModelCameraComponentData, ModelSkyboxComponentData, } from '../index'; @@ -156,8 +154,6 @@ export interface Loader { processMaterial (materials: GLTFMaterial[], fromGLTF: boolean): void, - createTreeOptions (scene: GLTFScene): ModelTreeOptions, - createAnimations (animations: GLTFAnimation[]): ModelAnimationOptions[], createGeometry (primitive: GLTFPrimitive, hasSkinAnim: boolean): Geometry, diff --git a/plugin-packages/model/src/index.ts b/plugin-packages/model/src/index.ts index 1ec7bc35..c53ea24d 100644 --- a/plugin-packages/model/src/index.ts +++ b/plugin-packages/model/src/index.ts @@ -1,9 +1,8 @@ import * as EFFECTS from '@galacean/effects'; import type { spec } from '@galacean/effects'; import { VFXItem, logger, registerPlugin } from '@galacean/effects'; -import { ModelPlugin, ModelTreePlugin } from './plugin'; +import { ModelPlugin } from './plugin'; -registerPlugin('tree', ModelTreePlugin, VFXItem, true); registerPlugin('model', ModelPlugin, VFXItem); export const version = __VERSION__; @@ -28,7 +27,6 @@ export type ModelLightOptions = spec.ModelLightOptions; export type ModelItemMesh = spec.ModelMeshItem<'studio'>; export type ModelItemSkybox = spec.ModelSkyboxItem<'studio'>; -export type ModelItemTree = spec.ModelTreeItem<'studio'>; export type ModelMeshContent = spec.ModelMeshItemContent<'studio'>; export type ModelSkyboxContent = spec.SkyboxContent<'studio'>; export type ModelMeshOptions = spec.ModelMeshOptions<'studio'>; diff --git a/plugin-packages/model/src/plugin/index.ts b/plugin-packages/model/src/plugin/index.ts index 708c9ee4..88fa1dc3 100644 --- a/plugin-packages/model/src/plugin/index.ts +++ b/plugin-packages/model/src/plugin/index.ts @@ -1,5 +1,4 @@ export * from './const'; export * from './model-plugin'; export * from './model-item'; -export * from './model-tree-item'; -export * from './model-tree-plugin'; +export * from './model-tree-component'; diff --git a/plugin-packages/model/src/plugin/model-tree-component.ts b/plugin-packages/model/src/plugin/model-tree-component.ts new file mode 100644 index 00000000..96ace358 --- /dev/null +++ b/plugin-packages/model/src/plugin/model-tree-component.ts @@ -0,0 +1,38 @@ +import type { Engine } from '@galacean/effects'; +import { Behaviour, effectsClass, spec } from '@galacean/effects'; +import type { ModelTreeContent } from '../index'; + +/** + * 插件场景树组件类,实现 3D 场景树功能 + * + * FIXME: 有些发布的新JSON包含TreeComponent,这里做兼容处理,否则会报错 + * @since 2.1.0 + */ +@effectsClass(spec.DataType.TreeComponent) +export class ModelTreeComponent extends Behaviour { + /** + * 参数 + */ + options?: ModelTreeContent; + + /** + * 构造函数,创建节点树元素 + * @param engine + * @param options + */ + constructor (engine: Engine, options?: ModelTreeContent) { + super(engine); + if (options) { + this.fromData(options); + } + } + + /** + * 反序列化,保存入参和创建节点树元素 + * @param options + */ + override fromData (options: ModelTreeContent): void { + super.fromData(options); + this.options = options; + } +} diff --git a/plugin-packages/model/src/plugin/model-tree-item.ts b/plugin-packages/model/src/plugin/model-tree-item.ts deleted file mode 100644 index 46ae5e5f..00000000 --- a/plugin-packages/model/src/plugin/model-tree-item.ts +++ /dev/null @@ -1,272 +0,0 @@ -import type { Engine, VFXItem } from '@galacean/effects'; -import { Behaviour, Transform, effectsClass, spec } from '@galacean/effects'; -import type { ModelTreeContent, ModelTreeOptions } from '../index'; -import { PAnimationManager } from '../runtime'; -import { getSceneManager } from './model-plugin'; - -/** - * 场景树节点描述 - */ -export interface ModelTreeNode { - /** - * 名称 - */ - name?: string, - /** - * 变换 - */ - transform: Transform, - /** - * 子节点 - */ - children: ModelTreeNode[], - /** - * 索引 - */ - id: string, - /** - * 场景树元素 - */ - tree: ModelTreeItem, -} - -/** - * 场景树元素类,支持插件中节点树相关的动画能力 - */ -export class ModelTreeItem { - private allNodes: ModelTreeNode[]; - private nodes: ModelTreeNode[]; - private cacheMap: Record; - /** - * 基础变换 - */ - readonly baseTransform: Transform; - /** - * 动画管理器 - */ - animationManager: PAnimationManager; - - /** - * 构造函数,创建场景树结构 - * @param props - 场景树数据 - * @param owner - 场景树元素 - */ - constructor (props: ModelTreeOptions, owner: VFXItem) { - this.baseTransform = owner.transform; - this.animationManager = new PAnimationManager(props, owner); - this.build(props); - } - - /** - * 场景树更新,主要是动画更新 - * @param dt - 时间间隔 - */ - tick (dt: number) { - this.animationManager.tick(dt); - } - - /** - * 获取所有节点 - * @returns - */ - getNodes () { - return this.nodes; - } - - /** - * 根据节点编号,查询节点 - * @param nodeId - 节点编号 - * @returns - */ - getNodeById (nodeId: string | number): ModelTreeNode | undefined { - const cache = this.cacheMap; - - if (!cache[nodeId]) { - const index = `^${nodeId}`; - - // @ts-expect-error - cache[nodeId] = this.allNodes.find(node => node.id === index); - } - - return cache[nodeId]; - } - - /** - * 根据节点名称,查询节点 - * @param name - 名称 - * @returns - */ - getNodeByName (name: string): ModelTreeNode | undefined { - const cache = this.cacheMap; - - if (!cache[name]) { - // @ts-expect-error - cache[name] = this.allNodes.find(node => node.name === name); - } - - return cache[name]; - } - - /** - * 根据节点 id 查询节点变换,如果查询不到节点就直接返回基础变换 - * @param nodeId - 节点 id - * @returns - */ - getNodeTransform (nodeId: string): Transform { - const node = this.getNodeById(nodeId); - - return node ? node.transform : this.baseTransform; - } - - /** - * 销毁场景树对象 - */ - dispose () { - this.allNodes = []; - this.nodes = []; - this.cacheMap = {}; - // @ts-expect-error - this.baseTransform = null; - this.animationManager?.dispose(); - // @ts-expect-error - this.animationManager = null; - } - - private build (options: ModelTreeOptions) { - const topTransform = this.baseTransform; - const nodes: ModelTreeNode[] = options.nodes.map((node, i) => ({ - name: node.name || node.id || (i + ''), - transform: new Transform({ - ...node.transform, - valid: true, - }, topTransform), - id: `^${node.id || i}`, - children: [], - tree: this, - })); - - this.cacheMap = {}; - nodes.forEach((node, i) => { - const children = options.nodes[i].children; - - // @ts-expect-error - node.transform.name = node.name; - node.transform.setValid(true); - if (children) { - children.forEach(function (index) { - const child = nodes[index]; - - if (child && child !== node) { - if (child.transform.parentTransform !== topTransform) { - console.error('Node parent has been set.'); - } - child.transform.parentTransform = node.transform; - node.children.push(child); - } - }); - } - }); - this.allNodes = nodes; - this.nodes = options.children.map(i => nodes[i]); - } -} - -/** - * 插件场景树组件类,实现 3D 场景树功能 - * @since 2.0.0 - */ -@effectsClass(spec.DataType.TreeComponent) -export class ModelTreeComponent extends Behaviour { - /** - * 内部节点树元素 - */ - content: ModelTreeItem; - /** - * 参数 - */ - options?: ModelTreeContent; - - /** - * 构造函数,创建节点树元素 - * @param engine - * @param options - */ - constructor (engine: Engine, options?: ModelTreeContent) { - super(engine); - if (options) { - this.fromData(options); - } - } - - /** - * 反序列化,保存入参和创建节点树元素 - * @param options - */ - override fromData (options: ModelTreeContent): void { - super.fromData(options); - this.options = options; - this.createContent(); - } - - /** - * 组件开始,查询合成中场景管理器并设置到动画管理器中 - */ - override onStart () { - this.item.type = spec.ItemType.tree; - this.content.baseTransform.setValid(true); - const sceneManager = getSceneManager(this); - - if (sceneManager) { - this.content.animationManager.setSceneManager(sceneManager); - } - } - - /** - * 组件更新,内部对象更新 - * @param dt - */ - override onUpdate (dt: number): void { - // this.timeline?.getRenderData(time, true); - // TODO: 需要使用lifetime - this.content?.tick(dt); - } - - /** - * 组件销毁,内部对象销毁 - */ - override onDestroy (): void { - this.content?.dispose(); - } - - /** - * 创建内部场景树元素 - */ - createContent () { - if (this.options) { - const treeOptions = this.options.options.tree; - - this.content = new ModelTreeItem(treeOptions, this.item); - } - } - - /** - * 获取元素的变换 - * @param itemId - 元素索引 - * @returns - */ - getNodeTransform (itemId: string): Transform { - if (this.content === undefined) { - return this.transform; - } - - const idWithSubfix = this.item.id + '^'; - - if (itemId.indexOf(idWithSubfix) === 0) { - const nodeId = itemId.substring(idWithSubfix.length); - - return this.content.getNodeTransform(nodeId); - } else { - return this.transform; - } - } -} diff --git a/plugin-packages/model/src/plugin/model-tree-plugin.ts b/plugin-packages/model/src/plugin/model-tree-plugin.ts deleted file mode 100644 index 0c494b3a..00000000 --- a/plugin-packages/model/src/plugin/model-tree-plugin.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { AbstractPlugin } from '@galacean/effects'; - -/** - * 场景树插件类,支持 3D 相关的节点动画和骨骼动画等 - */ -export class ModelTreePlugin extends AbstractPlugin { - /** - * 插件名称 - */ - override name = 'tree'; - /** - * 高优先级更新 - */ - override order = 2; -} - diff --git a/plugin-packages/model/src/runtime/animation.ts b/plugin-packages/model/src/runtime/animation.ts index d81246b8..8cf7e4aa 100644 --- a/plugin-packages/model/src/runtime/animation.ts +++ b/plugin-packages/model/src/runtime/animation.ts @@ -1,18 +1,9 @@ import type { Geometry, Engine, VFXItem, SkinProps } from '@galacean/effects'; import { glContext, Texture, TextureSourceType } from '@galacean/effects'; -import type { - ModelAnimTrackOptions, - ModelAnimationOptions, - ModelTreeOptions, -} from '../index'; import { Matrix4 } from './math'; import { PObjectType } from './common'; import { PObject } from './object'; -import type { InterpolationSampler } from './anim-sampler'; -import { createAnimationSampler } from './anim-sampler'; import { Float16ArrayWrapper } from '../utility/plugin-helper'; -import type { PSceneManager } from './scene'; -import { ModelTreeComponent } from '../plugin'; const forceTextureSkinning = false; @@ -473,193 +464,6 @@ export enum PAnimPathType { weights, } -/** - * 动画轨道类 - */ -export class PAnimTrack { - /** - * 节点索引 - */ - node: number; - /** - * 时间数组 - */ - timeArray: Float32Array; - /** - * 数据数组 - */ - dataArray: Float32Array; - /** - * 路径类型 - */ - path = PAnimPathType.translation; - /** - * 插值类型 - */ - interp = PAnimInterpType.linear; - /** - * 分量 - */ - component: number; - // - private sampler?: InterpolationSampler; - - /** - * 创建动画轨道对象 - * @param options - 动画轨道参数 - */ - constructor (options: ModelAnimTrackOptions) { - const { node, input, output, path, interpolation } = options; - - this.node = node; - this.timeArray = input; - this.dataArray = output; - // - if (path === 'translation') { - this.path = PAnimPathType.translation; - this.component = 3; - } else if (path === 'rotation') { - this.path = PAnimPathType.rotation; - this.component = 4; - } else if (path === 'scale') { - this.path = PAnimPathType.scale; - this.component = 3; - } else if (path === 'weights') { - this.path = PAnimPathType.weights; - this.component = this.dataArray.length / this.timeArray.length; - // special checker for weights animation - if (this.component <= 0) { - console.error(`Invalid weights component: ${this.timeArray.length}, ${this.component}, ${this.dataArray.length}.`); - } else if (this.timeArray.length * this.component != this.dataArray.length) { - console.error(`Invalid weights array length: ${this.timeArray.length}, ${this.component}, ${this.dataArray.length}.`); - } - } else { - // should never happened - console.error(`Invalid path status: ${path}.`); - } - - if (this.timeArray.length * this.component > this.dataArray.length) { - throw new Error(`Data length mismatch: ${this.timeArray.length}, ${this.component}, ${this.dataArray.length}.`); - } - - if (interpolation === 'LINEAR') { - this.interp = PAnimInterpType.linear; - } else if (interpolation === 'STEP') { - this.interp = PAnimInterpType.step; - } else { - this.interp = PAnimInterpType.cubicSpline; - } - - this.sampler = createAnimationSampler( - this.getInterpInfo(), this.timeArray, this.dataArray, this.component, this.getPathInfo() - ); - } - - /** - * 销毁 - */ - dispose () { - // @ts-expect-error - this.timeArray = undefined; - // @ts-expect-error - this.dataArray = undefined; - this.sampler?.dispose(); - this.sampler = undefined; - } - - /** - * 更新节点动画数据 - * @param time - 当前播放时间 - * @param treeItem - 节点树元素 - * @param sceneManager - 3D 场景管理器 - */ - tick (time: number, treeItem: VFXItem, sceneManager?: PSceneManager) { - const treeComponent = treeItem.getComponent(ModelTreeComponent); - const node = treeComponent?.content?.getNodeById(this.node); - - if (this.sampler !== undefined && node !== undefined) { - const result = this.sampler.evaluate(time); - - switch (this.path) { - case PAnimPathType.translation: - node.transform.setPosition(result[0], result[1], result[2]); - - break; - case PAnimPathType.rotation: - node.transform.setQuaternion(result[0], result[1], result[2], result[3]); - - break; - case PAnimPathType.scale: - node.transform.setScale(result[0], result[1], result[2]); - - break; - case PAnimPathType.weights: - { - /** - * 先生成Mesh的父节点id,然后通过id查询Mesh对象 - * 最后更新Mesh对象权重数据 - */ - const parentId = this.genParentId(treeItem.id, this.node); - const mesh = sceneManager?.queryMesh(parentId); - - if (mesh !== undefined) { - mesh.updateMorphWeights(result); - } - - } - - break; - } - } else { - if (this.sampler !== undefined) { - console.error('AnimTrack: error', this.sampler, node); - } - } - } - - /** - * 获取动画结束时间 - * @returns - */ - getEndTime (): number { - const index = this.timeArray.length - 1; - - return this.timeArray[index]; - } - - /** - * 生成 Mesh 元素的父节点 - * - * @param parentId - 父节点 id 名称 - * @param nodeIndex - Mesh 节点索引 - * - * @returns 生成的 Mesh 节点名称 - */ - private genParentId (parentId: string, nodeIndex: number): string { - return `${parentId}^${nodeIndex}`; - } - - private getPathInfo (): string { - if (this.path === PAnimPathType.scale) { - return 'scale'; - } else if (this.path === PAnimPathType.rotation) { - return 'rotation'; - } else { - return 'translation'; - } - } - - private getInterpInfo (): string { - if (this.interp === PAnimInterpType.cubicSpline) { - return 'CUBICSPLINE'; - } else if (this.interp === PAnimInterpType.step) { - return 'STEP'; - } else { - return 'LINEAR'; - } - } -} - /** * 动画纹理类 */ @@ -760,162 +564,3 @@ export class PAnimTexture { } } - -/** - * 动画类,负责动画数据创建、更新和销毁 - */ -export class PAnimation extends PObject { - private time = 0; - private duration = 0; - private tracks: PAnimTrack[] = []; - - /** - * 创建动画对象 - * @param options - 动画参数 - */ - create (options: ModelAnimationOptions) { - this.name = this.genName(options.name ?? 'Unnamed animation'); - this.type = PObjectType.animation; - // - this.time = 0; - this.duration = 0; - // - this.tracks = []; - options.tracks.forEach(inTrack => { - const track = new PAnimTrack(inTrack); - - this.tracks.push(track); - this.duration = Math.max(this.duration, track.getEndTime()); - }); - } - - /** - * 动画更新 - * @param time - 当前时间 - * @param treeItem - 场景树元素 - * @param sceneManager - 3D 场景管理器 - */ - tick (time: number, treeItem: VFXItem, sceneManager?: PSceneManager) { - this.time = time; - // TODO: 这里时间事件定义不明确,先兼容原先实现 - const newTime = this.time % this.duration; - - this.tracks.forEach(track => { - track.tick(newTime, treeItem, sceneManager); - }); - } - - /** - * 销毁 - */ - override dispose () { - this.tracks.forEach(track => { - track.dispose(); - }); - this.tracks = []; - } -} - -/** - * 动画管理类,负责管理动画对象 - */ -export class PAnimationManager extends PObject { - private ownerItem: VFXItem; - private animation = 0; - private speed = 0; - private delay = 0; - private time = 0; - private animations: PAnimation[] = []; - private sceneManager?: PSceneManager; - - /** - * 创建动画管理器 - * @param treeOptions - 场景树参数 - * @param ownerItem - 场景树所属元素 - */ - constructor (treeOptions: ModelTreeOptions, ownerItem: VFXItem) { - super(); - this.name = this.genName(ownerItem.name ?? 'Unnamed tree'); - this.type = PObjectType.animationManager; - // - this.ownerItem = ownerItem; - this.animation = treeOptions.animation ?? -1; - this.speed = 1.0; - this.delay = ownerItem.start ?? 0; - this.animations = []; - if (treeOptions.animations !== undefined) { - treeOptions.animations.forEach(animOpts => { - const anim = this.createAnimation(animOpts); - - this.animations.push(anim); - }); - } - } - - /** - * 设置场景管理器 - * @param sceneManager - 场景管理器 - */ - setSceneManager (sceneManager: PSceneManager) { - this.sceneManager = sceneManager; - } - - /** - * 创建动画对象 - * @param animationOpts - 动画参数 - * @returns 动画对象 - */ - createAnimation (animationOpts: ModelAnimationOptions) { - const animation = new PAnimation(); - - animation.create(animationOpts); - - return animation; - } - - /** - * 动画更新 - * @param deltaSeconds - 更新间隔 - */ - tick (deltaSeconds: number) { - const newDeltaSeconds = deltaSeconds * this.speed * 0.001; - - this.time += newDeltaSeconds; - // TODO: 需要合并到TreeItem中,通过lifetime进行计算 - const itemTime = this.time - this.delay; - - if (itemTime >= 0) { - if (this.animation >= 0 && this.animation < this.animations.length) { - const anim = this.animations[this.animation]; - - anim.tick(itemTime, this.ownerItem, this.sceneManager); - } else if (this.animation == -88888888) { - this.animations.forEach(anim => { - anim.tick(itemTime, this.ownerItem, this.sceneManager); - }); - } - } - } - - /** - * 销毁 - */ - override dispose () { - // @ts-expect-error - this.ownerItem = null; - this.animations.forEach(anim => { - anim.dispose(); - }); - this.animations = []; - // @ts-expect-error - this.sceneManager = null; - } - - /** - * 获取场景树元素 - * @returns - */ - getTreeItem () { - return this.ownerItem; - } -} diff --git a/plugin-packages/model/src/runtime/mesh.ts b/plugin-packages/model/src/runtime/mesh.ts index 59ddb0dc..5e1548b5 100644 --- a/plugin-packages/model/src/runtime/mesh.ts +++ b/plugin-packages/model/src/runtime/mesh.ts @@ -12,8 +12,6 @@ import type { PSkybox } from './skybox'; import { GeometryBoxProxy, HitTestingProxy } from '../utility/plugin-helper'; import { BoxMesh } from '../utility/ri-helper'; import { RayBoxTesting } from '../utility/hit-test-helper'; -import type { ModelTreeNode } from '../plugin'; -import { ModelTreeComponent } from '../plugin'; import type { ModelMeshComponent } from '../plugin/model-item'; type Box3 = math.Box3; @@ -1197,17 +1195,6 @@ class EffectsMeshProxy { return this.data.hide === true; } - getParentNode (): ModelTreeNode | undefined { - const nodeIndex = this.getParentIndex(); - const parentTree = this.parentItem?.getComponent(ModelTreeComponent); - - if (parentTree !== undefined && nodeIndex >= 0) { - return parentTree.content.getNodeById(nodeIndex); - } - - return undefined; - } - getParentIndex (): number { return -1; } diff --git a/plugin-packages/model/src/utility/plugin-helper.ts b/plugin-packages/model/src/utility/plugin-helper.ts index e2e5cd06..1f332899 100644 --- a/plugin-packages/model/src/utility/plugin-helper.ts +++ b/plugin-packages/model/src/utility/plugin-helper.ts @@ -982,34 +982,6 @@ export class PluginHelper { console.error(`setupItem3DOptions: Invalid inverseBindMatrices type, ${inverseBindMatrices}.`); } } - } else if (item.type === spec.ItemType.tree) { - const jsonItem = item as spec.ModelTreeItem<'json'>; - const studioItem = item as spec.ModelTreeItem<'studio'>; - const jsonAnimations = jsonItem.content.options.tree.animations; - const studioAnimations = studioItem.content.options.tree.animations; - - if (jsonAnimations !== undefined && studioAnimations !== undefined) { - jsonAnimations.forEach((jsonAnim, i) => { - const studioAnim = studioAnimations[i]; - - jsonAnim.tracks.forEach((jsonTrack, j) => { - const inputArray = typedArrayFromBinary(scene.bins, jsonTrack.input); - const outputArray = typedArrayFromBinary(scene.bins, jsonTrack.output); - const studioTrack = studioAnim.tracks[j]; - - if (inputArray instanceof Float32Array) { - studioTrack.input = inputArray; - } else { - console.error(`setupItem3DOptions: Type of inputArray should be float32, ${inputArray}.`); - } - if (outputArray instanceof Float32Array) { - studioTrack.output = outputArray; - } else { - console.error(`setupItem3DOptions: Type of outputArray should be float32, ${outputArray}.`); - } - }); - }); - } } else if (item.type === spec.ItemType.skybox) { const skybox = item as spec.ModelSkyboxItem<'json'>; const studioSkybox = item as spec.ModelSkyboxItem<'studio'>; From de821adbe5bea5ed1354743a4ccbef394abdd83a Mon Sep 17 00:00:00 2001 From: yiiqii Date: Tue, 22 Oct 2024 12:58:57 +0800 Subject: [PATCH 49/88] fix(demo): post processing gui create issue --- web-packages/demo/src/assets/post-processing-list.ts | 2 +- web-packages/demo/src/post-processing.ts | 11 +++++++---- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/web-packages/demo/src/assets/post-processing-list.ts b/web-packages/demo/src/assets/post-processing-list.ts index eebe83a1..0abc40d3 100644 --- a/web-packages/demo/src/assets/post-processing-list.ts +++ b/web-packages/demo/src/assets/post-processing-list.ts @@ -12,7 +12,7 @@ export default { name: 'Robin', }, bloomTest: { - url: 'https://mdn.alipayobjects.com/mars/afts/file/A*6j_ZQan_MhMAAAAAAAAAAAAADlB4AQ', + url: 'https://mdn.alipayobjects.com/mars/afts/file/A*y3eeR41N5dgAAAAAAAAAAAAADlB4AQ', name: 'BloomTest', }, }; diff --git a/web-packages/demo/src/post-processing.ts b/web-packages/demo/src/post-processing.ts index 2b5a54c5..99f34aed 100644 --- a/web-packages/demo/src/post-processing.ts +++ b/web-packages/demo/src/post-processing.ts @@ -5,14 +5,14 @@ import postProcessingList from './assets/post-processing-list'; // DATUI 参数面板 const postProcessSettings = { // Particle - color: [1, 0.5, 0], + color: [0, 0, 0], intensity: 1.0, }; const container = document.getElementById('J-container'); const resumeBtn = document.getElementById('J-resume'); -// const url = postProcessingList['robin'].url; -const url = 'https://mdn.alipayobjects.com/mars/afts/file/A*PubBSpHUbjYAAAAAAAAAAAAADlB4AQ'; +const url = postProcessingList['bloomTest'].url; let player: Player; +let gui: any; initSelectList(); setConfig(POST_PROCESS_SETTINGS, postProcessSettings); @@ -62,8 +62,11 @@ function initSelectList () { // dat gui 参数及修改 function setDatGUI (composition: Composition) { + if (gui) { + gui.destroy(); + } // @ts-expect-error - const gui = new window.GUI(); + gui = new window.GUI(); const ParticleFolder = gui.addFolder('Particle'); const BloomFolder = gui.addFolder('Bloom'); const ToneMappingFlolder = gui.addFolder('ToneMapping'); From ee521b08809f9b6af188e3684857e5ddacf123ba Mon Sep 17 00:00:00 2001 From: SuRuiMeng <934274351@qq.com> Date: Tue, 22 Oct 2024 14:24:12 +0800 Subject: [PATCH 50/88] feat: add support for shape property in item renderer --- .../src/components/base-render-component.ts | 27 +++++++++++++++++-- .../src/plugins/sprite/sprite-item.ts | 12 ++------- .../multimedia/src/video/video-component.ts | 3 +++ 3 files changed, 30 insertions(+), 12 deletions(-) diff --git a/packages/effects-core/src/components/base-render-component.ts b/packages/effects-core/src/components/base-render-component.ts index 53aecbce..b4049376 100644 --- a/packages/effects-core/src/components/base-render-component.ts +++ b/packages/effects-core/src/components/base-render-component.ts @@ -14,6 +14,7 @@ import { HitTestType, maxSpriteMeshItemCount, spriteMeshShaderFromRenderInfo } f import type { MaterialProps } from '../material'; import { getPreMultiAlpha, Material, setBlendMode, setMaskMode, setSideMode } from '../material'; import { trianglesFromRect } from '../math'; +import type { GeometryFromShape } from '../shape'; /** * 图层元素渲染属性, 经过处理后的 spec.SpriteContent.renderer @@ -22,6 +23,7 @@ export interface ItemRenderer extends Required, } @@ -86,6 +88,7 @@ export class VideoComponent extends BaseRenderComponent { mask: renderer.mask ?? 0, maskMode: renderer.maskMode ?? spec.MaskMode.NONE, order: listIndex, + shape: renderer.shape, }; this.interaction = interaction; From 905d54ba3fe0e74cf2f44aa6a16906e8d9727918 Mon Sep 17 00:00:00 2001 From: "wumaolin.wml" Date: Wed, 23 Oct 2024 16:18:42 +0800 Subject: [PATCH 51/88] feat: add shape component hit test --- .../src/components/effect-component.ts | 126 +----------------- .../src/components/mesh-component.ts | 46 +++++++ .../src/components/shape-component.ts | 21 +-- packages/effects-core/src/plugins/index.ts | 1 + .../src/plugins/interact/mesh-collider.ts | 60 +++++++++ 5 files changed, 119 insertions(+), 135 deletions(-) create mode 100644 packages/effects-core/src/components/mesh-component.ts create mode 100644 packages/effects-core/src/plugins/interact/mesh-collider.ts diff --git a/packages/effects-core/src/components/effect-component.ts b/packages/effects-core/src/components/effect-component.ts index 019ea13a..692af938 100644 --- a/packages/effects-core/src/components/effect-component.ts +++ b/packages/effects-core/src/components/effect-component.ts @@ -1,42 +1,18 @@ -import * as spec from '@galacean/effects-specification'; -import { Matrix4 } from '@galacean/effects-math/es/core/matrix4'; -import type { TriangleLike } from '@galacean/effects-math/es/core/type'; -import { Vector3 } from '@galacean/effects-math/es/core/vector3'; import { Vector4 } from '@galacean/effects-math/es/core/vector4'; -import { effectsClass, serialize } from '../decorators'; +import * as spec from '@galacean/effects-specification'; +import { effectsClass } from '../decorators'; import type { Engine } from '../engine'; -import type { Material, MaterialDestroyOptions } from '../material'; -import type { BoundingBoxTriangle, HitTestTriangleParams } from '../plugins'; -import { HitTestType } from '../plugins'; -import type { MeshDestroyOptions, Renderer } from '../render'; -import type { Geometry } from '../render'; -import { DestroyOptions } from '../utils'; -import { RendererComponent } from './renderer-component'; +import { MeshComponent } from './mesh-component'; /** * @since 2.0.0 */ @effectsClass(spec.DataType.EffectComponent) -export class EffectComponent extends RendererComponent { - /** - * Mesh 的世界矩阵 - */ - worldMatrix = Matrix4.fromIdentity(); - /** - * Mesh 的 Geometry - */ - @serialize() - geometry: Geometry; - - private triangles: TriangleLike[] = []; - private destroyed = false; - // TODO: 抽象到射线碰撞检测组件 - private hitTestGeometry: Geometry; +export class EffectComponent extends MeshComponent { constructor (engine: Engine) { super(engine); this.name = 'EffectComponent'; - this._priority = 0; } override onStart (): void { @@ -50,100 +26,8 @@ export class EffectComponent extends RendererComponent { this.material.setVector4('_Time', _Time.set(time / 20, time, time * 2, time * 3)); } - override render (renderer: Renderer) { - if (renderer.renderingData.currentFrame.globalUniforms) { - renderer.setGlobalMatrix('effects_ObjectToWorld', this.transform.getWorldMatrix()); - } - renderer.drawGeometry(this.geometry, this.material); - } - - /** - * 设置当前 Mesh 的材质 - * @param material - 要设置的材质 - * @param destroy - 可选的材质销毁选项 - */ - setMaterial (material: Material, destroy?: MaterialDestroyOptions | DestroyOptions.keep) { - if (destroy !== DestroyOptions.keep) { - this.material.dispose(destroy); - } - this.material = material; - } - - // TODO 点击测试后续抽象一个 Collider 组件 - getHitTestParams = (force?: boolean): HitTestTriangleParams | void => { - const area = this.getBoundingBox(); - - if (area) { - return { - type: area.type, - triangles: area.area, - }; - } - }; - - getBoundingBox (): BoundingBoxTriangle | void { - const worldMatrix = this.transform.getWorldMatrix(); - - if (this.hitTestGeometry !== this.geometry) { - this.triangles = geometryToTriangles(this.geometry); - this.hitTestGeometry = this.geometry; - } - const area = []; - - for (const triangle of this.triangles) { - area.push({ p0: triangle.p0, p1: triangle.p1, p2: triangle.p2 }); - } - - area.forEach(triangle => { - triangle.p0 = worldMatrix.transformPoint(triangle.p0 as Vector3, new Vector3()); - triangle.p1 = worldMatrix.transformPoint(triangle.p1 as Vector3, new Vector3()); - triangle.p2 = worldMatrix.transformPoint(triangle.p2 as Vector3, new Vector3()); - }); - - return { - type: HitTestType.triangle, - area, - }; - } - override fromData (data: unknown): void { super.fromData(data); this.material = this.materials[0]; } - - override toData (): void { - this.taggedProperties.id = this.guid; - } - - /** - * 销毁当前资源 - * @param options - 可选的销毁选项 - */ - override dispose (options?: MeshDestroyOptions) { - if (this.destroyed) { - return; - } - this.destroyed = true; - - super.dispose(); - } -} - -function geometryToTriangles (geometry: Geometry) { - const indices = geometry.getIndexData() ?? []; - const vertices = geometry.getAttributeData('aPos') ?? []; - const res: TriangleLike[] = []; - - for (let i = 0; i < indices.length; i += 3) { - const index0 = indices[i] * 3; - const index1 = indices[i + 1] * 3; - const index2 = indices[i + 2] * 3; - const p0 = { x: vertices[index0], y: vertices[index0 + 1], z: vertices[index0 + 2] }; - const p1 = { x: vertices[index1], y: vertices[index1 + 1], z: vertices[index1 + 2] }; - const p2 = { x: vertices[index2], y: vertices[index2 + 1], z: vertices[index2 + 2] }; - - res.push({ p0, p1, p2 }); - } - - return res; -} +} \ No newline at end of file diff --git a/packages/effects-core/src/components/mesh-component.ts b/packages/effects-core/src/components/mesh-component.ts new file mode 100644 index 00000000..9191e9ce --- /dev/null +++ b/packages/effects-core/src/components/mesh-component.ts @@ -0,0 +1,46 @@ +import { serialize } from '../decorators'; +import type { BoundingBoxTriangle, HitTestTriangleParams } from '../plugins'; +import { MeshCollider } from '../plugins'; +import type { Geometry } from '../render/geometry'; +import type { Renderer } from '../render/renderer'; +import { RendererComponent } from './renderer-component'; + +export class MeshComponent extends RendererComponent { + /** + * 渲染的 Geometry + */ + @serialize() + protected geometry: Geometry; + /** + * 用于点击测试的碰撞器 + */ + protected meshCollider = new MeshCollider(); + + override render (renderer: Renderer) { + if (renderer.renderingData.currentFrame.globalUniforms) { + renderer.setGlobalMatrix('effects_ObjectToWorld', this.transform.getWorldMatrix()); + } + renderer.drawGeometry(this.geometry, this.material); + } + + // TODO 点击测试后续抽象一个 Collider 组件 + getHitTestParams = (force?: boolean): HitTestTriangleParams | void => { + const area = this.getBoundingBox(); + + if (area) { + return { + type: area.type, + triangles: area.area, + }; + } + }; + + getBoundingBox (): BoundingBoxTriangle | void { + const worldMatrix = this.transform.getWorldMatrix(); + + this.meshCollider.setGeometry(this.geometry, worldMatrix); + const boundingBoxData = this.meshCollider.getBoundingBoxData(); + + return boundingBoxData; + } +} \ No newline at end of file diff --git a/packages/effects-core/src/components/shape-component.ts b/packages/effects-core/src/components/shape-component.ts index 602ed22b..e957e363 100644 --- a/packages/effects-core/src/components/shape-component.ts +++ b/packages/effects-core/src/components/shape-component.ts @@ -7,9 +7,8 @@ import type { MaterialProps } from '../material'; import { Material } from '../material'; import { GraphicsPath } from '../plugins/shape/graphics-path'; import type { ShapePath } from '../plugins/shape/shape-path'; -import type { Renderer } from '../render'; import { Geometry, GLSLVersion } from '../render'; -import { RendererComponent } from './renderer-component'; +import { MeshComponent } from './mesh-component'; interface CurveData { point: spec.Vector3Data, @@ -22,13 +21,12 @@ interface CurveData { * @since 2.1.0 */ @effectsClass('ShapeComponent') -export class ShapeComponent extends RendererComponent { +export class ShapeComponent extends MeshComponent { private path = new GraphicsPath(); private curveValues: CurveData[] = []; - private geometry: Geometry; private data: ShapeComponentData; - private animated = false; + private animated = true; private vert = ` precision highp float; @@ -100,6 +98,10 @@ void main() { } } + override onStart (): void { + this.item.getHitTestParams = this.getHitTestParams; + } + override onUpdate (dt: number): void { if (this.animated) { this.buildPath(this.data); @@ -107,13 +109,6 @@ void main() { } } - override render (renderer: Renderer): void { - if (renderer.renderingData.currentFrame.globalUniforms) { - renderer.setGlobalMatrix('effects_ObjectToWorld', this.transform.getWorldMatrix()); - } - renderer.drawGeometry(this.geometry, this.material); - } - private buildGeometryFromPath (shapePath: ShapePath) { const shapePrimitives = shapePath.shapePrimitives; const vertices: number[] = []; @@ -231,8 +226,6 @@ void main() { override fromData (data: ShapeComponentData): void { super.fromData(data); this.data = data; - - this.animated = true; } } diff --git a/packages/effects-core/src/plugins/index.ts b/packages/effects-core/src/plugins/index.ts index 5abd7158..f8522dde 100644 --- a/packages/effects-core/src/plugins/index.ts +++ b/packages/effects-core/src/plugins/index.ts @@ -7,6 +7,7 @@ export * from './interact/interact-loader'; export * from './interact/interact-mesh'; export * from './interact/interact-vfx-item'; export * from './interact/interact-item'; +export * from './interact/mesh-collider'; export * from './sprite/sprite-loader'; export * from './sprite/sprite-item'; export * from './sprite/sprite-mesh'; diff --git a/packages/effects-core/src/plugins/interact/mesh-collider.ts b/packages/effects-core/src/plugins/interact/mesh-collider.ts new file mode 100644 index 00000000..f039dfc8 --- /dev/null +++ b/packages/effects-core/src/plugins/interact/mesh-collider.ts @@ -0,0 +1,60 @@ +import type { TriangleLike } from '@galacean/effects-math/es/core/type'; +import type { Geometry } from '../../render/geometry'; +import type { BoundingBoxTriangle } from './click-handler'; +import { HitTestType } from './click-handler'; +import type { Matrix4 } from '@galacean/effects-math/es/core/matrix4'; +import { Vector3 } from '@galacean/effects-math/es/core/vector3'; + +export class MeshCollider { + private boundingBoxData: BoundingBoxTriangle; + private geometry: Geometry; + private triangles: TriangleLike[] = []; + + getBoundingBoxData (): BoundingBoxTriangle { + return this.boundingBoxData; + } + + setGeometry (geometry: Geometry, worldMatrix?: Matrix4) { + if (this.geometry !== geometry) { + this.triangles = this.geometryToTriangles(geometry); + this.geometry = geometry; + } + const area = []; + + for (const triangle of this.triangles) { + area.push({ p0: triangle.p0, p1: triangle.p1, p2: triangle.p2 }); + } + + if (worldMatrix) { + area.forEach(triangle => { + triangle.p0 = worldMatrix.transformPoint(triangle.p0 as Vector3, new Vector3()); + triangle.p1 = worldMatrix.transformPoint(triangle.p1 as Vector3, new Vector3()); + triangle.p2 = worldMatrix.transformPoint(triangle.p2 as Vector3, new Vector3()); + }); + } + + this.boundingBoxData = { + type: HitTestType.triangle, + area, + }; + } + + private geometryToTriangles (geometry: Geometry) { + const indices = geometry.getIndexData() ?? []; + const vertices = geometry.getAttributeData('aPos') ?? []; + const res: TriangleLike[] = []; + + for (let i = 0; i < indices.length; i += 3) { + const index0 = indices[i] * 3; + const index1 = indices[i + 1] * 3; + const index2 = indices[i + 2] * 3; + const p0 = { x: vertices[index0], y: vertices[index0 + 1], z: vertices[index0 + 2] }; + const p1 = { x: vertices[index1], y: vertices[index1 + 1], z: vertices[index1 + 2] }; + const p2 = { x: vertices[index2], y: vertices[index2 + 1], z: vertices[index2 + 2] }; + + res.push({ p0, p1, p2 }); + } + + return res; + } +} \ No newline at end of file From 63e3a63eedcd94af8cc114c22b487a350e983852 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=A3=82=E5=85=AE?= <151803898+liuxi150@users.noreply.github.com> Date: Wed, 23 Oct 2024 19:18:26 +0800 Subject: [PATCH 52/88] feat: editor mode support external skybox (#697) * feat: diffuse mode support IBL * chore: update demo * chore: update demo --- plugin-packages/model/demo/src/editor-mode.ts | 42 +++++++------- plugin-packages/model/src/gltf/loader-impl.ts | 57 ++++++++++++++++++- .../standard/metallic-roughness.frag.glsl | 12 ++-- 3 files changed, 84 insertions(+), 27 deletions(-) diff --git a/plugin-packages/model/demo/src/editor-mode.ts b/plugin-packages/model/demo/src/editor-mode.ts index 0cd870b8..d100d036 100644 --- a/plugin-packages/model/demo/src/editor-mode.ts +++ b/plugin-packages/model/demo/src/editor-mode.ts @@ -1,6 +1,6 @@ import type { Player } from '@galacean/effects'; import { math, spec, generateGUID } from '@galacean/effects'; -import { CameraGestureHandlerImp } from '@galacean/effects-plugin-model'; +import { CameraGestureHandlerImp, PSkyboxCreator, PSkyboxType } from '@galacean/effects-plugin-model'; import { LoaderImplEx } from '../../src/helper'; import { GizmoSubType } from '@galacean/effects-plugin-editor-gizmo'; @@ -56,7 +56,7 @@ async function getCurrentScene () { loader.addCamera({ near: 0.1, far: 5000, - fov: 60, + fov: 18, clipMode: 0, // name: 'extra-camera', @@ -66,33 +66,35 @@ async function getCurrentScene () { rotation: [0, 0, 0], }); - loader.addLight({ - lightType: spec.LightType.ambient, - color: { r: 1, g: 1, b: 1, a: 1 }, - intensity: 0.2, - // - name: 'ambient-light', - position: [0, 0, 0], - rotation: [0, 0, 0], - scale: [1, 1, 1], - duration: duration, - endBehavior: spec.EndBehavior.restart, - }); - loader.addLight({ lightType: spec.LightType.directional, color: { r: 1, g: 1, b: 1, a: 1 }, - intensity: 0.9, - followCamera: true, + intensity: 1, // name: 'main-light', position: [0, 0, 0], - rotation: [30, 325, 0], + rotation: [45, 45, 0], scale: [1, 1, 1], duration: duration, endBehavior: spec.EndBehavior.restart, }); + const skyboxParams = PSkyboxCreator.getSkyboxParams(PSkyboxType.NFT); + + const specularImageList = skyboxParams.specularImage; + const diffuseImageList = specularImageList[specularImageList.length - 1]; + + loader.addSkybox({ + type: 'url', + renderable: false, + intensity: 1, + reflectionsIntensity: 1, + diffuseImage: diffuseImageList, + specularImage: specularImageList, + specularImageSize: Math.pow(2, specularImageList.length - 1), + specularMipCount: specularImageList.length, + }); + const { jsonScene } = loader.getLoadResult(); loader.dispose(); @@ -323,7 +325,7 @@ export function createUI () { const uiDom = document.createElement('div'); const select = document.createElement('select'); - container.style.background = 'rgba(182,217,241)'; + container.style.background = 'rgba(0,0,0)'; uiDom.className = 'my_ui'; select.innerHTML = ` @@ -361,4 +363,4 @@ export function createUI () { const demoInfo = document.getElementsByClassName('demo-info')[0]; demoInfo.appendChild(uiDom); -} +} \ No newline at end of file diff --git a/plugin-packages/model/src/gltf/loader-impl.ts b/plugin-packages/model/src/gltf/loader-impl.ts index d0f5266f..877139b0 100644 --- a/plugin-packages/model/src/gltf/loader-impl.ts +++ b/plugin-packages/model/src/gltf/loader-impl.ts @@ -19,7 +19,7 @@ import { } from '../runtime'; import { LoaderHelper } from './loader-helper'; import { WebGLHelper } from '../utility'; -import type { PImageBufferData, PSkyboxBufferParams } from '../runtime/skybox'; +import type { PImageBufferData, PSkyboxBufferParams, PSkyboxURLParams } from '../runtime/skybox'; import type { GLTFMesh, GLTFImage, GLTFMaterial, GLTFTexture, GLTFLight, GLTFCamera, GLTFAnimation, GLTFResources, GLTFImageBasedLight, GLTFPrimitive, @@ -682,7 +682,60 @@ export class LoaderImpl implements Loader { this.components.push(component); } - async tryAddSkybox (skybox: ModelSkybox) { + addSkybox (skybox: PSkyboxURLParams) { + const itemId = generateGUID(); + const skyboxInfo = PSkyboxCreator.createSkyboxComponentData(skybox); + const { imageList, textureOptionsList, component } = skyboxInfo; + + component.item.id = itemId; + if (skybox.intensity !== undefined) { + component.intensity = skybox.intensity; + } + if (skybox.reflectionsIntensity !== undefined) { + component.reflectionsIntensity = skybox.reflectionsIntensity; + } + component.renderable = skybox.renderable ?? false; + + const item: spec.VFXItemData = { + id: itemId, + name: 'Skybox-Customize', + duration: 999, + type: spec.ItemType.skybox, + pn: 0, + visible: true, + endBehavior: spec.EndBehavior.freeze, + transform: { + position: { + x: 0, + y: 0, + z: 0, + }, + eulerHint: { + x: 0, + y: 0, + z: 0, + }, + scale: { + x: 1, + y: 1, + z: 1, + }, + }, + components: [ + { id: component.id }, + ], + content: {}, + dataType: spec.DataType.VFXItemData, + }; + + this.images.push(...imageList); + // @ts-expect-error + this.textures.push(...textureOptionsList); + this.items.push(item); + this.components.push(component); + } + + private async tryAddSkybox (skybox: ModelSkybox) { if (this.gltfImageBasedLights.length > 0 && !this.ignoreSkybox()) { const ibl = this.gltfImageBasedLights[0]; diff --git a/plugin-packages/model/src/runtime/shader-libs/standard/metallic-roughness.frag.glsl b/plugin-packages/model/src/runtime/shader-libs/standard/metallic-roughness.frag.glsl index 0a5b19ea..6761af4d 100644 --- a/plugin-packages/model/src/runtime/shader-libs/standard/metallic-roughness.frag.glsl +++ b/plugin-packages/model/src/runtime/shader-libs/standard/metallic-roughness.frag.glsl @@ -638,14 +638,13 @@ void main() #ifdef DEBUG_DIFFUSE vec3 debugDiffuse = vec3(0.0); #ifdef USE_PUNCTUAL - vec3 newBaseColor = vec3(dot(_BaseColorFactor.xyz, vec3(0.3))); MaterialInfo diffuseMaterialInfo = MaterialInfo( 1.0, - vec3(0.0), + f0, 1.0, - newBaseColor, - vec3(0.0), - vec3(0.0) + vec3(0.35), + f0, + f0 ); for (int i = 0; i < LIGHT_COUNT; ++i) { @@ -667,6 +666,9 @@ void main() debugDiffuse += applyAmbientLight(light, diffuseMaterialInfo); } } + #ifdef USE_IBL + debugDiffuse += getIBLContribution(diffuseMaterialInfo, normal, view); + #endif #endif gl_FragColor.rgb = toneMap(debugDiffuse); #endif From 606c97ef579615cc7fd417da6bd7d89598319c18 Mon Sep 17 00:00:00 2001 From: yiiqii Date: Thu, 24 Oct 2024 16:38:46 +0800 Subject: [PATCH 53/88] =?UTF-8?q?fix:=20=E5=A2=9E=E5=8A=A0=20player=20paus?= =?UTF-8?q?e=20=E4=BA=8B=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/components/base-render-component.ts | 7 ++++ .../src/components/mesh-component.ts | 5 ++- .../src/components/post-process-volume.ts | 3 ++ .../src/components/shape-component.ts | 12 ++++-- packages/effects-core/src/composition.ts | 25 ++++++----- .../src/composition/scene-ticking.ts | 14 +++++++ packages/effects-core/src/effects-object.ts | 23 +++++++++++ packages/effects-core/src/engine.ts | 9 ++++ .../effects-core/src/events/event-emitter.ts | 6 +++ packages/effects-core/src/events/types.ts | 2 +- .../src/plugins/interact/interact-item.ts | 1 + .../src/plugins/interact/mesh-collider.ts | 5 ++- packages/effects-core/src/transform.ts | 8 ++++ packages/effects-core/src/vfx-item.ts | 41 +++++++++++++++++++ packages/effects/src/index.ts | 3 ++ packages/effects/src/player.ts | 1 + packages/effects/src/types.ts | 6 +++ plugin-packages/alipay-downgrade/src/index.ts | 3 ++ plugin-packages/downgrade/src/index.ts | 3 ++ plugin-packages/editor-gizmo/src/index.ts | 3 ++ plugin-packages/model/src/index.ts | 3 ++ .../multimedia/src/audio/audio-loader.ts | 3 ++ plugin-packages/multimedia/src/index.ts | 17 +++++++- .../multimedia/src/video/video-loader.ts | 3 ++ .../orientation-transformer/src/index.ts | 9 ++-- plugin-packages/spine/src/index.ts | 3 ++ plugin-packages/spine/src/spine-component.ts | 1 + plugin-packages/stats/src/index.ts | 17 ++++++++ plugin-packages/stats/src/stats.ts | 8 ++++ typedoc.json | 5 ++- types/vendors.d.ts | 1 + web-packages/demo/src/interactive.ts | 3 ++ 32 files changed, 227 insertions(+), 26 deletions(-) diff --git a/packages/effects-core/src/components/base-render-component.ts b/packages/effects-core/src/components/base-render-component.ts index 53aecbce..33ef2622 100644 --- a/packages/effects-core/src/components/base-render-component.ts +++ b/packages/effects-core/src/components/base-render-component.ts @@ -40,6 +40,9 @@ export interface ItemRenderInfo { wireframe?: boolean, } +/** + * @since 2.1.0 + */ export class BaseRenderComponent extends RendererComponent { interaction?: { behavior: spec.InteractBehavior }; cachePrefix = '-'; @@ -60,6 +63,10 @@ export class BaseRenderComponent extends RendererComponent { protected isManualTimeSet = false; protected frameAnimationTime = 0; + /** + * + * @param engine + */ constructor (engine: Engine) { super(engine); diff --git a/packages/effects-core/src/components/mesh-component.ts b/packages/effects-core/src/components/mesh-component.ts index 9191e9ce..b5818459 100644 --- a/packages/effects-core/src/components/mesh-component.ts +++ b/packages/effects-core/src/components/mesh-component.ts @@ -5,6 +5,9 @@ import type { Geometry } from '../render/geometry'; import type { Renderer } from '../render/renderer'; import { RendererComponent } from './renderer-component'; +/** + * Mesh 组件 + */ export class MeshComponent extends RendererComponent { /** * 渲染的 Geometry @@ -43,4 +46,4 @@ export class MeshComponent extends RendererComponent { return boundingBoxData; } -} \ No newline at end of file +} diff --git a/packages/effects-core/src/components/post-process-volume.ts b/packages/effects-core/src/components/post-process-volume.ts index 70894858..289db461 100644 --- a/packages/effects-core/src/components/post-process-volume.ts +++ b/packages/effects-core/src/components/post-process-volume.ts @@ -2,6 +2,9 @@ import { effectsClass, serialize } from '../decorators'; import { Behaviour } from './component'; // TODO spec 增加 DataType +/** + * @since 2.1.0 + */ @effectsClass('PostProcessVolume') export class PostProcessVolume extends Behaviour { // Bloom diff --git a/packages/effects-core/src/components/shape-component.ts b/packages/effects-core/src/components/shape-component.ts index e957e363..38fafeec 100644 --- a/packages/effects-core/src/components/shape-component.ts +++ b/packages/effects-core/src/components/shape-component.ts @@ -54,6 +54,10 @@ void main() { } `; + /** + * + * @param engine + */ constructor (engine: Engine) { super(engine); @@ -393,7 +397,7 @@ export enum ShapePointType { } /** - * @description 椭圆组件参数 + * 椭圆组件参数 */ export interface ShapeEllipseComponent extends ShapeComponentData { type: ComponentShapeType.ELLIPSE, @@ -401,17 +405,17 @@ export interface ShapeEllipseComponent extends ShapeComponentData { } /** - * @description 椭圆参数 + * 椭圆参数 */ export interface ShapeEllipseParam { /** - * @description x轴半径 + * x 轴半径 * -- TODO 后续完善类型 * -- TODO 可以看一下用xRadius/yRadius 还是 width/height */ xRadius: number, /** - * @description y轴半径 + * y 轴半径 */ yRadius: number, /** diff --git a/packages/effects-core/src/composition.ts b/packages/effects-core/src/composition.ts index a7d28122..8672aa37 100644 --- a/packages/effects-core/src/composition.ts +++ b/packages/effects-core/src/composition.ts @@ -1,6 +1,5 @@ import * as spec from '@galacean/effects-specification'; import type { Ray } from '@galacean/effects-math/es/core/ray'; -import type { Vector3 } from '@galacean/effects-math/es/core/vector3'; import type { Matrix4 } from '@galacean/effects-math/es/core/matrix4'; import { Camera } from './camera'; import { CompositionComponent } from './comp-vfx-item'; @@ -24,6 +23,9 @@ import type { PostProcessVolume } from './components'; import { SceneTicking } from './composition/scene-ticking'; import { SerializationHelper } from './serialization-helper'; +/** + * 合成统计信息 + */ export interface CompositionStatistic { loadStart: number, loadTime: number, @@ -37,6 +39,9 @@ export interface CompositionStatistic { firstFrameTime: number, } +/** + * 合成消息对象 + */ export interface MessageItem { id: string, name: string, @@ -44,13 +49,6 @@ export interface MessageItem { compositionId: string, } -export interface CompItemClickedData { - name: string, - id: string, - hitPositions: Vector3[], - position: Vector3, -} - /** * */ @@ -60,6 +58,9 @@ export interface CompositionHitTestOptions { skip?: (item: VFXItem) => boolean, } +/** + * + */ export interface CompositionProps { reusable?: boolean, baseRenderOrder?: number, @@ -78,6 +79,9 @@ export interface CompositionProps { */ export class Composition extends EventEmitter> implements Disposable, LostHandler { renderer: Renderer; + /** + * + */ sceneTicking = new SceneTicking(); /** * 当前帧的渲染数据对象 @@ -136,7 +140,7 @@ export class Composition extends EventEmitter> imp /** * 是否在合成结束时自动销毁引用的纹理,合成重播时不销毁 */ - autoRefTex: boolean; + readonly autoRefTex: boolean; /** * 当前合成名称 */ @@ -164,7 +168,7 @@ export class Composition extends EventEmitter> imp /** * 预合成的合成属性,在 content 中会被其元素属性覆盖 */ - refCompositionProps: Map = new Map(); + readonly refCompositionProps: Map = new Map(); /** * 合成的相机对象 */ @@ -326,7 +330,6 @@ export class Composition extends EventEmitter> imp set viewportMatrix (matrix: Matrix4) { this.camera.setViewportMatrix(matrix); } - get viewportMatrix () { return this.camera.getViewportMatrix(); } diff --git a/packages/effects-core/src/composition/scene-ticking.ts b/packages/effects-core/src/composition/scene-ticking.ts index e6367739..a90c26ec 100644 --- a/packages/effects-core/src/composition/scene-ticking.ts +++ b/packages/effects-core/src/composition/scene-ticking.ts @@ -1,9 +1,16 @@ import { Component } from '../components'; +/** + * + */ export class SceneTicking { update: UpdateTickData = new UpdateTickData(); lateUpdate: LateUpdateTickData = new LateUpdateTickData(); + /** + * + * @param obj + */ addComponent (obj: Component): void { if (obj.onUpdate !== Component.prototype.onUpdate) { this.update.addComponent(obj); @@ -13,6 +20,10 @@ export class SceneTicking { } } + /** + * + * @param obj + */ removeComponent (obj: Component): void { if (obj.onUpdate !== Component.prototype.onUpdate) { this.update.removeComponent(obj); @@ -22,6 +33,9 @@ export class SceneTicking { } } + /** + * + */ clear (): void { this.update.clear(); this.lateUpdate.clear(); diff --git a/packages/effects-core/src/effects-object.ts b/packages/effects-core/src/effects-object.ts index 8e07dfcd..7aa8582e 100644 --- a/packages/effects-core/src/effects-object.ts +++ b/packages/effects-core/src/effects-object.ts @@ -6,6 +6,11 @@ import { generateGUID } from './utils'; * @since 2.0.0 */ export abstract class EffectsObject { + /** + * + * @param obj + * @returns + */ static is (obj: unknown): obj is EffectsObject { return obj instanceof EffectsObject && 'guid' in obj; } @@ -16,6 +21,10 @@ export abstract class EffectsObject { */ readonly taggedProperties: Record; + /** + * + * @param engine + */ constructor ( public engine: Engine, ) { @@ -24,16 +33,27 @@ export abstract class EffectsObject { this.engine.addInstance(this); } + /** + * + * @returns + */ getInstanceId () { return this.guid; } + /** + * + * @param guid + */ setInstanceId (guid: string) { this.engine.removeInstance(this.guid); this.guid = guid; this.engine.addInstance(this); } + /** + * + */ toData () { } /** @@ -47,5 +67,8 @@ export abstract class EffectsObject { } } + /** + * + */ dispose () { } } diff --git a/packages/effects-core/src/engine.ts b/packages/effects-core/src/engine.ts index 1cfebc04..22ca7db9 100644 --- a/packages/effects-core/src/engine.ts +++ b/packages/effects-core/src/engine.ts @@ -15,9 +15,15 @@ import { EffectsPackage } from './effects-package'; * Engine 基类,负责维护所有 GPU 资源的管理及销毁 */ export class Engine implements Disposable { + /** + * 渲染器 + */ renderer: Renderer; emptyTexture: Texture; transparentTexture: Texture; + /** + * GPU 能力 + */ gpuCapability: GPUCapability; jsonSceneData: SceneData; objectInstance: Record; @@ -36,6 +42,9 @@ export class Engine implements Disposable { protected meshes: Mesh[] = []; protected renderPasses: RenderPass[] = []; + /** + * + */ constructor () { this.jsonSceneData = {}; this.objectInstance = {}; diff --git a/packages/effects-core/src/events/event-emitter.ts b/packages/effects-core/src/events/event-emitter.ts index 19a95130..f4778511 100644 --- a/packages/effects-core/src/events/event-emitter.ts +++ b/packages/effects-core/src/events/event-emitter.ts @@ -1,3 +1,6 @@ +/** + * + */ export type EventEmitterListener

= (...callbackArgs: P) => void; /** @@ -10,6 +13,9 @@ export type EventEmitterOptions = { once?: boolean, }; +/** + * 事件监听器 + */ export class EventEmitter> { private listeners: Record, options?: EventEmitterOptions }>> = {}; diff --git a/packages/effects-core/src/events/types.ts b/packages/effects-core/src/events/types.ts index 6d1f4a16..d62cbcd0 100644 --- a/packages/effects-core/src/events/types.ts +++ b/packages/effects-core/src/events/types.ts @@ -16,7 +16,7 @@ export type ItemEvent = { }; /** - * Compositio 可以绑定的事件 + * Composition 可以绑定的事件 */ export type CompositionEvent = { /** diff --git a/packages/effects-core/src/plugins/interact/interact-item.ts b/packages/effects-core/src/plugins/interact/interact-item.ts index 3157feea..2420f03d 100644 --- a/packages/effects-core/src/plugins/interact/interact-item.ts +++ b/packages/effects-core/src/plugins/interact/interact-item.ts @@ -13,6 +13,7 @@ import type { Renderer } from '../../render'; import { effectsClass } from '../../decorators'; /** + * 交互组件 * @since 2.0.0 */ @effectsClass(spec.DataType.InteractComponent) diff --git a/packages/effects-core/src/plugins/interact/mesh-collider.ts b/packages/effects-core/src/plugins/interact/mesh-collider.ts index f039dfc8..f59658ca 100644 --- a/packages/effects-core/src/plugins/interact/mesh-collider.ts +++ b/packages/effects-core/src/plugins/interact/mesh-collider.ts @@ -5,6 +5,9 @@ import { HitTestType } from './click-handler'; import type { Matrix4 } from '@galacean/effects-math/es/core/matrix4'; import { Vector3 } from '@galacean/effects-math/es/core/vector3'; +/** + * + */ export class MeshCollider { private boundingBoxData: BoundingBoxTriangle; private geometry: Geometry; @@ -57,4 +60,4 @@ export class MeshCollider { return res; } -} \ No newline at end of file +} diff --git a/packages/effects-core/src/transform.ts b/packages/effects-core/src/transform.ts index e2350163..153e4cb1 100644 --- a/packages/effects-core/src/transform.ts +++ b/packages/effects-core/src/transform.ts @@ -19,6 +19,9 @@ const tempQuat = new Quaternion(); let seed = 1; // TODO 继承 Component +/** + * + */ export class Transform implements Disposable { /** * 转换右手坐标系左手螺旋对应的四元数到对应的旋转角 @@ -104,6 +107,11 @@ export class Transform implements Disposable { */ private readonly worldTRSCache = { position: new Vector3(0, 0, 0), quat: new Quaternion(0, 0, 0, 1), scale: new Vector3(1, 1, 1) }; + /** + * + * @param props + * @param parent + */ constructor (props: TransformProps = {}, parent?: Transform) { this.name = `transform_${seed++}`; if (props) { diff --git a/packages/effects-core/src/vfx-item.ts b/packages/effects-core/src/vfx-item.ts index e49205cb..6e5d4860 100644 --- a/packages/effects-core/src/vfx-item.ts +++ b/packages/effects-core/src/vfx-item.ts @@ -115,30 +115,66 @@ export class VFXItem extends EffectsObject implements Disposable { private isEnabled = false; private eventProcessor: EventEmitter = new EventEmitter(); + /** + * + * @param item + * @returns + */ static isComposition (item: VFXItem) { return item.type === spec.ItemType.composition; } + /** + * + * @param item + * @returns + */ static isSprite (item: VFXItem) { return item.type === spec.ItemType.sprite; } + /** + * + * @param item + * @returns + */ static isParticle (item: VFXItem) { return item.type === spec.ItemType.particle; } + /** + * + * @param item + * @returns + */ static isNull (item: VFXItem) { return item.type === spec.ItemType.null; } + /** + * + * @param item + * @returns + */ static isTree (item: VFXItem) { return item.type === spec.ItemType.tree; } + /** + * + * @param item + * @returns + */ static isCamera (item: VFXItem) { return item.type === spec.ItemType.camera; } + /** + * + * @param ancestorCandidate + * @param descendantCandidate + * @returns + */ static isAncestor ( ancestorCandidate: VFXItem, descendantCandidate: VFXItem, @@ -155,6 +191,11 @@ export class VFXItem extends EffectsObject implements Disposable { return false; } + /** + * + * @param engine + * @param props + */ constructor ( engine: Engine, props?: VFXItemProps, diff --git a/packages/effects/src/index.ts b/packages/effects/src/index.ts index 4f23df40..b51e2f9a 100644 --- a/packages/effects/src/index.ts +++ b/packages/effects/src/index.ts @@ -84,6 +84,9 @@ Engine.create = (gl: WebGLRenderingContext | WebGL2RenderingContext) => { return new GLEngine(gl); }; +/** + * Player 版本号 + */ export const version = __VERSION__; logger.info(`Player version: ${version}.`); diff --git a/packages/effects/src/player.ts b/packages/effects/src/player.ts index 6bc3a897..5e7b7296 100644 --- a/packages/effects/src/player.ts +++ b/packages/effects/src/player.ts @@ -539,6 +539,7 @@ export class Player extends EventEmitter> implements Disposa } this.ticker?.pause(); + this.emit('pause'); this.emit('update', { player: this, playing: false, diff --git a/packages/effects/src/types.ts b/packages/effects/src/types.ts index 9423ce28..5ece9980 100644 --- a/packages/effects/src/types.ts +++ b/packages/effects/src/types.ts @@ -48,6 +48,9 @@ export interface PlayerConfig { * player 的 name */ name?: string, + /** + * 渲染选项,传递给 WebGLRenderingContext 实例化的 WebGLContextAttributes 参数 + */ renderOptions?: { /** * 播放器是否需要截图(对应 WebGL 的 preserveDrawingBuffer 参数) @@ -71,6 +74,9 @@ export interface PlayerConfig { reportGPUTime?: (time: number) => void, } +/** + * 播放器事件 + */ export type PlayerEvent

= { /** * 播放器点击事件 diff --git a/plugin-packages/alipay-downgrade/src/index.ts b/plugin-packages/alipay-downgrade/src/index.ts index 873a456f..cf2de258 100644 --- a/plugin-packages/alipay-downgrade/src/index.ts +++ b/plugin-packages/alipay-downgrade/src/index.ts @@ -6,6 +6,9 @@ export * from './utils'; export * from './native-log'; export * from './types'; +/** + * 插件版本号 + */ export const version = __VERSION__; registerPlugin('alipay-downgrade', AlipayDowngradePlugin, VFXItem, true); diff --git a/plugin-packages/downgrade/src/index.ts b/plugin-packages/downgrade/src/index.ts index cef67b43..51fe8ec7 100644 --- a/plugin-packages/downgrade/src/index.ts +++ b/plugin-packages/downgrade/src/index.ts @@ -7,6 +7,9 @@ export * from './parser'; export * from './ua-decoder'; export * from './types'; +/** + * 插件版本号 + */ export const version = __VERSION__; registerPlugin('downgrade', DowngradePlugin, VFXItem, true); diff --git a/plugin-packages/editor-gizmo/src/index.ts b/plugin-packages/editor-gizmo/src/index.ts index 946f1e31..687d15be 100644 --- a/plugin-packages/editor-gizmo/src/index.ts +++ b/plugin-packages/editor-gizmo/src/index.ts @@ -14,6 +14,9 @@ export { }; export * from './gizmo-component'; +/** + * 插件版本号 + */ export const version = __VERSION__; logger.info(`Plugin editor gizmo version: ${version}.`); diff --git a/plugin-packages/model/src/index.ts b/plugin-packages/model/src/index.ts index c53ea24d..bcb6e30d 100644 --- a/plugin-packages/model/src/index.ts +++ b/plugin-packages/model/src/index.ts @@ -5,6 +5,9 @@ import { ModelPlugin } from './plugin'; registerPlugin('model', ModelPlugin, VFXItem); +/** + * 插件版本号 + */ export const version = __VERSION__; export type BaseTransform = spec.BaseItemTransform; diff --git a/plugin-packages/multimedia/src/audio/audio-loader.ts b/plugin-packages/multimedia/src/audio/audio-loader.ts index e90da9fd..2da4d3d5 100644 --- a/plugin-packages/multimedia/src/audio/audio-loader.ts +++ b/plugin-packages/multimedia/src/audio/audio-loader.ts @@ -2,6 +2,9 @@ import type { SceneLoadOptions } from '@galacean/effects'; import { spec, AbstractPlugin } from '@galacean/effects'; import { processMultimedia } from '../utils'; +/** + * 音频加载插件 + */ export class AudioLoader extends AbstractPlugin { static override async processAssets ( json: spec.JSONScene, diff --git a/plugin-packages/multimedia/src/index.ts b/plugin-packages/multimedia/src/index.ts index 693fb519..22a1553e 100644 --- a/plugin-packages/multimedia/src/index.ts +++ b/plugin-packages/multimedia/src/index.ts @@ -1,4 +1,5 @@ -import { registerPlugin, VFXItem } from '@galacean/effects'; +import * as EFFECTS from '@galacean/effects'; +import { logger, registerPlugin, VFXItem } from '@galacean/effects'; import { VideoLoader } from './video/video-loader'; import { AudioLoader } from './audio/audio-loader'; @@ -8,5 +9,19 @@ export * from './audio/audio-player'; export * from './constants'; export * from './utils'; +/** + * 插件版本号 + */ +export const version = __VERSION__; + registerPlugin('video', VideoLoader, VFXItem, true); registerPlugin('audio', AudioLoader, VFXItem, true); + +logger.info(`Plugin multimedia version: ${version}.`); + +if (version !== EFFECTS.version) { + console.error( + '注意:请统一 Multimedia 插件与 Player 版本,不统一的版本混用会有不可预知的后果!', + '\nAttention: Please ensure the Multimedia plugin is synchronized with the Player version. Mixing and matching incompatible versions may result in unpredictable consequences!' + ); +} diff --git a/plugin-packages/multimedia/src/video/video-loader.ts b/plugin-packages/multimedia/src/video/video-loader.ts index 95729a77..46738d87 100644 --- a/plugin-packages/multimedia/src/video/video-loader.ts +++ b/plugin-packages/multimedia/src/video/video-loader.ts @@ -2,6 +2,9 @@ import type { SceneLoadOptions } from '@galacean/effects'; import { spec, AbstractPlugin } from '@galacean/effects'; import { processMultimedia } from '../utils'; +/** + * 视频加载插件 + */ export class VideoLoader extends AbstractPlugin { static override async processAssets ( json: spec.JSONScene, diff --git a/plugin-packages/orientation-transformer/src/index.ts b/plugin-packages/orientation-transformer/src/index.ts index 0ed61f40..c543be67 100644 --- a/plugin-packages/orientation-transformer/src/index.ts +++ b/plugin-packages/orientation-transformer/src/index.ts @@ -2,18 +2,15 @@ import * as EFFECTS from '@galacean/effects'; import { VFXItem, logger, registerPlugin } from '@galacean/effects'; import { OrientationPluginLoader } from './orientation-plugin-loader'; -declare global { - interface Window { - ge: any, - } -} - registerPlugin('orientation-transformer', OrientationPluginLoader, VFXItem); export { getAdapter, closeDeviceMotion, openDeviceMotion, OrientationPluginLoader } from './orientation-plugin-loader'; export { OrientationAdapterAcceler } from './orientation-adapter-acceler'; export * from './orientation-component'; +/** + * 插件版本号 + */ export const version = __VERSION__; logger.info(`Plugin orientation transformer version: ${version}.`); diff --git a/plugin-packages/spine/src/index.ts b/plugin-packages/spine/src/index.ts index 3abc646a..2ad451ad 100644 --- a/plugin-packages/spine/src/index.ts +++ b/plugin-packages/spine/src/index.ts @@ -20,6 +20,9 @@ export { registerPlugin('spine', class SpineLoader extends AbstractPlugin { }, VFXItem); +/** + * 插件版本号 + */ export const version = __VERSION__; logger.info(`Plugin spine version: ${version}.`); diff --git a/plugin-packages/spine/src/spine-component.ts b/plugin-packages/spine/src/spine-component.ts index 5847f067..3cb64c37 100644 --- a/plugin-packages/spine/src/spine-component.ts +++ b/plugin-packages/spine/src/spine-component.ts @@ -52,6 +52,7 @@ export interface SpineDataCache extends SpineBaseData { } /** + * Spine 组件 * @since 2.0.0 */ @effectsClass(spec.DataType.SpineComponent) diff --git a/plugin-packages/stats/src/index.ts b/plugin-packages/stats/src/index.ts index c7e87e1f..eaffac1c 100644 --- a/plugin-packages/stats/src/index.ts +++ b/plugin-packages/stats/src/index.ts @@ -1 +1,18 @@ +import * as EFFECTS from '@galacean/effects'; +import { logger } from '@galacean/effects'; + export * from './stats'; + +/** + * 插件版本号 + */ +export const version = __VERSION__; + +logger.info(`Plugin stats version: ${version}.`); + +if (version !== EFFECTS.version) { + console.error( + '注意:请统一 Stats 插件与 Player 版本,不统一的版本混用会有不可预知的后果!', + '\nAttention: Please ensure the Stats plugin is synchronized with the Player version. Mixing and matching incompatible versions may result in unpredictable consequences!' + ); +} diff --git a/plugin-packages/stats/src/stats.ts b/plugin-packages/stats/src/stats.ts index ceaa035c..2645a7c3 100644 --- a/plugin-packages/stats/src/stats.ts +++ b/plugin-packages/stats/src/stats.ts @@ -79,9 +79,17 @@ const json = { 'compositionId': '2', }; +/** + * + */ export class Stats { static options: StatsOptions; + /** + * + * @param player + * @param options + */ constructor ( public readonly player: Player, options: StatsOptions = { debug: false }, diff --git a/typedoc.json b/typedoc.json index a483d36d..515ad84d 100644 --- a/typedoc.json +++ b/typedoc.json @@ -5,10 +5,13 @@ "packages/effects", "packages/effects-helper", "packages/effects-threejs", + "plugin-packages/downgrade", "plugin-packages/editor-gizmo", "plugin-packages/model", + "plugin-packages/multimedia", "plugin-packages/orientation-transformer", - "plugin-packages/spine" + "plugin-packages/spine", + "plugin-packages/stats" ], "entryPointStrategy": "packages", "out": "api", diff --git a/types/vendors.d.ts b/types/vendors.d.ts index 3f028e16..c3752f99 100644 --- a/types/vendors.d.ts +++ b/types/vendors.d.ts @@ -11,6 +11,7 @@ interface Window { _createOffscreenCanvas: (width: number, height: number) => HTMLCanvasElement; AlipayJSBridge: any; WindVane: any; + ge: any, __wxjs_environment: any; } diff --git a/web-packages/demo/src/interactive.ts b/web-packages/demo/src/interactive.ts index 6f840f62..4f375314 100644 --- a/web-packages/demo/src/interactive.ts +++ b/web-packages/demo/src/interactive.ts @@ -19,6 +19,9 @@ const container = document.getElementById('J-container'); player.on('update', e => { document.getElementById('J-playerState')!.innerText = `[player update] - player is ${e.playing ? 'playing' : 'paused'}`; }); + player.on('pause', () => { + console.info('[player pause] - player is paused.'); + }); document.getElementById('J-pauseBtn')?.addEventListener('click', () => { player.pause(); From 7b87bea4f6822b74ece9a4a777d107e002593414 Mon Sep 17 00:00:00 2001 From: "wumaolin.wml" Date: Mon, 28 Oct 2024 21:45:21 +0800 Subject: [PATCH 54/88] fix: shape bounding box --- .../src/components/mesh-component.ts | 9 ++++-- .../src/plugins/interact/mesh-collider.ts | 30 +++++++++++++++++++ 2 files changed, 36 insertions(+), 3 deletions(-) diff --git a/packages/effects-core/src/components/mesh-component.ts b/packages/effects-core/src/components/mesh-component.ts index b5818459..864b17bf 100644 --- a/packages/effects-core/src/components/mesh-component.ts +++ b/packages/effects-core/src/components/mesh-component.ts @@ -28,7 +28,10 @@ export class MeshComponent extends RendererComponent { // TODO 点击测试后续抽象一个 Collider 组件 getHitTestParams = (force?: boolean): HitTestTriangleParams | void => { - const area = this.getBoundingBox(); + const worldMatrix = this.transform.getWorldMatrix(); + + this.meshCollider.setGeometry(this.geometry, worldMatrix); + const area = this.meshCollider.getBoundingBoxData(); if (area) { return { @@ -42,8 +45,8 @@ export class MeshComponent extends RendererComponent { const worldMatrix = this.transform.getWorldMatrix(); this.meshCollider.setGeometry(this.geometry, worldMatrix); - const boundingBoxData = this.meshCollider.getBoundingBoxData(); + const boundingBox = this.meshCollider.getBoundingBox(); - return boundingBoxData; + return boundingBox; } } diff --git a/packages/effects-core/src/plugins/interact/mesh-collider.ts b/packages/effects-core/src/plugins/interact/mesh-collider.ts index f59658ca..be7f4ec3 100644 --- a/packages/effects-core/src/plugins/interact/mesh-collider.ts +++ b/packages/effects-core/src/plugins/interact/mesh-collider.ts @@ -17,6 +17,36 @@ export class MeshCollider { return this.boundingBoxData; } + getBoundingBox (): BoundingBoxTriangle { + let maxX = 0; + let maxY = 0; + + let minX = 0; + let minY = 0; + + for (const triangle of this.boundingBoxData.area) { + maxX = Math.max(triangle.p0.x, triangle.p1.x, triangle.p2.x, maxX); + maxY = Math.max(triangle.p0.y, triangle.p1.y, triangle.p2.y, maxY); + minX = Math.min(triangle.p0.x, triangle.p1.x, triangle.p2.x, minX); + minY = Math.min(triangle.p0.y, triangle.p1.y, triangle.p2.y, minY); + } + + const area = []; + + const point0 = new Vector3(minX, maxY, 0); + const point1 = new Vector3(maxX, maxY, 0); + const point2 = new Vector3(maxX, minY, 0); + const point3 = new Vector3(minX, minY, 0); + + area.push({ p0: point0, p1: point1, p2: point2 }); + area.push({ p0: point0, p1: point2, p2: point3 }); + + return { + type: HitTestType.triangle, + area, + }; + } + setGeometry (geometry: Geometry, worldMatrix?: Matrix4) { if (this.geometry !== geometry) { this.triangles = this.geometryToTriangles(geometry); From 701786cf0885c79650351cf7def5e62541724d7f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=8C=82=E5=AE=89?= <151805118+wumaolinmaoan@users.noreply.github.com> Date: Tue, 29 Oct 2024 09:31:57 +0800 Subject: [PATCH 55/88] feat: fake 3d component (#701) * feat: add fake 3d component * chore: add export --- .../src/components/fake-3d-component.ts | 108 ++++++++++++++++++ packages/effects-core/src/components/index.ts | 1 + 2 files changed, 109 insertions(+) create mode 100644 packages/effects-core/src/components/fake-3d-component.ts diff --git a/packages/effects-core/src/components/fake-3d-component.ts b/packages/effects-core/src/components/fake-3d-component.ts new file mode 100644 index 00000000..3b913ce3 --- /dev/null +++ b/packages/effects-core/src/components/fake-3d-component.ts @@ -0,0 +1,108 @@ +import { effectsClass, serialize } from '../decorators'; +import { Component } from './component'; +import { EffectComponent } from './effect-component'; + +@effectsClass('Fake3DComponent') +export class Fake3DComponent extends Component { + @serialize() + loop = false; + + @serialize() + amountOfMotion = 1.0; + + @serialize() + animationLength = 2.0; + + @serialize() + mode = Fake3DAnimationMode.Linear; + + @serialize() + startPositionX = 0; + @serialize() + startPositionY = 0; + @serialize() + startPositionZ = 0; + + @serialize() + endPositionX = 0; + @serialize() + endPositionY = 0; + @serialize() + endPositionZ = 0; + + @serialize() + amplitudeX = 0; + @serialize() + amplitudeY = 0; + @serialize() + amplitudeZ = 0; + + @serialize() + phaseX = 0; + @serialize() + phaseY = 0; + @serialize() + phaseZ = 0; + + effectComponent: EffectComponent; + + override onStart (): void { + this.effectComponent = this.item.getComponent(EffectComponent); + } + + override onUpdate (dt: number): void { + this.updateFake3D(); + } + + updateFake3D () { + if (!this.effectComponent) { + return; + } + + const time = this.item.time % this.animationLength / this.animationLength; + + let _PosX = 0; + let _PosY = 0; + let _PosZ = 0; + + switch (this.mode) { + case Fake3DAnimationMode.Circular:{ + const PI = Math.PI; + + _PosX = Math.sin(2.0 * PI * (time + this.phaseX)) * this.amplitudeX; + _PosY = Math.sin(2.0 * PI * (time + this.phaseY)) * this.amplitudeY; + _PosZ = Math.sin(2.0 * PI * (time + this.phaseZ)) * this.amplitudeZ; + + break; + } + case Fake3DAnimationMode.Linear:{ + let localTime = time; + + if (this.loop) { + if (localTime > 0.5) { + localTime = 1 - localTime; + } + + localTime *= 2; + } + + _PosX = this.startPositionX * (1 - localTime) + localTime * this.endPositionX; + _PosY = this.startPositionY * (1 - localTime) + localTime * this.endPositionY; + _PosZ = this.startPositionZ * (1 - localTime) + localTime * this.endPositionZ; + + break; + } + } + + const material = this.effectComponent.material; + + material.setFloat('_PosX', _PosX * this.amountOfMotion); + material.setFloat('_PosY', _PosY * this.amountOfMotion); + material.setFloat('_PosZ', _PosZ * this.amountOfMotion); + } +} + +export enum Fake3DAnimationMode { + Circular, + Linear +} \ No newline at end of file diff --git a/packages/effects-core/src/components/index.ts b/packages/effects-core/src/components/index.ts index 5980dc5b..bacc83b6 100644 --- a/packages/effects-core/src/components/index.ts +++ b/packages/effects-core/src/components/index.ts @@ -4,3 +4,4 @@ export * from './effect-component'; export * from './post-process-volume'; export * from './base-render-component'; export * from './shape-component'; +export * from './fake-3d-component'; From 6ce96ef83de01f4d3df0eb67648e10988ef6892e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=8C=82=E5=AE=89?= <151805118+wumaolinmaoan@users.noreply.github.com> Date: Wed, 30 Oct 2024 10:58:24 +0800 Subject: [PATCH 56/88] feat: add poly star shape (#705) * feat: add poly star shape * chore: rename shape param field * chore: flatten shape params * fix: demo * chore: update spec * chore: shapeComponentData support multi shape * chore: optimize --------- Co-authored-by: yiiqii --- packages/effects-core/package.json | 2 +- .../src/components/shape-component.ts | 254 +++++++++++++----- .../src/composition-source-manager.ts | 4 +- .../effects-core/src/plugins/shape/ellipse.ts | 4 +- .../src/plugins/shape/graphics-path.ts | 10 + .../src/plugins/shape/poly-star.ts | 195 ++++++++++++++ .../effects-core/src/plugins/shape/polygon.ts | 4 +- .../src/plugins/shape/rectangle.ts | 4 +- .../src/plugins/shape/shape-path.ts | 13 + .../src/plugins/shape/shape-primitive.ts | 6 +- .../src/plugins/shape/triangle.ts | 4 +- packages/effects-core/src/shape/geometry.ts | 6 +- pnpm-lock.yaml | 8 +- web-packages/demo/src/shape.ts | 158 +++++------ web-packages/imgui-demo/src/ge.ts | 9 +- 15 files changed, 510 insertions(+), 171 deletions(-) create mode 100644 packages/effects-core/src/plugins/shape/poly-star.ts diff --git a/packages/effects-core/package.json b/packages/effects-core/package.json index eece695a..4a6bd10a 100644 --- a/packages/effects-core/package.json +++ b/packages/effects-core/package.json @@ -51,7 +51,7 @@ "registry": "https://registry.npmjs.org" }, "dependencies": { - "@galacean/effects-specification": "2.1.0-alpha.1", + "@galacean/effects-specification": "2.1.0-alpha.2", "@galacean/effects-math": "1.1.0", "flatbuffers": "24.3.25", "uuid": "9.0.1", diff --git a/packages/effects-core/src/components/shape-component.ts b/packages/effects-core/src/components/shape-component.ts index 38fafeec..396b2799 100644 --- a/packages/effects-core/src/components/shape-component.ts +++ b/packages/effects-core/src/components/shape-component.ts @@ -4,11 +4,12 @@ import { effectsClass } from '../decorators'; import type { Engine } from '../engine'; import { glContext } from '../gl'; import type { MaterialProps } from '../material'; -import { Material } from '../material'; +import { Material, setMaskMode } from '../material'; import { GraphicsPath } from '../plugins/shape/graphics-path'; import type { ShapePath } from '../plugins/shape/shape-path'; import { Geometry, GLSLVersion } from '../render'; import { MeshComponent } from './mesh-component'; +import { StarType } from '../plugins/shape/poly-star'; interface CurveData { point: spec.Vector3Data, @@ -96,7 +97,7 @@ void main() { this.material = Material.create(engine, materialProps); this.material.setColor('_Color', new Color(1, 1, 1, 1)); - this.material.depthMask = true; + this.material.depthMask = false; this.material.depthTest = true; this.material.blending = true; } @@ -173,56 +174,79 @@ void main() { private buildPath (data: ShapeComponentData) { this.path.clear(); - switch (data.type) { - case ComponentShapeType.CUSTOM: { - const customData = data as ShapeCustomComponent; - const points = customData.param.points; - const easingIns = customData.param.easingIn; - const easingOuts = customData.param.easingOut; - this.curveValues = []; + for (const shapeData of data.shapeDatas) { + switch (shapeData.type) { + case ShapeType.Custom: { + const customData = shapeData as CustomShapeData; + const points = customData.points; + const easingIns = customData.easingIns; + const easingOuts = customData.easingOuts; - for (const shape of customData.param.shapes) { - const indices = shape.indexes; + this.curveValues = []; - for (let i = 1; i < indices.length; i++) { - const pointIndex = indices[i]; - const lastPointIndex = indices[i - 1]; + for (const shape of customData.shapes) { + const indices = shape.indexes; + for (let i = 1; i < indices.length; i++) { + const pointIndex = indices[i]; + const lastPointIndex = indices[i - 1]; + + this.curveValues.push({ + point: points[pointIndex.point], + controlPoint1: easingOuts[lastPointIndex.easingOut], + controlPoint2: easingIns[pointIndex.easingIn], + }); + } + + // Push the last curve this.curveValues.push({ - point: points[pointIndex.point], - controlPoint1: easingOuts[lastPointIndex.easingOut], - controlPoint2: easingIns[pointIndex.easingIn], + point: points[indices[0].point], + controlPoint1: easingOuts[indices[indices.length - 1].easingOut], + controlPoint2: easingIns[indices[0].easingIn], }); } - // Push the last curve - this.curveValues.push({ - point: points[indices[0].point], - controlPoint1: easingOuts[indices[indices.length - 1].easingOut], - controlPoint2: easingIns[indices[0].easingIn], - }); + this.path.moveTo(this.curveValues[this.curveValues.length - 1].point.x, this.curveValues[this.curveValues.length - 1].point.y); + + for (const curveValue of this.curveValues) { + const point = curveValue.point; + const control1 = curveValue.controlPoint1; + const control2 = curveValue.controlPoint2; + + this.path.bezierCurveTo(control1.x, control1.y, control2.x, control2.y, point.x, point.y, 1); + } + + break; } + case ShapeType.Ellipse: { + const ellipseData = shapeData as EllipseData; - this.path.moveTo(this.curveValues[this.curveValues.length - 1].point.x, this.curveValues[this.curveValues.length - 1].point.y); + this.path.ellipse(0, 0, ellipseData.xRadius, ellipseData.yRadius); - for (const curveValue of this.curveValues) { - const point = curveValue.point; - const control1 = curveValue.controlPoint1; - const control2 = curveValue.controlPoint2; + break; + } + case ShapeType.Rectangle: { + const rectangleData = shapeData as RectangleData; - this.path.bezierCurveTo(control1.x, control1.y, control2.x, control2.y, point.x, point.y, 1); + this.path.rect(-rectangleData.width / 2, rectangleData.height / 2, rectangleData.width, rectangleData.height); + + break; } + case ShapeType.Star: { + const starData = shapeData as StarData; - break; - } - case ComponentShapeType.ELLIPSE: { - const ellipseData = data as ShapeEllipseComponent; - const ellipseParam = ellipseData.param; + this.path.polyStar(starData.pointCount, starData.outerRadius, starData.innerRadius, starData.outerRoundness, starData.innerRoundness, StarType.Star); + + break; + } + case ShapeType.Polygon: { + const polygonData = shapeData as PolygonData; - this.path.ellipse(0, 0, ellipseParam.xRadius, ellipseParam.yRadius); + this.path.polyStar(polygonData.pointCount, polygonData.radius, polygonData.radius, polygonData.roundness, polygonData.roundness, StarType.Polygon); - break; + break; + } } } } @@ -230,6 +254,13 @@ void main() { override fromData (data: ShapeComponentData): void { super.fromData(data); this.data = data; + + const material = this.material; + + //@ts-expect-error // TODO 新版蒙版上线后重构 + material.stencilRef = data.renderer.mask !== undefined ? [data.renderer.mask, data.renderer.mask] : undefined; + //@ts-expect-error // TODO 新版蒙版上线后重构 + setMaskMode(material, data.renderer.maskMode); } } @@ -239,56 +270,50 @@ void main() { * 矢量图形组件 */ export interface ShapeComponentData extends spec.ComponentData { - /** - * 矢量类型 - */ - type: ComponentShapeType, + shapeDatas: ShapeData[], } /** * 矢量图形类型 */ -export enum ComponentShapeType { +export enum ShapeType { /** * 自定义图形 */ - CUSTOM, + Custom, /** * 矩形 */ - RECTANGLE, + Rectangle, /** * 椭圆 */ - ELLIPSE, + Ellipse, /** * 多边形 */ - POLYGON, + Polygon, /** * 星形 */ - STAR, + Star, } -/** - * 自定义图形组件 - */ -export interface ShapeCustomComponent extends ShapeComponentData { +export class ShapeData { /** - * 矢量类型 - 形状 - */ - type: ComponentShapeType.CUSTOM, - /** - * 矢量参数 - 形状 + * 矢量类型 */ - param: ShapeCustomParam, + type: ShapeType; } /** - * 矢量路径参数 + * 自定义图形组件 */ -export interface ShapeCustomParam { +export interface CustomShapeData extends ShapeData { + /** + * 矢量类型 - 形状 + */ + type: ShapeType.Custom, /** * 路径点 */ @@ -296,11 +321,11 @@ export interface ShapeCustomParam { /** * 入射控制点 */ - easingIn: spec.Vector3Data[], + easingIns: spec.Vector3Data[], /** * 入射控制点 */ - easingOut: spec.Vector3Data[], + easingOuts: spec.Vector3Data[], /** * 自定义形状 */ @@ -399,15 +424,8 @@ export enum ShapePointType { /** * 椭圆组件参数 */ -export interface ShapeEllipseComponent extends ShapeComponentData { - type: ComponentShapeType.ELLIPSE, - param: ShapeEllipseParam, -} - -/** - * 椭圆参数 - */ -export interface ShapeEllipseParam { +export interface EllipseData extends ShapeData { + type: ShapeType.Ellipse, /** * x 轴半径 * -- TODO 后续完善类型 @@ -431,3 +449,101 @@ export interface ShapeEllipseParam { */ transform?: spec.TransformData, } + +/** + * 星形参数 + */ +export interface StarData extends ShapeData { + /** + * 顶点数 - 内外顶点同数 + */ + pointCount: number, + /** + * 内径 + */ + innerRadius: number, + /** + * 外径 + */ + outerRadius: number, + /** + * 内径点圆度 + */ + innerRoundness: number, + /** + * 外径点圆度 + */ + outerRoundness: number, + /** + * 填充属性 + */ + fill?: ShapeFillParam, + /** + * 描边属性 + */ + stroke?: ShapeStrokeParam, + /** + * 空间变换 + */ + transform?: spec.TransformData, +} + +/** + * 多边形参数 + */ +export interface PolygonData extends ShapeData { + /** + * 顶点数 + */ + pointCount: number, + /** + * 外切圆半径 + */ + radius: number, + /** + * 角点圆度 + */ + roundness: number, + /** + * 填充属性 + */ + fill?: ShapeFillParam, + /** + * 描边属性 + */ + stroke?: ShapeStrokeParam, + /** + * 空间变换 + */ + transform?: spec.TransformData, +} + +/** + * 矩形参数 + */ +export interface RectangleData extends ShapeData { + /** + * 宽度 + */ + width: number, + /** + * 高度 + */ + height: number, + /** + * 角点元素 + */ + roundness: number, + /** + * 填充属性 + */ + fill?: ShapeFillParam, + /** + * 描边属性 + */ + stroke?: ShapeStrokeParam, + /** + * 空间变换 + */ + transform?: spec.TransformData, +} diff --git a/packages/effects-core/src/composition-source-manager.ts b/packages/effects-core/src/composition-source-manager.ts index 4c12fb39..66b4f723 100644 --- a/packages/effects-core/src/composition-source-manager.ts +++ b/packages/effects-core/src/composition-source-manager.ts @@ -119,7 +119,9 @@ export class CompositionSourceManager implements Disposable { if ( itemProps.type === spec.ItemType.sprite || - itemProps.type === spec.ItemType.particle + itemProps.type === spec.ItemType.particle || + //@ts-expect-error + itemProps.type === spec.ItemType.shape ) { for (const componentPath of itemProps.components) { const componentData = componentMap[componentPath.id] as spec.SpriteComponentData | spec.ParticleSystemData; diff --git a/packages/effects-core/src/plugins/shape/ellipse.ts b/packages/effects-core/src/plugins/shape/ellipse.ts index 0d0f502c..ff9bb7d8 100644 --- a/packages/effects-core/src/plugins/shape/ellipse.ts +++ b/packages/effects-core/src/plugins/shape/ellipse.ts @@ -149,11 +149,11 @@ export class Ellipse extends ShapePrimitive { return ellipse; } - override getX (): number { + getX (): number { return this.x; } - override getY (): number { + getY (): number { return this.y; } diff --git a/packages/effects-core/src/plugins/shape/graphics-path.ts b/packages/effects-core/src/plugins/shape/graphics-path.ts index f9c126ec..ddf7d61b 100644 --- a/packages/effects-core/src/plugins/shape/graphics-path.ts +++ b/packages/effects-core/src/plugins/shape/graphics-path.ts @@ -5,6 +5,7 @@ import type { Matrix4 } from '@galacean/effects-math/es/core/matrix4'; import { ShapePath } from './shape-path'; +import type { StarType } from './poly-star'; export class GraphicsPath { instructions: PathInstruction[] = []; @@ -103,6 +104,14 @@ export class GraphicsPath { return this; } + polyStar (pointCount: number, outerRadius: number, innerRadius: number, outerRoundness: number, innerRoundness: number, starType: StarType, transform?: Matrix4) { + this.instructions.push({ action: 'polyStar', data: [pointCount, outerRadius, innerRadius, outerRoundness, innerRoundness, starType, transform] }); + + this.dirty = true; + + return this; + } + clear (): GraphicsPath { this.instructions.length = 0; this.dirty = true; @@ -132,6 +141,7 @@ export interface PathInstruction { | 'roundShape' | 'filletRect' | 'chamferRect' + | 'polyStar' , data: any[], } diff --git a/packages/effects-core/src/plugins/shape/poly-star.ts b/packages/effects-core/src/plugins/shape/poly-star.ts new file mode 100644 index 00000000..5c699ca2 --- /dev/null +++ b/packages/effects-core/src/plugins/shape/poly-star.ts @@ -0,0 +1,195 @@ +import { buildAdaptiveBezier } from './build-adaptive-bezier'; +import { ShapePrimitive } from './shape-primitive'; +import { triangulate } from './triangulate'; + +export enum StarType { + Star, + Polygon, +} + +export class PolyStar extends ShapePrimitive { + /** + * bezier 顶点 + */ + private v: number[] = []; + /** + * bezier 缓入点 + */ + private in: number[] = []; + /** + * bezier 缓出点 + */ + private out: number[] = []; + + /** + * + * @param pointCount - 多边形顶点数量 + * @param outerRadius - 外半径大小 + * @param innerRadius - 内半径大小 + * @param outerRoundness - 外顶点圆滑度百分比 + * @param innerRoundness - 内顶点圆滑度百分比 + * @param starType - PolyStar 类型 + */ + constructor ( + public pointCount = 0, + public outerRadius = 0, + public innerRadius = 0, + public outerRoundness = 0, + public innerRoundness = 0, + public starType = StarType.Star, + ) { + super(); + } + + override clone (): ShapePrimitive { + const polyStar = new PolyStar( + this.pointCount, + this.outerRadius, + this.innerRadius, + this.outerRoundness, + this.innerRoundness, + this.starType + ); + + return polyStar; + } + + override copyFrom (source: PolyStar): void { + this.pointCount = source.pointCount; + this.outerRadius = source.outerRadius; + this.innerRadius = source.innerRadius; + this.outerRoundness = source.outerRoundness; + this.innerRoundness = source.innerRoundness; + this.starType = source.starType; + } + + override copyTo (destination: PolyStar): void { + destination.copyFrom(this); + } + + override build (points: number[]): void { + switch (this.starType) { + case StarType.Star: { + this.buildStarPath(); + + break; + } + case StarType.Polygon: { + this.buildPolygonPath(); + + break; + } + } + + const smoothness = 1; + + for (let i = 0; i < this.v.length - 2; i += 2) { + buildAdaptiveBezier( + points, + this.v[i], this.v[i + 1], + this.out[i], this.out[i + 1], this.in[i + 2], this.in[i + 3], this.v[i + 2], this.v[i + 3], + smoothness + ); + } + + // draw last curve + const lastIndex = this.v.length - 1; + + buildAdaptiveBezier( + points, + this.v[lastIndex - 1], this.v[lastIndex], + this.out[lastIndex - 1], this.out[lastIndex], this.in[0], this.in[1], this.v[0], this.v[1], + smoothness + ); + + } + + override triangulate (points: number[], vertices: number[], verticesOffset: number, indices: number[], indicesOffset: number): void { + const triangles = triangulate([points]); + + for (let i = 0; i < triangles.length; i++) { + vertices[verticesOffset + i] = triangles[i]; + } + + const vertexCount = triangles.length / 2; + + for (let i = 0; i < vertexCount; i++) { + indices[indicesOffset + i] = i; + } + } + + private buildStarPath () { + this.v = []; + this.in = []; + this.out = []; + + const numPts = Math.floor(this.pointCount) * 2; + const angle = (Math.PI * 2) / numPts; + let longFlag = true; + const longRad = this.outerRadius; + const shortRad = this.innerRadius; + const longRound = this.outerRoundness / 100; + const shortRound = this.innerRoundness / 100; + const longPerimSegment = (2 * Math.PI * longRad) / (numPts * 2); + const shortPerimSegment = (2 * Math.PI * shortRad) / (numPts * 2); + let i; + let rad; + let roundness; + let perimSegment; + let currentAng = -Math.PI / 2; + + const dir = 1; + + for (i = 0; i < numPts; i++) { + rad = longFlag ? longRad : shortRad; + roundness = longFlag ? longRound : shortRound; + perimSegment = longFlag ? longPerimSegment : shortPerimSegment; + const x = rad * Math.cos(currentAng); + const y = rad * Math.sin(currentAng); + const ox = x === 0 && y === 0 ? 0 : y / Math.sqrt(x * x + y * y); + const oy = x === 0 && y === 0 ? 0 : -x / Math.sqrt(x * x + y * y); + const offset = i * 2; + + this.v[offset] = x; + this.v[offset + 1] = y; + this.in[offset] = x + ox * perimSegment * roundness * dir; + this.in[offset + 1] = y + oy * perimSegment * roundness * dir; + this.out[offset] = x - ox * perimSegment * roundness * dir; + this.out[offset + 1] = y - oy * perimSegment * roundness * dir; + longFlag = !longFlag; + currentAng += angle * dir; + } + } + + private buildPolygonPath () { + this.v = []; + this.in = []; + this.out = []; + + const numPts = Math.floor(this.pointCount); + const angle = (Math.PI * 2) / numPts; + const rad = this.outerRadius; + const roundness = this.outerRoundness / 100; + const perimSegment = (2 * Math.PI * rad) / (numPts * 4); + let i; + let currentAng = -Math.PI * 0.5; + const dir = 1; + + for (i = 0; i < numPts; i++) { + const x = rad * Math.cos(currentAng); + const y = rad * Math.sin(currentAng); + const ox = x === 0 && y === 0 ? 0 : y / Math.sqrt(x * x + y * y); + const oy = x === 0 && y === 0 ? 0 : -x / Math.sqrt(x * x + y * y); + + const offset = i * 2; + + this.v[offset] = x; + this.v[offset + 1] = y; + this.in[offset] = x + ox * perimSegment * roundness * dir; + this.in[offset + 1] = y + oy * perimSegment * roundness * dir; + this.out[offset] = x - ox * perimSegment * roundness * dir; + this.out[offset + 1] = y - oy * perimSegment * roundness * dir; + currentAng += angle * dir; + } + } +} diff --git a/packages/effects-core/src/plugins/shape/polygon.ts b/packages/effects-core/src/plugins/shape/polygon.ts index 34ebe0ac..4dec0c39 100644 --- a/packages/effects-core/src/plugins/shape/polygon.ts +++ b/packages/effects-core/src/plugins/shape/polygon.ts @@ -133,14 +133,14 @@ export class Polygon extends ShapePrimitive { * Get the first X coordinate of the polygon * @readonly */ - override getX (): number { + getX (): number { return this.points[this.points.length - 2]; } /** * Get the first Y coordinate of the polygon * @readonly */ - override getY (): number { + getY (): number { return this.points[this.points.length - 1]; } diff --git a/packages/effects-core/src/plugins/shape/rectangle.ts b/packages/effects-core/src/plugins/shape/rectangle.ts index 6bc14f78..ba2cef8a 100644 --- a/packages/effects-core/src/plugins/shape/rectangle.ts +++ b/packages/effects-core/src/plugins/shape/rectangle.ts @@ -342,11 +342,11 @@ export class Rectangle extends ShapePrimitive { return out; } - override getX (): number { + getX (): number { return this.x; } - override getY (): number { + getY (): number { return this.y; } diff --git a/packages/effects-core/src/plugins/shape/shape-path.ts b/packages/effects-core/src/plugins/shape/shape-path.ts index d7d75479..785b011c 100644 --- a/packages/effects-core/src/plugins/shape/shape-path.ts +++ b/packages/effects-core/src/plugins/shape/shape-path.ts @@ -9,6 +9,8 @@ import { buildAdaptiveBezier } from './build-adaptive-bezier'; import type { GraphicsPath } from './graphics-path'; import type { ShapePrimitive } from './shape-primitive'; import { Ellipse } from './ellipse'; +import type { StarType } from './poly-star'; +import { PolyStar } from './poly-star'; export class ShapePath { currentPoly: Polygon | null = null; @@ -42,6 +44,11 @@ export class ShapePath { case 'ellipse': { this.ellipse(data[0], data[1], data[2], data[3], data[4]); + break; + } + case 'polyStar': { + this.polyStar(data[0], data[1], data[2], data[3], data[4], data[5], data[6]); + break; } } @@ -104,6 +111,12 @@ export class ShapePath { return this; } + polyStar (pointCount: number, outerRadius: number, innerRadius: number, outerRoundness: number, innerRoundness: number, starType: StarType, transform?: Matrix4) { + this.drawShape(new PolyStar(pointCount, outerRadius, innerRadius, outerRoundness, innerRoundness, starType), transform); + + return this; + } + /** * Draws a given shape on the canvas. * This is a generic method that can draw any type of shape specified by the `ShapePrimitive` parameter. diff --git a/packages/effects-core/src/plugins/shape/shape-primitive.ts b/packages/effects-core/src/plugins/shape/shape-primitive.ts index 0fef1682..81b1e5b9 100644 --- a/packages/effects-core/src/plugins/shape/shape-primitive.ts +++ b/packages/effects-core/src/plugins/shape/shape-primitive.ts @@ -1,7 +1,7 @@ export abstract class ShapePrimitive { /** Checks whether the x and y coordinates passed to this function are contained within this ShapePrimitive. */ - abstract contains (x: number, y: number): boolean; + // abstract contains (x: number, y: number): boolean; /** Checks whether the x and y coordinates passed to this function are contained within the stroke of this shape */ // abstract strokeContains (x: number, y: number, strokeWidth: number): boolean; /** Creates a clone of this ShapePrimitive instance. */ @@ -14,9 +14,9 @@ export abstract class ShapePrimitive { // getBounds(out?: Rectangle): Rectangle, /** The X coordinate of the shape */ - abstract getX (): number; + // abstract getX (): number; /** The Y coordinate of the shape */ - abstract getY (): number; + // abstract getY (): number; abstract build (points: number[]): void; diff --git a/packages/effects-core/src/plugins/shape/triangle.ts b/packages/effects-core/src/plugins/shape/triangle.ts index 7073664f..887b260e 100644 --- a/packages/effects-core/src/plugins/shape/triangle.ts +++ b/packages/effects-core/src/plugins/shape/triangle.ts @@ -160,11 +160,11 @@ export class Triangle extends ShapePrimitive { return out; } - override getX (): number { + getX (): number { return this.x; } - override getY (): number { + getY (): number { return this.y; } diff --git a/packages/effects-core/src/shape/geometry.ts b/packages/effects-core/src/shape/geometry.ts index 146ca6a1..d45264ef 100644 --- a/packages/effects-core/src/shape/geometry.ts +++ b/packages/effects-core/src/shape/geometry.ts @@ -23,7 +23,7 @@ export type GeometryFromShape = { }; type ShapeGeometryPre = { p: spec.ShapePoints[1], s: spec.ShapeSplits[1] }; // FIXME: 考虑合并 Shape2D -export type ShapeData = { gs: ShapeGeometryPre[] } | { g: ShapeGeometryPre } | spec.ShapeGeometry; +export type ShapeGeometryData = { gs: ShapeGeometryPre[] } | { g: ShapeGeometryPre } | spec.ShapeGeometry; const POINT_INDEX = 2; @@ -95,7 +95,7 @@ export function getGeometryTriangles (geometry: spec.ShapeGeometry, options: { i * 根据新老版形状数据获取形状几何数据 * @param shape 新老版形状数据 */ -function getGeometriesByShapeData (shape: ShapeData) { +function getGeometriesByShapeData (shape: ShapeGeometryData) { const geometries: spec.ShapeGeometry[] = []; // 该版本的单个形状数据可以包含多个形状,可以加个埋点,五福之后没有就可以下掉 @@ -118,7 +118,7 @@ function getGeometriesByShapeData (shape: ShapeData) { return geometries; } -export function getGeometryByShape (shape: ShapeData, uvTransform?: number[]): GeometryFromShape { +export function getGeometryByShape (shape: ShapeGeometryData, uvTransform?: number[]): GeometryFromShape { const datas = []; // 老数据兼容处理 const geometries = getGeometriesByShapeData(shape); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 8262369a..79a0c376 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -150,8 +150,8 @@ importers: specifier: 1.1.0 version: 1.1.0 '@galacean/effects-specification': - specifier: 2.1.0-alpha.1 - version: 2.1.0-alpha.1 + specifier: 2.1.0-alpha.2 + version: 2.1.0-alpha.2 flatbuffers: specifier: 24.3.25 version: 24.3.25 @@ -2208,8 +2208,8 @@ packages: /@galacean/effects-specification@2.0.0: resolution: {integrity: sha512-7ieZALZYn5fwKLIGAskmYSKGX82ZkLRdDUInG21KsOJ2eQ5+HcI6mJU5tmN8vjZxQUc+RVuUcssExGbiaj2+5w==} - /@galacean/effects-specification@2.1.0-alpha.1: - resolution: {integrity: sha512-kO06tQz8oCvtPVxzA8KRU6OdNENCrzFGqhJ5naUj8Xcs+5nTxWIDEzWFXbBZN8fiHeeOuuUwHCpWkatRZSjQAw==} + /@galacean/effects-specification@2.1.0-alpha.2: + resolution: {integrity: sha512-em2kefqQGPZ1T5OavVECtlSDeWjMaYwCZYcdTsRefhfEVqjXx0gpx9Y6I6jzekpaZbf9Iit07uHreRm2CXfwxQ==} dev: false /@humanwhocodes/config-array@0.11.14: diff --git a/web-packages/demo/src/shape.ts b/web-packages/demo/src/shape.ts index ca69a77e..6427c104 100644 --- a/web-packages/demo/src/shape.ts +++ b/web-packages/demo/src/shape.ts @@ -57,86 +57,88 @@ const json = { 'id': '21135ac68dfc49bcb2bc7552cbb9ad07', }, 'dataType': 'ShapeComponent', - 'type': 0, - 'param': { - 'points': [ - { - 'x': -1, - 'y': -1, - 'z': 0, - }, - { - 'x': 1, - 'y': -1, - 'z': 0, - }, - { - 'x': 0, - 'y': 1, - 'z': 0, - }, - ], - 'easingIn': [ - { - 'x': -1, - 'y': -0.5, - 'z': 0, - }, - { - 'x': 0.5, - 'y': -1.5, - 'z': 0, - }, - { - 'x': 0.5, - 'y': 1, - 'z': 0, - }, - ], - 'easingOut': [ - { - 'x': -0.5, - 'y': -1.5, - 'z': 0, - }, - { - 'x': 1, - 'y': -0.5, - 'z': 0, - }, - { - 'x': -0.5, - 'y': 1, - 'z': 0, - }, - ], - 'shapes': [ - { - 'verticalToPlane': 'z', - 'indexes': [ - { - 'point': 0, - 'easingIn': 0, - 'easingOut': 0, - }, - { - 'point': 1, - 'easingIn': 1, - 'easingOut': 1, - }, - { - 'point': 2, - 'easingIn': 2, - 'easingOut': 2, + 'shapeDatas':[ + { + 'type': 0, + 'points': [ + { + 'x': -1, + 'y': -1, + 'z': 0, + }, + { + 'x': 1, + 'y': -1, + 'z': 0, + }, + { + 'x': 0, + 'y': 1, + 'z': 0, + }, + ], + 'easingIns': [ + { + 'x': -1, + 'y': -0.5, + 'z': 0, + }, + { + 'x': 0.5, + 'y': -1.5, + 'z': 0, + }, + { + 'x': 0.5, + 'y': 1, + 'z': 0, + }, + ], + 'easingOuts': [ + { + 'x': -0.5, + 'y': -1.5, + 'z': 0, + }, + { + 'x': 1, + 'y': -0.5, + 'z': 0, + }, + { + 'x': -0.5, + 'y': 1, + 'z': 0, + }, + ], + 'shapes': [ + { + 'verticalToPlane': 'z', + 'indexes': [ + { + 'point': 0, + 'easingIn': 0, + 'easingOut': 0, + }, + { + 'point': 1, + 'easingIn': 1, + 'easingOut': 1, + }, + { + 'point': 2, + 'easingIn': 2, + 'easingOut': 2, + }, + ], + 'close': true, + 'fill': { + 'color': [8, [255, 0, 0, 255]], }, - ], - 'close': true, - 'fill': { - 'color': [8, [255, 0, 0, 255]], }, - }, - ], - }, + ], + }, + ], 'renderer': { 'renderMode': 1, }, diff --git a/web-packages/imgui-demo/src/ge.ts b/web-packages/imgui-demo/src/ge.ts index 416f2ae6..c56ed93a 100644 --- a/web-packages/imgui-demo/src/ge.ts +++ b/web-packages/imgui-demo/src/ge.ts @@ -300,8 +300,8 @@ export class GalaceanEffects { 'id': '21135ac68dfc49bcb2bc7552cbb9ad07', }, 'dataType': 'ShapeComponent', - 'type': 0, - 'param': { + 'shapeDatas':[{ + 'type': 0, 'points': [ { 'x': -1, @@ -319,7 +319,7 @@ export class GalaceanEffects { 'z': 0, }, ], - 'easingIn': [ + 'easingIns': [ { 'x': -1, 'y': -0.5, @@ -336,7 +336,7 @@ export class GalaceanEffects { 'z': 0, }, ], - 'easingOut': [ + 'easingOuts': [ { 'x': -0.5, 'y': -1.5, @@ -380,6 +380,7 @@ export class GalaceanEffects { }, ], }, + ], 'renderer': { 'renderMode': 1, }, From cc89d0b49eea1b0f2a3665f5cef7e8957fb68f48 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=8C=82=E5=AE=89?= <151805118+wumaolinmaoan@users.noreply.github.com> Date: Thu, 31 Oct 2024 10:51:50 +0800 Subject: [PATCH 57/88] fix: on end called too early (#711) * fix: on end called too early * refactor: remove unused end filed --- packages/effects-core/src/comp-vfx-item.ts | 5 +-- packages/effects-core/src/composition.ts | 34 +++++++++++---- .../src/plugins/interact/interact-item.ts | 42 +++++++++---------- .../src/plugins/timeline/track.ts | 8 ---- packages/effects-core/src/vfx-item.ts | 23 ---------- packages/effects/src/player.ts | 2 +- .../plugins/common/end-behevior.spec.ts | 8 ++-- 7 files changed, 53 insertions(+), 69 deletions(-) diff --git a/packages/effects-core/src/comp-vfx-item.ts b/packages/effects-core/src/comp-vfx-item.ts index 10b9580b..c3300e7f 100644 --- a/packages/effects-core/src/comp-vfx-item.ts +++ b/packages/effects-core/src/comp-vfx-item.ts @@ -5,7 +5,7 @@ import * as spec from '@galacean/effects-specification'; import { Behaviour } from './components'; import type { CompositionHitTestOptions } from './composition'; import type { Region, TrackAsset } from './plugins'; -import { HitTestType, ObjectBindingTrack } from './plugins'; +import { HitTestType } from './plugins'; import type { Playable } from './plugins/cal/playable-graph'; import { PlayableGraph } from './plugins/cal/playable-graph'; import { TimelineAsset } from './plugins/timeline'; @@ -55,9 +55,6 @@ export class CompositionComponent extends Behaviour { const boundObject = track.boundObject; if (boundObject instanceof VFXItem) { - if (track instanceof ObjectBindingTrack) { - boundObject.reusable = value; - } const subCompositionComponent = boundObject.getComponent(CompositionComponent); if (subCompositionComponent) { diff --git a/packages/effects-core/src/composition.ts b/packages/effects-core/src/composition.ts index 8672aa37..12c8cfd5 100644 --- a/packages/effects-core/src/composition.ts +++ b/packages/effects-core/src/composition.ts @@ -116,6 +116,12 @@ export class Composition extends EventEmitter> imp * @since 1.6.0 */ interactive: boolean; + + /** + * 合成是否结束 + */ + isEnded = false; + compositionSourceManager: CompositionSourceManager; /** * 合成id @@ -201,6 +207,8 @@ export class Composition extends EventEmitter> imp */ private paused = false; private lastVideoUpdateTime = 0; + private isEndCalled = false; + private readonly texInfo: Record; /** * 合成中消息元素创建/销毁时触发的回调 @@ -393,7 +401,7 @@ export class Composition extends EventEmitter> imp } play () { - if (this.rootItem.ended && this.reusable) { + if (this.isEnded && this.reusable) { this.restart(); } if (this.rootComposition.isStartCalled) { @@ -504,7 +512,8 @@ export class Composition extends EventEmitter> imp */ protected reset () { this.rendererOptions = null; - this.rootItem.ended = false; + this.isEnded = false; + this.isEndCalled = false; this.rootComposition.time = 0; this.pluginSystem.resetComposition(this, this.renderFrame); } @@ -564,13 +573,17 @@ export class Composition extends EventEmitter> imp this.updateCamera(); this.prepareRender(); + if (this.isEnded && !this.isEndCalled) { + this.isEndCalled = true; + this.emit('end', { composition: this }); + } if (this.shouldDispose()) { this.dispose(); } } private shouldDispose () { - return this.rootItem.ended && this.rootItem.endBehavior === spec.EndBehavior.destroy && !this.reusable; + return this.isEnded && this.rootItem.endBehavior === spec.EndBehavior.destroy && !this.reusable; } private getUpdateTime (t: number) { @@ -671,14 +684,14 @@ export class Composition extends EventEmitter> imp if (this.rootComposition.isActiveAndEnabled) { let localTime = this.time + deltaTime - this.rootItem.start; - let ended = false; + let isEnded = false; const duration = this.rootItem.duration; const endBehavior = this.rootItem.endBehavior; if (localTime - duration > 0.001) { - ended = true; + isEnded = true; switch (endBehavior) { case spec.EndBehavior.restart: { @@ -705,9 +718,14 @@ export class Composition extends EventEmitter> imp this.rootComposition.time = localTime; - if (ended && !this.rootItem.ended) { - this.rootItem.ended = true; - this.emit('end', { composition: this }); + // end state changed, handle onEnd flags + if (this.isEnded !== isEnded) { + if (isEnded) { + this.isEnded = true; + } else { + this.isEnded = false; + this.isEndCalled = false; + } } } } diff --git a/packages/effects-core/src/plugins/interact/interact-item.ts b/packages/effects-core/src/plugins/interact/interact-item.ts index 2420f03d..0ab488ba 100644 --- a/packages/effects-core/src/plugins/interact/interact-item.ts +++ b/packages/effects-core/src/plugins/interact/interact-item.ts @@ -44,7 +44,6 @@ export class InteractComponent extends RendererComponent { /** 是否响应点击和拖拽交互事件 */ private _interactive = true; - private hasBeenAddedToComposition = false; set interactive (enable: boolean) { this._interactive = enable; @@ -100,34 +99,35 @@ export class InteractComponent extends RendererComponent { this.materials = this.previewContent.mesh.materials; } this.item.getHitTestParams = this.getHitTestParams; - this.item.onEnd = () => { - if (this.item && this.item.composition) { - this.item.composition.removeInteractiveItem(this.item, (this.item.props as spec.InteractItem).content.options.type); - this.clickable = false; - this.hasBeenAddedToComposition = false; - this.previewContent?.mesh.dispose(); - this.endDragTarget(); - } - }; } - override onUpdate (dt: number): void { - if (!this.isActiveAndEnabled) { - return; + override onDisable (): void { + if (this.item && this.item.composition) { + this.item.composition.removeInteractiveItem(this.item, (this.item.props as spec.InteractItem).content.options.type); + this.clickable = false; + this.previewContent?.mesh.dispose(); + this.endDragTarget(); } - this.previewContent?.updateMesh(); - if (!this.hasBeenAddedToComposition && this.item.composition) { + } - const { type } = this.interactData.options as spec.ClickInteractOption; + override onEnable (): void { + const { type } = this.interactData.options as spec.ClickInteractOption; - if (type === spec.InteractType.CLICK) { - this.clickable = true; - } - const options = this.item.props.content.options as spec.DragInteractOption; + if (type === spec.InteractType.CLICK) { + this.clickable = true; + } + const options = this.item.props.content.options as spec.DragInteractOption; + if (this.item.composition) { this.item.composition.addInteractiveItem(this.item, options.type); - this.hasBeenAddedToComposition = true; } + } + + override onUpdate (dt: number): void { + if (!this.isActiveAndEnabled) { + return; + } + this.previewContent?.updateMesh(); if (!this.dragEvent || !this.bouncingArg) { return; diff --git a/packages/effects-core/src/plugins/timeline/track.ts b/packages/effects-core/src/plugins/timeline/track.ts index 0597bc80..2dbd9e2b 100644 --- a/packages/effects-core/src/plugins/timeline/track.ts +++ b/packages/effects-core/src/plugins/timeline/track.ts @@ -232,14 +232,6 @@ export class RuntimeClip { // 判断动画是否结束 if (ended) { - if (boundObject instanceof VFXItem && !boundObject.ended) { - boundObject.ended = true; - boundObject.onEnd(); - if (!boundObject.compositionReusable && !boundObject.reusable) { - boundObject.dispose(); - this.playable.dispose(); - } - } if (this.playable.getPlayState() === PlayState.Playing) { this.playable.pause(); } diff --git a/packages/effects-core/src/vfx-item.ts b/packages/effects-core/src/vfx-item.ts index 6e5d4860..3a0f93aa 100644 --- a/packages/effects-core/src/vfx-item.ts +++ b/packages/effects-core/src/vfx-item.ts @@ -76,10 +76,6 @@ export class VFXItem extends EffectsObject implements Disposable { * 元素动画结束时行为(如何处理元素) */ endBehavior: spec.EndBehavior = spec.EndBehavior.forward; - /** - * 元素是否可用 - */ - ended = false; /** * 元素名称 */ @@ -93,7 +89,6 @@ export class VFXItem extends EffectsObject implements Disposable { * 元素创建的数据图层/粒子/模型等 */ _content?: VFXItemContent; - reusable = false; type: spec.ItemType = spec.ItemType.base; props: VFXItemProps; isDuringPlay = false; @@ -384,14 +379,6 @@ export class VFXItem extends EffectsObject implements Disposable { } } - /** - * 元素动画结束播放时回调函数 - * @override - */ - onEnd () { - // OVERRIDE - } - /** * 通过指定 r、g、b、a 值设置元素的颜色 * @param {number} r @@ -533,16 +520,6 @@ export class VFXItem extends EffectsObject implements Disposable { return pos; } - /** - * 是否到达元素的结束时间 - * @param now - * @returns - */ - isEnded (now: number) { - // at least 1 ms - return now - this.duration > 0.001; - } - find (name: string): VFXItem | undefined { if (this.name === name) { return this; diff --git a/packages/effects/src/player.ts b/packages/effects/src/player.ts index 5e7b7296..281de9ff 100644 --- a/packages/effects/src/player.ts +++ b/packages/effects/src/player.ts @@ -776,7 +776,7 @@ export class Player extends EventEmitter> implements Disposa await video.play(); } } - newComposition.rootItem.ended = false; + newComposition.isEnded = false; newComposition.gotoAndPlay(currentTime); return newComposition; diff --git a/web-packages/test/unit/src/effects-core/plugins/common/end-behevior.spec.ts b/web-packages/test/unit/src/effects-core/plugins/common/end-behevior.spec.ts index 964da73d..de9fe980 100644 --- a/web-packages/test/unit/src/effects-core/plugins/common/end-behevior.spec.ts +++ b/web-packages/test/unit/src/effects-core/plugins/common/end-behevior.spec.ts @@ -28,9 +28,9 @@ describe('core/plugins/common/item-end', () => { const item = composition.getItemByName('sprite_1'); composition.gotoAndStop(0.01 + 1); - expect(item?.ended).to.equal(false); + expect(item?.transform.getValid()).to.equal(true); composition.gotoAndStop(0.01 + 2); - expect(item?.ended).to.equal(true); + expect(item?.transform.getValid()).to.equal(false); }); it('item freeze', async () => { @@ -38,7 +38,7 @@ describe('core/plugins/common/item-end', () => { const item = composition.getItemByName('sprite_1'); composition.gotoAndStop(0.01 + 2); - expect(item?.ended).to.equal(false); + expect(item?.transform.getValid()).to.equal(true); }); it('item loop', async () => { @@ -46,7 +46,7 @@ describe('core/plugins/common/item-end', () => { const item = composition.getItemByName('sprite_1'); composition.gotoAndStop(1); - expect(item?.ended).to.equal(false); + expect(item?.transform.getValid()).to.equal(true); }); }); From 237482a7645c97188bf2f13b74077323c3f0adc3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=8C=82=E5=AE=89?= <151805118+wumaolinmaoan@users.noreply.github.com> Date: Thu, 31 Oct 2024 14:25:11 +0800 Subject: [PATCH 58/88] refactor: opt shader variant create logic (#712) * refactor: opt shader variant create logic * chore: opt shader setting check --- .../effects-core/src/material/material.ts | 16 ++++++++++- packages/effects-webgl/src/gl-material.ts | 27 ++++++++++--------- 2 files changed, 29 insertions(+), 14 deletions(-) diff --git a/packages/effects-core/src/material/material.ts b/packages/effects-core/src/material/material.ts index a057118f..ef27ad27 100644 --- a/packages/effects-core/src/material/material.ts +++ b/packages/effects-core/src/material/material.ts @@ -62,7 +62,6 @@ let seed = 1; * Material 抽象类 */ export abstract class Material extends EffectsObject implements Disposable { - shader: Shader; shaderVariant: ShaderVariant; // TODO: 待移除 @@ -76,6 +75,9 @@ export abstract class Material extends EffectsObject implements Disposable { protected destroyed = false; protected initialized = false; + protected shaderDirty = true; + + private _shader: Shader; /** * @@ -106,6 +108,18 @@ export abstract class Material extends EffectsObject implements Disposable { } } + get shader () { + return this._shader; + } + + set shader (value: Shader) { + if (this._shader === value) { + return; + } + this._shader = value; + this.shaderDirty = true; + } + /******** effects-core 中会调用 引擎必须实现 ***********************/ /** * 设置 Material 的颜色融合开关 diff --git a/packages/effects-webgl/src/gl-material.ts b/packages/effects-webgl/src/gl-material.ts index 70e8cdc8..c53d2a0f 100644 --- a/packages/effects-webgl/src/gl-material.ts +++ b/packages/effects-webgl/src/gl-material.ts @@ -41,8 +41,8 @@ export class GLMaterial extends Material { private samplers: string[] = []; // material存放的sampler名称。 private uniforms: string[] = []; // material存放的uniform名称(不包括sampler)。 - private uniformDirtyFlag = true; - private macrosDirtyFlag = true; + private uniformDirty = true; + private macrosDirty = true; private glMaterialState = new GLMaterialState(); constructor ( @@ -217,14 +217,14 @@ export class GLMaterial extends Material { override enableMacro (keyword: string, value?: boolean | number): void { if (!this.isMacroEnabled(keyword) || this.enabledMacros[keyword] !== value) { this.enabledMacros[keyword] = value ?? true; - this.macrosDirtyFlag = true; + this.macrosDirty = true; } } override disableMacro (keyword: string): void { if (this.isMacroEnabled(keyword)) { delete this.enabledMacros[keyword]; - this.macrosDirtyFlag = true; + this.macrosDirty = true; } } @@ -275,10 +275,11 @@ export class GLMaterial extends Material { } override createShaderVariant () { - if (!this.shaderVariant || this.shaderVariant.shader !== this.shader || this.macrosDirtyFlag) { + if (this.shaderDirty || this.macrosDirty) { this.shaderVariant = this.shader.createVariant(this.enabledMacros); - this.macrosDirtyFlag = false; - this.uniformDirtyFlag = true; + this.macrosDirty = false; + this.shaderDirty = false; + this.uniformDirty = true; } } @@ -308,15 +309,15 @@ export class GLMaterial extends Material { for (name of globalUniforms.samplers) { if (!this.samplers.includes(name)) { this.samplers.push(name); - this.uniformDirtyFlag = true; + this.uniformDirty = true; } } } // 更新 cached uniform location - if (this.uniformDirtyFlag) { + if (this.uniformDirty) { shaderVariant.fillShaderInformation(this.uniforms, this.samplers); - this.uniformDirtyFlag = false; + this.uniformDirty = false; } if (globalUniforms) { @@ -493,7 +494,7 @@ export class GLMaterial extends Material { setTexture (name: string, texture: Texture) { if (!this.samplers.includes(name)) { this.samplers.push(name); - this.uniformDirtyFlag = true; + this.uniformDirty = true; } this.textures[name] = texture; } @@ -525,7 +526,7 @@ export class GLMaterial extends Material { clonedMaterial.matrixArrays = this.matrixArrays; clonedMaterial.samplers = this.samplers; clonedMaterial.uniforms = this.uniforms; - clonedMaterial.uniformDirtyFlag = true; + clonedMaterial.uniformDirty = true; return clonedMaterial; } @@ -723,7 +724,7 @@ export class GLMaterial extends Material { private checkUniform (uniformName: string): void { if (!this.uniforms.includes(uniformName)) { this.uniforms.push(uniformName); - this.uniformDirtyFlag = true; + this.uniformDirty = true; } } From 7e62aa2daeb2bf6a791f7055c609599271a8b69f Mon Sep 17 00:00:00 2001 From: wumaolinmaoan Date: Fri, 1 Nov 2024 13:51:26 +0800 Subject: [PATCH 59/88] feat: property clip use normalized time --- packages/effects-core/src/plugins/cal/playable-graph.ts | 9 +++++++++ .../plugins/timeline/playables/property-clip-playable.ts | 2 +- packages/effects-core/src/plugins/timeline/track.ts | 2 ++ 3 files changed, 12 insertions(+), 1 deletion(-) diff --git a/packages/effects-core/src/plugins/cal/playable-graph.ts b/packages/effects-core/src/plugins/cal/playable-graph.ts index fc0879f3..6e66e5b3 100644 --- a/packages/effects-core/src/plugins/cal/playable-graph.ts +++ b/packages/effects-core/src/plugins/cal/playable-graph.ts @@ -67,6 +67,7 @@ export class Playable implements Disposable { onPlayablePlayFlag = true; onPlayablePauseFlag = false; + private duration = 0; private destroyed = false; private inputs: Playable[] = []; private inputOuputPorts: number[] = []; @@ -185,6 +186,14 @@ export class Playable implements Disposable { return this.time; } + setDuration (duration: number) { + this.duration = duration; + } + + getDuration () { + return this.duration; + } + getPlayState () { return this.playState; } diff --git a/packages/effects-core/src/plugins/timeline/playables/property-clip-playable.ts b/packages/effects-core/src/plugins/timeline/playables/property-clip-playable.ts index fd467a3b..305bf6f9 100644 --- a/packages/effects-core/src/plugins/timeline/playables/property-clip-playable.ts +++ b/packages/effects-core/src/plugins/timeline/playables/property-clip-playable.ts @@ -7,6 +7,6 @@ export class PropertyClipPlayable extends Playable { curve: ValueGetter; override processFrame (context: FrameContext): void { - this.value = this.curve.getValue(this.time); + this.value = this.curve.getValue(this.time / this.getDuration()); } } \ No newline at end of file diff --git a/packages/effects-core/src/plugins/timeline/track.ts b/packages/effects-core/src/plugins/timeline/track.ts index 2dbd9e2b..a4ffdad0 100644 --- a/packages/effects-core/src/plugins/timeline/track.ts +++ b/packages/effects-core/src/plugins/timeline/track.ts @@ -99,6 +99,8 @@ export class TrackAsset extends PlayableAsset { for (const timelineClip of timelineClips) { const clipPlayable = this.createClipPlayable(graph, timelineClip); + clipPlayable.setDuration(timelineClip.duration); + const clip = new RuntimeClip(timelineClip, clipPlayable, mixer, this); runtimeClips.push(clip); From 506f302ef6d92bba86d21ad71263710d98ff3967 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=8C=82=E5=AE=89?= <151805118+wumaolinmaoan@users.noreply.github.com> Date: Mon, 4 Nov 2024 10:01:43 +0800 Subject: [PATCH 60/88] feat: shape component support fill color (#715) * refactor: remove shape data array * feat: support fill color * chore: opt code * Update web-packages/demo/src/shape.ts Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> * chore: opt code --------- Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- .../src/components/shape-component.ts | 162 +++++++++-------- web-packages/demo/src/shape.ts | 140 +++++++-------- web-packages/imgui-demo/src/ge.ts | 169 +++++++++--------- 3 files changed, 239 insertions(+), 232 deletions(-) diff --git a/packages/effects-core/src/components/shape-component.ts b/packages/effects-core/src/components/shape-component.ts index 396b2799..55c098d9 100644 --- a/packages/effects-core/src/components/shape-component.ts +++ b/packages/effects-core/src/components/shape-component.ts @@ -175,79 +175,97 @@ void main() { private buildPath (data: ShapeComponentData) { this.path.clear(); - for (const shapeData of data.shapeDatas) { - switch (shapeData.type) { - case ShapeType.Custom: { - const customData = shapeData as CustomShapeData; - const points = customData.points; - const easingIns = customData.easingIns; - const easingOuts = customData.easingOuts; - - this.curveValues = []; - - for (const shape of customData.shapes) { - const indices = shape.indexes; - - for (let i = 1; i < indices.length; i++) { - const pointIndex = indices[i]; - const lastPointIndex = indices[i - 1]; - - this.curveValues.push({ - point: points[pointIndex.point], - controlPoint1: easingOuts[lastPointIndex.easingOut], - controlPoint2: easingIns[pointIndex.easingIn], - }); - } - - // Push the last curve + const shapeData = data; + + switch (shapeData.type) { + case ShapePrimitiveType.Custom: { + const customData = shapeData as CustomShapeData; + const points = customData.points; + const easingIns = customData.easingIns; + const easingOuts = customData.easingOuts; + + this.curveValues = []; + + for (const shape of customData.shapes) { + this.setFillColor(shape.fill); + + const indices = shape.indexes; + + for (let i = 1; i < indices.length; i++) { + const pointIndex = indices[i]; + const lastPointIndex = indices[i - 1]; + this.curveValues.push({ - point: points[indices[0].point], - controlPoint1: easingOuts[indices[indices.length - 1].easingOut], - controlPoint2: easingIns[indices[0].easingIn], + point: points[pointIndex.point], + controlPoint1: easingOuts[lastPointIndex.easingOut], + controlPoint2: easingIns[pointIndex.easingIn], }); } - this.path.moveTo(this.curveValues[this.curveValues.length - 1].point.x, this.curveValues[this.curveValues.length - 1].point.y); + // Push the last curve + this.curveValues.push({ + point: points[indices[0].point], + controlPoint1: easingOuts[indices[indices.length - 1].easingOut], + controlPoint2: easingIns[indices[0].easingIn], + }); + } - for (const curveValue of this.curveValues) { - const point = curveValue.point; - const control1 = curveValue.controlPoint1; - const control2 = curveValue.controlPoint2; + this.path.moveTo(this.curveValues[this.curveValues.length - 1].point.x, this.curveValues[this.curveValues.length - 1].point.y); - this.path.bezierCurveTo(control1.x, control1.y, control2.x, control2.y, point.x, point.y, 1); - } + for (const curveValue of this.curveValues) { + const point = curveValue.point; + const control1 = curveValue.controlPoint1; + const control2 = curveValue.controlPoint2; - break; + this.path.bezierCurveTo(control1.x, control1.y, control2.x, control2.y, point.x, point.y, 1); } - case ShapeType.Ellipse: { - const ellipseData = shapeData as EllipseData; - this.path.ellipse(0, 0, ellipseData.xRadius, ellipseData.yRadius); + break; + } + case ShapePrimitiveType.Ellipse: { + const ellipseData = shapeData as EllipseData; - break; - } - case ShapeType.Rectangle: { - const rectangleData = shapeData as RectangleData; + this.path.ellipse(0, 0, ellipseData.xRadius, ellipseData.yRadius); - this.path.rect(-rectangleData.width / 2, rectangleData.height / 2, rectangleData.width, rectangleData.height); + this.setFillColor(ellipseData.fill); - break; - } - case ShapeType.Star: { - const starData = shapeData as StarData; + break; + } + case ShapePrimitiveType.Rectangle: { + const rectangleData = shapeData as RectangleData; - this.path.polyStar(starData.pointCount, starData.outerRadius, starData.innerRadius, starData.outerRoundness, starData.innerRoundness, StarType.Star); + this.path.rect(-rectangleData.width / 2, rectangleData.height / 2, rectangleData.width, rectangleData.height); - break; - } - case ShapeType.Polygon: { - const polygonData = shapeData as PolygonData; + this.setFillColor(rectangleData.fill); - this.path.polyStar(polygonData.pointCount, polygonData.radius, polygonData.radius, polygonData.roundness, polygonData.roundness, StarType.Polygon); + break; + } + case ShapePrimitiveType.Star: { + const starData = shapeData as StarData; - break; - } + this.path.polyStar(starData.pointCount, starData.outerRadius, starData.innerRadius, starData.outerRoundness, starData.innerRoundness, StarType.Star); + + this.setFillColor(starData.fill); + + break; } + case ShapePrimitiveType.Polygon: { + const polygonData = shapeData as PolygonData; + + this.path.polyStar(polygonData.pointCount, polygonData.radius, polygonData.radius, polygonData.roundness, polygonData.roundness, StarType.Polygon); + + this.setFillColor(polygonData.fill); + + break; + } + } + } + + private setFillColor (fill?: ShapeFillParam) { + if (fill) { + const color = fill.color; + + this.material.setColor('_Color', new Color(color.r, color.g, color.b, color.a)); } } @@ -270,13 +288,16 @@ void main() { * 矢量图形组件 */ export interface ShapeComponentData extends spec.ComponentData { - shapeDatas: ShapeData[], + /** + * 矢量类型 + */ + type: ShapePrimitiveType, } /** * 矢量图形类型 */ -export enum ShapeType { +export enum ShapePrimitiveType { /** * 自定义图形 */ @@ -299,21 +320,14 @@ export enum ShapeType { Star, } -export class ShapeData { - /** - * 矢量类型 - */ - type: ShapeType; -} - /** * 自定义图形组件 */ -export interface CustomShapeData extends ShapeData { +export interface CustomShapeData extends ShapeComponentData { /** * 矢量类型 - 形状 */ - type: ShapeType.Custom, + type: ShapePrimitiveType.Custom, /** * 路径点 */ @@ -387,7 +401,7 @@ export interface ShapeFillParam { /** * 填充颜色 */ - color: spec.ColorExpression, + color: spec.ColorData, } /** @@ -401,7 +415,7 @@ export interface ShapeStrokeParam { /** * 线颜色 */ - color: spec.ColorExpression, + color: spec.ColorData, /** * 连接类型 */ @@ -424,8 +438,8 @@ export enum ShapePointType { /** * 椭圆组件参数 */ -export interface EllipseData extends ShapeData { - type: ShapeType.Ellipse, +export interface EllipseData extends ShapeComponentData { + type: ShapePrimitiveType.Ellipse, /** * x 轴半径 * -- TODO 后续完善类型 @@ -453,7 +467,7 @@ export interface EllipseData extends ShapeData { /** * 星形参数 */ -export interface StarData extends ShapeData { +export interface StarData extends ShapeComponentData { /** * 顶点数 - 内外顶点同数 */ @@ -491,7 +505,7 @@ export interface StarData extends ShapeData { /** * 多边形参数 */ -export interface PolygonData extends ShapeData { +export interface PolygonData extends ShapeComponentData { /** * 顶点数 */ @@ -521,7 +535,7 @@ export interface PolygonData extends ShapeData { /** * 矩形参数 */ -export interface RectangleData extends ShapeData { +export interface RectangleData extends ShapeComponentData { /** * 宽度 */ diff --git a/web-packages/demo/src/shape.ts b/web-packages/demo/src/shape.ts index 6427c104..cedc08fc 100644 --- a/web-packages/demo/src/shape.ts +++ b/web-packages/demo/src/shape.ts @@ -57,86 +57,82 @@ const json = { 'id': '21135ac68dfc49bcb2bc7552cbb9ad07', }, 'dataType': 'ShapeComponent', - 'shapeDatas':[ + 'type': 0, + 'points': [ { - 'type': 0, - 'points': [ - { - 'x': -1, - 'y': -1, - 'z': 0, - }, - { - 'x': 1, - 'y': -1, - 'z': 0, - }, - { - 'x': 0, - 'y': 1, - 'z': 0, - }, - ], - 'easingIns': [ - { - 'x': -1, - 'y': -0.5, - 'z': 0, - }, - { - 'x': 0.5, - 'y': -1.5, - 'z': 0, - }, - { - 'x': 0.5, - 'y': 1, - 'z': 0, - }, - ], - 'easingOuts': [ - { - 'x': -0.5, - 'y': -1.5, - 'z': 0, - }, + 'x': -1, + 'y': -1, + 'z': 0, + }, + { + 'x': 1, + 'y': -1, + 'z': 0, + }, + { + 'x': 0, + 'y': 1, + 'z': 0, + }, + ], + 'easingIns': [ + { + 'x': -1, + 'y': -0.5, + 'z': 0, + }, + { + 'x': 0.5, + 'y': -1.5, + 'z': 0, + }, + { + 'x': 0.5, + 'y': 1, + 'z': 0, + }, + ], + 'easingOuts': [ + { + 'x': -0.5, + 'y': -1.5, + 'z': 0, + }, + { + 'x': 1, + 'y': -0.5, + 'z': 0, + }, + { + 'x': -0.5, + 'y': 1, + 'z': 0, + }, + ], + 'shapes': [ + { + 'verticalToPlane': 'z', + 'indexes': [ { - 'x': 1, - 'y': -0.5, - 'z': 0, + 'point': 0, + 'easingIn': 0, + 'easingOut': 0, }, { - 'x': -0.5, - 'y': 1, - 'z': 0, + 'point': 1, + 'easingIn': 1, + 'easingOut': 1, }, - ], - 'shapes': [ { - 'verticalToPlane': 'z', - 'indexes': [ - { - 'point': 0, - 'easingIn': 0, - 'easingOut': 0, - }, - { - 'point': 1, - 'easingIn': 1, - 'easingOut': 1, - }, - { - 'point': 2, - 'easingIn': 2, - 'easingOut': 2, - }, - ], - 'close': true, - 'fill': { - 'color': [8, [255, 0, 0, 255]], - }, + 'point': 2, + 'easingIn': 2, + 'easingOut': 2, }, ], + 'close': true, + 'fill': { + 'color': { 'r':1, 'g':0.7, 'b':0.5, 'a':1 }, + }, }, ], 'renderer': { diff --git a/web-packages/imgui-demo/src/ge.ts b/web-packages/imgui-demo/src/ge.ts index c56ed93a..bb035cef 100644 --- a/web-packages/imgui-demo/src/ge.ts +++ b/web-packages/imgui-demo/src/ge.ts @@ -130,7 +130,7 @@ export class GalaceanEffects { 'floats': { '_Speed': 1, }, - 'stringTags':{}, + 'stringTags': {}, 'vector4s': {}, 'textures': { '_MainTex': { @@ -300,86 +300,83 @@ export class GalaceanEffects { 'id': '21135ac68dfc49bcb2bc7552cbb9ad07', }, 'dataType': 'ShapeComponent', - 'shapeDatas':[{ - 'type': 0, - 'points': [ - { - 'x': -1, - 'y': -1, - 'z': 0, - }, - { - 'x': 1, - 'y': -1, - 'z': 0, - }, - { - 'x': 0, - 'y': 1, - 'z': 0, - }, - ], - 'easingIns': [ - { - 'x': -1, - 'y': -0.5, - 'z': 0, - }, - { - 'x': 0.5, - 'y': -1.5, - 'z': 0, - }, - { - 'x': 0.5, - 'y': 1, - 'z': 0, - }, - ], - 'easingOuts': [ - { - 'x': -0.5, - 'y': -1.5, - 'z': 0, - }, - { - 'x': 1, - 'y': -0.5, - 'z': 0, - }, - { - 'x': -0.5, - 'y': 1, - 'z': 0, - }, - ], - 'shapes': [ - { - 'verticalToPlane': 'z', - 'indexes': [ - { - 'point': 0, - 'easingIn': 0, - 'easingOut': 0, - }, - { - 'point': 1, - 'easingIn': 1, - 'easingOut': 1, - }, - { - 'point': 2, - 'easingIn': 2, - 'easingOut': 2, - }, - ], - 'close': true, - 'fill': { - 'color': [8, [255, 0, 0, 255]], + 'type': 0, + 'points': [ + { + 'x': -1, + 'y': -1, + 'z': 0, + }, + { + 'x': 1, + 'y': -1, + 'z': 0, + }, + { + 'x': 0, + 'y': 1, + 'z': 0, + }, + ], + 'easingIns': [ + { + 'x': -1, + 'y': -0.5, + 'z': 0, + }, + { + 'x': 0.5, + 'y': -1.5, + 'z': 0, + }, + { + 'x': 0.5, + 'y': 1, + 'z': 0, + }, + ], + 'easingOuts': [ + { + 'x': -0.5, + 'y': -1.5, + 'z': 0, + }, + { + 'x': 1, + 'y': -0.5, + 'z': 0, + }, + { + 'x': -0.5, + 'y': 1, + 'z': 0, + }, + ], + 'shapes': [ + { + 'verticalToPlane': 'z', + 'indexes': [ + { + 'point': 0, + 'easingIn': 0, + 'easingOut': 0, + }, + { + 'point': 1, + 'easingIn': 1, + 'easingOut': 1, }, + { + 'point': 2, + 'easingIn': 2, + 'easingOut': 2, + }, + ], + 'close': true, + 'fill': { + 'color': { 'r': 1, 'g': 0.7, 'b': 0.5, 'a': 1 }, }, - ], - }, + }, ], 'renderer': { 'renderMode': 1, @@ -504,8 +501,8 @@ export class GalaceanEffects { if (use3DConverter) { const converter = new JSONConverter(GalaceanEffects.player.renderer); - void converter.processScene(url).then(async (scene: any) =>{ - const composition = await GalaceanEffects.player.loadScene(scene, { autoplay:true }); + void converter.processScene(url).then(async (scene: any) => { + const composition = await GalaceanEffects.player.loadScene(scene, { autoplay: true }); composition.renderFrame.addRenderPass(new OutlinePass(composition.renderer, { name: 'OutlinePass', @@ -514,7 +511,7 @@ export class GalaceanEffects { }),); }); } else { - void GalaceanEffects.player.loadScene(url, { autoplay:true }).then(composition=>{ + void GalaceanEffects.player.loadScene(url, { autoplay: true }).then(composition => { composition.postProcessingEnabled = true; composition.createRenderFrame(); composition.rootItem.addComponent(PostProcessVolume); @@ -604,16 +601,16 @@ export class OutlinePass extends RenderPass { ]), }, }, - mode:glContext.LINE_LOOP, - drawCount:6, + mode: glContext.LINE_LOOP, + drawCount: 6, }); } if (!this.material) { const materialProps: MaterialProps = { shader: { - vertex:this.vert, - fragment:this.frag, + vertex: this.vert, + fragment: this.frag, glslVersion: GLSLVersion.GLSL1, }, }; From 2fdfbc6da6bbd0470996dcb106bba49b7ad5549f Mon Sep 17 00:00:00 2001 From: Sruim <934274351@qq.com> Date: Mon, 4 Nov 2024 11:46:24 +0800 Subject: [PATCH 61/88] feat: add autoplay permission check in audio and video loaders (#713) * feat: add autoplay permission check in audio and video loaders * fix: handle autoplay permission check errors in audio and video loaders * chore: env constants --------- Co-authored-by: yiiqii --- .../multimedia/src/audio/audio-loader.ts | 20 ++++++++++++++++--- .../multimedia/src/video/video-loader.ts | 20 ++++++++++++++++--- 2 files changed, 34 insertions(+), 6 deletions(-) diff --git a/plugin-packages/multimedia/src/audio/audio-loader.ts b/plugin-packages/multimedia/src/audio/audio-loader.ts index 2da4d3d5..af517339 100644 --- a/plugin-packages/multimedia/src/audio/audio-loader.ts +++ b/plugin-packages/multimedia/src/audio/audio-loader.ts @@ -1,11 +1,25 @@ -import type { SceneLoadOptions } from '@galacean/effects'; -import { spec, AbstractPlugin } from '@galacean/effects'; -import { processMultimedia } from '../utils'; +import type { PrecompileOptions, Renderer, SceneLoadOptions } from '@galacean/effects'; +import { spec, AbstractPlugin, PLAYER_OPTIONS_ENV_EDITOR } from '@galacean/effects'; +import { checkAutoplayPermission, processMultimedia } from '../utils'; /** * 音频加载插件 */ export class AudioLoader extends AbstractPlugin { + + static override precompile (compositions: spec.Composition[], renderer: Renderer, options?: PrecompileOptions): Promise { + const engine = renderer.engine; + const { env } = options ?? { env: '' }; + + if (env === PLAYER_OPTIONS_ENV_EDITOR) { + return checkAutoplayPermission().catch(error => { + engine.renderErrors.add(error); + }); + } + + return Promise.resolve(); + } + static override async processAssets ( json: spec.JSONScene, options: SceneLoadOptions = {}, diff --git a/plugin-packages/multimedia/src/video/video-loader.ts b/plugin-packages/multimedia/src/video/video-loader.ts index 46738d87..bfb1e76c 100644 --- a/plugin-packages/multimedia/src/video/video-loader.ts +++ b/plugin-packages/multimedia/src/video/video-loader.ts @@ -1,11 +1,25 @@ -import type { SceneLoadOptions } from '@galacean/effects'; -import { spec, AbstractPlugin } from '@galacean/effects'; -import { processMultimedia } from '../utils'; +import type { PrecompileOptions, Renderer, SceneLoadOptions } from '@galacean/effects'; +import { spec, AbstractPlugin, PLAYER_OPTIONS_ENV_EDITOR } from '@galacean/effects'; +import { checkAutoplayPermission, processMultimedia } from '../utils'; /** * 视频加载插件 */ export class VideoLoader extends AbstractPlugin { + + static override precompile (compositions: spec.Composition[], renderer: Renderer, options?: PrecompileOptions): Promise { + const engine = renderer.engine; + const { env } = options ?? { env: '' }; + + if (env === PLAYER_OPTIONS_ENV_EDITOR) { + return checkAutoplayPermission().catch(error => { + engine.renderErrors.add(error); + }); + } + + return Promise.resolve(); + } + static override async processAssets ( json: spec.JSONScene, options: SceneLoadOptions = {}, From ccdad883cec0670be9a7d013f6d11a199e6cf5dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=8C=82=E5=AE=89?= <151805118+wumaolinmaoan@users.noreply.github.com> Date: Mon, 4 Nov 2024 16:45:37 +0800 Subject: [PATCH 62/88] feat: item active setting (#716) * refactor: item active setting * chore: opt code * feat: add isVisible * chore: opt code --------- Co-authored-by: wumaolin.wml --- packages/effects-core/src/comp-vfx-item.ts | 10 ++-- .../effects-core/src/components/component.ts | 4 +- packages/effects-core/src/composition.ts | 2 +- .../playables/activation-mixer-playable.ts | 20 +------- .../sub-composition-mixer-playable.ts | 4 +- packages/effects-core/src/vfx-item.ts | 50 +++++++++++++++---- .../object-inspectors/vfx-item-inspector.ts | 6 +-- .../composition/composition.spec.ts | 2 +- 8 files changed, 55 insertions(+), 43 deletions(-) diff --git a/packages/effects-core/src/comp-vfx-item.ts b/packages/effects-core/src/comp-vfx-item.ts index c3300e7f..d4b5848f 100644 --- a/packages/effects-core/src/comp-vfx-item.ts +++ b/packages/effects-core/src/comp-vfx-item.ts @@ -105,15 +105,15 @@ export class CompositionComponent extends Behaviour { } } - showItems () { + override onEnable () { for (const item of this.items) { - item.setVisible(true); + item.setActive(true); } } - hideItems () { + override onDisable () { for (const item of this.items) { - item.setVisible(false); + item.setActive(false); } } @@ -143,7 +143,7 @@ export class CompositionComponent extends Behaviour { const item = this.items[i]; if ( - item.getVisible() + item.isActive && item.transform.getValid() && !VFXItem.isComposition(item) && !skip(item) diff --git a/packages/effects-core/src/components/component.ts b/packages/effects-core/src/components/component.ts index 28a74be9..8b50b64d 100644 --- a/packages/effects-core/src/components/component.ts +++ b/packages/effects-core/src/components/component.ts @@ -30,7 +30,7 @@ export abstract class Component extends EffectsObject { * 组件是否可以更新,true 更新,false 不更新 */ get isActiveAndEnabled () { - return this.item.getVisible() && this.enabled; + return this.item.isActive && this.enabled; } get enabled () { @@ -134,7 +134,7 @@ export abstract class Component extends EffectsObject { this.onAwake(); this.isAwakeCalled = true; } - if (item.getVisible() && this.enabled) { + if (item.isActive && this.enabled) { this.start(); this.enable(); } diff --git a/packages/effects-core/src/composition.ts b/packages/effects-core/src/composition.ts index 12c8cfd5..b39acb14 100644 --- a/packages/effects-core/src/composition.ts +++ b/packages/effects-core/src/composition.ts @@ -388,7 +388,7 @@ export class Composition extends EventEmitter> imp */ setVisible (visible: boolean) { this.items.forEach(item => { - item.setVisible(visible); + item.setActive(visible); }); } diff --git a/packages/effects-core/src/plugins/timeline/playables/activation-mixer-playable.ts b/packages/effects-core/src/plugins/timeline/playables/activation-mixer-playable.ts index d7175b2f..6de87478 100644 --- a/packages/effects-core/src/plugins/timeline/playables/activation-mixer-playable.ts +++ b/packages/effects-core/src/plugins/timeline/playables/activation-mixer-playable.ts @@ -25,26 +25,10 @@ export class ActivationMixerPlayable extends Playable { if (hasInput) { boundItem.transform.setValid(true); - this.showRendererComponents(boundItem); + boundItem.setActive(true); } else { boundItem.transform.setValid(false); - this.hideRendererComponents(boundItem); - } - } - - private hideRendererComponents (item: VFXItem) { - for (const rendererComponent of item.rendererComponents) { - if (rendererComponent.enabled) { - rendererComponent.enabled = false; - } - } - } - - private showRendererComponents (item: VFXItem) { - for (const rendererComponent of item.rendererComponents) { - if (!rendererComponent.enabled) { - rendererComponent.enabled = true; - } + boundItem.setActive(false); } } } diff --git a/packages/effects-core/src/plugins/timeline/playables/sub-composition-mixer-playable.ts b/packages/effects-core/src/plugins/timeline/playables/sub-composition-mixer-playable.ts index 222f77ef..1cd8dd22 100644 --- a/packages/effects-core/src/plugins/timeline/playables/sub-composition-mixer-playable.ts +++ b/packages/effects-core/src/plugins/timeline/playables/sub-composition-mixer-playable.ts @@ -23,9 +23,9 @@ export class SubCompositionMixerPlayable extends Playable { } if (hasInput) { - compositionComponent.showItems(); + compositionComponent.item.setActive(true); } else { - compositionComponent.hideItems(); + compositionComponent.item.setActive(false); } } } \ No newline at end of file diff --git a/packages/effects-core/src/vfx-item.ts b/packages/effects-core/src/vfx-item.ts index 3a0f93aa..a26e40ad 100644 --- a/packages/effects-core/src/vfx-item.ts +++ b/packages/effects-core/src/vfx-item.ts @@ -98,10 +98,13 @@ export class VFXItem extends EffectsObject implements Disposable { rendererComponents: RendererComponent[] = []; /** - * 元素可见性,该值的改变会触发 `handleVisibleChanged` 回调 - * @protected + * 元素是否激活 */ - protected visible = true; + private active = true; + /** + * 元素组件是否显示,用于批量开关元素组件 + */ + private visible = true; /** * 元素动画的速度 */ @@ -401,20 +404,45 @@ export class VFXItem extends EffectsObject implements Disposable { } /** - * 获取元素显隐属性 + * 激活或停用 VFXItem */ - getVisible () { - return this.visible; + setActive (value: boolean) { + if (this.active !== value) { + this.active = !!value; + this.onActiveChanged(); + } } /** - * 设置元素显隐属性 会触发 `handleVisibleChanged` 回调 + * 当前 VFXItem 是否激活 + */ + get isActive () { + return this.active; + } + + /** + * 设置元素的显隐,该设置会批量开关元素组件 */ setVisible (visible: boolean) { - if (this.visible !== visible) { - this.visible = !!visible; - this.onActiveChanged(); + for (const component of this.components) { + component.enabled = visible; } + this.visible = visible; + } + + /** + * 元素组件显隐状态 + */ + get isVisible () { + return this.visible; + } + + /** + * 元素组件显隐状态 + * @deprecated use isVisible instead + */ + getVisible () { + return this.visible; } /** @@ -549,7 +577,7 @@ export class VFXItem extends EffectsObject implements Disposable { beginPlay () { this.isDuringPlay = true; - if (this.composition && this.visible && !this.isEnabled) { + if (this.composition && this.active && !this.isEnabled) { this.onEnable(); } diff --git a/web-packages/imgui-demo/src/object-inspectors/vfx-item-inspector.ts b/web-packages/imgui-demo/src/object-inspectors/vfx-item-inspector.ts index 9d58fbd9..6612af28 100644 --- a/web-packages/imgui-demo/src/object-inspectors/vfx-item-inspector.ts +++ b/web-packages/imgui-demo/src/object-inspectors/vfx-item-inspector.ts @@ -31,10 +31,10 @@ export class VFXItemInspector extends ObjectInspector { ImGui.Text(activeObject.getInstanceId()); ImGui.Text('Visible'); ImGui.SameLine(alignWidth); - ImGui.Checkbox('##Visible', (_ = activeObject.getVisible()) => { - activeObject.setVisible(_); + ImGui.Checkbox('##Visible', (_ = activeObject.isActive) => { + activeObject.setActive(_); - return activeObject.getVisible(); + return activeObject.isActive; }); ImGui.Text('End Behavior'); diff --git a/web-packages/test/unit/src/effects-core/composition/composition.spec.ts b/web-packages/test/unit/src/effects-core/composition/composition.spec.ts index 27ec8f3a..60769b7d 100644 --- a/web-packages/test/unit/src/effects-core/composition/composition.spec.ts +++ b/web-packages/test/unit/src/effects-core/composition/composition.spec.ts @@ -149,6 +149,6 @@ describe('core/composition', () => { comp.setVisible(false); - expect(comp.items[0].getVisible()).to.eql(false, 'composition visible'); + expect(comp.items[0].isActive).to.eql(false, 'composition visible'); }); }); From f11477ea25b0fc8ea1922d8e0e2b30ea822dfb1f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=8C=82=E5=AE=89?= <151805118+wumaolinmaoan@users.noreply.github.com> Date: Mon, 4 Nov 2024 17:07:58 +0800 Subject: [PATCH 63/88] refactor: shape and post process volume data (#717) * chore: update shape data interface * chore: update postprocessing data * chore: opt code * chore: opt code * chore: opt import * chore: opt import --- packages/effects-core/package.json | 2 +- .../src/components/post-process-volume.ts | 57 ++++++++++------ .../src/components/shape-component.ts | 16 ++--- .../src/render/post-process-pass.ts | 66 ++++++++++++------- pnpm-lock.yaml | 8 +-- web-packages/demo/src/post-processing.ts | 21 +++--- 6 files changed, 101 insertions(+), 69 deletions(-) diff --git a/packages/effects-core/package.json b/packages/effects-core/package.json index 4a6bd10a..6c33f3ea 100644 --- a/packages/effects-core/package.json +++ b/packages/effects-core/package.json @@ -51,7 +51,7 @@ "registry": "https://registry.npmjs.org" }, "dependencies": { - "@galacean/effects-specification": "2.1.0-alpha.2", + "@galacean/effects-specification": "2.1.0-alpha.3", "@galacean/effects-math": "1.1.0", "flatbuffers": "24.3.25", "uuid": "9.0.1", diff --git a/packages/effects-core/src/components/post-process-volume.ts b/packages/effects-core/src/components/post-process-volume.ts index 289db461..01f7bd8a 100644 --- a/packages/effects-core/src/components/post-process-volume.ts +++ b/packages/effects-core/src/components/post-process-volume.ts @@ -1,39 +1,54 @@ +import * as spec from '@galacean/effects-specification'; import { effectsClass, serialize } from '../decorators'; import { Behaviour } from './component'; +import type { Engine } from '../engine'; // TODO spec 增加 DataType /** * @since 2.1.0 */ -@effectsClass('PostProcessVolume') +@effectsClass(spec.DataType.PostProcessVolume) export class PostProcessVolume extends Behaviour { - // Bloom - @serialize() - bloomEnabled = true; - @serialize() - threshold = 1.0; - @serialize() - bloomIntensity = 1.0; - // ColorAdjustments - @serialize() - brightness = 1.0; @serialize() - saturation = 1.0; - @serialize() - contrast = 1.0; + bloom: spec.Bloom; - // Vignette - @serialize() - vignetteIntensity = 0.2; @serialize() - vignetteSmoothness = 0.4; + vignette: spec.Vignette; + @serialize() - vignetteRoundness = 1.0; + tonemapping: spec.Tonemapping; - // ToneMapping @serialize() - toneMappingEnabled: boolean = true; // 1: true, 0: false + colorAdjustments: spec.ColorAdjustments; + + constructor (engine: Engine) { + super(engine); + + this.bloom = { + threshold: 0, + intensity: 0, + active: false, + }; + + this.vignette = { + intensity: 0, + smoothness: 0, + roundness: 0, + active: false, + }; + + this.tonemapping = { + active: false, + }; + + this.colorAdjustments = { + brightness: 0, + saturation: 0, + contrast: 0, + active: false, + }; + } override onStart (): void { const composition = this.item.composition; diff --git a/packages/effects-core/src/components/shape-component.ts b/packages/effects-core/src/components/shape-component.ts index 55c098d9..b7c49399 100644 --- a/packages/effects-core/src/components/shape-component.ts +++ b/packages/effects-core/src/components/shape-component.ts @@ -12,9 +12,9 @@ import { MeshComponent } from './mesh-component'; import { StarType } from '../plugins/shape/poly-star'; interface CurveData { - point: spec.Vector3Data, - controlPoint1: spec.Vector3Data, - controlPoint2: spec.Vector3Data, + point: spec.Vector2Data, + controlPoint1: spec.Vector2Data, + controlPoint2: spec.Vector2Data, } /** @@ -331,15 +331,15 @@ export interface CustomShapeData extends ShapeComponentData { /** * 路径点 */ - points: spec.Vector3Data[], + points: spec.Vector2Data[], /** * 入射控制点 */ - easingIns: spec.Vector3Data[], + easingIns: spec.Vector2Data[], /** * 入射控制点 */ - easingOuts: spec.Vector3Data[], + easingOuts: spec.Vector2Data[], /** * 自定义形状 */ @@ -350,10 +350,6 @@ export interface CustomShapeData extends ShapeComponentData { * 自定义形状参数 */ export interface CustomShape { - /** - * 是否垂直与平面 - 用于减少实时运算 - */ - verticalToPlane: 'x' | 'y' | 'z' | 'none', /** * 点索引 - 用于构成闭合图形 */ diff --git a/packages/effects-core/src/render/post-process-pass.ts b/packages/effects-core/src/render/post-process-pass.ts index 272b629d..41063c62 100644 --- a/packages/effects-core/src/render/post-process-pass.ts +++ b/packages/effects-core/src/render/post-process-pass.ts @@ -13,6 +13,7 @@ import type { ShaderWithSource } from './shader'; import { colorGradingFrag, gaussianDownHFrag, gaussianDownVFrag, gaussianUpFrag, screenMeshVert, thresholdFrag } from '../shader'; import { Vector2 } from '@galacean/effects-math/es/core/vector2'; import { Vector3 } from '@galacean/effects-math/es/core/vector3'; +import type * as spec from '@galacean/effects-specification'; // Bloom 阈值 Pass export class BloomThresholdPass extends RenderPass { @@ -68,7 +69,7 @@ export class BloomThresholdPass extends RenderPass { stencilAction: TextureStoreAction.clear, }); this.screenMesh.material.setTexture('_MainTex', this.mainTexture); - const threshold = renderer.renderingData.currentFrame.globalVolume?.threshold ?? 1.0; + const threshold = renderer.renderingData.currentFrame.globalVolume?.bloom?.threshold ?? 1.0; this.screenMesh.material.setFloat('_Threshold', threshold); renderer.renderMeshes([this.screenMesh]); @@ -263,36 +264,55 @@ export class ToneMappingPass extends RenderPass { depthAction: TextureStoreAction.clear, stencilAction: TextureStoreAction.clear, }); - const { - bloomEnabled = false, - bloomIntensity = 1.0, - brightness = 1.0, - saturation = 1.0, - contrast = 1.0, - toneMappingEnabled = true, - vignetteIntensity = 0.2, - vignetteSmoothness = 0.4, - vignetteRoundness = 1.0, - } = renderer.renderingData.currentFrame.globalVolume ?? {}; + const globalVolume = renderer.renderingData.currentFrame.globalVolume; + + const bloom: spec.Bloom = { + threshold: 0, + intensity: 0, + active: false, + ...globalVolume?.bloom, + }; + + const vignette: spec.Vignette = { + intensity: 0, + smoothness: 0, + roundness: 0, + active: false, + ...globalVolume?.vignette, + }; + + const colorAdjustments: spec.ColorAdjustments = { + brightness: 0, + saturation: 0, + contrast: 0, + active: false, + ...globalVolume?.colorAdjustments, + }; + + const tonemapping: spec.Tonemapping = { + active: false, + ...globalVolume?.tonemapping, + }; this.screenMesh.material.setTexture('_SceneTex', this.sceneTextureHandle.texture); - this.screenMesh.material.setFloat('_Brightness', brightness); - this.screenMesh.material.setFloat('_Saturation', saturation); - this.screenMesh.material.setFloat('_Contrast', contrast); - this.screenMesh.material.setInt('_UseBloom', Number(bloomEnabled)); - if (bloomEnabled) { + this.screenMesh.material.setFloat('_Brightness', Math.pow(2, colorAdjustments.brightness)); + this.screenMesh.material.setFloat('_Saturation', (colorAdjustments.saturation * 0.01) + 1); + this.screenMesh.material.setFloat('_Contrast', (colorAdjustments.contrast * 0.01) + 1); + + this.screenMesh.material.setInt('_UseBloom', Number(bloom.active)); + if (bloom.active) { this.screenMesh.material.setTexture('_GaussianTex', this.mainTexture); - this.screenMesh.material.setFloat('_BloomIntensity', bloomIntensity); + this.screenMesh.material.setFloat('_BloomIntensity', bloom.intensity); } - if (vignetteIntensity > 0) { - this.screenMesh.material.setFloat('_VignetteIntensity', vignetteIntensity); - this.screenMesh.material.setFloat('_VignetteSmoothness', vignetteSmoothness); - this.screenMesh.material.setFloat('_VignetteRoundness', vignetteRoundness); + if (vignette.intensity > 0) { + this.screenMesh.material.setFloat('_VignetteIntensity', vignette.intensity); + this.screenMesh.material.setFloat('_VignetteSmoothness', vignette.smoothness); + this.screenMesh.material.setFloat('_VignetteRoundness', vignette.roundness); this.screenMesh.material.setVector2('_VignetteCenter', new Vector2(0.5, 0.5)); this.screenMesh.material.setVector3('_VignetteColor', new Vector3(0.0, 0.0, 0.0)); } - this.screenMesh.material.setInt('_UseToneMapping', Number(toneMappingEnabled)); + this.screenMesh.material.setInt('_UseToneMapping', Number(tonemapping.active)); renderer.renderMeshes([this.screenMesh]); } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 79a0c376..d6f73980 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -150,8 +150,8 @@ importers: specifier: 1.1.0 version: 1.1.0 '@galacean/effects-specification': - specifier: 2.1.0-alpha.2 - version: 2.1.0-alpha.2 + specifier: 2.1.0-alpha.3 + version: 2.1.0-alpha.3 flatbuffers: specifier: 24.3.25 version: 24.3.25 @@ -2208,8 +2208,8 @@ packages: /@galacean/effects-specification@2.0.0: resolution: {integrity: sha512-7ieZALZYn5fwKLIGAskmYSKGX82ZkLRdDUInG21KsOJ2eQ5+HcI6mJU5tmN8vjZxQUc+RVuUcssExGbiaj2+5w==} - /@galacean/effects-specification@2.1.0-alpha.2: - resolution: {integrity: sha512-em2kefqQGPZ1T5OavVECtlSDeWjMaYwCZYcdTsRefhfEVqjXx0gpx9Y6I6jzekpaZbf9Iit07uHreRm2CXfwxQ==} + /@galacean/effects-specification@2.1.0-alpha.3: + resolution: {integrity: sha512-s11qcar30DqUGGXF3w7ioirYmt700TJWyGhAxARC3RmkRZOelYiDWczvjGRTHLSqBIB+B1oQ1jq380UHwn/iGg==} dev: false /@humanwhocodes/config-array@0.11.14: diff --git a/web-packages/demo/src/post-processing.ts b/web-packages/demo/src/post-processing.ts index 99f34aed..7119b181 100644 --- a/web-packages/demo/src/post-processing.ts +++ b/web-packages/demo/src/post-processing.ts @@ -42,6 +42,7 @@ async function handleLoadScene (url: string) { const composition = await player.loadScene(json); composition.rootItem.addComponent(PostProcessVolume); + setDatGUI(composition); } @@ -83,20 +84,20 @@ function setDatGUI (composition: Composition) { ParticleFolder.add(postProcessSettings, 'intensity', -10, 10).step(0.1); ParticleFolder.open(); - BloomFolder.add(globalVolume, 'bloomEnabled', 0, 1).step(1); - BloomFolder.add(globalVolume, 'threshold', 0, 40).step(0.1); - BloomFolder.add(globalVolume, 'bloomIntensity', 0, 10); + BloomFolder.add(globalVolume.bloom, 'active', 0, 1).step(1); + BloomFolder.add(globalVolume.bloom, 'threshold', 0, 40).step(0.1); + BloomFolder.add(globalVolume.bloom, 'intensity', 0, 10); BloomFolder.open(); - VignetteFolder.add(globalVolume, 'vignetteIntensity', 0, 2); - VignetteFolder.add(globalVolume, 'vignetteSmoothness', 0, 2); - VignetteFolder.add(globalVolume, 'vignetteRoundness', 0, 1.5); + VignetteFolder.add(globalVolume.vignette, 'intensity', 0, 2); + VignetteFolder.add(globalVolume.vignette, 'smoothness', 0, 2); + VignetteFolder.add(globalVolume.vignette, 'roundness', 0, 1.5); - ColorAdjustmentsFolder.add(globalVolume, 'brightness', -5, 5).step(0.1); - ColorAdjustmentsFolder.add(globalVolume, 'saturation', 0, 2); - ColorAdjustmentsFolder.add(globalVolume, 'contrast', 0, 2); + ColorAdjustmentsFolder.add(globalVolume.colorAdjustments, 'brightness').step(0.1); + ColorAdjustmentsFolder.add(globalVolume.colorAdjustments, 'saturation', -100, 100); + ColorAdjustmentsFolder.add(globalVolume.colorAdjustments, 'contrast', -100, 100); ColorAdjustmentsFolder.open(); - ToneMappingFlolder.add(globalVolume, 'toneMappingEnabled', 0, 1).step(1); + ToneMappingFlolder.add(globalVolume.tonemapping, 'active', 0, 1).step(1); ToneMappingFlolder.open(); } From 079df5b622ae8c4ad4ca7ed1c5307e877ef8e113 Mon Sep 17 00:00:00 2001 From: wumaolinmaoan Date: Mon, 4 Nov 2024 22:54:29 +0800 Subject: [PATCH 64/88] fix: interact item message --- .../src/plugins/interact/interact-item.ts | 26 ++++++++++++------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/packages/effects-core/src/plugins/interact/interact-item.ts b/packages/effects-core/src/plugins/interact/interact-item.ts index 0ab488ba..09f03fbc 100644 --- a/packages/effects-core/src/plugins/interact/interact-item.ts +++ b/packages/effects-core/src/plugins/interact/interact-item.ts @@ -42,6 +42,8 @@ export class InteractComponent extends RendererComponent { dyRange: [0, 0], }; + private duringPlay = false; + /** 是否响应点击和拖拽交互事件 */ private _interactive = true; @@ -57,7 +59,7 @@ export class InteractComponent extends RendererComponent { return this._interactive; } - getDragRangeX (): [min:number, max: number] { + getDragRangeX (): [min: number, max: number] { return this.dragRange.dxRange; } @@ -65,7 +67,7 @@ export class InteractComponent extends RendererComponent { this.dragRange.dxRange = [min, max]; } - getDragRangeY (): [min:number, max: number] { + getDragRangeY (): [min: number, max: number] { return this.dragRange.dyRange; } @@ -103,7 +105,10 @@ export class InteractComponent extends RendererComponent { override onDisable (): void { if (this.item && this.item.composition) { - this.item.composition.removeInteractiveItem(this.item, (this.item.props as spec.InteractItem).content.options.type); + if (this.duringPlay) { + this.item.composition.removeInteractiveItem(this.item, (this.item.props as spec.InteractItem).content.options.type); + this.duringPlay = false; + } this.clickable = false; this.previewContent?.mesh.dispose(); this.endDragTarget(); @@ -116,17 +121,18 @@ export class InteractComponent extends RendererComponent { if (type === spec.InteractType.CLICK) { this.clickable = true; } - const options = this.item.props.content.options as spec.DragInteractOption; - - if (this.item.composition) { - this.item.composition.addInteractiveItem(this.item, options.type); - } } override onUpdate (dt: number): void { - if (!this.isActiveAndEnabled) { - return; + this.duringPlay = true; + + // trigger messageBegin when item enter + if (this.item.time > 0 && this.item.time - dt / 1000 <= 0) { + const options = this.item.props.content.options as spec.DragInteractOption; + + this.item.composition?.addInteractiveItem(this.item, options.type); } + this.previewContent?.updateMesh(); if (!this.dragEvent || !this.bouncingArg) { From a21cc2c428ae623e0db7d230fb9c5761bfe63868 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=8C=82=E5=AE=89?= <151805118+wumaolinmaoan@users.noreply.github.com> Date: Wed, 6 Nov 2024 11:59:04 +0800 Subject: [PATCH 65/88] fix: rect shape drawing and bounding box (#723) * fix: draw rect shape * fix: shape component bounding box * chore: opt message item end logic --- .../src/plugins/interact/interact-item.ts | 2 +- .../src/plugins/interact/mesh-collider.ts | 8 +++---- .../src/plugins/shape/shape-path.ts | 21 +++++++++++++++++++ 3 files changed, 26 insertions(+), 5 deletions(-) diff --git a/packages/effects-core/src/plugins/interact/interact-item.ts b/packages/effects-core/src/plugins/interact/interact-item.ts index 09f03fbc..034579d0 100644 --- a/packages/effects-core/src/plugins/interact/interact-item.ts +++ b/packages/effects-core/src/plugins/interact/interact-item.ts @@ -105,7 +105,7 @@ export class InteractComponent extends RendererComponent { override onDisable (): void { if (this.item && this.item.composition) { - if (this.duringPlay) { + if (this.duringPlay && !this.item.transform.getValid()) { this.item.composition.removeInteractiveItem(this.item, (this.item.props as spec.InteractItem).content.options.type); this.duringPlay = false; } diff --git a/packages/effects-core/src/plugins/interact/mesh-collider.ts b/packages/effects-core/src/plugins/interact/mesh-collider.ts index be7f4ec3..616b9282 100644 --- a/packages/effects-core/src/plugins/interact/mesh-collider.ts +++ b/packages/effects-core/src/plugins/interact/mesh-collider.ts @@ -18,11 +18,11 @@ export class MeshCollider { } getBoundingBox (): BoundingBoxTriangle { - let maxX = 0; - let maxY = 0; + let maxX = -Number.MAX_VALUE; + let maxY = -Number.MAX_VALUE; - let minX = 0; - let minY = 0; + let minX = Number.MAX_VALUE; + let minY = Number.MAX_VALUE; for (const triangle of this.boundingBoxData.area) { maxX = Math.max(triangle.p0.x, triangle.p1.x, triangle.p2.x, maxX); diff --git a/packages/effects-core/src/plugins/shape/shape-path.ts b/packages/effects-core/src/plugins/shape/shape-path.ts index 785b011c..b29b4e3e 100644 --- a/packages/effects-core/src/plugins/shape/shape-path.ts +++ b/packages/effects-core/src/plugins/shape/shape-path.ts @@ -11,6 +11,7 @@ import type { ShapePrimitive } from './shape-primitive'; import { Ellipse } from './ellipse'; import type { StarType } from './poly-star'; import { PolyStar } from './poly-star'; +import { Rectangle } from './rectangle'; export class ShapePath { currentPoly: Polygon | null = null; @@ -49,6 +50,11 @@ export class ShapePath { case 'polyStar': { this.polyStar(data[0], data[1], data[2], data[3], data[4], data[5], data[6]); + break; + } + case 'rect': { + this.rect(data[0], data[1], data[2], data[3], data[4]); + break; } } @@ -117,6 +123,21 @@ export class ShapePath { return this; } + /** + * Draws a rectangle shape. This method adds a new rectangle path to the current drawing. + * @param x - The x-coordinate of the top-left corner of the rectangle. + * @param y - The y-coordinate of the top-left corner of the rectangle. + * @param w - The width of the rectangle. + * @param h - The height of the rectangle. + * @param transform - An optional `Matrix` object to apply a transformation to the rectangle. + * @returns The instance of the current object for chaining. + */ + rect (x: number, y: number, w: number, h: number, transform?: Matrix4): this { + this.drawShape(new Rectangle(x, y, w, h), transform); + + return this; + } + /** * Draws a given shape on the canvas. * This is a generic method that can draw any type of shape specified by the `ShapePrimitive` parameter. From ef0d2325ab319b7d06cac5fa5c5ea16c28949f1a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=8C=82=E5=AE=89?= <151805118+wumaolinmaoan@users.noreply.github.com> Date: Wed, 6 Nov 2024 13:59:48 +0800 Subject: [PATCH 66/88] feat: material add color and mainTexture interface (#722) * feat: material add color and mainTexture interface * chore: update imgui vfx item inspector * chore: remove note --------- Co-authored-by: wumaolin.wml --- .../src/components/base-render-component.ts | 17 +++------- .../src/components/post-process-volume.ts | 1 - .../effects-core/src/material/material.ts | 22 +++++++++++++ .../src/plugins/text/text-item.ts | 2 +- .../effects-core/src/shader/item.frag.glsl | 4 +-- .../src/material/three-material-util.ts | 17 +--------- .../src/material/three-material.ts | 7 ++-- .../multimedia/src/video/video-component.ts | 2 +- .../object-inspectors/vfx-item-inspector.ts | 33 +++++++++++-------- .../effects-core/interact/interact.spec.ts | 2 +- .../plugins/sprite/sprite-item.spec.ts | 2 +- 11 files changed, 55 insertions(+), 54 deletions(-) diff --git a/packages/effects-core/src/components/base-render-component.ts b/packages/effects-core/src/components/base-render-component.ts index ee1d757c..caabadda 100644 --- a/packages/effects-core/src/components/base-render-component.ts +++ b/packages/effects-core/src/components/base-render-component.ts @@ -10,7 +10,7 @@ import type { Engine } from '../engine'; import { glContext } from '../gl'; import { addItem } from '../utils'; import type { BoundingBoxTriangle, HitTestTriangleParams } from '../plugins'; -import { HitTestType, maxSpriteMeshItemCount, spriteMeshShaderFromRenderInfo } from '../plugins'; +import { HitTestType, spriteMeshShaderFromRenderInfo } from '../plugins'; import type { MaterialProps } from '../material'; import { getPreMultiAlpha, Material, setBlendMode, setMaskMode, setSideMode } from '../material'; import { trianglesFromRect } from '../math'; @@ -126,7 +126,7 @@ export class BaseRenderComponent extends RendererComponent { */ setTexture (texture: Texture) { this.renderer.texture = texture; - this.material.setTexture('uSampler0', texture); + this.material.setTexture('_MainTex', texture); } /** @@ -213,17 +213,8 @@ export class BaseRenderComponent extends RendererComponent { geometry.setIndexData(indexData); geometry.setAttributeData('atlasOffset', attributes.atlasOffset); geometry.setDrawCount(data.index.length); - for (let i = 0; i < textures.length; i++) { - const texture = textures[i]; - material.setTexture('uSampler' + i, texture); - } - // FIXME: 内存泄漏的临时方案,后面再调整 - const emptyTexture = this.emptyTexture; - - for (let k = textures.length; k < maxSpriteMeshItemCount; k++) { - material.setTexture('uSampler' + k, emptyTexture); - } + material.setTexture('_MainTex', texture); } protected getItemGeometryData () { @@ -307,7 +298,7 @@ export class BaseRenderComponent extends RendererComponent { setMaskMode(material, states.maskMode); setSideMode(material, states.side); - material.shader.shaderData.properties = 'uSampler0("uSampler0",2D) = "white" {}'; + material.shader.shaderData.properties = '_MainTex("_MainTex",2D) = "white" {}'; if (!material.hasUniform('_Color')) { material.setVector4('_Color', new Vector4(0, 0, 0, 1)); } diff --git a/packages/effects-core/src/components/post-process-volume.ts b/packages/effects-core/src/components/post-process-volume.ts index 01f7bd8a..2d14a4e8 100644 --- a/packages/effects-core/src/components/post-process-volume.ts +++ b/packages/effects-core/src/components/post-process-volume.ts @@ -3,7 +3,6 @@ import { effectsClass, serialize } from '../decorators'; import { Behaviour } from './component'; import type { Engine } from '../engine'; -// TODO spec 增加 DataType /** * @since 2.1.0 */ diff --git a/packages/effects-core/src/material/material.ts b/packages/effects-core/src/material/material.ts index ef27ad27..e185ddd7 100644 --- a/packages/effects-core/src/material/material.ts +++ b/packages/effects-core/src/material/material.ts @@ -120,6 +120,28 @@ export abstract class Material extends EffectsObject implements Disposable { this.shaderDirty = true; } + /** + * 材质的主纹理 + */ + get mainTexture () { + return this.getTexture('_MainTex') as Texture; + } + + set mainTexture (value: Texture) { + this.setTexture('_MainTex', value); + } + + /** + * 材质的主颜色 + */ + get color () { + return this.getColor('_Color') as Color; + } + + set color (value: Color) { + this.setColor('_Color', value); + } + /******** effects-core 中会调用 引擎必须实现 ***********************/ /** * 设置 Material 的颜色融合开关 diff --git a/packages/effects-core/src/plugins/text/text-item.ts b/packages/effects-core/src/plugins/text/text-item.ts index cbbc9da9..6f52e7d2 100644 --- a/packages/effects-core/src/plugins/text/text-item.ts +++ b/packages/effects-core/src/plugins/text/text-item.ts @@ -514,7 +514,7 @@ export class TextComponentBase { //与 toDataURL() 两种方式都需要像素读取操作 const imageData = context.getImageData(0, 0, this.canvas.width, this.canvas.height); - this.material.setTexture('uSampler0', + this.material.setTexture('_MainTex', Texture.createWithData( this.engine, { diff --git a/packages/effects-core/src/shader/item.frag.glsl b/packages/effects-core/src/shader/item.frag.glsl index 1f6c35f5..ce672f47 100644 --- a/packages/effects-core/src/shader/item.frag.glsl +++ b/packages/effects-core/src/shader/item.frag.glsl @@ -4,7 +4,7 @@ varying vec4 vColor; varying vec2 vTexCoord;//x y varying vec3 vParams;//texIndex mulAplha transparentOcclusion -uniform sampler2D uSampler0; +uniform sampler2D _MainTex; vec4 blendColor(vec4 color, vec4 vc, float mode) { vec4 ret = color * vc; @@ -24,7 +24,7 @@ vec4 blendColor(vec4 color, vec4 vc, float mode) { void main() { vec4 color = vec4(0.); - vec4 texColor = texture2D(uSampler0, vTexCoord.xy); + vec4 texColor = texture2D(_MainTex, vTexCoord.xy); color = blendColor(texColor, vColor, floor(0.5 + vParams.y)); if(vParams.z == 0. && color.a < 0.04) { // 1/256 = 0.04 discard; diff --git a/packages/effects-threejs/src/material/three-material-util.ts b/packages/effects-threejs/src/material/three-material-util.ts index 2b495fd7..46e2fff9 100644 --- a/packages/effects-threejs/src/material/three-material-util.ts +++ b/packages/effects-threejs/src/material/three-material-util.ts @@ -82,22 +82,7 @@ export function setUniformValue (uniforms: Record, name: string, va */ export const TEXTURE_UNIFORM_MAP = [ 'uMaskTex', - 'uSampler0', - 'uSampler1', - 'uSampler2', - 'uSampler3', - 'uSampler4', - 'uSampler5', - 'uSampler6', - 'uSampler7', - 'uSampler8', - 'uSampler9', - 'uSampler10', - 'uSampler11', - 'uSampler12', - 'uSampler13', - 'uSampler14', - 'uSampler15', + '_MainTex', 'uColorOverLifetime', 'uColorOverTrail', ]; diff --git a/packages/effects-threejs/src/material/three-material.ts b/packages/effects-threejs/src/material/three-material.ts index 2ab91f79..9b01bc09 100644 --- a/packages/effects-threejs/src/material/three-material.ts +++ b/packages/effects-threejs/src/material/three-material.ts @@ -2,7 +2,7 @@ import type { MaterialProps, Texture, UniformValue, MaterialDestroyOptions, UndefinedAble, Engine, math, GlobalUniforms, Renderer, } from '@galacean/effects-core'; -import { Material, Shader, ShaderType, ShaderFactory, generateGUID, maxSpriteMeshItemCount, spec } from '@galacean/effects-core'; +import { Material, Shader, ShaderType, ShaderFactory, generateGUID, spec } from '@galacean/effects-core'; import * as THREE from 'three'; import type { ThreeTexture } from '../three-texture'; import { @@ -56,10 +56,7 @@ export class ThreeMaterial extends Material { fragment: shader?.fragment ?? '', }; - for (let i = 0; i < maxSpriteMeshItemCount; i++) { - this.uniforms[`uSampler${i}`] = new THREE.Uniform(null); - } - + this.uniforms['_MainTex'] = new THREE.Uniform(null); this.uniforms['uEditorTransform'] = new THREE.Uniform([1, 1, 0, 0]); this.uniforms['effects_ObjectToWorld'] = new THREE.Uniform(new THREE.Matrix4().identity()); this.uniforms['effects_MatrixInvV'] = new THREE.Uniform([1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 8, 1]); diff --git a/plugin-packages/multimedia/src/video/video-component.ts b/plugin-packages/multimedia/src/video/video-component.ts index 4c6b261f..0be7e3bb 100644 --- a/plugin-packages/multimedia/src/video/video-component.ts +++ b/plugin-packages/multimedia/src/video/video-component.ts @@ -45,7 +45,7 @@ export class VideoComponent extends BaseRenderComponent { this.engine.removeTexture(oldTexture); this.renderer.texture = texture; - this.material.setTexture('uSampler0', texture); + this.material.setTexture('_MainTex', texture); this.video = (texture.source as Texture2DSourceOptionsVideo).video; } diff --git a/web-packages/imgui-demo/src/object-inspectors/vfx-item-inspector.ts b/web-packages/imgui-demo/src/object-inspectors/vfx-item-inspector.ts index 6612af28..1dbc1158 100644 --- a/web-packages/imgui-demo/src/object-inspectors/vfx-item-inspector.ts +++ b/web-packages/imgui-demo/src/object-inspectors/vfx-item-inspector.ts @@ -29,9 +29,9 @@ export class VFXItemInspector extends ObjectInspector { ImGui.Text('GUID'); ImGui.SameLine(alignWidth); ImGui.Text(activeObject.getInstanceId()); - ImGui.Text('Visible'); + ImGui.Text('Is Active'); ImGui.SameLine(alignWidth); - ImGui.Checkbox('##Visible', (_ = activeObject.isActive) => { + ImGui.Checkbox('##IsActive', (_ = activeObject.isActive) => { activeObject.setActive(_); return activeObject.isActive; @@ -65,33 +65,40 @@ export class VFXItemInspector extends ObjectInspector { for (const componet of activeObject.components) { const customEditor = UIManager.customEditors.get(componet.constructor); - if (customEditor) { - if (ImGui.CollapsingHeader(componet.constructor.name, ImGui.TreeNodeFlags.DefaultOpen)) { + if (ImGui.CollapsingHeader(componet.constructor.name, ImGui.TreeNodeFlags.DefaultOpen)) { + ImGui.Text('Enabled'); + ImGui.SameLine(alignWidth); + ImGui.Checkbox('##Enabled', (_ = componet.enabled) => { + componet.enabled = _; + + return componet.enabled; + }); + + if (customEditor) { customEditor.onInspectorGUI(); + continue; + } - continue; - } - if (ImGui.CollapsingHeader(componet.constructor.name, ImGui.TreeNodeFlags.DefaultOpen)) { const propertyDecoratorStore = getMergedStore(componet); - for (const peopertyName of Object.keys(componet)) { - const key = peopertyName as keyof Component; + for (const propertyName of Object.keys(componet)) { + const key = propertyName as keyof Component; const property = componet[key]; - const ImGuiID = componet.getInstanceId() + peopertyName; + const ImGuiID = componet.getInstanceId() + propertyName; if (typeof property === 'number') { - ImGui.Text(peopertyName); + ImGui.Text(propertyName); ImGui.SameLine(alignWidth); //@ts-expect-error ImGui.DragFloat('##DragFloat' + ImGuiID, (_ = componet[key]) => componet[key] = _, 0.03); } else if (typeof property === 'boolean') { - ImGui.Text(peopertyName); + ImGui.Text(propertyName); ImGui.SameLine(alignWidth); //@ts-expect-error ImGui.Checkbox('##Checkbox' + ImGuiID, (_ = componet[key]) => componet[key] = _); } else if (property instanceof EffectsObject) { - ImGui.Text(peopertyName); + ImGui.Text(propertyName); ImGui.SameLine(alignWidth); let name = 'EffectsObject'; diff --git a/web-packages/test/unit/src/effects-core/interact/interact.spec.ts b/web-packages/test/unit/src/effects-core/interact/interact.spec.ts index 10293d2e..a6a8b904 100644 --- a/web-packages/test/unit/src/effects-core/interact/interact.spec.ts +++ b/web-packages/test/unit/src/effects-core/interact/interact.spec.ts @@ -503,7 +503,7 @@ describe('core/interact/item', () => { player?.gotoAndStop(0.3); expect(messagePhrase).to.eql(spec.MESSAGE_ITEM_PHRASE_END, 'MESSAGE_ITEM_PHRASE_END'); - expect(messageSpy).to.have.been.called.once; + expect(messageSpy).to.have.been.called.twice; comp?.dispose(); }); diff --git a/web-packages/test/unit/src/effects-core/plugins/sprite/sprite-item.spec.ts b/web-packages/test/unit/src/effects-core/plugins/sprite/sprite-item.spec.ts index 7534fc12..7f429ca2 100644 --- a/web-packages/test/unit/src/effects-core/plugins/sprite/sprite-item.spec.ts +++ b/web-packages/test/unit/src/effects-core/plugins/sprite/sprite-item.spec.ts @@ -287,7 +287,7 @@ describe('core/plugins/sprite/item', () => { spriteItem?.setTexture(testTexture); const material = spriteItem?.material; - const texture = material?.getTexture('uSampler0'); + const texture = material?.getTexture('_MainTex'); expect(texture?.id).to.eql(testTexture.id, 'texture id'); }); From a730a232badcf4394eb5ea24e7a5b44bf82c5373 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=8C=82=E5=AE=89?= <151805118+wumaolinmaoan@users.noreply.github.com> Date: Wed, 6 Nov 2024 14:44:36 +0800 Subject: [PATCH 67/88] chore: unit test add canvas display (#724) --- .../test/case/2d/src/common/utilities.ts | 33 +++++++++++++++++-- 1 file changed, 30 insertions(+), 3 deletions(-) diff --git a/web-packages/test/case/2d/src/common/utilities.ts b/web-packages/test/case/2d/src/common/utilities.ts index 537b8890..5975dd63 100644 --- a/web-packages/test/case/2d/src/common/utilities.ts +++ b/web-packages/test/case/2d/src/common/utilities.ts @@ -21,12 +21,37 @@ const playerOptions: PlayerConfig = { export class TestPlayer { constructor (width, height, playerClass, playerOptions, renderFramework, registerFunc, Plugin, VFXItem, assetManager, oldVersion, is3DCase) { + + width /= 2; + height /= 2; + this.width = width; this.height = height; - // + + this.div = document.createElement('div'); + + this.div.style.position = 'absolute'; + this.div.style.width = width + 'px'; + this.div.style.height = height + 'px'; + this.div.style.backgroundColor = 'black'; + + const left = 1800; + const top = 800; + + if (oldVersion) { + this.div.style.left = left + 'px'; + this.div.style.top = top + 'px'; + } else { + this.div.style.left = (left + width) + 'px'; + this.div.style.top = top + 'px'; + } + this.canvas = document.createElement('canvas'); - this.canvas.width = width; - this.canvas.height = height; + + const body = document.getElementsByTagName('body')[0]; + + body.appendChild(this.div); + this.div.appendChild(this.canvas); this.renderFramework = renderFramework; // this.player = new playerClass({ @@ -210,6 +235,8 @@ export class TestPlayer { this.player = null; this.canvas.remove(); this.canvas = null; + this.div.remove(); + this.div = null; } } From a2375c8d78d8703e9f71ad3db43deeb2f286830e Mon Sep 17 00:00:00 2001 From: "wumaolin.wml" Date: Wed, 6 Nov 2024 15:01:41 +0800 Subject: [PATCH 68/88] chore: add half float texture support check --- packages/effects-core/src/render/render-frame.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/packages/effects-core/src/render/render-frame.ts b/packages/effects-core/src/render/render-frame.ts index 9746c94c..6bf8fdb2 100644 --- a/packages/effects-core/src/render/render-frame.ts +++ b/packages/effects-core/src/render/render-frame.ts @@ -245,6 +245,11 @@ export class RenderFrame implements Disposable { this.renderer = renderer; if (postProcessingEnabled) { const enableHDR = true; + + if (!this.renderer.engine.gpuCapability.detail.halfFloatTexture) { + throw new Error('Half float texture is not supported.'); + } + // 使用HDR浮点纹理,FLOAT在IOS上报错,使用HALF_FLOAT const textureType = enableHDR ? glContext.HALF_FLOAT : glContext.UNSIGNED_BYTE; From bc132f9d3a16a7d15d223f8fb5b731a9e0f7303e Mon Sep 17 00:00:00 2001 From: Sruim <934274351@qq.com> Date: Wed, 6 Nov 2024 17:48:34 +0800 Subject: [PATCH 69/88] =?UTF-8?q?feat:=20=E5=A2=9E=E5=8A=A0=E5=AF=8C?= =?UTF-8?q?=E6=96=87=E6=9C=AC=E6=8F=92=E4=BB=B6=20(#704)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat(rich-text): 增加 rich-text demo * feat: 增加富文本插件 * refactor: 移除不再使用的富文本相关代码 * refactor: 优化富文本插件名称和相关代码 - 优化富文本插件的名称,将 "@galacean/effects-plugin-richtext" 改为 "@galacean/effects-plugin-rich-text" - 移除不再使用的富文本相关代码 - 增加富文本插件的解析器和组件 * refactor: 优化富文本插件名称和相关代码 - 优化富文本插件名称和相关代码,修复了字体缩放导致的偏移问题。 * refactor: 优化富文本插件名称和相关代码 * refactor: 将富文本页面重命名为文本页面,并更新页面标题。同时,更新了引用富文本插件的脚本文件路径。 * refactor: 更新富文本组件,优化文本宽度和高度计算,重命名视频加载器文件,移除不再使用的富文本选项类 * test(rich-text): 更新测试文件 * refactor: 调整富文本组件的字体大小和颜色,优化文本高度计算逻辑 * refactor: 移除富文本组件中不再使用的图像创建逻辑 * refactor: 更新颜色工具类,增强错误处理并优化颜色转换逻辑 * feat: 增加富文本插件的小程序产物 * fix: 修复文本组件的布局计算逻辑,确保字符偏移正确 * fix: 调整文本布局计算,修正字符偏移和行高逻辑 * fix: 添加空字符信息检查,避免计算错误 * fix: 添加上下文保存和恢复,确保文本渲染一致性 * fix: 初始化 processedTextOptions,确保文本处理选项正确生成 --------- Co-authored-by: yiiqii --- .../src/plugins/text/text-item.ts | 2 + plugin-packages/rich-text/LICENSE | 22 ++ plugin-packages/rich-text/README.md | 25 ++ plugin-packages/rich-text/demo/index.html | 18 ++ plugin-packages/rich-text/demo/simple.html | 22 ++ plugin-packages/rich-text/demo/src/simple.ts | 122 ++++++++ plugin-packages/rich-text/demo/tsconfig.json | 6 + plugin-packages/rich-text/package.json | 59 ++++ .../rich-text/rollup.appx.config.js | 27 ++ plugin-packages/rich-text/rollup.config.js | 44 +++ plugin-packages/rich-text/src/color-utils.ts | 76 +++++ plugin-packages/rich-text/src/index.ts | 22 ++ .../rich-text/src/rich-text-component.ts | 214 +++++++++++++ .../rich-text/src/rich-text-loader.ts | 5 + .../rich-text/src/rich-text-parser.ts | 219 +++++++++++++ plugin-packages/rich-text/test/index.html | 46 +++ plugin-packages/rich-text/test/src/index.ts | 2 + .../test/src/rich-text-parser.spec.ts | 289 ++++++++++++++++++ plugin-packages/rich-text/test/tsconfig.json | 6 + plugin-packages/rich-text/tsconfig.json | 17 ++ plugin-packages/rich-text/typedoc.json | 8 + plugin-packages/rich-text/vite.config.js | 70 +++++ typedoc.json | 1 + 23 files changed, 1322 insertions(+) create mode 100644 plugin-packages/rich-text/LICENSE create mode 100644 plugin-packages/rich-text/README.md create mode 100644 plugin-packages/rich-text/demo/index.html create mode 100644 plugin-packages/rich-text/demo/simple.html create mode 100644 plugin-packages/rich-text/demo/src/simple.ts create mode 100644 plugin-packages/rich-text/demo/tsconfig.json create mode 100644 plugin-packages/rich-text/package.json create mode 100644 plugin-packages/rich-text/rollup.appx.config.js create mode 100644 plugin-packages/rich-text/rollup.config.js create mode 100644 plugin-packages/rich-text/src/color-utils.ts create mode 100644 plugin-packages/rich-text/src/index.ts create mode 100644 plugin-packages/rich-text/src/rich-text-component.ts create mode 100644 plugin-packages/rich-text/src/rich-text-loader.ts create mode 100644 plugin-packages/rich-text/src/rich-text-parser.ts create mode 100644 plugin-packages/rich-text/test/index.html create mode 100644 plugin-packages/rich-text/test/src/index.ts create mode 100644 plugin-packages/rich-text/test/src/rich-text-parser.spec.ts create mode 100644 plugin-packages/rich-text/test/tsconfig.json create mode 100644 plugin-packages/rich-text/tsconfig.json create mode 100644 plugin-packages/rich-text/typedoc.json create mode 100644 plugin-packages/rich-text/vite.config.js diff --git a/packages/effects-core/src/plugins/text/text-item.ts b/packages/effects-core/src/plugins/text/text-item.ts index 6f52e7d2..dafb4e2d 100644 --- a/packages/effects-core/src/plugins/text/text-item.ts +++ b/packages/effects-core/src/plugins/text/text-item.ts @@ -61,6 +61,8 @@ export class TextComponent extends BaseRenderComponent { * 文本行数 */ lineCount = 0; + protected readonly SCALE_FACTOR = 0.1; + protected readonly ALPHA_FIX_VALUE = 1 / 255; constructor (engine: Engine, props?: TextItemProps) { super(engine); diff --git a/plugin-packages/rich-text/LICENSE b/plugin-packages/rich-text/LICENSE new file mode 100644 index 00000000..93808239 --- /dev/null +++ b/plugin-packages/rich-text/LICENSE @@ -0,0 +1,22 @@ +MIT LICENSE + +Copyright (c) 2019-present Ant Group Co., Ltd. https://www.antgroup.com/ + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/plugin-packages/rich-text/README.md b/plugin-packages/rich-text/README.md new file mode 100644 index 00000000..650a3554 --- /dev/null +++ b/plugin-packages/rich-text/README.md @@ -0,0 +1,25 @@ +# Galacean Effects RichText Plugin + +## Usage + +### Simple Import + +``` ts +import { Player } from '@galacean/effects'; +import '@galacean/effects-plugin-rich-text'; +``` + +## Development + +### Getting Started + +``` bash +# demo +pnpm --filter @galacean/effects-plugin-rich-text dev +``` + +> [Open in browser](http://localhost:8081/demo/) + +## Frame Comparison Testing + +> [Open in browser](http://localhost:8081/test/) diff --git a/plugin-packages/rich-text/demo/index.html b/plugin-packages/rich-text/demo/index.html new file mode 100644 index 00000000..54925360 --- /dev/null +++ b/plugin-packages/rich-text/demo/index.html @@ -0,0 +1,18 @@ + + + + +RichText 插件 - demo + + + + + +

+
RichText 插件 Demo
+ +
+ + diff --git a/plugin-packages/rich-text/demo/simple.html b/plugin-packages/rich-text/demo/simple.html new file mode 100644 index 00000000..dafd0a7b --- /dev/null +++ b/plugin-packages/rich-text/demo/simple.html @@ -0,0 +1,22 @@ + + + + + + 简单使用 - 富文本 插件 - demo + + + + +
+
+
+ + + diff --git a/plugin-packages/rich-text/demo/src/simple.ts b/plugin-packages/rich-text/demo/src/simple.ts new file mode 100644 index 00000000..e01c473e --- /dev/null +++ b/plugin-packages/rich-text/demo/src/simple.ts @@ -0,0 +1,122 @@ +import { Player, spec } from '@galacean/effects'; +import '@galacean/effects-plugin-rich-text'; + +const json = { + playerVersion: { web: '2.0.6', native: '0.0.1.202311221223' }, + images: [], + fonts: [], + version: '3.0', + shapes: [], + plugins: ['richtext'], + type: 'ge', + compositions: [ + { + id: '9', + name: 'richtext', + duration: 5, + startTime: 0, + endBehavior: 5, + previewSize: [750, 1624], + items: [{ id: '41716f6d8a1748a2b09fd36a09c91fd4' }], + camera: { fov: 60, far: 40, near: 0.1, clipMode: 1, position: [0, 0, 8], rotation: [0, 0, 0] }, + sceneBindings: [ + { key: { id: 'ac2826cbde3d4b2aa6a2bc99b34eef7d' }, value: { id: '41716f6d8a1748a2b09fd36a09c91fd4' } }, + ], + timelineAsset: { id: '420a2bf13d60445aa8e42e71cb248d8d' }, + }, + ], + components: [ + { + id: 'a7f9b9e3127e4ffa9682f7692ce90e09', + item: { id: '41716f6d8a1748a2b09fd36a09c91fd4' }, + dataType: 'RichTextComponent', + options: { + text: ' We are absolutely definitely not \nWe are green
with envy \nWe are DDDD amused. \nWe are not amused. \nWe are usually not amused. \nWe are largely unaffected. \nWe are colorfully
amused \n88$', + fontFamily: 'sans-serif', + fontSize: 30, + textColor: [255, 255, 10, 1], + fontWeight: 'bold', + textAlign: 1, + fontStyle: 'normal', + }, + renderer: { renderMode: 1 }, + }, + ], + geometries: [], + materials: [], + items: [ + { + id: '41716f6d8a1748a2b09fd36a09c91fd4', + name: 'richtext', + duration: 5, + type: 'text', + visible: true, + endBehavior: 5, + delay: 0, + renderLevel: 'B+', + transform: { + position: { x: 0.2202, y: 2.5601, z: 0 }, + eulerHint: { x: 0, y: 0, z: 0 }, + scale: { x: 2.2803, y: 1.5492, z: 1 }, + }, + components: [{ id: 'a7f9b9e3127e4ffa9682f7692ce90e09' }], + dataType: 'VFXItemData', + }, + ], + shaders: [], + bins: [], + textures: [], + animations: [], + miscs: [ + { + id: '420a2bf13d60445aa8e42e71cb248d8d', + dataType: 'TimelineAsset', + tracks: [{ id: 'ac2826cbde3d4b2aa6a2bc99b34eef7d' }], + }, + { id: '6ab1609043ee4e1db92bcfa6d2587045', dataType: 'ActivationPlayableAsset' }, + { id: '394c84cd0cec4295993206d2a0f74695', dataType: 'TransformPlayableAsset', positionOverLifetime: {} }, + { id: 'da4c8f544dd44c20b93d6098c93398e2', dataType: 'SpriteColorPlayableAsset' }, + { + id: '99811f1e59d44335a27c58e44b5ede02', + dataType: 'ActivationTrack', + children: [], + clips: [{ start: 0, duration: 5, endBehavior: 5, asset: { id: '6ab1609043ee4e1db92bcfa6d2587045' } }], + }, + { + id: '563df4e1a3ec44bcb4632b1c31feb5ba', + dataType: 'TransformTrack', + children: [], + clips: [{ start: 0, duration: 5, endBehavior: 5, asset: { id: '394c84cd0cec4295993206d2a0f74695' } }], + }, + { + id: '18e32d67d8154082b6235d009a73d782', + dataType: 'SpriteColorTrack', + children: [], + clips: [{ start: 0, duration: 5, endBehavior: 5, asset: { id: 'da4c8f544dd44c20b93d6098c93398e2' } }], + }, + { + id: 'ac2826cbde3d4b2aa6a2bc99b34eef7d', + dataType: 'ObjectBindingTrack', + children: [ + { id: '99811f1e59d44335a27c58e44b5ede02' }, + { id: '563df4e1a3ec44bcb4632b1c31feb5ba' }, + { id: '18e32d67d8154082b6235d009a73d782' }, + ], + clips: [], + }, + ], + compositionId: '9', +}; +const container = document.getElementById('J-container'); + +(async () => { + try { + const player = new Player({ + container, + }); + + await player.loadScene(json); + } catch (e) { + console.error('biz', e); + } +})(); diff --git a/plugin-packages/rich-text/demo/tsconfig.json b/plugin-packages/rich-text/demo/tsconfig.json new file mode 100644 index 00000000..0e8412eb --- /dev/null +++ b/plugin-packages/rich-text/demo/tsconfig.json @@ -0,0 +1,6 @@ +{ + "extends": "../../../tsconfig.base.json", + "include": [ + "src" + ] +} diff --git a/plugin-packages/rich-text/package.json b/plugin-packages/rich-text/package.json new file mode 100644 index 00000000..404d7e19 --- /dev/null +++ b/plugin-packages/rich-text/package.json @@ -0,0 +1,59 @@ +{ + "name": "@galacean/effects-plugin-rich-text", + "version": "2.0.4", + "description": "Galacean Effects player richtext plugin", + "module": "./dist/index.mjs", + "main": "./dist/index.js", + "browser": "./dist/index.min.js", + "types": "./dist/index.d.ts", + "files": [ + "dist" + ], + "exports": { + ".": { + "import": "./dist/index.mjs", + "require": "./dist/index.js", + "types": "./dist/index.d.ts" + }, + "./alipay": { + "import": "./dist/alipay.mjs", + "require": "./dist/alipay.js", + "types": "./dist/index.d.ts" + }, + "./weapp": { + "import": "./dist/weapp.mjs", + "require": "./dist/weapp.js", + "types": "./dist/index.d.ts" + }, + "./douyin": { + "import": "./dist/douyin.mjs", + "require": "./dist/douyin.js", + "types": "./dist/index.d.ts" + } + }, + "scripts": { + "dev": "vite", + "preview": "concurrently -k \"vite build -w\" \"sleep 6 && vite preview\"", + "prebuild": "pnpm clean", + "build": "pnpm build:declaration && pnpm build:module", + "build:module": "rollup -c", + "build:declaration": "tsc -d --emitDeclarationOnly", + "build:demo": "rimraf dist && vite build", + "clean": "rimraf dist && rimraf \"*+(.tsbuildinfo)\"", + "prepublishOnly": "pnpm build" + }, + "contributors": [ + { + "name": "云垣" + } + ], + "author": "Ant Group CO., Ltd.", + "license": "MIT", + "publishConfig": { + "access": "public", + "registry": "https://registry.npmjs.org" + }, + "devDependencies": { + "@galacean/effects": "workspace:*" + } +} diff --git a/plugin-packages/rich-text/rollup.appx.config.js b/plugin-packages/rich-text/rollup.appx.config.js new file mode 100644 index 00000000..dbe5e7d4 --- /dev/null +++ b/plugin-packages/rich-text/rollup.appx.config.js @@ -0,0 +1,27 @@ +/** + * 小程序产物编译配置 + */ +export default [ + 'alipay', + 'weapp', + 'douyin', +].map(platform => { + const paths = { '@galacean/effects': `@galacean/effects/${platform}` }; + + return { + input: `src/index.ts`, + output: [{ + file: `./dist/${platform}.mjs`, + format: 'es', + sourcemap: true, + paths, + }, { + file: `./dist/${platform}.js`, + format: 'cjs', + sourcemap: true, + paths, + }], + external: ['@galacean/effects'], + plugins: [], + }; +}); diff --git a/plugin-packages/rich-text/rollup.config.js b/plugin-packages/rich-text/rollup.config.js new file mode 100644 index 00000000..ff9f6221 --- /dev/null +++ b/plugin-packages/rich-text/rollup.config.js @@ -0,0 +1,44 @@ +import { getBanner, getPlugins } from '../../scripts/rollup-config-helper'; +import appxConfig from './rollup.appx.config'; + +const pkg = require('./package.json'); +const globals = { + '@galacean/effects': 'ge', +}; +const external = Object.keys(globals); +const banner = getBanner(pkg); +const plugins = getPlugins(pkg, { external }); + +export default () => { + return [ + { + input: 'src/index.ts', + output: [{ + file: pkg.module, + format: 'es', + banner, + sourcemap: true, + }, { + file: pkg.main, + format: 'cjs', + banner, + sourcemap: true, + }], + external, + plugins, + }, { + input: 'src/index.ts', + output: { + file: pkg.browser, + format: 'umd', + name: 'ge.richTextPlugin', + banner, + globals, + sourcemap: true, + }, + external, + plugins: getPlugins(pkg, { min: true, external }), + }, + ...appxConfig.map(config => ({ ...config, plugins: config.plugins.concat(plugins) })) + ]; +}; diff --git a/plugin-packages/rich-text/src/color-utils.ts b/plugin-packages/rich-text/src/color-utils.ts new file mode 100644 index 00000000..7ce25ac8 --- /dev/null +++ b/plugin-packages/rich-text/src/color-utils.ts @@ -0,0 +1,76 @@ +import type { spec } from '@galacean/effects'; + +/** + * 将颜色名称转换为 RGBA + * @param colorName - 颜色名称 + * @returns RGBA 颜色字符串 + */ +export function colorNameToRGBA (colorName: string): string { + if (typeof colorName !== 'string' || !colorName) { + throw new Error('Invalid color name provided'); + } + if (typeof document === 'undefined') { + throw new Error('This method requires a browser environment'); + } + const canvas = document.createElement('canvas'); + const context = canvas.getContext('2d'); + + if (context) { + try { + context.fillStyle = colorName; + const result = context.fillStyle; + + return result; + } finally { + // Clean up DOM element + canvas.remove(); + } + } + + throw new Error('Failed to get 2D context for color conversion!'); +} + +/** + * 将 16 进制颜色转换为 RGBA + * @param hex - 16 进制颜色 + * @param alpha - 透明度 + * @returns - RGBA 颜色 + */ +export function hexToRGBA (hex: string, alpha: number = 1): spec.vec4 { + hex = hex.replace(/^#/, ''); + + if (hex.length === 3 || hex.length === 4) { + hex = hex.split('').map(char => char + char).join(''); + } + + // Handle alpha channel in hex + if (hex.length === 8) { + const a = parseInt(hex.slice(6, 8), 16) / 255; + + hex = hex.slice(0, 6); + alpha = a; + } + const bigint = parseInt(hex, 16); + const r = (bigint >> 16) & 255; + const g = (bigint >> 8) & 255; + const b = bigint & 255; + + return [r, g, b, alpha]; +} + +/** + * 将颜色字符串转换为 RGBA + * @param color - 颜色字符串 + * @param alpha - 透明度 + * @returns - RGBA 颜色 + */ +export function toRGBA (color: string, alpha: number = 1): spec.vec4 { + if (typeof color !== 'string' || !color) { + throw new Error('Invalid color string'); + } + if (color.startsWith('#')) { + return hexToRGBA(color, alpha); + } else { + return hexToRGBA(colorNameToRGBA(color)); + } +} diff --git a/plugin-packages/rich-text/src/index.ts b/plugin-packages/rich-text/src/index.ts new file mode 100644 index 00000000..c90525c5 --- /dev/null +++ b/plugin-packages/rich-text/src/index.ts @@ -0,0 +1,22 @@ +import * as EFFECTS from '@galacean/effects'; +import { logger, registerPlugin, VFXItem } from '@galacean/effects'; +import { RichTextLoader } from './rich-text-loader'; + +export * from './rich-text-parser'; +export * from './rich-text-component'; + +/** + * 插件版本号 + */ +export const version = __VERSION__; + +registerPlugin('richtext', RichTextLoader, VFXItem, true); + +logger.info(`Plugin rich text version: ${version}.`); + +if (version !== EFFECTS.version) { + console.error( + '注意:请统一 RichText 插件与 Player 版本,不统一的版本混用会有不可预知的后果!', + '\nAttention: Please ensure the RichText plugin is synchronized with the Player version. Mixing and matching incompatible versions may result in unpredictable consequences!' + ); +} diff --git a/plugin-packages/rich-text/src/rich-text-component.ts b/plugin-packages/rich-text/src/rich-text-component.ts new file mode 100644 index 00000000..7523e2b1 --- /dev/null +++ b/plugin-packages/rich-text/src/rich-text-component.ts @@ -0,0 +1,214 @@ +import type { Engine } from '@galacean/effects'; +import { effectsClass, glContext, spec, TextComponent, Texture, TextLayout, TextStyle } from '@galacean/effects'; +import { generateProgram } from './rich-text-parser'; +import { toRGBA } from './color-utils'; + +/** + * + */ +export interface RichTextOptions { + text: string, + fontSize: number, + fontFamily?: string, + fontWeight?: spec.TextWeight, + fontStyle?: spec.FontStyle, + fontColor?: spec.vec4, + textStyle?: TextStyle, + isNewLine: boolean, +} + +interface RichCharInfo { + offsetX: number[], + /** + * 字符参数 + */ + richOptions: RichTextOptions[], + /** + * 段落宽度 + */ + width: number, + /** + * 段落高度 + */ + lineHeight: number, + /** + * 字体偏移高度 + */ + offsetY: number, +} + +let seed = 0; + +@effectsClass(spec.DataType.RichTextComponent) +export class RichTextComponent extends TextComponent { + processedTextOptions: RichTextOptions[] = []; + + private singleLineHeight: number = 1.571; + + constructor (engine: Engine) { + super(engine); + + this.name = 'MRichText' + seed++; + } + + private generateTextProgram(text: string) { + this.processedTextOptions = []; + const program = generateProgram((text, context) => { + const textArr = text.split('\n'); + + textArr.forEach((text, index) => { + const options: RichTextOptions = { + text, + fontSize: this.textStyle.fontSize, + isNewLine: false, + }; + + if (index > 0) { + options.isNewLine = true; + } + if ('b' in context) { + options.fontWeight = spec.TextWeight.bold; + } + + if ('i' in context) { + options.fontStyle = spec.FontStyle.italic; + } + + if ('size' in context && context.size) { + options.fontSize = parseInt(context.size, 10); + } + + if ('color' in context && context.color) { + options.fontColor = toRGBA(context.color); + } + this.processedTextOptions.push(options); + }); + + }); + + program(text); + } + + override updateTexture (flipY = true) { + if (!this.isDirty || !this.context || !this.canvas) { + return; + } + this.generateTextProgram(this.text); + let width = 0, height = 0; + const { textLayout, textStyle } = this; + + const context = this.context; + + context.save(); + const charsInfo: RichCharInfo[] = []; + const fontHeight = textStyle.fontSize * this.textStyle.fontScale; + let charInfo: RichCharInfo = { + richOptions: [], + offsetX: [], + width: 0, + lineHeight: fontHeight * this.singleLineHeight, + offsetY: fontHeight * (this.singleLineHeight - 1) / 2, + }; + + this.processedTextOptions.forEach((options, index) => { + const { text, isNewLine, fontSize } = options; + + if (isNewLine) { + charsInfo.push(charInfo); + width = Math.max(width, charInfo.width); + charInfo = { + richOptions: [], + offsetX: [], + width: 0, + lineHeight: fontHeight * this.singleLineHeight, + offsetY: fontHeight * (this.singleLineHeight - 1) / 2, + }; + height += charInfo.lineHeight; + } + const textWidth = context.measureText(text).width; + const textHeight = fontSize * this.singleLineHeight * this.textStyle.fontScale; + + if (textHeight > charInfo.lineHeight) { + height += textHeight - charInfo.lineHeight; + + charInfo.lineHeight = textHeight; + charInfo.offsetY = fontSize * this.textStyle.fontScale * (this.singleLineHeight - 1) / 2; + } + charInfo.offsetX.push(charInfo.width); + + charInfo.width += textWidth * fontSize * this.SCALE_FACTOR * this.textStyle.fontScale; + charInfo.richOptions.push(options); + }); + charsInfo.push(charInfo); + width = Math.max(width, charInfo.width); + height += charInfo.lineHeight; + const scale = width / height; + + this.item.transform.size.set(textStyle.fontSize * this.SCALE_FACTOR, textStyle.fontSize * this.SCALE_FACTOR * scale); + this.textLayout.width = width / textStyle.fontScale; + this.textLayout.height = height / textStyle.fontScale; + this.canvas.width = width; + this.canvas.height = height; + context.clearRect(0, 0, width, height); + // fix bug 1/255 + context.fillStyle = `rgba(255, 255, 255, ${this.ALPHA_FIX_VALUE})`; + if (!flipY) { + context.translate(0, height); + context.scale(1, -1); + } + let charsLineHeight = charsInfo[0].lineHeight - charsInfo[0].offsetY; + + if (charsInfo.length === 0) { + return; + } + charsInfo.forEach((charInfo, index) => { + const { richOptions, offsetX } = charInfo; + const x = textLayout.getOffsetX(textStyle, charInfo.width); + + if (index > 0) { + charsLineHeight += charInfo.lineHeight - charInfo.offsetY + charsInfo[index - 1].offsetY; + } + richOptions.forEach((options, index) => { + const { fontScale, textColor, fontFamily: textFamily, textWeight, fontStyle: richStyle } = textStyle; + const { text, fontSize, fontColor = textColor, fontFamily = textFamily, fontWeight = textWeight, fontStyle = richStyle } = options; + + context.font = `${fontStyle} ${fontWeight} ${fontSize * fontScale}px ${fontFamily}`; + + context.fillStyle = `rgba(${fontColor[0]}, ${fontColor[1]}, ${fontColor[2]}, ${fontColor[3]})`; + + context.fillText(text, offsetX[index] + x, charsLineHeight); + }); + }); + + //与 toDataURL() 两种方式都需要像素读取操作 + const imageData = context.getImageData(0, 0, this.canvas.width, this.canvas.height); + + this.material.setTexture('uSampler0', + Texture.createWithData( + this.engine, + { + data: new Uint8Array(imageData.data), + width: imageData.width, + height: imageData.height, + }, + { + flipY, + magFilter: glContext.LINEAR, + minFilter: glContext.LINEAR, + wrapS: glContext.CLAMP_TO_EDGE, + wrapT: glContext.CLAMP_TO_EDGE, + }, + ), + ); + + this.isDirty = false; + context.restore(); + } + + override updateWithOptions (options: spec.TextContentOptions) { + this.textStyle = new TextStyle(options); + this.textLayout = new TextLayout(options); + this.text = options.text ? options.text.toString() : ''; + } + +} diff --git a/plugin-packages/rich-text/src/rich-text-loader.ts b/plugin-packages/rich-text/src/rich-text-loader.ts new file mode 100644 index 00000000..d7939a9b --- /dev/null +++ b/plugin-packages/rich-text/src/rich-text-loader.ts @@ -0,0 +1,5 @@ +import { AbstractPlugin } from '@galacean/effects'; + +export class RichTextLoader extends AbstractPlugin { + override name = 'richtext'; +} diff --git a/plugin-packages/rich-text/src/rich-text-parser.ts b/plugin-packages/rich-text/src/rich-text-parser.ts new file mode 100644 index 00000000..fd458042 --- /dev/null +++ b/plugin-packages/rich-text/src/rich-text-parser.ts @@ -0,0 +1,219 @@ +enum TokenType { + ContextStart = 'ContextStart', + Text = 'Text', + ContextEnd = 'ContextEnd', +} + +export type Token = { + tokenType: TokenType, + value: string, +}; + +const contextStartRegexp = /^<([a-z]+)(=([^>]+))?>$/; +const contextEndRegexp = /^<\/([a-z]+)>$/; + +const rules: [TokenType, RegExp][] = [ + [TokenType.ContextStart, /^<[a-z]+(=[^>]+)?>/], + [TokenType.Text, /^[^=]+/], + [TokenType.ContextEnd, /^<\/[a-z]+>/], +]; + +export const lexer = (input: string, lexed: Token[] = [], cursor = 0): Token[] => { + if (!input) { + return lexed; + } + + for (const [tokenType, regex] of rules) { + const [tokenMatch] = regex.exec(input) ?? []; + + if (tokenMatch) { + + const len = tokenMatch.length; + + return lexer(input.slice(len), lexed.concat({ tokenType, value: tokenMatch }), cursor + len); + } + } + + throw new Error(`Unexpected token: "${input[0]}" at position ${cursor} while reading "${input}"`); +}; + +export type Attribute = { + attributeName?: string, + attributeParam?: string, +}; + +export type RichTextAST = { + attributes: Attribute[], + text: string, +}; + +export const richTextParser = (input: string): RichTextAST[] => { + const lexed = lexer(input); + let cursor = 0; + + const shift = () => { + const shifted = lexed.shift(); + + cursor += shifted?.value.length ?? 0; + + return shifted; + }; + + const peek = () => { + return lexed[0]; + }; + + const ast: RichTextAST[] = []; + + function Grammar (attributes: Attribute[] = [], expectedEndAttributeName = '') { + const parsing = true; + + while (parsing) { + const maybeText = Text(); + + if (maybeText) { + ast.push({ + attributes, + text: maybeText, + }); + + continue; + } + + const { attributeName, attributeParam } = ContextStart(); + + if (attributeName) { + Grammar(attributes.concat({ attributeName, attributeParam }), attributeName); + + continue; + } + + if (expectedEndAttributeName) { + const { attributeName: endAttributeName } = ContextEnd(); + + if (!endAttributeName) { + throw new Error('Expected end of font style with tag "' + expectedEndAttributeName + '" at position ' + cursor + ' but not found any tag!'); + } + + if (endAttributeName !== expectedEndAttributeName) { + throw new Error('Expected end of font style with tag "' + expectedEndAttributeName + '" at position ' + cursor + ' but found tag "' + endAttributeName + '"'); + } + + return; + } + + break; + } + } + + function Text (): string | undefined { + const maybeText = peek(); + + if (maybeText?.tokenType === TokenType.Text) { + shift(); + + return maybeText.value; + } + + return undefined; + } + + function ContextStart (): { attributeName?: string, attributeParam?: string } { + const maybeContextStart = peek(); + + if (maybeContextStart?.tokenType === TokenType.ContextStart) { + shift(); + + const matches = maybeContextStart.value.match(contextStartRegexp); + + if (matches) { + const attributeName = matches[1]; + const attributeParam = matches[3] ?? ''; + + return { attributeName, attributeParam }; + } + + throw new Error('Expected a font style start tag at position ' + cursor); + } + + return {}; + } + + function ContextEnd (): { attributeName?: string } { + const maybeContextEnd = peek(); + + if (maybeContextEnd?.tokenType === TokenType.ContextEnd) { + shift(); + + const matches = maybeContextEnd.value.match(contextEndRegexp); + + if (matches) { + const attributeName = matches[1]; + + return { attributeName }; + } + + throw new Error('Expected a font style end tag at position ' + cursor); + } + + return {}; + } + Grammar(); + + return ast; +}; + +export function generateProgram (textHandler: (text: string, context: Record) => void) { + return (richText: string) => { + const ast = richTextParser(richText); + + for (const node of ast) { + const text = node.text; + const context = node.attributes.reduce>((ctx, { attributeName, attributeParam }) => { + if (attributeName) { + ctx[attributeName] = attributeParam; + } + + return ctx; + }, {}); + + textHandler(text, context); + } + }; +} + +export function isRichText (text: string): boolean { + const lexed = lexer(text); + const contextTokens = lexed.filter(({ tokenType }) => tokenType === TokenType.ContextStart || tokenType === TokenType.ContextEnd); + + const contextStartTokens = contextTokens.filter(({ tokenType }) => tokenType === TokenType.ContextStart); + const contextEndTokens = contextTokens.filter(({ tokenType }) => tokenType === TokenType.ContextEnd); + + if (contextStartTokens.length !== contextEndTokens.length || !contextStartTokens.length) { + return false; + } + + const tokensOfAttribute = contextTokens.map(({ tokenType, value }) => ({ tokenType, value: tokenType === TokenType.ContextStart ? value.match(contextStartRegexp)![1] : value.match(contextEndRegexp)![1] })); + + function checkPaired ([token, ...restToken]: Token[], startContextAttributes: string[] = []): boolean { + if (!token) { + return startContextAttributes.length === 0; + } + + if (token.tokenType === TokenType.ContextStart) { + return checkPaired(restToken, startContextAttributes.concat(token.value)); + } else if (token.tokenType === TokenType.ContextEnd) { + const attributeName = startContextAttributes[startContextAttributes.length - 1]; + + if (attributeName !== token.value) { + return false; + } + + return checkPaired(restToken, startContextAttributes.slice(0, -1)); + } + + throw new Error('Unexpected token: ' + token.tokenType); + } + + return checkPaired(tokensOfAttribute); +} diff --git a/plugin-packages/rich-text/test/index.html b/plugin-packages/rich-text/test/index.html new file mode 100644 index 00000000..6ec1a7c4 --- /dev/null +++ b/plugin-packages/rich-text/test/index.html @@ -0,0 +1,46 @@ + + + + + Plugin RichText Tests + + + + + + + + + +
+ + + + + + + + + + + diff --git a/plugin-packages/rich-text/test/src/index.ts b/plugin-packages/rich-text/test/src/index.ts new file mode 100644 index 00000000..e335e5fc --- /dev/null +++ b/plugin-packages/rich-text/test/src/index.ts @@ -0,0 +1,2 @@ +import '@galacean/effects-plugin-rich-text'; +import './rich-text-parser.spec'; diff --git a/plugin-packages/rich-text/test/src/rich-text-parser.spec.ts b/plugin-packages/rich-text/test/src/rich-text-parser.spec.ts new file mode 100644 index 00000000..59565bc2 --- /dev/null +++ b/plugin-packages/rich-text/test/src/rich-text-parser.spec.ts @@ -0,0 +1,289 @@ +import { lexer, richTextParser as parser, generateProgram, isRichText, type RichTextAST, type Attribute } from '@galacean/effects-plugin-rich-text'; +const { expect } = chai; +const richText = ` + We are absolutely definitely not amused + We are green
with envy + We are amused. + We are not amused. + We are usually not amused. + We are largely unaffected. + We are colorfully
amused +`; + +const richTextTokens = [ + { + 'tokenType': 'Text', + 'value': '\n We are ', + }, { + 'tokenType': 'ContextStart', + 'value': '', + }, { + 'tokenType': 'Text', + 'value': 'absolutely ', + }, { + 'tokenType': 'ContextStart', + 'value': '', + }, { + 'tokenType': 'Text', + 'value': 'definitely', + }, { + 'tokenType': 'ContextEnd', + 'value': '', + }, { + 'tokenType': 'Text', + 'value': ' not', + }, { + 'tokenType': 'ContextEnd', + 'value': '', + }, { + 'tokenType': 'Text', + 'value': ' amused\n We are ', + }, { + 'tokenType': 'ContextStart', + 'value': '', + }, { + 'tokenType': 'Text', + 'value': 'green', + }, { + 'tokenType': 'ContextEnd', + 'value': '
', + }, { + 'tokenType': 'Text', + 'value': ' with envy\n We are ', + }, { + 'tokenType': 'ContextStart', + 'value': '', + }, { + 'tokenType': 'ContextEnd', + 'value': '', + }, { + 'tokenType': 'Text', + 'value': ' amused.\n We are ', + }, { + 'tokenType': 'ContextStart', + 'value': '', + }, { + 'tokenType': 'Text', + 'value': 'not', + }, { + 'tokenType': 'ContextEnd', + 'value': '', + }, { + 'tokenType': 'Text', + 'value': ' amused.\n We are ', + }, { + 'tokenType': 'ContextStart', + 'value': '', + }, { + 'tokenType': 'Text', + 'value': 'usually', + }, { + 'tokenType': 'ContextEnd', + 'value': '', + }, { + 'tokenType': 'Text', + 'value': ' not amused.\n We are ', + }, { + 'tokenType': 'ContextStart', + 'value': '', + }, { + 'tokenType': 'Text', + 'value': 'largely', + }, { + 'tokenType': 'ContextEnd', + 'value': '', + }, { + 'tokenType': 'Text', + 'value': ' unaffected.\n We are ', + }, { + 'tokenType': 'ContextStart', + 'value': '', + }, { + 'tokenType': 'Text', + 'value': 'colorfully', + }, { + 'tokenType': 'ContextEnd', + 'value': '
', + }, { + 'tokenType': 'Text', + 'value': ' amused\n', + }, +]; + +const richTextTokenValues = [ + '\n' + + ' We are ', '', 'absolutely ', '', 'definitely', '', ' not', '', ' amused\n' + + ' We are ', '', 'green', '
', ' with envy\n' + + ' We are ', '', '', ' amused.\n' + + ' We are ', '', 'not', '', ' amused.\n' + + ' We are ', '', 'usually', '', ' not amused.\n' + + ' We are ', '', 'largely', '', ' unaffected.\n' + + ' We are ', '', 'colorfully', '', ' amused\n', +]; + +const richTextAst: RichTextAST[] = [ + { attributes: [], text: '\n We are ' }, + { attributes: [{ attributeName: 'b', attributeParam: '' }], text: 'absolutely ' }, + { attributes: [{ attributeName: 'b', attributeParam: '' }, { attributeName: 'i', attributeParam: '' }], text: 'definitely' }, + { attributes: [{ attributeName: 'b', attributeParam: '' }], text: ' not' }, + { attributes: [], text: ' amused\n We are ' }, + { attributes: [{ attributeName: 'color', attributeParam: 'green' }], text: 'green' }, + { attributes: [], text: ' with envy\n We are ' }, + { attributes: [], text: ' amused.\n We are ' }, + { attributes: [{ attributeName: 'b', attributeParam: '' }], text: 'not' }, + { attributes: [], text: ' amused.\n We are ' }, + { attributes: [{ attributeName: 'i', attributeParam: '' }], text: 'usually' }, + { attributes: [], text: ' not amused.\n We are ' }, + { attributes: [{ attributeName: 'size', attributeParam: '50' }], text: 'largely' }, + { attributes: [], text: ' unaffected.\n We are ' }, + { attributes: [{ attributeName: 'color', attributeParam: '#ff0000ff' }], text: 'colorfully' }, + { attributes: [], text: ' amused\n' }, +]; + +const richTextAndContext = [ + { text: '\n We are ', context: {} }, + { text: 'absolutely ', context: { b: '' } }, + { text: 'definitely', context: { b: '', i: '' } }, + { text: ' not', context: { b: '' } }, + { text: ' amused\n We are ', context: {} }, + { text: 'green', context: { color: 'green' } }, + { text: ' with envy\n We are ', context: {} }, + { text: ' amused.\n We are ', context: {} }, + { text: 'not', context: { b: '' } }, + { text: ' amused.\n We are ', context: {} }, + { text: 'usually', context: { i: '' } }, + { text: ' not amused.\n We are ', context: {} }, + { text: 'largely', context: { size: '50' } }, + { text: ' unaffected.\n We are ', context: {} }, + { text: 'colorfully', context: { color: '#ff0000ff' } }, + { text: ' amused\n', context: {} }, +]; + +describe('test lexer and parser', () => { + it('lexer', () => { + const lexed = lexer(richText); + + expect(lexed).to.deep.equals(richTextTokens); + + expect(lexed.map(token => token.value)).to.deep.equals(richTextTokenValues); + }); + + it('parser', () => { + const parsed = parser(richText); + + expect(parsed).to.deep.equals(richTextAst); + }); + + it('generateProgram', () => { + const processedTextAndContext: Array<{ text: string, context: Record }> = []; + + const program = generateProgram((text, context) => { + processedTextAndContext.push({ text, context }); + }); + + program(richText); + + expect(processedTextAndContext).to.deep.equals(richTextAndContext); + }); +}); + +describe('test lexer and parser with wrapped rich text', () => { + const wrappedRichText = '' + richText + ''; + + const wrappedRichTextTokens = [{ + tokenType: 'ContextStart', + value: '', + }].concat(richTextTokens).concat([{ + tokenType: 'ContextEnd', + value: '', + }]); + + const wrappedRichTextTokenValues = [''].concat(richTextTokenValues).concat(['']); + + const wrappedRichTextAst: RichTextAST[] = richTextAst.map(node => ({ + ...node, + attributes: ([{ attributeName: 'del', attributeParam: '' }] as Attribute[]).concat(node.attributes), + })); + + const wrappedRichTextAndContext = richTextAndContext.map(node => ({ + ...node, + context: { ...node.context, del: '' }, + })); + + it('lexer', () => { + const lexed = lexer(wrappedRichText); + + expect(lexed).to.deep.equals(wrappedRichTextTokens); + + expect(lexed.map(token => token.value)).to.deep.equals(wrappedRichTextTokenValues); + }); + + it('parser', () => { + const parsed = parser(wrappedRichText); + + expect(parsed).to.deep.equals(wrappedRichTextAst); + }); + + it('generateProgram', () => { + const processedTextAndContext: Array<{ text: string, context: Record }> = []; + + const program = generateProgram((text, context) => { + processedTextAndContext.push({ text, context }); + }); + + program(wrappedRichText); + + expect(processedTextAndContext).to.deep.equals(wrappedRichTextAndContext); + }); +}); + +const unparsableRichText1 = ` + We are absolutely definitely not amused +`; + +const unparsableRichText2 = ` + We are absolutely +`; + +describe('test unparsable text', () => { + it('case 1', () => { + const lexed = lexer(unparsableRichText1); + + expect(lexed).to.deep.equals([ + { tokenType: 'Text', value: '\n We are ' }, + { tokenType: 'ContextStart', value: '' }, + { tokenType: 'Text', value: 'absolutely ' }, + { tokenType: 'ContextStart', value: '' }, + { tokenType: 'Text', value: 'definitely' }, + { tokenType: 'ContextEnd', value: '' }, + { tokenType: 'Text', value: ' not' }, + { tokenType: 'ContextEnd', value: '' }, + { tokenType: 'Text', value: ' amused\n' }, + ]); + + expect(() => parser(unparsableRichText1)).to.throw('Expected end of font style with tag "i" at position 41 but found tag "b"'); + }); + + it('case 2', () => { + const lexed = lexer(unparsableRichText2); + + expect(lexed).to.deep.equals([ + { tokenType: 'Text', value: '\n We are ' }, + { tokenType: 'ContextStart', value: '' }, + { tokenType: 'Text', value: 'absolutely\n' }, + ]); + + expect(() => parser(unparsableRichText2)).to.throw('Expected end of font style with tag "color" at position 34 but not found any tag!'); + }); +}); + +describe('test isRichText', () => { + it('should return true for rich text', () => { + expect(isRichText(richText)).to.be.true; + }); + + it('should return false for unparsable rich text', () => { + expect(isRichText(unparsableRichText1)).to.be.false; + expect(isRichText(unparsableRichText2)).to.be.false; + }); +}); diff --git a/plugin-packages/rich-text/test/tsconfig.json b/plugin-packages/rich-text/test/tsconfig.json new file mode 100644 index 00000000..0e8412eb --- /dev/null +++ b/plugin-packages/rich-text/test/tsconfig.json @@ -0,0 +1,6 @@ +{ + "extends": "../../../tsconfig.base.json", + "include": [ + "src" + ] +} diff --git a/plugin-packages/rich-text/tsconfig.json b/plugin-packages/rich-text/tsconfig.json new file mode 100644 index 00000000..ecbcdd51 --- /dev/null +++ b/plugin-packages/rich-text/tsconfig.json @@ -0,0 +1,17 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "rootDir": "src", + "outDir": "dist", + "composite": true + }, + "include": [ + "src", + "../../types" + ], + "references": [ + { + "path": "../../packages/effects" + } + ] +} diff --git a/plugin-packages/rich-text/typedoc.json b/plugin-packages/rich-text/typedoc.json new file mode 100644 index 00000000..b6aba3cb --- /dev/null +++ b/plugin-packages/rich-text/typedoc.json @@ -0,0 +1,8 @@ +{ + "extends": [ + "../../typedoc.base.json" + ], + "entryPoints": [ + "src/index.ts" + ] +} diff --git a/plugin-packages/rich-text/vite.config.js b/plugin-packages/rich-text/vite.config.js new file mode 100644 index 00000000..fac66d14 --- /dev/null +++ b/plugin-packages/rich-text/vite.config.js @@ -0,0 +1,70 @@ +import { resolve } from 'path'; +import { defineConfig } from 'vite'; +import tsconfigPaths from 'vite-tsconfig-paths'; +import legacy from '@vitejs/plugin-legacy'; +import ip from 'ip'; +import { glslInner, getSWCPlugin } from '../../scripts/rollup-config-helper'; + +export default defineConfig(({ mode }) => { + const development = mode === 'development'; + + return { + base: './', + build: { + rollupOptions: { + input: { + 'index': resolve(__dirname, 'demo/index.html'), + 'simple': resolve(__dirname, 'demo/simple.html'), + } + }, + minify: false, // iOS 9 等低版本加载压缩代码报脚本异常 + }, + server: { + host: '0.0.0.0', + port: 8081, + }, + preview: { + host: '0.0.0.0', + port: 8081, + }, + define: { + __VERSION__: 0, + __DEBUG__: development, + }, + plugins: [ + legacy({ + targets: ['iOS >= 9'], + modernPolyfills: ['es/global-this'], + }), + glslInner(), + getSWCPlugin({ + baseUrl: resolve(__dirname, '..', '..'), + }), + tsconfigPaths(), + configureServerPlugin(), + ], + }; +}); + +// 用于配置开发服务器的钩子 +function configureServerPlugin() { + const handleServer = function (server) { + const host = ip.address() ?? 'localhost'; + const port = server.config.server.port; + const baseUrl = `http://${host}:${port}`; + + setTimeout(() => { + console.log(` \x1b[1m\x1b[32m->\x1b[97m Demo: \x1b[0m\x1b[96m${baseUrl}/demo/index.html\x1b[0m`); + }, 1000); + } + + return { + name: 'configure-server', + configurePreviewServer(server) { + server.httpServer.once('listening', handleServer.bind(this, server)); + }, + configureServer(server) { + server.httpServer.once('listening', handleServer.bind(this, server)); + }, + } +} diff --git a/typedoc.json b/typedoc.json index 515ad84d..cbfe2fd8 100644 --- a/typedoc.json +++ b/typedoc.json @@ -10,6 +10,7 @@ "plugin-packages/model", "plugin-packages/multimedia", "plugin-packages/orientation-transformer", + "plugin-packages/rich-text", "plugin-packages/spine", "plugin-packages/stats" ], From 2d5f14fc9f12353805487292fb713f86579d8352 Mon Sep 17 00:00:00 2001 From: "wumaolin.wml" Date: Thu, 7 Nov 2024 15:44:54 +0800 Subject: [PATCH 70/88] fix: shape mask --- packages/effects-core/src/components/shape-component.ts | 2 +- packages/effects-core/src/composition-source-manager.ts | 4 +++- packages/effects-core/src/plugins/shape/graphics-path.ts | 4 ++-- packages/effects-core/src/plugins/shape/rectangle.ts | 2 +- packages/effects-core/src/plugins/shape/shape-path.ts | 4 ++-- 5 files changed, 9 insertions(+), 7 deletions(-) diff --git a/packages/effects-core/src/components/shape-component.ts b/packages/effects-core/src/components/shape-component.ts index b7c49399..c2643325 100644 --- a/packages/effects-core/src/components/shape-component.ts +++ b/packages/effects-core/src/components/shape-component.ts @@ -234,7 +234,7 @@ void main() { case ShapePrimitiveType.Rectangle: { const rectangleData = shapeData as RectangleData; - this.path.rect(-rectangleData.width / 2, rectangleData.height / 2, rectangleData.width, rectangleData.height); + this.path.rect(-rectangleData.width / 2, -rectangleData.height / 2, rectangleData.width, rectangleData.height); this.setFillColor(rectangleData.fill); diff --git a/packages/effects-core/src/composition-source-manager.ts b/packages/effects-core/src/composition-source-manager.ts index c5d8a1e5..fa031782 100644 --- a/packages/effects-core/src/composition-source-manager.ts +++ b/packages/effects-core/src/composition-source-manager.ts @@ -120,7 +120,9 @@ export class CompositionSourceManager implements Disposable { if ( itemProps.type === spec.ItemType.sprite || itemProps.type === spec.ItemType.particle || - itemProps.type === spec.ItemType.spine + itemProps.type === spec.ItemType.spine || + //@ts-expect-error + itemProps.type === spec.ItemType.shape ) { for (const componentPath of itemProps.components) { const componentData = componentMap[componentPath.id] as spec.SpriteComponentData | spec.ParticleSystemData; diff --git a/packages/effects-core/src/plugins/shape/graphics-path.ts b/packages/effects-core/src/plugins/shape/graphics-path.ts index ddf7d61b..bc915125 100644 --- a/packages/effects-core/src/plugins/shape/graphics-path.ts +++ b/packages/effects-core/src/plugins/shape/graphics-path.ts @@ -89,8 +89,8 @@ export class GraphicsPath { /** * Draws a rectangle shape. This method adds a new rectangle path to the current drawing. - * @param x - The x-coordinate of the top-left corner of the rectangle. - * @param y - The y-coordinate of the top-left corner of the rectangle. + * @param x - The x-coordinate of the upper-left corner of the rectangle. + * @param y - The y-coordinate of the upper-left corner of the rectangle. * @param w - The width of the rectangle. * @param h - The height of the rectangle. * @param transform - An optional `Matrix` object to apply a transformation to the rectangle. diff --git a/packages/effects-core/src/plugins/shape/rectangle.ts b/packages/effects-core/src/plugins/shape/rectangle.ts index ba2cef8a..bf414ada 100644 --- a/packages/effects-core/src/plugins/shape/rectangle.ts +++ b/packages/effects-core/src/plugins/shape/rectangle.ts @@ -3,7 +3,7 @@ import { ShapePrimitive } from './shape-primitive'; // const tempPoints = [new Point(), new Point(), new Point(), new Point()]; /** - * The `Rectangle` object is an area defined by its position, as indicated by its top-left corner + * The `Rectangle` object is an area defined by its position, as indicated by its upper-left corner * point (`x`, `y`) and by its `width` and its `height`. */ export class Rectangle extends ShapePrimitive { diff --git a/packages/effects-core/src/plugins/shape/shape-path.ts b/packages/effects-core/src/plugins/shape/shape-path.ts index b29b4e3e..f0773f3c 100644 --- a/packages/effects-core/src/plugins/shape/shape-path.ts +++ b/packages/effects-core/src/plugins/shape/shape-path.ts @@ -125,8 +125,8 @@ export class ShapePath { /** * Draws a rectangle shape. This method adds a new rectangle path to the current drawing. - * @param x - The x-coordinate of the top-left corner of the rectangle. - * @param y - The y-coordinate of the top-left corner of the rectangle. + * @param x - The x-coordinate of the upper-left corner of the rectangle. + * @param y - The y-coordinate of the upper-left corner of the rectangle. * @param w - The width of the rectangle. * @param h - The height of the rectangle. * @param transform - An optional `Matrix` object to apply a transformation to the rectangle. From afd42369ebc10c52af33d10e344a3e8c6afa8e06 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=8C=82=E5=AE=89?= <151805118+wumaolinmaoan@users.noreply.github.com> Date: Thu, 7 Nov 2024 18:00:04 +0800 Subject: [PATCH 71/88] refactor: color and vector4 curve value (#730) * refactor: color and vector4 curve value * fix: shape data reuse error --------- Co-authored-by: wumaolin.wml --- packages/effects-core/package.json | 2 +- .../effects-core/src/composition-source-manager.ts | 6 ++++-- .../src/math/value-getters/color-curve.ts | 8 ++++---- .../src/math/value-getters/vector4-curve.ts | 8 ++++---- .../color-property-playable-asset.ts | 2 +- .../vector4-property-playable-asset.ts | 2 +- pnpm-lock.yaml | 14 ++++++++++---- 7 files changed, 25 insertions(+), 17 deletions(-) diff --git a/packages/effects-core/package.json b/packages/effects-core/package.json index 8c55538f..d09939d1 100644 --- a/packages/effects-core/package.json +++ b/packages/effects-core/package.json @@ -51,7 +51,7 @@ "registry": "https://registry.npmjs.org" }, "dependencies": { - "@galacean/effects-specification": "2.1.0-alpha.3", + "@galacean/effects-specification": "2.1.0-alpha.4", "@galacean/effects-math": "1.1.0", "flatbuffers": "24.3.25", "uuid": "9.0.1", diff --git a/packages/effects-core/src/composition-source-manager.ts b/packages/effects-core/src/composition-source-manager.ts index c5d8a1e5..c20d131d 100644 --- a/packages/effects-core/src/composition-source-manager.ts +++ b/packages/effects-core/src/composition-source-manager.ts @@ -170,8 +170,10 @@ export class CompositionSourceManager implements Disposable { } if (shapeData !== undefined) { - // @ts-expect-error 类型转换问题 - renderContent.renderer.shape = getGeometryByShape(shapeData, split); + if (!('aPoint' in shapeData && 'index' in shapeData)) { + // @ts-expect-error 类型转换问题 + renderContent.renderer.shape = getGeometryByShape(shapeData, split); + } } } diff --git a/packages/effects-core/src/math/value-getters/color-curve.ts b/packages/effects-core/src/math/value-getters/color-curve.ts index d4a80e5b..7f64a098 100644 --- a/packages/effects-core/src/math/value-getters/color-curve.ts +++ b/packages/effects-core/src/math/value-getters/color-curve.ts @@ -13,10 +13,10 @@ export class ColorCurve extends ValueGetter { private aCurve: BezierCurve; override onCreate (arg: spec.ColorCurveData) { - this.rCurve = createValueGetter(arg.r) as BezierCurve; - this.gCurve = createValueGetter(arg.g) as BezierCurve; - this.bCurve = createValueGetter(arg.b) as BezierCurve; - this.aCurve = createValueGetter(arg.a) as BezierCurve; + this.rCurve = createValueGetter(arg[0]) as BezierCurve; + this.gCurve = createValueGetter(arg[1]) as BezierCurve; + this.bCurve = createValueGetter(arg[2]) as BezierCurve; + this.aCurve = createValueGetter(arg[3]) as BezierCurve; } override getValue (t: number): Color { diff --git a/packages/effects-core/src/math/value-getters/vector4-curve.ts b/packages/effects-core/src/math/value-getters/vector4-curve.ts index 14c497e3..cf6ce812 100644 --- a/packages/effects-core/src/math/value-getters/vector4-curve.ts +++ b/packages/effects-core/src/math/value-getters/vector4-curve.ts @@ -13,10 +13,10 @@ export class Vector4Curve extends ValueGetter { private wCurve: BezierCurve; override onCreate (arg: spec.Vector4CurveData) { - this.xCurve = createValueGetter(arg.x) as BezierCurve; - this.yCurve = createValueGetter(arg.y) as BezierCurve; - this.zCurve = createValueGetter(arg.z) as BezierCurve; - this.wCurve = createValueGetter(arg.w) as BezierCurve; + this.xCurve = createValueGetter(arg[0]) as BezierCurve; + this.yCurve = createValueGetter(arg[1]) as BezierCurve; + this.zCurve = createValueGetter(arg[2]) as BezierCurve; + this.wCurve = createValueGetter(arg[3]) as BezierCurve; } override getValue (t: number): Vector4 { diff --git a/packages/effects-core/src/plugins/timeline/playable-assets/color-property-playable-asset.ts b/packages/effects-core/src/plugins/timeline/playable-assets/color-property-playable-asset.ts index db20d890..11bd603b 100644 --- a/packages/effects-core/src/plugins/timeline/playable-assets/color-property-playable-asset.ts +++ b/packages/effects-core/src/plugins/timeline/playable-assets/color-property-playable-asset.ts @@ -9,7 +9,7 @@ import * as spec from '@galacean/effects-specification'; @effectsClass(spec.DataType.ColorPropertyPlayableAsset) export class ColorPropertyPlayableAsset extends PlayableAsset { @serialize() - curveData: spec.ColorCurveData; + curveData: spec.ColorCurveValue; override createPlayable (graph: PlayableGraph): Playable { const clipPlayable = new PropertyClipPlayable(graph); diff --git a/packages/effects-core/src/plugins/timeline/playable-assets/vector4-property-playable-asset.ts b/packages/effects-core/src/plugins/timeline/playable-assets/vector4-property-playable-asset.ts index 132b5159..03ac3b46 100644 --- a/packages/effects-core/src/plugins/timeline/playable-assets/vector4-property-playable-asset.ts +++ b/packages/effects-core/src/plugins/timeline/playable-assets/vector4-property-playable-asset.ts @@ -9,7 +9,7 @@ import type * as spec from '@galacean/effects-specification'; @effectsClass('Vector4PropertyPlayableAsset') export class Vector4PropertyPlayableAsset extends PlayableAsset { @serialize() - curveData: spec.Vector4CurveData; + curveData: spec.Vector4CurveValue; override createPlayable (graph: PlayableGraph): Playable { const clipPlayable = new PropertyClipPlayable(graph); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d6f73980..aced4726 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -150,8 +150,8 @@ importers: specifier: 1.1.0 version: 1.1.0 '@galacean/effects-specification': - specifier: 2.1.0-alpha.3 - version: 2.1.0-alpha.3 + specifier: 2.1.0-alpha.4 + version: 2.1.0-alpha.4 flatbuffers: specifier: 24.3.25 version: 24.3.25 @@ -248,6 +248,12 @@ importers: specifier: workspace:* version: link:../../packages/effects + plugin-packages/rich-text: + devDependencies: + '@galacean/effects': + specifier: workspace:* + version: link:../../packages/effects + plugin-packages/spine: dependencies: '@esotericsoftware/spine-core': @@ -2208,8 +2214,8 @@ packages: /@galacean/effects-specification@2.0.0: resolution: {integrity: sha512-7ieZALZYn5fwKLIGAskmYSKGX82ZkLRdDUInG21KsOJ2eQ5+HcI6mJU5tmN8vjZxQUc+RVuUcssExGbiaj2+5w==} - /@galacean/effects-specification@2.1.0-alpha.3: - resolution: {integrity: sha512-s11qcar30DqUGGXF3w7ioirYmt700TJWyGhAxARC3RmkRZOelYiDWczvjGRTHLSqBIB+B1oQ1jq380UHwn/iGg==} + /@galacean/effects-specification@2.1.0-alpha.4: + resolution: {integrity: sha512-negj3uKHT7u251MTFQL32K5sHzjHfJpouKNBnhjz2HBfVnlK7fackc4MyAyTJzRmmjZOdPwgZnu0iWgeXjeqOg==} dev: false /@humanwhocodes/config-array@0.11.14: From dd0dbf8645086e55b62b843bc0de03abf1d69077 Mon Sep 17 00:00:00 2001 From: SuRuiMeng <934274351@qq.com> Date: Thu, 7 Nov 2024 20:08:05 +0800 Subject: [PATCH 72/88] =?UTF-8?q?feat:=20=E6=9B=B4=E6=96=B0=E8=A7=86?= =?UTF-8?q?=E9=A2=91=E7=BB=84=E4=BB=B6=E4=BB=A5=E6=94=AF=E6=8C=81=E6=B8=B2?= =?UTF-8?q?=E6=9F=93=E4=BF=A1=E6=81=AF=E5=92=8C=E5=87=A0=E4=BD=95=E4=BD=93?= =?UTF-8?q?=E5=88=9B=E5=BB=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../multimedia/src/video/video-component.ts | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/plugin-packages/multimedia/src/video/video-component.ts b/plugin-packages/multimedia/src/video/video-component.ts index 0be7e3bb..ec555c32 100644 --- a/plugin-packages/multimedia/src/video/video-component.ts +++ b/plugin-packages/multimedia/src/video/video-component.ts @@ -2,7 +2,7 @@ import type { Texture, Engine, Texture2DSourceOptionsVideo, Asset, SpriteItemProps, GeometryFromShape, } from '@galacean/effects'; -import { spec, math, BaseRenderComponent, effectsClass, glContext } from '@galacean/effects'; +import { spec, math, BaseRenderComponent, effectsClass, glContext, getImageItemRenderInfo } from '@galacean/effects'; /** * 用于创建 videoItem 的数据类型, 经过处理后的 spec.VideoContent @@ -93,10 +93,20 @@ export class VideoComponent extends BaseRenderComponent { this.interaction = interaction; this.pauseVideo(); + this.renderInfo = getImageItemRenderInfo(this); - this.setItem(); + const geometry = this.createGeometry(glContext.TRIANGLES); + const material = this.createMaterial(this.renderInfo, 2); + + this.worldMatrix = math.Matrix4.fromIdentity(); + this.material = material; + this.geometry = geometry; this.material.setVector4('_Color', new math.Vector4().setFromArray(startColor)); + this.material.setVector4('_TexOffset', new math.Vector4().setFromArray([0, 0, 1, 1])); + + this.setItem(); + } override onUpdate (dt: number): void { From 93ed78fe8364837681865223cc7ba4baf716bb03 Mon Sep 17 00:00:00 2001 From: "wumaolin.wml" Date: Fri, 8 Nov 2024 10:29:35 +0800 Subject: [PATCH 73/88] chore: remove item duration zero error check --- packages/effects-core/src/vfx-item.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/effects-core/src/vfx-item.ts b/packages/effects-core/src/vfx-item.ts index a26e40ad..ae324e6e 100644 --- a/packages/effects-core/src/vfx-item.ts +++ b/packages/effects-core/src/vfx-item.ts @@ -682,7 +682,7 @@ export class VFXItem extends EffectsObject implements Disposable { data.content = { options: {} }; } - if (duration <= 0) { + if (duration < 0) { throw new Error(`Item duration can't be less than 0, see ${HELP_LINK['Item duration can\'t be less than 0']}.`); } From 4f9b5fbf220dd11612957ac173318da648b56159 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=8C=82=E5=AE=89?= <151805118+wumaolinmaoan@users.noreply.github.com> Date: Fri, 8 Nov 2024 14:45:20 +0800 Subject: [PATCH 74/88] refactor: opt global uniforms setting perfromance (#735) * refactor: opt global uniforms setting perfromance * chore: opt code --------- Co-authored-by: wumaolin.wml --- .../effects-core/src/render/render-frame.ts | 3 +- packages/effects-core/src/render/renderer.ts | 6 ++- packages/effects-webgl/src/gl-material.ts | 3 ++ packages/effects-webgl/src/gl-renderer.ts | 37 ++++++++----------- 4 files changed, 25 insertions(+), 24 deletions(-) diff --git a/packages/effects-core/src/render/render-frame.ts b/packages/effects-core/src/render/render-frame.ts index 6bf8fdb2..53458bd3 100644 --- a/packages/effects-core/src/render/render-frame.ts +++ b/packages/effects-core/src/render/render-frame.ts @@ -29,6 +29,7 @@ import { BloomThresholdPass, HQGaussianDownSamplePass, HQGaussianUpSamplePass, ToneMappingPass, } from './post-process-pass'; import type { PostProcessVolume, RendererComponent } from '../components'; +import type { Vector3 } from '@galacean/effects-math/es/core/vector3'; /** * 渲染数据,保存了当前渲染使用到的数据。 @@ -1012,7 +1013,7 @@ class FinalCopyRP extends RenderPass { export class GlobalUniforms { floats: Record = {}; ints: Record = {}; - // vector3s: Record = {}; + vector3s: Record = {}; vector4s: Record = {}; matrices: Record = {}; //... diff --git a/packages/effects-core/src/render/renderer.ts b/packages/effects-core/src/render/renderer.ts index fc96b90e..6c2b9523 100644 --- a/packages/effects-core/src/render/renderer.ts +++ b/packages/effects-core/src/render/renderer.ts @@ -1,4 +1,4 @@ -import type { Matrix4, Vector4 } from '@galacean/effects-math/es/core/index'; +import type { Matrix4, Vector3, Vector4 } from '@galacean/effects-math/es/core/index'; import type { RendererComponent } from '../components'; import type { Engine } from '../engine'; import type { Material } from '../material'; @@ -41,6 +41,10 @@ export class Renderer implements LostHandler, RestoreHandler { // OVERRIDE } + setGlobalVector3 (name: string, value: Vector3) { + // OVERRIDE + } + setGlobalMatrix (name: string, value: Matrix4) { // OVERRIDE } diff --git a/packages/effects-webgl/src/gl-material.ts b/packages/effects-webgl/src/gl-material.ts index c53d2a0f..f6e76063 100644 --- a/packages/effects-webgl/src/gl-material.ts +++ b/packages/effects-webgl/src/gl-material.ts @@ -331,6 +331,9 @@ export class GLMaterial extends Material { for (name in globalUniforms.vector4s) { shaderVariant.setVector4(name, globalUniforms.vector4s[name]); } + for (name in globalUniforms.vector3s) { + shaderVariant.setVector3(name, globalUniforms.vector3s[name]); + } for (name in globalUniforms.matrices) { shaderVariant.setMatrix(name, globalUniforms.matrices[name]); } diff --git a/packages/effects-webgl/src/gl-renderer.ts b/packages/effects-webgl/src/gl-renderer.ts index ec0e2c46..15a2174d 100644 --- a/packages/effects-webgl/src/gl-renderer.ts +++ b/packages/effects-webgl/src/gl-renderer.ts @@ -18,6 +18,7 @@ import { GLTexture } from './gl-texture'; type Matrix4 = math.Matrix4; type Vector4 = math.Vector4; +type Vector3 = math.Vector3; export class GLRenderer extends Renderer implements Disposable { glRenderer: GLRendererInternal; @@ -111,8 +112,16 @@ export class GLRenderer extends Renderer implements Disposable { this.setFramebuffer(null); this.clear(frame.clearAction); + const currentCamera = frame.camera; + this.renderingData.currentFrame = frame; - this.renderingData.currentCamera = frame.camera; + this.renderingData.currentCamera = currentCamera; + + this.setGlobalMatrix('effects_MatrixInvV', currentCamera.getInverseViewMatrix()); + this.setGlobalMatrix('effects_MatrixV', currentCamera.getViewMatrix()); + this.setGlobalMatrix('effects_MatrixVP', currentCamera.getViewProjectionMatrix()); + this.setGlobalMatrix('_MatrixP', currentCamera.getProjectionMatrix()); + this.setGlobalVector3('effects_WorldSpaceCameraPos', currentCamera.position); // 根据 priority 排序 pass sortByOrder(passes); @@ -173,6 +182,11 @@ export class GLRenderer extends Renderer implements Disposable { this.renderingData.currentFrame.globalUniforms.matrices[name] = value; } + override setGlobalVector3 (name: string, value: Vector3) { + this.checkGlobalUniform(name); + this.renderingData.currentFrame.globalUniforms.vector3s[name] = value; + } + override drawGeometry (geometry: Geometry, material: Material, subMeshIndex = 0): void { if (!geometry || !material) { return; @@ -182,27 +196,6 @@ export class GLRenderer extends Renderer implements Disposable { geometry.flush(); const renderingData = this.renderingData; - // TODO 后面移到管线相机渲染开始位置 - if (renderingData.currentFrame.globalUniforms) { - if (renderingData.currentCamera) { - this.setGlobalMatrix('effects_MatrixInvV', renderingData.currentCamera.getInverseViewMatrix()); - this.setGlobalMatrix('effects_MatrixV', renderingData.currentCamera.getViewMatrix()); - this.setGlobalMatrix('effects_MatrixVP', renderingData.currentCamera.getViewProjectionMatrix()); - this.setGlobalMatrix('_MatrixP', renderingData.currentCamera.getProjectionMatrix()); - } - - // TODO 自定义材质测试代码 - const time = Date.now() % 100000000 * 0.001 * 1; - let _Time = this.getGlobalVector4('_Time'); - - // TODO 待移除 - this.setGlobalFloat('_GlobalTime', time); - if (!_Time) { - _Time = new math.Vector4(time / 20, time, time * 2, time * 3); - } - this.setGlobalVector4('_Time', _Time.set(time / 20, time, time * 2, time * 3)); - } - if (renderingData.currentFrame.editorTransform) { material.setVector4('uEditorTransform', renderingData.currentFrame.editorTransform); } From c48a7f0217653f8c4ae8532f4f50e04ffef52653 Mon Sep 17 00:00:00 2001 From: SuRuiMeng <934274351@qq.com> Date: Fri, 8 Nov 2024 15:23:07 +0800 Subject: [PATCH 75/88] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E8=A7=86?= =?UTF-8?q?=E9=A2=91=E5=92=8C=E9=9F=B3=E9=A2=91=E5=8A=A0=E8=BD=BD=E6=8F=92?= =?UTF-8?q?=E4=BB=B6=EF=BC=8C=E7=AE=80=E5=8C=96=E9=A2=84=E7=BC=96=E8=AF=91?= =?UTF-8?q?=E9=80=BB=E8=BE=91=E5=B9=B6=E5=A2=9E=E5=BC=BA=E8=A7=86=E9=A2=91?= =?UTF-8?q?=E6=92=AD=E6=94=BE=E6=8E=A7=E5=88=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/plugins/sprite/sprite-item.ts | 17 +++++++++-------- .../multimedia/src/audio/audio-loader.ts | 19 +++---------------- .../multimedia/src/video/video-loader.ts | 19 +++---------------- 3 files changed, 15 insertions(+), 40 deletions(-) diff --git a/packages/effects-core/src/plugins/sprite/sprite-item.ts b/packages/effects-core/src/plugins/sprite/sprite-item.ts index 870a2c16..40532700 100644 --- a/packages/effects-core/src/plugins/sprite/sprite-item.ts +++ b/packages/effects-core/src/plugins/sprite/sprite-item.ts @@ -90,7 +90,16 @@ export class SpriteComponent extends BaseRenderComponent { } const life = Math.min(Math.max(time / duration, 0.0), 1.0); const ta = this.textureSheetAnimation; + const { video } = this.renderer.texture.source as Texture2DSourceOptionsVideo; + + if (video) { + if (time === 0) { + video.pause(); + } else { + video.play().catch(e => { this.engine.renderErrors.add(e); }); + } + } if (ta) { const total = ta.total || (ta.row * ta.col); let texRectX = 0; @@ -139,15 +148,7 @@ export class SpriteComponent extends BaseRenderComponent { dx, dy, ]); } - const { video } = this.renderer.texture.source as Texture2DSourceOptionsVideo; - if (video) { - if (time === 0 || (time === this.item.duration)) { - video.pause(); - } else { - video.play().catch(e => { this.engine.renderErrors.add(e); }); - } - } } override onDestroy (): void { diff --git a/plugin-packages/multimedia/src/audio/audio-loader.ts b/plugin-packages/multimedia/src/audio/audio-loader.ts index af517339..cdf91cbb 100644 --- a/plugin-packages/multimedia/src/audio/audio-loader.ts +++ b/plugin-packages/multimedia/src/audio/audio-loader.ts @@ -1,25 +1,12 @@ -import type { PrecompileOptions, Renderer, SceneLoadOptions } from '@galacean/effects'; -import { spec, AbstractPlugin, PLAYER_OPTIONS_ENV_EDITOR } from '@galacean/effects'; -import { checkAutoplayPermission, processMultimedia } from '../utils'; +import type { SceneLoadOptions } from '@galacean/effects'; +import { spec, AbstractPlugin } from '@galacean/effects'; +import { processMultimedia } from '../utils'; /** * 音频加载插件 */ export class AudioLoader extends AbstractPlugin { - static override precompile (compositions: spec.Composition[], renderer: Renderer, options?: PrecompileOptions): Promise { - const engine = renderer.engine; - const { env } = options ?? { env: '' }; - - if (env === PLAYER_OPTIONS_ENV_EDITOR) { - return checkAutoplayPermission().catch(error => { - engine.renderErrors.add(error); - }); - } - - return Promise.resolve(); - } - static override async processAssets ( json: spec.JSONScene, options: SceneLoadOptions = {}, diff --git a/plugin-packages/multimedia/src/video/video-loader.ts b/plugin-packages/multimedia/src/video/video-loader.ts index bfb1e76c..bbca5604 100644 --- a/plugin-packages/multimedia/src/video/video-loader.ts +++ b/plugin-packages/multimedia/src/video/video-loader.ts @@ -1,25 +1,12 @@ -import type { PrecompileOptions, Renderer, SceneLoadOptions } from '@galacean/effects'; -import { spec, AbstractPlugin, PLAYER_OPTIONS_ENV_EDITOR } from '@galacean/effects'; -import { checkAutoplayPermission, processMultimedia } from '../utils'; +import type { SceneLoadOptions } from '@galacean/effects'; +import { spec, AbstractPlugin } from '@galacean/effects'; +import { processMultimedia } from '../utils'; /** * 视频加载插件 */ export class VideoLoader extends AbstractPlugin { - static override precompile (compositions: spec.Composition[], renderer: Renderer, options?: PrecompileOptions): Promise { - const engine = renderer.engine; - const { env } = options ?? { env: '' }; - - if (env === PLAYER_OPTIONS_ENV_EDITOR) { - return checkAutoplayPermission().catch(error => { - engine.renderErrors.add(error); - }); - } - - return Promise.resolve(); - } - static override async processAssets ( json: spec.JSONScene, options: SceneLoadOptions = {}, From 776608f40e0d8fc87ad65391ef02568e7eb399e1 Mon Sep 17 00:00:00 2001 From: "wumaolin.wml" Date: Mon, 11 Nov 2024 10:50:45 +0800 Subject: [PATCH 76/88] fix: playable asset export --- .../effects-core/src/plugins/timeline/playable-assets/index.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/effects-core/src/plugins/timeline/playable-assets/index.ts b/packages/effects-core/src/plugins/timeline/playable-assets/index.ts index c7221b60..1c61f96a 100644 --- a/packages/effects-core/src/plugins/timeline/playable-assets/index.ts +++ b/packages/effects-core/src/plugins/timeline/playable-assets/index.ts @@ -1,3 +1,5 @@ +export * from './color-property-playable-asset'; export * from './float-property-playable-asset'; export * from './sub-composition-playable-asset'; export * from './timeline-asset'; +export * from './vector4-property-playable-asset'; From 068c0b922d5c99efae245893fd4e3dd6671cb4c7 Mon Sep 17 00:00:00 2001 From: "wumaolin.wml" Date: Mon, 11 Nov 2024 13:00:08 +0800 Subject: [PATCH 77/88] fix: color property track create wrong mixer --- .../src/plugins/timeline/tracks/color-property-track.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/effects-core/src/plugins/timeline/tracks/color-property-track.ts b/packages/effects-core/src/plugins/timeline/tracks/color-property-track.ts index 9d47728f..9bd52de1 100644 --- a/packages/effects-core/src/plugins/timeline/tracks/color-property-track.ts +++ b/packages/effects-core/src/plugins/timeline/tracks/color-property-track.ts @@ -1,13 +1,13 @@ import * as spec from '@galacean/effects-specification'; import { effectsClass } from '../../../decorators'; import type { PlayableGraph, Playable } from '../../cal/playable-graph'; -import { FloatPropertyMixerPlayable } from '../playables'; +import { ColorPropertyMixerPlayable } from '../playables'; import { PropertyTrack } from './property-track'; @effectsClass(spec.DataType.ColorPropertyTrack) export class ColorPropertyTrack extends PropertyTrack { override createTrackMixer (graph: PlayableGraph): Playable { - const mixer = new FloatPropertyMixerPlayable(graph); + const mixer = new ColorPropertyMixerPlayable(graph); const propertyNames = this.propertyNames; From a8c7e6b0350df3873a752c0744e8c9f23afa4b2d Mon Sep 17 00:00:00 2001 From: SuRuiMeng <934274351@qq.com> Date: Mon, 11 Nov 2024 17:09:34 +0800 Subject: [PATCH 78/88] fix: update default text value and adjust scaling in rich text component --- packages/effects-core/src/plugins/text/text-layout.ts | 2 +- plugin-packages/rich-text/demo/src/simple.ts | 2 +- plugin-packages/rich-text/src/rich-text-component.ts | 10 +++++----- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/effects-core/src/plugins/text/text-layout.ts b/packages/effects-core/src/plugins/text/text-layout.ts index 6f508021..0acb03d7 100644 --- a/packages/effects-core/src/plugins/text/text-layout.ts +++ b/packages/effects-core/src/plugins/text/text-layout.ts @@ -23,7 +23,7 @@ export class TextLayout { lineHeight: number; constructor (options: spec.TextContentOptions) { - const { textHeight = 100, textWidth = 100, textOverflow = spec.TextOverflow.display, textBaseline = spec.TextBaseline.top, textAlign = spec.TextAlignment.left, text, letterSpace = 0, autoWidth = false, fontSize, lineHeight = fontSize } = options; + const { textHeight = 100, textWidth = 100, textOverflow = spec.TextOverflow.display, textBaseline = spec.TextBaseline.top, textAlign = spec.TextAlignment.left, text = ' ', letterSpace = 0, autoWidth = false, fontSize, lineHeight = fontSize } = options; const tempWidth = fontSize + letterSpace; diff --git a/plugin-packages/rich-text/demo/src/simple.ts b/plugin-packages/rich-text/demo/src/simple.ts index e01c473e..38ee546e 100644 --- a/plugin-packages/rich-text/demo/src/simple.ts +++ b/plugin-packages/rich-text/demo/src/simple.ts @@ -57,7 +57,7 @@ const json = { transform: { position: { x: 0.2202, y: 2.5601, z: 0 }, eulerHint: { x: 0, y: 0, z: 0 }, - scale: { x: 2.2803, y: 1.5492, z: 1 }, + scale: { x: 1, y: 1, z: 1 }, }, components: [{ id: 'a7f9b9e3127e4ffa9682f7692ce90e09' }], dataType: 'VFXItemData', diff --git a/plugin-packages/rich-text/src/rich-text-component.ts b/plugin-packages/rich-text/src/rich-text-component.ts index 7523e2b1..51f92ec0 100644 --- a/plugin-packages/rich-text/src/rich-text-component.ts +++ b/plugin-packages/rich-text/src/rich-text-component.ts @@ -51,7 +51,7 @@ export class RichTextComponent extends TextComponent { this.name = 'MRichText' + seed++; } - private generateTextProgram(text: string) { + private generateTextProgram (text: string) { this.processedTextOptions = []; const program = generateProgram((text, context) => { const textArr = text.split('\n'); @@ -142,9 +142,9 @@ export class RichTextComponent extends TextComponent { charsInfo.push(charInfo); width = Math.max(width, charInfo.width); height += charInfo.lineHeight; - const scale = width / height; + const size = this.item.transform.size; - this.item.transform.size.set(textStyle.fontSize * this.SCALE_FACTOR, textStyle.fontSize * this.SCALE_FACTOR * scale); + this.item.transform.size.set(size.x * width * this.SCALE_FACTOR * this.SCALE_FACTOR, size.y * height * this.SCALE_FACTOR * this.SCALE_FACTOR); this.textLayout.width = width / textStyle.fontScale; this.textLayout.height = height / textStyle.fontScale; this.canvas.width = width; @@ -183,7 +183,7 @@ export class RichTextComponent extends TextComponent { //与 toDataURL() 两种方式都需要像素读取操作 const imageData = context.getImageData(0, 0, this.canvas.width, this.canvas.height); - this.material.setTexture('uSampler0', + this.material.setTexture('_MainTex', Texture.createWithData( this.engine, { @@ -208,7 +208,7 @@ export class RichTextComponent extends TextComponent { override updateWithOptions (options: spec.TextContentOptions) { this.textStyle = new TextStyle(options); this.textLayout = new TextLayout(options); - this.text = options.text ? options.text.toString() : ''; + this.text = options.text ? options.text.toString() : ' '; } } From 2055ee81aad297ccb0e424431cb1719c82ccaa8c Mon Sep 17 00:00:00 2001 From: "wumaolin.wml" Date: Tue, 12 Nov 2024 17:37:14 +0800 Subject: [PATCH 79/88] chore: opt composition start logic --- packages/effects-core/src/composition.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/packages/effects-core/src/composition.ts b/packages/effects-core/src/composition.ts index b39acb14..245059ca 100644 --- a/packages/effects-core/src/composition.ts +++ b/packages/effects-core/src/composition.ts @@ -473,10 +473,6 @@ export class Composition extends EventEmitter> imp if (pause) { this.resume(); } - if (!this.rootComposition.isStartCalled) { - this.rootComposition.onStart(); - this.rootComposition.isStartCalled = true; - } this.setSpeed(1); this.forwardTime(time + this.startTime); this.setSpeed(speed); From 3ae7410d94bc67ddd3aaba83f92d46c2773b1319 Mon Sep 17 00:00:00 2001 From: "wumaolin.wml" Date: Wed, 13 Nov 2024 11:40:04 +0800 Subject: [PATCH 80/88] chore: remove test shape interface --- packages/effects-core/package.json | 2 +- .../src/components/shape-component.ts | 308 +----------------- pnpm-lock.yaml | 8 +- 3 files changed, 21 insertions(+), 297 deletions(-) diff --git a/packages/effects-core/package.json b/packages/effects-core/package.json index d09939d1..cc429aa7 100644 --- a/packages/effects-core/package.json +++ b/packages/effects-core/package.json @@ -51,7 +51,7 @@ "registry": "https://registry.npmjs.org" }, "dependencies": { - "@galacean/effects-specification": "2.1.0-alpha.4", + "@galacean/effects-specification": "2.1.0-alpha.5", "@galacean/effects-math": "1.1.0", "flatbuffers": "24.3.25", "uuid": "9.0.1", diff --git a/packages/effects-core/src/components/shape-component.ts b/packages/effects-core/src/components/shape-component.ts index c2643325..175e5186 100644 --- a/packages/effects-core/src/components/shape-component.ts +++ b/packages/effects-core/src/components/shape-component.ts @@ -1,5 +1,5 @@ import { Color } from '@galacean/effects-math/es/core/color'; -import type * as spec from '@galacean/effects-specification'; +import * as spec from '@galacean/effects-specification'; import { effectsClass } from '../decorators'; import type { Engine } from '../engine'; import { glContext } from '../gl'; @@ -26,7 +26,7 @@ export class ShapeComponent extends MeshComponent { private path = new GraphicsPath(); private curveValues: CurveData[] = []; - private data: ShapeComponentData; + private data: spec.ShapeComponentData; private animated = true; private vert = ` @@ -172,14 +172,14 @@ void main() { this.geometry.setDrawCount(indices.length); } - private buildPath (data: ShapeComponentData) { + private buildPath (data: spec.ShapeComponentData) { this.path.clear(); const shapeData = data; switch (shapeData.type) { - case ShapePrimitiveType.Custom: { - const customData = shapeData as CustomShapeData; + case spec.ShapePrimitiveType.Custom: { + const customData = shapeData as spec.CustomShapeData; const points = customData.points; const easingIns = customData.easingIns; const easingOuts = customData.easingOuts; @@ -222,8 +222,8 @@ void main() { break; } - case ShapePrimitiveType.Ellipse: { - const ellipseData = shapeData as EllipseData; + case spec.ShapePrimitiveType.Ellipse: { + const ellipseData = shapeData as spec.EllipseData; this.path.ellipse(0, 0, ellipseData.xRadius, ellipseData.yRadius); @@ -231,8 +231,8 @@ void main() { break; } - case ShapePrimitiveType.Rectangle: { - const rectangleData = shapeData as RectangleData; + case spec.ShapePrimitiveType.Rectangle: { + const rectangleData = shapeData as spec.RectangleData; this.path.rect(-rectangleData.width / 2, -rectangleData.height / 2, rectangleData.width, rectangleData.height); @@ -240,8 +240,8 @@ void main() { break; } - case ShapePrimitiveType.Star: { - const starData = shapeData as StarData; + case spec.ShapePrimitiveType.Star: { + const starData = shapeData as spec.StarData; this.path.polyStar(starData.pointCount, starData.outerRadius, starData.innerRadius, starData.outerRoundness, starData.innerRoundness, StarType.Star); @@ -249,8 +249,8 @@ void main() { break; } - case ShapePrimitiveType.Polygon: { - const polygonData = shapeData as PolygonData; + case spec.ShapePrimitiveType.Polygon: { + const polygonData = shapeData as spec.PolygonData; this.path.polyStar(polygonData.pointCount, polygonData.radius, polygonData.radius, polygonData.roundness, polygonData.roundness, StarType.Polygon); @@ -261,7 +261,7 @@ void main() { } } - private setFillColor (fill?: ShapeFillParam) { + private setFillColor (fill?: spec.ShapeFillParam) { if (fill) { const color = fill.color; @@ -269,7 +269,7 @@ void main() { } } - override fromData (data: ShapeComponentData): void { + override fromData (data: spec.ShapeComponentData): void { super.fromData(data); this.data = data; @@ -280,280 +280,4 @@ void main() { //@ts-expect-error // TODO 新版蒙版上线后重构 setMaskMode(material, data.renderer.maskMode); } -} - -/************************** Test Interface **********************************/ - -/** - * 矢量图形组件 - */ -export interface ShapeComponentData extends spec.ComponentData { - /** - * 矢量类型 - */ - type: ShapePrimitiveType, -} - -/** - * 矢量图形类型 - */ -export enum ShapePrimitiveType { - /** - * 自定义图形 - */ - Custom, - /** - * 矩形 - */ - Rectangle, - /** - * 椭圆 - */ - Ellipse, - /** - * 多边形 - */ - Polygon, - /** - * 星形 - */ - Star, -} - -/** - * 自定义图形组件 - */ -export interface CustomShapeData extends ShapeComponentData { - /** - * 矢量类型 - 形状 - */ - type: ShapePrimitiveType.Custom, - /** - * 路径点 - */ - points: spec.Vector2Data[], - /** - * 入射控制点 - */ - easingIns: spec.Vector2Data[], - /** - * 入射控制点 - */ - easingOuts: spec.Vector2Data[], - /** - * 自定义形状 - */ - shapes: CustomShape[], -} - -/** - * 自定义形状参数 - */ -export interface CustomShape { - /** - * 点索引 - 用于构成闭合图形 - */ - indexes: CustomShapePoint[], - /** - * 是否为闭合图形 - 用于Stroke - */ - close: boolean, - /** - * 填充属性 - */ - fill?: ShapeFillParam, - /** - * 描边属性 - */ - stroke?: ShapeStrokeParam, - /** - * 空间变换 - */ - transform?: spec.TransformData, -} - -/** - * 自定义形状点 - */ -export interface CustomShapePoint { - /** - * 顶点索引 - */ - point: number, - /** - * 入射点索引 - */ - easingIn: number, - /** - * 出射点索引 - */ - easingOut: number, -} - -/** - * 矢量填充参数 - */ -export interface ShapeFillParam { - /** - * 填充颜色 - */ - color: spec.ColorData, -} - -/** - * 矢量描边参数 - */ -export interface ShapeStrokeParam { - /** - * 线宽 - */ - width: number, - /** - * 线颜色 - */ - color: spec.ColorData, - /** - * 连接类型 - */ - connectType: ShapeConnectType, - /** - * 点类型 - */ - pointType: ShapePointType, -} - -// 本期无该功能 待补充 -export enum ShapeConnectType { - -} - -// @待补充 -export enum ShapePointType { -} - -/** - * 椭圆组件参数 - */ -export interface EllipseData extends ShapeComponentData { - type: ShapePrimitiveType.Ellipse, - /** - * x 轴半径 - * -- TODO 后续完善类型 - * -- TODO 可以看一下用xRadius/yRadius 还是 width/height - */ - xRadius: number, - /** - * y 轴半径 - */ - yRadius: number, - /** - * 填充属性 - */ - fill?: ShapeFillParam, - /** - * 描边属性 - */ - stroke?: ShapeStrokeParam, - /** - * 空间变换 - */ - transform?: spec.TransformData, -} - -/** - * 星形参数 - */ -export interface StarData extends ShapeComponentData { - /** - * 顶点数 - 内外顶点同数 - */ - pointCount: number, - /** - * 内径 - */ - innerRadius: number, - /** - * 外径 - */ - outerRadius: number, - /** - * 内径点圆度 - */ - innerRoundness: number, - /** - * 外径点圆度 - */ - outerRoundness: number, - /** - * 填充属性 - */ - fill?: ShapeFillParam, - /** - * 描边属性 - */ - stroke?: ShapeStrokeParam, - /** - * 空间变换 - */ - transform?: spec.TransformData, -} - -/** - * 多边形参数 - */ -export interface PolygonData extends ShapeComponentData { - /** - * 顶点数 - */ - pointCount: number, - /** - * 外切圆半径 - */ - radius: number, - /** - * 角点圆度 - */ - roundness: number, - /** - * 填充属性 - */ - fill?: ShapeFillParam, - /** - * 描边属性 - */ - stroke?: ShapeStrokeParam, - /** - * 空间变换 - */ - transform?: spec.TransformData, -} - -/** - * 矩形参数 - */ -export interface RectangleData extends ShapeComponentData { - /** - * 宽度 - */ - width: number, - /** - * 高度 - */ - height: number, - /** - * 角点元素 - */ - roundness: number, - /** - * 填充属性 - */ - fill?: ShapeFillParam, - /** - * 描边属性 - */ - stroke?: ShapeStrokeParam, - /** - * 空间变换 - */ - transform?: spec.TransformData, -} +} \ No newline at end of file diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index aced4726..32de7f56 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -150,8 +150,8 @@ importers: specifier: 1.1.0 version: 1.1.0 '@galacean/effects-specification': - specifier: 2.1.0-alpha.4 - version: 2.1.0-alpha.4 + specifier: 2.1.0-alpha.5 + version: 2.1.0-alpha.5 flatbuffers: specifier: 24.3.25 version: 24.3.25 @@ -2214,8 +2214,8 @@ packages: /@galacean/effects-specification@2.0.0: resolution: {integrity: sha512-7ieZALZYn5fwKLIGAskmYSKGX82ZkLRdDUInG21KsOJ2eQ5+HcI6mJU5tmN8vjZxQUc+RVuUcssExGbiaj2+5w==} - /@galacean/effects-specification@2.1.0-alpha.4: - resolution: {integrity: sha512-negj3uKHT7u251MTFQL32K5sHzjHfJpouKNBnhjz2HBfVnlK7fackc4MyAyTJzRmmjZOdPwgZnu0iWgeXjeqOg==} + /@galacean/effects-specification@2.1.0-alpha.5: + resolution: {integrity: sha512-PpfKmGJHxFZMKGNek1mubpDnE4RXNiwpktshEfXlZhmp2/MtATej5dv6a/shjvsDNg95V2iIVvoFt7txd77VfA==} dev: false /@humanwhocodes/config-array@0.11.14: From ca08ead46361258ff8f39fe4a8997f276524dd01 Mon Sep 17 00:00:00 2001 From: Sruim <934274351@qq.com> Date: Wed, 13 Nov 2024 14:23:25 +0800 Subject: [PATCH 81/88] feat: add goto event for composition and improve texture cleanup on destroy (#743) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: add goto event for composition and improve texture cleanup on destroy * refactor: 优化 video 获取逻辑 * feat: 添加 goto 事件以支持时间跳转 --- packages/effects-core/src/composition.ts | 1 + packages/effects-core/src/events/types.ts | 5 +++++ .../src/plugins/sprite/sprite-item.ts | 19 +++++++++++++++++-- packages/effects-webgl/src/gl-texture.ts | 9 --------- .../multimedia/src/video/video-component.ts | 19 +++++++++++++++++++ 5 files changed, 42 insertions(+), 11 deletions(-) diff --git a/packages/effects-core/src/composition.ts b/packages/effects-core/src/composition.ts index 245059ca..0da8ddb0 100644 --- a/packages/effects-core/src/composition.ts +++ b/packages/effects-core/src/composition.ts @@ -479,6 +479,7 @@ export class Composition extends EventEmitter> imp if (pause) { this.pause(); } + this.emit('goto', { time }); } addItem (item: VFXItem) { diff --git a/packages/effects-core/src/events/types.ts b/packages/effects-core/src/events/types.ts index d62cbcd0..d9945e1a 100644 --- a/packages/effects-core/src/events/types.ts +++ b/packages/effects-core/src/events/types.ts @@ -33,4 +33,9 @@ export type CompositionEvent = { * 合成行为为销毁/冻结时只会触发一次 */ ['end']: [endInfo: { composition: C }], + /** + * 时间跳转事件 + * 用于在合成中跳转到指定时间 + */ + ['goto']: [gotoInfo: { time: number }], }; diff --git a/packages/effects-core/src/plugins/sprite/sprite-item.ts b/packages/effects-core/src/plugins/sprite/sprite-item.ts index 40532700..c118e77d 100644 --- a/packages/effects-core/src/plugins/sprite/sprite-item.ts +++ b/packages/effects-core/src/plugins/sprite/sprite-item.ts @@ -6,7 +6,7 @@ import { glContext } from '../../gl'; import type { GeometryDrawMode } from '../../render'; import { Geometry } from '../../render'; import type { GeometryFromShape } from '../../shape'; -import type { Texture, Texture2DSourceOptionsVideo } from '../../texture'; +import { TextureSourceType, type Texture, type Texture2DSourceOptionsVideo } from '../../texture'; import type { PlayableGraph, Playable } from '../cal/playable-graph'; import { PlayableAsset } from '../cal/playable-graph'; import type { ColorPlayableAssetData } from '../../animation'; @@ -152,9 +152,24 @@ export class SpriteComponent extends BaseRenderComponent { } override onDestroy (): void { + const textures = this.getTextures(); + if (this.item && this.item.composition) { - this.item.composition.destroyTextures(this.getTextures()); + this.item.composition.destroyTextures(textures); } + + textures.forEach(texture => { + const source = texture.source; + + if ( + source.sourceType === TextureSourceType.video && + source?.video + ) { + source.video.pause(); + source.video.src = ''; + source.video.load(); + } + }); } override createGeometry (mode: GeometryDrawMode) { diff --git a/packages/effects-webgl/src/gl-texture.ts b/packages/effects-webgl/src/gl-texture.ts index d8bc394a..75fcd39b 100644 --- a/packages/effects-webgl/src/gl-texture.ts +++ b/packages/effects-webgl/src/gl-texture.ts @@ -481,15 +481,6 @@ export class GLTexture extends Texture implements Disposable, RestoreHandler { if (this.pipelineContext && this.textureBuffer) { this.pipelineContext.gl.deleteTexture(this.textureBuffer); } - if ( - this.source.sourceType === TextureSourceType.video && - this.source.video && - this.initialized - ) { - this.source.video.pause(); - this.source.video.src = ''; - this.source.video.load(); - } this.width = 0; this.height = 0; this.textureBuffer = null; diff --git a/plugin-packages/multimedia/src/video/video-component.ts b/plugin-packages/multimedia/src/video/video-component.ts index ec555c32..faa81892 100644 --- a/plugin-packages/multimedia/src/video/video-component.ts +++ b/plugin-packages/multimedia/src/video/video-component.ts @@ -109,6 +109,15 @@ export class VideoComponent extends BaseRenderComponent { } + override onStart (): void { + super.onStart(); + this.item.composition?.on('goto', (option: { time: number }) => { + if (option.time > 0) { + this.setCurrentTime(option.time); + } + }); + } + override onUpdate (dt: number): void { super.onUpdate(dt); @@ -224,6 +233,16 @@ export class VideoComponent extends BaseRenderComponent { } } + override onDestroy (): void { + super.onDestroy(); + + if (this.video) { + this.video.pause(); + this.video.src = ''; + this.video.load(); + } + } + override onDisable (): void { super.onDisable(); From 43de49e484ff69d95bad7ae4b0c001a8abbca3cd Mon Sep 17 00:00:00 2001 From: Zheeeng Date: Wed, 13 Nov 2024 14:24:08 +0800 Subject: [PATCH 82/88] fix: the reporting words for unexpected parse result (#745) * fix: the reporting words for unexpected parse result * fix: test cases --- plugin-packages/rich-text/src/rich-text-parser.ts | 10 ++++++---- .../rich-text/test/src/rich-text-parser.spec.ts | 4 ++-- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/plugin-packages/rich-text/src/rich-text-parser.ts b/plugin-packages/rich-text/src/rich-text-parser.ts index fd458042..57b239de 100644 --- a/plugin-packages/rich-text/src/rich-text-parser.ts +++ b/plugin-packages/rich-text/src/rich-text-parser.ts @@ -92,11 +92,11 @@ export const richTextParser = (input: string): RichTextAST[] => { const { attributeName: endAttributeName } = ContextEnd(); if (!endAttributeName) { - throw new Error('Expected end of font style with tag "' + expectedEndAttributeName + '" at position ' + cursor + ' but not found any tag!'); + throw new Error('Expect an end tag marker "' + expectedEndAttributeName + '" at position ' + cursor + ' but found no tag!'); } if (endAttributeName !== expectedEndAttributeName) { - throw new Error('Expected end of font style with tag "' + expectedEndAttributeName + '" at position ' + cursor + ' but found tag "' + endAttributeName + '"'); + throw new Error('Expect an end tag marker "' + expectedEndAttributeName + '" at position ' + cursor + ' but found tag "' + endAttributeName + '"'); } return; @@ -133,7 +133,7 @@ export const richTextParser = (input: string): RichTextAST[] => { return { attributeName, attributeParam }; } - throw new Error('Expected a font style start tag at position ' + cursor); + throw new Error('Expected a start tag marker at position ' + cursor); } return {}; @@ -153,11 +153,12 @@ export const richTextParser = (input: string): RichTextAST[] => { return { attributeName }; } - throw new Error('Expected a font style end tag at position ' + cursor); + throw new Error('Expect an end tag marker at position ' + cursor); } return {}; } + Grammar(); return ast; @@ -184,6 +185,7 @@ export function generateProgram (textHandler: (text: string, context: Record tokenType === TokenType.ContextStart || tokenType === TokenType.ContextEnd); const contextStartTokens = contextTokens.filter(({ tokenType }) => tokenType === TokenType.ContextStart); diff --git a/plugin-packages/rich-text/test/src/rich-text-parser.spec.ts b/plugin-packages/rich-text/test/src/rich-text-parser.spec.ts index 59565bc2..54d319b2 100644 --- a/plugin-packages/rich-text/test/src/rich-text-parser.spec.ts +++ b/plugin-packages/rich-text/test/src/rich-text-parser.spec.ts @@ -261,7 +261,7 @@ describe('test unparsable text', () => { { tokenType: 'Text', value: ' amused\n' }, ]); - expect(() => parser(unparsableRichText1)).to.throw('Expected end of font style with tag "i" at position 41 but found tag "b"'); + expect(() => parser(unparsableRichText1)).to.throw('Expect an end tag marker "i" at position 41 but found tag "b"'); }); it('case 2', () => { @@ -273,7 +273,7 @@ describe('test unparsable text', () => { { tokenType: 'Text', value: 'absolutely\n' }, ]); - expect(() => parser(unparsableRichText2)).to.throw('Expected end of font style with tag "color" at position 34 but not found any tag!'); + expect(() => parser(unparsableRichText2)).to.throw('Expect an end tag marker "color" at position 34 but found no tag!'); }); }); From 1c1517f8df08001ee5c8e21fb54de1ca16cc88a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=8C=82=E5=AE=89?= <151805118+wumaolinmaoan@users.noreply.github.com> Date: Thu, 14 Nov 2024 14:01:10 +0800 Subject: [PATCH 83/88] refactor: opt render frame add render component logic (#747) * refactor: opt render frame render component add logic * fix: imgui demo * feat: add drawObjectPass --------- Co-authored-by: wumaolin.wml --- .../src/components/renderer-component.ts | 8 + packages/effects-core/src/composition.ts | 15 - .../src/plugins/interact/interact-item.ts | 2 + .../effects-core/src/render/render-frame.ts | 311 +----------------- web-packages/imgui-demo/src/ge.ts | 4 +- 5 files changed, 28 insertions(+), 312 deletions(-) diff --git a/packages/effects-core/src/components/renderer-component.ts b/packages/effects-core/src/components/renderer-component.ts index 47b48b56..9b635dc1 100644 --- a/packages/effects-core/src/components/renderer-component.ts +++ b/packages/effects-core/src/components/renderer-component.ts @@ -42,6 +42,14 @@ export class RendererComponent extends Component { this.item.rendererComponents.push(this); } + override onEnable (): void { + this.item.composition?.renderFrame.addMeshToDefaultRenderPass(this); + } + + override onDisable (): void { + this.item.composition?.renderFrame.removeMeshFromDefaultRenderPass(this); + } + override fromData (data: unknown): void { super.fromData(data); } diff --git a/packages/effects-core/src/composition.ts b/packages/effects-core/src/composition.ts index 0da8ddb0..abbe9241 100644 --- a/packages/effects-core/src/composition.ts +++ b/packages/effects-core/src/composition.ts @@ -518,8 +518,6 @@ export class Composition extends EventEmitter> imp prepareRender () { const frame = this.renderFrame; - frame._renderPasses[0].meshes.length = 0; - this.postLoaders.length = 0; this.pluginSystem.plugins.forEach(loader => { if (loader.prepareRenderFrame(this, frame)) { @@ -527,22 +525,9 @@ export class Composition extends EventEmitter> imp } }); - this.gatherRendererComponent(this.rootItem, frame); this.postLoaders.forEach(loader => loader.postProcessFrame(this, frame)); } - protected gatherRendererComponent (vfxItem: VFXItem, renderFrame: RenderFrame) { - for (const rendererComponent of vfxItem.rendererComponents) { - if (rendererComponent.isActiveAndEnabled) { - renderFrame.addMeshToDefaultRenderPass(rendererComponent); - } - } - - for (const item of vfxItem.children) { - this.gatherRendererComponent(item, renderFrame); - } - } - /** * 合成更新,针对所有 item 的更新 * @param deltaTime - 更新的时间步长 diff --git a/packages/effects-core/src/plugins/interact/interact-item.ts b/packages/effects-core/src/plugins/interact/interact-item.ts index 034579d0..bdc45808 100644 --- a/packages/effects-core/src/plugins/interact/interact-item.ts +++ b/packages/effects-core/src/plugins/interact/interact-item.ts @@ -104,6 +104,7 @@ export class InteractComponent extends RendererComponent { } override onDisable (): void { + super.onDisable(); if (this.item && this.item.composition) { if (this.duringPlay && !this.item.transform.getValid()) { this.item.composition.removeInteractiveItem(this.item, (this.item.props as spec.InteractItem).content.options.type); @@ -116,6 +117,7 @@ export class InteractComponent extends RendererComponent { } override onEnable (): void { + super.onEnable(); const { type } = this.interactData.options as spec.ClickInteractOption; if (type === spec.InteractType.CLICK) { diff --git a/packages/effects-core/src/render/render-frame.ts b/packages/effects-core/src/render/render-frame.ts index 53458bd3..77afcb2c 100644 --- a/packages/effects-core/src/render/render-frame.ts +++ b/packages/effects-core/src/render/render-frame.ts @@ -221,6 +221,8 @@ export class RenderFrame implements Disposable { protected destroyed = false; protected renderPassInfoMap: WeakMap = new WeakMap(); + private drawObjectPass: RenderPass; + constructor (options: RenderFrameOptions) { const { camera, keepColorBuffer, renderer, @@ -263,17 +265,16 @@ export class RenderFrame implements Disposable { }; } - // 创建 drawObjectPass - const renderPasses = [ - new RenderPass(renderer, { - name: RENDER_PASS_NAME_PREFIX, - priority: RenderPassPriorityNormal, - meshOrder: OrderType.ascending, - depthStencilAttachment, - attachments, - clearAction: drawObjectPassClearAction, - }), - ]; + this.drawObjectPass = new RenderPass(renderer, { + name: RENDER_PASS_NAME_PREFIX, + priority: RenderPassPriorityNormal, + meshOrder: OrderType.ascending, + depthStencilAttachment, + attachments, + clearAction: drawObjectPassClearAction, + }); + + const renderPasses = [this.drawObjectPass]; this.setRenderPasses(renderPasses); @@ -399,48 +400,8 @@ export class RenderFrame implements Disposable { * 根据 Mesh 优先级添加到 RenderPass * @param mesh - 要添加的 Mesh 对象 */ - addMeshToDefaultRenderPass (mesh?: RendererComponent) { - if (!mesh) { - return; - } - - this.renderPasses[0].addMesh(mesh); - - // const renderPasses = this.renderPasses; - // const infoMap = this.renderPassInfoMap; - // const { priority } = mesh; - - // for (let i = 1; i < renderPasses.length; i++) { - // const renderPass = renderPasses[i - 1]; - // const info = infoMap.get(renderPasses[i])!; - - // if (info && info.listStart > priority && (priority > infoMap.get(renderPass)!.listEnd || i === 1)) { - // return this.addToRenderPass(renderPass, mesh); - // } - // } - // // TODO: diff逻辑待优化,有时会添加进找不到的元素 - // let last = renderPasses[renderPasses.length - 1]; - - // // TODO: 是否添加mesh到pass的判断方式需要优化,先通过长度判断是否有postprocess - // for (const pass of renderPasses) { - // if (!(pass instanceof HQGaussianDownSamplePass - // || pass instanceof BloomThresholdPass - // || pass instanceof ToneMappingPass - // || pass instanceof HQGaussianUpSamplePass - // || pass.name === 'mars-final-copy')) { - // last = pass; - // } - // } - - // // if (priority > infoMap.get(last)!.listStart || renderPasses.length === 1) { - // // return this.addToRenderPass(last, mesh); - // // } - - // return this.addToRenderPass(last, mesh); - - // if (__DEBUG__) { - // throw Error('render pass not found'); - // } + addMeshToDefaultRenderPass (mesh: RendererComponent) { + this.drawObjectPass.addMesh(mesh); } /** @@ -448,206 +409,10 @@ export class RenderFrame implements Disposable { * 如果 renderPass 中没有 mesh,此 renderPass 会被删除 * @param mesh - 要删除的 Mesh 对象 */ - removeMeshFromDefaultRenderPass (mesh: Mesh) { - // const renderPasses = this.renderPasses; - // const infoMap = this.renderPassInfoMap; - - // for (let i = renderPasses.length - 1; i >= 0; i--) { - // const renderPass = renderPasses[i]; - // const info = infoMap.get(renderPass)!; - - // // 只有渲染场景物体的pass才有 info - // if (!info) { - // continue; - // } - - // if (info.listStart <= mesh.priority && info.listEnd >= mesh.priority) { - // const idx = renderPass.meshes.indexOf(mesh); - - // if (idx === -1) { - // return; - // } - - // // TODO hack: 现在的除了rp1和finalcopy pass,所有renderpass的meshes是一个copy加上一个filter mesh,这里的判断当filter mesh被删除后当前pass需不需要删除, - // // 判断需要更鲁棒。 - // const shouldRestoreRenderPass = idx === 1 && renderPass.meshes[0].name === MARS_COPY_MESH_NAME; - - // renderPass.removeMesh(mesh); - // if (shouldRestoreRenderPass) { - // const nextRenderPass = renderPasses[i + 1]; - // const meshes = renderPass.meshes; - - // if (!info.intermedia) { - // info.preRenderPass?.resetColorAttachments([]); - // //this.renderer.extension.resetColorAttachments?.(info.preRenderPass, []); - // } - // for (let j = 1; j < meshes.length; j++) { - // info.preRenderPass?.addMesh(meshes[j]); - // } - // const cp = renderPass.attachments[0]?.texture; - // const keepColor = cp === this.resource.color_a || cp === this.resource.color_b; - - // renderPass.dispose({ - // meshes: DestroyOptions.keep, - // colorAttachment: keepColor ? RenderPassDestroyAttachmentType.keep : RenderPassDestroyAttachmentType.destroy, - // depthStencilAttachment: RenderPassDestroyAttachmentType.keep, - // }); - // removeItem(renderPasses, renderPass); - // this.removeRenderPass(renderPass); - // infoMap.delete(renderPass); - // if (nextRenderPass) { - // this.updateRenderInfo(nextRenderPass); - // } - // if (info.preRenderPass) { - // this.updateRenderInfo(info.preRenderPass); - // } - // if (info.prePasses) { - // info.prePasses.forEach(rp => { - // this.removeRenderPass(rp.pass); - // if (rp?.destroyOptions !== false) { - // rp.pass.attachments.forEach(c => { - // if (c.texture !== this.resource.color_b || c.texture !== this.resource.color_a) { - // c.texture.dispose(); - // } - // }); - // const options: RenderPassDestroyOptions = { - // ...(rp?.destroyOptions ? rp.destroyOptions as RenderPassDestroyOptions : {}), - // depthStencilAttachment: RenderPassDestroyAttachmentType.keep, - // }; - - // rp.pass.dispose(options); - // } - // }); - // } - // this.resetRenderPassDefaultAttachment(renderPasses, Math.max(i - 1, 0)); - // if (renderPasses.length === 1) { - // renderPasses[0].resetColorAttachments([]); - // //this.renderer.extension.resetColorAttachments?.(renderPasses[0], []); - // this.removeRenderPass(this.resource.finalCopyRP); - // } - // } - - // return this.resetClearActions(); - // } - // } + removeMeshFromDefaultRenderPass (mesh: RendererComponent) { + this.drawObjectPass.removeMesh(mesh); } - // /** - // * 将 Mesh 所有在 RenderPass 进行切分 - // * @param mesh - 目标 Mesh 对象 - // * @param options - 切分选项,包含 RenderPass 相关的 Attachment 等数据 - // */ - // splitDefaultRenderPassByMesh (mesh: Mesh, options: RenderPassSplitOptions): RenderPass { - // const index = this.findMeshRenderPassIndex(mesh); - // const renderPass = this.renderPasses[index]; - - // if (__DEBUG__) { - // if (!renderPass) { - // throw Error('RenderPassNotFound'); - // } - // } - // this.createResource(); - // const meshIndex = renderPass.meshes.indexOf(mesh); - // const ms0 = renderPass.meshes.slice(0, meshIndex); - // const ms1 = renderPass.meshes.slice(meshIndex); - // const infoMap = this.renderPassInfoMap; - - // // TODO 为什么要加这个判断? - // // if (renderPass.attachments[0] && this.renderPasses[index + 1] !== this.resource.finalCopyRP) { - // // throw Error('not implement'); - // // } else { - // if (!options.attachments?.length) { - // throw Error('should include at least one color attachment'); - // } - // const defRPS = this.renderPasses; - // const defIndex = defRPS.indexOf(renderPass); - // const lastDefRP = defRPS[defIndex - 1]; - - // removeItem(defRPS, renderPass); - // const lastInfo = infoMap.get(renderPass); - - // infoMap.delete(renderPass); - // const filter = GPUCapability.getInstance().level === 2 ? glContext.LINEAR : glContext.NEAREST; - // const rp0 = new RenderPass({ - // name: RENDER_PASS_NAME_PREFIX + defIndex, - // priority: renderPass.priority, - // attachments: [{ - // texture: { - // sourceType: TextureSourceType.framebuffer, - // format: glContext.RGBA, - // name: 'frame_a', - // minFilter: filter, - // magFilter: filter, - // }, - // }], - // clearAction: renderPass.clearAction || { colorAction: TextureLoadAction.clear }, - // storeAction: renderPass.storeAction, - // depthStencilAttachment: this.resource.depthStencil, - // meshes: ms0, - // meshOrder: OrderType.ascending, - // }); - - // ms1.unshift(this.createCopyMesh()); - - // const renderPasses = this.renderPasses; - - // renderPasses[index] = rp0; - // const prePasses: RenderPass[] = []; - - // const restMeshes = ms1.slice(); - - // if (options.prePasses) { - // options.prePasses.forEach((pass, i) => { - // pass.priority = renderPass.priority + 1 + i; - // pass.setMeshes(ms1); - // prePasses.push(pass); - // }); - // renderPasses.splice(index + 1, 0, ...prePasses); - // restMeshes.splice(0, 2); - // } - // const copyRP = this.resource.finalCopyRP; - - // if (!renderPasses.includes(copyRP)) { - // renderPasses.push(copyRP); - // } - // // let sourcePass = (prePasses.length && !options.useLastDefaultPassColor) ? prePasses[prePasses.length - 1] : rp0; - - // const finalFilterPass = prePasses[prePasses.length - 1]; - - // finalFilterPass.initialize(this.renderer); - - // // 不切RT,接着上一个pass的渲染结果渲染 - // const rp1 = new RenderPass({ - // name: RENDER_PASS_NAME_PREFIX + (defIndex + 1), - // priority: renderPass.priority + 1 + (options.prePasses?.length || 0), - // meshes: restMeshes, - // meshOrder: OrderType.ascending, - // depthStencilAttachment: this.resource.depthStencil, - // storeAction: options.storeAction, - // clearAction: { - // depthAction: TextureLoadAction.whatever, - // stencilAction: TextureLoadAction.whatever, - // colorAction: TextureLoadAction.whatever, - // }, - // }); - - // renderPasses.splice(index + 1 + (options.prePasses?.length || 0), 0, rp1); - // this.setRenderPasses(renderPasses); - // this.updateRenderInfo(finalFilterPass); - // this.updateRenderInfo(rp0); - // this.updateRenderInfo(rp1); - - // // 目的是删除滤镜元素后,把之前滤镜用到的prePass给删除,逻辑有些复杂,考虑优化 - // infoMap.get(rp0)!.prePasses = lastInfo!.prePasses; - // prePasses.pop(); - // infoMap.get(finalFilterPass)!.prePasses = prePasses.map((pass, i) => { - // return { pass, destroyOptions: false }; - // }); - // this.resetClearActions(); - - // return finalFilterPass; - // } - /** * 销毁 RenderFrame * @param options - 可以有选择销毁一些对象 @@ -781,50 +546,6 @@ export class RenderFrame implements Disposable { } } - // protected updateRenderInfo (renderPass: RenderPass): RenderPassInfo { - // const map = this.renderPassInfoMap; - // const passes = this.renderPasses; - // let info: RenderPassInfo; - - // if (!map.has(renderPass)) { - // info = { - // intermedia: false, - // renderPass: renderPass, - // listStart: 0, - // listEnd: 0, - // }; - // map.set(renderPass, info); - // } else { - // info = map.get(renderPass)!; - // } - // info.intermedia = renderPass.attachments.length > 0; - // const meshes = renderPass.meshes; - - // if (meshes[0]) { - // info.listStart = (meshes[0].name === MARS_COPY_MESH_NAME ? meshes[1] : meshes[0]).priority; - // info.listEnd = meshes[meshes.length - 1].priority; - // } else { - // info.listStart = 0; - // info.listEnd = 0; - // } - // const index = passes.indexOf(renderPass); - // const depthStencilActon = index === 0 ? TextureLoadAction.clear : TextureLoadAction.whatever; - - // if (index === 0) { - // renderPass.clearAction.colorAction = TextureLoadAction.clear; - // } - // renderPass.clearAction.depthAction = depthStencilActon; - // renderPass.clearAction.stencilAction = depthStencilActon; - // if (index > -1) { - // renderPass.semantics.setSemantic('EDITOR_TRANSFORM', () => this.editorTransform); - // } else { - // renderPass.semantics.setSemantic('EDITOR_TRANSFORM', undefined); - // } - // info.preRenderPass = passes[index - 1]; - - // return info; - // } - /** * 设置 RenderPass 数组,直接修改内部的 RenderPass 数组 * @param passes - RenderPass 数组 diff --git a/web-packages/imgui-demo/src/ge.ts b/web-packages/imgui-demo/src/ge.ts index bb035cef..d7a81646 100644 --- a/web-packages/imgui-demo/src/ge.ts +++ b/web-packages/imgui-demo/src/ge.ts @@ -512,8 +512,8 @@ export class GalaceanEffects { }); } else { void GalaceanEffects.player.loadScene(url, { autoplay: true }).then(composition => { - composition.postProcessingEnabled = true; - composition.createRenderFrame(); + // composition.postProcessingEnabled = true; + // composition.createRenderFrame(); composition.rootItem.addComponent(PostProcessVolume); composition.renderFrame.addRenderPass(new OutlinePass(composition.renderer, { From ec17248f5c5e3ac869322fc739c902cc81205945 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=8C=82=E5=AE=89?= <151805118+wumaolinmaoan@users.noreply.github.com> Date: Mon, 18 Nov 2024 09:16:09 +0800 Subject: [PATCH 84/88] feat: timeline asset add flattened tracks property (#748) * feat: timeline asset add flattened tracks property * fix: note --------- Co-authored-by: wumaolin.wml --- packages/effects-core/src/comp-vfx-item.ts | 4 +- .../playable-assets/timeline-asset.ts | 82 ++++++++++++------- 2 files changed, 53 insertions(+), 33 deletions(-) diff --git a/packages/effects-core/src/comp-vfx-item.ts b/packages/effects-core/src/comp-vfx-item.ts index d4b5848f..1f87bc6b 100644 --- a/packages/effects-core/src/comp-vfx-item.ts +++ b/packages/effects-core/src/comp-vfx-item.ts @@ -234,9 +234,9 @@ export class CompositionComponent extends Behaviour { sceneBinding.key.boundObject = sceneBinding.value; } - // 未了通过帧对比,需要保证和原有的 update 时机一致。 + // 为了通过帧对比,需要保证和原有的 update 时机一致。 // 因此这边更新一次对象绑定,后续 timeline playable 中 sort tracks 的排序才能和原先的版本对上。 - // 如果不需要严格保证和之前的 updata 时机一致,这边的更新和 timeline playable 中的 sortTracks 都能去掉。 + // 如果不需要严格保证和之前的 updata 时机一致,这边的更新和 timeline asset 中的 sortTracks 都能去掉。 for (const masterTrack of this.timelineAsset.tracks) { this.updateTrackAnimatedObject(masterTrack); } diff --git a/packages/effects-core/src/plugins/timeline/playable-assets/timeline-asset.ts b/packages/effects-core/src/plugins/timeline/playable-assets/timeline-asset.ts index 33098bd9..8701001a 100644 --- a/packages/effects-core/src/plugins/timeline/playable-assets/timeline-asset.ts +++ b/packages/effects-core/src/plugins/timeline/playable-assets/timeline-asset.ts @@ -13,16 +13,34 @@ export class TimelineAsset extends PlayableAsset { @serialize() tracks: TrackAsset[] = []; + private cacheFlattenedTracks: TrackAsset[] | null = null; + + get flattenedTracks () { + if (!this.cacheFlattenedTracks) { + this.cacheFlattenedTracks = []; + // flatten track tree + for (const masterTrack of this.tracks) { + this.cacheFlattenedTracks.push(masterTrack); + this.addSubTracksRecursive(masterTrack, this.cacheFlattenedTracks); + } + } + + return this.cacheFlattenedTracks; + } + override createPlayable (graph: PlayableGraph): Playable { const timelinePlayable = new TimelinePlayable(graph); timelinePlayable.setTraversalMode(PlayableTraversalMode.Passthrough); + for (const track of this.tracks) { if (track instanceof ObjectBindingTrack) { track.create(this); } } - timelinePlayable.compileTracks(graph, this.tracks); + + this.sortTracks(this.tracks); + timelinePlayable.compileTracks(graph, this.flattenedTracks); return timelinePlayable; } @@ -33,9 +51,40 @@ export class TimelineAsset extends PlayableAsset { newTrack.name = name ? name : classConstructor.name; parent.addChild(newTrack); + this.invalidate(); + return newTrack; } + /** + * Invalidates the asset, called when tracks data changed + */ + private invalidate () { + this.cacheFlattenedTracks = null; + } + + private addSubTracksRecursive (track: TrackAsset, allTracks: TrackAsset[]) { + for (const subTrack of track.getChildTracks()) { + allTracks.push(subTrack); + } + for (const subTrack of track.getChildTracks()) { + this.addSubTracksRecursive(subTrack, allTracks); + } + } + + private sortTracks (tracks: TrackAsset[]) { + const sortedTracks = []; + + for (let i = 0; i < tracks.length; i++) { + sortedTracks.push(new TrackSortWrapper(tracks[i], i)); + } + sortedTracks.sort(compareTracks); + tracks.length = 0; + for (const trackWrapper of sortedTracks) { + tracks.push(trackWrapper.track); + } + } + override fromData (data: spec.TimelineAssetData): void { } } @@ -62,14 +111,7 @@ export class TimelinePlayable extends Playable { } compileTracks (graph: PlayableGraph, tracks: TrackAsset[]) { - this.sortTracks(tracks); - const outputTrack: TrackAsset[] = []; - - // flatten track tree - for (const masterTrack of tracks) { - outputTrack.push(masterTrack); - this.addSubTracksRecursive(masterTrack, outputTrack); - } + const outputTrack: TrackAsset[] = tracks; // map for searching track instance with track asset guid const trackInstanceMap: Record = {}; @@ -120,28 +162,6 @@ export class TimelinePlayable extends Playable { this.updateTrackAnimatedObject(trackInstance.children); } } - - private sortTracks (tracks: TrackAsset[]) { - const sortedTracks = []; - - for (let i = 0; i < tracks.length; i++) { - sortedTracks.push(new TrackSortWrapper(tracks[i], i)); - } - sortedTracks.sort(compareTracks); - tracks.length = 0; - for (const trackWrapper of sortedTracks) { - tracks.push(trackWrapper.track); - } - } - - private addSubTracksRecursive (track: TrackAsset, allTracks: TrackAsset[]) { - for (const subTrack of track.getChildTracks()) { - allTracks.push(subTrack); - } - for (const subTrack of track.getChildTracks()) { - this.addSubTracksRecursive(subTrack, allTracks); - } - } } export class TrackSortWrapper { From 402b53bfdeed565802434fc1d11fe5624329e2b1 Mon Sep 17 00:00:00 2001 From: SuRuiMeng <934274351@qq.com> Date: Mon, 18 Nov 2024 11:21:06 +0800 Subject: [PATCH 85/88] =?UTF-8?q?fix:=20=E5=9C=A8=E5=AF=8C=E6=96=87?= =?UTF-8?q?=E6=9C=AC=E4=BB=85=E5=8C=85=E5=90=AB=E6=8D=A2=E8=A1=8C=E7=AC=A6?= =?UTF-8?q?=E6=97=B6=E6=B7=BB=E5=8A=A0=E7=A9=BA=E6=A0=BC=EF=BC=8C=E5=B9=B6?= =?UTF-8?q?=E4=BC=98=E5=8C=96=E6=97=A0=E5=86=85=E5=AE=B9=E6=97=B6=E7=9A=84?= =?UTF-8?q?=E8=84=8F=E6=A0=87=E5=BF=97=E5=A4=84=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- plugin-packages/rich-text/src/rich-text-component.ts | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/plugin-packages/rich-text/src/rich-text-component.ts b/plugin-packages/rich-text/src/rich-text-component.ts index 51f92ec0..d1a44465 100644 --- a/plugin-packages/rich-text/src/rich-text-component.ts +++ b/plugin-packages/rich-text/src/rich-text-component.ts @@ -54,6 +54,10 @@ export class RichTextComponent extends TextComponent { private generateTextProgram (text: string) { this.processedTextOptions = []; const program = generateProgram((text, context) => { + // 如果富文本仅包含换行符,则在每个换行符后添加一个空格 + if (/^\n+$/.test(text)) { + text = text.replace(/\n/g, '\n '); + } const textArr = text.split('\n'); textArr.forEach((text, index) => { @@ -142,6 +146,11 @@ export class RichTextComponent extends TextComponent { charsInfo.push(charInfo); width = Math.max(width, charInfo.width); height += charInfo.lineHeight; + if (width === 0 || height === 0) { + this.isDirty = false; + + return; + } const size = this.item.transform.size; this.item.transform.size.set(size.x * width * this.SCALE_FACTOR * this.SCALE_FACTOR, size.y * height * this.SCALE_FACTOR * this.SCALE_FACTOR); From e686d1ef472050fd9e5a6fac02c5fb903d98fffc Mon Sep 17 00:00:00 2001 From: "wumaolin.wml" Date: Mon, 18 Nov 2024 17:59:15 +0800 Subject: [PATCH 86/88] chore: update spec to 2.1.0 --- packages/effects-core/package.json | 2 +- pnpm-lock.yaml | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/effects-core/package.json b/packages/effects-core/package.json index cc429aa7..fca106e1 100644 --- a/packages/effects-core/package.json +++ b/packages/effects-core/package.json @@ -51,7 +51,7 @@ "registry": "https://registry.npmjs.org" }, "dependencies": { - "@galacean/effects-specification": "2.1.0-alpha.5", + "@galacean/effects-specification": "2.1.0", "@galacean/effects-math": "1.1.0", "flatbuffers": "24.3.25", "uuid": "9.0.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 32de7f56..9e83de60 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -150,8 +150,8 @@ importers: specifier: 1.1.0 version: 1.1.0 '@galacean/effects-specification': - specifier: 2.1.0-alpha.5 - version: 2.1.0-alpha.5 + specifier: 2.1.0 + version: 2.1.0 flatbuffers: specifier: 24.3.25 version: 24.3.25 @@ -2214,8 +2214,8 @@ packages: /@galacean/effects-specification@2.0.0: resolution: {integrity: sha512-7ieZALZYn5fwKLIGAskmYSKGX82ZkLRdDUInG21KsOJ2eQ5+HcI6mJU5tmN8vjZxQUc+RVuUcssExGbiaj2+5w==} - /@galacean/effects-specification@2.1.0-alpha.5: - resolution: {integrity: sha512-PpfKmGJHxFZMKGNek1mubpDnE4RXNiwpktshEfXlZhmp2/MtATej5dv6a/shjvsDNg95V2iIVvoFt7txd77VfA==} + /@galacean/effects-specification@2.1.0: + resolution: {integrity: sha512-gad/CBHvTlL182QExrPGqzafzfMjSEfVWRA4+mufHMvxlsDF0kEIvZ2EEcyG9IgvRkX81F1PU1vk+or6G1j2nw==} dev: false /@humanwhocodes/config-array@0.11.14: From 6b02795c9c70f77598211bc11c7466d63526163e Mon Sep 17 00:00:00 2001 From: yiiqii Date: Mon, 18 Nov 2024 19:11:19 +0800 Subject: [PATCH 87/88] chore: update resource-detection and fix type issue --- .../effects-core/src/fallback/particle.ts | 6 ++-- packages/effects-core/src/shape/shape.ts | 22 +++++++------- plugin-packages/model/package.json | 2 +- pnpm-lock.yaml | 30 +++++++++++++++---- web-packages/demo/src/single.ts | 2 +- web-packages/test/package.json | 2 +- .../fallback/particle/base.spec.ts | 2 +- 7 files changed, 43 insertions(+), 23 deletions(-) diff --git a/packages/effects-core/src/fallback/particle.ts b/packages/effects-core/src/fallback/particle.ts index 4f42fbb0..06cda1ab 100644 --- a/packages/effects-core/src/fallback/particle.ts +++ b/packages/effects-core/src/fallback/particle.ts @@ -1,5 +1,5 @@ import type { ParticleContent, ParticleShape, ParticleShapeSphere, ColorOverLifetime } from '@galacean/effects-specification'; -import { ShapeType } from '@galacean/effects-specification'; +import { ParticleEmitterShapeType } from '@galacean/effects-specification'; import { deleteEmptyValue, ensureColorExpression, ensureFixedNumber, ensureFixedNumberWithRandom, ensureFixedVec3, ensureNumberExpression, getGradientColor, objectValueToNumber, @@ -9,7 +9,7 @@ export function getStandardParticleContent (particle: any): ParticleContent { const options = particle.options; const transform = particle.transform; let shape: ParticleShape = { - type: ShapeType.NONE, + type: ParticleEmitterShapeType.NONE, }; if (particle.shape) { @@ -17,7 +17,7 @@ export function getStandardParticleContent (particle: any): ParticleContent { shape = { ...particle.shape, - type: ShapeType[shapeType as keyof typeof ShapeType], + type: ParticleEmitterShapeType[shapeType as keyof typeof ParticleEmitterShapeType], }; if (particle.shape.upDirection) { const [x, y, z] = particle.shape.upDirection; diff --git a/packages/effects-core/src/shape/shape.ts b/packages/effects-core/src/shape/shape.ts index 17e289ee..c7c25279 100644 --- a/packages/effects-core/src/shape/shape.ts +++ b/packages/effects-core/src/shape/shape.ts @@ -36,16 +36,16 @@ class ShapeNone implements Shape { } const map: Record): ShapeGenerator }> = { - [spec.ShapeType.NONE]: ShapeNone, - [spec.ShapeType.CONE]: Cone, - [spec.ShapeType.SPHERE]: Sphere, - [spec.ShapeType.HEMISPHERE]: Hemisphere, - [spec.ShapeType.CIRCLE]: Circle, - [spec.ShapeType.DONUT]: Donut, - [spec.ShapeType.RECTANGLE]: Rectangle, - [spec.ShapeType.EDGE]: Edge, - [spec.ShapeType.RECTANGLE_EDGE]: RectangleEdge, - [spec.ShapeType.TEXTURE]: TextureShape, + [spec.ParticleEmitterShapeType.NONE]: ShapeNone, + [spec.ParticleEmitterShapeType.CONE]: Cone, + [spec.ParticleEmitterShapeType.SPHERE]: Sphere, + [spec.ParticleEmitterShapeType.HEMISPHERE]: Hemisphere, + [spec.ParticleEmitterShapeType.CIRCLE]: Circle, + [spec.ParticleEmitterShapeType.DONUT]: Donut, + [spec.ParticleEmitterShapeType.RECTANGLE]: Rectangle, + [spec.ParticleEmitterShapeType.EDGE]: Edge, + [spec.ParticleEmitterShapeType.RECTANGLE_EDGE]: RectangleEdge, + [spec.ParticleEmitterShapeType.TEXTURE]: TextureShape, }; export function createShape (shapeOptions?: spec.ParticleShape): Shape { @@ -67,7 +67,7 @@ export function createShape (shapeOptions?: spec.ParticleShape): Shape { } const ctrl = new Ctrl(options); - if (type !== spec.ShapeType.NONE) { + if (type !== spec.ParticleEmitterShapeType.NONE) { const { alignSpeedDirection, upDirection = [0, 0, 1] } = shapeOptions as spec.ParticleShapeBase; ctrl.alignSpeedDirection = alignSpeedDirection; diff --git a/plugin-packages/model/package.json b/plugin-packages/model/package.json index b06c5a39..89ebce7e 100644 --- a/plugin-packages/model/package.json +++ b/plugin-packages/model/package.json @@ -64,7 +64,7 @@ "devDependencies": { "@galacean/effects": "workspace:*", "@galacean/effects-plugin-editor-gizmo": "workspace:*", - "@vvfx/resource-detection": "^0.6.2", + "@vvfx/resource-detection": "^0.7.0", "@types/hammerjs": "^2.0.45", "fpsmeter": "^0.3.1", "hammerjs": "^2.0.8" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9e83de60..57e8baa3 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -227,8 +227,8 @@ importers: specifier: ^2.0.45 version: 2.0.45 '@vvfx/resource-detection': - specifier: ^0.6.2 - version: 0.6.2 + specifier: ^0.7.0 + version: 0.7.0 fpsmeter: specifier: ^0.3.1 version: 0.3.1 @@ -338,8 +338,8 @@ importers: specifier: workspace:* version: link:../../packages/effects-webgl '@vvfx/resource-detection': - specifier: ^0.6.2 - version: 0.6.2 + specifier: ^0.7.0 + version: 0.7.0 packages: @@ -2213,10 +2213,10 @@ packages: /@galacean/effects-specification@2.0.0: resolution: {integrity: sha512-7ieZALZYn5fwKLIGAskmYSKGX82ZkLRdDUInG21KsOJ2eQ5+HcI6mJU5tmN8vjZxQUc+RVuUcssExGbiaj2+5w==} + dev: false /@galacean/effects-specification@2.1.0: resolution: {integrity: sha512-gad/CBHvTlL182QExrPGqzafzfMjSEfVWRA4+mufHMvxlsDF0kEIvZ2EEcyG9IgvRkX81F1PU1vk+or6G1j2nw==} - dev: false /@humanwhocodes/config-array@0.11.14: resolution: {integrity: sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==} @@ -3018,6 +3018,26 @@ packages: ndarray-lanczos: 0.1.2 ndarray-pixels: 1.0.0 node-fetch: 3.3.2 + dev: false + + /@vvfx/resource-detection@0.7.0: + resolution: {integrity: sha512-fV34OZRbRIjP1OMDjPfuLsZszpGg56DhSf1NeLUu4g6RJszwNFJ/Uazh7PVCrV8NyZhrWWyGQub3BA2CbE1SxQ==} + engines: {node: '>=10.0.0'} + dependencies: + '@galacean/effects-math': 1.1.0 + '@galacean/effects-specification': 2.1.0 + '@types/draco3dgltf': 1.4.3 + '@types/ndarray': 1.0.11 + '@types/sharp': 0.31.1 + fflate: 0.7.4 + gl-matrix: 3.4.3 + ktx-parse: 0.4.5 + maxrects-packer: 2.7.3 + meshoptimizer: 0.18.1 + ndarray: 1.0.19 + ndarray-lanczos: 0.1.2 + ndarray-pixels: 1.0.0 + node-fetch: 3.3.2 /JSONStream@1.3.5: resolution: {integrity: sha1-MgjB8I06TZkmGrZPkjArwV4RHKA=, tarball: https://registry.npmmirror.com/JSONStream/download/JSONStream-1.3.5.tgz} diff --git a/web-packages/demo/src/single.ts b/web-packages/demo/src/single.ts index 0fac7420..6978974f 100644 --- a/web-packages/demo/src/single.ts +++ b/web-packages/demo/src/single.ts @@ -14,7 +14,7 @@ import { JSONConverter } from '@galacean/effects-plugin-model'; // 3D // const json = 'https://mdn.alipayobjects.com/mars/afts/file/A*sA-6TJ695dYAAAAAAAAAAAAADlB4AQ'; // 特效元素 -const json = 'https://mdn.alipayobjects.com/mars/afts/file/A*bi3HRobVsk8AAAAAAAAAAAAADlB4AQ'; +const json = 'https://mdn.alipayobjects.com/mars/afts/file/A*GmmoRYoutZ4AAAAAAAAAAAAADlB4AQ'; const container = document.getElementById('J-container'); (async () => { diff --git a/web-packages/test/package.json b/web-packages/test/package.json index 641677d6..789b0b9a 100644 --- a/web-packages/test/package.json +++ b/web-packages/test/package.json @@ -9,6 +9,6 @@ "@galacean/effects": "workspace:*", "@galacean/effects-helper": "workspace:*", "@galacean/effects-threejs": "workspace:*", - "@vvfx/resource-detection": "^0.6.2" + "@vvfx/resource-detection": "^0.7.0" } } diff --git a/web-packages/test/unit/src/effects-core/fallback/particle/base.spec.ts b/web-packages/test/unit/src/effects-core/fallback/particle/base.spec.ts index cd5904cf..f4727525 100644 --- a/web-packages/test/unit/src/effects-core/fallback/particle/base.spec.ts +++ b/web-packages/test/unit/src/effects-core/fallback/particle/base.spec.ts @@ -195,7 +195,7 @@ describe('core/fallback/particle/base', () => { const neo = getStandardItem(item); const shape = neo.content.shape; - expect(shape.type).to.be.eql(spec.ShapeType.RECTANGLE_EDGE); + expect(shape.type).to.be.eql(spec.ParticleEmitterShapeType.RECTANGLE_EDGE); expect(shape.radius).to.be.eql(1); }); From 7ec9e837780c953d0f3c266582fd13aeaf82a951 Mon Sep 17 00:00:00 2001 From: "wumaolin.wml" Date: Tue, 19 Nov 2024 11:02:54 +0800 Subject: [PATCH 88/88] feat: shape component support alpha blend --- packages/effects-core/src/components/shape-component.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/effects-core/src/components/shape-component.ts b/packages/effects-core/src/components/shape-component.ts index 175e5186..d07e7af2 100644 --- a/packages/effects-core/src/components/shape-component.ts +++ b/packages/effects-core/src/components/shape-component.ts @@ -51,6 +51,7 @@ uniform vec4 _Color; void main() { vec4 color = _Color; + color.rgb *= color.a; gl_FragColor = color; } `;