@haiyue/extensions 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +25 -0
- package/dist/animation/Animation2DComponent.d.ts +18 -0
- package/dist/animation/Animation2DExtensionRegistry.d.ts +7 -0
- package/dist/animation/Animation2DRenderSystem.d.ts +6 -0
- package/dist/animation/Animation2DRuntime.d.ts +6 -2
- package/dist/animation/AnimationTextRasterizer.d.ts +4 -0
- package/dist/animation/AnimationVisual2D.d.ts +58 -2
- package/dist/animation/interaction/InteractionRuntime.d.ts +83 -0
- package/dist/animation/interaction/runtime-types.d.ts +257 -0
- package/dist/animation.d.ts +3 -1
- package/dist/animation.js +551 -59
- package/dist/animation3d.js +1 -1
- package/dist/canvas-text.js +1 -1
- package/dist/chunks/2d-ui-animation-2d.generated-FpziAeGJ.js +4 -0
- package/dist/chunks/2d-ui-indexed-sprite.generated-DmKR0CC3.js +4 -0
- package/dist/chunks/Animation2DStateMachine-BHcA6Cu3.js +18212 -0
- package/dist/chunks/{AnimationStateMachineController-BIcAh-sT.js → AnimationStateMachineController-4sKaqh9E.js} +2 -2
- package/dist/chunks/AnimationVisual2D-8y3qp0_U.js +230 -0
- package/dist/chunks/{RenderSystem2DBase-DDT61dAu.js → RenderSystem2DBase-CF2s00vg.js} +2 -2
- package/dist/controls/VirtualJoystickControls.d.ts +46 -0
- package/dist/controls/contracts.d.ts +91 -0
- package/dist/controls.d.ts +2 -0
- package/dist/controls.js +358 -0
- package/dist/deformable-animation/runtime/DeformableMesh2DRuntimeExtension.d.ts +11 -0
- package/dist/deformable-animation/runtime/DeformableMesh2DSampler.d.ts +11 -0
- package/dist/deformable-animation.d.ts +5 -0
- package/dist/deformable-animation.js +316 -0
- package/dist/experimental-indexed-sprite.d.ts +5 -0
- package/dist/experimental-indexed-sprite.js +482 -0
- package/dist/gltf-worker-runtime.js +9 -9
- package/dist/hya-state-machine.js +3 -2
- package/dist/index.js +1 -1
- package/dist/indexed-sprite/AtlasLayout.d.ts +2 -0
- package/dist/indexed-sprite/IndexedSpriteRenderer.d.ts +23 -0
- package/dist/indexed-sprite/contracts.d.ts +89 -0
- package/dist/internal/2d-ui-shader-artifact.js +232 -23
- package/dist/ray-tracing/acceleration/bvh.d.ts +37 -0
- package/dist/ray-tracing/acceleration/index.d.ts +6 -0
- package/dist/ray-tracing/acceleration/pack.d.ts +152 -0
- package/dist/ray-tracing/acceleration/query.d.ts +4 -0
- package/dist/ray-tracing/acceleration/runtime.d.ts +30 -0
- package/dist/ray-tracing/acceleration/tlas.d.ts +4 -0
- package/dist/ray-tracing/acceleration/types.d.ts +165 -0
- package/dist/ray-tracing/denoise/index.d.ts +3 -0
- package/dist/ray-tracing/denoise/layout.d.ts +9 -0
- package/dist/ray-tracing/denoise/runtime.d.ts +23 -0
- package/dist/ray-tracing/denoise/types.d.ts +28 -0
- package/dist/ray-tracing/hybrid/contract.d.ts +2 -0
- package/dist/ray-tracing/hybrid/index.d.ts +5 -0
- package/dist/ray-tracing/hybrid/layout.d.ts +17 -0
- package/dist/ray-tracing/hybrid/plan.d.ts +2 -0
- package/dist/ray-tracing/hybrid/runtime.d.ts +31 -0
- package/dist/ray-tracing/hybrid/shaders/hybrid-artifact.generated.d.ts +625 -0
- package/dist/ray-tracing/hybrid/types.d.ts +144 -0
- package/dist/ray-tracing/lifecycle/device-recovery.d.ts +31 -0
- package/dist/ray-tracing/lifecycle/index.d.ts +2 -0
- package/dist/ray-tracing/lifecycle/types.d.ts +37 -0
- package/dist/ray-tracing/material/index.d.ts +2 -0
- package/dist/ray-tracing/material/pack.d.ts +5 -0
- package/dist/ray-tracing/material/types.d.ts +63 -0
- package/dist/ray-tracing/reference/index.d.ts +122 -0
- package/dist/ray-tracing/renderer/index.d.ts +7 -0
- package/dist/ray-tracing/renderer/layout.d.ts +20 -0
- package/dist/ray-tracing/renderer/plan.d.ts +2 -0
- package/dist/ray-tracing/renderer/reference.d.ts +17 -0
- package/dist/ray-tracing/renderer/runtime.d.ts +39 -0
- package/dist/ray-tracing/renderer/scene.d.ts +4 -0
- package/dist/ray-tracing/renderer/types.d.ts +115 -0
- package/dist/ray-tracing/sampling/index.d.ts +8 -0
- package/dist/ray-tracing/sampling/layout.d.ts +12 -0
- package/dist/ray-tracing/sampling/plan.d.ts +5 -0
- package/dist/ray-tracing/sampling/revision.d.ts +15 -0
- package/dist/ray-tracing/sampling/runtime.d.ts +44 -0
- package/dist/ray-tracing/sampling/sequence.d.ts +2 -0
- package/dist/ray-tracing/sampling/types.d.ts +96 -0
- package/dist/ray-tracing/scene/index.d.ts +41 -0
- package/dist/ray-tracing/traversal/index.d.ts +4 -0
- package/dist/ray-tracing/traversal/layout.d.ts +16 -0
- package/dist/ray-tracing/traversal/plan.d.ts +2 -0
- package/dist/ray-tracing/traversal/runtime.d.ts +25 -0
- package/dist/ray-tracing/traversal/types.d.ts +79 -0
- package/dist/ray-tracing/worker/client.d.ts +25 -0
- package/dist/ray-tracing/worker/index.d.ts +5 -0
- package/dist/ray-tracing/worker/runtime.d.ts +11 -0
- package/dist/ray-tracing/worker/serialization.d.ts +16 -0
- package/dist/ray-tracing/worker/source.d.ts +1 -0
- package/dist/ray-tracing/worker/types.d.ts +91 -0
- package/dist/ray-tracing.d.ts +17 -0
- package/dist/ray-tracing.js +7666 -0
- package/dist/spine.js +1 -1
- package/dist/tilemap.js +1 -1
- package/package.json +26 -23
- package/dist/animation/Animation2DStateMachineVisualRuntime.d.ts +0 -27
- package/dist/animation/Particle2DDescriptorAdapter.d.ts +0 -5
- package/dist/animation3d/Animation3DResource.d.ts +0 -28
- package/dist/animation3d/hya/HyaAnimation3DStateMachineRuntime.d.ts +0 -65
- package/dist/animation3d/hya/index.d.ts +0 -4
- package/dist/animation3d/runtime/integration/Animation3DStateMachineMixerAdapter.d.ts +0 -81
- package/dist/animation3d/runtime/integration/Animation3DStateMachineMixerIntegration.d.ts +0 -39
- package/dist/animation3d/runtime/integration/index.d.ts +0 -2
- package/dist/animation3d/runtime/mixer/Action.d.ts +0 -150
- package/dist/animation3d/runtime/mixer/Animation3DMixerRuntimeStore.d.ts +0 -6
- package/dist/animation3d/runtime/mixer/Mixer.d.ts +0 -59
- package/dist/animation3d/runtime/mixer/PoseBuffer.d.ts +0 -1
- package/dist/animation3d/runtime/mixer/TrackSampler.d.ts +0 -24
- package/dist/animation3d/runtime/mixer/index.d.ts +0 -4
- package/dist/animation3d/runtime/state-machine/Animation3DBlendTreeWeights.d.ts +0 -1
- package/dist/animation3d/runtime/state-machine/Animation3DStateMachineCompiler.d.ts +0 -1
- package/dist/animation3d/runtime/state-machine/Animation3DStateMachineController.d.ts +0 -1
- package/dist/animation3d/runtime/state-machine/Animation3DStateMachineMixerPort.d.ts +0 -1
- package/dist/animation3d/runtime/state-machine/index.d.ts +0 -6
- package/dist/benchmark.d.ts +0 -30
- package/dist/chunks/2d-ui-animation-2d.generated-D4aHzFU2.js +0 -4
- package/dist/chunks/Animation2DStateMachine-9IonguNa.js +0 -3245
- package/dist/gltf/GltfAccessorReader.d.ts +0 -7
- package/dist/gltf/GltfDracoDecoder.d.ts +0 -4
- package/dist/gltf/GltfLoaderErrors.d.ts +0 -4
- package/dist/gltf/GltfMaterialDescriptor.d.ts +0 -23
- package/dist/gltf/GltfMaterialLoader.d.ts +0 -6
- package/dist/gltf-worker-runtime.d.ts +0 -7
- package/dist/plugins/pluginUtils.d.ts +0 -20
- package/dist/plugins.d.ts +0 -6
- package/dist/shaders/generated/2d-ui-artifact.generated.d.ts +0 -658
- package/dist/spine/Spine2DGpuRenderer.d.ts +0 -65
- package/dist/spine/Spine2DRuntime.d.ts +0 -103
- package/dist/spine/SpineAnimationBoneTimelines.d.ts +0 -8
- package/dist/spine/SpineFloatBuilder.d.ts +0 -15
- package/dist/spine/SpinePathConstraintSolver.d.ts +0 -32
- package/dist/spine/SpineSkeletonRuntime.d.ts +0 -185
- package/dist/spine/SpineSlotAnimation.d.ts +0 -17
- package/dist/spine/SpineTimelineRuntime.d.ts +0 -25
- package/dist/spine/SpineVertexBuilder.d.ts +0 -61
- package/dist/tilemap/Tilemap2DRenderer.d.ts +0 -24
- package/dist/utils/arrayAccess.d.ts +0 -4
- package/dist/utils/camera2d.d.ts +0 -12
- package/src/animation/Animation2DComponent.ts +0 -135
- package/src/animation/Animation2DExtensionRegistry.ts +0 -52
- package/src/animation/Animation2DRenderSystem.ts +0 -1026
- package/src/animation/Animation2DRuntime.ts +0 -1326
- package/src/animation/Animation2DStateMachine.ts +0 -39
- package/src/animation/Animation2DStateMachineComponent.ts +0 -228
- package/src/animation/Animation2DStateMachineRuntime.ts +0 -141
- package/src/animation/Animation2DStateMachineSystem.ts +0 -60
- package/src/animation/Animation2DStateMachineVisualRuntime.ts +0 -282
- package/src/animation/Animation2DSystem.ts +0 -72
- package/src/animation/AnimationAssetLoader.ts +0 -84
- package/src/animation/AnimationAudioClip.ts +0 -140
- package/src/animation/AnimationPathTessellator.ts +0 -691
- package/src/animation/AnimationTextRasterizer.ts +0 -340
- package/src/animation/AnimationVisual2D.ts +0 -153
- package/src/animation/HyaAnimation2DClipAdapter.ts +0 -845
- package/src/animation/Particle2DDescriptorAdapter.ts +0 -35
- package/src/animation/runtime/mixer/Action.ts +0 -876
- package/src/animation/runtime/mixer/Mixer.ts +0 -505
- package/src/animation/runtime/mixer/PoseBuffer.ts +0 -105
- package/src/animation/runtime/mixer/Sampler.ts +0 -252
- package/src/animation/runtime/mixer/Types.ts +0 -138
- package/src/animation/runtime/mixer/index.ts +0 -31
- package/src/animation-state-machine/AnimationStateMachine.ts +0 -146
- package/src/animation-state-machine/AnimationStateMachineChannels.ts +0 -116
- package/src/animation-state-machine/runtime/Animation2DStateMachineMixerAdapter.ts +0 -197
- package/src/animation-state-machine/runtime/AnimationStateMachineBlendTreeWeights.ts +0 -503
- package/src/animation-state-machine/runtime/AnimationStateMachineCompiler.ts +0 -763
- package/src/animation-state-machine/runtime/AnimationStateMachineController.ts +0 -987
- package/src/animation-state-machine/runtime/AnimationStateMachineMixerPort.ts +0 -54
- package/src/animation-state-machine/runtime/index.ts +0 -47
- package/src/animation.ts +0 -33
- package/src/animation3d/Animation3DAction.ts +0 -73
- package/src/animation3d/Animation3DBinding.ts +0 -116
- package/src/animation3d/Animation3DClip.ts +0 -22
- package/src/animation3d/Animation3DError.ts +0 -34
- package/src/animation3d/Animation3DMixer.ts +0 -82
- package/src/animation3d/Animation3DPose.ts +0 -100
- package/src/animation3d/Animation3DPoseBuffer.ts +0 -85
- package/src/animation3d/Animation3DResource.ts +0 -49
- package/src/animation3d/Animation3DStateMachine.ts +0 -126
- package/src/animation3d/Animation3DStateMachineRuntime.ts +0 -191
- package/src/animation3d/Animation3DTrack.ts +0 -29
- package/src/animation3d/hya/HyaAnimation3DRuntime.ts +0 -756
- package/src/animation3d/hya/HyaAnimation3DStateMachineRuntime.ts +0 -618
- package/src/animation3d/hya/HyaAnimation3DTypes.ts +0 -168
- package/src/animation3d/hya/index.ts +0 -10
- package/src/animation3d/index.ts +0 -76
- package/src/animation3d/runtime/integration/Animation3DStateMachineMixerAdapter.ts +0 -325
- package/src/animation3d/runtime/integration/Animation3DStateMachineMixerIntegration.ts +0 -158
- package/src/animation3d/runtime/integration/index.ts +0 -9
- package/src/animation3d/runtime/mixer/Action.ts +0 -833
- package/src/animation3d/runtime/mixer/Animation3DMixerRuntimeStore.ts +0 -26
- package/src/animation3d/runtime/mixer/Mixer.ts +0 -562
- package/src/animation3d/runtime/mixer/PoseBuffer.ts +0 -1
- package/src/animation3d/runtime/mixer/TrackSampler.ts +0 -399
- package/src/animation3d/runtime/mixer/index.ts +0 -8
- package/src/animation3d/runtime/state-machine/Animation3DBlendTreeWeights.ts +0 -4
- package/src/animation3d/runtime/state-machine/Animation3DStateMachineCompiler.ts +0 -1
- package/src/animation3d/runtime/state-machine/Animation3DStateMachineController.ts +0 -1
- package/src/animation3d/runtime/state-machine/Animation3DStateMachineMixerPort.ts +0 -1
- package/src/animation3d/runtime/state-machine/index.ts +0 -45
- package/src/animation3d.ts +0 -5
- package/src/benchmark.ts +0 -168
- package/src/canvas-text/CanvasText2DRenderSystem.ts +0 -292
- package/src/canvas-text/CanvasTextComponent.ts +0 -47
- package/src/canvas-text.ts +0 -4
- package/src/experimental-gltf-worker.ts +0 -18
- package/src/experimental-spine-worker.ts +0 -9
- package/src/gltf/GltfAccessorReader.ts +0 -271
- package/src/gltf/GltfAnimation3DAdapter.ts +0 -680
- package/src/gltf/GltfAnimationRuntime.ts +0 -392
- package/src/gltf/GltfAssetWorkerClient.ts +0 -136
- package/src/gltf/GltfCompatibilityReport.ts +0 -214
- package/src/gltf/GltfConservativeBounds.ts +0 -219
- package/src/gltf/GltfDracoDecoder.ts +0 -251
- package/src/gltf/GltfExtensionAdapter.ts +0 -340
- package/src/gltf/GltfLoaderContract.ts +0 -333
- package/src/gltf/GltfLoaderErrors.ts +0 -59
- package/src/gltf/GltfMaterialDescriptor.ts +0 -134
- package/src/gltf/GltfMaterialEncoder.ts +0 -173
- package/src/gltf/GltfMaterialLoader.ts +0 -293
- package/src/gltf/GltfModelComponent.ts +0 -115
- package/src/gltf/GltfModelSystem.ts +0 -208
- package/src/gltf/GltfPlugin.ts +0 -84
- package/src/gltf/GltfSchema.ts +0 -229
- package/src/gltf/GltfUvSemanticPlanner.ts +0 -166
- package/src/gltf/gltfLoader.ts +0 -910
- package/src/gltf-animation3d.ts +0 -10
- package/src/gltf-worker-runtime.ts +0 -10
- package/src/gltf.ts +0 -56
- package/src/grid/Grid2DComponent.ts +0 -69
- package/src/grid.ts +0 -2
- package/src/hya-state-machine.ts +0 -19
- package/src/index.ts +0 -2
- package/src/plugins/pluginUtils.ts +0 -50
- package/src/plugins.ts +0 -6
- package/src/shaders/generated/2d-ui-animation-2d.generated.wgsl +0 -242
- package/src/shaders/generated/2d-ui-artifact.generated.ts +0 -867
- package/src/shaders/generated/2d-ui-canvas-text-2d.generated.wgsl +0 -38
- package/src/shaders/generated/2d-ui-spine2d.generated.wgsl +0 -41
- package/src/shaders/generated/2d-ui-tilemap2d.generated.wgsl +0 -36
- package/src/spine/Spine2DComponent.ts +0 -168
- package/src/spine/Spine2DGpuRenderer.ts +0 -449
- package/src/spine/Spine2DRenderSystem.ts +0 -199
- package/src/spine/Spine2DRuntime.ts +0 -1529
- package/src/spine/SpineAnimationBoneTimelines.ts +0 -30
- package/src/spine/SpineAssetParser.ts +0 -20
- package/src/spine/SpineAssetWorkerClient.ts +0 -112
- package/src/spine/SpineAssetWorkerContract.ts +0 -4
- package/src/spine/SpineAtlasParser.ts +0 -127
- package/src/spine/SpineFloatBuilder.ts +0 -66
- package/src/spine/SpinePathConstraintSolver.ts +0 -582
- package/src/spine/SpinePlugin.ts +0 -76
- package/src/spine/SpineSkeletonRuntime.ts +0 -521
- package/src/spine/SpineSlotAnimation.ts +0 -195
- package/src/spine/SpineTimelineRuntime.ts +0 -453
- package/src/spine/SpineVertexBuilder.ts +0 -686
- package/src/spine.ts +0 -7
- package/src/tilemap/Tilemap2DComponent.ts +0 -232
- package/src/tilemap/Tilemap2DRenderSystem.ts +0 -58
- package/src/tilemap/Tilemap2DRenderer.ts +0 -289
- package/src/tilemap/TilemapPlugin.ts +0 -75
- package/src/tilemap.ts +0 -6
- package/src/tween/Tween2DComponent.ts +0 -83
- package/src/tween/Tween2DSystem.ts +0 -78
- package/src/tween.ts +0 -4
- package/src/types/earcut.d.ts +0 -7
- package/src/types/wgsl.d.ts +0 -4
- package/src/utils/RenderSystem2DBase.ts +0 -164
- package/src/utils/arrayAccess.ts +0 -17
- package/src/utils/camera2d.ts +0 -68
- package/src/utils/render2dGpu.ts +0 -132
- package/test/fixtures/gltf/animation-characterization.gltf +0 -344
package/dist/animation.js
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
|
-
import { parseAnimation, ANIMATION_MIME_TYPE } from '@haiyue/animation-spec';
|
|
1
|
+
import { parseAnimation, AnimationFormatError, ANIMATION_MIME_TYPE } from '@haiyue/animation-spec';
|
|
2
2
|
import { Component, System } from '@haiyue/engine';
|
|
3
3
|
import { UniqueCheckType, ComponentLifecycleFlags } from '@haiyue/engine/ecs';
|
|
4
|
-
import { A as Animation2DRuntime
|
|
5
|
-
export { c as createAnimation2DStateMachineController, t as tessellateAnimationPath } from './chunks/Animation2DStateMachine-
|
|
4
|
+
import { A as Animation2DRuntime } from './chunks/Animation2DStateMachine-BHcA6Cu3.js';
|
|
5
|
+
export { c as createAnimation2DStateMachineController, t as tessellateAnimationPath } from './chunks/Animation2DStateMachine-BHcA6Cu3.js';
|
|
6
6
|
import { requireEngineDevice, getExtensionGPUResourceTracker, beginRenderCommandPass, cloneRenderPassDescriptor, alignUp4, estimateTextureBytes, getExtensionSharedRendererResource } from '@haiyue/engine/extension-authoring';
|
|
7
|
-
import { R as RenderSystem2DBase, d as destroyCamera2DGpu, c as createCamera2DGpu, i as createCamera2DLayout } from './chunks/RenderSystem2DBase-
|
|
8
|
-
import { A as Animation2dWgsl } from './chunks/2d-ui-animation-2d.generated-
|
|
9
|
-
|
|
7
|
+
import { R as RenderSystem2DBase, d as destroyCamera2DGpu, c as createCamera2DGpu, i as createCamera2DLayout } from './chunks/RenderSystem2DBase-CF2s00vg.js';
|
|
8
|
+
import { A as Animation2dWgsl } from './chunks/2d-ui-animation-2d.generated-FpziAeGJ.js';
|
|
9
|
+
import { A as AnimationVisual2D, c as createAnimation2DPipeline, a as animation2DCullingPipelineKey, b as animation2DCullingPrimitive } from './chunks/AnimationVisual2D-8y3qp0_U.js';
|
|
10
|
+
export { A as AnimationStateMachineController, b as AnimationStateMachineValidationError, a as compileAnimationStateMachine, d as validateAnimationStateMachineDefinition } from './chunks/AnimationStateMachineController-4sKaqh9E.js';
|
|
10
11
|
import '@haiyue/engine/geometry';
|
|
11
12
|
import '@haiyue/engine/components';
|
|
12
13
|
import 'wgpu-matrix';
|
|
@@ -18,10 +19,13 @@ class Animation2DComponent extends Component {
|
|
|
18
19
|
animation;
|
|
19
20
|
playing;
|
|
20
21
|
loop;
|
|
22
|
+
loopStartTime;
|
|
23
|
+
loopEndTime;
|
|
21
24
|
speed;
|
|
22
25
|
currentTime;
|
|
23
26
|
completed = false;
|
|
24
27
|
runtimeExtensions;
|
|
28
|
+
_nodeOverrides = new Map();
|
|
25
29
|
_runtime = null;
|
|
26
30
|
_needsApply = true;
|
|
27
31
|
_forceParticleSeek = false;
|
|
@@ -30,6 +34,8 @@ class Animation2DComponent extends Component {
|
|
|
30
34
|
this.animation = isParsed(source) ? source : parseAnimation(source);
|
|
31
35
|
this.playing = options.autoplay ?? true;
|
|
32
36
|
this.loop = options.loop ?? this.animation.endBehavior === 'loop';
|
|
37
|
+
this.loopStartTime = normalizeLoopStartTime(options.loopStartTime ?? 0, this.animation.duration);
|
|
38
|
+
this.loopEndTime = normalizeLoopEndTime(options.loopEndTime ?? this.animation.duration, this.loopStartTime, this.animation.duration);
|
|
33
39
|
this.speed = finiteSpeed(options.speed ?? 1);
|
|
34
40
|
this.currentTime = clampTime(options.startTime ?? 0, this.animation.duration);
|
|
35
41
|
this.runtimeExtensions = options.runtimeExtensions;
|
|
@@ -73,6 +79,39 @@ class Animation2DComponent extends Component {
|
|
|
73
79
|
this.speed = finiteSpeed(speed);
|
|
74
80
|
return this;
|
|
75
81
|
}
|
|
82
|
+
setNodeOverride(nodeId, override) {
|
|
83
|
+
if (!this.animation.nodes.some(node => node.id === nodeId))
|
|
84
|
+
throw new ReferenceError(`Animation node "${nodeId}" does not exist.`);
|
|
85
|
+
const position = override.position;
|
|
86
|
+
if (position && (position.length !== 2 || position.some(value => !Number.isFinite(value)))) {
|
|
87
|
+
throw new RangeError('Animation2D node override position must contain two finite values.');
|
|
88
|
+
}
|
|
89
|
+
if (override.opacity !== undefined && (!Number.isFinite(override.opacity) || override.opacity < 0 || override.opacity > 1)) {
|
|
90
|
+
throw new RangeError('Animation2D node override opacity must be within 0..1.');
|
|
91
|
+
}
|
|
92
|
+
if (override.enabled !== undefined && typeof override.enabled !== 'boolean') {
|
|
93
|
+
throw new TypeError('Animation2D node override enabled must be boolean.');
|
|
94
|
+
}
|
|
95
|
+
this._nodeOverrides.set(nodeId, Object.freeze({
|
|
96
|
+
...(position ? { position: Object.freeze([position[0], position[1]]) } : {}),
|
|
97
|
+
...(override.opacity === undefined ? {} : { opacity: override.opacity }),
|
|
98
|
+
...(override.enabled === undefined ? {} : { enabled: override.enabled }),
|
|
99
|
+
}));
|
|
100
|
+
this._needsApply = true;
|
|
101
|
+
return this;
|
|
102
|
+
}
|
|
103
|
+
clearNodeOverride(nodeId) {
|
|
104
|
+
if (this._nodeOverrides.delete(nodeId))
|
|
105
|
+
this._needsApply = true;
|
|
106
|
+
return this;
|
|
107
|
+
}
|
|
108
|
+
clearNodeOverrides() {
|
|
109
|
+
if (this._nodeOverrides.size > 0) {
|
|
110
|
+
this._nodeOverrides.clear();
|
|
111
|
+
this._needsApply = true;
|
|
112
|
+
}
|
|
113
|
+
return this;
|
|
114
|
+
}
|
|
76
115
|
onEntityRemoveComponent(_entity, component) {
|
|
77
116
|
if (component === this)
|
|
78
117
|
this._disposeRuntime();
|
|
@@ -84,11 +123,15 @@ class Animation2DComponent extends Component {
|
|
|
84
123
|
const clone = new Animation2DComponent(this.animation, {
|
|
85
124
|
autoplay: this.playing,
|
|
86
125
|
loop: this.loop,
|
|
126
|
+
loopStartTime: this.loopStartTime,
|
|
127
|
+
loopEndTime: this.loopEndTime,
|
|
87
128
|
speed: this.speed,
|
|
88
129
|
startTime: this.currentTime,
|
|
89
130
|
...(this.runtimeExtensions ? { runtimeExtensions: this.runtimeExtensions } : {}),
|
|
90
131
|
});
|
|
91
132
|
clone.disabled = this.disabled;
|
|
133
|
+
for (const [nodeId, override] of this._nodeOverrides)
|
|
134
|
+
clone.setNodeOverride(nodeId, override);
|
|
92
135
|
return clone;
|
|
93
136
|
}
|
|
94
137
|
_disposeRuntime() {
|
|
@@ -109,6 +152,254 @@ function clampTime(value, duration) {
|
|
|
109
152
|
throw new RangeError('Animation2DComponent time must be finite.');
|
|
110
153
|
return Math.max(0, Math.min(duration, value));
|
|
111
154
|
}
|
|
155
|
+
function normalizeLoopStartTime(value, duration) {
|
|
156
|
+
if (!Number.isFinite(value) || value < 0 || value >= duration) {
|
|
157
|
+
throw new RangeError('Animation2DComponent loopStartTime must be finite, non-negative, and less than duration.');
|
|
158
|
+
}
|
|
159
|
+
return value;
|
|
160
|
+
}
|
|
161
|
+
function normalizeLoopEndTime(value, loopStartTime, duration) {
|
|
162
|
+
if (!Number.isFinite(value) || value <= loopStartTime || value > duration) {
|
|
163
|
+
throw new RangeError('Animation2DComponent loopEndTime must be finite, greater than loopStartTime, and no greater than duration.');
|
|
164
|
+
}
|
|
165
|
+
return value;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
class InteractionRuntimeError extends Error {
|
|
169
|
+
code;
|
|
170
|
+
path;
|
|
171
|
+
name = 'InteractionRuntimeError';
|
|
172
|
+
constructor(code, path, message) {
|
|
173
|
+
super(`${message} (${path})`);
|
|
174
|
+
this.code = code;
|
|
175
|
+
this.path = path;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
class InteractionRuntime {
|
|
180
|
+
document;
|
|
181
|
+
options;
|
|
182
|
+
targets;
|
|
183
|
+
listeners;
|
|
184
|
+
queue = [];
|
|
185
|
+
pointers = new Map();
|
|
186
|
+
captures = new Map();
|
|
187
|
+
focused;
|
|
188
|
+
sequence = 0;
|
|
189
|
+
transactionSequence = 0;
|
|
190
|
+
draining = false;
|
|
191
|
+
currentDepth = 0;
|
|
192
|
+
disposed = false;
|
|
193
|
+
maxEventQueue;
|
|
194
|
+
maxEventRecursion;
|
|
195
|
+
maxPointers;
|
|
196
|
+
maxStringBytes;
|
|
197
|
+
constructor(document, options = {}) {
|
|
198
|
+
this.document = document;
|
|
199
|
+
this.options = options;
|
|
200
|
+
this.targets = new Map(document.targets.map(target => [target.id, target]));
|
|
201
|
+
this.listeners = new Map();
|
|
202
|
+
for (const listener of document.listeners) {
|
|
203
|
+
const values = this.listeners.get(listener.target) ?? [];
|
|
204
|
+
values.push(listener);
|
|
205
|
+
this.listeners.set(listener.target, values);
|
|
206
|
+
}
|
|
207
|
+
this.maxEventQueue = options.maxEventQueue ?? document.limits?.maxEventQueue ?? 1_000_000;
|
|
208
|
+
this.maxEventRecursion = options.maxEventRecursion ?? document.limits?.maxEventRecursion ?? 64;
|
|
209
|
+
this.maxPointers = options.maxPointers ?? document.limits?.maxPointers ?? 1_024;
|
|
210
|
+
this.maxStringBytes = options.maxStringBytes ?? 4 * 1024 * 1024;
|
|
211
|
+
}
|
|
212
|
+
dispatchPointer(input) {
|
|
213
|
+
this.assertLive();
|
|
214
|
+
if (!Number.isSafeInteger(input.pointerId) || input.pointerId < 0 || input.point.some(value => !Number.isFinite(value)))
|
|
215
|
+
throw new InteractionRuntimeError('E_INTERACTION_RUNTIME_LIMIT', '$.pointer', 'invalid pointer input');
|
|
216
|
+
let state = this.pointers.get(input.pointerId);
|
|
217
|
+
if (!state) {
|
|
218
|
+
if (this.pointers.size >= this.maxPointers)
|
|
219
|
+
throw new InteractionRuntimeError('E_INTERACTION_RUNTIME_LIMIT', '$.pointers', 'pointer budget exceeded');
|
|
220
|
+
state = { point: input.point, hover: undefined, down: undefined, downPoint: undefined, dragging: false };
|
|
221
|
+
this.pointers.set(input.pointerId, state);
|
|
222
|
+
}
|
|
223
|
+
const hit = this.captures.get(input.pointerId) ?? this.hitTest(input.point), previousHover = state.hover;
|
|
224
|
+
state.point = input.point;
|
|
225
|
+
if (input.kind === 'move' && previousHover !== hit) {
|
|
226
|
+
if (previousHover)
|
|
227
|
+
this.enqueue({ kind: 'pointer-exit', target: previousHover, depth: 0, pointerId: input.pointerId, point: input.point });
|
|
228
|
+
if (hit)
|
|
229
|
+
this.enqueue({ kind: 'pointer-enter', target: hit, depth: 0, pointerId: input.pointerId, point: input.point });
|
|
230
|
+
state.hover = hit;
|
|
231
|
+
}
|
|
232
|
+
if (input.kind === 'down') {
|
|
233
|
+
state.down = hit;
|
|
234
|
+
state.downPoint = input.point;
|
|
235
|
+
state.dragging = false;
|
|
236
|
+
if (hit) {
|
|
237
|
+
this.enqueue({ kind: 'pointer-down', target: hit, depth: 0, pointerId: input.pointerId, point: input.point, button: input.button ?? 0 });
|
|
238
|
+
this.focusFromPointer(hit);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
else if (input.kind === 'move') {
|
|
242
|
+
if (hit)
|
|
243
|
+
this.enqueue({ kind: 'pointer-move', target: hit, depth: 0, pointerId: input.pointerId, point: input.point });
|
|
244
|
+
if (state.down && state.downPoint) {
|
|
245
|
+
const distance = Math.hypot(input.point[0] - state.downPoint[0], input.point[1] - state.downPoint[1]);
|
|
246
|
+
if (!state.dragging && distance >= this.document.dragThreshold) {
|
|
247
|
+
state.dragging = true;
|
|
248
|
+
this.enqueue({ kind: 'drag-start', target: state.down, depth: 0, pointerId: input.pointerId, point: input.point });
|
|
249
|
+
}
|
|
250
|
+
if (state.dragging)
|
|
251
|
+
this.enqueue({ kind: 'drag', target: state.down, depth: 0, pointerId: input.pointerId, point: input.point });
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
else {
|
|
255
|
+
if (hit)
|
|
256
|
+
this.enqueue({ kind: 'pointer-up', target: hit, depth: 0, pointerId: input.pointerId, point: input.point, button: input.button ?? 0 });
|
|
257
|
+
if (state.down) {
|
|
258
|
+
if (state.dragging)
|
|
259
|
+
this.enqueue({ kind: 'drag-end', target: state.down, depth: 0, pointerId: input.pointerId, point: input.point });
|
|
260
|
+
else if (hit === state.down)
|
|
261
|
+
this.enqueue({ kind: 'click', target: state.down, depth: 0, pointerId: input.pointerId, point: input.point, button: input.button ?? 0 });
|
|
262
|
+
}
|
|
263
|
+
state.down = undefined;
|
|
264
|
+
state.downPoint = undefined;
|
|
265
|
+
state.dragging = false;
|
|
266
|
+
this.captures.delete(input.pointerId);
|
|
267
|
+
}
|
|
268
|
+
this.drain();
|
|
269
|
+
}
|
|
270
|
+
dispatchKeyboard(input) { this.assertLive(); if (!this.focused)
|
|
271
|
+
return; this.enqueue({ kind: 'keyboard', target: this.focused, depth: 0, key: input.key, keyPhase: input.phase, modifiers: input.modifiers ?? [] }); this.drain(); }
|
|
272
|
+
dispatchText(text) { this.assertLive(); this.stringInput(text, '$.text'); if (!this.focused)
|
|
273
|
+
return; this.enqueue({ kind: 'text-input', target: this.focused, depth: 0, text }); this.drain(); }
|
|
274
|
+
dispatchDataChange(binding, value, target = this.focused) { this.assertLive(); this.stringInput(binding, '$.binding'); if (!target)
|
|
275
|
+
throw new InteractionRuntimeError('E_INTERACTION_RUNTIME_PORT', binding, 'data change requires a target'); this.enqueue({ kind: 'data-change', target, depth: 0, binding, value }); this.drain(); }
|
|
276
|
+
dispatchGamepad(input) { this.assertLive(); if (!this.focused)
|
|
277
|
+
return; this.enqueue({ kind: 'gamepad', target: this.focused, depth: 0, gamepad: input }); this.drain(); }
|
|
278
|
+
dispatchSemanticAction(target, action) { this.assertLive(); this.target(target); this.enqueue({ kind: 'semantic-action', target, depth: 0, semanticAction: action }); this.drain(); }
|
|
279
|
+
enqueueReportedEvent(name, payload = null, target = this.focused) { this.assertLive(); this.stringInput(name, '$.event.name'); if (!target)
|
|
280
|
+
throw new InteractionRuntimeError('E_INTERACTION_RUNTIME_PORT', name, 'reported event requires a target'); this.enqueue({ kind: 'reported-event', target, depth: this.draining ? this.currentDepth + 1 : 0, name, payload }); if (!this.draining)
|
|
281
|
+
this.drain(); }
|
|
282
|
+
enqueueComponentEvent(target, name, payload = null) { this.assertLive(); this.stringInput(name, '$.componentEvent.name'); this.enqueue({ kind: 'component-event', target, depth: this.draining ? this.currentDepth + 1 : 0, name, payload }); if (!this.draining)
|
|
283
|
+
this.drain(); }
|
|
284
|
+
focus(target) { this.assertLive(); if (target !== undefined) {
|
|
285
|
+
const item = this.target(target);
|
|
286
|
+
if (!item.focusable || item.enabled === false)
|
|
287
|
+
throw new InteractionRuntimeError('E_INTERACTION_RUNTIME_PORT', target, 'target is not focusable');
|
|
288
|
+
} if (this.focused === target)
|
|
289
|
+
return; const previous = this.focused; this.focused = target; if (previous)
|
|
290
|
+
this.enqueue({ kind: 'blur', target: previous, depth: this.draining ? this.currentDepth + 1 : 0 }); if (target)
|
|
291
|
+
this.enqueue({ kind: 'focus', target, depth: this.draining ? this.currentDepth + 1 : 0 }); if (!this.draining)
|
|
292
|
+
this.drain(); }
|
|
293
|
+
focusNext(reverse = false) { const ordered = [...this.targets.values()].filter(target => target.focusable && target.enabled !== false).sort((a, b) => (a.tabIndex ?? 0) - (b.tabIndex ?? 0) || a.order - b.order || a.id.localeCompare(b.id)); if (ordered.length === 0) {
|
|
294
|
+
this.focus(undefined);
|
|
295
|
+
return undefined;
|
|
296
|
+
} const current = ordered.findIndex(target => target.id === this.focused), delta = reverse ? -1 : 1, index = (current + delta + ordered.length) % ordered.length; this.focus(ordered[index].id); return this.focused; }
|
|
297
|
+
hitTest(point) { this.assertLive(); return [...this.targets.values()].filter(target => target.enabled !== false && this.options.geometryPort?.visible?.(target.id) !== false).sort((a, b) => b.order - a.order || b.id.localeCompare(a.id)).find(target => this.containsTarget(target, point, true))?.id; }
|
|
298
|
+
dispose() { if (this.disposed)
|
|
299
|
+
return; this.disposed = true; this.queue.length = 0; this.pointers.clear(); this.captures.clear(); this.focused = undefined; this.options.actionPort?.dispose?.(); }
|
|
300
|
+
get trace() { return Object.freeze({ sequence: this.sequence, focused: this.focused ?? null, captures: Object.freeze([...this.captures].map(([pointer, target]) => Object.freeze({ pointer, target }))), pointers: this.pointers.size, queued: this.queue.length, disposed: this.disposed }); }
|
|
301
|
+
enqueue(event) { if (event.depth > this.maxEventRecursion)
|
|
302
|
+
throw new InteractionRuntimeError('E_INTERACTION_RUNTIME_LIMIT', '$.events', 'event recursion budget exceeded'); if (this.queue.length >= this.maxEventQueue)
|
|
303
|
+
throw new InteractionRuntimeError('E_INTERACTION_RUNTIME_LIMIT', '$.events', 'event queue budget exceeded'); this.target(event.target); this.queue.push(event); }
|
|
304
|
+
drain() { if (this.draining)
|
|
305
|
+
return; this.draining = true; try {
|
|
306
|
+
while (this.queue.length) {
|
|
307
|
+
const event = this.queue.shift();
|
|
308
|
+
this.currentDepth = event.depth;
|
|
309
|
+
this.route(event);
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
finally {
|
|
313
|
+
this.currentDepth = 0;
|
|
314
|
+
this.draining = false;
|
|
315
|
+
} }
|
|
316
|
+
route(event) { const path = this.path(event.target), transactionId = ++this.transactionSequence, snapshot = this.snapshot(); const actionPort = this.options.actionPort; actionPort?.begin(transactionId); try {
|
|
317
|
+
for (const current of path.slice(0, -1))
|
|
318
|
+
this.invokeListeners(current, 'capture', event, transactionId);
|
|
319
|
+
this.invokeListeners(event.target, 'target', event, transactionId);
|
|
320
|
+
for (const current of path.slice(0, -1).reverse())
|
|
321
|
+
this.invokeListeners(current, 'bubble', event, transactionId);
|
|
322
|
+
actionPort?.commit(transactionId);
|
|
323
|
+
}
|
|
324
|
+
catch (error) {
|
|
325
|
+
this.restore(snapshot);
|
|
326
|
+
actionPort?.rollback(transactionId);
|
|
327
|
+
throw error;
|
|
328
|
+
} }
|
|
329
|
+
invokeListeners(currentTarget, phase, event, transactionId) { const listeners = this.listeners.get(currentTarget) ?? []; for (const listener of listeners) {
|
|
330
|
+
if (listener.event !== event.kind || !listener.phases.includes(phase) || !this.matches(listener, event))
|
|
331
|
+
continue;
|
|
332
|
+
const routed = Object.freeze({ sequence: ++this.sequence, kind: event.kind, phase, target: event.target, currentTarget, ...(event.pointerId === undefined ? {} : { pointerId: event.pointerId }), ...(event.point === undefined ? {} : { point: event.point, localPoint: this.toLocal(currentTarget, event.point) }), ...(event.button === undefined ? {} : { button: event.button }), ...(event.key === undefined ? {} : { key: event.key }), ...(event.keyPhase === undefined ? {} : { keyPhase: event.keyPhase }), ...(event.modifiers === undefined ? {} : { modifiers: event.modifiers }), ...(event.text === undefined ? {} : { text: event.text }), ...(event.binding === undefined ? {} : { binding: event.binding }), ...(event.value === undefined ? {} : { value: event.value }), ...(event.gamepad === undefined ? {} : { gamepad: event.gamepad }), ...(event.semanticAction === undefined ? {} : { semanticAction: event.semanticAction }), ...(event.name === undefined ? {} : { name: event.name }), ...(event.payload === undefined ? {} : { payload: event.payload }) });
|
|
333
|
+
for (const action of listener.actions)
|
|
334
|
+
this.invokeAction(action, routed);
|
|
335
|
+
} }
|
|
336
|
+
invokeAction(action, event) { if (action.kind === 'pointer-capture') {
|
|
337
|
+
if (event.pointerId === undefined)
|
|
338
|
+
throw new InteractionRuntimeError('E_INTERACTION_RUNTIME_PORT', event.currentTarget, 'pointer capture requires a pointer event');
|
|
339
|
+
this.captures.set(event.pointerId, event.currentTarget);
|
|
340
|
+
}
|
|
341
|
+
else if (action.kind === 'pointer-release') {
|
|
342
|
+
if (event.pointerId !== undefined)
|
|
343
|
+
this.captures.delete(event.pointerId);
|
|
344
|
+
}
|
|
345
|
+
else if (action.kind === 'focus')
|
|
346
|
+
this.focus(action.target ?? event.currentTarget); const port = this.options.actionPort; if (!port && !['pointer-capture', 'pointer-release', 'focus'].includes(action.kind))
|
|
347
|
+
throw new InteractionRuntimeError('E_INTERACTION_RUNTIME_PORT', event.currentTarget, `action port is required for ${action.kind}`); port?.invoke(action, event); }
|
|
348
|
+
matches(listener, event) { if (listener.pointerButton !== undefined && listener.pointerButton !== event.button)
|
|
349
|
+
return false; if (listener.key !== undefined && listener.key !== event.key)
|
|
350
|
+
return false; if (listener.keyPhase !== undefined && listener.keyPhase !== event.keyPhase)
|
|
351
|
+
return false; if (listener.modifiers && !listener.modifiers.every(modifier => event.modifiers?.includes(modifier)))
|
|
352
|
+
return false; if (listener.gamepad && (!event.gamepad || (listener.gamepad.index !== undefined && listener.gamepad.index !== event.gamepad.index) || listener.gamepad.control !== event.gamepad.control || listener.gamepad.phase !== event.gamepad.phase))
|
|
353
|
+
return false; if (listener.semanticAction !== undefined && listener.semanticAction !== event.semanticAction)
|
|
354
|
+
return false; return true; }
|
|
355
|
+
containsTarget(target, point, clips) { const local = this.toLocal(target.id, point); if (clips && target.clips?.some(id => !this.containsTarget(this.target(id), point, false)))
|
|
356
|
+
return false; const area = target.hitArea, kind = area.kind; if (kind === 'rect') {
|
|
357
|
+
const [x, y, width, height] = area.rect;
|
|
358
|
+
return local[0] >= x && local[1] >= y && local[0] <= x + width && local[1] <= y + height;
|
|
359
|
+
} if (kind === 'ellipse') {
|
|
360
|
+
const center = area.center, radius = area.radius;
|
|
361
|
+
if (radius[0] === 0 || radius[1] === 0)
|
|
362
|
+
return false;
|
|
363
|
+
return ((local[0] - center[0]) / radius[0]) ** 2 + ((local[1] - center[1]) / radius[1]) ** 2 <= 1;
|
|
364
|
+
} if (kind === 'polygon')
|
|
365
|
+
return pointInPolygon(local, area.points); if (kind === 'geometry') {
|
|
366
|
+
const port = this.options.geometryPort?.containsGeometry;
|
|
367
|
+
if (!port)
|
|
368
|
+
throw new InteractionRuntimeError('E_INTERACTION_RUNTIME_PORT', target.id, 'geometry hit area requires a geometry port');
|
|
369
|
+
return port(area.port, target.id, local);
|
|
370
|
+
} return false; }
|
|
371
|
+
toLocal(id, point) { const chain = this.path(id); let matrix = [1, 0, 0, 1, 0, 0]; for (const targetId of chain) {
|
|
372
|
+
const target = this.target(targetId), authored = target.transform, dynamic = this.options.geometryPort?.matrix?.(targetId);
|
|
373
|
+
if (authored)
|
|
374
|
+
matrix = multiply(matrix, authored);
|
|
375
|
+
if (dynamic)
|
|
376
|
+
matrix = multiply(matrix, dynamic);
|
|
377
|
+
} return transform(inverse(matrix), point); }
|
|
378
|
+
path(id) { const result = []; let current = this.target(id); while (current) {
|
|
379
|
+
result.unshift(current.id);
|
|
380
|
+
current = current.parent ? this.target(current.parent) : undefined;
|
|
381
|
+
} return result; }
|
|
382
|
+
focusFromPointer(id) { let target = this.target(id); while (target && !target.focusable)
|
|
383
|
+
target = target.parent ? this.target(target.parent) : undefined; if (target?.focusable)
|
|
384
|
+
this.focus(target.id); }
|
|
385
|
+
target(id) { const target = this.targets.get(id); if (!target)
|
|
386
|
+
throw new InteractionRuntimeError('E_INTERACTION_RUNTIME_PORT', id, 'unknown interaction target'); return target; }
|
|
387
|
+
snapshot() { return { pointers: new Map([...this.pointers].map(([id, state]) => [id, { ...state }])), captures: new Map(this.captures), focus: this.focused, queue: [...this.queue] }; }
|
|
388
|
+
restore(snapshot) { this.pointers = snapshot.pointers; this.captures = snapshot.captures; this.focused = snapshot.focus; this.queue.splice(0, this.queue.length, ...snapshot.queue); }
|
|
389
|
+
stringInput(value, path) { if (typeof value !== 'string' || new TextEncoder().encode(value).length > this.maxStringBytes)
|
|
390
|
+
throw new InteractionRuntimeError('E_INTERACTION_RUNTIME_LIMIT', path, 'string input exceeds budget'); }
|
|
391
|
+
assertLive() { if (this.disposed)
|
|
392
|
+
throw new InteractionRuntimeError('E_INTERACTION_RUNTIME_PORT', '$', 'interaction runtime is disposed'); }
|
|
393
|
+
}
|
|
394
|
+
function multiply(a, b) { return [a[0] * b[0] + a[2] * b[1], a[1] * b[0] + a[3] * b[1], a[0] * b[2] + a[2] * b[3], a[1] * b[2] + a[3] * b[3], a[0] * b[4] + a[2] * b[5] + a[4], a[1] * b[4] + a[3] * b[5] + a[5]]; }
|
|
395
|
+
function inverse(value) { const determinant = value[0] * value[3] - value[1] * value[2]; if (Math.abs(determinant) < 1e-12)
|
|
396
|
+
throw new InteractionRuntimeError('E_INTERACTION_RUNTIME_PORT', '$.transform', 'interaction transform is singular'); return [value[3] / determinant, -value[1] / determinant, -value[2] / determinant, value[0] / determinant, (value[2] * value[5] - value[3] * value[4]) / determinant, (value[1] * value[4] - value[0] * value[5]) / determinant]; }
|
|
397
|
+
function transform(matrix, point) { return [matrix[0] * point[0] + matrix[2] * point[1] + matrix[4], matrix[1] * point[0] + matrix[3] * point[1] + matrix[5]]; }
|
|
398
|
+
function pointInPolygon(point, points) { let inside = false; for (let index = 0, previous = points.length - 1; index < points.length; previous = index++) {
|
|
399
|
+
const a = points[index], b = points[previous], crosses = a[1] > point[1] !== b[1] > point[1] && point[0] < (b[0] - a[0]) * (point[1] - a[1]) / (b[1] - a[1]) + a[0];
|
|
400
|
+
if (crosses)
|
|
401
|
+
inside = !inside;
|
|
402
|
+
} return inside; }
|
|
112
403
|
|
|
113
404
|
class Animation2DSystem extends System {
|
|
114
405
|
_assetManager;
|
|
@@ -149,12 +440,15 @@ class Animation2DSystem extends System {
|
|
|
149
440
|
}
|
|
150
441
|
if (component.completed && component.animation.endBehavior === 'destroy' && !component.playing)
|
|
151
442
|
return;
|
|
152
|
-
component._runtime ??= new Animation2DRuntime(entity, component.animation, component.runtimeExtensions, this._assetManager);
|
|
443
|
+
component._runtime ??= new Animation2DRuntime(entity, component.animation, component.runtimeExtensions, this._assetManager, component._nodeOverrides);
|
|
153
444
|
if (component.playing && delta > 0 && component.speed > 0) {
|
|
154
445
|
component.currentTime += delta * 0.001 * component.speed;
|
|
155
|
-
|
|
446
|
+
const playbackEnd = component.loop ? component.loopEndTime : component.animation.duration;
|
|
447
|
+
if (component.currentTime >= playbackEnd) {
|
|
156
448
|
if (component.loop) {
|
|
157
|
-
component.
|
|
449
|
+
const loopDuration = component.loopEndTime - component.loopStartTime;
|
|
450
|
+
component.currentTime = component.loopStartTime
|
|
451
|
+
+ ((component.currentTime - component.loopEndTime) % loopDuration);
|
|
158
452
|
component._forceParticleSeek = true;
|
|
159
453
|
}
|
|
160
454
|
else {
|
|
@@ -178,9 +472,93 @@ class Animation2DSystem extends System {
|
|
|
178
472
|
}
|
|
179
473
|
}
|
|
180
474
|
|
|
475
|
+
/** 256 MiB of rgba8 mask targets before allocator/tracker overhead. */
|
|
476
|
+
const ANIMATION_2D_MAX_MASK_PIXELS = 64 * 1024 * 1024;
|
|
477
|
+
function animationMaskTargetKey(viewKey, sourceKey) {
|
|
478
|
+
return `${viewKey.length}:${viewKey}${sourceKey.length}:${sourceKey}`;
|
|
479
|
+
}
|
|
480
|
+
function animationMaskCompositeKey(viewKey, sourceKeys) {
|
|
481
|
+
return `${viewKey.length}:${viewKey}${sourceKeys.map(key => `${key.length}:${key}`).join('')}`;
|
|
482
|
+
}
|
|
483
|
+
/** Classifies every mask allocation limit before any GPU target is created. */
|
|
484
|
+
function assertAnimationMaskBudget(input) {
|
|
485
|
+
const basePath = `$runtime.views[${JSON.stringify(input.viewKey)}]`;
|
|
486
|
+
if (!Number.isSafeInteger(input.groupCount) || input.groupCount < 0) {
|
|
487
|
+
throw new AnimationFormatError('E_ANIMATION_INVALID_FORMAT', 'Mask group count must be a non-negative safe integer.', `${basePath}.maskGroups`);
|
|
488
|
+
}
|
|
489
|
+
if (input.groupCount > input.maxGroupCount) {
|
|
490
|
+
throw new AnimationFormatError('E_ANIMATION_LIMIT_EXCEEDED', `Mask group count ${input.groupCount} exceeds limit ${input.maxGroupCount}.`, `${basePath}.maskGroups`);
|
|
491
|
+
}
|
|
492
|
+
if (!Number.isSafeInteger(input.width) || input.width <= 0 || !Number.isSafeInteger(input.height) || input.height <= 0) {
|
|
493
|
+
throw new AnimationFormatError('E_ANIMATION_INVALID_FORMAT', 'Mask target dimensions must be positive safe integers.', `${basePath}.maskTexture`);
|
|
494
|
+
}
|
|
495
|
+
if (input.width > input.maxTextureDimension2D || input.height > input.maxTextureDimension2D) {
|
|
496
|
+
throw new AnimationFormatError('E_ANIMATION_LIMIT_EXCEEDED', `Mask target ${input.width}x${input.height} exceeds device maxTextureDimension2D ${input.maxTextureDimension2D}.`, `${basePath}.maskTexture`);
|
|
497
|
+
}
|
|
498
|
+
const pixels = input.width * input.height * input.groupCount;
|
|
499
|
+
const maxPixels = input.maxPixels ?? ANIMATION_2D_MAX_MASK_PIXELS;
|
|
500
|
+
if (!Number.isSafeInteger(pixels) || pixels > maxPixels) {
|
|
501
|
+
throw new AnimationFormatError('E_ANIMATION_LIMIT_EXCEEDED', `Mask target pixels ${pixels} exceed aggregate limit ${maxPixels}.`, `${basePath}.maskPixels`);
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
/** Official Cubism 5.2-and-earlier blend factors for premultiplied source colors. */
|
|
506
|
+
function animationBlendState(mode) {
|
|
507
|
+
if (mode === 'additive')
|
|
508
|
+
return {
|
|
509
|
+
color: { srcFactor: 'one', dstFactor: 'one', operation: 'add' },
|
|
510
|
+
alpha: { srcFactor: 'zero', dstFactor: 'one', operation: 'add' },
|
|
511
|
+
};
|
|
512
|
+
if (mode === 'multiplicative')
|
|
513
|
+
return {
|
|
514
|
+
color: { srcFactor: 'dst', dstFactor: 'one-minus-src-alpha', operation: 'add' },
|
|
515
|
+
alpha: { srcFactor: 'zero', dstFactor: 'one', operation: 'add' },
|
|
516
|
+
};
|
|
517
|
+
if (mode === 'screen')
|
|
518
|
+
return {
|
|
519
|
+
color: { srcFactor: 'one', dstFactor: 'one-minus-src', operation: 'add' },
|
|
520
|
+
alpha: { srcFactor: 'one', dstFactor: 'one-minus-src-alpha', operation: 'add' },
|
|
521
|
+
};
|
|
522
|
+
return {
|
|
523
|
+
color: { srcFactor: 'one', dstFactor: 'one-minus-src-alpha', operation: 'add' },
|
|
524
|
+
alpha: { srcFactor: 'one', dstFactor: 'one-minus-src-alpha', operation: 'add' },
|
|
525
|
+
};
|
|
526
|
+
}
|
|
527
|
+
function animationBlendFragmentEntryPoint(mode, textureAlphaMode = 'straight') {
|
|
528
|
+
return textureAlphaMode === 'premultiplied' ? 'fs_main_premultiplied_texture' : 'fs_main';
|
|
529
|
+
}
|
|
530
|
+
|
|
181
531
|
const SHADER = Animation2dWgsl;
|
|
532
|
+
const TEXT_MIPMAP_SHADER = /* wgsl */ `
|
|
533
|
+
struct VertexOutput {
|
|
534
|
+
@builtin(position) position: vec4f,
|
|
535
|
+
@location(0) uv: vec2f,
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
@group(0) @binding(0) var sourceTexture: texture_2d<f32>;
|
|
539
|
+
@group(0) @binding(1) var sourceSampler: sampler;
|
|
540
|
+
|
|
541
|
+
@vertex
|
|
542
|
+
fn vertexMain(@builtin(vertex_index) vertexIndex: u32) -> VertexOutput {
|
|
543
|
+
let positions = array(vec2f(-1.0, -1.0), vec2f(3.0, -1.0), vec2f(-1.0, 3.0));
|
|
544
|
+
let uvs = array(vec2f(0.0, 1.0), vec2f(2.0, 1.0), vec2f(0.0, -1.0));
|
|
545
|
+
var output: VertexOutput;
|
|
546
|
+
output.position = vec4f(positions[vertexIndex], 0.0, 1.0);
|
|
547
|
+
output.uv = uvs[vertexIndex];
|
|
548
|
+
return output;
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
@fragment
|
|
552
|
+
fn fragmentMain(input: VertexOutput) -> @location(0) vec4f {
|
|
553
|
+
return textureSample(sourceTexture, sourceSampler, input.uv);
|
|
554
|
+
}
|
|
555
|
+
`;
|
|
182
556
|
const MAX_COMPOSITE_LAYERS = 8;
|
|
183
|
-
const
|
|
557
|
+
const MULTIPLY_COLOR_OFFSET = 20;
|
|
558
|
+
const SCREEN_COLOR_OFFSET = 24;
|
|
559
|
+
const PARAMS_OFFSET = 28;
|
|
560
|
+
const UV_RECT_OFFSET = 32;
|
|
561
|
+
const COMPOSITE_PARAMS_OFFSET = 36;
|
|
184
562
|
const COMPOSITE_EXPANSION_OFFSET = COMPOSITE_PARAMS_OFFSET + MAX_COMPOSITE_LAYERS * 4;
|
|
185
563
|
const GRADIENT_PARAMS_OFFSET = COMPOSITE_EXPANSION_OFFSET + MAX_COMPOSITE_LAYERS;
|
|
186
564
|
const GRADIENT_GEOMETRY_OFFSET = GRADIENT_PARAMS_OFFSET + 4;
|
|
@@ -193,7 +571,7 @@ const MAX_EFFECTS = 8;
|
|
|
193
571
|
const OBJECT_FLOATS = EFFECT_DATA_OFFSET + MAX_EFFECTS * EFFECT_FLOATS;
|
|
194
572
|
const OBJECT_BYTES = OBJECT_FLOATS * 4;
|
|
195
573
|
function getAnimation2DSharedGpu(device, tracker) {
|
|
196
|
-
return getExtensionSharedRendererResource(device, 'Animation2D.sharedGpu:
|
|
574
|
+
return getExtensionSharedRendererResource(device, 'Animation2D.sharedGpu:v2', () => createAnimation2DSharedGpu(device, tracker));
|
|
197
575
|
}
|
|
198
576
|
function createAnimation2DSharedGpu(device, tracker) {
|
|
199
577
|
const cameraLayout = createCamera2DLayout(device);
|
|
@@ -216,7 +594,21 @@ function createAnimation2DSharedGpu(device, tracker) {
|
|
|
216
594
|
})),
|
|
217
595
|
{ binding: MAX_COMPOSITE_LAYERS, visibility: GPUShaderStage.FRAGMENT, sampler: { type: 'filtering' } },
|
|
218
596
|
] });
|
|
219
|
-
const sampler = device.createSampler({ magFilter: 'linear', minFilter: 'linear' });
|
|
597
|
+
const sampler = device.createSampler({ magFilter: 'linear', minFilter: 'linear', mipmapFilter: 'linear' });
|
|
598
|
+
const mipmapSampler = device.createSampler({
|
|
599
|
+
magFilter: 'linear',
|
|
600
|
+
minFilter: 'linear',
|
|
601
|
+
addressModeU: 'clamp-to-edge',
|
|
602
|
+
addressModeV: 'clamp-to-edge',
|
|
603
|
+
});
|
|
604
|
+
const mipmapShader = device.createShaderModule({ label: 'Animation2D.textMipmapShader', code: TEXT_MIPMAP_SHADER });
|
|
605
|
+
const mipmapPipeline = device.createRenderPipeline({
|
|
606
|
+
label: 'Animation2D.textMipmapPipeline',
|
|
607
|
+
layout: 'auto',
|
|
608
|
+
vertex: { module: mipmapShader, entryPoint: 'vertexMain' },
|
|
609
|
+
fragment: { module: mipmapShader, entryPoint: 'fragmentMain', targets: [{ format: 'rgba8unorm' }] },
|
|
610
|
+
primitive: { topology: 'triangle-list' },
|
|
611
|
+
});
|
|
220
612
|
const shader = device.createShaderModule({ label: 'Animation2D.shader', code: SHADER });
|
|
221
613
|
const pipelineLayout = device.createPipelineLayout({
|
|
222
614
|
bindGroupLayouts: [cameraLayout, objectLayout, textureLayout, compositeLayout],
|
|
@@ -260,6 +652,8 @@ function createAnimation2DSharedGpu(device, tracker) {
|
|
|
260
652
|
textureLayout,
|
|
261
653
|
compositeLayout,
|
|
262
654
|
sampler,
|
|
655
|
+
mipmapSampler,
|
|
656
|
+
mipmapPipeline,
|
|
263
657
|
shader,
|
|
264
658
|
pipelineLayout,
|
|
265
659
|
pipelines: new Map(),
|
|
@@ -285,6 +679,8 @@ class Animation2DRenderSystem extends RenderSystem2DBase {
|
|
|
285
679
|
textureLayout;
|
|
286
680
|
compositeLayout;
|
|
287
681
|
sampler;
|
|
682
|
+
mipmapSampler;
|
|
683
|
+
mipmapPipeline;
|
|
288
684
|
whiteTexture;
|
|
289
685
|
whiteBindGroup;
|
|
290
686
|
emptyCompositeBindGroup;
|
|
@@ -295,6 +691,8 @@ class Animation2DRenderSystem extends RenderSystem2DBase {
|
|
|
295
691
|
pipelines;
|
|
296
692
|
entityGpu = new Map();
|
|
297
693
|
geometryGpu = new Map();
|
|
694
|
+
explicitUvIds = new WeakMap();
|
|
695
|
+
nextExplicitUvId = 1;
|
|
298
696
|
textureBindGroups = new WeakMap();
|
|
299
697
|
maskTargets = new Map();
|
|
300
698
|
effectTargets = new Map();
|
|
@@ -371,6 +769,27 @@ class Animation2DRenderSystem extends RenderSystem2DBase {
|
|
|
371
769
|
}
|
|
372
770
|
group.push(item);
|
|
373
771
|
}
|
|
772
|
+
// Variant-expanded documents can retain many mutually exclusive mask
|
|
773
|
+
// sources. A completely transparent group contributes the transparent
|
|
774
|
+
// identity mask, so sharing the renderer's immutable transparent texture
|
|
775
|
+
// is exact and avoids allocating one full-viewport target per inactive
|
|
776
|
+
// state. Keep the group keys so alpha-inverted consumers still resolve the
|
|
777
|
+
// correct transparent input instead of being classified as unresolved.
|
|
778
|
+
const knownSourceKeys = new Set(this.sourceItems.keys());
|
|
779
|
+
let activeSourceGroupCount = 0;
|
|
780
|
+
for (const sources of this.sourceItems.values()) {
|
|
781
|
+
if (sources.some(source => !isFullyTransparent(source.visual)))
|
|
782
|
+
activeSourceGroupCount++;
|
|
783
|
+
}
|
|
784
|
+
const viewKey = context.view?.key ?? 'default';
|
|
785
|
+
assertAnimationMaskBudget({
|
|
786
|
+
groupCount: activeSourceGroupCount,
|
|
787
|
+
maxGroupCount: this.maxMaskTargets,
|
|
788
|
+
width: context.view?.target.width ?? this.engine.width,
|
|
789
|
+
height: context.view?.target.height ?? this.engine.height,
|
|
790
|
+
maxTextureDimension2D: context.device.limits.maxTextureDimension2D,
|
|
791
|
+
viewKey,
|
|
792
|
+
});
|
|
374
793
|
const maskTargets = this.activeMaskTargets;
|
|
375
794
|
maskTargets.clear();
|
|
376
795
|
let maskPixels = 0;
|
|
@@ -382,19 +801,19 @@ class Animation2DRenderSystem extends RenderSystem2DBase {
|
|
|
382
801
|
let effectPixels = 0;
|
|
383
802
|
for (const sourceKey of orderSourceGroups(this.sourceItems)) {
|
|
384
803
|
const sources = this.sourceItems.get(sourceKey);
|
|
385
|
-
if (
|
|
386
|
-
|
|
387
|
-
const targetKey =
|
|
804
|
+
if (sources.every(source => isFullyTransparent(source.visual)))
|
|
805
|
+
continue;
|
|
806
|
+
const targetKey = animationMaskTargetKey(viewKey, sourceKey);
|
|
388
807
|
const target = this.getMaskTarget(targetKey, context, frame);
|
|
389
808
|
maskPixels += target.width * target.height;
|
|
390
809
|
maskCount++;
|
|
391
810
|
let initialized = false;
|
|
392
811
|
for (const source of sources) {
|
|
393
|
-
|
|
394
|
-
if (!resolved) {
|
|
395
|
-
droppedCompositeCount++;
|
|
812
|
+
if (isFullyTransparent(source.visual))
|
|
396
813
|
continue;
|
|
397
|
-
|
|
814
|
+
const resolved = this.resolveCompositeBindGroup(source.visual, maskTargets, knownSourceKeys, viewKey);
|
|
815
|
+
if (!resolved)
|
|
816
|
+
throw unresolvedCompositeError(source.visual, viewKey);
|
|
398
817
|
compositeLayerCount += source.visual.compositeLayers.length;
|
|
399
818
|
const effectTarget = source.visual.effects.length > 0 && !context.passEncoder
|
|
400
819
|
? this.renderEffectStack(source, context, resolved, frame)
|
|
@@ -412,7 +831,7 @@ class Animation2DRenderSystem extends RenderSystem2DBase {
|
|
|
412
831
|
this.drawEffectResult(pass, source.entity, effectTarget.texture);
|
|
413
832
|
}
|
|
414
833
|
else {
|
|
415
|
-
pass.setPipeline(this.getPipeline('rgba8unorm', 1, false, false));
|
|
834
|
+
pass.setPipeline(this.getPipeline('rgba8unorm', 1, false, false, 'normal', source.visual.textureAlphaMode, source.visual.culling));
|
|
416
835
|
this.draw(pass, source.entity, source.visual, context, resolved, true);
|
|
417
836
|
}
|
|
418
837
|
pass.end();
|
|
@@ -434,7 +853,7 @@ class Animation2DRenderSystem extends RenderSystem2DBase {
|
|
|
434
853
|
applyViewport(passEncoder, context);
|
|
435
854
|
passEncoder.setBindGroup(0, this.cameraGpu.bindGroup);
|
|
436
855
|
for (const entry of pending) {
|
|
437
|
-
passEncoder.setPipeline(this.getPipeline(outputFormat, context.view?.sampleCount ?? this.engine.msaaSamples, true, context.view?.reverseZ ?? this.engine.reverseZ));
|
|
856
|
+
passEncoder.setPipeline(this.getPipeline(outputFormat, context.view?.sampleCount ?? this.engine.msaaSamples, true, context.view?.reverseZ ?? this.engine.reverseZ, entry.item.visual.blendMode, entry.item.visual.textureAlphaMode, entry.item.visual.culling));
|
|
438
857
|
this.draw(passEncoder, entry.item.entity, entry.item.visual, context, entry.composite, false);
|
|
439
858
|
visualCount++;
|
|
440
859
|
}
|
|
@@ -449,13 +868,11 @@ class Animation2DRenderSystem extends RenderSystem2DBase {
|
|
|
449
868
|
};
|
|
450
869
|
for (const item of this.items) {
|
|
451
870
|
const { visual } = item;
|
|
452
|
-
if (visual.sourceOnly)
|
|
453
|
-
continue;
|
|
454
|
-
const resolved = this.resolveCompositeBindGroup(visual, maskTargets, context.view?.key ?? 'default');
|
|
455
|
-
if (!resolved) {
|
|
456
|
-
droppedCompositeCount++;
|
|
871
|
+
if (visual.sourceOnly || isFullyTransparent(visual))
|
|
457
872
|
continue;
|
|
458
|
-
|
|
873
|
+
const resolved = this.resolveCompositeBindGroup(visual, maskTargets, knownSourceKeys, viewKey);
|
|
874
|
+
if (!resolved)
|
|
875
|
+
throw unresolvedCompositeError(visual, viewKey);
|
|
459
876
|
compositeLayerCount += visual.compositeLayers.length;
|
|
460
877
|
if (visual.effects.length > 0 && !context.passEncoder) {
|
|
461
878
|
const effectTarget = this.renderEffectStack(item, context, resolved, frame);
|
|
@@ -517,6 +934,8 @@ class Animation2DRenderSystem extends RenderSystem2DBase {
|
|
|
517
934
|
this.textureLayout = shared.textureLayout;
|
|
518
935
|
this.compositeLayout = shared.compositeLayout;
|
|
519
936
|
this.sampler = shared.sampler;
|
|
937
|
+
this.mipmapSampler = shared.mipmapSampler;
|
|
938
|
+
this.mipmapPipeline = shared.mipmapPipeline;
|
|
520
939
|
this.whiteTexture = shared.whiteTexture;
|
|
521
940
|
this.whiteBindGroup = shared.whiteBindGroup;
|
|
522
941
|
this.emptyCompositeBindGroup = shared.emptyCompositeBindGroup;
|
|
@@ -548,13 +967,13 @@ class Animation2DRenderSystem extends RenderSystem2DBase {
|
|
|
548
967
|
throw new Error('Animation2D ordered effects require an isolated render-pass descriptor.');
|
|
549
968
|
return { passEncoder: context.encoder.beginRenderPass(cloneRenderPassDescriptor(context.descriptor, 'load')), ownsPass: true };
|
|
550
969
|
}
|
|
551
|
-
getPipeline(format, sampleCount, withDepth, reverseZ) {
|
|
970
|
+
getPipeline(format, sampleCount, withDepth, reverseZ, blendMode = 'normal', textureAlphaMode = 'straight', culling = false) {
|
|
552
971
|
const depthFormat = withDepth ? this.engine.getDepthFormat(reverseZ) : 'none';
|
|
553
|
-
const key = `visual:${format}:${sampleCount}:${depthFormat}:${reverseZ ? 1 : 0}`;
|
|
972
|
+
const key = `visual:${format}:${sampleCount}:${depthFormat}:${reverseZ ? 1 : 0}:${blendMode}:${textureAlphaMode}:${animation2DCullingPipelineKey(culling)}`;
|
|
554
973
|
const cached = this.pipelines.get(key);
|
|
555
974
|
if (cached)
|
|
556
975
|
return cached;
|
|
557
|
-
const pipeline = requireEngineDevice(this.engine).createRenderPipeline({
|
|
976
|
+
const pipeline = createAnimation2DPipeline(key, () => requireEngineDevice(this.engine).createRenderPipeline({
|
|
558
977
|
label: `Animation2D.pipeline:${key}`,
|
|
559
978
|
layout: this.pipelineLayout,
|
|
560
979
|
vertex: { module: this.shader, entryPoint: 'vs_main', buffers: [{
|
|
@@ -564,18 +983,19 @@ class Animation2DRenderSystem extends RenderSystem2DBase {
|
|
|
564
983
|
{ shaderLocation: 1, offset: 8, format: 'float32x2' },
|
|
565
984
|
],
|
|
566
985
|
}] },
|
|
567
|
-
fragment: {
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
986
|
+
fragment: {
|
|
987
|
+
module: this.shader,
|
|
988
|
+
entryPoint: animationBlendFragmentEntryPoint(blendMode, textureAlphaMode),
|
|
989
|
+
targets: [{ format, blend: animationBlendState(blendMode) }],
|
|
990
|
+
},
|
|
991
|
+
primitive: animation2DCullingPrimitive(culling),
|
|
572
992
|
...(withDepth ? { depthStencil: {
|
|
573
993
|
format: depthFormat,
|
|
574
994
|
depthWriteEnabled: false,
|
|
575
995
|
depthCompare: 'always',
|
|
576
996
|
} } : {}),
|
|
577
997
|
multisample: { count: sampleCount },
|
|
578
|
-
});
|
|
998
|
+
}));
|
|
579
999
|
this.pipelines.set(key, pipeline);
|
|
580
1000
|
return pipeline;
|
|
581
1001
|
}
|
|
@@ -622,11 +1042,13 @@ class Animation2DRenderSystem extends RenderSystem2DBase {
|
|
|
622
1042
|
const object = this.getEntityGpu(entity);
|
|
623
1043
|
object.data.set(this.getWorldMatrix2D(entity, context), 0);
|
|
624
1044
|
object.data.set(visual.color, 16);
|
|
625
|
-
object.data
|
|
626
|
-
object.data
|
|
627
|
-
object.data[
|
|
628
|
-
object.data[
|
|
629
|
-
object.data.
|
|
1045
|
+
object.data.set(visual.multiplyColor, MULTIPLY_COLOR_OFFSET);
|
|
1046
|
+
object.data.set(visual.screenColor, SCREEN_COLOR_OFFSET);
|
|
1047
|
+
object.data[PARAMS_OFFSET] = visual.compositeLayers.length;
|
|
1048
|
+
object.data[PARAMS_OFFSET + 1] = outputMask ? 1 : 0;
|
|
1049
|
+
object.data[PARAMS_OFFSET + 2] = Math.min(MAX_EFFECTS, visual.effects.length);
|
|
1050
|
+
object.data[PARAMS_OFFSET + 3] = 0;
|
|
1051
|
+
object.data.set(visual.uvRect, UV_RECT_OFFSET);
|
|
630
1052
|
object.data.fill(0, COMPOSITE_PARAMS_OFFSET);
|
|
631
1053
|
for (let index = 0; index < visual.compositeLayers.length; index++) {
|
|
632
1054
|
const layer = visual.compositeLayers[index];
|
|
@@ -686,7 +1108,7 @@ class Animation2DRenderSystem extends RenderSystem2DBase {
|
|
|
686
1108
|
}],
|
|
687
1109
|
});
|
|
688
1110
|
applyViewport(sourcePass, context);
|
|
689
|
-
sourcePass.setPipeline(this.getPipeline('rgba8unorm', 1, false, false));
|
|
1111
|
+
sourcePass.setPipeline(this.getPipeline('rgba8unorm', 1, false, false, 'normal', item.visual.textureAlphaMode, item.visual.culling));
|
|
690
1112
|
sourcePass.setBindGroup(0, this.cameraGpu.bindGroup);
|
|
691
1113
|
this.draw(sourcePass, item.entity, item.visual, context, compositeBindGroup, false);
|
|
692
1114
|
sourcePass.end();
|
|
@@ -732,6 +1154,7 @@ class Animation2DRenderSystem extends RenderSystem2DBase {
|
|
|
732
1154
|
externalVersion: -1,
|
|
733
1155
|
externalWidth: 0,
|
|
734
1156
|
externalHeight: 0,
|
|
1157
|
+
externalMipLevelCount: 1,
|
|
735
1158
|
externalBindGroup: null,
|
|
736
1159
|
};
|
|
737
1160
|
});
|
|
@@ -762,22 +1185,26 @@ class Animation2DRenderSystem extends RenderSystem2DBase {
|
|
|
762
1185
|
retire();
|
|
763
1186
|
}
|
|
764
1187
|
}
|
|
1188
|
+
object.externalMipLevelCount = calculateMipLevelCount(size.width, size.height);
|
|
765
1189
|
object.externalTexture = requireEngineDevice(this.engine).createTexture({
|
|
766
1190
|
label: `Animation2D.text:${entity.id}`,
|
|
767
1191
|
size: [size.width, size.height],
|
|
1192
|
+
mipLevelCount: object.externalMipLevelCount,
|
|
768
1193
|
format: 'rgba8unorm',
|
|
769
1194
|
// WebGPU copyExternalImageToTexture requires RENDER_ATTACHMENT in addition
|
|
770
1195
|
// to COPY_DST for browser-backed image sources.
|
|
771
1196
|
usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST | GPUTextureUsage.RENDER_ATTACHMENT,
|
|
772
1197
|
});
|
|
773
|
-
getExtensionGPUResourceTracker(this.engine)?.trackTexture(object.externalTexture, 'Animation2D.textTexture', size.width
|
|
1198
|
+
getExtensionGPUResourceTracker(this.engine)?.trackTexture(object.externalTexture, 'Animation2D.textTexture', estimateRgba8MipChainBytes(size.width, size.height, object.externalMipLevelCount));
|
|
774
1199
|
object.externalBindGroup = this.createTextureBindGroup(object.externalTexture);
|
|
775
1200
|
object.externalWidth = size.width;
|
|
776
1201
|
object.externalHeight = size.height;
|
|
777
1202
|
object.externalVersion = -1;
|
|
778
1203
|
}
|
|
779
1204
|
if (object.externalSource !== source || object.externalVersion !== material.textureVersion) {
|
|
780
|
-
requireEngineDevice(this.engine)
|
|
1205
|
+
const device = requireEngineDevice(this.engine);
|
|
1206
|
+
device.queue.copyExternalImageToTexture({ source }, { texture: object.externalTexture, premultipliedAlpha: true }, [size.width, size.height]);
|
|
1207
|
+
this.generateTextMipChain(device, object.externalTexture, object.externalMipLevelCount, entity.id);
|
|
781
1208
|
object.externalSource = source;
|
|
782
1209
|
object.externalVersion = material.textureVersion;
|
|
783
1210
|
}
|
|
@@ -785,14 +1212,44 @@ class Animation2DRenderSystem extends RenderSystem2DBase {
|
|
|
785
1212
|
}
|
|
786
1213
|
return visual.requiresTexture ? this.transparentBindGroup : this.whiteBindGroup;
|
|
787
1214
|
}
|
|
1215
|
+
generateTextMipChain(device, texture, mipLevelCount, entityId) {
|
|
1216
|
+
if (mipLevelCount <= 1)
|
|
1217
|
+
return;
|
|
1218
|
+
const encoder = device.createCommandEncoder({ label: `Animation2D.textMipmaps:${entityId}` });
|
|
1219
|
+
for (let level = 1; level < mipLevelCount; level++) {
|
|
1220
|
+
const bindGroup = device.createBindGroup({
|
|
1221
|
+
label: `Animation2D.textMipmapBindGroup:${entityId}:${level}`,
|
|
1222
|
+
layout: this.mipmapPipeline.getBindGroupLayout(0),
|
|
1223
|
+
entries: [
|
|
1224
|
+
{ binding: 0, resource: texture.createView({ baseMipLevel: level - 1, mipLevelCount: 1 }) },
|
|
1225
|
+
{ binding: 1, resource: this.mipmapSampler },
|
|
1226
|
+
],
|
|
1227
|
+
});
|
|
1228
|
+
const pass = encoder.beginRenderPass({
|
|
1229
|
+
label: `Animation2D.textMipmapPass:${entityId}:${level}`,
|
|
1230
|
+
colorAttachments: [{
|
|
1231
|
+
view: texture.createView({ baseMipLevel: level, mipLevelCount: 1 }),
|
|
1232
|
+
loadOp: 'clear',
|
|
1233
|
+
storeOp: 'store',
|
|
1234
|
+
clearValue: { r: 0, g: 0, b: 0, a: 0 },
|
|
1235
|
+
}],
|
|
1236
|
+
});
|
|
1237
|
+
pass.setPipeline(this.mipmapPipeline);
|
|
1238
|
+
pass.setBindGroup(0, bindGroup);
|
|
1239
|
+
pass.draw(3);
|
|
1240
|
+
pass.end();
|
|
1241
|
+
}
|
|
1242
|
+
device.queue.submit([encoder.finish()]);
|
|
1243
|
+
}
|
|
788
1244
|
getGeometryGpu(visual) {
|
|
789
1245
|
const geometry = visual.geometry;
|
|
790
|
-
|
|
1246
|
+
const key = this.geometryKey(visual);
|
|
1247
|
+
let gpu = this.geometryGpu.get(key);
|
|
791
1248
|
if (gpu && gpu.version === geometry.version)
|
|
792
1249
|
return gpu;
|
|
793
1250
|
if (gpu)
|
|
794
1251
|
this.destroyGeometryGpu(gpu);
|
|
795
|
-
const vertices = buildVertices(geometry.positions);
|
|
1252
|
+
const vertices = buildVertices(geometry.positions, visual.uvs);
|
|
796
1253
|
const device = requireEngineDevice(this.engine);
|
|
797
1254
|
const vertexBuffer = device.createBuffer({ size: vertices.byteLength, usage: GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST });
|
|
798
1255
|
device.queue.writeBuffer(vertexBuffer, 0, vertices);
|
|
@@ -814,9 +1271,19 @@ class Animation2DRenderSystem extends RenderSystem2DBase {
|
|
|
814
1271
|
indexFormat: geometry.indices instanceof Uint32Array ? 'uint32' : 'uint16',
|
|
815
1272
|
version: geometry.version,
|
|
816
1273
|
};
|
|
817
|
-
this.geometryGpu.set(
|
|
1274
|
+
this.geometryGpu.set(key, gpu);
|
|
818
1275
|
return gpu;
|
|
819
1276
|
}
|
|
1277
|
+
geometryKey(visual) {
|
|
1278
|
+
if (!visual.uvs)
|
|
1279
|
+
return `${visual.geometry.id}:derived`;
|
|
1280
|
+
let uvId = this.explicitUvIds.get(visual.uvs);
|
|
1281
|
+
if (uvId === undefined) {
|
|
1282
|
+
uvId = this.nextExplicitUvId++;
|
|
1283
|
+
this.explicitUvIds.set(visual.uvs, uvId);
|
|
1284
|
+
}
|
|
1285
|
+
return `${visual.geometry.id}:uv:${uvId}`;
|
|
1286
|
+
}
|
|
820
1287
|
getTextureBindGroup(texture) {
|
|
821
1288
|
let bindGroup = this.textureBindGroups.get(texture);
|
|
822
1289
|
if (!bindGroup) {
|
|
@@ -831,20 +1298,28 @@ class Animation2DRenderSystem extends RenderSystem2DBase {
|
|
|
831
1298
|
{ binding: 1, resource: this.sampler },
|
|
832
1299
|
] });
|
|
833
1300
|
}
|
|
834
|
-
resolveCompositeBindGroup(visual, targets, viewKey) {
|
|
1301
|
+
resolveCompositeBindGroup(visual, targets, knownSourceKeys, viewKey) {
|
|
835
1302
|
if (visual.compositeLayers.length === 0)
|
|
836
1303
|
return this.emptyCompositeBindGroup;
|
|
837
1304
|
const resolved = [];
|
|
1305
|
+
const resolvedKeys = [];
|
|
838
1306
|
for (const key of visual.compositeKeys) {
|
|
839
1307
|
const target = targets.get(key);
|
|
840
|
-
if (
|
|
841
|
-
|
|
842
|
-
|
|
1308
|
+
if (target) {
|
|
1309
|
+
resolved.push(target.texture);
|
|
1310
|
+
resolvedKeys.push(key);
|
|
1311
|
+
}
|
|
1312
|
+
else {
|
|
1313
|
+
if (!knownSourceKeys.has(key))
|
|
1314
|
+
return null;
|
|
1315
|
+
resolved.push(this.transparentTexture);
|
|
1316
|
+
resolvedKeys.push(`transparent:${key}`);
|
|
1317
|
+
}
|
|
843
1318
|
}
|
|
844
|
-
const key =
|
|
1319
|
+
const key = animationMaskCompositeKey(viewKey, resolvedKeys);
|
|
845
1320
|
let bindGroup = this.compositeBindGroups.get(key);
|
|
846
1321
|
if (!bindGroup) {
|
|
847
|
-
bindGroup = this.createCompositeBindGroup(resolved
|
|
1322
|
+
bindGroup = this.createCompositeBindGroup(resolved);
|
|
848
1323
|
this.compositeBindGroups.set(key, bindGroup);
|
|
849
1324
|
}
|
|
850
1325
|
return bindGroup;
|
|
@@ -946,7 +1421,7 @@ class Animation2DRenderSystem extends RenderSystem2DBase {
|
|
|
946
1421
|
sweepGeometryGpu() {
|
|
947
1422
|
this.liveGeometryIds.clear();
|
|
948
1423
|
for (const item of this.items)
|
|
949
|
-
this.liveGeometryIds.add(item.visual
|
|
1424
|
+
this.liveGeometryIds.add(this.geometryKey(item.visual));
|
|
950
1425
|
for (const [id, gpu] of this.geometryGpu)
|
|
951
1426
|
if (!this.liveGeometryIds.has(id)) {
|
|
952
1427
|
this.destroyGeometryGpu(gpu);
|
|
@@ -980,6 +1455,9 @@ class Animation2DRenderSystem extends RenderSystem2DBase {
|
|
|
980
1455
|
}
|
|
981
1456
|
}
|
|
982
1457
|
}
|
|
1458
|
+
function isFullyTransparent(visual) {
|
|
1459
|
+
return visual.color[3] <= 0 && visual.effects.length === 0;
|
|
1460
|
+
}
|
|
983
1461
|
function writeBufferAligned(queue, buffer, source) {
|
|
984
1462
|
if (source.byteLength % 4 === 0) {
|
|
985
1463
|
queue.writeBuffer(buffer, 0, source);
|
|
@@ -1003,6 +1481,16 @@ function externalImageSize(source) {
|
|
|
1003
1481
|
height: Math.max(1, isImage ? source.naturalHeight || source.height : source.height),
|
|
1004
1482
|
};
|
|
1005
1483
|
}
|
|
1484
|
+
function calculateMipLevelCount(width, height) {
|
|
1485
|
+
return Math.floor(Math.log2(Math.max(1, width, height))) + 1;
|
|
1486
|
+
}
|
|
1487
|
+
function estimateRgba8MipChainBytes(width, height, mipLevelCount) {
|
|
1488
|
+
let bytes = 0;
|
|
1489
|
+
for (let level = 0; level < mipLevelCount; level++) {
|
|
1490
|
+
bytes += Math.max(1, width >> level) * Math.max(1, height >> level) * 4;
|
|
1491
|
+
}
|
|
1492
|
+
return bytes;
|
|
1493
|
+
}
|
|
1006
1494
|
function compareItems(a, b) {
|
|
1007
1495
|
return a.visual.instanceId - b.visual.instanceId || a.visual.order - b.visual.order || a.entity.id - b.entity.id;
|
|
1008
1496
|
}
|
|
@@ -1035,10 +1523,14 @@ function compositeOperationCode(operation) {
|
|
|
1035
1523
|
}
|
|
1036
1524
|
function effectKindCode(kind) {
|
|
1037
1525
|
return kind === 'tint' ? 1 : kind === 'fill' ? 2 : kind === 'opacity' ? 3
|
|
1038
|
-
: kind === 'color-matrix' ? 4 : kind === 'blur' ? 5 : 6
|
|
1526
|
+
: kind === 'color-matrix' ? 4 : kind === 'blur' ? 5 : kind === 'drop-shadow' ? 6
|
|
1527
|
+
: kind === 'inner-feather' ? 7 : 8;
|
|
1039
1528
|
}
|
|
1040
1529
|
function visualNodeKey(visual) { return visual.nodeKey; }
|
|
1041
|
-
function
|
|
1530
|
+
function unresolvedCompositeError(visual, viewKey) {
|
|
1531
|
+
return new AnimationFormatError('E_ANIMATION_INVALID_FORMAT', `Composite target for visual "${visual.nodeId}" could not be resolved in view "${viewKey}".`, `$runtime.visuals[${JSON.stringify(visual.nodeId)}].composite`);
|
|
1532
|
+
}
|
|
1533
|
+
function buildVertices(positions, explicitUvs) {
|
|
1042
1534
|
let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
|
|
1043
1535
|
for (let i = 0; i < positions.length; i += 2) {
|
|
1044
1536
|
const x = positions[i], y = positions[i + 1];
|
|
@@ -1053,8 +1545,8 @@ function buildVertices(positions) {
|
|
|
1053
1545
|
const x = positions[source], y = positions[source + 1];
|
|
1054
1546
|
out[target++] = x;
|
|
1055
1547
|
out[target++] = y;
|
|
1056
|
-
out[target++] = (x - minX) / width;
|
|
1057
|
-
out[target++] = 1 - (y - minY) / height;
|
|
1548
|
+
out[target++] = explicitUvs?.[source] ?? (x - minX) / width;
|
|
1549
|
+
out[target++] = explicitUvs?.[source + 1] ?? 1 - (y - minY) / height;
|
|
1058
1550
|
}
|
|
1059
1551
|
return out;
|
|
1060
1552
|
}
|
|
@@ -1167,5 +1659,5 @@ class Animation2DExtensionRegistry {
|
|
|
1167
1659
|
}
|
|
1168
1660
|
}
|
|
1169
1661
|
|
|
1170
|
-
export { Animation2DComponent, Animation2DExtensionRegistry, Animation2DRenderSystem, Animation2DSystem, HAIYUE_ANIMATION_ASSET_TYPE, createAnimationAssetLoader };
|
|
1662
|
+
export { Animation2DComponent, Animation2DExtensionRegistry, Animation2DRenderSystem, Animation2DSystem, HAIYUE_ANIMATION_ASSET_TYPE, InteractionRuntime, createAnimationAssetLoader };
|
|
1171
1663
|
//# sourceMappingURL=animation.js.map
|